diff --git a/src/myfasthtml/controls/DataGrid.py b/src/myfasthtml/controls/DataGrid.py
index 53aba86..478ab04 100644
--- a/src/myfasthtml/controls/DataGrid.py
+++ b/src/myfasthtml/controls/DataGrid.py
@@ -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
)
diff --git a/src/myfasthtml/controls/DataGridsManager.py b/src/myfasthtml/controls/DataGridsManager.py
index bdb9ef1..29ce8c5 100644
--- a/src/myfasthtml/controls/DataGridsManager.py
+++ b/src/myfasthtml/controls/DataGridsManager.py
@@ -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):
diff --git a/src/myfasthtml/controls/Panel.py b/src/myfasthtml/controls/Panel.py
index d7242b6..d27e46d 100644
--- a/src/myfasthtml/controls/Panel.py
+++ b/src/myfasthtml/controls/Panel.py
@@ -40,7 +40,7 @@ class PanelIds:
@dataclass
class PanelConf:
- left: bool = True
+ left: bool = False
right: bool = True
diff --git a/src/myfasthtml/controls/TabsManager.py b/src/myfasthtml/controls/TabsManager.py
index 9935fff..2eb82a9 100644
--- a/src/myfasthtml/controls/TabsManager.py
+++ b/src/myfasthtml/controls/TabsManager.py
@@ -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.
diff --git a/src/myfasthtml/controls/TreeView.py b/src/myfasthtml/controls/TreeView.py
index b5dfea3..0a7aeda 100644
--- a/src/myfasthtml/controls/TreeView.py
+++ b/src/myfasthtml/controls/TreeView.py
@@ -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:
diff --git a/src/myfasthtml/core/commands.py b/src/myfasthtml/core/commands.py
index c24eab5..ff07149 100644
--- a/src/myfasthtml/core/commands.py
+++ b/src/myfasthtml/core/commands.py
@@ -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
diff --git a/src/myfasthtml/core/utils.py b/src/myfasthtml/core/utils.py
index addfea4..64bb40c 100644
--- a/src/myfasthtml/core/utils.py
+++ b/src/myfasthtml/core/utils.py
@@ -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):
diff --git a/tests/controls/test_panel.py b/tests/controls/test_panel.py
index 4c95872..44b29ce 100644
--- a/tests/controls/test_panel.py
+++ b/tests/controls/test_panel.py
@@ -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"))