pike/glyph Public Public: anyone can read this; nobody can change it.
forked from apps/glyph
workbenchthis machine
Files main
| 1 | // What only a script can add to the page; without it the page still draws, |
| 2 | // saves and switches palettes by link. |
| 3 | // - Copy: the SVG, in the palette on screen, onto the clipboard. |
| 4 | // - Palettes swap in place (the address keeps the choice, so a live |
| 5 | // refresh and a reload render the same one). |
| 6 | // - The example prompts fill the box; a drawing in progress counts up. |
| 7 | |
| 8 | const board = () => document.getElementById("board"); |
| 9 | |
| 10 | async function swapIn(url) { |
| 11 | const response = await fetch(url, { headers: { accept: "text/html" } }); |
| 12 | const fresh = new DOMParser().parseFromString(await response.text(), "text/html"); |
| 13 | for (const id of ["board", "palettes"]) { |
| 14 | const next = fresh.getElementById(id); |
| 15 | const here = document.getElementById(id); |
| 16 | if (next && here) here.replaceWith(next); |
| 17 | } |
| 18 | wake(); |
| 19 | } |
| 20 | |
| 21 | document.addEventListener("click", async (event) => { |
| 22 | const swatch = event.target.closest("[data-palette]"); |
| 23 | if (swatch) { |
| 24 | event.preventDefault(); |
| 25 | const url = new URL(location.href); |
| 26 | url.searchParams.set("palette", swatch.dataset.palette); |
| 27 | history.replaceState(null, "", url); |
| 28 | try { |
| 29 | localStorage.setItem("glyph.palette", swatch.dataset.palette); |
| 30 | } catch {} |
| 31 | await swapIn(url); |
| 32 | return; |
| 33 | } |
| 34 | const copy = event.target.closest(".js-copy"); |
| 35 | if (copy) { |
| 36 | const said = copy.textContent; |
| 37 | try { |
| 38 | const svg = fetch(copy.dataset.href).then((r) => r.text()); |
| 39 | // Safari keeps the click's permission only for a ClipboardItem given a promise. |
| 40 | if (window.ClipboardItem) |
| 41 | await navigator.clipboard.write([ |
| 42 | new ClipboardItem({ "text/plain": svg.then((t) => new Blob([t], { type: "text/plain" })) }), |
| 43 | ]); |
| 44 | else await navigator.clipboard.writeText(await svg); |
| 45 | copy.textContent = "Copied"; |
| 46 | } catch { |
| 47 | copy.textContent = "Couldn't copy"; |
| 48 | } |
| 49 | setTimeout(() => (copy.textContent = said), 1600); |
| 50 | return; |
| 51 | } |
| 52 | const example = event.target.closest("[data-example]"); |
| 53 | if (example) { |
| 54 | const box = document.getElementById("prompt"); |
| 55 | if (box) { |
| 56 | box.value = example.dataset.example; |
| 57 | box.focus(); |
| 58 | } |
| 59 | } |
| 60 | }); |
| 61 | |
| 62 | // Enter draws; shift+enter is a new line. |
| 63 | document.addEventListener("keydown", (event) => { |
| 64 | if (event.target.id === "prompt" && event.key === "Enter" && !event.shiftKey) { |
| 65 | event.preventDefault(); |
| 66 | event.target.form.requestSubmit(); |
| 67 | } |
| 68 | }); |
| 69 | |
| 70 | function wake() { |
| 71 | for (const el of document.querySelectorAll(".js-copy, .examples")) el.hidden = false; |
| 72 | } |
| 73 | |
| 74 | // A drawing in progress says how long it has been at it. |
| 75 | setInterval(() => { |
| 76 | for (const el of document.querySelectorAll("[data-since]")) { |
| 77 | const s = Math.max(0, Math.round((Date.now() - Date.parse(el.dataset.since)) / 1000)); |
| 78 | el.textContent = `Claude is drawing… ${s}s`; |
| 79 | } |
| 80 | }, 1000); |
| 81 | |
| 82 | // live.js swaps the board when a drawing changes: wake what it brought. |
| 83 | new MutationObserver(wake).observe(document.body, { childList: true, subtree: true }); |
| 84 | |
| 85 | // The palette last picked, when the address names none. |
| 86 | try { |
| 87 | const kept = localStorage.getItem("glyph.palette"); |
| 88 | const url = new URL(location.href); |
| 89 | if (kept && !url.searchParams.has("palette") && board()) { |
| 90 | url.searchParams.set("palette", kept); |
| 91 | history.replaceState(null, "", url); |
| 92 | swapIn(url); |
| 93 | } |
| 94 | } catch {} |
| 95 | wake(); |