window.rustProxyDashboardLayout = (() => { const dialog = $("overview-presets-dialog"); const list = $("overview-presets-list"); const input = $("overview-presets-name"); const message = $("overview-presets-message"); const save = $("overview-presets-save"); const factory = $("overview-presets-factory"); const close = $("overview-presets-close"); let presets = []; let busy = false; const presetPath = name => `/api/dashboard-presets/${encodeURIComponent(name)}`; const nameValid = name => name.length > 0 && new TextEncoder().encode(name).length <= 64 && !/[\x00-\x1f\x7f/]/.test(name); const snapshot = () => { const current = window.rustProxyPreferences.current() || ADMIN_APPEARANCE_DEFAULTS; return { dashboard_widgets: [...normalizeDashboardWidgets(current.dashboard_widgets)], dashboard_hidden_widgets: [...normalizeHiddenDashboardWidgets(current.dashboard_hidden_widgets)], dashboard_widget_sizes: normalizeDashboardWidgetSizes(current.dashboard_widget_sizes), dashboard_widget_heights: normalizeDashboardWidgetHeights(current.dashboard_widget_heights), dashboard_density: normalizeAdminDensity(current.dashboard_density, "compact"), overview_samples: window.rustProxyOverviewSamples.getLimit(), }; }; const translateName = (key, name) => t(key).replace("{name}", name); function notify(key, isError = false) { message.textContent = isError ? key : t(key); message.classList.toggle("error", isError); } function lock(value) { busy = value; for (const control of [save, factory, close, input, ...list.querySelectorAll("button")]) if (control) control.disabled = value; } async function run(fn) { if (busy) return; lock(true); try { await fn(); } catch (error) { const message = error.status === 409 ? (String(error.code || "").includes("limit (16)") ? t("dashboardPresetsLimit") : t("dashboardPresetsConflict")) : (error.message || String(error)); notify(message, true); } finally { lock(false); } } async function reload() { presets = await api("/api/dashboard-presets"); render(); } function entryButton(key, className, fn, presetName) { const button = el("button", t(key), className); button.type = "button"; button.setAttribute("aria-label", `${t(key)}: ${presetName}`); button.dataset.presetAction = key; button.addEventListener("click", () => void run(fn)); return button; } function render() { const elements = presets.map(preset => { const row = el("div", undefined, "overview-presets-row"); row.setAttribute("role", "listitem"); const actions = el("div", undefined, "overview-presets-actions"); actions.append( entryButton("dashboardPresetsLoad", "primary", async () => { await apply(preset); dialog.close(); toast(t("dashboardPresetsLoaded")); }, preset.name), entryButton("dashboardPresetsOverwrite", "outline", async () => { if (!(await appConfirm(translateName("dashboardPresetsOverwriteConfirm", preset.name)))) return; await api(presetPath(preset.name), { method: "PUT", body: JSON.stringify(snapshot()) }); await reload(); notify("dashboardPresetsSaved"); }, preset.name), entryButton("dashboardPresetsRename", "quiet", async () => { const name = input.value.trim(); if (!nameValid(name)) { notify("dashboardPresetsNameRequired"); return; } if (name === preset.name) return; await api(presetPath(preset.name), { method: "PATCH", body: JSON.stringify({ name }) }); input.value = ""; await reload(); notify("dashboardPresetsRenamed"); }, preset.name), entryButton("dashboardPresetsDelete", "quiet", async () => { if (!(await appConfirm(translateName("dashboardPresetsDeleteConfirm", preset.name), { danger: true }))) return; await api(presetPath(preset.name), { method: "DELETE" }); await reload(); notify("dashboardPresetsDeleted"); }, preset.name), ); row.append(el("strong", preset.name), actions); return row; }); list.replaceChildren(...(elements.length ? elements : [el("p", t("dashboardPresetsEmpty"), "fine")])); } async function apply(layout) { await window.rustProxyPreferences.update({ dashboard_widgets: [...layout.dashboard_widgets], dashboard_hidden_widgets: [...layout.dashboard_hidden_widgets], dashboard_widget_sizes: layout.dashboard_widget_sizes === null ? null : { ...layout.dashboard_widget_sizes }, dashboard_widget_heights: normalizeDashboardWidgetHeights(layout.dashboard_widget_heights), dashboard_density: layout.dashboard_density, }); window.rustProxyOverviewSamples.setLimit(layout.overview_samples, true); } async function open() { if (dialog?.open || busy || window.rustProxyInlineLayout?.isEditing?.()) return; await run(async () => { await window.rustProxyPreferences.load(); await reload(); input.value = ""; notify(""); dialog.showModal(); input.focus(); }); } save?.addEventListener("click", () => void run(async () => { const name = input.value.trim(); if (!nameValid(name)) { notify("dashboardPresetsNameRequired"); return; } if (presets.length >= 16 && !presets.some(preset => preset.name === name)) { notify("dashboardPresetsLimit"); return; } if (presets.some(preset => preset.name === name) && !(await appConfirm(translateName("dashboardPresetsOverwriteConfirm", name)))) return; await api(presetPath(name), { method: "PUT", body: JSON.stringify(snapshot()) }); input.value = ""; await reload(); notify("dashboardPresetsSaved"); })); factory?.addEventListener("click", () => void run(async () => { if (!(await appConfirm(t("dashboardPresetsResetConfirm")))) return; await apply({ dashboard_widgets: [...ADMIN_DASHBOARD_WIDGETS], dashboard_hidden_widgets: [...ADMIN_DEFAULT_HIDDEN_WIDGETS], dashboard_widget_sizes: null, dashboard_widget_heights: null, dashboard_density: ADMIN_DASHBOARD_DEFAULTS.dashboard_density, overview_samples: ADMIN_DASHBOARD_DEFAULTS.overview_samples, }); dialog.close(); toast(t("dashboardPresetsRestored")); })); close?.addEventListener("click", () => { if (!busy) dialog.close(); }); dialog?.addEventListener("cancel", event => { if (busy) event.preventDefault(); }); for (const id of ["overview-layout-open", "overview-layout-open-settings"]) $(id)?.addEventListener("click", () => { void open(); }); return { open, refresh: () => { if (dialog?.open) render(); } }; })();