Added columns values in suggestion + fixed commands key conflicts bug

This commit is contained in:
2026-02-08 22:44:06 +01:00
parent d44e0a0c01
commit 0119f54f11
10 changed files with 127 additions and 15 deletions

View File

@@ -375,7 +375,7 @@ function triggerHtmxAction(elementId, config, combinationStr, isInside, event) {
}
// Make AJAX call with htmx
console.debug(`Triggering HTMX action for element ${elementId}: ${method} ${url}`, htmxOptions);
//console.debug(`Triggering HTMX action for element ${elementId}: ${method} ${url}`, htmxOptions);
htmx.ajax(method, url, htmxOptions);
}

View File

@@ -1,3 +1,5 @@
import logging
from fasthtml.components import Div
from myfasthtml.controls.BaseCommands import BaseCommands
@@ -6,6 +8,7 @@ from myfasthtml.core.commands import Command
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance
logger = logging.getLogger("CycleStateControl")
class CycleState(DbObject):
def __init__(self, owner, save_state):
@@ -24,7 +27,7 @@ class Commands(BaseCommands):
class CycleStateControl(MultipleInstance):
def __init__(self, parent, controls: dict, _id=None, save_state=True):
super().__init__(parent, _id)
super().__init__(parent, _id=_id)
self._state = CycleState(self, save_state)
self.controls_by_states = controls
self.commands = Commands(self)
@@ -34,6 +37,7 @@ class CycleStateControl(MultipleInstance):
self._state.state = next(iter(controls.keys()))
def cycle_state(self):
logger.debug(f"cycle_state datagrid={self._parent.get_table_name()}")
keys = list(self.controls_by_states.keys())
current_idx = keys.index(self._state.state)
self._state.state = keys[(current_idx + 1) % len(keys)]

View File

@@ -203,7 +203,10 @@ class DataGrid(MultipleInstance):
"column": mk.icon(column, tooltip="Column selection"),
"cell": mk.icon(grid, tooltip="Cell selection")
}
self._selection_mode_selector = CycleStateControl(self, controls=selection_types, save_state=False)
self._selection_mode_selector = CycleStateControl(self,
controls=selection_types,
save_state=False,
_id="-cycle_state")
self._selection_mode_selector.bind_command("CycleState", self.commands.change_selection_mode())
# add columns manager

View File

@@ -171,6 +171,9 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
def get_row_count(self, table_name):
return self._registry.get_row_count(table_name)
def get_column_type(self, table_name, column_name):
return self._registry.get_column_type(table_name, column_name)
def list_style_presets(self) -> list[str]:
return list(self.style_presets.keys())

View File

@@ -69,6 +69,33 @@ class DataGridsRegistry(SingleInstance):
except KeyError:
return 0
def get_column_type(self, table_name, column_name):
"""
Get the type of a column.
Args:
table_name: The DataGrid name
column_name: The column name
Returns:
ColumnType enum value or None if not found
"""
try:
as_fullname_dict = self._get_entries_as_full_name_dict()
grid_id = as_fullname_dict[table_name]
# load datagrid state
state_id = f"{grid_id}#state"
state = self._db_manager.load(state_id)
if state and "columns" in state:
for col in state["columns"]:
if col.col_id == column_name:
return col.type
return None
except KeyError:
return None
def _get_all_entries(self):
return {k: v for k, v in self._db_manager.load(DATAGRIDS_REGISTRY_ENTRY_KEY).items()
if not k.startswith("__")}

View File

@@ -48,10 +48,13 @@ class Command:
# either there is no parameter (so one single instance of the command is enough)
# or the parameter is a kwargs (so the parameters are provided when the command is called)
if key is None:
if owner is not None and args is None: # args is not provided
key = f"{owner.get_full_id()}-{name}"
else:
key = f"{name}-{_compute_from_args()}"
key_parts = []
if owner is not None:
key_parts.append(f"{owner.get_full_id()}")
key_parts.append(name)
if args:
key_parts.append(_compute_from_args())
key = "-".join(key_parts)
else:
key = key.replace("#{args}", _compute_from_args())
if owner is not None:
@@ -122,6 +125,8 @@ class Command:
def execute(self, client_response: dict = None):
logger.debug(f"Executing command {self.name} with arguments {client_response=}")
if self._htmx_extra.get("hx-target", "").startswith("#tsm_"):
logger.warning(f" Command {self.name} needs a selection manager to work properly.")
with ObservableResultCollector(self._bindings) as collector:
kwargs = self._create_kwargs(self.default_kwargs,
client_response,
@@ -145,6 +150,10 @@ class Command:
and r.get("id", None) is not None):
r.attrs["hx-swap-oob"] = r.attrs.get("hx-swap-oob", "true")
if self._htmx_extra.get("hx-target", "").startswith("#tsm_"):
ret_debug = [f"<{r.tag} id={r.attrs.get('id', '')}/>" if r else "None" for r in all_ret]
logger.warning(f" {ret_debug=}")
return all_ret[0] if len(all_ret) == 1 else all_ret
def htmx(self, target: Optional[str] = "this", swap="outerHTML", trigger=None, auto_swap_oob=True):

