llm-semantic-visualizer / src /ui /themeBootstrap.js
hbauzan's picture
Release v4.17.7 — sync from GitHub main
819a47f
Raw
History Blame Contribute Delete
4.45 kB
/** Numeric VITE_UI_THEME index → semantic data-theme id (UI layer only; 3D unchanged). */
export const THEME_MAP = {
'1': 'obsidian_slate',
'2': 'amber_charcoal',
'3': 'nordic_forest',
'4': 'titanium_oxide',
'5': 'midnight_ink',
'6': 'basalt_copper',
'7': 'swiss_brutalist',
'8': 'ibm_as400',
'9': 'amiga_os13',
'10': 'blueprint_heliography',
};
/** Human-readable labels (design-system/README.md catalog). */
export const THEME_LABELS = {
'1': 'Obsidian Slate',
'2': 'Amber & Warm Charcoal',
'3': 'Nordic Forest',
'4': 'Titanium Oxide',
'5': 'Midnight Ink',
'6': 'Basalt & Raw Copper',
'7': 'Swiss Brutalist',
'8': 'IBM AS/400 Terminal',
'9': 'Amiga Workbench 1.3',
'10': 'Blue-Line Blueprint',
};
export const THEME_STORAGE_KEY = 'ui_theme_index';
const DEFAULT_THEME_INDEX = 1;
const MIN_THEME_INDEX = 1;
const MAX_THEME_INDEX = 10;
/** @type {number} */
let currentThemeIndex = DEFAULT_THEME_INDEX;
/**
* @param {string | number | undefined | null} rawIndex
* @returns {number}
*/
export function normalizeThemeIndex(rawIndex) {
const n = Number.parseInt(String(rawIndex ?? '').trim(), 10);
if (!Number.isFinite(n) || n < MIN_THEME_INDEX || n > MAX_THEME_INDEX) {
return DEFAULT_THEME_INDEX;
}
return n;
}
/**
* Resolve env index to a theme id. Invalid or missing values fall back to obsidian_slate.
* @param {string | number | undefined | null} rawIndex
* @returns {string}
*/
export function resolveThemeName(rawIndex) {
const key = String(normalizeThemeIndex(rawIndex));
return THEME_MAP[key] ?? THEME_MAP[String(DEFAULT_THEME_INDEX)];
}
/**
* @returns {number | null}
*/
export function readStoredThemeIndex() {
if (typeof localStorage === 'undefined') return null;
try {
const raw = localStorage.getItem(THEME_STORAGE_KEY);
if (raw == null || raw === '') return null;
const n = Number.parseInt(raw, 10);
if (!Number.isFinite(n) || n < MIN_THEME_INDEX || n > MAX_THEME_INDEX) return null;
return n;
} catch {
return null;
}
}
/**
* Boot priority: localStorage → VITE_UI_THEME → default 1.
* @returns {number}
*/
export function resolveInitialThemeIndex() {
const stored = readStoredThemeIndex();
if (stored != null) return stored;
const fromEnv = import.meta.env.VITE_UI_THEME;
if (fromEnv != null && String(fromEnv).trim() !== '') {
return normalizeThemeIndex(fromEnv);
}
return DEFAULT_THEME_INDEX;
}
/**
* @param {number} index
* @param {{ persist?: boolean }} [options]
* @returns {{ index: number, id: string, label: string }}
*/
export function applyUiThemeByIndex(index, options = {}) {
const { persist = true } = options;
const normalized = normalizeThemeIndex(index);
const id = resolveThemeName(normalized);
const label = THEME_LABELS[String(normalized)] ?? THEME_LABELS[String(DEFAULT_THEME_INDEX)];
currentThemeIndex = normalized;
if (typeof document !== 'undefined' && document.body) {
document.body.setAttribute('data-theme', id);
}
if (persist && typeof localStorage !== 'undefined') {
try {
localStorage.setItem(THEME_STORAGE_KEY, String(normalized));
} catch {
/* quota / private mode — theme still applied for this session */
}
}
return { index: normalized, id, label };
}
/** Apply saved/env/default theme at boot without overwriting localStorage. */
export function bootstrapUiTheme() {
return applyUiThemeByIndex(resolveInitialThemeIndex(), { persist: false });
}
/**
* Advance 1→10→1 and persist.
* @returns {{ index: number, id: string, label: string }}
*/
export function cycleUiTheme() {
const next = currentThemeIndex >= MAX_THEME_INDEX ? MIN_THEME_INDEX : currentThemeIndex + 1;
return applyUiThemeByIndex(next, { persist: true });
}
/**
* @returns {{ index: number, id: string, label: string }}
*/
export function getUiThemeSummary() {
const normalized = normalizeThemeIndex(currentThemeIndex);
return {
index: normalized,
id: resolveThemeName(normalized),
label: THEME_LABELS[String(normalized)] ?? THEME_LABELS[String(DEFAULT_THEME_INDEX)],
};
}
/**
* Read VITE_UI_THEME and set body[data-theme]. Safe in SSR/test when document is absent.
* @param {string | number | undefined | null} [rawIndex]
* @returns {string} Applied theme id
*/
export function applyUiThemeFromEnv(rawIndex = import.meta.env.VITE_UI_THEME) {
return applyUiThemeByIndex(rawIndex, { persist: false }).id;
}