Use checkbox permission editor

This commit is contained in:
Mikei386
2026-06-23 15:03:02 +02:00
parent 5f014911a5
commit a050531f63
6 changed files with 137 additions and 25 deletions
+96 -16
View File
@@ -1025,7 +1025,7 @@ function openPermissionsDialog(win, entry) {
state.permissionsDialog = {
winId: win.id,
entries,
mode: entry.permissions ? `0${entry.permissions}`.slice(-4) : '0755',
mode: normalizeMode(entry.permissions || '0755'),
owner: entry.owner || '',
group: entry.group || '',
recursive: false,
@@ -1048,15 +1048,13 @@ function renderPermissionsDialog() {
<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>
<span>Berechtigungen ändern</span>
</label>
${permissionMatrixMarkup(dialog.mode)}
<label class="field-row">
<span>Numerischer Wert</span>
<input name="mode" class="mode-input" inputmode="numeric" pattern="[0-7]{3,4}" value="${escapeAttr(dialog.mode)}" aria-label="Numerischer Wert">
</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>
@@ -1094,13 +1092,7 @@ function renderPermissionsDialog() {
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;
});
}
bindPermissionMatrix(form);
form.addEventListener('submit', (event) => {
event.preventDefault();
applyPermissionsDialog(form).catch(showError);
@@ -1108,6 +1100,94 @@ function renderPermissionsDialog() {
return overlay;
}
function permissionMatrixMarkup(mode) {
const digits = modeToDigits(mode);
const rows = [
['owner', 'Besitzer-Berechtigungen', digits[0]],
['group', 'Gruppen-Berechtigungen', digits[1]],
['public', 'Öffentliche Berechtigungen', digits[2]]
];
return rows.map(([scope, label, digit]) => `
<fieldset class="permission-scope" data-scope="${scope}">
<legend>${label}</legend>
${permissionCheck(scope, 'read', 'Lesen', digit & 4)}
${permissionCheck(scope, 'write', 'Schreiben', digit & 2)}
${permissionCheck(scope, 'execute', 'Ausführen', digit & 1)}
</fieldset>
`).join('');
}
function permissionCheck(scope, bit, label, checked) {
return `
<label>
<input type="checkbox" data-permission="${scope}.${bit}" ${checked ? 'checked' : ''}>
<span>${label}</span>
</label>
`;
}
function bindPermissionMatrix(form) {
const changeMode = form.elements.changeMode;
const modeInput = form.elements.mode;
const permissionInputs = [...form.querySelectorAll('[data-permission]')];
const setModeDisabled = () => {
modeInput.disabled = !changeMode.checked;
for (const input of permissionInputs) {
input.disabled = !changeMode.checked;
}
};
changeMode.addEventListener('change', setModeDisabled);
for (const input of permissionInputs) {
input.addEventListener('change', () => {
changeMode.checked = true;
setModeDisabled();
modeInput.value = modeFromPermissionInputs(form);
});
}
modeInput.addEventListener('input', () => {
changeMode.checked = true;
setModeDisabled();
syncPermissionInputsFromMode(form, modeInput.value);
});
setModeDisabled();
}
function modeFromPermissionInputs(form) {
const scopes = ['owner', 'group', 'public'];
const values = scopes.map((scope) => {
let value = 0;
if (form.querySelector(`[data-permission="${scope}.read"]`)?.checked) value += 4;
if (form.querySelector(`[data-permission="${scope}.write"]`)?.checked) value += 2;
if (form.querySelector(`[data-permission="${scope}.execute"]`)?.checked) value += 1;
return String(value);
});
return values.join('');
}
function syncPermissionInputsFromMode(form, mode) {
if (!/^[0-7]{3,4}$/.test(mode)) return;
const digits = modeToDigits(mode);
for (const [index, scope] of ['owner', 'group', 'public'].entries()) {
const digit = digits[index];
const map = { read: 4, write: 2, execute: 1 };
for (const [bit, value] of Object.entries(map)) {
const input = form.querySelector(`[data-permission="${scope}.${bit}"]`);
if (input) input.checked = Boolean(digit & value);
}
}
}
function normalizeMode(mode) {
const value = String(mode || '');
if (!/^[0-7]{3,4}$/.test(value)) return '755';
return value.length === 4 ? value.slice(1) : value;
}
function modeToDigits(mode) {
const normalized = normalizeMode(mode);
return normalized.split('').slice(-3).map((digit) => Number(digit));
}
function closePermissionsDialog() {
state.permissionsDialog = null;
render();
+38 -6
View File
@@ -586,15 +586,47 @@
opacity: 0.5;
}
.u-nav .permission-presets {
.u-nav .permission-scope {
background: var(--surface-2);
border: 1px solid var(--line);
border-radius: 6px;
display: grid;
gap: 6px;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 8px;
grid-template-columns: repeat(3, minmax(0, 1fr));
margin: 0;
padding: 10px;
}
.u-nav .permission-presets button {
min-width: 0;
padding: 0 6px;
.u-nav .permission-scope legend {
color: var(--text);
font-size: 12px;
font-weight: 600;
padding: 0 4px;
}
.u-nav .permission-scope label {
align-items: center;
display: flex;
gap: 6px;
white-space: nowrap;
}
.u-nav .permission-scope input {
accent-color: var(--accent);
height: 17px;
margin: 0;
width: 17px;
}
.u-nav .field-row {
align-items: center;
display: grid;
gap: 8px;
grid-template-columns: max-content minmax(0, 1fr);
}
.u-nav .mode-input {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace !important;
}
.u-nav .transfer-panel {