feat(core): save all docs options by mode (#12654)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Refactor**
  - Improved state management for display preferences, view mode, and selected collection in the "All Docs" page, making the experience more modular and consistent, especially when using multiple views.
  - Updated the header component to handle view changes more directly, allowing smoother toggling between different document views.

- **New Features**
  - Enhanced support for independent display settings in split view or multiple "All Docs" instances.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
EYHN
2025-05-30 04:42:27 +00:00
parent a35e1b1882
commit 5c96566dd8
2 changed files with 196 additions and 55 deletions
@@ -28,11 +28,15 @@ const menuProps: Partial<MenuProps> = {
export const AllDocsHeader = ({
displayPreference,
onDisplayPreferenceChange,
view,
onViewChange,
}: {
displayPreference: ExplorerDisplayPreference;
onDisplayPreferenceChange: (
displayPreference: ExplorerDisplayPreference
) => void;
view: DocListItemView;
onViewChange: (view: DocListItemView) => void;
}) => {
const t = useI18n();
const workspaceService = useService(WorkspaceService);
@@ -77,22 +81,12 @@ export const AllDocsHeader = ({
});
}, [workspaceDialogService, handleOpenDocs]);
const handleViewChange = useCallback(
(view: DocListItemView) => {
onDisplayPreferenceChange({ ...displayPreference, view });
},
[displayPreference, onDisplayPreferenceChange]
);
return (
<div className={styles.header}>
<ExplorerNavigation active="docs" />
<div className={styles.actions}>
<ViewToggle
view={displayPreference.view ?? 'list'}
onViewChange={handleViewChange}
/>
<ViewToggle view={view} onViewChange={onViewChange} />
<ExplorerDisplayMenuButton
menuProps={menuProps}
displayPreference={displayPreference}
@@ -15,7 +15,7 @@ import type { FilterParams } from '@affine/core/modules/collection-rules/types';
import { WorkspaceLocalState } from '@affine/core/modules/workspace';
import { useI18n } from '@affine/i18n';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import {
ViewBody,
@@ -29,22 +29,89 @@ import { AllDocsHeader } from './all-page-header';
import { MigrationAllDocsDataNotification } from './migration-data';
import { PinnedCollections } from './pinned-collections';
interface AllDocsStateSave extends ExplorerDisplayPreference {
selectedCollectionId: string | null;
}
const DefaultDisplayPreference: {
[key in ViewMode]: ExplorerDisplayPreference;
} = {
grid: {
view: 'grid',
displayProperties: [
'system:createdAt',
'system:updatedAt',
'system:createdBy',
'system:tags',
],
orderBy: {
type: 'system',
key: 'updatedAt',
desc: true,
},
groupBy: undefined,
showDocIcon: true,
showDocPreview: true,
quickFavorite: true,
showDragHandle: true,
showMoreOperation: true,
},
masonry: {
view: 'masonry',
displayProperties: [
'system:createdAt',
'system:updatedAt',
'system:createdBy',
'system:tags',
],
orderBy: {
type: 'system',
key: 'updatedAt',
desc: true,
},
groupBy: undefined,
showDocIcon: true,
showDocPreview: true,
quickFavorite: true,
showDragHandle: true,
showMoreOperation: true,
},
list: {
view: 'list',
displayProperties: [
'system:createdAt',
'system:updatedAt',
'system:createdBy',
'system:tags',
],
orderBy: {
type: 'system',
key: 'updatedAt',
desc: true,
},
groupBy: {
type: 'system',
key: 'updatedAt',
},
showDocIcon: true,
showDocPreview: true,
quickFavorite: true,
showDragHandle: true,
showMoreOperation: true,
},
};
type ViewMode = NonNullable<ExplorerDisplayPreference['view']>;
export const AllPage = () => {
const t = useI18n();
const collectionService = useService(CollectionService);
const pinnedCollectionService = useService(PinnedCollectionService);
const workspaceLocalState = useService(WorkspaceLocalState);
const [initialState] = useState(() => {
return workspaceLocalState.get<AllDocsStateSave>(
'allDocsDisplayPreference'
);
});
const {
viewMode,
setViewMode,
selectedCollectionId,
setSelectedCollectionId,
displayPreference,
setDisplayPreference,
} = useAllDocsOptions();
const isCollectionDataReady = useLiveData(
collectionService.collectionDataReady$
@@ -58,9 +125,6 @@ export const AllPage = () => {
pinnedCollectionService.pinnedCollections$
);
const [selectedCollectionId, setSelectedCollectionId] = useState<
string | null
>(initialState?.selectedCollectionId ?? null);
const selectedCollection = useLiveData(
selectedCollectionId
? collectionService.collection$(selectedCollectionId)
@@ -76,14 +140,24 @@ export const AllPage = () => {
) {
setSelectedCollectionId(null);
}
}, [isPinnedCollectionDataReady, pinnedCollections, selectedCollectionId]);
}, [
isPinnedCollectionDataReady,
pinnedCollections,
selectedCollectionId,
setSelectedCollectionId,
]);
useEffect(() => {
// if selected collection is not found, set selected collection id to null
if (!selectedCollection && selectedCollectionId && isCollectionDataReady) {
setSelectedCollectionId(null);
}
}, [isCollectionDataReady, selectedCollection, selectedCollectionId]);
}, [
isCollectionDataReady,
selectedCollection,
selectedCollectionId,
setSelectedCollectionId,
]);
const selectedCollectionInfo = useLiveData(
selectedCollection ? selectedCollection.info$ : null
@@ -94,25 +168,15 @@ export const AllPage = () => {
useState<FilterParams | null>(null);
const [explorerContextValue] = useState(() =>
createDocExplorerContext(initialState)
createDocExplorerContext(displayPreference)
);
const groupBy = useLiveData(explorerContextValue.groupBy$);
const orderBy = useLiveData(explorerContextValue.orderBy$);
const displayPreference = useLiveData(
explorerContextValue.displayPreference$
);
const allDocsStateSave = useMemo(() => {
return {
...displayPreference,
selectedCollectionId,
};
}, [displayPreference, selectedCollectionId]);
useEffect(() => {
workspaceLocalState.set('allDocsDisplayPreference', allDocsStateSave);
}, [allDocsStateSave, workspaceLocalState]);
explorerContextValue.displayPreference$.next(displayPreference);
}, [displayPreference, explorerContextValue]);
const groupBy = displayPreference.groupBy;
const orderBy = displayPreference.orderBy;
const { openPromptModal } = usePromptModal();
@@ -211,15 +275,18 @@ export const AllPage = () => {
setTempFilters(filters);
}, []);
const handleSelectCollection = useCallback((collectionId: string) => {
setSelectedCollectionId(collectionId);
setTempFilters(null);
}, []);
const handleSelectCollection = useCallback(
(collectionId: string) => {
setSelectedCollectionId(collectionId);
setTempFilters(null);
},
[setSelectedCollectionId]
);
const handleSelectAll = useCallback(() => {
setSelectedCollectionId(null);
setTempFilters(null);
}, []);
}, [setSelectedCollectionId]);
const handleSaveFilters = useCallback(() => {
if (selectedCollectionId) {
@@ -263,21 +330,25 @@ export const AllPage = () => {
openPromptModal,
pinnedCollectionService,
selectedCollectionId,
setSelectedCollectionId,
t,
tempFilters,
]);
const handleNewTempFilter = useCallback((params: FilterParams) => {
setSelectedCollectionId(null);
setTempFilters([]);
setTempFiltersInitial(params);
}, []);
const handleNewTempFilter = useCallback(
(params: FilterParams) => {
setSelectedCollectionId(null);
setTempFilters([]);
setTempFiltersInitial(params);
},
[setSelectedCollectionId]
);
const handleDisplayPreferenceChange = useCallback(
(displayPreference: ExplorerDisplayPreference) => {
explorerContextValue.displayPreference$.next(displayPreference);
setDisplayPreference(displayPreference);
},
[explorerContextValue]
[setDisplayPreference]
);
return (
@@ -288,6 +359,8 @@ export const AllPage = () => {
<AllDocsHeader
displayPreference={displayPreference}
onDisplayPreferenceChange={handleDisplayPreferenceChange}
view={viewMode}
onViewChange={setViewMode}
/>
</ViewHeader>
<ViewBody>
@@ -338,3 +411,77 @@ export const AllPage = () => {
export const Component = () => {
return <AllPage />;
};
/**
* Since split view allows users to open multiple all docs simultaneously, each with its own state,
* we only read the stored state once during useState initialization to maintain independent states.
*/
const useAllDocsOptions = () => {
const workspaceLocalState = useService(WorkspaceLocalState);
const readSavedViewMode = useCallback(() => {
return workspaceLocalState.get<ViewMode>('allDocsMode') ?? 'list';
}, [workspaceLocalState]);
const readSavedDisplayPreference = useCallback(
(mode: ViewMode) => {
const saved = workspaceLocalState.get<ExplorerDisplayPreference>(
'allDocsDisplayPreference:' + mode
);
return {
...DefaultDisplayPreference[mode],
...saved,
view: mode,
};
},
[workspaceLocalState]
);
const [viewMode, setViewMode] = useState(readSavedViewMode);
const [displayPreference, setDisplayPreference] =
useState<ExplorerDisplayPreference>(() =>
readSavedDisplayPreference(viewMode)
);
const [selectedCollectionId, setSelectedCollectionId] = useState(
() =>
workspaceLocalState.get<string | null>('allDocsSelectedCollectionId') ??
null
);
const handleViewModeChange = useCallback(
(mode: ViewMode) => {
workspaceLocalState.set('allDocsMode', mode);
setViewMode(mode);
setDisplayPreference(readSavedDisplayPreference(mode));
},
[workspaceLocalState, readSavedDisplayPreference]
);
const handleDisplayPreferenceChange = useCallback(
(displayPreference: ExplorerDisplayPreference) => {
workspaceLocalState.set(
'allDocsDisplayPreference:' + viewMode,
displayPreference
);
setDisplayPreference(displayPreference);
},
[viewMode, workspaceLocalState]
);
const handleSelectedCollectionIdChange = useCallback(
(collectionId: string | null) => {
workspaceLocalState.set('allDocsSelectedCollectionId', collectionId);
setSelectedCollectionId(collectionId);
},
[workspaceLocalState]
);
return {
viewMode,
setViewMode: handleViewModeChange,
displayPreference,
setDisplayPreference: handleDisplayPreferenceChange,
selectedCollectionId,
setSelectedCollectionId: handleSelectedCollectionIdChange,
};
};