refactor!: next generation AFFiNE code structure (#1176)

This commit is contained in:
Himself65
2023-03-01 01:40:01 -06:00
committed by GitHub
parent 2dcccc772c
commit e0481d29ad
270 changed files with 8308 additions and 6829 deletions

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { BlockSuiteWorkspace } from '../../../shared';
import PageList from './page-list';
export type BlockSuitePageListProps = {
blockSuiteWorkspace: BlockSuiteWorkspace;
onOpenPage: (pageId: string, newTab?: boolean) => void;
};
export const BlockSuitePageList: React.FC<BlockSuitePageListProps> = ({
blockSuiteWorkspace,
onOpenPage,
}) => {
return (
<PageList
blockSuiteWorkspace={blockSuiteWorkspace}
onClickPage={onOpenPage}
listType="all"
/>
);
};
export const BlockSuitePublicPageList: React.FC<BlockSuitePageListProps> = ({
blockSuiteWorkspace,
onOpenPage,
}) => {
return (
<PageList
isPublic={true}
blockSuiteWorkspace={blockSuiteWorkspace}
onClickPage={onOpenPage}
listType="all"
/>
);
};

View File

@@ -0,0 +1,27 @@
import { TableCell, TableCellProps } from '@affine/component';
import { PageMeta } from '@blocksuite/store';
import dayjs from 'dayjs';
import localizedFormat from 'dayjs/plugin/localizedFormat';
import React from 'react';
dayjs.extend(localizedFormat);
export const DateCell = ({
pageMeta,
dateKey,
backupKey = '',
...props
}: {
pageMeta: PageMeta;
dateKey: keyof PageMeta;
backupKey?: keyof PageMeta;
} & Omit<TableCellProps, 'children'>) => {
const value = pageMeta[dateKey] ?? pageMeta[backupKey];
return (
<TableCell ellipsis={true} {...props}>
{value ? dayjs(value as string).format('YYYY-MM-DD HH:mm') : '--'}
</TableCell>
);
};
export default DateCell;

View File

@@ -0,0 +1,27 @@
import { Empty } from '@affine/component';
import { useTranslation } from '@affine/i18n';
import React from 'react';
export const PageListEmpty = (props: { listType?: string }) => {
const { listType } = props;
const { t } = useTranslation();
const getEmptyDescription = () => {
if (listType === 'all') {
return t('emptyAllPages');
}
if (listType === 'favorite') {
return t('emptyFavorite');
}
if (listType === 'trash') {
return t('emptyTrash');
}
};
return (
<div style={{ height: 'calc(100% - 60px)' }}>
<Empty description={getEmptyDescription()} />
</div>
);
};
export default PageListEmpty;

View File

@@ -0,0 +1,171 @@
import {
Confirm,
FlexWrapper,
IconButton,
Menu,
MenuItem,
Tooltip,
} from '@affine/component';
import { toast } from '@affine/component';
import { useTranslation } from '@affine/i18n';
import {
DeletePermanentlyIcon,
DeleteTemporarilyIcon,
FavoritedIcon,
FavoriteIcon,
MoreVerticalIcon,
OpenInNewIcon,
ResetIcon,
} from '@blocksuite/icons';
import { PageMeta } from '@blocksuite/store';
import React, { useState } from 'react';
export type OperationCellProps = {
pageMeta: PageMeta;
onOpenPageInNewTab: (pageId: string) => void;
onToggleFavoritePage: (pageId: string) => void;
onToggleTrashPage: (pageId: string) => void;
};
export const OperationCell: React.FC<OperationCellProps> = ({
pageMeta,
onOpenPageInNewTab,
onToggleFavoritePage,
onToggleTrashPage,
}) => {
const { id, favorite } = pageMeta;
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const OperationMenu = (
<>
<MenuItem
onClick={() => {
onToggleFavoritePage(id);
toast(
favorite ? t('Removed from Favorites') : t('Added to Favorites')
);
}}
icon={favorite ? <FavoritedIcon /> : <FavoriteIcon />}
>
{favorite ? t('Remove from favorites') : t('Add to Favorites')}
</MenuItem>
<MenuItem
onClick={() => {
onOpenPageInNewTab(id);
}}
icon={<OpenInNewIcon />}
>
{t('Open in new tab')}
</MenuItem>
<MenuItem
onClick={() => {
setOpen(true);
}}
icon={<DeleteTemporarilyIcon />}
>
{t('Delete')}
</MenuItem>
</>
);
return (
<>
<FlexWrapper alignItems="center" justifyContent="center">
<Menu
content={OperationMenu}
placement="bottom-end"
disablePortal={true}
trigger="click"
>
<IconButton darker={true}>
<MoreVerticalIcon />
</IconButton>
</Menu>
</FlexWrapper>
<Confirm
open={open}
title={t('Delete page?')}
content={t('will be permanently deleted', {
title: pageMeta.title || 'Untitled',
})}
confirmText={t('Delete')}
confirmType="danger"
onConfirm={() => {
onToggleTrashPage(id);
toast(t('Deleted'));
setOpen(false);
}}
onClose={() => {
setOpen(false);
}}
onCancel={() => {
setOpen(false);
}}
/>
</>
);
};
export type TrashOperationCellProps = {
pageMeta: PageMeta;
onPermanentlyDeletePage: (pageId: string) => void;
onRestorePage: (pageId: string) => void;
onOpenPage: (pageId: string) => void;
};
export const TrashOperationCell: React.FC<TrashOperationCellProps> = ({
pageMeta,
onPermanentlyDeletePage,
onRestorePage,
onOpenPage,
}) => {
const { id, title } = pageMeta;
// const { openPage, getPageMeta } = usePageHelper();
// const { toggleDeletePage, permanentlyDeletePage } = usePageHelper();
// const confirm = useConfirm(store => store.confirm);
const { t } = useTranslation();
const [open, setOpen] = useState(false);
return (
<FlexWrapper>
<Tooltip content={t('Restore it')} placement="top-start">
<IconButton
darker={true}
style={{ marginRight: '12px' }}
onClick={() => {
onRestorePage(id);
toast(t('restored', { title: title || 'Untitled' }));
onOpenPage(id);
}}
>
<ResetIcon />
</IconButton>
</Tooltip>
<Tooltip content={t('Delete permanently')} placement="top-start">
<IconButton
darker={true}
onClick={() => {
setOpen(true);
}}
>
<DeletePermanentlyIcon />
</IconButton>
</Tooltip>
<Confirm
title={t('Delete permanently?')}
content={t("Once deleted, you can't undo this action.")}
confirmText={t('Delete')}
confirmType="danger"
open={open}
onConfirm={() => {
onPermanentlyDeletePage(id);
toast(t('Permanently deleted'));
setOpen(false);
}}
onClose={() => {
setOpen(false);
}}
onCancel={() => {
setOpen(false);
}}
/>
</FlexWrapper>
);
};

View File

@@ -0,0 +1,216 @@
import {
Table,
TableBody,
TableCell,
TableHead,
TableRow,
} from '@affine/component';
import { Content, IconButton, toast, Tooltip } from '@affine/component';
import { useTranslation } from '@affine/i18n';
import {
EdgelessIcon,
FavoritedIcon,
FavoriteIcon,
PaperIcon,
} from '@blocksuite/icons';
import { PageMeta } from '@blocksuite/store';
import { useMediaQuery, useTheme as useMuiTheme } from '@mui/material';
import React, { useMemo } from 'react';
import {
usePageMeta,
usePageMetaHelper,
} from '../../../../hooks/use-page-meta';
import { useTheme } from '../../../../providers/ThemeProvider';
import { BlockSuiteWorkspace } from '../../../../shared';
import DateCell from './DateCell';
import Empty from './Empty';
import { OperationCell, TrashOperationCell } from './OperationCell';
import {
StyledTableContainer,
StyledTableRow,
StyledTitleLink,
StyledTitleWrapper,
} from './styles';
const FavoriteTag = ({
pageMeta: { favorite, id },
}: {
pageMeta: PageMeta;
}) => {
const { theme } = useTheme();
const { t } = useTranslation();
return (
<Tooltip
content={favorite ? t('Favorited') : t('Favorite')}
placement="top-start"
>
<IconButton
darker={true}
iconSize={[20, 20]}
onClick={e => {
e.stopPropagation();
// toggleFavoritePage(id);
toast(
favorite ? t('Removed from Favorites') : t('Added to Favorites')
);
}}
style={{
color: favorite ? theme.colors.primaryColor : theme.colors.iconColor,
}}
className={favorite ? '' : 'favorite-button'}
>
{favorite ? (
<FavoritedIcon data-testid="favorited-icon" />
) : (
<FavoriteIcon />
)}
</IconButton>
</Tooltip>
);
};
type PageListProps = {
blockSuiteWorkspace: BlockSuiteWorkspace;
isPublic?: boolean;
listType?: 'all' | 'trash' | 'favorite';
onClickPage: (pageId: string, newTab?: boolean) => void;
};
const filter = {
all: (pageMeta: PageMeta) => !pageMeta.trash,
trash: (pageMeta: PageMeta) => pageMeta.trash,
favorite: (pageMeta: PageMeta) => pageMeta.favorite,
};
export const PageList: React.FC<PageListProps> = ({
blockSuiteWorkspace,
isPublic = false,
listType,
onClickPage,
}) => {
const pageList = usePageMeta(blockSuiteWorkspace);
const helper = usePageMetaHelper(blockSuiteWorkspace);
const { t } = useTranslation();
const theme = useMuiTheme();
const matches = useMediaQuery(theme.breakpoints.up('sm'));
const isTrash = listType === 'trash';
const list = useMemo(
() => pageList.filter(filter[listType ?? 'all']),
[pageList, listType]
);
if (list.length === 0) {
return <Empty listType={listType} />;
}
return (
<StyledTableContainer>
<Table>
<TableHead>
<TableRow>
{matches && (
<>
<TableCell proportion={0.5}>{t('Title')}</TableCell>
<TableCell proportion={0.2}>{t('Created')}</TableCell>
<TableCell proportion={0.2}>
{isTrash ? t('Moved to Trash') : t('Updated')}
</TableCell>
<TableCell proportion={0.1}></TableCell>
</>
)}
</TableRow>
</TableHead>
<TableBody>
{list.map((pageMeta, index) => {
return (
<StyledTableRow
data-testid={`page-list-item-${pageMeta.id}}`}
key={`${pageMeta.id}-${index}`}
>
<TableCell
onClick={() => {
onClickPage(pageMeta.id);
}}
>
<StyledTitleWrapper>
<StyledTitleLink>
{pageMeta.mode === 'edgeless' ? (
<EdgelessIcon />
) : (
<PaperIcon />
)}
<Content ellipsis={true} color="inherit">
{pageMeta.title || t('Untitled')}
</Content>
</StyledTitleLink>
{!isTrash && <FavoriteTag pageMeta={pageMeta} />}
</StyledTitleWrapper>
</TableCell>
{matches && (
<>
<DateCell
pageMeta={pageMeta}
dateKey="createDate"
onClick={() => {
onClickPage(pageMeta.id);
}}
/>
<DateCell
pageMeta={pageMeta}
dateKey={isTrash ? 'trashDate' : 'updatedDate'}
backupKey={isTrash ? 'trashDate' : 'createDate'}
onClick={() => {
onClickPage(pageMeta.id);
}}
/>
{!isPublic && (
<TableCell
style={{ padding: 0 }}
data-testid={`more-actions-${pageMeta.id}`}
>
{isTrash ? (
<TrashOperationCell
pageMeta={pageMeta}
onPermanentlyDeletePage={pageId => {
blockSuiteWorkspace.removePage(pageId);
}}
onRestorePage={() => {
helper.setPageMeta(pageMeta.id, {
trash: false,
});
}}
onOpenPage={pageId => {
onClickPage(pageId, false);
}}
/>
) : (
<OperationCell
pageMeta={pageMeta}
onOpenPageInNewTab={pageId => {
onClickPage(pageId, true);
}}
onToggleFavoritePage={(pageId: string) => {
helper.setPageMeta(pageId, {
favorite: !pageMeta.favorite,
});
}}
onToggleTrashPage={() => {
helper.setPageMeta(pageMeta.id, {
trash: !pageMeta.trash,
});
}}
/>
)}
</TableCell>
)}
</>
)}
</StyledTableRow>
);
})}
</TableBody>
</Table>
</StyledTableContainer>
);
};
export default PageList;

View File

@@ -0,0 +1,51 @@
import { displayFlex, styled } from '@affine/component';
import { TableRow } from '@affine/component';
export const StyledTableContainer = styled.div(() => {
return {
height: 'calc(100vh - 60px)',
padding: '78px 72px',
overflowY: 'auto',
};
});
export const StyledTitleWrapper = styled.div(({ theme }) => {
return {
...displayFlex('flex-start', 'center'),
a: {
color: 'inherit',
},
'a:visited': {
color: 'unset',
},
'a:hover': {
color: theme.colors.primaryColor,
},
};
});
export const StyledTitleLink = styled.div(({ theme }) => {
return {
maxWidth: '80%',
marginRight: '18px',
...displayFlex('flex-start', 'center'),
color: theme.colors.textColor,
'>svg': {
fontSize: '24px',
marginRight: '12px',
color: theme.colors.iconColor,
},
};
});
export const StyledTableRow = styled(TableRow)(() => {
return {
cursor: 'pointer',
'.favorite-button': {
display: 'none',
},
'&:hover': {
'.favorite-button': {
display: 'flex',
},
},
};
});