appview: allows a default knot to be configured #858

open
opened by willdot.net targeting master from willdot.net/tangled-core: master

This follows on from the work carried out in #836

I've added a select box in the Knots settings page which pulls in the users knots and also adds in knot1.tangled.sh. When the user selects one of these options, it will save to their profile in the database. NOTE: I haven't yet implemented adding that to the AT record because I'm not sure on how the lexicon setup works yet!

Then when users go to create a new repo / fork, if there is a value in their profile for the default knot, then that will pre select the knot to use for the new repo / fork.

+7
appview/db/db.go
···
return err
})
+
runMigration(conn, logger, "add-default-knot-profile", func(tx *sql.Tx) error {
+
_, err := tx.Exec(`
+
alter table profile add column default_knot text;
+
`)
+
return err
+
})
+
return &DB{
db,
logger,
+11 -4
appview/db/profile.go
···
description,
include_bluesky,
location,
-
pronouns
+
pronouns,
+
default_knot
)
-
values (?, ?, ?, ?, ?)`,
+
values (?, ?, ?, ?, ?, ?)`,
profile.Did,
profile.Description,
includeBskyValue,
profile.Location,
profile.Pronouns,
+
profile.DefaultKnot,
)
if err != nil {
···
func GetProfile(e Execer, did string) (*models.Profile, error) {
var profile models.Profile
var pronouns sql.Null[string]
+
var defaultKnot sql.Null[string]
profile.Did = did
includeBluesky := 0
err := e.QueryRow(
-
`select description, include_bluesky, location, pronouns from profile where did = ?`,
+
`select description, include_bluesky, location, pronouns, default_knot from profile where did = ?`,
did,
-
).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns)
+
).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns, &defaultKnot)
if err == sql.ErrNoRows {
profile := models.Profile{}
profile.Did = did
···
if pronouns.Valid {
profile.Pronouns = pronouns.V
+
}
+
+
if defaultKnot.Valid {
+
profile.DefaultKnot = defaultKnot.V
}
rows, err := e.Query(`select link from profile_links where did = ?`, did)
+10
appview/knots/knots.go
···
return
}
+
defaultKnot := ""
+
profile, err := db.GetProfile(k.Db, user.Did)
+
if err != nil {
+
k.Logger.Warn("gettings user profile to get default knot", "error", err)
+
}
+
if profile != nil {
+
defaultKnot = profile.DefaultKnot
+
}
+
k.Pages.Knots(w, pages.KnotsParams{
LoggedInUser: user,
Registrations: registrations,
Tabs: knotsTabs,
Tab: "knots",
+
DefaultKnot: defaultKnot,
})
}
+1
appview/models/profile.go
···
Stats [2]VanityStat
PinnedRepos [6]syntax.ATURI
Pronouns string
+
DefaultKnot string
}
func (p Profile) IsLinksEmpty() bool {
+3
appview/pages/pages.go
···
Registrations []models.Registration
Tabs []map[string]any
Tab string
+
DefaultKnot string
}
func (p *Pages) Knots(w io.Writer, params KnotsParams) error {
···
type NewRepoParams struct {
LoggedInUser *oauth.User
Knots []string
+
DefaultKnot string
}
func (p *Pages) NewRepo(w io.Writer, params NewRepoParams) error {
···
LoggedInUser *oauth.User
Knots []string
RepoInfo repoinfo.RepoInfo
+
DefaultKnot string
}
func (p *Pages) ForkRepo(w io.Writer, params ForkRepoParams) error {
+24
appview/pages/templates/knots/index.html
···
<div class="flex flex-col gap-6">
{{ block "list" . }} {{ end }}
{{ block "register" . }} {{ end }}
+
{{ block "default-knot" . }} {{ end }}
</div>
</section>
{{ end }}
···
{{ end }}
</div>
<div id="operation-error" class="text-red-500 dark:text-red-400"></div>
+
</section>
+
{{ end }}
+
+
{{ define "default-knot" }}
+
<section class="rounded w-full flex flex-col gap-2">
+
<h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">default knot</h2>
+
<select id="default-knot" name="default-knot"
+
class="p-1 max-w-64 border border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700"
+
hx-post="/profile/default-knot"
+
hx-swap="none"
+
name="default-knot">
+
<option value="" >
+
Choose a default Knot
+
</option>
+
<option value="knot1.tangled.sh" {{if eq $.DefaultKnot "knot1.tangled.sh"}}selected{{end}} >
+
knot1.tangled.sh
+
</option>
+
{{ range $registration := .Registrations }}
+
<option value="{{ .Domain }}" class="py-1" {{if eq $.DefaultKnot .Domain}}selected{{end}}>
+
{{ .Domain }}
+
</option>
+
{{ end }}
+
</select>
</section>
{{ end }}
+3 -1
appview/pages/templates/repo/fork.html
···
value="{{ . }}"
class="mr-2"
id="domain-{{ . }}"
-
{{if eq (len $.Knots) 1}}checked{{end}}
+
{{if eq (len $.Knots) 1}}checked
+
{{else if eq $.DefaultKnot . }}checked
+
{{end}}
/>
<label for="domain-{{ . }}" class="dark:text-white">{{ . }}</label>
</div>
+1 -1
appview/pages/templates/repo/index.html
···
<div class="px-4 py-2 border-b border-gray-200 dark:border-gray-600 flex items-center gap-4 flex-wrap">
{{ range $value := .Languages }}
<div
-
class="flex items-center gap-2 text-xs align-items-center justify-center"
+
class="flex flex-grow items-center gap-2 text-xs align-items-center justify-center"
>
{{ template "repo/fragments/colorBall" (dict "color" (langColor $value.Name)) }}
<div>{{ or $value.Name "Other" }}
+3 -1
appview/pages/templates/repo/new.html
···
class="mr-2"
id="domain-{{ . }}"
required
-
{{if eq (len $.Knots) 1}}checked{{end}}
+
{{if eq (len $.Knots) 1}}checked
+
{{else if eq $.DefaultKnot . }}checked
+
{{end}}
/>
<label for="domain-{{ . }}" class="dark:text-white lowercase">{{ . }}</label>
</div>
+1 -3
appview/pages/templates/user/followers.html
···
"FollowersCount" .FollowersCount
"FollowingCount" .FollowingCount) }}
{{ else }}
-
<div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded">
-
<span>This user does not have any followers yet.</span>
-
</div>
+
<p class="px-6 dark:text-white">This user does not have any followers yet.</p>
{{ end }}
</div>
{{ end }}
+1 -3
appview/pages/templates/user/following.html
···
"FollowersCount" .FollowersCount
"FollowingCount" .FollowingCount) }}
{{ else }}
-
<div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded">
-
<span>This user does not follow anyone yet.</span>
-
</div>
+
<p class="px-6 dark:text-white">This user does not follow anyone yet.</p>
{{ end }}
</div>
{{ end }}
+2 -10
appview/pages/templates/user/overview.html
···
<p class="text-sm font-bold px-2 pb-4 dark:text-white">ACTIVITY</p>
<div class="flex flex-col gap-4 relative">
{{ if .ProfileTimeline.IsEmpty }}
-
<div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded">
-
<span class="flex items-center gap-2">
-
This user does not have any activity yet.
-
</span>
-
</div>
+
<p class="dark:text-white">This user does not have any activity yet.</p>
{{ end }}
{{ with .ProfileTimeline }}
···
{{ template "user/fragments/repoCard" (list $ . false) }}
</div>
{{ else }}
-
<div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded">
-
<span class="flex items-center gap-2">
-
This user does not have any pinned repos.
-
</span>
-
</div>
+
<p class="dark:text-white">This user does not have any pinned repos.</p>
{{ end }}
</div>
</div>
+1 -3
appview/pages/templates/user/repos.html
···
{{ template "user/fragments/repoCard" (list $ . false) }}
</div>
{{ else }}
-
<div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded">
-
<span>This user does not have any repos yet.</span>
-
</div>
+
<p class="px-6 dark:text-white">This user does not have any repos yet.</p>
{{ end }}
</div>
{{ end }}
+1 -3
appview/pages/templates/user/starred.html
···
{{ template "user/fragments/repoCard" (list $ . true) }}
</div>
{{ else }}
-
<div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded">
-
<span>This user does not have any starred repos yet.</span>
-
</div>
+
<p class="px-6 dark:text-white">This user does not have any starred repos yet.</p>
{{ end }}
</div>
{{ end }}
+1 -3
appview/pages/templates/user/strings.html
···
{{ template "singleString" (list $ .) }}
</div>
{{ else }}
-
<div class="text-base text-gray-500 flex items-center justify-center italic p-12 border border-gray-200 dark:border-gray-700 rounded">
-
<span>This user does not have any strings yet.</span>
-
</div>
+
<p class="px-6 dark:text-white">This user does not have any strings yet.</p>
{{ end }}
</div>
{{ end }}
+10
appview/repo/repo.go
···
return
+
defaultKnot := ""
+
profile, err := db.GetProfile(rp.db, user.Did)
+
if err != nil {
+
rp.logger.Warn("gettings user profile to get default knot", "error", err)
+
}
+
if profile != nil {
+
defaultKnot = profile.DefaultKnot
+
}
+
rp.pages.ForkRepo(w, pages.ForkRepoParams{
LoggedInUser: user,
Knots: knots,
RepoInfo: rp.repoResolver.GetRepoInfo(r, user),
+
DefaultKnot: defaultKnot,
})
case http.MethodPost:
+32
appview/state/profile.go
···
s.updateProfile(profile, w, r)
}
+
func (s *State) UpdateProfileDefaultKnot(w http.ResponseWriter, r *http.Request) {
+
err := r.ParseForm()
+
if err != nil {
+
log.Println("invalid profile update form", err)
+
return
+
}
+
user := s.oauth.GetUser(r)
+
+
profile, err := db.GetProfile(s.db, user.Did)
+
if err != nil {
+
log.Printf("getting profile data for %s: %s", user.Did, err)
+
}
+
+
if profile == nil {
+
return
+
}
+
+
profile.DefaultKnot = r.Form.Get("default-knot")
+
+
tx, err := s.db.BeginTx(r.Context(), nil)
+
if err != nil {
+
log.Println("failed to start transaction", err)
+
return
+
}
+
+
err = db.UpsertProfile(tx, profile)
+
if err != nil {
+
log.Println("failed to update profile", err)
+
return
+
}
+
}
+
func (s *State) updateProfile(profile *models.Profile, w http.ResponseWriter, r *http.Request) {
user := s.oauth.GetUser(r)
tx, err := s.db.BeginTx(r.Context(), nil)
+1
appview/state/router.go
···
r.Get("/edit-pins", s.EditPinsFragment)
r.Post("/bio", s.UpdateProfileBio)
r.Post("/pins", s.UpdateProfilePins)
+
r.Post("/default-knot", s.UpdateProfileDefaultKnot)
})
r.Mount("/settings", s.SettingsRouter())
+10
appview/state/state.go
···
return
}
+
defaultKnot := ""
+
profile, err := db.GetProfile(s.db, user.Did)
+
if err != nil {
+
s.logger.Warn("gettings user profile to get default knot", "error", err)
+
}
+
if profile != nil {
+
defaultKnot = profile.DefaultKnot
+
}
+
s.pages.NewRepo(w, pages.NewRepoParams{
LoggedInUser: user,
Knots: knots,
+
DefaultKnot: defaultKnot,
})
case http.MethodPost: