refactor(slideshow): replace per-event-type preset with a picpeak-wide 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.
This commit is contained in:
@@ -89,7 +89,6 @@ router.post('/', adminAuth, requirePermission('settings.edit'), [
|
||||
body('emoji').optional().trim(),
|
||||
body('theme_preset').optional().trim(),
|
||||
body('theme_config').optional(),
|
||||
body('slideshow_preset').optional(),
|
||||
body('display_order').optional().isInt({ min: 0 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
@@ -104,7 +103,6 @@ router.post('/', adminAuth, requirePermission('settings.edit'), [
|
||||
emoji,
|
||||
theme_preset,
|
||||
theme_config,
|
||||
slideshow_preset,
|
||||
display_order
|
||||
} = req.body;
|
||||
|
||||
@@ -114,7 +112,6 @@ router.post('/', adminAuth, requirePermission('settings.edit'), [
|
||||
emoji,
|
||||
theme_preset,
|
||||
theme_config,
|
||||
slideshow_preset,
|
||||
display_order
|
||||
});
|
||||
|
||||
@@ -153,7 +150,6 @@ router.put('/:id', adminAuth, requirePermission('settings.edit'), [
|
||||
body('emoji').optional().trim(),
|
||||
body('theme_preset').optional().trim(),
|
||||
body('theme_config').optional(),
|
||||
body('slideshow_preset').optional(),
|
||||
body('display_order').optional().isInt({ min: 0 }),
|
||||
body('is_active').optional().isBoolean()
|
||||
], async (req, res) => {
|
||||
|
||||
@@ -26,6 +26,7 @@ const { hasColumnCached } = require('../utils/schemaCache');
|
||||
const { validateFileType } = require('../utils/fileSecurityUtils');
|
||||
const { requireEventOwnership } = require('../middleware/ownership');
|
||||
const { requireFeatureFlag } = require('../middleware/requireFeatureFlag');
|
||||
const { getAppSetting } = require('../utils/appSettings');
|
||||
const { getFrontendBaseUrl } = require('../utils/frontendUrl');
|
||||
const downloadZipService = require('../services/downloadZipService');
|
||||
|
||||
@@ -673,40 +674,26 @@ router.post('/', adminAuth, requirePermission('events.create'), [
|
||||
const calendarColumnsExist = await hasColumnCached('events', 'is_full_day');
|
||||
|
||||
// Insert into database
|
||||
// Seed the new event's Live Slideshow settings from the event TYPE's preset
|
||||
// (migration 138). The admin configures "weddings fade slowly with our
|
||||
// white logo, sepia" once on the type; every new wedding inherits it. The
|
||||
// share token is NOT seeded — the link is still minted on demand. Guarded
|
||||
// Seed the new event's Live Slideshow display style from the PICPEAK-WIDE
|
||||
// preset (app_settings, Settings → Slideshow). New events inherit it and the
|
||||
// admin can still override per event. Watermark is left NULL = inherit the
|
||||
// global watermark; the share token is minted on demand, not seeded. Guarded
|
||||
// so un-migrated installs (mid-branch) don't reference missing columns.
|
||||
let slideshowSeed = {};
|
||||
if (await hasColumnCached('events', 'show_interval_ms')) {
|
||||
try {
|
||||
const type = await eventTypeService.getEventTypeBySlugPrefix(event_type);
|
||||
const preset = type?.slideshow_preset
|
||||
? (typeof type.slideshow_preset === 'string' ? JSON.parse(type.slideshow_preset) : type.slideshow_preset)
|
||||
: null;
|
||||
if (preset && typeof preset === 'object') {
|
||||
const intP = (v, min, max) => (Number.isFinite(+v) ? Math.min(max, Math.max(min, parseInt(v, 10))) : undefined);
|
||||
const oneOf = (v, allowed) => (allowed.includes(v) ? v : undefined);
|
||||
// Tri-state watermark: 'on'/'off' seed an explicit override; 'inherit'
|
||||
// (or unset) leaves the column NULL so the event follows the global.
|
||||
let watermarkSeed;
|
||||
if (preset.watermark === 'on' || preset.watermark === true) watermarkSeed = formatBoolean(true);
|
||||
else if (preset.watermark === 'off' || preset.watermark === false) watermarkSeed = formatBoolean(false);
|
||||
const seed = {
|
||||
show_interval_ms: intP(preset.interval_ms, 1000, 120000),
|
||||
show_transition: oneOf(preset.transition, SLIDESHOW_TRANSITIONS),
|
||||
show_transition_ms: intP(preset.transition_ms, 100, 5000),
|
||||
show_watermark: watermarkSeed,
|
||||
show_colorfilter: oneOf(preset.colorfilter, SLIDESHOW_COLORFILTERS),
|
||||
};
|
||||
// Only carry through fields the preset actually set.
|
||||
for (const [k, v] of Object.entries(seed)) {
|
||||
if (v !== undefined) slideshowSeed[k] = v;
|
||||
}
|
||||
}
|
||||
const intP = (v, min, max) => (Number.isFinite(+v) ? Math.min(max, Math.max(min, parseInt(v, 10))) : undefined);
|
||||
const oneOf = (v, allowed) => (allowed.includes(v) ? v : undefined);
|
||||
const i = intP(await getAppSetting('slideshow_interval_ms', undefined), 1000, 120000);
|
||||
const tr = oneOf(await getAppSetting('slideshow_transition', undefined), SLIDESHOW_TRANSITIONS);
|
||||
const tms = intP(await getAppSetting('slideshow_transition_ms', undefined), 100, 5000);
|
||||
const cf = oneOf(await getAppSetting('slideshow_colorfilter', undefined), SLIDESHOW_COLORFILTERS);
|
||||
if (i !== undefined) slideshowSeed.show_interval_ms = i;
|
||||
if (tr) slideshowSeed.show_transition = tr;
|
||||
if (tms !== undefined) slideshowSeed.show_transition_ms = tms;
|
||||
if (cf) slideshowSeed.show_colorfilter = cf;
|
||||
} catch (e) {
|
||||
logger.warn('Failed to seed slideshow settings from event type preset', { error: e.message });
|
||||
logger.warn('Failed to seed slideshow settings from global preset', { error: e.message });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -302,6 +302,23 @@ router.put('/slideshow', adminAuth, requirePermission('settings.edit'), async (r
|
||||
if (has('slideshow_fit')) {
|
||||
push('slideshow_fit', req.body.slideshow_fit === 'contain' ? 'contain' : 'cover');
|
||||
}
|
||||
// Picpeak-wide display preset (default style new events inherit).
|
||||
if (has('slideshow_interval_ms')) {
|
||||
const n = Math.min(120000, Math.max(1000, Math.round(Number(req.body.slideshow_interval_ms) || 5000)));
|
||||
push('slideshow_interval_ms', n);
|
||||
}
|
||||
if (has('slideshow_transition')) {
|
||||
const allowed = ['crossfade', 'cut', 'slide', 'kenburns', 'dipwhite', 'dipblack'];
|
||||
push('slideshow_transition', allowed.includes(req.body.slideshow_transition) ? req.body.slideshow_transition : 'crossfade');
|
||||
}
|
||||
if (has('slideshow_transition_ms')) {
|
||||
const n = Math.min(5000, Math.max(100, Math.round(Number(req.body.slideshow_transition_ms) || 800)));
|
||||
push('slideshow_transition_ms', n);
|
||||
}
|
||||
if (has('slideshow_colorfilter')) {
|
||||
const allowed = ['none', 'bw', 'sepia', 'warm', 'cool', 'vignette'];
|
||||
push('slideshow_colorfilter', allowed.includes(req.body.slideshow_colorfilter) ? req.body.slideshow_colorfilter : 'none');
|
||||
}
|
||||
if (has('slideshow_watermark_enabled')) push('slideshow_watermark_enabled', !!req.body.slideshow_watermark_enabled);
|
||||
if (has('slideshow_watermark_source')) {
|
||||
const v = ['logo', 'logo_dark', 'favicon', 'event'].includes(req.body.slideshow_watermark_source) ? req.body.slideshow_watermark_source : 'logo';
|
||||
|
||||
@@ -100,8 +100,7 @@ const createEventType = async (eventTypeData) => {
|
||||
emoji,
|
||||
theme_preset,
|
||||
theme_config,
|
||||
display_order,
|
||||
slideshow_preset
|
||||
display_order
|
||||
} = eventTypeData;
|
||||
|
||||
// Normalize slug_prefix
|
||||
@@ -128,7 +127,6 @@ const createEventType = async (eventTypeData) => {
|
||||
emoji: emoji || '📷',
|
||||
theme_preset: theme_preset || 'default',
|
||||
theme_config: theme_config ? JSON.stringify(theme_config) : null,
|
||||
slideshow_preset: slideshow_preset ? JSON.stringify(slideshow_preset) : null,
|
||||
display_order: finalDisplayOrder,
|
||||
is_system: false,
|
||||
is_active: true,
|
||||
@@ -193,10 +191,6 @@ const updateEventType = async (id, updates) => {
|
||||
updateData.theme_config = updates.theme_config ? JSON.stringify(updates.theme_config) : null;
|
||||
}
|
||||
|
||||
if (updates.slideshow_preset !== undefined) {
|
||||
updateData.slideshow_preset = updates.slideshow_preset ? JSON.stringify(updates.slideshow_preset) : null;
|
||||
}
|
||||
|
||||
if (updates.display_order !== undefined) {
|
||||
updateData.display_order = updates.display_order;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user