138 lines
3.6 KiB
TypeScript
138 lines
3.6 KiB
TypeScript
////
|
|
// The scrollback shows your history of interacting with the shell.
|
|
// input, output, etc
|
|
|
|
import { scrollback, $$ } from "./dom.js"
|
|
import { randomId } from "../shared/utils.js"
|
|
import type { CommandOutput } from "../shared/types.js"
|
|
|
|
type InputStatus = "waiting" | "streaming" | "ok" | "error"
|
|
|
|
export function autoScroll() {
|
|
// requestAnimationFrame(() => scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight)
|
|
// scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight
|
|
}
|
|
|
|
export function insert(node: HTMLElement) {
|
|
scrollback.append(node)
|
|
}
|
|
|
|
export function addInput(id: string, input: string) {
|
|
const parent = $$("li.input")
|
|
const status = $$("span.status.yellow", "•")
|
|
const content = $$("span.content", input)
|
|
parent.append(status, content)
|
|
parent.dataset.id = id
|
|
|
|
insert(parent)
|
|
scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight
|
|
}
|
|
|
|
export function setStatus(id: string, status: InputStatus) {
|
|
const statusEl = document.querySelector(`[data-id="${id}"].input .status`)
|
|
if (!statusEl) return
|
|
|
|
statusEl.className = ""
|
|
|
|
switch (status) {
|
|
case "waiting":
|
|
statusEl.classList.add("yellow")
|
|
break
|
|
case "streaming":
|
|
statusEl.classList.add("purple")
|
|
break
|
|
case "ok":
|
|
statusEl.classList.add("green")
|
|
break
|
|
case "error":
|
|
statusEl.classList.add("red")
|
|
break
|
|
}
|
|
}
|
|
|
|
export function addOutput(id: string, output: CommandOutput) {
|
|
const item = $$("li")
|
|
item.classList.add("output")
|
|
item.dataset.id = id || randomId()
|
|
|
|
const [format, content] = processOutput(output)
|
|
|
|
if (format === "html")
|
|
item.innerHTML = content
|
|
else
|
|
item.textContent = content
|
|
|
|
const input = document.querySelector(`[data-id="${id}"].input`)
|
|
if (input instanceof HTMLLIElement)
|
|
input.parentNode!.insertBefore(item, input.nextSibling)
|
|
else
|
|
insert(item)
|
|
|
|
autoScroll()
|
|
}
|
|
|
|
export function addErrorMessage(message: string) {
|
|
addOutput("", { html: `<span class="red">${message}</span>` })
|
|
}
|
|
|
|
|
|
export function appendOutput(id: string, output: CommandOutput) {
|
|
const item = document.querySelector(`[data-id="${id}"].output`)
|
|
|
|
if (!item) {
|
|
console.error(`output id ${id} not found`)
|
|
return
|
|
}
|
|
|
|
const [format, content] = processOutput(output)
|
|
|
|
if (format === "html")
|
|
item.innerHTML += content
|
|
else
|
|
item.textContent += content
|
|
|
|
autoScroll()
|
|
}
|
|
|
|
export function replaceOutput(id: string, output: CommandOutput) {
|
|
const item = document.querySelector(`[data-id="${id}"].output`)
|
|
|
|
if (!item) {
|
|
console.error(`output id ${id} not found`)
|
|
return
|
|
}
|
|
|
|
const [format, content] = processOutput(output)
|
|
|
|
if (format === "html")
|
|
item.innerHTML = content
|
|
else
|
|
item.textContent = content
|
|
|
|
autoScroll()
|
|
}
|
|
|
|
function processOutput(output: CommandOutput): ["html" | "text", string] {
|
|
let content = ""
|
|
let html = false
|
|
|
|
if (typeof output === "string") {
|
|
content = output
|
|
} else if (Array.isArray(output)) {
|
|
content = output.join(" ")
|
|
} else if ("html" in output) {
|
|
html = true
|
|
content = output.html
|
|
if (output.script) eval(output.script)
|
|
} else if ("text" in output) {
|
|
content = output.text
|
|
if (output.script) eval(output.script)
|
|
} else if ("script" in output) {
|
|
eval(output.script!)
|
|
} else {
|
|
content = JSON.stringify(output)
|
|
}
|
|
|
|
return [html ? "html" : "text", content]
|
|
}
|