feat(core): new worker workspace engine (#9257)

This commit is contained in:
EYHN
2025-01-17 00:22:18 +08:00
committed by GitHub
parent 7dc470e7ea
commit a2ffdb4047
219 changed files with 4267 additions and 7194 deletions

View File

@@ -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;
};

View File

@@ -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();
};

View File

@@ -16,7 +16,7 @@ export const LocalQuotaModal = () => {
}, [setOpen]);
useEffect(() => {
const disposable = currentWorkspace.engine.blob.onAbortLargeBlob(() => {
const disposable = currentWorkspace.engine.blob.onReachedMaxBlobSize(() => {
setOpen(true);
});
return () => {

View File

@@ -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(

View File

@@ -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;

View File

@@ -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,
};
};