Skip to content

feat: Live Slideshow ("Diashow") β€” fullscreen, auto-updating projector view for live events#646

Open
Luca-Timo wants to merge 18 commits into
the-luap:betafrom
Luca-Timo:feat/live-slideshow
Open

feat: Live Slideshow ("Diashow") β€” fullscreen, auto-updating projector view for live events#646
Luca-Timo wants to merge 18 commits into
the-luap:betafrom
Luca-Timo:feat/live-slideshow

Conversation

@Luca-Timo

Copy link
Copy Markdown
Contributor

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 slideshow feature 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)

  • Splash β†’ fullscreen (browser gesture requirement), cursor auto-hide, "waiting for photos" holding screen, loops at end.
  • Transitions: crossfade, cut, slide, Ken Burns, dip-to-white, dip-to-black.
  • Image fit: fill (crop) or black bars (no crop).
  • Color filters: none / B&W / sepia / warm / cool / vignette.
  • Logo watermark (TV-ident style): white-recolored or original-colour, sourced from your light/dark logo or favicon, with position / opacity / size.
  • Live poll: new uploads appended without disturbing the current slide; settings changes apply to a running projector within ~3s; disabling the feature kills every link on the next poll.
  • Decode-ahead preload so transitions don't stutter; show views are excluded from visitor analytics.

Admin

  • Per-event card: generate / copy / regenerate / disable the link + a watermark on / off / inherit override.
  • Settings β†’ Slideshow tab: the picpeak-wide defaults β€” default display style for new slideshows, image fit, and the watermark look. (Per-event overrides remain.)

Technical notes

  • Migrations 137 + 138 add per-event show_* columns; global defaults live in app_settings (no migration), read via getAppSetting.
  • The /show/:token route mints a short-lived accessLevel: 'slideshow' gallery JWT (+ per-slug cookie so <img> requests authorize) and reuses the normal photo endpoints.
  • The slideshow feature flag gates all admin UI and the public route (master kill-switch); minted tokens are suspended (not destroyed) when off.
  • i18n: English + German only (both native-reviewed).

Tests

25 backend route tests (real-SQLite integration harness): resolveSlideshow guards (kill-switch, token / expiry / draft / archived), the watermark cascade, image fit, /session minting, and the admin generate / disable / PATCH + settings validation. Frontend SlideshowPage left for manual QA. Full backend suite green apart from the repo's known env-dependent suites (storage / sharp / S3 / webhook).

Luca-Timo added 16 commits June 19, 2026 22:10
- 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).
@Luca-Timo Luca-Timo closed this Jun 20, 2026
- 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.
@Luca-Timo

Copy link
Copy Markdown
Contributor Author

πŸ“– Added docs for this feature:

  • docs/live-slideshow.md β€” full guide: enabling the flag, generating/rotating/disabling the link, running it on a projector, the global Settings β†’ Slideshow defaults (display preset, image fit, watermark), per-event overrides, how live updates work, and security notes.
  • README β€” a Live Slideshow bullet under Key Features, a Live Events use case, and a Documentation quick link.

Note: the doc has sidebar_position frontmatter, but if the hosted docs site uses an explicit sidebars config it may need a one-line entry there too.

@Luca-Timo Luca-Timo reopened this Jun 20, 2026
Removed metadata section from live-slideshow documentation.
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.

1 participant