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