Add dashboard UI updates and settings modal
This commit is contained in:
130
pikit-web/assets/settings.js
Normal file
130
pikit-web/assets/settings.js
Normal file
@@ -0,0 +1,130 @@
|
||||
// Handles user-facing settings (theme, motion, refresh cadence) and persistence
|
||||
// across reloads. Keeps side effects isolated so main.js simply wires callbacks.
|
||||
const DEFAULT_REFRESH_SEC = 10;
|
||||
const MIN_REFRESH_SEC = 5;
|
||||
const MAX_REFRESH_SEC = 120;
|
||||
const THEME_KEY = "pikit-theme";
|
||||
const ANIM_KEY = "pikit-anim";
|
||||
const REFRESH_KEY = "pikit-refresh-sec";
|
||||
|
||||
export function initSettings({
|
||||
refreshHintMain,
|
||||
refreshHintServices,
|
||||
refreshFlagTop,
|
||||
refreshIntervalInput,
|
||||
refreshIntervalSave,
|
||||
refreshIntervalMsg,
|
||||
themeToggle,
|
||||
themeToggleIcon,
|
||||
animToggle,
|
||||
onTick,
|
||||
toast = null,
|
||||
defaultIntervalSec = DEFAULT_REFRESH_SEC,
|
||||
}) {
|
||||
let refreshIntervalMs = defaultIntervalSec * 1000;
|
||||
let refreshTimer = null;
|
||||
const prefersReduce =
|
||||
typeof window !== "undefined" &&
|
||||
window.matchMedia &&
|
||||
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
|
||||
function updateRefreshHints(seconds) {
|
||||
const text = `${seconds} second${seconds === 1 ? "" : "s"}`;
|
||||
if (refreshHintMain) refreshHintMain.textContent = text;
|
||||
if (refreshHintServices) refreshHintServices.textContent = text;
|
||||
if (refreshFlagTop) refreshFlagTop.textContent = `Refresh: ${seconds}s`;
|
||||
if (refreshIntervalInput) refreshIntervalInput.value = seconds;
|
||||
}
|
||||
|
||||
function setRefreshInterval(seconds, { silent = false } = {}) {
|
||||
const sec = Math.max(
|
||||
MIN_REFRESH_SEC,
|
||||
Math.min(MAX_REFRESH_SEC, Math.floor(seconds)),
|
||||
);
|
||||
// Clamp to safe bounds; store milliseconds for setInterval
|
||||
refreshIntervalMs = sec * 1000;
|
||||
if (refreshTimer) clearInterval(refreshTimer);
|
||||
if (onTick) {
|
||||
refreshTimer = setInterval(onTick, refreshIntervalMs);
|
||||
}
|
||||
updateRefreshHints(sec);
|
||||
try {
|
||||
localStorage.setItem(REFRESH_KEY, String(sec));
|
||||
} catch (e) {
|
||||
console.warn("Refresh persistence unavailable", e);
|
||||
}
|
||||
if (!silent && refreshIntervalMsg) {
|
||||
refreshIntervalMsg.textContent = "";
|
||||
}
|
||||
}
|
||||
|
||||
refreshIntervalSave?.addEventListener("click", () => {
|
||||
if (!refreshIntervalInput) return;
|
||||
const sec = Number(refreshIntervalInput.value);
|
||||
if (Number.isNaN(sec)) {
|
||||
if (refreshIntervalMsg) refreshIntervalMsg.textContent = "";
|
||||
toast?.("Enter seconds.", "error");
|
||||
return;
|
||||
}
|
||||
setRefreshInterval(sec);
|
||||
onTick?.(); // immediate refresh on change
|
||||
if (refreshIntervalMsg) refreshIntervalMsg.textContent = "";
|
||||
toast?.("Refresh interval updated", "success");
|
||||
});
|
||||
|
||||
function applyTheme(mode) {
|
||||
const theme = mode === "light" ? "light" : "dark";
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
if (themeToggleIcon)
|
||||
themeToggleIcon.textContent = theme === "light" ? "☀️" : "🌙";
|
||||
try {
|
||||
localStorage.setItem(THEME_KEY, theme);
|
||||
} catch (e) {
|
||||
console.warn("Theme persistence unavailable", e);
|
||||
}
|
||||
}
|
||||
|
||||
themeToggle?.addEventListener("click", () => {
|
||||
const current =
|
||||
document.documentElement.getAttribute("data-theme") || "dark";
|
||||
applyTheme(current === "dark" ? "light" : "dark");
|
||||
});
|
||||
|
||||
function applyAnim(enabled) {
|
||||
const on = enabled !== false; // default true
|
||||
// Using a data attribute lets CSS fully disable motion when off
|
||||
document.documentElement.setAttribute("data-anim", on ? "on" : "off");
|
||||
if (animToggle) animToggle.checked = on;
|
||||
try {
|
||||
localStorage.setItem(ANIM_KEY, on ? "on" : "off");
|
||||
} catch (e) {
|
||||
console.warn("Anim persistence unavailable", e);
|
||||
}
|
||||
}
|
||||
|
||||
animToggle?.addEventListener("change", () => {
|
||||
applyAnim(animToggle.checked);
|
||||
});
|
||||
|
||||
// Initialize defaults
|
||||
let storedRefresh = defaultIntervalSec;
|
||||
try {
|
||||
const saved = localStorage.getItem(REFRESH_KEY);
|
||||
if (saved) {
|
||||
const n = Number(saved);
|
||||
if (!Number.isNaN(n)) storedRefresh = n;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn("Refresh persistence unavailable", e);
|
||||
}
|
||||
updateRefreshHints(storedRefresh);
|
||||
setRefreshInterval(storedRefresh, { silent: true });
|
||||
applyTheme(localStorage.getItem(THEME_KEY) || "dark");
|
||||
const storedAnim = localStorage.getItem(ANIM_KEY);
|
||||
const animDefault =
|
||||
storedAnim === "on" || storedAnim === "off"
|
||||
? storedAnim === "on"
|
||||
: !prefersReduce; // respect system reduce-motion if no user choice
|
||||
applyAnim(animDefault);
|
||||
return { setRefreshInterval, applyTheme };
|
||||
}
|
||||
Reference in New Issue
Block a user