Compare commits
	
		
			14 Commits
		
	
	
		
			2025.3.2-a
			...
			refine-piz
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 2402754dcc | ||
|   | 2493592bd0 | ||
|   | eec4ab841a | ||
|   | 7957ee5191 | ||
|   | d0b8ffe629 | ||
|   | b200743845 | ||
|   | cef7575b76 | ||
|   | 9842eb2eeb | ||
|   | 05078e9c14 | ||
|   | db5c6fa3c2 | ||
|   | 8a4e2659ed | ||
|   | d19c094a9b | ||
|   | 08f7e7d9b3 | ||
|   | a7f7ff33e7 | 
| @@ -74,7 +74,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({ | |||||||
|     let fetching = ref(true); |     let fetching = ref(true); | ||||||
|     let images = ref([]); |     let images = ref([]); | ||||||
|     function thumbnail(image) { |     function thumbnail(image) { | ||||||
|       return store.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl; |       return store.s.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl; | ||||||
|     } |     } | ||||||
|     onMounted(() => { |     onMounted(() => { | ||||||
|       const image = [ |       const image = [ | ||||||
| @@ -190,7 +190,7 @@ const index_photos = defineComponent({ | |||||||
|     let fetching = ref(true); |     let fetching = ref(true); | ||||||
|     let images = ref([]); |     let images = ref([]); | ||||||
|     function thumbnail(image) { |     function thumbnail(image) { | ||||||
|       return store.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl; |       return store.s.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl; | ||||||
|     } |     } | ||||||
|     onMounted(() => { |     onMounted(() => { | ||||||
|       const image = ["image/jpeg", "image/webp", "image/avif", "image/png", "image/gif", "image/apng", "image/vnd.mozilla.apng"]; |       const image = ["image/jpeg", "image/webp", "image/avif", "image/png", "image/gif", "image/apng", "image/vnd.mozilla.apng"]; | ||||||
|   | |||||||
| @@ -154,52 +154,52 @@ export async function common(createVue: () => App<Element>) { | |||||||
| 	//#endregion | 	//#endregion | ||||||
|  |  | ||||||
| 	// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため) | 	// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため) | ||||||
| 	watch(store.reactiveState.darkMode, (darkMode) => { | 	watch(store.r.darkMode, (darkMode) => { | ||||||
| 		applyTheme(darkMode | 		applyTheme(darkMode | ||||||
| 			? (prefer.s.darkTheme ?? defaultDarkTheme) | 			? (prefer.s.darkTheme ?? defaultDarkTheme) | ||||||
| 			: (prefer.s.lightTheme ?? defaultLightTheme), | 			: (prefer.s.lightTheme ?? defaultLightTheme), | ||||||
| 		); | 		); | ||||||
| 	}, { immediate: miLocalStorage.getItem('theme') == null }); | 	}, { immediate: miLocalStorage.getItem('theme') == null }); | ||||||
|  |  | ||||||
| 	document.documentElement.dataset.colorScheme = store.state.darkMode ? 'dark' : 'light'; | 	document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light'; | ||||||
|  |  | ||||||
| 	const darkTheme = prefer.model('darkTheme'); | 	const darkTheme = prefer.model('darkTheme'); | ||||||
| 	const lightTheme = prefer.model('lightTheme'); | 	const lightTheme = prefer.model('lightTheme'); | ||||||
|  |  | ||||||
| 	watch(darkTheme, (theme) => { | 	watch(darkTheme, (theme) => { | ||||||
| 		if (store.state.darkMode) { | 		if (store.s.darkMode) { | ||||||
| 			applyTheme(theme ?? defaultDarkTheme); | 			applyTheme(theme ?? defaultDarkTheme); | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	watch(lightTheme, (theme) => { | 	watch(lightTheme, (theme) => { | ||||||
| 		if (!store.state.darkMode) { | 		if (!store.s.darkMode) { | ||||||
| 			applyTheme(theme ?? defaultLightTheme); | 			applyTheme(theme ?? defaultLightTheme); | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	//#region Sync dark mode | 	//#region Sync dark mode | ||||||
| 	if (prefer.s.syncDeviceDarkMode) { | 	if (prefer.s.syncDeviceDarkMode) { | ||||||
| 		store.set('darkMode', isDeviceDarkmode()); | 		store.commit('darkMode', isDeviceDarkmode()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => { | 	window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (mql) => { | ||||||
| 		if (prefer.s.syncDeviceDarkMode) { | 		if (prefer.s.syncDeviceDarkMode) { | ||||||
| 			store.set('darkMode', mql.matches); | 			store.commit('darkMode', mql.matches); | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
| 	//#endregion | 	//#endregion | ||||||
|  |  | ||||||
| 	if (prefer.s.darkTheme && store.state.darkMode) { | 	if (prefer.s.darkTheme && store.s.darkMode) { | ||||||
| 		if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme); | 		if (miLocalStorage.getItem('themeId') !== prefer.s.darkTheme.id) applyTheme(prefer.s.darkTheme); | ||||||
| 	} else if (prefer.s.lightTheme && !store.state.darkMode) { | 	} else if (prefer.s.lightTheme && !store.s.darkMode) { | ||||||
| 		if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme); | 		if (miLocalStorage.getItem('themeId') !== prefer.s.lightTheme.id) applyTheme(prefer.s.lightTheme); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	fetchInstanceMetaPromise.then(() => { | 	fetchInstanceMetaPromise.then(() => { | ||||||
| 		// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア | 		// TODO: instance.defaultLightTheme/instance.defaultDarkThemeが不正な形式だった場合のケア | ||||||
| 		if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.set('lightTheme', JSON.parse(instance.defaultLightTheme)); | 		if (prefer.s.lightTheme == null && instance.defaultLightTheme != null) prefer.commit('lightTheme', JSON.parse(instance.defaultLightTheme)); | ||||||
| 		if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.set('darkTheme', JSON.parse(instance.defaultDarkTheme)); | 		if (prefer.s.darkTheme == null && instance.defaultDarkTheme != null) prefer.commit('darkTheme', JSON.parse(instance.defaultDarkTheme)); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	watch(prefer.r.overridedDeviceKind, (kind) => { | 	watch(prefer.r.overridedDeviceKind, (kind) => { | ||||||
|   | |||||||
| @@ -138,98 +138,98 @@ export async function mainBoot() { | |||||||
| 		store.loaded.then(async () => { | 		store.loaded.then(async () => { | ||||||
| 			// prefereces migration | 			// prefereces migration | ||||||
| 			// TODO: そのうち消す | 			// TODO: そのうち消す | ||||||
| 			if (store.state.menu.length > 0) { | 			if (store.s.menu.length > 0) { | ||||||
| 				const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []); | 				const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' }).catch(() => []); | ||||||
| 				if (themes.length > 0) { | 				if (themes.length > 0) { | ||||||
| 					prefer.set('themes', themes); | 					prefer.commit('themes', themes); | ||||||
| 				} | 				} | ||||||
| 				const plugins = ColdDeviceStorage.get('plugins'); | 				const plugins = ColdDeviceStorage.get('plugins'); | ||||||
| 				prefer.set('plugins', plugins.map(p => ({ | 				prefer.commit('plugins', plugins.map(p => ({ | ||||||
| 					...p, | 					...p, | ||||||
| 					installId: (p as any).id, | 					installId: (p as any).id, | ||||||
| 					id: undefined, | 					id: undefined, | ||||||
| 				}))); | 				}))); | ||||||
| 				prefer.set('lightTheme', ColdDeviceStorage.get('lightTheme')); | 				prefer.commit('lightTheme', ColdDeviceStorage.get('lightTheme')); | ||||||
| 				prefer.set('darkTheme', ColdDeviceStorage.get('darkTheme')); | 				prefer.commit('darkTheme', ColdDeviceStorage.get('darkTheme')); | ||||||
| 				prefer.set('syncDeviceDarkMode', ColdDeviceStorage.get('syncDeviceDarkMode')); | 				prefer.commit('syncDeviceDarkMode', ColdDeviceStorage.get('syncDeviceDarkMode')); | ||||||
| 				prefer.set('overridedDeviceKind', store.state.overridedDeviceKind); | 				prefer.commit('overridedDeviceKind', store.s.overridedDeviceKind); | ||||||
| 				prefer.set('widgets', store.state.widgets); | 				prefer.commit('widgets', store.s.widgets); | ||||||
| 				prefer.set('keepCw', store.state.keepCw); | 				prefer.commit('keepCw', store.s.keepCw); | ||||||
| 				prefer.set('collapseRenotes', store.state.collapseRenotes); | 				prefer.commit('collapseRenotes', store.s.collapseRenotes); | ||||||
| 				prefer.set('rememberNoteVisibility', store.state.rememberNoteVisibility); | 				prefer.commit('rememberNoteVisibility', store.s.rememberNoteVisibility); | ||||||
| 				prefer.set('uploadFolder', store.state.uploadFolder); | 				prefer.commit('uploadFolder', store.s.uploadFolder); | ||||||
| 				prefer.set('keepOriginalUploading', store.state.keepOriginalUploading); | 				prefer.commit('keepOriginalUploading', store.s.keepOriginalUploading); | ||||||
| 				prefer.set('menu', store.state.menu); | 				prefer.commit('menu', store.s.menu); | ||||||
| 				prefer.set('statusbars', store.state.statusbars); | 				prefer.commit('statusbars', store.s.statusbars); | ||||||
| 				prefer.set('pinnedUserLists', store.state.pinnedUserLists); | 				prefer.commit('pinnedUserLists', store.s.pinnedUserLists); | ||||||
| 				prefer.set('serverDisconnectedBehavior', store.state.serverDisconnectedBehavior); | 				prefer.commit('serverDisconnectedBehavior', store.s.serverDisconnectedBehavior); | ||||||
| 				prefer.set('nsfw', store.state.nsfw); | 				prefer.commit('nsfw', store.s.nsfw); | ||||||
| 				prefer.set('highlightSensitiveMedia', store.state.highlightSensitiveMedia); | 				prefer.commit('highlightSensitiveMedia', store.s.highlightSensitiveMedia); | ||||||
| 				prefer.set('animation', store.state.animation); | 				prefer.commit('animation', store.s.animation); | ||||||
| 				prefer.set('animatedMfm', store.state.animatedMfm); | 				prefer.commit('animatedMfm', store.s.animatedMfm); | ||||||
| 				prefer.set('advancedMfm', store.state.advancedMfm); | 				prefer.commit('advancedMfm', store.s.advancedMfm); | ||||||
| 				prefer.set('showReactionsCount', store.state.showReactionsCount); | 				prefer.commit('showReactionsCount', store.s.showReactionsCount); | ||||||
| 				prefer.set('enableQuickAddMfmFunction', store.state.enableQuickAddMfmFunction); | 				prefer.commit('enableQuickAddMfmFunction', store.s.enableQuickAddMfmFunction); | ||||||
| 				prefer.set('loadRawImages', store.state.loadRawImages); | 				prefer.commit('loadRawImages', store.s.loadRawImages); | ||||||
| 				prefer.set('imageNewTab', store.state.imageNewTab); | 				prefer.commit('imageNewTab', store.s.imageNewTab); | ||||||
| 				prefer.set('disableShowingAnimatedImages', store.state.disableShowingAnimatedImages); | 				prefer.commit('disableShowingAnimatedImages', store.s.disableShowingAnimatedImages); | ||||||
| 				prefer.set('emojiStyle', store.state.emojiStyle); | 				prefer.commit('emojiStyle', store.s.emojiStyle); | ||||||
| 				prefer.set('menuStyle', store.state.menuStyle); | 				prefer.commit('menuStyle', store.s.menuStyle); | ||||||
| 				prefer.set('useBlurEffectForModal', store.state.useBlurEffectForModal); | 				prefer.commit('useBlurEffectForModal', store.s.useBlurEffectForModal); | ||||||
| 				prefer.set('useBlurEffect', store.state.useBlurEffect); | 				prefer.commit('useBlurEffect', store.s.useBlurEffect); | ||||||
| 				prefer.set('showFixedPostForm', store.state.showFixedPostForm); | 				prefer.commit('showFixedPostForm', store.s.showFixedPostForm); | ||||||
| 				prefer.set('showFixedPostFormInChannel', store.state.showFixedPostFormInChannel); | 				prefer.commit('showFixedPostFormInChannel', store.s.showFixedPostFormInChannel); | ||||||
| 				prefer.set('enableInfiniteScroll', store.state.enableInfiniteScroll); | 				prefer.commit('enableInfiniteScroll', store.s.enableInfiniteScroll); | ||||||
| 				prefer.set('useReactionPickerForContextMenu', store.state.useReactionPickerForContextMenu); | 				prefer.commit('useReactionPickerForContextMenu', store.s.useReactionPickerForContextMenu); | ||||||
| 				prefer.set('showGapBetweenNotesInTimeline', store.state.showGapBetweenNotesInTimeline); | 				prefer.commit('showGapBetweenNotesInTimeline', store.s.showGapBetweenNotesInTimeline); | ||||||
| 				prefer.set('instanceTicker', store.state.instanceTicker); | 				prefer.commit('instanceTicker', store.s.instanceTicker); | ||||||
| 				prefer.set('emojiPickerScale', store.state.emojiPickerScale); | 				prefer.commit('emojiPickerScale', store.s.emojiPickerScale); | ||||||
| 				prefer.set('emojiPickerWidth', store.state.emojiPickerWidth); | 				prefer.commit('emojiPickerWidth', store.s.emojiPickerWidth); | ||||||
| 				prefer.set('emojiPickerHeight', store.state.emojiPickerHeight); | 				prefer.commit('emojiPickerHeight', store.s.emojiPickerHeight); | ||||||
| 				prefer.set('emojiPickerStyle', store.state.emojiPickerStyle); | 				prefer.commit('emojiPickerStyle', store.s.emojiPickerStyle); | ||||||
| 				prefer.set('reportError', store.state.reportError); | 				prefer.commit('reportError', store.s.reportError); | ||||||
| 				prefer.set('squareAvatars', store.state.squareAvatars); | 				prefer.commit('squareAvatars', store.s.squareAvatars); | ||||||
| 				prefer.set('showAvatarDecorations', store.state.showAvatarDecorations); | 				prefer.commit('showAvatarDecorations', store.s.showAvatarDecorations); | ||||||
| 				prefer.set('numberOfPageCache', store.state.numberOfPageCache); | 				prefer.commit('numberOfPageCache', store.s.numberOfPageCache); | ||||||
| 				prefer.set('showNoteActionsOnlyHover', store.state.showNoteActionsOnlyHover); | 				prefer.commit('showNoteActionsOnlyHover', store.s.showNoteActionsOnlyHover); | ||||||
| 				prefer.set('showClipButtonInNoteFooter', store.state.showClipButtonInNoteFooter); | 				prefer.commit('showClipButtonInNoteFooter', store.s.showClipButtonInNoteFooter); | ||||||
| 				prefer.set('reactionsDisplaySize', store.state.reactionsDisplaySize); | 				prefer.commit('reactionsDisplaySize', store.s.reactionsDisplaySize); | ||||||
| 				prefer.set('limitWidthOfReaction', store.state.limitWidthOfReaction); | 				prefer.commit('limitWidthOfReaction', store.s.limitWidthOfReaction); | ||||||
| 				prefer.set('forceShowAds', store.state.forceShowAds); | 				prefer.commit('forceShowAds', store.s.forceShowAds); | ||||||
| 				prefer.set('aiChanMode', store.state.aiChanMode); | 				prefer.commit('aiChanMode', store.s.aiChanMode); | ||||||
| 				prefer.set('devMode', store.state.devMode); | 				prefer.commit('devMode', store.s.devMode); | ||||||
| 				prefer.set('mediaListWithOneImageAppearance', store.state.mediaListWithOneImageAppearance); | 				prefer.commit('mediaListWithOneImageAppearance', store.s.mediaListWithOneImageAppearance); | ||||||
| 				prefer.set('notificationPosition', store.state.notificationPosition); | 				prefer.commit('notificationPosition', store.s.notificationPosition); | ||||||
| 				prefer.set('notificationStackAxis', store.state.notificationStackAxis); | 				prefer.commit('notificationStackAxis', store.s.notificationStackAxis); | ||||||
| 				prefer.set('enableCondensedLine', store.state.enableCondensedLine); | 				prefer.commit('enableCondensedLine', store.s.enableCondensedLine); | ||||||
| 				prefer.set('keepScreenOn', store.state.keepScreenOn); | 				prefer.commit('keepScreenOn', store.s.keepScreenOn); | ||||||
| 				prefer.set('disableStreamingTimeline', store.state.disableStreamingTimeline); | 				prefer.commit('disableStreamingTimeline', store.s.disableStreamingTimeline); | ||||||
| 				prefer.set('useGroupedNotifications', store.state.useGroupedNotifications); | 				prefer.commit('useGroupedNotifications', store.s.useGroupedNotifications); | ||||||
| 				prefer.set('dataSaver', store.state.dataSaver); | 				prefer.commit('dataSaver', store.s.dataSaver); | ||||||
| 				prefer.set('enableSeasonalScreenEffect', store.state.enableSeasonalScreenEffect); | 				prefer.commit('enableSeasonalScreenEffect', store.s.enableSeasonalScreenEffect); | ||||||
| 				prefer.set('enableHorizontalSwipe', store.state.enableHorizontalSwipe); | 				prefer.commit('enableHorizontalSwipe', store.s.enableHorizontalSwipe); | ||||||
| 				prefer.set('useNativeUiForVideoAudioPlayer', store.state.useNativeUIForVideoAudioPlayer); | 				prefer.commit('useNativeUiForVideoAudioPlayer', store.s.useNativeUIForVideoAudioPlayer); | ||||||
| 				prefer.set('keepOriginalFilename', store.state.keepOriginalFilename); | 				prefer.commit('keepOriginalFilename', store.s.keepOriginalFilename); | ||||||
| 				prefer.set('alwaysConfirmFollow', store.state.alwaysConfirmFollow); | 				prefer.commit('alwaysConfirmFollow', store.s.alwaysConfirmFollow); | ||||||
| 				prefer.set('confirmWhenRevealingSensitiveMedia', store.state.confirmWhenRevealingSensitiveMedia); | 				prefer.commit('confirmWhenRevealingSensitiveMedia', store.s.confirmWhenRevealingSensitiveMedia); | ||||||
| 				prefer.set('contextMenu', store.state.contextMenu); | 				prefer.commit('contextMenu', store.s.contextMenu); | ||||||
| 				prefer.set('skipNoteRender', store.state.skipNoteRender); | 				prefer.commit('skipNoteRender', store.s.skipNoteRender); | ||||||
| 				prefer.set('showSoftWordMutedWord', store.state.showSoftWordMutedWord); | 				prefer.commit('showSoftWordMutedWord', store.s.showSoftWordMutedWord); | ||||||
| 				prefer.set('confirmOnReact', store.state.confirmOnReact); | 				prefer.commit('confirmOnReact', store.s.confirmOnReact); | ||||||
| 				prefer.set('sound.masterVolume', store.state.sound_masterVolume); | 				prefer.commit('sound.masterVolume', store.s.sound_masterVolume); | ||||||
| 				prefer.set('sound.notUseSound', store.state.sound_notUseSound); | 				prefer.commit('sound.notUseSound', store.s.sound_notUseSound); | ||||||
| 				prefer.set('sound.useSoundOnlyWhenActive', store.state.sound_useSoundOnlyWhenActive); | 				prefer.commit('sound.useSoundOnlyWhenActive', store.s.sound_useSoundOnlyWhenActive); | ||||||
| 				prefer.set('sound.on.note', store.state.sound_note as any); | 				prefer.commit('sound.on.note', store.s.sound_note as any); | ||||||
| 				prefer.set('sound.on.noteMy', store.state.sound_noteMy as any); | 				prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any); | ||||||
| 				prefer.set('sound.on.notification', store.state.sound_notification as any); | 				prefer.commit('sound.on.notification', store.s.sound_notification as any); | ||||||
| 				prefer.set('sound.on.reaction', store.state.sound_reaction as any); | 				prefer.commit('sound.on.reaction', store.s.sound_reaction as any); | ||||||
| 				store.set('deck.profile', deckStore.state.profile); | 				store.commit('deck.profile', deckStore.s.profile); | ||||||
| 				store.set('deck.columns', deckStore.state.columns); | 				store.commit('deck.columns', deckStore.s.columns); | ||||||
| 				store.set('deck.layout', deckStore.state.layout); | 				store.commit('deck.layout', deckStore.s.layout); | ||||||
| 				store.set('menu', []); | 				store.commit('menu', []); | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			if (store.state.accountSetupWizard !== -1) { | 			if (store.s.accountSetupWizard !== -1) { | ||||||
| 				const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, { | 				const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, { | ||||||
| 					closed: () => dispose(), | 					closed: () => dispose(), | ||||||
| 				}); | 				}); | ||||||
| @@ -502,7 +502,7 @@ export async function mainBoot() { | |||||||
| 			post(); | 			post(); | ||||||
| 		}, | 		}, | ||||||
| 		'd': () => { | 		'd': () => { | ||||||
| 			store.set('darkMode', !store.state.darkMode); | 			store.commit('darkMode', !store.s.darkMode); | ||||||
| 		}, | 		}, | ||||||
| 		's': () => { | 		's': () => { | ||||||
| 			mainRouter.push('/search'); | 			mainRouter.push('/search'); | ||||||
|   | |||||||
| @@ -73,7 +73,7 @@ const emojiDb = computed(() => { | |||||||
| 		url: char2path(x.char), | 		url: char2path(x.char), | ||||||
| 	})); | 	})); | ||||||
|  |  | ||||||
| 	for (const index of Object.values(store.state.additionalUnicodeEmojiIndexes)) { | 	for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) { | ||||||
| 		for (const [emoji, keywords] of Object.entries(index)) { | 		for (const [emoji, keywords] of Object.entries(index)) { | ||||||
| 			for (const k of keywords) { | 			for (const k of keywords) { | ||||||
| 				unicodeEmojiDB.push({ | 				unicodeEmojiDB.push({ | ||||||
| @@ -155,10 +155,10 @@ function complete(type: string, value: any) { | |||||||
| 	emit('done', { type, value }); | 	emit('done', { type, value }); | ||||||
| 	emit('closed'); | 	emit('closed'); | ||||||
| 	if (type === 'emoji') { | 	if (type === 'emoji') { | ||||||
| 		let recents = store.state.recentlyUsedEmojis; | 		let recents = store.s.recentlyUsedEmojis; | ||||||
| 		recents = recents.filter((emoji: any) => emoji !== value); | 		recents = recents.filter((emoji: any) => emoji !== value); | ||||||
| 		recents.unshift(value); | 		recents.unshift(value); | ||||||
| 		store.set('recentlyUsedEmojis', recents.splice(0, 32)); | 		store.commit('recentlyUsedEmojis', recents.splice(0, 32)); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -238,7 +238,7 @@ function exec() { | |||||||
| 	} else if (props.type === 'emoji') { | 	} else if (props.type === 'emoji') { | ||||||
| 		if (!props.q || props.q === '') { | 		if (!props.q || props.q === '') { | ||||||
| 			// 最近使った絵文字をサジェスト | 			// 最近使った絵文字をサジェスト | ||||||
| 			emojis.value = store.state.recentlyUsedEmojis.map(emoji => emojiDb.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[]; | 			emojis.value = store.s.recentlyUsedEmojis.map(emoji => emojiDb.value.find(dbEmoji => dbEmoji.emoji === emoji)).filter(x => x) as EmojiDef[]; | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -154,7 +154,7 @@ async function requestRender() { | |||||||
|  |  | ||||||
| 		captchaWidgetId.value = captcha.value.render(elem, { | 		captchaWidgetId.value = captcha.value.render(elem, { | ||||||
| 			sitekey: props.sitekey, | 			sitekey: props.sitekey, | ||||||
| 			theme: store.state.darkMode ? 'dark' : 'light', | 			theme: store.s.darkMode ? 'dark' : 'light', | ||||||
| 			callback: callback, | 			callback: callback, | ||||||
| 			'expired-callback': () => callback(undefined), | 			'expired-callback': () => callback(undefined), | ||||||
| 			'error-callback': () => callback(undefined), | 			'error-callback': () => callback(undefined), | ||||||
|   | |||||||
| @@ -161,7 +161,7 @@ const render = () => { | |||||||
| 		chartInstance.destroy(); | 		chartInstance.destroy(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | 	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
|  |  | ||||||
| 	const maxes = chartData.series.map((x, i) => Math.max(...x.data.map(d => d.y))); | 	const maxes = chartData.series.map((x, i) => Math.max(...x.data.map(d => d.y))); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ const props = defineProps<{ | |||||||
| }>(); | }>(); | ||||||
|  |  | ||||||
| const highlighter = await getHighlighter(); | const highlighter = await getHighlighter(); | ||||||
| const darkMode = store.reactiveState.darkMode; | const darkMode = store.r.darkMode; | ||||||
| const codeLang = ref<BundledLanguage | 'aiscript'>('js'); | const codeLang = ref<BundledLanguage | 'aiscript'>('js'); | ||||||
|  |  | ||||||
| const [lightThemeName, darkThemeName] = await Promise.all([ | const [lightThemeName, darkThemeName] = await Promise.all([ | ||||||
|   | |||||||
| @@ -245,7 +245,7 @@ function deleteFolder() { | |||||||
| 		folderId: props.folder.id, | 		folderId: props.folder.id, | ||||||
| 	}).then(() => { | 	}).then(() => { | ||||||
| 		if (prefer.s.uploadFolder === props.folder.id) { | 		if (prefer.s.uploadFolder === props.folder.id) { | ||||||
| 			prefer.set('uploadFolder', null); | 			prefer.commit('uploadFolder', null); | ||||||
| 		} | 		} | ||||||
| 	}).catch(err => { | 	}).catch(err => { | ||||||
| 		switch (err.id) { | 		switch (err.id) { | ||||||
| @@ -266,7 +266,7 @@ function deleteFolder() { | |||||||
| } | } | ||||||
|  |  | ||||||
| function setAsUploadFolder() { | function setAsUploadFolder() { | ||||||
| 	prefer.set('uploadFolder', props.folder.id); | 	prefer.commit('uploadFolder', props.folder.id); | ||||||
| } | } | ||||||
|  |  | ||||||
| function onContextmenu(ev: MouseEvent) { | function onContextmenu(ev: MouseEvent) { | ||||||
|   | |||||||
| @@ -166,7 +166,7 @@ const { | |||||||
| 	emojiPickerHeight, | 	emojiPickerHeight, | ||||||
| } = prefer.r; | } = prefer.r; | ||||||
|  |  | ||||||
| const recentlyUsedEmojis = store.reactiveState.recentlyUsedEmojis; | const recentlyUsedEmojis = store.r.recentlyUsedEmojis; | ||||||
|  |  | ||||||
| const recentlyUsedEmojisDef = computed(() => { | const recentlyUsedEmojisDef = computed(() => { | ||||||
| 	return recentlyUsedEmojis.value.map(getDef); | 	return recentlyUsedEmojis.value.map(getDef); | ||||||
| @@ -319,7 +319,7 @@ watch(q, () => { | |||||||
| 			} | 			} | ||||||
| 			if (matches.size >= max) return matches; | 			if (matches.size >= max) return matches; | ||||||
|  |  | ||||||
| 			for (const index of Object.values(store.state.additionalUnicodeEmojiIndexes)) { | 			for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) { | ||||||
| 				for (const emoji of emojis) { | 				for (const emoji of emojis) { | ||||||
| 					if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) { | 					if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) { | ||||||
| 						matches.add(emoji); | 						matches.add(emoji); | ||||||
| @@ -336,7 +336,7 @@ watch(q, () => { | |||||||
| 			} | 			} | ||||||
| 			if (matches.size >= max) return matches; | 			if (matches.size >= max) return matches; | ||||||
|  |  | ||||||
| 			for (const index of Object.values(store.state.additionalUnicodeEmojiIndexes)) { | 			for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) { | ||||||
| 				for (const emoji of emojis) { | 				for (const emoji of emojis) { | ||||||
| 					if (index[emoji.char].some(k => k.startsWith(newQ))) { | 					if (index[emoji.char].some(k => k.startsWith(newQ))) { | ||||||
| 						matches.add(emoji); | 						matches.add(emoji); | ||||||
| @@ -353,7 +353,7 @@ watch(q, () => { | |||||||
| 			} | 			} | ||||||
| 			if (matches.size >= max) return matches; | 			if (matches.size >= max) return matches; | ||||||
|  |  | ||||||
| 			for (const index of Object.values(store.state.additionalUnicodeEmojiIndexes)) { | 			for (const index of Object.values(store.s.additionalUnicodeEmojiIndexes)) { | ||||||
| 				for (const emoji of emojis) { | 				for (const emoji of emojis) { | ||||||
| 					if (index[emoji.char].some(k => k.includes(newQ))) { | 					if (index[emoji.char].some(k => k.includes(newQ))) { | ||||||
| 						matches.add(emoji); | 						matches.add(emoji); | ||||||
| @@ -429,10 +429,10 @@ function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, | |||||||
|  |  | ||||||
| 	// 最近使った絵文字更新 | 	// 最近使った絵文字更新 | ||||||
| 	if (!pinned.value?.includes(key)) { | 	if (!pinned.value?.includes(key)) { | ||||||
| 		let recents = store.state.recentlyUsedEmojis; | 		let recents = store.s.recentlyUsedEmojis; | ||||||
| 		recents = recents.filter((emoji) => emoji !== key); | 		recents = recents.filter((emoji) => emoji !== key); | ||||||
| 		recents.unshift(key); | 		recents.unshift(key); | ||||||
| 		store.set('recentlyUsedEmojis', recents.splice(0, 32)); | 		store.commit('recentlyUsedEmojis', recents.splice(0, 32)); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -121,11 +121,11 @@ async function onClick() { | |||||||
| 			} else { | 			} else { | ||||||
| 				await misskeyApi('following/create', { | 				await misskeyApi('following/create', { | ||||||
| 					userId: props.user.id, | 					userId: props.user.id, | ||||||
| 					withReplies: store.state.defaultWithReplies, | 					withReplies: store.s.defaultWithReplies, | ||||||
| 				}); | 				}); | ||||||
| 				emit('update:user', { | 				emit('update:user', { | ||||||
| 					...props.user, | 					...props.user, | ||||||
| 					withReplies: store.state.defaultWithReplies, | 					withReplies: store.s.defaultWithReplies, | ||||||
| 				}); | 				}); | ||||||
| 				hasPendingFollowRequestFromYou.value = true; | 				hasPendingFollowRequestFromYou.value = true; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -106,7 +106,7 @@ async function renderChart() { | |||||||
|  |  | ||||||
| 	await nextTick(); | 	await nextTick(); | ||||||
|  |  | ||||||
| 	const color = store.state.darkMode ? '#b4e900' : '#86b300'; | 	const color = store.s.darkMode ? '#b4e900' : '#86b300'; | ||||||
|  |  | ||||||
| 	// 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする | 	// 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする | ||||||
| 	const max = values.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3; | 	const max = values.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3; | ||||||
|   | |||||||
| @@ -176,18 +176,18 @@ const text = ref(props.initialText ?? ''); | |||||||
| const files = ref(props.initialFiles ?? []); | const files = ref(props.initialFiles ?? []); | ||||||
| const poll = ref<PollEditorModelValue | null>(null); | const poll = ref<PollEditorModelValue | null>(null); | ||||||
| const useCw = ref<boolean>(!!props.initialCw); | const useCw = ref<boolean>(!!props.initialCw); | ||||||
| const showPreview = ref(store.state.showPreview); | const showPreview = ref(store.s.showPreview); | ||||||
| watch(showPreview, () => store.set('showPreview', showPreview.value)); | watch(showPreview, () => store.commit('showPreview', showPreview.value)); | ||||||
| const showAddMfmFunction = ref(prefer.s.enableQuickAddMfmFunction); | const showAddMfmFunction = ref(prefer.s.enableQuickAddMfmFunction); | ||||||
| watch(showAddMfmFunction, () => prefer.set('enableQuickAddMfmFunction', showAddMfmFunction.value)); | watch(showAddMfmFunction, () => prefer.commit('enableQuickAddMfmFunction', showAddMfmFunction.value)); | ||||||
| const cw = ref<string | null>(props.initialCw ?? null); | const cw = ref<string | null>(props.initialCw ?? null); | ||||||
| const localOnly = ref(props.initialLocalOnly ?? (prefer.s.rememberNoteVisibility ? store.state.localOnly : prefer.s.defaultNoteLocalOnly)); | const localOnly = ref(props.initialLocalOnly ?? (prefer.s.rememberNoteVisibility ? store.s.localOnly : prefer.s.defaultNoteLocalOnly)); | ||||||
| const visibility = ref(props.initialVisibility ?? (prefer.s.rememberNoteVisibility ? store.state.visibility : prefer.s.defaultNoteVisibility)); | const visibility = ref(props.initialVisibility ?? (prefer.s.rememberNoteVisibility ? store.s.visibility : prefer.s.defaultNoteVisibility)); | ||||||
| const visibleUsers = ref<Misskey.entities.UserDetailed[]>([]); | const visibleUsers = ref<Misskey.entities.UserDetailed[]>([]); | ||||||
| if (props.initialVisibleUsers) { | if (props.initialVisibleUsers) { | ||||||
| 	props.initialVisibleUsers.forEach(u => pushVisibleUser(u)); | 	props.initialVisibleUsers.forEach(u => pushVisibleUser(u)); | ||||||
| } | } | ||||||
| const reactionAcceptance = ref(store.state.reactionAcceptance); | const reactionAcceptance = ref(store.s.reactionAcceptance); | ||||||
| const draghover = ref(false); | const draghover = ref(false); | ||||||
| const quoteId = ref<string | null>(null); | const quoteId = ref<string | null>(null); | ||||||
| const hasNotSpecifiedMentions = ref(false); | const hasNotSpecifiedMentions = ref(false); | ||||||
| @@ -270,8 +270,8 @@ const canPost = computed((): boolean => { | |||||||
| 		(!poll.value || poll.value.choices.length >= 2); | 		(!poll.value || poll.value.choices.length >= 2); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const withHashtags = computed(store.makeGetterSetter('postFormWithHashtags')); | const withHashtags = store.model('postFormWithHashtags'); | ||||||
| const hashtags = computed(store.makeGetterSetter('postFormHashtags')); | const hashtags = store.model('postFormHashtags'); | ||||||
|  |  | ||||||
| watch(text, () => { | watch(text, () => { | ||||||
| 	checkMissingMention(); | 	checkMissingMention(); | ||||||
| @@ -480,7 +480,7 @@ function setVisibility() { | |||||||
| 		changeVisibility: v => { | 		changeVisibility: v => { | ||||||
| 			visibility.value = v; | 			visibility.value = v; | ||||||
| 			if (prefer.s.rememberNoteVisibility) { | 			if (prefer.s.rememberNoteVisibility) { | ||||||
| 				store.set('visibility', visibility.value); | 				store.commit('visibility', visibility.value); | ||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		closed: () => dispose(), | 		closed: () => dispose(), | ||||||
| @@ -528,7 +528,7 @@ async function toggleLocalOnly() { | |||||||
|  |  | ||||||
| 	localOnly.value = !localOnly.value; | 	localOnly.value = !localOnly.value; | ||||||
| 	if (prefer.s.rememberNoteVisibility) { | 	if (prefer.s.rememberNoteVisibility) { | ||||||
| 		store.set('localOnly', localOnly.value); | 		store.commit('localOnly', localOnly.value); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -75,7 +75,7 @@ async function renderChart() { | |||||||
|  |  | ||||||
| 	await nextTick(); | 	await nextTick(); | ||||||
|  |  | ||||||
| 	const color = store.state.darkMode ? '#b4e900' : '#86b300'; | 	const color = store.s.darkMode ? '#b4e900' : '#86b300'; | ||||||
|  |  | ||||||
| 	const getYYYYMMDD = (date: Date) => { | 	const getYYYYMMDD = (date: Date) => { | ||||||
| 		const y = date.getFullYear().toString().padStart(2, '0'); | 		const y = date.getFullYear().toString().padStart(2, '0'); | ||||||
|   | |||||||
| @@ -42,7 +42,7 @@ const getDate = (ymd: string) => { | |||||||
| onMounted(async () => { | onMounted(async () => { | ||||||
| 	let raw = await misskeyApi('retention', { }); | 	let raw = await misskeyApi('retention', { }); | ||||||
|  |  | ||||||
| 	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | 	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
|  |  | ||||||
| 	const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent')); | 	const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent')); | ||||||
| 	const color = accent.toHex(); | 	const color = accent.toHex(); | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 			sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin" | 			sandbox="allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin" | ||||||
| 			scrolling="no" | 			scrolling="no" | ||||||
| 			:style="{ position: 'relative', width: '100%', height: `${tweetHeight}px`, border: 0 }" | 			:style="{ position: 'relative', width: '100%', height: `${tweetHeight}px`, border: 0 }" | ||||||
| 			:src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${store.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`" | 			:src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${store.s.darkMode ? 'dark' : 'light'}&id=${tweetId}`" | ||||||
| 		></iframe> | 		></iframe> | ||||||
| 	</div> | 	</div> | ||||||
| 	<div :class="$style.action"> | 	<div :class="$style.action"> | ||||||
|   | |||||||
| @@ -128,10 +128,10 @@ async function ok() { | |||||||
| 	dialogEl.value?.close(); | 	dialogEl.value?.close(); | ||||||
|  |  | ||||||
| 	// 最近使ったユーザー更新 | 	// 最近使ったユーザー更新 | ||||||
| 	let recents = store.state.recentlyUsedUsers; | 	let recents = store.s.recentlyUsedUsers; | ||||||
| 	recents = recents.filter(x => x !== selected.value?.id); | 	recents = recents.filter(x => x !== selected.value?.id); | ||||||
| 	recents.unshift(selected.value.id); | 	recents.unshift(selected.value.id); | ||||||
| 	store.set('recentlyUsedUsers', recents.splice(0, 16)); | 	store.commit('recentlyUsedUsers', recents.splice(0, 16)); | ||||||
| } | } | ||||||
|  |  | ||||||
| function cancel() { | function cancel() { | ||||||
| @@ -141,7 +141,7 @@ function cancel() { | |||||||
|  |  | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
| 	misskeyApi('users/show', { | 	misskeyApi('users/show', { | ||||||
| 		userIds: store.state.recentlyUsedUsers, | 		userIds: store.s.recentlyUsedUsers, | ||||||
| 	}).then(foundUsers => { | 	}).then(foundUsers => { | ||||||
| 		let _users = foundUsers; | 		let _users = foundUsers; | ||||||
| 		_users = _users.filter((u) => { | 		_users = _users.filter((u) => { | ||||||
|   | |||||||
| @@ -149,10 +149,10 @@ const emit = defineEmits<{ | |||||||
| const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); | const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); | ||||||
|  |  | ||||||
| // eslint-disable-next-line vue/no-setup-props-reactivity-loss | // eslint-disable-next-line vue/no-setup-props-reactivity-loss | ||||||
| const page = ref(store.state.accountSetupWizard); | const page = ref(store.s.accountSetupWizard); | ||||||
|  |  | ||||||
| watch(page, () => { | watch(page, () => { | ||||||
| 	store.set('accountSetupWizard', page.value); | 	store.commit('accountSetupWizard', page.value); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| async function close(skip: boolean) { | async function close(skip: boolean) { | ||||||
| @@ -165,11 +165,11 @@ async function close(skip: boolean) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dialog.value?.close(); | 	dialog.value?.close(); | ||||||
| 	store.set('accountSetupWizard', -1); | 	store.commit('accountSetupWizard', -1); | ||||||
| } | } | ||||||
|  |  | ||||||
| function setupComplete() { | function setupComplete() { | ||||||
| 	store.set('accountSetupWizard', -1); | 	store.commit('accountSetupWizard', -1); | ||||||
| 	dialog.value?.close(); | 	dialog.value?.close(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -194,7 +194,7 @@ async function later(later: boolean) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	dialog.value?.close(); | 	dialog.value?.close(); | ||||||
| 	store.set('accountSetupWizard', 0); | 	store.commit('accountSetupWizard', 0); | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ async function renderChart() { | |||||||
|  |  | ||||||
| 	await nextTick(); | 	await nextTick(); | ||||||
|  |  | ||||||
| 	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | 	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
|  |  | ||||||
| 	const computedStyle = getComputedStyle(document.documentElement); | 	const computedStyle = getComputedStyle(document.documentElement); | ||||||
| 	const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); | 	const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ const choseAd = (): Ad | null => { | |||||||
| 		return props.specify; | 		return props.specify; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const allAds = instance.ads.map(ad => store.state.mutedAds.includes(ad.id) ? { | 	const allAds = instance.ads.map(ad => store.s.mutedAds.includes(ad.id) ? { | ||||||
| 		...ad, | 		...ad, | ||||||
| 		ratio: 0, | 		ratio: 0, | ||||||
| 	} : ad); | 	} : ad); | ||||||
| @@ -112,8 +112,8 @@ const shouldHide = ref(!prefer.s.forceShowAds && $i && $i.policies.canHideAds && | |||||||
|  |  | ||||||
| function reduceFrequency(): void { | function reduceFrequency(): void { | ||||||
| 	if (chosen.value == null) return; | 	if (chosen.value == null) return; | ||||||
| 	if (store.state.mutedAds.includes(chosen.value.id)) return; | 	if (store.s.mutedAds.includes(chosen.value.id)) return; | ||||||
| 	store.push('mutedAds', chosen.value.id); | 	store.commit('mutedAds', [...store.s.mutedAds, chosen.value.id]); | ||||||
| 	os.success(); | 	os.success(); | ||||||
| 	chosen.value = choseAd(); | 	chosen.value = choseAd(); | ||||||
| 	showMenu.value = false; | 	showMenu.value = false; | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| --> | --> | ||||||
|  |  | ||||||
| <template> | <template> | ||||||
| <div :class="[$style.spacer, store.reactiveState.darkMode.value ? $style.dark : $style.light]"></div> | <div :class="[$style.spacer, store.r.darkMode.value ? $style.dark : $style.light]"></div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|   | |||||||
| @@ -59,34 +59,34 @@ export const loadDeck = async () => { | |||||||
| 	try { | 	try { | ||||||
| 		deck = await misskeyApi('i/registry/get', { | 		deck = await misskeyApi('i/registry/get', { | ||||||
| 			scope: ['client', 'deck', 'profiles'], | 			scope: ['client', 'deck', 'profiles'], | ||||||
| 			key: store.state['deck.profile'], | 			key: store.s['deck.profile'], | ||||||
| 		}); | 		}); | ||||||
| 	} catch (err) { | 	} catch (err) { | ||||||
| 		if (typeof err === 'object' && err != null && 'code' in err && err.code === 'NO_SUCH_KEY') { | 		if (typeof err === 'object' && err != null && 'code' in err && err.code === 'NO_SUCH_KEY') { | ||||||
| 			// 後方互換性のため | 			// 後方互換性のため | ||||||
| 			if (store.state['deck.profile'] === 'default') { | 			if (store.s['deck.profile'] === 'default') { | ||||||
| 				saveDeck(); | 				saveDeck(); | ||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			store.set('deck.columns', []); | 			store.commit('deck.columns', []); | ||||||
| 			store.set('deck.layout', []); | 			store.commit('deck.layout', []); | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 		throw err; | 		throw err; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	store.set('deck.columns', deck.columns); | 	store.commit('deck.columns', deck.columns); | ||||||
| 	store.set('deck.layout', deck.layout); | 	store.commit('deck.layout', deck.layout); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export async function forceSaveDeck() { | export async function forceSaveDeck() { | ||||||
| 	await misskeyApi('i/registry/set', { | 	await misskeyApi('i/registry/set', { | ||||||
| 		scope: ['client', 'deck', 'profiles'], | 		scope: ['client', 'deck', 'profiles'], | ||||||
| 		key: store.state['deck.profile'], | 		key: store.s['deck.profile'], | ||||||
| 		value: { | 		value: { | ||||||
| 			columns: store.reactiveState['deck.columns'].value, | 			columns: store.r['deck.columns'].value, | ||||||
| 			layout: store.reactiveState['deck.layout'].value, | 			layout: store.r['deck.layout'].value, | ||||||
| 		}, | 		}, | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| @@ -111,40 +111,40 @@ export async function deleteProfile(key: string): Promise<void> { | |||||||
|  |  | ||||||
| export function addColumn(column: Column) { | export function addColumn(column: Column) { | ||||||
| 	if (column.name === undefined) column.name = null; | 	if (column.name === undefined) column.name = null; | ||||||
| 	store.push('deck.columns', column); | 	store.commit('deck.columns', [...store.s['deck.columns'], column]); | ||||||
| 	store.push('deck.layout', [column.id]); | 	store.commit('deck.layout', [...store.s['deck.layout'], [column.id]]); | ||||||
| 	saveDeck(); | 	saveDeck(); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function removeColumn(id: Column['id']) { | export function removeColumn(id: Column['id']) { | ||||||
| 	store.set('deck.columns', store.state['deck.columns'].filter(c => c.id !== id)); | 	store.commit('deck.columns', store.s['deck.columns'].filter(c => c.id !== id)); | ||||||
| 	store.set('deck.layout', store.state['deck.layout'] | 	store.commit('deck.layout', store.s['deck.layout'] | ||||||
| 		.map(ids => ids.filter(_id => _id !== id)) | 		.map(ids => ids.filter(_id => _id !== id)) | ||||||
| 		.filter(ids => ids.length > 0)); | 		.filter(ids => ids.length > 0)); | ||||||
| 	saveDeck(); | 	saveDeck(); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function swapColumn(a: Column['id'], b: Column['id']) { | export function swapColumn(a: Column['id'], b: Column['id']) { | ||||||
| 	const aX = store.state['deck.layout'].findIndex(ids => ids.indexOf(a) !== -1); | 	const aX = store.s['deck.layout'].findIndex(ids => ids.indexOf(a) !== -1); | ||||||
| 	const aY = store.state['deck.layout'][aX].findIndex(id => id === a); | 	const aY = store.s['deck.layout'][aX].findIndex(id => id === a); | ||||||
| 	const bX = store.state['deck.layout'].findIndex(ids => ids.indexOf(b) !== -1); | 	const bX = store.s['deck.layout'].findIndex(ids => ids.indexOf(b) !== -1); | ||||||
| 	const bY = store.state['deck.layout'][bX].findIndex(id => id === b); | 	const bY = store.s['deck.layout'][bX].findIndex(id => id === b); | ||||||
| 	const layout = deepClone(store.state['deck.layout']); | 	const layout = deepClone(store.s['deck.layout']); | ||||||
| 	layout[aX][aY] = b; | 	layout[aX][aY] = b; | ||||||
| 	layout[bX][bY] = a; | 	layout[bX][bY] = a; | ||||||
| 	store.set('deck.layout', layout); | 	store.commit('deck.layout', layout); | ||||||
| 	saveDeck(); | 	saveDeck(); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function swapLeftColumn(id: Column['id']) { | export function swapLeftColumn(id: Column['id']) { | ||||||
| 	const layout = deepClone(store.state['deck.layout']); | 	const layout = deepClone(store.s['deck.layout']); | ||||||
| 	store.state['deck.layout'].some((ids, i) => { | 	store.s['deck.layout'].some((ids, i) => { | ||||||
| 		if (ids.includes(id)) { | 		if (ids.includes(id)) { | ||||||
| 			const left = store.state['deck.layout'][i - 1]; | 			const left = store.s['deck.layout'][i - 1]; | ||||||
| 			if (left) { | 			if (left) { | ||||||
| 				layout[i - 1] = store.state['deck.layout'][i]; | 				layout[i - 1] = store.s['deck.layout'][i]; | ||||||
| 				layout[i] = left; | 				layout[i] = left; | ||||||
| 				store.set('deck.layout', layout); | 				store.commit('deck.layout', layout); | ||||||
| 			} | 			} | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| @@ -154,14 +154,14 @@ export function swapLeftColumn(id: Column['id']) { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function swapRightColumn(id: Column['id']) { | export function swapRightColumn(id: Column['id']) { | ||||||
| 	const layout = deepClone(store.state['deck.layout']); | 	const layout = deepClone(store.s['deck.layout']); | ||||||
| 	store.state['deck.layout'].some((ids, i) => { | 	store.s['deck.layout'].some((ids, i) => { | ||||||
| 		if (ids.includes(id)) { | 		if (ids.includes(id)) { | ||||||
| 			const right = store.state['deck.layout'][i + 1]; | 			const right = store.s['deck.layout'][i + 1]; | ||||||
| 			if (right) { | 			if (right) { | ||||||
| 				layout[i + 1] = store.state['deck.layout'][i]; | 				layout[i + 1] = store.s['deck.layout'][i]; | ||||||
| 				layout[i] = right; | 				layout[i] = right; | ||||||
| 				store.set('deck.layout', layout); | 				store.commit('deck.layout', layout); | ||||||
| 			} | 			} | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| @@ -171,9 +171,9 @@ export function swapRightColumn(id: Column['id']) { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function swapUpColumn(id: Column['id']) { | export function swapUpColumn(id: Column['id']) { | ||||||
| 	const layout = deepClone(store.state['deck.layout']); | 	const layout = deepClone(store.s['deck.layout']); | ||||||
| 	const idsIndex = store.state['deck.layout'].findIndex(ids => ids.includes(id)); | 	const idsIndex = store.s['deck.layout'].findIndex(ids => ids.includes(id)); | ||||||
| 	const ids = deepClone(store.state['deck.layout'][idsIndex]); | 	const ids = deepClone(store.s['deck.layout'][idsIndex]); | ||||||
| 	ids.some((x, i) => { | 	ids.some((x, i) => { | ||||||
| 		if (x === id) { | 		if (x === id) { | ||||||
| 			const up = ids[i - 1]; | 			const up = ids[i - 1]; | ||||||
| @@ -182,7 +182,7 @@ export function swapUpColumn(id: Column['id']) { | |||||||
| 				ids[i] = up; | 				ids[i] = up; | ||||||
|  |  | ||||||
| 				layout[idsIndex] = ids; | 				layout[idsIndex] = ids; | ||||||
| 				store.set('deck.layout', layout); | 				store.commit('deck.layout', layout); | ||||||
| 			} | 			} | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| @@ -192,9 +192,9 @@ export function swapUpColumn(id: Column['id']) { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function swapDownColumn(id: Column['id']) { | export function swapDownColumn(id: Column['id']) { | ||||||
| 	const layout = deepClone(store.state['deck.layout']); | 	const layout = deepClone(store.s['deck.layout']); | ||||||
| 	const idsIndex = store.state['deck.layout'].findIndex(ids => ids.includes(id)); | 	const idsIndex = store.s['deck.layout'].findIndex(ids => ids.includes(id)); | ||||||
| 	const ids = deepClone(store.state['deck.layout'][idsIndex]); | 	const ids = deepClone(store.s['deck.layout'][idsIndex]); | ||||||
| 	ids.some((x, i) => { | 	ids.some((x, i) => { | ||||||
| 		if (x === id) { | 		if (x === id) { | ||||||
| 			const down = ids[i + 1]; | 			const down = ids[i + 1]; | ||||||
| @@ -203,7 +203,7 @@ export function swapDownColumn(id: Column['id']) { | |||||||
| 				ids[i] = down; | 				ids[i] = down; | ||||||
|  |  | ||||||
| 				layout[idsIndex] = ids; | 				layout[idsIndex] = ids; | ||||||
| 				store.set('deck.layout', layout); | 				store.commit('deck.layout', layout); | ||||||
| 			} | 			} | ||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| @@ -213,74 +213,74 @@ export function swapDownColumn(id: Column['id']) { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function stackLeftColumn(id: Column['id']) { | export function stackLeftColumn(id: Column['id']) { | ||||||
| 	let layout = deepClone(store.state['deck.layout']); | 	let layout = deepClone(store.s['deck.layout']); | ||||||
| 	const i = store.state['deck.layout'].findIndex(ids => ids.includes(id)); | 	const i = store.s['deck.layout'].findIndex(ids => ids.includes(id)); | ||||||
| 	layout = layout.map(ids => ids.filter(_id => _id !== id)); | 	layout = layout.map(ids => ids.filter(_id => _id !== id)); | ||||||
| 	layout[i - 1].push(id); | 	layout[i - 1].push(id); | ||||||
| 	layout = layout.filter(ids => ids.length > 0); | 	layout = layout.filter(ids => ids.length > 0); | ||||||
| 	store.set('deck.layout', layout); | 	store.commit('deck.layout', layout); | ||||||
| 	saveDeck(); | 	saveDeck(); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function popRightColumn(id: Column['id']) { | export function popRightColumn(id: Column['id']) { | ||||||
| 	let layout = deepClone(store.state['deck.layout']); | 	let layout = deepClone(store.s['deck.layout']); | ||||||
| 	const i = store.state['deck.layout'].findIndex(ids => ids.includes(id)); | 	const i = store.s['deck.layout'].findIndex(ids => ids.includes(id)); | ||||||
| 	const affected = layout[i]; | 	const affected = layout[i]; | ||||||
| 	layout = layout.map(ids => ids.filter(_id => _id !== id)); | 	layout = layout.map(ids => ids.filter(_id => _id !== id)); | ||||||
| 	layout.splice(i + 1, 0, [id]); | 	layout.splice(i + 1, 0, [id]); | ||||||
| 	layout = layout.filter(ids => ids.length > 0); | 	layout = layout.filter(ids => ids.length > 0); | ||||||
| 	store.set('deck.layout', layout); | 	store.commit('deck.layout', layout); | ||||||
|  |  | ||||||
| 	const columns = deepClone(store.state['deck.columns']); | 	const columns = deepClone(store.s['deck.columns']); | ||||||
| 	for (const column of columns) { | 	for (const column of columns) { | ||||||
| 		if (affected.includes(column.id)) { | 		if (affected.includes(column.id)) { | ||||||
| 			column.active = true; | 			column.active = true; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	store.set('deck.columns', columns); | 	store.commit('deck.columns', columns); | ||||||
|  |  | ||||||
| 	saveDeck(); | 	saveDeck(); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function addColumnWidget(id: Column['id'], widget: ColumnWidget) { | export function addColumnWidget(id: Column['id'], widget: ColumnWidget) { | ||||||
| 	const columns = deepClone(store.state['deck.columns']); | 	const columns = deepClone(store.s['deck.columns']); | ||||||
| 	const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id); | 	const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id); | ||||||
| 	const column = deepClone(store.state['deck.columns'][columnIndex]); | 	const column = deepClone(store.s['deck.columns'][columnIndex]); | ||||||
| 	if (column == null) return; | 	if (column == null) return; | ||||||
| 	if (column.widgets == null) column.widgets = []; | 	if (column.widgets == null) column.widgets = []; | ||||||
| 	column.widgets.unshift(widget); | 	column.widgets.unshift(widget); | ||||||
| 	columns[columnIndex] = column; | 	columns[columnIndex] = column; | ||||||
| 	store.set('deck.columns', columns); | 	store.commit('deck.columns', columns); | ||||||
| 	saveDeck(); | 	saveDeck(); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { | export function removeColumnWidget(id: Column['id'], widget: ColumnWidget) { | ||||||
| 	const columns = deepClone(store.state['deck.columns']); | 	const columns = deepClone(store.s['deck.columns']); | ||||||
| 	const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id); | 	const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id); | ||||||
| 	const column = deepClone(store.state['deck.columns'][columnIndex]); | 	const column = deepClone(store.s['deck.columns'][columnIndex]); | ||||||
| 	if (column == null) return; | 	if (column == null) return; | ||||||
| 	if (column.widgets == null) column.widgets = []; | 	if (column.widgets == null) column.widgets = []; | ||||||
| 	column.widgets = column.widgets.filter(w => w.id !== widget.id); | 	column.widgets = column.widgets.filter(w => w.id !== widget.id); | ||||||
| 	columns[columnIndex] = column; | 	columns[columnIndex] = column; | ||||||
| 	store.set('deck.columns', columns); | 	store.commit('deck.columns', columns); | ||||||
| 	saveDeck(); | 	saveDeck(); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) { | export function setColumnWidgets(id: Column['id'], widgets: ColumnWidget[]) { | ||||||
| 	const columns = deepClone(store.state['deck.columns']); | 	const columns = deepClone(store.s['deck.columns']); | ||||||
| 	const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id); | 	const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id); | ||||||
| 	const column = deepClone(store.state['deck.columns'][columnIndex]); | 	const column = deepClone(store.s['deck.columns'][columnIndex]); | ||||||
| 	if (column == null) return; | 	if (column == null) return; | ||||||
| 	column.widgets = widgets; | 	column.widgets = widgets; | ||||||
| 	columns[columnIndex] = column; | 	columns[columnIndex] = column; | ||||||
| 	store.set('deck.columns', columns); | 	store.commit('deck.columns', columns); | ||||||
| 	saveDeck(); | 	saveDeck(); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) { | export function updateColumnWidget(id: Column['id'], widgetId: string, widgetData: any) { | ||||||
| 	const columns = deepClone(store.state['deck.columns']); | 	const columns = deepClone(store.s['deck.columns']); | ||||||
| 	const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id); | 	const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id); | ||||||
| 	const column = deepClone(store.state['deck.columns'][columnIndex]); | 	const column = deepClone(store.s['deck.columns'][columnIndex]); | ||||||
| 	if (column == null) return; | 	if (column == null) return; | ||||||
| 	if (column.widgets == null) column.widgets = []; | 	if (column.widgets == null) column.widgets = []; | ||||||
| 	column.widgets = column.widgets.map(w => w.id === widgetId ? { | 	column.widgets = column.widgets.map(w => w.id === widgetId ? { | ||||||
| @@ -288,19 +288,19 @@ export function updateColumnWidget(id: Column['id'], widgetId: string, widgetDat | |||||||
| 		data: widgetData, | 		data: widgetData, | ||||||
| 	} : w); | 	} : w); | ||||||
| 	columns[columnIndex] = column; | 	columns[columnIndex] = column; | ||||||
| 	store.set('deck.columns', columns); | 	store.commit('deck.columns', columns); | ||||||
| 	saveDeck(); | 	saveDeck(); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function updateColumn(id: Column['id'], column: Partial<Column>) { | export function updateColumn(id: Column['id'], column: Partial<Column>) { | ||||||
| 	const columns = deepClone(store.state['deck.columns']); | 	const columns = deepClone(store.s['deck.columns']); | ||||||
| 	const columnIndex = store.state['deck.columns'].findIndex(c => c.id === id); | 	const columnIndex = store.s['deck.columns'].findIndex(c => c.id === id); | ||||||
| 	const currentColumn = deepClone(store.state['deck.columns'][columnIndex]); | 	const currentColumn = deepClone(store.s['deck.columns'][columnIndex]); | ||||||
| 	if (currentColumn == null) return; | 	if (currentColumn == null) return; | ||||||
| 	for (const [k, v] of Object.entries(column)) { | 	for (const [k, v] of Object.entries(column)) { | ||||||
| 		currentColumn[k] = v; | 		currentColumn[k] = v; | ||||||
| 	} | 	} | ||||||
| 	columns[columnIndex] = currentColumn; | 	columns[columnIndex] = currentColumn; | ||||||
| 	store.set('deck.columns', columns); | 	store.commit('deck.columns', columns); | ||||||
| 	saveDeck(); | 	saveDeck(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -406,7 +406,7 @@ const easterEggEngine = ref<{ stop: () => void } | null>(null); | |||||||
| const containerEl = shallowRef<HTMLElement>(); | const containerEl = shallowRef<HTMLElement>(); | ||||||
|  |  | ||||||
| function iconLoaded() { | function iconLoaded() { | ||||||
| 	const emojis = store.state.reactions; | 	const emojis = store.s.reactions; | ||||||
| 	const containerWidth = containerEl.value.offsetWidth; | 	const containerWidth = containerEl.value.offsetWidth; | ||||||
| 	for (let i = 0; i < 32; i++) { | 	for (let i = 0; i < 32; i++) { | ||||||
| 		easterEggEmojis.value.push({ | 		easterEggEmojis.value.push({ | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 				<MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ i18n.ts.notificationSetting }}</MkButton> | 				<MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ i18n.ts.notificationSetting }}</MkButton> | ||||||
| 			</div> | 			</div> | ||||||
|  |  | ||||||
| 			<MkInfo v-if="!store.reactiveState.abusesTutorial.value" closable @close="closeTutorial()"> | 			<MkInfo v-if="!store.r.abusesTutorial.value" closable @close="closeTutorial()"> | ||||||
| 				{{ i18n.ts._abuseUserReport.resolveTutorial }} | 				{{ i18n.ts._abuseUserReport.resolveTutorial }} | ||||||
| 			</MkInfo> | 			</MkInfo> | ||||||
|  |  | ||||||
| @@ -93,7 +93,7 @@ function resolved(reportId) { | |||||||
| } | } | ||||||
|  |  | ||||||
| function closeTutorial() { | function closeTutorial() { | ||||||
| 	store.set('abusesTutorial', false); | 	store.commit('abusesTutorial', false); | ||||||
| } | } | ||||||
|  |  | ||||||
| const headerActions = computed(() => []); | const headerActions = computed(() => []); | ||||||
|   | |||||||
| @@ -54,7 +54,7 @@ async function renderChart() { | |||||||
|  |  | ||||||
| 	const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' }); | 	const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' }); | ||||||
|  |  | ||||||
| 	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | 	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
|  |  | ||||||
| 	const colorRead = '#3498db'; | 	const colorRead = '#3498db'; | ||||||
| 	const colorWrite = '#2ecc71'; | 	const colorWrite = '#2ecc71'; | ||||||
|   | |||||||
| @@ -68,7 +68,7 @@ onMounted(async () => { | |||||||
|  |  | ||||||
| 	const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' }); | 	const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' }); | ||||||
|  |  | ||||||
| 	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | 	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
| 	const succColor = '#87e000'; | 	const succColor = '#87e000'; | ||||||
| 	const failColor = '#ff4400'; | 	const failColor = '#ff4400'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ const color = | |||||||
| 	'?' as never; | 	'?' as never; | ||||||
|  |  | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
| 	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | 	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
|  |  | ||||||
| 	chartInstance = new Chart(chartEl.value, { | 	chartInstance = new Chart(chartEl.value, { | ||||||
| 		type: 'line', | 		type: 'line', | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ const color = | |||||||
| 	'?' as never; | 	'?' as never; | ||||||
|  |  | ||||||
| onMounted(() => { | onMounted(() => { | ||||||
| 	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | 	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
|  |  | ||||||
| 	chartInstance = new Chart(chartEl.value, { | 	chartInstance = new Chart(chartEl.value, { | ||||||
| 		type: 'line', | 		type: 'line', | ||||||
|   | |||||||
| @@ -56,7 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 			</div> | 			</div> | ||||||
|  |  | ||||||
| 			<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove"> | 			<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove"> | ||||||
| 				<img v-if="store.state.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/> | 				<img v-if="store.s.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/> | ||||||
| 				<img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/> | 				<img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/> | ||||||
| 				<canvas ref="canvasEl" :class="$style.canvas"/> | 				<canvas ref="canvasEl" :class="$style.canvas"/> | ||||||
| 				<Transition | 				<Transition | ||||||
| @@ -858,7 +858,7 @@ function updateSettings< | |||||||
| >(key: K, value: V) { | >(key: K, value: V) { | ||||||
| 	const changes: { [P in K]?: V } = {}; | 	const changes: { [P in K]?: V } = {}; | ||||||
| 	changes[key] = value; | 	changes[key] = value; | ||||||
| 	prefer.set('game.dropAndFusion', { | 	prefer.commit('game.dropAndFusion', { | ||||||
| 		...prefer.s['game.dropAndFusion'], | 		...prefer.s['game.dropAndFusion'], | ||||||
| 		...changes, | 		...changes, | ||||||
| 	}); | 	}); | ||||||
|   | |||||||
| @@ -144,7 +144,7 @@ if (prefer.s.uploadFolder) { | |||||||
|  |  | ||||||
| function chooseUploadFolder() { | function chooseUploadFolder() { | ||||||
| 	os.selectDriveFolder(false).then(async folder => { | 	os.selectDriveFolder(false).then(async folder => { | ||||||
| 		prefer.set('uploadFolder', folder[0] ? folder[0].id : null); | 		prefer.commit('uploadFolder', folder[0] ? folder[0].id : null); | ||||||
| 		os.success(); | 		os.success(); | ||||||
| 		if (prefer.s.uploadFolder) { | 		if (prefer.s.uploadFolder) { | ||||||
| 			uploadFolder.value = await misskeyApi('drive/folders/show', { | 			uploadFolder.value = await misskeyApi('drive/folders/show', { | ||||||
|   | |||||||
| @@ -154,8 +154,8 @@ import MkFolder from '@/components/MkFolder.vue'; | |||||||
| import { prefer } from '@/preferences.js'; | import { prefer } from '@/preferences.js'; | ||||||
| import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; | import MkPreferenceContainer from '@/components/MkPreferenceContainer.vue'; | ||||||
|  |  | ||||||
| const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(store.state.reactions)); | const pinnedEmojisForReaction: Ref<string[]> = ref(deepClone(store.s.reactions)); | ||||||
| const pinnedEmojis: Ref<string[]> = ref(deepClone(store.state.pinnedEmojis)); | const pinnedEmojis: Ref<string[]> = ref(deepClone(store.s.pinnedEmojis)); | ||||||
|  |  | ||||||
| const emojiPickerScale = prefer.model('emojiPickerScale'); | const emojiPickerScale = prefer.model('emojiPickerScale'); | ||||||
| const emojiPickerWidth = prefer.model('emojiPickerWidth'); | const emojiPickerWidth = prefer.model('emojiPickerWidth'); | ||||||
| @@ -240,13 +240,13 @@ function getHTMLElement(ev: MouseEvent): HTMLElement { | |||||||
| } | } | ||||||
|  |  | ||||||
| watch(pinnedEmojisForReaction, () => { | watch(pinnedEmojisForReaction, () => { | ||||||
| 	store.set('reactions', pinnedEmojisForReaction.value); | 	store.commit('reactions', pinnedEmojisForReaction.value); | ||||||
| }, { | }, { | ||||||
| 	deep: true, | 	deep: true, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| watch(pinnedEmojis, () => { | watch(pinnedEmojis, () => { | ||||||
| 	store.set('pinnedEmojis', pinnedEmojis.value); | 	store.commit('pinnedEmojis', pinnedEmojis.value); | ||||||
| }, { | }, { | ||||||
| 	deep: true, | 	deep: true, | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -159,7 +159,7 @@ import { store } from '@/store.js'; | |||||||
|  |  | ||||||
| const excludeMutingUsers = ref(false); | const excludeMutingUsers = ref(false); | ||||||
| const excludeInactiveUsers = ref(false); | const excludeInactiveUsers = ref(false); | ||||||
| const withReplies = ref(store.state.defaultWithReplies); | const withReplies = ref(store.s.defaultWithReplies); | ||||||
|  |  | ||||||
| const onExportSuccess = () => { | const onExportSuccess = () => { | ||||||
| 	os.alert({ | 	os.alert({ | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 				<div v-if="!narrow || currentPage?.route.name == null" class="nav"> | 				<div v-if="!narrow || currentPage?.route.name == null" class="nav"> | ||||||
| 					<div class="baaadecd"> | 					<div class="baaadecd"> | ||||||
| 						<MkInfo v-if="emailNotConfigured" warn class="info">{{ i18n.ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo> | 						<MkInfo v-if="emailNotConfigured" warn class="info">{{ i18n.ts.emailNotConfiguredWarning }} <MkA to="/settings/email" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo> | ||||||
| 						<MkInfo v-if="!store.reactiveState.enablePreferencesAutoCloudBackup.value && store.reactiveState.showPreferencesAutoCloudBackupSuggestion.value" class="info"> | 						<MkInfo v-if="!store.r.enablePreferencesAutoCloudBackup.value && store.r.showPreferencesAutoCloudBackupSuggestion.value" class="info"> | ||||||
| 							<div>{{ i18n.ts._preferencesBackup.autoPreferencesBackupIsNotEnabledForThisDevice }}</div> | 							<div>{{ i18n.ts._preferencesBackup.autoPreferencesBackupIsNotEnabledForThisDevice }}</div> | ||||||
| 							<div><button class="_textButton" @click="enableAutoBackup">{{ i18n.ts.enable }}</button> | <button class="_textButton" @click="skipAutoBackup">{{ i18n.ts.skip }}</button></div> | 							<div><button class="_textButton" @click="enableAutoBackup">{{ i18n.ts.enable }}</button> | <button class="_textButton" @click="skipAutoBackup">{{ i18n.ts.skip }}</button></div> | ||||||
| 						</MkInfo> | 						</MkInfo> | ||||||
| @@ -72,7 +72,7 @@ const ro = new ResizeObserver((entries, observer) => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| function skipAutoBackup() { | function skipAutoBackup() { | ||||||
| 	store.set('showPreferencesAutoCloudBackupSuggestion', false); | 	store.commit('showPreferencesAutoCloudBackupSuggestion', false); | ||||||
| } | } | ||||||
|  |  | ||||||
| const menuDef = computed<SuperMenuDef[]>(() => [{ | const menuDef = computed<SuperMenuDef[]>(() => [{ | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ const items = ref(prefer.s.menu.map(x => ({ | |||||||
| 	type: x, | 	type: x, | ||||||
| }))); | }))); | ||||||
|  |  | ||||||
| const menuDisplay = computed(store.makeGetterSetter('menuDisplay')); | const menuDisplay = store.model('menuDisplay'); | ||||||
|  |  | ||||||
| async function addItem() { | async function addItem() { | ||||||
| 	const menu = Object.keys(navbarItemDef).filter(k => !prefer.s.menu.includes(k)); | 	const menu = Object.keys(navbarItemDef).filter(k => !prefer.s.menu.includes(k)); | ||||||
| @@ -91,7 +91,7 @@ function removeItem(index: number) { | |||||||
| } | } | ||||||
|  |  | ||||||
| async function save() { | async function save() { | ||||||
| 	prefer.set('menu', items.value.map(x => x.type)); | 	prefer.commit('menu', items.value.map(x => x.type)); | ||||||
| 	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); | 	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -131,7 +131,7 @@ const reportError = prefer.model('reportError'); | |||||||
| const enableCondensedLine = prefer.model('enableCondensedLine'); | const enableCondensedLine = prefer.model('enableCondensedLine'); | ||||||
| const skipNoteRender = prefer.model('skipNoteRender'); | const skipNoteRender = prefer.model('skipNoteRender'); | ||||||
| const devMode = prefer.model('devMode'); | const devMode = prefer.model('devMode'); | ||||||
| const defaultWithReplies = computed(store.makeGetterSetter('defaultWithReplies')); | const defaultWithReplies = store.model('defaultWithReplies'); | ||||||
|  |  | ||||||
| watch(skipNoteRender, async () => { | watch(skipNoteRender, async () => { | ||||||
| 	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); | 	await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); | ||||||
|   | |||||||
| @@ -337,8 +337,8 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 							<template #label><SearchLabel>{{ i18n.ts.additionalEmojiDictionary }}</SearchLabel></template> | 							<template #label><SearchLabel>{{ i18n.ts.additionalEmojiDictionary }}</SearchLabel></template> | ||||||
| 							<div class="_buttons"> | 							<div class="_buttons"> | ||||||
| 								<template v-for="lang in emojiIndexLangs" :key="lang"> | 								<template v-for="lang in emojiIndexLangs" :key="lang"> | ||||||
| 									<MkButton v-if="store.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton> | 									<MkButton v-if="store.r.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton> | ||||||
| 									<MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ store.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton> | 									<MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ getEmojiIndexLangName(lang) }}{{ store.r.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton> | ||||||
| 								</template> | 								</template> | ||||||
| 							</div> | 							</div> | ||||||
| 						</MkFolder> | 						</MkFolder> | ||||||
| @@ -449,7 +449,7 @@ function getEmojiIndexLangName(targetLang: typeof emojiIndexLangs[number]) { | |||||||
|  |  | ||||||
| function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) { | function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) { | ||||||
| 	async function main() { | 	async function main() { | ||||||
| 		const currentIndexes = store.state.additionalUnicodeEmojiIndexes; | 		const currentIndexes = store.s.additionalUnicodeEmojiIndexes; | ||||||
|  |  | ||||||
| 		function download() { | 		function download() { | ||||||
| 			switch (lang) { | 			switch (lang) { | ||||||
| @@ -461,7 +461,7 @@ function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) { | |||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		currentIndexes[lang] = await download(); | 		currentIndexes[lang] = await download(); | ||||||
| 		await store.set('additionalUnicodeEmojiIndexes', currentIndexes); | 		await store.commit('additionalUnicodeEmojiIndexes', currentIndexes); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	os.promiseDialog(main()); | 	os.promiseDialog(main()); | ||||||
| @@ -469,9 +469,9 @@ function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) { | |||||||
|  |  | ||||||
| function removeEmojiIndex(lang: string) { | function removeEmojiIndex(lang: string) { | ||||||
| 	async function main() { | 	async function main() { | ||||||
| 		const currentIndexes = store.state.additionalUnicodeEmojiIndexes; | 		const currentIndexes = store.s.additionalUnicodeEmojiIndexes; | ||||||
| 		delete currentIndexes[lang]; | 		delete currentIndexes[lang]; | ||||||
| 		await store.set('additionalUnicodeEmojiIndexes', currentIndexes); | 		await store.commit('additionalUnicodeEmojiIndexes', currentIndexes); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	os.promiseDialog(main()); | 	os.promiseDialog(main()); | ||||||
| @@ -488,11 +488,11 @@ async function setPinnedList() { | |||||||
| 	if (canceled) return; | 	if (canceled) return; | ||||||
| 	if (list == null) return; | 	if (list == null) return; | ||||||
|  |  | ||||||
| 	prefer.set('pinnedUserLists', [list]); | 	prefer.commit('pinnedUserLists', [list]); | ||||||
| } | } | ||||||
|  |  | ||||||
| function removePinnedList() { | function removePinnedList() { | ||||||
| 	prefer.set('pinnedUserLists', []); | 	prefer.commit('pinnedUserLists', []); | ||||||
| } | } | ||||||
|  |  | ||||||
| function enableAllDataSaver() { | function enableAllDataSaver() { | ||||||
| @@ -512,7 +512,7 @@ function disableAllDataSaver() { | |||||||
| } | } | ||||||
|  |  | ||||||
| watch(dataSaver, (to) => { | watch(dataSaver, (to) => { | ||||||
| 	prefer.set('dataSaver', to); | 	prefer.commit('dataSaver', to); | ||||||
| }, { | }, { | ||||||
| 	deep: true, | 	deep: true, | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -177,7 +177,7 @@ const $i = signinRequired(); | |||||||
|  |  | ||||||
| const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); | const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); | ||||||
|  |  | ||||||
| const reactionAcceptance = computed(store.makeGetterSetter('reactionAcceptance')); | const reactionAcceptance = store.model('reactionAcceptance'); | ||||||
|  |  | ||||||
| function assertVaildLang(lang: string | null): lang is keyof typeof langmap { | function assertVaildLang(lang: string | null): lang is keyof typeof langmap { | ||||||
| 	return lang != null && lang in langmap; | 	return lang != null && lang in langmap; | ||||||
|   | |||||||
| @@ -101,14 +101,14 @@ async function updated(type: keyof typeof sounds.value, sound) { | |||||||
| 		volume: sound.volume, | 		volume: sound.volume, | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	prefer.set(`sound.on.${type}`, v); | 	prefer.commit(`sound.on.${type}`, v); | ||||||
| 	sounds.value[type] = v; | 	sounds.value[type] = v; | ||||||
| } | } | ||||||
|  |  | ||||||
| function reset() { | function reset() { | ||||||
| 	for (const sound of Object.keys(sounds.value) as Array<keyof typeof sounds.value>) { | 	for (const sound of Object.keys(sounds.value) as Array<keyof typeof sounds.value>) { | ||||||
| 		const v = PREF_DEF[`sound.on.${sound}`].default; | 		const v = PREF_DEF[`sound.on.${sound}`].default; | ||||||
| 		prefer.set(`sound.on.${sound}`, v); | 		prefer.commit(`sound.on.${sound}`, v); | ||||||
| 		sounds.value[sound] = v; | 		sounds.value[sound] = v; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -137,10 +137,10 @@ async function save() { | |||||||
| 	const i = prefer.s.statusbars.findIndex(x => x.id === props._id); | 	const i = prefer.s.statusbars.findIndex(x => x.id === props._id); | ||||||
| 	const statusbars = deepClone(prefer.s.statusbars); | 	const statusbars = deepClone(prefer.s.statusbars); | ||||||
| 	statusbars[i] = deepClone(statusbar); | 	statusbars[i] = deepClone(statusbar); | ||||||
| 	prefer.set('statusbars', statusbars); | 	prefer.commit('statusbars', statusbars); | ||||||
| } | } | ||||||
|  |  | ||||||
| function del() { | function del() { | ||||||
| 	prefer.set('statusbars', prefer.s.statusbars.filter(x => x.id !== props._id)); | 	prefer.commit('statusbars', prefer.s.statusbars.filter(x => x.id !== props._id)); | ||||||
| } | } | ||||||
| </script> | </script> | ||||||
|   | |||||||
| @@ -37,7 +37,7 @@ onMounted(() => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| async function add() { | async function add() { | ||||||
| 	prefer.set('statusbars', [...statusbars.value, { | 	prefer.commit('statusbars', [...statusbars.value, { | ||||||
| 		id: uuid(), | 		id: uuid(), | ||||||
| 		type: null, | 		type: null, | ||||||
| 		black: false, | 		black: false, | ||||||
|   | |||||||
| @@ -179,7 +179,7 @@ const darkThemeId = computed({ | |||||||
| 	set(id) { | 	set(id) { | ||||||
| 		const t = themes.value.find(x => x.id === id); | 		const t = themes.value.find(x => x.id === id); | ||||||
| 		if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる | 		if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる | ||||||
| 			prefer.set('darkTheme', t); | 			prefer.commit('darkTheme', t); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
| @@ -191,19 +191,19 @@ const lightThemeId = computed({ | |||||||
| 	set(id) { | 	set(id) { | ||||||
| 		const t = themes.value.find(x => x.id === id); | 		const t = themes.value.find(x => x.id === id); | ||||||
| 		if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる | 		if (t) { // テーマエディタでテーマを作成したときなどは、themesに反映されないため undefined になる | ||||||
| 			prefer.set('lightTheme', t); | 			prefer.commit('lightTheme', t); | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const darkMode = computed(store.makeGetterSetter('darkMode')); | const darkMode = store.model('darkMode'); | ||||||
| const syncDeviceDarkMode = prefer.model('syncDeviceDarkMode'); | const syncDeviceDarkMode = prefer.model('syncDeviceDarkMode'); | ||||||
| const wallpaper = ref(miLocalStorage.getItem('wallpaper')); | const wallpaper = ref(miLocalStorage.getItem('wallpaper')); | ||||||
| const themesCount = installedThemes.value.length; | const themesCount = installedThemes.value.length; | ||||||
|  |  | ||||||
| watch(syncDeviceDarkMode, () => { | watch(syncDeviceDarkMode, () => { | ||||||
| 	if (syncDeviceDarkMode.value) { | 	if (syncDeviceDarkMode.value) { | ||||||
| 		store.set('darkMode', isDeviceDarkmode()); | 		store.commit('darkMode', isDeviceDarkmode()); | ||||||
| 	} | 	} | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -44,11 +44,11 @@ const pagination = { | |||||||
| const notes = ref<InstanceType<typeof MkNotes>>(); | const notes = ref<InstanceType<typeof MkNotes>>(); | ||||||
|  |  | ||||||
| async function post() { | async function post() { | ||||||
| 	store.set('postFormHashtags', props.tag); | 	store.commit('postFormHashtags', props.tag); | ||||||
| 	store.set('postFormWithHashtags', true); | 	store.commit('postFormWithHashtags', true); | ||||||
| 	await os.post(); | 	await os.post(); | ||||||
| 	store.set('postFormHashtags', ''); | 	store.commit('postFormHashtags', ''); | ||||||
| 	store.set('postFormWithHashtags', false); | 	store.commit('postFormWithHashtags', false); | ||||||
| 	notes.value?.pagingComponent?.reload(); | 	notes.value?.pagingComponent?.reload(); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -200,10 +200,10 @@ async function saveAs() { | |||||||
| 	if (description.value) theme.value.desc = description.value; | 	if (description.value) theme.value.desc = description.value; | ||||||
| 	await addTheme(theme.value); | 	await addTheme(theme.value); | ||||||
| 	applyTheme(theme.value); | 	applyTheme(theme.value); | ||||||
| 	if (store.state.darkMode) { | 	if (store.s.darkMode) { | ||||||
| 		prefer.set('darkTheme', theme.value); | 		prefer.commit('darkTheme', theme.value); | ||||||
| 	} else { | 	} else { | ||||||
| 		prefer.set('lightTheme', theme.value); | 		prefer.commit('lightTheme', theme.value); | ||||||
| 	} | 	} | ||||||
| 	changed.value = false; | 	changed.value = false; | ||||||
| 	os.alert({ | 	os.alert({ | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	<MkSpacer :contentMax="800"> | 	<MkSpacer :contentMax="800"> | ||||||
| 		<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin"> | 		<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin"> | ||||||
| 			<div :key="src" ref="rootEl"> | 			<div :key="src" ref="rootEl"> | ||||||
| 				<MkInfo v-if="isBasicTimeline(src) && !store.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()"> | 				<MkInfo v-if="isBasicTimeline(src) && !store.r.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()"> | ||||||
| 					{{ i18n.ts._timelineDescription[src] }} | 					{{ i18n.ts._timelineDescription[src] }} | ||||||
| 				</MkInfo> | 				</MkInfo> | ||||||
| 				<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/> | 				<MkPostForm v-if="prefer.r.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/> | ||||||
| @@ -67,18 +67,18 @@ type TimelinePageSrc = BasicTimelineType | `list:${string}`; | |||||||
| const queue = ref(0); | const queue = ref(0); | ||||||
| const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global'); | const srcWhenNotSignin = ref<'local' | 'global'>(isAvailableBasicTimeline('local') ? 'local' : 'global'); | ||||||
| const src = computed<TimelinePageSrc>({ | const src = computed<TimelinePageSrc>({ | ||||||
| 	get: () => ($i ? store.reactiveState.tl.value.src : srcWhenNotSignin.value), | 	get: () => ($i ? store.r.tl.value.src : srcWhenNotSignin.value), | ||||||
| 	set: (x) => saveSrc(x), | 	set: (x) => saveSrc(x), | ||||||
| }); | }); | ||||||
| const withRenotes = computed<boolean>({ | const withRenotes = computed<boolean>({ | ||||||
| 	get: () => store.reactiveState.tl.value.filter.withRenotes, | 	get: () => store.r.tl.value.filter.withRenotes, | ||||||
| 	set: (x) => saveTlFilter('withRenotes', x), | 	set: (x) => saveTlFilter('withRenotes', x), | ||||||
| }); | }); | ||||||
|  |  | ||||||
| // computed内での無限ループを防ぐためのフラグ | // computed内での無限ループを防ぐためのフラグ | ||||||
| const localSocialTLFilterSwitchStore = ref<'withReplies' | 'onlyFiles' | false>( | const localSocialTLFilterSwitchStore = ref<'withReplies' | 'onlyFiles' | false>( | ||||||
| 	store.reactiveState.tl.value.filter.withReplies ? 'withReplies' : | 	store.r.tl.value.filter.withReplies ? 'withReplies' : | ||||||
| 	store.reactiveState.tl.value.filter.onlyFiles ? 'onlyFiles' : | 	store.r.tl.value.filter.onlyFiles ? 'onlyFiles' : | ||||||
| 	false, | 	false, | ||||||
| ); | ); | ||||||
|  |  | ||||||
| @@ -88,7 +88,7 @@ const withReplies = computed<boolean>({ | |||||||
| 		if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'onlyFiles') { | 		if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'onlyFiles') { | ||||||
| 			return false; | 			return false; | ||||||
| 		} else { | 		} else { | ||||||
| 			return store.reactiveState.tl.value.filter.withReplies; | 			return store.r.tl.value.filter.withReplies; | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	set: (x) => saveTlFilter('withReplies', x), | 	set: (x) => saveTlFilter('withReplies', x), | ||||||
| @@ -98,7 +98,7 @@ const onlyFiles = computed<boolean>({ | |||||||
| 		if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'withReplies') { | 		if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'withReplies') { | ||||||
| 			return false; | 			return false; | ||||||
| 		} else { | 		} else { | ||||||
| 			return store.reactiveState.tl.value.filter.onlyFiles; | 			return store.r.tl.value.filter.onlyFiles; | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	set: (x) => saveTlFilter('onlyFiles', x), | 	set: (x) => saveTlFilter('onlyFiles', x), | ||||||
| @@ -115,7 +115,7 @@ watch([withReplies, onlyFiles], ([withRepliesTo, onlyFilesTo]) => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| const withSensitive = computed<boolean>({ | const withSensitive = computed<boolean>({ | ||||||
| 	get: () => store.reactiveState.tl.value.filter.withSensitive, | 	get: () => store.r.tl.value.filter.withSensitive, | ||||||
| 	set: (x) => saveTlFilter('withSensitive', x), | 	set: (x) => saveTlFilter('withSensitive', x), | ||||||
| }); | }); | ||||||
|  |  | ||||||
| @@ -196,23 +196,23 @@ async function chooseChannel(ev: MouseEvent): Promise<void> { | |||||||
| } | } | ||||||
|  |  | ||||||
| function saveSrc(newSrc: TimelinePageSrc): void { | function saveSrc(newSrc: TimelinePageSrc): void { | ||||||
| 	const out = deepMerge({ src: newSrc }, store.state.tl); | 	const out = deepMerge({ src: newSrc }, store.s.tl); | ||||||
|  |  | ||||||
| 	if (newSrc.startsWith('userList:')) { | 	if (newSrc.startsWith('userList:')) { | ||||||
| 		const id = newSrc.substring('userList:'.length); | 		const id = newSrc.substring('userList:'.length); | ||||||
| 		out.userList = prefer.r.pinnedUserLists.value.find(l => l.id === id) ?? null; | 		out.userList = prefer.r.pinnedUserLists.value.find(l => l.id === id) ?? null; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	store.set('tl', out); | 	store.commit('tl', out); | ||||||
| 	if (['local', 'global'].includes(newSrc)) { | 	if (['local', 'global'].includes(newSrc)) { | ||||||
| 		srcWhenNotSignin.value = newSrc as 'local' | 'global'; | 		srcWhenNotSignin.value = newSrc as 'local' | 'global'; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| function saveTlFilter(key: keyof typeof store.state.tl.filter, newValue: boolean) { | function saveTlFilter(key: keyof typeof store.s.tl.filter, newValue: boolean) { | ||||||
| 	if (key !== 'withReplies' || $i) { | 	if (key !== 'withReplies' || $i) { | ||||||
| 		const out = deepMerge({ filter: { [key]: newValue } }, store.state.tl); | 		const out = deepMerge({ filter: { [key]: newValue } }, store.s.tl); | ||||||
| 		store.set('tl', out); | 		store.commit('tl', out); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -231,9 +231,9 @@ function focus(): void { | |||||||
|  |  | ||||||
| function closeTutorial(): void { | function closeTutorial(): void { | ||||||
| 	if (!isBasicTimeline(src.value)) return; | 	if (!isBasicTimeline(src.value)) return; | ||||||
| 	const before = store.state.timelineTutorials; | 	const before = store.s.timelineTutorials; | ||||||
| 	before[src.value] = true; | 	before[src.value] = true; | ||||||
| 	store.set('timelineTutorials', before); | 	store.commit('timelineTutorials', before); | ||||||
| } | } | ||||||
|  |  | ||||||
| function switchTlIfNeeded() { | function switchTlIfNeeded() { | ||||||
|   | |||||||
| @@ -64,7 +64,7 @@ async function renderChart() { | |||||||
|  |  | ||||||
| 	const raw = await misskeyApi('charts/user/following', { userId: props.user.id, limit: chartLimit, span: 'day' }); | 	const raw = await misskeyApi('charts/user/following', { userId: props.user.id, limit: chartLimit, span: 'day' }); | ||||||
|  |  | ||||||
| 	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | 	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
|  |  | ||||||
| 	const colorFollowLocal = '#008FFB'; | 	const colorFollowLocal = '#008FFB'; | ||||||
| 	const colorFollowRemote = '#008FFB88'; | 	const colorFollowRemote = '#008FFB88'; | ||||||
|   | |||||||
| @@ -64,7 +64,7 @@ async function renderChart() { | |||||||
|  |  | ||||||
| 	const raw = await misskeyApi('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' }); | 	const raw = await misskeyApi('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' }); | ||||||
|  |  | ||||||
| 	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | 	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
|  |  | ||||||
| 	const colorNormal = '#008FFB'; | 	const colorNormal = '#008FFB'; | ||||||
| 	const colorReply = '#FEB019'; | 	const colorReply = '#FEB019'; | ||||||
|   | |||||||
| @@ -64,7 +64,7 @@ async function renderChart() { | |||||||
|  |  | ||||||
| 	const raw = await misskeyApi('charts/user/pv', { userId: props.user.id, limit: chartLimit, span: 'day' }); | 	const raw = await misskeyApi('charts/user/pv', { userId: props.user.id, limit: chartLimit, span: 'day' }); | ||||||
|  |  | ||||||
| 	const vLineColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | 	const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; | ||||||
|  |  | ||||||
| 	const colorUser = '#3498db'; | 	const colorUser = '#3498db'; | ||||||
| 	const colorVisitor = '#2ecc71'; | 	const colorVisitor = '#2ecc71'; | ||||||
|   | |||||||
| @@ -3,252 +3,70 @@ | |||||||
|  * SPDX-License-Identifier: AGPL-3.0-only |  * SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| // PIZZAX --- A lightweight store | import { computed, onUnmounted, ref, watch } from 'vue'; | ||||||
|  | import { EventEmitter } from 'eventemitter3'; | ||||||
|  | import type { Ref, WritableComputedRef } from 'vue'; | ||||||
|  |  | ||||||
| import { onUnmounted, ref, watch } from 'vue'; | // NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない | ||||||
| import { BroadcastChannel } from 'broadcast-channel'; |  | ||||||
| import type { Ref } from 'vue'; |  | ||||||
| import { $i } from '@/account.js'; |  | ||||||
| import { misskeyApi } from '@/utility/misskey-api.js'; |  | ||||||
| import { get, set } from '@/utility/idb-proxy.js'; |  | ||||||
| import { store } from '@/store.js'; |  | ||||||
| import { useStream } from '@/stream.js'; |  | ||||||
| import { deepClone } from '@/utility/clone.js'; |  | ||||||
| import { deepMerge } from '@/utility/merge.js'; |  | ||||||
|  |  | ||||||
| type StateDef = Record<string, { | //type DottedToNested<T extends Record<string, any>> = { | ||||||
| 	where: 'account' | 'device' | 'deviceAccount'; | //	[K in keyof T as K extends string ? K extends `${infer A}.${infer B}` ? A : K : K]: K extends `${infer A}.${infer B}` ? DottedToNested<{ [key in B]: T[K] }> : T[K]; | ||||||
| 	default: any; | //}; | ||||||
| }>; |  | ||||||
|  |  | ||||||
| type State<T extends StateDef> = { [K in keyof T]: T[K]['default']; }; | type PizzaxEvent<Data extends Record<string, any>> = { | ||||||
| type ReactiveState<T extends StateDef> = { [K in keyof T]: Ref<T[K]['default']>; }; | 	updated: <K extends keyof Data>(ctx: { | ||||||
|  | 		key: K; | ||||||
| type ArrayElement<A> = A extends readonly (infer T)[] ? T : never; | 		value: Data[K]; | ||||||
|  | 	}) => void; | ||||||
| type PizzaxChannelMessage<T extends StateDef> = { |  | ||||||
| 	where: 'device' | 'deviceAccount'; |  | ||||||
| 	key: keyof T; |  | ||||||
| 	value: T[keyof T]['default']; |  | ||||||
| 	userId?: string; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export class Storage<T extends StateDef> { | export class Pizzax<Data extends Record<string, any>> extends EventEmitter<PizzaxEvent<Data>> { | ||||||
| 	public readonly ready: Promise<void>; | 	/** | ||||||
| 	public readonly loaded: Promise<void>; | 	 * static / state の略 (static が予約語のため) | ||||||
|  | 	 */ | ||||||
|  | 	public s = {} as { | ||||||
|  | 		[K in keyof Data]: Data[K]; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	public readonly key: string; | 	/** | ||||||
| 	public readonly deviceStateKeyName: `pizzax::${this['key']}`; | 	 * reactive の略 | ||||||
| 	public readonly deviceAccountStateKeyName: `pizzax::${this['key']}::${string}` | ''; | 	 */ | ||||||
| 	public readonly registryCacheKeyName: `pizzax::${this['key']}::cache::${string}` | ''; | 	public r = {} as { | ||||||
|  | 		[K in keyof Data]: Ref<Data[K]>; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
| 	public readonly def: T; | 	constructor(data: { [K in keyof Data]: Data[K] }) { | ||||||
|  | 		super(); | ||||||
|  |  | ||||||
| 	// TODO: これが実装されたらreadonlyにしたい: https://github.com/microsoft/TypeScript/issues/37487 | 		for (const key in data) { | ||||||
| 	public readonly state: State<T>; | 			this.s[key] = data[key]; | ||||||
| 	public readonly reactiveState: ReactiveState<T>; | 			this.r[key] = ref(this.s[key]); | ||||||
|  |  | ||||||
| 	private pizzaxChannel: BroadcastChannel<PizzaxChannelMessage<T>>; |  | ||||||
|  |  | ||||||
| 	// 簡易的にキューイングして占有ロックとする |  | ||||||
| 	private currentIdbJob: Promise<any> = Promise.resolve(); |  | ||||||
| 	private addIdbSetJob<T>(job: () => Promise<T>) { |  | ||||||
| 		const promise = this.currentIdbJob.then(job, err => { |  | ||||||
| 			console.error('Pizzax failed to save data to idb!', err); |  | ||||||
| 			return job(); |  | ||||||
| 		}); |  | ||||||
| 		this.currentIdbJob = promise; |  | ||||||
| 		return promise; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	constructor(key: string, def: T) { |  | ||||||
| 		this.key = key; |  | ||||||
| 		this.deviceStateKeyName = `pizzax::${key}`; |  | ||||||
| 		this.deviceAccountStateKeyName = $i ? `pizzax::${key}::${$i.id}` : ''; |  | ||||||
| 		this.registryCacheKeyName = $i ? `pizzax::${key}::cache::${$i.id}` : ''; |  | ||||||
| 		this.def = def; |  | ||||||
|  |  | ||||||
| 		this.pizzaxChannel = new BroadcastChannel(`pizzax::${key}`); |  | ||||||
|  |  | ||||||
| 		this.state = {} as State<T>; |  | ||||||
| 		this.reactiveState = {} as ReactiveState<T>; |  | ||||||
|  |  | ||||||
| 		for (const [k, v] of Object.entries(def) as [keyof T, T[keyof T]['default']][]) { |  | ||||||
| 			this.state[k] = v.default; |  | ||||||
| 			this.reactiveState[k] = ref(v.default); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		this.ready = this.init(); |  | ||||||
| 		this.loaded = this.ready.then(() => this.load()); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	private isPureObject(value: unknown): value is Record<string | number | symbol, unknown> { |  | ||||||
| 		return typeof value === 'object' && value !== null && !Array.isArray(value); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	private mergeState<X>(value: X, def: X): X { |  | ||||||
| 		if (this.isPureObject(value) && this.isPureObject(def)) { |  | ||||||
| 			const merged = deepMerge(value, def); |  | ||||||
|  |  | ||||||
| 			if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged); |  | ||||||
|  |  | ||||||
| 			return merged as X; |  | ||||||
| 		} |  | ||||||
| 		return value; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	private async init(): Promise<void> { |  | ||||||
| 		await this.migrate(); |  | ||||||
|  |  | ||||||
| 		const deviceState: State<T> = await get(this.deviceStateKeyName) || {}; |  | ||||||
| 		const deviceAccountState = $i ? await get(this.deviceAccountStateKeyName) || {} : {}; |  | ||||||
| 		const registryCache = $i ? await get(this.registryCacheKeyName) || {} : {}; |  | ||||||
|  |  | ||||||
| 		for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) { |  | ||||||
| 			if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) { |  | ||||||
| 				this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceState[k], v.default); |  | ||||||
| 			} else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) { |  | ||||||
| 				this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(registryCache[k], v.default); |  | ||||||
| 			} else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) { |  | ||||||
| 				this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default); |  | ||||||
| 			} else { |  | ||||||
| 				this.reactiveState[k].value = this.state[k] = v.default; |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 		this.pizzaxChannel.addEventListener('message', ({ where, key, value, userId }) => { | 	public commit<K extends keyof Data>(key: K, value: Data[K]) { | ||||||
| 			// アカウント変更すればunisonReloadが効くため、このreturnが発火することは | 		const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除 | ||||||
| 			// まずないと思うけど一応弾いておく | 		this.r[key].value = this.s[key] = v; | ||||||
| 			if (where === 'deviceAccount' && !($i && userId !== $i.id)) return; | 		this.emit('updated', { key, value: v }); | ||||||
| 			this.reactiveState[key].value = this.state[key] = value; |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 		if ($i) { |  | ||||||
| 			const connection = useStream().useChannel('main'); |  | ||||||
|  |  | ||||||
| 			// streamingのuser storage updateイベントを監視して更新 |  | ||||||
| 			connection.on('registryUpdated', ({ scope, key, value }: { scope?: string[], key: keyof T, value: T[typeof key]['default'] }) => { |  | ||||||
| 				if (!scope || scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.state[key] === value) return; |  | ||||||
|  |  | ||||||
| 				this.reactiveState[key].value = this.state[key] = value; |  | ||||||
|  |  | ||||||
| 				this.addIdbSetJob(async () => { |  | ||||||
| 					const cache = await get(this.registryCacheKeyName); |  | ||||||
| 					if (cache[key] !== value) { |  | ||||||
| 						cache[key] = value; |  | ||||||
| 						await set(this.registryCacheKeyName, cache); |  | ||||||
| 					} |  | ||||||
| 				}); |  | ||||||
| 			}); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	private load(): Promise<void> { | 	public rewrite<K extends keyof Data>(key: K, value: Data[K]) { | ||||||
| 		return new Promise((resolve, reject) => { | 		const v = JSON.parse(JSON.stringify(value)); // deep copy 兼 vueのプロキシ解除 | ||||||
| 			if ($i) { | 		this.r[key].value = this.s[key] = v; | ||||||
| 				// api関数と循環参照なので一応setTimeoutしておく |  | ||||||
| 				window.setTimeout(async () => { |  | ||||||
| 					await store.ready; |  | ||||||
|  |  | ||||||
| 					misskeyApi('i/registry/get-all', { scope: ['client', this.key] }) |  | ||||||
| 						.then(kvs => { |  | ||||||
| 							const cache: Partial<T> = {}; |  | ||||||
| 							for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) { |  | ||||||
| 								if (v.where === 'account') { |  | ||||||
| 									if (Object.prototype.hasOwnProperty.call(kvs, k)) { |  | ||||||
| 										this.reactiveState[k].value = this.state[k] = (kvs as Partial<T>)[k]; |  | ||||||
| 										cache[k] = (kvs as Partial<T>)[k]; |  | ||||||
| 									} else { |  | ||||||
| 										this.reactiveState[k].value = this.state[k] = v.default; |  | ||||||
| 									} |  | ||||||
| 								} |  | ||||||
| 							} |  | ||||||
|  |  | ||||||
| 							return set(this.registryCacheKeyName, cache); |  | ||||||
| 						}) |  | ||||||
| 						.then(() => resolve()); |  | ||||||
| 				}, 1); |  | ||||||
| 			} else { |  | ||||||
| 				resolve(); |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public set<K extends keyof T>(key: K, value: T[K]['default']): Promise<void> { |  | ||||||
| 		// IndexedDBやBroadcastChannelで扱うために単純なオブジェクトにする |  | ||||||
| 		// (JSON.parse(JSON.stringify(value))の代わり) |  | ||||||
| 		const rawValue = deepClone(value); |  | ||||||
|  |  | ||||||
| 		this.reactiveState[key].value = this.state[key] = rawValue; |  | ||||||
|  |  | ||||||
| 		return this.addIdbSetJob(async () => { |  | ||||||
| 			switch (this.def[key].where) { |  | ||||||
| 				case 'device': { |  | ||||||
| 					this.pizzaxChannel.postMessage({ |  | ||||||
| 						where: 'device', |  | ||||||
| 						key, |  | ||||||
| 						value: rawValue, |  | ||||||
| 					}); |  | ||||||
| 					const deviceState = await get(this.deviceStateKeyName) || {}; |  | ||||||
| 					deviceState[key] = rawValue; |  | ||||||
| 					await set(this.deviceStateKeyName, deviceState); |  | ||||||
| 					break; |  | ||||||
| 				} |  | ||||||
| 				case 'deviceAccount': { |  | ||||||
| 					if ($i == null) break; |  | ||||||
| 					this.pizzaxChannel.postMessage({ |  | ||||||
| 						where: 'deviceAccount', |  | ||||||
| 						key, |  | ||||||
| 						value: rawValue, |  | ||||||
| 						userId: $i.id, |  | ||||||
| 					}); |  | ||||||
| 					const deviceAccountState = await get(this.deviceAccountStateKeyName) || {}; |  | ||||||
| 					deviceAccountState[key] = rawValue; |  | ||||||
| 					await set(this.deviceAccountStateKeyName, deviceAccountState); |  | ||||||
| 					break; |  | ||||||
| 				} |  | ||||||
| 				case 'account': { |  | ||||||
| 					if ($i == null) break; |  | ||||||
| 					const cache = await get(this.registryCacheKeyName) || {}; |  | ||||||
| 					cache[key] = rawValue; |  | ||||||
| 					await set(this.registryCacheKeyName, cache); |  | ||||||
| 					await misskeyApi('i/registry/set', { |  | ||||||
| 						scope: ['client', this.key], |  | ||||||
| 						key: key.toString(), |  | ||||||
| 						value: rawValue, |  | ||||||
| 					}); |  | ||||||
| 					break; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public push<K extends keyof T>(key: K, value: ArrayElement<T[K]['default']>): void { |  | ||||||
| 		const currentState = this.state[key]; |  | ||||||
| 		this.set(key, [...currentState, value]); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public reset(key: keyof T) { |  | ||||||
| 		this.set(key, this.def[key].default); |  | ||||||
| 		return this.def[key].default; |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	/** | 	/** | ||||||
| 	 * 特定のキーの、簡易的なgetter/setterを作ります | 	 * 特定のキーの、簡易的なcomputed refを作ります | ||||||
| 	 * 主にvue上で設定コントロールのmodelとして使う用 | 	 * 主にvue上で設定コントロールのmodelとして使う用 | ||||||
| 	 */ | 	 */ | ||||||
| 	// TODO: 廃止 | 	public model<K extends keyof Data, V extends Data[K] = Data[K]>( | ||||||
| 	public makeGetterSetter<K extends keyof T, R = T[K]['default']>( |  | ||||||
| 		key: K, | 		key: K, | ||||||
| 		getter?: (v: T[K]['default']) => R, | 		getter?: (v: Data[K]) => V, | ||||||
| 		setter?: (v: R) => T[K]['default'], | 		setter?: (v: V) => Data[K], | ||||||
| 	): { | 	): WritableComputedRef<V> { | ||||||
| 			get: () => R; | 		const valueRef = ref(this.s[key]); | ||||||
| 			set: (value: R) => void; |  | ||||||
| 		} { |  | ||||||
| 		const valueRef = ref(this.state[key]); |  | ||||||
|  |  | ||||||
| 		const stop = watch(this.reactiveState[key], val => { | 		const stop = watch(this.r[key], val => { | ||||||
| 			valueRef.value = val; | 			valueRef.value = val; | ||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| @@ -258,7 +76,7 @@ export class Storage<T extends StateDef> { | |||||||
| 		}); | 		}); | ||||||
|  |  | ||||||
| 		// TODO: VueのcustomRef使うと良い感じになるかも | 		// TODO: VueのcustomRef使うと良い感じになるかも | ||||||
| 		return { | 		return computed({ | ||||||
| 			get: () => { | 			get: () => { | ||||||
| 				if (getter) { | 				if (getter) { | ||||||
| 					return getter(valueRef.value); | 					return getter(valueRef.value); | ||||||
| @@ -268,30 +86,9 @@ export class Storage<T extends StateDef> { | |||||||
| 			}, | 			}, | ||||||
| 			set: (value) => { | 			set: (value) => { | ||||||
| 				const val = setter ? setter(value) : value; | 				const val = setter ? setter(value) : value; | ||||||
| 				this.set(key, val); | 				this.commit(key, val); | ||||||
| 				valueRef.value = val; | 				valueRef.value = val; | ||||||
| 			}, | 			}, | ||||||
| 		}; | 		}); | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// localStorage => indexedDBのマイグレーション |  | ||||||
| 	private async migrate() { |  | ||||||
| 		const deviceState = localStorage.getItem(this.deviceStateKeyName); |  | ||||||
| 		if (deviceState) { |  | ||||||
| 			await set(this.deviceStateKeyName, JSON.parse(deviceState)); |  | ||||||
| 			localStorage.removeItem(this.deviceStateKeyName); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		const deviceAccountState = $i && localStorage.getItem(this.deviceAccountStateKeyName); |  | ||||||
| 		if ($i && deviceAccountState) { |  | ||||||
| 			await set(this.deviceAccountStateKeyName, JSON.parse(deviceAccountState)); |  | ||||||
| 			localStorage.removeItem(this.deviceAccountStateKeyName); |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		const registryCache = $i && localStorage.getItem(this.registryCacheKeyName); |  | ||||||
| 		if ($i && registryCache) { |  | ||||||
| 			await set(this.registryCacheKeyName, JSON.parse(registryCache)); |  | ||||||
| 			localStorage.removeItem(this.registryCacheKeyName); |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -93,7 +93,7 @@ export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta> | |||||||
|  |  | ||||||
| export async function authorizePlugin(plugin: Plugin) { | export async function authorizePlugin(plugin: Plugin) { | ||||||
| 	if (plugin.permissions == null || plugin.permissions.length === 0) return; | 	if (plugin.permissions == null || plugin.permissions.length === 0) return; | ||||||
| 	if (Object.hasOwn(store.state.pluginTokens, plugin.installId)) return; | 	if (Object.hasOwn(store.s.pluginTokens, plugin.installId)) return; | ||||||
|  |  | ||||||
| 	const token = await new Promise<string>((res, rej) => { | 	const token = await new Promise<string>((res, rej) => { | ||||||
| 		const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), { | 		const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), { | ||||||
| @@ -115,8 +115,8 @@ export async function authorizePlugin(plugin: Plugin) { | |||||||
| 		}); | 		}); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	store.set('pluginTokens', { | 	store.commit('pluginTokens', { | ||||||
| 		...store.state.pluginTokens, | 		...store.s.pluginTokens, | ||||||
| 		[plugin.installId]: token, | 		[plugin.installId]: token, | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| @@ -145,7 +145,7 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) { | |||||||
| 		src: code, | 		src: code, | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	prefer.set('plugins', prefer.s.plugins.concat(plugin)); | 	prefer.commit('plugins', prefer.s.plugins.concat(plugin)); | ||||||
|  |  | ||||||
| 	await authorizePlugin(plugin); | 	await authorizePlugin(plugin); | ||||||
|  |  | ||||||
| @@ -154,14 +154,14 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) { | |||||||
|  |  | ||||||
| export async function uninstallPlugin(plugin: Plugin) { | export async function uninstallPlugin(plugin: Plugin) { | ||||||
| 	abortPlugin(plugin); | 	abortPlugin(plugin); | ||||||
| 	prefer.set('plugins', prefer.s.plugins.filter(x => x.installId !== plugin.installId)); | 	prefer.commit('plugins', prefer.s.plugins.filter(x => x.installId !== plugin.installId)); | ||||||
| 	if (Object.hasOwn(store.state.pluginTokens, plugin.installId)) { | 	if (Object.hasOwn(store.s.pluginTokens, plugin.installId)) { | ||||||
| 		await os.apiWithDialog('i/revoke-token', { | 		await os.apiWithDialog('i/revoke-token', { | ||||||
| 			token: store.state.pluginTokens[plugin.installId], | 			token: store.s.pluginTokens[plugin.installId], | ||||||
| 		}); | 		}); | ||||||
| 		const pluginTokens = { ...store.state.pluginTokens }; | 		const pluginTokens = { ...store.s.pluginTokens }; | ||||||
| 		delete pluginTokens[plugin.installId]; | 		delete pluginTokens[plugin.installId]; | ||||||
| 		store.set('pluginTokens', pluginTokens); | 		store.commit('pluginTokens', pluginTokens); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -311,13 +311,13 @@ export async function configPlugin(plugin: Plugin) { | |||||||
| 	const { canceled, result } = await os.form(plugin.name, config); | 	const { canceled, result } = await os.form(plugin.name, config); | ||||||
| 	if (canceled) return; | 	if (canceled) return; | ||||||
|  |  | ||||||
| 	prefer.set('plugins', prefer.s.plugins.map(x => x.installId === plugin.installId ? { ...x, configData: result } : x)); | 	prefer.commit('plugins', prefer.s.plugins.map(x => x.installId === plugin.installId ? { ...x, configData: result } : x)); | ||||||
|  |  | ||||||
| 	reloadPlugin(plugin); | 	reloadPlugin(plugin); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function changePluginActive(plugin: Plugin, active: boolean) { | export function changePluginActive(plugin: Plugin, active: boolean) { | ||||||
| 	prefer.set('plugins', prefer.s.plugins.map(x => x.installId === plugin.installId ? { ...x, active } : x)); | 	prefer.commit('plugins', prefer.s.plugins.map(x => x.installId === plugin.installId ? { ...x, active } : x)); | ||||||
|  |  | ||||||
| 	if (active) { | 	if (active) { | ||||||
| 		launchPlugin(plugin.installId); | 		launchPlugin(plugin.installId); | ||||||
| @@ -341,7 +341,7 @@ function createPluginEnv(opts: { plugin: Plugin; storageKey: string }): Record<s | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const env: Record<string, values.Value> = { | 	const env: Record<string, values.Value> = { | ||||||
| 		...createAiScriptEnv({ ...opts, token: store.state.pluginTokens[id] }), | 		...createAiScriptEnv({ ...opts, token: store.s.pluginTokens[id] }), | ||||||
|  |  | ||||||
| 		'Plugin:register:post_form_action': values.FN_NATIVE(([title, handler]) => { | 		'Plugin:register:post_form_action': values.FN_NATIVE(([title, handler]) => { | ||||||
| 			utils.assertString(title); | 			utils.assertString(title); | ||||||
|   | |||||||
| @@ -65,7 +65,7 @@ let latestBackupAt = 0; | |||||||
|  |  | ||||||
| window.setInterval(() => { | window.setInterval(() => { | ||||||
| 	if ($i == null) return; | 	if ($i == null) return; | ||||||
| 	if (!store.state.enablePreferencesAutoCloudBackup) return; | 	if (!store.s.enablePreferencesAutoCloudBackup) return; | ||||||
| 	if (document.visibilityState !== 'visible') return; // 同期されていない古い値がバックアップされるのを防ぐ | 	if (document.visibilityState !== 'visible') return; // 同期されていない古い値がバックアップされるのを防ぐ | ||||||
| 	if (profileManager.profile.modifiedAt <= latestBackupAt) return; | 	if (profileManager.profile.modifiedAt <= latestBackupAt) return; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,11 +8,11 @@ import { v4 as uuid } from 'uuid'; | |||||||
| import { host, version } from '@@/js/config.js'; | import { host, version } from '@@/js/config.js'; | ||||||
| import { EventEmitter } from 'eventemitter3'; | import { EventEmitter } from 'eventemitter3'; | ||||||
| import { PREF_DEF } from './def.js'; | import { PREF_DEF } from './def.js'; | ||||||
| import { Store } from './store.js'; |  | ||||||
| import type { MenuItem } from '@/types/menu.js'; | import type { MenuItem } from '@/types/menu.js'; | ||||||
| import { $i } from '@/account.js'; | import { $i } from '@/account.js'; | ||||||
| import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; | import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
|  | import { Pizzax } from '@/pizzax.js'; | ||||||
|  |  | ||||||
| // NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない | // NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない | ||||||
|  |  | ||||||
| @@ -48,7 +48,7 @@ export class ProfileManager extends EventEmitter<{ | |||||||
| 	}) => void; | 	}) => void; | ||||||
| }> { | }> { | ||||||
| 	public profile: PreferencesProfile; | 	public profile: PreferencesProfile; | ||||||
| 	public store: Store<{ | 	public store: Pizzax<{ | ||||||
| 		[K in keyof PREF]: ValueOf<K>; | 		[K in keyof PREF]: ValueOf<K>; | ||||||
| 	}>; | 	}>; | ||||||
|  |  | ||||||
| @@ -58,7 +58,7 @@ export class ProfileManager extends EventEmitter<{ | |||||||
|  |  | ||||||
| 		const states = this.genStates(); | 		const states = this.genStates(); | ||||||
|  |  | ||||||
| 		this.store = new Store(states); | 		this.store = new Pizzax(states); | ||||||
| 		this.store.addListener('updated', ({ key, value }) => { | 		this.store.addListener('updated', ({ key, value }) => { | ||||||
| 			console.log('prefer:set', key, value); | 			console.log('prefer:set', key, value); | ||||||
|  |  | ||||||
| @@ -222,7 +222,7 @@ export class ProfileManager extends EventEmitter<{ | |||||||
| 			text: i18n.ts.resetToDefaultValue, | 			text: i18n.ts.resetToDefaultValue, | ||||||
| 			danger: true, | 			danger: true, | ||||||
| 			action: () => { | 			action: () => { | ||||||
| 				this.store.set(key, PREF_DEF[key].default); | 				this.store.commit(key, PREF_DEF[key].default); | ||||||
| 			}, | 			}, | ||||||
| 		}, { | 		}, { | ||||||
| 			type: 'divider', | 			type: 'divider', | ||||||
|   | |||||||
| @@ -1,92 +0,0 @@ | |||||||
| /* |  | ||||||
|  * SPDX-FileCopyrightText: syuilo and misskey-project |  | ||||||
|  * SPDX-License-Identifier: AGPL-3.0-only |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| import { computed, onUnmounted, ref, watch } from 'vue'; |  | ||||||
| import { EventEmitter } from 'eventemitter3'; |  | ||||||
| import type { Ref, WritableComputedRef } from 'vue'; |  | ||||||
|  |  | ||||||
| // NOTE: 明示的な設定値のひとつとして null もあり得るため、設定が存在しないかどうかを判定する目的で null で比較したり ?? を使ってはいけない |  | ||||||
|  |  | ||||||
| //type DottedToNested<T extends Record<string, any>> = { |  | ||||||
| //	[K in keyof T as K extends string ? K extends `${infer A}.${infer B}` ? A : K : K]: K extends `${infer A}.${infer B}` ? DottedToNested<{ [key in B]: T[K] }> : T[K]; |  | ||||||
| //}; |  | ||||||
|  |  | ||||||
| type StoreEvent<Data extends Record<string, any>> = { |  | ||||||
| 	updated: <K extends keyof Data>(ctx: { |  | ||||||
| 		key: K; |  | ||||||
| 		value: Data[K]; |  | ||||||
| 	}) => void; |  | ||||||
| }; |  | ||||||
|  |  | ||||||
| export class Store<Data extends Record<string, any>> extends EventEmitter<StoreEvent<Data>> { |  | ||||||
| 	/** |  | ||||||
| 	 * static の略 (static が予約語のため) |  | ||||||
| 	 */ |  | ||||||
| 	public s = {} as { |  | ||||||
| 		[K in keyof Data]: Data[K]; |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * reactive の略 |  | ||||||
| 	 */ |  | ||||||
| 	public r = {} as { |  | ||||||
| 		[K in keyof Data]: Ref<Data[K]>; |  | ||||||
| 	}; |  | ||||||
|  |  | ||||||
| 	constructor(data: { [K in keyof Data]: Data[K] }) { |  | ||||||
| 		super(); |  | ||||||
|  |  | ||||||
| 		for (const key in data) { |  | ||||||
| 			this.s[key] = data[key]; |  | ||||||
| 			this.r[key] = ref(this.s[key]); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public set<K extends keyof Data>(key: K, value: Data[K]) { |  | ||||||
| 		this.r[key].value = this.s[key] = value; |  | ||||||
| 		this.emit('updated', { key, value }); |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	public rewrite<K extends keyof Data>(key: K, value: Data[K]) { |  | ||||||
| 		this.r[key].value = this.s[key] = value; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/** |  | ||||||
| 	 * 特定のキーの、簡易的なcomputed refを作ります |  | ||||||
| 	 * 主にvue上で設定コントロールのmodelとして使う用 |  | ||||||
| 	 */ |  | ||||||
| 	public model<K extends keyof Data, V extends Data[K] = Data[K]>( |  | ||||||
| 		key: K, |  | ||||||
| 		getter?: (v: Data[K]) => V, |  | ||||||
| 		setter?: (v: V) => Data[K], |  | ||||||
| 	): WritableComputedRef<V> { |  | ||||||
| 		const valueRef = ref(this.s[key]); |  | ||||||
|  |  | ||||||
| 		const stop = watch(this.r[key], val => { |  | ||||||
| 			valueRef.value = val; |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 		// NOTE: vueコンポーネント内で呼ばれない限りは、onUnmounted は無意味なのでメモリリークする |  | ||||||
| 		onUnmounted(() => { |  | ||||||
| 			stop(); |  | ||||||
| 		}); |  | ||||||
|  |  | ||||||
| 		// TODO: VueのcustomRef使うと良い感じになるかも |  | ||||||
| 		return computed({ |  | ||||||
| 			get: () => { |  | ||||||
| 				if (getter) { |  | ||||||
| 					return getter(valueRef.value); |  | ||||||
| 				} else { |  | ||||||
| 					return valueRef.value; |  | ||||||
| 				} |  | ||||||
| 			}, |  | ||||||
| 			set: (value) => { |  | ||||||
| 				const val = setter ? setter(value) : value; |  | ||||||
| 				this.set(key, val); |  | ||||||
| 				valueRef.value = val; |  | ||||||
| 			}, |  | ||||||
| 		}); |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -21,7 +21,7 @@ function canAutoBackup() { | |||||||
| } | } | ||||||
|  |  | ||||||
| export function getPreferencesProfileMenu(): MenuItem[] { | export function getPreferencesProfileMenu(): MenuItem[] { | ||||||
| 	const autoBackupEnabled = ref(store.state.enablePreferencesAutoCloudBackup); | 	const autoBackupEnabled = ref(store.s.enablePreferencesAutoCloudBackup); | ||||||
|  |  | ||||||
| 	watch(autoBackupEnabled, () => { | 	watch(autoBackupEnabled, () => { | ||||||
| 		if (autoBackupEnabled.value) { | 		if (autoBackupEnabled.value) { | ||||||
| @@ -34,9 +34,9 @@ export function getPreferencesProfileMenu(): MenuItem[] { | |||||||
| 				return; | 				return; | ||||||
| 			} | 			} | ||||||
|  |  | ||||||
| 			store.set('enablePreferencesAutoCloudBackup', true); | 			store.commit('enablePreferencesAutoCloudBackup', true); | ||||||
| 		} else { | 		} else { | ||||||
| 			store.set('enablePreferencesAutoCloudBackup', false); | 			store.commit('enablePreferencesAutoCloudBackup', false); | ||||||
| 		} | 		} | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| @@ -183,7 +183,7 @@ export async function restoreFromCloudBackup() { | |||||||
|  |  | ||||||
| 	miLocalStorage.setItem('preferences', JSON.stringify(profile)); | 	miLocalStorage.setItem('preferences', JSON.stringify(profile)); | ||||||
| 	miLocalStorage.setItem('hidePreferencesRestoreSuggestion', 'true'); | 	miLocalStorage.setItem('hidePreferencesRestoreSuggestion', 'true'); | ||||||
| 	store.set('enablePreferencesAutoCloudBackup', true); | 	store.commit('enablePreferencesAutoCloudBackup', true); | ||||||
| 	shouldSuggestRestoreBackup.value = false; | 	shouldSuggestRestoreBackup.value = false; | ||||||
| 	unisonReload(); | 	unisonReload(); | ||||||
| } | } | ||||||
| @@ -197,7 +197,7 @@ export async function enableAutoBackup() { | |||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	store.set('enablePreferencesAutoCloudBackup', true); | 	store.commit('enablePreferencesAutoCloudBackup', true); | ||||||
| } | } | ||||||
|  |  | ||||||
| export const shouldSuggestRestoreBackup = ref(false); | export const shouldSuggestRestoreBackup = ref(false); | ||||||
|   | |||||||
| @@ -8,17 +8,219 @@ import * as Misskey from 'misskey-js'; | |||||||
| import lightTheme from '@@/themes/l-light.json5'; | import lightTheme from '@@/themes/l-light.json5'; | ||||||
| import darkTheme from '@@/themes/d-green-lime.json5'; | import darkTheme from '@@/themes/d-green-lime.json5'; | ||||||
| import { hemisphere } from '@@/js/intl-const.js'; | import { hemisphere } from '@@/js/intl-const.js'; | ||||||
|  | import { BroadcastChannel } from 'broadcast-channel'; | ||||||
| import type { DeviceKind } from '@/utility/device-kind.js'; | import type { DeviceKind } from '@/utility/device-kind.js'; | ||||||
| import type { Plugin } from '@/plugin.js'; | import type { Plugin } from '@/plugin.js'; | ||||||
| import type { Column } from '@/deck.js'; | import type { Column } from '@/deck.js'; | ||||||
| import { miLocalStorage } from '@/local-storage.js'; | import { miLocalStorage } from '@/local-storage.js'; | ||||||
| import { Storage } from '@/pizzax.js'; |  | ||||||
| import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js'; | import { DEFAULT_DEVICE_KIND } from '@/utility/device-kind.js'; | ||||||
|  | import { Pizzax } from '@/pizzax.js'; | ||||||
|  | import { $i } from '@/account.js'; | ||||||
|  | import * as idb from '@/utility/idb-proxy.js'; | ||||||
|  | import { misskeyApi } from '@/utility/misskey-api.js'; | ||||||
|  | import { useStream } from '@/stream.js'; | ||||||
|  | import { deepMerge } from '@/utility/merge.js'; | ||||||
|  | import { deepClone } from '@/utility/clone.js'; | ||||||
|  |  | ||||||
| /** | type StateDef = Record<string, { | ||||||
|  * 「状態」を管理するストア(not「設定」) | 	where: 'account' | 'device' | 'deviceAccount'; | ||||||
|  */ | 	default: any; | ||||||
| export const store = markRaw(new Storage('base', { | }>; | ||||||
|  |  | ||||||
|  | type State<T extends StateDef> = { [K in keyof T]: T[K]['default']; }; | ||||||
|  |  | ||||||
|  | type PizzaxChannelMessage<T extends StateDef> = { | ||||||
|  | 	where: 'device' | 'deviceAccount'; | ||||||
|  | 	key: keyof T; | ||||||
|  | 	value: T[keyof T]['default']; | ||||||
|  | 	userId?: string; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | // TODO: export消す | ||||||
|  | export class Store<T extends StateDef> extends Pizzax<State<T>> { | ||||||
|  | 	public readonly def: T; | ||||||
|  |  | ||||||
|  | 	public readonly ready: Promise<void>; | ||||||
|  | 	public readonly loaded: Promise<void>; | ||||||
|  |  | ||||||
|  | 	public readonly key: string; | ||||||
|  | 	public readonly deviceStateKeyName: `pizzax::${this['key']}`; | ||||||
|  | 	public readonly deviceAccountStateKeyName: `pizzax::${this['key']}::${string}` | ''; | ||||||
|  | 	public readonly registryCacheKeyName: `pizzax::${this['key']}::cache::${string}` | ''; | ||||||
|  |  | ||||||
|  | 	private pizzaxChannel: BroadcastChannel<PizzaxChannelMessage<T>>; | ||||||
|  |  | ||||||
|  | 	// 簡易的にキューイングして占有ロックとする | ||||||
|  | 	private currentIdbJob: Promise<any> = Promise.resolve(); | ||||||
|  | 	private addIdbSetJob<T>(job: () => Promise<T>) { | ||||||
|  | 		const promise = this.currentIdbJob.then(job, err => { | ||||||
|  | 			console.error('Pizzax failed to save data to idb!', err); | ||||||
|  | 			return job(); | ||||||
|  | 		}); | ||||||
|  | 		this.currentIdbJob = promise; | ||||||
|  | 		return promise; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	constructor(def: T, key = 'base') { | ||||||
|  | 		const data = {} as State<T>; | ||||||
|  |  | ||||||
|  | 		for (const [k, v] of Object.entries(def) as [keyof T, T[keyof T]['default']][]) { | ||||||
|  | 			data[k] = v.default; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		super(data); | ||||||
|  |  | ||||||
|  | 		this.key = key; | ||||||
|  | 		this.deviceStateKeyName = `pizzax::${key}`; | ||||||
|  | 		this.deviceAccountStateKeyName = $i ? `pizzax::${key}::${$i.id}` : ''; | ||||||
|  | 		this.registryCacheKeyName = $i ? `pizzax::${key}::cache::${$i.id}` : ''; | ||||||
|  | 		this.def = def; | ||||||
|  | 		this.pizzaxChannel = new BroadcastChannel(`pizzax::${key}`); | ||||||
|  | 		this.ready = this.init(); | ||||||
|  | 		this.loaded = this.ready.then(() => this.load()); | ||||||
|  |  | ||||||
|  | 		this.addListener('updated', ({ key, value }) => { | ||||||
|  | 			// IndexedDBやBroadcastChannelで扱うために単純なオブジェクトにする | ||||||
|  | 			// (JSON.parse(JSON.stringify(value))の代わり) | ||||||
|  | 			const rawValue = deepClone(value); | ||||||
|  |  | ||||||
|  | 			this.r[key].value = this.s[key] = rawValue; | ||||||
|  |  | ||||||
|  | 			return this.addIdbSetJob(async () => { | ||||||
|  | 				switch (this.def[key].where) { | ||||||
|  | 					case 'device': { | ||||||
|  | 						this.pizzaxChannel.postMessage({ | ||||||
|  | 							where: 'device', | ||||||
|  | 							key, | ||||||
|  | 							value: rawValue, | ||||||
|  | 						}); | ||||||
|  | 						const deviceState = await idb.get(this.deviceStateKeyName) || {}; | ||||||
|  | 						deviceState[key] = rawValue; | ||||||
|  | 						await idb.set(this.deviceStateKeyName, deviceState); | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 					case 'deviceAccount': { | ||||||
|  | 						if ($i == null) break; | ||||||
|  | 						this.pizzaxChannel.postMessage({ | ||||||
|  | 							where: 'deviceAccount', | ||||||
|  | 							key, | ||||||
|  | 							value: rawValue, | ||||||
|  | 							userId: $i.id, | ||||||
|  | 						}); | ||||||
|  | 						const deviceAccountState = await idb.get(this.deviceAccountStateKeyName) || {}; | ||||||
|  | 						deviceAccountState[key] = rawValue; | ||||||
|  | 						await idb.set(this.deviceAccountStateKeyName, deviceAccountState); | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 					case 'account': { | ||||||
|  | 						if ($i == null) break; | ||||||
|  | 						const cache = await idb.get(this.registryCacheKeyName) || {}; | ||||||
|  | 						cache[key] = rawValue; | ||||||
|  | 						await idb.set(this.registryCacheKeyName, cache); | ||||||
|  | 						await misskeyApi('i/registry/set', { | ||||||
|  | 							scope: ['client', this.key], | ||||||
|  | 							key: key.toString(), | ||||||
|  | 							value: rawValue, | ||||||
|  | 						}); | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			}); | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private async init(): Promise<void> { | ||||||
|  | 		const deviceState: State<T> = await idb.get(this.deviceStateKeyName) || {}; | ||||||
|  | 		const deviceAccountState = $i ? await idb.get(this.deviceAccountStateKeyName) || {} : {}; | ||||||
|  | 		const registryCache = $i ? await idb.get(this.registryCacheKeyName) || {} : {}; | ||||||
|  |  | ||||||
|  | 		for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) { | ||||||
|  | 			if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) { | ||||||
|  | 				this.rewrite(k, this.mergeState<T[keyof T]['default']>(deviceState[k], v.default)); | ||||||
|  | 			} else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) { | ||||||
|  | 				this.rewrite(k, this.mergeState<T[keyof T]['default']>(registryCache[k], v.default)); | ||||||
|  | 			} else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) { | ||||||
|  | 				this.rewrite(k, this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default)); | ||||||
|  | 			} else { | ||||||
|  | 				this.rewrite(k, v.default); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		this.pizzaxChannel.addEventListener('message', ({ where, key, value, userId }) => { | ||||||
|  | 			// アカウント変更すればunisonReloadが効くため、このreturnが発火することは | ||||||
|  | 			// まずないと思うけど一応弾いておく | ||||||
|  | 			if (where === 'deviceAccount' && !($i && userId !== $i.id)) return; | ||||||
|  | 			this.r[key].value = this.s[key] = value; | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		if ($i) { | ||||||
|  | 			const connection = useStream().useChannel('main'); | ||||||
|  |  | ||||||
|  | 			// streamingのuser storage updateイベントを監視して更新 | ||||||
|  | 			connection.on('registryUpdated', ({ scope, key, value }: { scope?: string[], key: keyof T, value: T[typeof key]['default'] }) => { | ||||||
|  | 				if (!scope || scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.s[key] === value) return; | ||||||
|  |  | ||||||
|  | 				this.rewrite(key, value); | ||||||
|  |  | ||||||
|  | 				this.addIdbSetJob(async () => { | ||||||
|  | 					const cache = await idb.get(this.registryCacheKeyName); | ||||||
|  | 					if (cache[key] !== value) { | ||||||
|  | 						cache[key] = value; | ||||||
|  | 						await idb.set(this.registryCacheKeyName, cache); | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private load(): Promise<void> { | ||||||
|  | 		return new Promise((resolve, reject) => { | ||||||
|  | 			if ($i) { | ||||||
|  | 				// api関数と循環参照なので一応setTimeoutしておく | ||||||
|  | 				window.setTimeout(async () => { | ||||||
|  | 					await store.ready; | ||||||
|  |  | ||||||
|  | 					misskeyApi('i/registry/get-all', { scope: ['client', this.key] }) | ||||||
|  | 						.then(kvs => { | ||||||
|  | 							const cache: Partial<T> = {}; | ||||||
|  | 							for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) { | ||||||
|  | 								if (v.where === 'account') { | ||||||
|  | 									if (Object.prototype.hasOwnProperty.call(kvs, k)) { | ||||||
|  | 										this.rewrite(k, (kvs as Partial<T>)[k]); | ||||||
|  | 										cache[k] = (kvs as Partial<T>)[k]; | ||||||
|  | 									} else { | ||||||
|  | 										this.r[k].value = this.s[k] = v.default; | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  |  | ||||||
|  | 							return idb.set(this.registryCacheKeyName, cache); | ||||||
|  | 						}) | ||||||
|  | 						.then(() => resolve()); | ||||||
|  | 				}, 1); | ||||||
|  | 			} else { | ||||||
|  | 				resolve(); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private isPureObject(value: unknown): value is Record<string | number | symbol, unknown> { | ||||||
|  | 		return typeof value === 'object' && value !== null && !Array.isArray(value); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private mergeState<X>(value: X, def: X): X { | ||||||
|  | 		if (this.isPureObject(value) && this.isPureObject(def)) { | ||||||
|  | 			const merged = deepMerge(value, def); | ||||||
|  |  | ||||||
|  | 			if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged); | ||||||
|  |  | ||||||
|  | 			return merged as X; | ||||||
|  | 		} | ||||||
|  | 		return value; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const STORE_DEF = { | ||||||
| 	accountSetupWizard: { | 	accountSetupWizard: { | ||||||
| 		where: 'account', | 		where: 'account', | ||||||
| 		default: 0, | 		default: 0, | ||||||
| @@ -465,9 +667,12 @@ export const store = markRaw(new Storage('base', { | |||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	//#endregion | 	//#endregion | ||||||
| })); | } satisfies StateDef; | ||||||
|  |  | ||||||
| // TODO: 他のタブと永続化されたstateを同期 | /** | ||||||
|  |  * 「状態」を管理するストア(not「設定」) | ||||||
|  |  */ | ||||||
|  | export const store = markRaw(new Store(STORE_DEF)); | ||||||
|  |  | ||||||
| const PREFIX = 'miux:' as const; | const PREFIX = 'miux:' as const; | ||||||
|  |  | ||||||
| @@ -547,7 +752,7 @@ export class ColdDeviceStorage { | |||||||
| 	 * 特定のキーの、簡易的なgetter/setterを作ります | 	 * 特定のキーの、簡易的なgetter/setterを作ります | ||||||
| 	 * 主にvue場で設定コントロールのmodelとして使う用 | 	 * 主にvue場で設定コントロールのmodelとして使う用 | ||||||
| 	 */ | 	 */ | ||||||
| 	public static makeGetterSetter<K extends keyof typeof ColdDeviceStorage.default>(key: K) { | 	public static model<K extends keyof typeof ColdDeviceStorage.default>(key: K) { | ||||||
| 		// TODO: VueのcustomRef使うと良い感じになるかも | 		// TODO: VueのcustomRef使うと良い感じになるかも | ||||||
| 		const valueRef = ColdDeviceStorage.ref(key); | 		const valueRef = ColdDeviceStorage.ref(key); | ||||||
| 		return { | 		return { | ||||||
|   | |||||||
| @@ -23,11 +23,11 @@ export async function addTheme(theme: Theme): Promise<void> { | |||||||
| 	if (themes.some(t => t.id === theme.id)) { | 	if (themes.some(t => t.id === theme.id)) { | ||||||
| 		throw new Error('already exists'); | 		throw new Error('already exists'); | ||||||
| 	} | 	} | ||||||
| 	prefer.set('themes', [...themes, theme]); | 	prefer.commit('themes', [...themes, theme]); | ||||||
| } | } | ||||||
|  |  | ||||||
| export async function removeTheme(theme: Theme): Promise<void> { | export async function removeTheme(theme: Theme): Promise<void> { | ||||||
| 	if ($i == null) return; | 	if ($i == null) return; | ||||||
| 	const themes = getThemes().filter(t => t.id !== theme.id); | 	const themes = getThemes().filter(t => t.id !== theme.id); | ||||||
| 	prefer.set('themes', themes); | 	prefer.commit('themes', themes); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -105,7 +105,7 @@ const router = useRouter(); | |||||||
|  |  | ||||||
| const forceIconOnly = ref(window.innerWidth <= 1279); | const forceIconOnly = ref(window.innerWidth <= 1279); | ||||||
| const iconOnly = computed(() => { | const iconOnly = computed(() => { | ||||||
| 	return forceIconOnly.value || (store.reactiveState.menuDisplay.value === 'sideIcon'); | 	return forceIconOnly.value || (store.r.menuDisplay.value === 'sideIcon'); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| const menu = computed(() => prefer.s.menu); | const menu = computed(() => prefer.s.menu); | ||||||
| @@ -123,12 +123,12 @@ function calcViewState() { | |||||||
|  |  | ||||||
| window.addEventListener('resize', calcViewState); | window.addEventListener('resize', calcViewState); | ||||||
|  |  | ||||||
| watch(store.reactiveState.menuDisplay, () => { | watch(store.r.menuDisplay, () => { | ||||||
| 	calcViewState(); | 	calcViewState(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
| function toggleIconOnly() { | function toggleIconOnly() { | ||||||
| 	store.set('menuDisplay', iconOnly.value ? 'sideFull' : 'sideIcon'); | 	store.commit('menuDisplay', iconOnly.value ? 'sideFull' : 'sideIcon'); | ||||||
| } | } | ||||||
|  |  | ||||||
| function openAccountMenu(ev: MouseEvent) { | function openAccountMenu(ev: MouseEvent) { | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ const WINDOW_THRESHOLD = 1400; | |||||||
|  |  | ||||||
| const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD); | const settingsWindowed = ref(window.innerWidth > WINDOW_THRESHOLD); | ||||||
| const menu = ref(prefer.s.menu); | const menu = ref(prefer.s.menu); | ||||||
| // const menuDisplay = computed(store.makeGetterSetter('menuDisplay')); | // const menuDisplay = store.model('menuDisplay'); | ||||||
| const otherNavItemIndicated = computed<boolean>(() => { | const otherNavItemIndicated = computed<boolean>(() => { | ||||||
| 	for (const def in navbarItemDef) { | 	for (const def in navbarItemDef) { | ||||||
| 		if (menu.value.includes(def)) continue; | 		if (menu.value.includes(def)) continue; | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ import { prefer } from '@/preferences.js'; | |||||||
| const WINDOW_THRESHOLD = 1400; | const WINDOW_THRESHOLD = 1400; | ||||||
|  |  | ||||||
| const menu = ref(prefer.s.menu); | const menu = ref(prefer.s.menu); | ||||||
| const menuDisplay = computed(store.makeGetterSetter('menuDisplay')); | const menuDisplay = store.model('menuDisplay'); | ||||||
| const otherNavItemIndicated = computed<boolean>(() => { | const otherNavItemIndicated = computed<boolean>(() => { | ||||||
| 	for (const def in navbarItemDef) { | 	for (const def in navbarItemDef) { | ||||||
| 		if (menu.value.includes(def)) continue; | 		if (menu.value.includes(def)) continue; | ||||||
| @@ -100,7 +100,7 @@ function openAccountMenu(ev: MouseEvent) { | |||||||
| 	}, ev); | 	}, ev); | ||||||
| } | } | ||||||
|  |  | ||||||
| watch(store.reactiveState.menuDisplay, () => { | watch(store.r.menuDisplay, () => { | ||||||
| 	calcViewState(); | 	calcViewState(); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -75,7 +75,7 @@ const widgetsShowing = ref(false); | |||||||
| const fullView = ref(false); | const fullView = ref(false); | ||||||
| const globalHeaderHeight = ref(0); | const globalHeaderHeight = ref(0); | ||||||
| const wallpaper = miLocalStorage.getItem('wallpaper') != null; | const wallpaper = miLocalStorage.getItem('wallpaper') != null; | ||||||
| const showMenuOnTop = computed(() => store.state.menuDisplay === 'top'); | const showMenuOnTop = computed(() => store.s.menuDisplay === 'top'); | ||||||
| const live2d = shallowRef<HTMLIFrameElement>(); | const live2d = shallowRef<HTMLIFrameElement>(); | ||||||
| const widgetsLeft = ref<HTMLElement>(); | const widgetsLeft = ref<HTMLElement>(); | ||||||
| const widgetsRight = ref<HTMLElement>(); | const widgetsRight = ref<HTMLElement>(); | ||||||
| @@ -97,7 +97,7 @@ provide('shouldHeaderThin', showMenuOnTop.value); | |||||||
| provide('forceSpacerMin', true); | provide('forceSpacerMin', true); | ||||||
|  |  | ||||||
| function attachSticky(el: HTMLElement) { | function attachSticky(el: HTMLElement) { | ||||||
| 	const sticky = new StickySidebar(el, 0, store.state.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す | 	const sticky = new StickySidebar(el, 0, store.s.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す | ||||||
| 	window.addEventListener('scroll', () => { | 	window.addEventListener('scroll', () => { | ||||||
| 		sticky.calc(window.scrollY); | 		sticky.calc(window.scrollY); | ||||||
| 	}, { passive: true }); | 	}, { passive: true }); | ||||||
| @@ -144,7 +144,7 @@ if (window.innerWidth < 1024) { | |||||||
| document.documentElement.style.overflowY = 'scroll'; | document.documentElement.style.overflowY = 'scroll'; | ||||||
|  |  | ||||||
| if (prefer.s.widgets.length === 0) { | if (prefer.s.widgets.length === 0) { | ||||||
| 	prefer.set('widgets', [{ | 	prefer.commit('widgets', [{ | ||||||
| 		name: 'calendar', | 		name: 'calendar', | ||||||
| 		id: 'a', place: null, data: {}, | 		id: 'a', place: null, data: {}, | ||||||
| 	}, { | 	}, { | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 			</div> | 			</div> | ||||||
| 			<div :class="$style.sideMenu"> | 			<div :class="$style.sideMenu"> | ||||||
| 				<div :class="$style.sideMenuTop"> | 				<div :class="$style.sideMenuTop"> | ||||||
| 					<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${store.state['deck.profile']}`" :class="$style.sideMenuButton" class="_button" @click="changeProfile"><i class="ti ti-caret-down"></i></button> | 					<button v-tooltip.noDelay.left="`${i18n.ts._deck.profile}: ${store.s['deck.profile']}`" :class="$style.sideMenuButton" class="_button" @click="changeProfile"><i class="ti ti-caret-down"></i></button> | ||||||
| 					<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.sideMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button> | 					<button v-tooltip.noDelay.left="i18n.ts._deck.deleteProfile" :class="$style.sideMenuButton" class="_button" @click="deleteProfile"><i class="ti ti-trash"></i></button> | ||||||
| 				</div> | 				</div> | ||||||
| 				<div :class="$style.sideMenuMiddle"> | 				<div :class="$style.sideMenuMiddle"> | ||||||
| @@ -137,7 +137,7 @@ const columnComponents = { | |||||||
|  |  | ||||||
| mainRouter.navHook = (path, flag): boolean => { | mainRouter.navHook = (path, flag): boolean => { | ||||||
| 	if (flag === 'forcePage') return false; | 	if (flag === 'forcePage') return false; | ||||||
| 	const noMainColumn = !store.state['deck.columns'].some(x => x.type === 'main'); | 	const noMainColumn = !store.s['deck.columns'].some(x => x.type === 'main'); | ||||||
| 	if (prefer.s['deck.navWindow'] || noMainColumn) { | 	if (prefer.s['deck.navWindow'] || noMainColumn) { | ||||||
| 		os.pageWindow(path); | 		os.pageWindow(path); | ||||||
| 		return true; | 		return true; | ||||||
| @@ -160,8 +160,8 @@ watch(route, () => { | |||||||
| }); | }); | ||||||
| */ | */ | ||||||
|  |  | ||||||
| const columns = store.reactiveState['deck.columns']; | const columns = store.r['deck.columns']; | ||||||
| const layout = store.reactiveState['deck.layout']; | const layout = store.r['deck.layout']; | ||||||
| const menuIndicated = computed(() => { | const menuIndicated = computed(() => { | ||||||
| 	if ($i == null) return false; | 	if ($i == null) return false; | ||||||
| 	for (const def in navbarItemDef) { | 	for (const def in navbarItemDef) { | ||||||
| @@ -214,15 +214,15 @@ loadDeck(); | |||||||
|  |  | ||||||
| function changeProfile(ev: MouseEvent) { | function changeProfile(ev: MouseEvent) { | ||||||
| 	let items: MenuItem[] = [{ | 	let items: MenuItem[] = [{ | ||||||
| 		text: store.state['deck.profile'], | 		text: store.s['deck.profile'], | ||||||
| 		active: true, | 		active: true, | ||||||
| 		action: () => {}, | 		action: () => {}, | ||||||
| 	}]; | 	}]; | ||||||
| 	getProfiles().then(profiles => { | 	getProfiles().then(profiles => { | ||||||
| 		items.push(...(profiles.filter(k => k !== store.state['deck.profile']).map(k => ({ | 		items.push(...(profiles.filter(k => k !== store.s['deck.profile']).map(k => ({ | ||||||
| 			text: k, | 			text: k, | ||||||
| 			action: () => { | 			action: () => { | ||||||
| 				store.set('deck.profile', k); | 				store.commit('deck.profile', k); | ||||||
| 				unisonReload(); | 				unisonReload(); | ||||||
| 			}, | 			}, | ||||||
| 		}))), { type: 'divider' as const }, { | 		}))), { type: 'divider' as const }, { | ||||||
| @@ -237,7 +237,7 @@ function changeProfile(ev: MouseEvent) { | |||||||
| 				if (canceled || name == null) return; | 				if (canceled || name == null) return; | ||||||
|  |  | ||||||
| 				os.promiseDialog((async () => { | 				os.promiseDialog((async () => { | ||||||
| 					await store.set('deck.profile', name); | 					await store.commit('deck.profile', name); | ||||||
| 					await forceSaveDeck(); | 					await forceSaveDeck(); | ||||||
| 				})(), () => { | 				})(), () => { | ||||||
| 					unisonReload(); | 					unisonReload(); | ||||||
| @@ -252,19 +252,19 @@ function changeProfile(ev: MouseEvent) { | |||||||
| async function deleteProfile() { | async function deleteProfile() { | ||||||
| 	const { canceled } = await os.confirm({ | 	const { canceled } = await os.confirm({ | ||||||
| 		type: 'warning', | 		type: 'warning', | ||||||
| 		text: i18n.tsx.deleteAreYouSure({ x: store.state['deck.profile'] }), | 		text: i18n.tsx.deleteAreYouSure({ x: store.s['deck.profile'] }), | ||||||
| 	}); | 	}); | ||||||
| 	if (canceled) return; | 	if (canceled) return; | ||||||
|  |  | ||||||
| 	os.promiseDialog((async () => { | 	os.promiseDialog((async () => { | ||||||
| 		if (store.state['deck.profile'] === 'default') { | 		if (store.s['deck.profile'] === 'default') { | ||||||
| 			await store.set('deck.columns', []); | 			await store.commit('deck.columns', []); | ||||||
| 			await store.set('deck.layout', []); | 			await store.commit('deck.layout', []); | ||||||
| 			await forceSaveDeck(); | 			await forceSaveDeck(); | ||||||
| 		} else { | 		} else { | ||||||
| 			await deleteProfile_(store.state['deck.profile']); | 			await deleteProfile_(store.s['deck.profile']); | ||||||
| 		} | 		} | ||||||
| 		await store.set('deck.profile', 'default'); | 		await store.commit('deck.profile', 'default'); | ||||||
| 	})(), () => { | 	})(), () => { | ||||||
| 		unisonReload(); | 		unisonReload(); | ||||||
| 	}); | 	}); | ||||||
|   | |||||||
| @@ -5,10 +5,10 @@ | |||||||
|  |  | ||||||
| import { markRaw } from 'vue'; | import { markRaw } from 'vue'; | ||||||
| import type { Column } from '@/deck.js'; | import type { Column } from '@/deck.js'; | ||||||
| import { Storage } from '@/pizzax.js'; | import { Store } from '@/store.js'; | ||||||
|  |  | ||||||
| // TODO: 消す(移行済みのため) | // TODO: 消す(移行済みのため) | ||||||
| export const deckStore = markRaw(new Storage('deck', { | export const deckStore = markRaw(new Store({ | ||||||
| 	profile: { | 	profile: { | ||||||
| 		where: 'deviceAccount', | 		where: 'deviceAccount', | ||||||
| 		default: 'default', | 		default: 'default', | ||||||
| @@ -21,4 +21,4 @@ export const deckStore = markRaw(new Storage('deck', { | |||||||
| 		where: 'deviceAccount', | 		where: 'deviceAccount', | ||||||
| 		default: [] as Column['id'][][], | 		default: [] as Column['id'][][], | ||||||
| 	}, | 	}, | ||||||
| })); | }, 'deck')); | ||||||
|   | |||||||
| @@ -178,7 +178,7 @@ if (window.innerWidth > 1024) { | |||||||
| } | } | ||||||
|  |  | ||||||
| if (prefer.s.widgets.length === 0) { | if (prefer.s.widgets.length === 0) { | ||||||
| 	prefer.set('widgets', [{ | 	prefer.commit('widgets', [{ | ||||||
| 		name: 'calendar', | 		name: 'calendar', | ||||||
| 		id: 'a', place: 'right', data: {}, | 		id: 'a', place: 'right', data: {}, | ||||||
| 	}, { | 	}, { | ||||||
|   | |||||||
| @@ -37,18 +37,18 @@ const widgets = computed(() => { | |||||||
| }); | }); | ||||||
|  |  | ||||||
| function addWidget(widget) { | function addWidget(widget) { | ||||||
| 	prefer.set('widgets', [{ | 	prefer.commit('widgets', [{ | ||||||
| 		...widget, | 		...widget, | ||||||
| 		place: props.place, | 		place: props.place, | ||||||
| 	}, ...prefer.s.widgets]); | 	}, ...prefer.s.widgets]); | ||||||
| } | } | ||||||
|  |  | ||||||
| function removeWidget(widget) { | function removeWidget(widget) { | ||||||
| 	prefer.set('widgets', prefer.s.widgets.filter(w => w.id !== widget.id)); | 	prefer.commit('widgets', prefer.s.widgets.filter(w => w.id !== widget.id)); | ||||||
| } | } | ||||||
|  |  | ||||||
| function updateWidget({ id, data }) { | function updateWidget({ id, data }) { | ||||||
| 	prefer.set('widgets', prefer.s.widgets.map(w => w.id === id ? { | 	prefer.commit('widgets', prefer.s.widgets.map(w => w.id === id ? { | ||||||
| 		...w, | 		...w, | ||||||
| 		data, | 		data, | ||||||
| 		place: props.place, | 		place: props.place, | ||||||
| @@ -57,17 +57,17 @@ function updateWidget({ id, data }) { | |||||||
|  |  | ||||||
| function updateWidgets(thisWidgets) { | function updateWidgets(thisWidgets) { | ||||||
| 	if (props.place === null) { | 	if (props.place === null) { | ||||||
| 		prefer.set('widgets', thisWidgets); | 		prefer.commit('widgets', thisWidgets); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	if (props.place === 'left') { | 	if (props.place === 'left') { | ||||||
| 		prefer.set('widgets', [ | 		prefer.commit('widgets', [ | ||||||
| 			...thisWidgets.map(w => ({ ...w, place: 'left' })), | 			...thisWidgets.map(w => ({ ...w, place: 'left' })), | ||||||
| 			...prefer.s.widgets.filter(w => w.place !== 'left' && !thisWidgets.some(t => w.id === t.id)), | 			...prefer.s.widgets.filter(w => w.place !== 'left' && !thisWidgets.some(t => w.id === t.id)), | ||||||
| 		]); | 		]); | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| 	prefer.set('widgets', [ | 	prefer.commit('widgets', [ | ||||||
| 		...prefer.s.widgets.filter(w => w.place === 'left' && !thisWidgets.some(t => w.id === t.id)), | 		...prefer.s.widgets.filter(w => w.place === 'left' && !thisWidgets.some(t => w.id === t.id)), | ||||||
| 		...thisWidgets.map(w => ({ ...w, place: 'right' })), | 		...thisWidgets.map(w => ({ ...w, place: 'right' })), | ||||||
| 	]); | 	]); | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class EmojiPicker { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public async init() { | 	public async init() { | ||||||
| 		const emojisRef = store.reactiveState.pinnedEmojis; | 		const emojisRef = store.r.pinnedEmojis; | ||||||
| 		await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { | 		await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { | ||||||
| 			src: this.src, | 			src: this.src, | ||||||
| 			pinnedEmojis: emojisRef, | 			pinnedEmojis: emojisRef, | ||||||
|   | |||||||
| @@ -608,8 +608,8 @@ export function getRenoteMenu(props: { | |||||||
| 					}); | 					}); | ||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				const configuredVisibility = prefer.s.rememberNoteVisibility ? store.state.visibility : prefer.s.defaultNoteVisibility; | 				const configuredVisibility = prefer.s.rememberNoteVisibility ? store.s.visibility : prefer.s.defaultNoteVisibility; | ||||||
| 				const localOnly = prefer.s.rememberNoteVisibility ? store.state.localOnly : prefer.s.defaultNoteLocalOnly; | 				const localOnly = prefer.s.rememberNoteVisibility ? store.s.localOnly : prefer.s.defaultNoteLocalOnly; | ||||||
|  |  | ||||||
| 				let visibility = appearNote.visibility; | 				let visibility = appearNote.visibility; | ||||||
| 				visibility = smallerVisibility(visibility, configuredVisibility); | 				visibility = smallerVisibility(visibility, configuredVisibility); | ||||||
|   | |||||||
| @@ -52,7 +52,7 @@ export function initChart() { | |||||||
| 	// フォントカラー | 	// フォントカラー | ||||||
| 	Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg'); | 	Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg'); | ||||||
|  |  | ||||||
| 	Chart.defaults.borderColor = store.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; | 	Chart.defaults.borderColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; | ||||||
|  |  | ||||||
| 	Chart.defaults.animation = false; | 	Chart.defaults.animation = false; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ class ReactionPicker { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	public async init() { | 	public async init() { | ||||||
| 		const reactionsRef = store.reactiveState.reactions; | 		const reactionsRef = store.r.reactions; | ||||||
| 		await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { | 		await popup(defineAsyncComponent(() => import('@/components/MkEmojiPickerDialog.vue')), { | ||||||
| 			src: this.src, | 			src: this.src, | ||||||
| 			pinnedEmojis: reactionsRef, | 			pinnedEmojis: reactionsRef, | ||||||
|   | |||||||
| @@ -48,12 +48,12 @@ const { widgetProps, configure } = useWidgetPropsManager(name, | |||||||
| 	emit, | 	emit, | ||||||
| ); | ); | ||||||
|  |  | ||||||
| const text = ref<string | null>(store.state.memo); | const text = ref<string | null>(store.s.memo); | ||||||
| const changed = ref(false); | const changed = ref(false); | ||||||
| let timeoutId; | let timeoutId; | ||||||
|  |  | ||||||
| const saveMemo = () => { | const saveMemo = () => { | ||||||
| 	store.set('memo', text.value); | 	store.commit('memo', text.value); | ||||||
| 	changed.value = false; | 	changed.value = false; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -63,7 +63,7 @@ const onChange = () => { | |||||||
| 	timeoutId = window.setTimeout(saveMemo, 1000); | 	timeoutId = window.setTimeout(saveMemo, 1000); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| watch(() => store.reactiveState.memo, newText => { | watch(() => store.r.memo, newText => { | ||||||
| 	text.value = newText.value; | 	text.value = newText.value; | ||||||
| }); | }); | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user