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