import { storeToRefs } from 'pinia'; import { toast } from 'vue-sonner'; import { THEME_CONFIG } from '../../constants'; import { i18n } from '../../../plugin/i18n'; import { router } from '../../../plugin/router'; import { textToHex } from './string'; import { useAppearanceSettingsStore } from '../../../stores'; import configRepository from '../../../service/config.js'; /** * * @returns {boolean} */ function systemIsDarkMode() { return window.matchMedia('(prefers-color-scheme: dark)').matches; } /** * * @param {boolean}isDark */ function changeAppDarkStyle(isDark) { if (isDark) { AppApi.ChangeTheme(1); } else { AppApi.ChangeTheme(0); } } function applyThemeFonts(themeKey, fontLinks = []) { document .querySelectorAll('link[data-theme-font]') .forEach((linkEl) => linkEl.remove()); if (!fontLinks?.length) { return; } const head = document.head; fontLinks.forEach((href) => { if (!href) { return; } const fontLink = document.createElement('link'); fontLink.rel = 'stylesheet'; fontLink.href = href; fontLink.dataset.themeFont = themeKey; head.appendChild(fontLink); }); } function changeAppThemeStyle(themeMode) { if (themeMode === 'system') { themeMode = systemIsDarkMode() ? 'dark' : 'light'; } let themeConfig = THEME_CONFIG[themeMode]; if (!themeConfig) { // fallback to system console.error('Invalid theme mode:', themeMode); configRepository.setString('VRCX_ThemeMode', 'system'); themeMode = systemIsDarkMode() ? 'dark' : 'light'; themeConfig = THEME_CONFIG[themeMode]; } applyThemeFonts(themeMode, themeConfig.fontLinks); document.documentElement.setAttribute('data-theme', themeMode); const shouldUseDarkClass = Boolean(themeConfig.isDark); if (shouldUseDarkClass) { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } changeAppDarkStyle(themeConfig.isDark); return { isDark: themeConfig.isDark }; // let $appThemeDarkStyle = document.getElementById('app-theme-dark-style'); // const darkThemeCssPath = `${filePathPrefix}theme.dark.css`; // const shouldApplyDarkBase = themeConfig.isDark; // if (shouldApplyDarkBase) { // if (!$appThemeDarkStyle) { // $appThemeDarkStyle = document.createElement('link'); // $appThemeDarkStyle.setAttribute('id', 'app-theme-dark-style'); // $appThemeDarkStyle.rel = 'stylesheet'; // $appThemeDarkStyle.href = darkThemeCssPath; // document.head.insertBefore($appThemeDarkStyle, $appThemeStyle); // } else if ($appThemeDarkStyle.href !== darkThemeCssPath) { // $appThemeDarkStyle.href = darkThemeCssPath; // } // } else { // $appThemeDarkStyle && $appThemeDarkStyle.remove(); // } } /** * * @param {object} trustColor */ function updateTrustColorClasses(trustColor) { if (document.getElementById('trustColor') !== null) { document.getElementById('trustColor').outerHTML = ''; } const style = document.createElement('style'); style.id = 'trustColor'; style.type = 'text/css'; let newCSS = ''; for (const rank in trustColor) { newCSS += `.x-tag-${rank} { color: ${trustColor[rank]} !important; border-color: ${trustColor[rank]} !important; } `; } style.innerHTML = newCSS; document.getElementsByTagName('head')[0].appendChild(style); } async function refreshCustomCss() { if (document.contains(document.getElementById('app-custom-style'))) { document.getElementById('app-custom-style').remove(); } const customCss = await AppApi.CustomCss(); if (customCss) { const head = document.head; const $appCustomStyle = document.createElement('link'); $appCustomStyle.setAttribute('id', 'app-custom-style'); $appCustomStyle.rel = 'stylesheet'; $appCustomStyle.type = 'text/css'; $appCustomStyle.href = URL.createObjectURL( new Blob([customCss], { type: 'text/css' }) ); head.appendChild($appCustomStyle); } } async function refreshCustomScript() { if (document.contains(document.getElementById('app-custom-script'))) { document.getElementById('app-custom-script').remove(); } const customScript = await AppApi.CustomScript(); if (customScript) { const head = document.head; const $appCustomScript = document.createElement('script'); $appCustomScript.setAttribute('id', 'app-custom-script'); $appCustomScript.type = 'text/javascript'; $appCustomScript.textContent = customScript; head.appendChild($appCustomScript); } } /** * * @param {number} hue * @returns {string} */ function HueToHex(hue) { const appSettingsStore = useAppearanceSettingsStore(); const { isDarkMode } = storeToRefs(appSettingsStore); // this.HSVtoRGB(hue / 65535, .8, .8); if (isDarkMode.value) { return HSVtoRGB(hue / 65535, 0.6, 1); } return HSVtoRGB(hue / 65535, 1, 0.7); } /** * * @param {number} h * @param {number} s * @param {number} v * @returns {string} */ function HSVtoRGB(h, s, v) { let r = 0; let g = 0; let b = 0; if (arguments.length === 1) { // @ts-ignore s = h.s; // @ts-ignore v = h.v; // @ts-ignore h = h.h; } const i = Math.floor(h * 6); const f = h * 6 - i; const p = v * (1 - s); const q = v * (1 - f * s); const t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: r = v; g = t; b = p; break; case 1: r = q; g = v; b = p; break; case 2: r = p; g = v; b = t; break; case 3: r = p; g = q; b = v; break; case 4: r = t; g = p; b = v; break; case 5: r = v; g = p; b = q; break; } const red = Math.round(r * 255); const green = Math.round(g * 255); const blue = Math.round(b * 255); const decColor = 0x1000000 + blue + 0x100 * green + 0x10000 * red; return `#${decColor.toString(16).substr(1)}`; } function formatJsonVars(ref) { // remove all object keys that start with $ const newRef = { ...ref }; for (const key in newRef) { if (key.startsWith('$')) { delete newRef[key]; } } // sort keys alphabetically const sortedKeys = Object.keys(newRef).sort(); const sortedRef = {}; sortedKeys.forEach((key) => { sortedRef[key] = newRef[key]; }); if ('displayName' in sortedRef) { // add _hexDisplayName to top return { // @ts-ignore _hexDisplayName: textToHex(sortedRef.displayName), ...sortedRef }; } if ('name' in sortedRef) { // add _hexName to top return { // @ts-ignore _hexName: textToHex(sortedRef.name), ...sortedRef }; } return sortedRef; } function changeHtmlLangAttribute(language) { const htmlElement = document.documentElement; htmlElement.setAttribute('lang', language); } async function getThemeMode(configRepository) { const initThemeMode = await configRepository.getString( 'VRCX_ThemeMode', 'system' ); let isDarkMode; if (initThemeMode === 'light') { isDarkMode = false; } else if (initThemeMode === 'system') { isDarkMode = systemIsDarkMode(); } else { isDarkMode = true; } return { initThemeMode, isDarkMode }; } function redirectToToolsTab() { router.push({ name: 'tools' }); toast(i18n.global.t('view.tools.redirect_message'), { duration: 3000 }); } export { systemIsDarkMode, changeAppDarkStyle, changeAppThemeStyle, updateTrustColorClasses, refreshCustomCss, refreshCustomScript, HueToHex, HSVtoRGB, formatJsonVars, changeHtmlLangAttribute, getThemeMode, redirectToToolsTab };