Add permissions editor modal
This commit is contained in:
+112
-16
@@ -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')]);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user