Monorepo for Tangled tangled.org

appview: improve label editing workflow

Signed-off-by: oppiliappan <me@oppi.li>

oppi.li 20e74e4a d161dea1

verified
Changed files
+433 -212
appview
+21 -2
appview/pages/templates/labels/fragments/label.html
··· 1 1 {{ define "labels/fragments/label" }} 2 2 {{ $d := .def }} 3 3 {{ $v := .val }} 4 + {{ $withPrefix := .withPrefix }} 4 5 <span class="flex items-center gap-2 font-normal normal-case rounded py-1 px-2 border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-800 text-sm"> 5 6 {{ template "repo/fragments/colorBall" (dict "color" $d.GetColor) }} 6 - {{ $d.Name }}{{ if not $d.ValueType.IsNull }}/{{ template "labelVal" (dict "def" $d "val" $v) }}{{ end }} 7 + 8 + {{ $lhs := printf "%s" $d.Name }} 9 + {{ $rhs := "" }} 10 + 11 + {{ if not $d.ValueType.IsNull }} 12 + {{ if $d.ValueType.IsDidFormat }} 13 + {{ $v = resolve $v }} 14 + {{ end }} 15 + 16 + {{ if not $withPrefix }} 17 + {{ $lhs = "" }} 18 + {{ else }} 19 + {{ $lhs = printf "%s/" $d.Name }} 20 + {{ end }} 21 + 22 + {{ $rhs = printf "%s" $v }} 23 + {{ end }} 24 + 25 + {{ printf "%s%s" $lhs $rhs }} 7 26 </span> 8 27 {{ end }} 9 28 ··· 13 32 {{ $v := .val }} 14 33 15 34 {{ if $d.ValueType.IsDidFormat }} 16 - {{ resolve $v }} 35 + {{ resolve $v }} 17 36 {{ else }} 18 37 {{ $v }} 19 38 {{ end }}
-127
appview/pages/templates/repo/fragments/addLabelModal.html
··· 1 - {{ define "repo/fragments/addLabelModal" }} 2 - {{ $root := .root }} 3 - {{ $subject := .subject }} 4 - {{ $state := .state }} 5 - {{ with $root }} 6 - <form 7 - hx-put="/{{ .RepoInfo.FullName }}/labels/perform" 8 - hx-on::after-request="this.reset()" 9 - hx-indicator="#spinner" 10 - hx-swap="none" 11 - class="flex flex-col gap-4" 12 - > 13 - <p class="text-gray-500 dark:text-gray-400">Add, remove or update labels.</p> 14 - 15 - <input class="hidden" name="repo" value="{{ .RepoInfo.RepoAt.String }}"> 16 - <input class="hidden" name="subject" value="{{ $subject }}"> 17 - 18 - <div class="flex flex-col gap-2"> 19 - {{ $id := 0 }} 20 - {{ range $k, $valset := $state.Inner }} 21 - {{ $d := index $root.LabelDefs $k }} 22 - {{ range $v, $s := $valset }} 23 - {{ template "labelCheckbox" (dict "def" $d "key" $k "val" $v "id" $id "isChecked" true) }} 24 - {{ $id = add $id 1 }} 25 - {{ end }} 26 - {{ end }} 27 - 28 - {{ range $k, $d := $root.LabelDefs }} 29 - {{ if not ($state.ContainsLabel $k) }} 30 - {{ template "labelCheckbox" (dict "def" $d "key" $k "val" "" "id" $id "isChecked" false) }} 31 - {{ $id = add $id 1 }} 32 - {{ end }} 33 - {{ else }} 34 - <span> 35 - No labels defined yet. You can define custom labels in <a class="underline" href="/{{ .RepoInfo.FullName }}/settings">settings</a>. 36 - </span> 37 - {{ end }} 38 - </div> 39 - 40 - <div class="flex gap-2 pt-2"> 41 - <button 42 - type="button" 43 - popovertarget="add-label-modal" 44 - popovertargetaction="hide" 45 - 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" 46 - > 47 - {{ i "x" "size-4" }} cancel 48 - </button> 49 - <button type="submit" class="btn w-1/2 flex items-center"> 50 - <span class="inline-flex gap-2 items-center">{{ i "check" "size-4" }} save</span> 51 - <span id="spinner" class="group"> 52 - {{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 53 - </span> 54 - </button> 55 - </div> 56 - <div id="add-label-error" class="text-red-500 dark:text-red-400"></div> 57 - </form> 58 - {{ end }} 59 - {{ end }} 60 - 61 - {{ define "labelCheckbox" }} 62 - {{ $key := .key }} 63 - {{ $val := .val }} 64 - {{ $def := .def }} 65 - {{ $id := .id }} 66 - {{ $isChecked := .isChecked }} 67 - <div class="grid grid-cols-[auto_1fr_50%] gap-2 items-center cursor-pointer"> 68 - <input type="checkbox" id="op-{{$id}}" name="op-{{$id}}" value="add" {{if $isChecked}}checked{{end}} class="peer"> 69 - <label for="op-{{$id}}" class="flex items-center gap-2 text-base">{{ template "labels/fragments/labelDef" $def }}</label> 70 - <div class="w-full hidden peer-checked:block">{{ template "valueTypeInput" (dict "valueType" $def.ValueType "value" $val "key" $key) }}</div> 71 - <input type="hidden" name="operand-key" value="{{ $key }}"> 72 - </div> 73 - {{ end }} 74 - 75 - {{ define "valueTypeInput" }} 76 - {{ $valueType := .valueType }} 77 - {{ $value := .value }} 78 - {{ $key := .key }} 79 - 80 - {{ if $valueType.IsEnumType }} 81 - {{ template "enumTypeInput" $ }} 82 - {{ else if $valueType.IsBool }} 83 - {{ template "boolTypeInput" $ }} 84 - {{ else if $valueType.IsInt }} 85 - {{ template "intTypeInput" $ }} 86 - {{ else if $valueType.IsString }} 87 - {{ template "stringTypeInput" $ }} 88 - {{ else if $valueType.IsNull }} 89 - {{ template "nullTypeInput" $ }} 90 - {{ end }} 91 - {{ end }} 92 - 93 - {{ define "enumTypeInput" }} 94 - {{ $valueType := .valueType }} 95 - {{ $value := .value }} 96 - <select name="operand-val" class="w-full p-1 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600"> 97 - {{ range $valueType.Enum }} 98 - <option value="{{.}}" {{ if eq $value . }} selected {{ end }}>{{.}}</option> 99 - {{ end }} 100 - </select> 101 - {{ end }} 102 - 103 - {{ define "boolTypeInput" }} 104 - {{ $value := .value }} 105 - <select name="operand-val" class="w-full p-1 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600"> 106 - <option value="true" {{ if $value }} selected {{ end }}>true</option> 107 - <option value="false" {{ if not $value }} selected {{ end }}>false</option> 108 - </select> 109 - {{ end }} 110 - 111 - {{ define "intTypeInput" }} 112 - {{ $value := .value }} 113 - <input class="p-1 w-full" type="number" name="operand-val" value="{{$value}}" max="100"> 114 - {{ end }} 115 - 116 - {{ define "stringTypeInput" }} 117 - {{ $valueType := .valueType }} 118 - {{ $value := .value }} 119 - {{ if $valueType.IsDidFormat }} 120 - {{ $value = resolve .value }} 121 - {{ end }} 122 - <input class="p-1 w-full" type="text" name="operand-val" value="{{$value}}"> 123 - {{ end }} 124 - 125 - {{ define "nullTypeInput" }} 126 - <input class="p-1" type="hidden" name="operand-val" value="null"> 127 - {{ end }}
+208
appview/pages/templates/repo/fragments/editLabelPanel.html
··· 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 + {{ $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" }} 24 + <div> 25 + {{ template "repo/fragments/labelSectionHeaderText" "Labels" }} 26 + 27 + <div class="flex gap-1 items-center flex-wrap"> 28 + {{ range $k, $d := $defs }} 29 + {{ $isChecked := $state.ContainsLabel $k }} 30 + {{ if $d.ValueType.IsNull }} 31 + {{ $fieldName := $d.AtUri }} 32 + <label class="{{$labelStyle}}"> 33 + <input type="checkbox" id="{{ $fieldName }}" name="{{ $fieldName }}" value="null" {{if $isChecked}}checked{{end}}> 34 + {{ template "labels/fragments/labelDef" $d }} 35 + </label> 36 + {{ end }} 37 + {{ else }} 38 + <p class="text-gray-500 dark:text-gray-400 text-sm py-1"> 39 + No labels defined yet. You can choose default labels or define custom 40 + labels in <a class="underline" href="/{{ $.RepoInfo.FullName }}/settings">settings</a>. 41 + </p> 42 + {{ end }} 43 + </div> 44 + </div> 45 + {{ end }} 46 + 47 + {{ define "editKvLabels" }} 48 + {{ $defs := .Defs }} 49 + {{ $subject := .Subject }} 50 + {{ $state := .State }} 51 + {{ $labelStyle := "font-normal normal-case flex items-center gap-2 p-0" }} 52 + 53 + {{ range $k, $d := $defs }} 54 + {{ if (not $d.ValueType.IsNull) }} 55 + {{ $fieldName := $d.AtUri }} 56 + {{ $valset := $state.GetValSet $k }} 57 + <div id="label-{{$d.Id}}" class="flex flex-col gap-1"> 58 + {{ template "repo/fragments/labelSectionHeaderText" $d.Name }} 59 + {{ if (and $d.Multiple $d.ValueType.IsEnum) }} 60 + <!-- checkbox --> 61 + {{ range $variant := $d.ValueType.Enum }} 62 + <label class="{{$labelStyle}}"> 63 + <input type="checkbox" name="{{ $fieldName }}" value="{{$variant}}" {{if $state.ContainsLabelAndVal $k $variant}}checked{{end}}> 64 + {{ $variant }} 65 + </label> 66 + {{ end }} 67 + {{ else if $d.Multiple }} 68 + <!-- dynamically growing input fields --> 69 + {{ range $v, $s := $valset }} 70 + {{ template "multipleInputField" (dict "def" $d "value" $v "key" $k) }} 71 + {{ else }} 72 + {{ template "multipleInputField" (dict "def" $d "value" "" "key" $k) }} 73 + {{ end }} 74 + {{ template "addFieldButton" $d }} 75 + {{ else if $d.ValueType.IsEnum }} 76 + <!-- radio buttons --> 77 + {{ $isUsed := $state.ContainsLabel $k }} 78 + {{ range $variant := $d.ValueType.Enum }} 79 + <label class="{{$labelStyle}}"> 80 + <input type="radio" name="{{ $fieldName }}" value="{{$variant}}" {{if $state.ContainsLabelAndVal $k $variant}}checked{{end}}> 81 + {{ $variant }} 82 + </label> 83 + {{ end }} 84 + <label class="{{$labelStyle}}"> 85 + <input type="radio" name="{{ $fieldName }}" value="" {{ if not $isUsed }}checked{{ end }}> 86 + None 87 + </label> 88 + {{ else }} 89 + <!-- single input field based on value type --> 90 + {{ range $v, $s := $valset }} 91 + {{ template "valueTypeInput" (dict "def" $d "value" $v "key" $k) }} 92 + {{ else }} 93 + {{ template "valueTypeInput" (dict "def" $d "value" "" "key" $k) }} 94 + {{ end }} 95 + {{ end }} 96 + </div> 97 + {{ end }} 98 + {{ end }} 99 + {{ end }} 100 + 101 + {{ define "multipleInputField" }} 102 + <div class="flex gap-1 items-stretch"> 103 + {{ template "valueTypeInput" . }} 104 + {{ template "removeFieldButton" }} 105 + </div> 106 + {{ end }} 107 + 108 + {{ define "addFieldButton" }} 109 + <div style="display:none" id="tpl-{{ .Id }}"> 110 + {{ template "multipleInputField" (dict "def" . "value" "" "key" .AtUri.String) }} 111 + </div> 112 + <button type="button" onClick="this.insertAdjacentHTML('beforebegin', document.getElementById('tpl-{{ .Id }}').innerHTML)" class="w-full btn flex items-center gap-2"> 113 + {{ i "plus" "size-4" }} add 114 + </button> 115 + {{ end }} 116 + 117 + {{ define "removeFieldButton" }} 118 + <button type="button" onClick="this.parentElement.remove()" class="btn flex items-center gap-2 text-red-400 dark:text-red-500"> 119 + {{ i "trash-2" "size-4" }} 120 + </button> 121 + {{ end }} 122 + 123 + {{ define "valueTypeInput" }} 124 + {{ $def := .def }} 125 + {{ $valueType := $def.ValueType }} 126 + {{ $value := .value }} 127 + {{ $key := .key }} 128 + 129 + {{ if $valueType.IsBool }} 130 + {{ template "boolTypeInput" $ }} 131 + {{ else if $valueType.IsInt }} 132 + {{ template "intTypeInput" $ }} 133 + {{ else if $valueType.IsString }} 134 + {{ template "stringTypeInput" $ }} 135 + {{ else if $valueType.IsNull }} 136 + {{ template "nullTypeInput" $ }} 137 + {{ end }} 138 + {{ end }} 139 + 140 + {{ define "boolTypeInput" }} 141 + {{ $def := .def }} 142 + {{ $fieldName := $def.AtUri }} 143 + {{ $value := .value }} 144 + {{ $labelStyle = "font-normal normal-case flex items-center gap-2" }} 145 + <div class="flex flex-col gap-1"> 146 + <label class="{{$labelStyle}}"> 147 + <input type="radio" name="{{ $fieldName }}" value="true" {{ if not $value }}checked{{ end }}> 148 + None 149 + </label> 150 + <label class="{{$labelStyle}}"> 151 + <input type="radio" name="{{ $fieldName }}" value="true" {{ if not $value }}checked{{ end }}> 152 + None 153 + </label> 154 + <label class="{{$labelStyle}}"> 155 + <input type="radio" name="{{ $fieldName }}" value="true" {{ if not $value }}checked{{ end }}> 156 + None 157 + </label> 158 + </div> 159 + {{ end }} 160 + 161 + {{ define "intTypeInput" }} 162 + {{ $def := .def }} 163 + {{ $fieldName := $def.AtUri }} 164 + {{ $value := .value }} 165 + <input class="p-1 w-full" type="number" name="{{$fieldName}}" value="{{$value}}"> 166 + {{ end }} 167 + 168 + {{ define "stringTypeInput" }} 169 + {{ $def := .def }} 170 + {{ $fieldName := $def.AtUri }} 171 + {{ $valueType := $def.ValueType }} 172 + {{ $value := .value }} 173 + {{ if $valueType.IsDidFormat }} 174 + {{ $value = trimPrefix (resolve .value) "@" }} 175 + {{ end }} 176 + <input class="p-1 w-full" type="text" name="{{$fieldName}}" value="{{$value}}"> 177 + {{ end }} 178 + 179 + {{ define "nullTypeInput" }} 180 + {{ $def := .def }} 181 + {{ $fieldName := $def.AtUri }} 182 + <input class="p-1" type="hidden" name="{{$fieldName}}" value="null"> 183 + {{ end }} 184 + 185 + {{ define "editLabelPanelActions" }} 186 + <div class="flex gap-2 pt-2"> 187 + <button 188 + id="cancel-btn" 189 + type="button" 190 + hx-get="/{{ .RepoInfo.FullName }}/label" 191 + hx-vals='{"subject": "{{.Subject}}"}' 192 + hx-swap="outerHTML" 193 + hx-target="#edit-label-panel" 194 + 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"> 195 + {{ i "x" "size-4" }} cancel 196 + </button> 197 + 198 + <button 199 + id="save-btn" 200 + type="submit" 201 + class="btn w-1/2 flex items-center"> 202 + <span class="inline-flex gap-2 items-center">{{ i "check" "size-4" }} save</span> 203 + <span id="spinner" class="group"> 204 + {{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 205 + </span> 206 + </button> 207 + </div> 208 + {{ end }}
+43
appview/pages/templates/repo/fragments/labelPanel.html
··· 1 + {{ define "repo/fragments/labelPanel" }} 2 + <div id="label-panel" class="flex flex-col gap-6"> 3 + {{ template "basicLabels" . }} 4 + {{ template "kvLabels" . }} 5 + </div> 6 + {{ end }} 7 + 8 + {{ define "basicLabels" }} 9 + <div> 10 + {{ template "repo/fragments/labelSectionHeader" (dict "Name" "Labels" "RepoInfo" .RepoInfo "Subject" .Subject) }} 11 + 12 + {{ $hasLabel := false }} 13 + <div class="flex gap-1 items-center flex-wrap"> 14 + {{ range $k, $d := .Defs }} 15 + {{ if (and $d.ValueType.IsNull ($.State.ContainsLabel $k)) }} 16 + {{ $hasLabel = true }} 17 + {{ template "labels/fragments/label" (dict "def" $d "val" "") }} 18 + {{ end }} 19 + {{ end }} 20 + 21 + {{ if not $hasLabel }} 22 + <p class="text-gray-500 dark:text-gray-400 text-sm py-1">None yet.</p> 23 + {{ end }} 24 + </div> 25 + </div> 26 + {{ end }} 27 + 28 + {{ define "kvLabels" }} 29 + {{ range $k, $d := .Defs }} 30 + {{ if (not $d.ValueType.IsNull) }} 31 + <div id="label-{{$d.Id}}"> 32 + {{ template "repo/fragments/labelSectionHeader" (dict "Name" $d.Name "RepoInfo" $.RepoInfo "Subject" $.Subject) }} 33 + <div class="flex gap-1 items-center flex-wrap"> 34 + {{ range $v, $s := $.State.GetValSet $d.AtUri.String }} 35 + {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" false) }} 36 + {{ else }} 37 + <p class="text-gray-500 dark:text-gray-400 text-sm py-1">None yet.</p> 38 + {{ end }} 39 + </div> 40 + </div> 41 + {{ end }} 42 + {{ end }} 43 + {{ end }}
+16
appview/pages/templates/repo/fragments/labelSectionHeader.html
··· 1 + {{ define "repo/fragments/labelSectionHeader" }} 2 + 3 + <div class="flex justify-between items-center gap-2"> 4 + {{ template "repo/fragments/labelSectionHeaderText" .Name }} 5 + {{ if (or .RepoInfo.Roles.IsOwner .RepoInfo.Roles.IsCollaborator) }} 6 + <a 7 + class="text-gray-500 dark:text-gray-400 flex gap-1 items-center group" 8 + hx-get="/{{ .RepoInfo.FullName }}/label/edit" 9 + hx-vals='{"subject": "{{.Subject}}"}' 10 + hx-swap="outerHTML" 11 + hx-target="#label-panel"> 12 + {{ i "pencil" "size-3" }} 13 + </a> 14 + {{ end }} 15 + </div> 16 + {{ end }}
+3
appview/pages/templates/repo/fragments/labelSectionHeaderText.html
··· 1 + {{ define "repo/fragments/labelSectionHeaderText" }} 2 + <span class="text-sm py-1 font-bold text-gray-500 dark:text-gray-400 capitalize">{{ . }}</span> 3 + {{ end }}
+135 -83
appview/pages/templates/repo/settings/fragments/addLabelDefModal.html
··· 1 1 {{ define "repo/settings/fragments/addLabelDefModal" }} 2 - <form 3 - hx-put="/{{ $.RepoInfo.FullName }}/settings/label" 4 - hx-indicator="#spinner" 5 - hx-swap="none" 6 - hx-on::after-request="if(event.detail.successful) this.reset()" 7 - class="flex flex-col gap-4" 8 - > 9 - <p class="text-gray-500 dark:text-gray-400">Labels can have a name and a value. Set the value type to "none" to create a simple label.</p> 2 + <div class="grid grid-cols-2"> 3 + <input type="radio" name="tab" id="basic-tab" value="basic" class="hidden peer/basic" checked> 4 + <input type="radio" name="tab" id="kv-tab" value="kv" class="hidden peer/kv"> 10 5 11 - <div class="w-full"> 12 - <label for="name">Name</label> 13 - <input class="w-full" type="text" id="label-name" name="name" required placeholder="improvement"/> 6 + <!-- Labels as direct siblings --> 7 + {{ $base := "py-2 text-sm font-normal normal-case block hover:no-underline text-center cursor-pointer bg-gray-100 dark:bg-gray-800 shadow-inner border border-gray-200 dark:border-gray-700" }} 8 + <label for="basic-tab" class="{{$base}} peer-checked/basic:bg-white peer-checked/basic:dark:bg-gray-700 peer-checked/basic:shadow-sm rounded-l"> 9 + Basic Labels 10 + </label> 11 + <label for="kv-tab" class="{{$base}} peer-checked/kv:bg-white peer-checked/kv:dark:bg-gray-700 peer-checked/kv:shadow-sm rounded-r"> 12 + Key-value Labels 13 + </label> 14 + 15 + <!-- Basic Labels Content - direct sibling --> 16 + <div class="mt-4 hidden peer-checked/basic:block col-span-full"> 17 + {{ template "basicLabelDef" . }} 18 + </div> 19 + 20 + <!-- Key-value Labels Content - direct sibling --> 21 + <div class="mt-4 hidden peer-checked/kv:block col-span-full"> 22 + {{ template "kvLabelDef" . }} 14 23 </div> 15 24 16 - <!-- Value Type --> 17 - <div class="w-full"> 18 - <label for="valueType">Value Type</label> 19 - <select id="value-type" name="valueType" class="w-full p-3 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600"> 20 - <option value="null" selected>None</option> 21 - <option value="string">String</option> 22 - <option value="integer">Integer</option> 23 - <option value="boolean">Boolean</option> 24 - </select> 25 - <details id="constrain-values" class="group hidden"> 26 - <summary class="list-none cursor-pointer flex items-center gap-2 py-2"> 27 - <span class="group-open:hidden inline text-gray-500 dark:text-gray-400">{{ i "square-plus" "w-4 h-4" }}</span> 28 - <span class="hidden group-open:inline text-gray-500 dark:text-gray-400">{{ i "square-minus" "w-4 h-4" }}</span> 29 - <span>Constrain values</span> 30 - </summary> 31 - <label for="enumValues">Permitted values</label> 32 - <input type="text" id="enumValues" name="enumValues" placeholder="value1, value2, value3" class="w-full"/> 33 - <p class="text-sm text-gray-400 dark:text-gray-500 mt-1">Enter comma-separated list of permitted values.</p> 25 + <div id="add-label-error" class="text-red-500 dark:text-red-400 col-span-full"></div> 26 + </div> 27 + {{ end }} 28 + 29 + {{ define "basicLabelDef" }} 30 + <form 31 + hx-put="/{{ $.RepoInfo.FullName }}/settings/label" 32 + hx-indicator="#spinner" 33 + hx-swap="none" 34 + hx-on::after-request="if(event.detail.successful) this.reset()" 35 + class="flex flex-col space-y-4"> 36 + 37 + <p class="text-gray-500 dark:text-gray-400">These labels can have a name and a color.</p> 38 + 39 + {{ template "nameInput" . }} 40 + {{ template "scopeInput" . }} 41 + {{ template "colorInput" . }} 42 + 43 + <div class="flex gap-2 pt-2"> 44 + {{ template "cancelButton" . }} 45 + {{ template "submitButton" . }} 46 + </div> 47 + </form> 48 + {{ end }} 34 49 35 - <label for="valueFormat">String format</label> 36 - <select id="valueFormat" name="valueFormat" class="w-full p-3 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600"> 37 - <option value="any" selected>Any</option> 38 - <option value="did">DID</option> 39 - </select> 40 - <p class="text-sm text-gray-400 dark:text-gray-500 mt-1">Choose a string format.</p> 41 - </details> 42 - </div> 50 + {{ define "kvLabelDef" }} 51 + <form 52 + hx-put="/{{ $.RepoInfo.FullName }}/settings/label" 53 + hx-indicator="#spinner" 54 + hx-swap="none" 55 + hx-on::after-request="if(event.detail.successful) this.reset()" 56 + class="flex flex-col space-y-4"> 43 57 44 - <!-- Scope --> 58 + <p class="text-gray-500 dark:text-gray-400"> 59 + These labels are more detailed, they can have a key and an associated 60 + value. You may define additional constraints on label values. 61 + </p> 62 + 63 + {{ template "nameInput" . }} 64 + {{ template "valueInput" . }} 65 + {{ template "multipleInput" . }} 66 + {{ template "scopeInput" . }} 67 + {{ template "colorInput" . }} 68 + 69 + <div class="flex gap-2 pt-2"> 70 + {{ template "cancelButton" . }} 71 + {{ template "submitButton" . }} 72 + </div> 73 + </form> 74 + {{ end }} 75 + 76 + {{ define "nameInput" }} 45 77 <div class="w-full"> 46 - <label for="scope">Scope</label> 47 - <select id="scope" name="scope" class="w-full p-3 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600"> 48 - <option value="sh.tangled.repo.issue">Issues</option> 49 - <option value="sh.tangled.repo.pull">Pull Requests</option> 50 - </select> 78 + <label for="name">Name</label> 79 + <input class="w-full" type="text" id="label-name" name="name" required placeholder="improvement"/> 51 80 </div> 81 + {{ end }} 52 82 53 - <!-- Color --> 83 + {{ define "colorInput" }} 54 84 <div class="w-full"> 55 85 <label for="color">Color</label> 56 86 <div class="grid grid-cols-4 grid-rows-2 place-items-center"> ··· 63 93 {{ end }} 64 94 </div> 65 95 </div> 96 + {{ end }} 66 97 67 - <!-- Multiple --> 68 - <div class="w-full flex flex-wrap gap-2"> 69 - <input type="checkbox" id="multiple" name="multiple" value="true" /> 70 - <span> 71 - Allow multiple values 72 - </span> 98 + {{ define "scopeInput" }} 99 + <div class="w-full"> 100 + <label>Scope</label> 101 + <label class="font-normal normal-case flex items-center gap-2 p-0"> 102 + <input type="checkbox" id="issue-scope" name="scope" value="sh.tangled.repo.issue" checked /> 103 + Issues 104 + </label> 105 + <label class="font-normal normal-case flex items-center gap-2 p-0"> 106 + <input type="checkbox" id="pulls-scope" name="scope" value="sh.tangled.repo.pull" checked /> 107 + Pull Requests 108 + </label> 109 + </div> 110 + {{ end }} 111 + 112 + {{ define "valueInput" }} 113 + <div class="w-full"> 114 + <label for="valueType">Value Type</label> 115 + <select id="value-type" name="valueType" class="w-full p-3 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600"> 116 + <option value="string">String</option> 117 + <option value="integer">Integer</option> 118 + </select> 119 + </div> 120 + 121 + <div class="w-full"> 122 + <label for="enumValues">Permitted values</label> 123 + <input type="text" id="enumValues" name="enumValues" placeholder="value1, value2, value3" class="w-full"/> 124 + <p class="text-sm text-gray-400 dark:text-gray-500 mt-1"> 125 + Enter comma-separated list of permitted values, or leave empty to allow any value. 126 + </p> 73 127 </div> 74 128 75 - <div class="flex gap-2 pt-2"> 76 - <button 77 - type="button" 78 - popovertarget="add-labeldef-modal" 79 - popovertargetaction="hide" 80 - 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" 81 - > 82 - {{ i "x" "size-4" }} cancel 83 - </button> 84 - <button type="submit" class="btn w-1/2 flex items-center"> 85 - <span class="inline-flex gap-2 items-center">{{ i "plus" "size-4" }} add</span> 86 - <span id="spinner" class="group"> 87 - {{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 88 - </span> 89 - </button> 129 + <div class="w-full"> 130 + <label for="valueFormat">String format</label> 131 + <select id="valueFormat" name="valueFormat" class="w-full p-3 rounded border border-gray-300 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-600"> 132 + <option value="any" selected>Any</option> 133 + <option value="did">DID</option> 134 + </select> 90 135 </div> 91 - <div id="add-label-error" class="text-red-500 dark:text-red-400"></div> 92 - </form> 136 + {{ end }} 93 137 94 - <script> 95 - document.getElementById('value-type').addEventListener('change', function() { 96 - const constrainValues = document.getElementById('constrain-values'); 97 - const selectedValue = this.value; 138 + {{ define "multipleInput" }} 139 + <div class="w-full flex flex-wrap gap-2"> 140 + <input type="checkbox" id="multiple" name="multiple" value="true" /> 141 + <span>Allow multiple values</span> 142 + </div> 143 + {{ end }} 98 144 99 - if (selectedValue === 'string') { 100 - constrainValues.classList.remove('hidden'); 101 - } else { 102 - constrainValues.classList.add('hidden'); 103 - constrainValues.removeAttribute('open'); 104 - document.getElementById('enumValues').value = ''; 105 - } 106 - }); 145 + {{ define "cancelButton" }} 146 + <button 147 + type="button" 148 + popovertarget="add-labeldef-modal" 149 + popovertargetaction="hide" 150 + 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" 151 + > 152 + {{ i "x" "size-4" }} cancel 153 + </button> 154 + {{ end }} 107 155 108 - function toggleDarkMode() { 109 - document.documentElement.classList.toggle('dark'); 110 - } 111 - </script> 156 + {{ define "submitButton" }} 157 + <button type="submit" class="btn-create w-1/2 flex items-center"> 158 + <span class="inline-flex gap-2 items-center">{{ i "plus" "size-4" }} add</span> 159 + <span id="spinner" class="group"> 160 + {{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 161 + </span> 162 + </button> 112 163 {{ end }} 113 164 165 +
+6
appview/repo/router.go
··· 64 64 r.Get("/*", rp.RepoCompare) 65 65 }) 66 66 67 + // label panel in issues/pulls/discussions/tasks 68 + r.Route("/label", func(r chi.Router) { 69 + r.Get("/", rp.LabelPanel) 70 + r.Get("/edit", rp.EditLabelPanel) 71 + }) 72 + 67 73 // settings routes, needs auth 68 74 r.Group(func(r chi.Router) { 69 75 r.Use(middleware.AuthMiddleware(rp.oauth))
+1
appview/reporesolver/resolver.go
··· 184 184 OwnerDid: f.OwnerDid(), 185 185 OwnerHandle: f.OwnerHandle(), 186 186 Name: f.Name, 187 + Rkey: f.Repo.Rkey, 187 188 RepoAt: repoAt, 188 189 Description: f.Description, 189 190 IsStarred: isStarred,