This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / web / src / lib / components / repo / DiffView.svelte
3.7 kB 117 lines
1<script lang="ts"> 2 import { diffFileName, type DiffFile, type DiffStat } from "$lib/api/diff"; 3 import { fileStats } from "$lib/api/rawdiff"; 4 import type { Snippet } from "svelte"; 5 import DiffFileCard from "./DiffFileCard.svelte"; 6 import DiffFileList from "./DiffFileList.svelte"; 7 import DiffTopbar from "./DiffTopbar.svelte"; 8 import { diffRowKey, toFileDiffMetadata } from "./fileDiff"; 9 import type { DiffStyle } from "./pierre"; 10 import { encodePathSegments } from "./urls"; 11 12 interface Props { 13 files: DiffFile[]; 14 stat?: DiffStat; 15 // side-by-side or stacked, persisted by the route in ?diff= 16 diffStyle?: DiffStyle; 17 // raw .patch/.diff targets, only present for full-hash refs 18 downloadUrls?: { patch: string; diff: string }; 19 // blob route prefix for the per-file "view file" links, hidden when absent 20 blobBase?: string; 21 // server-prerendered shadow dom per row key, only for the initial ssr 22 prerendered?: Record<string, string>; 23 // round/interdiff indicator slot in the topbar (pull pages) 24 topbarCenter?: Snippet; 25 // subscription-toggle slot at the topbar's far end (pull review panel) 26 topbarActions?: Snippet; 27 // review-button slot on each file header (pull pages) 28 fileHeaderActions?: Snippet<[DiffFile]>; 29 } 30 31 let { 32 files, 33 stat, 34 diffStyle = "unified", 35 downloadUrls, 36 blobBase, 37 prerendered, 38 topbarCenter, 39 topbarActions, 40 fileHeaderActions 41 }: Props = $props(); 42 43 // one structured diff per text file, binary notes stay 44 // interleaved in the original diff order 45 const rows = $derived( 46 files.map((file) => ({ 47 file, 48 name: diffFileName(file), 49 key: diffRowKey(file), 50 stat: { ...fileStats(file), files_changed: 1 }, 51 fileDiff: file.is_binary ? undefined : toFileDiffMetadata(file), 52 prerenderedHTML: file.is_binary ? undefined : prerendered?.[diffRowKey(file)], 53 blobUrl: 54 blobBase && file.name.new ? `${blobBase}/${encodePathSegments(file.name.new)}` : undefined 55 })) 56 ); 57 58 // cards default to open 59 let openStates = $state<Record<string, boolean>>({}); 60 const isOpen = (key: string) => openStates[key] ?? true; 61 const allOpen = $derived(rows.every((row) => isOpen(row.key))); 62 const setAll = (open: boolean) => { 63 openStates = Object.fromEntries(rows.map((row) => [row.key, open])); 64 }; 65 66 let filesOpen = $state(true); 67</script> 68 69<div class="flex flex-col"> 70 <DiffTopbar 71 {stat} 72 fileCount={files.length} 73 {diffStyle} 74 {downloadUrls} 75 {filesOpen} 76 onToggleFiles={rows.length > 0 ? () => (filesOpen = !filesOpen) : undefined} 77 {allOpen} 78 onSetAllOpen={setAll} 79 center={topbarCenter} 80 actions={topbarActions} 81 /> 82 83 <div class="flex grow gap-4"> 84 {#if rows.length > 0 && filesOpen} 85 <aside 86 class="sticky top-[calc(5.75rem+env(safe-area-inset-top))] hidden max-h-[calc(100vh-5.75rem-env(safe-area-inset-top))] w-fit max-w-60 shrink-0 overflow-y-auto md:block" 87 > 88 <section 89 class="mx-auto min-h-full w-full overflow-x-auto rounded border border-border-default bg-background-default px-6 py-2 typography-paragraph-regular" 90 > 91 <DiffFileList files={rows.map((row) => ({ name: row.name }))} /> 92 </section> 93 </aside> 94 {/if} 95 96 <div class="flex min-w-0 flex-1 flex-col gap-4"> 97 {#if rows.length === 0} 98 <div class="py-8 text-center text-foreground-muted"> 99 <p>No differences found between the selected revisions.</p> 100 </div> 101 {/if} 102 {#each rows as row (row.key)} 103 <DiffFileCard 104 file={row.file} 105 name={row.name} 106 stat={row.stat} 107 fileDiff={row.fileDiff} 108 prerenderedHTML={row.prerenderedHTML} 109 {diffStyle} 110 blobUrl={row.blobUrl} 111 bind:open={() => isOpen(row.key), (open) => (openStates[row.key] = open)} 112 headerActions={fileHeaderActions} 113 /> 114 {/each} 115 </div> 116 </div> 117</div>