xref: /MusicFree/src/core/config.ts (revision e22d5e4f85cc635e62cd81a872a6b85f645cb2fb)
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;