mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-15 17:16:16 +08:00
refactor(core): remove collection atom (#5832)
This commit is contained in:
-65
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import 'fake-indexeddb/auto';
|
||||
|
||||
import type { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { LiveData } from '@toeverything/infra';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
import { createDefaultFilter, vars } from '../filter/vars';
|
||||
import { useCollectionManager } from '../use-collection-manager';
|
||||
|
||||
const defaultMeta = { tags: { options: [] } };
|
||||
const collectionsSubject = new BehaviorSubject<Collection[]>([]);
|
||||
|
||||
const mockWorkspaceCollectionService = {
|
||||
collections: LiveData.from(collectionsSubject, []),
|
||||
addCollection: (...collections) => {
|
||||
const prev = collectionsSubject.value;
|
||||
collectionsSubject.next([...collections, ...prev]);
|
||||
},
|
||||
deleteCollection: (...ids) => {
|
||||
const prev = collectionsSubject.value;
|
||||
collectionsSubject.next(prev.filter(v => !ids.includes(v.id)));
|
||||
},
|
||||
updateCollection: (id, updater) => {
|
||||
const prev = collectionsSubject.value;
|
||||
collectionsSubject.next(
|
||||
prev.map(v => {
|
||||
if (v.id === id) {
|
||||
return updater(v);
|
||||
}
|
||||
return v;
|
||||
})
|
||||
);
|
||||
},
|
||||
} as CollectionService;
|
||||
|
||||
test('useAllPageSetting', async () => {
|
||||
const settingHook = renderHook(() =>
|
||||
useCollectionManager(mockWorkspaceCollectionService)
|
||||
);
|
||||
const prevCollection = settingHook.result.current.currentCollection;
|
||||
expect(settingHook.result.current.savedCollections).toEqual([]);
|
||||
settingHook.result.current.updateCollection({
|
||||
...settingHook.result.current.currentCollection,
|
||||
filterList: [createDefaultFilter(vars[0], defaultMeta)],
|
||||
});
|
||||
settingHook.rerender();
|
||||
const nextCollection = settingHook.result.current.currentCollection;
|
||||
expect(nextCollection).not.toBe(prevCollection);
|
||||
expect(nextCollection.filterList).toEqual([
|
||||
createDefaultFilter(vars[0], defaultMeta),
|
||||
]);
|
||||
settingHook.result.current.createCollection({
|
||||
...settingHook.result.current.currentCollection,
|
||||
id: '1',
|
||||
});
|
||||
settingHook.rerender();
|
||||
expect(settingHook.result.current.savedCollections.length).toBe(1);
|
||||
expect(settingHook.result.current.savedCollections[0].id).toBe('1');
|
||||
});
|
||||
+8
-9
@@ -18,19 +18,18 @@ import { CollectionOperationCell } from '../operation-cell';
|
||||
import { CollectionListItemRenderer } from '../page-group';
|
||||
import { ListTableHeader } from '../page-header';
|
||||
import type { CollectionMeta, ItemListHandle, ListItem } from '../types';
|
||||
import { useCollectionManager } from '../use-collection-manager';
|
||||
import type { AllPageListConfig } from '../view';
|
||||
import { VirtualizedList } from '../virtualized-list';
|
||||
import { CollectionListHeader } from './collection-list-header';
|
||||
|
||||
const useCollectionOperationsRenderer = ({
|
||||
info,
|
||||
setting,
|
||||
service,
|
||||
config,
|
||||
}: {
|
||||
info: DeleteCollectionInfo;
|
||||
config: AllPageListConfig;
|
||||
setting: ReturnType<typeof useCollectionManager>;
|
||||
service: CollectionService;
|
||||
}) => {
|
||||
const pageOperationsRenderer = useCallback(
|
||||
(collection: Collection) => {
|
||||
@@ -38,12 +37,12 @@ const useCollectionOperationsRenderer = ({
|
||||
<CollectionOperationCell
|
||||
info={info}
|
||||
collection={collection}
|
||||
setting={setting}
|
||||
service={service}
|
||||
config={config}
|
||||
/>
|
||||
);
|
||||
},
|
||||
[config, info, setting]
|
||||
[config, info, service]
|
||||
);
|
||||
|
||||
return pageOperationsRenderer;
|
||||
@@ -69,13 +68,13 @@ export const VirtualizedCollectionList = ({
|
||||
const [selectedCollectionIds, setSelectedCollectionIds] = useState<string[]>(
|
||||
[]
|
||||
);
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const collectionService = useService(CollectionService);
|
||||
const currentWorkspace = useService(Workspace);
|
||||
const info = useDeleteCollectionInfo();
|
||||
|
||||
const collectionOperations = useCollectionOperationsRenderer({
|
||||
info,
|
||||
setting,
|
||||
service: collectionService,
|
||||
config,
|
||||
});
|
||||
|
||||
@@ -105,8 +104,8 @@ export const VirtualizedCollectionList = ({
|
||||
}, []);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
return setting.deleteCollection(info, ...selectedCollectionIds);
|
||||
}, [setting, info, selectedCollectionIds]);
|
||||
return collectionService.deleteCollection(info, ...selectedCollectionIds);
|
||||
}, [collectionService, info, selectedCollectionIds]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -10,10 +10,7 @@ import { useCallback, useMemo } from 'react';
|
||||
|
||||
import { CollectionService } from '../../../modules/collection';
|
||||
import { createTagFilter } from '../filter/utils';
|
||||
import {
|
||||
createEmptyCollection,
|
||||
useCollectionManager,
|
||||
} from '../use-collection-manager';
|
||||
import { createEmptyCollection } from '../use-collection-manager';
|
||||
import { tagColorMap } from '../utils';
|
||||
import type { AllPageListConfig } from '../view/edit-collection/edit-collection';
|
||||
import {
|
||||
@@ -23,38 +20,12 @@ import {
|
||||
import * as styles from './page-list-header.css';
|
||||
import { PageListNewPageButton } from './page-list-new-page-button';
|
||||
|
||||
export const PageListHeader = ({ workspaceId }: { workspaceId: string }) => {
|
||||
export const PageListHeader = () => {
|
||||
const t = useAFFiNEI18N();
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const { jumpToCollections } = useNavigateHelper();
|
||||
|
||||
const handleJumpToCollections = useCallback(() => {
|
||||
jumpToCollections(workspaceId);
|
||||
}, [jumpToCollections, workspaceId]);
|
||||
|
||||
const title = useMemo(() => {
|
||||
if (setting.isDefault) {
|
||||
return t['com.affine.all-pages.header']();
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<div style={{ cursor: 'pointer' }} onClick={handleJumpToCollections}>
|
||||
{t['com.affine.collections.header']()} /
|
||||
</div>
|
||||
<div className={styles.titleIcon}>
|
||||
<ViewLayersIcon />
|
||||
</div>
|
||||
<div className={styles.titleCollectionName}>
|
||||
{setting.currentCollection.name}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}, [
|
||||
handleJumpToCollections,
|
||||
setting.currentCollection.name,
|
||||
setting.isDefault,
|
||||
t,
|
||||
]);
|
||||
return t['com.affine.all-pages.header']();
|
||||
}, [t]);
|
||||
|
||||
return (
|
||||
<div className={styles.docListHeader}>
|
||||
@@ -75,22 +46,19 @@ export const CollectionPageListHeader = ({
|
||||
workspaceId: string;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const { jumpToCollections } = useNavigateHelper();
|
||||
|
||||
const handleJumpToCollections = useCallback(() => {
|
||||
jumpToCollections(workspaceId);
|
||||
}, [jumpToCollections, workspaceId]);
|
||||
|
||||
const { updateCollection } = useCollectionManager(
|
||||
useService(CollectionService)
|
||||
);
|
||||
const collectionService = useService(CollectionService);
|
||||
const { node, open } = useEditCollection(config);
|
||||
|
||||
const handleAddPage = useAsyncCallback(async () => {
|
||||
const ret = await open({ ...collection }, 'page');
|
||||
updateCollection(ret);
|
||||
}, [collection, open, updateCollection]);
|
||||
collectionService.updateCollection(collection.id, () => ret);
|
||||
}, [collection, collectionService, open]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -103,9 +71,7 @@ export const CollectionPageListHeader = ({
|
||||
<div className={styles.titleIcon}>
|
||||
<ViewLayersIcon />
|
||||
</div>
|
||||
<div className={styles.titleCollectionName}>
|
||||
{setting.currentCollection.name}
|
||||
</div>
|
||||
<div className={styles.titleCollectionName}>{collection.name}</div>
|
||||
</div>
|
||||
<Button className={styles.addPageButton} onClick={handleAddPage}>
|
||||
{t['com.affine.collection.addPages']()}
|
||||
@@ -124,7 +90,7 @@ export const TagPageListHeader = ({
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const { jumpToTags, jumpToCollection } = useNavigateHelper();
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const collectionService = useService(CollectionService);
|
||||
const { open, node } = useEditCollectionName({
|
||||
title: t['com.affine.editCollection.saveCollection'](),
|
||||
showTips: true,
|
||||
@@ -136,13 +102,13 @@ export const TagPageListHeader = ({
|
||||
|
||||
const saveToCollection = useCallback(
|
||||
(collection: Collection) => {
|
||||
setting.createCollection({
|
||||
collectionService.addCollection({
|
||||
...collection,
|
||||
filterList: [createTagFilter(tag.id)],
|
||||
});
|
||||
jumpToCollection(workspaceId, collection.id);
|
||||
},
|
||||
[setting, tag.id, jumpToCollection, workspaceId]
|
||||
[collectionService, tag.id, jumpToCollection, workspaceId]
|
||||
);
|
||||
const handleClick = useCallback(() => {
|
||||
open('')
|
||||
|
||||
@@ -2,7 +2,7 @@ import { toast } from '@affine/component';
|
||||
import { useBlockSuiteMetaHelper } from '@affine/core/hooks/affine/use-block-suite-meta-helper';
|
||||
import { useTrashModalHelper } from '@affine/core/hooks/affine/use-trash-modal-helper';
|
||||
import { useBlockSuitePageMeta } from '@affine/core/hooks/use-block-suite-page-meta';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import type { Collection, Filter } from '@affine/env/filter';
|
||||
import { Trans } from '@affine/i18n';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import type { PageMeta, Tag } from '@blocksuite/store';
|
||||
@@ -81,15 +81,17 @@ const usePageOperationsRenderer = () => {
|
||||
export const VirtualizedPageList = ({
|
||||
tag,
|
||||
collection,
|
||||
filters,
|
||||
config,
|
||||
listItem,
|
||||
setHideHeaderCreateNewPage,
|
||||
}: {
|
||||
tag?: Tag;
|
||||
collection?: Collection;
|
||||
filters?: Filter[];
|
||||
config?: AllPageListConfig;
|
||||
listItem?: PageMeta[];
|
||||
setHideHeaderCreateNewPage: (hide: boolean) => void;
|
||||
setHideHeaderCreateNewPage?: (hide: boolean) => void;
|
||||
}) => {
|
||||
const listRef = useRef<ItemListHandle>(null);
|
||||
const [showFloatingToolbar, setShowFloatingToolbar] = useState(false);
|
||||
@@ -101,11 +103,10 @@ export const VirtualizedPageList = ({
|
||||
currentWorkspace.blockSuiteWorkspace
|
||||
);
|
||||
|
||||
const filteredPageMetas = useFilteredPageMetas(
|
||||
'all',
|
||||
pageMetas,
|
||||
currentWorkspace
|
||||
);
|
||||
const filteredPageMetas = useFilteredPageMetas(currentWorkspace, pageMetas, {
|
||||
filters,
|
||||
collection,
|
||||
});
|
||||
const pageMetasToRender = useMemo(() => {
|
||||
if (listItem) {
|
||||
return listItem;
|
||||
@@ -151,7 +152,7 @@ export const VirtualizedPageList = ({
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <PageListHeader workspaceId={currentWorkspace.id} />;
|
||||
return <PageListHeader />;
|
||||
}, [collection, config, currentWorkspace.id, tag]);
|
||||
|
||||
const { setTrashModal } = useTrashModalHelper(
|
||||
|
||||
@@ -23,10 +23,10 @@ import {
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import type { CollectionService } from '../../modules/collection';
|
||||
import { FavoriteTag } from './components/favorite-tag';
|
||||
import * as styles from './list.css';
|
||||
import { DisablePublicSharing, MoveToTrash } from './operation-menu-items';
|
||||
import type { useCollectionManager } from './use-collection-manager';
|
||||
import { ColWrapper, stopPropagationWithoutPrevent } from './utils';
|
||||
import {
|
||||
type AllPageListConfig,
|
||||
@@ -208,13 +208,13 @@ export interface CollectionOperationCellProps {
|
||||
collection: Collection;
|
||||
info: DeleteCollectionInfo;
|
||||
config: AllPageListConfig;
|
||||
setting: ReturnType<typeof useCollectionManager>;
|
||||
service: CollectionService;
|
||||
}
|
||||
|
||||
export const CollectionOperationCell = ({
|
||||
collection,
|
||||
config,
|
||||
setting,
|
||||
service,
|
||||
info,
|
||||
}: CollectionOperationCellProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
@@ -231,26 +231,29 @@ export const CollectionOperationCell = ({
|
||||
// use openRenameModal if it is in the sidebar collection list
|
||||
openEditCollectionNameModal(collection.name)
|
||||
.then(name => {
|
||||
return setting.updateCollection({ ...collection, name });
|
||||
return service.updateCollection(collection.id, collection => ({
|
||||
...collection,
|
||||
name,
|
||||
}));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [collection, openEditCollectionNameModal, setting]);
|
||||
}, [collection.id, collection.name, openEditCollectionNameModal, service]);
|
||||
|
||||
const handleEdit = useCallback(() => {
|
||||
openEditCollectionModal(collection)
|
||||
.then(collection => {
|
||||
return setting.updateCollection(collection);
|
||||
return service.updateCollection(collection.id, () => collection);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [setting, collection, openEditCollectionModal]);
|
||||
}, [openEditCollectionModal, collection, service]);
|
||||
|
||||
const handleDelete = useCallback(() => {
|
||||
return setting.deleteCollection(info, collection.id);
|
||||
}, [setting, info, collection]);
|
||||
return service.deleteCollection(info, collection.id);
|
||||
}, [service, info, collection]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -15,12 +15,10 @@ import { TagListHeader } from './tag-list-header';
|
||||
export const VirtualizedTagList = ({
|
||||
tags,
|
||||
tagMetas,
|
||||
setHideHeaderCreateNewTag,
|
||||
onTagDelete,
|
||||
}: {
|
||||
tags: Tag[];
|
||||
tagMetas: TagMeta[];
|
||||
setHideHeaderCreateNewTag: (hide: boolean) => void;
|
||||
onTagDelete: (tagIds: string[]) => void;
|
||||
}) => {
|
||||
const listRef = useRef<ItemListHandle>(null);
|
||||
@@ -63,7 +61,6 @@ export const VirtualizedTagList = ({
|
||||
draggable={false}
|
||||
groupBy={false}
|
||||
atTopThreshold={80}
|
||||
atTopStateChange={setHideHeaderCreateNewTag}
|
||||
onSelectionActiveChange={setShowFloatingToolbar}
|
||||
heading={<TagListHeader />}
|
||||
selectedIds={filteredSelectedTagIds}
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import type { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { Collection, Filter, VariableMap } from '@affine/env/filter';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import { useLiveData } from '@toeverything/infra/livedata';
|
||||
import { useAtom, useAtomValue } from 'jotai';
|
||||
import { atomWithReset } from 'jotai/utils';
|
||||
import { useCallback } from 'react';
|
||||
import { NIL } from 'uuid';
|
||||
|
||||
import { evalFilterList } from './filter';
|
||||
|
||||
@@ -21,79 +15,7 @@ export const createEmptyCollection = (
|
||||
...data,
|
||||
};
|
||||
};
|
||||
const defaultCollection: Collection = createEmptyCollection(NIL, {
|
||||
name: 'All',
|
||||
});
|
||||
const defaultCollectionAtom = atomWithReset<Collection>(defaultCollection);
|
||||
export const currentCollectionAtom = atomWithReset<string>(NIL);
|
||||
|
||||
export type Updater<T> = (value: T) => T;
|
||||
export type CollectionUpdater = Updater<Collection>;
|
||||
|
||||
export const useSavedCollections = (collectionService: CollectionService) => {
|
||||
const addPage = useCallback(
|
||||
(collectionId: string, pageId: string) => {
|
||||
collectionService.updateCollection(collectionId, old => {
|
||||
return {
|
||||
...old,
|
||||
allowList: [pageId, ...(old.allowList ?? [])],
|
||||
};
|
||||
});
|
||||
},
|
||||
[collectionService]
|
||||
);
|
||||
return {
|
||||
collectionService,
|
||||
addPage,
|
||||
};
|
||||
};
|
||||
|
||||
export const useCollectionManager = (collectionService: CollectionService) => {
|
||||
const collections = useLiveData(collectionService.collections);
|
||||
const { addPage } = useSavedCollections(collectionService);
|
||||
const currentCollectionId = useAtomValue(currentCollectionAtom);
|
||||
const [defaultCollection, updateDefaultCollection] = useAtom(
|
||||
defaultCollectionAtom
|
||||
);
|
||||
const update = useCallback(
|
||||
(collection: Collection) => {
|
||||
if (collection.id === NIL) {
|
||||
updateDefaultCollection(collection);
|
||||
} else {
|
||||
collectionService.updateCollection(collection.id, () => collection);
|
||||
}
|
||||
},
|
||||
[updateDefaultCollection, collectionService]
|
||||
);
|
||||
const setTemporaryFilter = useCallback(
|
||||
(filterList: Filter[]) => {
|
||||
updateDefaultCollection({
|
||||
...defaultCollection,
|
||||
filterList: filterList,
|
||||
});
|
||||
},
|
||||
[updateDefaultCollection, defaultCollection]
|
||||
);
|
||||
const currentCollection =
|
||||
currentCollectionId === NIL
|
||||
? defaultCollection
|
||||
: collections.find(v => v.id === currentCollectionId) ??
|
||||
defaultCollection;
|
||||
|
||||
return {
|
||||
currentCollection: currentCollection,
|
||||
savedCollections: collections,
|
||||
isDefault: currentCollectionId === NIL,
|
||||
|
||||
// actions
|
||||
createCollection: collectionService.addCollection.bind(collectionService),
|
||||
updateCollection: update,
|
||||
deleteCollection:
|
||||
collectionService.deleteCollection.bind(collectionService),
|
||||
addPage,
|
||||
setTemporaryFilter,
|
||||
};
|
||||
};
|
||||
export const filterByFilterList = (filterList: Filter[], varMap: VariableMap) =>
|
||||
evalFilterList(filterList, varMap);
|
||||
|
||||
|
||||
@@ -1,77 +1,55 @@
|
||||
import { allPageModeSelectAtom } from '@affine/core/atoms';
|
||||
import { usePageHelper } from '@affine/core/components/blocksuite/block-suite-page-list/utils';
|
||||
import { usePublicPages } from '@affine/core/hooks/affine/use-is-shared-page';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { Collection, Filter } from '@affine/env/filter';
|
||||
import type { PageMeta } from '@blocksuite/store';
|
||||
import type { Workspace } from '@toeverything/infra';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import {
|
||||
filterPage,
|
||||
filterPageByRules,
|
||||
useCollectionManager,
|
||||
} from './use-collection-manager';
|
||||
import { usePublicPages } from '../../hooks/affine/use-is-shared-page';
|
||||
import { filterPage, filterPageByRules } from './use-collection-manager';
|
||||
|
||||
export const useFilteredPageMetas = (
|
||||
route: 'all' | 'trash',
|
||||
workspace: Workspace,
|
||||
pageMetas: PageMeta[],
|
||||
workspace: Workspace
|
||||
options: {
|
||||
trash?: boolean;
|
||||
filters?: Filter[];
|
||||
collection?: Collection;
|
||||
} = {}
|
||||
) => {
|
||||
const { isPreferredEdgeless } = usePageHelper(workspace.blockSuiteWorkspace);
|
||||
const pageMode = useAtomValue(allPageModeSelectAtom);
|
||||
const { currentCollection, isDefault } = useCollectionManager(
|
||||
useService(CollectionService)
|
||||
);
|
||||
const { getPublicMode } = usePublicPages(workspace);
|
||||
|
||||
const filteredPageMetas = useMemo(
|
||||
() =>
|
||||
pageMetas
|
||||
.filter(pageMeta => {
|
||||
if (pageMode === 'all') {
|
||||
return true;
|
||||
}
|
||||
if (pageMode === 'edgeless') {
|
||||
return isPreferredEdgeless(pageMeta.id);
|
||||
}
|
||||
if (pageMode === 'page') {
|
||||
return !isPreferredEdgeless(pageMeta.id);
|
||||
}
|
||||
console.error('unknown filter mode', pageMeta, pageMode);
|
||||
return true;
|
||||
})
|
||||
.filter(pageMeta => {
|
||||
if (
|
||||
(route === 'trash' && !pageMeta.trash) ||
|
||||
(route === 'all' && pageMeta.trash)
|
||||
) {
|
||||
pageMetas.filter(pageMeta => {
|
||||
if (options.trash) {
|
||||
if (!pageMeta.trash) {
|
||||
return false;
|
||||
}
|
||||
if (!currentCollection) {
|
||||
return true;
|
||||
}
|
||||
const pageData = {
|
||||
meta: pageMeta,
|
||||
publicMode: getPublicMode(pageMeta.id),
|
||||
};
|
||||
return isDefault
|
||||
? filterPageByRules(
|
||||
currentCollection.filterList,
|
||||
currentCollection.allowList,
|
||||
pageData
|
||||
)
|
||||
: filterPage(currentCollection, pageData);
|
||||
}),
|
||||
} else if (pageMeta.trash) {
|
||||
return false;
|
||||
}
|
||||
const pageData = {
|
||||
meta: pageMeta,
|
||||
publicMode: getPublicMode(pageMeta.id),
|
||||
};
|
||||
if (
|
||||
options.filters &&
|
||||
!filterPageByRules(options.filters, [], pageData)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.collection && !filterPage(options.collection, pageData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
[
|
||||
currentCollection,
|
||||
isDefault,
|
||||
isPreferredEdgeless,
|
||||
getPublicMode,
|
||||
pageMetas,
|
||||
pageMode,
|
||||
route,
|
||||
options.trash,
|
||||
options.filters,
|
||||
options.collection,
|
||||
getPublicMode,
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
export const view = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 10,
|
||||
fontSize: 14,
|
||||
fontWeight: 600,
|
||||
height: '100%',
|
||||
});
|
||||
export const option = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: 4,
|
||||
cursor: 'pointer',
|
||||
borderRadius: 4,
|
||||
':hover': {
|
||||
backgroundColor: cssVar('hoverColor'),
|
||||
},
|
||||
opacity: 0,
|
||||
selectors: {
|
||||
[`${view}:hover &`]: {
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,112 +0,0 @@
|
||||
import { Button, Tooltip } from '@affine/component';
|
||||
import type { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { DeleteCollectionInfo, PropertiesMeta } from '@affine/env/filter';
|
||||
import type { GetPageInfoById } from '@affine/env/page-info';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ViewLayersIcon } from '@blocksuite/icons';
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useCollectionManager } from '../use-collection-manager';
|
||||
import * as styles from './collection-bar.css';
|
||||
import {
|
||||
type AllPageListConfig,
|
||||
EditCollectionModal,
|
||||
} from './edit-collection/edit-collection';
|
||||
import { useActions } from './use-action';
|
||||
|
||||
interface CollectionBarProps {
|
||||
getPageInfo: GetPageInfoById;
|
||||
propertiesMeta: PropertiesMeta;
|
||||
collectionService: CollectionService;
|
||||
backToAll: () => void;
|
||||
allPageListConfig: AllPageListConfig;
|
||||
info: DeleteCollectionInfo;
|
||||
}
|
||||
|
||||
export const CollectionBar = (props: CollectionBarProps) => {
|
||||
const { collectionService } = props;
|
||||
const t = useAFFiNEI18N();
|
||||
const setting = useCollectionManager(collectionService);
|
||||
const collection = setting.currentCollection;
|
||||
const [open, setOpen] = useState(false);
|
||||
const actions = useActions({
|
||||
collection,
|
||||
setting,
|
||||
info: props.info,
|
||||
openEdit: () => setOpen(true),
|
||||
});
|
||||
return !setting.isDefault ? (
|
||||
<div
|
||||
style={{
|
||||
userSelect: 'none',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '12px 20px',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<div className={styles.view}>
|
||||
<EditCollectionModal
|
||||
allPageListConfig={props.allPageListConfig}
|
||||
init={collection}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onConfirm={setting.updateCollection}
|
||||
/>
|
||||
<ViewLayersIcon
|
||||
style={{
|
||||
height: 20,
|
||||
width: 20,
|
||||
}}
|
||||
/>
|
||||
<Tooltip
|
||||
content={setting.currentCollection.name}
|
||||
rootOptions={{
|
||||
delayDuration: 1500,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
marginRight: 10,
|
||||
maxWidth: 200,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
}}
|
||||
>
|
||||
{setting.currentCollection.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
{actions.map(action => {
|
||||
return (
|
||||
<Tooltip key={action.name} content={action.tooltip}>
|
||||
<div
|
||||
data-testid={`collection-bar-option-${action.name}`}
|
||||
onClick={action.click}
|
||||
className={clsx(styles.option, action.className)}
|
||||
>
|
||||
{action.icon}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'end',
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
style={{ border: 'none', position: 'static' }}
|
||||
onClick={props.backToAll}
|
||||
>
|
||||
{t['com.affine.collectionBar.backToAll']()}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : null;
|
||||
};
|
||||
@@ -9,102 +9,73 @@ import type {
|
||||
import type { PropertiesMeta } from '@affine/env/filter';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { FilterIcon } from '@blocksuite/icons';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
import { CreateFilterMenu } from '../filter/vars';
|
||||
import type { useCollectionManager } from '../use-collection-manager';
|
||||
import * as styles from './collection-list.css';
|
||||
import { CollectionOperations } from './collection-operations';
|
||||
import {
|
||||
type AllPageListConfig,
|
||||
EditCollectionModal,
|
||||
} from './edit-collection/edit-collection';
|
||||
import { type AllPageListConfig } from './edit-collection/edit-collection';
|
||||
|
||||
export const CollectionList = ({
|
||||
setting,
|
||||
propertiesMeta,
|
||||
export const CollectionPageListOperationsMenu = ({
|
||||
collection,
|
||||
allPageListConfig,
|
||||
userInfo,
|
||||
disable,
|
||||
}: {
|
||||
setting: ReturnType<typeof useCollectionManager>;
|
||||
propertiesMeta: PropertiesMeta;
|
||||
collection: Collection;
|
||||
allPageListConfig: AllPageListConfig;
|
||||
userInfo: DeleteCollectionInfo;
|
||||
disable?: boolean;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [collection, setCollection] = useState<Collection>();
|
||||
const onChange = useCallback(
|
||||
(filterList: Filter[]) => {
|
||||
setting.updateCollection({
|
||||
...setting.currentCollection,
|
||||
filterList,
|
||||
});
|
||||
},
|
||||
[setting]
|
||||
);
|
||||
const closeUpdateCollectionModal = useCallback((open: boolean) => {
|
||||
if (!open) {
|
||||
setCollection(undefined);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onConfirm = useCallback(
|
||||
(view: Collection) => {
|
||||
setting.updateCollection(view);
|
||||
closeUpdateCollectionModal(false);
|
||||
},
|
||||
[closeUpdateCollectionModal, setting]
|
||||
);
|
||||
return (
|
||||
<FlexWrapper alignItems="center">
|
||||
{setting.isDefault ? (
|
||||
<>
|
||||
<Menu
|
||||
items={
|
||||
<CreateFilterMenu
|
||||
propertiesMeta={propertiesMeta}
|
||||
value={setting.currentCollection.filterList}
|
||||
onChange={onChange}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className={styles.filterMenuTrigger}
|
||||
type="default"
|
||||
icon={<FilterIcon />}
|
||||
data-is-hidden={disable}
|
||||
data-testid="create-first-filter"
|
||||
>
|
||||
{t['com.affine.filter']()}
|
||||
</Button>
|
||||
</Menu>
|
||||
<EditCollectionModal
|
||||
allPageListConfig={allPageListConfig}
|
||||
init={collection}
|
||||
open={!!collection}
|
||||
onOpenChange={closeUpdateCollectionModal}
|
||||
onConfirm={onConfirm}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<CollectionOperations
|
||||
info={userInfo}
|
||||
collection={setting.currentCollection}
|
||||
config={allPageListConfig}
|
||||
setting={setting}
|
||||
<CollectionOperations
|
||||
info={userInfo}
|
||||
collection={collection}
|
||||
config={allPageListConfig}
|
||||
>
|
||||
<Button
|
||||
className={styles.filterMenuTrigger}
|
||||
type="default"
|
||||
icon={<FilterIcon />}
|
||||
data-testid="create-first-filter"
|
||||
>
|
||||
<Button
|
||||
className={styles.filterMenuTrigger}
|
||||
type="default"
|
||||
icon={<FilterIcon />}
|
||||
data-testid="create-first-filter"
|
||||
>
|
||||
{t['com.affine.filter']()}
|
||||
</Button>
|
||||
</CollectionOperations>
|
||||
)}
|
||||
{t['com.affine.filter']()}
|
||||
</Button>
|
||||
</CollectionOperations>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export const AllPageListOperationsMenu = ({
|
||||
propertiesMeta,
|
||||
filterList,
|
||||
onChangeFilterList,
|
||||
}: {
|
||||
propertiesMeta: PropertiesMeta;
|
||||
filterList: Filter[];
|
||||
onChangeFilterList: (filterList: Filter[]) => void;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<FlexWrapper alignItems="center">
|
||||
<Menu
|
||||
items={
|
||||
<CreateFilterMenu
|
||||
propertiesMeta={propertiesMeta}
|
||||
value={filterList}
|
||||
onChange={onChangeFilterList}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Button
|
||||
className={styles.filterMenuTrigger}
|
||||
type="default"
|
||||
icon={<FilterIcon />}
|
||||
data-testid="create-first-filter"
|
||||
>
|
||||
{t['com.affine.filter']()}
|
||||
</Button>
|
||||
</Menu>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { DeleteIcon, EditIcon, FilterIcon } from '@blocksuite/icons';
|
||||
import { useService } from '@toeverything/infra/di';
|
||||
import {
|
||||
type PropsWithChildren,
|
||||
type ReactElement,
|
||||
@@ -14,7 +15,7 @@ import {
|
||||
useMemo,
|
||||
} from 'react';
|
||||
|
||||
import type { useCollectionManager } from '../use-collection-manager';
|
||||
import { CollectionService } from '../../../modules/collection';
|
||||
import * as styles from './collection-operations.css';
|
||||
import type { AllPageListConfig } from './index';
|
||||
import {
|
||||
@@ -25,7 +26,6 @@ import {
|
||||
export const CollectionOperations = ({
|
||||
collection,
|
||||
config,
|
||||
setting,
|
||||
info,
|
||||
openRenameModal,
|
||||
children,
|
||||
@@ -33,9 +33,9 @@ export const CollectionOperations = ({
|
||||
info: DeleteCollectionInfo;
|
||||
collection: Collection;
|
||||
config: AllPageListConfig;
|
||||
setting: ReturnType<typeof useCollectionManager>;
|
||||
openRenameModal?: () => void;
|
||||
}>) => {
|
||||
const service = useService(CollectionService);
|
||||
const { open: openEditCollectionModal, node: editModal } =
|
||||
useEditCollection(config);
|
||||
const t = useAFFiNEI18N();
|
||||
@@ -51,22 +51,25 @@ export const CollectionOperations = ({
|
||||
}
|
||||
openEditCollectionNameModal(collection.name)
|
||||
.then(name => {
|
||||
return setting.updateCollection({ ...collection, name });
|
||||
return service.updateCollection(collection.id, () => ({
|
||||
...collection,
|
||||
name,
|
||||
}));
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [openRenameModal, openEditCollectionNameModal, collection, setting]);
|
||||
}, [openRenameModal, openEditCollectionNameModal, collection, service]);
|
||||
|
||||
const showEdit = useCallback(() => {
|
||||
openEditCollectionModal(collection)
|
||||
.then(collection => {
|
||||
return setting.updateCollection(collection);
|
||||
return service.updateCollection(collection.id, () => collection);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [setting, collection, openEditCollectionModal]);
|
||||
}, [openEditCollectionModal, collection, service]);
|
||||
|
||||
const actions = useMemo<
|
||||
Array<
|
||||
@@ -112,12 +115,12 @@ export const CollectionOperations = ({
|
||||
),
|
||||
name: t['Delete'](),
|
||||
click: () => {
|
||||
setting.deleteCollection(info, collection.id);
|
||||
service.deleteCollection(info, collection.id);
|
||||
},
|
||||
type: 'danger',
|
||||
},
|
||||
],
|
||||
[t, showEditName, showEdit, setting, info, collection.id]
|
||||
[t, showEditName, showEdit, service, info, collection.id]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from './affine-shape';
|
||||
export * from './collection-bar';
|
||||
export * from './collection-list';
|
||||
export * from './collection-operations';
|
||||
export * from './create-collection';
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
||||
import type { Collection } from '@affine/env/filter';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { DeleteIcon, FilterIcon } from '@blocksuite/icons';
|
||||
import { type ReactNode, useMemo } from 'react';
|
||||
|
||||
import type { useCollectionManager } from '../use-collection-manager';
|
||||
|
||||
interface CollectionBarAction {
|
||||
icon: ReactNode;
|
||||
click: () => void;
|
||||
@@ -15,14 +13,12 @@ interface CollectionBarAction {
|
||||
|
||||
export const useActions = ({
|
||||
collection,
|
||||
setting,
|
||||
openEdit,
|
||||
info,
|
||||
onDelete,
|
||||
}: {
|
||||
info: DeleteCollectionInfo;
|
||||
collection: Collection;
|
||||
setting: ReturnType<typeof useCollectionManager>;
|
||||
openEdit: (open: Collection) => void;
|
||||
onDelete: () => void;
|
||||
}) => {
|
||||
const t = useAFFiNEI18N();
|
||||
return useMemo<CollectionBarAction[]>(() => {
|
||||
@@ -39,10 +35,8 @@ export const useActions = ({
|
||||
icon: <DeleteIcon style={{ color: 'var(--affine-error-color)' }} />,
|
||||
name: 'delete',
|
||||
tooltip: t['com.affine.collection-bar.action.tooltip.delete'](),
|
||||
click: () => {
|
||||
setting.deleteCollection(info, collection.id);
|
||||
},
|
||||
click: onDelete,
|
||||
},
|
||||
];
|
||||
}, [info, collection, t, setting, openEdit]);
|
||||
}, [t, onDelete, openEdit, collection]);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { useCollectionManager } from '@affine/core/components/page-list';
|
||||
import {
|
||||
useBlockSuitePageMeta,
|
||||
usePageMetaHelper,
|
||||
@@ -322,9 +321,8 @@ export const collectionToCommand = (
|
||||
|
||||
export const useCollectionsCommands = () => {
|
||||
// todo: considering collections for searching pages
|
||||
const { savedCollections } = useCollectionManager(
|
||||
useService(CollectionService)
|
||||
);
|
||||
const collectionService = useService(CollectionService);
|
||||
const collections = useLiveData(collectionService.collections);
|
||||
const query = useAtomValue(cmdkQueryAtom);
|
||||
const navigationHelper = useNavigateHelper();
|
||||
const t = useAFFiNEI18N();
|
||||
@@ -340,7 +338,7 @@ export const useCollectionsCommands = () => {
|
||||
if (query.trim() === '') {
|
||||
return results;
|
||||
} else {
|
||||
results = savedCollections.map(collection => {
|
||||
results = collections.map(collection => {
|
||||
const command = collectionToCommand(
|
||||
collection,
|
||||
navigationHelper,
|
||||
@@ -352,14 +350,7 @@ export const useCollectionsCommands = () => {
|
||||
});
|
||||
return results;
|
||||
}
|
||||
}, [
|
||||
query,
|
||||
savedCollections,
|
||||
navigationHelper,
|
||||
selectCollection,
|
||||
t,
|
||||
workspace,
|
||||
]);
|
||||
}, [query, collections, navigationHelper, selectCollection, t, workspace]);
|
||||
};
|
||||
|
||||
export const useCMDKCommandGroups = () => {
|
||||
|
||||
@@ -4,18 +4,19 @@ import { allPageFilterSelectAtom } from '@affine/core/atoms';
|
||||
import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper';
|
||||
import { WorkspaceSubPath } from '@affine/core/shared';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { Workspace } from '@toeverything/infra';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import * as styles from './index.css';
|
||||
|
||||
export const WorkspaceModeFilterTab = ({
|
||||
workspaceId,
|
||||
activeFilter,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
activeFilter: AllPageFilterOption;
|
||||
}) => {
|
||||
const workspace = useService(Workspace);
|
||||
const t = useAFFiNEI18N();
|
||||
const [value, setValue] = useState(activeFilter);
|
||||
const [filterMode, setFilterMode] = useAtom(allPageFilterSelectAtom);
|
||||
@@ -24,17 +25,17 @@ export const WorkspaceModeFilterTab = ({
|
||||
(value: AllPageFilterOption) => {
|
||||
switch (value) {
|
||||
case 'collections':
|
||||
jumpToCollections(workspaceId);
|
||||
jumpToCollections(workspace.id);
|
||||
break;
|
||||
case 'tags':
|
||||
jumpToTags(workspaceId);
|
||||
jumpToTags(workspace.id);
|
||||
break;
|
||||
case 'docs':
|
||||
jumpToSubPath(workspaceId, WorkspaceSubPath.ALL);
|
||||
jumpToSubPath(workspace.id, WorkspaceSubPath.ALL);
|
||||
break;
|
||||
}
|
||||
},
|
||||
[jumpToCollections, jumpToSubPath, jumpToTags, workspaceId]
|
||||
[jumpToCollections, jumpToSubPath, jumpToTags, workspace]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
+8
-10
@@ -6,7 +6,6 @@ import {
|
||||
CollectionOperations,
|
||||
filterPage,
|
||||
stopPropagation,
|
||||
useCollectionManager,
|
||||
} from '@affine/core/components/page-list';
|
||||
import { CollectionService } from '@affine/core/modules/collection';
|
||||
import type { Collection, DeleteCollectionInfo } from '@affine/env/filter';
|
||||
@@ -40,20 +39,20 @@ const CollectionRenderer = ({
|
||||
}) => {
|
||||
const [collapsed, setCollapsed] = useState(true);
|
||||
const [open, setOpen] = useState(false);
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const collectionService = useService(CollectionService);
|
||||
const t = useAFFiNEI18N();
|
||||
const dragItemId = getDropItemId('collections', collection.id);
|
||||
|
||||
const removeFromAllowList = useCallback(
|
||||
(id: string) => {
|
||||
setting.updateCollection({
|
||||
collectionService.updateCollection(collection.id, () => ({
|
||||
...collection,
|
||||
allowList: collection.allowList?.filter(v => v !== id),
|
||||
});
|
||||
}));
|
||||
|
||||
toast(t['com.affine.collection.removePage.success']());
|
||||
},
|
||||
[collection, setting, t]
|
||||
[collection, collectionService, t]
|
||||
);
|
||||
|
||||
const { setNodeRef, isOver } = useDroppable({
|
||||
@@ -66,7 +65,7 @@ const CollectionRenderer = ({
|
||||
} else {
|
||||
toast(t['com.affine.collection.addPage.success']());
|
||||
}
|
||||
setting.addPage(collection.id, id);
|
||||
collectionService.addPageToCollection(collection.id, id);
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -95,13 +94,13 @@ const CollectionRenderer = ({
|
||||
|
||||
const onRename = useCallback(
|
||||
(name: string) => {
|
||||
setting.updateCollection({
|
||||
collectionService.updateCollection(collection.id, () => ({
|
||||
...collection,
|
||||
name,
|
||||
});
|
||||
}));
|
||||
toast(t['com.affine.toastMessage.rename']());
|
||||
},
|
||||
[collection, setting, t]
|
||||
[collection, collectionService, t]
|
||||
);
|
||||
const handleOpen = useCallback(() => {
|
||||
setOpen(true);
|
||||
@@ -124,7 +123,6 @@ const CollectionRenderer = ({
|
||||
<CollectionOperations
|
||||
info={info}
|
||||
collection={collection}
|
||||
setting={setting}
|
||||
config={config}
|
||||
openRenameModal={handleOpen}
|
||||
>
|
||||
|
||||
@@ -38,7 +38,6 @@ import { WorkspaceSubPath } from '../../shared';
|
||||
import {
|
||||
createEmptyCollection,
|
||||
MoveToTrash,
|
||||
useCollectionManager,
|
||||
useEditCollectionName,
|
||||
} from '../page-list';
|
||||
import { CollectionsList } from '../pure/workspace-slider-bar/collections';
|
||||
@@ -177,7 +176,7 @@ export const RootAppSidebar = ({
|
||||
useRegisterBrowserHistoryCommands(router.back, router.forward);
|
||||
const userInfo = useDeleteCollectionInfo();
|
||||
|
||||
const setting = useCollectionManager(useService(CollectionService));
|
||||
const collection = useService(CollectionService);
|
||||
const { node, open } = useEditCollectionName({
|
||||
title: t['com.affine.editCollection.createCollection'](),
|
||||
showTips: true,
|
||||
@@ -186,13 +185,13 @@ export const RootAppSidebar = ({
|
||||
open('')
|
||||
.then(name => {
|
||||
const id = nanoid();
|
||||
setting.createCollection(createEmptyCollection(id, { name }));
|
||||
collection.addCollection(createEmptyCollection(id, { name }));
|
||||
navigateHelper.jumpToCollection(blockSuiteWorkspace.id, id);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [blockSuiteWorkspace.id, navigateHelper, open, setting]);
|
||||
}, [blockSuiteWorkspace.id, collection, navigateHelper, open]);
|
||||
|
||||
const allPageActive = useMemo(() => {
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user