Feat: emoji picker profile

This commit is contained in:
mattyatea 2024-01-14 13:54:37 +09:00
parent 693efb99a0
commit 6fa319afc6
12 changed files with 502 additions and 345 deletions

View File

@ -180,7 +180,9 @@ id: 'aidx'
#outgoingAddressFamily: ipv4 #outgoingAddressFamily: ipv4
# Proxy for HTTP/HTTPS # Proxy for HTTP/HTTPS
#proxy: http://127.0.0.1:3128 #
proxyBypassHosts: proxyBypassHosts:
- api.deepl.com - api.deepl.com

View File

@ -9,6 +9,8 @@ notifications: "Notifications"
username: "Username" username: "Username"
password: "Password" password: "Password"
forgotPassword: "Forgot password" forgotPassword: "Forgot password"
setDefaultProfileConfirm: "Do you want to make this profile the default?"
emojiPickerProfile: "Emoji picker profile"
fetchingAsApObject: "Fetching from the Fediverse..." fetchingAsApObject: "Fetching from the Fediverse..."
ok: "OK" ok: "OK"
gotIt: "Got it!" gotIt: "Got it!"

3
locales/index.d.ts vendored
View File

@ -14,6 +14,8 @@ export interface Locale {
"forgotPassword": string; "forgotPassword": string;
"fetchingAsApObject": string; "fetchingAsApObject": string;
"ok": string; "ok": string;
"setDefaultProfileConfirm": string;
"emojiPickerProfile": string;
"notificationIndicator": string; "notificationIndicator": string;
"hanntenn": string; "hanntenn": string;
"hanntennInfo": string; "hanntennInfo": string;
@ -1767,6 +1769,7 @@ export interface Locale {
}; };
"_options": { "_options": {
"gtlAvailable": string; "gtlAvailable": string;
"emojiPickerProfileLimit": string;
"ltlAvailable": string; "ltlAvailable": string;
"canPublicNote": string; "canPublicNote": string;
"canEditNote": string; "canEditNote": string;

View File

@ -11,6 +11,8 @@ password: "パスワード"
forgotPassword: "パスワードを忘れた" forgotPassword: "パスワードを忘れた"
fetchingAsApObject: "連合に照会中" fetchingAsApObject: "連合に照会中"
ok: "OK" ok: "OK"
setDefaultProfileConfirm: "このプロファイルをデフォルトにしますか?"
emojiPickerProfile: "絵文字ピッカーのプロファイル"
notificationIndicator: "通知のインジケーターの数字を表示する" notificationIndicator: "通知のインジケーターの数字を表示する"
hanntenn: "アイコンとバナーを反転させる" hanntenn: "アイコンとバナーを反転させる"
hanntennInfo: "ダークだったらライトのアイコンに、ライトだったらダークのアイコンに。" hanntennInfo: "ダークだったらライトのアイコンに、ライトだったらダークのアイコンに。"
@ -1673,6 +1675,7 @@ _role:
high: "高" high: "高"
_options: _options:
gtlAvailable: "グローバルタイムラインの閲覧" gtlAvailable: "グローバルタイムラインの閲覧"
emojiPickerProfileLimit: "絵文字ピッカーのプロファイルの上限数(最大5)"
ltlAvailable: "ローカルタイムラインの閲覧" ltlAvailable: "ローカルタイムラインの閲覧"
canPublicNote: "パブリック投稿の許可" canPublicNote: "パブリック投稿の許可"
canEditNote: "ノートの編集" canEditNote: "ノートの編集"

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2023.12.2-PrisMisskey.4", "version": "2023.12.2-PrisMisskey.5",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -59,6 +59,7 @@ export type RolePolicies = {
userEachUserListsLimit: number; userEachUserListsLimit: number;
rateLimitFactor: number; rateLimitFactor: number;
avatarDecorationLimit: number; avatarDecorationLimit: number;
emojiPickerProfileLimit: number;
}; };
export const DEFAULT_POLICIES: RolePolicies = { export const DEFAULT_POLICIES: RolePolicies = {
@ -74,7 +75,6 @@ export const DEFAULT_POLICIES: RolePolicies = {
canManageCustomEmojis: false, canManageCustomEmojis: false,
canRequestCustomEmojis: false, canRequestCustomEmojis: false,
canManageAvatarDecorations: false, canManageAvatarDecorations: false,
canRequestCustomEmojis: false,
canSearchNotes: false, canSearchNotes: false,
canUseTranslator: true, canUseTranslator: true,
canHideAds: false, canHideAds: false,
@ -90,6 +90,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
userEachUserListsLimit: 50, userEachUserListsLimit: 50,
rateLimitFactor: 1, rateLimitFactor: 1,
avatarDecorationLimit: 1, avatarDecorationLimit: 1,
emojiPickerProfileLimit: 2,
}; };
@Injectable() @Injectable()
@ -355,6 +356,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)), userEachUserListsLimit: calc('userEachUserListsLimit', vs => Math.max(...vs)),
rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)), rateLimitFactor: calc('rateLimitFactor', vs => Math.max(...vs)),
avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)), avatarDecorationLimit: calc('avatarDecorationLimit', vs => Math.max(...vs)),
emojiPickerProfileLimit: calc('emojiPickerProfileLimit', vs => Math.max(...vs)),
}; };
} }

