Skip to content

74 more controls for media in lightbox#132

Merged
Darkkal merged 24 commits into
masterfrom
74-more-controls-for-media-in-lightbox
Jul 23, 2026
Merged

74 more controls for media in lightbox#132
Darkkal merged 24 commits into
masterfrom
74-more-controls-for-media-in-lightbox

Conversation

@Darkkal

@Darkkal Darkkal commented Jul 22, 2026

Copy link
Copy Markdown
Owner

This PR will contain changes for all sub issues of #74. Walkthroughs of each implemented sub issue will be commented after committing. Sub issues will be closed as related commits are verified

@Darkkal Darkkal linked an issue Jul 22, 2026 that may be closed by this pull request
@Darkkal

Darkkal commented Jul 22, 2026

Copy link
Copy Markdown
Owner Author

[Walkthrough] Issue #121: Lightbox settings for new controls

Sub-issue 1 of parent issue #74More controls for media in lightbox.

Summary of Changes

We implemented configuration settings for all subsequent lightbox features in issue #74 under a new "Lightbox Settings" section on the App Settings tab in /settings.

1. Data Model & Defaults (src/types/settings.ts)

Added 6 new fields to AppSettings interface and DEFAULT_SETTINGS.app:

lightboxFitMode: "fitBoth" | "fitWidth" | "fitHeight"; // default: "fitBoth"
lightboxZoomMin: number;                               // default: 25 (range: 10-100%)
lightboxZoomMax: number;                               // default: 500 (range: 100-1000%)
lightboxZoomStep: number;                              // default: 25 (range: 5-100%)
lightboxAutoHideControls: boolean;                     // default: false
lightboxAutoHideDelay: number;                        // default: 3 (range: 1-30s)

2. Settings UI Page (src/app/settings/page-client.tsx)

  • Rendered a new "Lightbox Settings" section under the App Settings tab, positioned immediately after "Playback & Media".
  • Added form controls:
    • Default Fit Mode dropdown (Fit Both (Default), Fit Width, Fit Height)
    • Minimum Zoom Level (%) number input with range limits (10–100%)
    • Maximum Zoom Level (%) number input with range limits (100–1000%)
    • Zoom Step Increment (%) number input with range limits (5–100%)
    • Auto-Hide Lightbox Controls toggle switch
    • Auto-Hide Inactivity Delay (seconds) number input (disabled when auto-hide switch is off)
  • Form validation in handleSubmit:
    • Enforces min/max numerical bounds for zoom inputs & delay
    • Validates lightboxZoomMin <= lightboxZoomMax

3. Test Coverage

  • Added unit test in tests/unit/settings-defaults.test.ts verifying default values in DEFAULT_SETTINGS.app.
  • Added Playwright E2E tests in tests/settings.spec.ts:
    • Renders Lightbox Settings section and controls
    • Handles auto-hide toggle state (disabling delay input when off)
    • Validates zoom bound ranges and min > max rule
    • Modifies, saves, reloads, and verifies persistence of Lightbox settings across page loads

Verification Results

Unit Tests

npm run test:unit
# Output: 16 passed (16 files, 159 tests)

E2E Tests (Playwright)

docker compose -f compose.test.yaml up --build -d
npx playwright test tests/settings.spec.ts
# Output: 10 passed (10.9s)

@Darkkal

Darkkal commented Jul 22, 2026

Copy link
Copy Markdown
Owner Author

The issue with the validation message ordering has been resolved and verified:

Root Cause: The validation in page-client.tsx was checking lightboxZoomMin > 100 before checking lightboxZoomMin > lightboxZoomMax. For a test input of zoomMin = 800, the range check was triggered first, returning "Lightbox minimum zoom must be between 10% and 100%." instead of "Lightbox minimum zoom cannot be greater than maximum zoom.".

Fix Applied: Reordered handleSubmit validation checks in
src/app/settings/page-client.tsx
to evaluate min > max first before validating individual parameter bounds.

Verification: Re-built the test container and ran npx playwright test tests/settings.spec.ts --project=chromium-serial --no-deps. All 10/10 tests passed in 5.2s.

@Darkkal Darkkal mentioned this pull request Jul 22, 2026
7 tasks
@Darkkal

Darkkal commented Jul 22, 2026

Copy link
Copy Markdown
Owner Author

