1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3package web
4
5// The fragments of the live updates (events.go). Each is a top-level element
6// with an id: the page swaps the element that has the same id.
7
8// FactValue is the value of a fact; its id, when it has one, is the target of
9// the live updates ("last push").
10templ FactValue(f Fact) {
11 <dd
12 if f.ID != "" {
13 id={ f.ID }
14 }
15 >
16 if f.Time != "" {
17 <time datetime={ f.Time }>{ f.Val }</time>
18 } else {
19 { f.Val }
20 }
21 </dd>
22}
23
24// RepoGone replaces the card of a repository that is deleted or not visible
25// any more; the page removes the element.
26templ RepoGone(id string) {
27 <li id={ id } data-gone hidden></li>
28}
29
30// RefsBody is the rows of the refs table.
31templ RefsBody(p RefsPage) {
32 <tbody id="ref-rows" data-rows>
33 for _, b := range p.Branches {
34 @refRow("branch", b)
35 }
36 for _, t := range p.Tags {
37 @refRow("tag", t)
38 }
39 </tbody>
40}
41
42// RefsKV is the facts of the rail of the refs page.
43templ RefsKV(p RefsPage) {
44 <dl class="kv" id="refs-kv">
45 <dt>default</dt>
46 <dd>{ p.Default }</dd>
47 <dt>branches</dt>
48 <dd>{ itoa(len(p.Branches)) }</dd>
49 <dt>tags</dt>
50 <dd>{ itoa(len(p.Tags)) }</dd>
51 if p.Latest != "" {
52 <dt>latest tag</dt>
53 <dd>{ p.Latest }</dd>
54 }
55 </dl>
56}
57
58// LiveSlot is the place of a live banner, empty: a status region that stays
59// in the page. The live updates (ermiteLive in ermite.js) replace its children,
60// never the element, so that a screen reader announces the new banner (a
61// region that is inserted with its content is not announced).
62templ LiveSlot(id string) {
63 <div id={ id } class="live-slot" role="status" data-slot></div>
64}
65
66// liveBanner is the slot with its banner: a link that reloads or opens the
67// page. Only the children of the element reach the page that has the slot.
68templ liveBanner(id, text, href string) {
69 <div id={ id } class="live-slot" role="status" data-slot><a href={ templ.SafeURL(href) }>{ text }</a></div>
70}
71
72// CommitsBanner announces new commits on the branch the page lists. It links
73// to the first page of the branch.
74templ CommitsBanner(text, href string) {
75 @liveBanner("commits-head", text, href)
76}
77
78// MRNotice tells a merge request page that it is out of date, or the list that
79// its content changed.
80templ MRNotice(text, href string) {
81 @liveBanner("mr-notice", text, href)
82}
83
84// Live is the connection to the event stream, in the hint badge. It shows
85// only once a connection stays lost for a few seconds (ermiteLive.lost): a
86// page that never connected, that is live, or whose stream comes back right
87// away (a proxy closing an idle stream) says nothing. The events of the stream are fragments that ermiteLive
88// (ermite.js) swaps in the page. A page without a stream renders nothing.
89//
90// hx-sse retries a lost connection for ever, with a growing delay. A refusal
91// that will not change (404: the repository is gone or hidden; 401) is handled
92// by ermiteLive.error, which has the page fetch itself again.
93templ Live(l Layout) {
94 if l.Live && l.LiveURL != "" {
95 <span
96 class="hint-live"
97 data-state="connecting"
98 hx-sse:connect={ l.LiveURL }
99 hx-swap="none"
100 :text="data.state === 'offline' ? 'live updates paused' : ''"
101 hx-on="htmx:sse:after:connection -> ermiteLive.back();
102 htmx:sse:close, htmx:sse:error -> ermiteLive.lost();
103 htmx:sse:error -> ermiteLive.error(event.detail.status);
104 htmx:sse:before:connection -> ermiteLive.gate(event);
105 home-list, repo-last-push, refs-list, commits-head, mr-notice, ci-run, ci-job -> ermiteLive.swap(event.detail.data);
106 gap -> ermiteLive.reload()"
107 ></span>
108 }
109}