From 3e5d2f1c0b76c2334de0604d743b4805b23e9605 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Tue, 10 Jun 2025 12:15:03 +0100 Subject: [PATCH] appview: profile: render punchcard on profile Change-Id: lmuwzztnulmnryzrqnqqzyxzknzrmort Signed-off-by: oppiliappan --- appview/pages/funcmap.go | 7 + appview/pages/pages.go | 1 + appview/pages/templates/user/profile.html | 181 ++++++++++++++-------- appview/pages/templates/user/repos.html | 2 +- appview/state/profile.go | 14 ++ cmd/punchcardPopulate/main.go | 49 ++++++ tailwind.config.js | 4 + 7 files changed, 191 insertions(+), 67 deletions(-) create mode 100644 cmd/punchcardPopulate/main.go diff --git a/appview/pages/funcmap.go b/appview/pages/funcmap.go index 5995b76..6730551 100644 --- a/appview/pages/funcmap.go +++ b/appview/pages/funcmap.go @@ -33,9 +33,15 @@ func funcMap() template.FuncMap { "splitOn": func(s, sep string) []string { return strings.Split(s, sep) }, + "int64": func(a int) int64 { + return int64(a) + }, "add": func(a, b int) int { return a + b }, + "now": func() time.Time { + return time.Now() + }, // the absolute state of go templates "add64": func(a, b int64) int64 { return a + b @@ -79,6 +85,7 @@ func funcMap() template.FuncMap { "longTimeFmt": func(t time.Time) string { return t.Format("2006-01-02 * 3:04 PM") }, + "commaFmt": humanize.Comma, "shortTimeFmt": func(t time.Time) string { return humanize.CustomRelTime(t, time.Now(), "", "", []humanize.RelTimeMagnitude{ {time.Second, "now", time.Second}, diff --git a/appview/pages/pages.go b/appview/pages/pages.go index 5b342d9..29cb760 100644 --- a/appview/pages/pages.go +++ b/appview/pages/pages.go @@ -316,6 +316,7 @@ type ProfilePageParams struct { CollaboratingRepos []db.Repo ProfileTimeline *db.ProfileTimeline Card ProfileCard + Punchcard db.Punchcard DidHandleMap map[string]string } diff --git a/appview/pages/templates/user/profile.html b/appview/pages/templates/user/profile.html index f2a420f..bb4a70f 100644 --- a/appview/pages/templates/user/profile.html +++ b/appview/pages/templates/user/profile.html @@ -8,13 +8,18 @@ {{ end }} {{ define "content" }} -
+
+
{{ template "user/fragments/profileCard" .Card }} + {{ block "punchcard" .Punchcard }} {{ end }} +
+
{{ block "ownRepos" . }}{{ end }} {{ block "collaboratingRepos" . }}{{ end }} +
{{ block "profileTimeline" . }}{{ end }} @@ -24,7 +29,7 @@ {{ define "profileTimeline" }}

ACTIVITY

-
+
{{ with .ProfileTimeline }} {{ range $idx, $byMonth := .ByMonth }} {{ with $byMonth }} @@ -233,67 +238,35 @@ {{ end }} {{ define "ownRepos" }} -
- - PINNED REPOS - - view all {{ i "chevron-right" "w-4 h-4" }} - - - {{ if and .LoggedInUser (eq .LoggedInUser.Did .Card.UserDid) }} - - {{ end }} -
-
- {{ range .Repos }} -
- - {{ if .Description }} -
- {{ .Description }} -
- {{ end }} -
- {{ if .RepoStats.StarCount }} -
- {{ i "star" "w-3 h-3 fill-current" }} - {{ .RepoStats.StarCount }} -
- {{ end }} -
-
- {{ else }} -

This user does not have any repos yet.

- {{ end }} -
-{{ end }} - -{{ define "collaboratingRepos" }} - {{ if gt (len .CollaboratingRepos) 0 }} -

COLLABORATING ON

-
- {{ range .CollaboratingRepos }} +
+
+ + PINNED REPOS + + view all {{ i "chevron-right" "w-4 h-4" }} + + + {{ if and .LoggedInUser (eq .LoggedInUser.Did .Card.UserDid) }} + + {{ end }} +
+
+ {{ range .Repos }}
-
- - {{ index $.DidHandleMap .Did }}/{{ .Name }} - + class="py-4 px-6 drop-shadow-sm rounded bg-white dark:bg-gray-800"> + {{ if .Description }}
@@ -301,18 +274,94 @@
{{ end }}
+ {{ if .RepoStats.StarCount }} +
+ {{ i "star" "w-3 h-3 fill-current" }} + {{ .RepoStats.StarCount }} +
+ {{ end }} +
+
+ {{ else }} +

This user does not have any repos yet.

+ {{ end }} +
+
+{{ end }} +{{ define "collaboratingRepos" }} + {{ if gt (len .CollaboratingRepos) 0 }} +
+

COLLABORATING ON

+
+ {{ range .CollaboratingRepos }} +
+ + {{ if .Description }} +
+ {{ .Description }} +
+ {{ end }} +
{{ if .RepoStats.StarCount }}
{{ i "star" "w-3 h-3 fill-current" }} {{ .RepoStats.StarCount }}
{{ end }} -
-
- {{ else }} -

This user is not collaborating.

- {{ end }} +
+
+ {{ else }} +

This user is not collaborating.

+ {{ end }} +
{{ end }} {{ end }} + +{{ define "punchcard" }} + {{ $now := now }} +
+

+ PUNCHCARD + + {{ .Total | int64 | commaFmt }} commits + +

+
+
+ {{ range .Punches }} + {{ $count := .Count }} + {{ $theme := "bg-gray-200 dark:bg-gray-700 size-[4px]" }} + {{ if lt $count 1 }} + {{ $theme = "bg-gray-200 dark:bg-gray-700 size-[4px]" }} + {{ else if lt $count 2 }} + {{ $theme = "bg-green-200 dark:bg-green-900 size-[5px]" }} + {{ else if lt $count 4 }} + {{ $theme = "bg-green-300 dark:bg-green-800 size-[5px]" }} + {{ else if lt $count 8 }} + {{ $theme = "bg-green-400 dark:bg-green-700 size-[6px]" }} + {{ else }} + {{ $theme = "bg-green-500 dark:bg-green-600 size-[7px]" }} + {{ end }} + + {{ if .Date.After $now }} + {{ $theme = "border border-gray-200 dark:border-gray-700 size-[4px]" }} + {{ end }} +
+
+
+
+ {{ end }} +
+
+
+{{ end }} diff --git a/appview/pages/templates/user/repos.html b/appview/pages/templates/user/repos.html index 2aec5aa..494799f 100644 --- a/appview/pages/templates/user/repos.html +++ b/appview/pages/templates/user/repos.html @@ -8,7 +8,7 @@ {{ end }} {{ define "content" }} -
+
{{ template "user/fragments/profileCard" .Card }}
diff --git a/appview/state/profile.go b/appview/state/profile.go index 0590651..e4731c7 100644 --- a/appview/state/profile.go +++ b/appview/state/profile.go @@ -9,6 +9,7 @@ import ( "net/http" "slices" "strings" + "time" comatproto "github.com/bluesky-social/indigo/api/atproto" "github.com/bluesky-social/indigo/atproto/identity" @@ -126,6 +127,18 @@ func (s *State) profilePage(w http.ResponseWriter, r *http.Request) { followStatus = db.GetFollowStatus(s.db, loggedInUser.Did, ident.DID.String()) } + now := time.Now() + startOfYear := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC) + punchcard, err := db.MakePunchcard( + s.db, + db.FilterEq("did", ident.DID.String()), + db.FilterGte("date", startOfYear.Format(time.DateOnly)), + db.FilterLte("date", now.Format(time.DateOnly)), + ) + if err != nil { + log.Println("failed to get punchcard for did", "did", ident.DID.String(), "err", err) + } + profileAvatarUri := s.GetAvatarUri(ident.Handle.String()) s.pages.ProfilePage(w, pages.ProfilePageParams{ LoggedInUser: loggedInUser, @@ -141,6 +154,7 @@ func (s *State) profilePage(w http.ResponseWriter, r *http.Request) { Followers: followers, Following: following, }, + Punchcard: punchcard, ProfileTimeline: timeline, }) } diff --git a/cmd/punchcardPopulate/main.go b/cmd/punchcardPopulate/main.go new file mode 100644 index 0000000..2159762 --- /dev/null +++ b/cmd/punchcardPopulate/main.go @@ -0,0 +1,49 @@ +package main + +import ( + "database/sql" + "fmt" + "log" + "math/rand" + "time" + + _ "github.com/mattn/go-sqlite3" +) + +func main() { + db, err := sql.Open("sqlite3", "./appview.db") + if err != nil { + log.Fatal("Failed to open database:", err) + } + defer db.Close() + + const did = "did:plc:qfpnj4og54vl56wngdriaxug" + + now := time.Now() + start := time.Date(now.Year(), 1, 1, 0, 0, 0, 0, time.UTC) + + tx, err := db.Begin() + if err != nil { + log.Fatal(err) + } + stmt, err := tx.Prepare("INSERT INTO punchcard (did, date, count) VALUES (?, ?, ?)") + if err != nil { + log.Fatal(err) + } + defer stmt.Close() + + for day := start; !day.After(now); day = day.AddDate(0, 0, 1) { + count := rand.Intn(16) // 0–5 + dateStr := day.Format("2006-01-02") + _, err := stmt.Exec(did, dateStr, count) + if err != nil { + log.Println("Failed to insert for date %s: %v", dateStr, err) + } + } + + if err := tx.Commit(); err != nil { + log.Fatal("Failed to commit:", err) + } + + fmt.Println("Done populating punchcard.") +} diff --git a/tailwind.config.js b/tailwind.config.js index 42351a3..8dbdb54 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -67,6 +67,10 @@ module.exports = { }, }, }, + gridTemplateColumns: { + '14': 'repeat(14, minmax(0, 1fr))', + '28': 'repeat(28, minmax(0, 1fr))', + } }, }, plugins: [require("@tailwindcss/typography")], -- 2.43.0