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

appview/pages: display DID format labels handles

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

oppi.li d66a4745 a20273b1

verified
Changed files
+91 -67
appview
labels
pages
templates
repo
+25 -24
appview/labels/labels.go
···
"github.com/go-chi/chi/v5"
"tangled.sh/tangled.sh/core/api/tangled"
-
"tangled.sh/tangled.sh/core/appview/config"
"tangled.sh/tangled.sh/core/appview/db"
"tangled.sh/tangled.sh/core/appview/middleware"
"tangled.sh/tangled.sh/core/appview/oauth"
"tangled.sh/tangled.sh/core/appview/pages"
-
"tangled.sh/tangled.sh/core/appview/reporesolver"
+
"tangled.sh/tangled.sh/core/appview/validator"
"tangled.sh/tangled.sh/core/appview/xrpcclient"
-
"tangled.sh/tangled.sh/core/eventconsumer"
-
"tangled.sh/tangled.sh/core/idresolver"
"tangled.sh/tangled.sh/core/log"
-
"tangled.sh/tangled.sh/core/rbac"
"tangled.sh/tangled.sh/core/tid"
)
type Labels struct {
-
repoResolver *reporesolver.RepoResolver
-
idResolver *idresolver.Resolver
-
oauth *oauth.OAuth
-
pages *pages.Pages
-
db *db.DB
-
logger *slog.Logger
+
oauth *oauth.OAuth
+
pages *pages.Pages
+
db *db.DB
+
logger *slog.Logger
+
validator *validator.Validator
}
func New(
oauth *oauth.OAuth,
-
repoResolver *reporesolver.RepoResolver,
pages *pages.Pages,
-
spindlestream *eventconsumer.Consumer,
-
idResolver *idresolver.Resolver,
db *db.DB,
-
config *config.Config,
-
enforcer *rbac.Enforcer,
+
validator *validator.Validator,
) *Labels {
logger := log.New("labels")
return &Labels{
-
oauth: oauth,
-
repoResolver: repoResolver,
-
pages: pages,
-
idResolver: idResolver,
-
db: db,
-
logger: logger,
+
oauth: oauth,
+
pages: pages,
+
db: db,
+
logger: logger,
+
validator: validator,
}
}
···
})
}
-
// TODO: validate the operations
-
// find all the labels that this repo subscribes to
repoLabels, err := db.GetRepoLabels(l.db, db.FilterEq("repo_at", repoAt))
if err != nil {
···
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)
+
}
+
// calculate the start state by applying already known labels
existingOps, err := db.GetLabelOps(l.db, db.FilterEq("subject", subjectUri))
if err != nil {
···
labelState := db.NewLabelState()
actx.ApplyLabelOps(labelState, existingOps)
+
+
l.logger.Info("state", "state", labelState)
// next, apply all ops introduced in this request and filter out ones that are no-ops
validLabelOps := labelOps[:0]
+28 -20
appview/pages/templates/repo/fragments/addLabelModal.html
···
{{ with $root }}
<form
hx-put="/{{ .RepoInfo.FullName }}/labels/perform"
-
hx-on::after-request="if(event.detail.successful) this.reset()"
+
hx-on::after-request="this.reset()"
hx-indicator="#spinner"
hx-swap="none"
class="flex flex-col gap-4"
···
<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">
+
<div class="flex flex-col gap-2">
{{ $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>
+
{{ template "labelCheckbox" (dict "def" $d "key" $k "val" $v "id" $id "isChecked" true) }}
+
{{ $id = add $id 1 }}
{{ 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>
+
{{ template "labelCheckbox" (dict "def" $d "key" $k "val" "" "id" $id "isChecked" false) }}
+
{{ $id = add $id 1 }}
{{ end }}
+
{{ else }}
+
<span>
+
No labels defined yet. You can define custom labels in <a class="underline" href="/{{ .RepoInfo.FullName }}/settings">settings</a>.
+
</span>
{{ end }}
</div>
···
{{ end }}
{{ end }}
+
{{ define "labelCheckbox" }}
+
{{ $key := .key }}
+
{{ $val := .val }}
+
{{ $def := .def }}
+
{{ $id := .id }}
+
{{ $isChecked := .isChecked }}
+
<div class="grid grid-cols-[auto_1fr_50%] gap-2 items-center cursor-pointer">
+
<input type="checkbox" id="op-{{$id}}" name="op-{{$id}}" value="add" {{if $isChecked}}checked{{end}} class="peer">
+
<label for="op-{{$id}}" class="flex items-center gap-2 text-base">{{ template "labels/fragments/labelDef" $def }}</label>
+
<div class="w-full hidden peer-checked:block">{{ template "valueTypeInput" (dict "valueType" $def.ValueType "value" $val "key" $key) }}</div>
+
<input type="hidden" name="operand-key" value="{{ $key }}">
+
</div>
+
{{ end }}
+
{{ define "valueTypeInput" }}
{{ $valueType := .valueType }}
{{ $value := .value }}
···
{{ end }}
{{ define "stringTypeInput" }}
+
{{ $valueType := .valueType }}
{{ $value := .value }}
+
{{ if $valueType.IsDidFormat }}
+
{{ $value = resolve .value }}
+
{{ end }}
<input class="p-1 w-full" type="text" name="operand-val" value="{{$value}}">
{{ end }}
+4 -4
appview/pages/templates/repo/issues/fragments/commentList.html
···
{{ range $item := .CommentList }}
{{ template "commentListing" (list $ .) }}
{{ end }}
-
<div>
+
</div>
{{ end }}
{{ define "commentListing" }}
···
"Issue" $root.Issue
"Comment" $comment.Self) }}
-
<div class="rounded border border-gray-300 dark:border-gray-700 w-full overflow-hidden shadow-sm">
+
<div class="rounded border border-gray-200 dark:border-gray-700 w-full overflow-hidden shadow-sm bg-gray-50 dark:bg-gray-800/50">
{{ template "topLevelComment" $params }}
-
<div class="relative ml-4 border-l border-gray-300 dark:border-gray-700">
+
<div class="relative ml-4 border-l-2 border-gray-200 dark:border-gray-700">
{{ range $index, $reply := $comment.Replies }}
<div class="relative ">
<!-- Horizontal connector -->
-
<div class="absolute left-0 top-6 w-4 h-px bg-gray-300 dark:bg-gray-700"></div>
+
<div class="absolute left-0 top-6 w-4 h-1 bg-gray-200 dark:bg-gray-700"></div>
<div class="pl-2">
{{
+13 -4
appview/pages/templates/repo/settings/fragments/addLabelDefModal.html
···
hx-put="/{{ $.RepoInfo.FullName }}/settings/label"
hx-indicator="#spinner"
hx-swap="none"
+
hx-on::after-request="if(event.detail.successful) this.reset()"
class="flex flex-col gap-4"
>
<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>
···
<div class="w-full">
<label for="valueType">Value Type</label>
<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">
-
<option value="string" selected>String</option>
+
<option value="null" selected>None</option>
+
<option value="string">String</option>
<option value="integer">Integer</option>
<option value="boolean">Boolean</option>
-
<option value="null">None</option>
</select>
-
<details id="constrain-values" class="group">
+
<details id="constrain-values" class="group hidden">
<summary class="list-none cursor-pointer flex items-center gap-2 py-2">
<span class="group-open:hidden inline text-gray-500 dark:text-gray-400">{{ i "square-plus" "w-4 h-4" }}</span>
<span class="hidden group-open:inline text-gray-500 dark:text-gray-400">{{ i "square-minus" "w-4 h-4" }}</span>
<span>Constrain values</span>
</summary>
+
<label for="enumValues">Permitted values</label>
<input type="text" id="enumValues" name="enumValues" placeholder="value1, value2, value3" class="w-full"/>
<p class="text-sm text-gray-400 dark:text-gray-500 mt-1">Enter comma-separated list of permitted values.</p>
+
+
<label for="valueFormat">String format</label>
+
<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">
+
<option value="any" selected>Any</option>
+
<option value="did">DID</option>
+
</select>
+
<p class="text-sm text-gray-400 dark:text-gray-500 mt-1">Choose a string format.</p>
</details>
</div>
···
const constrainValues = document.getElementById('constrain-values');
const selectedValue = this.value;
-
if (selectedValue === 'string' || selectedValue === 'integer') {
+
if (selectedValue === 'string') {
constrainValues.classList.remove('hidden');
} else {
constrainValues.classList.add('hidden');
+19 -13
appview/pages/templates/repo/settings/fragments/labelListing.html
···
<div class="flex flex-col gap-1 text-sm min-w-0 max-w-[80%]">
{{ template "labels/fragments/labelDef" $label }}
<div class="flex flex-wrap text items-center gap-1 text-gray-500 dark:text-gray-400">
-
{{ $label.ValueType.Type }}
+
{{ $label.ValueType.Type }} type
{{ if $label.ValueType.IsEnumType }}
<span class="before:content-['·'] before:select-none"></span>
{{ join $label.ValueType.Enum ", " }}
{{ end }}
+
{{ if $label.ValueType.IsDidFormat }}
+
<span class="before:content-['·'] before:select-none"></span>
+
DID format
+
{{ end }}
</div>
</div>
-
<button
-
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
-
title="Delete label"
-
hx-delete="/{{ $root.RepoInfo.FullName }}/settings/label"
-
hx-swap="none"
-
hx-vals='{"label-id": "{{ $label.Id }}"}'
-
hx-confirm="Are you sure you want to delete the label `{{ $label.Name }}`?"
-
>
-
{{ i "trash-2" "w-5 h-5" }}
-
<span class="hidden md:inline">delete</span>
-
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
-
</button>
+
{{ if $root.RepoInfo.Roles.IsOwner }}
+
<button
+
class="btn text-red-500 hover:text-red-700 dark:text-red-400 dark:hover:text-red-300 gap-2 group"
+
title="Delete label"
+
hx-delete="/{{ $root.RepoInfo.FullName }}/settings/label"
+
hx-swap="none"
+
hx-vals='{"label-id": "{{ $label.Id }}"}'
+
hx-confirm="Are you sure you want to delete the label `{{ $label.Name }}`?"
+
>
+
{{ i "trash-2" "w-5 h-5" }}
+
<span class="hidden md:inline">delete</span>
+
{{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }}
+
</button>
+
{{ end }}
</div>
{{ end }}
+2 -2
appview/pages/templates/repo/settings/general.html
···
<button
class="btn flex items-center gap-2"
popovertarget="add-labeldef-modal"
-
{{ if not (or .RepoInfo.Roles.IsOwner .RepoInfo.Roles.IsCollaborator) }}disabled{{ end }}
+
{{ if not .RepoInfo.Roles.IsOwner }}disabled{{ end }}
popovertargetaction="toggle">
{{ i "plus" "size-4" }}
add label
···
<div
id="add-labeldef-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">
+
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/settings/fragments/addLabelDefModal" . }}
</div>
</div>