feat: implement new grid creation with inline rename in DataGridsManager

- Add new_grid() method to create empty DataGrid under selected folder/leaf parent
  - Generate unique sheet names (Sheet1, Sheet2, ...) with _generate_unique_sheet_name()
  - Auto-select and open new node in edit mode for immediate renaming
  - Fix TreeView to cancel edit mode when selecting any node
  - Wire "New grid" icon to new_grid() instead of clear_tree()
  - Add 14 unit tests covering new_grid() scenarios and TreeView behavior
This commit is contained in:
2026-02-20 21:50:47 +01:00
parent 8f3b2e795e
commit 40a90c7ff5
4 changed files with 368 additions and 10 deletions

View File

@@ -50,7 +50,7 @@ class Commands(BaseCommands):
return Command("NewGrid",
"New grid",
self._owner,
self._owner.new_grid)
self._owner.new_grid).htmx(target=f"#{self._owner._tree.get_id()}")
def open_from_excel(self, tab_id, file_upload):
return Command("OpenFromExcel",
@@ -104,6 +104,62 @@ 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()
if selected_id is None:
parent_id = self._tree.ensure_path("Untitled")
else:
node = self._tree._state.items[selected_id]
if node.type == "folder":
parent_id = selected_id
else: # leaf
parent_id = node.parent
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)
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}
n = 1
while f"Sheet{n}" in existing_labels:
n += 1
return f"Sheet{n}"
def open_from_excel(self, tab_id, file_upload: FileUpload):
excel_content = file_upload.get_content()
df = pd.read_excel(BytesIO(excel_content), file_upload.get_sheet_name())
@@ -257,7 +313,7 @@ class DataGridsManager(SingleInstance, DatagridMetadataProvider):
def mk_main_icons(self):
return Div(
mk.icon(folder_open20_regular, tooltip="Upload from source", command=self.commands.upload_from_source()),
mk.icon(table_add20_regular, tooltip="New grid", command=self.commands.clear_tree()),
mk.icon(table_add20_regular, tooltip="New grid", command=self.commands.new_grid()),
cls="flex"
)

View File

@@ -184,10 +184,10 @@ class TreeView(MultipleInstance):
self._state = TreeViewState(self)
self.conf = conf or TreeViewConf()
self.commands = Commands(self)
if items:
self._state.items = items
if self.conf.icons:
self._state.icon_config = self.conf.icons
@@ -350,6 +350,7 @@ class TreeView(MultipleInstance):
def _save_rename(self, node_id: str, node_label: str):
"""Save renamed node with new label."""
logger.debug(f"_save_rename {node_id=}, {node_label=}")
if node_id not in self._state.items:
raise ValueError(f"Node {node_id} does not exist")
@@ -393,7 +394,9 @@ 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
return self
@@ -401,11 +404,11 @@ class TreeView(MultipleInstance):
"""Render action buttons for a node (visible on hover)."""
is_leaf = len(self._state.items[node_id].children) == 0
conf = self.conf
add_visible = conf.add_leaf if is_leaf else conf.add_node
edit_visible = conf.edit_leaf if is_leaf else conf.edit_node
add_visible = conf.add_leaf if is_leaf else conf.add_node
edit_visible = conf.edit_leaf if is_leaf else conf.edit_node
delete_visible = conf.delete_leaf if is_leaf else conf.delete_node
buttons = []
if add_visible:
buttons.append(mk.icon(add_circle20_regular, command=self.commands.add_child(node_id)))
@@ -413,7 +416,7 @@ class TreeView(MultipleInstance):
buttons.append(mk.icon(edit20_regular, command=self.commands.start_rename(node_id)))
if delete_visible:
buttons.append(mk.icon(delete20_regular, command=self.commands.delete_node(node_id)))
return Div(*buttons, cls="mf-treenode-actions")
def _render_node(self, node_id: str, level: int = 0):