import hexToRgba from 'hex-to-rgba'; import { createPinia, defineStore, setActivePinia } from 'pinia'; /** * @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components * @see https://github.com/vuejs/pinia/discussions/1085 */ setActivePinia(createPinia()); export interface Theme { banner: boolean; bannerGradient: boolean; bgColor: string; descriptionShow: boolean; metaColor: string; name: string; textColor: string; } interface ThemeVariables { [key: string]: string; } export const defaultColors: Record = { dark: { '--background': '0 0% 3.9%', '--foreground': '0 0% 98%', '--muted': '0 0% 14.9%', '--muted-foreground': '0 0% 63.9%', '--popover': '0 0% 3.9%', '--popover-foreground': '0 0% 98%', '--card': '0 0% 14.9%', '--card-foreground': '0 0% 98%', '--border': '0 0% 20%', '--input': '0 0% 14.9%', '--primary': '24 100% 50%', '--primary-foreground': '0 0% 98%', '--secondary': '0 0% 14.9%', '--secondary-foreground': '0 0% 77%', '--accent': '0 0% 14.9%', '--accent-foreground': '0 0% 98%', '--destructive': '0 62.8% 30.6%', '--destructive-foreground': '0 0% 98%', '--ring': '0 0% 83.1%', '--header-text-primary': '#1c1c1c', '--header-text-secondary': '#999999', '--header-background-color': '#f2f2f2', }, light: { '--background': '0 0% 100%', '--foreground': '0 0% 3.9%', '--muted': '0 0% 96.1%', '--muted-foreground': '0 0% 45.1%', '--popover': '0 0% 100%', '--popover-foreground': '0 0% 3.9%', '--card': '0 0% 100%', '--card-foreground': '0 0% 3.9%', '--border': '0 0% 89.8%', '--input': '0 0% 89.8%', '--primary': '24 100% 50%', '--primary-foreground': '0 0% 98%', '--secondary': '0 0% 96.1%', '--secondary-foreground': '0 0% 45%', '--accent': '0 0% 96.1%', '--accent-foreground': '0 0% 9%', '--destructive': '0 84.2% 60.2%', '--destructive-foreground': '0 0% 98%', '--ring': '0 0% 3.9%', '--radius': '0.5rem', '--header-text-primary': '#f2f2f2', '--header-text-secondary': '#999999', '--header-background-color': '#1c1b1b', }, } as const; /** * Unraid default theme colors do not have consistent colors for the header. * This is a workaround to set the correct colors for the header. * DARK THEMES: black, gray * DARK HEADER THEMES: white, gray * LIGHT THEMES: white, gray * LIGHT HEADER THEMES: black, gray */ export const defaultAzureGrayHeaderColors: ThemeVariables = { // azure and gray header colors are the same but the background color is different '--header-text-primary': '#39587f', '--header-text-secondary': '#606e7f', }; export const defaultHeaderColors: Record = { azure: { ...defaultAzureGrayHeaderColors, '--header-background-color': '#1c1b1b', }, black: { '--header-text-primary': '#1c1c1c', '--header-text-secondary': '#999999', '--header-background-color': '#f2f2f2', }, gray: { ...defaultAzureGrayHeaderColors, '--header-background-color': '#f2f2f2', }, white: { '--header-text-primary': '#f2f2f2', '--header-text-secondary': '#999999', '--header-background-color': '#1c1b1b', }, }; // used to swap the UPC text color when using the azure or gray theme export const DARK_THEMES = ['black', 'gray'] as const; export const useThemeStore = defineStore('theme', () => { // State const theme = ref(); const activeColorVariables = ref({ ...defaultColors.light, ...defaultHeaderColors['white'], }); // Getters const darkMode = computed( () => DARK_THEMES.includes(theme.value?.name as (typeof DARK_THEMES)[number]) ?? false ); const bannerGradient = computed(() => { if (!theme.value?.banner || !theme.value?.bannerGradient) { return undefined; } const start = theme.value?.bgColor ? 'var(--color-customgradient-start)' : 'rgba(0, 0, 0, 0)'; const end = theme.value?.bgColor ? 'var(--color-customgradient-end)' : 'var(--color-beta)'; return `background-image: linear-gradient(90deg, ${start} 0, ${end} 30%);`; }); // Actions const setTheme = (data: Theme) => { theme.value = data; }; const setCssVars = () => { const customColorVariables = structuredClone(defaultColors); const body = document.body; const selectedMode = darkMode.value ? 'dark' : 'light'; // set the default header colors for the current theme const themeName = theme.value?.name; if (themeName && themeName in defaultHeaderColors) { customColorVariables[selectedMode] = { ...customColorVariables[selectedMode], ...defaultHeaderColors[themeName], }; } // overwrite with hex colors set in webGUI @ /Settings/DisplaySettings if (theme.value?.textColor) { customColorVariables[selectedMode]['--header-text-primary'] = theme.value.textColor; } if (theme.value?.metaColor) { customColorVariables[selectedMode]['--header-text-secondary'] = theme.value.metaColor; } if (theme.value?.bgColor) { customColorVariables[selectedMode]['--header-background-color'] = theme.value.bgColor; body.style.setProperty('--color-customgradient-start', hexToRgba(theme.value.bgColor, 0)); body.style.setProperty('--color-customgradient-end', hexToRgba(theme.value.bgColor, 0.7)); } Object.entries(customColorVariables[selectedMode]).forEach(([key, value]) => { body.style.setProperty(key, value); }); if (darkMode.value) { document.body.classList.add('dark'); } else { document.body.classList.remove('dark'); } activeColorVariables.value = customColorVariables[selectedMode]; }; watch(theme, () => { setCssVars(); }); return { // state activeColorVariables, bannerGradient, darkMode, theme, // actions setTheme, }; });