View File

@@ -79,11 +79,10 @@ class DbObject:
return # still under initialization
if self._reload_self():
logger.debug(f"finalize_initialization ({self._name}) : Loaded existing content.")
# logger.debug(f"finalize_initialization ({self._name}) : Loaded existing content.")
return
else:
logger.debug(
f"finalize_initialization ({self._name}) : No existing content found, creating new entry {self._save_state=}.")
# logger.debug(f"finalize_initialization ({self._name}) : No existing content found, creating new entry {self._save_state=}.")
self._save_self()
def _reload_self(self):

View File

@@ -181,8 +181,8 @@ class FormattingCompletionEngine(BaseCompletionEngine):
return presets.COMPARISON_OPERATORS
case Context.OPERATOR_VALUE | Context.BETWEEN_VALUE:
# col., True, False + column values
base = presets.OPERATOR_VALUE_BASE.copy()
# Filter base suggestions according to column type
base = self._get_base_suggestions_for_column_type(scope)
base.extend(self._get_column_value_suggestions(scope))
return base
@@ -363,6 +363,44 @@ class FormattingCompletionEngine(BaseCompletionEngine):
except Exception:
return []
def _get_base_suggestions_for_column_type(self, scope: DetectedScope) -> list[Suggestion]:
"""
Filter base suggestions according to column type.
Only suggests True/False for Boolean columns.
Always suggests col. for cross-column comparisons.
Args:
scope: The detected scope containing column information
Returns:
List of filtered base suggestions
"""
from myfasthtml.core.constants import ColumnType
if not scope.column_name:
# No column context, return all base suggestions
return presets.OPERATOR_VALUE_BASE.copy()
try:
table_name = scope.table_name or self.table_name or ""
col_type = self.provider.get_column_type(table_name, scope.column_name)
suggestions = []
# Always suggest col. for cross-column comparisons
suggestions.append(Suggestion("col.", "Reference another column", "keyword"))
# Add True/False only for Boolean columns
if col_type == ColumnType.Bool:
suggestions.append(Suggestion("True", "Boolean true", "literal"))
suggestions.append(Suggestion("False", "Boolean false", "literal"))
return suggestions
except Exception:
# Fallback to default if type detection fails
return presets.OPERATOR_VALUE_BASE.copy()
def get_completions(
text: str,

View File

@@ -72,3 +72,18 @@ class DatagridMetadataProvider(BaseMetadataProvider):
Number of rows
"""
...
def get_column_type(self, table_name: str, column_name: str):
"""
Return the type of a column.
Used to filter suggestions based on column type.
Args:
table_name: The DataGrid name
column_name: The column name
Returns:
ColumnType enum value or None if not found
"""
...

View File

@@ -6,6 +6,8 @@ from myfasthtml.controls.helpers import Ids
from myfasthtml.core.constants import NO_DEFAULT_VALUE
from myfasthtml.core.utils import pascal_to_snake, get_class, snake_to_pascal
VERBOSE_VERBOSE = False
logger = logging.getLogger("InstancesManager")
special_session = {
@@ -26,12 +28,19 @@ class BaseInstance:
def __new__(cls, *args, **kwargs):
# Extract arguments from both positional and keyword arguments
# Signature matches __init__: parent, session=None, _id=None, auto_register=True
if VERBOSE_VERBOSE:
logger.debug(f"Creating new instance of type {cls.__name__}")
parent = args[0] if len(args) > 0 and isinstance(args[0], BaseInstance) else kwargs.get("parent", None)
session = args[1] if len(args) > 1 and isinstance(args[1], dict) else kwargs.get("session", None)
_id = args[2] if len(args) > 2 and isinstance(args[2], str) else kwargs.get("_id", None)
if VERBOSE_VERBOSE:
logger.debug(f" parent={parent}, session={session}, _id={_id}")
# Compute _id
_id = cls.compute_id(_id, parent)
if VERBOSE_VERBOSE:
logger.debug(f" computed id={_id}")
if session is None:
if parent is not None:
@@ -46,9 +55,14 @@ class BaseInstance:
res = InstancesManager.instances[key]
if type(res) is not cls:
raise TypeError(f"Instance with id {_id} already exists, but is of type {type(res)}")
if VERBOSE_VERBOSE:
logger.debug(f" instance {_id} already exists, returning existing instance")
return res
# Otherwise create a new instance
if VERBOSE_VERBOSE:
logger.debug(f" creating new instance")
instance = super().__new__(cls)
instance._is_new_instance = True # mark as fresh
return instance