Improved Command class management.

This commit is contained in:
2025-12-19 21:12:55 +01:00
parent b26abc4257
commit 1347f12618
23 changed files with 349 additions and 169 deletions

View File

@@ -1,7 +1,6 @@
from fasthtml.xtend import Script
from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.helpers import Ids
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import SingleInstance

View File

@@ -1,6 +1,6 @@
from myfasthtml.controls.VisNetwork import VisNetwork
from myfasthtml.core.commands import CommandsManager
from myfasthtml.core.instances import SingleInstance
from myfasthtml.core.instances import SingleInstance, InstancesManager
from myfasthtml.core.network_utils import from_parent_child_list
@@ -9,22 +9,18 @@ class CommandsDebugger(SingleInstance):
Represents a debugger designed for visualizing and managing commands in a parent-child
hierarchical structure.
"""
def __init__(self, parent, _id=None):
super().__init__(parent, _id=_id)
def render(self):
commands = self._get_commands()
nodes, edges = from_parent_child_list(commands,
id_getter=lambda x: str(x.id),
label_getter=lambda x: x.name,
parent_getter=lambda x: str(self.get_command_parent(x))
)
nodes, edges = self._get_nodes_and_edges()
vis_network = VisNetwork(self, nodes=nodes, edges=edges)
return vis_network
@staticmethod
def get_command_parent(command):
def get_command_parent_from_ft(command):
if (ft := command.get_ft()) is None:
return None
if hasattr(ft, "get_id") and callable(ft.get_id):
@@ -36,6 +32,30 @@ class CommandsDebugger(SingleInstance):
return None
@staticmethod
def get_command_parent_from_instance(command):
if command.owner is None:
return None
return command.owner.get_full_id()
def _get_nodes_and_edges(self):
commands = self._get_commands()
nodes, edges = from_parent_child_list(commands,
id_getter=lambda x: str(x.id),
label_getter=lambda x: x.name,
parent_getter=lambda x: str(self.get_command_parent_from_instance(x)),
ghost_label_getter=lambda x: InstancesManager.get(*x.split("#")).get_id()
)
for edge in edges:
edge["color"] = "blue"
edge["arrows"] = {"to": {"enabled": False, "type": "circle"}}
for node in nodes:
node["shape"] = "box"
return nodes, edges
def _get_commands(self):
return list(CommandsManager.commands.values())

View File

@@ -10,7 +10,7 @@ from myfasthtml.controls.Panel import Panel
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.controls.TreeView import TreeView, TreeNode
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.commands import Command, LambdaCommand
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance, InstancesManager
from myfasthtml.icons.fluent_p1 import table_add20_regular
@@ -51,22 +51,29 @@ class Commands(BaseCommands):
"Open from Excel",
self._owner,
self._owner.open_from_excel,
tab_id,
file_upload).htmx(target=f"#{self._owner._tree.get_id()}")
args=[tab_id,
file_upload]).htmx(target=f"#{self._owner._tree.get_id()}")
def clear_tree(self):
return Command("ClearTree",
"Clear tree",
self._owner,
self._owner.clear_tree).htmx(target=f"#{self._owner._tree.get_id()}")
def show_document(self):
return LambdaCommand(self._owner, lambda: print("show_document"))
class DataGridsManager(MultipleInstance):
def __init__(self, parent, _id=None):
if not getattr(self, "_is_new_instance", False):
# Skip __init__ if instance already existed
return
super().__init__(parent, _id=_id)
self.commands = Commands(self)
self._state = DataGridsState(self)
self._tree = self._mk_tree()
self._tree.bind_command("SelectNode", self.commands.show_document())
self._tabs_manager = InstancesManager.get_by_type(self._session, TabsManager)
def upload_from_source(self):
@@ -86,7 +93,7 @@ class DataGridsManager(MultipleInstance):
name=file_upload.get_sheet_name(),
type="excel",
tab_id=tab_id,
datagrid_id=None
datagrid_id=dg.get_id()
)
self._state.elements = self._state.elements + [document]
parent_id = self._tree.ensure_path(document.namespace)
@@ -110,7 +117,7 @@ class DataGridsManager(MultipleInstance):
tree = TreeView(self, _id="-treeview")
for element in self._state.elements:
parent_id = tree.ensure_path(element.namespace)
tree.add_node(TreeNode(label=element.name, type=element.type, parent=parent_id))
tree.add_node(TreeNode(label=element.name, type=element.type, parent=parent_id, id=element.datagrid_id))
return tree
def render(self):

View File

@@ -34,6 +34,7 @@ class Dropdown(MultipleInstance):
The dropdown provides functionality to manage its state, including opening, closing, and
handling user interactions.
"""
def __init__(self, parent, content=None, button=None, _id=None):
super().__init__(parent, _id=_id)
self.button = Div(button) if not isinstance(button, FT) else button

