From fbf590ddd43cee5003ecf3c97325ad57b20199c6 Mon Sep 17 00:00:00 2001 From: EYHN Date: Mon, 19 May 2025 09:15:37 +0000 Subject: [PATCH] feat(core): support save and restore display preference in all docs (#12315) ## Summary by CodeRabbit - **New Features** - Display preferences and selected collections are now saved and restored across sessions, providing a persistent and personalized experience in the All Documents page. - **Refactor** - Display settings menus and related components have been updated to use a controlled component pattern, allowing preferences to be managed externally for improved consistency and flexibility. - Preference state management has been consolidated, simplifying how display options are handled throughout the interface. - Various headers and detail views now accept display preferences and update callbacks as props, enabling external control of display settings. - Components previously relying on internal context and reactive streams were refactored to receive explicit props and callbacks for state management. - **Bug Fixes** - Improved collection activation logic to prevent unnecessary updates when the selected collection is already active. - Added fallback default view to ensure consistent display in document list items. --- .../infra/src/framework/react/index.tsx | 4 +- .../core/src/components/explorer/context.ts | 66 ++++++++++++----- .../explorer/display-menu/index.tsx | 74 ++++++++++++++----- .../explorer/display-menu/properties.tsx | 45 +++++++---- .../explorer/display-menu/quick-actions.tsx | 49 ++++++++---- .../explorer/display-menu/view-toggle.tsx | 20 ++--- .../explorer/docs-view/doc-list-item.tsx | 4 +- .../explorer/quick-actions.constants.tsx | 7 +- .../core/src/components/explorer/types.ts | 7 +- .../workspace/all-page/all-page-header.tsx | 31 +++++++- .../pages/workspace/all-page/all-page.tsx | 54 ++++++++++++-- .../workspace/all-page/pinned-collections.tsx | 12 ++- .../pages/workspace/collection/header.tsx | 23 +++++- .../pages/workspace/collection/index.tsx | 16 +++- .../desktop/pages/workspace/tag/header.tsx | 18 ++++- .../src/desktop/pages/workspace/tag/index.tsx | 19 ++++- 16 files changed, 344 insertions(+), 105 deletions(-) diff --git a/packages/common/infra/src/framework/react/index.tsx b/packages/common/infra/src/framework/react/index.tsx index 4805e37ac..296510d91 100644 --- a/packages/common/infra/src/framework/react/index.tsx +++ b/packages/common/infra/src/framework/react/index.tsx @@ -12,9 +12,7 @@ export function useFramework(): FrameworkProvider { return useContext(FrameworkProviderContext); // never null, because the default value } -export function useService( - identifier: GeneralIdentifier -): T { +export function useService(identifier: GeneralIdentifier): T { return useContext(FrameworkProviderContext).get(identifier); } diff --git a/packages/frontend/core/src/components/explorer/context.ts b/packages/frontend/core/src/components/explorer/context.ts index 8ab7a2a05..8d58e8ba2 100644 --- a/packages/frontend/core/src/components/explorer/context.ts +++ b/packages/frontend/core/src/components/explorer/context.ts @@ -1,19 +1,18 @@ import { LiveData } from '@toeverything/infra'; import { createContext } from 'react'; -import type { DocListItemView } from './docs-view/doc-list-item'; -import type { ExplorerPreference } from './types'; +import type { ExplorerDisplayPreference } from './types'; export type DocExplorerContextType = { - view$: LiveData; groups$: LiveData>; collapsedGroups$: LiveData; selectMode$?: LiveData; selectedDocIds$: LiveData; prevCheckAnchorId$?: LiveData; + displayPreference$: LiveData; } & { - [K in keyof Omit as `${K}$`]: LiveData< - ExplorerPreference[K] + [K in keyof ExplorerDisplayPreference as `${K}$`]: LiveData< + ExplorerDisplayPreference[K] >; }; @@ -21,24 +20,51 @@ export const DocExplorerContext = createContext( {} as any ); -export const createDocExplorerContext = () => - ({ - view$: new LiveData('list'), +export const createDocExplorerContext = ( + initialState?: ExplorerDisplayPreference +) => { + const displayPreference$ = new LiveData( + initialState ?? {} + ); + return { groups$: new LiveData>([]), collapsedGroups$: new LiveData([]), selectMode$: new LiveData(false), selectedDocIds$: new LiveData([]), prevCheckAnchorId$: new LiveData(null), - groupBy$: new LiveData(undefined), - orderBy$: new LiveData(undefined), - displayProperties$: new LiveData( - [] + displayPreference$: displayPreference$, + view$: displayPreference$.selector( + displayPreference => displayPreference.view ), - showDocIcon$: new LiveData(true), - showDocPreview$: new LiveData(true), - quickFavorite$: new LiveData(false), - quickSelect$: new LiveData(false), - quickSplit$: new LiveData(false), - quickTrash$: new LiveData(false), - quickTab$: new LiveData(false), - }) satisfies DocExplorerContextType; + groupBy$: displayPreference$.selector( + displayPreference => displayPreference.groupBy + ), + orderBy$: displayPreference$.selector( + displayPreference => displayPreference.orderBy + ), + displayProperties$: displayPreference$.selector( + displayPreference => displayPreference.displayProperties + ), + showDocIcon$: displayPreference$.selector( + displayPreference => displayPreference.showDocIcon + ), + showDocPreview$: displayPreference$.selector( + displayPreference => displayPreference.showDocPreview + ), + quickFavorite$: displayPreference$.selector( + displayPreference => displayPreference.quickFavorite + ), + quickSelect$: displayPreference$.selector( + displayPreference => displayPreference.quickSelect + ), + quickSplit$: displayPreference$.selector( + displayPreference => displayPreference.quickSplit + ), + quickTrash$: displayPreference$.selector( + displayPreference => displayPreference.quickTrash + ), + quickTab$: displayPreference$.selector( + displayPreference => displayPreference.quickTab + ), + } satisfies DocExplorerContextType; +}; diff --git a/packages/frontend/core/src/components/explorer/display-menu/index.tsx b/packages/frontend/core/src/components/explorer/display-menu/index.tsx index fc2b0f1d4..86df96800 100644 --- a/packages/frontend/core/src/components/explorer/display-menu/index.tsx +++ b/packages/frontend/core/src/components/explorer/display-menu/index.tsx @@ -11,63 +11,87 @@ import type { } from '@affine/core/modules/collection-rules/types'; import { useI18n } from '@affine/i18n'; import { ArrowDownSmallIcon } from '@blocksuite/icons/rc'; -import { useLiveData } from '@toeverything/infra'; import type React from 'react'; -import { useCallback, useContext } from 'react'; +import { useCallback } from 'react'; -import { DocExplorerContext } from '../context'; +import type { ExplorerDisplayPreference } from '../types'; import { GroupByList, GroupByName } from './group'; import { OrderByList, OrderByName } from './order'; import { DisplayProperties } from './properties'; import { QuickActionsConfig } from './quick-actions'; import * as styles from './styles.css'; -const ExplorerDisplayMenu = () => { +const ExplorerDisplayMenu = ({ + displayPreference, + onDisplayPreferenceChange, +}: { + displayPreference: ExplorerDisplayPreference; + onDisplayPreferenceChange: ( + displayPreference: ExplorerDisplayPreference + ) => void; +}) => { const t = useI18n(); - const explorerContextValue = useContext(DocExplorerContext); - const groupBy = useLiveData(explorerContextValue.groupBy$); - const orderBy = useLiveData(explorerContextValue.orderBy$); const handleGroupByChange = useCallback( (groupBy: GroupByParams) => { - explorerContextValue.groupBy$?.next(groupBy); + onDisplayPreferenceChange({ ...displayPreference, groupBy }); }, - [explorerContextValue.groupBy$] + [displayPreference, onDisplayPreferenceChange] ); const handleOrderByChange = useCallback( (orderBy: OrderByParams) => { - explorerContextValue.orderBy$?.next(orderBy); + onDisplayPreferenceChange({ ...displayPreference, orderBy }); }, - [explorerContextValue.orderBy$] + [displayPreference, onDisplayPreferenceChange] ); return (
} + items={ + + } >
{t['com.affine.explorer.display-menu.grouping']()} - {groupBy ? : null} + {displayPreference.groupBy ? ( + + ) : null}
} + items={ + + } >
{t['com.affine.explorer.display-menu.ordering']()} - {orderBy ? : null} + {displayPreference.orderBy ? ( + + ) : null}
- + - +
); }; @@ -76,14 +100,28 @@ export const ExplorerDisplayMenuButton = ({ style, className, menuProps, + displayPreference, + onDisplayPreferenceChange, }: { style?: React.CSSProperties; className?: string; menuProps?: Omit; + displayPreference: ExplorerDisplayPreference; + onDisplayPreferenceChange: ( + displayPreference: ExplorerDisplayPreference + ) => void; }) => { const t = useI18n(); return ( - } {...menuProps}> + + } + {...menuProps} + >