fix(core): optimize performance when editing doc title (#7328)

This commit is contained in:
EYHN 2024-06-26 07:37:25 +00:00
parent ad746b6376
commit 092c639b0a
No known key found for this signature in database
GPG Key ID: 46C9E26A75AB276C
16 changed files with 136 additions and 142 deletions

View File

@ -17,6 +17,7 @@ export class Doc extends Entity {
readonly meta$ = this.record.meta$;
readonly mode$ = this.record.mode$;
readonly title$ = this.record.title$;
readonly trash$ = this.record.trash$;
setMode(mode: DocMode) {
return this.record.setMode(mode);
@ -33,4 +34,12 @@ export class Doc extends Entity {
observeMode() {
return this.record.observeMode();
}
moveToTrash() {
return this.record.moveToTrash();
}
restoreFromTrash() {
return this.record.restoreFromTrash();
}
}

View File

@ -50,5 +50,14 @@ export class DocRecord extends Entity<{ id: string }> {
return this.docsStore.watchDocModeSetting(this.id);
}
moveToTrash() {
return this.setMeta({ trash: true });
}
restoreFromTrash() {
return this.setMeta({ trash: false });
}
title$ = this.meta$.map(meta => meta.title ?? '');
trash$ = this.meta$.map(meta => meta.trash ?? false);
}

View File

@ -12,17 +12,14 @@ import { CollectionOperationCell } from '../operation-cell';
import { CollectionListItemRenderer } from '../page-group';
import { ListTableHeader } from '../page-header';
import type { CollectionMeta, ItemListHandle, ListItem } from '../types';
import type { AllPageListConfig } from '../view';
import { VirtualizedList } from '../virtualized-list';
import { CollectionListHeader } from './collection-list-header';
const useCollectionOperationsRenderer = ({
info,
service,
config,
}: {
info: DeleteCollectionInfo;
config: AllPageListConfig;
service: CollectionService;
}) => {
const collectionOperationsRenderer = useCallback(
@ -32,11 +29,10 @@ const useCollectionOperationsRenderer = ({
info={info}
collection={collection}
service={service}
config={config}
/>
);
},
[config, info, service]
[info, service]
);
return collectionOperationsRenderer;
@ -48,11 +44,9 @@ export const VirtualizedCollectionList = ({
setHideHeaderCreateNewCollection,
node,
handleCreateCollection,
config,
}: {
collections: Collection[];
collectionMetas: CollectionMeta[];
config: AllPageListConfig;
node: ReactElement | null;
handleCreateCollection: () => void;
setHideHeaderCreateNewCollection: (hide: boolean) => void;
@ -69,7 +63,6 @@ export const VirtualizedCollectionList = ({
const collectionOperations = useCollectionOperationsRenderer({
info,
service: collectionService,
config,
});
const filteredSelectedCollectionIds = useMemo(() => {

View File

@ -28,7 +28,6 @@ import { CollectionService } from '../../../modules/collection';
import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
import { createTagFilter } from '../filter/utils';
import { createEmptyCollection } from '../use-collection-manager';
import type { AllPageListConfig } from '../view/edit-collection/edit-collection';
import {
useEditCollection,
useEditCollectionName,
@ -87,9 +86,7 @@ export const PageListHeader = () => {
export const CollectionPageListHeader = ({
collection,
workspaceId,
config,
}: {
config: AllPageListConfig;
collection: Collection;
workspaceId: string;
}) => {
@ -101,7 +98,7 @@ export const CollectionPageListHeader = ({
}, [jumpToCollections, workspaceId]);
const collectionService = useService(CollectionService);
const { node, open } = useEditCollection(config);
const { node, open } = useEditCollection();
const handleEdit = useAsyncCallback(async () => {
const ret = await open({ ...collection }, 'page');

View File

@ -18,7 +18,6 @@ import { PageListItemRenderer } from '../page-group';
import { ListTableHeader } from '../page-header';
import type { ItemListHandle, ListItem } from '../types';
import { useFilteredPageMetas } from '../use-filtered-page-metas';
import type { AllPageListConfig } from '../view/edit-collection/edit-collection';
import { VirtualizedList } from '../virtualized-list';
import {
CollectionPageListHeader,
@ -55,14 +54,12 @@ export const VirtualizedPageList = ({
tag,
collection,
filters,
config,
listItem,
setHideHeaderCreateNewPage,
}: {
tag?: Tag;
collection?: Collection;
filters?: Filter[];
config?: AllPageListConfig;
listItem?: DocMeta[];
setHideHeaderCreateNewPage?: (hide: boolean) => void;
}) => {
@ -116,17 +113,16 @@ export const VirtualizedPageList = ({
if (tag) {
return <TagPageListHeader workspaceId={currentWorkspace.id} tag={tag} />;
}
if (collection && config) {
if (collection) {
return (
<CollectionPageListHeader
workspaceId={currentWorkspace.id}
collection={collection}
config={config}
/>
);
}
return <PageListHeader />;
}, [collection, config, currentWorkspace.id, tag]);
}, [collection, currentWorkspace.id, tag]);
const { setTrashModal } = useTrashModalHelper(currentWorkspace.docCollection);

View File

@ -43,7 +43,6 @@ import { DisablePublicSharing, MoveToTrash } from './operation-menu-items';
import { CreateOrEditTag } from './tags/create-tag';
import type { TagMeta } from './types';
import { ColWrapper, stopPropagationWithoutPrevent } from './utils';
import type { AllPageListConfig } from './view';
import { useEditCollection, useEditCollectionName } from './view';
export interface PageOperationCellProps {
@ -282,27 +281,26 @@ export const TrashOperationCell = ({
export interface CollectionOperationCellProps {
collection: Collection;
info: DeleteCollectionInfo;
config: AllPageListConfig;
service: CollectionService;
}
export const CollectionOperationCell = ({
collection,
config,
service,
info,
}: CollectionOperationCellProps) => {
const t = useI18n();
const favAdapter = useService(FavoriteItemsAdapter);
const { createPage } = usePageHelper(config.docCollection);
const docCollection = useService(WorkspaceService).workspace.docCollection;
const { createPage } = usePageHelper(docCollection);
const { openConfirmModal } = useConfirmModal();
const favourite = useLiveData(
favAdapter.isFavorite$(collection.id, 'collection')
);
const { open: openEditCollectionModal, node: editModal } =
useEditCollection(config);
useEditCollection();
const { open: openEditCollectionNameModal, node: editNameModal } =
useEditCollectionName({

View File

@ -1,36 +1,10 @@
import { Button, FlexWrapper, Menu } from '@affine/component';
import type { Collection, Filter, PropertiesMeta } from '@affine/env/filter';
import type { Filter, PropertiesMeta } from '@affine/env/filter';
import { useI18n } from '@affine/i18n';
import { FilterIcon } from '@blocksuite/icons/rc';
import { CreateFilterMenu } from '../filter/vars';
import * as styles from './collection-list.css';
import { CollectionOperations } from './collection-operations';
import type { AllPageListConfig } from './edit-collection/edit-collection';
export const CollectionPageListOperationsMenu = ({
collection,
allPageListConfig,
}: {
collection: Collection;
allPageListConfig: AllPageListConfig;
}) => {
const t = useI18n();
return (
<FlexWrapper alignItems="center">
<CollectionOperations collection={collection} config={allPageListConfig}>
<Button
className={styles.filterMenuTrigger}
type="default"
icon={<FilterIcon />}
data-testid="create-first-filter"
>
{t['com.affine.filter']()}
</Button>
</CollectionOperations>
</FlexWrapper>
);
};
export const AllPageListOperationsMenu = ({
propertiesMeta,

View File

@ -21,7 +21,6 @@ import { useCallback, useMemo } from 'react';
import { CollectionService } from '../../../modules/collection';
import * as styles from './collection-operations.css';
import type { AllPageListConfig } from './index';
import {
useEditCollection,
useEditCollectionName,
@ -29,13 +28,11 @@ import {
export const CollectionOperations = ({
collection,
config,
openRenameModal,
onAddDocToCollection,
children,
}: PropsWithChildren<{
collection: Collection;
config: AllPageListConfig;
openRenameModal?: () => void;
onAddDocToCollection?: () => void;
}>) => {
@ -44,7 +41,7 @@ export const CollectionOperations = ({
const service = useService(CollectionService);
const workbench = useService(WorkbenchService).workbench;
const { open: openEditCollectionModal, node: editModal } =
useEditCollection(config);
useEditCollection();
const t = useI18n();
const { open: openEditCollectionNameModal, node: editNameModal } =
useEditCollectionName({

View File

@ -4,6 +4,7 @@ import {
RadioButton,
RadioButtonGroup,
} from '@affine/component';
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
import type { Collection } from '@affine/env/filter';
import { useI18n } from '@affine/i18n';
import type { DocCollection, DocMeta } from '@blocksuite/store';
@ -24,7 +25,6 @@ export interface EditCollectionModalProps {
mode?: EditCollectionMode;
onOpenChange: (open: boolean) => void;
onConfirm: (view: Collection) => void;
allPageListConfig: AllPageListConfig;
}
const contentOptions: DialogContentProps = {
@ -44,7 +44,6 @@ export const EditCollectionModal = ({
onOpenChange,
title,
mode,
allPageListConfig,
}: EditCollectionModalProps) => {
const t = useI18n();
const onConfirmOnCollection = useCallback(
@ -67,7 +66,7 @@ export const EditCollectionModal = ({
height="80%"
contentOptions={contentOptions}
>
{init ? (
{open && init ? (
<EditCollection
title={title}
onConfirmText={t['com.affine.editCollection.save']()}
@ -75,7 +74,6 @@ export const EditCollectionModal = ({
mode={mode}
onCancel={onCancel}
onConfirm={onConfirmOnCollection}
allPageListConfig={allPageListConfig}
/>
) : null}
</Modal>
@ -89,7 +87,6 @@ export interface EditCollectionProps {
mode?: EditCollectionMode;
onCancel: () => void;
onConfirm: (collection: Collection) => void;
allPageListConfig: AllPageListConfig;
}
export const EditCollection = ({
@ -98,9 +95,9 @@ export const EditCollection = ({
onCancel,
onConfirmText,
mode: initMode,
allPageListConfig,
}: EditCollectionProps) => {
const t = useI18n();
const config = useAllPageListConfig();
const [value, onChange] = useState<Collection>(init);
const [mode, setMode] = useState<'page' | 'rule'>(
initMode ?? (init.filterList.length === 0 ? 'page' : 'rule')
@ -182,11 +179,11 @@ export const EditCollection = ({
updateCollection={onChange}
switchMode={switchMode}
buttons={buttons}
allPageListConfig={allPageListConfig}
allPageListConfig={config}
></PagesMode>
) : (
<RulesMode
allPageListConfig={allPageListConfig}
allPageListConfig={config}
collection={value}
switchMode={switchMode}
reset={reset}

View File

@ -2,13 +2,10 @@ import type { Collection } from '@affine/env/filter';
import { useCallback, useState } from 'react';
import { CreateCollectionModal } from './create-collection';
import type {
AllPageListConfig,
EditCollectionMode,
} from './edit-collection/edit-collection';
import type { EditCollectionMode } from './edit-collection/edit-collection';
import { EditCollectionModal } from './edit-collection/edit-collection';
export const useEditCollection = (config: AllPageListConfig) => {
export const useEditCollection = () => {
const [data, setData] = useState<{
collection: Collection;
mode?: 'page' | 'rule';
@ -19,7 +16,6 @@ export const useEditCollection = (config: AllPageListConfig) => {
return {
node: data ? (
<EditCollectionModal
allPageListConfig={config}
init={data.collection}
open={!!data}
mode={data.mode}

View File

@ -60,25 +60,13 @@ export const CollectionSidebarNavItem = ({
dndId: DNDIdentifier;
className?: string;
}) => {
const pages = useBlockSuiteDocMeta(docCollection);
const [collapsed, setCollapsed] = useState(true);
const [open, setOpen] = useState(false);
const collectionService = useService(CollectionService);
const favAdapter = useService(FavoriteItemsAdapter);
const { createPage } = usePageHelper(docCollection);
const { openConfirmModal } = useConfirmModal();
const t = useI18n();
const favourites = useLiveData(favAdapter.favorites$);
const removeFromAllowList = useCallback(
(id: string) => {
collectionService.deletePageFromCollection(collection.id, id);
toast(t['com.affine.collection.removePage.success']());
},
[collection, collectionService, t]
);
const overlayPreview = useMemo(() => {
return (
<DragMenuItemOverlay icon={<ViewLayersIcon />} title={collection.name} />
@ -112,25 +100,6 @@ export const CollectionSidebarNavItem = ({
const isOver = over?.id === dndId && dragOverIntent === 'collection:add';
const config = useAllPageListConfig();
const allPagesMeta = useMemo(
() => Object.fromEntries(pages.map(v => [v.id, v])),
[pages]
);
const allowList = useMemo(
() => new Set(collection.allowList),
[collection.allowList]
);
const pagesToRender = pages.filter(meta => {
if (meta.trash) return false;
const pageData = {
meta,
publicMode: config.getPublicMode(meta.id),
favorite: favourites.some(fav => fav.id === meta.id),
};
return filterPage(collection, pageData);
});
const currentPath = useLiveData(
useService(WorkbenchService).workbench.location$.map(
location => location.pathname
@ -205,7 +174,6 @@ export const CollectionSidebarNavItem = ({
</IconButton>
<CollectionOperations
collection={collection}
config={config}
openRenameModal={handleOpen}
onAddDocToCollection={onConfirmAddDocToCollection}
>
@ -226,30 +194,90 @@ export const CollectionSidebarNavItem = ({
/>
</div>
}
collapsed={pagesToRender.length > 0 ? collapsed : undefined}
collapsed={collapsed}
>
<span>{collection.name}</span>
</SidebarMenuLinkItem>
<Collapsible.Content className={styles.collapsibleContent}>
<div className={styles.docsListContainer}>
{pagesToRender.map(page => {
return (
<Doc
parentId={dndId}
inAllowList={allowList.has(page.id)}
removeFromAllowList={removeFromAllowList}
allPageMeta={allPagesMeta}
doc={page}
key={page.id}
docCollection={docCollection}
/>
);
})}
</div>
{!collapsed && (
<CollectionSidebarNavItemContent
collection={collection}
docCollection={docCollection}
dndId={dndId}
/>
)}
</Collapsible.Content>
</Collapsible.Root>
);
};
export const CollectionSidebarNavItemContent = ({
collection,
docCollection,
dndId,
}: {
collection: Collection;
docCollection: DocCollection;
dndId: DNDIdentifier;
}) => {
const t = useI18n();
const pages = useBlockSuiteDocMeta(docCollection);
const favAdapter = useService(FavoriteItemsAdapter);
const collectionService = useService(CollectionService);
const config = useAllPageListConfig();
const favourites = useLiveData(favAdapter.favorites$);
const allowList = useMemo(
() => new Set(collection.allowList),
[collection.allowList]
);
const allPagesMeta = useMemo(
() => Object.fromEntries(pages.map(v => [v.id, v])),
[pages]
);
const removeFromAllowList = useCallback(
(id: string) => {
collectionService.deletePageFromCollection(collection.id, id);
toast(t['com.affine.collection.removePage.success']());
},
[collection, collectionService, t]
);
const filtered = pages.filter(meta => {
if (meta.trash) return false;
const pageData = {
meta,
publicMode: config.getPublicMode(meta.id),
favorite: favourites.some(fav => fav.id === meta.id),
};
return filterPage(collection, pageData);
});
return (
<div className={styles.docsListContainer}>
{filtered.length > 0 ? (
filtered.map(page => {
return (
<Doc
parentId={dndId}
inAllowList={allowList.has(page.id)}
removeFromAllowList={removeFromAllowList}
allPageMeta={allPagesMeta}
doc={page}
key={page.id}
docCollection={docCollection}
/>
);
})
) : (
<div className={styles.emptyCollection}>
{t['com.affine.collection.emptyCollection']()}
</div>
)}
</div>
);
};
export const CollectionsList = ({
docCollection: workspace,
onCreate,

View File

@ -156,3 +156,10 @@ export const docsListContainer = style({
flexDirection: 'column',
gap: 4,
});
export const emptyCollection = style({
fontSize: cssVar('fontSm'),
textAlign: 'left',
paddingLeft: '32px',
color: cssVar('black30'),
userSelect: 'none',
});

View File

@ -12,6 +12,9 @@ import { useCallback, useEffect, useMemo } from 'react';
import { usePageHelper } from '../../components/blocksuite/block-suite-page-list/utils';
/**
* @deprecated very poor performance
*/
export const useAllPageListConfig = () => {
const currentWorkspace = useService(WorkspaceService).workspace;
const shareDocService = useService(ShareDocsService);

View File

@ -3,12 +3,10 @@ import {
PreconditionStrategy,
registerAffineCommand,
} from '@affine/core/commands';
import { useDocMetaHelper } from '@affine/core/hooks/use-block-suite-page-meta';
import { FavoriteItemsAdapter } from '@affine/core/modules/properties';
import { mixpanel } from '@affine/core/utils';
import { WorkspaceFlavour } from '@affine/env/workspace';
import { useI18n } from '@affine/i18n';
import { assertExists } from '@blocksuite/global/utils';
import { EdgelessIcon, HistoryIcon, PageIcon } from '@blocksuite/icons/rc';
import {
DocService,
@ -31,15 +29,10 @@ export function useRegisterBlocksuiteEditorCommands() {
const t = useI18n();
const workspace = useService(WorkspaceService).workspace;
const docCollection = workspace.docCollection;
const { getDocMeta } = useDocMetaHelper(docCollection);
const currentPage = docCollection.getDoc(docId);
assertExists(currentPage);
const pageMeta = getDocMeta(docId);
assertExists(pageMeta);
const favAdapter = useService(FavoriteItemsAdapter);
const favorite = useLiveData(favAdapter.isFavorite$(docId, 'doc'));
const trash = pageMeta.trash ?? false;
const trash = useLiveData(doc.trash$);
const setPageHistoryModalState = useSetAtom(pageHistoryModalAtom);
@ -52,15 +45,18 @@ export function useRegisterBlocksuiteEditorCommands() {
const { restoreFromTrash, duplicate } =
useBlockSuiteMetaHelper(docCollection);
const exportHandler = useExportPage(currentPage);
const exportHandler = useExportPage(doc.blockSuiteDoc);
const { setTrashModal } = useTrashModalHelper(docCollection);
const onClickDelete = useCallback(() => {
setTrashModal({
open: true,
pageIds: [docId],
pageTitles: [pageMeta.title],
});
}, [docId, pageMeta.title, setTrashModal]);
const onClickDelete = useCallback(
(title: string) => {
setTrashModal({
open: true,
pageIds: [docId],
pageTitles: [title],
});
},
[docId, setTrashModal]
);
const isCloudWorkspace = workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD;
@ -213,7 +209,7 @@ export function useRegisterBlocksuiteEditorCommands() {
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['com.affine.moveToTrash.title'](),
run() {
onClickDelete();
onClickDelete(doc.title$.value);
},
})
);
@ -227,7 +223,7 @@ export function useRegisterBlocksuiteEditorCommands() {
icon: mode === 'page' ? <PageIcon /> : <EdgelessIcon />,
label: t['com.affine.cmdk.affine.editor.restore-from-trash'](),
run() {
restoreFromTrash(docId);
doc.restoreFromTrash();
},
})
);

View File

@ -5,7 +5,6 @@ import {
useEditCollectionName,
VirtualizedCollectionList,
} from '@affine/core/components/page-list';
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { useI18n } from '@affine/i18n';
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
@ -25,7 +24,6 @@ export const AllCollection = () => {
const collectionService = useService(CollectionService);
const collections = useLiveData(collectionService.collections$);
const config = useAllPageListConfig();
const collectionMetas = useMemo(() => {
const collectionsList: CollectionMeta[] = collections.map(collection => {
@ -71,7 +69,6 @@ export const AllCollection = () => {
collectionMetas={collectionMetas}
setHideHeaderCreateNewCollection={setHideHeaderCreateNew}
node={node}
config={config}
handleCreateCollection={handleCreateCollection}
/>
) : (

View File

@ -4,7 +4,6 @@ import {
useEditCollection,
VirtualizedPageList,
} from '@affine/core/components/page-list';
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
import { CollectionService } from '@affine/core/modules/collection';
import type { Collection } from '@affine/env/filter';
@ -30,8 +29,7 @@ export const CollectionDetail = ({
}: {
collection: Collection;
}) => {
const config = useAllPageListConfig();
const { node, open } = useEditCollection(useAllPageListConfig());
const { node, open } = useEditCollection();
const collectionService = useService(CollectionService);
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
@ -51,7 +49,6 @@ export const CollectionDetail = ({
<ViewBodyIsland>
<VirtualizedPageList
collection={collection}
config={config}
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
/>
</ViewBodyIsland>
@ -104,7 +101,7 @@ export const Component = function CollectionPage() {
const Placeholder = ({ collection }: { collection: Collection }) => {
const workspace = useService(WorkspaceService).workspace;
const collectionService = useService(CollectionService);
const { node, open } = useEditCollection(useAllPageListConfig());
const { node, open } = useEditCollection();
const { jumpToCollections } = useNavigateHelper();
const openPageEdit = useAsyncCallback(async () => {
const ret = await open({ ...collection }, 'page');