Refactored DataGridsManager.py for better reading
This commit is contained in:
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
/* ********************************************* */
|
/* ********************************************* */
|
||||||
/* ************* Datagrid Component ************ */
|
/* ************* Datagrid Component ************ */
|
||||||
/* ********************************************* */
|
/* ********************************************* */
|
||||||
@@ -9,6 +8,11 @@
|
|||||||
background-color: var(--color-base-200);
|
background-color: var(--color-base-200);
|
||||||
border-radius: 10px 10px 0 0;
|
border-radius: 10px 10px 0 0;
|
||||||
min-width: max-content; /* Content width propagates to scrollable parent */
|
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 */
|
/* Body */
|
||||||
@@ -131,10 +135,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Selection border - outlines the entire selection rectangle */
|
/* Selection border - outlines the entire selection rectangle */
|
||||||
.dt2-selection-border-top { border-top: 2px solid var(--color-primary); }
|
.dt2-selection-border-top {
|
||||||
.dt2-selection-border-bottom { border-bottom: 2px solid var(--color-primary); }
|
border-top: 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-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",
|
cls="dt2-cell dt2-resizable flex",
|
||||||
)
|
)
|
||||||
|
|
||||||
header_class = "dt2-row dt2-header"
|
header_class = "dt2-header"
|
||||||
return Div(
|
return Div(
|
||||||
*[_mk_header(col_def) for col_def in self._state.columns],
|
*[_mk_header(col_def) for col_def in self._state.columns],
|
||||||
cls=header_class,
|
cls=header_class,
|
||||||
@@ -829,7 +829,6 @@ class DataGrid(MultipleInstance):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def mk_footers(self):
|
def mk_footers(self):
|
||||||
return self.mk_headers()
|
|
||||||
return Div(
|
return Div(
|
||||||
*[Div(
|
*[Div(
|
||||||
*[self.mk_aggregation_cell(col_def, row_index, footer) for col_def in self._state.columns],
|
*[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.DataGrid import DataGrid, DatagridConf
|
||||||
from myfasthtml.controls.FileUpload import FileUpload
|
from myfasthtml.controls.FileUpload import FileUpload
|
||||||
from myfasthtml.controls.TabsManager import TabsManager
|
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.controls.helpers import mk
|
||||||
from myfasthtml.core.DataGridsRegistry import DataGridsRegistry
|
from myfasthtml.core.DataGridsRegistry import DataGridsRegistry
|
||||||
from myfasthtml.core.commands import Command
|
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))
|
file_upload.set_on_ok(self.commands.open_from_excel(tab_id, file_upload))
|
||||||
return self._tabs_manager.show_tab(tab_id)
|
return self._tabs_manager.show_tab(tab_id)
|
||||||
|
|
||||||
def new_grid(self):
|
def _create_and_register_grid(self, namespace: str, name: str, df: pd.DataFrame) -> DataGrid:
|
||||||
selected_id = self._tree.get_selected_id()
|
"""
|
||||||
|
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:
|
if selected_id is None:
|
||||||
parent_id = self._tree.ensure_path("Untitled")
|
parent_id = self._tree.ensure_path("Untitled")
|
||||||
else:
|
else:
|
||||||
@@ -116,37 +183,22 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
|
|||||||
else: # leaf
|
else: # leaf
|
||||||
parent_id = node.parent
|
parent_id = node.parent
|
||||||
|
|
||||||
|
# Get namespace and generate unique name
|
||||||
namespace = self._tree._state.items[parent_id].label
|
namespace = self._tree._state.items[parent_id].label
|
||||||
name = self._generate_unique_sheet_name(parent_id)
|
name = self._generate_unique_sheet_name(parent_id)
|
||||||
|
|
||||||
dg_conf = DatagridConf(namespace=namespace, name=name)
|
# Create and register DataGrid
|
||||||
dg = DataGrid(self, conf=dg_conf, save_state=True)
|
dg = self._create_and_register_grid(namespace, name, pd.DataFrame())
|
||||||
dg.init_from_dataframe(pd.DataFrame())
|
|
||||||
self._registry.put(namespace, name, dg.get_id())
|
|
||||||
|
|
||||||
tab_id = self._tabs_manager.create_tab(name, dg)
|
# Create document and tab
|
||||||
document = DocumentDefinition(
|
tab_id, document = self._create_document(namespace, name, dg)
|
||||||
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(
|
# Add to tree
|
||||||
id=document.document_id,
|
self._add_document_to_tree(document, parent_id)
|
||||||
label=name,
|
|
||||||
type="excel",
|
|
||||||
parent=parent_id,
|
|
||||||
bag=document.document_id
|
|
||||||
)
|
|
||||||
self._tree.add_node(tree_node, parent_id=parent_id)
|
|
||||||
|
|
||||||
|
# UI-specific handling: open parent, select node, start rename
|
||||||
if parent_id not in self._tree._state.opened:
|
if parent_id not in self._tree._state.opened:
|
||||||
self._tree._state.opened.append(parent_id)
|
self._tree._state.opened.append(parent_id)
|
||||||
|
|
||||||
self._tree._state.selected = document.document_id
|
self._tree._state.selected = document.document_id
|
||||||
self._tree._start_rename(document.document_id)
|
self._tree._start_rename(document.document_id)
|
||||||
|
|
||||||
@@ -161,26 +213,22 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
|
|||||||
return f"Sheet{n}"
|
return f"Sheet{n}"
|
||||||
|
|
||||||
def open_from_excel(self, tab_id, file_upload: FileUpload):
|
def open_from_excel(self, tab_id, file_upload: FileUpload):
|
||||||
|
# Read Excel data
|
||||||
excel_content = file_upload.get_content()
|
excel_content = file_upload.get_content()
|
||||||
df = pd.read_excel(BytesIO(excel_content), file_upload.get_sheet_name())
|
df = pd.read_excel(BytesIO(excel_content), file_upload.get_sheet_name())
|
||||||
namespace = file_upload.get_file_basename()
|
namespace = file_upload.get_file_basename()
|
||||||
name = file_upload.get_sheet_name()
|
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
|
# Create and register DataGrid
|
||||||
dg.init_from_dataframe(df)
|
dg = self._create_and_register_grid(namespace, name, df)
|
||||||
self._registry.put(namespace, name, dg.get_id())
|
|
||||||
document = DocumentDefinition(
|
# Create document with existing tab
|
||||||
document_id=str(uuid.uuid4()),
|
tab_id, document = self._create_document(namespace, name, dg, tab_id=tab_id)
|
||||||
namespace=namespace,
|
|
||||||
name=name,
|
# Add to tree
|
||||||
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
|
|
||||||
parent_id = self._tree.ensure_path(document.namespace)
|
parent_id = self._tree.ensure_path(document.namespace)
|
||||||
tree_node = TreeNode(label=document.name, type="excel", parent=parent_id)
|
self._add_document_to_tree(document, 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, dg)
|
return self._mk_tree(), self._tabs_manager.change_tab_content(tab_id, document.name, dg)
|
||||||
|
|
||||||
def select_document(self, node_id):
|
def select_document(self, node_id):
|
||||||
@@ -318,7 +366,8 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _mk_tree(self):
|
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:
|
for element in self._state.elements:
|
||||||
parent_id = tree.ensure_path(element.namespace, node_type="folder")
|
parent_id = tree.ensure_path(element.namespace, node_type="folder")
|
||||||
tree.add_node(TreeNode(id=element.document_id,
|
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, \
|
from myfasthtml.icons.fluent import question20_regular, brain_circuit20_regular, number_symbol20_regular, \
|
||||||
number_row20_regular
|
number_row20_regular
|
||||||
from myfasthtml.icons.fluent_p1 import checkbox_checked20_regular, checkbox_unchecked20_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_p2 import text_field20_regular, text_bullet_list_square20_regular
|
||||||
from myfasthtml.icons.fluent_p3 import calendar_ltr20_regular
|
from myfasthtml.icons.fluent_p3 import calendar_ltr20_regular
|
||||||
|
|
||||||
@@ -12,6 +12,7 @@ default_icons = {
|
|||||||
False: checkbox_unchecked20_regular,
|
False: checkbox_unchecked20_regular,
|
||||||
|
|
||||||
"Brain": brain_circuit20_regular,
|
"Brain": brain_circuit20_regular,
|
||||||
|
"TreeViewFolder" : folder20_regular,
|
||||||
|
|
||||||
ColumnType.RowIndex: number_symbol20_regular,
|
ColumnType.RowIndex: number_symbol20_regular,
|
||||||
ColumnType.Text: text_field20_regular,
|
ColumnType.Text: text_field20_regular,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from typing import Optional
|
|||||||
from fasthtml.components import Div, Input, Span
|
from fasthtml.components import Div, Input, Span
|
||||||
|
|
||||||
from myfasthtml.controls.BaseCommands import BaseCommands
|
from myfasthtml.controls.BaseCommands import BaseCommands
|
||||||
|
from myfasthtml.controls.IconsHelper import IconsHelper
|
||||||
from myfasthtml.controls.Keyboard import Keyboard
|
from myfasthtml.controls.Keyboard import Keyboard
|
||||||
from myfasthtml.controls.helpers import mk
|
from myfasthtml.controls.helpers import mk
|
||||||
from myfasthtml.core.commands import Command, CommandTemplate
|
from myfasthtml.core.commands import Command, CommandTemplate
|
||||||
@@ -190,6 +191,8 @@ class TreeView(MultipleInstance):
|
|||||||
|
|
||||||
if self.conf.icons:
|
if self.conf.icons:
|
||||||
self._state.icon_config = 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]):
|
def set_icon_config(self, config: dict[str, str]):
|
||||||
"""
|
"""
|
||||||
@@ -446,6 +449,7 @@ class TreeView(MultipleInstance):
|
|||||||
toggle = None
|
toggle = None
|
||||||
|
|
||||||
# Label or input for editing
|
# Label or input for editing
|
||||||
|
icon = IconsHelper.get(self._state.icon_config.get(node.type, None))
|
||||||
if is_editing:
|
if is_editing:
|
||||||
label_element = mk.mk(Input(
|
label_element = mk.mk(Input(
|
||||||
name="node_label",
|
name="node_label",
|
||||||
@@ -453,18 +457,24 @@ class TreeView(MultipleInstance):
|
|||||||
cls="mf-treenode-input input input-sm"
|
cls="mf-treenode-input input input-sm"
|
||||||
), command=CommandTemplate("TreeView.SaveRename", self.commands.save_rename, args=[node_id]))
|
), command=CommandTemplate("TreeView.SaveRename", self.commands.save_rename, args=[node_id]))
|
||||||
else:
|
else:
|
||||||
label_element = mk.mk(
|
label_element = mk.label(
|
||||||
Span(node.label, cls="mf-treenode-label text-sm"),
|
Span(node.label, cls="mf-treenode-label text-sm"),
|
||||||
|
icon=icon,
|
||||||
|
enable_button=False,
|
||||||
command=self.commands.select_node(node_id)
|
command=self.commands.select_node(node_id)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
offset = 20
|
||||||
|
if icon is not None:
|
||||||
|
offset += 25
|
||||||
|
|
||||||
# Node element
|
# Node element
|
||||||
node_element = Div(
|
node_element = Div(
|
||||||
toggle,
|
toggle,
|
||||||
label_element,
|
label_element,
|
||||||
*([self._render_action_buttons(node_id)] if not is_editing else []),
|
*([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 ''}",
|
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)
|
# Children (if expanded)
|
||||||
|
|||||||
@@ -95,10 +95,14 @@ class mk:
|
|||||||
icon=None,
|
icon=None,
|
||||||
size: str = "sm",
|
size: str = "sm",
|
||||||
cls='',
|
cls='',
|
||||||
|
enable_button=True,
|
||||||
command: Command | CommandTemplate = None,
|
command: Command | CommandTemplate = None,
|
||||||
binding: Binding = None,
|
binding: Binding = None,
|
||||||
**kwargs):
|
**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
|
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")
|
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)
|
return mk.mk(Label(icon_part, text_part, cls=merged_cls, **kwargs), command=command, binding=binding)
|
||||||
|
|||||||
Reference in New Issue
Block a user