eight-hourr/frontend/common.js

77 lines
2.3 KiB
JavaScript

const API = "";
const THEME_KEY = "pts_theme";
function resolveTheme(pref) {
if (pref === "light" || pref === "dark") return pref;
// system
return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
}
function getThemePref() {
return localStorage.getItem(THEME_KEY) || "system";
}
function applyTheme(pref) {
const resolved = resolveTheme(pref);
document.documentElement.setAttribute("data-theme", resolved);
document.documentElement.dataset.themePref = pref;
document.documentElement.classList.toggle("dark", resolved === "dark");
document.querySelectorAll("[data-theme-option]").forEach((btn) => {
btn.classList.toggle("active", btn.getAttribute("data-theme-option") === pref);
});
}
function setThemePref(pref) {
localStorage.setItem(THEME_KEY, pref);
applyTheme(pref);
}
function initTheme() {
applyTheme(getThemePref());
// react to OS changes when using system
window.matchMedia("(prefers-color-scheme: light)").addEventListener("change", () => {
if (getThemePref() === "system") applyTheme("system");
});
document.querySelectorAll("[data-theme-option]").forEach((btn) => {
btn.addEventListener("click", () => {
setThemePref(btn.getAttribute("data-theme-option"));
});
});
}
async function api(path, options = {}) {
const res = await fetch(`${API}${path}`, {
headers: { "Content-Type": "application/json", ...(options.headers || {}) },
...options,
});
const data = await res.json().catch(() => ({}));
if (!res.ok) {
const detail = data.detail;
const message =
typeof detail === "string"
? detail
: Array.isArray(detail)
? detail.map((d) => d.msg || JSON.stringify(d)).join("; ")
: res.statusText;
throw new Error(message || res.statusText);
}
return data;
}
function todayStr() {
return new Date().toISOString().slice(0, 10);
}
function pill(label, value, ok) {
const cls = ok === true ? "ok" : ok === false ? "warn" : "";
return `<span class="pill ${cls}"><span class="pill-label">${label}</span><span class="pill-value">${value}</span></span>`;
}
// apply ASAP if script loads after head bootstrap
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initTheme);
} else {
initTheme();
}