Use UI modals for dialogs

This commit is contained in:
Mikei386
2026-07-04 10:34:19 +02:00
parent f6b81afba3
commit f1ee45ad18
6 changed files with 140 additions and 8 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
set -eu
PLUGIN="u-navigator"
VERSION="2026.07.04.r005"
VERSION="2026.07.04.r006"
PKG_DIR="packages"
WORK_DIR=".plugin-build"
PKG_NAME="${PLUGIN}-${VERSION}.tgz"
Binary file not shown.
Binary file not shown.
+119 -5
View File
@@ -13,6 +13,7 @@ const state = {
contextMenu: null,
permissionsDialog: null,
uploadConflictDialog: null,
uiDialog: null,
draggedEntry: null,
pointerDrag: null,
suppressClickPath: null,
@@ -215,6 +216,9 @@ function render() {
if (state.uploadConflictDialog) {
workspace.appendChild(renderUploadConflictDialog());
}
if (state.uiDialog) {
workspace.appendChild(renderUiDialog());
}
renderTransferPanel();
renderSettingsPanel();
}
@@ -1288,8 +1292,8 @@ async function readAllDirectoryEntries(reader) {
function showError(error) {
const debug = recordDebug('error.shown', serializeError(error));
const message = error?.message || String(error);
const detail = debug ? `\n\nDebug #${debug.id}: ${debug.label}` : '';
alert(`${message}${detail}`);
const detail = debug ? `Debug #${debug.id}: ${debug.label}` : '';
showMessageDialog('Fehler', message, detail).catch(() => {});
}
function getDroppedFiles(event) {
@@ -1330,7 +1334,7 @@ async function renameEntry(win, entry) {
showError(new Error('Read-only mode ist aktiv.'));
return;
}
const nextName = prompt('Neuer Name', entry.name);
const nextName = await showPromptDialog('Umbenennen', 'Neuer Name', entry.name);
if (!nextName || nextName === entry.name) {
return;
}
@@ -1346,7 +1350,11 @@ async function promptTransfer(win, entry, type) {
showError(new Error('Read-only mode ist aktiv.'));
return;
}
const targetDir = prompt(type === 'copy' ? 'Kopieren nach Ordner' : 'Verschieben nach Ordner', win.data.path);
const targetDir = await showPromptDialog(
type === 'copy' ? 'Kopieren' : 'Verschieben',
type === 'copy' ? 'Kopieren nach Ordner' : 'Verschieben nach Ordner',
win.data.path
);
if (!targetDir) {
return;
}
@@ -1364,7 +1372,11 @@ async function deleteEntries(win, entry = null) {
return;
}
const label = entries.length === 1 ? `"${entries[0].name}" wirklich löschen?` : `${entries.length} Objekte wirklich löschen?`;
if (!confirm(`${label}\n\nDieser Vorgang löscht direkt vom Dateisystem.`)) {
const confirmed = await showConfirmDialog('Löschen bestätigen', label, 'Dieser Vorgang löscht direkt vom Dateisystem.', {
confirmLabel: 'Löschen',
danger: true
});
if (!confirmed) {
return;
}
for (const item of entries) {
@@ -1536,6 +1548,108 @@ function closeUploadConflictDialog(action) {
render();
}
function showMessageDialog(title, message, detail = '') {
return showUiDialog({
type: 'message',
title,
message,
detail,
confirmLabel: 'OK'
});
}
function showConfirmDialog(title, message, detail = '', options = {}) {
return showUiDialog({
type: 'confirm',
title,
message,
detail,
confirmLabel: options.confirmLabel || 'OK',
cancelLabel: options.cancelLabel || 'Abbrechen',
danger: Boolean(options.danger)
});
}
function showPromptDialog(title, label, value = '') {
return showUiDialog({
type: 'prompt',
title,
message: label,
value,
confirmLabel: 'OK',
cancelLabel: 'Abbrechen'
});
}
function showUiDialog(options) {
return new Promise((resolve) => {
state.uiDialog = { ...options, resolve };
render();
});
}
function renderUiDialog() {
const dialog = state.uiDialog;
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
const isPrompt = dialog.type === 'prompt';
const isConfirm = dialog.type === 'confirm';
overlay.innerHTML = `
<form class="permissions-modal ui-modal" aria-label="${escapeAttr(dialog.title)}">
<header>
<strong>${escapeHtml(dialog.title)}</strong>
<button type="button" class="icon" data-action="cancel" title="Schließen" aria-label="Schließen">${iconSvg('close')}</button>
</header>
<div class="modal-body">
<p>${escapeHtml(dialog.message || '')}</p>
${dialog.detail ? `<p class="muted">${escapeHtml(dialog.detail)}</p>` : ''}
${isPrompt ? `<input name="value" value="${escapeAttr(dialog.value || '')}" aria-label="${escapeAttr(dialog.message || dialog.title)}">` : ''}
</div>
<footer>
${isConfirm || isPrompt ? `<button type="button" data-action="cancel">${escapeHtml(dialog.cancelLabel || 'Abbrechen')}</button>` : ''}
<button type="submit" class="primary ${dialog.danger ? 'danger-action' : ''}">${escapeHtml(dialog.confirmLabel || 'OK')}</button>
</footer>
</form>
`;
const form = overlay.querySelector('form');
form.addEventListener('click', (event) => event.stopPropagation());
overlay.addEventListener('click', () => closeUiDialog(cancelDialogValue(dialog)));
overlay.querySelectorAll('[data-action="cancel"]').forEach((button) => {
button.addEventListener('click', () => closeUiDialog(cancelDialogValue(dialog)));
});
form.addEventListener('submit', (event) => {
event.preventDefault();
if (isPrompt) {
closeUiDialog(form.elements.value.value);
return;
}
closeUiDialog(isConfirm ? true : undefined);
});
setTimeout(() => {
const focusTarget = isPrompt ? form.elements.value : form.querySelector('.primary');
focusTarget?.focus();
if (isPrompt) {
focusTarget.select();
}
}, 0);
return overlay;
}
function cancelDialogValue(dialog) {
if (dialog.type === 'confirm') return false;
if (dialog.type === 'prompt') return null;
return undefined;
}
function closeUiDialog(value) {
const dialog = state.uiDialog;
if (!dialog) return;
state.uiDialog = null;
dialog.resolve(value);
render();
}
function permissionMatrixMarkup(mode) {
const digits = modeToDigits(mode);
const rows = [
+18
View File
@@ -624,6 +624,24 @@
width: 100%;
}
.u-nav .ui-modal {
width: min(460px, calc(100vw - 28px));
}
.u-nav .ui-modal .modal-body p {
margin: 0;
overflow-wrap: anywhere;
}
.u-nav .ui-modal footer {
gap: 8px;
}
.u-nav .ui-modal .danger-action {
background: #c4382d;
border-color: #c4382d;
}
.u-nav .conflict-details {
background: var(--surface-2);
border: 1px solid var(--line);
+2 -2
View File
@@ -1,7 +1,7 @@
<!DOCTYPE PLUGIN [
<!ENTITY name "u-navigator">
<!ENTITY author "Michael Roll">
<!ENTITY version "2026.07.04.r005">
<!ENTITY version "2026.07.04.r006">
<!ENTITY package "&name;-&version;.tgz">
<!ENTITY pluginURL "https://git.casaderoll.de/michael/Unraid-Navigator/raw/branch/main/u-navigator.plg">
<!ENTITY support "https://git.casaderoll.de/michael/Unraid-Navigator">
@@ -24,7 +24,7 @@
<FILE Name="/boot/config/plugins/&name;/&package;">
<URL>https://git.casaderoll.de/michael/Unraid-Navigator/raw/branch/main/packages/&package;</URL>
<MD5>e990c21ddfd6cb6be0dd48f8b25bc3f8</MD5>
<MD5>d7ee76b55ed3afd4dbef81ad29e7a17e</MD5>
</FILE>
<FILE Run="/bin/bash">