mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat(core): new worker workspace engine (#9257)
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-page-meta';
|
||||
import { useDocCollectionPage } from '@affine/core/components/hooks/use-block-suite-workspace-page';
|
||||
import { FetchService, GraphQLService } from '@affine/core/modules/cloud';
|
||||
import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace';
|
||||
import {
|
||||
getAFFiNEWorkspaceSchema,
|
||||
type WorkspaceFlavourProvider,
|
||||
WorkspaceService,
|
||||
WorkspacesService,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { WorkspaceImpl } from '@affine/core/modules/workspace/impls/workspace';
|
||||
import { DebugLogger } from '@affine/debug';
|
||||
import type { ListHistoryQuery } from '@affine/graphql';
|
||||
@@ -25,7 +30,6 @@ import {
|
||||
useMutation,
|
||||
} from '../../../components/hooks/use-mutation';
|
||||
import { useQueryInfinite } from '../../../components/hooks/use-query';
|
||||
import { CloudBlobStorage } from '../../../modules/workspace-engine/impls/engine/blob-cloud';
|
||||
|
||||
const logger = new DebugLogger('page-history');
|
||||
|
||||
@@ -105,19 +109,28 @@ const docCollectionMap = new Map<string, Workspace>();
|
||||
// assume the workspace is a cloud workspace since the history feature is only enabled for cloud workspace
|
||||
const getOrCreateShellWorkspace = (
|
||||
workspaceId: string,
|
||||
fetchService: FetchService,
|
||||
graphQLService: GraphQLService
|
||||
flavourProvider?: WorkspaceFlavourProvider
|
||||
) => {
|
||||
let docCollection = docCollectionMap.get(workspaceId);
|
||||
if (!docCollection) {
|
||||
const blobStorage = new CloudBlobStorage(
|
||||
workspaceId,
|
||||
fetchService,
|
||||
graphQLService
|
||||
);
|
||||
docCollection = new WorkspaceImpl({
|
||||
id: workspaceId,
|
||||
blobSource: blobStorage,
|
||||
blobSource: {
|
||||
name: 'cloud',
|
||||
readonly: true,
|
||||
async get(key) {
|
||||
return flavourProvider?.getWorkspaceBlob(workspaceId, key) ?? null;
|
||||
},
|
||||
set() {
|
||||
return Promise.resolve('');
|
||||
},
|
||||
delete() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
list() {
|
||||
return Promise.resolve([]);
|
||||
},
|
||||
},
|
||||
schema: getAFFiNEWorkspaceSchema(),
|
||||
});
|
||||
docCollectionMap.set(workspaceId, docCollection);
|
||||
@@ -150,6 +163,8 @@ export const useSnapshotPage = (
|
||||
pageDocId: string,
|
||||
ts?: string
|
||||
) => {
|
||||
const affineWorkspace = useService(WorkspaceService).workspace;
|
||||
const workspacesService = useService(WorkspacesService);
|
||||
const fetchService = useService(FetchService);
|
||||
const graphQLService = useService(GraphQLService);
|
||||
const snapshot = usePageHistory(docCollection.id, pageDocId, ts);
|
||||
@@ -160,8 +175,7 @@ export const useSnapshotPage = (
|
||||
const pageId = pageDocId + '-' + ts;
|
||||
const historyShellWorkspace = getOrCreateShellWorkspace(
|
||||
docCollection.id,
|
||||
fetchService,
|
||||
graphQLService
|
||||
workspacesService.getWorkspaceFlavourProvider(affineWorkspace.meta)
|
||||
);
|
||||
let page = historyShellWorkspace.getDoc(pageId);
|
||||
if (!page && snapshot) {
|
||||
@@ -175,19 +189,31 @@ export const useSnapshotPage = (
|
||||
}); // must load before applyUpdate
|
||||
}
|
||||
return page ?? undefined;
|
||||
}, [ts, pageDocId, docCollection.id, fetchService, graphQLService, snapshot]);
|
||||
}, [
|
||||
ts,
|
||||
pageDocId,
|
||||
docCollection.id,
|
||||
workspacesService,
|
||||
affineWorkspace.meta,
|
||||
snapshot,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const historyShellWorkspace = getOrCreateShellWorkspace(
|
||||
docCollection.id,
|
||||
fetchService,
|
||||
graphQLService
|
||||
workspacesService.getWorkspaceFlavourProvider(affineWorkspace.meta)
|
||||
);
|
||||
// apply the rootdoc's update to the current workspace
|
||||
// this makes sure the page reference links are not deleted ones in the preview
|
||||
const update = encodeStateAsUpdate(docCollection.doc);
|
||||
applyUpdate(historyShellWorkspace.doc, update);
|
||||
}, [docCollection, fetchService, graphQLService]);
|
||||
}, [
|
||||
affineWorkspace.meta,
|
||||
docCollection,
|
||||
fetchService,
|
||||
graphQLService,
|
||||
workspacesService,
|
||||
]);
|
||||
|
||||
return page;
|
||||
};
|
||||
|
||||
@@ -68,11 +68,11 @@ export const CloudQuotaModal = () => {
|
||||
}, [userQuota, isOwner, workspaceQuota, t]);
|
||||
|
||||
const onAbortLargeBlob = useAsyncCallback(
|
||||
async (blob: Blob) => {
|
||||
async (byteSize: number) => {
|
||||
// wait for quota revalidation
|
||||
await workspaceQuotaService.quota.waitForRevalidation();
|
||||
if (
|
||||
blob.size > (workspaceQuotaService.quota.quota$.value?.blobLimit ?? 0)
|
||||
byteSize > (workspaceQuotaService.quota.quota$.value?.blobLimit ?? 0)
|
||||
) {
|
||||
setOpen(true);
|
||||
}
|
||||
@@ -85,10 +85,10 @@ export const CloudQuotaModal = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
currentWorkspace.engine.blob.singleBlobSizeLimit = workspaceQuota.blobLimit;
|
||||
currentWorkspace.engine.blob.setMaxBlobSize(workspaceQuota.blobLimit);
|
||||
|
||||
const disposable =
|
||||
currentWorkspace.engine.blob.onAbortLargeBlob(onAbortLargeBlob);
|
||||
currentWorkspace.engine.blob.onReachedMaxBlobSize(onAbortLargeBlob);
|
||||
return () => {
|
||||
disposable();
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ export const LocalQuotaModal = () => {
|
||||
}, [setOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
const disposable = currentWorkspace.engine.blob.onAbortLargeBlob(() => {
|
||||
const disposable = currentWorkspace.engine.blob.onReachedMaxBlobSize(() => {
|
||||
setOpen(true);
|
||||
});
|
||||
return () => {
|
||||
|
||||
@@ -57,7 +57,6 @@ import {
|
||||
patchForClipboardInElectron,
|
||||
patchForEdgelessNoteConfig,
|
||||
patchForMobile,
|
||||
patchForSharedPage,
|
||||
patchGenerateDocUrlExtension,
|
||||
patchNotificationService,
|
||||
patchOpenDocExtension,
|
||||
@@ -93,7 +92,7 @@ interface BlocksuiteEditorProps {
|
||||
defaultOpenProperty?: DefaultOpenProperty;
|
||||
}
|
||||
|
||||
const usePatchSpecs = (shared: boolean, mode: DocMode) => {
|
||||
const usePatchSpecs = (mode: DocMode) => {
|
||||
const [reactToLit, portals] = useLitPortalFactory();
|
||||
const {
|
||||
peekViewService,
|
||||
@@ -168,9 +167,6 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => {
|
||||
patched = patched.concat(patchParseDocUrlExtension(framework));
|
||||
patched = patched.concat(patchGenerateDocUrlExtension(framework));
|
||||
patched = patched.concat(patchQuickSearchService(framework));
|
||||
if (shared) {
|
||||
patched = patched.concat(patchForSharedPage());
|
||||
}
|
||||
if (BUILD_CONFIG.isMobileEdition) {
|
||||
patched = patched.concat(patchForMobile());
|
||||
}
|
||||
@@ -190,7 +186,6 @@ const usePatchSpecs = (shared: boolean, mode: DocMode) => {
|
||||
peekViewService,
|
||||
reactToLit,
|
||||
referenceRenderer,
|
||||
shared,
|
||||
specs,
|
||||
featureFlagService,
|
||||
]);
|
||||
@@ -261,7 +256,7 @@ export const BlocksuiteDocEditor = forwardRef<
|
||||
[externalTitleRef]
|
||||
);
|
||||
|
||||
const [specs, portals] = usePatchSpecs(!!shared, 'page');
|
||||
const [specs, portals] = usePatchSpecs('page');
|
||||
|
||||
const displayBiDirectionalLink = useLiveData(
|
||||
editorSettingService.editorSetting.settings$.selector(
|
||||
@@ -349,8 +344,8 @@ export const BlocksuiteDocEditor = forwardRef<
|
||||
export const BlocksuiteEdgelessEditor = forwardRef<
|
||||
EdgelessEditor,
|
||||
BlocksuiteEditorProps
|
||||
>(function BlocksuiteEdgelessEditor({ page, shared }, ref) {
|
||||
const [specs, portals] = usePatchSpecs(!!shared, 'edgeless');
|
||||
>(function BlocksuiteEdgelessEditor({ page }, ref) {
|
||||
const [specs, portals] = usePatchSpecs('edgeless');
|
||||
const editorRef = useRef<EdgelessEditor | null>(null);
|
||||
|
||||
const onDocRef = useCallback(
|
||||
|
||||
@@ -3,6 +3,7 @@ import { WorkspaceDialogService } from '@affine/core/modules/dialogs';
|
||||
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import type { BlobSyncState } from '@affine/nbstore';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { debounce } from 'lodash-es';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
@@ -31,8 +32,8 @@ export const OverCapacityNotification = () => {
|
||||
// debounce sync engine status
|
||||
useEffect(() => {
|
||||
const disposableOverCapacity =
|
||||
currentWorkspace.engine.blob.isStorageOverCapacity$.subscribe(
|
||||
debounce((isStorageOverCapacity: boolean) => {
|
||||
currentWorkspace.engine.blob.state$.subscribe(
|
||||
debounce(({ isStorageOverCapacity }: BlobSyncState) => {
|
||||
const isOver = isStorageOverCapacity;
|
||||
if (!isOver) {
|
||||
return;
|
||||
|
||||
@@ -20,11 +20,11 @@ import {
|
||||
TeamWorkspaceIcon,
|
||||
UnsyncIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import { useLiveData } from '@toeverything/infra';
|
||||
import { LiveData, useLiveData } from '@toeverything/infra';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import clsx from 'clsx';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
import { forwardRef, useCallback, useEffect, useState } from 'react';
|
||||
import { forwardRef, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { useCatchEventCallback } from '../../hooks/use-catch-event-hook';
|
||||
import { WorkspaceAvatar } from '../../workspace-avatar';
|
||||
@@ -85,7 +85,11 @@ const useSyncEngineSyncProgress = (meta: WorkspaceMetadata) => {
|
||||
const workspace = useWorkspace(meta);
|
||||
|
||||
const engineState = useLiveData(
|
||||
workspace?.engine.docEngineState$.throttleTime(100)
|
||||
useMemo(() => {
|
||||
return workspace
|
||||
? LiveData.from(workspace.engine.doc.state$, null).throttleTime(100)
|
||||
: null;
|
||||
}, [workspace])
|
||||
);
|
||||
|
||||
if (!engineState || !workspace) {
|
||||
@@ -94,7 +98,7 @@ const useSyncEngineSyncProgress = (meta: WorkspaceMetadata) => {
|
||||
|
||||
const progress =
|
||||
(engineState.total - engineState.syncing) / engineState.total;
|
||||
const syncing = engineState.syncing > 0 || engineState.retrying;
|
||||
const syncing = engineState.syncing > 0 || engineState.syncRetrying;
|
||||
|
||||
let content;
|
||||
// TODO(@eyhn): add i18n
|
||||
@@ -106,9 +110,9 @@ const useSyncEngineSyncProgress = (meta: WorkspaceMetadata) => {
|
||||
}
|
||||
} else if (!isOnline) {
|
||||
content = 'Disconnected, please check your network connection';
|
||||
} else if (engineState.retrying && engineState.errorMessage) {
|
||||
content = `${engineState.errorMessage}, reconnecting.`;
|
||||
} else if (engineState.retrying) {
|
||||
} else if (engineState.syncRetrying && engineState.syncErrorMessage) {
|
||||
content = `${engineState.syncErrorMessage}, reconnecting.`;
|
||||
} else if (engineState.syncRetrying) {
|
||||
content = 'Sync disconnected due to unexpected issues, reconnecting.';
|
||||
} else if (syncing) {
|
||||
content =
|
||||
@@ -123,7 +127,7 @@ const useSyncEngineSyncProgress = (meta: WorkspaceMetadata) => {
|
||||
return SyncingWorkspaceStatus({
|
||||
progress: progress ? Math.max(progress, 0.2) : undefined,
|
||||
});
|
||||
} else if (engineState.retrying) {
|
||||
} else if (engineState.syncRetrying) {
|
||||
return UnSyncWorkspaceStatus();
|
||||
} else {
|
||||
return CloudWorkspaceStatus();
|
||||
@@ -145,7 +149,7 @@ const useSyncEngineSyncProgress = (meta: WorkspaceMetadata) => {
|
||||
progress,
|
||||
active:
|
||||
workspace.flavour !== 'local' &&
|
||||
((syncing && progress !== undefined) || engineState.retrying), // active if syncing or retrying,
|
||||
((syncing && progress !== undefined) || engineState.syncRetrying), // active if syncing or retrying,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user