forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

appview/issues: display labels on issues

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

oppi.li 427a47e2 debd92e8

verified
Changed files
+179 -9
appview
db
issues
pages
templates
repo
fragments
issues
+13 -1
appview/db/issues.go
···
// optionally, populate this when querying for reverse mappings
// like comment counts, parent repo etc.
Comments []IssueComment
+
Labels LabelState
Repo *Repo
}
···
// collect comments
issueAts := slices.Collect(maps.Keys(issueMap))
+
comments, err := GetIssueComments(e, FilterIn("issue_at", issueAts))
if err != nil {
return nil, fmt.Errorf("failed to query comments: %w", err)
}
-
for i := range comments {
issueAt := comments[i].IssueAt
if issue, ok := issueMap[issueAt]; ok {
issue.Comments = append(issue.Comments, comments[i])
+
}
+
}
+
+
// collect allLabels for each issue
+
allLabels, err := GetLabels(e, FilterIn("subject", issueAts))
+
if err != nil {
+
return nil, fmt.Errorf("failed to query labels: %w", err)
+
}
+
for issueAt, labels := range allLabels {
+
if issue, ok := issueMap[issueAt.String()]; ok {
+
issue.Labels = labels
}
}
+13
appview/issues/issues.go
···
userReactions = db.GetReactionStatusMap(rp.db, user.Did, issue.AtUri())
}
+
labelDefs, err := db.GetLabelDefinitions(rp.db, db.FilterIn("at_uri", f.Repo.Labels))
+
if err != nil {
+
log.Println("failed to fetch labels", err)
+
rp.pages.Error503(w)
+
return
+
}
+
+
defs := make(map[string]*db.LabelDefinition)
+
for _, l := range labelDefs {
+
defs[l.AtUri().String()] = &l
+
}
+
rp.pages.RepoSingleIssue(w, pages.RepoSingleIssueParams{
LoggedInUser: user,
RepoInfo: f.RepoInfo(user),
···
OrderedReactionKinds: db.OrderedReactionKinds,
Reactions: reactionCountMap,
UserReacted: userReactions,
+
LabelDefs: defs,
})
}
+6 -6
appview/pages/pages.go
···
}
type RepoSingleIssueParams struct {
-
LoggedInUser *oauth.User
-
RepoInfo repoinfo.RepoInfo
-
Active string
-
Issue *db.Issue
-
CommentList []db.CommentListItem
-
IssueOwnerHandle string
+
LoggedInUser *oauth.User
+
RepoInfo repoinfo.RepoInfo
+
Active string
+
Issue *db.Issue
+
CommentList []db.CommentListItem
+
LabelDefs map[string]*db.LabelDefinition
OrderedReactionKinds []db.ReactionKind
Reactions map[db.ReactionKind]int
+119
appview/pages/templates/repo/fragments/addLabelModal.html
···
+
{{ define "repo/fragments/addLabelModal" }}
+
{{ $root := .root }}
+
{{ $subject := .subject }}
+
{{ $state := .state }}
+
{{ with $root }}
+
<form
+
hx-put="/{{ .RepoInfo.FullName }}/labels/perform"
+
hx-on::after-request="if(event.detail.successful) this.reset()"
+
hx-indicator="#spinner"
+
hx-swap="none"
+
class="flex flex-col gap-4"
+
>
+
<p class="text-gray-500 dark:text-gray-400">Add, remove or update labels.</p>
+
+
<input class="hidden" name="repo" value="{{ .RepoInfo.RepoAt.String }}">
+
<input class="hidden" name="subject" value="{{ $subject }}">
+
+
<div class="flex flex-col gap-2 max-h-64 overflow-y-auto">
+
{{ $id := 0 }}
+
{{ range $k, $valset := $state.Inner }}
+
{{ $d := index $root.LabelDefs $k }}
+
{{ range $v, $s := $valset }}
+
<div class="grid grid-cols-2 cursor-pointer rounded">
+
<label class="w-full flex items-center gap-2">
+
<input type="checkbox" name="op-{{$id}}" value="add" checked>
+
{{ template "labels/fragments/labelDef" $d }}
+
</label>
+
{{ template "valueTypeInput" (dict "valueType" $d.ValueType "value" $v "key" $k) }}
+
<input type="hidden" name="operand-key" value="{{ $k }}">
+
{{ $id = add $id 1 }}
+
</div>
+
{{ end }}
+
{{ end }}
+
+
{{ range $k, $d := $root.LabelDefs }}
+
{{ if not ($state.ContainsLabel $k) }}
+
<div class="grid grid-cols-2 cursor-pointer rounded">
+
<label class="w-full flex items-center gap-2">
+
<input type="checkbox" name="op-{{$id}}" value="add">
+
{{ template "labels/fragments/labelDef" $d }}
+
</label>
+
{{ template "valueTypeInput" (dict "valueType" $d.ValueType "value" "" "key" $k) }}
+
<input type="hidden" name="operand-key" value="{{ $k }}">
+
{{ $id = add $id 1 }}
+
</div>
+
{{ end }}
+
{{ end }}
+
</div>
+
+
<div class="flex gap-2 pt-2">
+
<button
+
type="button"
+
popovertarget="add-label-modal"
+
popovertargetaction="hide"
+
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"
+
>
+
{{ i "x" "size-4" }} cancel
+
</button>
+
<button type="submit" class="btn w-1/2 flex items-center">
+
<span class="inline-flex gap-2 items-center">{{ i "check" "size-4" }} save</span>
+
<span id="spinner" class="group">
+
{{ i "loader-circle" "ml-2 w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
+
</span>
+
</button>
+
</div>
+
<div id="add-label-error" class="text-red-500 dark:text-red-400"></div>
+
</form>
+
{{ end }}
+
{{ end }}
+
+
{{ define "valueTypeInput" }}
+
{{ $valueType := .valueType }}
+
{{ $value := .value }}
+
{{ $key := .key }}
+
+
{{ if $valueType.IsEnumType }}
+
{{ template "enumTypeInput" $ }}
+
{{ else if $valueType.IsBool }}
+
{{ template "boolTypeInput" $ }}
+
{{ else if $valueType.IsInt }}
+
{{ template "intTypeInput" $ }}
+
{{ else if $valueType.IsString }}
+
{{ template "stringTypeInput" $ }}
+
{{ else if $valueType.IsNull }}
+
{{ template "nullTypeInput" $ }}
+
{{ end }}
+
{{ end }}
+
+
{{ define "enumTypeInput" }}
+
{{ $valueType := .valueType }}
+
{{ $value := .value }}
+
<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">
+
{{ range $valueType.Enum }}
+
<option value="{{.}}" {{ if eq $value . }} selected {{ end }}>{{.}}</option>
+
{{ end }}
+
</select>
+
{{ end }}
+
+
{{ define "boolTypeInput" }}
+
{{ $value := .value }}
+
<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">
+
<option value="true" {{ if $value }} selected {{ end }}>true</option>
+
<option value="false" {{ if not $value }} selected {{ end }}>false</option>
+
</select>
+
{{ end }}
+
+
{{ define "intTypeInput" }}
+
{{ $value := .value }}
+
<input class="p-1 w-full" type="number" name="operand-val" value="{{$value}}" max="100">
+
{{ end }}
+
+
{{ define "stringTypeInput" }}
+
{{ $value := .value }}
+
<input class="p-1 w-full" type="text" name="operand-val" value="{{$value}}">
+
{{ end }}
+
+
{{ define "nullTypeInput" }}
+
<input class="p-1" type="hidden" name="operand-val" value="null">
+
{{ end }}
+28 -2
appview/pages/templates/repo/issues/issue.html
···
{{ if .Issue.Body }}
<article id="body" class="mt-4 prose dark:prose-invert">{{ .Issue.Body | markdown }}</article>
{{ end }}
-
{{ template "issueReactions" . }}
+
<div class="flex flex-wrap gap-2 items-stretch">
+
{{ template "issueReactions" . }}
+
{{ template "issueLabels" . }}
+
</div>
</section>
{{ end }}
···
{{ end }}
{{ define "issueReactions" }}
-
<div class="flex items-center gap-2 mt-2">
+
<div class="flex items-center gap-2">
{{ template "repo/fragments/reactionsPopUp" .OrderedReactionKinds }}
{{ range $kind := .OrderedReactionKinds }}
{{
···
"ThreadAt" $.Issue.AtUri)
}}
{{ end }}
+
</div>
+
{{ end }}
+
+
{{ define "issueLabels" }}
+
{{ range $k, $valset := $.Issue.Labels.Inner }}
+
{{ $d := index $.LabelDefs $k }}
+
{{ range $v, $s := $valset }}
+
{{ template "labels/fragments/label" (dict "def" $d "val" $v) }}
+
{{ end }}
+
{{ end }}
+
+
<button
+
class="btn text-gray-500 dark:text-gray-400"
+
popovertarget="add-label-modal"
+
{{ if not (or .RepoInfo.Roles.IsOwner .RepoInfo.Roles.IsCollaborator) }}disabled{{ end }}
+
popovertargetaction="toggle">
+
{{ i "plus" "size-4" }}
+
</button>
+
<div
+
id="add-label-modal"
+
popover
+
class="bg-white w-full sm:w-96 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>
{{ end }}