Files
RustProxy/src/admin/web/app/dashboard-layout-editor.js
T
2026-10-15 23:29:15 +02:00

143 lines
7.3 KiB
JavaScript

window.rustProxyDashboardLayout = (() => {
const dialog = $("overview-layout-dialog");
const nameInput = $("overview-preset-name");
const saveButton = $("overview-preset-save");
const list = $("overview-preset-list");
const empty = $("overview-preset-empty");
const count = $("overview-preset-count");
const message = $("overview-layout-message");
const closeButton = $("overview-layout-cancel");
const MAX_PRESETS = 20;
let busy = false;
let opening = false;
const current = () => window.rustProxyPreferences?.current?.() || ADMIN_APPEARANCE_DEFAULTS;
const presets = () => normalizeDashboardPresets(current().dashboard_presets);
const densityLabel = value => t({ normal: "densityNormal", compact: "densityCompact", ultra: "densityUltra" }[value] || "densityCompact");
function snapshot(name) {
const prefs = current();
const order = normalizeDashboardWidgets(prefs.dashboard_widgets);
return {
name,
dashboard_density: normalizeAdminDensity(prefs.dashboard_density, "compact"),
dashboard_widgets: order,
dashboard_hidden_widgets: normalizeHiddenDashboardWidgets(prefs.dashboard_hidden_widgets, prefs.dashboard_widgets),
dashboard_widget_sizes: normalizeDashboardWidgetSizes(prefs.dashboard_widget_sizes),
};
}
function summary(preset) {
const visible = preset.dashboard_widgets.length - preset.dashboard_hidden_widgets.length;
const widths = Object.keys(preset.dashboard_widget_sizes || {}).length;
return t("dashboardPresetSummary")
.replace("{visible}", String(visible))
.replace("{density}", densityLabel(preset.dashboard_density))
.replace("{widths}", String(widths));
}
function setBusy(value) {
busy = value;
if (nameInput) nameInput.disabled = value;
if (closeButton) closeButton.disabled = value;
render();
}
async function saveCurrent() {
const name = (nameInput?.value || "").trim();
if (busy || !name) return;
const next = presets();
const key = name.toLowerCase();
const index = next.findIndex(preset => preset.name.toLowerCase() === key);
if (index < 0 && next.length >= MAX_PRESETS) {
message.textContent = t("dashboardPresetLimitReached").replace("{max}", String(MAX_PRESETS));
return;
}
const value = snapshot(name);
if (index >= 0) next[index] = value;
else next.unshift(value);
setBusy(true);
message.textContent = t("dashboardPresetSaving");
try {
await window.rustProxyPreferences.update({ dashboard_presets: next });
if (nameInput) nameInput.value = "";
message.textContent = t(index >= 0 ? "dashboardPresetUpdated" : "dashboardPresetSavedMessage").replace("{name}", name);
toast(message.textContent);
} catch (error) { message.textContent = error.message; }
finally { setBusy(false); }
}
async function loadPreset(preset) {
if (busy) return;
setBusy(true);
message.textContent = t("dashboardPresetLoading").replace("{name}", preset.name);
try {
await window.rustProxyPreferences.update({
dashboard_density: preset.dashboard_density,
dashboard_widgets: [...preset.dashboard_widgets],
dashboard_hidden_widgets: [...preset.dashboard_hidden_widgets],
dashboard_widget_sizes: preset.dashboard_widget_sizes === null ? null : { ...preset.dashboard_widget_sizes },
});
dialog.close();
toast(t("dashboardPresetLoaded").replace("{name}", preset.name));
} catch (error) { message.textContent = error.message; }
finally { setBusy(false); }
}
async function deletePreset(preset) {
if (busy || !confirm(t("dashboardPresetDeleteConfirm").replace("{name}", preset.name))) return;
setBusy(true);
message.textContent = t("dashboardPresetDeleting").replace("{name}", preset.name);
try {
const next = presets().filter(item => item.name.toLowerCase() !== preset.name.toLowerCase());
await window.rustProxyPreferences.update({ dashboard_presets: next });
message.textContent = t("dashboardPresetDeleted").replace("{name}", preset.name);
} catch (error) { message.textContent = error.message; }
finally { setBusy(false); }
}
function presetRow(preset) {
const row = el("article", undefined, "dashboard-preset-row");
row.setAttribute("role", "listitem");
const info = el("div", undefined, "dashboard-preset-info");
info.append(el("strong", preset.name), el("small", summary(preset), "fine"));
const actions = el("div", undefined, "dashboard-preset-actions");
const load = el("button", t("dashboardPresetLoad"), "outline");
load.type = "button"; load.disabled = busy;
load.setAttribute("aria-label", `${t("dashboardPresetLoad")}: ${preset.name}`);
load.addEventListener("click", () => { void loadPreset(preset); });
const remove = el("button", t("dashboardPresetDelete"), "quiet");
remove.type = "button"; remove.disabled = busy;
remove.setAttribute("aria-label", `${t("dashboardPresetDelete")}: ${preset.name}`);
remove.addEventListener("click", () => { void deletePreset(preset); });
actions.append(load, remove);
row.append(info, actions);
return row;
}
function render() {
if (!dialog || !list) return;
const values = presets();
list.replaceChildren(...values.map(presetRow));
if (empty) empty.hidden = values.length > 0;
if (count) count.textContent = t("dashboardPresetCount").replace("{count}", String(values.length)).replace("{max}", String(MAX_PRESETS));
if (saveButton) saveButton.disabled = busy || !(nameInput?.value || "").trim();
if (nameInput) nameInput.disabled = busy;
if (closeButton) closeButton.disabled = busy;
}
async function open() {
if (dialog?.open || opening || window.rustProxyInlineLayout?.isEditing?.()) return;
opening = true;
try { await window.rustProxyPreferences?.load?.(); }
catch (error) { if (error?.status === 401) { opening = false; return; } message.textContent = error.message; }
opening = false;
if (nameInput) nameInput.value = "";
message.textContent = "";
render();
dialog.showModal();
nameInput?.focus();
}
for (const id of ["overview-layout-open", "overview-layout-open-settings"]) $(id)?.addEventListener("click", () => { void open(); });
nameInput?.addEventListener("input", render);
nameInput?.addEventListener("keydown", event => {
if (event.key === "Enter" && !saveButton?.disabled) { event.preventDefault(); void saveCurrent(); }
});
saveButton?.addEventListener("click", () => { void saveCurrent(); });
closeButton?.addEventListener("click", () => { if (!busy) dialog.close(); });
dialog?.addEventListener("cancel", event => { if (busy) event.preventDefault(); });
dialog?.addEventListener("close", () => { message.textContent = ""; });
return { open, refresh: () => { if (dialog?.open) render(); } };
})();