diff --git a/js/themeController.js b/js/themeController.js index 41c0d4c4..f0a3b071 100644 --- a/js/themeController.js +++ b/js/themeController.js @@ -1,4 +1,4 @@ -import { THEME_ACCENT_OVERRIDES } from "../config/instance-config.js"; +import { THEME_ACCENT_OVERRIDES as CONFIG_THEME_ACCENT_OVERRIDES } from "../config/instance-config.js"; import { userLogger } from "./utils/logger.js"; const STORAGE_KEY = "bitvid:theme"; const FALLBACK_THEME = "dark"; @@ -24,6 +24,7 @@ const boundToggles = new WeakSet(); const registeredToggles = new Set(); let currentTheme = null; let storageListenerBound = false; +let accentOverrides = null; const isBrowser = () => typeof window !== "undefined" && typeof document !== "undefined"; @@ -80,17 +81,56 @@ const getRootElement = () => { const normalizeAccentValue = (value) => typeof value === "string" ? value.trim() : ""; +const cloneAccentOverrides = (overrides) => { + if (!overrides || typeof overrides !== "object") { + return null; + } + + const sanitized = Object.entries(overrides).reduce((accumulator, [themeKey, themeOverrides]) => { + if (!themeOverrides || typeof themeOverrides !== "object") { + return accumulator; + } + + const nextThemeOverrides = Object.entries(themeOverrides).reduce( + (themeAccumulator, [token, value]) => { + if (Object.prototype.hasOwnProperty.call(ACCENT_CSS_VARIABLES, token)) { + themeAccumulator[token] = value; + } + return themeAccumulator; + }, + {}, + ); + + if (Object.keys(nextThemeOverrides).length > 0) { + accumulator[themeKey] = nextThemeOverrides; + } + + return accumulator; + }, {}); + return Object.keys(sanitized).length > 0 ? sanitized : null; +}; + +const setAccentOverrides = (overrides) => { + accentOverrides = cloneAccentOverrides(overrides); +}; + +setAccentOverrides( + typeof CONFIG_THEME_ACCENT_OVERRIDES !== "undefined" + ? CONFIG_THEME_ACCENT_OVERRIDES + : null, +); + const applyAccentOverrides = (root, theme) => { if (!root) { return; } const themeOverrides = - THEME_ACCENT_OVERRIDES && - typeof THEME_ACCENT_OVERRIDES === "object" && - THEME_ACCENT_OVERRIDES[theme] && - typeof THEME_ACCENT_OVERRIDES[theme] === "object" - ? THEME_ACCENT_OVERRIDES[theme] + accentOverrides && + typeof accentOverrides === "object" && + accentOverrides[theme] && + typeof accentOverrides[theme] === "object" + ? accentOverrides[theme] : null; Object.entries(ACCENT_CSS_VARIABLES).forEach(([token, cssVariable]) => { @@ -258,6 +298,20 @@ const applyTheme = (theme, { persist = true } = {}) => { return normalized; }; +export const setThemeAccentOverrides = (overrides) => { + setAccentOverrides(overrides); + + const root = getRootElement(); + if (!root) { + return; + } + + const theme = currentTheme || FALLBACK_THEME; + applyAccentOverrides(root, theme); +}; + +export const getThemeAccentOverrides = () => cloneAccentOverrides(accentOverrides); + export const setTheme = (theme) => { if (!isBrowser()) { currentTheme = normalizeTheme(theme) || FALLBACK_THEME; diff --git a/scripts/build-beacon.mjs b/scripts/build-beacon.mjs index 39dbb3bd..8c96300f 100755 --- a/scripts/build-beacon.mjs +++ b/scripts/build-beacon.mjs @@ -40,6 +40,7 @@ await build({ target: ["es2020"], minify: true, sourcemap: false, + external: ["../config/instance-config.js", "../../config/instance-config.js"], loader: { ".css": "css", ".woff": "dataurl", diff --git a/torrent/src/beacon-vendor.js b/torrent/src/beacon-vendor.js index 077d426c..6bca9005 100644 --- a/torrent/src/beacon-vendor.js +++ b/torrent/src/beacon-vendor.js @@ -1,7 +1,12 @@ import { devLogger, userLogger } from "../js/utils/logger.js"; -import { initThemeController } from "../../js/themeController.js"; +import { + initThemeController, + setThemeAccentOverrides, +} from "../../js/themeController.js"; import { createBeaconApp } from "../app.js"; +const CONFIG_MODULE_ID = "../../config/instance-config.js"; + function resolveGlobalScope() { return ( (typeof window !== "undefined" && window) || @@ -22,7 +27,32 @@ function resolveWebTorrent() { let appInstance = null; -const initializeThemeController = () => { +const loadThemeAccentOverrides = async () => { + try { + const configModule = await import(CONFIG_MODULE_ID); + if (configModule && typeof configModule.THEME_ACCENT_OVERRIDES !== "undefined") { + return configModule.THEME_ACCENT_OVERRIDES; + } + } catch (error) { + devLogger.warn( + `[beacon] Failed to load theme accent overrides from ${CONFIG_MODULE_ID}`, + error, + ); + } + + return null; +}; + +const initializeThemeController = async () => { + try { + const overrides = await loadThemeAccentOverrides(); + if (overrides) { + setThemeAccentOverrides(overrides); + } + } catch (error) { + devLogger.warn("[beacon] Unable to hydrate theme accent overrides", error); + } + try { initThemeController(); } catch (error) {