toes/src/server/tui.ts

115 lines
2.6 KiB
TypeScript

import type { App } from '$apps'
import { TOES_URL } from '$apps'
const RENDER_DEBOUNCE = 50
let _apps: App[] = []
let _enabled = process.stdout.isTTY ?? false
let _lastRender = 0
let _renderTimer: Timer | undefined
let _showEmoji = false
export const setShowEmoji = (show: boolean) => {
_showEmoji = show
scheduleRender()
}
export function appLog(app: App, ...msg: string[]) {
if (!_enabled) {
console.log('🐾', `[${app.name}]`, msg.join(' '))
}
}
export function hostLog(...msg: string[]) {
if (!_enabled) {
console.log('🐾', msg.join(' '))
}
}
export function setApps(apps: App[]) {
_apps = apps
scheduleRender()
}
const formatStatus = (app: App): string => {
switch (app.state) {
case 'running':
return '\x1b[32m●\x1b[0m'
case 'starting':
return '\x1b[33m◐\x1b[0m'
case 'stopping':
return '\x1b[33m◑\x1b[0m'
case 'stopped':
return '\x1b[90m○\x1b[0m'
case 'invalid':
return '\x1b[31m✕\x1b[0m'
default:
return '\x1b[90m?\x1b[0m'
}
}
const formatAppLine = (app: App, indent: boolean): string => {
const status = formatStatus(app)
const port = app.port ? `\x1b[90m:${app.port}\x1b[0m` : ''
const prefix = indent ? ' ' : ''
if (_showEmoji) {
const icon = app.icon ?? '📦'
return `${prefix}${icon} ${app.name} ${status}${port}`
} else {
return `${prefix}${status} ${app.name}${port}`
}
}
const scheduleRender = () => {
if (_renderTimer) return
const elapsed = Date.now() - _lastRender
const delay = Math.max(0, RENDER_DEBOUNCE - elapsed)
_renderTimer = setTimeout(() => {
_renderTimer = undefined
render()
}, delay)
}
function render() {
if (!_enabled) return
_lastRender = Date.now()
const lines: string[] = []
// Clear screen and move cursor to top
lines.push('\x1b[2J\x1b[H')
// Header
lines.push(`\x1b[1m🐾 Toes\x1b[0m \x1b[90m${TOES_URL}\x1b[0m`)
lines.push('')
// Apps section
const regularApps = _apps.filter(a => !a.tool)
const tools = _apps.filter(a => a.tool)
if (tools.length === 0) {
// No tools, just list apps without header/indent
for (const app of regularApps) {
lines.push(formatAppLine(app, false))
}
lines.push('')
} else {
if (regularApps.length > 0) {
lines.push('\x1b[1mApps\x1b[0m')
for (const app of regularApps) {
lines.push(formatAppLine(app, true))
}
lines.push('')
}
lines.push('\x1b[1mTools\x1b[0m')
for (const app of tools) {
lines.push(formatAppLine(app, true))
}
lines.push('')
}
process.stdout.write(lines.join('\n'))
}