sync-playground/client/main.ts

74 lines
2.1 KiB
TypeScript

import diff from "npm:fast-diff";
import { Packet } from "../proto.ts";
import { CausalTree, CausalTreeOp } from "../sync/ordt/causal-tree.ts";
import { PlainTextOperation, PlainTextORDT } from "../sync/ordt/plain-text.ts";
const textarea = document.querySelector("textarea")!;
const pt = new PlainTextORDT();
let me: string | undefined = undefined;
pt.onevent = event => {
if (event.at[0] === me) return;
textarea.value = pt.render()[0];
};
const socket = new WebSocket("/api/connect");
const initialized = Promise.withResolvers<Packet & { t: "init" }>();
socket.addEventListener("message", ev => {
const packet = JSON.parse(ev.data) as Packet;
if (packet.t === "init") {
for (const op of packet.ops) pt.apply(op);
initialized.resolve(packet);
} else if (packet.t === "op") {
pt.apply(packet.op);
}
});
const initPacket = await initialized.promise;
me = initPacket.you;
textarea.value = pt.render()[0];
textarea.addEventListener("input", () => {
const text = pt.render()[0];
const diffResults = diff(text, textarea.value, textarea.selectionStart);
let idx = 0;
for (const [id, str] of diffResults) {
if (id === diff.INSERT) {
let parentOp = pt.operations[pt.findOpAtTextIndex(idx)];
for (const glyph of str) {
const op: CausalTreeOp<PlainTextOperation> = {
type: "insert",
at: [me, ++pt.clock],
parent: parentOp,
sequence: glyph,
};
pt.apply(op);
socket.send(JSON.stringify(CausalTree.toWeakOp(op)));
parentOp = op;
idx += glyph.length;
}
}
if (id === diff.EQUAL) {
idx += str.length;
}
if (id === diff.DELETE) {
for (const glyph of str) {
const parentOp = pt.operations[pt.findOpAtTextIndex(idx + glyph.length)];
const op: CausalTreeOp<PlainTextOperation> = {
type: "delete",
at: [me, ++pt.clock],
parent: parentOp,
};
pt.apply(op);
socket.send(JSON.stringify(CausalTree.toWeakOp(op)));
}
}
}
});
Object.defineProperty(globalThis, "pt", { value: pt });
Object.defineProperty(globalThis, "textarea", { value: textarea });