[Walkthrough] Issue #122: Full-height navigation zones in lightbox

Sub-issue 2 of parent issue #74More controls for media in lightbox.

Summary of Changes

Replaced the circular 50x50px navigation buttons inside the lightbox media area with full-height click zones (.navZonePrev and .navZoneNext) spanning top to bottom along the left and right boundaries of .mainArea.

1. Lightbox UI Component (src/components/Lightbox.tsx)

  • Imported ChevronLeft and ChevronRight from lucide-react.
  • Replaced .navButton circular elements with .navZone.navZonePrev and .navZone.navZoneNext buttons.
  • Rendered <ChevronLeft size={32} /> inside .navZonePrev and <ChevronRight size={32} /> (or loading spinner when isPageLoading) inside .navZoneNext.
  • Preserved existing click handlers, loading state spinner, keyboard shortcuts, and swipe gestures.

2. Styling System (src/components/Lightbox.module.css)

  • Created .navZone, .navZonePrev, and .navZoneNext classes:
    • position: absolute; top: 0; bottom: 0; height: 100%; width: 60px; z-index: 5;
    • Transparent background by default with subtle chevron indicator (opacity: 0.3).
    • On hover, reveals a linear gradient (linear-gradient(to right/left, hsl(0 0% 0% / 0.4), transparent)) and brightens the chevron (opacity: 1, transform: scale(1.15)).
  • Added mobile @media (max-width: 767px) rule setting .navZone width to 44px (satisfying WCAG touch target guidelines).

3. Test Coverage (tests/gallery.spec.ts)

  • Added Playwright E2E test lightbox navigation zones span full-height and handle navigation verifying:
    • .navZoneNext and .navZonePrev elements exist and render.
    • Bounding box height of .navZone matches full height of .mainArea.
    • Next zone click navigates to next item and reveals prev zone.

Verification Results

Unit Tests

npm run test:unit
# Output: 16 passed (16 files, 159 tests)

E2E Tests (Playwright)

docker compose -f compose.test.yaml up --build -d
npx playwright test tests/gallery.spec.ts --project=chromium-parallel --no-deps
# Output: 8 passed (8/8 tests passed)

@Darkkal

Darkkal commented Jul 22, 2026

Copy link
Copy Markdown
Owner Author

[Walkthrough] Issue #123: Show/hide lightbox controls with auto-hide

Sub-issue 3 of parent issue #74More controls for media in lightbox.

Summary of Changes

Implemented a unified show/hide controls framework (controlsVisible master toggle state) inside the Lightbox with auto-hide interactivity timer support and sidebar state coupling.

1. Page Data Flow & Props (page.tsx & page-client.tsx)

  • Passed autoHideControls and autoHideDelay from server page components (/gallery and /timeline) down to GalleryPageClient, TimelinePageClient, and into <Lightbox>.

2. Lightbox Component (src/components/Lightbox.tsx)

  • Added controlsVisible (master toggle state, default true) and isAutoHidden state.
  • Persistent Toggle Button: Rendered persistent Eye/EyeOff toggle button at top-left (.persistentToggleBtn). Button remains visible (z-index: 300) even when controls are hidden, fading to 0.3 opacity during auto-hide.
  • Image Click Toggle: Single-click on image calls setControlsVisible((prev) => !prev). Single-click on video retains e.stopPropagation() and preserves video controls.
  • Keyboard Shortcuts:
    • H key: toggles controlsVisible.
    • Escape key: two-step behavior. If !controlsVisible, first press reveals controls; if controlsVisible, closes lightbox.
  • Inactivity Auto-Hide Timer: Added full suite of user activity event listeners (pointerdown, mousedown, click, pointermove, mousemove, keydown, touchstart, touchend, touchmove, wheel) to ensure clicking next/prev buttons without moving the mouse and mobile touch controls continuously reset the auto-hide timer. Also resets timer whenever active media item (item.id) changes.
  • Sidebar Decoupled: Post information sidebar (.sidebar) visibility is strictly controlled by showInfo (deliberately toggled by tapping the image or clicking the info button ). Auto-hiding or toggling overlay controls no longer auto-hides the post information sidebar.

