mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-18 18:46:19 +08:00
feat(infra): framework
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { Entity } from '../../../framework';
|
||||
import type { DocScope } from '../scopes/doc';
|
||||
import type { DocMode } from './record';
|
||||
|
||||
export class Doc extends Entity {
|
||||
constructor(public readonly scope: DocScope) {
|
||||
super();
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.scope.props.docId;
|
||||
}
|
||||
|
||||
public readonly blockSuiteDoc = this.scope.props.blockSuiteDoc;
|
||||
public readonly record = this.scope.props.record;
|
||||
|
||||
readonly meta$ = this.record.meta$;
|
||||
readonly mode$ = this.record.mode$;
|
||||
readonly title$ = this.record.title$;
|
||||
|
||||
setMode(mode: DocMode) {
|
||||
this.record.setMode(mode);
|
||||
}
|
||||
|
||||
toggleMode() {
|
||||
this.record.toggleMode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { map } from 'rxjs';
|
||||
|
||||
import { Entity } from '../../../framework';
|
||||
import { LiveData } from '../../../livedata';
|
||||
import type { DocsStore } from '../stores/docs';
|
||||
import { DocRecord } from './record';
|
||||
|
||||
export class DocRecordList extends Entity {
|
||||
constructor(private readonly store: DocsStore) {
|
||||
super();
|
||||
}
|
||||
|
||||
private readonly pool = new Map<string, DocRecord>();
|
||||
|
||||
public readonly docs$ = LiveData.from<DocRecord[]>(
|
||||
this.store.watchDocIds().pipe(
|
||||
map(ids =>
|
||||
ids.map(id => {
|
||||
const exists = this.pool.get(id);
|
||||
if (exists) {
|
||||
return exists;
|
||||
}
|
||||
const record = this.framework.createEntity(DocRecord, { id });
|
||||
this.pool.set(id, record);
|
||||
return record;
|
||||
})
|
||||
)
|
||||
),
|
||||
[]
|
||||
);
|
||||
|
||||
public readonly isReady$ = LiveData.from(
|
||||
this.store.watchDocListReady(),
|
||||
false
|
||||
);
|
||||
|
||||
public doc$(id: string) {
|
||||
return this.docs$.map(record => record.find(record => record.id === id));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { DocMeta } from '@blocksuite/store';
|
||||
|
||||
import { Entity } from '../../../framework';
|
||||
import { LiveData } from '../../../livedata';
|
||||
import type { DocsStore } from '../stores/docs';
|
||||
|
||||
export type DocMode = 'edgeless' | 'page';
|
||||
|
||||
/**
|
||||
* # DocRecord
|
||||
*
|
||||
* Some data you can use without open a doc.
|
||||
*/
|
||||
export class DocRecord extends Entity<{ id: string }> {
|
||||
id: string = this.props.id;
|
||||
meta: Partial<DocMeta> | null = null;
|
||||
constructor(private readonly docsStore: DocsStore) {
|
||||
super();
|
||||
}
|
||||
|
||||
meta$ = LiveData.from<Partial<DocMeta>>(
|
||||
this.docsStore.watchDocMeta(this.id),
|
||||
{}
|
||||
);
|
||||
|
||||
setMeta(meta: Partial<DocMeta>): void {
|
||||
this.docsStore.setDocMeta(this.id, meta);
|
||||
}
|
||||
|
||||
mode$: LiveData<DocMode> = LiveData.from(
|
||||
this.docsStore.watchDocModeSetting(this.id),
|
||||
'page'
|
||||
).map(mode => (mode === 'edgeless' ? 'edgeless' : 'page'));
|
||||
|
||||
setMode(mode: DocMode) {
|
||||
this.docsStore.setDocModeSetting(this.id, mode);
|
||||
}
|
||||
|
||||
toggleMode() {
|
||||
this.setMode(this.mode$.value === 'edgeless' ? 'page' : 'edgeless');
|
||||
return this.mode$.value;
|
||||
}
|
||||
|
||||
title$ = this.meta$.map(meta => meta.title ?? '');
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
export { Doc } from './entities/doc';
|
||||
export type { DocMode } from './entities/record';
|
||||
export { DocRecord } from './entities/record';
|
||||
export { DocRecordList } from './entities/record-list';
|
||||
export { DocScope } from './scopes/doc';
|
||||
export { DocService } from './services/doc';
|
||||
export { DocsService } from './services/docs';
|
||||
|
||||
import type { Framework } from '../../framework';
|
||||
import {
|
||||
WorkspaceLocalState,
|
||||
WorkspaceScope,
|
||||
WorkspaceService,
|
||||
} from '../workspace';
|
||||
import { Doc } from './entities/doc';
|
||||
import { DocRecord } from './entities/record';
|
||||
import { DocRecordList } from './entities/record-list';
|
||||
import { DocScope } from './scopes/doc';
|
||||
import { DocService } from './services/doc';
|
||||
import { DocsService } from './services/docs';
|
||||
import { DocsStore } from './stores/docs';
|
||||
|
||||
export function configureDocModule(framework: Framework) {
|
||||
framework
|
||||
.scope(WorkspaceScope)
|
||||
.service(DocsService, [DocsStore])
|
||||
.store(DocsStore, [WorkspaceService, WorkspaceLocalState])
|
||||
.entity(DocRecord, [DocsStore])
|
||||
.entity(DocRecordList, [DocsStore])
|
||||
.scope(DocScope)
|
||||
.entity(Doc, [DocScope])
|
||||
.service(DocService);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Doc as BlockSuiteDoc } from '@blocksuite/store';
|
||||
|
||||
import { Scope } from '../../../framework';
|
||||
import type { DocRecord } from '../entities/record';
|
||||
|
||||
export class DocScope extends Scope<{
|
||||
docId: string;
|
||||
record: DocRecord;
|
||||
blockSuiteDoc: BlockSuiteDoc;
|
||||
}> {}
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Service } from '../../../framework';
|
||||
import { Doc } from '../entities/doc';
|
||||
|
||||
export class DocService extends Service {
|
||||
public readonly doc = this.framework.createEntity(Doc);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Service } from '../../../framework';
|
||||
import { ObjectPool } from '../../../utils';
|
||||
import type { Doc } from '../entities/doc';
|
||||
import { DocRecordList } from '../entities/record-list';
|
||||
import { DocScope } from '../scopes/doc';
|
||||
import type { DocsStore } from '../stores/docs';
|
||||
import { DocService } from './doc';
|
||||
|
||||
export class DocsService extends Service {
|
||||
list = this.framework.createEntity(DocRecordList);
|
||||
|
||||
pool = new ObjectPool<string, Doc>({
|
||||
onDelete(obj) {
|
||||
obj.scope.dispose();
|
||||
},
|
||||
});
|
||||
|
||||
constructor(private readonly store: DocsStore) {
|
||||
super();
|
||||
}
|
||||
|
||||
open(docId: string) {
|
||||
const docRecord = this.list.doc$(docId).value;
|
||||
if (!docRecord) {
|
||||
throw new Error('Doc record not found');
|
||||
}
|
||||
const blockSuiteDoc = this.store.getBlockSuiteDoc(docId);
|
||||
if (!blockSuiteDoc) {
|
||||
throw new Error('Doc not found');
|
||||
}
|
||||
|
||||
const exists = this.pool.get(docId);
|
||||
if (exists) {
|
||||
return { doc: exists.obj, release: exists.release };
|
||||
}
|
||||
|
||||
const docScope = this.framework.createScope(DocScope, {
|
||||
docId,
|
||||
blockSuiteDoc,
|
||||
record: docRecord,
|
||||
});
|
||||
|
||||
const doc = docScope.get(DocService).doc;
|
||||
|
||||
const { obj, release } = this.pool.put(docId, doc);
|
||||
|
||||
return { doc: obj, release };
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
import { type DocMeta } from '@blocksuite/store';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { distinctUntilChanged, Observable } from 'rxjs';
|
||||
|
||||
import { Store } from '../../../framework';
|
||||
import type { WorkspaceLocalState, WorkspaceService } from '../../workspace';
|
||||
import type { DocMode } from '../entities/record';
|
||||
|
||||
export class DocsStore extends Store {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly localState: WorkspaceLocalState
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getBlockSuiteDoc(id: string) {
|
||||
return this.workspaceService.workspace.docCollection.getDoc(id);
|
||||
}
|
||||
|
||||
watchDocIds() {
|
||||
return new Observable<string[]>(subscriber => {
|
||||
const emit = () => {
|
||||
subscriber.next(
|
||||
this.workspaceService.workspace.docCollection.meta.docMetas.map(
|
||||
v => v.id
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
emit();
|
||||
|
||||
const dispose =
|
||||
this.workspaceService.workspace.docCollection.meta.docMetaUpdated.on(
|
||||
emit
|
||||
).dispose;
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}).pipe(distinctUntilChanged((p, c) => isEqual(p, c)));
|
||||
}
|
||||
|
||||
watchDocMeta(id: string) {
|
||||
let meta: DocMeta | null = null;
|
||||
return new Observable<Partial<DocMeta>>(subscriber => {
|
||||
const emit = () => {
|
||||
if (meta === null) {
|
||||
// getDocMeta is heavy, so we cache the doc meta reference
|
||||
meta =
|
||||
this.workspaceService.workspace.docCollection.meta.getDocMeta(id) ||
|
||||
null;
|
||||
}
|
||||
subscriber.next({ ...meta });
|
||||
};
|
||||
|
||||
emit();
|
||||
|
||||
const dispose =
|
||||
this.workspaceService.workspace.docCollection.meta.docMetaUpdated.on(
|
||||
emit
|
||||
).dispose;
|
||||
return () => {
|
||||
dispose();
|
||||
};
|
||||
}).pipe(distinctUntilChanged((p, c) => isEqual(p, c)));
|
||||
}
|
||||
|
||||
watchDocListReady() {
|
||||
return this.workspaceService.workspace.engine.rootDocState$
|
||||
.map(state => !state.syncing)
|
||||
.asObservable();
|
||||
}
|
||||
|
||||
setDocMeta(id: string, meta: Partial<DocMeta>) {
|
||||
this.workspaceService.workspace.docCollection.setDocMeta(id, meta);
|
||||
}
|
||||
|
||||
setDocModeSetting(id: string, mode: DocMode) {
|
||||
this.localState.set(`page:${id}:mode`, mode);
|
||||
}
|
||||
|
||||
watchDocModeSetting(id: string) {
|
||||
return this.localState.watch<DocMode>(`page:${id}:mode`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user