diff --git a/web/src/lib/components/repo/pulls/PullCommitList.stories.svelte b/web/src/lib/components/repo/pulls/PullCommitList.stories.svelte index 0b8d23769..8a0862ace 100644 --- a/web/src/lib/components/repo/pulls/PullCommitList.stories.svelte +++ b/web/src/lib/components/repo/pulls/PullCommitList.stories.svelte @@ -63,6 +63,7 @@ selection: { mode: "range", range: { start: 0, end: commits.length }, + whole: true, oncommit: () => {} } } @@ -75,6 +76,7 @@ // the story is the owner: it holds the picked range and only moves it when // `oncommit` asks, same as the real page's confirmCommits let picked = $state({ start: 0, end: commits.length }); + let pickedWhole = $state(true); let narrowed = $state(""); @@ -93,7 +95,23 @@ {} } + selection: { mode: "range", range: { start: 0, end: 1 }, whole: false, oncommit: () => {} } + }} +> + {#snippet template(args)} + {@render frame(args)} + {/snippet} + + + {} + } }} > {#snippet template(args)} @@ -267,7 +285,11 @@ selection={{ mode: "range", range: picked, - oncommit: (next) => (picked = next) + whole: pickedWhole, + oncommit: (next, whole) => { + picked = next; + pickedWhole = whole; + } }} /> picked v{picked.start}..v{picked.end} void } + | { + mode: "range"; + range: Range; + whole: boolean; + oncommit: (range: Range, whole: boolean) => void; + } | { mode: "change"; changeId: string; onselect: (changeId: string) => void }; @@ -38,7 +43,8 @@ const items = $derived<(CommitSummary | "base")[]>([...commits, "base"]); const resetRange = () => { - if (selection.mode === "range") selection.oncommit({ start: 0, end: items.length - 1 }); + if (selection.mode === "range") + selection.oncommit({ start: 0, end: items.length - 1 }, true); }; const listRange = $derived({ start: 0, end: commits.length - 1 }); @@ -95,7 +101,8 @@ minLength={2} muteFullSelection mode="select-top" - bind:range={() => commitRange, (next) => oncommit(next)} + whole={selection.whole} + bind:range={() => commitRange, (next) => oncommit(next, false)} /> {:else} {@const onselect = selection.onselect} diff --git a/web/src/lib/components/repo/pulls/PullCompose.svelte b/web/src/lib/components/repo/pulls/PullCompose.svelte index 3eacb2ea7..4fb97c156 100644 --- a/web/src/lib/components/repo/pulls/PullCompose.svelte +++ b/web/src/lib/components/repo/pulls/PullCompose.svelte @@ -186,9 +186,11 @@ ); let stagedRange = $state(null); + let stagedWhole = $state(true); $effect(() => { void view; stagedRange = null; + stagedWhole = true; }); const commitRange = $derived(stagedRange ?? { start: 0, end: commits.length }); @@ -426,7 +428,11 @@ selection={{ mode: "range", range: commitRange, - oncommit: (range) => (stagedRange = range) + whole: stagedWhole, + oncommit: (range, whole) => { + stagedRange = range; + stagedWhole = whole; + } }} /> {/if} diff --git a/web/src/lib/components/ui/RangeSelector.svelte b/web/src/lib/components/ui/RangeSelector.svelte index f6e46404d..86a50476c 100644 --- a/web/src/lib/components/ui/RangeSelector.svelte +++ b/web/src/lib/components/ui/RangeSelector.svelte @@ -33,6 +33,7 @@ * nudging it with the arrow keys always moves one handle freely, whatever the mode. */ mode?: RangeMode; + whole?: boolean; row: Snippet<[number, T, RowActions]>; } @@ -42,6 +43,7 @@ range = $bindable({ start: 0, end: Infinity }), muteFullSelection = false, mode = "resize", + whole: wholeProp, row }: Props = $props(); @@ -154,12 +156,14 @@ // just the span between the two handles -- at rest that's `selectedRange` let displayRange = $derived({ start: Math.min(pos.a, pos.b), end: Math.max(pos.a, pos.b) }); - let displayRows = $derived(previewRows ?? rowsOf(displayRange)); - let whole = $derived( - selectedRange.start === listRange.start && selectedRange.end === listRange.end + (wholeProp ?? true) && + selectedRange.start === listRange.start && + selectedRange.end === listRange.end ); + let displayRows = $derived(previewRows ?? rowsOf(displayRange, previewed === null && whole)); + let muted = $derived(muteFullSelection && whole); // the working copy keeps no memory of a range once the reader is back to the whole list @@ -206,10 +210,8 @@ // the bottom end names the row past the span when the list is half-open, so the rows a // range actually covers stop short of it -- except at the foot of the list, where that row // is the boundary the range runs to rather than a row it excludes - function rowsOf({ start, end }: Range): Range { - const last = items.length - 1; - const whole = start === 0 && end === last; - return { start, end: whole ? end : end - (minLength - 1) }; + function rowsOf({ start, end }: Range, isWhole = false): Range { + return { start, end: isWhole ? end : end - (minLength - 1) }; } // rows back to a range: the foot of the list is the boundary a range runs to, so a span @@ -221,7 +223,7 @@ // pointing anywhere grows the span from the end of the selection furthest from it, and the // row pointed at is part of the span rather than the edge past it function rowSpanTo(at: number): Range | null { - const rows = rowsOf(selectedRange); + const rows = rowsOf(selectedRange, whole); if (at === items.length - 1) return null; const anchor = at < rows.start ? rows.end : rows.start; return { start: Math.min(anchor, at), end: Math.max(anchor, at) }; @@ -395,10 +397,10 @@ {@render bandBox(displayRows, "bg-background-subtle rounded")} {/if} {#if !muted} - {@render bandBox(rowsOf(selectedRange), "bg-background-subtle rounded")} + {@render bandBox(rowsOf(selectedRange, whole), "bg-background-subtle rounded")} {@render bandBox( - rowsOf(selectedRange), + rowsOf(selectedRange, whole), "relative z-10 rounded border border-border-default dark:border-border-strong" )} {/if} diff --git a/web/src/routes/[handle]/[repo]/pulls/[aturi]/[version]/[[range]]/PullViewPage.svelte b/web/src/routes/[handle]/[repo]/pulls/[aturi]/[version]/[[range]]/PullViewPage.svelte index 5c586b0f2..fa7128f61 100644 --- a/web/src/routes/[handle]/[repo]/pulls/[aturi]/[version]/[[range]]/PullViewPage.svelte +++ b/web/src/routes/[handle]/[repo]/pulls/[aturi]/[version]/[[range]]/PullViewPage.svelte @@ -274,10 +274,12 @@ // hold the committed range until navigation lands let stagedRange = $state(null); + let stagedWhole = $state(null); $effect(() => { // `pendingRange` is written but never read here, so this can't retrigger itself void data.view; stagedRange = null; + stagedWhole = null; }); const commitRange = $derived( @@ -287,6 +289,8 @@ : { start: 0, end: commits.length }) // interdiff doesn't use commitRange ); + const commitWhole = $derived(stagedWhole ?? page.params.range === undefined); + // rows are newest-first and `end` names the base marker, so in range is [start, end) const inCommitRange = (commit: CommitSummary) => { const row = rowOf(commit.hash); @@ -300,11 +304,11 @@ void goto(href(start === end ? `${start}` : `${start}..${end}`), inPage); }; - const confirmCommits = ({ start, end }: Range) => { + const confirmCommits = ({ start, end }: Range, whole: boolean) => { stagedRange = { start, end }; + stagedWhole = whole; const head = commits[start]?.hash ?? commits[0]?.hash ?? ""; const base = commits[end]?.hash ?? ""; - const whole = start === 0 && end >= commits.length; void goto(href(view.version, whole ? undefined : `${short(base)}..${short(head)}`), inPage); }; @@ -771,7 +775,12 @@ changeId: view.mode === "interdiff" ? view.changeId : "", onselect: confirmChangeId } - : { mode: "range", range: commitRange, oncommit: confirmCommits }} + : { + mode: "range", + range: commitRange, + whole: commitWhole, + oncommit: confirmCommits + }} /> {:else}