Ely Office: six offline-first desktop apps in Rust and GPUI
ElyDoc, ElyTable, ElyShow (LibreOfficeKit), ElyPDF (MuPDF), ElyTree (Git), ElyMark (Milkdown and CodeMirror in CEF). ElyMail and ElyAgent follow.
This commit is contained in:
817 files changed
+143101
No files matched your search
@@ -0,0 +1,90 @@
|
||||
// Mermaid previews; each editor owns its records.
|
||||
import { failure } from './failure.js'
|
||||
|
||||
// Beside this script; the page's base is the document's folder.
|
||||
const here = document.currentScript.src
|
||||
|
||||
/** The Mermaid bundle, added once as a classic script. */
|
||||
let loading
|
||||
export function bundle() {
|
||||
loading ??= new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script')
|
||||
script.src = new URL('diagram.js', here).href
|
||||
script.onload = () => resolve(window.elyDiagram)
|
||||
script.onerror = () => reject(new Error('diagram.js did not load'))
|
||||
document.head.append(script)
|
||||
})
|
||||
return loading
|
||||
}
|
||||
|
||||
let queue = Promise.resolve()
|
||||
let count = 0
|
||||
const owners = new Set()
|
||||
|
||||
// New tokens mean a new theme; every owner redraws.
|
||||
new MutationObserver(() => {
|
||||
for (const owner of owners) owner.redraw()
|
||||
}).observe(document.documentElement, { attributes: true, attributeFilter: ['style'] })
|
||||
|
||||
/** A block's diagram, by its id on the page. */
|
||||
const box = (id, inner) => `<div class="ely-diagram" data-diagram="${id}">${inner}</div>`
|
||||
const placed = (id) => document.querySelector(`[data-diagram="${id}"]`) !== null
|
||||
// After the code block's next render.
|
||||
const settle = () => new Promise((done) => setTimeout(done))
|
||||
|
||||
/** One editor's diagrams; a result lands only on its placeholder. */
|
||||
export function diagrams() {
|
||||
const records = new Map()
|
||||
|
||||
/** Drops diagrams whose placeholder has left the page. */
|
||||
const sweep = () => {
|
||||
for (const [id, record] of records) {
|
||||
if (record.settled && !placed(id)) records.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
/** Each draw has a turn; a later draw voids earlier. */
|
||||
const draw = (id, record) => {
|
||||
const turn = ++record.turn
|
||||
const live = () => records.get(id) === record && record.turn === turn && placed(id)
|
||||
queue = queue.then(async () => {
|
||||
await settle()
|
||||
if (!live()) return
|
||||
try {
|
||||
const render = await bundle()
|
||||
if (!live()) return
|
||||
const svg = await render(record.source)
|
||||
await settle()
|
||||
if (live()) record.apply(box(id, svg))
|
||||
} catch (error) {
|
||||
await settle()
|
||||
if (!live()) return
|
||||
records.delete(id)
|
||||
record.apply(failure(record.source, error.message))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const owner = {
|
||||
/** The placeholder for `source`; its diagram follows. */
|
||||
show(source, apply) {
|
||||
sweep()
|
||||
const id = `${++count}`
|
||||
const record = { source, apply, settled: false, turn: 0 }
|
||||
records.set(id, record)
|
||||
settle().then(() => (record.settled = true))
|
||||
draw(id, record)
|
||||
return box(id, '')
|
||||
},
|
||||
redraw() {
|
||||
sweep()
|
||||
for (const [id, record] of records) draw(id, record)
|
||||
},
|
||||
release() {
|
||||
records.clear()
|
||||
owners.delete(owner)
|
||||
},
|
||||
}
|
||||
owners.add(owner)
|
||||
return owner
|
||||
}
|
||||
Reference in new issue
Block a user