Use UI modals for dialogs
This commit is contained in:
+1
-1
@@ -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
@@ -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 = [
|
||||
|
||||
@@ -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
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user