Skip to content

Commit

Permalink
feat(core): show sync state at doc info (#7244)
Browse files Browse the repository at this point in the history
  • Loading branch information
EYHN committed Jun 18, 2024
1 parent ea718d3 commit 98258b0
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 41 deletions.
2 changes: 1 addition & 1 deletion packages/common/infra/src/livedata/livedata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ export class LiveData<T = unknown>
duration: number,
{ trailing = true, leading = true }: ThrottleConfig = {}
) {
return LiveData.from(
return LiveData.from<T>(
this.pipe(throttleTime(duration, undefined, { trailing, leading })),
null as any
);
Expand Down
23 changes: 18 additions & 5 deletions packages/common/infra/src/sync/doc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,25 @@ export class DocEngine {
const localState$ = this.localPart.docState$(docId);
const remoteState$ = this.remotePart?.docState$(docId);
return LiveData.computed(get => {
const local = get(localState$);
const remote = remoteState$ ? get(remoteState$) : null;
const localState = get(localState$);
const remoteState = remoteState$ ? get(remoteState$) : null;
if (remoteState) {
return {
syncing: remoteState.syncing,
saving: localState.syncing,
retrying: remoteState.retrying,
ready: localState.ready,
errorMessage: remoteState.errorMessage,
serverClock: remoteState.serverClock,
};
}
return {
ready: local.ready,
saving: local.syncing,
syncing: local.syncing || remote?.syncing,
syncing: localState.syncing,
saving: localState.syncing,
ready: localState.ready,
retrying: false,
errorMessage: null,
serverClock: null,
};
});
}
Expand Down
30 changes: 21 additions & 9 deletions packages/common/infra/src/sync/doc/remote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ export interface RemoteEngineState {

export interface RemoteDocState {
syncing: boolean;
retrying: boolean;
serverClock: number | null;
errorMessage: string | null;
}

export class DocEngineRemotePart {
Expand Down Expand Up @@ -87,20 +90,22 @@ export class DocEngineRemotePart {
new Observable(subscribe => {
const next = () => {
if (!this.status.syncing) {
// if syncing = false, jobMap is empty
subscribe.next({
total: this.status.docs.size,
syncing: this.status.docs.size,
retrying: this.status.retrying,
errorMessage: this.status.errorMessage,
});
} else {
const syncing = this.status.jobMap.size;
subscribe.next({
total: this.status.docs.size,
syncing: syncing,
retrying: this.status.retrying,
errorMessage: this.status.errorMessage,
});
}
const syncing = this.status.jobMap.size;
subscribe.next({
total: this.status.docs.size,
syncing: syncing,
retrying: this.status.retrying,
errorMessage: this.status.errorMessage,
});
};
next();
return this.statusUpdatedSubject$.subscribe(() => {
Expand All @@ -123,14 +128,17 @@ export class DocEngineRemotePart {
syncing:
!this.status.connectedDocs.has(docId) ||
this.status.jobMap.has(docId),
serverClock: this.status.serverClocks.get(docId),
retrying: this.status.retrying,
errorMessage: this.status.errorMessage,
});
};
next();
return this.statusUpdatedSubject$.subscribe(updatedId => {
if (updatedId === true || updatedId === docId) next();
});
}),
{ syncing: false }
{ syncing: false, retrying: false, errorMessage: null, serverClock: null }
);
}

Expand Down Expand Up @@ -326,6 +334,7 @@ export class DocEngineRemotePart {
readonly actions = {
updateServerClock: (docId: string, serverClock: number) => {
this.status.serverClocks.setIfBigger(docId, serverClock);
this.statusUpdatedSubject$.next(docId);
},
addDoc: (docId: string) => {
if (!this.status.docs.has(docId)) {
Expand Down Expand Up @@ -359,7 +368,6 @@ export class DocEngineRemotePart {
// eslint-disable-next-line no-constant-condition
while (true) {
try {
this.status.retrying = false;
await this.retryLoop(signal);
} catch (err) {
if (signal?.aborted) {
Expand Down Expand Up @@ -448,6 +456,10 @@ export class DocEngineRemotePart {
}),
]);

// reset retrying flag after connected with server
this.status.retrying = false;
this.statusUpdatedSubject$.next(true);

throwIfAborted(signal);
disposes.push(
await this.server.subscribeAllDocs(({ docId, data, serverClock }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function useDocEngineStatus() {
() => ({
...engineState,
progress,
syncing: engineState.syncing > 0,
syncing: engineState.syncing > 0 || engineState.retrying,
}),
[engineState, progress]
);
Expand Down
39 changes: 39 additions & 0 deletions packages/frontend/core/src/utils/intl-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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;
Expand Down Expand Up @@ -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);
}
};
2 changes: 2 additions & 0 deletions packages/frontend/i18n/src/resources/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1303,6 +1303,8 @@
"com.affine.workspaceType.offline": "Available Offline",
"com.affine.write_with_a_blank_page": "Write with a blank page",
"com.affine.yesterday": "Yesterday",
"com.affine.just-now": "Just now",
"com.affine.syncing": "Syncing",
"core": "core",
"dark": "Dark",
"emptyAllPages": "Click on the <1>$t(New Doc)</1> button to create your first doc.",
Expand Down

0 comments on commit 98258b0

Please sign in to comment.