Files
reader-star/apps/admin/src/store/modules/theme/index.ts
2026-01-16 13:06:44 +08:00

306 lines
7.4 KiB
TypeScript

import type { Ref } from 'vue'
import { getPaletteColorByNumber } from '@sa/color'
import { useDateFormat, useEventListener, useNow, usePreferredColorScheme } from '@vueuse/core'
import { defineStore } from 'pinia'
import { computed, effectScope, onScopeDispose, ref, toRefs, watch } from 'vue'
import { SetupStoreId } from '@/enum'
import { localStg } from '@/utils/storage'
import { useAuthStore } from '../auth'
import {
addThemeVarsToGlobal,
createThemeToken,
getNaiveTheme,
initThemeSettings,
toggleAuxiliaryColorModes,
toggleCssDarkMode,
} from './shared'
/** Theme store */
export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
const scope = effectScope()
const osTheme = usePreferredColorScheme()
const authStore = useAuthStore()
/** Theme settings */
const settings: Ref<App.Theme.ThemeSetting> = ref(initThemeSettings())
/** Optional NaiveUI theme overrides from preset */
const naiveThemeOverrides: Ref<App.Theme.NaiveUIThemeOverride | undefined> = ref(undefined)
/** Watermark time instance with controls */
const { now: watermarkTime, pause: pauseWatermarkTime, resume: resumeWatermarkTime } = useNow({ controls: true })
/** Dark mode */
const darkMode = computed(() => {
if (settings.value.themeScheme === 'auto') {
return osTheme.value === 'dark'
}
return settings.value.themeScheme === 'dark'
})
/** grayscale mode */
const grayscaleMode = computed(() => settings.value.grayscale)
/** colourWeakness mode */
const colourWeaknessMode = computed(() => settings.value.colourWeakness)
/** Theme colors */
const themeColors = computed(() => {
const { themeColor, otherColor, isInfoFollowPrimary } = settings.value
const colors: App.Theme.ThemeColor = {
primary: themeColor,
...otherColor,
info: isInfoFollowPrimary ? themeColor : otherColor.info,
}
return colors
})
/** Naive theme */
const naiveTheme = computed(() => getNaiveTheme(themeColors.value, settings.value, naiveThemeOverrides.value))
/**
* Settings json
*
* It is for copy settings
*/
const settingsJson = computed(() => JSON.stringify(settings.value))
/** Watermark time date formatter */
const formattedWatermarkTime = computed(() => {
const { watermark } = settings.value
const date = useDateFormat(watermarkTime, watermark.timeFormat)
return date.value
})
/** Watermark content */
const watermarkContent = computed(() => {
const { watermark } = settings.value
if (watermark.enableUserName && authStore.userInfo.userName) {
return authStore.userInfo.userName
}
if (watermark.enableTime) {
return formattedWatermarkTime.value
}
return watermark.text
})
/** Reset store */
function resetStore() {
const themeStore = useThemeStore()
themeStore.$reset()
}
/**
* Set theme scheme
*
* @param themeScheme
*/
function setThemeScheme(themeScheme: UnionKey.ThemeScheme) {
settings.value.themeScheme = themeScheme
}
/**
* Set grayscale value
*
* @param isGrayscale
*/
function setGrayscale(isGrayscale: boolean) {
settings.value.grayscale = isGrayscale
}
/**
* Set colourWeakness value
*
* @param isColourWeakness
*/
function setColourWeakness(isColourWeakness: boolean) {
settings.value.colourWeakness = isColourWeakness
}
/** Toggle theme scheme */
function toggleThemeScheme() {
const themeSchemes: UnionKey.ThemeScheme[] = ['light', 'dark', 'auto']
const index = themeSchemes.findIndex(item => item === settings.value.themeScheme)
const nextIndex = index === themeSchemes.length - 1 ? 0 : index + 1
const nextThemeScheme = themeSchemes[nextIndex]
setThemeScheme(nextThemeScheme)
}
/**
* Update theme colors
*
* @param key Theme color key
* @param color Theme color
*/
function updateThemeColors(key: App.Theme.ThemeColorKey, color: string) {
let colorValue = color
if (settings.value.recommendColor) {
// get a color palette by provided color and color name, and use the suitable color
colorValue = getPaletteColorByNumber(color, 500, true)
}
if (key === 'primary') {
settings.value.themeColor = colorValue
}
else {
settings.value.otherColor[key] = colorValue
}
}
/**
* Set theme layout
*
* @param mode Theme layout mode
*/
function setThemeLayout(mode: UnionKey.ThemeLayoutMode) {
settings.value.layout.mode = mode
}
/** Setup theme vars to global */
function setupThemeVarsToGlobal() {
const { themeTokens, darkThemeTokens } = createThemeToken(
themeColors.value,
settings.value.tokens,
settings.value.recommendColor,
)
addThemeVarsToGlobal(themeTokens, darkThemeTokens)
}
/**
* Set watermark enable user name
*
* @param enable Whether to enable user name watermark
*/
function setWatermarkEnableUserName(enable: boolean) {
settings.value.watermark.enableUserName = enable
if (enable) {
settings.value.watermark.enableTime = false
}
}
/**
* Set watermark enable time
*
* @param enable Whether to enable time watermark
*/
function setWatermarkEnableTime(enable: boolean) {
settings.value.watermark.enableTime = enable
if (enable) {
settings.value.watermark.enableUserName = false
}
}
/**
* Set NaiveUI theme overrides
*
* @param overrides NaiveUI theme overrides or undefined to clear
*/
function setNaiveThemeOverrides(overrides?: App.Theme.NaiveUIThemeOverride) {
naiveThemeOverrides.value = overrides
}
/** Only run timer when watermark is visible and time display is enabled */
function updateWatermarkTimer() {
const { watermark } = settings.value
const shouldRunTimer = watermark.visible && watermark.enableTime
if (shouldRunTimer) {
resumeWatermarkTime()
}
else {
pauseWatermarkTime()
}
}
/** Cache theme settings */
function cacheThemeSettings() {
const isProd = import.meta.env.PROD
if (!isProd)
return
localStg.set('themeSettings', settings.value)
}
// cache theme settings when page is closed or refreshed
useEventListener(window, 'beforeunload', () => {
cacheThemeSettings()
})
// watch store
scope.run(() => {
// watch dark mode
watch(
darkMode,
(val) => {
toggleCssDarkMode(val)
localStg.set('darkMode', val)
},
{ immediate: true },
)
watch(
[grayscaleMode, colourWeaknessMode],
(val) => {
toggleAuxiliaryColorModes(val[0], val[1])
},
{ immediate: true },
)
// themeColors change, update css vars and storage theme color
watch(
themeColors,
(val) => {
setupThemeVarsToGlobal()
localStg.set('themeColor', val.primary)
},
{ immediate: true },
)
// watch watermark settings to control timer
watch(
() => [settings.value.watermark.visible, settings.value.watermark.enableTime],
() => {
updateWatermarkTimer()
},
{ immediate: true },
)
})
/** On scope dispose */
onScopeDispose(() => {
scope.stop()
})
return {
...toRefs(settings.value),
darkMode,
themeColors,
naiveTheme,
settingsJson,
watermarkContent,
setGrayscale,
setColourWeakness,
resetStore,
setThemeScheme,
toggleThemeScheme,
updateThemeColors,
setThemeLayout,
setWatermarkEnableUserName,
setWatermarkEnableTime,
setNaiveThemeOverrides,
}
})