feat: Live Slideshow ("Diashow") β fullscreen, auto-updating projector view for live events#646
Open
Luca-Timo wants to merge 18 commits into
Open
feat: Live Slideshow ("Diashow") β fullscreen, auto-updating projector view for live events#646Luca-Timo wants to merge 18 commits into
Luca-Timo wants to merge 18 commits into
Conversation
- 137: events.show_share_token + show_interval_ms/transition/transition_ms - 138: per-event watermark (tri-state, nullable=inherit global), source/ position/opacity/style + color filter columns, and event_types.slideshow_preset JSON so new events inherit a per-type default. Opt-in, no backfill.
- public GET /gallery/:slug/show/:token/session (validates token, mints a slideshow-scoped gallery JWT + sets the per-slug cookie so <img> requests authorize) and /state (cheap settings + photo-count poll). Reuse /photos for the list; skip the view-log for slideshow access so the kiosk does not pollute visitor analytics. - admin slideshow link generate/disable + live style PATCH on events. - event-type slideshow_preset whitelisted in CRUD; create-event seeds the new event's show_* columns from the type preset. - global watermark defaults via PUT /admin/settings/slideshow; watermark cascade (global default -> per-event override) resolving the light/dark/favicon/event logo url.
- /gallery/:slug/show/:token route + SlideshowPage: splash -> fullscreen kiosk, crossfade/cut/slide/kenburns/dip-to-white/dip-to-black transitions, color filters, white/original logo watermark overlay, contain/letterbox, cursor auto-hide, quiet-append of new uploads, live settings poll, and decode-ahead preload (first slide decoded before playback) so transitions do not struggle. - slideshow.service for session/state + shared style types.
- per-event Live Slideshow card on the event detail page: generate/copy/ regenerate/disable the share link + live style (transition, timing, color filter, watermark). - shared SlideshowStyleFields, reused by the per-event card and the per-event- type preset section in the Edit Event Type modal. - global watermark default card on the Event Types page (Settings -> slideshow). - WatermarkSourcePicker: visible logo tiles with previews (light logo / dark-mode logo / favicon / event logo) instead of a blind dropdown. - watermark mode tri-state (inherit/on/off) + white-vs-original style. - supporting service methods + Event/EventType types.
Adds the slideshow.* block (transitions, color filters, watermark mode/style/ source, global defaults) and eventTypes.form.slideshowPreset labels in English and German.
The events table has no updated_at column (only created_at, and no migration adds one), so the slideshow generate/disable/settings endpoints 500'd with 'column "updated_at" does not exist'. Write only the show_* columns, and guard the settings PATCH against an empty update.
Log the failing request (status + body) to the console and show the backend error message in the toast instead of a generic "Error", so failures are diagnosable without server log access.
β¦ngs table
slideshowSettings used settingsService.getSetting, which queries db('settings')
- a table that does not exist in this app (globals live in app_settings). Every
GET /gallery/:slug/show/:token/session and /state therefore threw and returned
500 INTERNAL_ERROR once a valid token resolved. Switch to getAppSetting
(utils/appSettings), which reads app_settings where the slideshow_watermark_*
and branding_* values are actually written.
The slide <img> used maxWidth/maxHeight:100% with no width/height, so it rendered at the photo's intrinsic size (e.g. the 1920px preview) and never scaled up to the projector, leaving black bars all around. Pin the image to 100% x 100% and use object-fit: cover so it fills the whole page.
The flash overlay had no base opacity and the keyframe animation has fill-mode none, so after the first dip it reverted to opacity 1 and stayed opaque between slides β hiding the image, then briefly revealing it on each advance. Set base opacity 0, and swap the image at the flash peak so the cut stays hidden.
β¦ngs tab - New `slideshow` feature flag (backend KNOWN_FLAGS/DEFAULT_FLAGS, frontend FeatureKey + context default, a toggle card under Settings -> Features -> Core). Default off; strictly opt-in. - Move the global watermark defaults off the Event Types page into a dedicated Settings -> Slideshow tab (new SlideshowSettingsPage), shown only when the flag is on. - Gate the per-event Live Slideshow card and the per-event-type preset section behind the flag too (and stop writing a type preset when it's off). - en/de strings for the feature card + settings tab.
β¦size)
The watermark look (logo / position / opacity / style) was configurable in three
places β the global Settings tab, the per-event-type preset, and the per-event
card. Consolidate it to ONE: the global Settings -> Slideshow tab. Per-event and
per-event-type now carry only the watermark MODE (inherit / on / off) β the
override structure β and render with the global look.
- New global "Size (% of screen)" control (slideshow_watermark_size, vmin-based)
so the logo can be scaled; resolved server-side into the watermark payload and
applied to the kiosk <img>.
- Backend slideshowSettings resolves the whole look from app_settings always;
per-event show_watermark only toggles enabled. adminEvents PATCH + type-preset
seeding no longer accept/seed per-event look fields; unused enums removed.
- Frontend SlideshowStyle drops the look fields (mode only); SlideshowStyleFields
watermark section is a single mode select with a "configured under Settings"
hint; SlideshowSettingsCard + Event type cleaned up.
- en/de: watermarkSizeLabel + watermarkModeHint.
(events.show_watermark_{source,position,opacity,style} columns from migration
138 are left in place but inert β the look is global now.)
Disabling the `slideshow` feature previously only hid the admin UI β the public
/show/:token route ignored the flag, so already-minted links kept working. Gate
resolveSlideshow on isFeatureEnabled('slideshow') so every /session and /state
404s when the feature is off: clicking Start shows "link not active" and a
running projector stops within one /state poll. Belt-and-braces: also gate the
admin generate + settings PATCH endpoints with requireFeatureFlag so links can't
be minted/changed while off (disable stays open so stale tokens can be cleared).
object-fit was hardcoded to 'cover', which crops portrait photos heavily. Add a global `slideshow_fit` setting (Settings -> Slideshow): 'cover' fills + crops, 'contain' shows the whole image with black bars (no crop). Default 'cover' (unchanged). Stored in app_settings (no migration), resolved server-side into the slideshow settings + /state poll so a running projector picks it up live.
β¦e one
The slideshow display preset (transition / interval / speed / color filter) was
set PER EVENT TYPE in the Edit Event Type dialog. Replace it with a single
picpeak-wide default in Settings -> Slideshow ("Default style for new
slideshows"). New events seed their show_* columns from this global preset
(was: from the event type's slideshow_preset); the per-event override is
unchanged.
- Removed event_types.slideshow_preset usage everywhere (EventTypeModal section,
eventTypes.service types, eventTypeService whitelist, adminEventTypes
validators/POST). The DB column from migration 138 is left inert.
- Global preset stored in app_settings (slideshow_interval_ms/transition/
transition_ms/colorfilter), saved via PUT /admin/settings/slideshow.
- adminEvents create-seeding now reads the global preset (getAppSetting) instead
of the event type.
- en/de: presetTitle + presetHint.
25 tests over two files, using the integration test-DB helper (real sqlite, all migrations): - slideshowPublic: resolveSlideshow guards (feature-flag kill-switch -> 404, unknown/null token, expired/draft/archived), the watermark cascade (global look + per-event on/off + source->URL resolution + "null when no logo"), image fit, and /session minting (token + cookie). Regression-guards the app_settings reads (vs the nonexistent `settings` table bug). - slideshowAdmin: generate/disable/regenerate, PATCH display + watermark mode, feature-flag 403, no-token 401, and PUT /admin/settings/slideshow validation + clamping. Both generate and PATCH assert success despite events having no `updated_at` column (the original 500).
- docs/live-slideshow.md: full feature guide (enable, generate link, run on a projector, global Settings -> Slideshow defaults, per-event overrides, how live updates work, security notes). - README: Live Slideshow bullet under Key Features, a Live Events use case, and a Documentation quick link.
Contributor
Author
|
π Added docs for this feature:
Note: the doc has |
Removed metadata section from live-slideshow documentation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a Live Slideshow ("Diashow"): a separate, token-only, fullscreen URL per event that auto-picks-up newly uploaded photos while it runs β built for projectors/kiosks at live events (weddings, concerts). It reuses the existing gallery auth/photo plumbing and is off by default behind a
slideshowfeature flag.16 commits Β· 27 files Β· +2,537/β13 Β· 2 migrations (137, 138)Quick use case
A photographer is shooting a live concert. Before the show they enable Settings β Features β Live Slideshow, open the event, and click Generate slideshow link. They open that link on the venue's projector laptop and hit βΆ Start β it goes fullscreen. As they cull and upload photos throughout the night, the projector quietly appends the new shots and keeps cycling β crossfading, with the studio's white logo watermark in the corner β no refresh, no touching the laptop. Portrait shots? They flip Image fit β Black bars once, globally, so faces aren't cropped.
What's included
Public viewer (
/gallery/:slug/show/:token)Admin
Technical notes
show_*columns; global defaults live inapp_settings(no migration), read viagetAppSetting./show/:tokenroute mints a short-livedaccessLevel: 'slideshow'gallery JWT (+ per-slug cookie so<img>requests authorize) and reuses the normal photo endpoints.slideshowfeature flag gates all admin UI and the public route (master kill-switch); minted tokens are suspended (not destroyed) when off.Tests
25 backend route tests (real-SQLite integration harness):
resolveSlideshowguards (kill-switch, token / expiry / draft / archived), the watermark cascade, image fit,/sessionminting, and the admin generate / disable / PATCH + settings validation. FrontendSlideshowPageleft for manual QA. Full backend suite green apart from the repo's known env-dependent suites (storage / sharp / S3 / webhook).