mirror of
https://github.com/toeverything/AFFiNE.git
synced 2024-12-01 21:59:04 +03:00
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:
parent
b92f2cb29a
commit
b84494ef86
@ -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,
|
||||
|
@ -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 ? (
|
||||
|
@ -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)',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user