From edcd3ae1a86d432dec3e044a43c0b55b91eb90d7 Mon Sep 17 00:00:00 2001 From: Kodjo Sossouvi Date: Sun, 16 Nov 2025 16:17:42 +0100 Subject: [PATCH] Fixed Tab content lost on reload --- src/myfasthtml/controls/TabsManager.py | 24 ++++++----- src/myfasthtml/controls/VisNetwork.py | 56 ++++++++++++++++++-------- src/myfasthtml/core/instances.py | 2 +- 3 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/myfasthtml/controls/TabsManager.py b/src/myfasthtml/controls/TabsManager.py index eafe18f..8915575 100644 --- a/src/myfasthtml/controls/TabsManager.py +++ b/src/myfasthtml/controls/TabsManager.py @@ -114,12 +114,12 @@ class TabsManager(MultipleInstance): logger.debug(f"on_new_tab {label=}, {component=}, {auto_increment=}") if auto_increment: label = f"{label}_{self._get_tab_count()}" - tab_id = self.add_tab(label, component) component = component or VisNetwork(self._session, nodes=vis_nodes, edges=vis_edges) + tab_id = self.add_tab(label, component) return ( self._mk_tabs_controller(), self._wrap_tab_content(self._mk_tab_content(tab_id, component)), - self._mk_tabs_header(True), + self._mk_tabs_header_wrapper(True), ) def add_tab(self, label: str, component: Any, activate: bool = True) -> str: @@ -258,7 +258,7 @@ class TabsManager(MultipleInstance): Script(f'updateTabs("{self._id}-controller");'), ) - def _mk_tabs_header(self, oob=False): + def _mk_tabs_header_wrapper(self, oob=False): # Create visible tab buttons visible_tab_buttons = [ self._mk_tab_button(self._state.tabs[tab_id]) @@ -318,14 +318,20 @@ class TabsManager(MultipleInstance): Returns: Div element containing the active tab content or empty container """ - content = None - if self._state.active_tab and self._state.active_tab in self._state._tabs_content: - component = self._state._tabs_content[self._state.active_tab] - content = component + if self._state.active_tab: + active_tab = self._state.active_tab + if active_tab in self._state._tabs_content: + tab_content = self._state._tabs_content[active_tab] + else: + content = self._get_tab_content(active_tab) + tab_content = self._mk_tab_content(active_tab, content) + self._state._tabs_content[active_tab] = tab_content + else: + tab_content = self._mk_tab_content("", None) return Div( - self._mk_tab_content(self._state.active_tab, content), + tab_content, cls="mf-tab-content-wrapper", id=f"{self._id}-content-wrapper", ) @@ -374,7 +380,7 @@ class TabsManager(MultipleInstance): """ return Div( self._mk_tabs_controller(), - self._mk_tabs_header(), + self._mk_tabs_header_wrapper(), self._mk_tab_content_wrapper(), cls="mf-tabs-manager", id=self._id, diff --git a/src/myfasthtml/controls/VisNetwork.py b/src/myfasthtml/controls/VisNetwork.py index 3e41069..9da5a91 100644 --- a/src/myfasthtml/controls/VisNetwork.py +++ b/src/myfasthtml/controls/VisNetwork.py @@ -3,48 +3,72 @@ import logging from fasthtml.components import Script, Div from myfasthtml.controls.helpers import Ids +from myfasthtml.core.dbmanager import DbObject from myfasthtml.core.instances import MultipleInstance logger = logging.getLogger("VisNetwork") + +class VisNetworkState(DbObject): + def __init__(self, owner): + super().__init__(owner.get_session(), owner.get_id()) + with self.initializing(): + # persisted in DB + self.nodes: list = [] + self.edges: list = [] + self.options: dict = { + "autoResize": True, + "interaction": { + "dragNodes": True, + "zoomView": True, + "dragView": True, + }, + "physics": {"enabled": True} + } + + class VisNetwork(MultipleInstance): def __init__(self, session, _id=None, nodes=None, edges=None, options=None): super().__init__(session, Ids.VisNetwork, _id=_id) logger.debug(f"VisNetwork created with id: {self._id}") - # Default values - self.nodes = nodes or [] - self.edges = edges or [] - self.options = options or { - "autoResize": True, - "interaction": { - "dragNodes": True, - "zoomView": True, - "dragView": True, - }, - "physics": {"enabled": True} - } + self._state = VisNetworkState(self) + self._update_state(nodes, edges, options) + + def _update_state(self, nodes, edges, options): + logger.debug(f"Updating VisNetwork state with {nodes=}, {edges=}, {options=}") + if not nodes and not edges and not options: + return + + state = self._state.copy() + if nodes is not None: + state.nodes = nodes + if edges is not None: + state.edges = edges + if options is not None: + state.options = options + + self._state.update(state) def render(self): # Prepare JS arrays (no JSON library needed) js_nodes = ",\n ".join( f'{{ id: {n["id"]}, label: "{n.get("label", "")}" }}' - for n in self.nodes + for n in self._state.nodes ) js_edges = ",\n ".join( f'{{ from: {e["from"]}, to: {e["to"]} }}' - for e in self.edges + for e in self._state.edges ) # Convert Python options to JS import json - js_options = json.dumps(self.options, indent=2) + js_options = json.dumps(self._state.options, indent=2) return ( Div( id=self._id, cls="mf-vis", - #style="width:100%; height:100%;", # Let parent control the layout ), # The script initializing Vis.js diff --git a/src/myfasthtml/core/instances.py b/src/myfasthtml/core/instances.py index 094bda3..9fa3e3a 100644 --- a/src/myfasthtml/core/instances.py +++ b/src/myfasthtml/core/instances.py @@ -60,7 +60,7 @@ class MultipleInstance(BaseInstance): """ def __init__(self, session: dict, prefix: str, auto_register: bool = True, _id=None): - super().__init__(session, prefix, f"{prefix}-{_id or str(uuid.uuid4())}", auto_register) + super().__init__(session, prefix, _id or f"{prefix}-{str(uuid.uuid4())}", auto_register) self._prefix = prefix