Add upload overwrite dialog

This commit is contained in:
Mikei386
2026-06-24 20:20:57 +02:00
parent 6df1c7ea97
commit 9a1d54ca88
6 changed files with 108 additions and 6 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
set -eu set -eu
PLUGIN="u-navigator" PLUGIN="u-navigator"
VERSION="2026.06.24.r001" VERSION="2026.06.24.r002"
PKG_DIR="packages" PKG_DIR="packages"
WORK_DIR=".plugin-build" WORK_DIR=".plugin-build"
PKG_NAME="${PLUGIN}-${VERSION}.tgz" PKG_NAME="${PLUGIN}-${VERSION}.tgz"
Binary file not shown.
Binary file not shown.
+80 -3
View File
@@ -12,6 +12,7 @@ const state = {
selectedEntry: null, selectedEntry: null,
contextMenu: null, contextMenu: null,
permissionsDialog: null, permissionsDialog: null,
uploadConflictDialog: null,
draggedEntry: null, draggedEntry: null,
pointerDrag: null, pointerDrag: null,
suppressClickPath: null, suppressClickPath: null,
@@ -197,6 +198,9 @@ function render() {
if (state.permissionsDialog) { if (state.permissionsDialog) {
workspace.appendChild(renderPermissionsDialog()); workspace.appendChild(renderPermissionsDialog());
} }
if (state.uploadConflictDialog) {
workspace.appendChild(renderUploadConflictDialog());
}
renderTransferPanel(); renderTransferPanel();
renderSettingsPanel(); renderSettingsPanel();
} }
@@ -928,6 +932,7 @@ async function uploadFileList(win, files, targetPath) {
if (!files.length) return; if (!files.length) return;
const uploadItems = normalizeUploadItems(files); const uploadItems = normalizeUploadItems(files);
const transfer = createUploadTransfer(targetPath, uploadItems); const transfer = createUploadTransfer(targetPath, uploadItems);
let overwriteAll = false;
for (const [index, item] of uploadItems.entries()) { for (const [index, item] of uploadItems.entries()) {
updateUploadTransfer(transfer, { updateUploadTransfer(transfer, {
status: 'running', status: 'running',
@@ -955,11 +960,21 @@ async function uploadFileList(win, files, targetPath) {
}, true); }, true);
throw error; throw error;
} }
const overwrite = window.confirm(`"${item.path}" existiert bereits.\n\nVorhandene Datei überschreiben?`); const conflictAction = overwriteAll ? 'overwrite' : await askUploadConflict({
recordDebug('upload.overwrite.prompt', { path: item.path, overwrite }); 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) { if (!overwrite) {
updateUploadTransfer(transfer, { updateUploadTransfer(transfer, {
skippedFiles: transfer.skippedFiles + 1, skippedFiles: transfer.skippedFiles + 1,
skippedBytes: transfer.skippedBytes + Number(item.file.size || 0),
currentFile: item.path, currentFile: item.path,
progress: uploadProgressPercent(transfer) progress: uploadProgressPercent(transfer)
}); });
@@ -1051,6 +1066,7 @@ function createUploadTransfer(targetPath, uploadItems) {
overwrittenFiles: 0, overwrittenFiles: 0,
totalBytes: uploadItems.reduce((sum, item) => sum + Number(item.file?.size || 0), 0), totalBytes: uploadItems.reduce((sum, item) => sum + Number(item.file?.size || 0), 0),
uploadedBytes: 0, uploadedBytes: 0,
skippedBytes: 0,
currentFile: '', currentFile: '',
currentIndex: 0, currentIndex: 0,
progress: 0, progress: 0,
@@ -1084,7 +1100,7 @@ function addUploadProgress(transfer, bytes) {
function uploadProgressPercent(transfer) { function uploadProgressPercent(transfer) {
if (transfer.totalBytes > 0) { if (transfer.totalBytes > 0) {
return (transfer.uploadedBytes / transfer.totalBytes) * 100; return ((transfer.uploadedBytes + transfer.skippedBytes) / transfer.totalBytes) * 100;
} }
if (transfer.totalFiles > 0) { if (transfer.totalFiles > 0) {
return ((transfer.completedFiles + transfer.skippedFiles) / transfer.totalFiles) * 100; return ((transfer.completedFiles + transfer.skippedFiles) / transfer.totalFiles) * 100;
@@ -1424,6 +1440,67 @@ function renderPermissionsDialog() {
return overlay; 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 = `
<form class="permissions-modal conflict-modal" aria-label="Datei existiert bereits">
<header>
<strong>Datei existiert bereits</strong>
<button type="button" class="icon" data-action="skip" title="Überspringen" aria-label="Überspringen">×</button>
</header>
<div class="modal-body">
<p>Im Zielordner existiert bereits eine Datei mit diesem Namen.</p>
<dl class="conflict-details">
<dt>Datei</dt>
<dd>${escapeHtml(dialog.path)}</dd>
<dt>Ziel</dt>
<dd>${escapeHtml(dialog.targetPath)}</dd>
<dt>Upload</dt>
<dd>${escapeHtml(`${dialog.index}/${dialog.total}`)}</dd>
</dl>
</div>
<footer>
<button type="button" data-action="skip">Überspringen</button>
<button type="button" data-action="overwrite">Überschreiben</button>
<button type="submit" class="primary">Alle überschreiben</button>
</footer>
</form>
`;
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) { function permissionMatrixMarkup(mode) {
const digits = modeToDigits(mode); const digits = modeToDigits(mode);
const rows = [ const rows = [
+25
View File
@@ -581,6 +581,31 @@
opacity: 0.48; 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 { .u-nav .check-row {
align-items: center; align-items: center;
display: flex; display: flex;
+2 -2
View File
@@ -1,7 +1,7 @@
<!DOCTYPE PLUGIN [ <!DOCTYPE PLUGIN [
<!ENTITY name "u-navigator"> <!ENTITY name "u-navigator">
<!ENTITY author "Michael Roll"> <!ENTITY author "Michael Roll">
<!ENTITY version "2026.06.24.r001"> <!ENTITY version "2026.06.24.r002">
<!ENTITY package "&name;-&version;.tgz"> <!ENTITY package "&name;-&version;.tgz">
<!ENTITY pluginURL "https://git.casaderoll.de/michael/Unraid-Navigator/raw/branch/main/u-navigator.plg"> <!ENTITY pluginURL "https://git.casaderoll.de/michael/Unraid-Navigator/raw/branch/main/u-navigator.plg">
<!ENTITY support "https://git.casaderoll.de/michael/Unraid-Navigator"> <!ENTITY support "https://git.casaderoll.de/michael/Unraid-Navigator">
@@ -24,7 +24,7 @@
<FILE Name="/boot/config/plugins/&name;/&package;"> <FILE Name="/boot/config/plugins/&name;/&package;">
<URL>https://git.casaderoll.de/michael/Unraid-Navigator/raw/branch/main/packages/&package;</URL> <URL>https://git.casaderoll.de/michael/Unraid-Navigator/raw/branch/main/packages/&package;</URL>
<MD5>5d36280f152114104067f4d3e3a97234</MD5> <MD5>d2034d7eb911d38a0b4cb5044c26f404</MD5>
</FILE> </FILE>
<FILE Run="/bin/bash"> <FILE Run="/bin/bash">