this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+352 -264
appview
spindle
models
types
+39
appview/models/pipeline.go
··· 1 1 package models 2 2 3 3 import ( 4 + "fmt" 4 5 "slices" 6 + "strings" 5 7 "time" 6 8 7 9 "github.com/bluesky-social/indigo/atproto/syntax" ··· 50 52 } 51 53 52 54 return 0 55 + } 56 + 57 + // produces short summary of successes: 58 + // - "0/4" when zero successes of 4 workflows 59 + // - "4/4" when all successes of 4 workflows 60 + // - "0/0" when no workflows run in this pipeline 61 + func (p Pipeline) ShortStatusSummary() string { 62 + counts := make(map[spindle.StatusKind]int) 63 + for _, w := range p.Statuses { 64 + counts[w.Latest().Status] += 1 65 + } 66 + 67 + total := len(p.Statuses) 68 + successes := counts[spindle.StatusKindSuccess] 69 + 70 + return fmt.Sprintf("%d/%d", successes, total) 71 + } 72 + 73 + // produces a string of the form "3/4 success, 2/4 failed, 1/4 pending" 74 + func (p Pipeline) LongStatusSummary() string { 75 + counts := make(map[spindle.StatusKind]int) 76 + for _, w := range p.Statuses { 77 + counts[w.Latest().Status] += 1 78 + } 79 + 80 + total := len(p.Statuses) 81 + 82 + var result []string 83 + // finish states first, followed by start states 84 + states := append(spindle.FinishStates[:], spindle.StartStates[:]...) 85 + for _, state := range states { 86 + if count, ok := counts[state]; ok { 87 + result = append(result, fmt.Sprintf("%d/%d %s", count, total, state.String())) 88 + } 89 + } 90 + 91 + return strings.Join(result, ", ") 53 92 } 54 93 55 94 func (p Pipeline) Counts() map[string]int {
+1
appview/pages/templates/fragments/tabSelector.html
··· 9 9 {{ range $index, $value := $all }} 10 10 {{ $isActive := eq $value.Key $active }} 11 11 <a href="?{{ $name }}={{ $value.Key }}" 12 + hx-boost=true 12 13 {{ if $include }} 13 14 hx-get="?{{ $name }}={{ $value.Key }}" 14 15 hx-include="{{ $include }}"
+1 -1
appview/pages/templates/repo/fragments/diff.html
··· 18 18 {{ range $idx, $hunk := $diff }} 19 19 {{ with $hunk }} 20 20 <details open id="file-{{ .Id }}" class="group border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm" tabindex="{{ add $idx 1 }}"> 21 - <summary class="list-none cursor-pointer sticky top-0"> 21 + <summary class="list-none cursor-pointer sticky top-12"> 22 22 <div id="diff-file-header" class="rounded cursor-pointer bg-white dark:bg-gray-800 flex justify-between"> 23 23 <div id="left-side-items" class="p-2 flex gap-2 items-center overflow-x-auto"> 24 24 <span class="group-open:hidden inline">{{ i "chevron-right" "w-4 h-4" }}</span>
+1 -8
appview/pages/templates/repo/fragments/diffChangedFiles.html
··· 1 1 {{ define "repo/fragments/diffChangedFiles" }} 2 - {{ $stat := .Stat }} 3 2 {{ $fileTree := fileTree .ChangedFiles }} 4 3 <section class="overflow-x-auto text-sm px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto min-h-full rounded bg-white dark:bg-gray-800 drop-shadow-sm"> 5 - <div class="diff-stat"> 6 - <div class="flex gap-2 items-center"> 7 - <strong class="text-sm uppercase dark:text-gray-200">Changed files</strong> 8 - {{ template "repo/fragments/diffStatPill" $stat }} 9 - </div> 10 - {{ template "repo/fragments/fileTree" $fileTree }} 11 - </div> 4 + {{ template "repo/fragments/fileTree" $fileTree }} 12 5 </section> 13 6 {{ end }}
+22 -25
appview/pages/templates/repo/fragments/diffOpts.html
··· 1 1 {{ define "repo/fragments/diffOpts" }} 2 - <section class="flex flex-col gap-2 overflow-x-auto text-sm px-6 py-2 border border-gray-200 dark:border-gray-700 w-full mx-auto rounded bg-white dark:bg-gray-800 drop-shadow-sm"> 3 - <strong class="text-sm uppercase dark:text-gray-200">options</strong> 4 - {{ $active := "unified" }} 5 - {{ if .Split }} 6 - {{ $active = "split" }} 7 - {{ end }} 2 + {{ $active := "unified" }} 3 + {{ if .Split }} 4 + {{ $active = "split" }} 5 + {{ end }} 8 6 9 - {{ $unified := 10 - (dict 11 - "Key" "unified" 12 - "Value" "unified" 13 - "Icon" "square-split-vertical" 14 - "Meta" "") }} 15 - {{ $split := 16 - (dict 17 - "Key" "split" 18 - "Value" "split" 19 - "Icon" "square-split-horizontal" 20 - "Meta" "") }} 21 - {{ $values := list $unified $split }} 7 + {{ $unified := 8 + (dict 9 + "Key" "unified" 10 + "Value" "unified" 11 + "Icon" "square-split-vertical" 12 + "Meta" "") }} 13 + {{ $split := 14 + (dict 15 + "Key" "split" 16 + "Value" "split" 17 + "Icon" "square-split-horizontal" 18 + "Meta" "") }} 19 + {{ $values := list $unified $split }} 22 20 23 - {{ template "fragments/tabSelector" 24 - (dict 25 - "Name" "diff" 26 - "Values" $values 27 - "Active" $active) }} 28 - </section> 21 + {{ template "fragments/tabSelector" 22 + (dict 23 + "Name" "diff" 24 + "Values" $values 25 + "Active" $active) }} 29 26 {{ end }} 30 27
+60 -69
appview/pages/templates/repo/pipelines/fragments/pipelineSymbol.html
··· 1 1 {{ define "repo/pipelines/fragments/pipelineSymbol" }} 2 - <div class="cursor-pointer"> 3 - {{ $c := .Counts }} 4 - {{ $statuses := .Statuses }} 5 - {{ $total := len $statuses }} 6 - {{ $success := index $c "success" }} 7 - {{ $fail := index $c "failed" }} 8 - {{ $timeout := index $c "timeout" }} 9 - {{ $empty := eq $total 0 }} 10 - {{ $allPass := eq $success $total }} 11 - {{ $allFail := eq $fail $total }} 12 - {{ $allTimeout := eq $timeout $total }} 13 - 14 - {{ if $empty }} 15 - <div class="flex gap-1 items-center"> 16 - {{ i "hourglass" "size-4 text-gray-600 dark:text-gray-400 " }} 17 - <span>0/{{ $total }}</span> 18 - </div> 19 - {{ else if $allPass }} 20 - <div class="flex gap-1 items-center"> 21 - {{ i "check" "size-4 text-green-600" }} 22 - <span>{{ $total }}/{{ $total }}</span> 23 - </div> 24 - {{ else if $allFail }} 25 - <div class="flex gap-1 items-center"> 26 - {{ i "x" "size-4 text-red-500" }} 27 - <span>0/{{ $total }}</span> 28 - </div> 29 - {{ else if $allTimeout }} 30 - <div class="flex gap-1 items-center"> 31 - {{ i "clock-alert" "size-4 text-orange-500" }} 32 - <span>0/{{ $total }}</span> 33 - </div> 2 + <div class="cursor-pointer flex gap-2 items-center"> 3 + {{ template "symbol" .Pipeline }} 4 + {{ if .ShortSummary }} 5 + {{ .Pipeline.ShortStatusSummary }} 34 6 {{ else }} 35 - {{ $radius := f64 8 }} 36 - {{ $circumference := mulf64 2.0 (mulf64 3.1416 $radius) }} 37 - {{ $offset := 0.0 }} 38 - <div class="flex gap-1 items-center"> 39 - <svg class="w-4 h-4 transform -rotate-90" viewBox="0 0 20 20"> 40 - <circle cx="10" cy="10" r="{{ $radius }}" fill="none" stroke="#f3f4f633" stroke-width="2"/> 7 + {{ .Pipeline.LongStatusSummary }} 8 + {{ end }} 9 + </div> 10 + {{ end }} 41 11 42 - {{ range $kind, $count := $c }} 43 - {{ $color := "" }} 44 - {{ if or (eq $kind "pending") (eq $kind "running") }} 45 - {{ $color = "#eab308" }} {{/* amber-500 */}} 46 - {{ else if eq $kind "success" }} 47 - {{ $color = "#10b981" }} {{/* green-500 */}} 48 - {{ else if eq $kind "cancelled" }} 49 - {{ $color = "#6b7280" }} {{/* gray-500 */}} 50 - {{ else if eq $kind "timeout" }} 51 - {{ $color = "#fb923c" }} {{/* orange-400 */}} 52 - {{ else }} 53 - {{ $color = "#ef4444" }} {{/* red-500 for failed or unknown */}} 54 - {{ end }} 12 + {{ define "symbol" }} 13 + {{ $c := .Counts }} 14 + {{ $statuses := .Statuses }} 15 + {{ $total := len $statuses }} 16 + {{ $success := index $c "success" }} 17 + {{ $fail := index $c "failed" }} 18 + {{ $timeout := index $c "timeout" }} 19 + {{ $empty := eq $total 0 }} 20 + {{ $allPass := eq $success $total }} 21 + {{ $allFail := eq $fail $total }} 22 + {{ $allTimeout := eq $timeout $total }} 55 23 56 - {{ $percent := divf64 (f64 $count) (f64 $total) }} 57 - {{ $length := mulf64 $percent $circumference }} 58 - 59 - <circle 60 - cx="10" cy="10" r="{{ $radius }}" 61 - fill="none" 62 - stroke="{{ $color }}" 63 - stroke-width="2" 64 - stroke-dasharray="{{ printf "%.2f %.2f" $length (subf64 $circumference $length) }}" 65 - stroke-dashoffset="{{ printf "%.2f" (negf64 $offset) }}" 66 - /> 67 - {{ $offset = addf64 $offset $length }} 68 - {{ end }} 69 - </svg> 70 - <span>{{ $success }}/{{ $total }}</span> 71 - </div> 72 - {{ end }} 73 - </div> 24 + {{ if $empty }} 25 + {{ i "hourglass" "size-4 text-gray-600 dark:text-gray-400 " }} 26 + {{ else if $allPass }} 27 + {{ i "check" "size-4 text-green-600 dark:text-green-500" }} 28 + {{ else if $allFail }} 29 + {{ i "x" "size-4 text-red-600 dark:text-red-500" }} 30 + {{ else if $allTimeout }} 31 + {{ i "clock-alert" "size-4 text-orange-500" }} 32 + {{ else }} 33 + {{ $radius := f64 8 }} 34 + {{ $circumference := mulf64 2.0 (mulf64 3.1416 $radius) }} 35 + {{ $offset := 0.0 }} 36 + <svg class="w-4 h-4 transform -rotate-90" viewBox="0 0 20 20"> 37 + <circle cx="10" cy="10" r="{{ $radius }}" fill="none" class="stroke-gray-200 dark:stroke-gray-700" stroke-width="2"/> 38 + {{ range $kind, $count := $c }} 39 + {{ $colorClass := "" }} 40 + {{ if or (eq $kind "pending") (eq $kind "running") }} 41 + {{ $colorClass = "stroke-yellow-600 dark:stroke-yellow-500" }} 42 + {{ else if eq $kind "success" }} 43 + {{ $colorClass = "stroke-green-600 dark:stroke-green-500" }} 44 + {{ else if eq $kind "cancelled" }} 45 + {{ $colorClass = "stroke-gray-600 dark:stroke-gray-500" }} 46 + {{ else if eq $kind "timeout" }} 47 + {{ $colorClass = "stroke-orange-600 dark:stroke-orange-500" }} 48 + {{ else }} 49 + {{ $colorClass = "stroke-red-600 dark:stroke-red-500" }} 50 + {{ end }} 51 + {{ $percent := divf64 (f64 $count) (f64 $total) }} 52 + {{ $length := mulf64 $percent $circumference }} 53 + <circle 54 + cx="10" cy="10" r="{{ $radius }}" 55 + fill="none" 56 + class="{{ $colorClass }}" 57 + stroke-width="2" 58 + stroke-dasharray="{{ printf "%.2f %.2f" $length (subf64 $circumference $length) }}" 59 + stroke-dashoffset="{{ printf "%.2f" (negf64 $offset) }}" 60 + /> 61 + {{ $offset = addf64 $offset $length }} 62 + {{ end }} 63 + </svg> 64 + {{ end }} 74 65 {{ end }}
+1 -1
appview/pages/templates/repo/pipelines/fragments/pipelineSymbolLong.html
··· 4 4 <div class="relative inline-block"> 5 5 <details class="relative"> 6 6 <summary class="cursor-pointer list-none"> 7 - {{ template "repo/pipelines/fragments/pipelineSymbol" .Pipeline }} 7 + {{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" true) }} 8 8 </summary> 9 9 {{ template "repo/pipelines/fragments/tooltip" $ }} 10 10 </details>
+11 -11
appview/pages/templates/repo/pulls/fragments/pullActions.html
··· 22 22 {{ $isLastRound := eq $roundNumber $lastIdx }} 23 23 {{ $isSameRepoBranch := .Pull.IsBranchBased }} 24 24 {{ $isUpToDate := .ResubmitCheck.No }} 25 - <div id="actions-{{$roundNumber}}" class="flex flex-wrap gap-2 relative py-2 px-4"> 25 + <div id="actions-{{$roundNumber}}" class="flex flex-wrap gap-2 relative p-2"> 26 26 <button 27 27 hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ $roundNumber }}/comment" 28 28 hx-target="#actions-{{$roundNumber}}" 29 29 hx-swap="outerHtml" 30 30 class="btn-flat p-2 flex items-center gap-2 no-underline hover:no-underline group"> 31 - {{ i "message-square-plus" "w-4 h-4" }} 32 - <span>comment</span> 31 + {{ i "message-square-plus" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 33 32 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 33 + comment 34 34 </button> 35 35 {{ if .BranchDeleteStatus }} 36 36 <button ··· 53 53 hx-swap="none" 54 54 hx-confirm="Are you sure you want to merge pull #{{ .Pull.PullId }} into the `{{ .Pull.TargetBranch }}` branch?" 55 55 class="btn-flat p-2 flex items-center gap-2 group" {{ $disabled }}> 56 - {{ i "git-merge" "w-4 h-4" }} 57 - <span>merge{{if $stackCount}} {{$stackCount}}{{end}}</span> 56 + {{ i "git-merge" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 58 57 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 58 + merge{{if $stackCount}} {{$stackCount}}{{end}} 59 59 </button> 60 60 {{ end }} 61 61 ··· 82 82 title="Resubmit this pull request" 83 83 {{ end }} 84 84 > 85 - {{ i "rotate-ccw" "w-4 h-4" }} 86 - <span>resubmit</span> 85 + {{ i "rotate-ccw" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 87 86 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 87 + resubmit 88 88 </button> 89 89 {{ end }} 90 90 ··· 93 93 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/close" 94 94 hx-swap="none" 95 95 class="btn-flat p-2 flex items-center gap-2 group"> 96 - {{ i "ban" "w-4 h-4" }} 97 - <span>close</span> 96 + {{ i "ban" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 98 97 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 98 + close 99 99 </button> 100 100 {{ end }} 101 101 ··· 104 104 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/reopen" 105 105 hx-swap="none" 106 106 class="btn-flat p-2 flex items-center gap-2 group"> 107 - {{ i "refresh-ccw-dot" "w-4 h-4" }} 108 - <span>reopen</span> 107 + {{ i "refresh-ccw-dot" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 109 108 {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 109 + reopen 110 110 </button> 111 111 {{ end }} 112 112 </div>
+39 -24
appview/pages/templates/repo/pulls/fragments/pullNewComment.html
··· 1 1 {{ define "repo/pulls/fragments/pullNewComment" }} 2 2 <div 3 3 id="pull-comment-card-{{ .RoundNumber }}" 4 - class="bg-white dark:bg-gray-800 rounded drop-shadow-sm p-4 relative w-full flex flex-col gap-2"> 5 - <div class="text-sm text-gray-500 dark:text-gray-400"> 6 - {{ resolve .LoggedInUser.Did }} 7 - </div> 4 + class="w-full flex flex-col gap-2"> 5 + {{ template "user/fragments/picHandleLink" .LoggedInUser.Did }} 8 6 <form 9 7 hx-post="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ .RoundNumber }}/comment" 10 - hx-indicator="#create-comment-spinner" 11 8 hx-swap="none" 12 - class="w-full flex flex-wrap gap-2" 9 + hx-on::after-request="if(event.detail.successful) this.reset()" 10 + hx-disabled-elt="#reply-{{ .RoundNumber }}" 11 + class="w-full flex flex-wrap gap-2 group" 13 12 > 14 13 <textarea 15 14 name="body" 16 15 class="w-full p-2 rounded border border-gray-200" 16 + rows=8 17 17 placeholder="Add to the discussion..."></textarea 18 18 > 19 - <button type="submit" class="btn flex items-center gap-2"> 20 - {{ i "message-square" "w-4 h-4" }} 21 - <span>comment</span> 22 - <span id="create-comment-spinner" class="group"> 23 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 24 - </span> 25 - </button> 26 - <button 27 - type="button" 28 - class="btn flex items-center gap-2 group" 29 - hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ .RoundNumber }}/actions" 30 - hx-swap="outerHTML" 31 - hx-target="#actions-{{.RoundNumber}}" 32 - > 33 - {{ i "x" "w-4 h-4" }} 34 - <span>cancel</span> 35 - {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 36 - </button> 19 + {{ template "replyActions" . }} 37 20 <div id="pull-comment"></div> 38 21 </form> 39 22 </div> 40 23 {{ end }} 24 + 25 + {{ define "replyActions" }} 26 + <div class="flex flex-wrap items-stretch justify-end gap-2 text-gray-500 dark:text-gray-400 text-sm w-full"> 27 + {{ template "cancel" . }} 28 + {{ template "reply" . }} 29 + </div> 30 + {{ end }} 31 + 32 + {{ define "cancel" }} 33 + <button 34 + type="button" 35 + class="btn text-red-500 dark:text-red-400 flex gap-2 items-center group" 36 + hx-get="/{{ .RepoInfo.FullName }}/pulls/{{ .Pull.PullId }}/round/{{ .RoundNumber }}/actions" 37 + hx-swap="outerHTML" 38 + hx-target="#actions-{{.RoundNumber}}" 39 + > 40 + {{ i "x" "w-4 h-4" }} 41 + <span>cancel</span> 42 + </button> 43 + {{ end }} 44 + 45 + {{ define "reply" }} 46 + <button 47 + type="submit" 48 + id="reply-{{ .RoundNumber }}" 49 + class="btn-create flex items-center gap-2"> 50 + {{ i "reply" "w-4 h-4 inline group-[.htmx-request]:hidden" }} 51 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 52 + reply 53 + </button> 54 + {{ end }} 55 +
+1 -1
appview/pages/templates/repo/pulls/fragments/summarizedPullHeader.html
··· 18 18 {{ $lastSubmission := index .Submissions $latestRound }} 19 19 {{ $commentCount := len $lastSubmission.Comments }} 20 20 {{ if and $pipeline $pipeline.Id }} 21 - {{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }} 21 + {{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" true) }} 22 22 <span class="before:content-['ยท'] before:select-none text-gray-500 dark:text-gray-400"></span> 23 23 {{ end }} 24 24 <span>
+169 -119
appview/pages/templates/repo/pulls/pull.html
··· 15 15 {{ end }} 16 16 17 17 {{ define "repoContentLayout" }} 18 - <div class="grid grid-cols-1 md:grid-cols-10 gap-4 w-full"> 18 + <div class="grid grid-cols-1 md:grid-cols-10 gap-y-2 gap-x-4 w-full"> 19 19 <div class="col-span-1 md:col-span-7"> 20 20 <section class="bg-white dark:bg-gray-800 p-6 rounded relative w-full mx-auto dark:text-white h-full"> 21 21 {{ block "repoContent" . }}{{ end }} ··· 35 35 {{ template "repo/fragments/externalLinkPanel" $.Pull.AtUri }} 36 36 </div> 37 37 38 - <div class="col-span-1 md:col-span-7"> 39 - {{ template "repo/fragments/diff" (list .Diff .DiffOpts) }} 38 + <style> 39 + #filesToggle:checked ~ div label[for="filesToggle"] .show-text { display: none; } 40 + #filesToggle:checked ~ div label[for="filesToggle"] .hide-text { display: inline; } 41 + #filesToggle:not(:checked) ~ div label[for="filesToggle"] .hide-text { display: none; } 42 + 43 + #filesToggle:checked ~ div div#files { width: 10vw; margin-right: 1rem; } 44 + #filesToggle:not(:checked) ~ div div#files { width: 0; display: hidden; margin-right: 0; } 45 + 46 + #subsToggle:checked ~ div div#subs { width: 25vw; margin-left: 1rem; } 47 + #subsToggle:not(:checked) ~ div div#subs { width: 0; display: hidden; margin-left: 0; } 48 + </style> 49 + 50 + <!-- Checkboxes must come first as siblings --> 51 + <input type="checkbox" id="filesToggle" class="peer/files hidden" checked/> 52 + <input type="checkbox" id="subsToggle" class="peer/subs hidden" checked/> 53 + 54 + <!-- Top bar with controls --> 55 + <div class="sticky top-0 z-30 bg-slate-100 dark:bg-gray-900 flex items-center gap-2 col-span-full h-12"> 56 + <label for="filesToggle" class="inline-flex items-center justify-center rounded cursor-pointer p-2 text-normal font-normal normalcase"> 57 + <span class="show-text">{{ i "panel-left-open" "size-5" }}</span> 58 + <span class="hide-text">{{ i "panel-left-close" "size-5" }}</span> 59 + </label> 60 + {{ template "repo/fragments/diffStatPill" .Diff.Stat }} 61 + {{ .Diff.Stat.FilesChanged }} changed file{{ if ne .Diff.Stat.FilesChanged 1 }}s{{ end }} 62 + <div class="flex-grow"></div> 63 + {{ template "repo/fragments/diffOpts" .DiffOpts }} 64 + <label for="subsToggle" class="inline-flex items-center justify-center rounded cursor-pointer p-2"> 65 + {{ i "message-square-more" "size-5" }} 66 + </label> 40 67 </div> 41 - <div class="col-span-1 md:col-span-3 flex flex-col max-h-dvh sticky top-0"> 42 - <div class="z-20 sticky top-0 rounded-t p-3 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700"> 43 - <h2 class="font-bold uppercase">history</h2> 68 + 69 + <div class="flex col-span-full"> 70 + <!-- left panel --> 71 + <div id="files" class="w-0 overflow-hidden sticky top-12 max-h-screen overflow-y-auto pb-12"> 72 + {{ template "repo/fragments/diffChangedFiles" .Diff }} 44 73 </div> 45 - <div class="flex flex-col-reverse gap-4 overflow-y-auto"> 46 - {{ template "submissions2" . }} 74 + 75 + <!-- main content --> 76 + <div class="flex-1 min-w-0 sticky top-12 pb-12"> 77 + {{ template "repo/fragments/diff" (list .Diff .DiffOpts) }} 78 + </div> 79 + 80 + <!-- right panel --> 81 + <div id="subs" class="w-0 overflow-hidden max-h-screen flex flex-col sticky top-12 pb-12"> 82 + <div class="z-20 sticky top-0 rounded-t p-3 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700"> 83 + <h2 class="font-bold uppercase">history</h2> 84 + </div> 85 + <div class="flex flex-col-reverse gap-4 overflow-y-auto"> 86 + {{ template "submissions2" . }} 87 + </div> 47 88 </div> 48 89 </div> 49 90 </div> ··· 51 92 52 93 {{ define "repoContent" }} 53 94 {{ template "repo/pulls/fragments/pullHeader" . }} 54 - 55 95 {{ if .Pull.IsStacked }} 56 96 <div class="mt-8"> 57 97 {{ template "repo/pulls/fragments/pullStack" . }} ··· 81 121 /> 82 122 </div> 83 123 <!-- right column: name and body in two rows --> 84 - <div class="flex-1 min-w-0 flex flex-col gap-2"> 124 + <div class="flex-1 min-w-0 flex flex-col gap-1"> 85 125 <div class="flex gap-2 items-center justify-between mb-1"> 86 - <span class="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-500"> 126 + <span class="inline-flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400"> 87 127 {{ resolve $.Pull.OwnerDid }} submitted v{{ $round }} 88 128 <span class="select-none before:content-['\00B7']"></span> 89 129 <a class="text-gray-500 dark:text-gray-400 hover:text-gray-500" href="#round-#{{ $round }}">{{ template "repo/fragments/shortTimeAgo" .Created }}</a> ··· 97 137 </a> 98 138 {{ end }} 99 139 </div> 100 - <div> 101 - {{ if eq 1 (len $patches) }} 102 - <!-- only one commit, just inline the message into the round header --> 103 - {{ $commit := index $patches 0 }} 104 - <span>{{ $commit.Title | description }}</span> 105 - {{ if gt (len $commit.Body) 0 }} 106 - <p id="body-{{$round}}-{{$commit.SHA}}" class="mt-1 pb-2"> 107 - {{ nl2br $commit.Body }} 108 - </p> 109 - {{ end }} 110 - {{ else }} 111 - <span>Commits:</span> 112 - {{ range $patches }} 113 - <div id="commit-{{.SHA}}" class="py-1 px-2 relative w-full md:max-w-3/5 md:w-fit flex flex-col"> 114 - <div class="flex items-center gap-2"> 115 - {{ i "git-commit-horizontal" "w-4 h-4 flex-shrink-0" }} 116 - <div class="flex items-center"> 117 - <span>{{ .Title | description }}</span> 118 - {{ if gt (len .Body) 0 }} 119 - <button 120 - class="py-1/2 px-1 mx-2 bg-gray-200 hover:bg-gray-400 rounded dark:bg-gray-700 dark:hover:bg-gray-600" 121 - hx-on:click="document.getElementById('body-{{$round}}-{{.SHA}}').classList.toggle('hidden')" 122 - > 123 - {{ i "ellipsis" "w-3 h-3" }} 124 - </button> 125 - {{ end }} 126 - </div> 127 - </div> 128 - {{ if gt (len .Body) 0 }} 129 - <p id="body-{{$round}}-{{.SHA}}" class="hidden mt-1 text-sm pb-2"> 130 - {{ nl2br .Body }} 131 - </p> 132 - {{ end }} 133 - </div> 134 - {{ end }} 140 + <details class="group"> 141 + <summary class="list-none cursor-pointer flex items-center gap-2"> 142 + <span>{{ i "git-commit-horizontal" "w-4 h-4" }}</span> 143 + {{ len $patches }} commit{{ if ne (len $patches) 1 }}s{{ end }} 144 + </summary> 145 + {{ range $patches }} 146 + <div id="commit-{{.SHA}}" class="py-1 relative w-full md:max-w-3/5 md:w-fit flex flex-col text-gray-600 dark:text-gray-300"> 147 + <div class="flex items-baseline gap-2"> 148 + <div> 149 + <!-- attempt to resolve $fullRepo: this is possible only on non-deleted forks and branches --> 150 + {{ $fullRepo := "" }} 151 + {{ if and $.Pull.IsForkBased $.Pull.PullSource.Repo }} 152 + {{ $fullRepo = printf "%s/%s" $.Pull.OwnerDid $.Pull.PullSource.Repo.Name }} 153 + {{ else if $.Pull.IsBranchBased }} 154 + {{ $fullRepo = $.RepoInfo.FullName }} 155 + {{ end }} 156 + 157 + <!-- if $fullRepo was resolved, link to it, otherwise just span without a link --> 158 + {{ if $fullRepo }} 159 + <a href="/{{ $fullRepo }}/commit/{{ .SHA }}" class="font-mono text-gray-600 dark:text-gray-300">{{ slice .SHA 0 8 }}</a> 160 + {{ else }} 161 + <span class="font-mono">{{ slice .SHA 0 8 }}</span> 162 + {{ end }} 163 + </div> 164 + 165 + <div> 166 + <span>{{ .Title | description }}</span> 167 + {{ if gt (len .Body) 0 }} 168 + <button 169 + class="py-1/2 px-1 mx-2 bg-gray-200 hover:bg-gray-400 rounded dark:bg-gray-700 dark:hover:bg-gray-600" 170 + hx-on:click="document.getElementById('body-{{$round}}-{{.SHA}}').classList.toggle('hidden')" 171 + > 172 + {{ i "ellipsis" "w-3 h-3" }} 173 + </button> 174 + {{ end }} 175 + {{ if gt (len .Body) 0 }} 176 + <p id="body-{{$round}}-{{.SHA}}" class="hidden mt-1 text-sm pb-2">{{ nl2br .Body }}</p> 177 + {{ end }} 178 + </div> 179 + </div> 180 + </div> 135 181 {{ end }} 136 - </div> 137 - <div> 138 - {{ block "pipelineStatus" (list $ .) }} {{ end }} 139 - </div> 182 + </details> 183 + <div> 184 + {{ block "pipelineStatus" (list $ .) }} {{ end }} 185 + </div> 186 + {{ if eq $lastIdx .RoundNumber }} 187 + {{ block "mergeCheck" $ }} {{ end }} 188 + {{ end }} 140 189 </div> 141 190 </div> 142 191 <div class="relative ml-10 border-l-2 border-gray-200 dark:border-gray-700"> ··· 414 463 {{ end }} 415 464 {{ end }} 416 465 466 + {{ define "mergeCheck" }} 467 + {{ $isOpen := .Pull.State.IsOpen }} 468 + {{ if and $isOpen .MergeCheck .MergeCheck.Error }} 469 + <div class="flex items-center gap-2"> 470 + {{ i "triangle-alert" "w-4 h-4 text-red-500 dark:text-red-300" }} 471 + {{ .MergeCheck.Error }} 472 + </div> 473 + {{ else if and $isOpen .MergeCheck .MergeCheck.IsConflicted }} 474 + <details class="group"> 475 + <summary class="flex items-center justify-between cursor-pointer list-none"> 476 + <div class="flex items-center gap-2 "> 477 + {{ i "triangle-alert" "w-4 h-4" }} 478 + <span class="font-medium">merge conflicts detected</span> 479 + </div> 480 + <div> 481 + <span class="group-open:hidden inline">{{ i "chevrons-up-down" "w-4 h-4" }}</span> 482 + <span class="hidden group-open:inline">{{ i "chevrons-down-up" "w-4 h-4" }}</span> 483 + </div> 484 + </summary> 485 + {{ if gt (len .MergeCheck.Conflicts) 0 }} 486 + <ul class="space-y-1 mt-2"> 487 + {{ range .MergeCheck.Conflicts }} 488 + {{ if .Filename }} 489 + <li class="flex items-center"> 490 + {{ i "file-warning" "inline-flex w-4 h-4 mr-1.5 text-red-500 dark:text-red-300 flex-shrink-0" }} 491 + <span class="font-mono" style="word-break: keep-all; overflow-wrap: break-word;">{{ .Filename }}</span> 492 + </li> 493 + {{ else if .Reason }} 494 + <li class="flex items-center"> 495 + {{ i "file-warning" "w-4 h-4 mr-1.5 text-red-500 dark:text-red-300" }} 496 + <span>{{.Reason}}</span> 497 + </li> 498 + {{ end }} 499 + {{ end }} 500 + </ul> 501 + {{ end }} 502 + </details> 503 + {{ else if and $isOpen .MergeCheck }} 504 + <div class="flex items-center gap-2"> 505 + {{ i "check" "w-4 h-4 text-green-600 dark:text-green-500" }} 506 + <span>no conflicts, ready to merge</span> 507 + </div> 508 + {{ end }} 509 + {{ end }} 510 + 417 511 {{ define "mergeStatus" }} 418 512 {{ if .Pull.State.IsClosed }} 419 513 <div class="bg-gray-50 dark:bg-gray-700 border border-black dark:border-gray-500 rounded drop-shadow-sm px-6 py-2 relative"> ··· 438 532 <span class="font-medium">This pull has been deleted (possibly by jj abandon or jj squash)</span> 439 533 </div> 440 534 </div> 441 - {{ else if and .MergeCheck .MergeCheck.Error }} 442 - <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative"> 443 - <div class="flex items-center gap-2 text-red-500 dark:text-red-300"> 444 - {{ i "triangle-alert" "w-4 h-4" }} 445 - <span class="font-medium">{{ .MergeCheck.Error }}</span> 446 - </div> 447 - </div> 448 - {{ else if and .MergeCheck .MergeCheck.IsConflicted }} 449 - <div class="bg-red-50 dark:bg-red-900 border border-red-500 rounded drop-shadow-sm px-6 py-2 relative"> 450 - <details class="text-red-500 dark:text-red-300 group"> 451 - <summary class="flex items-center justify-between cursor-pointer list-none"> 452 - <div class="flex items-center gap-2 "> 453 - {{ i "triangle-alert" "w-4 h-4" }} 454 - <span class="font-medium">merge conflicts detected</span> 455 - </div> 456 - <div> 457 - <span class="group-open:hidden inline">{{ i "chevrons-up-down" "w-4 h-4" }}</span> 458 - <span class="hidden group-open:inline">{{ i "chevrons-down-up" "w-4 h-4" }}</span> 459 - </div> 460 - </summary> 461 - {{ if gt (len .MergeCheck.Conflicts) 0 }} 462 - <ul class="space-y-1 mt-2"> 463 - {{ range .MergeCheck.Conflicts }} 464 - {{ if .Filename }} 465 - <li class="flex items-center"> 466 - {{ i "file-warning" "inline-flex w-4 h-4 mr-1.5 text-red-500 dark:text-red-300 flex-shrink-0" }} 467 - <span class="font-mono" style="word-break: keep-all; overflow-wrap: break-word;">{{ .Filename }}</span> 468 - </li> 469 - {{ else if .Reason }} 470 - <li class="flex items-center"> 471 - {{ i "file-warning" "w-4 h-4 mr-1.5 text-red-500 dark:text-red-300" }} 472 - <span>{{.Reason}}</span> 473 - </li> 474 - {{ end }} 475 - {{ end }} 476 - </ul> 477 - {{ end }} 478 - </details> 479 - </div> 480 - {{ else if .MergeCheck }} 481 - <div class="bg-green-50 dark:bg-green-900 border border-green-500 rounded drop-shadow-sm px-6 py-2 relative"> 482 - <div class="flex items-center gap-2 text-green-500 dark:text-green-300"> 483 - {{ i "circle-check-big" "w-4 h-4" }} 484 - <span class="font-medium">no conflicts, ready to merge</span> 485 - </div> 486 - </div> 487 535 {{ end }} 488 536 {{ end }} 489 537 ··· 505 553 {{ with $pipeline }} 506 554 {{ $id := .Id }} 507 555 {{ if .Statuses }} 508 - <span>Workflows:</span> 509 - <div class=" grid grid-cols-1 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700"> 510 - {{ range $name, $all := .Statuses }} 511 - <a href="/{{ $root.RepoInfo.FullName }}/pipelines/{{ $id }}/workflow/{{ $name }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 512 - <div 513 - class="flex gap-2 items-center justify-between p-2"> 514 - {{ $lastStatus := $all.Latest }} 515 - {{ $kind := $lastStatus.Status.String }} 556 + <details> 557 + <summary class="cursor-pointer list-none">{{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" false) }}</summary> 558 + <div class="my-2 grid grid-cols-1 bg-white dark:bg-gray-800 rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700"> 559 + {{ range $name, $all := .Statuses }} 560 + <a href="/{{ $root.RepoInfo.FullName }}/pipelines/{{ $id }}/workflow/{{ $name }}" class="no-underline hover:no-underline hover:bg-gray-100/25 hover:dark:bg-gray-700/25"> 561 + <div 562 + class="flex gap-2 items-center justify-between p-2"> 563 + {{ $lastStatus := $all.Latest }} 564 + {{ $kind := $lastStatus.Status.String }} 516 565 517 - <div id="left" class="flex items-center gap-2 flex-shrink-0"> 518 - {{ template "repo/pipelines/fragments/workflowSymbol" $all }} 519 - {{ $name }} 520 - </div> 521 - <div id="right" class="flex items-center gap-2 flex-shrink-0"> 522 - <span class="font-bold">{{ $kind }}</span> 523 - {{ if .TimeTaken }} 524 - {{ template "repo/fragments/duration" .TimeTaken }} 525 - {{ else }} 526 - {{ template "repo/fragments/shortTimeAgo" $lastStatus.Created }} 527 - {{ end }} 528 - </div> 566 + <div id="left" class="flex items-center gap-2 flex-shrink-0"> 567 + {{ template "repo/pipelines/fragments/workflowSymbol" $all }} 568 + {{ $name }} 569 + </div> 570 + <div id="right" class="flex items-center gap-2 flex-shrink-0"> 571 + <span class="font-bold">{{ $kind }}</span> 572 + {{ if .TimeTaken }} 573 + {{ template "repo/fragments/duration" .TimeTaken }} 574 + {{ else }} 575 + {{ template "repo/fragments/shortTimeAgo" $lastStatus.Created }} 576 + {{ end }} 577 + </div> 578 + </div> 579 + </a> 580 + {{ end }} 529 581 </div> 530 - </a> 531 - {{ end }} 532 - </div> 582 + </details> 533 583 {{ end }} 534 584 {{ end }} 535 585 {{ end }}
+1 -1
appview/pages/templates/repo/pulls/pulls.html
··· 136 136 {{ $pipeline := index $.Pipelines .LatestSha }} 137 137 {{ if and $pipeline $pipeline.Id }} 138 138 <span class="before:content-['ยท']"></span> 139 - {{ template "repo/pipelines/fragments/pipelineSymbol" $pipeline }} 139 + {{ template "repo/pipelines/fragments/pipelineSymbol" (dict "Pipeline" $pipeline "ShortSummary" true) }} 140 140 {{ end }} 141 141 142 142 {{ $state := .Labels }}
+2 -2
spindle/models/models.go
··· 53 53 StatusKindRunning, 54 54 } 55 55 FinishStates [4]StatusKind = [4]StatusKind{ 56 - StatusKindCancelled, 57 56 StatusKindFailed, 58 - StatusKindSuccess, 59 57 StatusKindTimeout, 58 + StatusKindCancelled, 59 + StatusKindSuccess, 60 60 } 61 61 ) 62 62
+4 -2
types/diff.go
··· 27 27 } 28 28 29 29 type DiffStat struct { 30 - Insertions int64 31 - Deletions int64 30 + Insertions int64 31 + Deletions int64 32 + FilesChanged int 32 33 } 33 34 34 35 func (d *Diff) Stats() DiffStat { ··· 37 38 stats.Insertions += f.LinesAdded 38 39 stats.Deletions += f.LinesDeleted 39 40 } 41 + stats.FilesChanged = len(d.TextFragments) 40 42 return stats 41 43 } 42 44