diff --git a/projects/frontend/src/components/shared/theme-picker.vue b/projects/frontend/src/components/shared/theme-picker.vue index d156b32..b4ae4c7 100644 --- a/projects/frontend/src/components/shared/theme-picker.vue +++ b/projects/frontend/src/components/shared/theme-picker.vue @@ -3,20 +3,39 @@ import { computed, onMounted, ref } from 'vue' import PrimeVueSelect from 'primevue/select' -import type { SiteTheme } from '@goldenwere/mackenzii-types' +import type { SiteTheme, SiteThemeType } from '@goldenwere/mackenzii-types' import { injectStylesheet } from 'src/utilities/dom' import { storage } from 'src/utilities/fetch' import { useRouteStore } from 'src/routes' +type Theme = SiteTheme & { id: string } +const systemTheme = { + id: 'SYSTEM', + displayName: 'System', + type: null as any, + url: null as any, +} + const routeStore = useRouteStore() const globalConfig = routeStore._globals const options = ref(globalConfig.themes) -const optionsArray = computed(() => Object.keys(options.value).map(key => ({...options.value[key], id: key}))) +const optionsArray = computed(() => { + const val = Object.keys(options.value).map(key => ({...options.value[key], id: key})) + if (val.find(other => other.type === 'dark') && val.find(other => other.type === 'light')) { + val.push(systemTheme) + } + return val +}) const currentTheme = ref(null as any) +const currentSystem = ref(null as any) let node: HTMLLinkElement const setModeClass = () => { - switch (currentTheme.value.type) { + const type = currentTheme.value.id === 'SYSTEM' + ? currentSystem.value + : currentTheme.value.type + + switch (type) { case 'dark': document.body.parentElement!.classList.add('app-dark') document.body.parentElement!.classList.remove('app-dark-hc') @@ -50,29 +69,63 @@ const setModeClass = () => { } } -const onThemeChosen = (event: Event) => { +const getUrlOfTheme = (id: string) => { + if (id === 'SYSTEM') { + return optionsArray.value.find(other => other.type === currentSystem.value)!.url + } else { + return globalConfig.themes[id].url + } +} + +const onThemeChosen = (event?: Event) => { const themeId = currentTheme.value.id storage.write(`${globalConfig.id}::currentTheme`, themeId) - node.setAttribute('href', globalConfig.themes[themeId].url) + node.setAttribute('href', getUrlOfTheme(themeId)) setModeClass() } +const onSystemThemeChanged = (query: MediaQueryListEvent | MediaQueryList) => { + if (query.matches) { + currentSystem.value = 'dark' + } else { + currentSystem.value = 'light' + } + onThemeChosen() +} + +const setupSystemThemeListener = () => { + const themeListener = window.matchMedia('(prefers-color-scheme: dark)') + onSystemThemeChanged(themeListener) + themeListener.addEventListener('change', onSystemThemeChanged) +} + +const loadThemeFromStorage = () => { + const themeId = storage.read(`${globalConfig.id}::currentTheme`) || ( + !!optionsArray.value.find(other => other.id === 'SYSTEM') ? 'SYSTEM' : Object.keys(globalConfig.themes)[0] + ) + currentTheme.value = themeId === 'SYSTEM' ? systemTheme : { ...options.value[themeId], id: themeId } + node = injectStylesheet(getUrlOfTheme(themeId), 'theme-stylesheet') +} + onMounted(async () => { - const themeId = storage.read(`${globalConfig.id}::currentTheme`) || Object.keys(globalConfig.themes)[0] - currentTheme.value = { ...options.value[themeId], id: themeId } - node = injectStylesheet(globalConfig.themes[themeId].url, 'theme-stylesheet') + loadThemeFromStorage() + setupSystemThemeListener() setModeClass() })