Monorepo for Tangled tangled.org

appview: allow users to decide how the profile punchcard is displayed #1018

open opened by willdot.net targeting master from willdot.net/tangled-fork: disable-punchcard

This solves https://tangled.org/tangled.org/core/issues/189 although the issue wasn't clear if it was to hide the punchcard for me (the user) so that I don't see any punchcards, or if it hides my punchcard from being seen by other users.

I've implemented both so that you can either disable your punchcard from being seen by any one, or you can hide everyones punchcards from being seen by you.

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:dadhhalkfcq3gucaq25hjqon/sh.tangled.repo.pull/3mdbrb5a7cn22
+177 -39
Diff #0
+8 -1
appview/db/db.go
··· 1078 1078 // transfer data, constructing pull_at from pulls table 1079 1079 _, err = tx.Exec(` 1080 1080 insert into pull_submissions_new (id, pull_at, round_number, patch, created) 1081 - select 1081 + select 1082 1082 ps.id, 1083 1083 'at://' || p.owner_did || '/sh.tangled.repo.pull/' || p.rkey, 1084 1084 ps.round_number, ··· 1173 1173 return err 1174 1174 }) 1175 1175 1176 + orm.RunMigration(conn, logger, "add-punchcard-setting-profile", func(tx *sql.Tx) error { 1177 + _, err := tx.Exec(` 1178 + alter table profile add column punchard_setting string; 1179 + `) 1180 + return err 1181 + }) 1182 + 1176 1183 return &DB{ 1177 1184 db, 1178 1185 logger,
+31 -21
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 ··· 353 355 includeBluesky := 0 354 356 355 357 err := e.QueryRow( 356 - `select description, include_bluesky, location, pronouns from profile where did = ?`, 358 + `select description, include_bluesky, location, pronouns, punchard_setting from profile where did = ?`, 357 359 did, 358 - ).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns) 360 + ).Scan(&profile.Description, &includeBluesky, &profile.Location, &pronouns, &profile.PunchardSetting) 359 361 if err == sql.ErrNoRows { 360 362 profile := models.Profile{} 361 363 profile.Did = did ··· 536 538 } 537 539 return nil 538 540 } 541 + 542 + func SetProfilePunchcardStatus(e Execer, did string, punchcard models.ProfilePunchcardOption) error { 543 + _, err := e.Exec( 544 + `update profile set punchard_setting = ? where did = ?`, 545 + punchcard, did, 546 + ) 547 + return err 548 + }
+26 -7
appview/models/profile.go
··· 7 7 "tangled.org/core/api/tangled" 8 8 ) 9 9 10 + type ProfilePunchcardOption string 11 + 12 + const ( 13 + ProfilePunchcardOptionHideMine ProfilePunchcardOption = "HIDE_MINE" 14 + ProfilePunchcardOptionHideAll ProfilePunchcardOption = "HIDE_ALL" 15 + ) 16 + 17 + func ProfilePunchcardFromString(s string) ProfilePunchcardOption { 18 + switch s { 19 + case "HIDE_MINE": 20 + return ProfilePunchcardOptionHideMine 21 + case "HIDE_ALL": 22 + return ProfilePunchcardOptionHideAll 23 + default: 24 + return "" 25 + } 26 + } 27 + 10 28 type Profile struct { 11 29 // ids 12 30 ID int 13 31 Did string 14 32 15 33 // data 16 - Description string 17 - IncludeBluesky bool 18 - Location string 19 - Links [5]string 20 - Stats [2]VanityStat 21 - PinnedRepos [6]syntax.ATURI 22 - Pronouns string 34 + Description string 35 + IncludeBluesky bool 36 + Location string 37 + Links [5]string 38 + Stats [2]VanityStat 39 + PinnedRepos [6]syntax.ATURI 40 + Pronouns string 41 + PunchardSetting ProfilePunchcardOption 23 42 } 24 43 25 44 func (p Profile) IsLinksEmpty() bool {
+5 -3
appview/pages/pages.go
··· 337 337 } 338 338 339 339 type UserProfileSettingsParams struct { 340 - LoggedInUser *oauth.MultiAccountUser 341 - Tabs []map[string]any 342 - Tab string 340 + LoggedInUser *oauth.MultiAccountUser 341 + Tabs []map[string]any 342 + Tab string 343 + PunchcardSetting models.ProfilePunchcardOption 343 344 } 344 345 345 346 func (p *Pages) UserProfileSettings(w io.Writer, params UserProfileSettingsParams) error { ··· 537 538 ProfileTimeline *models.ProfileTimeline 538 539 Card *ProfileCard 539 540 Active string 541 + ShowPunchcard bool 540 542 } 541 543 542 544 func (p *Pages) ProfileOverview(w io.Writer, params ProfileOverviewParams) error {
+4 -2
appview/pages/templates/layouts/profilebase.html
··· 10 10 <meta property="og:image" content="{{ $avatarUrl }}" /> 11 11 <meta property="og:image:width" content="512" /> 12 12 <meta property="og:image:height" content="512" /> 13 - 13 + 14 14 <meta name="twitter:card" content="summary" /> 15 15 <meta name="twitter:title" content="{{ $handle }}" /> 16 16 <meta name="twitter:description" content="{{ or .Card.Profile.Description $handle }}" /> ··· 28 28 <div class="{{ $style }} order-1 order-1"> 29 29 <div class="flex flex-col gap-4"> 30 30 {{ template "user/fragments/profileCard" .Card }} 31 - {{ block "punchcard" .Card.Punchcard }} {{ end }} 31 + {{ if .ShowPunchcard }} 32 + {{ block "punchcard" .Card.Punchcard }} {{ end }} 33 + {{ end }} 32 34 </div> 33 35 </div> 34 36
+31
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 + {{/* For some reason, we can't use an empty string in a <select> in all scenarios unless it is preceded by a disabled select?? No idea, could just be a Firefox thing? */}} 75 + <option value="[[none]]" class="py-1" {{ if not $.PunchcardSetting }}selected{{ end }}> 76 + Show all 77 + </option> 78 + <option value="HIDE_MINE" class="py-1" {{ if eq "HIDE_MINE" $.PunchcardSetting }}selected{{ end }}> 79 + Hide mine for others 80 + </option> 81 + <option value="HIDE_ALL" class="py-1" {{ if eq "HIDE_ALL" $.PunchcardSetting }}selected{{ end }}> 82 + Hide everyones for me 83 + </option> 84 + </select> 85 + <button class="btn flex gap-2 items-center" type="submit"> 86 + {{ i "check" "size-4" }} 87 + {{ i "loader-circle" "w-4 h-4 animate-spin hidden group-[.htmx-request]:inline" }} 88 + </button> 89 + </form> 90 + </div> 91 + </div> 92 + </div> 62 93 {{ end }}
+11 -2
appview/settings/settings.go
··· 82 82 83 83 func (s *Settings) profileSettings(w http.ResponseWriter, r *http.Request) { 84 84 user := s.OAuth.GetMultiAccountUser(r) 85 + profile, err := db.GetProfile(s.Db, user.Did()) 86 + if err != nil { 87 + log.Printf("failed to get profile to check punchcard settings: %s", err) 88 + } 85 89 86 - s.Pages.UserProfileSettings(w, pages.UserProfileSettingsParams{ 90 + params := pages.UserProfileSettingsParams{ 87 91 LoggedInUser: user, 88 92 Tabs: settingsTabs, 89 93 Tab: "profile", 90 - }) 94 + } 95 + if profile != nil { 96 + params.PunchcardSetting = profile.PunchardSetting 97 + } 98 + 99 + s.Pages.UserProfileSettings(w, params) 91 100 } 92 101 93 102 func (s *Settings) notificationsSettings(w http.ResponseWriter, r *http.Request) {
+60 -3
appview/state/profile.go
··· 157 157 } 158 158 } 159 159 160 - timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid) 160 + loggedInUser := s.oauth.GetMultiAccountUser(r) 161 + 162 + forProfile, err := db.GetProfile(s.db, profile.UserDid) 163 + if err != nil { 164 + l.Error("failed to get for profile to check punchcard settings", "err", err) 165 + } 166 + requesterProfile, err := db.GetProfile(s.db, loggedInUser.Did()) 167 + if err != nil { 168 + l.Error("failed to get requester profile to check punchcard settings", "err", err) 169 + } 170 + 171 + showPunchcard := true 172 + if forProfile != nil && forProfile.PunchardSetting == models.ProfilePunchcardOptionHideMine { 173 + showPunchcard = false 174 + } 175 + if requesterProfile != nil && requesterProfile.PunchardSetting == models.ProfilePunchcardOptionHideAll { 176 + showPunchcard = false 177 + } 178 + 179 + timeline, err := db.MakeProfileTimeline(s.db, profile.UserDid, showPunchcard) 161 180 if err != nil { 162 181 l.Error("failed to create timeline", "err", err) 163 182 } 164 183 184 + loggedInUserProfile, err := db.GetProfile(s.db, loggedInUser.Did()) 185 + if err != nil { 186 + l.Error("failed to get logged in user profile to check punchcard settings", "err", err) 187 + } 188 + 189 + if loggedInUserProfile != nil && loggedInUserProfile.PunchardSetting == models.ProfilePunchcardOptionHideAll { 190 + 191 + } 192 + 165 193 s.pages.ProfileOverview(w, pages.ProfileOverviewParams{ 166 - LoggedInUser: s.oauth.GetMultiAccountUser(r), 194 + LoggedInUser: loggedInUser, 167 195 Card: profile, 168 196 Repos: pinnedRepos, 169 197 CollaboratingRepos: pinnedCollaboratingRepos, 170 198 ProfileTimeline: timeline, 199 + ShowPunchcard: showPunchcard, 171 200 }) 172 201 } 173 202 ··· 404 433 } 405 434 406 435 func (s *State) getProfileFeed(ctx context.Context, id *identity.Identity) (*feeds.Feed, error) { 407 - timeline, err := db.MakeProfileTimeline(s.db, id.DID.String()) 436 + timeline, err := db.MakeProfileTimeline(s.db, id.DID.String(), false) 408 437 if err != nil { 409 438 return nil, err 410 439 } ··· 728 757 AllRepos: allRepos, 729 758 }) 730 759 } 760 + 761 + func (s *State) UpdateProfilePunchcardSetting(w http.ResponseWriter, r *http.Request) { 762 + err := r.ParseForm() 763 + if err != nil { 764 + log.Println("invalid profile update form", err) 765 + return 766 + } 767 + user := s.oauth.GetUser(r) 768 + 769 + profile, err := db.GetProfile(s.db, user.Did) 770 + if err != nil { 771 + log.Printf("getting profile data for %s: %s", user.Did, err) 772 + } 773 + 774 + if profile == nil { 775 + return 776 + } 777 + 778 + punchcard := r.Form.Get("punchcard-setting") 779 + 780 + err = db.SetProfilePunchcardStatus(s.db, profile.Did, models.ProfilePunchcardFromString(punchcard)) 781 + if err != nil { 782 + log.Println("failed to update profile", err) 783 + return 784 + } 785 + 786 + s.pages.HxRefresh(w) 787 + }
+1
appview/state/router.go
··· 165 165 r.Get("/edit-pins", s.EditPinsFragment) 166 166 r.Post("/bio", s.UpdateProfileBio) 167 167 r.Post("/pins", s.UpdateProfilePins) 168 + r.Post("/punchcard", s.UpdateProfilePunchcardSetting) 168 169 }) 169 170 170 171 r.Mount("/settings", s.SettingsRouter())

History

1 round 0 comments
sign up or login to add to the discussion
willdot.net submitted #0
1 commit
expand
appview: allow users to decide how the profile punchcard is displayed
merge conflicts detected
expand
  • appview/db/db.go:1078
  • appview/db/profile.go:353
  • appview/models/profile.go:7
  • appview/pages/pages.go:337
  • appview/settings/settings.go:82
  • appview/state/profile.go:728
  • appview/state/router.go:165
expand 0 comments