Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import "@ironcalc/workbook/style.css";
import { Webxdc } from "@webxdc/types";
import { encode as base64Encode, decode as base64Decode } from "base64-arraybuffer";

// Concurrent WASM initialization can replace the instance backing existing
// models. Share the pending initialization across StrictMode effect runs.
let initialization: ReturnType<typeof init> | undefined;

declare global {
interface Window {
webxdc: Webxdc<{ data: string, sender: string }>;
Expand Down Expand Up @@ -53,16 +57,24 @@ function App() {
// sure we don't actually
let cancelled = false;
async function start() {
// initialize ironcalc. This is idempotent so can be called multiple times in StrictMode.
await init();
initialization ??= init();
await initialization;
// the first time around in StrictMode, we have been cancelled so
// we don't attempt to set the model.
if (cancelled) {
return;
}
// the second time in StrictMode (or the first time outside of StrictMode) is the real one
// so we load the model from storage or create a new one and set it in state.
const newModel = loadSelectedModelFromStorage() ?? createNewModel();
const storedModel = loadSelectedModelFromStorage();
if (!storedModel) {
// A replay cursor is only valid together with the workbook it describes.
localStorage.removeItem("last_serial");
// With no cached local edits, historical updates from this device
// must also be replayed instead of being mistaken for live echoes.
localStorage.removeItem("uuid");
}
const newModel = storedModel ?? createNewModel();
setModel(newModel);
}
// we start the async here, not waiting for its completion
Expand Down Expand Up @@ -107,31 +119,35 @@ function App() {

// Incoming: apply remote diffs to the model.
useEffect(() => {
if (listenerRegistered.current) {
// Register only after WASM and the workbook are ready.
// Delta Chat retains updates and replays them when we register.
if (!model || listenerRegistered.current) {
return
}
listenerRegistered.current = true
window.webxdc.setUpdateListener((update) => {
const model = modelRef.current;
const payload = update.payload;
localStorage.setItem("last_serial", update.serial.toString());
if (!model) {
console.warn("Received external diffs but model is not initialized yet");
return
}
if (payload.sender === uuid) {
localStorage.setItem("last_serial", update.serial.toString());
return
}
// Decode base64 back to binary and convert to Uint8Array
const diffBuffer = base64Decode(payload.data);
const diff = new Uint8Array(diffBuffer);
model.applyExternalDiffs(diff);
saveSelectedModelInStorage(model);
// Never acknowledge an update before its changes have been saved.
localStorage.setItem("last_serial", update.serial.toString());
// The model is mutated in place; bump the revision so IronCalc repaints
// the canvas without disturbing an in-progress edit.
setExternalRevision((revision) => revision + 1);
}, get_last_serial())
}, [uuid])
}, [model, uuid])


if (!model) {
Expand Down