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 []