Refactored DataGridsManager.py for better reading

This commit is contained in:
2026-02-20 23:32:11 +01:00
parent c49f28da26
commit 730f55d65b
6 changed files with 141 additions and 63 deletions

View File

@@ -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);
}
/* *********************************************** */

View File

@@ -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],

View File

@@ -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:
@@ -116,37 +183,22 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
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())
# Create and register DataGrid
dg = self._create_and_register_grid(namespace, name, pd.DataFrame())
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]
# Create document and tab
tab_id, document = self._create_document(namespace, name, dg)
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)
# 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)
@@ -161,26 +213,22 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
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,

View File

@@ -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,

View File

@@ -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]):
"""
@@ -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)

View File

@@ -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)