tricu

An interpreted language for exploring Tree Calculus
Log | Files | Refs | README | LICENSE

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 }