Files
nexus/modules/pi_control/assets/commands.js
2026-03-08 02:03:36 +01:00

163 lines
5.2 KiB
JavaScript

(() => {
const form = document.querySelector('[data-command-form]');
const list = document.querySelector('[data-command-list]');
if (!form) return;
const idInput = form.querySelector('input[name="id"]');
const labelInput = form.querySelector('input[name="label"]');
const commandInput = form.querySelector('textarea[name="command"]');
const timeoutInput = form.querySelector('input[name="timeout_sec"]');
const adminInput = form.querySelector('input[name="admin_only"]');
const submitBtn = form.querySelector('[data-command-submit]');
const cancelBtn = form.querySelector('[data-command-cancel]');
const modal = document.querySelector('[data-command-modal]');
const modalTitle = document.querySelector('[data-command-modal-title]');
const closeBtn = document.querySelector('[data-command-close]');
const newBtn = document.querySelector('[data-command-new]');
const unsavedBar = document.querySelector('[data-command-unsaved]');
const discardBtn = document.querySelector('[data-command-discard]');
let initialSnapshot = '';
const resetForm = () => {
if (idInput) idInput.value = '';
if (labelInput) labelInput.value = '';
if (commandInput) commandInput.value = '';
if (timeoutInput) timeoutInput.value = '';
if (adminInput) adminInput.checked = false;
if (submitBtn) submitBtn.textContent = 'Speichern';
if (unsavedBar) unsavedBar.style.display = 'none';
};
const snapshot = () => {
return JSON.stringify({
id: idInput ? idInput.value : '',
label: labelInput ? labelInput.value : '',
command: commandInput ? commandInput.value : '',
timeout: timeoutInput ? timeoutInput.value : '',
admin: adminInput ? adminInput.checked : false,
});
};
const isDirty = () => snapshot() !== initialSnapshot;
const openModal = () => {
if (!modal) return;
modal.classList.add('is-open');
modal.setAttribute('aria-hidden', 'false');
initialSnapshot = snapshot();
};
const closeModal = (force = false) => {
if (!modal) return;
if (!force && isDirty()) {
if (unsavedBar) unsavedBar.style.display = 'flex';
return;
}
modal.classList.remove('is-open');
modal.setAttribute('aria-hidden', 'true');
if (unsavedBar) unsavedBar.style.display = 'none';
};
document.querySelectorAll('[data-command-edit]').forEach((btn) => {
btn.addEventListener('click', () => {
const item = btn.closest('.command-item');
if (!item) return;
if (idInput) idInput.value = item.dataset.commandId || '';
if (labelInput) labelInput.value = item.dataset.label || '';
if (commandInput) commandInput.value = item.dataset.command || '';
if (timeoutInput) timeoutInput.value = item.dataset.timeout || '';
if (adminInput) adminInput.checked = item.dataset.admin === '1';
if (submitBtn) submitBtn.textContent = 'Aktualisieren';
if (modalTitle) modalTitle.textContent = 'Befehl bearbeiten';
const details = btn.closest('details');
if (details) details.removeAttribute('open');
openModal();
});
});
if (cancelBtn) {
cancelBtn.addEventListener('click', (e) => {
e.preventDefault();
resetForm();
});
}
if (newBtn) {
newBtn.addEventListener('click', () => {
resetForm();
if (modalTitle) modalTitle.textContent = 'Neuer Befehl';
openModal();
});
}
if (closeBtn) {
closeBtn.addEventListener('click', () => closeModal(false));
}
if (discardBtn) {
discardBtn.addEventListener('click', () => {
resetForm();
closeModal(true);
});
}
form.addEventListener('input', () => {
if (unsavedBar && isDirty()) {
unsavedBar.style.display = 'flex';
}
});
if (!list) return;
let dragging = null;
list.querySelectorAll('.command-item').forEach((item) => {
item.addEventListener('dragstart', () => {
dragging = item;
item.classList.add('is-dragging');
});
item.addEventListener('dragend', () => {
item.classList.remove('is-dragging');
dragging = null;
saveOrder();
});
});
list.addEventListener('dragover', (e) => {
e.preventDefault();
if (!dragging) return;
const after = getDragAfterElement(list, e.clientY);
if (after == null) {
list.appendChild(dragging);
} else if (after !== dragging) {
list.insertBefore(dragging, after);
}
});
const getDragAfterElement = (container, y) => {
const elements = [...container.querySelectorAll('.command-item:not(.is-dragging)')];
return elements.reduce(
(closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset, element: child };
}
return closest;
},
{ offset: Number.NEGATIVE_INFINITY, element: null }
).element;
};
const saveOrder = () => {
const order = [...list.querySelectorAll('.command-item')].map((el) => el.dataset.commandId);
fetch(window.location.pathname + '?reorder_json=1', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ order }),
cache: 'no-store',
}).catch(() => {});
};
})();