feat(core): adjust history modal styles (#11675)

- add avatar/name info to each history snapshot item
- add avatar to audio transcription job owner

fix AF-2483

![image.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/T2klNLEk0wxLh4NRDzhk/4a2b88c2-311a-470e-a8a9-113931cdf17e.png)
This commit is contained in:
pengx17
2025-04-15 07:16:52 +00:00
parent e02b159bf4
commit 7b2ae7f573
7 changed files with 137 additions and 103 deletions

View File

@@ -1,18 +0,0 @@
import { useEffect, useState } from 'react';
export const useBlobUrl = (buffer?: Buffer) => {
const [blobUrl, setBlobUrl] = useState<string | null>(null);
useEffect(() => {
if (!buffer) {
return;
}
const url = URL.createObjectURL(new Blob([buffer]));
setBlobUrl(url);
return () => {
URL.revokeObjectURL(url);
};
}, [buffer]);
return blobUrl;
};

View File

@@ -1,4 +1,4 @@
import { Loading, Scrollable } from '@affine/component'; import { Avatar, Loading, Scrollable } from '@affine/component';
import { EditorLoading } from '@affine/component/page-detail-skeleton'; import { EditorLoading } from '@affine/component/page-detail-skeleton';
import { Button, IconButton } from '@affine/component/ui/button'; import { Button, IconButton } from '@affine/component/ui/button';
import { Modal, useConfirmModal } from '@affine/component/ui/modal'; import { Modal, useConfirmModal } from '@affine/component/ui/modal';
@@ -304,6 +304,7 @@ const PageHistoryList = ({
<PlanPrompt /> <PlanPrompt />
{historyListByDay.map(([day, list], i) => { {historyListByDay.map(([day, list], i) => {
const collapsed = collapsedMap[i]; const collapsed = collapsedMap[i];
const isLastGroup = i === historyListByDay.length - 1;
return ( return (
<Collapsible.Root <Collapsible.Root
open={!collapsed} open={!collapsed}
@@ -328,8 +329,9 @@ const PageHistoryList = ({
</div> </div>
{day} {day}
</Collapsible.Trigger> </Collapsible.Trigger>
<Collapsible.Content> <Collapsible.Content className={styles.historyItemGroupContent}>
{list.map((history, idx) => { {list.map((history, idx) => {
const isLastItem = idx === list.length - 1;
return ( return (
<Fragment key={history.timestamp}> <Fragment key={history.timestamp}>
<div <div
@@ -341,14 +343,43 @@ const PageHistoryList = ({
}} }}
data-active={activeVersion === history.timestamp} data-active={activeVersion === history.timestamp}
> >
<button> <span className={styles.historyItemTimestamp}>
{i18nTime(history.timestamp, { {i18nTime(history.timestamp, {
absolute: { noDate: true, accuracy: 'minute' }, absolute: { noDate: true, accuracy: 'minute' },
})} })}
</button> {activeVersion === history.timestamp ? (
<>
<span>·</span>
<div className={styles.historyItemCurrent}>
{t['current']()}
</div> </div>
{idx > list.length - 1 ? ( </>
<div className={styles.historyItemGap} /> ) : null}
</span>
<div className={styles.historyItemNameWrapper}>
<Avatar
className={styles.historyItemAvatar}
url={history.editor?.avatarUrl ?? ''}
name={history.editor?.name ?? ''}
size={22}
/>
<span className={styles.historyItemName}>
{history.editor?.name ?? t['unnamed']()}
</span>
</div>
</div>
{isLastGroup && isLastItem && onLoadMore ? (
<Button
variant="plain"
loading={loadingMore}
disabled={loadingMore}
className={styles.historyItemLoadMore}
onClick={onLoadMore}
>
{t[
'com.affine.history.confirm-restore-modal.load-more'
]()}
</Button>
) : null} ) : null}
</Fragment> </Fragment>
); );
@@ -357,17 +388,6 @@ const PageHistoryList = ({
</Collapsible.Root> </Collapsible.Root>
); );
})} })}
{onLoadMore ? (
<Button
variant="plain"
loading={loadingMore}
disabled={loadingMore}
className={styles.historyItemLoadMore}
onClick={onLoadMore}
>
{t['com.affine.history.confirm-restore-modal.load-more']()}
</Button>
) : null}
</Scrollable.Viewport> </Scrollable.Viewport>
<Scrollable.Scrollbar /> <Scrollable.Scrollbar />
</Scrollable.Root> </Scrollable.Root>

View File

@@ -1,4 +1,5 @@
import { cssVar } from '@toeverything/theme'; import { cssVar } from '@toeverything/theme';
import { cssVarV2 } from '@toeverything/theme/v2';
import { createVar, globalStyle, style } from '@vanilla-extract/css'; import { createVar, globalStyle, style } from '@vanilla-extract/css';
import { range } from 'lodash-es'; import { range } from 'lodash-es';
@@ -9,6 +10,7 @@ const previewTopOffset = createVar('preview-top-offset');
export const root = style({ export const root = style({
height: '100%', height: '100%',
width: '100%', width: '100%',
fontFamily: 'Inter',
vars: { vars: {
[headerHeight]: '52px', [headerHeight]: '52px',
[footerHeight]: '68px', [footerHeight]: '68px',
@@ -119,32 +121,8 @@ export const editor = style({
}); });
export const rowWrapper = style({ export const rowWrapper = style({
display: 'flex', display: 'flex',
height: '100%',
position: 'relative', position: 'relative',
overflow: 'hidden', overflow: 'hidden',
':before': {
content: '""',
width: 1,
height: '100%',
backgroundColor: cssVar('borderColor'),
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({ export const loadingContainer = style({
display: 'flex', display: 'flex',
@@ -166,6 +144,8 @@ export const historyListScrollable = style({
export const historyListScrollableInner = style({ export const historyListScrollableInner = style({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
paddingBottom: 16,
height: '-webkit-fill-available',
}); });
export const historyListHeader = style({ export const historyListHeader = style({
display: 'flex', display: 'flex',
@@ -179,71 +159,101 @@ export const historyListHeader = style({
export const historyItemGroup = style({ export const historyItemGroup = style({
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
padding: '0 8px',
gap: 8,
marginTop: 4,
marginBottom: 4,
});
export const historyItemGroupContent = style({
position: 'relative',
display: 'flex',
flexDirection: 'column',
gap: 8,
paddingLeft: 24,
'::before': {
position: 'absolute',
top: 0,
left: 15.5,
content: '""',
display: 'block',
height: '100%',
width: '1px',
borderLeft: `1px solid ${cssVar('borderColor')}`,
},
}); });
export const historyItemGroupTitle = style({ export const historyItemGroupTitle = style({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
padding: '0 12px 0 4px', padding: '0 12px 0 4px',
borderRadius: 4,
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
color: cssVar('textSecondaryColor'), fontSize: cssVar('fontXs'),
color: cssVarV2('text/secondary'),
fontWeight: 500,
textTransform: 'capitalize',
gap: 4, gap: 4,
backgroundColor: cssVar('backgroundPrimaryColor'), backgroundColor: cssVarV2('layer/background/primary'),
height: 28, height: 24,
':hover': { ':hover': {
background: cssVar('hoverColor'), background: cssVarV2('layer/background/hoverOverlay'),
}, },
}); });
export const historyItem = style([ export const historyItem = style([
rowWrapper, rowWrapper,
{ {
borderRadius: 4,
display: 'flex',
gap: 4,
flexDirection: 'column',
padding: '4px 8px',
cursor: 'pointer',
':hover': {
background: cssVarV2('layer/background/hoverOverlay'),
},
},
]);
export const historyItemTimestamp = style({
color: cssVarV2('text/primary'),
borderRadius: 4,
fontSize: cssVar('fontXs'),
fontWeight: 500,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
padding: '0 32px', gap: '0.5em',
height: 30, });
cursor: 'pointer',
selectors: { export const historyItemCurrent = style({
'&:hover, &[data-active=true]': { color: cssVarV2('text/emphasis'),
backgroundColor: cssVar('hoverColor'), textTransform: 'capitalize',
}, });
// draw circle
'&::after': { export const historyItemNameWrapper = style({
content: '""', display: 'flex',
width: 7, alignItems: 'center',
height: 7, gap: 4,
backgroundColor: cssVar('backgroundSecondaryColor'), color: cssVarV2('text/secondary'),
borderRadius: '50%', fontSize: cssVar('fontXs'),
border: `1px solid ${cssVar('borderColor')}`, });
position: 'absolute',
left: 16, export const historyItemAvatar = style({
top: '50%', backgroundColor: cssVarV2('layer/background/secondary'),
bottom: 0, });
transform: 'translate(-50%, -50%)',
}, export const historyItemName = style({
'&[data-active=true]::after': { overflow: 'hidden',
backgroundColor: cssVar('primaryColor'), textOverflow: 'ellipsis',
borderColor: cssVar('black30'), whiteSpace: 'nowrap',
}, });
},
},
]);
export const historyItemGap = style([
rowWrapper,
{
height: 16,
},
]);
export const historyItemLoadMore = style([ export const historyItemLoadMore = style([
historyItem, historyItem,
{ {
cursor: 'pointer', cursor: 'pointer',
color: cssVar('textSecondaryColor'), color: cssVarV2('text/secondary'),
flexShrink: 0, flexShrink: 0,
borderRadius: 0, textAlign: 'left',
selectors: { alignItems: 'flex-start',
'&:hover': {
backgroundColor: cssVar('hoverColor'),
},
},
}, },
]); ]);
globalStyle(`${historyItem} button`, { globalStyle(`${historyItem} button`, {
@@ -302,7 +312,7 @@ export const collapsedIconContainer = style({
color: 'inherit', color: 'inherit',
}); });
export const planPromptWrapper = style({ export const planPromptWrapper = style({
padding: '4px 12px', padding: '4px 8px',
}); });
export const planPrompt = style({ export const planPrompt = style({
gap: 6, gap: 6,

View File

@@ -2,6 +2,8 @@ import { style } from '@vanilla-extract/css';
export const publicUserLabel = style({ export const publicUserLabel = style({
fontSize: 'inherit', fontSize: 'inherit',
display: 'flex',
alignItems: 'center',
}); });
export const publicUserLabelLoading = style([ export const publicUserLabelLoading = style([
@@ -18,3 +20,7 @@ export const publicUserLabelRemoved = style([
textDecoration: 'line-through', textDecoration: 'line-through',
}, },
]); ]);
export const publicUserLabelAvatar = style({
marginRight: '0.5em',
});

View File

@@ -1,3 +1,4 @@
import { Avatar } from '@affine/component';
import { useCurrentServerService } from '@affine/core/components/providers/current-server-scope'; import { useCurrentServerService } from '@affine/core/components/providers/current-server-scope';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { useLiveData } from '@toeverything/infra'; import { useLiveData } from '@toeverything/infra';
@@ -34,5 +35,15 @@ export const PublicUserLabel = ({ id }: { id: string }) => {
); );
} }
return <span className={styles.publicUserLabel}>{user?.name}</span>; return (
<span className={styles.publicUserLabel}>
<Avatar
url={user?.avatar}
name={user?.name ?? ''}
size={20}
className={styles.publicUserLabelAvatar}
/>
{user?.name}
</span>
);
}; };

View File

@@ -585,6 +585,10 @@ export function useAFFiNEI18N(): {
* `all` * `all`
*/ */
all(): string; all(): string;
/**
* `current`
*/
current(): string;
/** /**
* `Automatically check for new updates periodically.` * `Automatically check for new updates periodically.`
*/ */

View File

@@ -135,6 +135,7 @@
"Unknown User": "Unknown User", "Unknown User": "Unknown User",
"Deleted User": "Deleted User", "Deleted User": "Deleted User",
"all": "all", "all": "all",
"current": "current",
"com.affine.aboutAFFiNE.autoCheckUpdate.description": "Automatically check for new updates periodically.", "com.affine.aboutAFFiNE.autoCheckUpdate.description": "Automatically check for new updates periodically.",
"com.affine.aboutAFFiNE.autoCheckUpdate.title": "Check for updates automatically", "com.affine.aboutAFFiNE.autoCheckUpdate.title": "Check for updates automatically",
"com.affine.aboutAFFiNE.autoDownloadUpdate.description": "Automatically download updates (to this device).", "com.affine.aboutAFFiNE.autoDownloadUpdate.description": "Automatically download updates (to this device).",