From 2af43f357d6dd9013c173f7bde665fa15df09a91 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Sun, 22 Feb 2026 22:01:26 +0100 Subject: [PATCH] Added menu management --- .../controls/HierarchicalCanvasGraph.py | 17 +++-- src/myfasthtml/controls/IconsHelper.py | 1 + src/myfasthtml/controls/InstancesDebugger.py | 15 +---- src/myfasthtml/controls/Menu.py | 65 +++++++++++++++++++ src/myfasthtml/core/commands.py | 2 + 5 files changed, 84 insertions(+), 16 deletions(-) create mode 100644 src/myfasthtml/controls/Menu.py diff --git a/src/myfasthtml/controls/HierarchicalCanvasGraph.py b/src/myfasthtml/controls/HierarchicalCanvasGraph.py index 8ba80bf..aaa31b2 100644 --- a/src/myfasthtml/controls/HierarchicalCanvasGraph.py +++ b/src/myfasthtml/controls/HierarchicalCanvasGraph.py @@ -7,10 +7,12 @@ from fasthtml.components import Div from fasthtml.xtend import Script from myfasthtml.controls.BaseCommands import BaseCommands +from myfasthtml.controls.Menu import Menu, MenuConf from myfasthtml.controls.Query import Query, QueryConf from myfasthtml.core.commands import Command from myfasthtml.core.dbmanager import DbObject from myfasthtml.core.instances import MultipleInstance +from myfasthtml.icons.fluent import arrow_reset20_regular logger = logging.getLogger("HierarchicalCanvasGraph") @@ -91,10 +93,11 @@ class Commands(BaseCommands): This command can be used to fix stuck/frozen canvas by resetting zoom/pan state. """ return Command( - "ResetTransform", + "ResetView", "Reset view transform", self._owner, - self._owner.handle_reset_view + self._owner.handle_reset_view, + icon=arrow_reset20_regular ).htmx(target=f"#{self._id}") @@ -136,6 +139,9 @@ class HierarchicalCanvasGraph(MultipleInstance): self._query.bind_command("QueryChanged", self.commands.apply_filter()) self._query.bind_command("CancelQuery", self.commands.apply_filter()) + # Add Menu + self._menu = Menu(self, conf=MenuConf(["ResetView"])) + logger.debug(f"HierarchicalCanvasGraph created with id={self._id}, " f"nodes={len(conf.nodes)}, edges={len(conf.edges)}") @@ -341,8 +347,11 @@ class HierarchicalCanvasGraph(MultipleInstance): options_json = json.dumps(options, indent=2) return Div( - # Query filter bar - self._query, + Div( + self._query, + self._menu, + cls="flex justify-between m-2" + ), # Canvas element (sized by JS to fill container) Div( diff --git a/src/myfasthtml/controls/IconsHelper.py b/src/myfasthtml/controls/IconsHelper.py index 542df2f..c517ff7 100644 --- a/src/myfasthtml/controls/IconsHelper.py +++ b/src/myfasthtml/controls/IconsHelper.py @@ -13,6 +13,7 @@ default_icons = { False: checkbox_unchecked20_regular, "Brain": brain_circuit20_regular, + "QuestionMark" : question20_regular, # TreeView icons "TreeViewFolder": folder20_regular, diff --git a/src/myfasthtml/controls/InstancesDebugger.py b/src/myfasthtml/controls/InstancesDebugger.py index 62aaac8..c3d0057 100644 --- a/src/myfasthtml/controls/InstancesDebugger.py +++ b/src/myfasthtml/controls/InstancesDebugger.py @@ -1,11 +1,10 @@ from dataclasses import dataclass -from fasthtml.components import Div, Button +from fasthtml.components import Div from myfasthtml.controls.HierarchicalCanvasGraph import HierarchicalCanvasGraph, HierarchicalCanvasGraphConf from myfasthtml.controls.Panel import Panel from myfasthtml.controls.Properties import Properties -from myfasthtml.controls.helpers import mk from myfasthtml.core.commands import Command from myfasthtml.core.instances import SingleInstance, UniqueInstance, MultipleInstance, InstancesManager @@ -43,20 +42,12 @@ class InstancesDebugger(SingleInstance): } ) self._canvas_graph = HierarchicalCanvasGraph(self, conf=graph_conf, _id="-canvas-graph") - - # Debug button to reset transform - reset_btn = mk.button( - "🔄 Reset View", - command=self._canvas_graph.commands.reset_view(), - cls="btn btn-sm btn-ghost" - ) - + main_content = Div( - Div(reset_btn, cls="p-2 flex justify-end bg-base-200"), self._canvas_graph, cls="flex flex-col h-full" ) - + return self._panel.set_main(main_content) def on_select_node(self, node_id=None, label=None, kind=None, type=None): diff --git a/src/myfasthtml/controls/Menu.py b/src/myfasthtml/controls/Menu.py new file mode 100644 index 0000000..7e020ce --- /dev/null +++ b/src/myfasthtml/controls/Menu.py @@ -0,0 +1,65 @@ +from dataclasses import dataclass, field +from typing import Optional + +from fasthtml.components import Div + +from myfasthtml.controls.IconsHelper import IconsHelper +from myfasthtml.controls.helpers import mk +from myfasthtml.core.dbmanager import DbObject +from myfasthtml.core.instances import MultipleInstance + + +@dataclass +class MenuConf: + fixed_items: list = field(default_factory=list) + + +class MenuState(DbObject): + def __init__(self, owner, save_state): + with self.initializing(): + super().__init__(owner, save_state=save_state) + self.last_used: Optional[list] = None + + +class Menu(MultipleInstance): + def __init__(self, parent, conf=None, save_state=True, _id=None): + super().__init__(parent, _id=_id) + self.conf = conf or MenuConf() + self._state = MenuState(self, save_state=save_state) + self.usable_commands = self._get_parent_commands() + + def _get_parent_commands(self): + commands_obj = self._parent.commands + + callables = [ + name + for name in dir(commands_obj) + if not name.startswith("_") + and callable(getattr(commands_obj, name)) + ] + + return { + c.name: c for c in [getattr(commands_obj, name)() for name in callables] + } + + def _mk_menu(self, command_name): + command = self.usable_commands.get(command_name) + return mk.icon(command.icon or IconsHelper.get("QuestionMark"), + command=command, + tooltip=command.description) + + def render(self): + return Div( + Div( + *[self._mk_menu(command_name) for command_name in self.conf.fixed_items], + *( + Div("|"), + *[self._mk_menu(command_name) for command_name in self._state.last_used[:3]] + ) if self._state.last_used else [], + cls="flex" + ), + id=self._id + ) + + def __ft__(self): + return self.render() diff --git a/src/myfasthtml/core/commands.py b/src/myfasthtml/core/commands.py index 7d0b45c..26e2a9e 100644 --- a/src/myfasthtml/core/commands.py +++ b/src/myfasthtml/core/commands.py @@ -85,10 +85,12 @@ class Command: args: list = None, kwargs: dict = None, key=None, + icon=None, auto_register=True): self.id = uuid.uuid4() self.name = name self.description = description + self.icon = icon self.owner = owner self.callback = callback self.default_args = args or []