Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210<script lang="ts"> import "#lib/components/landing/features/pr.css"; import { play } from "#lib/components/landing/sound.ts"; import { tick } from "svelte"; import { on } from "svelte/events"; import ChevronDown from "$icon/chevron-down"; import FileDiff from "$icon/file-diff"; import Minus from "$icon/minus"; import Plus from "$icon/plus"; import { getGuest } from "#lib/components/landing/guest.svelte.ts"; import { CHANGE_IDS, VERSIONS } from "#lib/components/landing/features/pr-stack.ts";
/* * Pull requests, reimagined: wren's stack as two hairline plates laid flat in * iso. The version button opens the picker, floating above, and picking a round swaps the * stack's content; the "Changes" label flips the IDs column to commit hashes. Change IDs hold * across rounds, hashes don't — that's the point it makes. Laid out on a 502 × 480 * group; the first round the visitor opens counts as reviewing it. */ const guest = getGuest();
let version = $state(VERSIONS.length - 1); let commits = $state(false); let open = $state(false); /** The change being looked at — the highlight slides to it. */ let selected = $state(0);
const current = $derived(VERSIONS[version]);
let trigger: HTMLButtonElement; let menu: HTMLDivElement;
// Focus never scrolls: the skewed panels overhang the stage, whose overflow-hidden wrapper // would otherwise be scrolled sideways to "reveal" them. const items = () => [...menu.querySelectorAll<HTMLButtonElement>('[role="menuitemradio"]')];
async function toggle(viaKeyboard: boolean) { open = !open; if (open && viaKeyboard) { await tick(); items()[version]?.focus({ preventScroll: true }); } }
function close(refocus = false) { open = false; if (refocus) trigger.focus({ preventScroll: true }); }
function pick(index: number) { version = index; close(true); if (guest.done.has("pr")) return; const label = VERSIONS[index].label; guest.markDone("pr"); guest.ev("pr", "you", `reviewed round ${label} of wren's stack`, { sub: "tangled.org/core" }); guest.ev( "pr", "fennel", "vouched for you", { shield: true, q: "careful reviewer, welcome aboard" }, 2400 ); }
function onMenuKey(event: KeyboardEvent) { const list = items(); const at = list.indexOf(document.activeElement as HTMLButtonElement); const step = { ArrowDown: 1, ArrowUp: -1 }[event.key]; if (step) { event.preventDefault(); list[(at + step + list.length) % list.length].focus({ preventScroll: true }); } else if (event.key === "Home" || event.key === "End") { event.preventDefault(); list[event.key === "Home" ? 0 : list.length - 1].focus({ preventScroll: true }); } else if (event.key === "Tab") close(); }
/** Every click in the stack — a row, a toggle, the picker — taps. */ const tap = (node: HTMLElement) => on(node, "click", () => play("tap"));
function onWindowPointer(event: PointerEvent) { const target = event.target as Node; if (open && !menu.contains(target) && !trigger.contains(target)) close(); }</script>
<svelte:window onpointerdown={onWindowPointer} onkeydown={(event) => { if (open && event.key === "Escape") close(true); }}/>
<div class="pr-iso" {@attach tap}> <div class="pr-group" data-open={open || undefined}> <!-- The pull request: the slab's thickness, then the plate. --> <span class="pr-panel pr-main composite rim-copy at" aria-hidden="true"></span> <section class="pr-panel pr-main composite glint at" aria-label="Stacked pull request"> <header class="pr-bar"> <button type="button" class="pr-mode" aria-pressed={commits} title={commits ? "Show change IDs" : "Show commit hashes"} onclick={() => (commits = !commits)} > {#key commits}<span class="pr-swap">{commits ? "Commits" : "Changes"}</span >{/key} <span class="pr-n">{CHANGE_IDS.length}</span> </button>
<button bind:this={trigger} type="button" class="pr-ver" aria-haspopup="menu" aria-expanded={open} aria-controls="pr-versions" aria-label="Version {current.label}" onclick={(event) => toggle(event.detail === 0)} > <FileDiff class="pr-file" aria-hidden="true" /> <span>{current.label}</span> <ChevronDown class="pr-caret" aria-hidden="true" /> </button> </header>
<div class="pr-list"> <!-- One highlight for the whole list, moved to the selected row. --> <span class="pr-sel" style:--sel={selected} aria-hidden="true"></span> <ol> {#each current.changes as change, i (CHANGE_IDS[i])} <li> <button type="button" class="pr-row" style:--i={i} aria-current={i === selected || undefined} onclick={() => (selected = i)} > <i aria-hidden="true"></i> {#key `${version}:${commits}`} <span class="pr-swap pr-id" >{commits ? change.commit : CHANGE_IDS[i]}</span > <span class="pr-swap pr-t" title={change.title} >{change.title}</span > <span class="pr-swap pr-df"> <b ><Plus aria-hidden="true" /><span class="sr-only" >added </span>{change.added}</b > <s ><Minus aria-hidden="true" /><span class="sr-only" >removed </span>{change.removed}</s > </span> {/key} </button> </li> {/each} <li class="pr-row base"> <i aria-hidden="true"></i><span class="pr-id">Base</span> </li> </ol> </div> </section>
<!-- The version picker, floating above. --> <div bind:this={menu} id="pr-versions" class="pr-menu at" role="menu" aria-label="Versions" tabindex="-1" inert={!open} onkeydown={onMenuKey} > <div class="pr-pick"> <!-- The plate grows up out of the slide; the rows follow it in. --> <span class="pr-panel pr-pick-bg composite glint" aria-hidden="true"></span> <p class="pr-pick-h pr-in" style:--i={0}>Versions</p> <div class="pr-pick-list"> {#each VERSIONS as v, i (v.label)} <button type="button" role="menuitemradio" aria-checked={i === version} class="pr-item pr-in" style:--i={i + 1} onclick={() => pick(i)} > <span class="pr-v">{v.label}</span> <span class="pr-hash">{v.hash}</span> <span class="pr-age">{v.age}</span> </button> {/each} </div> </div> </div> </div></div>