3. Styling & Transitions (src/components/Lightbox.module.css)

  • Added .persistentToggleBtn and .autoHidden styles.
  • Added smooth opacity and pointer-events transitions for [data-visible="false"] on .controls and .navZone.

4. Tests & Verification (tests/gallery.spec.ts)

  • Added E2E Playwright test lightbox show/hide controls toggles, H key shortcut, and two-step Escape verifying:
    • Persistent toggle button click hides controls + sidebar.
    • H key toggles controls.
    • First Escape press restores controls; second Escape press closes lightbox.

Verification Results

Unit Tests

npm run test:unit
# Output: 16 passed (16 files, 159 tests)

E2E Tests (Playwright)

docker compose -f compose.test.yaml up --build -d
npx playwright test tests/gallery.spec.ts --project=chromium-parallel --no-deps
# Output: 9 passed (9/9 tests passed)

@Darkkal

Darkkal commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

[Walkthrough] Issue #124: Lightbox image fit mode & zoom controls

Sub-issue 4 of parent issue #74More controls for media in lightbox.

Summary of Changes

Implemented image fit modes, interactive zoom controls, mouse wheel zooming, and drag panning inside the Lightbox.

1. Page Data Flow & Settings Props

  • Passed lightboxFitMode, lightboxZoomMin, lightboxZoomMax, and lightboxZoomStep settings from /gallery and /timeline server pages through client components into <Lightbox>.

2. Fit Modes & Zoom Controls (src/components/Lightbox.tsx & src/components/Lightbox.module.css)

  • Fit Mode Cycle Button:
    • Toggles between fitBoth (contain), fitWidth (scroll height), and fitHeight (scroll width).
    • Renders dynamic icons (Maximize2, MoveHorizontal, MoveVertical).
  • Interactive Zoom Buttons:
    • + (Zoom In) and - (Zoom Out) buttons respecting configured zoomMin, zoomMax, and zoomStep.
    • Reset indicator button showing current zoom percentage (e.g. 100%, 125%). Clicking resets zoom to 100% and clears pan offset.
  • Mouse Wheel Zoom & Bounded Drag Panning:
    • Scrolling mouse wheel over media wrapper zooms in/out.
    • Fit Width Mode: Panning is strictly locked to vertical (Y-axis only). Panning is clamped so top and bottom fitted edges never leave the screen.
    • Fit Height Mode: Panning is strictly locked to horizontal (X-axis only). Panning is clamped so left and right fitted edges never leave the screen.
    • Fit Both Mode (100% Zoom): Panning is disabled (panOffset = { x: 0, y: 0 }).
    • Zoom Mode (zoom > 1.0): Panning is enabled in both axes, clamped so an image edge can only pan up to the screen center (half the screen).
    • Added drag distance check so dragging to pan does not accidentally toggle post info sidebar.
  • Controls Layout Protection:
    • Applied min-width: 0; min-height: 0; overflow: hidden; to .mainArea and max-width: calc(100% - 80px); flex-wrap: wrap; to .controls in Lightbox.module.css, ensuring wide images in fitHeight mode never push controls off screen.
  • Navigation Reset:
    • Navigating between media items automatically resets zoom to 100% and clears pan offsets.

3. Tests & Verification (tests/gallery.spec.ts)

  • Added E2E Playwright test lightbox fit mode cycling and zoom controls verifying:
    • Fit mode cycle button title updates when clicked.
    • Zoom In, Zoom Out, and Reset buttons adjust and display zoom level indicator text.

Verification Results

Unit Tests

npm run test:unit
# Output: 16 passed (16 files, 159 tests)

E2E Tests (Playwright)

docker compose -f compose.test.yaml up --build -d
npx playwright test tests/gallery.spec.ts --project=chromium-parallel --no-deps
# Output: 10 passed (10/10 tests passed)

@Darkkal

Darkkal commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

[Walkthrough] Issue #125 (Mobile pinch-to-zoom)


Summary of Accomplishments

1. Image Fit Modes & Zoom Reset (src/components/Lightbox.tsx)

  • Fit Mode Cycling:
    • Control button cycles through fitBoth (contain), fitWidth (fit width / scroll height), and fitHeight (fit height / scroll width).
    • Changing fit mode automatically resets zoom to 1.0 (100%) and clears panOffset to { x: 0, y: 0 }.
  • Interactive Zoom Buttons:
    • Zoom In (+), Zoom Out (-), and Reset indicator (100%) buttons respecting user settings bounds.
    • Reset button displays current zoom percentage (e.g. 125%) and resets zoom level to 100% when clicked.

