feat: Implement birthday mode with confetti animations and a celebrat…#611
feat: Implement birthday mode with confetti animations and a celebrat…#611aviv926 wants to merge 4 commits into
Conversation
WalkthroughAdds a birthday celebration feature: new config flag, backend birthday detection and age calculation, view-data plumbing, frontend overlay/CSS, and confetti animation triggered at initial display and on image swaps. Changes
Sequence Diagram(s)sequenceDiagram
participant Config as Config
participant Backend as Backend (Home handler)
participant Check as BirthdayCheck
participant Utils as Utils (CalculateAge)
participant Template as Template Renderer
participant Frontend as Frontend (Kiosk)
participant UI as Birthday UI & Confetti
Config->>Backend: birthday flag enabled
Backend->>Check: Request today's birthdays
Check->>Check: Filter people by date
Check->>Utils: Compute ages
Check-->>Backend: Return people + ages
Backend->>Template: Populate ViewData (BirthdayModeActive, People, Ages)
Template->>Frontend: Render kioskData with birthday fields
Frontend->>UI: Build overlay and display names/ages
Frontend->>UI: Trigger initial confetti
Frontend->>UI: Trigger confetti on each image swap (htmx afterSwap)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (7)
config.schema.json (1)
50-52: Adddefault/descriptionto the newbirthdayschema field for clarity.
Right now it’s a bare boolean; adding adefault: false(and a short description) makes tooling/UX nicer and keeps parity withconfig.example.yaml.config.example.yaml (1)
20-23: Consider adding a one-line comment describing whatbirthdaydoes.
This keeps the example config self-explanatory (e.g., “show birthday overlay/confetti for people whose birthday is today (requires named people in Immich)”).internal/common/common.go (1)
103-108: ViewData additions look good; consider default initialisation forBirthdayAges.
IfBirthdayAgesis leftnil, some consumers may need extra guarding; initialising tomap[string]int{}where ViewData is constructed can simplify usage.frontend/src/ts/kiosk.ts (3)
51-74: Consider making the new birthday fields optional (or defaulting them) to avoid runtimeundefinedsurprises.
BecausekioskDatais parsed from JSON, older payloads or partial data can produceundefinedeven if the TS type says otherwise.Also applies to: 81-85
137-140: Avoid unconditional logging of the fullkioskDatain production.
This can be noisy and may inadvertently expose config-derived information; consider gating behinddebug/debugVerbose.
279-307: Confetti loop: consider clamping origins and respecting reduced motion.
origin.ycan go negative (Math.random() - 0.2); if that’s intended, fine, but if not, clamp to[0,1]. Also consider skipping the animation whenmatchMedia('(prefers-reduced-motion: reduce)')is true.frontend/src/css/birthday.css (1)
17-24: Remove redundant border declarations.Lines 18–20 define
border-image-slice,border-width, and an initialborderproperty, but these are immediately overridden by the solid border declaration on line 23. The comments on lines 21–22 are also unnecessary once the redundant properties are removed.Apply this diff to clean up the CSS:
.birthday-frame { padding: clamp(10px, 2vh, 20px) clamp(15px, 4vw, 40px); - border: 4px solid; - border-image-slice: 1; - border-width: 4px; - border-radius: 15px; /* Note: border-image and border-radius conflict involved, using box-shadow for "border" or pseudo-element is safer for rounded gradient borders */ - /* Let's use a simpler solid gold border with radius for now, or a double border */ border: 5px solid #BF953F; border-radius: 20px; background: rgba(0, 0, 0, 0.6); /* Semi-transparent black background to pop against photos */
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
frontend/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (11)
config.example.yaml(1 hunks)config.schema.json(1 hunks)frontend/src/css/birthday.css(1 hunks)frontend/src/css/kiosk.css(1 hunks)frontend/src/ts/kiosk.ts(5 hunks)internal/common/common.go(1 hunks)internal/config/config.go(1 hunks)internal/routes/routes_asset_helpers.go(3 hunks)internal/routes/routes_home.go(1 hunks)internal/templates/views/views_home.templ(1 hunks)internal/utils/utils.go(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
internal/routes/routes_asset_helpers.go (3)
internal/common/common.go (1)
ViewData(96-108)internal/config/config.go (1)
Config(210-442)internal/immich/immich.go (2)
Person(131-139)BirthDate(122-122)
🔇 Additional comments (7)
frontend/src/css/kiosk.css (1)
29-30: Import change looks fine; just ensure the ordering is intentional.
Ifbirthday.cssrelies on variables/utility classes, this placement should work given the earlier imports.internal/templates/views/views_home.templ (1)
103-105: Wiring new birthday fields into the frontend payload is consistent and clear.
One small robustness note: ifBirthdayAgescan benil, it will serialise asnull; either initialise to{}server-side or ensure the TS access is optional-chained.internal/config/config.go (1)
378-380: LGTM!The Birthday configuration field is properly defined with appropriate tags and a sensible default value of
false. The placement in the struct is logical.internal/routes/routes_asset_helpers.go (4)
31-37: LGTM!The package-level variables for birthday logging are properly defined with a mutex for thread-safe access. This allows for deduplication of birthday log messages across requests.
724-749: LGTM!The
CheckBirthdaysfunction correctly retrieves all named people, filters by matching birth month and day, and handles errors appropriately by silently skipping people without valid birthdates.
781-792: LGTM!The birthday logging deduplication logic correctly uses a mutex for thread safety and prevents duplicate log entries for the same birthday people on the same day. The formatted log message with emoji is a nice touch for birthday celebrations.
794-801: LGTM!The configuration override logic correctly restricts the kiosk to display only birthday people when birthday mode is active. Clearing other buckets (albums, tags, dates, memories) and disabling
RequireAllPeopleensures the birthday celebration takes precedence.
| @@ -1,5 +1,6 @@ | |||
| import DOMPurify from "dompurify"; | |||
| import htmx from "htmx.org"; | |||
| import confetti from "canvas-confetti"; | |||
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
canvas-confetti npm TypeScript import default export types
💡 Result:
Short answer
- Use the community types package: npm i --save-dev @types/canvas-confetti. That provides type declarations for canvas‑confetti. [1][2]
- Import style depends on your tsconfig:
- If you have "esModuleInterop": true (or "allowSyntheticDefaultImports"), use:
import confetti from "canvas-confetti"; - Otherwise use the namespace import:
import * as confetti from "canvas-confetti";
Either works at runtime; the former needs the TS interop flags to avoid type errors. [1][2]
- If you have "esModuleInterop": true (or "allowSyntheticDefaultImports"), use:
If you still get "Could not find a declaration file" or ts(7016)
- Install @types/canvas-confetti (preferred). [1]
- Or add a minimal declaration file (e.g. src/types/canvas-confetti.d.ts) with:
declare module "canvas-confetti";
to silence TypeScript until you add proper types. [3]
References
[1] @types/canvas-confetti (npm / DefinitelyTyped). [2] @types/canvas-confetti package.json (UNPKG). [3] Example fix / declare module guidance (article).
🏁 Script executed:
# Check project configuration files
find . -maxdepth 2 -type f \( -name "tsconfig.json" -o -name "package.json" -o -name "vite.config.*" -o -name "webpack.config.*" \) | head -20Repository: damongolding/immich-kiosk
Length of output: 117
🏁 Script executed:
# Check if canvas-confetti is in dependencies and which types package is used
cd . && (cat package.json 2>/dev/null | jq '.dependencies, .devDependencies' 2>/dev/null || echo "package.json not found")Repository: damongolding/immich-kiosk
Length of output: 51
🏁 Script executed:
# Search for any existing canvas-confetti usage in the codebase
rg "canvas-confetti|confetti" --type ts --type js -B 2 -A 2Repository: damongolding/immich-kiosk
Length of output: 1103
🏁 Script executed:
# Read tsconfig.json to check compiler options
cat -n frontend/tsconfig.jsonRepository: damongolding/immich-kiosk
Length of output: 853
🏁 Script executed:
# Read package.json to check if @types/canvas-confetti is present
cat -n frontend/package.json | head -50Repository: damongolding/immich-kiosk
Length of output: 1858
Install @types/canvas-confetti as a dev dependency.
The import style is correct given "esModuleInterop": true in your TypeScript configuration; however, @types/canvas-confetti is missing from devDependencies in frontend/package.json. Without it, TypeScript will raise a TS7016 error during type checking (which runs in your pnpm test step). Run pnpm add -D @types/canvas-confetti to resolve this.
🤖 Prompt for AI Agents
In frontend/src/ts/kiosk.ts around line 3, TypeScript emits TS7016 because the
package @types/canvas-confetti is not installed; add the missing type
definitions as a dev dependency by running: pnpm add -D @types/canvas-confetti
(or add it to frontend/package.json devDependencies and run pnpm install), then
re-run type checking to ensure the import import confetti from
"canvas-confetti"; is properly typed.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
frontend/src/ts/kiosk.ts (1)
3-3: Ensure@types/canvas-confettiis installed.The import style is correct, but TypeScript will raise TS7016 during type checking if
@types/canvas-confettiis missing fromdevDependencies. This was previously flagged.
🧹 Nitpick comments (1)
frontend/src/ts/kiosk.ts (1)
283-311: Consider respectingprefers-reduced-motionfor accessibility.The confetti implementation is well-structured with throttling and smooth animation. However, users who have enabled
prefers-reduced-motionmay prefer to skip the confetti effects.You can add a check at the start of
triggerConfetti:function triggerConfetti() { + if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) { + return; + } const duration = 3000; const animationEnd = Date.now() + duration;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
frontend/src/ts/kiosk.ts(5 hunks)internal/routes/routes_asset_helpers.go(3 hunks)internal/routes/routes_home.go(1 hunks)internal/utils/utils.go(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- internal/routes/routes_home.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-11-20T10:52:53.804Z
Learnt from: damongolding
Repo: damongolding/immich-kiosk PR: 192
File: frontend/src/ts/clock.ts:1-1
Timestamp: 2024-11-20T10:52:53.804Z
Learning: In the `frontend/src/ts` files of the `immich-kiosk` project, importing `{ format }` from `"date-fns"` causes a TypeScript error. The correct import statement is `import { format } from "date-fns/format";`.
Applied to files:
frontend/src/ts/kiosk.ts
🧬 Code graph analysis (1)
internal/routes/routes_asset_helpers.go (3)
internal/common/common.go (2)
New(28-41)ViewData(96-108)internal/immich/immich.go (3)
New(402-408)Person(131-139)BirthDate(122-122)internal/utils/utils.go (1)
CalculateAge(752-773)
🔇 Additional comments (6)
internal/utils/utils.go (2)
750-773: LGTM! Age calculation correctly addresses previous concerns.The implementation properly handles timezone normalization and Feb 29 birthdays. The logic is clear and correct.
775-784: LGTM! Leap year calculation is correct.The function implements the standard leap year algorithm correctly.
frontend/src/ts/kiosk.ts (1)
212-281: LGTM! Birthday mode implementation addresses previous concerns.The function properly prevents duplicate overlays, scopes event listeners to the kiosk element, and safely handles birthday data with optional chaining. The DOM construction and string formatting logic are correct.
internal/routes/routes_asset_helpers.go (3)
31-37: LGTM! Birthday logging state is properly protected.The package-level variables for birthday logging deduplication are appropriately guarded with a mutex.
724-749: LGTM! Birthday detection logic is correct.The function properly filters named people with birthdays matching today's month and day, handling missing or invalid birthdates gracefully.
762-806: LGTM! Birthday mode logic is correct and uses consistent age calculation.The implementation properly detects birthdays, calculates ages using
utils.CalculateAge(consistent with the codebase standard), and overrides the configuration to display only birthday people. The logging deduplication with mutex is a nice touch.
This PR adds a new feature called "Birthday" which essentially makes a person appear for that day as the birthday person when photos belonging to them are displayed (with the addition of confetti and a frame indicating their name and age)
Important information:
A birthday person will not be displayed if there is no name for them in the immich UI
When 2 people have a birthday on the same day, both their names and photos belonging to them will be displayed.
Summary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings.