Skip to content

Conversation

@cdzombak
Copy link
Contributor

@cdzombak cdzombak commented Nov 18, 2025

This PR:

  • Fixes a bug wherein any embedded gallery limit parameter other than "no limit" resulted in only one photo being returned
  • Adds video support to embedded galleries

Summary by CodeRabbit

Release Notes

  • New Features
    • Added video playback support to embedded photo galleries with play indicator overlays
    • Video items now display duration and metadata (frame rate, ISO) in full-screen view
    • Video controls (play, pause, volume) available when viewing videos

cdzombak and others added 2 commits November 18, 2025 17:16
Add full video support to the embed widget, allowing videos to be displayed
with play icon overlays on thumbnails and native HTML5 video playback in the
lightbox.

Changes:
- Add is_video and duration fields to EmbedPhotoResource
- Display play icon overlay on video thumbnails in all layouts
- Implement video player in lightbox with autoplay
- Show video-specific metadata (duration and framerate) instead of photo EXIF
- Update TypeScript types to support video content
- Fix size variants type to allow nullable original for videos

Videos now work seamlessly in embedded galleries across all layout modes
(justified, masonry, grid, square, filmstrip) with proper metadata display.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Fixed variable reference error in EmbededRequest that caused only 1 photo
to be returned regardless of the selected limit (6, 12, 18, 30, etc.).

The bug was on line 69 where the code incorrectly cast $this->limit
instead of the local $limit variable, causing (int) null to equal 0,
which always evaluated to max(1, min(0, 500)) = 1.

Issue: embedded galleries only showed one item when a specific photo count was selected.
Fix: now correctly cast $limit variable to properly apply user-selected limits.
@cdzombak cdzombak requested a review from a team as a code owner November 18, 2025 22:23
@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

📝 Walkthrough

Walkthrough

This pull request adds video support to the embed widget system. Changes include modifying limit validation in embed requests, adding video metadata properties to photo resources, introducing video overlay UI components, implementing video playback in the lightbox viewer, updating TypeScript types to support video fields, and adjusting utility function signatures for nullable size variants.

Changes

Cohort / File(s) Summary
Backend Request & Resource Updates
app/Http/Requests/Embed/EmbededRequest.php, app/Http/Resources/Embed/EmbedPhotoResource.php
Modified limit validation to use incoming request value directly; added is_video (bool) and duration (?string) properties to photo resource; integrated video duration calculation using seconds-to-HMS conversion from Helpers; instantiated SizeVariantsResouce within constructor.
TypeScript Type Definitions
resources/js/embed/types.ts
Added is_video: boolean and duration: string | null fields to Photo interface; changed size_variants.original from fixed object shape to nullable SizeVariantData | null.
Vue Component Updates
resources/js/embed/components/EmbedWidget.vue, resources/js/embed/components/Lightbox.vue
Added conditional video overlay with play icon SVG to main viewer and thumbnails; implemented video element rendering in lightbox with autoplay/controls; separated EXIF display logic for photos vs. videos; added video-specific metadata section for duration/fps/ISO; introduced getVideoUrl() helper.
Utility Function Signatures
resources/js/embed/utils/columns.ts
Updated getSafeDimensions() signature to accept size_variants.original as nullable optional object instead of required shape; logic remains compatible via optional chaining.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Backend changes: Verify limit validation logic in EmbededRequest and video duration calculation using Helpers::secondsToHMS in EmbedPhotoResource
  • Type safety: Confirm nullable original size variant handling throughout TypeScript codebase and optional chaining implementation in columns.ts
  • Frontend video rendering: Validate conditional video element rendering, overlay positioning, and metadata display logic across both EmbedWidget and Lightbox components
  • Data flow: Ensure is_video and duration properties propagate correctly from backend resources through frontend components

Poem

🐰 Hop hop, the videos play!
With overlays bright as the day,
Play icons gleam, duration shown,
In lightbox and grid, seeds are sown,
Video support has come to stay! 🎬✨

Pre-merge checks

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
resources/js/embed/components/EmbedWidget.vue (1)

60-66: Consider extracting the play icon SVG to reduce duplication.

The same play icon SVG markup is repeated in three locations (filmstrip main viewer, filmstrip thumbnails, and regular grid). While this works fine, extracting it to a reusable component or constant would improve maintainability.

Example approach:

<template>
  <div v-if="isVideo" class="lychee-embed__video-overlay">
    <PlayIcon />
  </div>
</template>

Or define the SVG as a constant if you prefer to keep it inline.

Also applies to: 127-133, 165-171

app/Http/Resources/Embed/EmbedPhotoResource.php (1)

42-45: Add clarifying comment to EXIF export for video duration in aperture field.

The frontend correctly protects against displaying video duration as aperture via the !is_video check (Lightbox.vue lines 95–101). However, the EXIF export at line 57 of EmbedPhotoResource.php exposes the aperture field for all photos and videos without documenting that it contains duration in seconds for videos. To prevent future confusion and improve API transparency, add a clarifying comment above the EXIF array noting this dual-purpose behavior:

