Add permissions editor modal

This commit is contained in:
Mikei386
2026-06-23 14:55:59 +02:00
parent 1e20799499
commit 5f014911a5
6 changed files with 216 additions and 19 deletions
+112 -16
View File
@@ -11,6 +11,7 @@ const state = {
focusedId: null,
selectedEntry: null,
contextMenu: null,
permissionsDialog: null,
draggedEntry: null,
pointerDrag: null,
suppressClickPath: null,
@@ -191,6 +192,9 @@ function render() {
if (state.contextMenu) {
workspace.appendChild(renderContextMenu());
}
if (state.permissionsDialog) {
workspace.appendChild(renderPermissionsDialog());
}
renderTransferPanel();
renderSettingsPanel();
}
@@ -1012,32 +1016,124 @@ async function promptTransfer(win, entry, type) {
await createJob(type, entry.path, `${targetDir.replace(/\/$/, '')}/${entry.name}`, []);
}
async function promptPermissions(win, entry) {
function openPermissionsDialog(win, entry) {
if (isReadOnly()) {
showError(new Error('Read-only mode ist aktiv.'));
return;
}
const entries = selectedPathSet(win).has(entry.path) ? selectedEntries(win) : [entry];
const mode = prompt('Berechtigungen als Oktalmodus, z.B. 0644 oder 0755', entry.permissions ? `0${entry.permissions}`.slice(-4) : '');
if (mode === null) return;
const owner = prompt('Besitzer ändern, leer lassen für unverändert', entry.owner || '');
if (owner === null) return;
const group = prompt('Gruppe ändern, leer lassen für unverändert', entry.group || '');
if (group === null) return;
const hasDirectory = entries.some((item) => item.type === 'directory');
const recursive = hasDirectory && confirm('Rekursiv auf Unterordner und Dateien anwenden?');
state.permissionsDialog = {
winId: win.id,
entries,
mode: entry.permissions ? `0${entry.permissions}`.slice(-4) : '0755',
owner: entry.owner || '',
group: entry.group || '',
recursive: false,
hasDirectory: entries.some((item) => item.type === 'directory')
};
render();
}
function renderPermissionsDialog() {
const dialog = state.permissionsDialog;
const overlay = document.createElement('div');
overlay.className = 'modal-overlay';
overlay.innerHTML = `
<form class="permissions-modal" aria-label="Berechtigungen ändern">
<header>
<strong>Berechtigungen ändern</strong>
<button type="button" class="icon" data-action="cancel" title="Schließen" aria-label="Schließen">×</button>
</header>
<div class="modal-body">
<p class="muted">${dialog.entries.length === 1 ? escapeHtml(dialog.entries[0].name) : `${dialog.entries.length} Objekte ausgewählt`}</p>
<label class="check-row">
<input type="checkbox" name="changeMode" checked>
<span>Modus ändern</span>
</label>
<input name="mode" inputmode="numeric" pattern="[0-7]{3,4}" value="${escapeAttr(dialog.mode)}" aria-label="Oktalmodus">
<div class="permission-presets" aria-label="Schnellauswahl">
<button type="button" data-mode="0644">0644</button>
<button type="button" data-mode="0755">0755</button>
<button type="button" data-mode="0775">0775</button>
<button type="button" data-mode="0777">0777</button>
</div>
<label class="check-row">
<input type="checkbox" name="changeOwner">
<span>Besitzer ändern</span>
</label>
<input name="owner" value="${escapeAttr(dialog.owner)}" aria-label="Besitzer" disabled>
<label class="check-row">
<input type="checkbox" name="changeGroup">
<span>Gruppe ändern</span>
</label>
<input name="group" value="${escapeAttr(dialog.group)}" aria-label="Gruppe" disabled>
<label class="check-row ${dialog.hasDirectory ? '' : 'disabled'}">
<input type="checkbox" name="recursive" ${dialog.hasDirectory ? '' : 'disabled'}>
<span>Rekursiv auf Unterordner und Dateien anwenden</span>
</label>
</div>
<footer>
<button type="button" data-action="cancel">Abbrechen</button>
<button type="submit" class="primary">Anwenden</button>
</footer>
</form>
`;
const form = overlay.querySelector('form');
form.addEventListener('click', (event) => event.stopPropagation());
overlay.addEventListener('click', () => closePermissionsDialog());
overlay.querySelectorAll('[data-action="cancel"]').forEach((button) => {
button.addEventListener('click', () => closePermissionsDialog());
});
for (const checkboxName of ['changeMode', 'changeOwner', 'changeGroup']) {
const checkbox = form.elements[checkboxName];
const inputName = checkboxName.replace('change', '').toLowerCase();
const input = form.elements[inputName];
checkbox.addEventListener('change', () => {
input.disabled = !checkbox.checked;
if (!input.disabled) input.focus();
});
}
for (const button of overlay.querySelectorAll('[data-mode]')) {
button.addEventListener('click', () => {
form.elements.changeMode.checked = true;
form.elements.mode.disabled = false;
form.elements.mode.value = button.dataset.mode;
});
}
form.addEventListener('submit', (event) => {
event.preventDefault();
applyPermissionsDialog(form).catch(showError);
});
return overlay;
}
function closePermissionsDialog() {
state.permissionsDialog = null;
render();
}
async function applyPermissionsDialog(form) {
const dialog = state.permissionsDialog;
if (!dialog) return;
const options = {
mode: mode.trim(),
owner: owner.trim(),
group: group.trim(),
recursive: recursive ? '1' : '0'
mode: form.elements.changeMode.checked ? form.elements.mode.value.trim() : '',
owner: form.elements.changeOwner.checked ? form.elements.owner.value.trim() : '',
group: form.elements.changeGroup.checked ? form.elements.group.value.trim() : '',
recursive: form.elements.recursive.checked ? '1' : '0'
};
if (!options.mode && !options.owner && !options.group) {
showError(new Error('Keine Änderung angegeben.'));
return;
}
for (const item of entries) {
await createJob('permissions', item.path, null, [win.id], options);
if (options.mode && !/^[0-7]{3,4}$/.test(options.mode)) {
showError(new Error('Berechtigungen müssen aus 3 oder 4 Oktalziffern bestehen.'));
return;
}
const win = state.windows.find((item) => item.id === dialog.winId);
closePermissionsDialog();
for (const item of dialog.entries) {
await createJob('permissions', item.path, null, [win?.id], options);
}
}
@@ -1076,7 +1172,7 @@ function renderContextMenu() {
actions.push(['Eigenschaften', () => showPropertiesForEntry(win, entry)]);
if (!isReadOnly()) {
actions.push(['Umbenennen', () => renameEntry(win, entry)]);
actions.push(['Berechtigungen...', () => promptPermissions(win, entry)]);
actions.push(['Berechtigungen...', () => openPermissionsDialog(win, entry)]);
actions.push(['Kopieren nach...', () => promptTransfer(win, entry, 'copy')]);
actions.push(['Verschieben nach...', () => promptTransfer(win, entry, 'move')]);
}
+101
View File
@@ -51,6 +51,12 @@
border-color: var(--accent);
}
.u-nav button.primary {
background: var(--accent);
border-color: var(--accent);
color: #ffffff;
}
.u-nav button.icon {
align-items: center;
display: inline-flex;
@@ -496,6 +502,101 @@
border-color: var(--line);
}
.u-nav .modal-overlay {
align-items: center;
background: rgba(10, 14, 18, 0.42);
display: flex;
inset: 0;
justify-content: center;
padding: 18px;
position: absolute;
z-index: 100020;
}
.u-nav .permissions-modal {
background: var(--surface);
border: 1px solid var(--line);
border-radius: 8px;
box-shadow: var(--shadow);
color: var(--text);
display: grid;
max-height: min(640px, 100%);
max-width: 100%;
min-width: min(420px, 100%);
overflow: hidden;
width: 460px;
}
.u-nav .permissions-modal header,
.u-nav .permissions-modal footer {
align-items: center;
background: var(--surface-2);
display: flex;
gap: 8px;
justify-content: space-between;
padding: 8px 10px 8px 12px;
}
.u-nav .permissions-modal header {
border-bottom: 1px solid var(--line);
}
.u-nav .permissions-modal footer {
border-top: 1px solid var(--line);
justify-content: flex-end;
}
.u-nav .modal-body {
display: grid;
gap: 9px;
overflow: auto;
padding: 12px;
}
.u-nav .permissions-modal input[type="text"],
.u-nav .permissions-modal input:not([type]) {
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: 6px;
color: var(--text);
font: inherit;
min-height: 32px;
padding: 0 9px;
width: 100%;
}
.u-nav .permissions-modal input:disabled {
opacity: 0.48;
}
.u-nav .check-row {
align-items: center;
display: flex;
gap: 9px;
}
.u-nav .check-row input {
accent-color: var(--accent);
height: 18px;
margin: 0;
width: 18px;
}
.u-nav .check-row.disabled {
opacity: 0.5;
}
.u-nav .permission-presets {
display: grid;
gap: 6px;
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.u-nav .permission-presets button {
min-width: 0;
padding: 0 6px;
}
.u-nav .transfer-panel {
background: var(--surface);
border: 1px solid var(--line);