From a3f8e6c852380a7216c123e00b270a024862d945 Mon Sep 17 00:00:00 2001 From: JimmFly Date: Fri, 27 Sep 2024 09:32:24 +0000 Subject: [PATCH] refactor(core): refactor left sidebar to use di (#8385) --- .../components/resize-panel/resize-panel.tsx | 86 ++++++++++--------- .../core/src/commands/affine-layout.tsx | 12 ++- .../src/components/affine/app-container.tsx | 5 +- .../src/components/app-sidebar/index.jotai.ts | 14 --- .../hooks/affine/use-sidebar-status.ts | 20 ----- .../hooks/use-register-workspace-commands.ts | 6 +- .../components/layouts/workspace-layout.tsx | 8 +- .../core/src/components/pure/header/index.tsx | 9 +- .../root-app-sidebar/import-page.tsx | 2 +- .../src/components/root-app-sidebar/index.tsx | 24 +++--- .../root-app-sidebar/journal-button.tsx | 3 +- .../root-app-sidebar/trash-button.tsx | 3 +- .../root-app-sidebar/updater-button.tsx | 3 +- .../src/components/workspace/index.css.ts | 3 - .../core/src/components/workspace/index.tsx | 9 +- .../detail-page/detail-page-header.tsx | 12 +-- .../app-sidebar/entities/app-sidebar.ts | 71 +++++++++++++++ .../src/modules/app-sidebar/impls/storage.ts | 38 ++++++++ .../core/src/modules/app-sidebar/index.ts | 15 ++++ .../modules/app-sidebar/providers/storage.ts | 6 ++ .../app-sidebar/services/app-sidebar.ts | 7 ++ .../views}/add-page-button/index.css.ts | 0 .../views}/add-page-button/index.tsx | 0 .../views}/app-download-button/index.css.ts | 0 .../views}/app-download-button/index.tsx | 0 .../app-updater-button/dot-animation.svg | 0 .../views}/app-updater-button/index.css.ts | 0 .../views}/app-updater-button/index.tsx | 0 .../views}/category-divider/index.css.ts | 0 .../views}/category-divider/index.stories.tsx | 0 .../views}/category-divider/index.tsx | 0 .../app-sidebar/views}/fallback.css.ts | 0 .../app-sidebar/views}/index.css.ts | 0 .../app-sidebar/views}/index.tsx | 78 +++++++++++------ .../menu-item/external-menu-link-item.tsx | 2 +- .../app-sidebar/views}/menu-item/index.css.ts | 0 .../views}/menu-item/index.stories.tsx | 0 .../app-sidebar/views}/menu-item/index.tsx | 0 .../views}/quick-search-input/index.css.ts | 0 .../quick-search-input/index.stories.tsx | 0 .../views}/quick-search-input/index.tsx | 0 .../views}/sidebar-containers/index.css.ts | 0 .../views}/sidebar-containers/index.tsx | 0 .../views}/sidebar-header/index.tsx | 7 +- .../sidebar-header/sidebar-switch.css.ts | 0 .../views}/sidebar-header/sidebar-switch.tsx | 15 +++- .../app-sidebar/views}/spolight/index.css.ts | 0 .../views}/spolight/index.stories.tsx | 0 .../app-sidebar/views}/spolight/index.tsx | 0 .../app-tabs-header/views/app-tabs-header.tsx | 15 ++-- .../views/layouts/collapsible-section.tsx | 2 +- .../src/modules/explorer/views/tree/node.tsx | 9 +- packages/frontend/core/src/modules/index.ts | 2 + .../workbench/view/route-container.tsx | 10 +-- 54 files changed, 316 insertions(+), 180 deletions(-) delete mode 100644 packages/frontend/core/src/components/app-sidebar/index.jotai.ts delete mode 100644 packages/frontend/core/src/components/hooks/affine/use-sidebar-status.ts create mode 100644 packages/frontend/core/src/modules/app-sidebar/entities/app-sidebar.ts create mode 100644 packages/frontend/core/src/modules/app-sidebar/impls/storage.ts create mode 100644 packages/frontend/core/src/modules/app-sidebar/index.ts create mode 100644 packages/frontend/core/src/modules/app-sidebar/providers/storage.ts create mode 100644 packages/frontend/core/src/modules/app-sidebar/services/app-sidebar.ts rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/add-page-button/index.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/add-page-button/index.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/app-download-button/index.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/app-download-button/index.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/app-updater-button/dot-animation.svg (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/app-updater-button/index.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/app-updater-button/index.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/category-divider/index.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/category-divider/index.stories.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/category-divider/index.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/fallback.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/index.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/index.tsx (79%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/menu-item/external-menu-link-item.tsx (95%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/menu-item/index.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/menu-item/index.stories.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/menu-item/index.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/quick-search-input/index.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/quick-search-input/index.stories.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/quick-search-input/index.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/sidebar-containers/index.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/sidebar-containers/index.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/sidebar-header/index.tsx (54%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/sidebar-header/sidebar-switch.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/sidebar-header/sidebar-switch.tsx (68%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/spolight/index.css.ts (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/spolight/index.stories.tsx (100%) rename packages/frontend/core/src/{components/app-sidebar => modules/app-sidebar/views}/spolight/index.tsx (100%) diff --git a/packages/frontend/component/src/components/resize-panel/resize-panel.tsx b/packages/frontend/component/src/components/resize-panel/resize-panel.tsx index 73e68eff29..5b1f182070 100644 --- a/packages/frontend/component/src/components/resize-panel/resize-panel.tsx +++ b/packages/frontend/component/src/components/resize-panel/resize-panel.tsx @@ -1,4 +1,3 @@ -import { assertExists } from '@blocksuite/affine/global/utils'; import { assignInlineVars } from '@vanilla-extract/dynamic'; import clsx from 'clsx'; import { @@ -60,48 +59,53 @@ const ResizeHandle = ({ ...rest }: ResizeHandleProps) => { const ref = useRef(null); - const onResizeStart = useCallback(() => { - let resized = false; - const panelContainer = ref.current?.parentElement; - assertExists( - panelContainer, - 'parent element not found for resize indicator' - ); - - const { left: anchorLeft, right: anchorRight } = - panelContainer.getBoundingClientRect(); - - function onMouseMove(e: MouseEvent) { - e.preventDefault(); + const onResizeStart = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + let resized = false; + const panelContainer = ref.current?.parentElement; if (!panelContainer) return; - const newWidth = Math.min( - maxWidth, - Math.max( - resizeHandlePos === 'right' - ? e.clientX - anchorLeft - : anchorRight - e.clientX, - minWidth - ) - ); - onWidthChange(newWidth); - onResizing(true); - resized = true; - } - document.addEventListener('mousemove', onMouseMove); - document.addEventListener( - 'mouseup', - () => { - // if not resized, toggle sidebar - if (!resized) { - onOpen(false); - } - onResizing(false); - document.removeEventListener('mousemove', onMouseMove); - }, - { once: true } - ); - }, [maxWidth, resizeHandlePos, minWidth, onWidthChange, onResizing, onOpen]); + // add cursor style to body + document.body.style.cursor = 'col-resize'; + + const { left: anchorLeft, right: anchorRight } = + panelContainer.getBoundingClientRect(); + + function onMouseMove(e: MouseEvent) { + e.preventDefault(); + if (!panelContainer) return; + const newWidth = Math.min( + maxWidth, + Math.max( + resizeHandlePos === 'right' + ? e.clientX - anchorLeft + : anchorRight - e.clientX, + minWidth + ) + ); + onWidthChange(newWidth); + onResizing(true); + resized = true; + } + + document.addEventListener('mousemove', onMouseMove); + document.addEventListener( + 'mouseup', + () => { + // if not resized, toggle sidebar + if (!resized) { + onOpen(false); + } + onResizing(false); + document.removeEventListener('mousemove', onMouseMove); + document.body.style.cursor = ''; + }, + { once: true } + ); + }, + [maxWidth, resizeHandlePos, minWidth, onWidthChange, onResizing, onOpen] + ); return (
; - store: ReturnType; + appSidebarService: AppSidebarService; }) { const unsubs: Array<() => void> = []; unsubs.push( @@ -20,7 +19,7 @@ export function registerAffineLayoutCommands({ category: 'affine:layout', icon: , label: () => - store.get(appSidebarOpenAtom) + appSidebarService.sidebar.open$.value ? t['com.affine.cmdk.affine.left-sidebar.collapse']() : t['com.affine.cmdk.affine.left-sidebar.expand'](), @@ -29,8 +28,7 @@ export function registerAffineLayoutCommands({ }, run() { track.$.navigationPanel.$.toggle(); - - store.set(appSidebarOpenAtom, v => !v); + appSidebarService.sidebar.toggleSidebar(); }, }) ); diff --git a/packages/frontend/core/src/components/affine/app-container.tsx b/packages/frontend/core/src/components/affine/app-container.tsx index c500e2ec67..adc2a2dac1 100644 --- a/packages/frontend/core/src/components/affine/app-container.tsx +++ b/packages/frontend/core/src/components/affine/app-container.tsx @@ -1,8 +1,11 @@ +import { + AppSidebarFallback, + ShellAppSidebarFallback, +} from '@affine/core/modules/app-sidebar/views'; import clsx from 'clsx'; import type { PropsWithChildren, ReactElement } from 'react'; import { useAppSettingHelper } from '../../components/hooks/affine/use-app-setting-helper'; -import { AppSidebarFallback, ShellAppSidebarFallback } from '../app-sidebar'; import type { WorkspaceRootProps } from '../workspace'; import { AppContainer as AppContainerWithoutSettings, diff --git a/packages/frontend/core/src/components/app-sidebar/index.jotai.ts b/packages/frontend/core/src/components/app-sidebar/index.jotai.ts deleted file mode 100644 index e33777a4f4..0000000000 --- a/packages/frontend/core/src/components/app-sidebar/index.jotai.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { atom } from 'jotai'; -import { atomWithStorage } from 'jotai/utils'; - -export const APP_SIDEBAR_OPEN = 'app-sidebar-open'; -export const isMobile = !BUILD_CONFIG.isElectron && window.innerWidth < 768; - -export const appSidebarOpenAtom = atomWithStorage(APP_SIDEBAR_OPEN, !isMobile); -export const appSidebarFloatingAtom = atom(isMobile); - -export const appSidebarResizingAtom = atom(false); -export const appSidebarWidthAtom = atomWithStorage( - 'app-sidebar-width', - 248 /* px */ -); diff --git a/packages/frontend/core/src/components/hooks/affine/use-sidebar-status.ts b/packages/frontend/core/src/components/hooks/affine/use-sidebar-status.ts deleted file mode 100644 index a527846a25..0000000000 --- a/packages/frontend/core/src/components/hooks/affine/use-sidebar-status.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useAtom } from 'jotai'; -import { useCallback, useMemo } from 'react'; - -import { appSidebarOpenAtom } from '../../../components/app-sidebar'; - -export function useSwitchSidebarStatus() { - const [isOpened, setOpened] = useAtom(appSidebarOpenAtom); - - const onOpenChange = useCallback(() => { - setOpened(open => !open); - }, [setOpened]); - - return useMemo( - () => ({ - onOpenChange, - isOpened, - }), - [isOpened, onOpenChange] - ); -} diff --git a/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts b/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts index 5014218e02..f80f383f4b 100644 --- a/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts +++ b/packages/frontend/core/src/components/hooks/use-register-workspace-commands.ts @@ -1,3 +1,4 @@ +import { AppSidebarService } from '@affine/core/modules/app-sidebar'; import { useI18n } from '@affine/i18n'; import type { AffineEditorContainer } from '@blocksuite/affine/presets'; import { useService, WorkspaceService } from '@toeverything/infra'; @@ -71,6 +72,7 @@ export function useRegisterWorkspaceCommands() { const cmdkQuickSearchService = useService(CMDKQuickSearchService); const editorSettingService = useService(EditorSettingService); const createWorkspaceDialogService = useService(CreateWorkspaceDialogService); + const appSidebarService = useService(AppSidebarService); useEffect(() => { const unsub = registerCMDKCommand(cmdkQuickSearchService, editor); @@ -123,12 +125,12 @@ export function useRegisterWorkspaceCommands() { // register AffineLayoutCommands useEffect(() => { - const unsub = registerAffineLayoutCommands({ t, store }); + const unsub = registerAffineLayoutCommands({ t, appSidebarService }); return () => { unsub(); }; - }, [store, t]); + }, [appSidebarService, store, t]); // register AffineCreationCommands useEffect(() => { diff --git a/packages/frontend/core/src/components/layouts/workspace-layout.tsx b/packages/frontend/core/src/components/layouts/workspace-layout.tsx index b71ad55cac..f85d06cd99 100644 --- a/packages/frontend/core/src/components/layouts/workspace-layout.tsx +++ b/packages/frontend/core/src/components/layouts/workspace-layout.tsx @@ -3,6 +3,7 @@ import { pushGlobalLoadingEventAtom, resolveGlobalLoadingEventAtom, } from '@affine/component/global-loading'; +import { SidebarSwitch } from '@affine/core/modules/app-sidebar/views'; import { useI18n } from '@affine/i18n'; import { type DocMode, ZipTransformer } from '@blocksuite/affine/blocks'; import { @@ -17,7 +18,7 @@ import { useServices, WorkspaceService, } from '@toeverything/infra'; -import { useAtomValue, useSetAtom } from 'jotai'; +import { useSetAtom } from 'jotai'; import type { PropsWithChildren } from 'react'; import { useEffect } from 'react'; import { @@ -40,7 +41,6 @@ import { WorkbenchService } from '../../modules/workbench'; import { WorkspaceAIOnboarding } from '../affine/ai-onboarding'; import { AppContainer } from '../affine/app-container'; import { SyncAwareness } from '../affine/awareness'; -import { appSidebarResizingAtom, SidebarSwitch } from '../app-sidebar'; import { useRegisterFindInPageCommands } from '../hooks/affine/use-register-find-in-page-commands'; import { useSubscriptionNotifyReader } from '../hooks/affine/use-subscription-notify'; import { useRegisterWorkspaceCommands } from '../hooks/use-register-workspace-commands'; @@ -221,10 +221,8 @@ const WorkspaceLayoutUIContainer = ({ children }: PropsWithChildren) => { }) ); - const resizing = useAtomValue(appSidebarResizingAtom); - return ( - + {children} ); diff --git a/packages/frontend/core/src/components/pure/header/index.tsx b/packages/frontend/core/src/components/pure/header/index.tsx index 0651132d1b..831961a867 100644 --- a/packages/frontend/core/src/components/pure/header/index.tsx +++ b/packages/frontend/core/src/components/pure/header/index.tsx @@ -1,8 +1,8 @@ +import { AppSidebarService } from '@affine/core/modules/app-sidebar'; +import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; -import { useAtomValue } from 'jotai'; import type { ReactNode } from 'react'; -import { appSidebarFloatingAtom, appSidebarOpenAtom } from '../../app-sidebar'; import * as style from './style.css'; interface HeaderPros { @@ -16,8 +16,9 @@ interface HeaderPros { // 1. Manage layout issues independently of page or business logic // 2. Dynamic centered middle element (relative to the main-container), when the middle element is detected to collide with the two elements, the line wrapping process is performed export const Header = ({ left, center, right }: HeaderPros) => { - const open = useAtomValue(appSidebarOpenAtom); - const appSidebarFloating = useAtomValue(appSidebarFloatingAtom); + const appSidebarService = useService(AppSidebarService).sidebar; + const open = useLiveData(appSidebarService.open$); + const appSidebarFloating = useLiveData(appSidebarService.responsiveFloating$); return (
{ diff --git a/packages/frontend/core/src/components/root-app-sidebar/index.tsx b/packages/frontend/core/src/components/root-app-sidebar/index.tsx index 39bf3583b4..84614e98c8 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/index.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/index.tsx @@ -1,5 +1,17 @@ import { openSettingModalAtom } from '@affine/core/components/atoms'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; +import { + AddPageButton, + AppDownloadButton, + AppSidebar, + CategoryDivider, + MenuItem, + MenuLinkItem, + QuickSearchInput, + SidebarContainer, + SidebarScrollableContainer, +} from '@affine/core/modules/app-sidebar/views'; +import { ExternalMenuLinkItem } from '@affine/core/modules/app-sidebar/views/menu-item/external-menu-link-item'; import { ExplorerCollections, ExplorerFavorites, @@ -30,18 +42,6 @@ import type { MouseEvent, ReactElement } from 'react'; import { useCallback, useEffect } from 'react'; import { WorkbenchService } from '../../modules/workbench'; -import { - AddPageButton, - AppDownloadButton, - AppSidebar, - CategoryDivider, - MenuItem, - MenuLinkItem, - QuickSearchInput, - SidebarContainer, - SidebarScrollableContainer, -} from '../app-sidebar'; -import { ExternalMenuLinkItem } from '../app-sidebar/menu-item/external-menu-link-item'; import { usePageHelper } from '../blocksuite/block-suite-page-list/utils'; import { WorkspaceNavigator } from '../workspace-selector'; import ImportPage from './import-page'; diff --git a/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx b/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx index 4e456cf8fa..9da13c1678 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/journal-button.tsx @@ -3,6 +3,7 @@ import { useJournalInfoHelper, useJournalRouteHelper, } from '@affine/core/components/hooks/use-journal'; +import { MenuItem } from '@affine/core/modules/app-sidebar/views'; import { DocDisplayMetaService } from '@affine/core/modules/doc-display-meta'; import { WorkbenchService } from '@affine/core/modules/workbench'; import { isNewTabTrigger } from '@affine/core/utils'; @@ -12,8 +13,6 @@ import { TodayIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; import { type MouseEvent } from 'react'; -import { MenuItem } from '../app-sidebar'; - interface AppSidebarJournalButtonProps { docCollection: DocCollection; } diff --git a/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx b/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx index 0f30111f37..862379b4be 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/trash-button.tsx @@ -3,6 +3,7 @@ import { useConfirmModal, useDropTarget, } from '@affine/component'; +import { MenuLinkItem } from '@affine/core/modules/app-sidebar/views'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { useI18n } from '@affine/i18n'; import { @@ -12,8 +13,6 @@ import { useService, } from '@toeverything/infra'; -import { MenuLinkItem } from '../app-sidebar'; - export const TrashButton = () => { const t = useI18n(); const docsService = useService(DocsService); diff --git a/packages/frontend/core/src/components/root-app-sidebar/updater-button.tsx b/packages/frontend/core/src/components/root-app-sidebar/updater-button.tsx index aea718965c..885e08251a 100644 --- a/packages/frontend/core/src/components/root-app-sidebar/updater-button.tsx +++ b/packages/frontend/core/src/components/root-app-sidebar/updater-button.tsx @@ -1,8 +1,7 @@ import { useAppUpdater } from '@affine/core/components/hooks/use-app-updater'; +import { AppUpdaterButton } from '@affine/core/modules/app-sidebar/views'; import { Suspense } from 'react'; -import { AppUpdaterButton } from '../app-sidebar'; - const UpdaterButtonInner = () => { const appUpdater = useAppUpdater(); diff --git a/packages/frontend/core/src/components/workspace/index.css.ts b/packages/frontend/core/src/components/workspace/index.css.ts index c789518b04..c46fb50364 100644 --- a/packages/frontend/core/src/components/workspace/index.css.ts +++ b/packages/frontend/core/src/components/workspace/index.css.ts @@ -9,9 +9,6 @@ export const appStyle = style({ display: 'flex', backgroundColor: cssVar('backgroundPrimaryColor'), selectors: { - '&[data-is-resizing="true"]': { - cursor: 'col-resize', - }, '&.blur-background': { backgroundColor: 'transparent', }, diff --git a/packages/frontend/core/src/components/workspace/index.tsx b/packages/frontend/core/src/components/workspace/index.tsx index cc18169124..68bc3bb6ee 100644 --- a/packages/frontend/core/src/components/workspace/index.tsx +++ b/packages/frontend/core/src/components/workspace/index.tsx @@ -1,4 +1,5 @@ import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper'; +import { AppSidebarService } from '@affine/core/modules/app-sidebar'; import { DocsService, GlobalContextService, @@ -6,22 +7,18 @@ import { useService, } from '@toeverything/infra'; import { clsx } from 'clsx'; -import { useAtomValue } from 'jotai'; import type { HTMLAttributes, PropsWithChildren, ReactElement } from 'react'; import { forwardRef } from 'react'; -import { appSidebarOpenAtom } from '../app-sidebar'; import { appStyle, mainContainerStyle, toolStyle } from './index.css'; export type WorkspaceRootProps = PropsWithChildren<{ - resizing?: boolean; className?: string; useNoisyBackground?: boolean; useBlurBackground?: boolean; }>; export const AppContainer = ({ - resizing, useNoisyBackground, useBlurBackground, children, @@ -39,7 +36,6 @@ export const AppContainer = ({ 'blur-background': blurBackground, })} data-noise-background={noisyBackground} - data-is-resizing={resizing} data-blur-background={blurBackground} > {children} @@ -53,7 +49,8 @@ export const MainContainer = forwardRef< HTMLDivElement, PropsWithChildren >(function MainContainer({ className, children, ...props }, ref): ReactElement { - const appSideBarOpen = useAtomValue(appSidebarOpenAtom); + const appSidebarService = useService(AppSidebarService).sidebar; + const appSideBarOpen = useLiveData(appSidebarService.open$); const { appSettings } = useAppSettingHelper(); return ( diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-header.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-header.tsx index a8df2f9f81..d57ffc4f0b 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-header.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-header.tsx @@ -3,27 +3,26 @@ import { type InlineEditHandle, observeResize, } from '@affine/component'; +import { SharePageButton } from '@affine/core/components/affine/share-page-modal'; import { FavoriteButton } from '@affine/core/components/blocksuite/block-suite-header/favorite'; import { InfoButton } from '@affine/core/components/blocksuite/block-suite-header/info'; import { JournalWeekDatePicker } from '@affine/core/components/blocksuite/block-suite-header/journal/date-picker'; import { JournalTodayButton } from '@affine/core/components/blocksuite/block-suite-header/journal/today-button'; import { PageHeaderMenuButton } from '@affine/core/components/blocksuite/block-suite-header/menu'; import { DetailPageHeaderPresentButton } from '@affine/core/components/blocksuite/block-suite-header/present/detail-header-present-button'; +import { BlocksuiteHeaderTitle } from '@affine/core/components/blocksuite/block-suite-header/title'; import { EditorModeSwitch } from '@affine/core/components/blocksuite/block-suite-mode-switch'; import { useRegisterCopyLinkCommands } from '@affine/core/components/hooks/affine/use-register-copy-link-commands'; import { useDocCollectionPageTitle } from '@affine/core/components/hooks/use-block-suite-workspace-page-title'; import { useJournalInfoHelper } from '@affine/core/components/hooks/use-journal'; +import { HeaderDivider } from '@affine/core/components/pure/header'; +import { AppSidebarService } from '@affine/core/modules/app-sidebar'; import { EditorService } from '@affine/core/modules/editor'; import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench'; import type { Doc } from '@blocksuite/affine/store'; import { useLiveData, useService, type Workspace } from '@toeverything/infra'; -import { useAtomValue } from 'jotai'; import { forwardRef, useCallback, useEffect, useRef, useState } from 'react'; -import { SharePageButton } from '../../../../components/affine/share-page-modal'; -import { appSidebarFloatingAtom } from '../../../../components/app-sidebar'; -import { BlocksuiteHeaderTitle } from '../../../../components/blocksuite/block-suite-header/title/index'; -import { HeaderDivider } from '../../../../components/pure/header'; import * as styles from './detail-page-header.css'; import { useDetailPageHeaderResponsive } from './use-header-responsive'; @@ -35,7 +34,8 @@ const Header = forwardRef< style?: React.CSSProperties; } >(({ children, style, className }, ref) => { - const appSidebarFloating = useAtomValue(appSidebarFloatingAtom); + const appSidebarService = useService(AppSidebarService).sidebar; + const appSidebarFloating = useLiveData(appSidebarService.responsiveFloating$); return (
(APP_SIDEBAR_STATE.OPEN) + .pipe(map(value => value ?? !isMobile)), + !isMobile + ); + + width$ = LiveData.from( + this.appSidebarState + .watch(APP_SIDEBAR_STATE.WIDTH) + .pipe(map(value => value ?? 248)), + 248 + ); + responsiveFloating$ = new LiveData(isMobile); + hoverFloating$ = new LiveData(false); + resizing$ = new LiveData(false); + + getCachedAppSidebarOpenState = () => { + return this.appSidebarState.get(APP_SIDEBAR_STATE.OPEN); + }; + + toggleSidebar = () => { + this.setOpen(!this.open$.value); + }; + + setOpen = (open: boolean) => { + this.appSidebarState.set(APP_SIDEBAR_STATE.OPEN, open); + if (!open && this.hoverFloating$.value) { + const timeout = setTimeout(() => { + this.setHoverFloating(false); + }, 500); + return () => { + clearTimeout(timeout); + }; + } + return; + }; + + setResponsiveFloating = (floating: boolean) => { + this.responsiveFloating$.next(floating); + }; + + setHoverFloating = (hoverFloating: boolean) => { + this.hoverFloating$.next(hoverFloating); + }; + + setResizing = (resizing: boolean) => { + this.resizing$.next(resizing); + }; + + setWidth = (width: number) => { + this.appSidebarState.set(APP_SIDEBAR_STATE.WIDTH, width); + }; +} diff --git a/packages/frontend/core/src/modules/app-sidebar/impls/storage.ts b/packages/frontend/core/src/modules/app-sidebar/impls/storage.ts new file mode 100644 index 0000000000..9261e692ff --- /dev/null +++ b/packages/frontend/core/src/modules/app-sidebar/impls/storage.ts @@ -0,0 +1,38 @@ +import { + type GlobalState, + type Memento, + wrapMemento, +} from '@toeverything/infra'; + +import type { AppSidebarState } from '../providers/storage'; + +export class AppSidebarStateImpl implements AppSidebarState { + wrapped: Memento; + constructor(globalState: GlobalState) { + this.wrapped = wrapMemento(globalState, `app-sidebar-state:`); + } + + keys(): string[] { + return this.wrapped.keys(); + } + + get(key: string): T | undefined { + return this.wrapped.get(key); + } + + watch(key: string) { + return this.wrapped.watch(key); + } + + set(key: string, value: T): void { + return this.wrapped.set(key, value); + } + + del(key: string): void { + return this.wrapped.del(key); + } + + clear(): void { + return this.wrapped.clear(); + } +} diff --git a/packages/frontend/core/src/modules/app-sidebar/index.ts b/packages/frontend/core/src/modules/app-sidebar/index.ts new file mode 100644 index 0000000000..13dcd19fac --- /dev/null +++ b/packages/frontend/core/src/modules/app-sidebar/index.ts @@ -0,0 +1,15 @@ +import { type Framework, GlobalState } from '@toeverything/infra'; + +import { AppSidebar } from './entities/app-sidebar'; +import { AppSidebarStateImpl } from './impls/storage'; +import { AppSidebarState } from './providers/storage'; +import { AppSidebarService } from './services/app-sidebar'; + +export * from './services/app-sidebar'; + +export function configureAppSidebarModule(framework: Framework) { + framework + .service(AppSidebarService) + .entity(AppSidebar, [AppSidebarState]) + .impl(AppSidebarState, AppSidebarStateImpl, [GlobalState]); +} diff --git a/packages/frontend/core/src/modules/app-sidebar/providers/storage.ts b/packages/frontend/core/src/modules/app-sidebar/providers/storage.ts new file mode 100644 index 0000000000..03685bf5cb --- /dev/null +++ b/packages/frontend/core/src/modules/app-sidebar/providers/storage.ts @@ -0,0 +1,6 @@ +import { createIdentifier, type Memento } from '@toeverything/infra'; + +export interface AppSidebarState extends Memento {} + +export const AppSidebarState = + createIdentifier('AppSidebarState'); diff --git a/packages/frontend/core/src/modules/app-sidebar/services/app-sidebar.ts b/packages/frontend/core/src/modules/app-sidebar/services/app-sidebar.ts new file mode 100644 index 0000000000..dafb93a619 --- /dev/null +++ b/packages/frontend/core/src/modules/app-sidebar/services/app-sidebar.ts @@ -0,0 +1,7 @@ +import { GlobalState, Service } from '@toeverything/infra'; + +import { AppSidebar } from '../entities/app-sidebar'; + +export class AppSidebarService extends Service { + sidebar = this.framework.createEntity(AppSidebar, [GlobalState]); +} diff --git a/packages/frontend/core/src/components/app-sidebar/add-page-button/index.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/add-page-button/index.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/add-page-button/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/add-page-button/index.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/add-page-button/index.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/app-download-button/index.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/app-download-button/index.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/app-download-button/index.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/app-download-button/index.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/app-download-button/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/app-download-button/index.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/app-download-button/index.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/app-download-button/index.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/app-updater-button/dot-animation.svg b/packages/frontend/core/src/modules/app-sidebar/views/app-updater-button/dot-animation.svg similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/app-updater-button/dot-animation.svg rename to packages/frontend/core/src/modules/app-sidebar/views/app-updater-button/dot-animation.svg diff --git a/packages/frontend/core/src/components/app-sidebar/app-updater-button/index.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/app-updater-button/index.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/app-updater-button/index.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/app-updater-button/index.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/app-updater-button/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/app-updater-button/index.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/app-updater-button/index.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/app-updater-button/index.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/category-divider/index.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/category-divider/index.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/category-divider/index.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/category-divider/index.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/category-divider/index.stories.tsx b/packages/frontend/core/src/modules/app-sidebar/views/category-divider/index.stories.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/category-divider/index.stories.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/category-divider/index.stories.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/category-divider/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/category-divider/index.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/category-divider/index.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/category-divider/index.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/fallback.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/fallback.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/fallback.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/fallback.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/index.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/index.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/index.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/index.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/index.tsx similarity index 79% rename from packages/frontend/core/src/components/app-sidebar/index.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/index.tsx index 3c221d3c8f..efd9d91918 100644 --- a/packages/frontend/core/src/components/app-sidebar/index.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/index.tsx @@ -2,13 +2,18 @@ import { Skeleton } from '@affine/component'; import { ResizePanel } from '@affine/component/resize-panel'; import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper'; import { NavigateContext } from '@affine/core/components/hooks/use-navigate-helper'; -import { useServiceOptional, WorkspaceService } from '@toeverything/infra'; -import { useAtom, useAtomValue } from 'jotai'; +import { WorkspaceNavigator } from '@affine/core/components/workspace-selector'; +import { + useLiveData, + useService, + useServiceOptional, + WorkspaceService, +} from '@toeverything/infra'; import { debounce } from 'lodash-es'; import type { PropsWithChildren, ReactElement } from 'react'; -import { useContext, useEffect, useMemo } from 'react'; +import { useCallback, useContext, useEffect, useMemo } from 'react'; -import { WorkspaceNavigator } from '../workspace-selector'; +import { AppSidebarService } from '../services/app-sidebar'; import * as styles from './fallback.css'; import { floatingMaxWidth, @@ -18,13 +23,6 @@ import { navWrapperStyle, sidebarFloatMaskStyle, } from './index.css'; -import { - APP_SIDEBAR_OPEN, - appSidebarFloatingAtom, - appSidebarOpenAtom, - appSidebarResizingAtom, - appSidebarWidthAtom, -} from './index.jotai'; import { SidebarHeader } from './sidebar-header'; export type History = { @@ -40,10 +38,12 @@ export function AppSidebar({ children }: PropsWithChildren) { const clientBorder = appSettings.clientBorder; - const [open, setOpen] = useAtom(appSidebarOpenAtom); - const [width, setWidth] = useAtom(appSidebarWidthAtom); - const [floating, setFloating] = useAtom(appSidebarFloatingAtom); - const [resizing, setResizing] = useAtom(appSidebarResizingAtom); + const appSidebarService = useService(AppSidebarService).sidebar; + + const open = useLiveData(appSidebarService.open$); + const width = useLiveData(appSidebarService.width$); + const floating = useLiveData(appSidebarService.responsiveFloating$); + const resizing = useLiveData(appSidebarService.resizing$); useEffect(() => { // do not float app sidebar on desktop @@ -61,13 +61,13 @@ export function AppSidebar({ children }: PropsWithChildren) { const isFloating = isFloatingMaxWidth || isOverflowWidth; if ( open === undefined && - localStorage.getItem(APP_SIDEBAR_OPEN) === null + appSidebarService.getCachedAppSidebarOpenState() === undefined ) { // give the initial value, // so that the sidebar can be closed on mobile by default - setOpen(!isFloating); + appSidebarService.setOpen(!isFloating); } - setFloating(isFloating); + appSidebarService.setResponsiveFloating(isFloating); } const dOnResize = debounce(onResize, 50); @@ -75,11 +75,36 @@ export function AppSidebar({ children }: PropsWithChildren) { return () => { window.removeEventListener('resize', dOnResize); }; - }, [open, setFloating, setOpen, width]); + }, [appSidebarService, open, width]); const hasRightBorder = !BUILD_CONFIG.isElectron && !clientBorder; const isMacosDesktop = BUILD_CONFIG.isElectron && environment.isMacOs; + const handleOpenChange = useCallback( + (open: boolean) => { + appSidebarService.setOpen(open); + }, + [appSidebarService] + ); + + const handleResizing = useCallback( + (resizing: boolean) => { + appSidebarService.setResizing(resizing); + }, + [appSidebarService] + ); + + const handleWidthChange = useCallback( + (width: number) => { + appSidebarService.setWidth(width); + }, + [appSidebarService] + ); + + const handleClose = useCallback(() => { + appSidebarService.setOpen(false); + }, [appSidebarService]); + return ( <> setOpen(false)} + onClick={handleClose} /> ); @@ -207,7 +232,8 @@ const FallbackBody = () => { }; export const AppSidebarFallback = (): ReactElement | null => { - const width = useAtomValue(appSidebarWidthAtom); + const appSidebarService = useService(AppSidebarService).sidebar; + const width = useLiveData(appSidebarService.width$); const { appSettings } = useAppSettingHelper(); const clientBorder = appSettings.clientBorder; @@ -235,7 +261,8 @@ export const AppSidebarFallback = (): ReactElement | null => { * NOTE(@forehalo): this is a copy of [AppSidebarFallback] without [WorkspaceNavigator] which will introduce a lot useless dependencies for shell(tab bar) */ export const ShellAppSidebarFallback = () => { - const width = useAtomValue(appSidebarWidthAtom); + const appSidebarService = useService(AppSidebarService).sidebar; + const width = useLiveData(appSidebarService.width$); const { appSettings } = useAppSettingHelper(); const clientBorder = appSettings.clientBorder; @@ -268,4 +295,3 @@ export * from './menu-item'; export * from './quick-search-input'; export * from './sidebar-containers'; export * from './sidebar-header'; -export { appSidebarFloatingAtom, appSidebarOpenAtom, appSidebarResizingAtom }; diff --git a/packages/frontend/core/src/components/app-sidebar/menu-item/external-menu-link-item.tsx b/packages/frontend/core/src/modules/app-sidebar/views/menu-item/external-menu-link-item.tsx similarity index 95% rename from packages/frontend/core/src/components/app-sidebar/menu-item/external-menu-link-item.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/menu-item/external-menu-link-item.tsx index f8613ff8a9..ec7eb1d9eb 100644 --- a/packages/frontend/core/src/components/app-sidebar/menu-item/external-menu-link-item.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/menu-item/external-menu-link-item.tsx @@ -3,7 +3,7 @@ import { cssVarV2 } from '@toeverything/theme/v2'; import type { ReactElement } from 'react'; import type { To } from 'react-router-dom'; -import { MenuLinkItem } from '.'; +import { MenuLinkItem } from './index'; const RawLink = ({ children, diff --git a/packages/frontend/core/src/components/app-sidebar/menu-item/index.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/menu-item/index.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/menu-item/index.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/menu-item/index.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/menu-item/index.stories.tsx b/packages/frontend/core/src/modules/app-sidebar/views/menu-item/index.stories.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/menu-item/index.stories.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/menu-item/index.stories.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/menu-item/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/menu-item/index.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/menu-item/index.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/menu-item/index.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/quick-search-input/index.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/quick-search-input/index.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/quick-search-input/index.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/quick-search-input/index.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/quick-search-input/index.stories.tsx b/packages/frontend/core/src/modules/app-sidebar/views/quick-search-input/index.stories.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/quick-search-input/index.stories.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/quick-search-input/index.stories.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/quick-search-input/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/quick-search-input/index.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/quick-search-input/index.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/quick-search-input/index.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/sidebar-containers/index.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/sidebar-containers/index.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/sidebar-containers/index.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/sidebar-containers/index.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/sidebar-containers/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/sidebar-containers/index.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/sidebar-containers/index.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/sidebar-containers/index.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/sidebar-header/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/index.tsx similarity index 54% rename from packages/frontend/core/src/components/app-sidebar/sidebar-header/index.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/index.tsx index 3ee548b27e..0e873df208 100644 --- a/packages/frontend/core/src/components/app-sidebar/sidebar-header/index.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/index.tsx @@ -1,11 +1,12 @@ -import { useAtomValue } from 'jotai'; +import { useLiveData, useService } from '@toeverything/infra'; +import { AppSidebarService } from '../../services/app-sidebar'; import { navHeaderStyle } from '../index.css'; -import { appSidebarOpenAtom } from '../index.jotai'; import { SidebarSwitch } from './sidebar-switch'; export const SidebarHeader = () => { - const open = useAtomValue(appSidebarOpenAtom); + const appSidebarService = useService(AppSidebarService).sidebar; + const open = useLiveData(appSidebarService.open$); return (
diff --git a/packages/frontend/core/src/components/app-sidebar/sidebar-header/sidebar-switch.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/sidebar-switch.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/sidebar-header/sidebar-switch.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/sidebar-switch.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/sidebar-header/sidebar-switch.tsx b/packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/sidebar-switch.tsx similarity index 68% rename from packages/frontend/core/src/components/app-sidebar/sidebar-header/sidebar-switch.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/sidebar-switch.tsx index 0fbdb44a17..91c7d85da6 100644 --- a/packages/frontend/core/src/components/app-sidebar/sidebar-header/sidebar-switch.tsx +++ b/packages/frontend/core/src/modules/app-sidebar/views/sidebar-header/sidebar-switch.tsx @@ -1,9 +1,10 @@ import { IconButton } from '@affine/component'; import { useI18n } from '@affine/i18n'; import { SidebarIcon } from '@blocksuite/icons/rc'; -import { useAtom } from 'jotai'; +import { useLiveData, useService } from '@toeverything/infra'; +import { useCallback } from 'react'; -import { appSidebarOpenAtom } from '../index.jotai'; +import { AppSidebarService } from '../../services/app-sidebar'; import * as styles from './sidebar-switch.css'; export const SidebarSwitch = ({ @@ -13,12 +14,18 @@ export const SidebarSwitch = ({ show: boolean; className?: string; }) => { - const [open, setOpen] = useAtom(appSidebarOpenAtom); + const appSidebarService = useService(AppSidebarService).sidebar; + const open = useLiveData(appSidebarService.open$); + const t = useI18n(); const tooltipContent = open ? t['com.affine.sidebarSwitch.collapse']() : t['com.affine.sidebarSwitch.expand'](); + const toggleSidebar = useCallback(() => { + appSidebarService.toggleSidebar(); + }, [appSidebarService]); + return (
setOpen(open => !open)} + onClick={toggleSidebar} > diff --git a/packages/frontend/core/src/components/app-sidebar/spolight/index.css.ts b/packages/frontend/core/src/modules/app-sidebar/views/spolight/index.css.ts similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/spolight/index.css.ts rename to packages/frontend/core/src/modules/app-sidebar/views/spolight/index.css.ts diff --git a/packages/frontend/core/src/components/app-sidebar/spolight/index.stories.tsx b/packages/frontend/core/src/modules/app-sidebar/views/spolight/index.stories.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/spolight/index.stories.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/spolight/index.stories.tsx diff --git a/packages/frontend/core/src/components/app-sidebar/spolight/index.tsx b/packages/frontend/core/src/modules/app-sidebar/views/spolight/index.tsx similarity index 100% rename from packages/frontend/core/src/components/app-sidebar/spolight/index.tsx rename to packages/frontend/core/src/modules/app-sidebar/views/spolight/index.tsx diff --git a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx index a8a88e8535..da8ed70639 100644 --- a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx +++ b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx @@ -7,11 +7,6 @@ import { useDraggable, useDropTarget, } from '@affine/component'; -import { - appSidebarOpenAtom, - appSidebarResizingAtom, -} from '@affine/core/components/app-sidebar'; -import { appSidebarWidthAtom } from '@affine/core/components/app-sidebar/index.jotai'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useCatchEventCallback } from '@affine/core/components/hooks/use-catch-event-hook'; import type { AffineDNDData } from '@affine/core/types/dnd'; @@ -25,7 +20,6 @@ import { useServiceOptional, } from '@toeverything/infra'; import clsx from 'clsx'; -import { useAtomValue } from 'jotai'; import { partition } from 'lodash-es'; import { Fragment, @@ -35,6 +29,7 @@ import { useState, } from 'react'; +import { AppSidebarService } from '../../app-sidebar'; import { iconNameToIcon } from '../../workbench/constants'; import { DesktopStateSynchronizer } from '../../workbench/services/desktop-state-synchronizer'; import { @@ -298,9 +293,11 @@ export const AppTabsHeader = ({ left?: ReactNode; }) => { const t = useI18n(); - const sidebarWidth = useAtomValue(appSidebarWidthAtom); - const sidebarOpen = useAtomValue(appSidebarOpenAtom); - const sidebarResizing = useAtomValue(appSidebarResizingAtom); + const appSidebarService = useService(AppSidebarService).sidebar; + const sidebarWidth = useLiveData(appSidebarService.width$); + const sidebarOpen = useLiveData(appSidebarService.open$); + const sidebarResizing = useLiveData(appSidebarService.resizing$); + const isMacosDesktop = BUILD_CONFIG.isElectron && environment.isMacOs; const isWindowsDesktop = BUILD_CONFIG.isElectron && environment.isWindows; const fullScreen = useIsFullScreen(); diff --git a/packages/frontend/core/src/modules/explorer/views/layouts/collapsible-section.tsx b/packages/frontend/core/src/modules/explorer/views/layouts/collapsible-section.tsx index 4ad7fedbb7..828e2d5d26 100644 --- a/packages/frontend/core/src/modules/explorer/views/layouts/collapsible-section.tsx +++ b/packages/frontend/core/src/modules/explorer/views/layouts/collapsible-section.tsx @@ -1,4 +1,4 @@ -import { CategoryDivider } from '@affine/core/components/app-sidebar'; +import { CategoryDivider } from '@affine/core/modules/app-sidebar/views'; import * as Collapsible from '@radix-ui/react-collapsible'; import { useLiveData, useService } from '@toeverything/infra'; import clsx from 'clsx'; diff --git a/packages/frontend/core/src/modules/explorer/views/tree/node.tsx b/packages/frontend/core/src/modules/explorer/views/tree/node.tsx index f636392210..c4e5484aa4 100644 --- a/packages/frontend/core/src/modules/explorer/views/tree/node.tsx +++ b/packages/frontend/core/src/modules/explorer/views/tree/node.tsx @@ -10,7 +10,7 @@ import { useDropTarget, } from '@affine/component'; import { RenameModal } from '@affine/component/rename-modal'; -import { appSidebarWidthAtom } from '@affine/core/components/app-sidebar/index.jotai'; +import { AppSidebarService } from '@affine/core/modules/app-sidebar'; import { WorkbenchLink } from '@affine/core/modules/workbench'; import type { AffineDNDData } from '@affine/core/types/dnd'; import { extractEmojiIcon } from '@affine/core/utils'; @@ -21,10 +21,10 @@ import { MoreHorizontalIcon, } from '@blocksuite/icons/rc'; import * as Collapsible from '@radix-ui/react-collapsible'; +import { useLiveData, useService } from '@toeverything/infra'; import { assignInlineVars } from '@vanilla-extract/dynamic'; import clsx from 'clsx'; import type { To } from 'history'; -import { useAtomValue } from 'jotai'; import { Fragment, type RefAttributes, @@ -117,10 +117,13 @@ export const ExplorerTreeNode = ({ // If no onClick or to is provided, clicking on the node will toggle the collapse state const clickForCollapse = !onClick && !to && !disabled; const [childCount, setChildCount] = useState(0); - const sidebarWidth = useAtomValue(appSidebarWidthAtom); const [renaming, setRenaming] = useState(defaultRenaming); const [lastInGroup, setLastInGroup] = useState(false); const rootRef = useRef(null); + + const appSidebarService = useService(AppSidebarService).sidebar; + const sidebarWidth = useLiveData(appSidebarService.width$); + const { emoji, name } = useMemo(() => { if (!extractEmojiAsIcon || !rawName) { return { diff --git a/packages/frontend/core/src/modules/index.ts b/packages/frontend/core/src/modules/index.ts index 3d4ebc151a..c52c397747 100644 --- a/packages/frontend/core/src/modules/index.ts +++ b/packages/frontend/core/src/modules/index.ts @@ -1,6 +1,7 @@ import { configureQuotaModule } from '@affine/core/modules/quota'; import { configureInfraModules, type Framework } from '@toeverything/infra'; +import { configureAppSidebarModule } from './app-sidebar'; import { configureCloudModule } from './cloud'; import { configureCollectionModule } from './collection'; import { configureCreateWorkspaceModule } from './create-workspace'; @@ -57,4 +58,5 @@ export function configureCommonModules(framework: Framework) { configureCreateWorkspaceModule(framework); configureUserspaceModule(framework); configureDocInfoModule(framework); + configureAppSidebarModule(framework); } diff --git a/packages/frontend/core/src/modules/workbench/view/route-container.tsx b/packages/frontend/core/src/modules/workbench/view/route-container.tsx index 8a61b70454..89f501d61b 100644 --- a/packages/frontend/core/src/modules/workbench/view/route-container.tsx +++ b/packages/frontend/core/src/modules/workbench/view/route-container.tsx @@ -1,13 +1,12 @@ import { IconButton } from '@affine/component'; +import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; import { RightSidebarIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService } from '@toeverything/infra'; -import { useAtomValue } from 'jotai'; import { Suspense, useCallback } from 'react'; import { Outlet } from 'react-router-dom'; -import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary'; -import { appSidebarOpenAtom } from '../../../components/app-sidebar/index.jotai'; -import { SidebarSwitch } from '../../../components/app-sidebar/sidebar-header/sidebar-switch'; +import { AppSidebarService } from '../../app-sidebar'; +import { SidebarSwitch } from '../../app-sidebar/views'; import { ViewService } from '../services/view'; import { WorkbenchService } from '../services/workbench'; import * as styles from './route-container.css'; @@ -43,7 +42,8 @@ const ToggleButton = ({ export const RouteContainer = () => { const viewPosition = useViewPosition(); - const leftSidebarOpen = useAtomValue(appSidebarOpenAtom); + const appSidebarService = useService(AppSidebarService).sidebar; + const leftSidebarOpen = useLiveData(appSidebarService.open$); const workbench = useService(WorkbenchService).workbench; const view = useService(ViewService).view; const sidebarOpen = useLiveData(workbench.sidebarOpen$);