import { render } from "preact"; import { html } from "htm/preact"; import { Signal, signal, useSignal } from "@preact/signals"; import { useId } from "preact/hooks"; import { ActorID, createActorID, Dot, LWWMapCRDT, LWWRegisterCRDT, SequenceCRDT, } from "@caret-js/core"; import { Arrow } from "./components/Arrow"; const crdt1 = signal( new SequenceCRDT, LWWMapCRDT>(), ); const crdt2 = signal( new SequenceCRDT, LWWMapCRDT>(), ); const actor1 = createActorID("A"); const actor2 = createActorID("B"); function App() { return html`

CRDT 1

<${SequenceCRDTPlayground} actorId=${actor1} crdt=${crdt1.value} setCrdt=${( newCrdt: SequenceCRDT< Map, LWWMapCRDT >, ) => (crdt1.value = newCrdt)} />

CRDT 2

<${SequenceCRDTPlayground} actorId=${actor2} crdt=${crdt2.value} setCrdt=${( newCrdt: SequenceCRDT< Map, LWWMapCRDT >, ) => (crdt2.value = newCrdt)} />
`; } interface SequenceCRDTPlaygroundProps { actorId: ActorID; crdt: SequenceCRDT, LWWMapCRDT>; setCrdt: ( crdt: SequenceCRDT, LWWMapCRDT>, ) => void; } function SequenceCRDTPlayground({ actorId, crdt, setCrdt, }: SequenceCRDTPlaygroundProps) { const cursor = useSignal(null); const [value, cursorIndex] = crdt.getValueWithCursor(cursor.value); function drawCursor(index: number) { if (cursorIndex === index) { return html``; } return null; } const playgroundId = useId(); return html`
        ${[...crdt.DEBUG_data.values()]
          .sort((a, b) => a.id.compare(b.id))
          .map((item) => JSON.stringify(item, replacer))
          .join("\n")}
      
{ // Focus the div to receive keyboard events console.log("click", e.currentTarget); (e.currentTarget as HTMLDivElement).focus(); cursor.value = null; }} onKeyDown=${(e: KeyboardEvent) => { if (e.key.length === 1) { const result = crdt.insert( actorId, cursor.value, new LWWMapCRDT( new Map([ ["char", new LWWRegisterCRDT(e.key, new Dot(actorId, 1))], ]), ), ); setCrdt(result[0]); cursor.value = result[1]; e.preventDefault(); } }} > ${drawCursor(0)} ${value.map((item, i) => { return html`
{ e.stopPropagation(); cursor.value = item.id; const inputDiv = (e.currentTarget as HTMLDivElement).closest( ".input", ) as HTMLDivElement; inputDiv.focus(); }} onDblClick=${(e: MouseEvent) => { e.stopPropagation(); console.log("delete", item.id); setCrdt(crdt.delete(actorId, item.id)); }} > ${`${item.id.actorId}-${item.id.counter}`}: <${LWWMapCRDTPlayground} actorId=${actorId} crdt=${crdt.DEBUG_data.get(item.id)?.value} setCrdt=${(newCrdt: LWWMapCRDT) => { setCrdt(crdt.setItemValue(actorId, item.id, newCrdt)); }} />
${drawCursor(i + 1)} <${Arrow} from=${`[data-id='${playgroundId}-${item.id.actorId}-${item.id.counter}']`} to=${`[data-id=${item.DEBUG_left ? `'${playgroundId}-${item.DEBUG_left.actorId}-${item.DEBUG_left.counter}'` : `'${playgroundId}-null'`}]`} fromSide="left" toSide=${item.DEBUG_left === null ? "left" : "right"} /> `; })}
`; } interface LWWMapCRDTPlaygroundProps { actorId: ActorID; crdt: LWWMapCRDT; setCrdt: (crdt: LWWMapCRDT) => void; } function LWWMapCRDTPlayground({ actorId, crdt, setCrdt, }: LWWMapCRDTPlaygroundProps) { return html`
${[...crdt.DEBUG_data.entries()].map( ([key, register]) => html`
${key}: <${LWWRegisterCRDTPlayground} actorId=${actorId} crdt=${crdt.getRegister(key)!} />
`, )}
`; } interface LWWRegisterCRDTPlaygroundProps { actorId: ActorID; crdt: LWWRegisterCRDT; } function LWWRegisterCRDTPlayground({ actorId, crdt, }: LWWRegisterCRDTPlaygroundProps) { console.log("LWWRegisterCRDTPlayground render", crdt); return html` { e.stopPropagation(); const input = e.target as HTMLInputElement; console.log(crdt.set(actorId, input.value)); }} onClick=${(e: MouseEvent) => e.stopPropagation()} /> `; } function replacer(key: string, value: unknown): unknown { if (value instanceof Map) { return Object.fromEntries(value.entries()); } if (value instanceof Set) { return Array.from(value).sort(); } if (value instanceof Dot) { return `#${value.actorId}-${value.counter}`; } if (value instanceof LWWRegisterCRDT) { return `LWWRegister(${JSON.stringify(value.getValue(), replacer)})`; } return value; } render(html`<${App} />`, document.querySelector("#app")!);