Refactored DataGridsManager.py for better reading
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
/* *********************************************** */
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user