feat(infra): framework

This commit is contained in:
EYHN
2024-04-17 14:12:29 +08:00
parent ab17a05df3
commit 06fda3b62c
467 changed files with 9996 additions and 8697 deletions
@@ -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`);
}
}