refactor(core): refactor left sidebar to use di

This commit is contained in:
Jimmfly
2024-09-25 15:42:26 +08:00
parent abd57484ba
commit 17182fc763
54 changed files with 317 additions and 180 deletions

View File

@@ -1,17 +1,16 @@
import type { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { SidebarIcon } from '@blocksuite/icons/rc';
import type { createStore } from 'jotai';
import { appSidebarOpenAtom } from '../components/app-sidebar';
import type { AppSidebarService } from '../modules/app-sidebar';
import { registerAffineCommand } from './registry';
export function registerAffineLayoutCommands({
t,
store,
appSidebarService,
}: {
t: ReturnType<typeof useI18n>;
store: ReturnType<typeof createStore>;
appSidebarService: AppSidebarService;
}) {
const unsubs: Array<() => void> = [];
unsubs.push(
@@ -20,7 +19,7 @@ export function registerAffineLayoutCommands({
category: 'affine:layout',
icon: <SidebarIcon />,
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();
},
})
);

View File

@@ -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,

View File

@@ -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 */
);

View File

@@ -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]
);
}

View File

@@ -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(() => {

View File

@@ -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 (
<AppContainer data-current-path={currentPath} resizing={resizing}>
<AppContainer data-current-path={currentPath}>
<LayoutComponent>{children}</LayoutComponent>
</AppContainer>
);

View File

@@ -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 (
<div
className={clsx(style.header)}

View File

@@ -1,10 +1,10 @@
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { MenuItem } from '@affine/core/modules/app-sidebar/views';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import type { DocCollection } from '@blocksuite/affine/store';
import { ImportIcon } from '@blocksuite/icons/rc';
import { MenuItem } from '../app-sidebar';
import { usePageHelper } from '../blocksuite/block-suite-page-list/utils';
const ImportPage = ({ docCollection }: { docCollection: DocCollection }) => {

View File

@@ -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';

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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',
},

View File

@@ -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<MainContainerProps>
>(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 (

View File

@@ -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 (
<div
data-testid="header"

View File

@@ -0,0 +1,71 @@
import { Entity, LiveData } from '@toeverything/infra';
import { map } from 'rxjs';
import type { AppSidebarLocalState } from '../providers/storage';
const isMobile = !BUILD_CONFIG.isElectron && window.innerWidth < 768;
enum APP_SIDEBAR_STATE {
OPEN = 'open',
WIDTH = 'width',
}
export class AppSidebar extends Entity {
constructor(private readonly appSidebarLocalState: AppSidebarLocalState) {
super();
}
open$ = LiveData.from(
this.appSidebarLocalState
.watch<boolean>(APP_SIDEBAR_STATE.OPEN)
.pipe(map(value => value ?? !isMobile)),
!isMobile
);
width$ = LiveData.from(
this.appSidebarLocalState
.watch<number>(APP_SIDEBAR_STATE.WIDTH)
.pipe(map(value => value ?? 248)),
248
);
responsiveFloating$ = new LiveData<boolean>(isMobile);
hoverFloating$ = new LiveData<boolean>(false);
resizing$ = new LiveData<boolean>(false);
getCachedAppSidebarOpenState = () => {
return this.appSidebarLocalState.get<boolean>(APP_SIDEBAR_STATE.OPEN);
};
toggleSidebar = () => {
this.setOpen(!this.open$.value);
};
setOpen = (open: boolean) => {
this.appSidebarLocalState.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.appSidebarLocalState.set(APP_SIDEBAR_STATE.WIDTH, width);
};
}

View File

@@ -0,0 +1,38 @@
import {
type GlobalState,
type Memento,
wrapMemento,
} from '@toeverything/infra';
import type { AppSidebarLocalState } from '../providers/storage';
export class AppSidebarLocalStateImpl implements AppSidebarLocalState {
wrapped: Memento;
constructor(globalState: GlobalState) {
this.wrapped = wrapMemento(globalState, `app-sidebar-state:`);
}
keys(): string[] {
return this.wrapped.keys();
}
get<T>(key: string): T | undefined {
return this.wrapped.get<T>(key);
}
watch<T>(key: string) {
return this.wrapped.watch<T>(key);
}
set<T>(key: string, value: T): void {
return this.wrapped.set<T>(key, value);
}
del(key: string): void {
return this.wrapped.del(key);
}
clear(): void {
return this.wrapped.clear();
}
}

View File

@@ -0,0 +1,15 @@
import { type Framework, GlobalState } from '@toeverything/infra';
import { AppSidebar } from './entities/app-sidebar';
import { AppSidebarLocalStateImpl } from './impls/storage';
import { AppSidebarLocalState } from './providers/storage';
import { AppSidebarService } from './services/app-sidebar';
export * from './services/app-sidebar';
export function configureAppSidebarModule(framework: Framework) {
framework
.service(AppSidebarService)
.entity(AppSidebar, [AppSidebarLocalState])
.impl(AppSidebarLocalState, AppSidebarLocalStateImpl, [GlobalState]);
}

View File

@@ -0,0 +1,7 @@
import { createIdentifier, type Memento } from '@toeverything/infra';
export interface AppSidebarLocalState extends Memento {}
export const AppSidebarLocalState = createIdentifier<AppSidebarLocalState>(
'AppSidebarLocalState'
);

View File

@@ -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]);
}

View File

@@ -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 (
<>
<ResizePanel
@@ -90,9 +115,9 @@ export function AppSidebar({ children }: PropsWithChildren) {
minWidth={MIN_WIDTH}
width={width}
resizeHandlePos="right"
onOpen={setOpen}
onResizing={setResizing}
onWidthChange={setWidth}
onOpen={handleOpenChange}
onResizing={handleResizing}
onWidthChange={handleWidthChange}
className={navWrapperStyle}
resizeHandleOffset={0}
resizeHandleVerticalPadding={clientBorder ? 16 : 0}
@@ -115,7 +140,7 @@ export function AppSidebar({ children }: PropsWithChildren) {
data-open={open}
data-is-floating={floating}
className={sidebarFloatMaskStyle}
onClick={() => 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 };

View File

@@ -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,

View File

@@ -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 (
<div className={navHeaderStyle} data-open={open}>

View File

@@ -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 (
<div
data-show={show}
@@ -34,7 +41,7 @@ export const SidebarSwitch = ({
style={{
zIndex: 1,
}}
onClick={() => setOpen(open => !open)}
onClick={toggleSidebar}
>
<SidebarIcon />
</IconButton>

View File

@@ -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();

View File

@@ -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';

View File

@@ -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<HTMLDivElement>(null);
const appSidebarService = useService(AppSidebarService).sidebar;
const sidebarWidth = useLiveData(appSidebarService.width$);
const { emoji, name } = useMemo(() => {
if (!extractEmojiAsIcon || !rawName) {
return {

View File

@@ -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);
}

View File

@@ -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$);