Skip to content

Commit

Permalink
feat(core): add page group
Browse files Browse the repository at this point in the history
  • Loading branch information
JimmFly committed Mar 22, 2024
1 parent 85ee223 commit 7ccaf5b
Show file tree
Hide file tree
Showing 11 changed files with 454 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { useCallback, useMemo, useRef, useState } from 'react';

import { usePageHelper } from '../../blocksuite/block-suite-page-list/utils';
import { ListFloatingToolbar } from '../components/list-floating-toolbar';
import { usePageItemGroupDefinitions } from '../group-definitions';
import { pageHeaderColsDef } from '../header-col-def';
import { PageOperationCell } from '../operation-cell';
import { PageListItemRenderer } from '../page-group';
Expand Down Expand Up @@ -180,6 +181,8 @@ export const VirtualizedPageList = ({
hideFloatingToolbar();
}, [filteredSelectedPageIds, hideFloatingToolbar, pageMetas, setTrashModal]);

const group = usePageItemGroupDefinitions();

return (
<>
<VirtualizedList
Expand All @@ -190,6 +193,7 @@ export const VirtualizedPageList = ({
atTopStateChange={setHideHeaderCreateNewPage}
onSelectionActiveChange={setShowFloatingToolbar}
heading={heading}
groupBy={group}
selectedIds={filteredSelectedPageIds}
onSelectedIdsChange={setSelectedPageIds}
items={pageMetasToRender}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css';

export const groupLabelWrapper = style({
display: 'flex',
alignItems: 'center',
gap: '4px',
});

export const tagIcon = style({
width: '8px',
height: '8px',
borderRadius: '50%',
marginLeft: '4px',
marginRight: '6px',
});

export const groupLabel = style({
fontSize: cssVar('fontSm'),
lineHeight: '1.5em',
color: cssVar('textPrimaryColor'),
});

export const pageCount = style({
fontSize: cssVar('fontBase'),
lineHeight: '1.6em',
color: cssVar('textSecondaryColor'),
});

export const favouritedIcon = style({
color: cssVar('primaryColor'),
marginRight: '6px',
fontSize: '16px',
});

export const notFavouritedIcon = style({
color: cssVar('iconColor'),
marginRight: '6px',
fontSize: '16px',
});
172 changes: 172 additions & 0 deletions packages/frontend/core/src/components/page-list/group-definitions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { TagService } from '@affine/core/modules/tag';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { FavoritedIcon, FavoriteIcon } from '@blocksuite/icons';
import type { DocMeta } from '@blocksuite/store';
import { useService } from '@toeverything/infra/di';
import { useLiveData } from '@toeverything/infra/livedata';
import { useAtomValue } from 'jotai';
import { atomWithStorage } from 'jotai/utils';
import { type ReactNode, useMemo } from 'react';

import * as styles from './group-definitions.css';
import type { ItemGroupDefinition, ListItem, PageGroupByType } from './types';
import { type DateKey } from './types';
import { betweenDaysAgo, withinDaysAgo } from './utils';

export const pageGroupByTypeAtom = atomWithStorage<PageGroupByType>(
'pageGroupByType',
'updatedDate'
);

const GroupLabel = ({
label,
count,
icon,
}: {
label: string | ReactNode;
count: number;
icon?: ReactNode;
}) => (
<div className={styles.groupLabelWrapper}>
{icon}
<div className={styles.groupLabel}>{label}</div>
<div className={styles.pageCount}>{` · ${count}`}</div>
</div>
);

// todo: optimize date matchers
export const useDateGroupDefinitions = <T extends ListItem>(
key: DateKey
): ItemGroupDefinition<T>[] => {
const t = useAFFiNEI18N();
return useMemo(
() => [
{
id: 'today',
label: count => (
<GroupLabel label={t['com.affine.today']()} count={count} />
),
match: item =>
withinDaysAgo(new Date(item[key] ?? item.createDate ?? ''), 1),
},
{
id: 'yesterday',
label: count => (
<GroupLabel label={t['com.affine.yesterday']()} count={count} />
),
match: item =>
betweenDaysAgo(new Date(item[key] ?? item.createDate ?? ''), 1, 2),
},
{
id: 'last7Days',
label: count => (
<GroupLabel label={t['com.affine.last7Days']()} count={count} />
),
match: item =>
betweenDaysAgo(new Date(item[key] ?? item.createDate ?? ''), 2, 7),
},
{
id: 'last30Days',
label: count => (
<GroupLabel label={t['com.affine.last30Days']()} count={count} />
),
match: item =>
betweenDaysAgo(new Date(item[key] ?? item.createDate ?? ''), 7, 30),
},
{
id: 'moreThan30Days',
label: count => (
<GroupLabel label={t['com.affine.moreThan30Days']()} count={count} />
),
match: item =>
!withinDaysAgo(new Date(item[key] ?? item.createDate ?? ''), 30),
},
],
[key, t]
);
};
export const useTagGroupDefinitions = (): ItemGroupDefinition<ListItem>[] => {
const tagService = useService(TagService);
const tagMetas = useLiveData(tagService.tagMetas);
return useMemo(() => {
return tagMetas.map(tag => ({
id: tag.id,
label: count => (
<GroupLabel
label={tag.title}
count={count}
icon={
<div
className={styles.tagIcon}
style={{
backgroundColor: tag.color,
}}
></div>
}
/>
),
match: item => (item as DocMeta).tags?.includes(tag.id),
}));
}, [tagMetas]);
};

export const useFavoriteGroupDefinitions = <
T extends ListItem,
>(): ItemGroupDefinition<T>[] => {
const t = useAFFiNEI18N();
return useMemo(
() => [
{
id: 'favourited',
label: count => (
<GroupLabel
label={t['com.affine.page.group-header.favourited']()}
count={count}
icon={<FavoritedIcon className={styles.favouritedIcon} />}
/>
),
match: item => !!(item as DocMeta).favorite,
},
{
id: 'notFavourited',
label: count => (
<GroupLabel
label={t['com.affine.page.group-header.not-favourited']()}
count={count}
icon={<FavoriteIcon className={styles.notFavouritedIcon} />}
/>
),
match: item => !(item as DocMeta).favorite,
},
],
[t]
);
};

export const usePageItemGroupDefinitions = () => {
const key = useAtomValue(pageGroupByTypeAtom);
const tagGroupDefinitions = useTagGroupDefinitions();
const createDateGroupDefinitions = useDateGroupDefinitions('createDate');
const updatedDateGroupDefinitions = useDateGroupDefinitions('updatedDate');
const favouriteGroupDefinitions = useFavoriteGroupDefinitions();

return useMemo(() => {
const itemGroupDefinitions = {
createDate: createDateGroupDefinitions,
updatedDate: updatedDateGroupDefinitions,
tag: tagGroupDefinitions,
favourites: favouriteGroupDefinitions,
none: undefined,

// add more here later
// todo: some page group definitions maybe dynamic
};
return itemGroupDefinitions[key];
}, [
createDateGroupDefinitions,
favouriteGroupDefinitions,
key,
tagGroupDefinitions,
updatedDateGroupDefinitions,
]);
};
1 change: 1 addition & 0 deletions packages/frontend/core/src/components/page-list/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from './docs';
export * from './docs/page-list-item';
export * from './docs/page-tags';
export * from './filter';
export * from './group-definitions';
export * from './header-col-def';
export * from './list';
export * from './operation-cell';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,57 +1,10 @@
import { Trans } from '@affine/i18n';

import type { ItemGroupDefinition, ItemGroupProps, ListItem } from './types';
import { type DateKey } from './types';
import { betweenDaysAgo, withinDaysAgo } from './utils';

// todo: optimize date matchers
const getDateGroupDefinitions = <T extends ListItem>(
key: DateKey
): ItemGroupDefinition<T>[] => [
{
id: 'today',
label: <Trans i18nKey="com.affine.today" />,
match: item =>
withinDaysAgo(new Date(item[key] ?? item.createDate ?? ''), 1),
},
{
id: 'yesterday',
label: <Trans i18nKey="com.affine.yesterday" />,
match: item =>
betweenDaysAgo(new Date(item[key] ?? item.createDate ?? ''), 1, 2),
},
{
id: 'last7Days',
label: <Trans i18nKey="com.affine.last7Days" />,
match: item =>
betweenDaysAgo(new Date(item[key] ?? item.createDate ?? ''), 2, 7),
},
{
id: 'last30Days',
label: <Trans i18nKey="com.affine.last30Days" />,
match: item =>
betweenDaysAgo(new Date(item[key] ?? item.createDate ?? ''), 7, 30),
},
{
id: 'moreThan30Days',
label: <Trans i18nKey="com.affine.moreThan30Days" />,
match: item =>
!withinDaysAgo(new Date(item[key] ?? item.createDate ?? ''), 30),
},
];

const itemGroupDefinitions = {
createDate: getDateGroupDefinitions('createDate'),
updatedDate: getDateGroupDefinitions('updatedDate'),
// add more here later
// todo: some page group definitions maybe dynamic
};

export function itemsToItemGroups<T extends ListItem>(
items: T[],
key?: DateKey
groupDefs?: ItemGroupDefinition<T>[] | false
): ItemGroupProps<T>[] {
if (!key) {
if (!groupDefs) {
return [
{
id: 'all',
Expand All @@ -62,8 +15,12 @@ export function itemsToItemGroups<T extends ListItem>(
}

// assume pages are already sorted, we will use the page order to determine the group order
const groupDefs = itemGroupDefinitions[key];
const groups: ItemGroupProps<T>[] = [];
let groups: ItemGroupProps<T>[] = groupDefs.map(groupDef => ({
id: groupDef.id,
label: undefined, // Will be set later
items: [],
allItems: items,
}));

for (const item of items) {
// for a single page, there could be multiple groups that it belongs to
Expand All @@ -72,19 +29,24 @@ export function itemsToItemGroups<T extends ListItem>(
const group = groups.find(g => g.id === groupDef.id);
if (group) {
group.items.push(item);
} else {
const label =
typeof groupDef.label === 'function'
? groupDef.label()
: groupDef.label;
groups.push({
id: groupDef.id,
label: label,
items: [item],
allItems: items,
});
}
}
}

// Now that all items have been added to groups, we can get the correct label for each group
groups = groups
.map(group => {
const groupDef = groupDefs.find(def => def.id === group.id);
if (groupDef) {
if (typeof groupDef.label === 'function') {
group.label = groupDef.label(group.items.length);
} else {
group.label = groupDef.label;
}
}
return group;
})
.filter(group => group.items.length > 0);

return groups;
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export const header = style({
display: 'flex',
alignItems: 'center',
padding: '0px 16px 0px 6px',
gap: 4,
height: '28px',
background: cssVar('backgroundPrimaryColor'),
':hover': {
Expand Down Expand Up @@ -88,6 +87,8 @@ export const selectAllButton = style({
});
export const collapsedIcon = style({
opacity: 0,
fontSize: '20px',
color: cssVar('iconColor'),
transition: 'transform 0.2s ease-in-out',
selectors: {
'&[data-collapsed="false"]': {
Expand All @@ -99,8 +100,6 @@ export const collapsedIcon = style({
},
});
export const collapsedIconContainer = style({
width: '16px',
height: '16px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,6 @@ export const groupsAtom = atom(get => {

if (groupBy === false) {
groupBy = undefined;
} else if (groupBy === undefined) {
groupBy =
sorter.key === 'createDate' || sorter.key === 'updatedDate'
? sorter.key
: // default sort
!sorter.key
? DEFAULT_SORT_KEY
: undefined;
}
return itemsToItemGroups<ListItem>(sorter.items, groupBy);
});
Expand Down
Loading

0 comments on commit 7ccaf5b

Please sign in to comment.