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.Text | Y.Map>[] ) => { 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(); protected readonly _proxy: DocCollectionMetaState; protected readonly _yMap: Y.Map< DocCollectionMetaState[keyof DocCollectionMetaState] >; commonFieldsUpdated = new Subject(); readonly doc: Y.Doc; docMetaAdded = new Subject(); docMetaRemoved = new Subject(); docMetaUpdated = new Subject(); 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; } 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(); 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) { 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; 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(); } }