diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index c34e0bbf48..6be672c861 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -3,31 +3,31 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { markRaw, ref } from 'vue'; -import * as Misskey from 'misskey-js'; -import { hemisphere } from '@@/js/intl-const.js'; -import lightTheme from '@@/themes/l-cherry.json5'; -import darkTheme from '@@/themes/d-ice.json5'; -import { searchEngineMap } from './scripts/search-engine-map.js'; -import type { SoundType } from '@/scripts/sound.js'; -import { DEFAULT_DEVICE_KIND, type DeviceKind } from '@/scripts/device-kind.js'; -import { miLocalStorage } from '@/local-storage.js'; -import { defaultFollowingFeedState } from '@/scripts/following-feed-utils.js'; -import { Storage } from '@/pizzax.js'; -import type { Ast } from '@syuilo/aiscript'; +import { markRaw, ref } from "vue"; +import * as Misskey from "misskey-js"; +import { hemisphere } from "@@/js/intl-const.js"; +import lightTheme from "@@/themes/l-cherry.json5"; +import darkTheme from "@@/themes/d-ice.json5"; +import { searchEngineMap } from "./scripts/search-engine-map.js"; +import type { SoundType } from "@/scripts/sound.js"; +import { DEFAULT_DEVICE_KIND, type DeviceKind } from "@/scripts/device-kind.js"; +import { miLocalStorage } from "@/local-storage.js"; +import { defaultFollowingFeedState } from "@/scripts/following-feed-utils.js"; +import { Storage } from "@/pizzax.js"; +import type { Ast } from "@syuilo/aiscript"; interface PostFormAction { - title: string, + title: string; handler: (form: T, update: (key: unknown, value: unknown) => void) => void; } interface UserAction { - title: string, + title: string; handler: (user: Misskey.entities.UserDetailed) => void; } interface NoteAction { - title: string, + title: string; handler: (note: Misskey.entities.Note) => void; } @@ -44,20 +44,22 @@ interface PageViewInterruptor { } /** サウンド設定 */ -export type SoundStore = { - type: Exclude; - volume: number; -} | { - type: '_driveFile_'; +export type SoundStore = + | { + type: Exclude; + volume: number; + } + | { + type: "_driveFile_"; - /** ドライブのファイルID */ - fileId: string; + /** ドライブのファイルID */ + fileId: string; - /** ファイルURL(こちらが優先される) */ - fileUrl: string; + /** ファイルURL(こちらが優先される) */ + fileUrl: string; - volume: number; -} + volume: number; + }; export const postFormActions: PostFormAction[] = []; export const userActions: UserAction[] = []; @@ -68,533 +70,550 @@ export const pageViewInterruptors: PageViewInterruptor[] = []; // TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう) // あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない -export const defaultStore = markRaw(new Storage('base', { - accountSetupWizard: { - where: 'account', - default: 0, - }, - timelineTutorials: { - where: 'account', - default: { - home: false, - local: false, - social: false, - global: false, +export const defaultStore = markRaw( + new Storage("base", { + accountSetupWizard: { + where: "account", + default: 0, }, - }, - abusesTutorial: { - where: 'account', - default: false, - }, - keepCw: { - where: 'account', - default: true, - }, - showFullAcct: { - where: 'account', - default: false, - }, - collapseRenotes: { - where: 'account', - default: false, - }, - collapseNotesRepliedTo: { - where: 'account', - default: false, - }, - collapseFiles: { - where: 'account', - default: false, - }, - uncollapseCW: { - where: 'account', - default: false, - }, - expandLongNote: { - where: 'device', - default: false, - }, - rememberNoteVisibility: { - where: 'account', - default: false, - }, - defaultNoteVisibility: { - where: 'account', - default: 'public' as (typeof Misskey.noteVisibilities)[number], - }, - defaultNoteLocalOnly: { - where: 'account', - default: false, - }, - uploadFolder: { - where: 'account', - default: null as string | null, - }, - pastedFileName: { - where: 'account', - default: 'yyyy-MM-dd HH-mm-ss [{{number}}]', - }, - keepOriginalUploading: { - where: 'account', - default: false, - }, - memo: { - where: 'account', - default: null, - }, - reactions: { - where: 'account', - default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'], - }, - pinnedEmojis: { - where: 'account', - default: [], - }, - reactionAcceptance: { - where: 'account', - default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null, - }, - like: { - where: 'account', - default: null as string | null, - }, - mutedAds: { - where: 'account', - default: [] as string[], - }, - autoloadConversation: { - where: 'account', - default: true, - }, - showVisibilitySelectorOnBoost: { - where: 'account', - default: true, - }, - visibilityOnBoost: { - where: 'account', - default: 'public' as 'public' | 'home' | 'followers', - }, - trustedDomains: { - where: 'account', - default: [] as string[], - }, - warnExternalUrl: { - where: 'account', - default: true, - }, - - menu: { - where: 'deviceAccount', - default: [ - 'notifications', - 'explore', - 'followRequests', - '-', - 'announcements', - 'search', - '-', - 'favorites', - 'drive', - 'achievements', - ], - }, - visibility: { - where: 'deviceAccount', - default: 'public' as (typeof Misskey.noteVisibilities)[number], - }, - localOnly: { - where: 'deviceAccount', - default: false, - }, - showPreview: { - where: 'device', - default: false, - }, - statusbars: { - where: 'deviceAccount', - default: [] as { - name: string; - id: string; - type: string; - size: 'verySmall' | 'small' | 'medium' | 'large' | 'veryLarge'; - black: boolean; - props: Record; - }[], - }, - widgets: { - where: 'account', - default: [] as { - name: string; - id: string; - place: string | null; - data: Record; - }[], - }, - tl: { - where: 'deviceAccount', - default: { - src: 'home' as 'home' | 'local' | 'social' | 'global' | 'bubble' | `list:${string}`, - userList: null as Misskey.entities.UserList | null, - filter: { - withReplies: true, - withRenotes: true, - withBots: true, - withSensitive: true, - onlyFiles: false, + timelineTutorials: { + where: "account", + default: { + home: false, + local: false, + social: false, + global: false, }, }, - }, - pinnedUserLists: { - where: 'deviceAccount', - default: [] as Misskey.entities.UserList[], - }, - followingFeed: { - where: 'account', - default: defaultFollowingFeedState, - }, - - overridedDeviceKind: { - where: 'device', - default: null as DeviceKind | null, - }, - serverDisconnectedBehavior: { - where: 'device', - default: 'disabled' as 'quiet' | 'dialog' | 'disabled', - }, - nsfw: { - where: 'device', - default: 'respect' as 'respect' | 'force' | 'ignore', - }, - highlightSensitiveMedia: { - where: 'device', - default: false, - }, - animation: { - where: 'device', - default: !window.matchMedia('(prefers-reduced-motion)').matches, - }, - animatedMfm: { - where: 'device', - default: !window.matchMedia('(prefers-reduced-motion)').matches, - }, - advancedMfm: { - where: 'device', - default: true, - }, - showReactionsCount: { - where: 'device', - default: false, - }, - enableQuickAddMfmFunction: { - where: 'device', - default: false, - }, - loadRawImages: { - where: 'device', - default: false, - }, - warnMissingAltText: { - where: 'device', - default: true, - }, - enableFaviconNotificationDot: { - where: 'device', - default: true, - }, - imageNewTab: { - where: 'device', - default: false, - }, - disableShowingAnimatedImages: { - where: 'device', - default: window.matchMedia('(prefers-reduced-motion)').matches, - }, - disableCatSpeak: { - where: 'account', - default: false, - }, - emojiStyle: { - where: 'device', - default: 'twemoji', // twemoji / fluentEmoji / native - }, - menuStyle: { - where: 'device', - default: 'auto' as 'auto' | 'popup' | 'drawer', - }, - useBlurEffectForModal: { - where: 'device', - default: DEFAULT_DEVICE_KIND === 'desktop', - }, - useBlurEffect: { - where: 'device', - default: DEFAULT_DEVICE_KIND === 'desktop', - }, - showFixedPostForm: { - where: 'device', - default: false, - }, - showFixedPostFormInChannel: { - where: 'device', - default: false, - }, - showTickerOnReplies: { - where: 'device', - default: false, - }, - searchEngine: { - where: 'account', - default: Object.keys(searchEngineMap)[0], - }, - noteDesign: { - where: 'device', - default: 'sharkey' as 'sharkey' | 'misskey', - }, - enableInfiniteScroll: { - where: 'device', - default: true, - }, - useReactionPickerForContextMenu: { - where: 'device', - default: false, - }, - showGapBetweenNotesInTimeline: { - where: 'device', - default: false, - }, - darkMode: { - where: 'device', - default: false, - }, - instanceTicker: { - where: 'device', - default: 'remote' as 'none' | 'remote' | 'always', - }, - emojiPickerScale: { - where: 'device', - default: 1, - }, - emojiPickerWidth: { - where: 'device', - default: 1, - }, - emojiPickerHeight: { - where: 'device', - default: 2, - }, - emojiPickerStyle: { - where: 'device', - default: 'auto' as 'auto' | 'popup' | 'drawer', - }, - recentlyUsedEmojis: { - where: 'device', - default: [] as string[], - }, - recentlyUsedUsers: { - where: 'device', - default: [] as string[], - }, - defaultSideView: { - where: 'device', - default: false, - }, - menuDisplay: { - where: 'device', - default: 'sideFull' as 'sideFull' | 'sideIcon' | 'top', - }, - reportError: { - where: 'device', - default: false, - }, - squareAvatars: { - where: 'device', - default: true, - }, - showAvatarDecorations: { - where: 'device', - default: true, - }, - postFormWithHashtags: { - where: 'device', - default: false, - }, - postFormHashtags: { - where: 'device', - default: '', - }, - themeInitial: { - where: 'device', - default: true, - }, - numberOfPageCache: { - where: 'device', - default: 3, - }, - numberOfReplies: { - where: 'device', - default: 5, - }, - showNoteActionsOnlyHover: { - where: 'device', - default: false, - }, - showClipButtonInNoteFooter: { - where: 'device', - default: false, - }, - reactionsDisplaySize: { - where: 'device', - default: 'medium' as 'small' | 'medium' | 'large', - }, - limitWidthOfReaction: { - where: 'device', - default: true, - }, - forceShowAds: { - where: 'device', - default: false, - }, - oneko: { - where: 'device', - default: false, - }, - clickToOpen: { - where: 'device', - default: true, - }, - aiChanMode: { - where: 'device', - default: false, - }, - devMode: { - where: 'device', - default: false, - }, - mediaListWithOneImageAppearance: { - where: 'device', - default: 'expand' as 'expand' | '16_9' | '1_1' | '2_3', - }, - notificationPosition: { - where: 'device', - default: 'rightBottom' as 'leftTop' | 'leftBottom' | 'rightTop' | 'rightBottom', - }, - notificationStackAxis: { - where: 'device', - default: 'horizontal' as 'vertical' | 'horizontal', - }, - notificationClickable: { - where: 'device', - default: false, - }, - enableCondensedLine: { - where: 'device', - default: true, - }, - additionalUnicodeEmojiIndexes: { - where: 'device', - default: {} as Record>, - }, - keepScreenOn: { - where: 'device', - default: false, - }, - defaultWithReplies: { - where: 'account', - default: false, - }, - disableStreamingTimeline: { - where: 'device', - default: false, - }, - useGroupedNotifications: { - where: 'device', - default: true, - }, - dataSaver: { - where: 'device', - default: { - media: false, - avatar: false, - urlPreview: false, - code: false, - } as Record, - }, - enableSeasonalScreenEffect: { - where: 'device', - default: false, - }, - dropAndFusion: { - where: 'device', - default: { - bgmVolume: 0.25, - sfxVolume: 1, + abusesTutorial: { + where: "account", + default: false, + }, + keepCw: { + where: "account", + default: true, + }, + showFullAcct: { + where: "account", + default: false, + }, + collapseRenotes: { + where: "account", + default: false, + }, + collapseNotesRepliedTo: { + where: "account", + default: false, + }, + collapseFiles: { + where: "account", + default: false, + }, + uncollapseCW: { + where: "account", + default: false, + }, + expandLongNote: { + where: "device", + default: false, + }, + rememberNoteVisibility: { + where: "account", + default: false, + }, + defaultNoteVisibility: { + where: "account", + default: "public" as (typeof Misskey.noteVisibilities)[number], + }, + defaultNoteLocalOnly: { + where: "account", + default: false, + }, + uploadFolder: { + where: "account", + default: null as string | null, + }, + pastedFileName: { + where: "account", + default: "yyyy-MM-dd HH-mm-ss [{{number}}]", + }, + keepOriginalUploading: { + where: "account", + default: false, + }, + memo: { + where: "account", + default: null, + }, + reactions: { + where: "account", + default: ["👍", "❤", "😆", "🤔", "😮", "🎉", "💢", "😥", "😇", "🍮"], + }, + pinnedEmojis: { + where: "account", + default: [], + }, + reactionAcceptance: { + where: "account", + default: "nonSensitiveOnly" as + | "likeOnly" + | "likeOnlyForRemote" + | "nonSensitiveOnly" + | "nonSensitiveOnlyForLocalLikeOnlyForRemote" + | null, + }, + like: { + where: "account", + default: null as string | null, + }, + mutedAds: { + where: "account", + default: [] as string[], + }, + autoloadConversation: { + where: "account", + default: true, + }, + showVisibilitySelectorOnBoost: { + where: "account", + default: true, + }, + visibilityOnBoost: { + where: "account", + default: "public" as "public" | "home" | "followers", + }, + trustedDomains: { + where: "account", + default: [] as string[], + }, + warnExternalUrl: { + where: "account", + default: true, }, - }, - hemisphere: { - where: 'device', - default: hemisphere as 'N' | 'S', - }, - enableHorizontalSwipe: { - where: 'device', - default: true, - }, - useNativeUIForVideoAudioPlayer: { - where: 'device', - default: false, - }, - keepOriginalFilename: { - where: 'device', - default: true, - }, - alwaysConfirmFollow: { - where: 'device', - default: true, - }, - confirmWhenRevealingSensitiveMedia: { - where: 'device', - default: false, - }, - contextMenu: { - where: 'device', - default: 'app' as 'app' | 'appWithShift' | 'native', - }, - skipNoteRender: { - where: 'device', - default: true, - }, - sound_masterVolume: { - where: 'device', - default: 0.3, - }, - sound_notUseSound: { - where: 'device', - default: false, - }, - sound_useSoundOnlyWhenActive: { - where: 'device', - default: false, - }, - sound_note: { - where: 'device', - default: { type: 'syuilo/n-aec', volume: 0 } as SoundStore, - }, - sound_noteMy: { - where: 'device', - default: { type: 'syuilo/n-cea-4va', volume: 1 } as SoundStore, - }, - sound_notification: { - where: 'device', - default: { type: 'syuilo/n-ea', volume: 1 } as SoundStore, - }, - sound_reaction: { - where: 'device', - default: { type: 'syuilo/bubble2', volume: 1 } as SoundStore, - }, -})); + menu: { + where: "deviceAccount", + default: [ + "notifications", + "explore", + "followRequests", + "-", + "announcements", + "search", + "-", + "favorites", + "drive", + "achievements", + ], + }, + visibility: { + where: "deviceAccount", + default: "public" as (typeof Misskey.noteVisibilities)[number], + }, + localOnly: { + where: "deviceAccount", + default: false, + }, + showPreview: { + where: "device", + default: false, + }, + statusbars: { + where: "deviceAccount", + default: [] as { + name: string; + id: string; + type: string; + size: "verySmall" | "small" | "medium" | "large" | "veryLarge"; + black: boolean; + props: Record; + }[], + }, + widgets: { + where: "account", + default: [] as { + name: string; + id: string; + place: string | null; + data: Record; + }[], + }, + tl: { + where: "deviceAccount", + default: { + src: "home" as + | "home" + | "local" + | "social" + | "global" + | "bubble" + | `list:${string}`, + userList: null as Misskey.entities.UserList | null, + filter: { + withReplies: true, + withRenotes: true, + withBots: true, + withSensitive: true, + onlyFiles: false, + }, + }, + }, + pinnedUserLists: { + where: "deviceAccount", + default: [] as Misskey.entities.UserList[], + }, + followingFeed: { + where: "account", + default: defaultFollowingFeedState, + }, + + overridedDeviceKind: { + where: "device", + default: null as DeviceKind | null, + }, + serverDisconnectedBehavior: { + where: "device", + default: "disabled" as "quiet" | "dialog" | "disabled", + }, + nsfw: { + where: "device", + default: "respect" as "respect" | "force" | "ignore", + }, + highlightSensitiveMedia: { + where: "device", + default: false, + }, + animation: { + where: "device", + default: !window.matchMedia("(prefers-reduced-motion)").matches, + }, + animatedMfm: { + where: "device", + default: !window.matchMedia("(prefers-reduced-motion)").matches, + }, + advancedMfm: { + where: "device", + default: true, + }, + showReactionsCount: { + where: "device", + default: false, + }, + enableQuickAddMfmFunction: { + where: "device", + default: false, + }, + loadRawImages: { + where: "device", + default: false, + }, + warnMissingAltText: { + where: "device", + default: true, + }, + enableFaviconNotificationDot: { + where: "device", + default: true, + }, + imageNewTab: { + where: "device", + default: false, + }, + disableShowingAnimatedImages: { + where: "device", + default: window.matchMedia("(prefers-reduced-motion)").matches, + }, + disableCatSpeak: { + where: "account", + default: false, + }, + emojiStyle: { + where: "device", + default: "twemoji", // twemoji / fluentEmoji / native + }, + menuStyle: { + where: "device", + default: "auto" as "auto" | "popup" | "drawer", + }, + useBlurEffectForModal: { + where: "device", + default: DEFAULT_DEVICE_KIND === "desktop", + }, + useBlurEffect: { + where: "device", + default: DEFAULT_DEVICE_KIND === "desktop", + }, + showFixedPostForm: { + where: "device", + default: false, + }, + showFixedPostFormInChannel: { + where: "device", + default: false, + }, + showTickerOnReplies: { + where: "device", + default: false, + }, + searchEngine: { + where: "account", + default: Object.keys(searchEngineMap)[0], + }, + noteDesign: { + where: "device", + default: "sharkey" as "sharkey" | "misskey", + }, + enableInfiniteScroll: { + where: "device", + default: true, + }, + useReactionPickerForContextMenu: { + where: "device", + default: false, + }, + showGapBetweenNotesInTimeline: { + where: "device", + default: false, + }, + darkMode: { + where: "device", + default: false, + }, + instanceTicker: { + where: "device", + default: "remote" as "none" | "remote" | "always", + }, + emojiPickerScale: { + where: "device", + default: 1, + }, + emojiPickerWidth: { + where: "device", + default: 1, + }, + emojiPickerHeight: { + where: "device", + default: 2, + }, + emojiPickerStyle: { + where: "device", + default: "auto" as "auto" | "popup" | "drawer", + }, + recentlyUsedEmojis: { + where: "device", + default: [] as string[], + }, + recentlyUsedUsers: { + where: "device", + default: [] as string[], + }, + defaultSideView: { + where: "device", + default: false, + }, + menuDisplay: { + where: "device", + default: "sideFull" as "sideFull" | "sideIcon" | "top", + }, + reportError: { + where: "device", + default: false, + }, + squareAvatars: { + where: "device", + default: true, + }, + showAvatarDecorations: { + where: "device", + default: true, + }, + postFormWithHashtags: { + where: "device", + default: false, + }, + postFormHashtags: { + where: "device", + default: "", + }, + themeInitial: { + where: "device", + default: true, + }, + numberOfPageCache: { + where: "device", + default: 3, + }, + numberOfReplies: { + where: "device", + default: 5, + }, + showNoteActionsOnlyHover: { + where: "device", + default: false, + }, + showClipButtonInNoteFooter: { + where: "device", + default: false, + }, + reactionsDisplaySize: { + where: "device", + default: "medium" as "small" | "medium" | "large", + }, + limitWidthOfReaction: { + where: "device", + default: true, + }, + forceShowAds: { + where: "device", + default: false, + }, + oneko: { + where: "device", + default: false, + }, + clickToOpen: { + where: "device", + default: true, + }, + aiChanMode: { + where: "device", + default: false, + }, + devMode: { + where: "device", + default: false, + }, + mediaListWithOneImageAppearance: { + where: "device", + default: "expand" as "expand" | "16_9" | "1_1" | "2_3", + }, + notificationPosition: { + where: "device", + default: "rightBottom" as + | "leftTop" + | "leftBottom" + | "rightTop" + | "rightBottom", + }, + notificationStackAxis: { + where: "device", + default: "horizontal" as "vertical" | "horizontal", + }, + notificationClickable: { + where: "device", + default: false, + }, + enableCondensedLine: { + where: "device", + default: true, + }, + additionalUnicodeEmojiIndexes: { + where: "device", + default: {} as Record>, + }, + keepScreenOn: { + where: "device", + default: false, + }, + defaultWithReplies: { + where: "account", + default: false, + }, + disableStreamingTimeline: { + where: "device", + default: false, + }, + useGroupedNotifications: { + where: "device", + default: true, + }, + dataSaver: { + where: "device", + default: { + media: false, + avatar: false, + urlPreview: false, + code: false, + } as Record, + }, + enableSeasonalScreenEffect: { + where: "device", + default: false, + }, + dropAndFusion: { + where: "device", + default: { + bgmVolume: 0.25, + sfxVolume: 1, + }, + }, + hemisphere: { + where: "device", + default: hemisphere as "N" | "S", + }, + enableHorizontalSwipe: { + where: "device", + default: true, + }, + useNativeUIForVideoAudioPlayer: { + where: "device", + default: false, + }, + keepOriginalFilename: { + where: "device", + default: true, + }, + alwaysConfirmFollow: { + where: "device", + default: true, + }, + confirmWhenRevealingSensitiveMedia: { + where: "device", + default: false, + }, + contextMenu: { + where: "device", + default: "app" as "app" | "appWithShift" | "native", + }, + skipNoteRender: { + where: "device", + default: true, + }, + + sound_masterVolume: { + where: "device", + default: 0.3, + }, + sound_notUseSound: { + where: "device", + default: false, + }, + sound_useSoundOnlyWhenActive: { + where: "device", + default: false, + }, + sound_note: { + where: "device", + default: { type: "syuilo/n-aec", volume: 0 } as SoundStore, + }, + sound_noteMy: { + where: "device", + default: { type: "syuilo/n-cea-4va", volume: 1 } as SoundStore, + }, + sound_notification: { + where: "device", + default: { type: "syuilo/n-ea", volume: 1 } as SoundStore, + }, + sound_reaction: { + where: "device", + default: { type: "syuilo/bubble2", volume: 1 } as SoundStore, + }, + }), +); // TODO: 他のタブと永続化されたstateを同期 -const PREFIX = 'miux:' as const; +const PREFIX = "miux:" as const; export type Plugin = { id: string; @@ -630,7 +649,9 @@ export class ColdDeviceStorage { public static watchers: Watcher[] = []; - public static get(key: T): typeof ColdDeviceStorage.default[T] { + public static get( + key: T, + ): (typeof ColdDeviceStorage.default)[T] { // TODO: indexedDBにする // ただしその際はnullチェックではなくキー存在チェックにしないとダメ // (indexedDBはnullを保存できるため、ユーザーが意図してnullを格納した可能性がある) @@ -643,7 +664,9 @@ export class ColdDeviceStorage { } public static getAll(): Partial { - return (Object.keys(this.default) as (keyof typeof this.default)[]).reduce>((acc, key) => { + return (Object.keys(this.default) as (keyof typeof this.default)[]).reduce< + Partial + >((acc, key) => { const value = localStorage.getItem(PREFIX + key); if (value != null) { acc[key] = JSON.parse(value); @@ -652,7 +675,10 @@ export class ColdDeviceStorage { }, {}); } - public static set(key: T, value: typeof ColdDeviceStorage.default[T]): void { + public static set( + key: T, + value: (typeof ColdDeviceStorage.default)[T], + ): void { // 呼び出し側のバグ等で undefined が来ることがある // undefined を文字列として miLocalStorage に入れると参照する際の JSON.parse でコケて不具合の元になるため無視 @@ -677,7 +703,7 @@ export class ColdDeviceStorage { const v = ColdDeviceStorage.get(key); const r = ref(v); // TODO: このままではwatcherがリークするので開放する方法を考える - this.watch(key, v => { + this.watch(key, (v) => { r.value = v; }); return r; @@ -687,14 +713,16 @@ export class ColdDeviceStorage { * 特定のキーの、簡易的なgetter/setterを作ります * 主にvue場で設定コントロールのmodelとして使う用 */ - public static makeGetterSetter(key: K) { + 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: typeof ColdDeviceStorage.default[K]) => { + set: (value: (typeof ColdDeviceStorage.default)[K]) => { const val = value; ColdDeviceStorage.set(key, val); },