Fixed Tab content lost on reload

This commit is contained in:
2025-11-16 16:17:42 +01:00
parent ca238303b8
commit edcd3ae1a8
3 changed files with 56 additions and 26 deletions

View File

@@ -114,12 +114,12 @@ class TabsManager(MultipleInstance):
logger.debug(f"on_new_tab {label=}, {component=}, {auto_increment=}") logger.debug(f"on_new_tab {label=}, {component=}, {auto_increment=}")
if auto_increment: if auto_increment:
label = f"{label}_{self._get_tab_count()}" 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) component = component or VisNetwork(self._session, nodes=vis_nodes, edges=vis_edges)
tab_id = self.add_tab(label, component)
return ( return (
self._mk_tabs_controller(), self._mk_tabs_controller(),
self._wrap_tab_content(self._mk_tab_content(tab_id, component)), 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: 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");'), 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 # Create visible tab buttons
visible_tab_buttons = [ visible_tab_buttons = [
self._mk_tab_button(self._state.tabs[tab_id]) self._mk_tab_button(self._state.tabs[tab_id])
@@ -318,14 +318,20 @@ class TabsManager(MultipleInstance):
Returns: Returns:
Div element containing the active tab content or empty container 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: if self._state.active_tab:
component = self._state._tabs_content[self._state.active_tab] active_tab = self._state.active_tab
content = component 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( return Div(
self._mk_tab_content(self._state.active_tab, content), tab_content,
cls="mf-tab-content-wrapper", cls="mf-tab-content-wrapper",
id=f"{self._id}-content-wrapper", id=f"{self._id}-content-wrapper",
) )
@@ -374,7 +380,7 @@ class TabsManager(MultipleInstance):
""" """
return Div( return Div(
self._mk_tabs_controller(), self._mk_tabs_controller(),
self._mk_tabs_header(), self._mk_tabs_header_wrapper(),
self._mk_tab_content_wrapper(), self._mk_tab_content_wrapper(),
cls="mf-tabs-manager", cls="mf-tabs-manager",
id=self._id, id=self._id,

View File

@@ -3,19 +3,20 @@ import logging
from fasthtml.components import Script, Div from fasthtml.components import Script, Div
from myfasthtml.controls.helpers import Ids from myfasthtml.controls.helpers import Ids
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance from myfasthtml.core.instances import MultipleInstance
logger = logging.getLogger("VisNetwork") logger = logging.getLogger("VisNetwork")
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 class VisNetworkState(DbObject):
self.nodes = nodes or [] def __init__(self, owner):
self.edges = edges or [] super().__init__(owner.get_session(), owner.get_id())
self.options = options or { with self.initializing():
# persisted in DB
self.nodes: list = []
self.edges: list = []
self.options: dict = {
"autoResize": True, "autoResize": True,
"interaction": { "interaction": {
"dragNodes": True, "dragNodes": True,
@@ -25,26 +26,49 @@ class VisNetwork(MultipleInstance):
"physics": {"enabled": 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}")
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): def render(self):
# Prepare JS arrays (no JSON library needed) # Prepare JS arrays (no JSON library needed)
js_nodes = ",\n ".join( js_nodes = ",\n ".join(
f'{{ id: {n["id"]}, label: "{n.get("label", "")}" }}' f'{{ id: {n["id"]}, label: "{n.get("label", "")}" }}'
for n in self.nodes for n in self._state.nodes
) )
js_edges = ",\n ".join( js_edges = ",\n ".join(
f'{{ from: {e["from"]}, to: {e["to"]} }}' f'{{ from: {e["from"]}, to: {e["to"]} }}'
for e in self.edges for e in self._state.edges
) )
# Convert Python options to JS # Convert Python options to JS
import json import json
js_options = json.dumps(self.options, indent=2) js_options = json.dumps(self._state.options, indent=2)
return ( return (
Div( Div(
id=self._id, id=self._id,
cls="mf-vis", cls="mf-vis",
#style="width:100%; height:100%;", # Let parent control the layout
), ),
# The script initializing Vis.js # The script initializing Vis.js

View File

@@ -60,7 +60,7 @@ class MultipleInstance(BaseInstance):
""" """
def __init__(self, session: dict, prefix: str, auto_register: bool = True, _id=None): 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 self._prefix = prefix