show tiny browser

This commit is contained in:
Chris Wanstrath 2025-10-03 06:38:21 -07:00
parent a87564b082
commit 6fcffc65f9
3 changed files with 50 additions and 45 deletions

View File

@ -4,23 +4,31 @@
import { $, $$ } from "./dom" import { $, $$ } from "./dom"
import { focusInput } from "./focus" import { focusInput } from "./focus"
import { scrollback } from "./dom"
import { addInput } from "./scrollback"
import { randomId } from "../shared/utils"
const HEIGHT = 540
const WIDTH = 960
const controls = $("browser-controls") as HTMLDivElement const controls = $("browser-controls") as HTMLDivElement
const address = $("browser-address") as HTMLSpanElement const address = $("browser-address") as HTMLSpanElement
let iframe: HTMLIFrameElement let iframe: HTMLIFrameElement
let iframeWindow: Window let realUrl = ""
let showInput = true
export function isBrowsing(): boolean { export function isBrowsing(): boolean {
return document.querySelector("iframe.browser.active") !== null return document.querySelector("iframe.browser.active") !== null
} }
export function openBrowser(url: string) { export function openBrowser(url: string, openedVia: "click" | "command" = "click") {
showInput = openedVia === "click"
iframe = $$("iframe.browser.active") as HTMLIFrameElement iframe = $$("iframe.browser.active") as HTMLIFrameElement
iframeWindow = iframe.contentWindow as Window
iframe.src = url iframe.src = url
iframe.sandbox.add("allow-scripts", "allow-same-origin", "allow-forms") iframe.sandbox.add("allow-scripts", "allow-same-origin", "allow-forms")
iframe.height = "540" iframe.height = String(HEIGHT)
iframe.width = "960" iframe.width = String(WIDTH)
iframe.tabIndex = 0 iframe.tabIndex = 0
window.addEventListener("message", handleAppMessage) window.addEventListener("message", handleAppMessage)
@ -35,6 +43,28 @@ export function openBrowser(url: string) {
setAddress(url) setAddress(url)
} }
function closeBrowser() {
window.removeEventListener("keydown", handleBrowserKeydown)
window.removeEventListener("message", handleAppMessage)
controls.removeEventListener("click", handleClick)
iframe.removeEventListener("load", handlePageLoad)
const id = randomId()
if (showInput)
addInput(id, "browse " + realUrl, "ok")
iframe.height = String(HEIGHT / 2)
iframe.width = String(WIDTH / 2)
iframe.style.pointerEvents = "none"
iframe.tabIndex = -1
iframe.classList.remove("fullscreen", "active")
iframe.style.border = "2px solid var(--c64-light-blue)"
scrollback.append(iframe)
controls.style.display = "none"
focusInput()
}
function handleAppMessage(event: MessageEvent) { function handleAppMessage(event: MessageEvent) {
const origin = event.origin const origin = event.origin
if (!origin.includes('localhost') && !origin.match(/\.local$/)) { if (!origin.includes('localhost') && !origin.match(/\.local$/)) {
@ -74,20 +104,6 @@ function handleAppMessage(event: MessageEvent) {
} }
} }
function closeBrowser() {
window.removeEventListener("keydown", handleBrowserKeydown)
window.removeEventListener("message", handleAppMessage)
controls.removeEventListener("click", handleClick)
iframe.removeEventListener("load", handlePageLoad)
iframe.src = 'about:blank'
setTimeout(() => {
iframe.remove()
controls.style.display = "none"
focusInput()
}, 0)
}
function handleBrowserKeydown(e: KeyboardEvent) { function handleBrowserKeydown(e: KeyboardEvent) {
if (e.key === "Escape" || (e.ctrlKey && e.key === "c")) { if (e.key === "Escape" || (e.ctrlKey && e.key === "c")) {
e.preventDefault() e.preventDefault()
@ -123,6 +139,7 @@ function handlePageLoad() {
} }
function setAddress(url: string) { function setAddress(url: string) {
realUrl = url
address.textContent = url.replace(/https?:\/\//, "") address.textContent = url.replace(/https?:\/\//, "")
} }

View File

@ -2,16 +2,17 @@
// temporary hack for browser commands // temporary hack for browser commands
import type { CommandOutput } from "../shared/types" import type { CommandOutput } from "../shared/types"
import { openBrowser } from "./browser"
import { scrollback, content } from "./dom" import { scrollback, content } from "./dom"
import { focusInput } from "./focus"
import { resize } from "./resize" import { resize } from "./resize"
import { autoScroll } from "./scrollback"
import { sessionId } from "./session" import { sessionId } from "./session"
import { send } from "./websocket" import { send } from "./websocket"
import { focusInput } from "./focus"
export const commands: string[] = [] export const commands: string[] = []
export const browserCommands: Record<string, (...args: string[]) => void | Promise<void> | CommandOutput> = { export const browserCommands: Record<string, (...args: string[]) => void | Promise<void> | CommandOutput> = {
browse: (url: string) => openBrowser(url, "command"),
"browser-session": () => sessionId, "browser-session": () => sessionId,
clear: () => scrollback.innerHTML = "", clear: () => scrollback.innerHTML = "",
commands: () => { commands: () => {
@ -27,7 +28,6 @@ export const browserCommands: Record<string, (...args: string[]) => void | Promi
content.style.display = "" content.style.display = ""
document.body.dataset.mode = mode document.body.dataset.mode = mode
resize() resize()
autoScroll()
focusInput() focusInput()
}, },
reload: () => window.location.reload(), reload: () => window.location.reload(),

View File

@ -7,25 +7,26 @@ import { scrollback, cmdInput, $$ } from "./dom"
import { randomId } from "../shared/utils" import { randomId } from "../shared/utils"
type InputStatus = "waiting" | "streaming" | "ok" | "error" type InputStatus = "waiting" | "streaming" | "ok" | "error"
const statusColors = {
waiting: "yellow",
streaming: "purple",
ok: "green",
error: "red"
}
export function initScrollback() { export function initScrollback() {
window.addEventListener("click", handleInputClick) window.addEventListener("click", handleInputClick)
} }
export function autoScroll() {
// requestAnimationFrame(() => scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight)
// scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight
}
export function insert(node: HTMLElement) { export function insert(node: HTMLElement) {
scrollback.append(node) scrollback.append(node)
} }
export function addInput(id: string, input: string) { export function addInput(id: string, input: string, status?: InputStatus) {
const parent = $$("li.input") const parent = $$("li.input")
const status = $$("span.status.yellow", "•") const statusSpan = $$(`span.status.${statusColors[status || "waiting"]}`, "•")
const content = $$("span.content", input) const content = $$("span.content", input)
parent.append(status, content) parent.append(statusSpan, content)
parent.dataset.id = id parent.dataset.id = id
insert(parent) insert(parent)
@ -36,15 +37,8 @@ export function setStatus(id: string, status: InputStatus) {
const statusEl = document.querySelector(`[data-id="${id}"].input .status`) const statusEl = document.querySelector(`[data-id="${id}"].input .status`)
if (!statusEl) return if (!statusEl) return
const colors = { statusEl.classList.remove(...Object.values(statusColors))
waiting: "yellow", statusEl.classList.add(statusColors[status])
streaming: "purple",
ok: "green",
error: "red"
}
statusEl.classList.remove(...Object.values(colors))
statusEl.classList.add(colors[status])
} }
export function addOutput(id: string, output: CommandOutput) { export function addOutput(id: string, output: CommandOutput) {
@ -65,8 +59,6 @@ export function addOutput(id: string, output: CommandOutput) {
} else { } else {
insert(item) insert(item)
} }
autoScroll()
} }
export function addErrorMessage(message: string) { export function addErrorMessage(message: string) {
@ -87,8 +79,6 @@ export function appendOutput(id: string, output: CommandOutput) {
item.innerHTML += content item.innerHTML += content
else else
item.textContent += content item.textContent += content
autoScroll()
} }
export function replaceOutput(id: string, output: CommandOutput) { export function replaceOutput(id: string, output: CommandOutput) {
@ -105,8 +95,6 @@ export function replaceOutput(id: string, output: CommandOutput) {
item.innerHTML = content item.innerHTML = content
else else
item.textContent = content item.textContent = content
autoScroll()
} }
function processOutput(output: CommandOutput): ["html" | "text", string] { function processOutput(output: CommandOutput): ["html" | "text", string] {