// For videos, aperture contains duration in seconds (see line 43)
$this->exif = [
    'make' => $photo->make,
    'model' => $photo->model,
    'lens' => $photo->lens,
    'iso' => $photo->iso,
    'aperture' => $photo->aperture, // Dual-purpose: f-stop for photos, duration (seconds) for videos
    'shutter' => $photo->shutter,
    'focal' => $photo->focal,
    'taken_at' => $photo->taken_at?->toIso8601String(),
];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05d6463 and 5199268.

📒 Files selected for processing (6)
  • app/Http/Requests/Embed/EmbededRequest.php (1 hunks)
  • app/Http/Resources/Embed/EmbedPhotoResource.php (3 hunks)
  • resources/js/embed/components/EmbedWidget.vue (4 hunks)
  • resources/js/embed/components/Lightbox.vue (4 hunks)
  • resources/js/embed/types.ts (2 hunks)
  • resources/js/embed/utils/columns.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-14T10:17:35.082Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3616
File: app/Actions/Album/PositionData.php:38-39
Timestamp: 2025-08-14T10:17:35.082Z
Learning: PositionDataResource uses toPhotoResources() method to convert photos to PhotoResource instances, and PhotoResource accesses the tags relationship ($photo->tags->pluck('name')->all()), making the 'tags' eager-loading in PositionData necessary to avoid N+1 queries.

Applied to files:

  • app/Http/Resources/Embed/EmbedPhotoResource.php
🧬 Code graph analysis (1)
app/Http/Resources/Embed/EmbedPhotoResource.php (1)
app/Assets/Helpers.php (1)
  • secondsToHMS (135-144)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: 3️⃣ PHP dist / 8.4 - postgresql
  • GitHub Check: 3️⃣ PHP dist / 8.3 - sqlite
  • GitHub Check: 3️⃣ PHP dist / 8.4 - sqlite
  • GitHub Check: 3️⃣ PHP dist / 8.3 - postgresql
  • GitHub Check: 2️⃣ PHP tests / 8.4 - sqlite -- Install
  • GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Feature_v2
  • GitHub Check: 2️⃣ PHP tests / 8.4 - postgresql -- Install
  • GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- ImageProcessing
  • GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- ImageProcessing
  • GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Webshop
  • GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Install
  • GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Unit
  • GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Webshop
  • GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Unit
  • GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Unit
  • GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- ImageProcessing
  • GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Feature_v2
  • GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Install
  • GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Webshop
  • GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Feature_v2
🔇 Additional comments (8)
app/Http/Requests/Embed/EmbededRequest.php (1)

69-69: Excellent bug fix!

This correctly fixes the gallery limit bug mentioned in the PR title. The original code was incorrectly using $this->limit (which is null at this point) instead of the local $limit variable, causing the limit validation to fail.

resources/js/embed/types.ts (2)

99-100: LGTM: Clean addition of video metadata fields.

The is_video and duration fields align well with the backend EmbedPhotoResource and enable the video support features throughout the UI.


109-109: Good consistency improvement for size variant types.

Changing original from a nested object to SizeVariantData | null makes it consistent with other size variants. The codebase already handles this correctly with optional chaining in getSafeDimensions (columns.ts) and getVideoUrl (Lightbox.vue).

resources/js/embed/utils/columns.ts (1)

175-175: LGTM: Type signature update aligns with nullable original variant.

The signature change correctly supports the updated original type from types.ts. Making width and height optional provides additional safety, and the function already handles missing dimensions with fallback logic.

resources/js/embed/components/EmbedWidget.vue (1)

636-664: LGTM: Well-implemented video overlay styling.

The CSS provides good visual feedback with hover transitions and responsive sizing constraints. The pointer-events: none ensures clicks pass through to the underlying elements correctly.

resources/js/embed/components/Lightbox.vue (3)

40-52: LGTM: Clean video element implementation.

The video element correctly uses controls and autoplay for good UX, and properly handles the loadeddata event for videos (vs. load for images).

Minor note: The error handler at line 318 displays "Failed to load image" which would be shown for videos too. This is a very minor UX inconsistency.


219-225: LGTM: Clean video URL helper.

The getVideoUrl function correctly returns the original size variant URL for videos, ensuring best quality playback. The optional chaining and empty string fallback handle missing variants safely.


114-130: Video metadata section correctly implements documented field reuse pattern.

The focal field reuse for framerate is intentionally documented in the Photo model (lines 350-355) with an acknowledgment that it should be refactored. The Vue component correctly displays this field with the "fps" suffix for videos (line 126-128), and the aperture field similarly stores duration for videos.

The conditional rendering properly separates photo EXIF from video metadata using is_video checks.

@cdzombak
Copy link
Contributor Author

You can test this change live at https://www.dzombak.com/blog/2025/11/beavers-in-dexter-mi/ .

@ildyria ildyria enabled auto-merge (squash) November 20, 2025 07:47
@ildyria ildyria merged commit f2473b7 into LycheeOrg:master Nov 20, 2025
93 of 101 checks passed
@codecov
Copy link

codecov bot commented Nov 20, 2025

Codecov Report

❌ Patch coverage is 80.00000% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 90.41%. Comparing base (05d6463) to head (5199268).
⚠️ Report is 2 commits behind head on master.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants