131 lines
4.2 KiB
JavaScript
131 lines
4.2 KiB
JavaScript
// 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 };
|
|
}
|