Added columns values in suggestion + fixed commands key conflicts bug
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
@@ -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("__")}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
...
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user