diff --git a/build-plugin.sh b/build-plugin.sh index 7315f46..f885efa 100755 --- a/build-plugin.sh +++ b/build-plugin.sh @@ -2,7 +2,7 @@ set -eu PLUGIN="u-navigator" -VERSION="2026.06.24.r001" +VERSION="2026.06.24.r002" PKG_DIR="packages" WORK_DIR=".plugin-build" PKG_NAME="${PLUGIN}-${VERSION}.tgz" diff --git a/packages/u-navigator-2026.06.24.r001.tgz b/packages/u-navigator-2026.06.24.r001.tgz deleted file mode 100644 index 41e0f36..0000000 Binary files a/packages/u-navigator-2026.06.24.r001.tgz and /dev/null differ diff --git a/packages/u-navigator-2026.06.24.r002.tgz b/packages/u-navigator-2026.06.24.r002.tgz new file mode 100644 index 0000000..ec3a99e Binary files /dev/null and b/packages/u-navigator-2026.06.24.r002.tgz differ diff --git a/server/public/app.js b/server/public/app.js index 4071a0a..4720105 100644 --- a/server/public/app.js +++ b/server/public/app.js @@ -12,6 +12,7 @@ const state = { selectedEntry: null, contextMenu: null, permissionsDialog: null, + uploadConflictDialog: null, draggedEntry: null, pointerDrag: null, suppressClickPath: null, @@ -197,6 +198,9 @@ function render() { if (state.permissionsDialog) { workspace.appendChild(renderPermissionsDialog()); } + if (state.uploadConflictDialog) { + workspace.appendChild(renderUploadConflictDialog()); + } renderTransferPanel(); renderSettingsPanel(); } @@ -928,6 +932,7 @@ async function uploadFileList(win, files, targetPath) { if (!files.length) return; const uploadItems = normalizeUploadItems(files); const transfer = createUploadTransfer(targetPath, uploadItems); + let overwriteAll = false; for (const [index, item] of uploadItems.entries()) { updateUploadTransfer(transfer, { status: 'running', @@ -955,11 +960,21 @@ async function uploadFileList(win, files, targetPath) { }, true); throw error; } - const overwrite = window.confirm(`"${item.path}" existiert bereits.\n\nVorhandene Datei überschreiben?`); - recordDebug('upload.overwrite.prompt', { path: item.path, overwrite }); + const conflictAction = overwriteAll ? 'overwrite' : await askUploadConflict({ + path: item.path, + targetPath, + index: index + 1, + total: uploadItems.length + }); + if (conflictAction === 'overwrite-all') { + overwriteAll = true; + } + const overwrite = overwriteAll || conflictAction === 'overwrite'; + recordDebug('upload.overwrite.prompt', { path: item.path, action: conflictAction, overwrite }); if (!overwrite) { updateUploadTransfer(transfer, { skippedFiles: transfer.skippedFiles + 1, + skippedBytes: transfer.skippedBytes + Number(item.file.size || 0), currentFile: item.path, progress: uploadProgressPercent(transfer) }); @@ -1051,6 +1066,7 @@ function createUploadTransfer(targetPath, uploadItems) { overwrittenFiles: 0, totalBytes: uploadItems.reduce((sum, item) => sum + Number(item.file?.size || 0), 0), uploadedBytes: 0, + skippedBytes: 0, currentFile: '', currentIndex: 0, progress: 0, @@ -1084,7 +1100,7 @@ function addUploadProgress(transfer, bytes) { function uploadProgressPercent(transfer) { if (transfer.totalBytes > 0) { - return (transfer.uploadedBytes / transfer.totalBytes) * 100; + return ((transfer.uploadedBytes + transfer.skippedBytes) / transfer.totalBytes) * 100; } if (transfer.totalFiles > 0) { return ((transfer.completedFiles + transfer.skippedFiles) / transfer.totalFiles) * 100; @@ -1424,6 +1440,67 @@ function renderPermissionsDialog() { return overlay; } +function askUploadConflict(details) { + return new Promise((resolve) => { + state.uploadConflictDialog = { ...details, resolve }; + render(); + }); +} + +function renderUploadConflictDialog() { + const dialog = state.uploadConflictDialog; + const overlay = document.createElement('div'); + overlay.className = 'modal-overlay'; + overlay.innerHTML = ` +
+
+ Datei existiert bereits + +
+ + +
+ `; + + const form = overlay.querySelector('form'); + form.addEventListener('click', (event) => event.stopPropagation()); + overlay.addEventListener('click', () => closeUploadConflictDialog('skip')); + overlay.querySelectorAll('[data-action="skip"]').forEach((button) => { + button.addEventListener('click', () => closeUploadConflictDialog('skip')); + }); + overlay.querySelector('[data-action="overwrite"]').addEventListener('click', () => closeUploadConflictDialog('overwrite')); + form.addEventListener('submit', (event) => { + event.preventDefault(); + closeUploadConflictDialog('overwrite-all'); + }); + setTimeout(() => { + form.querySelector('.primary')?.focus(); + }, 0); + return overlay; +} + +function closeUploadConflictDialog(action) { + const dialog = state.uploadConflictDialog; + if (!dialog) return; + state.uploadConflictDialog = null; + dialog.resolve(action); + render(); +} + function permissionMatrixMarkup(mode) { const digits = modeToDigits(mode); const rows = [ diff --git a/server/public/styles.css b/server/public/styles.css index a20eaab..2040e59 100644 --- a/server/public/styles.css +++ b/server/public/styles.css @@ -581,6 +581,31 @@ opacity: 0.48; } +.u-nav .conflict-modal { + width: 520px; +} + +.u-nav .conflict-details { + background: var(--surface-2); + border: 1px solid var(--line); + border-radius: 6px; + display: grid; + gap: 8px 12px; + grid-template-columns: auto minmax(0, 1fr); + margin: 0; + padding: 10px; +} + +.u-nav .conflict-details dt { + color: var(--muted); + font-weight: 700; +} + +.u-nav .conflict-details dd { + margin: 0; + overflow-wrap: anywhere; +} + .u-nav .check-row { align-items: center; display: flex; diff --git a/u-navigator.plg b/u-navigator.plg index a533996..ddf347d 100644 --- a/u-navigator.plg +++ b/u-navigator.plg @@ -1,7 +1,7 @@ - + @@ -24,7 +24,7 @@ https://git.casaderoll.de/michael/Unraid-Navigator/raw/branch/main/packages/&package; -5d36280f152114104067f4d3e3a97234 +d2034d7eb911d38a0b4cb5044c26f404