feature: duplicate 02

This commit is contained in:
kodi
2026-03-14 17:27:24 +01:00
parent 7f7665880f
commit cc5a978e79
5 changed files with 57 additions and 4 deletions
Binary file not shown.
@@ -84,6 +84,7 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('id="context-menu-edit-btn"', body)
self.assertIn('id="context-menu-download-btn"', body)
self.assertIn('id="context-menu-rename-btn"', body)
self.assertIn('id="context-menu-duplicate-btn"', body)
self.assertIn('id="context-menu-copy-btn"', body)
self.assertIn('id="context-menu-move-btn"', body)
self.assertIn('id="context-menu-delete-btn"', body)
@@ -307,6 +308,7 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('setStatus(zipDownload ? "Preparing download..." : "Requesting download...");', app_js)
self.assertIn('setStatus(`Download requested: ${anchor.download}`);', app_js)
self.assertIn('"/api/files/download/archive-prepare"', app_js)
self.assertIn('"/api/files/duplicate"', app_js)
self.assertIn('`/api/tasks/${encodeURIComponent(taskId)}`', app_js)
self.assertIn('`/api/files/download/archive/${encodeURIComponent(taskId)}`', app_js)
self.assertIn('`/api/files/download/archive/${encodeURIComponent(taskId)}/cancel`', app_js)
@@ -330,10 +332,13 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('function startContextMenuEdit()', app_js)
self.assertIn('function startContextMenuDownload()', app_js)
self.assertIn('function startContextMenuRename()', app_js)
self.assertIn('function startDuplicateSelected()', app_js)
self.assertIn('function startContextMenuDuplicate()', app_js)
self.assertIn('function startContextMenuCopy()', app_js)
self.assertIn('function startContextMenuMove()', app_js)
self.assertIn('function startContextMenuDelete()', app_js)
self.assertIn('function startContextMenuProperties()', app_js)
self.assertIn('contextMenu.duplicateButton.onclick = startContextMenuDuplicate;', app_js)
self.assertIn('selectedPathsSet.has(entry.path)', app_js)
self.assertIn('entry.isParent', app_js)
self.assertIn('row.oncontextmenu = (event) => {', app_js)
@@ -354,6 +359,8 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('elements.downloadButton.classList.remove("hidden");', app_js)
self.assertIn('elements.downloadButton.disabled = !downloadableSelection;', app_js)
self.assertIn('elements.renameButton.classList.toggle("hidden", isMulti);', app_js)
self.assertIn('elements.duplicateButton.classList.remove("hidden");', app_js)
self.assertIn('elements.duplicateButton.disabled = items.length === 0;', app_js)
self.assertIn('elements.copyButton.classList.remove("hidden");', app_js)
self.assertIn('elements.copyButton.disabled = items.length === 0;', app_js)
self.assertIn('elements.moveButton.classList.remove("hidden");', app_js)
@@ -368,6 +375,9 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('const { blob, fileName } = await downloadFileRequest(selectedPaths);', app_js)
self.assertIn('anchor.download = fileName || selected.name;', app_js)
self.assertIn('openRenamePopup();', app_js)
self.assertIn('const result = await createDuplicateTask(selectedItems.map((item) => item.path));', app_js)
self.assertIn('showActionSummary("Duplicate", 1, 0, null);', app_js)
self.assertIn('showActionSummary("Duplicate", 0, 1, err.message);', app_js)
self.assertIn('startCopySelected();', app_js)
self.assertIn('openF6Flow();', app_js)
self.assertIn('deleteSelected();', app_js)
@@ -395,7 +405,7 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertNotIn('if (event.altKey) {', app_js)
self.assertIn('document.getElementById("settings-btn").onclick = () => openSettings("general");', app_js)
self.assertIn('function collectDeleteRecursivePaths(selectedItems)', app_js)
self.assertIn('openDeleteConfirmModal(pane, selectedItems, recursivePaths);', app_js)
self.assertIn('const confirmed = await openConfirmModal({', app_js)
self.assertIn('recursivePaths.has(item.path)', app_js)
self.assertIn('Delete selected items and folder contents?', app_js)
self.assertIn('async function loadSettings()', app_js)
@@ -420,8 +430,10 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('paneState("left").currentPath = settingsState.preferredStartupPathLeft || "/Volumes";', app_js)
self.assertIn('paneState("right").currentPath = settingsState.preferredStartupPathRight || "/Volumes";', app_js)
self.assertIn('applyTheme(settingsState.selectedTheme, settingsState.selectedColorMode);', app_js)
self.assertIn('settings.interfaceTab.onclick = () => setSettingsTab("interface");', app_js)
self.assertIn('settings.downloadsTab.onclick = () => setSettingsTab("downloads");', app_js)
self.assertIn('settings.interfaceTab.onclick = () => {', app_js)
self.assertIn('setSettingsTab("interface");', app_js)
self.assertIn('settings.downloadsTab.onclick = () => {', app_js)
self.assertIn('setSettingsTab("downloads");', app_js)
self.assertIn('"/api/settings"', app_js)
self.assertIn('function uploadElements()', app_js)
self.assertIn('function openUploadPicker()', app_js)
@@ -436,7 +448,7 @@ class UiSmokeGoldenTest(unittest.TestCase):
self.assertIn('async function executeFolderUploadPlan(plan)', app_js)
self.assertIn('async function handleFolderSelection(event)', app_js)
self.assertIn('function deleteConfirmElements()', app_js)
self.assertIn('function openDeleteConfirmModal(pane, items, recursivePaths)', app_js)
self.assertIn('function openConfirmModal({ title, message, path, applyText = "Confirm" })', app_js)
self.assertIn('async function executeDeleteItems(pane, items, recursivePaths)', app_js)
self.assertIn('async function submitDeleteConfirmModal()', app_js)
self.assertIn('recursive: recursivePaths.has(item.path)', app_js)
+40
View File
@@ -359,6 +359,7 @@ function contextMenuElements() {
editButton: document.getElementById("context-menu-edit-btn"),
downloadButton: document.getElementById("context-menu-download-btn"),
renameButton: document.getElementById("context-menu-rename-btn"),
duplicateButton: document.getElementById("context-menu-duplicate-btn"),
copyButton: document.getElementById("context-menu-copy-btn"),
moveButton: document.getElementById("context-menu-move-btn"),
deleteButton: document.getElementById("context-menu-delete-btn"),
@@ -706,6 +707,8 @@ function openContextMenu(pane, entry, event) {
elements.downloadButton.classList.remove("hidden");
elements.downloadButton.disabled = !downloadableSelection;
elements.renameButton.classList.toggle("hidden", isMulti);
elements.duplicateButton.classList.remove("hidden");
elements.duplicateButton.disabled = items.length === 0;
elements.copyButton.classList.remove("hidden");
elements.copyButton.disabled = items.length === 0;
elements.moveButton.classList.remove("hidden");
@@ -782,6 +785,36 @@ function startContextMenuCopy() {
startCopySelected();
}
async function startDuplicateSelected() {
const sourcePane = state.activePane;
const selectedItems = [...paneState(sourcePane).selectedItems];
if (selectedItems.length === 0) {
return;
}
setError("actions-error", "");
try {
const result = await createDuplicateTask(selectedItems.map((item) => item.path));
state.selectedTaskId = result.task_id;
await refreshTasksSnapshot();
await Promise.all([loadBrowsePane("left"), loadBrowsePane("right")]);
showActionSummary("Duplicate", 1, 0, null);
} catch (err) {
showActionSummary("Duplicate", 0, 1, err.message);
}
}
function startContextMenuDuplicate() {
if (contextMenuElements().duplicateButton?.disabled) {
return;
}
if (!applyContextMenuSelection()) {
closeContextMenu();
return;
}
closeContextMenu();
startDuplicateSelected();
}
function startContextMenuOpen() {
if (contextMenuElements().openButton?.disabled) {
return;
@@ -1165,6 +1198,10 @@ async function createArchiveDownloadTask(paths) {
return apiRequest("POST", "/api/files/download/archive-prepare", { paths });
}
async function createDuplicateTask(paths) {
return apiRequest("POST", "/api/files/duplicate", { paths });
}
async function getTaskRequest(taskId) {
return apiRequest("GET", `/api/tasks/${encodeURIComponent(taskId)}`);
}
@@ -4633,6 +4670,9 @@ function setupEvents() {
if (contextMenu.downloadButton) {
contextMenu.downloadButton.onclick = startContextMenuDownload;
}
if (contextMenu.duplicateButton) {
contextMenu.duplicateButton.onclick = startContextMenuDuplicate;
}
if (contextMenu.copyButton) {
contextMenu.copyButton.onclick = startContextMenuCopy;
}
+1
View File
@@ -144,6 +144,7 @@
<button id="context-menu-edit-btn" type="button" role="menuitem">Edit</button>
<button id="context-menu-download-btn" type="button" role="menuitem">Download</button>
<button id="context-menu-rename-btn" type="button" role="menuitem">Rename</button>
<button id="context-menu-duplicate-btn" type="button" role="menuitem">Duplicate</button>
<button id="context-menu-copy-btn" type="button" role="menuitem">Copy</button>
<button id="context-menu-move-btn" type="button" role="menuitem">Move</button>
<button id="context-menu-delete-btn" type="button" role="menuitem">Delete</button>