This commit is contained in:
かっこかり 2025-09-24 15:41:26 +09:00 committed by GitHub
commit ed80b8006b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 20 additions and 25 deletions

View File

@ -12,6 +12,7 @@ import { i18n } from '@/i18n.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { waiting, popup, popupMenu, success, alert } from '@/os.js'; import { waiting, popup, popupMenu, success, alert } from '@/os.js';
import { unisonReload, reloadChannel } from '@/utility/unison-reload.js'; import { unisonReload, reloadChannel } from '@/utility/unison-reload.js';
import { deepClone } from '@/utility/clone.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { store } from '@/store.js'; import { store } from '@/store.js';
import { $i } from '@/i.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']) { 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]; delete tokens[host + '/' + id];
store.set('accountTokens', tokens); store.set('accountTokens', tokens);
const accountInfos = JSON.parse(JSON.stringify(store.s.accountInfos)); const accountInfos = deepClone(store.s.accountInfos);
delete accountInfos[host + '/' + id]; delete accountInfos[host + '/' + id];
store.set('accountInfos', accountInfos); store.set('accountInfos', accountInfos);
@ -365,8 +366,7 @@ export function getAccountWithSignupDialog(): Promise<{ id: string, token: strin
return new Promise((resolve) => { return new Promise((resolve) => {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, {
done: async (res: Misskey.entities.SignupResponse) => { done: async (res: Misskey.entities.SignupResponse) => {
const user = JSON.parse(JSON.stringify(res)); const { token, ...user } = deepClone(res);
delete user.token;
await addAccount(host, user, res.token); await addAccount(host, user, res.token);
resolve({ id: res.id, token: res.token }); resolve({ id: res.id, token: res.token });
}, },

View File

@ -3,21 +3,13 @@
* SPDX-License-Identifier: AGPL-3.0-only * SPDX-License-Identifier: AGPL-3.0-only
*/ */
import { computed, reactive, watch } from 'vue'; import { computed, reactive, watch, toRaw } from 'vue';
import type { Reactive } from 'vue'; import { deepEqual } from '@/utility/deep-equal.js';
import { deepEqual } from '@/utility/deep-equal'; import { deepClone } from '@/utility/clone.js';
function copy<T>(v: T): T {
return JSON.parse(JSON.stringify(v));
}
function unwrapReactive<T>(v: Reactive<T>): T {
return JSON.parse(JSON.stringify(v));
}
export function useForm<T extends Record<string, any>>(initialState: T, save: (newState: T) => Promise<void>) { export function useForm<T extends Record<string, any>>(initialState: T, save: (newState: T) => Promise<void>) {
const currentState = reactive<T>(copy(initialState)); const currentState = reactive<T>(deepClone(initialState));
const previousState = reactive<T>(copy(initialState)); const previousState = reactive<T>(deepClone(initialState));
const modifiedStates = reactive<Record<keyof T, boolean>>({} as any); const modifiedStates = reactive<Record<keyof T, boolean>>({} as any);
for (const key in currentState) { for (const key in currentState) {
@ -33,15 +25,15 @@ export function useForm<T extends Record<string, any>>(initialState: T, save: (n
}, { deep: true }); }, { deep: true });
async function _save() { async function _save() {
await save(unwrapReactive(currentState)); await save(toRaw(currentState) as T);
for (const key in currentState) { for (const key in currentState) {
previousState[key] = copy(currentState[key]); previousState[key] = deepClone(currentState[key]);
} }
} }
function discard() { function discard() {
for (const key in currentState) { for (const key in currentState) {
currentState[key] = copy(previousState[key]); currentState[key] = deepClone(previousState[key]);
} }
} }

View File

@ -88,6 +88,7 @@ import { validators } from '@/components/grid/cell-validators.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
import MkPagingButtons from '@/components/MkPagingButtons.vue'; import MkPagingButtons from '@/components/MkPagingButtons.vue';
import { selectFile } from '@/utility/drive.js'; import { selectFile } from '@/utility/drive.js';
import { deepClone } from '@/utility/clone.js';
import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js'; import { copyGridDataToClipboard, removeDataFromGrid } from '@/components/grid/grid-utils.js';
import { useLoading } from '@/composables/use-loading.js'; import { useLoading } from '@/composables/use-loading.js';
@ -513,7 +514,7 @@ function refreshGridItems() {
originalUrl: it.originalUrl, originalUrl: it.originalUrl,
type: it.type, type: it.type,
})); }));
originGridItems.value = JSON.parse(JSON.stringify(gridItems.value)); originGridItems.value = deepClone(gridItems.value);
} }
onMounted(async () => { onMounted(async () => {

View File

@ -212,6 +212,7 @@ import { $i } from '@/i.js';
import * as sound from '@/utility/sound.js'; import * as sound from '@/utility/sound.js';
import MkRange from '@/components/MkRange.vue'; import MkRange from '@/components/MkRange.vue';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { deepClone } from '@/utility/clone.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
type FrontendMonoDefinition = { type FrontendMonoDefinition = {
@ -956,8 +957,8 @@ function attachGameEvents() {
}); });
game.addListener('changeStock', value => { game.addListener('changeStock', value => {
currentPick.value = JSON.parse(JSON.stringify(value[0])); currentPick.value = deepClone(value[0]);
stock.value = JSON.parse(JSON.stringify(value.slice(1))); stock.value = deepClone(value.slice(1));
}); });
game.addListener('changeHolding', value => { game.addListener('changeHolding', value => {

View File

@ -13,6 +13,7 @@ import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { deepEqual } from '@/utility/deep-equal.js'; import { deepEqual } from '@/utility/deep-equal.js';
import { deepClone } from '@/utility/clone.js';
// NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない // NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない
@ -228,14 +229,14 @@ export class PreferencesManager {
} }
private rewriteRawState<K extends keyof PREF>(key: K, value: ValueOf<K>) { private rewriteRawState<K extends keyof PREF>(key: K, value: ValueOf<K>) {
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; this.r[key].value = this.s[key] = v;
} }
// TODO: desync対策 cloudの値のfetchが正常に完了していない状態でcommitすると多分値が上書きされる // TODO: desync対策 cloudの値のfetchが正常に完了していない状態でcommitすると多分値が上書きされる
public commit<K extends keyof PREF>(key: K, value: ValueOf<K>) { public commit<K extends keyof PREF>(key: K, value: ValueOf<K>) {
const currentAccount = this.currentAccount; // TSを黙らせるため 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 (deepEqual(this.s[key], v)) {
if (_DEV_) console.log('(skip) prefer:commit', key, v); if (_DEV_) console.log('(skip) prefer:commit', key, v);