I can persist tabmanager state

This commit is contained in:
2025-11-11 23:03:52 +01:00
parent 7f56b89e66
commit fb57a6a81d
9 changed files with 164 additions and 50 deletions

View File

@@ -5,7 +5,8 @@ from fasthtml.components import *
from myfasthtml.controls.Layout import Layout
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.controls.helpers import Ids
from myfasthtml.controls.helpers import Ids, mk
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import InstancesManager
from myfasthtml.myfastapp import create_app
@@ -30,11 +31,14 @@ def index(session):
for i in range(1000):
layout.left_drawer.append(Div(f"Left Drawer Item {i}"))
tabs_manager = InstancesManager.get(session, Ids.TabsManager, TabsManager)
tabs_manager.add_tab("Users", Div("Content 1"))
tabs_manager.add_tab("Users", Div("Content 2"))
tabs_manager.add_tab("Users", Div("Content 3"))
tabs_manager = TabsManager(session, _id="main")
btn = mk.button("Add Tab",
command=Command("AddTab",
"Add a new tab",
tabs_manager.on_new_tab, "Tabs", Div("Content")).
htmx(target=f"#{tabs_manager.get_id()}"))
layout.set_main(tabs_manager)
layout.set_footer(btn)
return layout

View File

@@ -1,4 +1,5 @@
class BaseCommands:
def __init__(self, owner):
self._owner = owner
self._id = owner.get_id()
self._id = owner.get_id()
self._prefix = owner.get_prefix()

View File

@@ -1,11 +1,11 @@
import uuid
from dataclasses import dataclass
from typing import Any
from fasthtml.common import Div, Button, Span
from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.helpers import Ids
from myfasthtml.controls.helpers import Ids, mk
from myfasthtml.core.commands import Command
from myfasthtml.core.dbmanager import DbObject
from myfasthtml.core.instances import MultipleInstance, BaseInstance
from myfasthtml.icons.fluent_p3 import dismiss_circle16_regular
@@ -35,18 +35,25 @@ class TabsManagerState(DbObject):
class Commands(BaseCommands):
pass
def show_tab(self, tab_id):
return Command(f"{self._prefix}SowTab",
"Activate or show a specific tab",
self._owner.show_tab, tab_id).htmx(target=f"#{self._id}", swap="outerHTML")
class TabsManager(MultipleInstance):
def __init__(self, session):
super().__init__(session, Ids.TabsManager)
def __init__(self, session, _id=None):
super().__init__(session, Ids.TabsManager, _id=_id)
self._state = TabsManagerState(self)
self._commands = Commands(self)
self.commands = Commands(self)
def get_state(self):
return self._state
def on_new_tab(self, label: str, component: Any):
self.add_tab(label, component)
return self
def add_tab(self, label: str, component: Any, activate: bool = True) -> str:
"""
Add a new tab or update an existing one with the same component type, ID and label.
@@ -108,6 +115,13 @@ class TabsManager(MultipleInstance):
return tab_id
def show_tab(self, tab_id):
if tab_id not in self._state.tabs:
return None
self._state.active_tab = tab_id
return self
def _mk_tab_button(self, tab_id: str, tab_data: dict):
"""
Create a single tab button with its label and close button.
@@ -122,7 +136,7 @@ class TabsManager(MultipleInstance):
is_active = tab_id == self._state.active_tab
return Button(
Span(tab_data.get("label", "Untitled"), cls="mf-tab-label"),
mk.mk(Span(tab_data.get("label", "Untitled"), cls="mf-tab-label"), command=self.commands.show_tab(tab_id)),
Span(dismiss_circle16_regular, cls="mf-tab-close-btn"),
cls=f"mf-tab-button {'mf-tab-active' if is_active else ''}",
data_tab_id=tab_id,

View File

@@ -42,14 +42,7 @@ class DbObject:
self._name = name or self.__class__.__name__
self._db_manager = db_manager or InstancesManager.get(self._session, Ids.DbManager, DbManager)
# init is possible
if self._db_manager.exists_entry(self._name):
props = self._db_manager.load(self._name)
for k, v in props.items():
if hasattr(self, k):
setattr(self, k, v)
else:
self._save_self()
self._finalize_initialization()
@contextmanager
def initializing(self):
@@ -58,8 +51,8 @@ class DbObject:
try:
yield
finally:
self._finalize_initialization()
self._initializing = old_state
self._save_self()
def __setattr__(self, name: str, value: str):
if name.startswith("_") or getattr(self, "_initializing", False):
@@ -73,6 +66,13 @@ class DbObject:
super().__setattr__(name, value)
self._save_self()
def _finalize_initialization(self):
if self._db_manager.exists_entry(self._name):
props = self._db_manager.load(self._name)
self.update(props)
else:
self._save_self()
def _save_self(self):
props = {k: getattr(self, k) for k, v in self._get_properties().items() if not k.startswith("_")}
if props:
@@ -103,11 +103,14 @@ class DbObject:
properties |= kwargs
with self.initializing():
for k, v in properties.items():
if hasattr(self, k) and k not in DbObject._forbidden_attrs: # internal variables cannot be updated
setattr(self, k, v)
# save the new state
old_state = getattr(self, "_initializing", False)
self._initializing = True
for k, v in properties.items():
if hasattr(self, k) and k not in DbObject._forbidden_attrs: # internal variables cannot be updated
setattr(self, k, v)
self._save_self()
self._initializing = old_state
def copy(self):
as_dict = self._get_properties().copy()

View File

@@ -17,9 +17,10 @@ class BaseInstance:
Base class for all instances (manageable by InstancesManager)
"""
def __init__(self, session: dict, _id: str, auto_register: bool = True):
def __init__(self, session: dict, prefix: str, _id: str, auto_register: bool = True):
self._session = session
self._id = _id
self._prefix = prefix
if auto_register:
InstancesManager.register(session, self)
@@ -28,6 +29,9 @@ class BaseInstance:
def get_session(self):
return self._session
def get_prefix(self):
return self._prefix
class SingleInstance(BaseInstance):
@@ -36,8 +40,7 @@ class SingleInstance(BaseInstance):
"""
def __init__(self, session: dict, prefix: str, auto_register: bool = True):
super().__init__(session, prefix, auto_register)
self._instance = None
super().__init__(session, prefix, prefix, auto_register)
class UniqueInstance(BaseInstance):
@@ -47,8 +50,8 @@ class UniqueInstance(BaseInstance):
"""
def __init__(self, session: dict, prefix: str, auto_register: bool = True):
super().__init__(session, prefix, auto_register)
self._instance = None
super().__init__(session, prefix, prefix, auto_register)
self._prefix = prefix
class MultipleInstance(BaseInstance):
@@ -56,9 +59,9 @@ class MultipleInstance(BaseInstance):
Base class for instances that can have multiple instances at a time.
"""
def __init__(self, session: dict, prefix: str, auto_register: bool = True):
super().__init__(session, f"{prefix}-{str(uuid.uuid4())}", auto_register)
self._instance = 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)
self._prefix = prefix
class InstancesManager:

View File

@@ -90,6 +90,14 @@ class Empty(ChildrenPredicate):
return len(actual.children) == 0 and len(actual.attrs) == 0
class NoChildren(ChildrenPredicate):
def __init__(self):
super().__init__(None)
def validate(self, actual):
return len(actual.children) == 0
class AttributeForbidden(ChildrenPredicate):
"""
To validate that an attribute is not present in an element.