Use UI modals for dialogs
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
set -eu
|
set -eu
|
||||||
|
|
||||||
PLUGIN="u-navigator"
|
PLUGIN="u-navigator"
|
||||||
VERSION="2026.07.04.r005"
|
VERSION="2026.07.04.r006"
|
||||||
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.
+119
-5
@@ -13,6 +13,7 @@ const state = {
|
|||||||
contextMenu: null,
|
contextMenu: null,
|
||||||
permissionsDialog: null,
|
permissionsDialog: null,
|
||||||
uploadConflictDialog: null,
|
uploadConflictDialog: null,
|
||||||
|
uiDialog: null,
|
||||||
draggedEntry: null,
|
draggedEntry: null,
|
||||||
pointerDrag: null,
|
pointerDrag: null,
|
||||||
suppressClickPath: null,
|
suppressClickPath: null,
|
||||||
@@ -215,6 +216,9 @@ function render() {
|
|||||||
if (state.uploadConflictDialog) {
|
if (state.uploadConflictDialog) {
|
||||||
workspace.appendChild(renderUploadConflictDialog());
|
workspace.appendChild(renderUploadConflictDialog());
|
||||||
}
|
}
|
||||||
|
if (state.uiDialog) {
|
||||||
|
workspace.appendChild(renderUiDialog());
|
||||||
|
}
|
||||||
renderTransferPanel();
|
renderTransferPanel();
|
||||||
renderSettingsPanel();
|
renderSettingsPanel();
|
||||||
}
|
}
|
||||||
@@ -1288,8 +1292,8 @@ async function readAllDirectoryEntries(reader) {
|
|||||||
function showError(error) {
|
function showError(error) {
|
||||||
const debug = recordDebug('error.shown', serializeError(error));
|
const debug = recordDebug('error.shown', serializeError(error));
|
||||||
const message = error?.message || String(error);
|
const message = error?.message || String(error);
|
||||||
const detail = debug ? `\n\nDebug #${debug.id}: ${debug.label}` : '';
|
const detail = debug ? `Debug #${debug.id}: ${debug.label}` : '';
|
||||||
alert(`${message}${detail}`);
|
showMessageDialog('Fehler', message, detail).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDroppedFiles(event) {
|
function getDroppedFiles(event) {
|
||||||
@@ -1330,7 +1334,7 @@ async function renameEntry(win, entry) {
|
|||||||
showError(new Error('Read-only mode ist aktiv.'));
|
showError(new Error('Read-only mode ist aktiv.'));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nextName = prompt('Neuer Name', entry.name);
|
const nextName = await showPromptDialog('Umbenennen', 'Neuer Name', entry.name);
|
||||||
if (!nextName || nextName === entry.name) {
|
if (!nextName || nextName === entry.name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1346,7 +1350,11 @@ async function promptTransfer(win, entry, type) {
|
|||||||
showError(new Error('Read-only mode ist aktiv.'));
|
showError(new Error('Read-only mode ist aktiv.'));
|
||||||
return;
|
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) {
|
if (!targetDir) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1364,7 +1372,11 @@ async function deleteEntries(win, entry = null) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const label = entries.length === 1 ? `"${entries[0].name}" wirklich löschen?` : `${entries.length} Objekte wirklich löschen?`;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
for (const item of entries) {
|
for (const item of entries) {
|
||||||
@@ -1536,6 +1548,108 @@ function closeUploadConflictDialog(action) {
|
|||||||
render();
|
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) {
|
function permissionMatrixMarkup(mode) {
|
||||||
const digits = modeToDigits(mode);
|
const digits = modeToDigits(mode);
|
||||||
const rows = [
|
const rows = [
|
||||||
|
|||||||
@@ -624,6 +624,24 @@
|
|||||||
width: 100%;
|
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 {
|
.u-nav .conflict-details {
|
||||||
background: var(--surface-2);
|
background: var(--surface-2);
|
||||||
border: 1px solid var(--line);
|
border: 1px solid var(--line);
|
||||||
|
|||||||
+2
-2
@@ -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.07.04.r005">
|
<!ENTITY version "2026.07.04.r006">
|
||||||
<!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>e990c21ddfd6cb6be0dd48f8b25bc3f8</MD5>
|
<MD5>d7ee76b55ed3afd4dbef81ad29e7a17e</MD5>
|
||||||
</FILE>
|
</FILE>
|
||||||
|
|
||||||
<FILE Run="/bin/bash">
|
<FILE Run="/bin/bash">
|
||||||
|
|||||||
Reference in New Issue
Block a user