View File

@@ -37,7 +37,7 @@ class InstancesDebugger(SingleInstance):
instances,
id_getter=lambda x: x.get_full_id(),
label_getter=lambda x: f"{x.get_id()}",
parent_getter=lambda x: x.get_full_parent_id()
parent_getter=lambda x: x.get_parent_full_id()
)
for edge in edges:
edge["color"] = "green"

View File

@@ -2,7 +2,7 @@ import json
from fasthtml.xtend import Script
from myfasthtml.core.commands import BaseCommand
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import MultipleInstance
@@ -17,7 +17,7 @@ class Keyboard(MultipleInstance):
super().__init__(parent, _id=_id)
self.combinations = combinations or {}
def add(self, sequence: str, command: BaseCommand):
def add(self, sequence: str, command: Command):
self.combinations[sequence] = command
return self

View File

@@ -40,7 +40,8 @@ class Commands(BaseCommands):
return Command("ToggleDrawer",
f"Toggle {side} layout drawer",
self._owner,
self._owner.toggle_drawer, side)
self._owner.toggle_drawer,
args=[side])
def update_drawer_width(self, side: Literal["left", "right"], width: int = None):
"""
@@ -57,7 +58,7 @@ class Commands(BaseCommands):
f"Update {side} drawer width",
self._owner,
self._owner.update_drawer_width,
side)
args=[side])
class Layout(SingleInstance):

View File

@@ -2,7 +2,7 @@ import json
from fasthtml.xtend import Script
from myfasthtml.core.commands import BaseCommand
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import MultipleInstance
@@ -17,7 +17,7 @@ class Mouse(MultipleInstance):
super().__init__(parent, _id=_id)
self.combinations = combinations or {}
def add(self, sequence: str, command: BaseCommand):
def add(self, sequence: str, command: Command):
self.combinations[sequence] = command
return self

View File

@@ -21,7 +21,7 @@ class Commands(BaseCommands):
f"Toggle {side} side panel",
self._owner,
self._owner.toggle_side,
side)
args=[side])
def update_side_width(self, side: Literal["left", "right"]):
"""
@@ -37,7 +37,7 @@ class Commands(BaseCommands):
f"Update {side} side panel width",
self._owner,
self._owner.update_side_width,
side)
args=[side])
class Panel(MultipleInstance):

View File

@@ -62,25 +62,25 @@ class Commands(BaseCommands):
"Activate or show a specific tab",
self._owner,
self._owner.show_tab,
tab_id,
True,
False).htmx(target=f"#{self._id}-controller", swap="outerHTML")
args=[tab_id,
True,
False]).htmx(target=f"#{self._id}-controller", swap="outerHTML")
def close_tab(self, tab_id):
return Command(f"{self._prefix}CloseTab",
"Close a specific tab",
self._owner,
self._owner.close_tab,
tab_id).htmx(target=f"#{self._id}-controller", swap="outerHTML")
args=[tab_id]).htmx(target=f"#{self._id}-controller", swap="outerHTML")
def add_tab(self, label: str, component: Any, auto_increment=False):
return Command(f"{self._prefix}AddTab",
"Add a new tab",
self._owner,
self._owner.on_new_tab,
label,
component,
auto_increment).htmx(target=f"#{self._id}-controller", swap="outerHTML")
args=[label,
component,
auto_increment]).htmx(target=f"#{self._id}-controller", swap="outerHTML")
class TabsManager(MultipleInstance):

View File

@@ -13,7 +13,7 @@ from fasthtml.components import Div, Input, Span
from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.Keyboard import Keyboard
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command
from myfasthtml.core.commands import Command, CommandTemplate
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance
from myfasthtml.icons.fluent_p1 import chevron_right20_regular, edit20_regular
@@ -37,7 +37,7 @@ class TreeNode:
type: str = "default"
parent: Optional[str] = None
children: list[str] = field(default_factory=list)
bag: Optional[dict] = None # to keep extra info
bag: Optional[dict] = None # to keep extra info
class TreeViewState(DbObject):
@@ -71,7 +71,9 @@ class Commands(BaseCommands):
f"Toggle node {node_id}",
self._owner,
self._owner._toggle_node,
node_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-ToggleNode"
).htmx(target=f"#{self._owner.get_id()}")
def add_child(self, parent_id: str):
"""Create command to add a child node."""
@@ -79,7 +81,9 @@ class Commands(BaseCommands):
f"Add child to {parent_id}",
self._owner,
self._owner._add_child,
parent_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"parent_id": parent_id},
key=f"{self._owner.get_safe_parent_key()}-AddChild"
).htmx(target=f"#{self._owner.get_id()}")
def add_sibling(self, node_id: str):
"""Create command to add a sibling node."""
@@ -87,7 +91,8 @@ class Commands(BaseCommands):
f"Add sibling to {node_id}",
self._owner,
self._owner._add_sibling,
node_id
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-AddSibling"
).htmx(target=f"#{self._owner.get_id()}")
def start_rename(self, node_id: str):
@@ -96,7 +101,9 @@ class Commands(BaseCommands):
f"Start renaming {node_id}",
self._owner,
self._owner._start_rename,
node_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-StartRename"
).htmx(target=f"#{self._owner.get_id()}")
def save_rename(self, node_id: str):
"""Create command to save renamed node."""
@@ -104,14 +111,18 @@ class Commands(BaseCommands):
f"Save rename for {node_id}",
self._owner,
self._owner._save_rename,
node_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-SaveRename"
).htmx(target=f"#{self._owner.get_id()}")
def cancel_rename(self):
"""Create command to cancel renaming."""
return Command("CancelRename",
"Cancel rename",
self._owner,
self._owner._cancel_rename).htmx(target=f"#{self._owner.get_id()}")
self._owner._cancel_rename,
key=f"{self._owner.get_safe_parent_key()}-CancelRename"
).htmx(target=f"#{self._owner.get_id()}")
def delete_node(self, node_id: str):
"""Create command to delete a node."""
@@ -119,7 +130,9 @@ class Commands(BaseCommands):
f"Delete node {node_id}",
self._owner,
self._owner._delete_node,
node_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-DeleteNode"
).htmx(target=f"#{self._owner.get_id()}")
def select_node(self, node_id: str):
"""Create command to select a node."""
@@ -127,7 +140,9 @@ class Commands(BaseCommands):
f"Select node {node_id}",
self._owner,
self._owner._select_node,
node_id).htmx(target=f"#{self._owner.get_id()}")
kwargs={"node_id": node_id},
key=f"{self._owner.get_safe_parent_key()}-SelectNode"
).htmx(target=f"#{self._owner.get_id()}")
class TreeView(MultipleInstance):
@@ -394,7 +409,7 @@ class TreeView(MultipleInstance):
name="node_label",
value=node.label,
cls="mf-treenode-input input input-sm"
), command=self.commands.save_rename(node_id))
), command=CommandTemplate("TreeView.SaveRename", self.commands.save_rename, args=[node_id]))
else:
label_element = mk.mk(
Span(node.label, cls="mf-treenode-label text-sm"),

View File

@@ -1,7 +1,7 @@
from fasthtml.components import *
from myfasthtml.core.bindings import Binding
from myfasthtml.core.commands import Command
from myfasthtml.core.commands import Command, CommandTemplate
from myfasthtml.core.utils import merge_classes
@@ -14,7 +14,7 @@ class Ids:
class mk:
@staticmethod
def button(element, command: Command = None, binding: Binding = None, **kwargs):
def button(element, command: Command | CommandTemplate = None, binding: Binding = None, **kwargs):
"""
Defines a static method for creating a Button object with specific configurations.
@@ -33,7 +33,7 @@ class mk:
@staticmethod
def dialog_buttons(ok_title: str = "OK",
cancel_title: str = "Cancel",
on_ok: Command = None,
on_ok: Command | CommandTemplate = None,
on_cancel: Command = None,
cls=None):
return Div(
@@ -52,7 +52,7 @@ class mk:
can_hover=False,
tooltip=None,
cls='',
command: Command = None,
command: Command | CommandTemplate = None,
binding: Binding = None,
**kwargs):
"""
@@ -92,7 +92,7 @@ class mk:
icon=None,
size: str = "sm",
cls='',
command: Command = None,
command: Command | CommandTemplate = None,
binding: Binding = None,
**kwargs):
merged_cls = merge_classes("flex", cls, kwargs)
@@ -109,7 +109,10 @@ class mk:
replace("xl", "32"))
@staticmethod
def manage_command(ft, command: Command):
def manage_command(ft, command: Command | CommandTemplate):
if isinstance(command, CommandTemplate):
command = command.command
if command:
ft = command.bind_ft(ft)
@@ -130,7 +133,7 @@ class mk:
return ft
@staticmethod
def mk(ft, command: Command = None, binding: Binding = None, init_binding=True):
def mk(ft, command: Command | CommandTemplate = None, binding: Binding = None, init_binding=True):
ft = mk.manage_command(ft, command) if command else ft
ft = mk.manage_binding(ft, binding, init_binding=init_binding) if binding else ft
return ft