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:
@@ -28,11 +28,15 @@ const menuProps: Partial<MenuProps> = {
|
|||||||
export const AllDocsHeader = ({
|
export const AllDocsHeader = ({
|
||||||
displayPreference,
|
displayPreference,
|
||||||
onDisplayPreferenceChange,
|
onDisplayPreferenceChange,
|
||||||
|
view,
|
||||||
|
onViewChange,
|
||||||
}: {
|
}: {
|
||||||
displayPreference: ExplorerDisplayPreference;
|
displayPreference: ExplorerDisplayPreference;
|
||||||
onDisplayPreferenceChange: (
|
onDisplayPreferenceChange: (
|
||||||
displayPreference: ExplorerDisplayPreference
|
displayPreference: ExplorerDisplayPreference
|
||||||
) => void;
|
) => void;
|
||||||
|
view: DocListItemView;
|
||||||
|
onViewChange: (view: DocListItemView) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
const workspaceService = useService(WorkspaceService);
|
const workspaceService = useService(WorkspaceService);
|
||||||
@@ -77,22 +81,12 @@ export const AllDocsHeader = ({
|
|||||||
});
|
});
|
||||||
}, [workspaceDialogService, handleOpenDocs]);
|
}, [workspaceDialogService, handleOpenDocs]);
|
||||||
|
|
||||||
const handleViewChange = useCallback(
|
|
||||||
(view: DocListItemView) => {
|
|
||||||
onDisplayPreferenceChange({ ...displayPreference, view });
|
|
||||||
},
|
|
||||||
[displayPreference, onDisplayPreferenceChange]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<ExplorerNavigation active="docs" />
|
<ExplorerNavigation active="docs" />
|
||||||
|
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<ViewToggle
|
<ViewToggle view={view} onViewChange={onViewChange} />
|
||||||
view={displayPreference.view ?? 'list'}
|
|
||||||
onViewChange={handleViewChange}
|
|
||||||
/>
|
|
||||||
<ExplorerDisplayMenuButton
|
<ExplorerDisplayMenuButton
|
||||||
menuProps={menuProps}
|
menuProps={menuProps}
|
||||||
displayPreference={displayPreference}
|
displayPreference={displayPreference}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import type { FilterParams } from '@affine/core/modules/collection-rules/types';
|
|||||||
import { WorkspaceLocalState } from '@affine/core/modules/workspace';
|
import { WorkspaceLocalState } from '@affine/core/modules/workspace';
|
||||||
import { useI18n } from '@affine/i18n';
|
import { useI18n } from '@affine/i18n';
|
||||||
import { useLiveData, useService } from '@toeverything/infra';
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ViewBody,
|
ViewBody,
|
||||||
@@ -29,22 +29,89 @@ import { AllDocsHeader } from './all-page-header';
|
|||||||
import { MigrationAllDocsDataNotification } from './migration-data';
|
import { MigrationAllDocsDataNotification } from './migration-data';
|
||||||
import { PinnedCollections } from './pinned-collections';
|
import { PinnedCollections } from './pinned-collections';
|
||||||
|
|
||||||
interface AllDocsStateSave extends ExplorerDisplayPreference {
|
const DefaultDisplayPreference: {
|
||||||
selectedCollectionId: string | null;
|
[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 = () => {
|
export const AllPage = () => {
|
||||||
const t = useI18n();
|
const t = useI18n();
|
||||||
|
|
||||||
const collectionService = useService(CollectionService);
|
const collectionService = useService(CollectionService);
|
||||||
const pinnedCollectionService = useService(PinnedCollectionService);
|
const pinnedCollectionService = useService(PinnedCollectionService);
|
||||||
const workspaceLocalState = useService(WorkspaceLocalState);
|
const {
|
||||||
|
viewMode,
|
||||||
const [initialState] = useState(() => {
|
setViewMode,
|
||||||
return workspaceLocalState.get<AllDocsStateSave>(
|
selectedCollectionId,
|
||||||
'allDocsDisplayPreference'
|
setSelectedCollectionId,
|
||||||
);
|
displayPreference,
|
||||||
});
|
setDisplayPreference,
|
||||||
|
} = useAllDocsOptions();
|
||||||
|
|
||||||
const isCollectionDataReady = useLiveData(
|
const isCollectionDataReady = useLiveData(
|
||||||
collectionService.collectionDataReady$
|
collectionService.collectionDataReady$
|
||||||
@@ -58,9 +125,6 @@ export const AllPage = () => {
|
|||||||
pinnedCollectionService.pinnedCollections$
|
pinnedCollectionService.pinnedCollections$
|
||||||
);
|
);
|
||||||
|
|
||||||
const [selectedCollectionId, setSelectedCollectionId] = useState<
|
|
||||||
string | null
|
|
||||||
>(initialState?.selectedCollectionId ?? null);
|
|
||||||
const selectedCollection = useLiveData(
|
const selectedCollection = useLiveData(
|
||||||
selectedCollectionId
|
selectedCollectionId
|
||||||
? collectionService.collection$(selectedCollectionId)
|
? collectionService.collection$(selectedCollectionId)
|
||||||
@@ -76,14 +140,24 @@ export const AllPage = () => {
|
|||||||
) {
|
) {
|
||||||
setSelectedCollectionId(null);
|
setSelectedCollectionId(null);
|
||||||
}
|
}
|
||||||
}, [isPinnedCollectionDataReady, pinnedCollections, selectedCollectionId]);
|
}, [
|
||||||
|
isPinnedCollectionDataReady,
|
||||||
|
pinnedCollections,
|
||||||
|
selectedCollectionId,
|
||||||
|
setSelectedCollectionId,
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// if selected collection is not found, set selected collection id to null
|
// if selected collection is not found, set selected collection id to null
|
||||||
if (!selectedCollection && selectedCollectionId && isCollectionDataReady) {
|
if (!selectedCollection && selectedCollectionId && isCollectionDataReady) {
|
||||||
setSelectedCollectionId(null);
|
setSelectedCollectionId(null);
|
||||||
}
|
}
|
||||||
}, [isCollectionDataReady, selectedCollection, selectedCollectionId]);
|
}, [
|
||||||
|
isCollectionDataReady,
|
||||||
|
selectedCollection,
|
||||||
|
selectedCollectionId,
|
||||||
|
setSelectedCollectionId,
|
||||||
|
]);
|
||||||
|
|
||||||
const selectedCollectionInfo = useLiveData(
|
const selectedCollectionInfo = useLiveData(
|
||||||
selectedCollection ? selectedCollection.info$ : null
|
selectedCollection ? selectedCollection.info$ : null
|
||||||
@@ -94,25 +168,15 @@ export const AllPage = () => {
|
|||||||
useState<FilterParams | null>(null);
|
useState<FilterParams | null>(null);
|
||||||
|
|
||||||
const [explorerContextValue] = useState(() =>
|
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(() => {
|
useEffect(() => {
|
||||||
workspaceLocalState.set('allDocsDisplayPreference', allDocsStateSave);
|
explorerContextValue.displayPreference$.next(displayPreference);
|
||||||
}, [allDocsStateSave, workspaceLocalState]);
|
}, [displayPreference, explorerContextValue]);
|
||||||
|
|
||||||
|
const groupBy = displayPreference.groupBy;
|
||||||
|
const orderBy = displayPreference.orderBy;
|
||||||
|
|
||||||
const { openPromptModal } = usePromptModal();
|
const { openPromptModal } = usePromptModal();
|
||||||
|
|
||||||
@@ -211,15 +275,18 @@ export const AllPage = () => {
|
|||||||
setTempFilters(filters);
|
setTempFilters(filters);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSelectCollection = useCallback((collectionId: string) => {
|
const handleSelectCollection = useCallback(
|
||||||
|
(collectionId: string) => {
|
||||||
setSelectedCollectionId(collectionId);
|
setSelectedCollectionId(collectionId);
|
||||||
setTempFilters(null);
|
setTempFilters(null);
|
||||||
}, []);
|
},
|
||||||
|
[setSelectedCollectionId]
|
||||||
|
);
|
||||||
|
|
||||||
const handleSelectAll = useCallback(() => {
|
const handleSelectAll = useCallback(() => {
|
||||||
setSelectedCollectionId(null);
|
setSelectedCollectionId(null);
|
||||||
setTempFilters(null);
|
setTempFilters(null);
|
||||||
}, []);
|
}, [setSelectedCollectionId]);
|
||||||
|
|
||||||
const handleSaveFilters = useCallback(() => {
|
const handleSaveFilters = useCallback(() => {
|
||||||
if (selectedCollectionId) {
|
if (selectedCollectionId) {
|
||||||
@@ -263,21 +330,25 @@ export const AllPage = () => {
|
|||||||
openPromptModal,
|
openPromptModal,
|
||||||
pinnedCollectionService,
|
pinnedCollectionService,
|
||||||
selectedCollectionId,
|
selectedCollectionId,
|
||||||
|
setSelectedCollectionId,
|
||||||
t,
|
t,
|
||||||
tempFilters,
|
tempFilters,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleNewTempFilter = useCallback((params: FilterParams) => {
|
const handleNewTempFilter = useCallback(
|
||||||
|
(params: FilterParams) => {
|
||||||
setSelectedCollectionId(null);
|
setSelectedCollectionId(null);
|
||||||
setTempFilters([]);
|
setTempFilters([]);
|
||||||
setTempFiltersInitial(params);
|
setTempFiltersInitial(params);
|
||||||
}, []);
|
},
|
||||||
|
[setSelectedCollectionId]
|
||||||
|
);
|
||||||
|
|
||||||
const handleDisplayPreferenceChange = useCallback(
|
const handleDisplayPreferenceChange = useCallback(
|
||||||
(displayPreference: ExplorerDisplayPreference) => {
|
(displayPreference: ExplorerDisplayPreference) => {
|
||||||
explorerContextValue.displayPreference$.next(displayPreference);
|
setDisplayPreference(displayPreference);
|
||||||
},
|
},
|
||||||
[explorerContextValue]
|
[setDisplayPreference]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -288,6 +359,8 @@ export const AllPage = () => {
|
|||||||
<AllDocsHeader
|
<AllDocsHeader
|
||||||
displayPreference={displayPreference}
|
displayPreference={displayPreference}
|
||||||
onDisplayPreferenceChange={handleDisplayPreferenceChange}
|
onDisplayPreferenceChange={handleDisplayPreferenceChange}
|
||||||
|
view={viewMode}
|
||||||
|
onViewChange={setViewMode}
|
||||||
/>
|
/>
|
||||||
</ViewHeader>
|
</ViewHeader>
|
||||||
<ViewBody>
|
<ViewBody>
|
||||||
@@ -338,3 +411,77 @@ export const AllPage = () => {
|
|||||||
export const Component = () => {
|
export const Component = () => {
|
||||||
return <AllPage />;
|
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,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user