diff --git a/src/myfasthtml/assets/datagrid/datagrid.css b/src/myfasthtml/assets/datagrid/datagrid.css
index e755403..96dbaed 100644
--- a/src/myfasthtml/assets/datagrid/datagrid.css
+++ b/src/myfasthtml/assets/datagrid/datagrid.css
@@ -1,4 +1,3 @@
-
/* ********************************************* */
/* ************* Datagrid Component ************ */
/* ********************************************* */
@@ -9,6 +8,11 @@
background-color: var(--color-base-200);
border-radius: 10px 10px 0 0;
min-width: max-content; /* Content width propagates to scrollable parent */
+ height: 24px;
+ display: flex;
+ width: 100%;
+ font-size: var(--text-xl);
+ margin: 4px 0;
}
/* Body */
@@ -131,10 +135,21 @@
}
/* Selection border - outlines the entire selection rectangle */
-.dt2-selection-border-top { border-top: 2px solid var(--color-primary); }
-.dt2-selection-border-bottom { border-bottom: 2px solid var(--color-primary); }
-.dt2-selection-border-left { border-left: 2px solid var(--color-primary); }
-.dt2-selection-border-right { border-right: 2px solid var(--color-primary); }
+.dt2-selection-border-top {
+ border-top: 2px solid var(--color-primary);
+}
+
+.dt2-selection-border-bottom {
+ border-bottom: 2px solid var(--color-primary);
+}
+
+.dt2-selection-border-left {
+ border-left: 2px solid var(--color-primary);
+}
+
+.dt2-selection-border-right {
+ border-right: 2px solid var(--color-primary);
+}
/* *********************************************** */
diff --git a/src/myfasthtml/controls/DataGrid.py b/src/myfasthtml/controls/DataGrid.py
index 8ec725d..a3fa3be 100644
--- a/src/myfasthtml/controls/DataGrid.py
+++ b/src/myfasthtml/controls/DataGrid.py
@@ -667,7 +667,7 @@ class DataGrid(MultipleInstance):
cls="dt2-cell dt2-resizable flex",
)
- header_class = "dt2-row dt2-header"
+ header_class = "dt2-header"
return Div(
*[_mk_header(col_def) for col_def in self._state.columns],
cls=header_class,
@@ -829,7 +829,6 @@ class DataGrid(MultipleInstance):
)
def mk_footers(self):
- return self.mk_headers()
return Div(
*[Div(
*[self.mk_aggregation_cell(col_def, row_index, footer) for col_def in self._state.columns],
diff --git a/src/myfasthtml/controls/DataGridsManager.py b/src/myfasthtml/controls/DataGridsManager.py
index 14c0899..998f439 100644
--- a/src/myfasthtml/controls/DataGridsManager.py
+++ b/src/myfasthtml/controls/DataGridsManager.py
@@ -9,7 +9,7 @@ from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.DataGrid import DataGrid, DatagridConf
from myfasthtml.controls.FileUpload import FileUpload
from myfasthtml.controls.TabsManager import TabsManager
-from myfasthtml.controls.TreeView import TreeView, TreeNode
+from myfasthtml.controls.TreeView import TreeView, TreeNode, TreeViewConf
from myfasthtml.controls.helpers import mk
from myfasthtml.core.DataGridsRegistry import DataGridsRegistry
from myfasthtml.core.commands import Command
@@ -104,9 +104,76 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
file_upload.set_on_ok(self.commands.open_from_excel(tab_id, file_upload))
return self._tabs_manager.show_tab(tab_id)
- def new_grid(self):
- selected_id = self._tree.get_selected_id()
+ def _create_and_register_grid(self, namespace: str, name: str, df: pd.DataFrame) -> DataGrid:
+ """
+ Create and register a DataGrid.
+ Args:
+ namespace: Grid namespace
+ name: Grid name
+ df: DataFrame to initialize the grid with
+
+ Returns:
+ Created DataGrid instance
+ """
+ dg_conf = DatagridConf(namespace=namespace, name=name)
+ dg = DataGrid(self, conf=dg_conf, save_state=True)
+ dg.init_from_dataframe(df)
+ self._registry.put(namespace, name, dg.get_id())
+ return dg
+
+ def _create_document(self, namespace: str, name: str, datagrid: DataGrid, tab_id: str = None) -> tuple[
+ str, DocumentDefinition]:
+ """
+ Create a DocumentDefinition and its associated tab.
+
+ Args:
+ namespace: Document namespace
+ name: Document name
+ datagrid: Associated DataGrid instance
+ tab_id: Optional existing tab ID. If None, creates a new tab
+
+ Returns:
+ Tuple of (tab_id, document)
+ """
+ if tab_id is None:
+ tab_id = self._tabs_manager.create_tab(name, datagrid)
+
+ document = DocumentDefinition(
+ document_id=str(uuid.uuid4()),
+ namespace=namespace,
+ name=name,
+ type="excel",
+ tab_id=tab_id,
+ datagrid_id=datagrid.get_id()
+ )
+ self._state.elements = self._state.elements + [document]
+ return tab_id, document
+
+ def _add_document_to_tree(self, document: DocumentDefinition, parent_id: str) -> TreeNode:
+ """
+ Add a document to the tree view.
+
+ Args:
+ document: Document to add
+ parent_id: Parent node ID in the tree
+
+ Returns:
+ Created TreeNode
+ """
+ tree_node = TreeNode(
+ id=document.document_id,
+ label=document.name,
+ type=document.type,
+ parent=parent_id,
+ bag=document.document_id
+ )
+ self._tree.add_node(tree_node, parent_id=parent_id)
+ return tree_node
+
+ def new_grid(self):
+ # Determine parent folder
+ selected_id = self._tree.get_selected_id()
if selected_id is None:
parent_id = self._tree.ensure_path("Untitled")
else:
@@ -115,43 +182,28 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
parent_id = selected_id
else: # leaf
parent_id = node.parent
-
+
+ # Get namespace and generate unique name
namespace = self._tree._state.items[parent_id].label
name = self._generate_unique_sheet_name(parent_id)
-
- dg_conf = DatagridConf(namespace=namespace, name=name)
- dg = DataGrid(self, conf=dg_conf, save_state=True)
- dg.init_from_dataframe(pd.DataFrame())
- self._registry.put(namespace, name, dg.get_id())
-
- tab_id = self._tabs_manager.create_tab(name, dg)
- document = DocumentDefinition(
- document_id=str(uuid.uuid4()),
- namespace=namespace,
- name=name,
- type="excel",
- tab_id=tab_id,
- datagrid_id=dg.get_id()
- )
- self._state.elements = self._state.elements + [document]
-
- tree_node = TreeNode(
- id=document.document_id,
- label=name,
- type="excel",
- parent=parent_id,
- bag=document.document_id
- )
- self._tree.add_node(tree_node, parent_id=parent_id)
-
+
+ # Create and register DataGrid
+ dg = self._create_and_register_grid(namespace, name, pd.DataFrame())
+
+ # Create document and tab
+ tab_id, document = self._create_document(namespace, name, dg)
+
+ # Add to tree
+ self._add_document_to_tree(document, parent_id)
+
+ # UI-specific handling: open parent, select node, start rename
if parent_id not in self._tree._state.opened:
self._tree._state.opened.append(parent_id)
-
self._tree._state.selected = document.document_id
self._tree._start_rename(document.document_id)
-
+
return self._tree, self._tabs_manager.show_tab(tab_id)
-
+
def _generate_unique_sheet_name(self, parent_id: str) -> str:
children = self._tree._state.items[parent_id].children
existing_labels = {self._tree._state.items[c].label for c in children}
@@ -159,28 +211,24 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
while f"Sheet{n}" in existing_labels:
n += 1
return f"Sheet{n}"
-
+
def open_from_excel(self, tab_id, file_upload: FileUpload):
+ # Read Excel data
excel_content = file_upload.get_content()
df = pd.read_excel(BytesIO(excel_content), file_upload.get_sheet_name())
namespace = file_upload.get_file_basename()
name = file_upload.get_sheet_name()
- dg_conf = DatagridConf(namespace=namespace, name=name)
- dg = DataGrid(self, conf=dg_conf, save_state=True) # first time the Datagrid is created
- dg.init_from_dataframe(df)
- self._registry.put(namespace, name, dg.get_id())
- document = DocumentDefinition(
- document_id=str(uuid.uuid4()),
- namespace=namespace,
- name=name,
- type="excel",
- tab_id=tab_id,
- datagrid_id=dg.get_id()
- )
- self._state.elements = self._state.elements + [document] # do not use append() other it won't be saved
+
+ # Create and register DataGrid
+ dg = self._create_and_register_grid(namespace, name, df)
+
+ # Create document with existing tab
+ tab_id, document = self._create_document(namespace, name, dg, tab_id=tab_id)
+
+ # Add to tree
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)
+ self._add_document_to_tree(document, parent_id)
+
return self._mk_tree(), self._tabs_manager.change_tab_content(tab_id, document.name, dg)
def select_document(self, node_id):
@@ -318,7 +366,8 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
)
def _mk_tree(self):
- tree = TreeView(self, _id="-treeview")
+ conf = TreeViewConf(add_leaf=False, icons={"folder": "database20_regular", "excel": "table20_regular"})
+ tree = TreeView(self, conf=conf, _id="-treeview")
for element in self._state.elements:
parent_id = tree.ensure_path(element.namespace, node_type="folder")
tree.add_node(TreeNode(id=element.document_id,
diff --git a/src/myfasthtml/controls/IconsHelper.py b/src/myfasthtml/controls/IconsHelper.py
index d83e7a2..efa3428 100644
--- a/src/myfasthtml/controls/IconsHelper.py
+++ b/src/myfasthtml/controls/IconsHelper.py
@@ -2,7 +2,7 @@ from myfasthtml.core.constants import ColumnType
from myfasthtml.icons.fluent import question20_regular, brain_circuit20_regular, number_symbol20_regular, \
number_row20_regular
from myfasthtml.icons.fluent_p1 import checkbox_checked20_regular, checkbox_unchecked20_regular, \
- checkbox_checked20_filled, math_formula16_regular
+ checkbox_checked20_filled, math_formula16_regular, folder20_regular
from myfasthtml.icons.fluent_p2 import text_field20_regular, text_bullet_list_square20_regular
from myfasthtml.icons.fluent_p3 import calendar_ltr20_regular
@@ -12,6 +12,7 @@ default_icons = {
False: checkbox_unchecked20_regular,
"Brain": brain_circuit20_regular,
+ "TreeViewFolder" : folder20_regular,
ColumnType.RowIndex: number_symbol20_regular,
ColumnType.Text: text_field20_regular,
diff --git a/src/myfasthtml/controls/TreeView.py b/src/myfasthtml/controls/TreeView.py
index 87bc06c..60677f5 100644
--- a/src/myfasthtml/controls/TreeView.py
+++ b/src/myfasthtml/controls/TreeView.py
@@ -12,6 +12,7 @@ from typing import Optional
from fasthtml.components import Div, Input, Span
from myfasthtml.controls.BaseCommands import BaseCommands
+from myfasthtml.controls.IconsHelper import IconsHelper
from myfasthtml.controls.Keyboard import Keyboard
from myfasthtml.controls.helpers import mk
from myfasthtml.core.commands import Command, CommandTemplate
@@ -190,6 +191,8 @@ class TreeView(MultipleInstance):
if self.conf.icons:
self._state.icon_config = self.conf.icons
+ else:
+ self._state.icon_config = {"folder": "TreeViewFolder"}
def set_icon_config(self, config: dict[str, str]):
"""
@@ -344,7 +347,7 @@ class TreeView(MultipleInstance):
"""Start renaming a node (sets editing state and selection)."""
if node_id not in self._state.items:
raise ValueError(f"Node {node_id} does not exist")
-
+
self._state.selected = node_id
self._state.editing = node_id
return self
@@ -395,7 +398,7 @@ class TreeView(MultipleInstance):
"""Select a node."""
if node_id not in self._state.items:
raise ValueError(f"Node {node_id} does not exist")
-
+
# Cancel edit mode when selecting
self._state.editing = None
self._state.selected = node_id
@@ -446,6 +449,7 @@ class TreeView(MultipleInstance):
toggle = None
# Label or input for editing
+ icon = IconsHelper.get(self._state.icon_config.get(node.type, None))
if is_editing:
label_element = mk.mk(Input(
name="node_label",
@@ -453,18 +457,24 @@ class TreeView(MultipleInstance):
cls="mf-treenode-input input input-sm"
), command=CommandTemplate("TreeView.SaveRename", self.commands.save_rename, args=[node_id]))
else:
- label_element = mk.mk(
+ label_element = mk.label(
Span(node.label, cls="mf-treenode-label text-sm"),
+ icon=icon,
+ enable_button=False,
command=self.commands.select_node(node_id)
)
+ offset = 20
+ if icon is not None:
+ offset += 25
+
# Node element
node_element = Div(
toggle,
label_element,
*([self._render_action_buttons(node_id)] if not is_editing else []),
cls=f"mf-treenode flex {'selected' if is_selected and not is_editing else ''}",
- style=f"padding-left: {level * 20}px"
+ style=f"padding-left: {level * offset}px"
)
# Children (if expanded)
diff --git a/src/myfasthtml/controls/helpers.py b/src/myfasthtml/controls/helpers.py
index 420e170..e1bc59c 100644
--- a/src/myfasthtml/controls/helpers.py
+++ b/src/myfasthtml/controls/helpers.py
@@ -95,10 +95,14 @@ class mk:
icon=None,
size: str = "sm",
cls='',
+ enable_button=True,
command: Command | CommandTemplate = None,
binding: Binding = None,
**kwargs):
- merged_cls = merge_classes("flex truncate items-center pr-2", "mf-button" if command else None, cls, kwargs)
+ merged_cls = merge_classes("flex truncate items-center pr-2",
+ "mf-button" if command and enable_button else None,
+ cls,
+ kwargs)
icon_part = Span(icon, cls=f"mf-icon-{mk.convert_size(size)} mr-1") if icon else None
text_part = Span(text, cls=f"text-{size} truncate")
return mk.mk(Label(icon_part, text_part, cls=merged_cls, **kwargs), command=command, binding=binding)