diff --git a/src/myfasthtml/controls/InstancesDebugger.py b/src/myfasthtml/controls/InstancesDebugger.py index 5528b64..d4c1810 100644 --- a/src/myfasthtml/controls/InstancesDebugger.py +++ b/src/myfasthtml/controls/InstancesDebugger.py @@ -1,6 +1,7 @@ from myfasthtml.controls.Panel import Panel +from myfasthtml.controls.Properties import Properties from myfasthtml.controls.VisNetwork import VisNetwork -from myfasthtml.core.commands import LambdaCommand +from myfasthtml.core.commands import Command from myfasthtml.core.instances import SingleInstance, InstancesManager from myfasthtml.core.network_utils import from_parent_child_list @@ -9,13 +10,23 @@ class InstancesDebugger(SingleInstance): def __init__(self, parent, _id=None): super().__init__(parent, _id=_id) self._panel = Panel(self, _id="-panel") + self._command = Command("ShowInstance", + "Display selected Instance", + self.on_network_event).htmx(target=f"#{self._panel.get_id()}_r") def render(self): nodes, edges = self._get_nodes_and_edges() - c = LambdaCommand(lambda event_data: print("received", event_data)) - vis_network = VisNetwork(self, nodes=nodes, edges=edges, _id="-vis", events_handlers={"select_node": c}) + vis_network = VisNetwork(self, nodes=nodes, edges=edges, _id="-vis", events_handlers={"select_node": self._command}) return self._panel.set_main(vis_network) + def on_network_event(self, event_data: dict): + session, instance_id = event_data["nodes"][0].split("#") + properties = {"Id": "_id", "Parent Id": "_parent._id"} + return self._panel.set_right(Properties(self, + InstancesManager.get(session, instance_id), + properties, + _id="-properties")) + def _get_nodes_and_edges(self): instances = self._get_instances() nodes, edges = from_parent_child_list( diff --git a/src/myfasthtml/controls/Panel.py b/src/myfasthtml/controls/Panel.py index 74f2961..eecf312 100644 --- a/src/myfasthtml/controls/Panel.py +++ b/src/myfasthtml/controls/Panel.py @@ -46,6 +46,7 @@ class Panel(MultipleInstance): sides, and adjust the width of the sides dynamically. The class also handles rendering the panel with appropriate HTML elements and JavaScript for interactivity. """ + def __init__(self, parent, conf=None, _id=None): super().__init__(parent, _id=_id) self.conf = conf or PanelConf() @@ -66,35 +67,35 @@ class Panel(MultipleInstance): def set_right(self, right): self._right = right - return self + return Div(self._right, id=f"{self._id}_r") def set_left(self, left): self._left = left - return self + return Div(self._left, id=f"{self._id}_l") def _mk_right(self): if not self.conf.right: return None - + resizer = Div( cls="mf-resizer mf-resizer-right", data_command_id=self.commands.update_side_width("right").id, data_side="right" ) - - return Div(resizer, self._right, cls="mf-panel-right") - + + return Div(resizer, Div(self._right, id=f"{self._id}_r"), cls="mf-panel-right") + def _mk_left(self): if not self.conf.left: return None - + resizer = Div( cls="mf-resizer mf-resizer-left", data_command_id=self.commands.update_side_width("left").id, data_side="left" ) - - return Div(self._left, resizer, cls="mf-panel-left") + + return Div(Div(self._left, id=f"{self._id}_l"), resizer, cls="mf-panel-left") def render(self): return Div( diff --git a/src/myfasthtml/controls/Properties.py b/src/myfasthtml/controls/Properties.py new file mode 100644 index 0000000..8055d29 --- /dev/null +++ b/src/myfasthtml/controls/Properties.py @@ -0,0 +1,44 @@ +from fasthtml.components import Div +from myutils.Expando import Expando + +from myfasthtml.core.instances import MultipleInstance + + +class Properties(MultipleInstance): + def __init__(self, parent, obj=None, properties: dict = None, _id=None): + super().__init__(parent, _id=_id) + self.obj = obj + self.properties = properties or self._get_default_properties(obj) + self.expando = self._create_expando() + + def set_obj(self, obj, properties: list[str] = None): + self.obj = obj + self.properties = properties or self._get_default_properties(obj) + + def render(self): + return Div( + *[Div(k, ":", v) for k, v in self.expando.as_dict().items()], + id=self._id, + ) + + def _create_expando(self): + res = {} + for attr_name, mapping in self.properties.items(): + attrs_path = mapping.split(".") + current = self.obj + for attr in attrs_path: + if hasattr(current, attr): + current = getattr(current, attr) + else: + res[attr_name] = None + break + res[attr_name] = current + + return Expando(res) + + @staticmethod + def _get_default_properties(obj): + return {k: k for k, v in dir(obj) if not k.startswith("_")} if obj else {} + + def __ft__(self): + return self.render() diff --git a/src/myfasthtml/controls/VisNetwork.py b/src/myfasthtml/controls/VisNetwork.py index 67b7882..73e4b01 100644 --- a/src/myfasthtml/controls/VisNetwork.py +++ b/src/myfasthtml/controls/VisNetwork.py @@ -101,13 +101,15 @@ class VisNetwork(MultipleInstance): event_handlers_js += f""" network.on('{vis_event_name}', function(params) {{ const event_data = {{ + event_name: '{event_name}', nodes: params.nodes, edges: params.edges, pointer: params.pointer }}; htmx.ajax('POST', '{command_htmx_options['url']}', {{ values: {{event_data: JSON.stringify(event_data)}}, - swap: 'none' + target: '{command_htmx_options['target']}', + swap: '{command_htmx_options['swap']}' }}); }}); """ diff --git a/src/myfasthtml/core/commands.py b/src/myfasthtml/core/commands.py index 0f56e3b..4871fea 100644 --- a/src/myfasthtml/core/commands.py +++ b/src/myfasthtml/core/commands.py @@ -1,4 +1,5 @@ import inspect +import json import uuid from typing import Optional @@ -98,14 +99,12 @@ class BaseCommand: return f"{ROUTE_ROOT}{Routes.Commands}?c_id={self.id}" def ajax_htmx_options(self): - res = {"url": self.url} - if "hx-target" in self._htmx_extra: - res["target"] = self._htmx_extra["hx-target"] - if "hx-swap" in self._htmx_extra: - res["swap"] = self._htmx_extra["hx-swap"] - res["values"] = {} - - return res + return { + "url": self.url, + "target": self._htmx_extra.get("hx-target", "this"), + "swap": self._htmx_extra.get("hx-swap", "outerHTML"), + "values": {} + } def get_ft(self): return self._ft @@ -151,6 +150,8 @@ class Command(BaseCommand): return float(value) elif param.annotation == list: return value.split(",") + elif param.annotation == dict: + return json.loads(value) return value def ajax_htmx_options(self): diff --git a/src/myfasthtml/core/instances.py b/src/myfasthtml/core/instances.py index 7b5d394..4d45958 100644 --- a/src/myfasthtml/core/instances.py +++ b/src/myfasthtml/core/instances.py @@ -84,7 +84,7 @@ class BaseInstance: return self._prefix def get_full_id(self) -> str: - return f"{InstancesManager.get_session_id(self._session)}-{self._id}" + return f"{InstancesManager.get_session_id(self._session)}#{self._id}" def get_full_parent_id(self) -> Optional[str]: parent = self.get_parent() diff --git a/src/myfasthtml/core/network_utils.py b/src/myfasthtml/core/network_utils.py index 4840cf0..5a9c077 100644 --- a/src/myfasthtml/core/network_utils.py +++ b/src/myfasthtml/core/network_utils.py @@ -3,6 +3,7 @@ from collections.abc import Callable ROOT_COLOR = "#ff9999" GHOST_COLOR = "#cccccc" + def from_nested_dict(trees: list[dict]) -> tuple[list, list]: """ Convert a list of nested dictionaries to vis.js nodes and edges format.