feat(core): optimize history list item ui (#5440)

![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/T2klNLEk0wxLh4NRDzhk/fa1b3626-4c07-411a-ae15-680b1ad70522.png)

fix TOV-90
This commit is contained in:
Peng Xiao
2024-01-02 05:46:56 +00:00
parent b92f2cb29a
commit b84494ef86
3 changed files with 169 additions and 38 deletions

View File

@@ -178,7 +178,11 @@ export const useSnapshotPage = (
export const historyListGroupByDay = (histories: DocHistory[]) => { export const historyListGroupByDay = (histories: DocHistory[]) => {
const map = new Map<string, DocHistory[]>(); const map = new Map<string, DocHistory[]>();
for (const history of histories) { for (const history of histories) {
const day = new Date(history.timestamp).toLocaleDateString(); const day = new Date(history.timestamp).toLocaleDateString(undefined, {
year: 'numeric',
month: 'short',
day: 'numeric',
});
const list = map.get(day) ?? []; const list = map.get(day) ?? [];
list.push(history); list.push(history);
map.set(day, list); map.set(day, list);
@@ -208,8 +212,8 @@ export const useRestorePage = (workspace: Workspace, pageId: string) => {
// should also update the page title, since it may be changed in the history // should also update the page title, since it may be changed in the history
const title = page.meta.title; const title = page.meta.title;
if (getPageMeta(pageDocId)?.title !== title) { if (getPageMeta(pageId)?.title !== title) {
setPageTitle(pageDocId, title); setPageTitle(pageId, title);
} }
await recover({ await recover({
@@ -230,6 +234,7 @@ export const useRestorePage = (workspace: Workspace, pageId: string) => {
getPageMeta, getPageMeta,
mutateQueryResource, mutateQueryResource,
page, page,
pageId,
recover, recover,
setPageTitle, setPageTitle,
workspace.id, workspace.id,

View File

@@ -1,4 +1,4 @@
import { Scrollable } from '@affine/component'; import { Loading, Scrollable } from '@affine/component';
import { import {
BlockSuiteEditor, BlockSuiteEditor,
EditorLoading, EditorLoading,
@@ -8,11 +8,14 @@ import { ConfirmModal, Modal } from '@affine/component/ui/modal';
import type { PageMode } from '@affine/core/atoms'; import type { PageMode } from '@affine/core/atoms';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom'; import { waitForCurrentWorkspaceAtom } from '@affine/workspace/atom';
import { ToggleCollapseIcon } from '@blocksuite/icons';
import type { Page, Workspace } from '@blocksuite/store'; import type { Page, Workspace } from '@blocksuite/store';
import * as Collapsible from '@radix-ui/react-collapsible';
import type { DialogContentProps } from '@radix-ui/react-dialog'; import type { DialogContentProps } from '@radix-ui/react-dialog';
import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks'; import { useAsyncCallback } from '@toeverything/hooks/affine-async-hooks';
import { useAtom, useAtomValue } from 'jotai'; import { useAtom, useAtomValue } from 'jotai';
import { import {
Fragment,
type PropsWithChildren, type PropsWithChildren,
Suspense, Suspense,
useCallback, useCallback,
@@ -142,7 +145,9 @@ const HistoryEditorPreview = ({
onModeChange={onModeChange} onModeChange={onModeChange}
/> />
) : ( ) : (
<EditorLoading /> <div className={styles.loadingContainer}>
<Loading size={24} />
</div>
)} )}
</div> </div>
); );
@@ -167,6 +172,8 @@ const PageHistoryList = ({
return historyListGroupByDay(historyList); return historyListGroupByDay(historyList);
}, [historyList]); }, [historyList]);
const [collapsedMap, setCollapsedMap] = useState<Record<number, boolean>>({});
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
useLayoutEffect(() => { useLayoutEffect(() => {
@@ -182,25 +189,57 @@ const PageHistoryList = ({
</div> </div>
<Scrollable.Root className={styles.historyListScrollable}> <Scrollable.Root className={styles.historyListScrollable}>
<Scrollable.Viewport className={styles.historyListScrollableInner}> <Scrollable.Viewport className={styles.historyListScrollableInner}>
{historyListByDay.map(([day, list]) => { {historyListByDay.map(([day, list], i) => {
const collapsed = collapsedMap[i];
return ( return (
<div key={day} className={styles.historyItemGroup}> <Collapsible.Root
<div className={styles.historyItemGroupTitle}>{day}</div> open={!collapsed}
{list.map(history => ( key={day}
className={styles.historyItemGroup}
>
<Collapsible.Trigger
role="button"
onClick={() =>
setCollapsedMap(prev => ({ ...prev, [i]: !collapsed }))
}
className={styles.historyItemGroupTitle}
>
<div <div
className={styles.historyItem} data-testid="page-list-group-header-collapsed-button"
key={history.timestamp} className={styles.collapsedIconContainer}
data-testid="version-history-item"
onClick={e => {
e.stopPropagation();
onVersionChange(history.timestamp);
}}
data-active={activeVersion === history.timestamp}
> >
<button>{timestampToLocalTime(history.timestamp)}</button> <ToggleCollapseIcon
className={styles.collapsedIcon}
data-collapsed={!!collapsed}
/>
</div> </div>
))} {day}
</div> </Collapsible.Trigger>
<Collapsible.Content>
{list.map((history, idx) => {
return (
<Fragment key={history.timestamp}>
<div
className={styles.historyItem}
data-testid="version-history-item"
onClick={e => {
e.stopPropagation();
onVersionChange(history.timestamp);
}}
data-active={activeVersion === history.timestamp}
>
<button>
{timestampToLocalTime(history.timestamp)}
</button>
</div>
{idx > list.length - 1 ? (
<div className={styles.historyItemGap} />
) : null}
</Fragment>
);
})}
</Collapsible.Content>
</Collapsible.Root>
); );
})} })}
{loadMore ? ( {loadMore ? (

View File

@@ -71,6 +71,44 @@ export const editor = style({
overflow: 'hidden', overflow: 'hidden',
}); });
export const rowWrapper = style({
display: 'flex',
height: '100%',
position: 'relative',
overflow: 'hidden',
':before': {
content: '""',
width: 1,
height: '100%',
backgroundColor: 'var(--affine-border-color)',
position: 'absolute',
left: 16,
top: 0,
bottom: 0,
transform: 'translate(-50%)',
},
selectors: {
'&:is(:last-of-type, :first-of-type):not(:last-of-type:first-of-type)::before':
{
height: '50%',
},
'&:last-of-type:first-of-type::before': {
display: 'none',
},
'&:first-of-type::before': {
top: '50%',
},
},
});
export const loadingContainer = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100%',
backgroundColor: 'var(--affine-background-primary-color)',
});
export const historyList = style({ export const historyList = style({
overflow: 'hidden', overflow: 'hidden',
height: '100%', height: '100%',
@@ -85,7 +123,6 @@ export const historyListScrollable = style({
export const historyListScrollableInner = style({ export const historyListScrollableInner = style({
display: 'flex', display: 'flex',
gap: 16,
flexDirection: 'column', flexDirection: 'column',
}); });
@@ -102,32 +139,58 @@ export const historyListHeader = style({
export const historyItemGroup = style({ export const historyItemGroup = style({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
rowGap: 6,
}); });
export const historyItemGroupTitle = style({ export const historyItemGroupTitle = style({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
padding: '12px', padding: '0 12px 0 4px',
fontWeight: 'bold', whiteSpace: 'nowrap',
color: 'var(--affine-text-secondary-color)',
gap: 4,
backgroundColor: 'var(--affine-background-primary-color)', backgroundColor: 'var(--affine-background-primary-color)',
position: 'sticky', height: 28,
top: 0, ':hover': {
}); background: 'var(--affine-hover-color)',
export const historyItem = style({
display: 'flex',
alignItems: 'center',
padding: '0 12px',
height: 32,
cursor: 'pointer',
selectors: {
'&:hover, &[data-active=true]': {
backgroundColor: 'var(--affine-hover-color)',
},
}, },
}); });
export const historyItem = style([
rowWrapper,
{
display: 'flex',
alignItems: 'center',
padding: '0 32px',
height: 30,
cursor: 'pointer',
selectors: {
'&:hover, &[data-active=true]': {
backgroundColor: 'var(--affine-hover-color)',
},
// draw circle
'&::after': {
content: '""',
width: 7,
height: 7,
backgroundColor: 'var(--affine-background-secondary-color)',
borderRadius: '50%',
border: '1px solid var(--affine-border-color)',
position: 'absolute',
left: 16,
top: '50%',
bottom: 0,
transform: 'translate(-50%, -50%)',
},
'&[data-active=true]::after': {
backgroundColor: 'var(--affine-primary-color)',
borderColor: 'var(--affine-black-30)',
},
},
},
]);
export const historyItemGap = style([rowWrapper, { height: 16 }]);
export const historyItemLoadMore = style([ export const historyItemLoadMore = style([
historyItem, historyItem,
{ {
@@ -186,3 +249,27 @@ export const emptyHistoryPromptDescription = style({
fontSize: 'var(--affine-font-xs)', fontSize: 'var(--affine-font-xs)',
color: 'var(--affine-text-secondary-color)', color: 'var(--affine-text-secondary-color)',
}); });
export const collapsedIcon = style({
transition: 'transform 0.2s ease-in-out',
selectors: {
'&[data-collapsed="false"]': {
transform: 'rotate(90deg)',
},
},
});
export const collapsedIconContainer = style({
fontSize: 24,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '2px',
transition: 'transform 0.2s',
color: 'inherit',
selectors: {
'&[data-collapsed="true"]': {
transform: 'rotate(-90deg)',
},
},
});