From 6f324a3dcdf02f9832285979d22dcd7fb427a864 Mon Sep 17 00:00:00 2001 From: Lhcfl <Lhcfl@outlook.com> Date: Tue, 23 Apr 2024 16:56:39 +0800 Subject: [PATCH] refactor: move ColdDeviceStorage into a module --- packages/client/src/cold-store.ts | 121 +++++++++++++++++++++++++++++ packages/client/src/store.ts | 116 ++------------------------- packages/client/src/stream.ts | 1 + packages/client/src/theme-store.ts | 3 +- packages/firefish-js/src/api.ts | 20 +++-- 5 files changed, 146 insertions(+), 115 deletions(-) create mode 100644 packages/client/src/cold-store.ts diff --git a/packages/client/src/cold-store.ts b/packages/client/src/cold-store.ts new file mode 100644 index 0000000000..63e7782264 --- /dev/null +++ b/packages/client/src/cold-store.ts @@ -0,0 +1,121 @@ +import { ref as vueRef } from "vue"; +import type { UnwrapRef } from "vue"; + +// TODO: 他のタブと永続化されたstateを同期 + +const PREFIX = "miux:"; + +interface Plugin { + id: string; + name: string; + active: boolean; + configData: Record<string, unknown>; + token: string; + ast: unknown[]; +} + +import darkTheme from "@/themes/d-rosepine.json5"; +/** + * Storage for configuration information that does not need to be constantly loaded into memory (non-reactive) + */ +import lightTheme from "@/themes/l-rosepinedawn.json5"; + +const ColdStoreDefault = { + lightTheme, + darkTheme, + syncDeviceDarkMode: true, + plugins: [] as Plugin[], + mediaVolume: 0.5, + vibrate: false, + sound_masterVolume: 0.3, + sound_note: { type: "none", volume: 0 }, + sound_noteMy: { type: "syuilo/up", volume: 1 }, + sound_notification: { type: "syuilo/pope2", volume: 1 }, + sound_chat: { type: "syuilo/pope1", volume: 1 }, + sound_chatBg: { type: "syuilo/waon", volume: 1 }, + sound_antenna: { type: "syuilo/triple", volume: 1 }, + sound_channel: { type: "syuilo/square-pico", volume: 1 }, +}; + +const watchers: { + key: string; + callback: (value) => void; +}[] = []; + +function get<T extends keyof typeof ColdStoreDefault>( + key: T, +): (typeof ColdStoreDefault)[T] { + // TODO: indexedDBにする + // ただしその際はnullチェックではなくキー存在チェックにしないとダメ + // (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある) + const value = localStorage.getItem(PREFIX + key); + if (value == null) { + return ColdStoreDefault[key]; + } else { + return JSON.parse(value); + } +} + +function set<T extends keyof typeof ColdStoreDefault>( + key: T, + value: (typeof ColdStoreDefault)[T], +): void { + // 呼び出し側のバグ等で undefined が来ることがある + // undefined を文字列として localStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視 + if (value === undefined) { + console.error(`attempt to store undefined value for key '${key}'`); + return; + } + + localStorage.setItem(PREFIX + key, JSON.stringify(value)); + + for (const watcher of watchers) { + if (watcher.key === key) watcher.callback(value); + } +} + +function watch<T extends keyof typeof ColdStoreDefault>( + key: T, + callback: (value: (typeof ColdStoreDefault)[T]) => void, +) { + watchers.push({ key, callback }); +} + +// TODO: VueのcustomRef使うと良い感じになるかも +function ref<T extends keyof typeof ColdStoreDefault>(key: T) { + const v = get(key); + const r = vueRef(v); + // TODO: このままではwatcherがリークするので開放する方法を考える + watch(key, (v) => { + r.value = v as UnwrapRef<typeof v>; + }); + return r; +} + +/** + * 特定のキーの、簡易的なgetter/setterを作ります + * 主にvue場で設定コントロールのmodelとして使う用 + */ +function makeGetterSetter<K extends keyof typeof ColdStoreDefault>(key: K) { + // TODO: VueのcustomRef使うと良い感じになるかも + const valueRef = ref(key); + return { + get: () => { + return valueRef.value; + }, + set: (value: (typeof ColdStoreDefault)[K]) => { + const val = value; + set(key, val); + }, + }; +} + +export default { + default: ColdStoreDefault, + watchers, + get, + set, + watch, + ref, + makeGetterSetter, +}; diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index fed9acc035..8443038435 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -1,9 +1,12 @@ -import { markRaw, ref } from "vue"; +import { markRaw } from "vue"; import type { ApiTypes, entities } from "firefish-js"; import { isSignedIn, me } from "./me"; import { Storage } from "./pizzax"; import type { NoteVisibility } from "@/types/note"; +// biome-ignore lint/suspicious/noExplicitAny: <explanation> +type TODO = any; + export const postFormActions: { title: string; handler: (from, update) => void | Promise<void>; @@ -152,7 +155,7 @@ export const defaultStore = markRaw( type: string; size: "verySmall" | "small" | "medium" | "large" | "veryLarge"; black: boolean; - props: Record<string, any>; + props: Record<string, TODO>; }[], }, widgets: { @@ -161,7 +164,7 @@ export const defaultStore = markRaw( name: string; id: string; place: string | null; - data: Record<string, any>; + data: Record<string, TODO>; }[], }, tl: { @@ -453,109 +456,6 @@ export const defaultStore = markRaw( }), ); -// TODO: 他のタブと永続化されたstateを同期 +import ColdStore from "./cold-store"; -const PREFIX = "miux:"; - -interface Plugin { - id: string; - name: string; - active: boolean; - configData: Record<string, any>; - token: string; - ast: any[]; -} - -import darkTheme from "@/themes/d-rosepine.json5"; -/** - * Storage for configuration information that does not need to be constantly loaded into memory (non-reactive) - */ -import lightTheme from "@/themes/l-rosepinedawn.json5"; - -export class ColdDeviceStorage { - public static default = { - lightTheme, - darkTheme, - syncDeviceDarkMode: true, - plugins: [] as Plugin[], - mediaVolume: 0.5, - vibrate: false, - sound_masterVolume: 0.3, - sound_note: { type: "none", volume: 0 }, - sound_noteMy: { type: "syuilo/up", volume: 1 }, - sound_notification: { type: "syuilo/pope2", volume: 1 }, - sound_chat: { type: "syuilo/pope1", volume: 1 }, - sound_chatBg: { type: "syuilo/waon", volume: 1 }, - sound_antenna: { type: "syuilo/triple", volume: 1 }, - sound_channel: { type: "syuilo/square-pico", volume: 1 }, - }; - - public static watchers = []; - - public static get<T extends keyof typeof ColdDeviceStorage.default>( - key: T, - ): (typeof ColdDeviceStorage.default)[T] { - // TODO: indexedDBにする - // ただしその際はnullチェックではなくキー存在チェックにしないとダメ - // (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある) - const value = localStorage.getItem(PREFIX + key); - if (value == null) { - return ColdDeviceStorage.default[key]; - } else { - return JSON.parse(value); - } - } - - public static set<T extends keyof typeof ColdDeviceStorage.default>( - key: T, - value: (typeof ColdDeviceStorage.default)[T], - ): void { - // 呼び出し側のバグ等で undefined が来ることがある - // undefined を文字列として localStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視 - if (value === undefined) { - console.error(`attempt to store undefined value for key '${key}'`); - return; - } - - localStorage.setItem(PREFIX + key, JSON.stringify(value)); - - for (const watcher of this.watchers) { - if (watcher.key === key) watcher.callback(value); - } - } - - public static watch(key, callback) { - this.watchers.push({ key, callback }); - } - - // TODO: VueのcustomRef使うと良い感じになるかも - public static ref<T extends keyof typeof ColdDeviceStorage.default>(key: T) { - const v = ColdDeviceStorage.get(key); - const r = ref(v); - // TODO: このままではwatcherがリークするので開放する方法を考える - this.watch(key, (v) => { - r.value = v; - }); - return r; - } - - /** - * 特定のキーの、簡易的なgetter/setterを作ります - * 主にvue場で設定コントロールのmodelとして使う用 - */ - public static makeGetterSetter< - K extends keyof typeof ColdDeviceStorage.default, - >(key: K) { - // TODO: VueのcustomRef使うと良い感じになるかも - const valueRef = ColdDeviceStorage.ref(key); - return { - get: () => { - return valueRef.value; - }, - set: (value: unknown) => { - const val = value; - ColdDeviceStorage.set(key, val); - }, - }; - } -} +export const ColdDeviceStorage = ColdStore; diff --git a/packages/client/src/stream.ts b/packages/client/src/stream.ts index 89dda63f08..b620c60c9d 100644 --- a/packages/client/src/stream.ts +++ b/packages/client/src/stream.ts @@ -31,6 +31,7 @@ export function reloadStream() { isReloading = true; stream.close(); + // biome-ignore lint/suspicious/noAssignInExpressions: assign intentionally stream.once("_connected_", () => (isReloading = false)); stream.stream.reconnect(); isReloading = false; diff --git a/packages/client/src/theme-store.ts b/packages/client/src/theme-store.ts index bc344b339d..6f4616878f 100644 --- a/packages/client/src/theme-store.ts +++ b/packages/client/src/theme-store.ts @@ -17,7 +17,8 @@ export async function fetchThemes(): Promise<void> { key: "themes", }); localStorage.setItem(lsCacheKey, JSON.stringify(themes)); - } catch (err) { + // biome-ignore lint/suspicious/noExplicitAny: Safely any + } catch (err: any) { if (err.code === "NO_SUCH_KEY") return; throw err; } diff --git a/packages/firefish-js/src/api.ts b/packages/firefish-js/src/api.ts index 4a7e1f0b64..3cfdfedbfe 100644 --- a/packages/firefish-js/src/api.ts +++ b/packages/firefish-js/src/api.ts @@ -7,10 +7,12 @@ export type APIError = { code: string; message: string; kind: "client" | "server"; - info: Record<string, any>; + info: Record<string, unknown>; }; -export function isAPIError(reason: any): reason is APIError { +// biome-ignore lint/suspicious/noExplicitAny: used it intentially +type ExplicitlyUsedAny = any; +export function isAPIError(reason: ExplicitlyUsedAny): reason is APIError { return reason[MK_API_ERROR] === true; } @@ -24,7 +26,7 @@ export type FetchLike = ( }, ) => Promise<{ status: number; - json(): Promise<any>; + json(): Promise<ExplicitlyUsedAny>; }>; type IsNeverType<T> = [T] extends [never] ? true : false; @@ -36,7 +38,10 @@ type IsCaseMatched< P extends Endpoints[E]["req"], C extends number, > = IsNeverType< - StrictExtract<Endpoints[E]["res"]["$switch"]["$cases"][C], [P, any]> + StrictExtract< + Endpoints[E]["res"]["$switch"]["$cases"][C], + [P, ExplicitlyUsedAny] + > > extends false ? true : false; @@ -45,7 +50,10 @@ type GetCaseResult< E extends keyof Endpoints, P extends Endpoints[E]["req"], C extends number, -> = StrictExtract<Endpoints[E]["res"]["$switch"]["$cases"][C], [P, any]>[1]; +> = StrictExtract< + Endpoints[E]["res"]["$switch"]["$cases"][C], + [P, ExplicitlyUsedAny] +>[1]; export class APIClient { public origin: string; @@ -70,7 +78,7 @@ export class APIClient { credential?: string | null | undefined, ): Promise< Endpoints[E]["res"] extends { - $switch: { $cases: [any, any][]; $default: any }; + $switch: { $cases: [unknown, unknown][]; $default: unknown }; } ? IsCaseMatched<E, P, 0> extends true ? GetCaseResult<E, P, 0>