read/write PPPPP persistence of excalidraw elements

This commit is contained in:
Andre Staltz 2024-01-05 19:34:58 +02:00
parent 8cd3dc130d
commit 1205933083
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
3 changed files with 101 additions and 19 deletions

81
main.js
View File

@ -4,12 +4,28 @@ const URL = require('node:url')
const p = require('node:util').promisify const p = require('node:util').promisify
const Keypair = require('ppppp-keypair') const Keypair = require('ppppp-keypair')
// WARNING monkey patch! --------------------------------------
const na = require('sodium-native')
na.sodium_malloc = function sodium_malloc_monkey_patched(n) {
return Buffer.alloc(n)
}
na.sodium_free = function sodium_free_monkey_patched() {}
// Electron > 20.3.8 breaks a napi method that `sodium_malloc`
// depends on to create external buffers. (see v8 memory cage)
//
// This crashes electron when called by various libraries, so
// we monkey-patch this particular function.
// ------------------------------------------------------------
process.env.ZOOBOARD_DATA ??= Path.join(app.getPath('appData'), 'zooboard') process.env.ZOOBOARD_DATA ??= Path.join(app.getPath('appData'), 'zooboard')
app.setPath('userData', process.env.ZOOBOARD_DATA) app.setPath('userData', process.env.ZOOBOARD_DATA)
const path = Path.resolve(app.getPath('userData'), 'ppppp') const path = Path.resolve(app.getPath('userData'), 'ppppp')
const keypairPath = Path.join(path, 'keypair.json') const keypairPath = Path.join(path, 'keypair.json')
const keypair = Keypair.loadOrCreateSync(keypairPath) const keypair = Keypair.loadOrCreateSync(keypairPath)
let mainWindow
let globalAccountID = null
let globalAccountName = null
const peer = require('secret-stack/bare')() const peer = require('secret-stack/bare')()
.use(require('secret-stack/plugins/net')) .use(require('secret-stack/plugins/net'))
@ -50,23 +66,10 @@ const peer = require('secret-stack/bare')()
}, },
}) })
// WARNING monkey patch! --------------------------------------
const na = require('sodium-native')
na.sodium_malloc = function sodium_malloc_monkey_patched(n) {
return Buffer.alloc(n)
}
na.sodium_free = function sodium_free_monkey_patched() {}
// Electron > 20.3.8 breaks a napi method that `sodium_malloc`
// depends on to create external buffers. (see v8 memory cage)
//
// This crashes electron when called by various libraries, so
// we monkey-patch this particular function.
// ------------------------------------------------------------
function createWindow() { function createWindow() {
const mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 1200, width: 1200,
height: 800, height: 1200,
title: 'Zooboard', title: 'Zooboard',
webPreferences: { webPreferences: {
preload: Path.join(__dirname, 'preload.js'), preload: Path.join(__dirname, 'preload.js'),
@ -82,27 +85,67 @@ function createWindow() {
}) })
mainWindow.loadURL(startUrl) mainWindow.loadURL(startUrl)
// mainWindow.webContents.openDevTools() mainWindow.webContents.openDevTools({ mode: 'bottom', activate: true })
} }
async function loadAccount() { async function loadAccount() {
if (globalAccountID !== null) {
return { id: globalAccountID, name: globalAccountName }
}
await peer.db.loaded() await peer.db.loaded()
const id = await p(peer.db.account.findOrCreate)({ subdomain: 'account' }) const id = await p(peer.db.account.findOrCreate)({ subdomain: 'account' })
globalAccountID = id
await p(peer.set.load)(id) await p(peer.set.load)(id)
await p(peer.dict.load)(id) await p(peer.dict.load)(id)
const profile = peer.dict.read(id, 'profile') const profile = peer.dict.read(id, 'profile')
return { id, name: profile?.name ?? '' } const name = profile?.name ?? ''
globalAccountName = name
return { id, name }
} }
async function setProfileName(ev, name,b,c,d) { async function setProfileName(ev, name) {
console.log('ev',ev,'name',name,b,c,d);
await p(peer.dict.update)('profile', { name }) await p(peer.dict.update)('profile', { name })
return name return name
} }
async function writeElements(ev, actions) {
if (globalAccountID === null) throw new Error('account not loaded')
for (const action of actions) {
await p(peer.db.feed.publish)({
account: globalAccountID,
domain: 'zooboardElements',
data: action,
})
}
}
function subscribeToReadElements() {
const elementsByID = new Map()
for (const msg of peer.db.msgs()) {
if (msg.data && msg.metadata.domain === 'zooboardElements') {
const { id, isDeleted } = msg.data
if (isDeleted) {
elementsByID.delete(id)
} else {
elementsByID.set(id, msg.data)
}
}
}
const initialElements = [...elementsByID.values()]
mainWindow.webContents.send('readElements', initialElements)
peer.db.onRecordAdded(({ msg }) => {
if (msg.data && msg.metadata.domain === 'zooboardElements') {
mainWindow.webContents.send('readElements', [msg.data])
}
})
}
app.whenReady().then(() => { app.whenReady().then(() => {
ipcMain.handle('loadAccount', loadAccount) ipcMain.handle('loadAccount', loadAccount)
ipcMain.handle('setProfileName', setProfileName) ipcMain.handle('setProfileName', setProfileName)
ipcMain.handle('writeElements', writeElements)
ipcMain.handle('subscribeToReadElements', subscribeToReadElements)
createWindow() createWindow()
app.on('activate', function () { app.on('activate', function () {

View File

@ -3,4 +3,9 @@ const { contextBridge, ipcRenderer } = require('electron/renderer')
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('electronAPI', {
loadAccount: () => ipcRenderer.invoke('loadAccount'), loadAccount: () => ipcRenderer.invoke('loadAccount'),
setProfileName: (name) => ipcRenderer.invoke('setProfileName', name), setProfileName: (name) => ipcRenderer.invoke('setProfileName', name),
writeElements: (actions) => ipcRenderer.invoke('writeElements', actions),
onReadElements: (callback) => {
ipcRenderer.invoke('subscribeToReadElements')
ipcRenderer.on('readElements', (_event, value) => callback(value))
}
}) })

View File

@ -52,6 +52,38 @@ function MyAccount() {
} }
function App() { function App() {
const elemsPersisted = new Map()
const [excalidrawAPI, setExcalidrawAPI] = useState(null)
function loadExcalidraw(api) {
if (excalidrawAPI) return
setExcalidrawAPI(api)
window.electronAPI.onReadElements((elems) => {
for (const elem of elems) {
if (elem.isDeleted) {
elemsPersisted.delete(elem.id)
} else {
elemsPersisted.set(elem.id, elem)
}
}
api.updateScene({ elements: [...elemsPersisted.values()] })
})
}
const updateElements = debounce((elems) => {
const actions = []
for (const elem of elems) {
const oldVersion = elemsPersisted.get(elem.id)?.version ?? 0
if (elem.version > oldVersion) {
actions.push(elem)
elemsPersisted.set(elem.id, { ...elem })
}
}
if (actions.length > 0) {
window.electronAPI.writeElements(actions)
}
}, 100)
return ( return (
<div className="flex flex-row items-stretch h-screen"> <div className="flex flex-row items-stretch h-screen">
<div className="w-1/5 flex flex-col bg-gray-200 p-2"> <div className="w-1/5 flex flex-col bg-gray-200 p-2">
@ -73,6 +105,8 @@ function App() {
image: false, image: false,
}, },
}} }}
onChange={updateElements}
excalidrawAPI={loadExcalidraw}
/> />
</div> </div>
</div> </div>