diff --git a/main.js b/main.js index ab85f30..96cbefd 100644 --- a/main.js +++ b/main.js @@ -106,14 +106,6 @@ async function loadAccount() { globalAccountID = id await p(peer.set.load)(id) await p(peer.dict.load)(id) - peer.conductor.start( - id, - [ - ['profile@dict', 'zooboardElements@newest-100', 'hubs@set'], - ['profile@dict', 'zooboardElements@newest-100'], - ], - 64_000_000 - ) // Read hubs const multiaddrs = peer.set.values('hubs') @@ -134,38 +126,82 @@ async function setProfileName(ev, name) { return name } -async function writeElements(ev, actions) { - await loadAccount() // FIXME: ideally the frontend shouldn't do this +async function writeElements(ev, elements) { if (globalAccountID === null) throw new Error('account not loaded') - for (const action of actions) { + for (const element of elements) { await p(peer.db.feed.publish)({ account: globalAccountID, domain: 'zooboardElements', - data: action, + data: element, }) } } +let hasSubscribedToReadElements = false function subscribeToReadElements() { + if (hasSubscribedToReadElements) return + hasSubscribedToReadElements = true + + // Load initial elements and inform renderer const elementsByID = new Map() - for (const msg of peer.db.msgs()) { + const msgIDToElemID = new Map() + for (const { id: msgID, msg } of peer.db.records()) { if (msg.data && msg.metadata.domain === 'zooboardElements') { - const { id, isDeleted } = msg.data + const { id: elemID, isDeleted } = msg.data if (isDeleted) { - elementsByID.delete(id) + elementsByID.delete(elemID) } else { - elementsByID.set(id, msg.data) + msgIDToElemID.set(msgID, elemID) + elementsByID.set(elemID, msg.data) } } } const initialElements = [...elementsByID.values()] mainWindow.webContents.send('readElements', initialElements) - peer.db.onRecordAdded(({ msg }) => { + // Subscribe to new elements and inform renderer + peer.db.onRecordAdded(({ id: msgID, msg }) => { if (msg.data && msg.metadata.domain === 'zooboardElements') { + const { id: elemID, isDeleted } = msg.data + if (isDeleted) { + elementsByID.delete(elemID) + } else { + msgIDToElemID.set(msgID, elemID) + elementsByID.set(elemID, msg.data) + } mainWindow.webContents.send('readElements', [msg.data]) } }) + + // Subscribe to deleted elements and inform renderer + peer.db.onRecordDeletedOrErased((msgID) => { + const elemID = msgIDToElemID.get(msgID) + if (!elemID) return + msgIDToElemID.delete(msgID) + // Is there some other msgID that supports this elemID? If so, bail out + for (const [, remainingElemID] of msgIDToElemID) { + if (remainingElemID === elemID) { + return + } + } + // If not, delete the element + elementsByID.delete(elemID) + mainWindow.webContents.send('readElements', [ + { id: elemID, isDeleted: true }, + ]) + }) + + // Finally safe to kickstart replication and garbage collection + setTimeout(() => { + peer.conductor.start( + globalAccountID, + [ + ['profile@dict', 'zooboardElements@newest-100', 'hubs@set'], + ['profile@dict', 'zooboardElements@newest-100'], + ], + 64_000 + ) + }, 32) } async function scheduleWithHub(multiaddr) { diff --git a/src/App.js b/src/App.js index d6a0ef5..b3467c1 100644 --- a/src/App.js +++ b/src/App.js @@ -3,6 +3,9 @@ import { Excalidraw } from '@excalidraw/excalidraw' import './App.css' import debounce from 'debounce' +const elemsPersisted = new Map() +let sceneInitialized = false + function MyAccount() { const nameInput = createRef() const [loaded, setLoaded] = useState(false) @@ -52,7 +55,6 @@ function MyAccount() { } function App() { - const elemsPersisted = new Map() const [excalidrawAPI, setExcalidrawAPI] = useState(null) function loadExcalidraw(api) { @@ -67,14 +69,15 @@ function App() { } } api.updateScene({ elements: [...elemsPersisted.values()] }) + if (!sceneInitialized) sceneInitialized = true }) } const updateElements = debounce((elems) => { - if (excalidrawAPI) return + if (!sceneInitialized) return const actions = [] for (const elem of elems) { - const oldVersion = elemsPersisted.get(elem.id)?.version ?? 0 + const oldVersion = elemsPersisted.get(elem.id)?.version ?? -1 if (elem.version > oldVersion) { actions.push(elem) elemsPersisted.set(elem.id, { ...elem })