Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2 (Please be gentle).
0
fork

Configure Feed

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

appvie: switch to new label panel interface

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

+129 -97
+1 -1
appview/db/issues.go
··· 174 174 return i.ReplyTo == nil 175 175 } 176 176 177 - func IssueCommentFromRecord(e Execer, did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) { 177 + func IssueCommentFromRecord(did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) { 178 178 created, err := time.Parse(time.RFC3339, record.CreatedAt) 179 179 if err != nil { 180 180 created = time.Now()
+1
appview/db/repos.go
··· 53 53 CreatedAt: r.Created.Format(time.RFC3339), 54 54 Source: source, 55 55 Spindle: spindle, 56 + Labels: r.Labels, 56 57 } 57 58 } 58 59
+71 -48
appview/labels/labels.go
··· 53 53 func (l *Labels) Router(mw *middleware.Middleware) http.Handler { 54 54 r := chi.NewRouter() 55 55 56 - r.With(middleware.AuthMiddleware(l.oauth)).Put("/perform", l.PerformLabelOp) 56 + r.Use(middleware.AuthMiddleware(l.oauth)) 57 + r.Put("/perform", l.PerformLabelOp) 57 58 58 59 return r 59 60 } 60 61 62 + // this is a tricky handler implementation: 63 + // - the user selects the new state of all the labels in the label panel and hits save 64 + // - this handler should calculate the diff in order to create the labelop record 65 + // - we need the diff in order to maintain a "history" of operations performed by users 61 66 func (l *Labels) PerformLabelOp(w http.ResponseWriter, r *http.Request) { 62 67 user := l.oauth.GetUser(r) 63 68 69 + noticeId := "add-label-error" 70 + 71 + fail := func(msg string, err error) { 72 + l.logger.Error("failed to add label", "err", err) 73 + l.pages.Notice(w, noticeId, msg) 74 + } 75 + 64 76 if err := r.ParseForm(); err != nil { 65 - l.logger.Error("failed to parse form data", "error", err) 66 - http.Error(w, "Invalid form data", http.StatusBadRequest) 77 + fail("Invalid form.", err) 67 78 return 68 79 } 69 80 ··· 84 73 indexedAt := time.Now() 85 74 repoAt := r.Form.Get("repo") 86 75 subjectUri := r.Form.Get("subject") 87 - keys := r.Form["operand-key"] 88 - vals := r.Form["operand-val"] 89 - 90 - var labelOps []db.LabelOp 91 - for i := range len(keys) { 92 - op := r.FormValue(fmt.Sprintf("op-%d", i)) 93 - if op == "" { 94 - op = string(db.LabelOperationDel) 95 - } 96 - key := keys[i] 97 - val := vals[i] 98 - 99 - labelOps = append(labelOps, db.LabelOp{ 100 - Did: did, 101 - Rkey: rkey, 102 - Subject: syntax.ATURI(subjectUri), 103 - Operation: db.LabelOperation(op), 104 - OperandKey: key, 105 - OperandValue: val, 106 - PerformedAt: performedAt, 107 - IndexedAt: indexedAt, 108 - }) 109 - } 110 76 111 77 // find all the labels that this repo subscribes to 112 78 repoLabels, err := db.GetRepoLabels(l.db, db.FilterEq("repo_at", repoAt)) 113 79 if err != nil { 114 - http.Error(w, "Invalid form data", http.StatusBadRequest) 80 + fail("Failed to get labels for this repository.", err) 115 81 return 116 82 } 117 83 ··· 99 111 100 112 actx, err := db.NewLabelApplicationCtx(l.db, db.FilterIn("at_uri", labelAts)) 101 113 if err != nil { 102 - http.Error(w, "Invalid form data", http.StatusBadRequest) 114 + fail("Invalid form data.", err) 103 115 return 104 116 } 105 117 106 - for i := range labelOps { 107 - def := actx.Defs[labelOps[i].OperandKey] 108 - if err := l.validator.ValidateLabelOp(def, &labelOps[i]); err != nil { 109 - l.logger.Error("form failed to validate", "err", err) 110 - http.Error(w, "Invalid form data", http.StatusBadRequest) 111 - return 112 - } 113 - 114 - l.logger.Info("value changed to: ", "v", labelOps[i].OperandValue) 115 - } 118 + l.logger.Info("actx", "labels", labelAts) 119 + l.logger.Info("actx", "defs", actx.Defs) 116 120 117 121 // calculate the start state by applying already known labels 118 122 existingOps, err := db.GetLabelOps(l.db, db.FilterEq("subject", subjectUri)) 119 123 if err != nil { 120 - http.Error(w, "Invalid form data", http.StatusBadRequest) 124 + fail("Invalid form data.", err) 121 125 return 122 126 } 123 127 124 128 labelState := db.NewLabelState() 125 129 actx.ApplyLabelOps(labelState, existingOps) 126 130 127 - l.logger.Info("state", "state", labelState) 131 + var labelOps []db.LabelOp 132 + 133 + // first delete all existing state 134 + for key, vals := range labelState.Inner() { 135 + for val := range vals { 136 + labelOps = append(labelOps, db.LabelOp{ 137 + Did: did, 138 + Rkey: rkey, 139 + Subject: syntax.ATURI(subjectUri), 140 + Operation: db.LabelOperationDel, 141 + OperandKey: key, 142 + OperandValue: val, 143 + PerformedAt: performedAt, 144 + IndexedAt: indexedAt, 145 + }) 146 + } 147 + } 148 + 149 + // add all the new state the user specified 150 + for key, vals := range r.Form { 151 + if _, ok := actx.Defs[key]; !ok { 152 + continue 153 + } 154 + 155 + for _, val := range vals { 156 + labelOps = append(labelOps, db.LabelOp{ 157 + Did: did, 158 + Rkey: rkey, 159 + Subject: syntax.ATURI(subjectUri), 160 + Operation: db.LabelOperationAdd, 161 + OperandKey: key, 162 + OperandValue: val, 163 + PerformedAt: performedAt, 164 + IndexedAt: indexedAt, 165 + }) 166 + } 167 + } 168 + 169 + // reduce the opset 170 + labelOps = db.ReduceLabelOps(labelOps) 171 + 172 + for i := range labelOps { 173 + def := actx.Defs[labelOps[i].OperandKey] 174 + if err := l.validator.ValidateLabelOp(def, &labelOps[i]); err != nil { 175 + fail(fmt.Sprintf("Invalid form data: %s", err), err) 176 + return 177 + } 178 + } 128 179 129 180 // next, apply all ops introduced in this request and filter out ones that are no-ops 130 181 validLabelOps := labelOps[:0] ··· 184 157 185 158 client, err := l.oauth.AuthorizedClient(r) 186 159 if err != nil { 187 - l.logger.Error("failed to create client", "error", err) 188 - http.Error(w, "Invalid form data", http.StatusBadRequest) 160 + fail("Failed to authorize user.", err) 189 161 return 190 162 } 191 163 ··· 197 171 }, 198 172 }) 199 173 if err != nil { 200 - l.logger.Error("failed to write to PDS", "error", err) 201 - http.Error(w, "failed to write to PDS", http.StatusInternalServerError) 174 + fail("Failed to create record on PDS for user.", err) 202 175 return 203 176 } 204 177 atUri := resp.Uri 205 178 206 179 tx, err := l.db.BeginTx(r.Context(), nil) 207 180 if err != nil { 208 - l.logger.Error("failed to start tx", "error", err) 181 + fail("Failed to update labels. Try again later.", err) 209 182 return 210 183 } 211 184 ··· 225 200 226 201 for _, o := range validLabelOps { 227 202 if _, err := db.AddLabelOp(l.db, &o); err != nil { 228 - l.logger.Error("failed to add op", "err", err) 203 + fail("Failed to update labels. Try again later.", err) 229 204 return 230 205 } 231 - 232 - l.logger.Info("performed label op", "did", o.Did, "rkey", o.Rkey, "kind", o.Operation, "subjcet", o.Subject, "key", o.OperandKey) 233 206 } 234 207 235 208 err = tx.Commit()
+11
appview/pages/funcmap.go
··· 29 29 "split": func(s string) []string { 30 30 return strings.Split(s, "\n") 31 31 }, 32 + "trimPrefix": func(s, prefix string) string { 33 + return strings.TrimPrefix(s, prefix) 34 + }, 32 35 "join": func(elems []string, sep string) string { 33 36 return strings.Join(elems, sep) 34 37 }, 35 38 "contains": func(s string, target string) bool { 36 39 return strings.Contains(s, target) 40 + }, 41 + "mapContains": func(m any, key any) bool { 42 + mapValue := reflect.ValueOf(m) 43 + if mapValue.Kind() != reflect.Map { 44 + return false 45 + } 46 + keyValue := reflect.ValueOf(key) 47 + return mapValue.MapIndex(keyValue).IsValid() 37 48 }, 38 49 "resolve": func(s string) string { 39 50 identity, err := p.resolver.ResolveIdent(context.Background(), s)
+33 -7
appview/pages/pages.go
··· 838 838 } 839 839 840 840 type RepoGeneralSettingsParams struct { 841 - LoggedInUser *oauth.User 842 - RepoInfo repoinfo.RepoInfo 843 - Labels []db.LabelDefinition 844 - Active string 845 - Tabs []map[string]any 846 - Tab string 847 - Branches []types.Branch 841 + LoggedInUser *oauth.User 842 + RepoInfo repoinfo.RepoInfo 843 + Labels []db.LabelDefinition 844 + DefaultLabels []db.LabelDefinition 845 + SubscribedLabels map[string]struct{} 846 + Active string 847 + Tabs []map[string]any 848 + Tab string 849 + Branches []types.Branch 848 850 } 849 851 850 852 func (p *Pages) RepoGeneralSettings(w io.Writer, params RepoGeneralSettingsParams) error { ··· 1230 1228 1231 1229 func (p *Pages) RepoCompareDiff(w io.Writer, params RepoCompareDiffParams) error { 1232 1230 return p.executePlain("repo/fragments/diff", w, []any{params.RepoInfo.FullName, &params.Diff}) 1231 + } 1232 + 1233 + type LabelPanelParams struct { 1234 + LoggedInUser *oauth.User 1235 + RepoInfo repoinfo.RepoInfo 1236 + Defs map[string]*db.LabelDefinition 1237 + Subject string 1238 + State db.LabelState 1239 + } 1240 + 1241 + func (p *Pages) LabelPanel(w io.Writer, params LabelPanelParams) error { 1242 + return p.executePlain("repo/fragments/labelPanel", w, params) 1243 + } 1244 + 1245 + type EditLabelPanelParams struct { 1246 + LoggedInUser *oauth.User 1247 + RepoInfo repoinfo.RepoInfo 1248 + Defs map[string]*db.LabelDefinition 1249 + Subject string 1250 + State db.LabelState 1251 + } 1252 + 1253 + func (p *Pages) EditLabelPanel(w io.Writer, params EditLabelPanelParams) error { 1254 + return p.executePlain("repo/fragments/editLabelPanel", w, params) 1233 1255 } 1234 1256 1235 1257 type PipelinesParams struct {
+1
appview/pages/repoinfo/repoinfo.go
··· 52 52 53 53 type RepoInfo struct { 54 54 Name string 55 + Rkey string 55 56 OwnerDid string 56 57 OwnerHandle string 57 58 Description string
+7 -34
appview/pages/templates/repo/issues/issue.html
··· 17 17 {{ block "repoAfter" . }}{{ end }} 18 18 </div> 19 19 <div class="col-span-1 md:col-span-2 flex flex-col gap-6"> 20 - {{ template "issueLabels" . }} 20 + {{ template "repo/fragments/labelPanel" 21 + (dict "RepoInfo" $.RepoInfo 22 + "Defs" $.LabelDefs 23 + "Subject" $.Issue.AtUri 24 + "State" $.Issue.Labels) }} 21 25 {{ template "issueParticipants" . }} 22 26 </div> 23 27 </div> ··· 122 118 </div> 123 119 {{ end }} 124 120 125 - {{ define "issueLabels" }} 126 - <div> 127 - <div class="text-sm py-1 flex items-center gap-2 font-bold text-gray-500 dark:text-gray-400 capitalize"> 128 - Labels 129 - <button 130 - class="inline-flex text-gray-500 dark:text-gray-400 {{ if not (or .RepoInfo.Roles.IsOwner .RepoInfo.Roles.IsCollaborator) }}hidden{{ end }}" 131 - popovertarget="add-label-modal" 132 - popovertargetaction="toggle"> 133 - {{ i "plus" "size-4" }} 134 - </button> 135 - </div> 136 - <div class="flex gap-1 items-center flex-wrap"> 137 - {{ range $k, $valset := $.Issue.Labels.Inner }} 138 - {{ $d := index $.LabelDefs $k }} 139 - {{ range $v, $s := $valset }} 140 - {{ template "labels/fragments/label" (dict "def" $d "val" $v) }} 141 - {{ end }} 142 - {{ else }} 143 - <p class="text-gray-500 dark:text-gray-400 ">No labels yet.</p> 144 - {{ end }} 145 - 146 - <div 147 - id="add-label-modal" 148 - popover 149 - class="bg-white w-full sm:w-[30rem] dark:bg-gray-800 p-6 rounded border border-gray-200 dark:border-gray-700 drop-shadow dark:text-white backdrop:bg-gray-400/50 dark:backdrop:bg-gray-800/50"> 150 - {{ template "repo/fragments/addLabelModal" (dict "root" $ "subject" $.Issue.AtUri.String "state" $.Issue.Labels) }} 151 - </div> 152 - </div> 153 - </div> 154 - {{ end }} 155 - 156 121 {{ define "issueParticipants" }} 157 122 {{ $all := .Issue.Participants }} 158 123 {{ $ps := take $all 5 }} ··· 130 157 <span class="font-bold text-gray-500 dark:text-gray-400 capitalize">Participants</span> 131 158 <span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 ml-1">{{ len $all }}</span> 132 159 </div> 133 - <div class="flex items-center -space-x-2 mt-2"> 160 + <div class="flex items-center -space-x-3 mt-2"> 134 161 {{ $c := "z-50 z-40 z-30 z-20 z-10" }} 135 162 {{ range $i, $p := $ps }} 136 163 <img 137 164 src="{{ tinyAvatar . }}" 138 165 alt="" 139 - class="rounded-full h-8 w-8 mr-1 border-2 border-gray-300 dark:border-gray-700 z-{{sub 5 $i}}0" 166 + class="rounded-full h-8 w-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0" 140 167 /> 141 168 {{ end }} 142 169
+4 -7
appview/pages/templates/repo/issues/issues.html
··· 80 80 <a href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a> 81 81 </span> 82 82 83 - {{ if .Labels.Inner }} 84 - <span class="before:content-['·']"></span> 85 - {{ range $k, $valset := .Labels.Inner }} 86 - {{ $d := index $.LabelDefs $k }} 87 - {{ range $v, $s := $valset }} 88 - {{ template "labels/fragments/label" (dict "def" $d "val" $v) }} 89 - {{ end }} 83 + {{ $state := .Labels }} 84 + {{ range $k, $d := $.LabelDefs }} 85 + {{ range $v, $s := $state.GetValSet $d.AtUri.String }} 86 + {{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }} 90 87 {{ end }} 91 88 {{ end }} 92 89 </div>