╭─────────────╮ │scroll to top│ ╰─────────────╯ ╭ script.js ────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
79 const bytes = encoder.encode(value); 80 const ptr = wasmInstance.exports._alloc(bytes.length); 81 new Uint8Array(wasmInstance.exports.memory.buffer, ptr, bytes.length).set(bytes); 82 wasmInstance.exports._setTextInputValue(focusId, ptr, bytes.length); 83 wasmInstance.exports._tick(maxCols()); 84 } 85 86 // POST the current input values to the server form route. on response (any 87 // status) we navigate to / so the server can render the page with whatever 88 // session cookie it just set. 89 function submitForm(url) { 90 const params = new URLSearchParams(); 91 const inputs = grid.querySelectorAll("input"); 92 for (const input of inputs) { 93 if (input.name) params.append(input.name, input.value); 94 } 95 fetch(url, { method: "POST", body: params, redirect: "manual", credentials: "same-origin" }) 96 .finally(() => { window.location.href = "/"; }); 97 } 98 99 WebAssembly.instantiateStreaming(fetch("haxy.wasm"), importObject).then(async (result) => { 100 wasmInstance = result.instance; 101 102 // wait for the @font-face to load before measuring cell width, otherwise 103 // we get the fallback font's metrics. 104 if (document.fonts && document.fonts.ready) { 105 await document.fonts.ready; 106 } 107 108 // the page is embedded as base64 so it can sit inside the host html 109 // without worrying about characters that would terminate the script tag. 110 // decode here and hand the raw json bytes to the wasm. 111 const jsonBytes = Uint8Array.from(atob(pageJsonBase64), (c) => c.charCodeAt(0)); 112 const ptr = wasmInstance.exports._alloc(jsonBytes.length); 113 new Uint8Array(wasmInstance.exports.memory.buffer, ptr, jsonBytes.length).set(jsonBytes); 114 wasmInstance.exports._start(ptr, jsonBytes.length, maxCols()); 115 116 document.addEventListener("keydown", (event) => { 117 // when an overlay input owns focus, the browser handles typing & 118 // cursor movement natively; wasm gets the new value via input events 119 // instead of per-key dispatch. don't forward, and don't intercept 120 // arrow / home / end which the input needs for cursor movement. 121 // exception: Enter triggers form submission when one is on the page. 122 if (document.activeElement && document.activeElement.tagName === "INPUT") { 123 if (event.key === "Enter") { 124 event.preventDefault(); 125 const submitSpan = grid.querySelector('[data-action="submit"]'); 126 if (submitSpan) { 127 submitForm(submitSpan.dataset.url); 128 } 129 } 130 return; 131 } 132 // a focused submit-button span (Tab can land here now that it has a 133 // tabindex) should submit on Enter the same as a click does. 134 if (event.key === "Enter" && document.activeElement && document.activeElement.dataset && document.activeElement.dataset.action === "submit") { 135 event.preventDefault(); 136 submitForm(document.activeElement.dataset.url); 137 return; 138 } 139 if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "PageUp", "PageDown", "Home", "End"].includes(event.key)) { 140 event.preventDefault(); 141 } 142 wasmInstance.exports._onKeyDown(event.keyCode); 143 wasmInstance.exports._tick(maxCols()); 144 }); 145 146 // event delegation: each render rebuilds the input elements (innerHTML 147 // wipe), so we can't attach listeners per-input. delegating on grid 148 // catches inputs created at any point. 149 grid.addEventListener("input", (event) => { 150 const t = event.target; 151 if (!t || t.tagName !== "INPUT" || !t.dataset.focusId) return; 152 sendTextInputValue(Number(t.dataset.focusId), t.value); 153 }); 154 155 grid.addEventListener("focusin", (event) => { 156 const t = event.target; 157 if (!t || !t.dataset || !t.dataset.focusId) return; 158 wasmInstance.exports._onMouseClick(Number(t.dataset.focusId)); 159 wasmInstance.exports._tick(maxCols()); 160 }); 161 162 grid.addEventListener("click", (event) => { 163 const span = event.target.closest(".clickable"); 164 if (!span) return; 165 if (span.dataset.action === "submit") { 166 event.preventDefault(); 167 submitForm(span.dataset.url); 168 return; 169 } 170 const focusId = Number(span.dataset.focusId); 171 wasmInstance.exports._onMouseClick(focusId); 172 // synthesize Enter so the focused widget's "activate" handler fires 173 // (e.g. a button click runs its submit handler). widgets that don't 174 // care about Enter just ignore it. 175 wasmInstance.exports._onKeyDown(13); 176 wasmInstance.exports._tick(maxCols()); 177 }); 178 179 let resizeTimer = null; 180 window.addEventListener("resize", () => { 181 clearTimeout(resizeTimer); 182 resizeTimer = setTimeout(() => { 183 wasmInstance.exports._tick(maxCols()); 184 }, 100); 185 }); 186 }); 187