227 lines
5.4 KiB
TypeScript
227 lines
5.4 KiB
TypeScript
import { computed, effectScope, onScopeDispose, ref, toRefs, watch } from 'vue';
|
|
import type { Ref } from 'vue';
|
|
import { defineStore } from 'pinia';
|
|
import { useEventListener, usePreferredColorScheme } from '@vueuse/core';
|
|
import { getPaletteColorByNumber } from '@sa/color';
|
|
import { SetupStoreId } from '@/enum';
|
|
import { localStg } from '@/utils/storage';
|
|
import { useWatermark } from '@/hooks/common/watermark';
|
|
import {
|
|
addThemeVarsToHtml,
|
|
createThemeToken,
|
|
getNaiveTheme,
|
|
initThemeSettings,
|
|
toggleCssDarkMode,
|
|
toggleGrayscaleMode
|
|
} from './shared';
|
|
|
|
/** Theme store */
|
|
export const useThemeStore = defineStore(SetupStoreId.Theme, () => {
|
|
const scope = effectScope();
|
|
const osTheme = usePreferredColorScheme();
|
|
|
|
/** Theme settings */
|
|
const settings: Ref<App.Theme.ThemeSetting> = ref(initThemeSettings());
|
|
|
|
/** 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);
|
|
|
|
/** 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.recommendColor));
|
|
|
|
/**
|
|
* Settings json
|
|
*
|
|
* It is for copy settings
|
|
*/
|
|
const settingsJson = computed(() => JSON.stringify(settings.value));
|
|
|
|
/** Watermarks */
|
|
const { setWatermark, clearWatermark } = useWatermark({ id: 'global_watermark_id' });
|
|
|
|
/** 开启水印 */
|
|
function toggleWatermark(visible: boolean = false) {
|
|
visible ? setWatermark(settings.value?.watermark.text) : clearWatermark();
|
|
}
|
|
|
|
/** 修改水印文案 */
|
|
function setWatermarkText(text: string) {
|
|
if (!text) {
|
|
clearWatermark();
|
|
return;
|
|
}
|
|
if (settings.value.watermark && settings.value.watermark?.visible) {
|
|
settings.value.watermark.text = text;
|
|
setWatermark(settings.value.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;
|
|
}
|
|
|
|
/** 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 html */
|
|
function setupThemeVarsToHtml() {
|
|
const { themeTokens, darkThemeTokens } = createThemeToken(themeColors.value, settings.value.recommendColor);
|
|
addThemeVarsToHtml(themeTokens, darkThemeTokens);
|
|
}
|
|
|
|
/** 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);
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
watch(
|
|
grayscaleMode,
|
|
val => {
|
|
toggleGrayscaleMode(val);
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
// themeColors change, update css vars and storage theme color
|
|
watch(
|
|
themeColors,
|
|
val => {
|
|
setupThemeVarsToHtml();
|
|
localStg.set('themeColor', val.primary);
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
|
|
watch(
|
|
settings.value?.watermark,
|
|
val => {
|
|
toggleWatermark(val?.visible);
|
|
setWatermarkText(val?.text);
|
|
},
|
|
{ immediate: true }
|
|
);
|
|
});
|
|
|
|
/** On scope dispose */
|
|
onScopeDispose(() => {
|
|
scope.stop();
|
|
});
|
|
|
|
return {
|
|
...toRefs(settings.value),
|
|
darkMode,
|
|
themeColors,
|
|
naiveTheme,
|
|
settingsJson,
|
|
setGrayscale,
|
|
resetStore,
|
|
setThemeScheme,
|
|
toggleThemeScheme,
|
|
updateThemeColors,
|
|
setThemeLayout,
|
|
setWatermarkText,
|
|
toggleWatermark
|
|
};
|
|
});
|