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
|
// 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);
|
htmx.ajax(method, url, htmxOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
from fasthtml.components import Div
|
from fasthtml.components import Div
|
||||||
|
|
||||||
from myfasthtml.controls.BaseCommands import BaseCommands
|
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.dbmanager import DbObject
|
||||||
from myfasthtml.core.instances import MultipleInstance
|
from myfasthtml.core.instances import MultipleInstance
|
||||||
|
|
||||||
|
logger = logging.getLogger("CycleStateControl")
|
||||||
|
|
||||||
class CycleState(DbObject):
|
class CycleState(DbObject):
|
||||||
def __init__(self, owner, save_state):
|
def __init__(self, owner, save_state):
|
||||||
@@ -24,7 +27,7 @@ class Commands(BaseCommands):
|
|||||||
|
|
||||||
class CycleStateControl(MultipleInstance):
|
class CycleStateControl(MultipleInstance):
|
||||||
def __init__(self, parent, controls: dict, _id=None, save_state=True):
|
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._state = CycleState(self, save_state)
|
||||||
self.controls_by_states = controls
|
self.controls_by_states = controls
|
||||||
self.commands = Commands(self)
|
self.commands = Commands(self)
|
||||||
@@ -34,6 +37,7 @@ class CycleStateControl(MultipleInstance):
|
|||||||
self._state.state = next(iter(controls.keys()))
|
self._state.state = next(iter(controls.keys()))
|
||||||
|
|
||||||
def cycle_state(self):
|
def cycle_state(self):
|
||||||
|
logger.debug(f"cycle_state datagrid={self._parent.get_table_name()}")
|
||||||
keys = list(self.controls_by_states.keys())
|
keys = list(self.controls_by_states.keys())
|
||||||
current_idx = keys.index(self._state.state)
|
current_idx = keys.index(self._state.state)
|
||||||
self._state.state = keys[(current_idx + 1) % len(keys)]
|
self._state.state = keys[(current_idx + 1) % len(keys)]
|
||||||
|
|||||||
@@ -203,7 +203,10 @@ class DataGrid(MultipleInstance):
|
|||||||
"column": mk.icon(column, tooltip="Column selection"),
|
"column": mk.icon(column, tooltip="Column selection"),
|
||||||
"cell": mk.icon(grid, tooltip="Cell 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())
|
self._selection_mode_selector.bind_command("CycleState", self.commands.change_selection_mode())
|
||||||
|
|
||||||
# add columns manager
|
# add columns manager
|
||||||
|
|||||||
@@ -171,6 +171,9 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
|
|||||||
def get_row_count(self, table_name):
|
def get_row_count(self, table_name):
|
||||||
return self._registry.get_row_count(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]:
|
def list_style_presets(self) -> list[str]:
|
||||||
return list(self.style_presets.keys())
|
return list(self.style_presets.keys())
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,33 @@ class DataGridsRegistry(SingleInstance):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
return 0
|
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):
|
def _get_all_entries(self):
|
||||||
return {k: v for k, v in self._db_manager.load(DATAGRIDS_REGISTRY_ENTRY_KEY).items()
|
return {k: v for k, v in self._db_manager.load(DATAGRIDS_REGISTRY_ENTRY_KEY).items()
|
||||||
if not k.startswith("__")}
|
if not k.startswith("__")}
|
||||||
|
|||||||
@@ -48,10 +48,13 @@ class Command:
|
|||||||
# either there is no parameter (so one single instance of the command is enough)
|
# 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)
|
# or the parameter is a kwargs (so the parameters are provided when the command is called)
|
||||||
if key is None:
|
if key is None:
|
||||||
if owner is not None and args is None: # args is not provided
|
key_parts = []
|
||||||
key = f"{owner.get_full_id()}-{name}"
|
if owner is not None:
|
||||||
else:
|
key_parts.append(f"{owner.get_full_id()}")
|
||||||
key = f"{name}-{_compute_from_args()}"
|
key_parts.append(name)
|
||||||
|
if args:
|
||||||
|
key_parts.append(_compute_from_args())
|
||||||
|
key = "-".join(key_parts)
|
||||||
else:
|
else:
|
||||||
key = key.replace("#{args}", _compute_from_args())
|
key = key.replace("#{args}", _compute_from_args())
|
||||||
if owner is not None:
|
if owner is not None:
|
||||||
@@ -122,6 +125,8 @@ class Command:
|
|||||||
|
|
||||||
def execute(self, client_response: dict = None):
|
def execute(self, client_response: dict = None):
|
||||||
logger.debug(f"Executing command {self.name} with arguments {client_response=}")
|
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:
|
with ObservableResultCollector(self._bindings) as collector:
|
||||||
kwargs = self._create_kwargs(self.default_kwargs,
|
kwargs = self._create_kwargs(self.default_kwargs,
|
||||||
client_response,
|
client_response,
|
||||||
@@ -145,6 +150,10 @@ class Command:
|
|||||||
and r.get("id", None) is not None):
|
and r.get("id", None) is not None):
|
||||||
r.attrs["hx-swap-oob"] = r.attrs.get("hx-swap-oob", "true")
|
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
|
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):
|
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
|
return # still under initialization
|
||||||
|
|
||||||
if self._reload_self():
|
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
|
return
|
||||||
else:
|
else:
|
||||||
logger.debug(
|
# logger.debug(f"finalize_initialization ({self._name}) : No existing content found, creating new entry {self._save_state=}.")
|
||||||
f"finalize_initialization ({self._name}) : No existing content found, creating new entry {self._save_state=}.")
|
|
||||||
self._save_self()
|
self._save_self()
|
||||||
|
|
||||||
def _reload_self(self):
|
def _reload_self(self):
|
||||||
|
|||||||
@@ -181,8 +181,8 @@ class FormattingCompletionEngine(BaseCompletionEngine):
|
|||||||
return presets.COMPARISON_OPERATORS
|
return presets.COMPARISON_OPERATORS
|
||||||
|
|
||||||
case Context.OPERATOR_VALUE | Context.BETWEEN_VALUE:
|
case Context.OPERATOR_VALUE | Context.BETWEEN_VALUE:
|
||||||
# col., True, False + column values
|
# Filter base suggestions according to column type
|
||||||
base = presets.OPERATOR_VALUE_BASE.copy()
|
base = self._get_base_suggestions_for_column_type(scope)
|
||||||
base.extend(self._get_column_value_suggestions(scope))
|
base.extend(self._get_column_value_suggestions(scope))
|
||||||
return base
|
return base
|
||||||
|
|
||||||
@@ -363,6 +363,44 @@ class FormattingCompletionEngine(BaseCompletionEngine):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return []
|
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(
|
def get_completions(
|
||||||
text: str,
|
text: str,
|
||||||
|
|||||||
@@ -72,3 +72,18 @@ class DatagridMetadataProvider(BaseMetadataProvider):
|
|||||||
Number of rows
|
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.constants import NO_DEFAULT_VALUE
|
||||||
from myfasthtml.core.utils import pascal_to_snake, get_class, snake_to_pascal
|
from myfasthtml.core.utils import pascal_to_snake, get_class, snake_to_pascal
|
||||||
|
|
||||||
|
VERBOSE_VERBOSE = False
|
||||||
|
|
||||||
logger = logging.getLogger("InstancesManager")
|
logger = logging.getLogger("InstancesManager")
|
||||||
|
|
||||||
special_session = {
|
special_session = {
|
||||||
@@ -26,12 +28,19 @@ class BaseInstance:
|
|||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
# Extract arguments from both positional and keyword arguments
|
# Extract arguments from both positional and keyword arguments
|
||||||
# Signature matches __init__: parent, session=None, _id=None, auto_register=True
|
# 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)
|
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)
|
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)
|
_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
|
# Compute _id
|
||||||
_id = cls.compute_id(_id, parent)
|
_id = cls.compute_id(_id, parent)
|
||||||
|
if VERBOSE_VERBOSE:
|
||||||
|
logger.debug(f" computed id={_id}")
|
||||||
|
|
||||||
if session is None:
|
if session is None:
|
||||||
if parent is not None:
|
if parent is not None:
|
||||||
@@ -46,9 +55,14 @@ class BaseInstance:
|
|||||||
res = InstancesManager.instances[key]
|
res = InstancesManager.instances[key]
|
||||||
if type(res) is not cls:
|
if type(res) is not cls:
|
||||||
raise TypeError(f"Instance with id {_id} already exists, but is of type {type(res)}")
|
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
|
return res
|
||||||
|
|
||||||
# Otherwise create a new instance
|
# Otherwise create a new instance
|
||||||
|
if VERBOSE_VERBOSE:
|
||||||
|
logger.debug(f" creating new instance")
|
||||||
instance = super().__new__(cls)
|
instance = super().__new__(cls)
|
||||||
instance._is_new_instance = True # mark as fresh
|
instance._is_new_instance = True # mark as fresh
|
||||||
return instance
|
return instance
|
||||||
|
|||||||
Reference in New Issue
Block a user