diff --git a/webui/backend/data/tasks.db b/webui/backend/data/tasks.db index 2924554..1ffe4b1 100644 Binary files a/webui/backend/data/tasks.db and b/webui/backend/data/tasks.db differ diff --git a/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc b/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc index 0584c95..75bb586 100644 Binary files a/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc and b/webui/backend/tests/golden/__pycache__/test_ui_smoke_golden.cpython-313.pyc differ diff --git a/webui/backend/tests/golden/test_ui_smoke_golden.py b/webui/backend/tests/golden/test_ui_smoke_golden.py index 2340646..f31a6db 100644 --- a/webui/backend/tests/golden/test_ui_smoke_golden.py +++ b/webui/backend/tests/golden/test_ui_smoke_golden.py @@ -71,7 +71,8 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('id="context-menu"', body) self.assertIn('id="context-menu-scope"', body) self.assertIn('id="context-menu-target"', body) - self.assertIn('id="context-menu-open-placeholder"', body) + self.assertIn('id="context-menu-rename-btn"', body) + self.assertIn('id="context-menu-delete-btn"', body) self.assertIn('id="settings-btn"', body) self.assertIn('id="rename-btn"', body) self.assertIn('id="view-btn"', body) @@ -207,12 +208,18 @@ class UiSmokeGoldenTest(unittest.TestCase): self.assertIn('function contextMenuElements()', app_js) self.assertIn('function openContextMenu(pane, entry, event)', app_js) self.assertIn('function closeContextMenu()', app_js) + self.assertIn('function applyContextMenuSelection()', app_js) + self.assertIn('function startContextMenuRename()', app_js) + self.assertIn('function startContextMenuDelete()', app_js) self.assertIn('selectedPathsSet.has(entry.path)', app_js) self.assertIn('entry.isParent', app_js) self.assertIn('row.oncontextmenu = (event) => {', app_js) self.assertIn('event.target.closest("li[data-row-index]")', app_js) self.assertIn('if (!row) {', app_js) self.assertIn('closeContextMenu();', app_js) + self.assertIn('elements.renameButton.classList.toggle("hidden", isMulti);', app_js) + self.assertIn('openRenamePopup();', app_js) + self.assertIn('deleteSelected();', app_js) self.assertIn('document.getElementById("upload-menu-toggle").onclick = (event) => {', app_js) self.assertIn('document.getElementById("upload-folder-btn").onclick = openFolderPicker;', app_js) self.assertIn('throw createApiError(response, data);', app_js) diff --git a/webui/html/app.js b/webui/html/app.js index a8f5fd4..eab4c0a 100644 --- a/webui/html/app.js +++ b/webui/html/app.js @@ -326,7 +326,8 @@ function contextMenuElements() { menu: document.getElementById("context-menu"), scope: document.getElementById("context-menu-scope"), target: document.getElementById("context-menu-target"), - placeholder: document.getElementById("context-menu-open-placeholder"), + renameButton: document.getElementById("context-menu-rename-btn"), + deleteButton: document.getElementById("context-menu-delete-btn"), }; } @@ -367,6 +368,8 @@ function openContextMenu(pane, entry, event) { const isMulti = items.length > 1; elements.scope.textContent = isMulti ? "Multi-selection" : "Single item"; elements.target.textContent = isMulti ? `${items.length} selected items` : entry.name; + elements.renameButton.classList.toggle("hidden", isMulti); + elements.deleteButton.classList.remove("hidden"); const menuWidth = 220; const menuHeight = 120; @@ -377,6 +380,45 @@ function openContextMenu(pane, entry, event) { elements.menu.classList.remove("hidden"); } +function applyContextMenuSelection() { + if (!contextMenuState.items.length) { + return false; + } + const pane = contextMenuState.pane; + const model = paneState(pane); + setActivePane(pane); + model.selectedItems = contextMenuState.items.map((item) => ({ ...item })); + model.selectedItem = model.selectedItems.length > 0 ? model.selectedItems[model.selectedItems.length - 1] : null; + const anchorPath = contextMenuState.anchorPath; + if (anchorPath) { + const currentIndex = model.visibleItems.findIndex((item) => !item.isParent && item.path === anchorPath); + if (currentIndex >= 0) { + model.currentRowIndex = currentIndex; + setSelectionAnchor(pane, currentIndex); + } + } + renderPaneItems(pane); + return true; +} + +function startContextMenuRename() { + if (!applyContextMenuSelection()) { + closeContextMenu(); + return; + } + closeContextMenu(); + openRenamePopup(); +} + +function startContextMenuDelete() { + if (!applyContextMenuSelection()) { + closeContextMenu(); + return; + } + closeContextMenu(); + deleteSelected(); +} + function settingsElements() { return { overlay: document.getElementById("settings-modal"), @@ -3694,6 +3736,13 @@ function setupEvents() { } }; } + const contextMenu = contextMenuElements(); + if (contextMenu.renameButton) { + contextMenu.renameButton.onclick = startContextMenuRename; + } + if (contextMenu.deleteButton) { + contextMenu.deleteButton.onclick = startContextMenuDelete; + } document.addEventListener("click", (event) => { const elements = uploadElements(); if (!elements.menu || elements.menu.contains(event.target)) { diff --git a/webui/html/favicon.ico b/webui/html/favicon.ico new file mode 100644 index 0000000..dee6a96 Binary files /dev/null and b/webui/html/favicon.ico differ diff --git a/webui/html/index.html b/webui/html/index.html index 9d36b75..16a6754 100644 --- a/webui/html/index.html +++ b/webui/html/index.html @@ -122,7 +122,8 @@
- + +