From 8bf6d5f68278adc7c1c2ca0d07fc65d41c23b245 Mon Sep 17 00:00:00 2001 From: otukado Date: Mon, 27 Oct 2025 17:45:28 +0900 Subject: [PATCH 1/4] =?UTF-8?q?refactor:=20`responsive.ts`=20=E3=82=92=20`?= =?UTF-8?q?Pinia`=20=E3=81=8B=E3=82=89=20`effectScope`=20=E3=81=AB?= =?UTF-8?q?=E7=A7=BB=E6=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/ui/responsive.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/store/ui/responsive.ts b/src/store/ui/responsive.ts index 51852ba31a..60f85a31f7 100644 --- a/src/store/ui/responsive.ts +++ b/src/store/ui/responsive.ts @@ -1,9 +1,9 @@ -import { defineStore, acceptHMRUpdate } from 'pinia' -import { ref } from 'vue' +import { ref, effectScope, readonly } from 'vue' import { mobileMinBreakpoint } from '/@/lib/media' -import { convertToRefsStore } from '/@/store/utils/convertToRefsStore' -const useResponsiveStorePinia = defineStore('ui/responsive', () => { +const scope = effectScope() + +const responsiveState = scope.run(() => { const queryList = window.matchMedia(`(max-width: ${mobileMinBreakpoint}px)`) const isHoverSupported = window.matchMedia( '(hover: hover) and (pointer: fine)' @@ -11,20 +11,24 @@ const useResponsiveStorePinia = defineStore('ui/responsive', () => { const isMobile = ref(queryList.matches) const isTouchDevice = ref(!isHoverSupported.matches) + queryList.addEventListener('change', (event: MediaQueryListEvent) => { isMobile.value = event.matches }) isHoverSupported.addEventListener('change', (event: MediaQueryListEvent) => { - isTouchDevice.value = event.matches + isTouchDevice.value = !event.matches }) return { isMobile, isTouchDevice } }) -export const useResponsiveStore = convertToRefsStore(useResponsiveStorePinia) - -if (import.meta.hot) { - import.meta.hot.accept( - acceptHMRUpdate(useResponsiveStorePinia, import.meta.hot) - ) +if (!responsiveState) { + throw new Error('Failed to initialize responsive state') } + +const { isMobile, isTouchDevice } = responsiveState + +export const useResponsiveStore = () => ({ + isMobile: readonly(isMobile), + isTouchDevice: readonly(isTouchDevice) +}) From b729a94e6069a5c27e8a901a2910f364156e342f Mon Sep 17 00:00:00 2001 From: otukado Date: Mon, 27 Oct 2025 17:48:24 +0900 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20`currentMainViewComponentState`=20?= =?UTF-8?q?=E3=82=92=20`indexedDb`=20=E3=81=AB=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/ui/mainView.ts | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/store/ui/mainView.ts b/src/store/ui/mainView.ts index cd6a0d000f..00911c17f0 100644 --- a/src/store/ui/mainView.ts +++ b/src/store/ui/mainView.ts @@ -11,10 +11,18 @@ import type { import { useBrowserSettings } from '/@/store/app/browserSettings' import { useChannelsStore } from '/@/store/entities/channels' import { useUsersStore } from '/@/store/entities/users' +import useIndexedDbValue from '/@/composables/utils/useIndexedDbValue' +import { watch } from 'vue' +import { useResponsiveStore } from './responsive' interface ViewInformationBase { type: string } + +type State = { + currentMainViewComponentState: MainViewComponentState +} + export type PrimaryViewInformation = ChannelView | ClipsView | DMView export type SecondaryViewInformation = QallView export type ViewInformation = ChannelView | QallView | ClipsView | DMView @@ -65,7 +73,19 @@ export enum MainViewComponentState { export type HeaderStyle = 'default' | 'dark' +const { isMobile } = useResponsiveStore() + const useMainViewStorePinia = defineStore('ui/mainView', () => { + const initialValue: State = { + currentMainViewComponentState: MainViewComponentState.Hidden + } + + const [state, _restoring, restoringPromise] = useIndexedDbValue( + 'store/ui/mainView', + 1, + {}, + initialValue + ) const { lastOpenChannelName } = useBrowserSettings() const channelsStore = useChannelsStore() const usersStore = useUsersStore() @@ -73,6 +93,13 @@ const useMainViewStorePinia = defineStore('ui/mainView', () => { const layout = ref('single') const currentMainViewComponentState = ref(MainViewComponentState.Hidden) + + if (!isMobile.value) { + restoringPromise.value.then(() => { + currentMainViewComponentState.value = state.currentMainViewComponentState + }) + } + const isSidebarOpen = computed( () => currentMainViewComponentState.value === @@ -86,6 +113,10 @@ const useMainViewStorePinia = defineStore('ui/mainView', () => { () => currentMainViewComponentState.value === MainViewComponentState.Hidden ) + watch(currentMainViewComponentState, newState => { + state.currentMainViewComponentState = newState + }) + const lastScrollPosition = ref(0) const primaryView = ref({ type: 'channel', From 136d7a9325e2606adb504d8e0ade75cecceade08 Mon Sep 17 00:00:00 2001 From: otukado Date: Fri, 31 Oct 2025 18:00:31 +0900 Subject: [PATCH 3/4] =?UTF-8?q?Revert=20"refactor:=20`responsive.ts`=20?= =?UTF-8?q?=E3=82=92=20`Pinia`=20=E3=81=8B=E3=82=89=20`effectScope`=20?= =?UTF-8?q?=E3=81=AB=E7=A7=BB=E6=A4=8D"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 8bf6d5f68278adc7c1c2ca0d07fc65d41c23b245. --- src/store/ui/responsive.ts | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/src/store/ui/responsive.ts b/src/store/ui/responsive.ts index 60f85a31f7..51852ba31a 100644 --- a/src/store/ui/responsive.ts +++ b/src/store/ui/responsive.ts @@ -1,9 +1,9 @@ -import { ref, effectScope, readonly } from 'vue' +import { defineStore, acceptHMRUpdate } from 'pinia' +import { ref } from 'vue' import { mobileMinBreakpoint } from '/@/lib/media' +import { convertToRefsStore } from '/@/store/utils/convertToRefsStore' -const scope = effectScope() - -const responsiveState = scope.run(() => { +const useResponsiveStorePinia = defineStore('ui/responsive', () => { const queryList = window.matchMedia(`(max-width: ${mobileMinBreakpoint}px)`) const isHoverSupported = window.matchMedia( '(hover: hover) and (pointer: fine)' @@ -11,24 +11,20 @@ const responsiveState = scope.run(() => { const isMobile = ref(queryList.matches) const isTouchDevice = ref(!isHoverSupported.matches) - queryList.addEventListener('change', (event: MediaQueryListEvent) => { isMobile.value = event.matches }) isHoverSupported.addEventListener('change', (event: MediaQueryListEvent) => { - isTouchDevice.value = !event.matches + isTouchDevice.value = event.matches }) return { isMobile, isTouchDevice } }) -if (!responsiveState) { - throw new Error('Failed to initialize responsive state') -} - -const { isMobile, isTouchDevice } = responsiveState +export const useResponsiveStore = convertToRefsStore(useResponsiveStorePinia) -export const useResponsiveStore = () => ({ - isMobile: readonly(isMobile), - isTouchDevice: readonly(isTouchDevice) -}) +if (import.meta.hot) { + import.meta.hot.accept( + acceptHMRUpdate(useResponsiveStorePinia, import.meta.hot) + ) +} From 3c22bff74a27e6c287c92262dbd6c36e89ba64cb Mon Sep 17 00:00:00 2001 From: otukado Date: Fri, 31 Oct 2025 19:32:40 +0900 Subject: [PATCH 4/4] =?UTF-8?q?feat:=20=E3=82=B5=E3=82=A4=E3=83=89?= =?UTF-8?q?=E3=83=90=E3=83=BC=E3=81=AE=E5=B1=95=E9=96=8B=E7=8A=B6=E6=85=8B?= =?UTF-8?q?=E3=82=92=E4=BF=9D=E6=8C=81=E3=81=99=E3=82=8B=E3=81=8B=E3=81=A9?= =?UTF-8?q?=E3=81=86=E3=81=8B=E3=82=92=20`featureFlag`=20=E3=81=AB?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/app/featureFlagSettings.ts | 6 ++++++ src/store/ui/mainView.ts | 21 ++++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/store/app/featureFlagSettings.ts b/src/store/app/featureFlagSettings.ts index 13a1766020..9691516512 100644 --- a/src/store/app/featureFlagSettings.ts +++ b/src/store/app/featureFlagSettings.ts @@ -41,6 +41,12 @@ export const featureFlagDescriptions = { 'お気に入りチャンネル一覧を、お気に入りに登録されたチャンネルのみの木構造で表示します。', defaultValue: false, endAt: new Date('2025-12-31T23:59') + }, + does_save_sidebar_expansion_state: { + title: 'サイドバーの展開状態を保持', + description: 'サイドバーが展開されているかどうかを保持します。', + defaultValue: false, + endAt: new Date('2025-12-31T23:59') } } as const satisfies Record diff --git a/src/store/ui/mainView.ts b/src/store/ui/mainView.ts index 00911c17f0..86e37967c5 100644 --- a/src/store/ui/mainView.ts +++ b/src/store/ui/mainView.ts @@ -14,6 +14,7 @@ import { useUsersStore } from '/@/store/entities/users' import useIndexedDbValue from '/@/composables/utils/useIndexedDbValue' import { watch } from 'vue' import { useResponsiveStore } from './responsive' +import { useFeatureFlagSettings } from '../app/featureFlagSettings' interface ViewInformationBase { type: string @@ -73,8 +74,6 @@ export enum MainViewComponentState { export type HeaderStyle = 'default' | 'dark' -const { isMobile } = useResponsiveStore() - const useMainViewStorePinia = defineStore('ui/mainView', () => { const initialValue: State = { currentMainViewComponentState: MainViewComponentState.Hidden @@ -87,6 +86,8 @@ const useMainViewStorePinia = defineStore('ui/mainView', () => { initialValue ) const { lastOpenChannelName } = useBrowserSettings() + const { featureFlags } = useFeatureFlagSettings() + const { isMobile } = useResponsiveStore() const channelsStore = useChannelsStore() const usersStore = useUsersStore() @@ -94,11 +95,17 @@ const useMainViewStorePinia = defineStore('ui/mainView', () => { const currentMainViewComponentState = ref(MainViewComponentState.Hidden) - if (!isMobile.value) { - restoringPromise.value.then(() => { - currentMainViewComponentState.value = state.currentMainViewComponentState - }) - } + watch( + () => featureFlags.value.does_save_sidebar_expansion_state.enabled, + async enabled => { + if (!isMobile.value && enabled) { + await restoringPromise.value + currentMainViewComponentState.value = + state.currentMainViewComponentState + } + }, + { immediate: true } + ) const isSidebarOpen = computed( () =>