UNPKG

collaborative-ui

Version:

React component library for building real-time collaborative editing applications.

164 lines (163 loc) 7.24 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.JsonCrdtExplorerState = void 0; const json_crdt_1 = require("json-joy/lib/json-crdt"); const json_crdt_extensions_1 = require("json-joy/lib/json-crdt-extensions"); const Log_1 = require("json-joy/lib/json-crdt/log/Log"); const LogDecoder_1 = require("json-joy/lib/json-crdt/log/codec/LogDecoder"); const CborDecoder_1 = require("@jsonjoy.com/json-pack/lib/cbor/CborDecoder"); const rxjs_1 = require("rxjs"); const gzip_1 = require("@jsonjoy.com/util/lib/compression/gzip"); const util_1 = require("./util"); const JsonCrdtLogState_1 = require("../JsonCrdtLog/JsonCrdtLogState"); class JsonCrdtExplorerState { constructor() { this.files$ = new rxjs_1.BehaviorSubject([]); this.selected$ = new rxjs_1.BehaviorSubject(''); this.file$ = new rxjs_1.BehaviorSubject(null); this.sid = json_crdt_1.Model.sid(); this.select = (id) => { this.selected$.next(id); }; this.openFile = (log, name = 'JSON CRDT document' + (this.newCnt > 1 ? ` (${this.newCnt})` : '')) => { const now = Date.now(); const logState = new JsonCrdtLogState_1.JsonCrdtLogState(log, { view: 'model' }); const file = { id: Math.random().toString(36).slice(2) + '.' + now, openTime: now, log, name, logState, }; this.files$.next([...this.files$.getValue(), file]); this.select(file.id); return file; }; this.close = (id) => { const list = this.files$.getValue().filter((m) => m.id !== id); this.files$.next(list); const files = this.files$.getValue(); if (files.length && !this.file$.getValue()) this.select(files[0].id); }; this.rename = (id, name) => { const files = this.files$.getValue(); const file = files.find((m) => m.id === id); if (!file) return; file.name = name; this.files$.next([...files]); }; this.addFile = async (file) => { if (!file) return; let uint8 = new Uint8Array(await file.arrayBuffer()); const name = file.name ? (0, util_1.stripExtensions)(file.name) : 'model'; if (file.name.endsWith('patches.bin')) { const cborDecoder = new CborDecoder_1.CborDecoder(); const array = cborDecoder.decode(uint8); if (!Array.isArray(array)) throw new Error('Incompatible JSON CRDT log file.'); const patches = array.map((patch) => json_crdt_1.Patch.fromBinary(patch)); const lastPatch = patches[patches.length - 1]; if (!lastPatch) throw new Error('Incompatible JSON CRDT log file.'); const id = lastPatch.getId(); if (!id) throw new Error('Incompatible JSON CRDT log file.'); const model = json_crdt_1.Model.create(undefined, id.sid); const log = new Log_1.Log(() => model.clone()); log.end.applyBatch(patches); log.end.api.autoFlush(); log.end.setSid(this.sid); this.openFile(log, name); } if (file.name.endsWith('.crdt')) { try { uint8 = (await (0, gzip_1.ungzip)(uint8)); } catch { } const model = json_crdt_1.Model.load(uint8, this.sid); const log = new Log_1.Log(() => model); log.end.api.autoFlush(); log.end.setSid(this.sid); this.openFile(log, name); } else if (file.name.endsWith('.cbor.gz') || file.name.endsWith('.seq.cbor') || file.name.endsWith('.seq.cbor.gz')) { await this.addLog(uint8, name); } }; this.addLog = async (uint8, name, display) => { try { uint8 = await (0, gzip_1.ungzip)(uint8); } catch { } const cborDecoder = new CborDecoder_1.CborDecoder(); const decoder = new LogDecoder_1.LogDecoder({ cborDecoder }); const { history: log } = decoder.decode(uint8, { history: true, frontier: true, format: 'seq.cbor' }); if (!log) throw new Error('Incompatible JSON CRDT log file.'); const start = log.start; log.start = () => { const model = start(); model.ext.register(json_crdt_extensions_1.ext.quill); return model; }; log.end.ext.register(json_crdt_extensions_1.ext.quill); log.end.api.autoFlush(); log.end.setSid(this.sid); const file = this.openFile(log, name); file.display = display; if (file.display === 'text') { const logState = file.logState; logState.patchState.show$.next(false); logState.modelState.showModel$.next(false); logState.modelState.showView$.next(false); logState.modelState.showDisplay$.next(true); } else if (file.display === 'blogpost' || file.display === 'todo') { const logState = file.logState; logState.patchState.show$.next(false); logState.modelState.showModel$.next(false); logState.modelState.showView$.next(true); logState.modelState.showDisplay$.next(true); } else if (file.display === 'quill') { const logState = file.logState; logState.patchState.show$.next(false); logState.modelState.showModel$.next(false); logState.modelState.showView$.next(true); logState.modelState.showDisplay$.next(true); } }; this.addTrace = async (uint8, trace) => { return await this.addLog(uint8, trace.name, trace.display); }; this.addFiles = async (files) => { files.map((file) => this.addFile(file).catch(() => { })); }; this.newCnt = 0; this.createNew = () => { const schema = json_crdt_1.s.obj({}); const model = json_crdt_1.Model.create(schema, this.sid); this.createFromModel(model); }; this.createFromModel = (model) => { this.newCnt++; const log = Log_1.Log.fromNewModel(model); log.end.api.autoFlush(); this.openFile(log); }; this.files$ .pipe((0, rxjs_1.switchMap)(() => this.selected$), (0, rxjs_1.map)((selected) => { if (!selected) return null; const files = this.files$.getValue(); return files.find((file) => file.id === selected) ?? null; })) .subscribe(this.file$); } } exports.JsonCrdtExplorerState = JsonCrdtExplorerState;