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(); } }; })();