diff --git a/blocksuite/affine/widget-remote-selection/src/manager/remote-color-manager.ts b/blocksuite/affine/widget-remote-selection/src/manager/remote-color-manager.ts index 4971598a44..b90e5b7845 100644 --- a/blocksuite/affine/widget-remote-selection/src/manager/remote-color-manager.ts +++ b/blocksuite/affine/widget-remote-selection/src/manager/remote-color-manager.ts @@ -5,7 +5,7 @@ import { multiPlayersColor } from './color-picker'; export class RemoteColorManager { private get awarenessStore() { - return this.std.store.workspace.awarenessStore; + return this.std.store.awarenessStore; } constructor(readonly std: BlockStdScope) { diff --git a/blocksuite/framework/store/src/model/workspace.ts b/blocksuite/framework/store/src/model/workspace.ts index 8a005ba96d..6c4590cf09 100644 --- a/blocksuite/framework/store/src/model/workspace.ts +++ b/blocksuite/framework/store/src/model/workspace.ts @@ -4,7 +4,6 @@ import type { Awareness } from 'y-protocols/awareness.js'; import type * as Y from 'yjs'; import type { IdGenerator } from '../utils/id-generator.js'; -import type { AwarenessStore } from '../yjs/awareness.js'; import type { CreateBlocksOptions, Doc, GetBlocksOptions } from './doc.js'; import type { Store } from './store/store.js'; import type { WorkspaceMeta } from './workspace-meta.js'; @@ -14,7 +13,6 @@ export interface Workspace { readonly meta: WorkspaceMeta; readonly idGenerator: IdGenerator; readonly blobSync: BlobEngine; - readonly awarenessStore: AwarenessStore; readonly onLoadDoc?: (doc: Y.Doc) => void; readonly onLoadAwareness?: (awareness: Awareness) => void; diff --git a/packages/frontend/core/src/components/affine/awareness/index.tsx b/packages/frontend/core/src/components/affine/awareness/index.tsx deleted file mode 100644 index b1531e2489..0000000000 --- a/packages/frontend/core/src/components/affine/awareness/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { WorkspaceService } from '@affine/core/modules/workspace'; -import { useLiveData, useService } from '@toeverything/infra'; -import { useEffect } from 'react'; - -import { AuthService } from '../../../modules/cloud'; - -const SyncAwarenessInnerLoggedIn = () => { - const authService = useService(AuthService); - const account = useLiveData(authService.session.account$); - const currentWorkspace = useService(WorkspaceService).workspace; - - useEffect(() => { - if (account && currentWorkspace) { - currentWorkspace.docCollection.awarenessStore.awareness.setLocalStateField( - 'user', - { - name: account.label, - // TODO(@eyhn): add avatar? - } - ); - - return () => { - currentWorkspace.docCollection.awarenessStore.awareness.setLocalStateField( - 'user', - null - ); - }; - } - return; - }, [currentWorkspace, account]); - - return null; -}; - -const SyncAwarenessInner = () => { - const session = useService(AuthService).session; - const loginStatus = useLiveData(session.status$); - - if (loginStatus === 'authenticated') { - return ; - } - - return null; -}; - -// TODO(@eyhn): we could do something more interesting here, e.g., show where the current user is -export const SyncAwareness = () => { - return ; -}; diff --git a/packages/frontend/core/src/components/providers/workspace-side-effects.tsx b/packages/frontend/core/src/components/providers/workspace-side-effects.tsx index b5e16abb8a..ef4bc26b9a 100644 --- a/packages/frontend/core/src/components/providers/workspace-side-effects.tsx +++ b/packages/frontend/core/src/components/providers/workspace-side-effects.tsx @@ -8,7 +8,6 @@ import { CopilotClient, setupAIProvider, } from '@affine/core/blocksuite/ai'; -import { SyncAwareness } from '@affine/core/components/affine/awareness'; import { useRegisterFindInPageCommands } from '@affine/core/components/hooks/affine/use-register-find-in-page-commands'; import { useRegisterWorkspaceCommands } from '@affine/core/components/hooks/use-register-workspace-commands'; import { OverCapacityNotification } from '@affine/core/components/over-capacity'; @@ -176,7 +175,6 @@ export const WorkspaceSideEffects = () => { return ( <> - ); diff --git a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-wrapper.tsx b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-wrapper.tsx index 2ceb81f39e..5b08c0c22a 100644 --- a/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-wrapper.tsx +++ b/packages/frontend/core/src/desktop/pages/workspace/detail-page/detail-page-wrapper.tsx @@ -50,7 +50,7 @@ const useLoadDoc = (pageId: string) => { if (doc && isInTrash) { doc.blockSuiteDoc.readonly = true; } - }, [currentWorkspace.docCollection.awarenessStore, doc, isInTrash]); + }, [doc, isInTrash]); return { doc, diff --git a/packages/frontend/core/src/modules/cloud/index.ts b/packages/frontend/core/src/modules/cloud/index.ts index d5046f38d5..3c06452a6d 100644 --- a/packages/frontend/core/src/modules/cloud/index.ts +++ b/packages/frontend/core/src/modules/cloud/index.ts @@ -39,6 +39,7 @@ import { type Framework } from '@toeverything/infra'; import { DocScope } from '../doc/scopes/doc'; import { DocService } from '../doc/services/doc'; +import { EditorScope } from '../editor'; import { GlobalCache, GlobalState } from '../storage/providers/global'; import { GlobalStateService } from '../storage/services/global'; import { UrlService } from '../url'; @@ -63,6 +64,7 @@ import { AuthService } from './services/auth'; import { CaptchaService } from './services/captcha'; import { CloudDocMetaService } from './services/cloud-doc-meta'; import { DefaultServerService } from './services/default-server'; +import { EditorUserCursorLabelService } from './services/editor-user-cursor-label'; import { EventSourceService } from './services/eventsource'; import { FetchService } from './services/fetch'; import { GraphQLService } from './services/graphql'; @@ -168,4 +170,10 @@ export function configureCloudModule(framework: Framework) { .entity(WorkspaceInvoices, [WorkspaceService, WorkspaceServerService]) .service(SelfhostLicenseService, [SelfhostLicenseStore, WorkspaceService]) .store(SelfhostLicenseStore, [WorkspaceServerService]); + + framework + .scope(WorkspaceScope) + .scope(DocScope) + .scope(EditorScope) + .service(EditorUserCursorLabelService, [WorkspaceServerService]); } diff --git a/packages/frontend/core/src/modules/cloud/services/editor-user-cursor-label.ts b/packages/frontend/core/src/modules/cloud/services/editor-user-cursor-label.ts new file mode 100644 index 0000000000..4ff455e952 --- /dev/null +++ b/packages/frontend/core/src/modules/cloud/services/editor-user-cursor-label.ts @@ -0,0 +1,27 @@ +import { OnEvent, Service } from '@toeverything/infra'; + +import type { Editor } from '../../editor'; +import { EditorInitialized } from '../../editor/events'; +import type { WorkspaceServerService } from './workspace-server'; + +@OnEvent(EditorInitialized, i => i.onEditorInitialized) +export class EditorUserCursorLabelService extends Service { + constructor(private readonly workspaceServerService: WorkspaceServerService) { + super(); + } + + onEditorInitialized(editor: Editor) { + if (this.workspaceServerService.server) { + const subscription = + this.workspaceServerService.server.account$.subscribe(account => { + editor.doc.blockSuiteDoc.awarenessStore.awareness.setLocalStateField( + 'user', + { + name: account?.label, + } + ); + }); + this.disposables.push(() => subscription.unsubscribe()); + } + } +} diff --git a/packages/frontend/core/src/modules/doc/events/index.ts b/packages/frontend/core/src/modules/doc/events/index.ts index b647517595..35affb82e6 100644 --- a/packages/frontend/core/src/modules/doc/events/index.ts +++ b/packages/frontend/core/src/modules/doc/events/index.ts @@ -1,5 +1,8 @@ import { createEvent } from '@toeverything/infra'; +import type { Doc } from '../entities/doc'; import type { DocRecord } from '../entities/record'; export const DocCreated = createEvent('DocCreated'); + +export const DocInitialized = createEvent('DocInitialized'); diff --git a/packages/frontend/core/src/modules/doc/services/docs.ts b/packages/frontend/core/src/modules/doc/services/docs.ts index 11be2445bb..b8d4d1add3 100644 --- a/packages/frontend/core/src/modules/doc/services/docs.ts +++ b/packages/frontend/core/src/modules/doc/services/docs.ts @@ -20,7 +20,7 @@ import { getAFFiNEWorkspaceSchema } from '../../workspace'; import type { Doc } from '../entities/doc'; import { DocPropertyList } from '../entities/property-list'; import { DocRecordList } from '../entities/record-list'; -import { DocCreated } from '../events'; +import { DocCreated, DocInitialized } from '../events'; import { DocScope } from '../scopes/doc'; import type { DocPropertiesStore } from '../stores/doc-properties'; import type { DocsStore } from '../stores/docs'; @@ -105,6 +105,8 @@ export class DocsService extends Service { const doc = docScope.get(DocService).doc; + doc.scope.emitEvent(DocInitialized, doc); + const { obj, release } = this.pool.put(docId, doc); return { doc: obj, release }; diff --git a/packages/frontend/core/src/modules/editor/events/index.ts b/packages/frontend/core/src/modules/editor/events/index.ts new file mode 100644 index 0000000000..549143b84a --- /dev/null +++ b/packages/frontend/core/src/modules/editor/events/index.ts @@ -0,0 +1,5 @@ +import { createEvent } from '@toeverything/infra'; + +import type { Editor } from '../entities/editor'; + +export const EditorInitialized = createEvent('EditorInitialized'); diff --git a/packages/frontend/core/src/modules/editor/services/editors.ts b/packages/frontend/core/src/modules/editor/services/editors.ts index ee38b5415a..0c68395ade 100644 --- a/packages/frontend/core/src/modules/editor/services/editors.ts +++ b/packages/frontend/core/src/modules/editor/services/editors.ts @@ -1,9 +1,12 @@ import { Service } from '@toeverything/infra'; import { Editor } from '../entities/editor'; +import { EditorInitialized } from '../events'; export class EditorsService extends Service { createEditor() { - return this.framework.createEntity(Editor); + const editor = this.framework.createEntity(Editor); + editor.scope.emitEvent(EditorInitialized, editor); + return editor; } } diff --git a/packages/frontend/core/src/modules/workspace/entities/workspace.ts b/packages/frontend/core/src/modules/workspace/entities/workspace.ts index bc40da16fc..bebad39be7 100644 --- a/packages/frontend/core/src/modules/workspace/entities/workspace.ts +++ b/packages/frontend/core/src/modules/workspace/entities/workspace.ts @@ -1,7 +1,6 @@ import type { Workspace as WorkspaceInterface } from '@blocksuite/affine/store'; import { Entity, LiveData } from '@toeverything/infra'; import { Observable } from 'rxjs'; -import type { Awareness } from 'y-protocols/awareness.js'; import { WorkspaceImpl } from '../impls/workspace'; import type { WorkspaceScope } from '../scopes/workspace'; @@ -58,10 +57,6 @@ export class Workspace extends Entity { return this._docCollection; } - get awareness() { - return this.docCollection.awarenessStore.awareness as Awareness; - } - get rootYDoc() { return this.docCollection.doc; } diff --git a/packages/frontend/core/src/modules/workspace/impls/doc.ts b/packages/frontend/core/src/modules/workspace/impls/doc.ts index 73a5a04b15..f042f7bf18 100644 --- a/packages/frontend/core/src/modules/workspace/impls/doc.ts +++ b/packages/frontend/core/src/modules/workspace/impls/doc.ts @@ -1,7 +1,7 @@ import { SpecProvider } from '@blocksuite/affine/blocks'; import { Slot } from '@blocksuite/affine/global/utils'; import { - type AwarenessStore, + AwarenessStore, type Doc, type GetBlocksOptions, type Query, @@ -10,13 +10,13 @@ import { type YBlock, } from '@blocksuite/affine/store'; import { signal } from '@preact/signals-core'; +import { Awareness } from 'y-protocols/awareness.js'; import * as Y from 'yjs'; type DocOptions = { id: string; collection: Workspace; doc: Y.Doc; - awarenessStore: AwarenessStore; }; export class DocImpl implements Doc { @@ -155,12 +155,11 @@ export class DocImpl implements Doc { return this._yBlocks; } - constructor({ id, collection, doc, awarenessStore }: DocOptions) { + constructor({ id, collection, doc }: DocOptions) { this.id = id; this.rootDoc = doc; - this.awarenessStore = awarenessStore; - this._ySpaceDoc = this._initSubDoc() as Y.Doc; + this.awarenessStore = new AwarenessStore(new Awareness(this._ySpaceDoc)); this._yBlocks = this._ySpaceDoc.getMap('blocks'); this._collection = collection; @@ -228,12 +227,14 @@ export class DocImpl implements Doc { } private _destroy() { + this.awarenessStore.destroy(); this._ySpaceDoc.destroy(); this._onLoadSlot.dispose(); this._loaded = false; } dispose() { + this._destroy(); this.slots.historyUpdated.dispose(); if (this.ready) { @@ -303,6 +304,7 @@ export class DocImpl implements Doc { this.spaceDoc.load(); this.workspace.onLoadDoc?.(this.spaceDoc); + this.workspace.onLoadAwareness?.(this.awarenessStore.awareness); this._initYBlocks(); diff --git a/packages/frontend/core/src/modules/workspace/impls/workspace.ts b/packages/frontend/core/src/modules/workspace/impls/workspace.ts index 6e1bc02fdf..650d3b1885 100644 --- a/packages/frontend/core/src/modules/workspace/impls/workspace.ts +++ b/packages/frontend/core/src/modules/workspace/impls/workspace.ts @@ -4,7 +4,6 @@ import { } from '@blocksuite/affine/global/exceptions'; import { NoopLogger, Slot } from '@blocksuite/affine/global/utils'; import { - AwarenessStore, type CreateBlocksOptions, type Doc, type GetBlocksOptions, @@ -19,7 +18,7 @@ import { type BlobSource, MemoryBlobSource, } from '@blocksuite/affine/sync'; -import { Awareness } from 'y-protocols/awareness.js'; +import type { Awareness } from 'y-protocols/awareness.js'; import * as Y from 'yjs'; import { DocImpl } from './doc'; @@ -33,8 +32,6 @@ type WorkspaceOptions = { }; export class WorkspaceImpl implements Workspace { - readonly awarenessStore: AwarenessStore; - readonly blobSync: BlobEngine; readonly blockCollections = new Map(); @@ -68,11 +65,9 @@ export class WorkspaceImpl implements Workspace { }: WorkspaceOptions = {}) { this.id = id || ''; this.doc = new Y.Doc({ guid: id }); - this.awarenessStore = new AwarenessStore(new Awareness(this.doc)); this.onLoadDoc = onLoadDoc; - this.onLoadAwareness = onLoadAwareness; this.onLoadDoc?.(this.doc); - this.onLoadAwareness?.(this.awarenessStore.awareness); + this.onLoadAwareness = onLoadAwareness; blobSource = blobSource ?? new MemoryBlobSource(); const logger = new NoopLogger(); @@ -91,7 +86,6 @@ export class WorkspaceImpl implements Workspace { id: docId, collection: this, doc: this.doc, - awarenessStore: this.awarenessStore, }); this.blockCollections.set(doc.id, doc); }); @@ -139,10 +133,6 @@ export class WorkspaceImpl implements Workspace { }) as Store; } - dispose() { - this.awarenessStore.destroy(); - } - private _getDoc(docId: string): Doc | null { const space = this.docs.get(docId) as Doc | undefined; return space ?? null; @@ -172,4 +162,8 @@ export class WorkspaceImpl implements Workspace { this.meta.removeDocMeta(docId); this.blockCollections.delete(docId); } + + dispose() { + this.blockCollections.forEach(doc => doc.dispose()); + } }