mirror of https://codeberg.org/pzp/zooboard.git
read/write PPPPP persistence of excalidraw elements
This commit is contained in:
parent
8cd3dc130d
commit
1205933083
81
main.js
81
main.js
|
@ -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 () {
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
34
src/App.js
34
src/App.js
|
@ -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>
|
||||||
|
|
Loading…
Reference in New Issue