Improved command management to reduce the number of instances
This commit is contained in:
@@ -57,7 +57,7 @@ class DataGrid(MultipleInstance):
|
||||
|
||||
def render(self):
|
||||
return Div(
|
||||
NotStr(self._state.html),
|
||||
NotStr(self._state.html) if self._state.html else "Content lost !",
|
||||
id=self._id
|
||||
)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import uuid
|
||||
from dataclasses import dataclass
|
||||
|
||||
import pandas as pd
|
||||
@@ -10,7 +11,7 @@ from myfasthtml.controls.Panel import Panel
|
||||
from myfasthtml.controls.TabsManager import TabsManager
|
||||
from myfasthtml.controls.TreeView import TreeView, TreeNode
|
||||
from myfasthtml.controls.helpers import mk
|
||||
from myfasthtml.core.commands import Command, LambdaCommand
|
||||
from myfasthtml.core.commands import Command
|
||||
from myfasthtml.core.dbmanager import DbObject
|
||||
from myfasthtml.core.instances import MultipleInstance, InstancesManager
|
||||
from myfasthtml.icons.fluent_p1 import table_add20_regular
|
||||
@@ -19,6 +20,7 @@ from myfasthtml.icons.fluent_p3 import folder_open20_regular
|
||||
|
||||
@dataclass
|
||||
class DocumentDefinition:
|
||||
document_id: str
|
||||
namespace: str
|
||||
name: str
|
||||
type: str
|
||||
@@ -30,7 +32,7 @@ class DataGridsState(DbObject):
|
||||
def __init__(self, owner, name=None):
|
||||
super().__init__(owner, name=name)
|
||||
with self.initializing():
|
||||
self.elements: list = []
|
||||
self.elements: list = [DocumentDefinition]
|
||||
|
||||
|
||||
class Commands(BaseCommands):
|
||||
@@ -61,7 +63,11 @@ class Commands(BaseCommands):
|
||||
self._owner.clear_tree).htmx(target=f"#{self._owner._tree.get_id()}")
|
||||
|
||||
def show_document(self):
|
||||
return LambdaCommand(self._owner, lambda: print("show_document"))
|
||||
return Command("ShowDocument",
|
||||
"Show document",
|
||||
self._owner,
|
||||
self._owner.select_document,
|
||||
key="SelectNode")
|
||||
|
||||
|
||||
class DataGridsManager(MultipleInstance):
|
||||
@@ -89,18 +95,29 @@ class DataGridsManager(MultipleInstance):
|
||||
dg = DataGrid(self._tabs_manager)
|
||||
dg.set_html(html)
|
||||
document = DocumentDefinition(
|
||||
document_id=str(uuid.uuid4()),
|
||||
namespace=file_upload.get_file_basename(),
|
||||
name=file_upload.get_sheet_name(),
|
||||
type="excel",
|
||||
tab_id=tab_id,
|
||||
datagrid_id=dg.get_id()
|
||||
)
|
||||
self._state.elements = self._state.elements + [document]
|
||||
self._state.elements = self._state.elements + [document] # do not use append() other it won't be saved
|
||||
parent_id = self._tree.ensure_path(document.namespace)
|
||||
tree_node = TreeNode(label=document.name, type="excel", parent=parent_id)
|
||||
self._tree.add_node(tree_node, parent_id=parent_id)
|
||||
return self._mk_tree(), self._tabs_manager.change_tab_content(tab_id, document.name, Panel(self).set_main(dg))
|
||||
|
||||
def select_document(self, node_id):
|
||||
document_id = self._tree.get_bag(node_id)
|
||||
try:
|
||||
document = next(filter(lambda x: x.document_id == document_id, self._state.elements))
|
||||
dg = DataGrid(self._tabs_manager, _id=document.datagrid_id)
|
||||
return self._tabs_manager.show_or_create_tab(document.tab_id, document.name, dg)
|
||||
except StopIteration:
|
||||
# the selected node is not a document (it's a folder)
|
||||
return None
|
||||
|
||||
def clear_tree(self):
|
||||
self._state.elements = []
|
||||
self._tree.clear()
|
||||
@@ -117,7 +134,11 @@ class DataGridsManager(MultipleInstance):
|
||||
tree = TreeView(self, _id="-treeview")
|
||||
for element in self._state.elements:
|
||||
parent_id = tree.ensure_path(element.namespace)
|
||||
tree.add_node(TreeNode(label=element.name, type=element.type, parent=parent_id, id=element.datagrid_id))
|
||||
tree.add_node(TreeNode(id=element.document_id,
|
||||
label=element.name,
|
||||
type=element.type,
|
||||
parent=parent_id,
|
||||
bag=element.document_id))
|
||||
return tree
|
||||
|
||||
def render(self):
|
||||
|
||||
@@ -40,7 +40,7 @@ class PanelIds:
|
||||
|
||||
@dataclass
|
||||
class PanelConf:
|
||||
left: bool = True
|
||||
left: bool = False
|
||||
right: bool = True
|
||||
|
||||
|
||||
|
||||
@@ -58,29 +58,34 @@ class TabsManagerState(DbObject):
|
||||
|
||||
class Commands(BaseCommands):
|
||||
def show_tab(self, tab_id):
|
||||
return Command(f"{self._prefix}ShowTab",
|
||||
return Command(f"ShowTab",
|
||||
"Activate or show a specific tab",
|
||||
self._owner,
|
||||
self._owner.show_tab,
|
||||
args=[tab_id,
|
||||
True,
|
||||
False]).htmx(target=f"#{self._id}-controller", swap="outerHTML")
|
||||
False],
|
||||
key=f"{self._owner.get_full_id()}-ShowTab-{tab_id}",
|
||||
).htmx(target=f"#{self._id}-controller", swap="outerHTML")
|
||||
|
||||
def close_tab(self, tab_id):
|
||||
return Command(f"{self._prefix}CloseTab",
|
||||
return Command(f"CloseTab",
|
||||
"Close a specific tab",
|
||||
self._owner,
|
||||
self._owner.close_tab,
|
||||
args=[tab_id]).htmx(target=f"#{self._id}-controller", swap="outerHTML")
|
||||
kwargs={"tab_id": tab_id},
|
||||
).htmx(target=f"#{self._id}-controller", swap="outerHTML")
|
||||
|
||||
def add_tab(self, label: str, component: Any, auto_increment=False):
|
||||
return Command(f"{self._prefix}AddTab",
|
||||
return Command(f"AddTab",
|
||||
"Add a new tab",
|
||||
self._owner,
|
||||
self._owner.on_new_tab,
|
||||
args=[label,
|
||||
component,
|
||||
auto_increment]).htmx(target=f"#{self._id}-controller", swap="outerHTML")
|
||||
auto_increment],
|
||||
key="#{id-name-args}",
|
||||
).htmx(target=f"#{self._id}-controller", swap="outerHTML")
|
||||
|
||||
|
||||
class TabsManager(MultipleInstance):
|
||||
@@ -148,6 +153,13 @@ class TabsManager(MultipleInstance):
|
||||
tab_id = self.create_tab(label, component)
|
||||
return self.show_tab(tab_id, oob=False)
|
||||
|
||||
def show_or_create_tab(self, tab_id, label, component, activate=True):
|
||||
logger.debug(f"show_or_create_tab {tab_id=}, {label=}, {component=}, {activate=}")
|
||||
if tab_id not in self._state.tabs:
|
||||
self._add_or_update_tab(tab_id, label, component, activate)
|
||||
|
||||
return self.show_tab(tab_id, activate=activate, oob=True)
|
||||
|
||||
def create_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.
|
||||
|
||||
@@ -267,6 +267,12 @@ class TreeView(MultipleInstance):
|
||||
self._state.update(state)
|
||||
return self
|
||||
|
||||
def get_bag(self, node_id: str):
|
||||
try:
|
||||
return self._state.items[node_id].bag
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def _toggle_node(self, node_id: str):
|
||||
"""Toggle expand/collapse state of a node."""
|
||||
if node_id in self._state.opened:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
@@ -8,6 +9,8 @@ from myutils.observable import NotObservableError, ObservableResultCollector
|
||||
from myfasthtml.core.constants import Routes, ROUTE_ROOT
|
||||
from myfasthtml.core.utils import flatten
|
||||
|
||||
logger = logging.getLogger("Commands")
|
||||
|
||||
|
||||
class Command:
|
||||
"""
|
||||
@@ -26,6 +29,33 @@ class Command:
|
||||
:type description: str
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def process_key(key, name, owner, args, kwargs):
|
||||
def _compute_from_args():
|
||||
res = []
|
||||
for arg in args:
|
||||
if hasattr(arg, "get_full_id"):
|
||||
res.append(arg.get_full_id())
|
||||
else:
|
||||
res.append(str(arg))
|
||||
return "-".join(res)
|
||||
|
||||
# special management when kwargs are provided
|
||||
# In this situation,
|
||||
# either there is no parameter (so one single instance of the command is enough)
|
||||
# or the parameter is a kwargs (so the parameters are provided when the command is called)
|
||||
if (key is None
|
||||
and owner is not None
|
||||
and args is None # args is not provided
|
||||
):
|
||||
key = f"{owner.get_full_id()}-{name}"
|
||||
|
||||
key = key.replace("#{args}", _compute_from_args())
|
||||
key = key.replace("#{id}", owner.get_full_id())
|
||||
key = key.replace("#{id-name-args}", f"{owner.get_full_id()}-{name}-{_compute_from_args()}")
|
||||
|
||||
return key
|
||||
|
||||
def __init__(self, name,
|
||||
description,
|
||||
owner=None,
|
||||
@@ -47,10 +77,20 @@ class Command:
|
||||
self._callback_parameters = dict(inspect.signature(callback).parameters) if callback else {}
|
||||
self._key = key
|
||||
|
||||
# special management when kwargs are provided
|
||||
# In this situation,
|
||||
# either there is no parameter (so one single instance of the command is enough)
|
||||
# or the parameter is a kwargs (so the parameters are provided when the command is called)
|
||||
if (self._key is None
|
||||
and self.owner is not None
|
||||
and args is None # args is not provided
|
||||
):
|
||||
self._key = f"{owner.get_full_id()}-{name}"
|
||||
|
||||
# register the command
|
||||
if auto_register:
|
||||
if key in CommandsManager.commands_by_key:
|
||||
self.id = CommandsManager.commands_by_key[key].id
|
||||
if self._key in CommandsManager.commands_by_key:
|
||||
self.id = CommandsManager.commands_by_key[self._key].id
|
||||
else:
|
||||
CommandsManager.register(self)
|
||||
|
||||
@@ -78,6 +118,7 @@ class Command:
|
||||
return res
|
||||
|
||||
def execute(self, client_response: dict = None):
|
||||
logger.debug(f"Executing command {self.name}")
|
||||
with ObservableResultCollector(self._bindings) as collector:
|
||||
kwargs = self._create_kwargs(self.default_kwargs,
|
||||
client_response,
|
||||
@@ -87,6 +128,7 @@ class Command:
|
||||
ret_from_bound_commands = []
|
||||
if self.owner:
|
||||
for command in self.owner.get_bound_commands(self.name):
|
||||
logger.debug(f" will execute bound command {command.name}...")
|
||||
r = command.execute(client_response)
|
||||
ret_from_bound_commands.append(r) # it will be flatten if needed later
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from myfasthtml.core.constants import Routes, ROUTE_ROOT
|
||||
from myfasthtml.test.MyFT import MyFT
|
||||
|
||||
utils_app, utils_rt = fast_app()
|
||||
logger = logging.getLogger("Commands")
|
||||
logger = logging.getLogger("Routing")
|
||||
|
||||
|
||||
def mount_if_not_exists(app, path: str, sub_app):
|
||||
|
||||
@@ -24,7 +24,7 @@ class TestPanelBehaviour:
|
||||
panel = Panel(root_instance)
|
||||
|
||||
assert panel is not None
|
||||
assert panel.conf.left is True
|
||||
assert panel.conf.left is False
|
||||
assert panel.conf.right is True
|
||||
|
||||
def test_i_can_create_panel_with_custom_config(self, root_instance):
|
||||
@@ -157,7 +157,7 @@ class TestPanelBehaviour:
|
||||
"""Test that update_side_width() returns a panel element."""
|
||||
panel = Panel(root_instance)
|
||||
|
||||
result = panel.update_side_width("left", 300)
|
||||
result = panel.update_side_width("right", 300)
|
||||
|
||||
assert result is not None
|
||||
|
||||
@@ -196,7 +196,7 @@ class TestPanelRender:
|
||||
|
||||
@pytest.fixture
|
||||
def panel(self, root_instance):
|
||||
panel = Panel(root_instance)
|
||||
panel = Panel(root_instance, PanelConf(True, True))
|
||||
panel.set_main(Div("Main content"))
|
||||
panel.set_left(Div("Left content"))
|
||||
panel.set_right(Div("Right content"))
|
||||
|
||||
Reference in New Issue
Block a user