View File

@ -36,10 +36,15 @@ SPDX-License-Identifier: AGPL-3.0-only
</section> </section>
<div v-if="tab === 'index'" class="group index"> <div v-if="tab === 'index'" class="group index">
<section v-if="showPinned && pinned.length > 0"> <section v-if="showPinned">
<div style="display: flex; ">
<div v-for="a in profileMax" :key="a" :title="defaultStore.state[`pickerProfileName${a > 1 ? a - 1 : ''}`]" class="sllfktkhgl" :class="{ active: activeIndex === a || isDefaultProfile === a }" @click="pinnedProfileSelect(a)">
{{ defaultStore.state[`pickerProfileName${a > 1 ? a - 1 : ''}`] }}
</div>
</div>
<div class="body"> <div class="body">
<button <button
v-for="emoji in pinned" v-for="emoji in pinnedEmojis"
:key="emoji" :key="emoji"
:data-emoji="emoji" :data-emoji="emoji"
class="_button item" class="_button item"
@ -117,8 +122,10 @@ import { deviceKind } from '@/scripts/device-kind.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { defaultStore } from '@/store.js'; import { defaultStore } from '@/store.js';
import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js'; import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js';
import { $i } from '@/account.js'; import { signinRequired } from '@/account.js';
import MkButton from '@/components/MkButton.vue';
import { deepClone } from '@/scripts/clone.js';
const $i = signinRequired();
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
showPinned?: boolean; showPinned?: boolean;
pinnedEmojis?: string[]; pinnedEmojis?: string[];
@ -133,7 +140,7 @@ const props = withDefaults(defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'chosen', v: string): void; (ev: 'chosen', v: string): void;
}>(); }>();
const profileMax = $i.policies.emojiPickerProfileLimit;
const searchEl = shallowRef<HTMLInputElement>(); const searchEl = shallowRef<HTMLInputElement>();
const emojisEl = shallowRef<HTMLDivElement>(); const emojisEl = shallowRef<HTMLDivElement>();
@ -152,16 +159,15 @@ const q = ref<string>('');
const searchResultCustom = ref<Misskey.entities.EmojiSimple[]>([]); const searchResultCustom = ref<Misskey.entities.EmojiSimple[]>([]);
const searchResultUnicode = ref<UnicodeEmojiDef[]>([]); const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index'); const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index');
const pinnedEmojis = ref(pinned.value);
const customEmojiFolderRoot: CustomEmojiFolderTree = { value: '', category: '', children: [] }; const customEmojiFolderRoot: CustomEmojiFolderTree = { value: '', category: '', children: [] };
function parseAndMergeCategories(input: string, root: CustomEmojiFolderTree): CustomEmojiFolderTree { function parseAndMergeCategories(input: string, root: CustomEmojiFolderTree): CustomEmojiFolderTree {
const parts = input.split('/').map(p => p.trim()); // const parts = input.split('/').map(p => p.trim()); //
let currentNode: CustomEmojiFolderTree = root; // currentNode root let currentNode: CustomEmojiFolderTree = root; // currentNode root
let includesPart = customEmojis.value.some(emoji => emoji.category !== null && emoji.category.includes(parts[0] + '/')); let includesPart = customEmojis.value.some(emoji => emoji.category !== null && emoji.category.includes(parts[0] + '/'));
console.log(includesPart)
if (parts.length === 1 && parts[0] !== '' && includesPart) { // parts 1 if (parts.length === 1 && parts[0] !== '' && includesPart) { // parts 1
parts.push(parts[0]) // parts parts[0] (test category test/test category ) parts.push(parts[0]); // parts parts[0] (test category test/test category )
} }
for (const part of parts) { // parts for (const part of parts) { // parts
@ -410,6 +416,14 @@ function onEnter(ev: KeyboardEvent) {
done(); done();
} }
const activeIndex = ref(defaultStore.state.pickerProfileDefault);
pinnedEmojis.value = props.asReactionPicker ? deepClone(defaultStore.state[`reactions${activeIndex.value > 1 ? activeIndex.value - 1 : ''}`]) : deepClone(defaultStore.state[`pinnedEmojis${activeIndex.value > 1 ? activeIndex.value - 1 : ''}`]);
function pinnedProfileSelect(index:number) {
pinnedEmojis.value = props.asReactionPicker ? deepClone(defaultStore.state[`reactions${index > 1 ? index - 1 : ''}`]) : deepClone(defaultStore.state[`pinnedEmojis${index > 1 ? index - 1 : ''}`]);
activeIndex.value = index;
}
function done(query?: string): boolean | void { function done(query?: string): boolean | void {
if (query == null) query = q.value; if (query == null) query = q.value;
if (query == null || typeof query !== 'string') return; if (query == null || typeof query !== 'string') return;
@ -685,4 +699,24 @@ left: 0;*/
} }
} }
} }
.sllfktkhgl{
display: inline-block;
padding: 0 4px;
font-size: 12px;
line-height: 32px;
text-align: center;
color: var(--fg);
cursor: pointer;
width: 100%;
transition: transform 0.3s ease;
box-shadow: 0 1.5px 0 var(--divider);
height: 32px;
overflow: hidden;
&:hover {
transform: translateY(1.5px);
}
&.active {
transform: translateY(5px);
}
}
</style> </style>

