1*e22d5e4fS猫头猫import {getStorage, setStorage} from '@/utils/storage'; 2*e22d5e4fS猫头猫import produce from 'immer'; 3*e22d5e4fS猫头猫import {useEffect, useState} from 'react'; 4*e22d5e4fS猫头猫 5*e22d5e4fS猫头猫type ExceptionType = IMusic.IMusicItem | IMusic.IMusicItem[]; 6*e22d5e4fS猫头猫interface IConfig { 7*e22d5e4fS猫头猫 setting: { 8*e22d5e4fS猫头猫 basic: { 9*e22d5e4fS猫头猫 /** 最大同时下载 */ 10*e22d5e4fS猫头猫 maxDownload: number | string; 11*e22d5e4fS猫头猫 /** 同时播放 */ 12*e22d5e4fS猫头猫 notInterrupt: boolean; 13*e22d5e4fS猫头猫 /** 播放错误时自动停止 */ 14*e22d5e4fS猫头猫 autoStopWhenError: boolean; 15*e22d5e4fS猫头猫 16*e22d5e4fS猫头猫 debug: { 17*e22d5e4fS猫头猫 errorLog: boolean; 18*e22d5e4fS猫头猫 traceLog: boolean; 19*e22d5e4fS猫头猫 }; 20*e22d5e4fS猫头猫 }; 21*e22d5e4fS猫头猫 22*e22d5e4fS猫头猫 /** 主题 */ 23*e22d5e4fS猫头猫 theme: { 24*e22d5e4fS猫头猫 mode: 'light' | 'dark' | 'custom-light' | 'custom-dark'; 25*e22d5e4fS猫头猫 background: string; 26*e22d5e4fS猫头猫 backgroundOpacity: number; 27*e22d5e4fS猫头猫 backgroundBlur: number; 28*e22d5e4fS猫头猫 colors: { 29*e22d5e4fS猫头猫 primary: string; 30*e22d5e4fS猫头猫 secondary: string; 31*e22d5e4fS猫头猫 textHighlight: string; 32*e22d5e4fS猫头猫 pageBackground: string; 33*e22d5e4fS猫头猫 }; 34*e22d5e4fS猫头猫 }; 35*e22d5e4fS猫头猫 }; 36*e22d5e4fS猫头猫 status: { 37*e22d5e4fS猫头猫 music: { 38*e22d5e4fS猫头猫 /** 当前的音乐 */ 39*e22d5e4fS猫头猫 track: IMusic.IMusicItem; 40*e22d5e4fS猫头猫 /** 进度 */ 41*e22d5e4fS猫头猫 progress: number; 42*e22d5e4fS猫头猫 /** 模式 */ 43*e22d5e4fS猫头猫 repeatMode: string; 44*e22d5e4fS猫头猫 /** 列表 */ 45*e22d5e4fS猫头猫 musicQueue: IMusic.IMusicItem[]; 46*e22d5e4fS猫头猫 }; 47*e22d5e4fS猫头猫 }; 48*e22d5e4fS猫头猫} 49*e22d5e4fS猫头猫 50*e22d5e4fS猫头猫type FilterType<T, R = never> = T extends Record<string | number, any> 51*e22d5e4fS猫头猫 ? { 52*e22d5e4fS猫头猫 [P in keyof T]: T[P] extends ExceptionType ? R : T[P]; 53*e22d5e4fS猫头猫 } 54*e22d5e4fS猫头猫 : never; 55*e22d5e4fS猫头猫 56*e22d5e4fS猫头猫type KeyPaths< 57*e22d5e4fS猫头猫 T extends object, 58*e22d5e4fS猫头猫 Root extends boolean = true, 59*e22d5e4fS猫头猫 R = FilterType<T, ''>, 60*e22d5e4fS猫头猫 K extends keyof R = keyof R, 61*e22d5e4fS猫头猫> = K extends string | number 62*e22d5e4fS猫头猫 ? 63*e22d5e4fS猫头猫 | (Root extends true ? `${K}` : `.${K}`) 64*e22d5e4fS猫头猫 | (R[K] extends Record<string | number, any> 65*e22d5e4fS猫头猫 ? `${Root extends true ? `${K}` : `.${K}`}${KeyPaths<R[K], false>}` 66*e22d5e4fS猫头猫 : never) 67*e22d5e4fS猫头猫 : never; 68*e22d5e4fS猫头猫 69*e22d5e4fS猫头猫type KeyPathValue<T extends object, K extends string> = T extends Record< 70*e22d5e4fS猫头猫 string | number, 71*e22d5e4fS猫头猫 any 72*e22d5e4fS猫头猫> 73*e22d5e4fS猫头猫 ? K extends `${infer S}.${infer R}` 74*e22d5e4fS猫头猫 ? KeyPathValue<T[S], R> 75*e22d5e4fS猫头猫 : T[K] 76*e22d5e4fS猫头猫 : never; 77*e22d5e4fS猫头猫 78*e22d5e4fS猫头猫type KeyPathsObj< 79*e22d5e4fS猫头猫 T extends object, 80*e22d5e4fS猫头猫 K extends string = KeyPaths<T>, 81*e22d5e4fS猫头猫> = T extends Record<string | number, any> 82*e22d5e4fS猫头猫 ? { 83*e22d5e4fS猫头猫 [R in K]: KeyPathValue<T, R>; 84*e22d5e4fS猫头猫 } 85*e22d5e4fS猫头猫 : never; 86*e22d5e4fS猫头猫 87*e22d5e4fS猫头猫type DeepPartial<T> = { 88*e22d5e4fS猫头猫 [K in keyof T]?: T[K] extends Record<string | number, any> 89*e22d5e4fS猫头猫 ? T[K] extends ExceptionType 90*e22d5e4fS猫头猫 ? T[K] 91*e22d5e4fS猫头猫 : DeepPartial<T[K]> 92*e22d5e4fS猫头猫 : T[K]; 93*e22d5e4fS猫头猫}; 94*e22d5e4fS猫头猫 95*e22d5e4fS猫头猫type IConfigPaths = KeyPaths<IConfig>; 96*e22d5e4fS猫头猫type PartialConfig = DeepPartial<IConfig> | null; 97*e22d5e4fS猫头猫type IConfigPathsObj = KeyPathsObj<DeepPartial<IConfig>, IConfigPaths>; 98*e22d5e4fS猫头猫 99*e22d5e4fS猫头猫let config: PartialConfig = null; 100*e22d5e4fS猫头猫/** 初始化config */ 101*e22d5e4fS猫头猫async function loadConfig() { 102*e22d5e4fS猫头猫 config = (await getStorage('local-config')) ?? {}; 103*e22d5e4fS猫头猫 // await checkValidPath(['setting.theme.background']); 104*e22d5e4fS猫头猫 notify(); 105*e22d5e4fS猫头猫} 106*e22d5e4fS猫头猫 107*e22d5e4fS猫头猫/** 设置config */ 108*e22d5e4fS猫头猫async function setConfig<T extends IConfigPaths>( 109*e22d5e4fS猫头猫 key: T, 110*e22d5e4fS猫头猫 value: IConfigPathsObj[T], 111*e22d5e4fS猫头猫 shouldNotify = true, 112*e22d5e4fS猫头猫) { 113*e22d5e4fS猫头猫 if (config === null) { 114*e22d5e4fS猫头猫 return; 115*e22d5e4fS猫头猫 } 116*e22d5e4fS猫头猫 const keys = key.split('.'); 117*e22d5e4fS猫头猫 118*e22d5e4fS猫头猫 const result = produce(config, draft => { 119*e22d5e4fS猫头猫 draft[keys[0] as keyof IConfig] = draft[keys[0] as keyof IConfig] ?? {}; 120*e22d5e4fS猫头猫 let conf: any = draft[keys[0] as keyof IConfig]; 121*e22d5e4fS猫头猫 for (let i = 1; i < keys.length - 1; ++i) { 122*e22d5e4fS猫头猫 if (!conf?.[keys[i]]) { 123*e22d5e4fS猫头猫 conf[keys[i]] = {}; 124*e22d5e4fS猫头猫 } 125*e22d5e4fS猫头猫 conf = conf[keys[i]]; 126*e22d5e4fS猫头猫 } 127*e22d5e4fS猫头猫 conf[keys[keys.length - 1]] = value; 128*e22d5e4fS猫头猫 return draft; 129*e22d5e4fS猫头猫 }); 130*e22d5e4fS猫头猫 131*e22d5e4fS猫头猫 setStorage('local-config', result); 132*e22d5e4fS猫头猫 config = result; 133*e22d5e4fS猫头猫 if (shouldNotify) { 134*e22d5e4fS猫头猫 notify(); 135*e22d5e4fS猫头猫 } 136*e22d5e4fS猫头猫} 137*e22d5e4fS猫头猫 138*e22d5e4fS猫头猫/** 获取config */ 139*e22d5e4fS猫头猫function getConfig(): PartialConfig; 140*e22d5e4fS猫头猫function getConfig<T extends IConfigPaths>(key: T): IConfigPathsObj[T]; 141*e22d5e4fS猫头猫function getConfig(key?: string) { 142*e22d5e4fS猫头猫 let result: any = config; 143*e22d5e4fS猫头猫 if (key && config) { 144*e22d5e4fS猫头猫 result = getPathValue(config, key); 145*e22d5e4fS猫头猫 } 146*e22d5e4fS猫头猫 147*e22d5e4fS猫头猫 return result; 148*e22d5e4fS猫头猫} 149*e22d5e4fS猫头猫 150*e22d5e4fS猫头猫/** 通过path获取值 */ 151*e22d5e4fS猫头猫function getPathValue(obj: Record<string, any>, path: string) { 152*e22d5e4fS猫头猫 const keys = path.split('.'); 153*e22d5e4fS猫头猫 let tmp = obj; 154*e22d5e4fS猫头猫 for (let i = 0; i < keys.length; ++i) { 155*e22d5e4fS猫头猫 tmp = tmp?.[keys[i]]; 156*e22d5e4fS猫头猫 } 157*e22d5e4fS猫头猫 return tmp; 158*e22d5e4fS猫头猫} 159*e22d5e4fS猫头猫 160*e22d5e4fS猫头猫/** 同步hook */ 161*e22d5e4fS猫头猫const notifyCbs = new Set<() => void>(); 162*e22d5e4fS猫头猫function notify() { 163*e22d5e4fS猫头猫 notifyCbs.forEach(_ => _?.()); 164*e22d5e4fS猫头猫} 165*e22d5e4fS猫头猫 166*e22d5e4fS猫头猫/** hook */ 167*e22d5e4fS猫头猫function useConfig(): PartialConfig; 168*e22d5e4fS猫头猫function useConfig<T extends IConfigPaths>(key: T): IConfigPathsObj[T]; 169*e22d5e4fS猫头猫function useConfig(key?: string) { 170*e22d5e4fS猫头猫 const [_cfg, _setCfg] = useState<PartialConfig>(config); 171*e22d5e4fS猫头猫 function setCfg() { 172*e22d5e4fS猫头猫 _setCfg(config); 173*e22d5e4fS猫头猫 } 174*e22d5e4fS猫头猫 useEffect(() => { 175*e22d5e4fS猫头猫 notifyCbs.add(setCfg); 176*e22d5e4fS猫头猫 return () => { 177*e22d5e4fS猫头猫 notifyCbs.delete(setCfg); 178*e22d5e4fS猫头猫 }; 179*e22d5e4fS猫头猫 }, []); 180*e22d5e4fS猫头猫 181*e22d5e4fS猫头猫 if (key) { 182*e22d5e4fS猫头猫 return _cfg ? getPathValue(_cfg, key) : undefined; 183*e22d5e4fS猫头猫 } else { 184*e22d5e4fS猫头猫 return _cfg; 185*e22d5e4fS猫头猫 } 186*e22d5e4fS猫头猫} 187*e22d5e4fS猫头猫 188*e22d5e4fS猫头猫const Config = { 189*e22d5e4fS猫头猫 get: getConfig, 190*e22d5e4fS猫头猫 set: setConfig, 191*e22d5e4fS猫头猫 useConfig, 192*e22d5e4fS猫头猫 setup: loadConfig, 193*e22d5e4fS猫头猫}; 194*e22d5e4fS猫头猫 195*e22d5e4fS猫头猫export default Config;