Files
AFFiNE-Mirror/blocksuite/framework/store/src/test/test-meta.ts
T

210 lines
4.4 KiB
TypeScript

import { Subject } from 'rxjs';
import type * as Y from 'yjs';
import type {
DocMeta,
DocsPropertiesMeta,
WorkspaceMeta,
} from '../model/index.js';
import { createYProxy } from '../reactive/proxy.js';
type DocCollectionMetaState = {
pages?: unknown[];
properties?: DocsPropertiesMeta;
name?: string;
avatar?: string;
};
export class TestMeta implements WorkspaceMeta {
private readonly _handleDocCollectionMetaEvents = (
events: Y.YEvent<Y.Array<unknown> | Y.Text | Y.Map<unknown>>[]
) => {
events.forEach(e => {
const hasKey = (k: string) =>
e.target === this._yMap && e.changes.keys.has(k);
if (
e.target === this.yDocs ||
e.target.parent === this.yDocs ||
hasKey('pages')
) {
this._handleDocMetaEvent();
}
if (hasKey('name') || hasKey('avatar')) {
this._handleCommonFieldsEvent();
}
});
};
private _prevDocs = new Set<string>();
protected readonly _proxy: DocCollectionMetaState;
protected readonly _yMap: Y.Map<
DocCollectionMetaState[keyof DocCollectionMetaState]
>;
commonFieldsUpdated = new Subject<void>();
readonly doc: Y.Doc;
docMetaAdded = new Subject<string>();
docMetaRemoved = new Subject<string>();
docMetaUpdated = new Subject<void>();
readonly id: string = 'meta';
get avatar() {
return this._proxy.avatar;
}
get docMetas() {
if (!this._proxy.pages) {
return [] as DocMeta[];
}
return this._proxy.pages as DocMeta[];
}
get docs() {
return this._proxy.pages;
}
get name() {
return this._proxy.name;
}
get properties(): DocsPropertiesMeta {
const meta = this._proxy.properties;
if (!meta) {
return {
tags: {
options: [],
},
};
}
return meta;
}
get yDocs() {
return this._yMap.get('pages') as unknown as Y.Array<unknown>;
}
constructor(doc: Y.Doc) {
this.doc = doc;
const map = doc.getMap(this.id) as Y.Map<
DocCollectionMetaState[keyof DocCollectionMetaState]
>;
this._yMap = map;
this._proxy = createYProxy(map);
this._yMap.observeDeep(this._handleDocCollectionMetaEvents);
}
private _handleCommonFieldsEvent() {
this.commonFieldsUpdated.next();
}
private _handleDocMetaEvent() {
const { docMetas, _prevDocs } = this;
const newDocs = new Set<string>();
docMetas.forEach(docMeta => {
if (!_prevDocs.has(docMeta.id)) {
this.docMetaAdded.next(docMeta.id);
}
newDocs.add(docMeta.id);
});
_prevDocs.forEach(prevDocId => {
const isRemoved = newDocs.has(prevDocId) === false;
if (isRemoved) {
this.docMetaRemoved.next(prevDocId);
}
});
this._prevDocs = newDocs;
this.docMetaUpdated.next();
}
addDocMeta(doc: DocMeta, index?: number) {
this.doc.transact(() => {
if (!this.docs) {
return;
}
const docs = this.docs as unknown[];
if (index === undefined) {
docs.push(doc);
} else {
docs.splice(index, 0, doc);
}
}, this.doc.clientID);
}
getDocMeta(id: string) {
return this.docMetas.find(doc => doc.id === id);
}
initialize() {
if (!this._proxy.pages) {
this._proxy.pages = [];
}
}
removeDocMeta(id: string) {
// you cannot delete a doc if there's no doc
if (!this.docs) {
return;
}
const docMeta = this.docMetas;
const index = docMeta.findIndex((doc: DocMeta) => id === doc.id);
if (index === -1) {
return;
}
this.doc.transact(() => {
if (!this.docs) {
return;
}
this.docs.splice(index, 1);
}, this.doc.clientID);
}
setAvatar(avatar: string) {
this.doc.transact(() => {
this._proxy.avatar = avatar;
}, this.doc.clientID);
}
setDocMeta(id: string, props: Partial<DocMeta>) {
const docs = (this.docs as DocMeta[]) ?? [];
const index = docs.findIndex((doc: DocMeta) => id === doc.id);
this.doc.transact(() => {
if (!this.docs) {
return;
}
if (index === -1) return;
const doc = this.docs[index] as Record<string, unknown>;
Object.entries(props).forEach(([key, value]) => {
doc[key] = value;
});
}, this.doc.clientID);
}
setName(name: string) {
this.doc.transact(() => {
this._proxy.name = name;
}, this.doc.clientID);
}
setProperties(meta: DocsPropertiesMeta) {
this._proxy.properties = meta;
this.docMetaUpdated.next();
}
}