View File

@ -68,8 +68,6 @@ import { defaultStore } from '@/store.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { $i } from '@/account.js'; import { $i } from '@/account.js';
import { hostname } from '@/config.js'; import { hostname } from '@/config.js';
import { multipleSelectUser } from '@/os.js';
const emit = defineEmits<{ const emit = defineEmits<{
(ev: 'ok', selected: Misskey.entities.UserDetailed): void; (ev: 'ok', selected: Misskey.entities.UserDetailed): void;
(ev: 'cancel'): void; (ev: 'cancel'): void;

View File

@ -99,6 +99,7 @@ export const ROLE_POLICIES = [
'userEachUserListsLimit', 'userEachUserListsLimit',
'rateLimitFactor', 'rateLimitFactor',
'avatarDecorationLimit', 'avatarDecorationLimit',
'emojiPickerProfileLimit'
] as const; ] as const;
// なんか動かない // なんか動かない

View File

@ -72,6 +72,13 @@ SPDX-License-Identifier: AGPL-3.0-only
</MkSwitch> </MkSwitch>
</MkFolder> </MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.emojiPickerProfileLimit, 'pickerProfileDefault'])">
<template #label>{{ i18n.ts._role._options.emojiPickerProfileLimit }}</template>
<template #suffix>{{ policies.emojiPickerProfileLimit }}</template>
<MkInput v-model="policies.emojiPickerProfileLimit" type="number">
</MkInput>
</MkFolder>
<MkFolder v-if="matchQuery([i18n.ts._role._options.inviteLimit, 'inviteLimit'])"> <MkFolder v-if="matchQuery([i18n.ts._role._options.inviteLimit, 'inviteLimit'])">
<template #label>{{ i18n.ts._role._options.inviteLimit }}</template> <template #label>{{ i18n.ts._role._options.inviteLimit }}</template>
<template #suffix>{{ policies.inviteLimit }}</template> <template #suffix>{{ policies.inviteLimit }}</template>

View File

