Fixed Tab content lost on reload
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user