diff --git a/packages/frontend/src/accounts.ts b/packages/frontend/src/accounts.ts index 60f7cd0b4b..8cdc9c1413 100644 --- a/packages/frontend/src/accounts.ts +++ b/packages/frontend/src/accounts.ts @@ -12,6 +12,7 @@ import { i18n } from '@/i18n.js'; import { miLocalStorage } from '@/local-storage.js'; import { waiting, popup, popupMenu, success, alert } from '@/os.js'; import { unisonReload, reloadChannel } from '@/utility/unison-reload.js'; +import { deepClone } from '@/utility/clone.js'; import { prefer } from '@/preferences.js'; import { store } from '@/store.js'; import { $i } from '@/i.js'; @@ -47,10 +48,10 @@ async function addAccount(host: string, user: Misskey.entities.MeDetailed, token } export async function removeAccount(host: string, id: AccountWithToken['id']) { - const tokens = JSON.parse(JSON.stringify(store.s.accountTokens)); + const tokens = deepClone(store.s.accountTokens); delete tokens[host + '/' + id]; store.set('accountTokens', tokens); - const accountInfos = JSON.parse(JSON.stringify(store.s.accountInfos)); + const accountInfos = deepClone(store.s.accountInfos); delete accountInfos[host + '/' + id]; store.set('accountInfos', accountInfos); @@ -365,8 +366,7 @@ export function getAccountWithSignupDialog(): Promise<{ id: string, token: strin return new Promise((resolve) => { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, { done: async (res: Misskey.entities.SignupResponse) => { - const user = JSON.parse(JSON.stringify(res)); - delete user.token; + const { token, ...user } = deepClone(res); await addAccount(host, user, res.token); resolve({ id: res.id, token: res.token }); }, diff --git a/packages/frontend/src/composables/use-form.ts b/packages/frontend/src/composables/use-form.ts index 1c93557413..b1a8e304c7 100644 --- a/packages/frontend/src/composables/use-form.ts +++ b/packages/frontend/src/composables/use-form.ts @@ -3,21 +3,13 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { computed, reactive, watch } from 'vue'; -import type { Reactive } from 'vue'; -import { deepEqual } from '@/utility/deep-equal'; - -function copy(v: T): T { - return JSON.parse(JSON.stringify(v)); -} - -function unwrapReactive(v: Reactive): T { - return JSON.parse(JSON.stringify(v)); -} +import { computed, reactive, watch, toRaw } from 'vue'; +import { deepEqual } from '@/utility/deep-equal.js'; +import { deepClone } from '@/utility/clone.js'; export function useForm>(initialState: T, save: (newState: T) => Promise) { - const currentState = reactive(copy(initialState)); - const previousState = reactive(copy(initialState)); + const currentState = reactive(deepClone(initialState)); + const previousState = reactive(deepClone(initialState)); const modifiedStates = reactive>({} as any); for (const key in currentState) { @@ -33,15 +25,15 @@ export function useForm>(initialState: T, save: (n }, { deep: true }); async function _save() { - await save(unwrapReactive(currentState)); + await save(toRaw(currentState) as T); for (const key in currentState) { - previousState[key] = copy(currentState[key]); + previousState[key] = deepClone(currentState[key]); } } function discard() { for (const key in currentState) { - currentState[key] = copy(previousState[key]); + currentState[key] = deepClone(previousState[key]); } } diff --git a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue index a380bd133e..ef49e6be77 100644 --- a/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue +++ b/packages/frontend/src/pages/admin/custom-emojis-manager.local.list.vue @@ -88,6 +88,7 @@ import { validators } from '@/components/grid/cell-validators.js'; import { misskeyApi } from '@/utility/misskey-api.js'; import MkPagingButtons from '@/components/MkPagingButtons.vue'; import { selectFile } from '@/utility/drive.js'; +import { deepClone } from '@/utility/clone.js'; import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js'; import { useLoading } from '@/composables/use-loading.js'; @@ -513,7 +514,7 @@ function refreshGridItems() { originalUrl: it.originalUrl, type: it.type, })); - originGridItems.value = JSON.parse(JSON.stringify(gridItems.value)); + originGridItems.value = deepClone(gridItems.value); } onMounted(async () => { diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index 88300d8a74..f361fea1a0 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -212,6 +212,7 @@ import { $i } from '@/i.js'; import * as sound from '@/utility/sound.js'; import MkRange from '@/components/MkRange.vue'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; +import { deepClone } from '@/utility/clone.js'; import { prefer } from '@/preferences.js'; type FrontendMonoDefinition = { @@ -956,8 +957,8 @@ function attachGameEvents() { }); game.addListener('changeStock', value => { - currentPick.value = JSON.parse(JSON.stringify(value[0])); - stock.value = JSON.parse(JSON.stringify(value.slice(1))); + currentPick.value = deepClone(value[0]); + stock.value = deepClone(value.slice(1)); }); game.addListener('changeHolding', value => { diff --git a/packages/frontend/src/preferences/manager.ts b/packages/frontend/src/preferences/manager.ts index b6d3d55a5f..e7872c5f13 100644 --- a/packages/frontend/src/preferences/manager.ts +++ b/packages/frontend/src/preferences/manager.ts @@ -13,6 +13,7 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { deepEqual } from '@/utility/deep-equal.js'; +import { deepClone } from '@/utility/clone.js'; // NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない @@ -228,14 +229,14 @@ export class PreferencesManager { } private rewriteRawState(key: K, value: ValueOf) { - const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除 + const v = deepClone(value); // deep copy 兼 vueのプロキシ解除 this.r[key].value = this.s[key] = v; } // TODO: desync対策 cloudの値のfetchが正常に完了していない状態でcommitすると多分値が上書きされる public commit(key: K, value: ValueOf) { const currentAccount = this.currentAccount; // TSを黙らせるため - const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除 + const v = deepClone(value); // deep copy 兼 vueのプロキシ解除 if (deepEqual(this.s[key], v)) { if (_DEV_) console.log('(skip) prefer:commit', key, v);