feat(core): reuse select-page for edit collection pages-mode (#7548)

This commit is contained in:
CatsJuice
2024-07-19 04:47:48 +00:00
parent 544236f1a0
commit 239de4c283
3 changed files with 55 additions and 206 deletions
@@ -8,8 +8,8 @@ import type { ReactNode } from 'react';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import * as styles from './edit-collection.css'; import * as styles from './edit-collection.css';
import { PagesMode } from './pages-mode';
import { RulesMode } from './rules-mode'; import { RulesMode } from './rules-mode';
import { SelectPage } from './select-page';
export type EditCollectionMode = 'page' | 'rule'; export type EditCollectionMode = 'page' | 'rule';
@@ -110,6 +110,12 @@ export const EditCollection = ({
allowList: init.allowList, allowList: init.allowList,
}); });
}, [init.allowList, init.filterList, value]); }, [init.allowList, init.filterList, value]);
const onIdsChange = useCallback(
(ids: string[]) => {
onChange({ ...value, allowList: ids });
},
[value]
);
const buttons = useMemo( const buttons = useMemo(
() => ( () => (
<> <>
@@ -164,13 +170,13 @@ export const EditCollection = ({
className={styles.collectionEditContainer} className={styles.collectionEditContainer}
> >
{mode === 'page' ? ( {mode === 'page' ? (
<PagesMode <SelectPage
collection={value}
updateCollection={onChange}
switchMode={switchMode}
buttons={buttons}
allPageListConfig={config} allPageListConfig={config}
></PagesMode> init={value.allowList}
onChange={onIdsChange}
header={switchMode}
buttons={buttons}
/>
) : ( ) : (
<RulesMode <RulesMode
allPageListConfig={config} allPageListConfig={config}
@@ -1,178 +0,0 @@
import { Menu } from '@affine/component';
import { FavoriteItemsAdapter } from '@affine/core/modules/properties';
import type { Collection } from '@affine/env/filter';
import { useI18n } from '@affine/i18n';
import { FilterIcon } from '@blocksuite/icons/rc';
import type { DocMeta } from '@blocksuite/store';
import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx';
import type { ReactNode } from 'react';
import { useCallback } from 'react';
import { FilterList } from '../../filter/filter-list';
import { VariableSelect } from '../../filter/vars';
import { usePageHeaderColsDef } from '../../header-col-def';
import { PageListItemRenderer } from '../../page-group';
import { ListTableHeader } from '../../page-header';
import type { ListItem } from '../../types';
import { VirtualizedList } from '../../virtualized-list';
import type { AllPageListConfig } from './edit-collection';
import * as styles from './edit-collection.css';
import { EmptyList } from './select-page';
import { useFilter } from './use-filter';
import { useSearch } from './use-search';
export const PagesMode = ({
switchMode,
collection,
updateCollection,
buttons,
allPageListConfig,
}: {
collection: Collection;
updateCollection: (collection: Collection) => void;
buttons: ReactNode;
switchMode: ReactNode;
allPageListConfig: AllPageListConfig;
}) => {
const t = useI18n();
const favAdapter = useService(FavoriteItemsAdapter);
const favorites = useLiveData(favAdapter.favorites$);
const {
showFilter,
filters,
updateFilters,
clickFilter,
createFilter,
filteredList,
} = useFilter(
allPageListConfig.allPages.map(meta => ({
meta,
publicMode: allPageListConfig.getPublicMode(meta.id),
favorite: favorites.some(f => f.id === meta.id),
}))
);
const pageHeaderColsDef = usePageHeaderColsDef();
const { searchText, updateSearchText, searchedList } =
useSearch(filteredList);
const clearSelected = useCallback(() => {
updateCollection({
...collection,
allowList: [],
});
}, [collection, updateCollection]);
const pageOperationsRenderer = useCallback(
(item: ListItem) => {
const page = item as DocMeta;
return allPageListConfig.favoriteRender(page);
},
[allPageListConfig]
);
const pageItemRenderer = useCallback((item: ListItem) => {
return <PageListItemRenderer {...item} />;
}, []);
const pageHeaderRenderer = useCallback(() => {
return <ListTableHeader headerCols={pageHeaderColsDef} />;
}, [pageHeaderColsDef]);
return (
<>
<input
value={searchText}
onChange={e => updateSearchText(e.target.value)}
className={styles.rulesTitle}
style={{
color: 'var(--affine-text-primary-color)',
}}
placeholder={t['com.affine.editCollection.search.placeholder']()}
></input>
<div className={styles.pagesList}>
<div className={styles.pagesTab}>
<div className={styles.pagesTabContent}>
{switchMode}
{!showFilter && filters.length === 0 ? (
<Menu
items={
<VariableSelect
propertiesMeta={
allPageListConfig.docCollection.meta.properties
}
selected={filters}
onSelect={createFilter}
/>
}
>
<div>
<FilterIcon
className={clsx(styles.icon, styles.button)}
onClick={clickFilter}
width={24}
height={24}
></FilterIcon>
</div>
</Menu>
) : (
<FilterIcon
className={clsx(styles.icon, styles.button)}
onClick={clickFilter}
width={24}
height={24}
></FilterIcon>
)}
</div>
{showFilter ? (
<div style={{ padding: '12px 16px 16px' }}>
<FilterList
propertiesMeta={allPageListConfig.docCollection.meta.properties}
value={filters}
onChange={updateFilters}
/>
</div>
) : null}
{searchedList.length ? (
<VirtualizedList
className={styles.pageList}
items={searchedList}
docCollection={allPageListConfig.docCollection}
selectable
onSelectedIdsChange={ids => {
updateCollection({
...collection,
allowList: ids,
});
}}
itemRenderer={pageItemRenderer}
operationsRenderer={pageOperationsRenderer}
headerRenderer={pageHeaderRenderer}
selectedIds={collection.allowList}
isPreferredEdgeless={allPageListConfig.isEdgeless}
/>
) : (
<EmptyList search={searchText} />
)}
</div>
</div>
<div className={styles.pagesBottom}>
<div className={styles.pagesBottomLeft}>
<div className={styles.selectedCountTips}>
{t['com.affine.selectPage.selected']()}
<span
style={{ marginLeft: 7 }}
className={styles.previewCountTipsHighlight}
>
{collection.allowList.length}
</span>
</div>
<div
className={clsx(styles.button, styles.bottomButton)}
style={{ fontSize: 12, lineHeight: '20px' }}
onClick={clearSelected}
>
{t['com.affine.editCollection.pages.clear']()}
</div>
</div>
<div>{buttons}</div>
</div>
</>
);
};
@@ -5,7 +5,7 @@ import { FilterIcon } from '@blocksuite/icons/rc';
import type { DocMeta } from '@blocksuite/store'; import type { DocMeta } from '@blocksuite/store';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import clsx from 'clsx'; import clsx from 'clsx';
import { useCallback, useState } from 'react'; import { type ReactNode, useCallback, useState } from 'react';
import { FilterList } from '../../filter'; import { FilterList } from '../../filter';
import { VariableSelect } from '../../filter/vars'; import { VariableSelect } from '../../filter/vars';
@@ -25,20 +25,35 @@ export const SelectPage = ({
init, init,
onConfirm, onConfirm,
onCancel, onCancel,
onChange: propsOnChange,
confirmText,
header,
buttons,
}: { }: {
allPageListConfig: AllPageListConfig; allPageListConfig: AllPageListConfig;
init: string[]; init: string[];
onConfirm: (pageIds: string[]) => void; onConfirm?: (pageIds: string[]) => void;
onCancel: () => void; onCancel?: () => void;
onChange?: (values: string[]) => void;
confirmText?: ReactNode;
header?: ReactNode;
buttons?: ReactNode;
}) => { }) => {
const t = useI18n(); const t = useI18n();
const [value, onChange] = useState(init); const [value, setValue] = useState(init);
const onChange = useCallback(
(value: string[]) => {
propsOnChange?.(value);
setValue(value);
},
[propsOnChange]
);
const confirm = useCallback(() => { const confirm = useCallback(() => {
onConfirm(value); onConfirm?.(value);
}, [value, onConfirm]); }, [value, onConfirm]);
const clearSelected = useCallback(() => { const clearSelected = useCallback(() => {
onChange([]); onChange([]);
}, []); }, [onChange]);
const favAdapter = useService(FavoriteItemsAdapter); const favAdapter = useService(FavoriteItemsAdapter);
const favourites = useLiveData(favAdapter.favorites$); const favourites = useLiveData(favAdapter.favorites$);
const pageHeaderColsDef = usePageHeaderColsDef(); const pageHeaderColsDef = usePageHeaderColsDef();
@@ -85,9 +100,11 @@ export const SelectPage = ({
></input> ></input>
<div className={styles.pagesTab}> <div className={styles.pagesTab}>
<div className={styles.pagesTabContent}> <div className={styles.pagesTabContent}>
<div style={{ fontSize: 12, lineHeight: '20px', fontWeight: 600 }}> {header ?? (
{t['com.affine.selectPage.title']()} <div style={{ fontSize: 12, lineHeight: '20px', fontWeight: 600 }}>
</div> {t['com.affine.selectPage.title']()}
</div>
)}
{!showFilter && filters.length === 0 ? ( {!showFilter && filters.length === 0 ? (
<Menu <Menu
items={ items={
@@ -164,18 +181,22 @@ export const SelectPage = ({
</div> </div>
</div> </div>
<div> <div>
<Button size="large" onClick={onCancel}> {buttons ?? (
{t['com.affine.editCollection.button.cancel']()} <>
</Button> <Button size="large" onClick={onCancel}>
<Button {t['com.affine.editCollection.button.cancel']()}
className={styles.confirmButton} </Button>
size="large" <Button
data-testid="save-collection" className={styles.confirmButton}
type="primary" size="large"
onClick={confirm} data-testid="save-collection"
> type="primary"
{t['Confirm']()} onClick={confirm}
</Button> >
{confirmText ?? t['Confirm']()}
</Button>
</>
)}
</div> </div>
</div> </div>
</div> </div>