Files
web-cad/frontend/src/crdt/YjsDocument.ts
T

132 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* YjsDocument Manages a Y.Doc with shared maps for CAD elements, layers, and blocks.
* Provides helpers to sync local state with the CRDT document.
*/
import * as Y from 'yjs';
import type { CADElement, CADLayer, BlockDefinition } from '../types/cad.types';
export interface YjsDocumentData {
elements: Y.Map<CADElement>;
layers: Y.Map<CADLayer>;
blocks: Y.Map<BlockDefinition>;
meta: Y.Map<unknown>;
}
export class YjsDocument {
readonly doc: Y.Doc;
readonly data: YjsDocumentData;
constructor() {
this.doc = new Y.Doc();
this.data = {
elements: this.doc.getMap<CADElement>('elements'),
layers: this.doc.getMap<CADLayer>('layers'),
blocks: this.doc.getMap<BlockDefinition>('blocks'),
meta: this.doc.getMap<unknown>('meta'),
};
}
/** Get all elements as a plain array */
getElements(): CADElement[] {
return Array.from(this.data.elements.values());
}
/** Get all layers as a plain array */
getLayers(): CADLayer[] {
return Array.from(this.data.layers.values());
}
/** Get all blocks as a plain array */
getBlocks(): BlockDefinition[] {
return Array.from(this.data.blocks.values());
}
/** Add or update a single element */
setElement(el: CADElement): void {
this.doc.transact(() => {
this.data.elements.set(el.id, el);
});
}
/** Remove an element by id */
deleteElement(id: string): void {
this.data.elements.delete(id);
}
/** Add or update a layer */
setLayer(layer: CADLayer): void {
this.data.layers.set(layer.id, layer);
}
/** Remove a layer by id */
deleteLayer(id: string): void {
this.data.layers.delete(id);
}
/** Add or update a block definition */
setBlock(block: BlockDefinition): void {
this.data.blocks.set(block.id, block);
}
/** Remove a block by id */
deleteBlock(id: string): void {
this.data.blocks.delete(id);
}
/** Bulk-load local state into the Y.Doc (replaces all content) */
loadFromState(state: {
elements: CADElement[];
layers: CADLayer[];
blocks: BlockDefinition[];
}): void {
this.doc.transact(() => {
this.data.elements.clear();
for (const el of state.elements) {
this.data.elements.set(el.id, el);
}
this.data.layers.clear();
for (const layer of state.layers) {
this.data.layers.set(layer.id, layer);
}
this.data.blocks.clear();
for (const block of state.blocks) {
this.data.blocks.set(block.id, block);
}
});
}
/** Export current state as a plain object */
toState(): {
elements: CADElement[];
layers: CADLayer[];
blocks: BlockDefinition[];
} {
return {
elements: this.getElements(),
layers: this.getLayers(),
blocks: this.getBlocks(),
};
}
/** Encode full document state as update binary */
encodeState(): Uint8Array {
return Y.encodeStateAsUpdate(this.doc);
}
/** Apply a remote update binary */
applyUpdate(update: Uint8Array): void {
Y.applyUpdate(this.doc, update);
}
/** Register a callback for document changes */
onChange(callback: () => void): () => void {
this.doc.on('update', callback);
return () => this.doc.off('update', callback);
}
/** Destroy the document */
destroy(): void {
this.doc.destroy();
}
}