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
No known key found for this signature in database
GPG Key ID: 23F23D9E8B3971ED
3 changed files with 169 additions and 38 deletions

View File

@ -178,7 +178,11 @@ export const useSnapshotPage = (
export const historyListGroupByDay = (histories: DocHistory[]) => {
const map = new Map<string, DocHistory[]>();
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) ?? [];
list.push(history);
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
const title = page.meta.title;
if (getPageMeta(pageDocId)?.title !== title) {
setPageTitle(pageDocId, title);
if (getPageMeta(pageId)?.title !== title) {
setPageTitle(pageId, title);
}
await recover({
@ -230,6 +234,7 @@ export const useRestorePage = (workspace: Workspace, pageId: string) => {
getPageMeta,
mutateQueryResource,
page,
pageId,
recover,
setPageTitle,
workspace.id,

View File

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

View File

@ -71,6 +71,44 @@ export const editor = style({
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({
overflow: 'hidden',
height: '100%',
@ -85,7 +123,6 @@ export const historyListScrollable = style({
export const historyListScrollableInner = style({
display: 'flex',
gap: 16,
flexDirection: 'column',
});
@ -102,32 +139,58 @@ export const historyListHeader = style({
export const historyItemGroup = style({
display: 'flex',
flexDirection: 'column',
rowGap: 6,
});
export const historyItemGroupTitle = style({
display: 'flex',
alignItems: 'center',
padding: '12px',
fontWeight: 'bold',
padding: '0 12px 0 4px',
whiteSpace: 'nowrap',
color: 'var(--affine-text-secondary-color)',
gap: 4,
backgroundColor: 'var(--affine-background-primary-color)',
position: 'sticky',
top: 0,
});
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)',
},
height: 28,
':hover': {
background: '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([
historyItem,
{
@ -186,3 +249,27 @@ export const emptyHistoryPromptDescription = style({
fontSize: 'var(--affine-font-xs)',
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)',
},
},
});