Monorepo for Tangled
tangled.org
1{{ define "repo/fragments/editLabelPanel" }}
2 <form
3 id="edit-label-panel"
4 hx-put="/{{ .RepoInfo.FullName }}/labels/perform"
5 hx-indicator="#spinner"
6 hx-disabled-elt="#save-btn,#cancel-btn"
7 hx-swap="none"
8 class="flex flex-col gap-6"
9 >
10 <input type="hidden" name="repo" value="{{ .RepoInfo.RepoAt }}">
11 <input type="hidden" name="subject" value="{{ .Subject }}">
12 {{ template "editBasicLabels" . }}
13 {{ template "editKvLabels" . }}
14 {{ template "editLabelPanelActions" . }}
15 <div id="add-label-error" class="text-red-500 dark:text-red-400"></div>
16 </form>
17{{ end }}
18
19{{ define "editBasicLabels" }}
20 {{ $defs := .Defs }}
21 {{ $subject := .Subject }}
22 {{ $state := .State }}
23 {{ $prefix := .Prefix }}
24 {{ $labelStyle := "flex items-center gap-2 rounded py-1 px-2 border border-gray-200 dark:border-gray-700 text-sm bg-white dark:bg-gray-800 text-black dark:text-white" }}
25 {{ $hasNullDefs := false }}
26 {{ range $k, $d := $defs }}
27 {{ if $d.ValueType.IsNull }}{{ $hasNullDefs = true }}{{ end }}
28 {{ end }}
29 {{ if or $hasNullDefs (not $defs) }}
30 <div>
31 {{ template "repo/fragments/labelSectionHeaderText" "Labels" }}
32
33 <div class="flex flex-col gap-1 items-start">
34 {{ range $k, $d := $defs }}
35 {{ $isChecked := $state.ContainsLabel $k }}
36 {{ if $d.ValueType.IsNull }}
37 {{ $fieldName := $d.AtUri.String }}
38 {{ if $prefix }}{{ $fieldName = printf "%s[%s]" $prefix $d.AtUri }}{{ end }}
39 <label class="{{$labelStyle}}">
40 <input type="checkbox" name="{{ $fieldName }}" value="null" {{if $isChecked}}checked{{end}}>
41 {{ template "labels/fragments/labelDef" $d }}
42 </label>
43 {{ end }}
44 {{ else }}
45 <p class="text-gray-500 dark:text-gray-400 text-sm py-1">
46 No labels defined yet. You can choose default labels or define custom
47 labels in <a class="underline" href="/{{ $.RepoInfo.FullName }}/settings">settings</a>.
48 </p>
49 {{ end }}
50 </div>
51 </div>
52 {{ end }}
53{{ end }}
54
55{{ define "editKvLabels" }}
56 {{ $defs := .Defs }}
57 {{ $subject := .Subject }}
58 {{ $state := .State }}
59 {{ $prefix := .Prefix }}
60 {{ $groupSuffix := "" }}
61 {{ if $prefix }}{{ $groupSuffix = printf "-%s" $prefix }}{{ end }}
62 {{ $labelStyle := "font-normal normal-case flex items-center gap-2 p-0" }}
63
64 {{ range $k, $d := $defs }}
65 {{ if (not $d.ValueType.IsNull) }}
66 {{ $fieldName := $d.AtUri.String }}
67 {{ if $prefix }}{{ $fieldName = printf "%s[%s]" $prefix $d.AtUri }}{{ end }}
68 {{ $valset := $state.GetValSet $k }}
69 <div id="label-{{$d.Id}}{{ $groupSuffix }}" class="flex flex-col gap-1">
70 {{ if not $d.ValueType.IsBool }}
71 {{ template "repo/fragments/labelSectionHeaderText" $d.Name }}
72 {{ end }}
73 {{ if (and $d.Multiple $d.ValueType.IsEnum) }}
74 <!-- checkbox -->
75 {{ range $variant := $d.ValueType.Enum }}
76 <label class="{{$labelStyle}}">
77 <input type="checkbox" name="{{ $fieldName }}" value="{{$variant}}" {{if $state.ContainsLabelAndVal $k $variant}}checked{{end}}>
78 {{ $variant }}
79 </label>
80 {{ end }}
81 {{ else if $d.Multiple }}
82 <!-- dynamically growing input fields -->
83 {{ range $v, $s := $valset }}
84 {{ template "multipleInputField" (dict "def" $d "value" $v "key" $k "prefix" $prefix) }}
85 {{ else }}
86 {{ template "multipleInputField" (dict "def" $d "value" "" "key" $k "prefix" $prefix) }}
87 {{ end }}
88 {{ template "addFieldButton" (dict "def" $d "prefix" $prefix "groupSuffix" $groupSuffix) }}
89 {{ if and $.LoggedInUser $d.ValueType.IsString $d.ValueType.IsDidFormat }}
90 {{ template "assignToMeButton" (dict "def" $d "user" $.LoggedInUser "groupSuffix" $groupSuffix) }}
91 {{ end }}
92 {{ else if $d.ValueType.IsEnum }}
93 <!-- radio buttons -->
94 {{ $isUsed := $state.ContainsLabel $k }}
95 {{ range $variant := $d.ValueType.Enum }}
96 <label class="{{$labelStyle}}">
97 <input type="radio" name="{{ $fieldName }}" value="{{$variant}}" {{if $state.ContainsLabelAndVal $k $variant}}checked{{end}}>
98 {{ $variant }}
99 </label>
100 {{ end }}
101 <label class="{{$labelStyle}}">
102 <input type="radio" name="{{ $fieldName }}" value="" {{ if not $isUsed }}checked{{ end }}>
103 None
104 </label>
105 {{ else }}
106 <!-- single input field based on value type -->
107 {{ range $v, $s := $valset }}
108 {{ template "valueTypeInput" (dict "def" $d "value" $v "key" $k "prefix" $prefix) }}
109 {{ else }}
110 {{ template "valueTypeInput" (dict "def" $d "value" "" "key" $k "prefix" $prefix) }}
111 {{ end }}
112 {{ end }}
113 </div>
114 {{ end }}
115 {{ end }}
116{{ end }}
117
118{{ define "multipleInputField" }}
119 <div class="flex gap-1 items-stretch">
120 <div class="flex-1 min-w-0">
121 {{ template "valueTypeInput" . }}
122 </div>
123 {{ template "removeFieldButton" }}
124 </div>
125{{ end }}
126
127{{ define "assignToMeButton" }}
128 {{ $def := .def }}
129 {{ $user := .user }}
130 {{ $groupSuffix := .groupSuffix }}
131 {{ $handle := trimPrefix (resolve $user.Did) "@" }}
132 <button type="button"
133 data-assign-to-me="{{ $def.Id }}{{ $groupSuffix }}"
134 data-handle="{{ $handle }}"
135 onclick="(() => {
136 const group = document.getElementById('label-' + this.dataset.assignToMe);
137 const handle = this.dataset.handle;
138 const inputs = group.querySelectorAll('input[type=text]');
139 const empty = Array.from(inputs).find(i => !i.value.trim());
140 if (empty) { empty.value = handle; return; }
141 if (Array.from(inputs).some(i => i.value.trim() === handle)) return;
142 const tpl = document.getElementById('tpl-' + this.dataset.assignToMe);
143 if (!tpl) return;
144 const addBtn = tpl.nextElementSibling;
145 addBtn.insertAdjacentHTML('beforebegin', tpl.innerHTML);
146 const newInput = addBtn.previousElementSibling.querySelector('input[type=text]');
147 if (newInput) newInput.value = handle;
148 })()"
149 class="text-xs text-gray-500 dark:text-gray-400 hover:underline self-end inline-flex items-center gap-1">
150 {{ i "user-round-plus" "size-3.5" }}
151 Assign to me
152 </button>
153{{ end }}
154
155{{ define "addFieldButton" }}
156 {{ $def := .def }}
157 {{ $prefix := .prefix }}
158 {{ $groupSuffix := .groupSuffix }}
159 <div style="display:none" id="tpl-{{ $def.Id }}{{ $groupSuffix }}">
160 {{ template "multipleInputField" (dict "def" $def "value" "" "key" $def.AtUri.String "prefix" $prefix) }}
161 </div>
162 <button type="button" onClick="this.insertAdjacentHTML('beforebegin', document.getElementById('tpl-{{ $def.Id }}{{ $groupSuffix }}').innerHTML)" class="w-full btn flex items-center gap-2">
163 {{ i "plus" "size-4" }} Add
164 </button>
165{{ end }}
166
167{{ define "removeFieldButton" }}
168 <button type="button" onClick="this.parentElement.remove()" class="btn flex items-center gap-2 text-red-400 dark:text-red-500">
169 {{ i "trash-2" "size-4" }}
170 </button>
171{{ end }}
172
173{{ define "valueTypeInput" }}
174 {{ $def := .def }}
175 {{ $valueType := $def.ValueType }}
176 {{ $value := .value }}
177 {{ $key := .key }}
178
179 {{ if $valueType.IsBool }}
180 {{ template "boolTypeInput" $ }}
181 {{ else if $valueType.IsInt }}
182 {{ template "intTypeInput" $ }}
183 {{ else if $valueType.IsString }}
184 {{ template "stringTypeInput" $ }}
185 {{ else if $valueType.IsNull }}
186 {{ template "nullTypeInput" $ }}
187 {{ end }}
188{{ end }}
189
190{{ define "boolTypeInput" }}
191 {{ $def := .def }}
192 {{ $prefix := .prefix }}
193 {{ $fieldName := $def.AtUri.String }}
194 {{ if $prefix }}{{ $fieldName = printf "%s[%s]" $prefix $def.AtUri }}{{ end }}
195 {{ $value := .value }}
196 {{ $isOn := eq $value "true" }}
197 <label class="font-normal normal-case flex items-center gap-2">
198 <input type="checkbox" name="{{ $fieldName }}" value="true" {{ if $isOn }}checked{{ end }}>
199 {{ $def.Name }}
200 </label>
201{{ end }}
202
203{{ define "intTypeInput" }}
204 {{ $def := .def }}
205 {{ $prefix := .prefix }}
206 {{ $fieldName := $def.AtUri.String }}
207 {{ if $prefix }}{{ $fieldName = printf "%s[%s]" $prefix $def.AtUri }}{{ end }}
208 {{ $value := .value }}
209 <input class="p-1 w-full" type="number" name="{{$fieldName}}" value="{{$value}}">
210{{ end }}
211
212{{ define "stringTypeInput" }}
213 {{ $def := .def }}
214 {{ $prefix := .prefix }}
215 {{ $fieldName := $def.AtUri.String }}
216 {{ if $prefix }}{{ $fieldName = printf "%s[%s]" $prefix $def.AtUri }}{{ end }}
217 {{ $valueType := $def.ValueType }}
218 {{ $value := .value }}
219
220 {{ if $valueType.IsDidFormat }}
221 {{ $value = trimPrefix (resolve .value) "@" }}
222 <actor-typeahead>
223 <input
224 autocapitalize="none"
225 autocorrect="off"
226 autocomplete="off"
227 placeholder="user.tngl.sh"
228 value="{{$value}}"
229 name="{{$fieldName}}"
230 type="text"
231 class="p-1 w-full h-full text-sm"
232 />
233 </actor-typeahead>
234 {{ else }}
235 <input class="p-1 w-full" type="text" name="{{$fieldName}}" value="{{$value}}">
236 {{ end }}
237{{ end }}
238
239{{ define "nullTypeInput" }}
240 {{ $def := .def }}
241 {{ $prefix := .prefix }}
242 {{ $fieldName := $def.AtUri.String }}
243 {{ if $prefix }}{{ $fieldName = printf "%s[%s]" $prefix $def.AtUri }}{{ end }}
244 <input class="p-1" type="hidden" name="{{$fieldName}}" value="null">
245{{ end }}
246
247{{ define "editLabelPanelActions" }}
248 <div class="flex gap-2 pt-2">
249 <button
250 id="cancel-btn"
251 type="button"
252 hx-get="/{{ .RepoInfo.FullName }}/label"
253 hx-vals='{"subject": "{{.Subject}}"}'
254 hx-swap="outerHTML"
255 hx-target="#edit-label-panel"
256 class="btn w-1/2 flex items-center gap-2 text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 group">
257 {{ i "x" "size-4" }} Cancel
258 </button>
259
260 <button
261 id="save-btn"
262 type="submit"
263 class="btn w-1/2 flex items-center">
264 <span class="inline-flex gap-2 items-center">{{ i "check" "size-4" }} Save</span>
265 <span id="spinner" class="group">
266 {{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
267 </span>
268 </button>
269 </div>
270{{ end }}