@ -5,6 +5,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div class="_gaps_m"> <div class="_gaps_m">
<MkSelect v-model="nowProfileId">
<template #label>{{ i18n.ts.emojiPickerProfile }}</template>
<option v-for="a in profileMax" :key="a" :value="a">{{ a }}. {{ defaultStore.state[`pickerProfileName${a > 1 ? a - 1 : ''}`] }} </option>
</MkSelect>
<MkInput v-model="profileName">
<template #label>{{ i18n.ts.name }}</template>
</MkInput>
<MkFolder :defaultOpen="true"> <MkFolder :defaultOpen="true">
<template #icon><i class="ti ti-pin"></i></template> <template #icon><i class="ti ti-pin"></i></template>
<template #label>{{ i18n.ts.pinned }} ({{ i18n.ts.reaction }})</template> <template #label>{{ i18n.ts.pinned }} ({{ i18n.ts.reaction }})</template>
@ -84,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div> </div>
</div> </div>
</MkFolder> </MkFolder>
<MkButton inline primary @click="setDefaultProfile"> {{ i18n.ts.default }}</MkButton>
<FormSection> <FormSection>
<template #label>{{ i18n.ts.emojiPickerDisplay }}</template> <template #label>{{ i18n.ts.emojiPickerDisplay }}</template>
@ -139,6 +146,9 @@ import { emojiPicker } from '@/scripts/emoji-picker.js';
import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue'; import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
import MkEmoji from '@/components/global/MkEmoji.vue'; import MkEmoji from '@/components/global/MkEmoji.vue';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import MkSelect from '@/components/MkSelect.vue';
import { signinRequired } from '@/account.js';
import MkInput from '@/components/MkInput.vue';
const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(defaultStore.state.reactions)); const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(defaultStore.state.reactions));
const pinnedEmojis: Ref<string[]> = ref(deepClone(defaultStore.state.pinnedEmojis)); const pinnedEmojis: Ref<string[]> = ref(deepClone(defaultStore.state.pinnedEmojis));
@ -155,6 +165,20 @@ const setDefaultReaction = () => setDefault(pinnedEmojisForReaction);
const removeEmoji = (reaction: string, ev: MouseEvent) => remove(pinnedEmojis, reaction, ev); const removeEmoji = (reaction: string, ev: MouseEvent) => remove(pinnedEmojis, reaction, ev);
const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev); const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev);
const setDefaultEmoji = () => setDefault(pinnedEmojis); const setDefaultEmoji = () => setDefault(pinnedEmojis);
const nowProfileId = ref(defaultStore.state.pickerProfileDefault);
const $i = signinRequired();
const profileMax = $i.policies.emojiPickerProfileLimit;
const profileName = ref(defaultStore.state[`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
pinnedEmojisForReaction.value = deepClone(defaultStore.state[`reactions${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
pinnedEmojis.value = deepClone(defaultStore.state[`pinnedEmojis${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
profileName.value = deepClone(defaultStore.state[`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
watch(nowProfileId, () => {
pinnedEmojisForReaction.value = deepClone(defaultStore.state[`reactions${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
pinnedEmojis.value = deepClone(defaultStore.state[`pinnedEmojis${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
profileName.value = deepClone(defaultStore.state[`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`]);
});
function previewReaction(ev: MouseEvent) { function previewReaction(ev: MouseEvent) {
reactionPicker.show(getHTMLElement(ev)); reactionPicker.show(getHTMLElement(ev));
@ -226,17 +250,32 @@ function getHTMLElement(ev: MouseEvent): HTMLElement {
} }
watch(pinnedEmojisForReaction, () => { watch(pinnedEmojisForReaction, () => {
defaultStore.set('reactions', pinnedEmojisForReaction.value); defaultStore.set(`reactions${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`, pinnedEmojisForReaction.value);
}, { }, {
deep: true, deep: true,
}); });
watch(pinnedEmojis, () => { watch(pinnedEmojis, () => {
defaultStore.set('pinnedEmojis', pinnedEmojis.value); defaultStore.set( `pinnedEmojis${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`, pinnedEmojis.value);
}, { }, {
deep: true, deep: true,
}); });
watch(profileName, () => {
defaultStore.set(`pickerProfileName${nowProfileId.value > 1 ? nowProfileId.value - 1 : ''}`, profileName.value);
}, {
deep: true,
});
async function setDefaultProfile() {
const { canceled } = await os.confirm({
type: 'info',
text: i18n.ts.setDefaultProfileConfirm,
});
if (canceled) return;
await defaultStore.set('pickerProfileDefault', nowProfileId.value);
}
definePageMetadata({ definePageMetadata({
title: i18n.ts.emojiPicker, title: i18n.ts.emojiPicker,
icon: 'ti ti-mood-happy', icon: 'ti ti-mood-happy',

View File

@ -58,11 +58,10 @@ export const noteActions: NoteAction[] = [];
export const noteViewInterruptors: NoteViewInterruptor[] = []; export const noteViewInterruptors: NoteViewInterruptor[] = [];
export const notePostInterruptors: NotePostInterruptor[] = []; export const notePostInterruptors: NotePostInterruptor[] = [];
export const pageViewInterruptors: PageViewInterruptor[] = []; export const pageViewInterruptors: PageViewInterruptor[] = [];
export const bannerDark='https://files.prismisskey.space/misskey/e088c6d1-b07f-4312-8d41-fee2f64071e9.png' export const bannerDark = 'https://files.prismisskey.space/misskey/e088c6d1-b07f-4312-8d41-fee2f64071e9.png';
export const bannerLight ='https://files.prismisskey.space/misskey/85500d2f-41a9-48ff-a737-65d6fdf74604.png' export const bannerLight = 'https://files.prismisskey.space/misskey/85500d2f-41a9-48ff-a737-65d6fdf74604.png';
export const iconDark='https://files.prismisskey.space/misskey/484efc68-de41-4786-b2b6-e5085c31c2c4.webp' export const iconDark = 'https://files.prismisskey.space/misskey/484efc68-de41-4786-b2b6-e5085c31c2c4.webp';
export const iconLight='https://files.prismisskey.space/misskey/c3d722fe-379f-4c85-9414-90c232d53237.webp' export const iconLight = 'https://files.prismisskey.space/misskey/c3d722fe-379f-4c85-9414-90c232d53237.webp';
// TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう) // TODO: それぞれいちいちwhereとかdefaultというキーを付けなきゃいけないの冗長なのでなんとかする(ただ型定義が面倒になりそう)
// あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない // あと、現行の定義の仕方なら「whereが何であるかに関わらずキー名の重複不可」という制約を付けられるメリットもあるからそのメリットを引き継ぐ方法も考えないといけない
@ -129,6 +128,74 @@ export const defaultStore = markRaw(new Storage('base', {
where: 'account', where: 'account',
default: [], default: [],
}, },
reactions1: {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
pinnedEmojis1: {
where: 'account',
default: [],
},
reactions2: {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
pinnedEmojis2: {
where: 'account',
default: [],
},
reactions3: {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
pinnedEmojis3: {
where: 'account',
default: [],
},
reactions4: {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
pinnedEmojis4: {
where: 'account',
default: [],
},
reactions5: {
where: 'account',
default: ['👍', '❤️', '😆', '🤔', '😮', '🎉', '💢', '😥', '😇', '🍮'],
},
pinnedEmojis5: {
where: 'account',
default: [],
},
pickerProfileName: {
where: 'account',
default: 'default',
},
pickerProfileName1: {
where: 'account',
default: '1',
},
pickerProfileName2: {
where: 'account',
default: '2',
},
pickerProfileName3: {
where: 'account',
default: '3',
},
pickerProfileName4: {
where: 'account',
default: '4',
},
pickerProfileName5: {
where: 'account',
default: '5',
},
pickerProfileDefault: {
where: 'account',
default: 1,
},
reactionAcceptance: { reactionAcceptance: {
where: 'account', where: 'account',
default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null, default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null,
@ -304,11 +371,11 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
bannerUrl: { bannerUrl: {
where: 'device', where: 'device',
default: bannerDark default: bannerDark,
}, },
iconUrl: { iconUrl: {
where: 'device', where: 'device',
default: iconDark default: iconDark,
}, },
instanceTicker: { instanceTicker: {
where: 'device', where: 'device',
@ -336,7 +403,7 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
enablehanntenn: { enablehanntenn: {
where: 'device', where: 'device',
default: false default: false,
}, },
recentlyUsedUsers: { recentlyUsedUsers: {
where: 'device', where: 'device',
@ -392,7 +459,7 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
localOnlyColor: { localOnlyColor: {
where: 'device', where: 'device',
default: '#2b2c41' default: '#2b2c41',
}, },
numberOfGamingSpeed: { numberOfGamingSpeed: {
where: 'device', where: 'device',
@ -557,7 +624,6 @@ export const defaultStore = markRaw(new Storage('base', {
}, },
})); }));
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
const PREFIX = 'miux:' as const; const PREFIX = 'miux:' as const;