Fixed darkmode load and save
This commit is contained in:
@@ -10,7 +10,7 @@ from myfasthtml.controls.Keyboard import Keyboard
|
||||
from myfasthtml.controls.Layout import Layout
|
||||
from myfasthtml.controls.TabsManager import TabsManager
|
||||
from myfasthtml.controls.helpers import Ids, mk
|
||||
from myfasthtml.core.instances import SingleInstance
|
||||
from myfasthtml.core.instances import UniqueInstance
|
||||
from myfasthtml.icons.carbon import volume_object_storage
|
||||
from myfasthtml.icons.fluent_p3 import folder_open20_regular
|
||||
from myfasthtml.myfastapp import create_app
|
||||
@@ -32,11 +32,11 @@ app, rt = create_app(protect_routes=True,
|
||||
|
||||
@rt("/")
|
||||
def index(session):
|
||||
session_instance = SingleInstance(session=session, _id=Ids.UserSession)
|
||||
session_instance = UniqueInstance(session=session, _id=Ids.UserSession)
|
||||
layout = Layout(session_instance, "Testing Layout")
|
||||
layout.set_footer("Goodbye World")
|
||||
|
||||
tabs_manager = TabsManager(layout, _id=f"{TabsManager.get_prefix()}-main")
|
||||
tabs_manager = TabsManager(layout, _id=f"{TabsManager.compute_prefix()}-main")
|
||||
btn_show_right_drawer = mk.button("show",
|
||||
command=layout.commands.toggle_drawer("right"),
|
||||
id="btn_show_right_drawer_id")
|
||||
|
||||
@@ -9,11 +9,12 @@ class InstancesDebugger(SingleInstance):
|
||||
|
||||
def render(self):
|
||||
instances = self._get_instances()
|
||||
nodes, edges = from_parent_child_list(instances,
|
||||
id_getter=lambda x: x.get_id(),
|
||||
label_getter=lambda x: x.get_prefix(),
|
||||
parent_getter=lambda x: x.get_parent().get_id() if x.get_parent() else None
|
||||
)
|
||||
nodes, edges = from_parent_child_list(
|
||||
instances,
|
||||
id_getter=lambda x: f"{InstancesManager.get_session_id(x.get_session())}-{x.get_id()}",
|
||||
label_getter=lambda x: x.get_prefix(),
|
||||
parent_getter=lambda x: x.get_parent().get_id() if x.get_parent() else None
|
||||
)
|
||||
for edge in edges:
|
||||
edge["color"] = "green"
|
||||
edge["arrows"] = {"to": {"enabled": False, "type": "circle"}}
|
||||
@@ -22,7 +23,7 @@ class InstancesDebugger(SingleInstance):
|
||||
node["shape"] = "box"
|
||||
|
||||
vis_network = VisNetwork(self, nodes=nodes, edges=edges)
|
||||
#vis_network.add_to_options(physics={"wind": {"x": 0, "y": 1}})
|
||||
# vis_network.add_to_options(physics={"wind": {"x": 0, "y": 1}})
|
||||
return vis_network
|
||||
|
||||
def _get_instances(self):
|
||||
|
||||
@@ -192,7 +192,7 @@ class Layout(SingleInstance):
|
||||
cls="flex gap-1"
|
||||
),
|
||||
Div( # right
|
||||
*self.header_right.get_content(),
|
||||
*self.header_right.get_content()[None],
|
||||
UserProfile(self),
|
||||
cls="flex gap-1"
|
||||
),
|
||||
|
||||
@@ -12,8 +12,7 @@ from myfasthtml.controls.VisNetwork import VisNetwork
|
||||
from myfasthtml.controls.helpers import mk
|
||||
from myfasthtml.core.commands import Command
|
||||
from myfasthtml.core.dbmanager import DbObject
|
||||
from myfasthtml.core.instances import MultipleInstance, BaseInstance
|
||||
from myfasthtml.core.instances_helper import InstancesHelper
|
||||
from myfasthtml.core.instances import MultipleInstance, BaseInstance, InstancesManager
|
||||
from myfasthtml.icons.fluent_p1 import tabs24_regular
|
||||
from myfasthtml.icons.fluent_p3 import dismiss_circle16_regular, tab_add24_regular
|
||||
|
||||
@@ -102,7 +101,7 @@ class TabsManager(MultipleInstance):
|
||||
tab_config = self._state.tabs[tab_id]
|
||||
if tab_config["component_type"] is None:
|
||||
return None
|
||||
return InstancesHelper.dynamic_get(self, tab_config["component_type"], tab_config["component_id"])
|
||||
return InstancesManager.dynamic_get(self, tab_config["component_type"], tab_config["component_id"])
|
||||
|
||||
@staticmethod
|
||||
def _get_tab_count():
|
||||
|
||||
@@ -4,7 +4,7 @@ from myfasthtml.controls.BaseCommands import BaseCommands
|
||||
from myfasthtml.controls.helpers import mk
|
||||
from myfasthtml.core.AuthProxy import AuthProxy
|
||||
from myfasthtml.core.commands import Command
|
||||
from myfasthtml.core.instances import SingleInstance, InstancesManager, RootInstance
|
||||
from myfasthtml.core.instances import SingleInstance, RootInstance
|
||||
from myfasthtml.core.utils import retrieve_user_info
|
||||
from myfasthtml.icons.material import dark_mode_filled, person_outline_sharp
|
||||
from myfasthtml.icons.material_p1 import light_mode_filled, alternate_email_filled
|
||||
@@ -16,6 +16,7 @@ class UserProfileState:
|
||||
self._session = owner.get_session()
|
||||
|
||||
self.theme = "light"
|
||||
self.load()
|
||||
|
||||
def load(self):
|
||||
user_info = retrieve_user_info(self._session)
|
||||
@@ -44,6 +45,7 @@ class UserProfile(SingleInstance):
|
||||
def update_dark_mode(self, client_response):
|
||||
self._state.theme = client_response.get("theme", "light")
|
||||
self._state.save()
|
||||
retrieve_user_info(self._session).get("user_settings", {})["theme"] = self._state.theme
|
||||
|
||||
def render(self):
|
||||
user_info = retrieve_user_info(self._session)
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import logging
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from dbengine.utils import get_class
|
||||
|
||||
from myfasthtml.controls.helpers import Ids
|
||||
from myfasthtml.core.utils import pascal_to_snake
|
||||
from myfasthtml.core.utils import pascal_to_snake, snake_to_pascal
|
||||
|
||||
logger = logging.getLogger("InstancesManager")
|
||||
|
||||
special_session = {
|
||||
"user_info": {"id": "** SPECIAL SESSION **"}
|
||||
@@ -57,13 +62,14 @@ class BaseInstance:
|
||||
if not getattr(self, "_is_new_instance", False):
|
||||
# Skip __init__ if instance already existed
|
||||
return
|
||||
else:
|
||||
# make sure that it's no longer considered as a new instance
|
||||
elif not isinstance(self, UniqueInstance):
|
||||
# No more __init__ unless it's UniqueInstance
|
||||
self._is_new_instance = False
|
||||
|
||||
self._parent = parent
|
||||
self._session = session or (parent.get_session() if parent else None)
|
||||
self._id = _id or self.compute_id()
|
||||
self._prefix = self._id if isinstance(self, (UniqueInstance, SingleInstance)) else self.compute_prefix()
|
||||
|
||||
if auto_register:
|
||||
InstancesManager.register(self._session, self)
|
||||
@@ -77,13 +83,16 @@ class BaseInstance:
|
||||
def get_parent(self) -> Optional['BaseInstance']:
|
||||
return self._parent
|
||||
|
||||
def get_prefix(self) -> str:
|
||||
return self._prefix
|
||||
|
||||
@classmethod
|
||||
def get_prefix(cls):
|
||||
def compute_prefix(cls):
|
||||
return f"mf-{pascal_to_snake(cls.__name__)}"
|
||||
|
||||
@classmethod
|
||||
def compute_id(cls):
|
||||
prefix = cls.get_prefix()
|
||||
prefix = cls.compute_prefix()
|
||||
if issubclass(cls, SingleInstance):
|
||||
_id = prefix
|
||||
else:
|
||||
@@ -104,6 +113,20 @@ class SingleInstance(BaseInstance):
|
||||
super().__init__(parent, session, _id, auto_register)
|
||||
|
||||
|
||||
class UniqueInstance(BaseInstance):
|
||||
"""
|
||||
Base class for instances that can only have one instance at a time.
|
||||
But unlike SingleInstance, the __init__ is called every time it's instantiated.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
parent: Optional[BaseInstance] = None,
|
||||
session: Optional[dict] = None,
|
||||
_id: Optional[str] = None,
|
||||
auto_register: bool = True):
|
||||
super().__init__(parent, session, _id, auto_register)
|
||||
|
||||
|
||||
class MultipleInstance(BaseInstance):
|
||||
"""
|
||||
Base class for instances that can have multiple instances at a time.
|
||||
@@ -157,6 +180,20 @@ class InstancesManager:
|
||||
@staticmethod
|
||||
def reset():
|
||||
InstancesManager.instances.clear()
|
||||
|
||||
@staticmethod
|
||||
def dynamic_get(parent: BaseInstance, component_type: str, instance_id: str):
|
||||
logger.debug(f"Dynamic get: {component_type=} {instance_id=}")
|
||||
cls = InstancesManager._get_class_name(component_type)
|
||||
fully_qualified_name = f"myfasthtml.controls.{cls}.{cls}"
|
||||
cls = get_class(fully_qualified_name)
|
||||
return cls(parent, instance_id)
|
||||
|
||||
@staticmethod
|
||||
def _get_class_name(component_type: str) -> str:
|
||||
component_type = component_type.replace("mf-", "")
|
||||
component_type = snake_to_pascal(component_type)
|
||||
return component_type
|
||||
|
||||
|
||||
RootInstance = SingleInstance(None, special_session, Ids.Root)
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import logging
|
||||
|
||||
from myfasthtml.controls.CommandsDebugger import CommandsDebugger
|
||||
from myfasthtml.controls.FileUpload import FileUpload
|
||||
from myfasthtml.controls.InstancesDebugger import InstancesDebugger
|
||||
from myfasthtml.controls.VisNetwork import VisNetwork
|
||||
from myfasthtml.controls.helpers import Ids
|
||||
from myfasthtml.core.instances import BaseInstance, InstancesManager
|
||||
|
||||
logger = logging.getLogger("InstancesHelper")
|
||||
|
||||
|
||||
class InstancesHelper:
|
||||
@staticmethod
|
||||
def dynamic_get(parent: BaseInstance, component_type: str, instance_id: str):
|
||||
logger.debug(f"Dynamic get: {component_type} {instance_id}")
|
||||
if component_type == Ids.VisNetwork:
|
||||
return InstancesManager.get(parent.get_session(), instance_id,
|
||||
VisNetwork, parent=parent, _id=instance_id)
|
||||
elif component_type == Ids.InstancesDebugger:
|
||||
return InstancesManager.get(parent.get_session(), instance_id,
|
||||
InstancesDebugger, parent.get_session(), parent, instance_id)
|
||||
elif component_type == Ids.CommandsDebugger:
|
||||
return InstancesManager.get(parent.get_session(), instance_id,
|
||||
CommandsDebugger, parent.get_session(), parent, instance_id)
|
||||
elif component_type == Ids.FileUpload:
|
||||
return InstancesManager.get(parent.get_session(), instance_id, FileUpload, parent)
|
||||
logger.warning(f"Unknown component type: {component_type}")
|
||||
return None
|
||||
@@ -247,6 +247,21 @@ def pascal_to_snake(name: str) -> str:
|
||||
s2 = re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', s1)
|
||||
return s2.lower()
|
||||
|
||||
|
||||
def snake_to_pascal(name: str) -> str:
|
||||
"""Convert a snake_case string to PascalCase."""
|
||||
if name is None:
|
||||
return None
|
||||
|
||||
name = name.strip()
|
||||
if not name:
|
||||
return ""
|
||||
|
||||
# Split on underscores and capitalize each part
|
||||
parts = name.split('_')
|
||||
return ''.join(word.capitalize() for word in parts if word)
|
||||
|
||||
|
||||
@utils_rt(Routes.Commands)
|
||||
def post(session, c_id: str, client_response: dict = None):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user