Distributed Concurrent Editor

Artifact [912760b48e]
Login

Artifact 912760b48e41675bca15c641e275b248681374a53cd34d09a8a240ba7e55a0e2:


import * as edist from './protocol';
import * as websocket from './websocket';
import * as doc from './document';

function error(msg: string): void {
    document.body.setAttribute('style', "background-color: mistyrose;");
    console.error(msg);
}

function connected(url: string): void {
    document.body.setAttribute('style', "background-color: honeydew;");
    console.log(`WebSocket connection to ${url} established.`);
}

function loaded(): void {
    const documentNameInput = document.getElementById("documentname") as HTMLInputElement;
    if (!documentNameInput) {
        error("Unable to find document name input element");
        return;
    }
    const form = document.getElementById("form") as HTMLFormElement;
    if (!form) {
        error("Unable to find form element");
        return;
    }

    const content = document.getElementById("content") as HTMLDivElement;
    if (!content) {
        error("Unable to find content element");
        return;
    }

    const clientId = BigInt(1 + Math.round(4294967294 * Math.random()));

    const websocketManager = new websocket.WebSocketManager();

    form.onsubmit = (event) => {
        event.preventDefault();
        event.stopPropagation();
        const documentName = documentNameInput.value;
        if (connect(content, clientId, websocketManager, documentName)) {
            documentNameInput.value = "";
            documentNameInput.placeholder = documentName;
        }
    };

    const documentNameDefault = "hello world";
    if (connect(content, clientId, websocketManager, documentNameDefault)) {
        documentNameInput.value = "";
        documentNameInput.placeholder = documentNameDefault;
    }
}

function connect(content: HTMLDivElement, clientId: bigint, websocketManager: websocket.WebSocketManager, documentName: string): boolean {
    if (documentName === "") {
        return false;
    }
    if (!websocketManager.connect(websocket.documentNameToURL(documentName))) {
        return false;
    }
    content.replaceChildren();
    let awaitingFirstMessage = true;
    websocketManager.onopen = (url: string, conn: WebSocket) => {
        connected(url);
        awaitingFirstMessage = true;
    };
    const docu = new doc.Document(clientId, websocketManager);
    websocketManager.ondata = (url: string, conn: WebSocket, msg: MessageEvent) => {
        const data: Blob = msg.data;
        data.arrayBuffer().then(buffer => {
            const array = new Uint8Array(buffer);
            const imessage = edist.Message.decode(array);
            if (imessage.updates) {
                docu.applyUpdates(imessage.updates);
                docu.render(content);

                if (awaitingFirstMessage) {
                    awaitingFirstMessage = false;
                    docu.markAllDirty();
                    const selection = window.getSelection();
                    if (selection !== null && content.firstChild !== null) {
                        selection.setBaseAndExtent(content.firstChild, 0, content.firstChild, 0);
                    }
                }
            }
        });
    };
    websocketManager.onclose = (url: string) => {
        error(`WebSocket connection to ${url} lost`);
    };
    return true;
}

window.addEventListener('DOMContentLoaded', loaded);