mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-30 00:29:46 +08:00
feat(core): show sync state at doc info (#7244)
This commit is contained in:
@@ -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);
|
||||
|
||||
+12
-9
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user