const { invoke } = window.__TAURI__.core
const { listen } = window.__TAURI__.event
const pathEl = document.getElementById('path')
const cellsEl = document.getElementById('cells')
const statusEl = document.getElementById('status')
const runAllButton = document.getElementById('run-all')
const stopButton = document.getElementById('stop')
const reloadButton = document.getElementById('reload')
let running = null
let busy = false
let subscribed = true
function setStatus(text, kind) {
statusEl.textContent = text
statusEl.dataset.kind = kind || 'info'
}
function setBusy(value) {
busy = value
runAllButton.disabled = value
reloadButton.disabled = value
stopButton.disabled = value ? subscribed : true
for (const button of cellsEl.querySelectorAll('button.run')) {
button.disabled = value
}
}
function render(doc) {
pathEl.textContent = doc.path
pathEl.title = doc.path
cellsEl.replaceChildren()
if (doc.cells.length === 0) {
const empty = document.createElement('p')
empty.className = 'empty'
empty.textContent = 'No shell / sh / bash / zsh code block found.'
cellsEl.append(empty)
return
}
for (const cell of doc.cells) {
cellsEl.append(renderCell(cell))
}
}
function renderCell(cell) {
const section = document.createElement('section')
section.className = 'cell'
section.dataset.index = String(cell.index)
if (running === cell.index) {
section.classList.add('running')
}
const head = document.createElement('div')
head.className = 'cell-head'
const number = document.createElement('span')
number.className = 'number'
number.textContent = String(cell.number)
head.append(number)
const button = document.createElement('button')
button.className = 'run'
button.type = 'button'
const hasResult = cell.result !== null && cell.result !== undefined
button.textContent =
running === cell.index ? '▶ Running…' : hasResult ? '↻ Re-run' : '▶ Run'
button.disabled = busy
button.addEventListener('click', () => runCell(cell.index))
head.append(button)
if (cell.out_file) {
const out = document.createElement('span')
out.className = 'out'
out.textContent = `→ ${cell.out_file}`
head.append(out)
}
const command = document.createElement('pre')
command.className = 'command'
command.textContent = cell.command
section.append(head, command)
if (hasResult) {
const result = document.createElement('pre')
result.className = 'result'
result.textContent = cell.result
section.append(result)
}
return section
}
async function refresh() {
try {
render(await invoke('document'))
return true
} catch (error) {
setStatus(String(error), 'error')
return false
}
}
async function runCell(index) {
if (busy) {
return
}
setBusy(true)
try {
const report = await invoke('run_cell', { index })
setStatus(
`Cell ${index + 1} done (${report.status})`,
report.success ? 'ok' : 'error',
)
} catch (error) {
setStatus(String(error), 'error')
await reload(true)
} finally {
running = null
setBusy(false)
await refresh()
}
}
async function runAll() {
if (busy) {
return
}
setBusy(true)
try {
const { reports, stopped } = await invoke('run_all')
const failed = reports.filter((report) => !report.success).length
setStatus(
stopped
? `Stopped after ${reports.length} cells.`
: failed === 0
? `Ran ${reports.length} cells.`
: `Ran ${reports.length} cells, ${failed} failed.`,
failed === 0 ? 'ok' : 'error',
)
} catch (error) {
setStatus(String(error), 'error')
await reload(true)
} finally {
running = null
setBusy(false)
await refresh()
}
}
async function stop() {
try {
const stopped = await invoke('cancel')
setStatus(stopped ? 'Stopping…' : 'Nothing is running.', 'info')
} catch (error) {
setStatus(String(error), 'error')
}
}
async function reload(quiet) {
try {
render(await invoke('reload'))
if (!quiet) {
setStatus('Reloaded.', 'ok')
}
} catch (error) {
setStatus(`Reload failed: ${error}`, 'error')
}
}
runAllButton.addEventListener('click', runAll)
stopButton.addEventListener('click', stop)
reloadButton.addEventListener('click', () => reload(false))
async function start() {
setBusy(true)
setStatus('Starting…', 'info')
const attempts = await Promise.allSettled([
listen('runandlog://document', (event) => {
render(event.payload)
}),
listen('runandlog://started', (event) => {
running = event.payload
stopButton.disabled = false
setStatus(`Running cell ${event.payload + 1}…`, 'info')
refresh()
}),
listen('runandlog://finished', () => {
running = null
}),
])
const failure = attempts.find((attempt) => attempt.status === 'rejected')
subscribed = failure === undefined
if (!subscribed) {
await Promise.allSettled(
attempts
.filter((attempt) => attempt.status === 'fulfilled')
.map((attempt) => attempt.value()),
)
}
setBusy(false)
if (await refresh()) {
setStatus(
subscribed ? 'Ready.' : `Cannot follow a run: ${failure.reason}`,
subscribed ? 'info' : 'error',
)
}
}
start()