Monorepo for Tangled tangled.org

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

merged opened by willdot.net targeting master from willdot.net/tangled-fork: punchcard-prefs

This allows users to set their preference for the punchcard.

It allows them to either:

1: Hide their punchcard so that no one can see it 2: Hide all punchcards so that they don't see anyones punchcards

Labels

None yet.

assignee

None yet.

Participants 2
AT URI
at://did:plc:dadhhalkfcq3gucaq25hjqon/sh.tangled.repo.pull/3meygakiwru22
+197 -25
Diff #0
+7
appview/db/db.go
··· 573 573 name text unique 574 574 ); 575 575 576 + create table if not exists punchcard_preferences ( 577 + id integer primary key autoincrement, 578 + user_did text not null unique, 579 + hide_mine bool, 580 + hide_all bool 581 + ); 582 + 576 583 -- indexes for better performance 577 584 create index if not exists idx_notifications_recipient_created on notifications(recipient_did, created desc); 578 585 create index if not exists idx_notifications_recipient_read on notifications(recipient_did, read);
+46
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 + err := e.QueryRow( 15 + `select id, hide_mine, hide_all from punchcard_preferences where user_did = ?`, 16 + did, 17 + ).Scan(&preference.ID, &preference.HideMine, &preference.HideAll) 18 + if err == sql.ErrNoRows { 19 + return preference, nil 20 + } 21 + 22 + if err != nil { 23 + return preference, err 24 + } 25 + 26 + return preference, nil 27 + } 28 + 29 + func UpsertPunchcardPreference(e Execer, did string, hideMine, hideAll bool) error { 30 + _, err := e.Exec( 31 + `insert or replace into punchcard_preferences ( 32 + user_did, 33 + hide_mine, 34 + hide_all 35 + ) 36 + values (?, ?, ?)`, 37 + did, 38 + hideMine, 39 + hideAll, 40 + ) 41 + if err != nil { 42 + return err 43 + } 44 + 45 + return nil 46 + }
+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 118 - } 115 + monthsAgo := monthsBetween(punch.Date, now) 116 + if monthsAgo >= TimeframeMonths { 117 + // shouldn't happen; but times are weird 118 + continue 119 + } 119 120 120 - idx := monthsAgo 121 - timeline.ByMonth[idx].Commits += punch.Count 121 + idx := monthsAgo 122 + timeline.ByMonth[idx].Commits += punch.Count 123 + } 122 124 } 123 125 124 126 return &timeline, nil
+26
appview/models/preferences.go
··· 1 + package models 2 + 3 + const ( 4 + PunchcardPreferenceHideAll = "HIDE_ALL" 5 + PunchcardPreferenceHideMine = "HIDE_MINE" 6 + PunchcardPreferenceShowAll = "SHOW_ALL" 7 + ) 8 + 9 + type PunchcardPreference struct { 10 + ID int 11 + Did string 12 + HideMine bool 13 + HideAll bool 14 + } 15 + 16 + func (p PunchcardPreference) GetPunchcardPrefences() string { 17 + if p.HideAll == true { 18 + return PunchcardPreferenceHideAll 19 + } 20 + 21 + if p.HideMine == true { 22 + return PunchcardPreferenceHideMine 23 + } 24 + 25 + return PunchcardPreferenceShowAll 26 + }
+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 string 364 365 } 365 366 366 367 func (p *Pages) UserProfileSettings(w io.Writer, params UserProfileSettingsParams) error { ··· 557 558 ProfileTimeline *models.ProfileTimeline 558 559 Card *ProfileCard 559 560 Active string 561 + ShowPunchcard bool 560 562 } 561 563 562 564 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
+30
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 + <select 70 + id="punchcard-setting" 71 + name="punchcard-setting" 72 + required 73 + class="p-1 max-w-64 border border-gray-200 bg-white dark:bg-gray-800 dark:text-white dark:border-gray-700"> 74 + <option value="SHOW_ALL" class="py-1" {{ if eq "SHOW_ALL" $.PunchcardPreference }}selected{{ end }}> 75 + Show all 76 + </option> 77 + <option value="HIDE_MINE" class="py-1" {{ if eq "HIDE_MINE" $.PunchcardPreference }}selected{{ end }}> 78 + Hide mine for others 79 + </option> 80 + <option value="HIDE_ALL" class="py-1" {{ if eq "HIDE_ALL" $.PunchcardPreference }}selected{{ end }}> 81 + Hide everyones for me 82 + </option> 83 + </select> 84 + <button class="btn flex gap-2 items-center" type="submit"> 85 + {{ i "check" "size-4" }} 86 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 87 + </button> 88 + </form> 89 + </div> 90 + </div> 91 + </div> 62 92 {{ 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.GetPunchcardPrefences(), 75 81 }) 76 82 } 77 83
+52 -2
appview/state/profile.go
··· 164 164 } 165 165 } 166 166 167 - timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid) 167 + loggedInUser := s.oauth.GetMultiAccountUser(r) 168 + 169 + targetPunchcardPreferences, err := db.GetPunchcardPreference(s.db, profile.UserDid) 170 + if err != nil { 171 + l.Error("failed to get target users punchcard preferences", "err", err) 172 + } 173 + 174 + requesterPunchcardPreferences, err := db.GetPunchcardPreference(s.db, loggedInUser.Did()) 175 + if err != nil { 176 + l.Error("failed to get requester users punchcard preferences", "err", err) 177 + } 178 + 179 + showPunchcard := true 180 + 181 + if targetPunchcardPreferences.HideMine || requesterPunchcardPreferences.HideAll { 182 + showPunchcard = false 183 + } 184 + 185 + timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid, showPunchcard) 168 186 if err != nil { 169 187 l.Error("failed to create timeline", "err", err) 170 188 } ··· 175 193 Repos: pinnedRepos, 176 194 CollaboratingRepos: pinnedCollaboratingRepos, 177 195 ProfileTimeline: timeline, 196 + ShowPunchcard: showPunchcard, 178 197 }) 179 198 } 180 199 ··· 411 430 } 412 431 413 432 func (s *State) getProfileFeed(ctx context.Context, id *identity.Identity) (*feeds.Feed, error) { 414 - timeline, err := db.MakeProfileTimeline(s.db, id.DID.String()) 433 + timeline, err := db.MakeProfileTimeline(s.db, id.DID.String(), false) 415 434 if err != nil { 416 435 return nil, err 417 436 } ··· 936 955 937 956 s.pages.HxRedirect(w, r.Header.Get("Referer")) 938 957 } 958 + 959 + func (s *State) UpdateProfilePunchcardSetting(w http.ResponseWriter, r *http.Request) { 960 + err := r.ParseForm() 961 + if err != nil { 962 + log.Println("invalid profile update form", err) 963 + return 964 + } 965 + user := s.oauth.GetUser(r) 966 + 967 + punchcard := r.Form.Get("punchcard-setting") 968 + 969 + hideAll := false 970 + hideMine := false 971 + 972 + switch punchcard { 973 + case models.PunchcardPreferenceHideAll: 974 + hideAll = true 975 + hideMine = false 976 + case models.PunchcardPreferenceHideMine: 977 + hideMine = true 978 + hideAll = false 979 + } 980 + 981 + err = db.UpsertPunchcardPreference(s.db, user.Did, hideMine, hideAll) 982 + if err != nil { 983 + log.Println("failed to update punchcard preferences", err) 984 + return 985 + } 986 + 987 + s.pages.HxRefresh(w) 988 + }
+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())

History

5 rounds 3 comments
sign up or login to add to the discussion
1 commit
expand
appview: allow users to set their preferences for the punchcard being displayed
expand 1 comment

thanks for the work on this! this looks good to me in the current state.

pull request successfully merged
1 commit
expand
appview: allow users to set their preferences for the punchcard being displayed
expand 0 comments
1 commit
expand
appview: allow users to set their preferences for the punchcard being displayed
expand 0 comments
1 commit
expand
appview: allow users to set their preferences for the punchcard being displayed
expand 0 comments
willdot.net submitted #0
1 commit
expand
appview: allow users to set their preferences for the punchcard being displayed
expand 2 comments
  • here: there is no bool type in sqlite! so we will have to use ints here. i tested this locally and it seems that is valid sql and executes fine, but it seems any value can be stored in that column.
  • here: nice, this is super neat

all in all, rest of the code lgtm!

Oh wow, TIL that there isn't a bool type in sqlite ๐Ÿซฃ

I've fixed that so that the field is an integer type with a default of 0 (false) and then added the relevant mapping into the query code to map 0 is false and > 0 is true.

Tested by stuffing random integers manually into the table and appears to work as expected.