| 1 |
|
| 2 |
|
| 3 |
|
| 4 |
|
| 5 |
|
| 6 |
|
| 7 |
|
| 8 |
|
| 9 |
import { |
| 10 |
deleteDrawing, |
| 11 |
listDrawings, |
| 12 |
startDrawing, |
| 13 |
svgsOf, |
| 14 |
type Drawing, |
| 15 |
} from "./actions.ts"; |
| 16 |
import { openDb } from "./db.ts"; |
| 17 |
import { drawerFromEnv, setDrawer } from "./draw.ts"; |
| 18 |
import { dataDir, publicOrigin, ssh, title, where } from "./env.ts"; |
| 19 |
import { nameOf } from "./glyph.ts"; |
| 20 |
import { whoAtTty } from "./who.ts"; |
| 21 |
|
| 22 |
const db = openDb(); |
| 23 |
const drawer = drawerFromEnv(dataDir); |
| 24 |
if (drawer !== undefined) setDrawer(drawer); |
| 25 |
const me = whoAtTty(); |
| 26 |
const esc = "\x1b["; |
| 27 |
const dim = (s: string): string => `${esc}2m${s}${esc}0m`; |
| 28 |
const bold = (s: string): string => `${esc}1m${s}${esc}0m`; |
| 29 |
const out = (s: string): void => { |
| 30 |
process.stdout.write(s); |
| 31 |
}; |
| 32 |
|
| 33 |
let cursor = 0; |
| 34 |
let typing: string | undefined; |
| 35 |
let said = ""; |
| 36 |
|
| 37 |
const drawings = (): Drawing[] => { |
| 38 |
const listed = listDrawings(db, me); |
| 39 |
return listed.ok ? listed.value : []; |
| 40 |
}; |
| 41 |
|
| 42 |
const state = (d: Drawing): string => |
| 43 |
d.status === "done" |
| 44 |
? svgsOf(d).map(nameOf).join(" · ") |
| 45 |
: d.status === "drawing" |
| 46 |
? "drawing…" |
| 47 |
: `failed: ${d.error ?? ""}`; |
| 48 |
|
| 49 |
function draw(): void { |
| 50 |
const rows = drawings(); |
| 51 |
const lines = [ |
| 52 |
`${bold(title)} ${dim(where)} ${dim(me.role === "anon" ? "guest" : `${me.name}${me.role === "admin" ? " (admin)" : ""}`)}`, |
| 53 |
ssh === undefined ? "" : dim(`connect: ${ssh}`), |
| 54 |
dim(`see them at ${publicOrigin}`), |
| 55 |
"", |
| 56 |
]; |
| 57 |
if (rows.length === 0) |
| 58 |
lines.push(dim(me.role === "anon" ? "Sign in to draw." : "Nothing drawn yet. Press n.")); |
| 59 |
rows.forEach((d, i) => { |
| 60 |
const prompt = i === cursor ? bold(d.prompt) : d.prompt; |
| 61 |
lines.push(`${i === cursor ? "▸" : " "} ${prompt} ${dim(`${d.kind} · ${state(d)}`)}`); |
| 62 |
}); |
| 63 |
lines.push(""); |
| 64 |
if (typing !== undefined) lines.push(`Draw: ${typing}${esc}7m ${esc}0m`); |
| 65 |
else if (said !== "") lines.push(said); |
| 66 |
else lines.push(dim("j/k move · n new · d delete · r reload · q quit")); |
| 67 |
out(`${esc}2J${esc}H${lines.join("\r\n")}`); |
| 68 |
} |
| 69 |
|
| 70 |
process.stdin.setRawMode?.(true); |
| 71 |
process.stdin.resume(); |
| 72 |
process.stdin.setEncoding("utf8"); |
| 73 |
draw(); |
| 74 |
|
| 75 |
setInterval(() => { |
| 76 |
if (typing === undefined && drawings().some((d) => d.status === "drawing")) draw(); |
| 77 |
}, 3000); |
| 78 |
process.stdin.on("data", (key: string) => { |
| 79 |
said = ""; |
| 80 |
if (typing !== undefined) { |
| 81 |
if (key === "\r") { |
| 82 |
const started = startDrawing(db, me, { prompt: typing }); |
| 83 |
said = started.ok ? "Drawing… it shows here and on the page when Claude is done." : started.message; |
| 84 |
typing = undefined; |
| 85 |
cursor = 0; |
| 86 |
} else if (key === "\x1b") typing = undefined; |
| 87 |
else if (key === "\x7f") typing = typing.slice(0, -1); |
| 88 |
else if (key >= " ") typing += key; |
| 89 |
draw(); |
| 90 |
return; |
| 91 |
} |
| 92 |
if (key === "q" || key === "\x03") { |
| 93 |
out(`${esc}2J${esc}H`); |
| 94 |
process.exit(0); |
| 95 |
} |
| 96 |
const rows = drawings(); |
| 97 |
if (key === "j" || key === "\x1b[B") cursor = Math.min(cursor + 1, Math.max(0, rows.length - 1)); |
| 98 |
if (key === "k" || key === "\x1b[A") cursor = Math.max(cursor - 1, 0); |
| 99 |
if (key === "n") { |
| 100 |
if (me.role === "anon") said = "Sign in to draw."; |
| 101 |
else typing = ""; |
| 102 |
} |
| 103 |
if (key === "d") { |
| 104 |
const d = rows[cursor]; |
| 105 |
if (d === undefined) said = "Nothing to remove."; |
| 106 |
else { |
| 107 |
const removed = deleteDrawing(db, me, { id: d.id }); |
| 108 |
if (!removed.ok) said = removed.message; |
| 109 |
cursor = Math.min(cursor, Math.max(0, rows.length - 2)); |
| 110 |
} |
| 111 |
} |
| 112 |
draw(); |
| 113 |
}); |
| 114 |
process.stdout.on("resize", draw); |