refactor(core): remove collection atom (#5832)

This commit is contained in:
EYHN
2024-02-27 03:50:56 +00:00
parent ad9b0303c4
commit 5cd488fe1d
41 changed files with 644 additions and 962 deletions
@@ -0,0 +1,49 @@
import { IconButton } from '@affine/component';
import { Header } from '@affine/core/components/pure/header';
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
import { PlusIcon } from '@blocksuite/icons';
import clsx from 'clsx';
import { useMemo } from 'react';
import * as styles from '../all-page/all-page.css';
export const AllCollectionHeader = ({
showCreateNew,
onCreateCollection,
}: {
showCreateNew: boolean;
onCreateCollection?: () => void;
}) => {
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
const renderRightItem = useMemo(() => {
return (
<IconButton
type="default"
icon={<PlusIcon fontSize={16} />}
onClick={onCreateCollection}
className={clsx(
styles.headerCreateNewButton,
styles.headerCreateNewCollectionIconButton,
!showCreateNew && styles.headerCreateNewButtonHidden
)}
/>
);
}, [onCreateCollection, showCreateNew]);
return (
<Header
right={
<div
className={styles.headerRightWindows}
data-is-windows-desktop={isWindowsDesktop}
>
{renderRightItem}
{isWindowsDesktop ? <WindowsAppControls /> : null}
</div>
}
center={<WorkspaceModeFilterTab activeFilter={'collections'} />}
/>
);
};
@@ -0,0 +1,93 @@
import { HubIsland } from '@affine/core/components/affine/hub-island';
import {
CollectionListHeader,
type CollectionMeta,
createEmptyCollection,
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 { useAFFiNEI18N } from '@affine/i18n/hooks';
import { useService } from '@toeverything/infra';
import { useLiveData } from '@toeverything/infra';
import { Workspace } from '@toeverything/infra';
import { nanoid } from 'nanoid';
import { useCallback, useMemo, useState } from 'react';
import { CollectionService } from '../../../modules/collection';
import * as styles from '../all-page/all-page.css';
import { EmptyCollectionList } from '../page-list-empty';
import { AllCollectionHeader } from './header';
export const AllCollection = () => {
const t = useAFFiNEI18N();
const currentWorkspace = useService(Workspace);
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
const collectionService = useService(CollectionService);
const collections = useLiveData(collectionService.collections);
const config = useAllPageListConfig();
const collectionMetas = useMemo(() => {
const collectionsList: CollectionMeta[] = collections.map(collection => {
return {
...collection,
title: collection.name,
};
});
return collectionsList;
}, [collections]);
const navigateHelper = useNavigateHelper();
const { open, node } = useEditCollectionName({
title: t['com.affine.editCollection.createCollection'](),
showTips: true,
});
const handleCreateCollection = useCallback(() => {
open('')
.then(name => {
const id = nanoid();
collectionService.addCollection(createEmptyCollection(id, { name }));
navigateHelper.jumpToCollection(currentWorkspace.id, id);
})
.catch(err => {
console.error(err);
});
}, [collectionService, currentWorkspace, navigateHelper, open]);
return (
<div className={styles.root}>
<AllCollectionHeader
showCreateNew={!hideHeaderCreateNew}
onCreateCollection={handleCreateCollection}
/>
{collectionMetas.length > 0 ? (
<VirtualizedCollectionList
collections={collections}
collectionMetas={collectionMetas}
setHideHeaderCreateNewCollection={setHideHeaderCreateNew}
node={node}
config={config}
handleCreateCollection={handleCreateCollection}
/>
) : (
<EmptyCollectionList
heading={
<CollectionListHeader
node={node}
onCreate={handleCreateCollection}
/>
}
/>
)}
<HubIsland />
</div>
);
};
export const Component = () => {
return <AllCollection />;
};
@@ -8,36 +8,31 @@ import { filterContainerStyle } from '../../../components/filter-container.css';
import {
FilterList,
SaveAsCollectionButton,
useCollectionManager,
} from '../../../components/page-list';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
export const FilterContainer = () => {
export const FilterContainer = ({
filters,
onChangeFilters,
}: {
filters: Filter[];
onChangeFilters: (filters: Filter[]) => void;
}) => {
const currentWorkspace = useService(Workspace);
const navigateHelper = useNavigateHelper();
const setting = useCollectionManager(useService(CollectionService));
const collectionService = useService(CollectionService);
const saveToCollection = useCallback(
(collection: Collection) => {
setting.createCollection({
collectionService.addCollection({
...collection,
filterList: setting.currentCollection.filterList,
filterList: filters,
});
navigateHelper.jumpToCollection(currentWorkspace.id, collection.id);
},
[setting, navigateHelper, currentWorkspace.id]
[collectionService, filters, navigateHelper, currentWorkspace.id]
);
const onFilterChange = useCallback(
(filterList: Filter[]) => {
setting.updateCollection({
...setting.currentCollection,
filterList,
});
},
[setting]
);
if (!setting.isDefault || !setting.currentCollection.filterList.length) {
if (!filters.length) {
return null;
}
@@ -46,12 +41,12 @@ export const FilterContainer = () => {
<div style={{ flex: 1 }}>
<FilterList
propertiesMeta={currentWorkspace.blockSuiteWorkspace.meta.properties}
value={setting.currentCollection.filterList}
onChange={onFilterChange}
value={filters}
onChange={onChangeFilters}
/>
</div>
<div>
{setting.currentCollection.filterList.length > 0 ? (
{filters.length > 0 ? (
<SaveAsCollectionButton onConfirm={saveToCollection} />
) : null}
</div>
@@ -1,69 +1,33 @@
import { IconButton } from '@affine/component';
import type { AllPageFilterOption } from '@affine/core/atoms';
import {
CollectionList,
AllPageListOperationsMenu,
PageListNewPageButton,
useCollectionManager,
} from '@affine/core/components/page-list';
import { Header } from '@affine/core/components/pure/header';
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
import { useDeleteCollectionInfo } from '@affine/core/hooks/affine/use-delete-collection-info';
import type { Filter } from '@affine/env/filter';
import { PlusIcon } from '@blocksuite/icons';
import type { Workspace } from '@blocksuite/store';
import { useService } from '@toeverything/infra/di';
import { useService } from '@toeverything/infra';
import { Workspace } from '@toeverything/infra';
import clsx from 'clsx';
import { useMemo } from 'react';
import { CollectionService } from '../../../modules/collection';
import * as styles from './all-page.css';
import { FilterContainer } from './all-page-filter';
export const AllPageHeader = ({
workspace,
showCreateNew,
isDefaultFilter,
activeFilter,
onCreateCollection,
filters,
onChangeFilters,
}: {
workspace: Workspace;
showCreateNew: boolean;
isDefaultFilter: boolean;
activeFilter: AllPageFilterOption;
onCreateCollection?: () => void;
filters: Filter[];
onChangeFilters: (filters: Filter[]) => void;
}) => {
const setting = useCollectionManager(useService(CollectionService));
const config = useAllPageListConfig();
const userInfo = useDeleteCollectionInfo();
const workspace = useService(Workspace);
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
const disableFilterButton = useMemo(() => {
return activeFilter !== 'docs' && isDefaultFilter;
}, [activeFilter, isDefaultFilter]);
const renderRightItem = useMemo(() => {
if (activeFilter === 'tags') {
return null;
}
if (
activeFilter === 'collections' &&
isDefaultFilter &&
onCreateCollection
) {
return (
<IconButton
type="default"
icon={<PlusIcon fontSize={16} />}
onClick={onCreateCollection}
className={clsx(
styles.headerCreateNewButton,
styles.headerCreateNewCollectionIconButton,
!showCreateNew && styles.headerCreateNewButtonHidden
)}
/>
);
}
return (
<PageListNewPageButton
size="small"
@@ -75,18 +39,16 @@ export const AllPageHeader = ({
<PlusIcon />
</PageListNewPageButton>
);
}, [activeFilter, isDefaultFilter, onCreateCollection, showCreateNew]);
}, [showCreateNew]);
return (
<>
<Header
left={
<CollectionList
userInfo={userInfo}
allPageListConfig={config}
setting={setting}
propertiesMeta={workspace.meta.properties}
disable={disableFilterButton}
<AllPageListOperationsMenu
filterList={filters}
onChangeFilterList={onChangeFilters}
propertiesMeta={workspace.blockSuiteWorkspace.meta.properties}
/>
}
right={
@@ -98,14 +60,9 @@ export const AllPageHeader = ({
{isWindowsDesktop ? <WindowsAppControls /> : null}
</div>
}
center={
<WorkspaceModeFilterTab
workspaceId={workspace.id}
activeFilter={activeFilter}
/>
}
center={<WorkspaceModeFilterTab activeFilter={'docs'} />}
/>
<FilterContainer />
<FilterContainer filters={filters} onChangeFilters={onChangeFilters} />
</>
);
};
@@ -1,211 +1,50 @@
import type { AllPageFilterOption } from '@affine/core/atoms';
import { HubIsland } from '@affine/core/components/affine/hub-island';
import {
CollectionListHeader,
type CollectionMeta,
createEmptyCollection,
currentCollectionAtom,
PageListHeader,
useCollectionManager,
useEditCollectionName,
useFilteredPageMetas,
useTagMetas,
VirtualizedCollectionList,
VirtualizedPageList,
} from '@affine/core/components/page-list';
import {
TagListHeader,
VirtualizedTagList,
} from '@affine/core/components/page-list/tags';
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
import { performanceRenderLogger } from '@affine/core/shared';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { Filter } from '@affine/env/filter';
import { useService } from '@toeverything/infra';
import { useLiveData } from '@toeverything/infra';
import { Workspace } from '@toeverything/infra';
import { useSetAtom } from 'jotai';
import { nanoid } from 'nanoid';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { NIL } from 'uuid';
import { useEffect, useState } from 'react';
import { CollectionService } from '../../../modules/collection';
import {
EmptyCollectionList,
EmptyPageList,
EmptyTagList,
} from '../page-list-empty';
import { EmptyPageList } from '../page-list-empty';
import * as styles from './all-page.css';
import { AllPageHeader } from './all-page-header';
// even though it is called all page, it is also being used for collection route as well
export const AllPage = ({
activeFilter,
}: {
activeFilter: AllPageFilterOption;
}) => {
const t = useAFFiNEI18N();
const params = useParams();
export const AllPage = () => {
const currentWorkspace = useService(Workspace);
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
const collectionService = useService(CollectionService);
const collections = useLiveData(collectionService.collections);
const setting = useCollectionManager(collectionService);
const config = useAllPageListConfig();
const { tags, tagMetas, filterPageMetaByTag, deleteTags } = useTagMetas(
currentWorkspace.blockSuiteWorkspace,
pageMetas
);
const filteredPageMetas = useFilteredPageMetas(
'all',
pageMetas,
currentWorkspace
);
const tagPageMetas = useMemo(() => {
if (params.tagId) {
return filterPageMetaByTag(params.tagId);
}
return [];
}, [filterPageMetaByTag, params.tagId]);
const collectionMetas = useMemo(() => {
const collectionsList: CollectionMeta[] = collections.map(collection => {
return {
...collection,
title: collection.name,
};
});
return collectionsList;
}, [collections]);
const navigateHelper = useNavigateHelper();
const { open, node } = useEditCollectionName({
title: t['com.affine.editCollection.createCollection'](),
showTips: true,
const [filters, setFilters] = useState<Filter[]>([]);
const filteredPageMetas = useFilteredPageMetas(currentWorkspace, pageMetas, {
filters: filters,
});
const handleCreateCollection = useCallback(() => {
open('')
.then(name => {
const id = nanoid();
setting.createCollection(createEmptyCollection(id, { name }));
navigateHelper.jumpToCollection(currentWorkspace.id, id);
})
.catch(err => {
console.error(err);
});
}, [currentWorkspace.id, navigateHelper, open, setting]);
const currentTag = useMemo(() => {
if (params.tagId) {
return tags.find(tag => tag.id === params.tagId);
}
return;
}, [params.tagId, tags]);
const content = useMemo(() => {
if (filteredPageMetas.length > 0 && activeFilter === 'docs') {
return (
<VirtualizedPageList
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
/>
);
} else if (activeFilter === 'collections' && !setting.isDefault) {
return (
<VirtualizedPageList
collection={setting.currentCollection}
config={config}
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
/>
);
} else if (activeFilter === 'collections' && setting.isDefault) {
return collectionMetas.length > 0 ? (
<VirtualizedCollectionList
collections={collections}
collectionMetas={collectionMetas}
setHideHeaderCreateNewCollection={setHideHeaderCreateNew}
node={node}
config={config}
handleCreateCollection={handleCreateCollection}
/>
) : (
<EmptyCollectionList
heading={
<CollectionListHeader
node={node}
onCreate={handleCreateCollection}
/>
}
/>
);
} else if (activeFilter === 'tags') {
if (params.tagId) {
return tagPageMetas.length > 0 ? (
<VirtualizedPageList
tag={currentTag}
listItem={tagPageMetas}
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
/>
) : (
<EmptyPageList
type="all"
heading={<PageListHeader workspaceId={currentWorkspace.id} />}
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
/>
);
}
return tags.length > 0 ? (
<VirtualizedTagList
tags={tags}
tagMetas={tagMetas}
setHideHeaderCreateNewTag={setHideHeaderCreateNew}
onTagDelete={deleteTags}
/>
) : (
<EmptyTagList heading={<TagListHeader />} />
);
}
return (
<EmptyPageList
type="all"
heading={<PageListHeader workspaceId={currentWorkspace.id} />}
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
/>
);
}, [
activeFilter,
collectionMetas,
collections,
config,
currentTag,
currentWorkspace.blockSuiteWorkspace,
currentWorkspace.id,
deleteTags,
filteredPageMetas.length,
handleCreateCollection,
node,
params.tagId,
setting.currentCollection,
setting.isDefault,
tagMetas,
tagPageMetas,
tags,
]);
return (
<div className={styles.root}>
<AllPageHeader
workspace={currentWorkspace.blockSuiteWorkspace}
showCreateNew={!hideHeaderCreateNew}
isDefaultFilter={setting.isDefault}
activeFilter={activeFilter}
onCreateCollection={handleCreateCollection}
filters={filters}
onChangeFilters={setFilters}
/>
{content}
{filteredPageMetas.length > 0 ? (
<VirtualizedPageList
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
filters={filters}
/>
) : (
<EmptyPageList
type="all"
heading={<PageListHeader />}
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
/>
)}
<HubIsland />
</div>
);
@@ -215,21 +54,8 @@ export const Component = () => {
performanceRenderLogger.info('AllPage');
const currentWorkspace = useService(Workspace);
const currentCollection = useSetAtom(currentCollectionAtom);
const navigateHelper = useNavigateHelper();
const location = useLocation();
const activeFilter = useMemo(() => {
const query = new URLSearchParams(location.search);
const filterMode = query.get('filterMode');
if (filterMode === 'collections') {
return 'collections';
} else if (filterMode === 'tags') {
return 'tags';
}
return 'docs';
}, [location.search]);
useEffect(() => {
function checkJumpOnce() {
for (const [pageId] of currentWorkspace.blockSuiteWorkspace.pages) {
@@ -252,9 +78,5 @@ export const Component = () => {
navigateHelper,
]);
useEffect(() => {
currentCollection(NIL);
}, [currentCollection]);
return <AllPage activeFilter={activeFilter} />;
return <AllPage />;
};
@@ -0,0 +1,23 @@
import { Header } from '@affine/core/components/pure/header';
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
import * as styles from '../all-page/all-page.css';
export const AllTagHeader = () => {
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
return (
<Header
right={
<div
className={styles.headerRightWindows}
data-is-windows-desktop={isWindowsDesktop}
>
{isWindowsDesktop ? <WindowsAppControls /> : null}
</div>
}
center={<WorkspaceModeFilterTab activeFilter={'tags'} />}
/>
);
};
@@ -0,0 +1,43 @@
import { HubIsland } from '@affine/core/components/affine/hub-island';
import { useTagMetas } from '@affine/core/components/page-list';
import {
TagListHeader,
VirtualizedTagList,
} from '@affine/core/components/page-list/tags';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { useService } from '@toeverything/infra';
import { Workspace } from '@toeverything/infra';
import * as styles from '../all-page/all-page.css';
import { EmptyTagList } from '../page-list-empty';
import { AllTagHeader } from './header';
export const AllTag = () => {
const currentWorkspace = useService(Workspace);
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
const { tags, tagMetas, deleteTags } = useTagMetas(
currentWorkspace.blockSuiteWorkspace,
pageMetas
);
return (
<div className={styles.root}>
<AllTagHeader />
{tags.length > 0 ? (
<VirtualizedTagList
tags={tags}
tagMetas={tagMetas}
onTagDelete={deleteTags}
/>
) : (
<EmptyTagList heading={<TagListHeader />} />
)}
<HubIsland />
</div>
);
};
export const Component = () => {
return <AllTag />;
};
@@ -0,0 +1,49 @@
import { IconButton } from '@affine/component';
import { Header } from '@affine/core/components/pure/header';
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
import { PlusIcon } from '@blocksuite/icons';
import clsx from 'clsx';
import { useMemo } from 'react';
import * as styles from '../all-page/all-page.css';
export const CollectionDetailHeader = ({
showCreateNew,
onCreate,
}: {
showCreateNew: boolean;
onCreate: () => void;
}) => {
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
const renderRightItem = useMemo(() => {
return (
<IconButton
type="default"
icon={<PlusIcon fontSize={16} />}
onClick={onCreate}
className={clsx(
styles.headerCreateNewButton,
styles.headerCreateNewCollectionIconButton,
!showCreateNew && styles.headerCreateNewButtonHidden
)}
/>
);
}, [onCreate, showCreateNew]);
return (
<Header
right={
<div
className={styles.headerRightWindows}
data-is-windows-desktop={isWindowsDesktop}
>
{renderRightItem}
{isWindowsDesktop ? <WindowsAppControls /> : null}
</div>
}
center={<WorkspaceModeFilterTab activeFilter={'collections'} />}
/>
);
};
@@ -3,11 +3,11 @@ import {
SidebarSwitch,
} from '@affine/component/app-sidebar';
import { pushNotificationAtom } from '@affine/component/notification-center';
import { HubIsland } from '@affine/core/components/affine/hub-island';
import {
AffineShapeIcon,
currentCollectionAtom,
useCollectionManager,
useEditCollection,
VirtualizedPageList,
} from '@affine/core/components/page-list';
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
import { useAllPageListConfig } from '@affine/core/hooks/affine/use-all-page-list-config';
@@ -23,30 +23,54 @@ import {
ViewLayersIcon,
} from '@blocksuite/icons';
import { Workspace } from '@toeverything/infra';
import { getCurrentStore } from '@toeverything/infra/atom';
import { useService } from '@toeverything/infra/di';
import { useLiveData } from '@toeverything/infra/livedata';
import { useAtomValue } from 'jotai';
import { useSetAtom } from 'jotai';
import { useCallback, useEffect, useState } from 'react';
import { type LoaderFunction, redirect, useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { useNavigateHelper } from '../../hooks/use-navigate-helper';
import { WorkspaceSubPath } from '../../shared';
import { AllPage } from './all-page/all-page';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { WorkspaceSubPath } from '../../../shared';
import * as allPageStyles from '../all-page/all-page.css';
import * as styles from './collection.css';
import { CollectionDetailHeader } from './header';
export const loader: LoaderFunction = async args => {
const rootStore = getCurrentStore();
if (!args.params.collectionId) {
return redirect('/404');
}
rootStore.set(currentCollectionAtom, args.params.collectionId);
return null;
export const CollectionDetail = ({
collection,
}: {
collection: Collection;
}) => {
const config = useAllPageListConfig();
const { node, open } = useEditCollection(useAllPageListConfig());
const collectionService = useService(CollectionService);
const [hideHeaderCreateNew, setHideHeaderCreateNew] = useState(true);
const handleEditCollection = useAsyncCallback(async () => {
const ret = await open({ ...collection }, 'page');
collectionService.updateCollection(ret.id, () => ret);
}, [collection, collectionService, open]);
return (
<div className={allPageStyles.root}>
<CollectionDetailHeader
showCreateNew={!hideHeaderCreateNew}
onCreate={handleEditCollection}
/>
<VirtualizedPageList
collection={collection}
config={config}
setHideHeaderCreateNewPage={setHideHeaderCreateNew}
/>
<HubIsland />
{node}
</div>
);
};
export const Component = function CollectionPage() {
const collectionService = useService(CollectionService);
const collections = useLiveData(collectionService.collections);
const navigate = useNavigateHelper();
const params = useParams();
@@ -87,7 +111,7 @@ export const Component = function CollectionPage() {
return isEmpty(collection) ? (
<Placeholder collection={collection} />
) : (
<AllPage activeFilter="collections" />
<CollectionDetail collection={collection} />
);
};
@@ -95,16 +119,16 @@ const isWindowsDesktop = environment.isDesktop && environment.isWindows;
const Placeholder = ({ collection }: { collection: Collection }) => {
const workspace = useService(Workspace);
const collectionService = useCollectionManager(useService(CollectionService));
const collectionService = useService(CollectionService);
const { node, open } = useEditCollection(useAllPageListConfig());
const { jumpToCollections } = useNavigateHelper();
const openPageEdit = useAsyncCallback(async () => {
const ret = await open({ ...collection }, 'page');
collectionService.updateCollection(ret);
collectionService.updateCollection(ret.id, () => ret);
}, [open, collection, collectionService]);
const openRuleEdit = useAsyncCallback(async () => {
const ret = await open({ ...collection }, 'rule');
collectionService.updateCollection(ret);
collectionService.updateCollection(ret.id, () => ret);
}, [collection, open, collectionService]);
const [showTips, setShowTips] = useState(false);
useEffect(() => {
@@ -2,7 +2,6 @@ import { Scrollable } from '@affine/component';
import { PageDetailSkeleton } from '@affine/component/page-detail-skeleton';
import { ResizePanel } from '@affine/component/resize-panel';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { CollectionService } from '@affine/core/modules/collection';
import type { PageService } from '@blocksuite/blocks';
import {
BookmarkService,
@@ -42,17 +41,13 @@ import { HubIsland } from '../../../components/affine/hub-island';
import { GlobalPageHistoryModal } from '../../../components/affine/page-history-modal';
import { ImagePreviewModal } from '../../../components/image-preview';
import { PageDetailEditor } from '../../../components/page-detail-editor';
import {
createTagFilter,
useCollectionManager,
} from '../../../components/page-list';
import { TrashPageFooter } from '../../../components/pure/trash-page-footer';
import { TopTip } from '../../../components/top-tip';
import { useRegisterBlocksuiteEditorCommands } from '../../../hooks/affine/use-register-blocksuite-editor-commands';
import { usePageDocumentTitle } from '../../../hooks/use-global-state';
import { useNavigateHelper } from '../../../hooks/use-navigate-helper';
import { CurrentPageService } from '../../../modules/page';
import { performanceRenderLogger, WorkspaceSubPath } from '../../../shared';
import { performanceRenderLogger } from '../../../shared';
import { PageNotFound } from '../../404';
import * as styles from './detail-page.css';
import { DetailPageHeader, RightSidebarHeader } from './detail-page-header';
@@ -117,7 +112,7 @@ const DetailPageImpl = memo(function DetailPageImpl() {
const page = useService(Page);
const pageRecordList = useService(PageRecordList);
const currentPageId = page.id;
const { openPage, jumpToSubPath } = useNavigateHelper();
const { openPage, jumpToTag } = useNavigateHelper();
const currentWorkspace = useService(Workspace);
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
@@ -127,8 +122,6 @@ const DetailPageImpl = memo(function DetailPageImpl() {
const isInTrash = pageMeta?.trash;
const collectionService = useService(CollectionService);
const { setTemporaryFilter } = useCollectionManager(collectionService);
const mode = useLiveData(page.mode);
useRegisterBlocksuiteEditorCommands();
const title = useLiveData(page.title);
@@ -191,9 +184,8 @@ const DetailPageImpl = memo(function DetailPageImpl() {
const dispose = editor.slots.pageLinkClicked.on(({ pageId }) => {
return openPage(blockSuiteWorkspace.id, pageId);
});
const disposeTagClick = editor.slots.tagClicked.on(async ({ tagId }) => {
jumpToSubPath(currentWorkspace.id, WorkspaceSubPath.ALL);
setTemporaryFilter([createTagFilter(tagId)]);
const disposeTagClick = editor.slots.tagClicked.on(({ tagId }) => {
jumpToTag(currentWorkspace.id, tagId);
});
return () => {
dispose.dispose();
@@ -201,14 +193,13 @@ const DetailPageImpl = memo(function DetailPageImpl() {
};
},
[
page,
mode,
pageRecordList,
openPage,
blockSuiteWorkspace.id,
jumpToSubPath,
currentWorkspace.id,
setTemporaryFilter,
jumpToTag,
mode,
openPage,
page,
pageRecordList,
]
);
@@ -1,51 +0,0 @@
import { TagListHeader, useTagMetas } from '@affine/core/components/page-list';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { useService, Workspace } from '@toeverything/infra';
import { useMemo } from 'react';
import { type LoaderFunction, redirect, useParams } from 'react-router-dom';
import { AllPage } from './all-page/all-page';
import { AllPageHeader } from './all-page/all-page-header';
import { EmptyPageList } from './page-list-empty';
export const loader: LoaderFunction = async args => {
if (!args.params.tagId) {
return redirect('/404');
}
return null;
};
export const Component = function TagPage() {
const params = useParams();
const currentWorkspace = useService(Workspace);
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
const { tagUsageCounts } = useTagMetas(
currentWorkspace.blockSuiteWorkspace,
pageMetas
);
const isEmpty = useMemo(() => {
if (params.tagId) {
return tagUsageCounts[params.tagId] === 0;
}
return true;
}, [params.tagId, tagUsageCounts]);
return isEmpty ? (
<>
<AllPageHeader
workspace={currentWorkspace.blockSuiteWorkspace}
showCreateNew={false}
isDefaultFilter={true}
activeFilter={'tags'}
/>
<EmptyPageList
type="all"
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
heading={<TagListHeader />}
/>
</>
) : (
<AllPage activeFilter="tags" />
);
};
@@ -0,0 +1,23 @@
import { Header } from '@affine/core/components/pure/header';
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
import { WorkspaceModeFilterTab } from '@affine/core/components/pure/workspace-mode-filter-tab';
import * as styles from '../all-page/all-page.css';
export const TagDetailHeader = () => {
const isWindowsDesktop = environment.isDesktop && environment.isWindows;
return (
<Header
right={
<div
className={styles.headerRightWindows}
data-is-windows-desktop={isWindowsDesktop}
>
{isWindowsDesktop ? <WindowsAppControls /> : null}
</div>
}
center={<WorkspaceModeFilterTab activeFilter={'tags'} />}
/>
);
};
@@ -0,0 +1,63 @@
import { HubIsland } from '@affine/core/components/affine/hub-island';
import {
PageListHeader,
useTagMetas,
VirtualizedPageList,
} from '@affine/core/components/page-list';
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
import { useService } from '@toeverything/infra';
import { Workspace } from '@toeverything/infra';
import { useMemo } from 'react';
import { useParams } from 'react-router-dom';
import { PageNotFound } from '../../404';
import * as styles from '../all-page/all-page.css';
import { EmptyPageList } from '../page-list-empty';
import { TagDetailHeader } from './header';
export const TagDetail = ({ tagId }: { tagId?: string }) => {
const currentWorkspace = useService(Workspace);
const pageMetas = useBlockSuitePageMeta(currentWorkspace.blockSuiteWorkspace);
const { tags, filterPageMetaByTag } = useTagMetas(
currentWorkspace.blockSuiteWorkspace,
pageMetas
);
const tagPageMetas = useMemo(() => {
if (tagId) {
return filterPageMetaByTag(tagId);
}
return [];
}, [filterPageMetaByTag, tagId]);
const currentTag = useMemo(
() => tags.find(tag => tag.id === tagId),
[tagId, tags]
);
if (!currentTag) {
return <PageNotFound />;
}
return (
<div className={styles.root}>
<TagDetailHeader />
{tagPageMetas.length > 0 ? (
<VirtualizedPageList tag={currentTag} listItem={tagPageMetas} />
) : (
<EmptyPageList
type="all"
heading={<PageListHeader />}
blockSuiteWorkspace={currentWorkspace.blockSuiteWorkspace}
/>
)}
<HubIsland />
</div>
);
};
export const Component = () => {
const params = useParams();
return <TagDetail tagId={params.tagId} />;
};
@@ -1,7 +1,6 @@
import { toast } from '@affine/component';
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
import {
currentCollectionAtom,
type ListItem,
ListTableHeader,
PageListItemRenderer,
@@ -19,11 +18,8 @@ import { assertExists } from '@blocksuite/global/utils';
import { DeleteIcon } from '@blocksuite/icons';
import type { PageMeta } from '@blocksuite/store';
import { Workspace } from '@toeverything/infra';
import { getCurrentStore } from '@toeverything/infra/atom';
import { useService } from '@toeverything/infra/di';
import { useCallback } from 'react';
import { type LoaderFunction } from 'react-router-dom';
import { NIL } from 'uuid';
import { EmptyPageList } from './page-list-empty';
import * as styles from './trash-page.css';
@@ -50,27 +46,15 @@ const TrashHeader = () => {
);
};
export const loader: LoaderFunction = async () => {
// to fix the bug that the trash page list is not updated when route from collection to trash
// but it's not a good solution, the page will jitter when collection and trash are switched between each other.
// TODO: fix this bug
const rootStore = getCurrentStore();
rootStore.set(currentCollectionAtom, NIL);
return null;
};
export const TrashPage = () => {
const currentWorkspace = useService(Workspace);
const blockSuiteWorkspace = currentWorkspace.blockSuiteWorkspace;
assertExists(blockSuiteWorkspace);
const pageMetas = useBlockSuitePageMeta(blockSuiteWorkspace);
const filteredPageMetas = useFilteredPageMetas(
'trash',
pageMetas,
currentWorkspace
);
const filteredPageMetas = useFilteredPageMetas(currentWorkspace, pageMetas, {
trash: true,
});
const { restoreFromTrash, permanentlyDeletePage } =
useBlockSuiteMetaHelper(blockSuiteWorkspace);