Mirror of @tangled.org/core. Running on a Raspberry Pi Zero 2

appview: allow users to set their preferences for the punchcard being displayed

Signed-off-by: Will Andrews <will7989@hotmail.com>

authored by willdot.net and committed by tangled.org 2482a23f 0dee98c3

+190 -25
+7
appview/db/db.go
··· 601 601 name text unique 602 602 ); 603 603 604 + create table if not exists punchcard_preferences ( 605 + id integer primary key autoincrement, 606 + user_did text not null unique, 607 + hide_mine integer default 0, 608 + hide_others integer default 0 609 + ); 610 + 604 611 -- indexes for better performance 605 612 create index if not exists idx_notifications_recipient_created on notifications(recipient_did, created desc); 606 613 create index if not exists idx_notifications_recipient_read on notifications(recipient_did, read);
+52
appview/db/preferences.go
··· 1 + package db 2 + 3 + import ( 4 + "database/sql" 5 + 6 + "tangled.org/core/appview/models" 7 + ) 8 + 9 + func GetPunchcardPreference(e Execer, did string) (models.PunchcardPreference, error) { 10 + preference := models.PunchcardPreference{ 11 + Did: did, 12 + } 13 + 14 + hideMine := 0 15 + hideOthers := 0 16 + 17 + err := e.QueryRow( 18 + `select id, hide_mine, hide_others from punchcard_preferences where user_did = ?`, 19 + did, 20 + ).Scan(&preference.ID, &hideMine, &hideOthers) 21 + if err == sql.ErrNoRows { 22 + return preference, nil 23 + } 24 + 25 + preference.HideMine = hideMine > 0 26 + preference.HideOthers = hideOthers > 0 27 + 28 + if err != nil { 29 + return preference, err 30 + } 31 + 32 + return preference, nil 33 + } 34 + 35 + func UpsertPunchcardPreference(e Execer, did string, hideMine, hideOthers bool) error { 36 + _, err := e.Exec( 37 + `insert or replace into punchcard_preferences ( 38 + user_did, 39 + hide_mine, 40 + hide_others 41 + ) 42 + values (?, ?, ?)`, 43 + did, 44 + hideMine, 45 + hideOthers, 46 + ) 47 + if err != nil { 48 + return err 49 + } 50 + 51 + return nil 52 + }
+21 -19
appview/db/profile.go
··· 16 16 17 17 const TimeframeMonths = 7 18 18 19 - func MakeProfileTimeline(e Execer, forDid string) (*models.ProfileTimeline, error) { 19 + func MakeProfileTimeline(e Execer, forDid string, includePunchcard bool) (*models.ProfileTimeline, error) { 20 20 timeline := models.ProfileTimeline{ 21 21 ByMonth: make([]models.ByMonth, TimeframeMonths), 22 22 } ··· 98 98 }) 99 99 } 100 100 101 - punchcard, err := MakePunchcard( 102 - e, 103 - orm.FilterEq("did", forDid), 104 - orm.FilterGte("date", time.Now().AddDate(0, -TimeframeMonths, 0)), 105 - ) 106 - if err != nil { 107 - return nil, fmt.Errorf("error getting commits by did: %w", err) 108 - } 109 - for _, punch := range punchcard.Punches { 110 - if punch.Date.After(now) { 111 - continue 101 + if includePunchcard { 102 + punchcard, err := MakePunchcard( 103 + e, 104 + orm.FilterEq("did", forDid), 105 + orm.FilterGte("date", time.Now().AddDate(0, -TimeframeMonths, 0)), 106 + ) 107 + if err != nil { 108 + return nil, fmt.Errorf("error getting commits by did: %w", err) 112 109 } 110 + for _, punch := range punchcard.Punches { 111 + if punch.Date.After(now) { 112 + continue 113 + } 113 114 114 - monthsAgo := monthsBetween(punch.Date, now) 115 - if monthsAgo >= TimeframeMonths { 116 - // shouldn't happen; but times are weird 117 - continue 115 + monthsAgo := monthsBetween(punch.Date, now) 116 + if monthsAgo >= TimeframeMonths { 117 + // shouldn't happen; but times are weird 118 + continue 119 + } 120 + 121 + idx := monthsAgo 122 + timeline.ByMonth[idx].Commits += punch.Count 118 123 } 119 - 120 - idx := monthsAgo 121 - timeline.ByMonth[idx].Commits += punch.Count 122 124 } 123 125 124 126 return &timeline, nil
+8
appview/models/preferences.go
··· 1 + package models 2 + 3 + type PunchcardPreference struct { 4 + ID int 5 + Did string 6 + HideMine bool 7 + HideOthers bool 8 + }
+4 -2
appview/pages/pages.go
··· 359 359 } 360 360 361 361 type UserProfileSettingsParams struct { 362 - LoggedInUser *oauth.MultiAccountUser 363 - Tab string 362 + LoggedInUser *oauth.MultiAccountUser 363 + Tab string 364 + PunchcardPreference models.PunchcardPreference 364 365 } 365 366 366 367 func (p *Pages) UserProfileSettings(w io.Writer, params UserProfileSettingsParams) error { ··· 558 557 ProfileTimeline *models.ProfileTimeline 559 558 Card *ProfileCard 560 559 Active string 560 + ShowPunchcard bool 561 561 } 562 562 563 563 func (p *Pages) ProfileOverview(w io.Writer, params ProfileOverviewParams) error {
+3 -1
appview/pages/templates/layouts/profilebase.html
··· 52 52 <div class="{{ $style }} order-1 order-1"> 53 53 <div class="flex flex-col gap-4"> 54 54 {{ template "user/fragments/profileCard" .Card }} 55 - {{ block "punchcard" .Card.Punchcard }} {{ end }} 55 + {{ if .ShowPunchcard }} 56 + {{ block "punchcard" .Card.Punchcard }} {{ end }} 57 + {{ end }} 56 58 </div> 57 59 </div> 58 60
+23
appview/pages/templates/user/settings/profile.html
··· 59 59 </div> 60 60 </div> 61 61 </div> 62 + <div class="flex flex-col rounded border border-gray-200 dark:border-gray-700 divide-y divide-gray-200 dark:divide-gray-700 w-full"> 63 + <div class="flex items-center justify-between p-4"> 64 + <div class="hover:no-underline flex flex-col gap-1 min-w-0 max-w-[80%]"> 65 + <div class="flex flex-wrap text-sm items-center gap-1 text-gray-500 dark:text-gray-400"> 66 + <span>Punchcard settings</span> 67 + </div> 68 + <form hx-post="/profile/punchcard" class="col-span-1 md:col-span-1 md:justify-self-end group flex gap-2 items-stretch"> 69 + <div> 70 + <input type="checkbox" id="hideMine" name="hideMine" value="on" {{ if eq true $.PunchcardPreference.HideMine }}checked{{ end }}> 71 + <label for="hideMine" class="my-0 py-0 normal-case font-normal">Hide mine</label> 72 + </div> 73 + <div> 74 + <input type="checkbox" id="hideOthers" name="hideOthers" value="on" {{ if eq true $.PunchcardPreference.HideOthers }}checked{{ end }}> 75 + <label for="hideOthers" class="my-0 py-0 normal-case font-normal">Hide others from me</label> 76 + </div> 77 + <button class="btn flex gap-2 items-center" type="submit"> 78 + {{ i "check" "size-4" }} 79 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 80 + </button> 81 + </form> 82 + </div> 83 + </div> 84 + </div> 62 85 {{ end }}
+7 -1
appview/settings/settings.go
··· 70 70 func (s *Settings) profileSettings(w http.ResponseWriter, r *http.Request) { 71 71 user := s.OAuth.GetMultiAccountUser(r) 72 72 73 + punchcardPreferences, err := db.GetPunchcardPreference(s.Db, user.Did()) 74 + if err != nil { 75 + log.Printf("failed to get users punchcard preferences: %s", err) 76 + } 77 + 73 78 s.Pages.UserProfileSettings(w, pages.UserProfileSettingsParams{ 74 - LoggedInUser: user, 79 + LoggedInUser: user, 80 + PunchcardPreference: punchcardPreferences, 75 81 }) 76 82 } 77 83
+64 -2
appview/state/profile.go
··· 4 4 "context" 5 5 "fmt" 6 6 "log" 7 + "log/slog" 7 8 "net/http" 8 9 "slices" 9 10 "strings" ··· 165 164 } 166 165 } 167 166 168 - timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid) 167 + loggedInUser := s.oauth.GetMultiAccountUser(r) 168 + 169 + showPunchcard := checkIfPunchcardShouldShow(s.db, l, profile.UserDid, loggedInUser.Did()) 170 + 171 + timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid, showPunchcard) 169 172 if err != nil { 170 173 l.Error("failed to create timeline", "err", err) 171 174 } ··· 180 175 Repos: pinnedRepos, 181 176 CollaboratingRepos: pinnedCollaboratingRepos, 182 177 ProfileTimeline: timeline, 178 + ShowPunchcard: showPunchcard, 183 179 }) 180 + } 181 + 182 + func checkIfPunchcardShouldShow(e db.Execer, l *slog.Logger, targetDid, requesterDid string) bool { 183 + targetPunchcardPreferences, err := db.GetPunchcardPreference(e, targetDid) 184 + if err != nil { 185 + l.Error("failed to get target users punchcard preferences", "err", err) 186 + return true 187 + } 188 + 189 + requesterPunchcardPreferences, err := db.GetPunchcardPreference(e, requesterDid) 190 + if err != nil { 191 + l.Error("failed to get requester users punchcard preferences", "err", err) 192 + return true 193 + } 194 + 195 + showPunchcard := true 196 + 197 + // looking at their own profile 198 + if targetDid == requesterDid { 199 + if targetPunchcardPreferences.HideMine { 200 + return false 201 + } 202 + return true 203 + } 204 + 205 + if targetPunchcardPreferences.HideMine || requesterPunchcardPreferences.HideOthers { 206 + showPunchcard = false 207 + } 208 + return showPunchcard 184 209 } 185 210 186 211 func (s *State) reposPage(w http.ResponseWriter, r *http.Request) { ··· 446 411 } 447 412 448 413 func (s *State) getProfileFeed(ctx context.Context, id *identity.Identity) (*feeds.Feed, error) { 449 - timeline, err := db.MakeProfileTimeline(s.db, id.DID.String()) 414 + timeline, err := db.MakeProfileTimeline(s.db, id.DID.String(), false) 450 415 if err != nil { 451 416 return nil, err 452 417 } ··· 970 935 } 971 936 972 937 s.pages.HxRedirect(w, r.Header.Get("Referer")) 938 + } 939 + 940 + func (s *State) UpdateProfilePunchcardSetting(w http.ResponseWriter, r *http.Request) { 941 + err := r.ParseForm() 942 + if err != nil { 943 + log.Println("invalid profile update form", err) 944 + return 945 + } 946 + user := s.oauth.GetUser(r) 947 + 948 + hideOthers := false 949 + hideMine := false 950 + 951 + if r.Form.Get("hideMine") == "on" { 952 + hideMine = true 953 + } 954 + if r.Form.Get("hideOthers") == "on" { 955 + hideOthers = true 956 + } 957 + 958 + err = db.UpsertPunchcardPreference(s.db, user.Did, hideMine, hideOthers) 959 + if err != nil { 960 + log.Println("failed to update punchcard preferences", err) 961 + return 962 + } 963 + 964 + s.pages.HxRefresh(w) 973 965 }
+1
appview/state/router.go
··· 167 167 r.Post("/pins", s.UpdateProfilePins) 168 168 r.Post("/avatar", s.UploadProfileAvatar) 169 169 r.Delete("/avatar", s.RemoveProfileAvatar) 170 + r.Post("/punchcard", s.UpdateProfilePunchcardSetting) 170 171 }) 171 172 172 173 r.Mount("/settings", s.SettingsRouter())