export-app.js (4761B)
1 import { treeFormat } from './tree-export.js'; 2 function el(id) { 3 const element = document.getElementById(id); 4 if (!element) 5 throw new Error(`Missing element ${id}`); 6 return element; 7 } 8 // Presentation owns its output and invalidation; the package controller owns the 9 // shared selection and worker. No arguments or execution settings enter exports. 10 export function createTreeExport(request, select, stop) { 11 const definition = el('tree-definition'), format = el('tree-format'); 12 const button = el('export-tree'), cancel = el('stop-tree'); 13 const copy = el('copy-tree'), download = el('download-tree'); 14 const preview = el('tree-preview'), status = el('tree-provenance'), empty = el('tree-empty'); 15 let exported, pending = false; 16 const descriptions = { 17 ternary: 'A single line: 0 is Leaf, 1 introduces a Stem, and 2 introduces a Fork. Shared branches are repeated.', 18 tricu: 'A fully parenthesized tree expression using t. This is generated tree structure, not the original source.', 19 triangle: 'A fully parenthesized tree expression using △. Shared branches are repeated.', 20 dag: 'Shared △ applications with a fixed export named tree. Choose DAG when an expanded tree would be too large.', 21 }; 22 function clear() { 23 exported = undefined; 24 pending = false; 25 preview.textContent = ''; 26 preview.hidden = true; 27 empty.hidden = false; 28 status.textContent = 'Export the selected stored definition.'; 29 status.classList.remove('error'); 30 copy.disabled = true; 31 download.disabled = true; 32 } 33 function help() { el('tree-format-help').textContent = descriptions[treeFormat(format.value)]; } 34 function begin() { clear(); pending = true; status.textContent = 'Preparing definition export…'; } 35 function fail(message) { clear(); status.textContent = message; status.classList.add('error'); } 36 format.onchange = () => { clear(); help(); }; 37 definition.onchange = () => { clear(); select(definition.value); }; 38 button.onclick = () => { 39 try { 40 begin(); 41 request({ type: 'export', export: definition.value, format: treeFormat(format.value) }); 42 } 43 catch (error) { 44 fail(error instanceof Error ? error.message : String(error)); 45 } 46 }; 47 cancel.onclick = stop; 48 copy.onclick = async () => { 49 const value = exported; 50 if (!value) 51 return; 52 try { 53 await navigator.clipboard.writeText(value.file.text); 54 if (exported === value) 55 status.textContent += ' · Copied complete tree.'; 56 } 57 catch { 58 if (exported === value) 59 status.textContent += ' · Clipboard unavailable. Use Download tree.'; 60 } 61 }; 62 download.onclick = () => { 63 if (!exported) 64 return; 65 const url = URL.createObjectURL(new Blob([exported.file.text], { type: 'text/plain;charset=utf-8' })); 66 const anchor = document.createElement('a'); 67 anchor.href = url; 68 const name = exported.export.replace(/[^a-zA-Z0-9_-]/gu, '-').slice(0, 64) || 'tree'; 69 anchor.download = `${name}-${exported.hash.slice(0, 12)}.${exported.file.extension}`; 70 document.body.append(anchor); 71 anchor.click(); 72 anchor.remove(); 73 setTimeout(() => URL.revokeObjectURL(url), 1000); 74 }; 75 help(); 76 return { 77 clear, begin, fail, 78 get pending() { return pending; }, 79 selection(input) { 80 definition.replaceChildren(...[...input.options].map(option => option.cloneNode(true))); 81 definition.value = input.value; 82 }, 83 controls(busy, loaded) { 84 button.disabled = format.disabled = definition.disabled = busy || !loaded; 85 cancel.disabled = !busy; 86 copy.disabled = download.disabled = busy || !exported; 87 }, 88 receive(data, hash, name) { 89 if (data.hash !== hash || data.export !== name || data.format !== format.value) 90 return false; 91 exported = data; 92 pending = false; 93 preview.textContent = data.file.text.slice(0, 16_384); 94 preview.hidden = false; 95 empty.hidden = true; 96 status.classList.remove('error'); 97 status.textContent = `SHA-256 ${data.hash} · definition ${data.export} · ${data.file.bytes.toLocaleString()} UTF-8 bytes · ${data.file.storedNodes.toLocaleString()} stored nodes` + 98 (preview.textContent.length < data.file.text.length ? ' · Preview: first 16,384 characters. Copy and Download contain the complete tree.' : ' · Complete tree.'); 99 return true; 100 }, 101 }; 102 }