2. Mouse & Touch Drag Panning with Directional Boundaries (src/components/Lightbox.tsx)

  • Directional Panning Locks:
    • Fit Width Mode: Panning is strictly locked to vertical movement (Y-axis only). Clamped so top and bottom fitted edges never leave the viewport bounds.
    • Fit Height Mode: Panning is strictly locked to horizontal movement (X-axis only). Clamped so left and right fitted edges never leave the viewport bounds.
    • Fit Both Mode (zoom = 1.0): Panning is disabled (panOffset = { x: 0, y: 0 }).
    • Zoom Mode (zoom > 1.0): Panning is enabled in both axes, clamped so an image edge can only pan up to the screen center (half the viewport).

3. Mobile Multi-Touch Pinch-to-Zoom & Viewport Zoom Prevention (Lightbox.tsx & Lightbox.module.css)

  • Two-Finger Touch Pinch Gesture:
    • Detects two active touch points (e.touches.length === 2) on image elements.
    • Measures real-time finger distance dist = Math.hypot(t1.clientX - t2.clientX, t1.clientY - t2.clientY) to scale zoom level smoothly between zoomMin and zoomMax.
    • Centers zoom scaling dynamically at the finger midpoint (midX, midY).
  • Mobile Viewport Zoom Prevention:
    • Applied touch-action: none; on .overlay, .mediaWrapper, and .image in Lightbox.module.css.
    • Attached a non-passive { passive: false } touchmove listener in Lightbox.tsx calling e.preventDefault() on multi-touch. This prevents Safari/Chrome mobile browsers from zooming the outer web application.
  • Swipe Navigation & Pan Isolation:
    • Single-finger swipe-to-navigate is disabled while pinching or when isPannable.
    • Resumes swipe-to-navigate when zoom returns to 100% in fitBoth mode.
    • Videos and text items are excluded from pinch gestures.

4. Layout Protection & System Defaults

  • Controls Layout Protection:
    • Applied min-width: 0; min-height: 0; overflow: hidden; to .mainArea and max-width: calc(100% - 80px); flex-wrap: wrap; justify-content: flex-end; to .controls in Lightbox.module.css, ensuring wide images in fitHeight mode never push controls off screen.
  • Adjusted System Default Zoom Settings:
    • Updated default zoom bounds to 50% min, 200% max, and 25% step across DEFAULT_SETTINGS (src/types/settings.ts), page fallbacks, and <Lightbox>.

Verification Results

Unit Tests

npm run test:unit
# Output: 16 passed (16 files, 159 tests)

E2E Tests (Playwright)

docker compose -f compose.test.yaml up --build -d
npx playwright test --project=chromium-parallel --no-deps
# Output: 34 passed, 2 skipped (34/34 executed tests passed)

@Darkkal Darkkal mentioned this pull request Jul 23, 2026
9 tasks
@Darkkal

Darkkal commented Jul 23, 2026

Copy link
Copy Markdown
Owner Author

Code Review: PR #132

PR: feat(lightbox): implement fit modes, zoom controls, navigation zones, and touch gestures
Branch: 74-more-controls-for-media-in-lightboxmaster
Resolves: Issue #74
Scope: 11 files changed, +1,363 / -54 lines across 23 commits


Overview

This PR delivers a substantial feature set to the Lightbox media viewer: configurable fit modes, zoom controls, full-height navigation zones, auto-hide controls, mouse/touch drag panning with directional boundaries, and multi-touch pinch-to-zoom with browser zoom prevention. The commit history is clean, incremental, and follows Conventional Commits throughout.


✅ Strengths

Commit Discipline

  • 23 well-scoped commits following feat, fix, test, refactor conventions — each is a self-contained logical unit.
  • Test commits consistently follow their corresponding feature commits.

Feature Completeness

  • Fit mode cycling (fitBothfitWidthfitHeight) with automatic zoom/pan reset.
  • Directional pan boundaries are correctly scoped: Y-only for fitWidth, X-only for fitHeight, none for fitBoth, proportional for zoomed-in.
  • Pinch-to-zoom uses midpoint anchoring for natural gesture feel.
  • Browser viewport zoom prevention is scoped to the Lightbox container (not global), preserving accessibility.
  • Controls overflow prevention via max-width + flex-wrap.

