mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
feat(core): new worker workspace engine (#9257)
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
import { PropertyName, PropertyRoot, PropertyValue } from '@affine/component';
|
||||
import { DocsService } from '@affine/core/modules/doc';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { i18nTime, useI18n } from '@affine/i18n';
|
||||
import { DateTimeIcon, HistoryIcon } from '@blocksuite/icons/rc';
|
||||
import { DateTimeIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import type { ConfigType } from 'dayjs';
|
||||
@@ -19,11 +18,7 @@ export const TimeRow = ({
|
||||
className?: string;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const workspaceService = useService(WorkspaceService);
|
||||
const docsService = useService(DocsService);
|
||||
const { syncing, retrying, serverClock } = useLiveData(
|
||||
workspaceService.workspace.engine.doc.docState$(docId)
|
||||
);
|
||||
const docRecord = useLiveData(docsService.list.doc$(docId));
|
||||
const docMeta = useLiveData(docRecord?.meta$);
|
||||
|
||||
@@ -43,38 +38,14 @@ export const TimeRow = ({
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PropertyRoot>
|
||||
<PropertyName name={t['Created']()} icon={<DateTimeIcon />} />
|
||||
<PropertyValue>
|
||||
{docMeta ? formatI18nTime(docMeta.createDate) : localizedCreateTime}
|
||||
</PropertyValue>
|
||||
</PropertyRoot>
|
||||
{serverClock ? (
|
||||
<PropertyRoot>
|
||||
<PropertyName
|
||||
name={t[
|
||||
!syncing && !retrying ? 'Updated' : 'com.affine.syncing'
|
||||
]()}
|
||||
icon={<HistoryIcon />}
|
||||
/>
|
||||
<PropertyValue>
|
||||
{!syncing && !retrying
|
||||
? formatI18nTime(serverClock)
|
||||
: docMeta?.updatedDate
|
||||
? formatI18nTime(docMeta.updatedDate)
|
||||
: null}
|
||||
</PropertyValue>
|
||||
</PropertyRoot>
|
||||
) : docMeta?.updatedDate ? (
|
||||
<PropertyRoot>
|
||||
<PropertyName name={t['Updated']()} icon={<HistoryIcon />} />
|
||||
<PropertyValue>{formatI18nTime(docMeta.updatedDate)}</PropertyValue>
|
||||
</PropertyRoot>
|
||||
) : null}
|
||||
</>
|
||||
<PropertyRoot>
|
||||
<PropertyName name={t['Created']()} icon={<DateTimeIcon />} />
|
||||
<PropertyValue>
|
||||
{docMeta ? formatI18nTime(docMeta.createDate) : localizedCreateTime}
|
||||
</PropertyValue>
|
||||
</PropertyRoot>
|
||||
);
|
||||
}, [docMeta, retrying, serverClock, syncing, t]);
|
||||
}, [docMeta, t]);
|
||||
|
||||
const dTimestampElement = useDebouncedValue(timestampElement, 500);
|
||||
|
||||
|
||||
@@ -63,7 +63,6 @@ const SettingModalInner = ({
|
||||
const currentServerId = useLiveData(
|
||||
globalContextService.globalContext.serverId.$
|
||||
);
|
||||
console.log(currentServerId);
|
||||
const serversService = useService(ServersService);
|
||||
const defaultServerService = useService(DefaultServerService);
|
||||
const currentServer =
|
||||
|
||||
@@ -39,8 +39,8 @@ export const DesktopExportPanel = ({ workspace }: ExportPanelProps) => {
|
||||
type: 'workspace',
|
||||
});
|
||||
if (isOnline) {
|
||||
await workspace.engine.waitForDocSynced();
|
||||
await workspace.engine.blob.sync();
|
||||
await workspace.engine.doc.waitForSynced();
|
||||
await workspace.engine.blob.fullSync();
|
||||
}
|
||||
|
||||
const result = await desktopApi.handler?.dialog.saveDBFileAs(workspaceId);
|
||||
|
||||
@@ -8,9 +8,7 @@ import { WorkspaceServerService } from '@affine/core/modules/cloud';
|
||||
import { WorkspaceService } from '@affine/core/modules/workspace';
|
||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { ArrowRightSmallIcon } from '@blocksuite/icons/rc';
|
||||
import { FrameworkScope, useService } from '@toeverything/infra';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { DeleteLeaveWorkspace } from './delete-leave-workspace';
|
||||
import { EnableCloudPanel } from './enable-cloud';
|
||||
@@ -34,17 +32,6 @@ export const WorkspaceSettingDetail = ({
|
||||
|
||||
const workspaceInfo = useWorkspaceInfo(workspace);
|
||||
|
||||
const handleResetSyncStatus = useCallback(() => {
|
||||
workspace?.engine.doc
|
||||
.resetSyncStatus()
|
||||
.then(() => {
|
||||
window.location.reload();
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}, [workspace]);
|
||||
|
||||
if (!workspace) {
|
||||
return null;
|
||||
}
|
||||
@@ -71,8 +58,10 @@ export const WorkspaceSettingDetail = ({
|
||||
<TemplateDocSetting />
|
||||
<SettingWrapper title={t['com.affine.brand.affineCloud']()}>
|
||||
<EnableCloudPanel onCloseSetting={onCloseSetting} />
|
||||
<WorkspaceQuotaPanel />
|
||||
<MembersPanel onChangeSettingState={onChangeSettingState} />
|
||||
{workspace.flavour !== 'local' && <WorkspaceQuotaPanel />}
|
||||
{workspace.flavour !== 'local' && (
|
||||
<MembersPanel onChangeSettingState={onChangeSettingState} />
|
||||
)}
|
||||
</SettingWrapper>
|
||||
<SharingPanel />
|
||||
{BUILD_CONFIG.isElectron && (
|
||||
@@ -82,19 +71,6 @@ export const WorkspaceSettingDetail = ({
|
||||
)}
|
||||
<SettingWrapper>
|
||||
<DeleteLeaveWorkspace onCloseSetting={onCloseSetting} />
|
||||
<SettingRow
|
||||
name={
|
||||
<span style={{ color: 'var(--affine-text-secondary-color)' }}>
|
||||
{t['com.affine.resetSyncStatus.button']()}
|
||||
</span>
|
||||
}
|
||||
desc={t['com.affine.resetSyncStatus.description']()}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={handleResetSyncStatus}
|
||||
data-testid="reset-sync-status"
|
||||
>
|
||||
<ArrowRightSmallIcon />
|
||||
</SettingRow>
|
||||
</SettingWrapper>
|
||||
</FrameworkScope>
|
||||
</FrameworkScope>
|
||||
|
||||
@@ -9,9 +9,10 @@ import { validateAndReduceImage } from '@affine/core/utils/reduce-image';
|
||||
import { UNTITLED_WORKSPACE_NAME } from '@affine/env/constant';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { CameraIcon } from '@blocksuite/icons/rc';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { LiveData, useLiveData, useService } from '@toeverything/infra';
|
||||
import type { KeyboardEvent } from 'react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import * as style from './style.css';
|
||||
|
||||
@@ -24,8 +25,18 @@ export const ProfilePanel = () => {
|
||||
useEffect(() => {
|
||||
permissionService.permission.revalidate();
|
||||
}, [permissionService]);
|
||||
const workspaceIsReady = useLiveData(workspace?.engine.rootDocState$)?.ready;
|
||||
|
||||
const workspaceIsReady = useLiveData(
|
||||
useMemo(() => {
|
||||
return workspace
|
||||
? LiveData.from(
|
||||
workspace.engine.doc
|
||||
.docState$(workspace.id)
|
||||
.pipe(map(v => v.ready)),
|
||||
false
|
||||
)
|
||||
: null;
|
||||
}, [workspace])
|
||||
);
|
||||
const [name, setName] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -33,7 +33,7 @@ const useLoadAttachment = (pageId: string, attachmentId: string) => {
|
||||
if (!doc.blockSuiteDoc.ready) {
|
||||
doc.blockSuiteDoc.load();
|
||||
}
|
||||
doc.setPriorityLoad(10);
|
||||
const dispose = doc.addPriorityLoad(10);
|
||||
|
||||
doc
|
||||
.waitForSyncReady()
|
||||
@@ -47,6 +47,7 @@ const useLoadAttachment = (pageId: string, attachmentId: string) => {
|
||||
|
||||
return () => {
|
||||
release();
|
||||
dispose();
|
||||
};
|
||||
}, [docRecord, docsService, pageId, attachmentId]);
|
||||
|
||||
|
||||
@@ -41,10 +41,7 @@ const useLoadDoc = (pageId: string) => {
|
||||
|
||||
// set sync engine priority target
|
||||
useEffect(() => {
|
||||
currentWorkspace.engine.doc.setPriority(pageId, 10);
|
||||
return () => {
|
||||
currentWorkspace.engine.doc.setPriority(pageId, 5);
|
||||
};
|
||||
return currentWorkspace.engine.doc.addPriority(pageId, 10);
|
||||
}, [currentWorkspace, pageId]);
|
||||
|
||||
const isInTrash = useLiveData(doc?.meta$.map(meta => meta.trash));
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
import { ZipTransformer } from '@blocksuite/affine/blocks';
|
||||
import {
|
||||
FrameworkScope,
|
||||
LiveData,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServices,
|
||||
@@ -28,6 +29,7 @@ import {
|
||||
useParams,
|
||||
useSearchParams,
|
||||
} from 'react-router-dom';
|
||||
import { map } from 'rxjs';
|
||||
import * as _Y from 'yjs';
|
||||
|
||||
import { AffineErrorBoundary } from '../../../components/affine/affine-error-boundary';
|
||||
@@ -247,7 +249,20 @@ const WorkspacePage = ({ meta }: { meta: WorkspaceMetadata }) => {
|
||||
}, [meta, workspacesService]);
|
||||
|
||||
const isRootDocReady =
|
||||
useLiveData(workspace?.engine.rootDocState$.map(v => v.ready)) ?? false;
|
||||
useLiveData(
|
||||
useMemo(
|
||||
() =>
|
||||
workspace
|
||||
? LiveData.from(
|
||||
workspace.engine.doc
|
||||
.docState$(workspace.id)
|
||||
.pipe(map(v => v.ready)),
|
||||
false
|
||||
)
|
||||
: null,
|
||||
[workspace]
|
||||
)
|
||||
) ?? false;
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
|
||||
@@ -25,13 +25,13 @@ export const WorkspaceLayout = function WorkspaceLayout({
|
||||
<WorkspaceDialogs />
|
||||
|
||||
{/* ---- some side-effect components ---- */}
|
||||
{currentWorkspace?.flavour === 'local' ? (
|
||||
<LocalQuotaModal />
|
||||
) : (
|
||||
{currentWorkspace?.flavour !== 'local' ? (
|
||||
<>
|
||||
<CloudQuotaModal />
|
||||
<QuotaCheck workspaceMeta={currentWorkspace.meta} />
|
||||
</>
|
||||
) : (
|
||||
<LocalQuotaModal />
|
||||
)}
|
||||
<AiLoginRequiredModal />
|
||||
<WorkspaceSideEffects />
|
||||
|
||||
@@ -5,12 +5,7 @@ import { usePageDocumentTitle } from '@affine/core/components/hooks/use-global-s
|
||||
import { useNavigateHelper } from '@affine/core/components/hooks/use-navigate-helper';
|
||||
import { PageDetailEditor } from '@affine/core/components/page-detail-editor';
|
||||
import { AppContainer } from '@affine/core/desktop/components/app-container';
|
||||
import {
|
||||
AuthService,
|
||||
FetchService,
|
||||
GraphQLService,
|
||||
ServerService,
|
||||
} from '@affine/core/modules/cloud';
|
||||
import { AuthService, ServerService } from '@affine/core/modules/cloud';
|
||||
import { type Doc, DocsService } from '@affine/core/modules/doc';
|
||||
import {
|
||||
type Editor,
|
||||
@@ -19,13 +14,11 @@ import {
|
||||
EditorsService,
|
||||
} from '@affine/core/modules/editor';
|
||||
import { PeekViewManagerModal } from '@affine/core/modules/peek-view';
|
||||
import { ShareReaderService } from '@affine/core/modules/share-doc';
|
||||
import { ViewIcon, ViewTitle } from '@affine/core/modules/workbench';
|
||||
import {
|
||||
type Workspace,
|
||||
WorkspacesService,
|
||||
} from '@affine/core/modules/workspace';
|
||||
import { CloudBlobStorage } from '@affine/core/modules/workspace-engine';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import {
|
||||
type DocMode,
|
||||
@@ -35,22 +28,9 @@ import {
|
||||
import type { AffineEditorContainer } from '@blocksuite/affine/presets';
|
||||
import { DisposableGroup } from '@blocksuite/global/utils';
|
||||
import { Logo1Icon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
EmptyBlobStorage,
|
||||
FrameworkScope,
|
||||
ReadonlyDocStorage,
|
||||
useLiveData,
|
||||
useService,
|
||||
useServices,
|
||||
} from '@toeverything/infra';
|
||||
import { FrameworkScope, useLiveData, useService } from '@toeverything/infra';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { PageNotFound } from '../../404';
|
||||
@@ -65,15 +45,6 @@ export const SharePage = ({
|
||||
workspaceId: string;
|
||||
docId: string;
|
||||
}) => {
|
||||
const { shareReaderService, serverService } = useServices({
|
||||
ShareReaderService,
|
||||
ServerService,
|
||||
});
|
||||
|
||||
const isLoading = useLiveData(shareReaderService.reader.isLoading$);
|
||||
const error = useLiveData(shareReaderService.reader.error$);
|
||||
const data = useLiveData(shareReaderService.reader.data$);
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
const { mode, selector, isTemplate, templateName, templateSnapshotUrl } =
|
||||
@@ -105,47 +76,26 @@ export const SharePage = ({
|
||||
};
|
||||
}, [location.search]);
|
||||
|
||||
useEffect(() => {
|
||||
shareReaderService.reader.loadShare({
|
||||
serverId: serverService.server.id,
|
||||
workspaceId,
|
||||
docId,
|
||||
});
|
||||
}, [shareReaderService, docId, workspaceId, serverService.server.id]);
|
||||
|
||||
let element: ReactNode = null;
|
||||
if (isLoading) {
|
||||
element = null;
|
||||
} else if (data) {
|
||||
element = (
|
||||
return (
|
||||
<AppContainer>
|
||||
<SharePageInner
|
||||
workspaceId={data.workspaceId}
|
||||
docId={data.docId}
|
||||
workspaceBinary={data.workspaceBinary}
|
||||
docBinary={data.docBinary}
|
||||
publishMode={mode || data.publishMode}
|
||||
workspaceId={workspaceId}
|
||||
docId={docId}
|
||||
key={workspaceId + ':' + docId}
|
||||
publishMode={mode ?? undefined}
|
||||
selector={selector}
|
||||
isTemplate={isTemplate}
|
||||
templateName={templateName}
|
||||
templateSnapshotUrl={templateSnapshotUrl}
|
||||
/>
|
||||
);
|
||||
} else if (error) {
|
||||
// TODO(@JimmFly): handle error
|
||||
element = <PageNotFound />;
|
||||
} else {
|
||||
element = <PageNotFound noPermission />;
|
||||
}
|
||||
|
||||
return <AppContainer fallback={!element}>{element}</AppContainer>;
|
||||
</AppContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const SharePageInner = ({
|
||||
workspaceId,
|
||||
docId,
|
||||
workspaceBinary,
|
||||
docBinary,
|
||||
publishMode = 'page' as DocMode,
|
||||
publishMode = 'page',
|
||||
selector,
|
||||
isTemplate,
|
||||
templateName,
|
||||
@@ -153,20 +103,18 @@ const SharePageInner = ({
|
||||
}: {
|
||||
workspaceId: string;
|
||||
docId: string;
|
||||
workspaceBinary: Uint8Array;
|
||||
docBinary: Uint8Array;
|
||||
publishMode?: DocMode;
|
||||
selector?: EditorSelector;
|
||||
isTemplate?: boolean;
|
||||
templateName?: string;
|
||||
templateSnapshotUrl?: string;
|
||||
}) => {
|
||||
const serverService = useService(ServerService);
|
||||
const workspacesService = useService(WorkspacesService);
|
||||
const fetchService = useService(FetchService);
|
||||
const graphQLService = useService(GraphQLService);
|
||||
const [workspace, setWorkspace] = useState<Workspace | null>(null);
|
||||
const [page, setPage] = useState<Doc | null>(null);
|
||||
const [editor, setEditor] = useState<Editor | null>(null);
|
||||
const [noPermission, setNoPermission] = useState(false);
|
||||
const [editorContainer, setActiveBlocksuiteEditor] =
|
||||
useActiveBlocksuiteEditor();
|
||||
|
||||
@@ -181,38 +129,41 @@ const SharePageInner = ({
|
||||
isSharedMode: true,
|
||||
},
|
||||
{
|
||||
getDocStorage() {
|
||||
return new ReadonlyDocStorage({
|
||||
[workspaceId]: workspaceBinary,
|
||||
[docId]: docBinary,
|
||||
});
|
||||
},
|
||||
getAwarenessConnections() {
|
||||
return [];
|
||||
},
|
||||
getDocServer() {
|
||||
return null;
|
||||
},
|
||||
getLocalBlobStorage() {
|
||||
return EmptyBlobStorage;
|
||||
},
|
||||
getRemoteBlobStorages() {
|
||||
return [
|
||||
new CloudBlobStorage(workspaceId, fetchService, graphQLService),
|
||||
];
|
||||
local: {
|
||||
doc: {
|
||||
name: 'StaticCloudDocStorage',
|
||||
opts: {
|
||||
id: workspaceId,
|
||||
serverBaseUrl: serverService.server.baseUrl,
|
||||
},
|
||||
},
|
||||
blob: {
|
||||
name: 'CloudBlobStorage',
|
||||
opts: {
|
||||
id: workspaceId,
|
||||
serverBaseUrl: serverService.server.baseUrl,
|
||||
},
|
||||
},
|
||||
},
|
||||
remotes: {},
|
||||
}
|
||||
);
|
||||
|
||||
setWorkspace(workspace);
|
||||
|
||||
workspace.engine
|
||||
.waitForRootDocReady()
|
||||
.then(() => {
|
||||
workspace.engine.doc
|
||||
.waitForDocLoaded(workspace.id)
|
||||
.then(async () => {
|
||||
const { doc } = workspace.scope.get(DocsService).open(docId);
|
||||
|
||||
doc.blockSuiteDoc.load();
|
||||
doc.blockSuiteDoc.readonly = true;
|
||||
|
||||
await workspace.engine.doc.waitForDocLoaded(docId);
|
||||
|
||||
if (!doc.blockSuiteDoc.root) {
|
||||
throw new Error('Doc is empty');
|
||||
}
|
||||
|
||||
setPage(doc);
|
||||
|
||||
const editor = doc.scope.get(EditorsService).createEditor();
|
||||
@@ -226,6 +177,7 @@ const SharePageInner = ({
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
setNoPermission(true);
|
||||
});
|
||||
}, [
|
||||
docId,
|
||||
@@ -233,10 +185,7 @@ const SharePageInner = ({
|
||||
workspacesService,
|
||||
publishMode,
|
||||
selector,
|
||||
workspaceBinary,
|
||||
docBinary,
|
||||
fetchService,
|
||||
graphQLService,
|
||||
serverService.server.baseUrl,
|
||||
]);
|
||||
|
||||
const t = useI18n();
|
||||
@@ -281,6 +230,10 @@ const SharePageInner = ({
|
||||
[editor, setActiveBlocksuiteEditor, jumpToPageBlock, openPage, workspaceId]
|
||||
);
|
||||
|
||||
if (noPermission) {
|
||||
return <PageNotFound noPermission />;
|
||||
}
|
||||
|
||||
if (!workspace || !page || !editor) {
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user