AppView: allows a default knot to be configured #857

closed
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.

Changed files
+113 -8
appview
+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
···
profile.Pronouns = pronouns.V
}
+
if defaultKnot.Valid {
+
profile.DefaultKnot = defaultKnot.V
+
}
+
rows, err := e.Query(`select link from profile_links where did = ?`, did)
if err != nil {
return nil, err
+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 }}
···
</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 }}
+
{{ define "register" }}
<section class="rounded w-full lg:w-fit flex flex-col gap-2">
<h2 class="text-sm font-bold py-2 uppercase dark:text-gray-300">register a knot</h2>
+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>
+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>
+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:
+28
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)
+
}
+
+
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:
+2 -2
flake.lock
···
"lastModified": 1731402384,
"narHash": "sha256-OwUmrPfEehLDz0fl2ChYLK8FQM2p0G1+EMrGsYEq+6g=",
"type": "tarball",
-
"url": "https://github.com/IBM/plex/releases/download/@ibm/plex-mono@1.1.0/ibm-plex-mono.zip"
+
"url": "https://github.com/IBM/plex/releases/download/@ibm%2Fplex-mono@1.1.0/ibm-plex-mono.zip"
},
"original": {
"type": "tarball",
-
"url": "https://github.com/IBM/plex/releases/download/@ibm/plex-mono@1.1.0/ibm-plex-mono.zip"
+
"url": "https://github.com/IBM/plex/releases/download/@ibm%2Fplex-mono@1.1.0/ibm-plex-mono.zip"
}
},
"indigo": {