Test Coverage

  • 225 new lines of E2E tests covering navigation zones, controls toggle, fit modes, zoom controls, and multi-touch pinch gestures.
  • 120 new lines of settings E2E tests for lightbox configuration.
  • Unit test coverage for updated default settings values.

⚠️ Issues & Recommendations

1. Component Size — Lightbox.tsx (1,479 lines)

Warning

Lightbox.tsx is now the largest component in the codebase at 1,479 lines. The zoom/pan/gesture logic alone accounts for ~300 lines of handler code (L126–L380). While this isn't blocking, it increases cognitive load for future contributors and makes targeted testing harder.

Recommendation: Consider extracting the zoom/pan/gesture logic into a custom hook (e.g., useZoomPan) in a future refactor issue. This would:

  • Isolate ~300 lines of stateful gesture logic into a testable unit
  • Allow unit-testing pan clamping math independently
  • Keep Lightbox.tsx focused on layout, rendering, and composition

This is not a blocking concern for this PR — the logic is correct and well-commented inline. But it would be worth tracking as a follow-up.


2. clampPanOffset Dependency on DOM Refs (L126–L194)

The clampPanOffset function reads from mediaWrapperRef and imageRef for dimensions. If either ref is null at call time (e.g., during a render cycle where the image hasn't loaded yet), it falls back to window.innerWidth/innerHeight (zoom mode) or returns { x: 0, y: 0 } (fit modes).

const wrapperWidth = wrapper ? wrapper.clientWidth : window.innerWidth;
const wrapperHeight = wrapper ? wrapper.clientHeight : window.innerHeight;
const imgWidth = img ? img.offsetWidth * zoom : wrapperWidth * zoom;

Assessment: The fallback behavior is reasonable and won't cause crashes, but the window.innerWidth fallback in zoom mode could produce a brief visual jump if the wrapper dimensions differ significantly from the window. This is a minor edge case.

Recommendation: No action needed — the code handles it gracefully.


3. !important Overrides in CSS Fit Modes (L69–L83)

.fitWidth {
  width: 100% !important;
  max-width: 100% !important;
  height: auto !important;
  max-height: none !important;
}

Assessment: The !important declarations are necessary here because the base .image class sets max-width: 100% and max-height: 100%, and the fit mode classes need to override them conditionally. This is a standard CSS specificity workaround for dynamic class toggling.

Recommendation: Acceptable. If this becomes unwieldy, a future refactor could restructure the base .image class to use CSS custom properties for its constraints, eliminating the need for !important. Not a blocking concern.


4. Inline transform Style Computation (L862)

style={{
  transform: `scale(${zoom}) translate(${panOffset.x / zoom}px, ${panOffset.y / zoom}px)`,
  transition: isDragging ? "none" : "transform 0.15s ease-out",
}}

Assessment: Dividing panOffset by zoom inside the translate() compensates for the scale() transform. This is a correct approach — CSS transforms apply in right-to-left order, so scale is applied first to the element, then translate moves it in the scaled coordinate space. Dividing by zoom brings the translation back to screen-pixel space.

Minor note: This computation runs on every render during drag. For smooth 60fps panning, this is fine since React batches state updates, but it's worth knowing this is a hot path.


5. Non-Passive Touch Event Listener (L623–L640)

container.addEventListener("touchmove", preventBrowserZoom, {
  passive: false,
});

Assessment: This is correct and necessary. Mobile browsers default touchmove listeners to passive: true as a performance optimization, which prevents preventDefault() from working. The passive: false option is required to actually block browser-level pinch zoom. The listener is properly scoped to the Lightbox overlay container (not document or window) and is cleaned up on unmount.

Recommendation: This is well-implemented. The scoped approach (only active while Lightbox is mounted) avoids accessibility issues that a global user-scalable=no meta tag would cause.


6. dragDistanceRef Threshold for Click vs. Drag (L875)

if (dragDistanceRef.current > 3) {
  dragDistanceRef.current = 0;
  return;
}

Assessment: The threshold of 3 is a count of mousemove events, not pixels. This works but is somewhat indirect — a fast drag could produce fewer events than a slow one. In practice, this threshold is sufficient for distinguishing click from drag because any intentional drag will produce significantly more than 3 move events.

Recommendation: Acceptable as-is. A pixel-based threshold would be more precise but is unnecessary complexity for this use case.


7. Swipe-vs-Pan Interaction (L588–L619)

The overlay-level handleTouchEnd correctly bails out when isPannable is true, preventing swipe navigation from interfering with pan gestures:

if (isPinchingRef.current || isPannable) {
  touchStartX.current = null;
  touchStartY.current = null;
  return;
}

Assessment: This is correct — when the image is pannable (zoomed or in a directional fit mode), touch gestures should pan rather than navigate. The 50px swipe threshold is reasonable.


📋 Documentation Gap

Important

No changes to ARCHITECTURE.md or CONTRIBUTING.md were made in this PR. Per the project's own documentation rules (CONTRIBUTING.md §2.9):

"When implementing new features, modifications to subsystems, or introducing new architectural patterns, you MUST update both ARCHITECTURE.md and CONTRIBUTING.md to reflect these changes."

What's Missing

ARCHITECTURE.md — Lightbox.tsx entry

The current entry reads:

│   ├── Lightbox.tsx            #   Full-screen media viewer

This one-liner no longer captures the component's complexity. It should document:

  • Zoom & Pan State Machine: The zoom, panOffset, currentFitMode state trio and how they interact
  • Gesture Handler Architecture: Mouse drag, touch pan, and multi-touch pinch-to-zoom with midpoint anchoring
  • Pan Boundary Logic: Directional clamping based on fit mode (clampPanOffset)
  • Browser Zoom Prevention: Non-passive touchmove listener scoped to overlay

ARCHITECTURE.md — Key Design Decisions (§8)

Two design decisions should be documented:

  1. Scoped browser zoom prevention over global user-scalable=no

    • Rationale: Global user-scalable=no violates WCAG accessibility guidelines. The Lightbox instead uses a non-passive touchmove listener attached only to the overlay container, preventing browser pinch zoom only while the viewer is open.
  2. Pan offset divided by zoom in CSS transform

    • Rationale: CSS transforms apply right-to-left (scale then translate). Dividing pan offset by zoom factor converts screen-pixel drag distance back to pre-scale coordinate space, ensuring 1:1 finger-to-pixel tracking during drag.

CONTRIBUTING.md — Settings Interface Documentation

The settings.ts type table at §1.7 lists:

| settings.ts | SystemSettings, AppSettings definitions |

This should mention the new LightboxSettings fields (lightboxFitMode, lightboxZoomMin/Max/Step, lightboxAutoHideControls/Delay) as part of AppSettings, since future contributors need to know these exist when modifying settings behavior.


🧪 Test Coverage Assessment

Area E2E Unit Notes
Navigation zones Height verification, click navigation
Controls toggle + H key Two-step Escape tested
Fit mode cycling Title attribute assertions
Zoom in/out/reset Percentage text assertions
Pinch-to-zoom Multi-touch dispatch with identifiers
Settings defaults 50/200/25 values validated
Settings UI validation Min/max boundary checks
Pan boundaries Not directly tested
Browser zoom prevention Hard to test in E2E without real device

Note

Pan boundary clamping logic (clampPanOffset) has no direct test coverage. This is another argument for extracting it into a useZoomPan hook — the pure math functions could then have lightweight unit tests for each boundary mode.


Verdict

Approve with documentation follow-up. The implementation is solid, well-tested, and the commit history is exemplary. The only actionable item before merge is updating ARCHITECTURE.md and CONTRIBUTING.md per the project's own documentation synchronization rules.

Recommended Actions

Priority Action Blocking?
High Update ARCHITECTURE.md Lightbox entry + add Key Design Decisions Yes — per §2.9 rule
High Update CONTRIBUTING.md settings type documentation Yes — per §2.9 rule
Low Track useZoomPan hook extraction as a future issue No
Low Track pan boundary unit tests as a future issue No

@Darkkal
Darkkal merged commit c155043 into master Jul 23, 2026
6 checks passed
@Darkkal
Darkkal deleted the 74-more-controls-for-media-in-lightbox branch July 23, 2026 05:41
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.

More controls for media in lightbox

1 participant