feat(core): show sync state at doc info (#7244)

This commit is contained in:
EYHN
2024-06-18 08:35:22 +00:00
parent ea718d30e9
commit 98258b0211
9 changed files with 154 additions and 41 deletions
@@ -89,8 +89,8 @@ export const backlinksList = style({
export const tableHeaderTimestamp = style({
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
flexDirection: 'column',
alignItems: 'start',
gap: '8px',
cursor: 'default',
padding: '0 6px',
@@ -14,7 +14,11 @@ import type {
PageInfoCustomPropertyMeta,
PagePropertyType,
} from '@affine/core/modules/properties/services/schema';
import { timestampToLocalDate } from '@affine/core/utils';
import {
timestampToHumanTime,
timestampToLocalDate,
timestampToLocalDateTime,
} from '@affine/core/utils';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { assertExists } from '@blocksuite/global/utils';
import {
@@ -41,6 +45,12 @@ import {
} from '@dnd-kit/modifiers';
import { SortableContext, useSortable } from '@dnd-kit/sortable';
import * as Collapsible from '@radix-ui/react-collapsible';
import {
DocService,
useLiveData,
useServices,
WorkspaceService,
} from '@toeverything/infra';
import clsx from 'clsx';
import { use } from 'foxact/use';
import { atom, useAtomValue, useSetAtom } from 'jotai';
@@ -596,35 +606,69 @@ export const PagePropertiesTableHeader = ({
manager.pageId
);
const timestampElement = useMemo(() => {
const localizedUpdateTime = manager.updatedDate
? timestampToLocalDate(manager.updatedDate)
: null;
const { docService, workspaceService } = useServices({
DocService,
WorkspaceService,
});
const { syncing, retrying, serverClock } = useLiveData(
workspaceService.workspace.engine.doc.docState$(docService.doc.id)
);
const timestampElement = useMemo(() => {
const localizedCreateTime = manager.createDate
? timestampToLocalDate(manager.createDate)
: null;
const updateTimeElement = (
<div className={styles.tableHeaderTimestamp}>
{t['Updated']()} {localizedUpdateTime}
</div>
);
const createTimeElement = (
<div className={styles.tableHeaderTimestamp}>
{t['Created']()} {localizedCreateTime}
</div>
);
return localizedUpdateTime ? (
return serverClock ? (
<Tooltip
side="right"
content={
<>
<div className={styles.tableHeaderTimestamp}>
{t['Updated']()} {timestampToLocalDateTime(serverClock)}
</div>
{manager.createDate && (
<div className={styles.tableHeaderTimestamp}>
{t['Created']()} {timestampToLocalDateTime(manager.createDate)}
</div>
)}
</>
}
>
<div className={styles.tableHeaderTimestamp}>
{!syncing && !retrying ? (
<>
{t['Updated']()} {timestampToHumanTime(serverClock)}
</>
) : (
<>{t['com.affine.syncing']()}</>
)}
</div>
</Tooltip>
) : manager.updatedDate ? (
<Tooltip side="right" content={createTimeElement}>
{updateTimeElement}
<div className={styles.tableHeaderTimestamp}>
{t['Updated']()} {timestampToLocalDate(manager.updatedDate)}
</div>
</Tooltip>
) : (
createTimeElement
);
}, [manager.createDate, manager.updatedDate, t]);
}, [
manager.createDate,
manager.updatedDate,
retrying,
serverClock,
syncing,
t,
]);
const handleCollapse = useCallback(() => {
onOpenChange(!open);
@@ -145,20 +145,22 @@ const useSyncEngineSyncProgress = () => {
if (!isOnline) {
return 'Disconnected, please check your network connection';
}
if (syncing) {
return (
`Syncing with AFFiNE Cloud` +
(progress ? ` (${Math.floor(progress * 100)}%)` : '')
);
} else if (retrying && errorMessage) {
if (isOverCapacity) {
return 'Sync failed due to insufficient cloud storage space.';
}
if (retrying && errorMessage) {
return `${errorMessage}, reconnecting.`;
}
if (retrying) {
return 'Sync disconnected due to unexpected issues, reconnecting.';
}
if (isOverCapacity) {
return 'Sync failed due to insufficient cloud storage space.';
if (syncing) {
return (
`Syncing with AFFiNE Cloud` +
(progress ? ` (${Math.floor(progress * 100)}%)` : '')
);
}
return 'Synced with AFFiNE Cloud';
}, [
currentWorkspace.flavour,
@@ -196,7 +198,8 @@ const useSyncEngineSyncProgress = () => {
),
active:
currentWorkspace.flavour === WorkspaceFlavour.AFFINE_CLOUD &&
((syncing && progress !== undefined) || isOverCapacity || !isOnline),
((syncing && progress !== undefined) || retrying) && // active if syncing or retrying
!isOverCapacity, // not active if isOffline or OverCapacity
};
};
const usePauseAnimation = (timeToResume = 5000) => {
@@ -14,7 +14,7 @@ export function useDocEngineStatus() {
() => ({
...engineState,
progress,
syncing: engineState.syncing > 0,
syncing: engineState.syncing > 0 || engineState.retrying,
}),
[engineState, progress]
);
@@ -7,6 +7,13 @@ function createTimeFormatter() {
});
}
function createDateTimeFormatter() {
return new Intl.DateTimeFormat(getI18n()?.language, {
timeStyle: 'medium',
dateStyle: 'medium',
});
}
function createDateFormatter() {
return new Intl.DateTimeFormat(getI18n()?.language, {
year: 'numeric',
@@ -31,6 +38,17 @@ export const timestampToLocalDate = (ts: string | number) => {
return formatter.format(dayjs(ts).toDate());
};
export const timestampToLocalDateTime = (ts: string | number) => {
const formatter = createDateTimeFormatter();
return formatter.format(dayjs(ts).toDate());
};
export const createRelativeTimeFormatter = () => {
return new Intl.RelativeTimeFormat(getI18n()?.language, {
style: 'narrow',
});
};
export interface CalendarTranslation {
yesterday: () => string;
today: () => string;
@@ -64,3 +82,24 @@ export const timestampToCalendarDate = (
? `${translation.nextWeek()} ${week}`
: sameElse;
};
// TODO: refactor this to @affine/i18n
export const timestampToHumanTime = (ts: number) => {
const diff = Math.abs(dayjs(ts).diff(dayjs()));
if (diff < 1000 * 60) {
return getI18n().t('com.affine.just-now');
} else if (diff < 1000 * 60 * 60) {
return createRelativeTimeFormatter().format(
-Math.floor(diff / 1000 / 60),
'minutes'
);
} else if (diff < 1000 * 60 * 60 * 24) {
return createRelativeTimeFormatter().format(
-Math.floor(diff / 1000 / 60 / 60),
'hours'
);
} else {
return timestampToLocalDate(ts);
}
};