forked from tangled.org/core
this repo has no description

appvie: switch to new label panel interface

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

oppi.li ac6cb315 d0cc53d6

verified
Changed files
+129 -97
appview
db
labels
pages
repoinfo
templates
repo
+1 -1
appview/db/issues.go
···
return i.ReplyTo == nil
}
-
func IssueCommentFromRecord(e Execer, did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) {
+
func IssueCommentFromRecord(did, rkey string, record tangled.RepoIssueComment) (*IssueComment, error) {
created, err := time.Parse(time.RFC3339, record.CreatedAt)
if err != nil {
created = time.Now()
+1
appview/db/repos.go
···
CreatedAt: r.Created.Format(time.RFC3339),
Source: source,
Spindle: spindle,
+
Labels: r.Labels,
}
}
+71 -48
appview/labels/labels.go
···
func (l *Labels) Router(mw *middleware.Middleware) http.Handler {
r := chi.NewRouter()
-
r.With(middleware.AuthMiddleware(l.oauth)).Put("/perform", l.PerformLabelOp)
+
r.Use(middleware.AuthMiddleware(l.oauth))
+
r.Put("/perform", l.PerformLabelOp)
return r
}
+
// this is a tricky handler implementation:
+
// - the user selects the new state of all the labels in the label panel and hits save
+
// - this handler should calculate the diff in order to create the labelop record
+
// - we need the diff in order to maintain a "history" of operations performed by users
func (l *Labels) PerformLabelOp(w http.ResponseWriter, r *http.Request) {
user := l.oauth.GetUser(r)
+
noticeId := "add-label-error"
+
+
fail := func(msg string, err error) {
+
l.logger.Error("failed to add label", "err", err)
+
l.pages.Notice(w, noticeId, msg)
+
}
+
if err := r.ParseForm(); err != nil {
-
l.logger.Error("failed to parse form data", "error", err)
-
http.Error(w, "Invalid form data", http.StatusBadRequest)
+
fail("Invalid form.", err)
return
}
···
indexedAt := time.Now()
repoAt := r.Form.Get("repo")
subjectUri := r.Form.Get("subject")
-
keys := r.Form["operand-key"]
-
vals := r.Form["operand-val"]
-
-
var labelOps []db.LabelOp
-
for i := range len(keys) {
-
op := r.FormValue(fmt.Sprintf("op-%d", i))
-
if op == "" {
-
op = string(db.LabelOperationDel)
-
}
-
key := keys[i]
-
val := vals[i]
-
-
labelOps = append(labelOps, db.LabelOp{
-
Did: did,
-
Rkey: rkey,
-
Subject: syntax.ATURI(subjectUri),
-
Operation: db.LabelOperation(op),
-
OperandKey: key,
-
OperandValue: val,
-
PerformedAt: performedAt,
-
IndexedAt: indexedAt,
-
})
-
}
// find all the labels that this repo subscribes to
repoLabels, err := db.GetRepoLabels(l.db, db.FilterEq("repo_at", repoAt))
if err != nil {
-
http.Error(w, "Invalid form data", http.StatusBadRequest)
+
fail("Failed to get labels for this repository.", err)
return
}
···
actx, err := db.NewLabelApplicationCtx(l.db, db.FilterIn("at_uri", labelAts))
if err != nil {
-
http.Error(w, "Invalid form data", http.StatusBadRequest)
+
fail("Invalid form data.", err)
return
}
-
for i := range labelOps {
-
def := actx.Defs[labelOps[i].OperandKey]
-
if err := l.validator.ValidateLabelOp(def, &labelOps[i]); err != nil {
-
l.logger.Error("form failed to validate", "err", err)
-
http.Error(w, "Invalid form data", http.StatusBadRequest)
-
return
-
}
-
-
l.logger.Info("value changed to: ", "v", labelOps[i].OperandValue)
-
}
+
l.logger.Info("actx", "labels", labelAts)
+
l.logger.Info("actx", "defs", actx.Defs)
// calculate the start state by applying already known labels
existingOps, err := db.GetLabelOps(l.db, db.FilterEq("subject", subjectUri))
if err != nil {
-
http.Error(w, "Invalid form data", http.StatusBadRequest)
+
fail("Invalid form data.", err)
return
}
labelState := db.NewLabelState()
actx.ApplyLabelOps(labelState, existingOps)
-
l.logger.Info("state", "state", labelState)
+
var labelOps []db.LabelOp
+
+
// first delete all existing state
+
for key, vals := range labelState.Inner() {
+
for val := range vals {
+
labelOps = append(labelOps, db.LabelOp{
+
Did: did,
+
Rkey: rkey,
+
Subject: syntax.ATURI(subjectUri),
+
Operation: db.LabelOperationDel,
+
OperandKey: key,
+
OperandValue: val,
+
PerformedAt: performedAt,
+
IndexedAt: indexedAt,
+
})
+
}
+
}
+
+
// add all the new state the user specified
+
for key, vals := range r.Form {
+
if _, ok := actx.Defs[key]; !ok {
+
continue
+
}
+
+
for _, val := range vals {
+
labelOps = append(labelOps, db.LabelOp{
+
Did: did,
+
Rkey: rkey,
+
Subject: syntax.ATURI(subjectUri),
+
Operation: db.LabelOperationAdd,
+
OperandKey: key,
+
OperandValue: val,
+
PerformedAt: performedAt,
+
IndexedAt: indexedAt,
+
})
+
}
+
}
+
+
// reduce the opset
+
labelOps = db.ReduceLabelOps(labelOps)
+
+
for i := range labelOps {
+
def := actx.Defs[labelOps[i].OperandKey]
+
if err := l.validator.ValidateLabelOp(def, &labelOps[i]); err != nil {
+
fail(fmt.Sprintf("Invalid form data: %s", err), err)
+
return
+
}
+
}
// next, apply all ops introduced in this request and filter out ones that are no-ops
validLabelOps := labelOps[:0]
···
client, err := l.oauth.AuthorizedClient(r)
if err != nil {
-
l.logger.Error("failed to create client", "error", err)
-
http.Error(w, "Invalid form data", http.StatusBadRequest)
+
fail("Failed to authorize user.", err)
return
}
···
},
})
if err != nil {
-
l.logger.Error("failed to write to PDS", "error", err)
-
http.Error(w, "failed to write to PDS", http.StatusInternalServerError)
+
fail("Failed to create record on PDS for user.", err)
return
}
atUri := resp.Uri
tx, err := l.db.BeginTx(r.Context(), nil)
if err != nil {
-
l.logger.Error("failed to start tx", "error", err)
+
fail("Failed to update labels. Try again later.", err)
return
}
···
for _, o := range validLabelOps {
if _, err := db.AddLabelOp(l.db, &o); err != nil {
-
l.logger.Error("failed to add op", "err", err)
+
fail("Failed to update labels. Try again later.", err)
return
}
-
-
l.logger.Info("performed label op", "did", o.Did, "rkey", o.Rkey, "kind", o.Operation, "subjcet", o.Subject, "key", o.OperandKey)
}
err = tx.Commit()
+11
appview/pages/funcmap.go
···
"split": func(s string) []string {
return strings.Split(s, "\n")
},
+
"trimPrefix": func(s, prefix string) string {
+
return strings.TrimPrefix(s, prefix)
+
},
"join": func(elems []string, sep string) string {
return strings.Join(elems, sep)
},
"contains": func(s string, target string) bool {
return strings.Contains(s, target)
+
},
+
"mapContains": func(m any, key any) bool {
+
mapValue := reflect.ValueOf(m)
+
if mapValue.Kind() != reflect.Map {
+
return false
+
}
+
keyValue := reflect.ValueOf(key)
+
return mapValue.MapIndex(keyValue).IsValid()
},
"resolve": func(s string) string {
identity, err := p.resolver.ResolveIdent(context.Background(), s)
+33 -7
appview/pages/pages.go
···
}
type RepoGeneralSettingsParams struct {
-
LoggedInUser *oauth.User
-
RepoInfo repoinfo.RepoInfo
-
Labels []db.LabelDefinition
-
Active string
-
Tabs []map[string]any
-
Tab string
-
Branches []types.Branch
+
LoggedInUser *oauth.User
+
RepoInfo repoinfo.RepoInfo
+
Labels []db.LabelDefinition
+
DefaultLabels []db.LabelDefinition
+
SubscribedLabels map[string]struct{}
+
Active string
+
Tabs []map[string]any
+
Tab string
+
Branches []types.Branch
}
func (p *Pages) RepoGeneralSettings(w io.Writer, params RepoGeneralSettingsParams) error {
···
func (p *Pages) RepoCompareDiff(w io.Writer, params RepoCompareDiffParams) error {
return p.executePlain("repo/fragments/diff", w, []any{params.RepoInfo.FullName, &params.Diff})
+
}
+
+
type LabelPanelParams struct {
+
LoggedInUser *oauth.User
+
RepoInfo repoinfo.RepoInfo
+
Defs map[string]*db.LabelDefinition
+
Subject string
+
State db.LabelState
+
}
+
+
func (p *Pages) LabelPanel(w io.Writer, params LabelPanelParams) error {
+
return p.executePlain("repo/fragments/labelPanel", w, params)
+
}
+
+
type EditLabelPanelParams struct {
+
LoggedInUser *oauth.User
+
RepoInfo repoinfo.RepoInfo
+
Defs map[string]*db.LabelDefinition
+
Subject string
+
State db.LabelState
+
}
+
+
func (p *Pages) EditLabelPanel(w io.Writer, params EditLabelPanelParams) error {
+
return p.executePlain("repo/fragments/editLabelPanel", w, params)
type PipelinesParams struct {
+1
appview/pages/repoinfo/repoinfo.go
···
type RepoInfo struct {
Name string
+
Rkey string
OwnerDid string
OwnerHandle string
Description string
+7 -34
appview/pages/templates/repo/issues/issue.html
···
{{ block "repoAfter" . }}{{ end }}
</div>
<div class="col-span-1 md:col-span-2 flex flex-col gap-6">
-
{{ template "issueLabels" . }}
+
{{ template "repo/fragments/labelPanel"
+
(dict "RepoInfo" $.RepoInfo
+
"Defs" $.LabelDefs
+
"Subject" $.Issue.AtUri
+
"State" $.Issue.Labels) }}
{{ template "issueParticipants" . }}
</div>
</div>
···
</div>
{{ end }}
-
{{ define "issueLabels" }}
-
<div>
-
<div class="text-sm py-1 flex items-center gap-2 font-bold text-gray-500 dark:text-gray-400 capitalize">
-
Labels
-
<button
-
class="inline-flex text-gray-500 dark:text-gray-400 {{ if not (or .RepoInfo.Roles.IsOwner .RepoInfo.Roles.IsCollaborator) }}hidden{{ end }}"
-
popovertarget="add-label-modal"
-
popovertargetaction="toggle">
-
{{ i "plus" "size-4" }}
-
</button>
-
</div>
-
<div class="flex gap-1 items-center flex-wrap">
-
{{ range $k, $valset := $.Issue.Labels.Inner }}
-
{{ $d := index $.LabelDefs $k }}
-
{{ range $v, $s := $valset }}
-
{{ template "labels/fragments/label" (dict "def" $d "val" $v) }}
-
{{ end }}
-
{{ else }}
-
<p class="text-gray-500 dark:text-gray-400 ">No labels yet.</p>
-
{{ end }}
-
-
<div
-
id="add-label-modal"
-
popover
-
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">
-
{{ template "repo/fragments/addLabelModal" (dict "root" $ "subject" $.Issue.AtUri.String "state" $.Issue.Labels) }}
-
</div>
-
</div>
-
</div>
-
{{ end }}
-
{{ define "issueParticipants" }}
{{ $all := .Issue.Participants }}
{{ $ps := take $all 5 }}
···
<span class="font-bold text-gray-500 dark:text-gray-400 capitalize">Participants</span>
<span class="bg-gray-200 dark:bg-gray-700 rounded py-1/2 px-1 ml-1">{{ len $all }}</span>
</div>
-
<div class="flex items-center -space-x-2 mt-2">
+
<div class="flex items-center -space-x-3 mt-2">
{{ $c := "z-50 z-40 z-30 z-20 z-10" }}
{{ range $i, $p := $ps }}
<img
src="{{ tinyAvatar . }}"
alt=""
-
class="rounded-full h-8 w-8 mr-1 border-2 border-gray-300 dark:border-gray-700 z-{{sub 5 $i}}0"
+
class="rounded-full h-8 w-8 mr-1 border-2 border-gray-100 dark:border-gray-900 z-{{sub 5 $i}}0"
/>
{{ end }}
+4 -7
appview/pages/templates/repo/issues/issues.html
···
<a href="/{{ $.RepoInfo.FullName }}/issues/{{ .IssueId }}" class="text-gray-500 dark:text-gray-400">{{ len .Comments }} comment{{$s}}</a>
</span>
-
{{ if .Labels.Inner }}
-
<span class="before:content-['·']"></span>
-
{{ range $k, $valset := .Labels.Inner }}
-
{{ $d := index $.LabelDefs $k }}
-
{{ range $v, $s := $valset }}
-
{{ template "labels/fragments/label" (dict "def" $d "val" $v) }}
-
{{ end }}
+
{{ $state := .Labels }}
+
{{ range $k, $d := $.LabelDefs }}
+
{{ range $v, $s := $state.GetValSet $d.AtUri.String }}
+
{{ template "labels/fragments/label" (dict "def" $d "val" $v "withPrefix" true) }}
{{ end }}
{{ end }}
</div>