updated css. Added orientation. Saving orientation, position and scale in state
This commit is contained in:
@@ -6,6 +6,8 @@ from typing import Optional
|
||||
from fasthtml.components import Div
|
||||
from fasthtml.xtend import Script
|
||||
|
||||
from myfasthtml.controls.BaseCommands import BaseCommands
|
||||
from myfasthtml.core.commands import Command
|
||||
from myfasthtml.core.dbmanager import DbObject
|
||||
from myfasthtml.core.instances import MultipleInstance
|
||||
|
||||
@@ -30,20 +32,39 @@ class HierarchicalCanvasGraphConf:
|
||||
class HierarchicalCanvasGraphState(DbObject):
|
||||
"""Persistent state for HierarchicalCanvasGraph.
|
||||
|
||||
Only the collapsed state is persisted. Zoom, pan, and selection are ephemeral.
|
||||
Persists collapsed nodes, view transform (zoom/pan), and layout orientation.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, owner, save_state=True):
|
||||
super().__init__(owner, save_state=save_state)
|
||||
with self.initializing():
|
||||
# Persisted: set of collapsed node IDs (stored as list for JSON serialization)
|
||||
self.collapsed: list = []
|
||||
|
||||
|
||||
# Persisted: zoom/pan transform
|
||||
self.transform: dict = {"x": 0, "y": 0, "scale": 1}
|
||||
|
||||
# Persisted: layout orientation ('horizontal' or 'vertical')
|
||||
self.layout_mode: str = 'horizontal'
|
||||
|
||||
# Not persisted: current selection (ephemeral)
|
||||
self.ns_selected_id: Optional[str] = None
|
||||
|
||||
# Not persisted: zoom/pan transform (ephemeral)
|
||||
self.ns_transform: dict = {"x": 0, "y": 0, "scale": 1}
|
||||
|
||||
class Commands(BaseCommands):
|
||||
"""Commands for HierarchicalCanvasGraph internal state management."""
|
||||
|
||||
def update_view_state(self):
|
||||
"""Update view transform and layout mode.
|
||||
|
||||
This command is called internally by the JS to persist view state changes.
|
||||
"""
|
||||
return Command(
|
||||
"UpdateViewState",
|
||||
"Update view transform and layout mode",
|
||||
self._owner,
|
||||
self._owner._handle_update_view_state
|
||||
).htmx(target=f"#{self._id}", swap='none')
|
||||
|
||||
|
||||
class HierarchicalCanvasGraph(MultipleInstance):
|
||||
@@ -65,7 +86,7 @@ class HierarchicalCanvasGraph(MultipleInstance):
|
||||
- select_node: Fired when a node is clicked (not on toggle button)
|
||||
- toggle_node: Fired when a node's expand/collapse button is clicked
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, parent, conf: HierarchicalCanvasGraphConf, _id=None):
|
||||
"""Initialize the HierarchicalCanvasGraph control.
|
||||
|
||||
@@ -77,10 +98,11 @@ class HierarchicalCanvasGraph(MultipleInstance):
|
||||
super().__init__(parent, _id=_id)
|
||||
self.conf = conf
|
||||
self._state = HierarchicalCanvasGraphState(self)
|
||||
|
||||
self.commands = Commands(self)
|
||||
|
||||
logger.debug(f"HierarchicalCanvasGraph created with id={self._id}, "
|
||||
f"nodes={len(conf.nodes)}, edges={len(conf.edges)}")
|
||||
|
||||
|
||||
def get_state(self):
|
||||
"""Get the control's persistent state.
|
||||
|
||||
@@ -88,7 +110,7 @@ class HierarchicalCanvasGraph(MultipleInstance):
|
||||
HierarchicalCanvasGraphState: The state object
|
||||
"""
|
||||
return self._state
|
||||
|
||||
|
||||
def get_selected_id(self) -> Optional[str]:
|
||||
"""Get the currently selected node ID.
|
||||
|
||||
@@ -96,7 +118,7 @@ class HierarchicalCanvasGraph(MultipleInstance):
|
||||
str or None: The selected node ID, or None if no selection
|
||||
"""
|
||||
return self._state.ns_selected_id
|
||||
|
||||
|
||||
def set_collapsed(self, node_ids: set):
|
||||
"""Set the collapsed state of nodes.
|
||||
|
||||
@@ -105,7 +127,7 @@ class HierarchicalCanvasGraph(MultipleInstance):
|
||||
"""
|
||||
self._state.collapsed = list(node_ids)
|
||||
logger.debug(f"set_collapsed: {len(node_ids)} nodes collapsed")
|
||||
|
||||
|
||||
def toggle_node(self, node_id: str):
|
||||
"""Toggle the collapsed state of a node.
|
||||
|
||||
@@ -122,10 +144,29 @@ class HierarchicalCanvasGraph(MultipleInstance):
|
||||
else:
|
||||
collapsed_set.add(node_id)
|
||||
logger.debug(f"toggle_node: collapsed {node_id}")
|
||||
|
||||
|
||||
self._state.collapsed = list(collapsed_set)
|
||||
return self
|
||||
|
||||
def _handle_update_view_state(self, event_data: dict):
|
||||
"""Internal handler to update view state from client.
|
||||
|
||||
Args:
|
||||
event_data: Dictionary with 'transform' and/or 'layout_mode' keys
|
||||
|
||||
Returns:
|
||||
str: Empty string (no UI update needed)
|
||||
"""
|
||||
if 'transform' in event_data:
|
||||
self._state.transform = event_data['transform']
|
||||
logger.debug(f"Transform updated: {self._state.transform}")
|
||||
|
||||
if 'layout_mode' in event_data:
|
||||
self._state.layout_mode = event_data['layout_mode']
|
||||
logger.debug(f"Layout mode updated: {self._state.layout_mode}")
|
||||
|
||||
return ""
|
||||
|
||||
def _prepare_options(self) -> dict:
|
||||
"""Prepare JavaScript options object.
|
||||
|
||||
@@ -134,17 +175,24 @@ class HierarchicalCanvasGraph(MultipleInstance):
|
||||
"""
|
||||
# Convert event handlers to HTMX options
|
||||
events = {}
|
||||
|
||||
# Add internal handler for view state persistence
|
||||
events['_internal_update_state'] = self.commands.update_view_state().ajax_htmx_options()
|
||||
|
||||
# Add user-provided event handlers
|
||||
if self.conf.events_handlers:
|
||||
for event_name, command in self.conf.events_handlers.items():
|
||||
events[event_name] = command.ajax_htmx_options()
|
||||
|
||||
|
||||
return {
|
||||
"nodes": self.conf.nodes,
|
||||
"edges": self.conf.edges,
|
||||
"collapsed": self._state.collapsed,
|
||||
"events": events
|
||||
"nodes": self.conf.nodes,
|
||||
"edges": self.conf.edges,
|
||||
"collapsed": self._state.collapsed,
|
||||
"transform": self._state.transform,
|
||||
"layout_mode": self._state.layout_mode,
|
||||
"events": events
|
||||
}
|
||||
|
||||
|
||||
def render(self):
|
||||
"""Render the HierarchicalCanvasGraph control.
|
||||
|
||||
@@ -153,14 +201,14 @@ class HierarchicalCanvasGraph(MultipleInstance):
|
||||
"""
|
||||
options = self._prepare_options()
|
||||
options_json = json.dumps(options, indent=2)
|
||||
|
||||
|
||||
return Div(
|
||||
# Canvas element (sized by JS to fill container)
|
||||
Div(
|
||||
id=f"{self._id}_container",
|
||||
cls="mf-hcg-container"
|
||||
),
|
||||
|
||||
|
||||
# Initialization script
|
||||
Script(f"""
|
||||
(function() {{
|
||||
@@ -171,11 +219,11 @@ class HierarchicalCanvasGraph(MultipleInstance):
|
||||
}}
|
||||
}})();
|
||||
"""),
|
||||
|
||||
|
||||
id=self._id,
|
||||
cls="mf-hierarchical-canvas-graph"
|
||||
)
|
||||
|
||||
|
||||
def __ft__(self):
|
||||
"""FastHTML magic method for rendering.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user