UNPKG

@oobleck/fluid-backend

Version:

Fluid Framework backend for nteract RTC

147 lines (130 loc) 4.28 kB
import { EMPTY, fromEvent, Observable, of } from "rxjs"; import { filter, map, mergeMap } from "rxjs/operators"; import { DataObject, IMergeTreeDelta, IMergeTreeGroupMsg, IMergeTreeInsertMsg, IMergeTreeRemoveMsg, MergeTreeDeltaType, SharedMap, SharedString } from "@fluid-experimental/fluid-framework"; import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions"; import { CellSourceEdit, ICellMetadataEvent, MetadataEntryDef, PatchCellSourceInput, TextCellInput } from "../schema"; import { AugmentedCellEdit } from "./types"; enum PropertyKey { Metadata = "metadata", Source = "source", } /** * Fluid DataObject */ export class ProtoCellDDS<S extends TextCellInput = TextCellInput> extends DataObject<{}, S> { private sharedSource!: SharedString; private metadataMap!: SharedMap; //#region ISolidCell get source(): string { return this.sharedSource.getText(); } edits$!: Observable<AugmentedCellEdit>; metadata$!: Observable<ICellMetadataEvent>; get metadata(): MetadataEntryDef[] { const result: MetadataEntryDef[] = []; this.metadataMap?.forEach((value, key) => result.push({ key, value })); return result; } applySourceEdit({ type, diff, start, len }: PatchCellSourceInput) { switch (type) { case "insert": this.sharedSource.insertText(start, diff ?? ""); break; case "replace": this.sharedSource.replaceText(start, start + (len ?? 0), diff ?? ""); break; case "delete": this.sharedSource.removeText(start, start + (len ?? 0)); break; } } updateMetadata(key: string, value: unknown) { this.metadataMap.set(key, value); } //#endregion //#region DataObject protected async initializingFirstTime(input?: S): Promise<void> { const source = SharedString.create(this.runtime); const metadata = SharedMap.create(this.runtime); if (input) { source.insertText(0, input.source); input.metadata?.forEach(({ key, value }) => { metadata.set(key, value); }); } this.root.set(PropertyKey.Source, source.handle).set(PropertyKey.Metadata, metadata.handle); } protected async hasInitialized(): Promise<void> { this.sharedSource = await this.root.get(PropertyKey.Source).get(); this.metadataMap = await this.root.get(PropertyKey.Metadata).get(); this.setupObservables(); } private setupObservables() { this.edits$ = fromEvent<[ISequencedDocumentMessage, boolean]>(this.sharedSource, "op").pipe( filter(([, local]) => !local), mergeMap(([{ contents: delta }]): Observable<IMergeTreeDelta> => { if (delta.type === MergeTreeDeltaType.GROUP) { const { ops } = delta as IMergeTreeGroupMsg; return of(...ops); } else { return of(delta); } }), mergeMap((delta): Observable<CellSourceEdit> => { switch (delta.type) { case MergeTreeDeltaType.INSERT: { const { seg, pos1 } = delta as IMergeTreeInsertMsg; return of({ __typename: "TextInserted", id: this.id, diff: seg ?? "", start: pos1! }); } case MergeTreeDeltaType.REMOVE: { const { pos1, pos2 } = delta as IMergeTreeRemoveMsg; return of({ __typename: "TextDeleted", id: this.id, start: pos1!, len: pos2! - pos1! }); } case MergeTreeDeltaType.GROUP: console.log(delta); break; } return EMPTY; }), map((edit) => ({ source: this.sharedSource, edit })) ); this.metadata$ = fromEvent<[ISequencedDocumentMessage, boolean]>(this.metadataMap, "op").pipe( filter(([op, local]) => !local && op.contents.type === "set"), map(([{ contents }]) => { return { id: this.id, entry: { key: contents.key, value: contents.value.value } } as ICellMetadataEvent; }) ); } //#endregion }