output-app.ts (2844B)
1 import type { ValueView } from './codecs.js'; 2 import type { TreeFile } from './tree-export.js'; 3 // One output surface. The saved payload carries its own provenance/format; 4 // changing controls cannot relabel an earlier result or change its download. 5 export function createOutput() { 6 const text = document.getElementById('result')!; 7 const provenance = document.getElementById('output-provenance')!; 8 const copy = document.getElementById('copy-output') as HTMLButtonElement; 9 const download = document.getElementById('download-output') as HTMLButtonElement; 10 let saved: { text: string; filename: string } | undefined, busy = false; 11 const controls = (working: boolean) => { busy = working; copy.disabled = download.disabled = busy || !saved; }; 12 function clear(): void { saved = undefined; text.textContent = ''; text.className = ''; provenance.textContent = 'Evaluate an expression, or show a stored definition. Nothing runs automatically.'; controls(busy); } 13 function show(value: string, filename: string, description: string): void { 14 saved = { text: value, filename }; text.textContent = value.slice(0, 16_384); text.className = ''; 15 provenance.textContent = description + (value.length > 16_384 ? ' · Preview: first 16,384 characters. Copy and Download include the complete output.' : ' · Complete output.'); 16 controls(busy); 17 } 18 function file(value: TreeFile, name: string, description: string): void { 19 show(value.text, `${name.replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 64)}.${value.extension}`, `${description} · ${value.bytes.toLocaleString()} UTF-8 bytes`); 20 } 21 function result(value: ValueView, description: string): void { 22 if ('file' in value) { file(value.file, 'result', `${description} · ${value.type}`); return; } 23 if (value.type === 'tree' || value.type === 'list') throw new Error('Select tricu source or DAG to inspect tree results'); 24 const content = typeof value.value === 'string' ? value.value : JSON.stringify(value.value, null, 2); 25 show(content, value.type === 'record' || value.type === 'bytes' ? 'result.json' : 'result.txt', `${description} · ${value.type}`); 26 } 27 copy.onclick = async () => { 28 const value = saved; if (!value) return; 29 try { await navigator.clipboard.writeText(value.text); if (saved === value) provenance.textContent += ' · Copied.'; } 30 catch { if (saved === value) provenance.textContent += ' · Clipboard unavailable; use Download.'; } 31 }; 32 download.onclick = () => { 33 if (!saved) return; 34 const url = URL.createObjectURL(new Blob([saved.text], { type: 'text/plain;charset=utf-8' })); 35 const a = document.createElement('a'); a.href = url; a.download = saved.filename; document.body.append(a); a.click(); a.remove(); 36 setTimeout(() => URL.revokeObjectURL(url), 1000); 37 }; 38 clear(); return { clear, controls, file, result }; 39 }