refactor(core): refactor left sidebar to use di (#8385)
This commit is contained in:
54 files changed
+282
-146
No files matched your search
@@ -1,4 +1,3 @@
|
||||
import { assertExists } from '@blocksuite/affine/global/utils';
|
||||
import { assignInlineVars } from '@vanilla-extract/dynamic';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
@@ -60,13 +59,15 @@ const ResizeHandle = ({
|
||||
...rest
|
||||
}: ResizeHandleProps) => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const onResizeStart = useCallback(() => {
|
||||
const onResizeStart = useCallback(
|
||||
(event: React.MouseEvent<HTMLDivElement>) => {
|
||||
event.preventDefault();
|
||||
let resized = false;
|
||||
const panelContainer = ref.current?.parentElement;
|
||||
assertExists(
|
||||
panelContainer,
|
||||
'parent element not found for resize indicator'
|
||||
);
|
||||
if (!panelContainer) return;
|
||||
|
||||
// add cursor style to body
|
||||
document.body.style.cursor = 'col-resize';
|
||||
|
||||
const { left: anchorLeft, right: anchorRight } =
|
||||
panelContainer.getBoundingClientRect();
|
||||
@@ -98,10 +99,13 @@ const ResizeHandle = ({
|
||||
}
|
||||
onResizing(false);
|
||||
document.removeEventListener('mousemove', onMouseMove);
|
||||
document.body.style.cursor = '';
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
}, [maxWidth, resizeHandlePos, minWidth, onWidthChange, onResizing, onOpen]);
|
||||
},
|
||||
[maxWidth, resizeHandlePos, minWidth, onWidthChange, onResizing, onOpen]
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -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();
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 */
|
||||
);
|
||||
@@ -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]
|
||||
);
|
||||
}
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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 (
|
||||
|
||||
+6
-6
@@ -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"
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import { Entity, LiveData } from '@toeverything/infra';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import type { AppSidebarState } 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 appSidebarState: AppSidebarState) {
|
||||
super();
|
||||
}
|
||||
|
||||
open$ = LiveData.from(
|
||||
this.appSidebarState
|
||||
.watch<boolean>(APP_SIDEBAR_STATE.OPEN)
|
||||
.pipe(map(value => value ?? !isMobile)),
|
||||
!isMobile
|
||||
);
|
||||
|
||||
width$ = LiveData.from(
|
||||
this.appSidebarState
|
||||
.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.appSidebarState.get<boolean>(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);
|
||||
};
|
||||
}
|
||||
@@ -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<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();
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { createIdentifier, type Memento } from '@toeverything/infra';
|
||||
|
||||
export interface AppSidebarState extends Memento {}
|
||||
|
||||
export const AppSidebarState =
|
||||
createIdentifier<AppSidebarState>('AppSidebarState');
|
||||
@@ -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]);
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+52
-26
@@ -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 };
|
||||
+1
-1
@@ -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,
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+4
-3
@@ -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}>
|
||||
File renamed without changes.
+11
-4
@@ -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>
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -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();
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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$);
|
||||
|
||||
Reference in new issue
Block a user