Release v2.4.2
This commit is contained in:
@@ -1,41 +1,8 @@
|
||||
// Announcement add page renderer and form defaults.
|
||||
// Announcement add page renderer.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
const { DEFAULT_ANNOUNCEMENT_ICON } = require('#src/data/announcement-icons');
|
||||
const { buildAnnouncementFormViewModel } = require('./form-view-model');
|
||||
|
||||
function buildDefaultAnnouncement() {
|
||||
return {
|
||||
id: null,
|
||||
message: '',
|
||||
short_label: '',
|
||||
announcement_type: 'lower-third',
|
||||
color_key: 'primary',
|
||||
icon_key: DEFAULT_ANNOUNCEMENT_ICON,
|
||||
durationMode: 'duration',
|
||||
duration_seconds: null,
|
||||
durationValue: 5,
|
||||
durationUnit: 'minutes',
|
||||
durationLabel: 'Until disabled',
|
||||
screen_ids: []
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = function renderAnnouncementFormPage(announcement, mode, message, currentUser, options) {
|
||||
const isEdit = mode === 'edit';
|
||||
const announcementTypes = Array.isArray(options && options.announcementTypes) ? options.announcementTypes : [];
|
||||
const announcementColors = Array.isArray(options && options.announcementColors) ? options.announcementColors : [];
|
||||
const announcementIcons = Array.isArray(options && options.announcementIcons) ? options.announcementIcons : [];
|
||||
const announcementScreens = Array.isArray(options && options.announcementScreens) ? options.announcementScreens : [];
|
||||
|
||||
return renderView(isEdit ? 'signage/announcements/edit' : 'signage/announcements/add', {
|
||||
title: isEdit ? 'Edit announcement' : 'Add announcement',
|
||||
active: 'announcements',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
announcement: Object.assign({}, buildDefaultAnnouncement(), announcement || {}),
|
||||
announcementTypes: announcementTypes,
|
||||
announcementColors: announcementColors,
|
||||
announcementIcons: announcementIcons,
|
||||
announcementScreens: announcementScreens
|
||||
});
|
||||
module.exports = function renderAnnouncementAddPage(announcement, message, currentUser, options) {
|
||||
return renderView('signage/announcements/form', buildAnnouncementFormViewModel(announcement, message, currentUser, options, false));
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
// Announcement edit page renderer that reuses the add form.
|
||||
// Announcement edit page renderer.
|
||||
|
||||
const renderAnnouncementFormPage = require('./add');
|
||||
const { renderView } = require('../../../view');
|
||||
const { buildAnnouncementFormViewModel } = require('./form-view-model');
|
||||
|
||||
module.exports = function renderAnnouncementEditPage(announcement, _data, message, currentUser, options) {
|
||||
return renderAnnouncementFormPage(announcement, 'edit', message, currentUser, options);
|
||||
return renderView('signage/announcements/form', buildAnnouncementFormViewModel(announcement, message, currentUser, options, true));
|
||||
};
|
||||
@@ -0,0 +1,54 @@
|
||||
// Shared announcement form view-model builder.
|
||||
|
||||
const { DEFAULT_ANNOUNCEMENT_ICON } = require('#src/data/announcement-icons');
|
||||
|
||||
function buildDefaultAnnouncement() {
|
||||
return {
|
||||
id: null,
|
||||
message: '',
|
||||
short_label: '',
|
||||
announcement_type: 'lower-third',
|
||||
color_key: 'primary',
|
||||
icon_key: DEFAULT_ANNOUNCEMENT_ICON,
|
||||
durationMode: 'duration',
|
||||
duration_seconds: null,
|
||||
durationValue: 5,
|
||||
durationUnit: 'minutes',
|
||||
durationLabel: 'Until disabled',
|
||||
screen_ids: []
|
||||
};
|
||||
}
|
||||
|
||||
function buildAnnouncementFormViewModel(announcement, message, currentUser, options, isEdit) {
|
||||
const announcementTypes = Array.isArray(options && options.announcementTypes) ? options.announcementTypes : [];
|
||||
const announcementColors = Array.isArray(options && options.announcementColors) ? options.announcementColors : [];
|
||||
const announcementIcons = Array.isArray(options && options.announcementIcons) ? options.announcementIcons : [];
|
||||
const announcementScreens = Array.isArray(options && options.announcementScreens) ? options.announcementScreens : [];
|
||||
const viewAnnouncement = Object.assign({}, buildDefaultAnnouncement(), announcement || {});
|
||||
const canDelete = Boolean(currentUser && Array.isArray(currentUser.permissionKeys) && currentUser.permissionKeys.indexOf('announcements.delete') !== -1);
|
||||
|
||||
return {
|
||||
title: isEdit ? 'Edit announcement' : 'Add announcement',
|
||||
active: 'announcements',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
announcement: viewAnnouncement,
|
||||
isEdit: Boolean(isEdit),
|
||||
showSaveSecondaryActions: Boolean(isEdit),
|
||||
showDeleteAction: Boolean(isEdit && canDelete),
|
||||
deleteDisabled: !isEdit || !Boolean(viewAnnouncement && viewAnnouncement.canDelete),
|
||||
formAction: isEdit && viewAnnouncement && viewAnnouncement.id ? '/announcements/' + viewAnnouncement.id : '/announcements',
|
||||
formAttrs: 'data-async-save' + (isEdit ? ' data-async-save-close-url="/announcements"' : ' data-async-save-new-redirect="response-url"') + ' data-async-save-new-url="/announcements/new"',
|
||||
cancelUrl: '/announcements',
|
||||
deleteUrl: isEdit && viewAnnouncement && viewAnnouncement.id ? '/announcements/' + viewAnnouncement.id + '/delete' : '',
|
||||
actionFormId: 'announcement-action-form',
|
||||
announcementTypes: announcementTypes,
|
||||
announcementColors: announcementColors,
|
||||
announcementIcons: announcementIcons,
|
||||
announcementScreens: announcementScreens
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildAnnouncementFormViewModel: buildAnnouncementFormViewModel
|
||||
};
|
||||
@@ -261,7 +261,7 @@ module.exports = function registerAnnouncementRoutes(app, deps) {
|
||||
app.get('/announcements/new', requirePermission('announcements.create'), async function (req, res, next) {
|
||||
try {
|
||||
const screens = await fetchAnnouncementScreens();
|
||||
res.send(pages.renderAnnouncementFormPage(buildAnnouncementFormData({
|
||||
res.send(pages.renderAnnouncementAddPage(buildAnnouncementFormData({
|
||||
message: '',
|
||||
short_label: '',
|
||||
announcement_type: 'lower-third',
|
||||
@@ -272,7 +272,7 @@ module.exports = function registerAnnouncementRoutes(app, deps) {
|
||||
durationValue: 5,
|
||||
durationUnit: 'minutes',
|
||||
screen_ids: []
|
||||
}), 'add', req.query.message ? String(req.query.message) : '', req.currentUser, {
|
||||
}), req.query.message ? String(req.query.message) : '', req.currentUser, {
|
||||
announcementTypes: ANNOUNCEMENT_TYPES,
|
||||
announcementColors: ANNOUNCEMENT_COLORS,
|
||||
announcementIcons: ANNOUNCEMENT_ICONS,
|
||||
|
||||
@@ -1,24 +1,8 @@
|
||||
// Canvas size add page renderer and form defaults.
|
||||
// Canvas size add page renderer.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
const { buildCanvasSizeFormViewModel } = require('./form-view-model');
|
||||
|
||||
function buildDefaultCanvasSize() {
|
||||
return {
|
||||
id: null,
|
||||
name: '',
|
||||
width: 1920,
|
||||
height: 1080
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = function renderCanvasSizeFormPage(canvasSize, mode, message, currentUser) {
|
||||
const isEdit = mode === 'edit';
|
||||
return renderView(isEdit ? 'canvas-sizes/edit' : 'canvas-sizes/add', {
|
||||
title: isEdit ? 'Edit canvas size' : 'Add canvas size',
|
||||
active: 'canvas-sizes',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
canvasSize: canvasSize || buildDefaultCanvasSize(),
|
||||
inUse: Boolean(canvasSize && canvasSize.inUse)
|
||||
});
|
||||
module.exports = function renderCanvasSizeAddPage(canvasSize, message, currentUser) {
|
||||
return renderView('canvas-sizes/form', buildCanvasSizeFormViewModel(canvasSize, message, currentUser, false));
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Canvas size edit page renderer that reuses the add form.
|
||||
// Canvas size edit page renderer.
|
||||
|
||||
const renderCanvasSizeFormPage = require('./add');
|
||||
const { renderView } = require('../../../view');
|
||||
const { buildCanvasSizeFormViewModel } = require('./form-view-model');
|
||||
|
||||
module.exports = function renderCanvasSizeEditPage(canvasSize, _data, message, currentUser) {
|
||||
return renderCanvasSizeFormPage(canvasSize, 'edit', message, currentUser);
|
||||
return renderView('canvas-sizes/form', buildCanvasSizeFormViewModel(canvasSize, message, currentUser, true));
|
||||
};
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// Shared canvas size form view-model builder.
|
||||
|
||||
function buildDefaultCanvasSize() {
|
||||
return {
|
||||
id: null,
|
||||
name: '',
|
||||
width: 1920,
|
||||
height: 1080
|
||||
};
|
||||
}
|
||||
|
||||
function buildCanvasSizeFormViewModel(canvasSize, message, currentUser, isEdit) {
|
||||
const viewCanvasSize = Object.assign(buildDefaultCanvasSize(), canvasSize || {});
|
||||
|
||||
return {
|
||||
title: isEdit ? 'Edit canvas size' : 'Add canvas size',
|
||||
active: 'canvas-sizes',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
canvasSize: viewCanvasSize,
|
||||
inUse: Boolean(viewCanvasSize && viewCanvasSize.inUse),
|
||||
isEdit: Boolean(isEdit),
|
||||
formAction: isEdit && viewCanvasSize ? '/canvas-sizes/' + viewCanvasSize.id : '/canvas-sizes',
|
||||
formAttrs: 'data-async-save' + (isEdit ? ' data-async-save-close-url="/canvas-sizes"' : ' data-async-save-new-redirect="response-url"') + ' data-async-save-new-url="/canvas-sizes/new"',
|
||||
cancelUrl: '/canvas-sizes',
|
||||
deleteUrl: isEdit && viewCanvasSize ? '/canvas-sizes/' + viewCanvasSize.id + '/delete' : '',
|
||||
deleteConfirmMessage: 'Delete this canvas size?',
|
||||
deleteDisabled: !isEdit || Boolean(viewCanvasSize && viewCanvasSize.inUse)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildCanvasSizeFormViewModel: buildCanvasSizeFormViewModel
|
||||
};
|
||||
@@ -9,6 +9,7 @@ module.exports = function renderConnectedClientsPage(data, message, currentUser)
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
scripts: ['js/dashboard/dashboard-page.js'],
|
||||
screens: data.screens || [],
|
||||
clients: data.clients || [],
|
||||
pagination: data.pagination || null
|
||||
});
|
||||
|
||||
@@ -60,6 +60,7 @@ module.exports = function registerClientsRoutes(app, deps) {
|
||||
const startIndex = (page - 1) * LIST_PAGE_SIZE;
|
||||
const clients = sortedClients.slice(startIndex, startIndex + LIST_PAGE_SIZE);
|
||||
res.send(pages.renderConnectedClientsPage({
|
||||
screens: data.screens || [],
|
||||
clients: clients,
|
||||
pagination: buildPagination(sortedClients.length, page, 'page', { search: search, sort: sort, direction: direction }, LIST_PAGE_SIZE, 'clients', 'Client pages')
|
||||
}, req.query.message ? String(req.query.message) : '', req.currentUser));
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
// Playlist add page renderer.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
const renderPlaylistEditorPage = require('./form-page');
|
||||
|
||||
module.exports = function renderPlaylistFormPage(message, currentUser) {
|
||||
return renderView('playlists/add', {
|
||||
title: 'Create playlist',
|
||||
active: 'playlists',
|
||||
playlist: {
|
||||
fade_between_slides: false,
|
||||
skip_unavailable_rtmp: false
|
||||
},
|
||||
message: message,
|
||||
currentUser: currentUser || null
|
||||
});
|
||||
module.exports = async function renderPlaylistFormPage(message, currentUser, data, pool) {
|
||||
const adminData = data || (pool ? await require('#src/data').fetchAdminData(pool) : { slides: [] });
|
||||
return renderPlaylistEditorPage(null, adminData, message, currentUser, { isEdit: false });
|
||||
};
|
||||
|
||||
|
||||
@@ -1,226 +1,7 @@
|
||||
// Playlist edit page renderer.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return '';
|
||||
}
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
function formatTime(value) {
|
||||
return value ? String(value).slice(0, 5) : '';
|
||||
}
|
||||
|
||||
function formatDays(daysJson) {
|
||||
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
let days = [];
|
||||
if (Array.isArray(daysJson)) {
|
||||
days = daysJson;
|
||||
} else if (daysJson) {
|
||||
try {
|
||||
const parsed = JSON.parse(daysJson);
|
||||
days = Array.isArray(parsed) ? parsed : [];
|
||||
} catch (_error) {
|
||||
days = [];
|
||||
}
|
||||
}
|
||||
return days.map((day) => dayNames[Number(day)] || '').filter(Boolean).join(', ');
|
||||
}
|
||||
|
||||
function normalizeScheduleDaysJson(daysJson) {
|
||||
if (Array.isArray(daysJson)) {
|
||||
return JSON.stringify(daysJson.map((day) => Number(day)).filter((day) => Number.isInteger(day) && day >= 0 && day <= 6));
|
||||
}
|
||||
|
||||
if (!daysJson) {
|
||||
return '[]';
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(daysJson);
|
||||
if (Array.isArray(parsed)) {
|
||||
return JSON.stringify(parsed.map((day) => Number(day)).filter((day) => Number.isInteger(day) && day >= 0 && day <= 6));
|
||||
}
|
||||
} catch (_error) {
|
||||
return JSON.stringify(String(daysJson).split(',').map((day) => Number(day)).filter((day) => Number.isInteger(day) && day >= 0 && day <= 6));
|
||||
}
|
||||
|
||||
return '[]';
|
||||
}
|
||||
|
||||
function scheduleSummary(item) {
|
||||
const mode = String(item.schedule_mode || 'always');
|
||||
if (mode === 'dates') {
|
||||
const start = formatDateTime(item.schedule_start_datetime);
|
||||
const end = formatDateTime(item.schedule_end_datetime);
|
||||
return start && end ? `Dates: ${start} to ${end}` : 'Dates: not set';
|
||||
}
|
||||
if (mode === 'times') {
|
||||
const days = formatDays(item.schedule_days_json);
|
||||
const start = formatTime(item.schedule_start_time);
|
||||
const end = formatTime(item.schedule_end_time);
|
||||
return days && start && end ? `Times: ${days} ${start}-${end}` : 'Times: not set';
|
||||
}
|
||||
return 'Always visible';
|
||||
}
|
||||
|
||||
function getCanvasSignature(width, height) {
|
||||
const normalizedWidth = Number(width);
|
||||
const normalizedHeight = Number(height);
|
||||
if (!Number.isFinite(normalizedWidth) || !Number.isFinite(normalizedHeight)) {
|
||||
return null;
|
||||
}
|
||||
return normalizedWidth + 'x' + normalizedHeight;
|
||||
}
|
||||
|
||||
function hasVideoRegion(contentJson) {
|
||||
if (!contentJson) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = typeof contentJson === 'string' ? JSON.parse(contentJson) : contentJson;
|
||||
return Boolean(parsed && typeof parsed === 'object' && Object.keys(parsed).some((key) => {
|
||||
const region = parsed[key];
|
||||
return region && typeof region === 'object' && String(region.type || '').trim().toLowerCase() === 'video';
|
||||
}));
|
||||
} catch (_error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getVideoSourcePath(slide) {
|
||||
if (!slide) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const contentJson = slide.content_json;
|
||||
if (!contentJson) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = typeof contentJson === 'string' ? JSON.parse(contentJson) : contentJson;
|
||||
if (!parsed || typeof parsed !== 'object') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const videoRegion = Object.keys(parsed).map((key) => parsed[key]).find((region) => {
|
||||
return region && typeof region === 'object' && String(region.type || '').trim().toLowerCase() === 'video' && String(region.value || '').trim();
|
||||
});
|
||||
|
||||
return videoRegion ? String(videoRegion.value || '').trim() : '';
|
||||
} catch (_error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function getVideoDurationSeconds(slide) {
|
||||
if (!slide) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const contentJson = slide.content_json;
|
||||
if (!contentJson) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = typeof contentJson === 'string' ? JSON.parse(contentJson) : contentJson;
|
||||
if (!parsed || typeof parsed !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const videoRegion = Object.keys(parsed).map((key) => parsed[key]).find((region) => {
|
||||
return region && typeof region === 'object' && String(region.type || '').trim().toLowerCase() === 'video' && Number(region.duration_seconds || 0) > 0;
|
||||
});
|
||||
|
||||
const duration = Math.round(Number(videoRegion && videoRegion.duration_seconds || 0) * 1000) / 1000;
|
||||
return Number.isFinite(duration) && duration > 0 ? duration : null;
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getVideoDurationValue(slide) {
|
||||
const storedDuration = Number(slide && slide.duration_seconds || 0);
|
||||
const videoDuration = getVideoDurationSeconds(slide);
|
||||
return Boolean(slide && slide.use_video_duration) && videoDuration ? videoDuration : storedDuration;
|
||||
}
|
||||
const renderPlaylistEditorPage = require('./form-page');
|
||||
|
||||
module.exports = function renderPlaylistEditPage(playlist, data, message, currentUser) {
|
||||
const playlistSlides = (data.playlistSlides || [])
|
||||
.filter((item) => item.playlist_id === playlist.id)
|
||||
.sort((a, b) => {
|
||||
if (a.position !== b.position) {
|
||||
return a.position - b.position;
|
||||
}
|
||||
return a.id - b.id;
|
||||
})
|
||||
.map((item, index, items) => Object.assign({}, item, {
|
||||
displayOrder: index + 1,
|
||||
isFirst: index === 0,
|
||||
isLast: index === items.length - 1,
|
||||
canvasSignature: getCanvasSignature(item.canvas_width, item.canvas_height) || '',
|
||||
showVideoDurationButton: hasVideoRegion(item.content_json),
|
||||
videoSourcePath: getVideoSourcePath(item),
|
||||
videoDurationSeconds: getVideoDurationSeconds(item),
|
||||
useVideoDuration: Boolean(item.use_video_duration),
|
||||
durationSeconds: getVideoDurationValue(item),
|
||||
schedule_days_json: normalizeScheduleDaysJson(item.schedule_days_json),
|
||||
scheduleSummary: scheduleSummary(item)
|
||||
}));
|
||||
const assignedSlideIds = new Set(playlistSlides.map((item) => item.slide_id));
|
||||
const playlistCanvasSignatures = Array.from(new Set(playlistSlides.map((item) => getCanvasSignature(item.canvas_width, item.canvas_height)).filter(Boolean)));
|
||||
const playlistCanvasSignature = playlistCanvasSignatures.length === 1 ? playlistCanvasSignatures[0] : null;
|
||||
const playlistCanvasMismatch = playlistCanvasSignatures.length > 1;
|
||||
const selectableSlides = (data.slides || []).filter((slide) => {
|
||||
if (playlistCanvasMismatch) {
|
||||
return false;
|
||||
}
|
||||
if (!playlistCanvasSignature) {
|
||||
return true;
|
||||
}
|
||||
return getCanvasSignature(slide.canvas_width, slide.canvas_height) === playlistCanvasSignature;
|
||||
}).map((slide) => Object.assign({}, slide, {
|
||||
canvasSignature: getCanvasSignature(slide.canvas_width, slide.canvas_height) || '',
|
||||
isAssigned: assignedSlideIds.has(slide.id),
|
||||
showVideoDurationButton: hasVideoRegion(slide.content_json),
|
||||
videoSourcePath: getVideoSourcePath(slide),
|
||||
videoDurationSeconds: getVideoDurationSeconds(slide),
|
||||
useVideoDuration: Boolean(slide.use_video_duration),
|
||||
durationSeconds: getVideoDurationValue(slide)
|
||||
}));
|
||||
const hasAddableSlides = selectableSlides.some((slide) => !slide.isAssigned);
|
||||
|
||||
return renderView('playlists/edit', {
|
||||
title: 'Edit playlist',
|
||||
active: 'playlists',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
playlist: playlist,
|
||||
inUse: Boolean(playlist.inUse),
|
||||
playlistFadeBetweenSlides: Boolean(playlist.fade_between_slides),
|
||||
playlistSkipUnavailableRtmp: Boolean(playlist.skip_unavailable_rtmp),
|
||||
playlistSlides: playlistSlides,
|
||||
slides: data.slides || [],
|
||||
hasSlides: (data.slides || []).length > 0,
|
||||
hasSelectableSlides: selectableSlides.length > 0,
|
||||
hasAddableSlides: hasAddableSlides,
|
||||
selectableSlides: selectableSlides,
|
||||
playlistCanvasSignature: playlistCanvasSignature,
|
||||
playlistCanvasMismatch: playlistCanvasMismatch,
|
||||
scripts: ['js/lib/modal.js?v=20260726.7', 'vendor/sortable.min.js?v=20260726.7', 'js/playlists/playlist-schedule.js?v=20260726.7']
|
||||
});
|
||||
return renderPlaylistEditorPage(playlist, data || {}, message, currentUser, { isEdit: true });
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Shared playlist form page renderer.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
const { buildPlaylistFormViewModel } = require('./form-view-model');
|
||||
|
||||
module.exports = function renderPlaylistFormPage(playlist, data, message, currentUser, options) {
|
||||
return renderView('playlists/form', buildPlaylistFormViewModel(playlist || null, data || {}, message, currentUser, options || {}));
|
||||
};
|
||||
@@ -0,0 +1,289 @@
|
||||
// Shared playlist form view-model builder.
|
||||
|
||||
const { formatScheduleRuleSummary, formatScheduleRulesSummary, normalizeDayValues } = require('#src/web/lib/playlist-schedule');
|
||||
|
||||
function formatDateTime(value) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
const date = new Date(value);
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return '';
|
||||
}
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const hours = String(date.getHours()).padStart(2, '0');
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
function formatTime(value) {
|
||||
return value ? String(value).slice(0, 5) : '';
|
||||
}
|
||||
|
||||
function scheduleSummary(item) {
|
||||
return formatScheduleRulesSummary(item && item.scheduleRules ? item.scheduleRules : []);
|
||||
}
|
||||
|
||||
function formatScheduleRuleForView(rule, index) {
|
||||
const input = rule && typeof rule === 'object' ? rule : {};
|
||||
const dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||||
const selectedDays = new Set(normalizeDayValues(input.days || input.schedule_days_json));
|
||||
const normalizedDays = Array.from(selectedDays);
|
||||
|
||||
return {
|
||||
index: index + 1,
|
||||
position: index,
|
||||
startDateTimeValue: formatDateTime(input.start_datetime),
|
||||
endDateTimeValue: formatDateTime(input.end_datetime),
|
||||
startTimeValue: formatTime(input.start_time),
|
||||
endTimeValue: formatTime(input.end_time),
|
||||
daysCsv: normalizedDays.join(','),
|
||||
ruleSummary: formatScheduleRuleSummary(input),
|
||||
dayOptions: dayNames.map((label, value) => ({
|
||||
value: value,
|
||||
label: label,
|
||||
checked: selectedDays.has(value),
|
||||
inputId: 'schedule-rule-' + (index + 1) + '-day-' + value
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
function getCanvasSignature(width, height) {
|
||||
const normalizedWidth = Number(width);
|
||||
const normalizedHeight = Number(height);
|
||||
if (!Number.isFinite(normalizedWidth) || !Number.isFinite(normalizedHeight)) {
|
||||
return null;
|
||||
}
|
||||
return normalizedWidth + 'x' + normalizedHeight;
|
||||
}
|
||||
|
||||
function hasVideoRegion(contentJson) {
|
||||
if (!contentJson) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = typeof contentJson === 'string' ? JSON.parse(contentJson) : contentJson;
|
||||
return Boolean(parsed && typeof parsed === 'object' && Object.keys(parsed).some((key) => {
|
||||
const region = parsed[key];
|
||||
return region && typeof region === 'object' && String(region.type || '').trim().toLowerCase() === 'video';
|
||||
}));
|
||||
} catch (_error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function hasPlayableMediaRegion(contentJson) {
|
||||
if (!contentJson) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = typeof contentJson === 'string' ? JSON.parse(contentJson) : contentJson;
|
||||
return Boolean(parsed && typeof parsed === 'object' && Object.keys(parsed).some((key) => {
|
||||
const region = parsed[key];
|
||||
const regionType = String(region && region.type || '').trim().toLowerCase();
|
||||
return region && typeof region === 'object' && (regionType === 'video' || regionType === 'rtmp');
|
||||
}));
|
||||
} catch (_error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getDisableAudioValue(slide) {
|
||||
if (!slide || slide.disable_audio === undefined || slide.disable_audio === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Boolean(slide.disable_audio);
|
||||
}
|
||||
|
||||
function getVideoSourcePath(slide) {
|
||||
if (!slide) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const contentJson = slide.content_json;
|
||||
if (!contentJson) {
|
||||
return '';
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = typeof contentJson === 'string' ? JSON.parse(contentJson) : contentJson;
|
||||
if (!parsed || typeof parsed !== 'object') {
|
||||
return '';
|
||||
}
|
||||
|
||||
const videoRegion = Object.keys(parsed).map((key) => parsed[key]).find((region) => {
|
||||
return region && typeof region === 'object' && String(region.type || '').trim().toLowerCase() === 'video' && String(region.value || '').trim();
|
||||
});
|
||||
|
||||
return videoRegion ? String(videoRegion.value || '').trim() : '';
|
||||
} catch (_error) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function getVideoDurationSeconds(slide) {
|
||||
if (!slide) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const contentJson = slide.content_json;
|
||||
if (!contentJson) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = typeof contentJson === 'string' ? JSON.parse(contentJson) : contentJson;
|
||||
if (!parsed || typeof parsed !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const videoRegion = Object.keys(parsed).map((key) => parsed[key]).find((region) => {
|
||||
return region && typeof region === 'object' && String(region.type || '').trim().toLowerCase() === 'video' && Number(region.duration_seconds || 0) > 0;
|
||||
});
|
||||
|
||||
const duration = Math.round(Number(videoRegion && videoRegion.duration_seconds || 0) * 1000) / 1000;
|
||||
return Number.isFinite(duration) && duration > 0 ? duration : null;
|
||||
} catch (_error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getVideoDurationValue(slide) {
|
||||
const storedDuration = Number(slide && slide.duration_seconds || 0);
|
||||
const videoDuration = getVideoDurationSeconds(slide);
|
||||
return Boolean(slide && slide.use_video_duration) && videoDuration ? videoDuration : storedDuration;
|
||||
}
|
||||
|
||||
function formatCanvasSizeLabel(size) {
|
||||
const width = Number(size && size.width);
|
||||
const height = Number(size && size.height);
|
||||
const dimensions = Number.isFinite(width) && Number.isFinite(height) ? width + 'x' + height : '';
|
||||
const name = String(size && size.name ? size.name : '').trim();
|
||||
if (!dimensions) {
|
||||
return name;
|
||||
}
|
||||
return name ? dimensions + ' - ' + name : dimensions;
|
||||
}
|
||||
|
||||
function buildPlaylistFormViewModel(playlist, data, message, currentUser, options) {
|
||||
const isEdit = Boolean(options && options.isEdit);
|
||||
const viewPlaylist = Object.assign({
|
||||
id: null,
|
||||
name: '',
|
||||
fade_between_slides: false,
|
||||
skip_unavailable_rtmp: false
|
||||
}, playlist || {});
|
||||
const playlistSlides = (data.playlistSlides || [])
|
||||
.filter((item) => Number(item.playlist_id) === Number(viewPlaylist.id))
|
||||
.sort((a, b) => {
|
||||
if (a.position !== b.position) {
|
||||
return a.position - b.position;
|
||||
}
|
||||
return a.id - b.id;
|
||||
})
|
||||
.map((item, index, items) => Object.assign({}, item, {
|
||||
row_key: String(item.row_key || item.id || ''),
|
||||
displayOrder: index + 1,
|
||||
isFirst: index === 0,
|
||||
isLast: index === items.length - 1,
|
||||
canvasSizeId: Number(item.canvas_size_id) || null,
|
||||
showVideoDurationButton: hasVideoRegion(item.content_json),
|
||||
showMuteButton: hasVideoRegion(item.content_json),
|
||||
videoSourcePath: getVideoSourcePath(item),
|
||||
videoDurationSeconds: getVideoDurationSeconds(item),
|
||||
useVideoDuration: Boolean(item.use_video_duration),
|
||||
disableAudio: getDisableAudioValue(item),
|
||||
durationSeconds: getVideoDurationValue(item),
|
||||
scheduleRules: (Array.isArray(item.scheduleRules) ? item.scheduleRules : []).map(formatScheduleRuleForView),
|
||||
scheduleSummary: scheduleSummary(item)
|
||||
}));
|
||||
const assignedSlideIds = new Set(playlistSlides.map((item) => item.slide_id));
|
||||
const storedCanvasId = Number(viewPlaylist.canvas_id) || null;
|
||||
const playlistSlideCanvasIds = Array.from(new Set(playlistSlides.map((item) => Number(item.canvasSizeId || item.canvas_size_id)).filter((value) => Number.isInteger(value) && value > 0)));
|
||||
const computedPlaylistCanvasId = playlistSlideCanvasIds.length === 1 ? playlistSlideCanvasIds[0] : null;
|
||||
const playlistCanvasId = storedCanvasId || computedPlaylistCanvasId;
|
||||
const selectedCanvasSize = (data.canvasSizes || []).find((size) => Number(size.id) === Number(playlistCanvasId)) || null;
|
||||
const canvasSizes = (data.canvasSizes || []).map((size) => Object.assign({}, size, {
|
||||
label: formatCanvasSizeLabel(size),
|
||||
canvasId: Number(size.id) || null,
|
||||
selected: Boolean(selectedCanvasSize && Number(selectedCanvasSize.id) === Number(size.id))
|
||||
}));
|
||||
const playlistCanvasMismatch = Boolean(
|
||||
(storedCanvasId && playlistSlides.some((item) => Number(item.canvasSizeId || item.canvas_size_id) > 0 && Number(item.canvasSizeId || item.canvas_size_id) !== storedCanvasId)) ||
|
||||
(!storedCanvasId && playlistSlideCanvasIds.length > 1)
|
||||
);
|
||||
const selectableSlides = (data.slides || []).map((slide) => Object.assign({}, slide, {
|
||||
canvasSizeId: Number(slide.canvas_size_id) || null,
|
||||
isAssigned: assignedSlideIds.has(slide.id),
|
||||
showVideoDurationButton: hasVideoRegion(slide.content_json),
|
||||
showMuteButton: hasVideoRegion(slide.content_json),
|
||||
videoSourcePath: getVideoSourcePath(slide),
|
||||
videoDurationSeconds: getVideoDurationSeconds(slide),
|
||||
useVideoDuration: Boolean(slide.use_video_duration),
|
||||
durationSeconds: getVideoDurationValue(slide)
|
||||
}));
|
||||
const playlistSlideTemplate = {
|
||||
id: '',
|
||||
row_key: '',
|
||||
slide_id: '',
|
||||
title: '',
|
||||
thumbnail_path: '',
|
||||
canvas_width: '',
|
||||
canvas_height: '',
|
||||
canvasSizeId: '',
|
||||
showVideoDurationButton: false,
|
||||
showMuteButton: false,
|
||||
videoSourcePath: '',
|
||||
videoDurationSeconds: '',
|
||||
useVideoDuration: false,
|
||||
disableAudio: true,
|
||||
durationSeconds: 10,
|
||||
scheduleRules: [],
|
||||
scheduleSummary: 'Always visible',
|
||||
displayOrder: ''
|
||||
};
|
||||
const hasAddableSlides = selectableSlides.some((slide) => !slide.isAssigned && (!playlistCanvasId || slide.canvasSizeId === playlistCanvasId));
|
||||
const isCreate = !isEdit;
|
||||
|
||||
return {
|
||||
title: 'Playlist editor',
|
||||
active: 'playlists',
|
||||
isEdit: isEdit,
|
||||
playlist: viewPlaylist,
|
||||
formAction: isEdit && viewPlaylist.id ? '/playlists/' + viewPlaylist.id : '/playlists',
|
||||
formAttrs: 'data-async-save data-async-save-refresh-target="#playlist-items-body" data-async-save-close-url="/playlists" data-async-save-new-url="/playlists/new" data-async-save-new-redirect="response-url"',
|
||||
cancelUrl: '/playlists',
|
||||
deleteUrl: isEdit && viewPlaylist.id ? '/playlists/' + viewPlaylist.id + '/delete' : '',
|
||||
deleteDisabled: isCreate ? true : Boolean(viewPlaylist.inUse),
|
||||
showSaveSecondaryActions: true,
|
||||
playlistFadeBetweenSlides: Boolean(viewPlaylist.fade_between_slides),
|
||||
playlistSkipUnavailableRtmp: Boolean(viewPlaylist.skip_unavailable_rtmp),
|
||||
canvasSizes: canvasSizes,
|
||||
playlistCanvasSizeId: selectedCanvasSize ? Number(selectedCanvasSize.id) : '',
|
||||
playlistCanvasSizeLabel: selectedCanvasSize ? formatCanvasSizeLabel(selectedCanvasSize) : '',
|
||||
playlistCanvasEditable: Boolean(!isEdit || !playlistCanvasId),
|
||||
playlistScheduleConfigUrl: '/playlists/new/slides/0/config',
|
||||
hasSlides: (data.slides || []).length > 0,
|
||||
hasSelectableSlides: selectableSlides.length > 0,
|
||||
hasAddableSlides: hasAddableSlides,
|
||||
playlistSlides: playlistSlides,
|
||||
playlistSlideTemplate: playlistSlideTemplate,
|
||||
slides: data.slides || [],
|
||||
selectableSlides: selectableSlides,
|
||||
playlistCanvasId: playlistCanvasId,
|
||||
playlistCanvasMismatch: playlistCanvasMismatch,
|
||||
scripts: ['js/lib/modal.js?v=20260726.7', 'js/playlists/playlist-table-drag.js?v=20260802.5', 'js/playlists/playlist-schedule.js?v=20260803.1'],
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
inUse: Boolean(viewPlaylist.inUse)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildPlaylistFormViewModel: buildPlaylistFormViewModel
|
||||
};
|
||||
@@ -43,7 +43,12 @@ module.exports = function registerPlaylistsRoutes(app, deps) {
|
||||
});
|
||||
|
||||
app.get('/playlists/new', requirePermission('playlists.create'), function (req, res) {
|
||||
res.send(pages.renderPlaylistFormPage(req.query.message ? String(req.query.message) : '', req.currentUser));
|
||||
(async function () {
|
||||
const data = await common.fetchAdminData(pool);
|
||||
res.send(await pages.renderPlaylistFormPage(req.query.message ? String(req.query.message) : '', req.currentUser, data, pool));
|
||||
})().catch(function (error) {
|
||||
res.status(500).send(error.message || 'Unable to load page.');
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/playlists/:id/edit', requirePermission('playlists.update'), async function (req, res, next) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Shared playlist slide configuration rendering helpers.
|
||||
|
||||
const { renderFragment } = require('../../../view');
|
||||
const { formatScheduleRuleSummary, formatScheduleRulesSummary, normalizeDayValues } = require('#src/web/lib/playlist-schedule');
|
||||
|
||||
const DAY_OPTIONS = [
|
||||
{ value: 0, label: 'Sun' },
|
||||
@@ -41,17 +42,34 @@ function formatTimeInput(value) {
|
||||
module.exports = function renderPlaylistSlideConfigPage(playlist, playlistSlide, message, rowKey, currentUser) {
|
||||
const slide = playlistSlide || {
|
||||
id: 0,
|
||||
schedule_mode: 'always',
|
||||
schedule_days: [],
|
||||
schedule_start_datetime: null,
|
||||
schedule_end_datetime: null,
|
||||
schedule_start_time: null,
|
||||
schedule_end_time: null
|
||||
scheduleRules: []
|
||||
};
|
||||
const scheduleMode = String(slide.schedule_mode || 'always');
|
||||
const selectedDays = new Set(Array.isArray(slide.schedule_days) ? slide.schedule_days.map(Number) : []);
|
||||
const startTimeValue = formatTimeInput(slide.schedule_start_time) || (scheduleMode === 'times' ? DEFAULT_START_TIME : '');
|
||||
const endTimeValue = formatTimeInput(slide.schedule_end_time) || (scheduleMode === 'times' ? DEFAULT_END_TIME : '');
|
||||
const scheduleRules = Array.isArray(slide.scheduleRules) ? slide.scheduleRules : [];
|
||||
const renderedRules = scheduleRules.map((rule, index) => {
|
||||
const ruleNumber = index + 1;
|
||||
const selectedDays = new Set(normalizeDayValues(rule.days || rule.schedule_days_json));
|
||||
|
||||
return {
|
||||
id: 'schedule-rule-' + ruleNumber,
|
||||
index: ruleNumber,
|
||||
startDateTimeValue: formatDateTimeInput(rule.start_datetime),
|
||||
endDateTimeValue: formatDateTimeInput(rule.end_datetime),
|
||||
startTimeValue: formatTimeInput(rule.start_time),
|
||||
endTimeValue: formatTimeInput(rule.end_time),
|
||||
ruleSummary: formatScheduleRuleSummary(rule),
|
||||
dayOptions: DAY_OPTIONS.map((day) => ({
|
||||
...day,
|
||||
checked: selectedDays.has(day.value),
|
||||
inputId: 'schedule-rule-' + ruleNumber + '-day-' + day.value
|
||||
}))
|
||||
};
|
||||
});
|
||||
|
||||
const templateDayOptions = DAY_OPTIONS.map((day) => ({
|
||||
...day,
|
||||
checked: false,
|
||||
inputId: 'schedule-rule-template-day-' + day.value
|
||||
}));
|
||||
|
||||
return renderFragment('playlists/slide-config', {
|
||||
title: 'Slide schedule',
|
||||
@@ -63,19 +81,10 @@ module.exports = function renderPlaylistSlideConfigPage(playlist, playlistSlide,
|
||||
currentUser: currentUser || null,
|
||||
playlist: playlist,
|
||||
playlistSlide: slide,
|
||||
scheduleMode: scheduleMode,
|
||||
isAlwaysMode: scheduleMode === 'always',
|
||||
isDatesMode: scheduleMode === 'dates',
|
||||
isTimesMode: scheduleMode === 'times',
|
||||
rowKey: String(rowKey || ''),
|
||||
startDateTimeValue: formatDateTimeInput(slide.schedule_start_datetime),
|
||||
endDateTimeValue: formatDateTimeInput(slide.schedule_end_datetime),
|
||||
startTimeValue: startTimeValue,
|
||||
endTimeValue: endTimeValue,
|
||||
dayOptions: DAY_OPTIONS.map((day) => ({
|
||||
...day,
|
||||
checked: selectedDays.has(day.value)
|
||||
})),
|
||||
scripts: ['js/lib/modal.js', 'js/playlists/playlist-schedule.js']
|
||||
scheduleRules: renderedRules,
|
||||
templateDayOptions: templateDayOptions,
|
||||
scheduleRulesSummary: formatScheduleRulesSummary(scheduleRules),
|
||||
scripts: ['js/lib/modal.js', 'js/playlists/playlist-schedule.js?v=20260802.8']
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
// Screen add page renderer.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
const { buildScreenFormViewModel } = require('./form-view-model');
|
||||
|
||||
module.exports = function renderScreenFormPage(data, message, currentUser) {
|
||||
return renderView('screens/add', {
|
||||
title: 'Add screen',
|
||||
active: 'screens',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
playlists: data.playlists || []
|
||||
});
|
||||
return renderView('screens/form', buildScreenFormViewModel(null, data || {}, message, currentUser, false));
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,9 @@
|
||||
// Screen edit page renderer.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
const { buildScreenFormViewModel } = require('./form-view-model');
|
||||
|
||||
module.exports = function renderScreenEditPage(screen, data, message, currentUser) {
|
||||
return renderView('screens/edit', {
|
||||
title: 'Edit screen',
|
||||
active: 'screens',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
screen: screen,
|
||||
inUse: Boolean(screen.inUse),
|
||||
playlists: data.playlists || []
|
||||
});
|
||||
return renderView('screens/form', buildScreenFormViewModel(screen, data || {}, message, currentUser, true));
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// Shared screen form view-model builder.
|
||||
|
||||
function buildScreenFormViewModel(screen, data, message, currentUser, isEdit) {
|
||||
const viewScreen = Object.assign({
|
||||
name: '',
|
||||
slug: '',
|
||||
playlist_id: null
|
||||
}, screen || {});
|
||||
|
||||
return {
|
||||
title: isEdit ? 'Edit screen' : 'Add screen',
|
||||
active: 'screens',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
playlists: (data && data.playlists) || [],
|
||||
screen: viewScreen,
|
||||
inUse: Boolean(viewScreen.inUse),
|
||||
isEdit: Boolean(isEdit),
|
||||
showSaveSecondaryActions: Boolean(isEdit),
|
||||
deleteDisabled: Boolean(isEdit) ? Boolean(viewScreen.inUse) : true,
|
||||
formAction: Boolean(isEdit) && viewScreen.id ? '/screens/' + viewScreen.id : '/screens',
|
||||
formAttrs: Boolean(isEdit)
|
||||
? 'data-async-save data-async-save-close-url="/screens" data-async-save-new-url="/screens/new"'
|
||||
: 'data-async-save data-async-save-new-redirect="response-url" data-async-save-new-url="/screens/new"',
|
||||
cancelUrl: '/screens',
|
||||
deleteUrl: Boolean(isEdit) && viewScreen.id ? '/screens/' + viewScreen.id + '/delete' : ''
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildScreenFormViewModel: buildScreenFormViewModel
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
// Slide add page renderer.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
const { buildSlideFormViewModel } = require('./form-view-model');
|
||||
|
||||
module.exports = function renderSlideAddPage(data, message, currentUser) {
|
||||
return renderView('slides/form', buildSlideFormViewModel(data || {}, null, message, currentUser, false));
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
// Slide edit page renderer.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
const { buildSlideFormViewModel } = require('./form-view-model');
|
||||
|
||||
module.exports = function renderSlideEditPage(data, slide, message, currentUser) {
|
||||
return renderView('slides/form', buildSlideFormViewModel(data || {}, slide || null, message, currentUser, true));
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
// Shared slide form view-model builder.
|
||||
|
||||
const { getRegionEditorScripts } = require('../../../lib/region-scripts');
|
||||
|
||||
function buildDefaultSlide() {
|
||||
return {
|
||||
title: '',
|
||||
template_id: null,
|
||||
content: {}
|
||||
};
|
||||
}
|
||||
|
||||
function buildTemplates(templates, templateRegions) {
|
||||
const regions = Array.isArray(templateRegions) ? templateRegions : [];
|
||||
return (Array.isArray(templates) ? templates : []).map((template) => Object.assign({}, template, {
|
||||
canvas_size_width: template.canvas_size_width,
|
||||
canvas_size_height: template.canvas_size_height,
|
||||
regions: regions.filter((region) => Number(region.template_id) === Number(template.id))
|
||||
}));
|
||||
}
|
||||
|
||||
function buildSlideFormViewModel(data, slide, message, currentUser, isEdit) {
|
||||
const assetVersion = Date.now().toString(36);
|
||||
const templateRegions = data && data.templateRegions ? data.templateRegions : [];
|
||||
const rssFeeds = data && data.rssFeeds ? data.rssFeeds : [];
|
||||
const apiSources = data && data.apiSources ? data.apiSources : [];
|
||||
const timetableGroups = data && data.timetableGroups ? data.timetableGroups : [];
|
||||
const fontLibrary = data && data.fontLibrary ? data.fontLibrary : null;
|
||||
const templates = buildTemplates(data && data.templates ? data.templates : [], templateRegions);
|
||||
const viewSlide = slide || buildDefaultSlide();
|
||||
|
||||
return {
|
||||
title: isEdit ? 'Edit slide' : 'Create slide',
|
||||
active: 'slides',
|
||||
message: message,
|
||||
isEdit: Boolean(isEdit),
|
||||
inUse: Boolean(viewSlide && viewSlide.inUse),
|
||||
action: isEdit && viewSlide.id ? '/slides/' + viewSlide.id : '/slides',
|
||||
formAttrs: isEdit
|
||||
? 'data-async-save data-async-save-close-url="/slides" data-async-save-new-url="/slides/new"'
|
||||
: 'data-async-save data-async-save-close-url="/slides" data-async-save-new-redirect="response-url" data-async-save-new-url="/slides/new"',
|
||||
slide: viewSlide,
|
||||
templates: templates,
|
||||
stylesheets: fontLibrary && fontLibrary.stylesheetHref ? [fontLibrary.stylesheetHref] : [],
|
||||
slideEditorData: {
|
||||
templates: templates,
|
||||
rssFeeds: rssFeeds,
|
||||
apiSources: apiSources,
|
||||
timetableGroups: timetableGroups,
|
||||
fontStylesheetHref: fontLibrary && fontLibrary.stylesheetHref ? fontLibrary.stylesheetHref : '',
|
||||
fontFamilyFormats: fontLibrary && fontLibrary.fontFamilyFormats ? fontLibrary.fontFamilyFormats : '',
|
||||
existingTemplateId: viewSlide && viewSlide.template_id ? viewSlide.template_id : null,
|
||||
existingContent: viewSlide && viewSlide.content ? viewSlide.content : {}
|
||||
},
|
||||
assetVersion: assetVersion,
|
||||
slideEditorScripts: getRegionEditorScripts(assetVersion),
|
||||
currentUser: currentUser || null
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildSlideFormViewModel: buildSlideFormViewModel
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
// Slide form page renderer for add and edit flows.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
const { getRegionEditorScripts } = require('../../../lib/region-scripts');
|
||||
|
||||
module.exports = function renderSlideFormPage(data, mode, slide, message, currentUser) {
|
||||
const assetVersion = Date.now().toString(36);
|
||||
const templateRegions = data.templateRegions || [];
|
||||
const rssFeeds = data.rssFeeds || [];
|
||||
const apiSources = data.apiSources || [];
|
||||
const scheduleGroups = data.scheduleGroups || [];
|
||||
const fontLibrary = data.fontLibrary || null;
|
||||
const templates = (data.templates || []).map((template) => ({
|
||||
...template,
|
||||
canvas_size_width: template.canvas_size_width,
|
||||
canvas_size_height: template.canvas_size_height,
|
||||
regions: templateRegions.filter((region) => Number(region.template_id) === Number(template.id))
|
||||
}));
|
||||
return renderView('slides/form', {
|
||||
title: mode === 'edit' ? 'Edit slide' : 'Create slide',
|
||||
active: 'slides',
|
||||
message: message,
|
||||
isEdit: mode === 'edit',
|
||||
inUse: Boolean(slide && slide.inUse),
|
||||
saveLabel: 'Save',
|
||||
action: mode === 'edit' ? `/slides/${slide.id}` : '/slides',
|
||||
slide: slide || { title: '', template_id: null, content: {} },
|
||||
templates: templates,
|
||||
stylesheets: fontLibrary && fontLibrary.stylesheetHref ? [fontLibrary.stylesheetHref] : [],
|
||||
slideEditorData: {
|
||||
templates: templates,
|
||||
rssFeeds: rssFeeds,
|
||||
apiSources: apiSources,
|
||||
scheduleGroups: scheduleGroups,
|
||||
fontStylesheetHref: fontLibrary && fontLibrary.stylesheetHref ? fontLibrary.stylesheetHref : '',
|
||||
fontFamilyFormats: fontLibrary && fontLibrary.fontFamilyFormats ? fontLibrary.fontFamilyFormats : '',
|
||||
existingTemplateId: slide && slide.template_id ? slide.template_id : null,
|
||||
existingContent: slide && slide.content ? slide.content : {}
|
||||
},
|
||||
assetVersion: assetVersion,
|
||||
slideEditorScripts: getRegionEditorScripts(assetVersion),
|
||||
currentUser: currentUser || null
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,61 +1,8 @@
|
||||
// Template add page renderer and form defaults.
|
||||
// Template add page renderer.
|
||||
|
||||
const { renderView } = require('../../../view');
|
||||
const { getRegionEditorScripts } = require('../../../lib/region-scripts');
|
||||
const { buildTemplateFormViewModel } = require('./form-view-model');
|
||||
|
||||
function buildDefaultTemplate() {
|
||||
return {
|
||||
id: null,
|
||||
name: '',
|
||||
canvas_size_id: null,
|
||||
canvas_size_width: 1920,
|
||||
canvas_size_height: 1080,
|
||||
background_color: '#111111',
|
||||
background_image_path: '',
|
||||
region_usage: [],
|
||||
regions: []
|
||||
};
|
||||
}
|
||||
|
||||
function resolveTemplateCanvasSize(template, canvasSizes) {
|
||||
const currentWidth = Math.max(1, Number(template.canvas_size_width || 1920));
|
||||
const currentHeight = Math.max(1, Number(template.canvas_size_height || 1080));
|
||||
const sizeId = template.canvas_size_id ? Number(template.canvas_size_id) : null;
|
||||
const matched = (canvasSizes || []).find((size) => Number(size.id) === sizeId)
|
||||
|| (canvasSizes || []).find((size) => Number(size.width) === currentWidth && Number(size.height) === currentHeight);
|
||||
|
||||
if (matched) {
|
||||
return Object.assign({}, template, {
|
||||
canvas_size_id: matched.id,
|
||||
canvas_size_label: matched.name,
|
||||
canvas_size_width: matched.width,
|
||||
canvas_size_height: matched.height,
|
||||
background_color: String(template.background_color || '#111111').trim() || '#111111'
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign({}, template, {
|
||||
canvas_size_id: '',
|
||||
canvas_size_label: 'Current size (' + currentWidth + 'x' + currentHeight + ')',
|
||||
canvas_size_width: currentWidth,
|
||||
canvas_size_height: currentHeight,
|
||||
background_color: String(template.background_color || '#111111').trim() || '#111111'
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = function renderTemplateFormPage(template, mode, message, canvasSizes, currentUser) {
|
||||
const isEdit = mode === 'edit';
|
||||
const current = resolveTemplateCanvasSize(template || buildDefaultTemplate(), canvasSizes || []);
|
||||
const assetVersion = Date.now().toString(36);
|
||||
|
||||
return renderView(isEdit ? 'templates/edit' : 'templates/add', {
|
||||
title: isEdit ? 'Edit template' : 'Create Template',
|
||||
active: 'templates',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
template: current,
|
||||
inUse: Boolean(current.inUse),
|
||||
canvasSizes: canvasSizes || [],
|
||||
scripts: ['js/lib/modal.js?v=' + assetVersion].concat(getRegionEditorScripts(assetVersion), ['js/templates/template-designer-utils.js?v=' + assetVersion, 'js/templates/template-designer.js?v=' + assetVersion])
|
||||
});
|
||||
module.exports = function renderTemplateAddPage(template, message, canvasSizes, currentUser) {
|
||||
return renderView('templates/form', buildTemplateFormViewModel(template, message, canvasSizes, currentUser, false));
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// Template edit page renderer that reuses the add form.
|
||||
// Template edit page renderer.
|
||||
|
||||
const renderTemplateFormPage = require('./add');
|
||||
const { renderView } = require('../../../view');
|
||||
const { buildTemplateFormViewModel } = require('./form-view-model');
|
||||
|
||||
module.exports = function renderTemplateEditPage(template, data, message, currentUser) {
|
||||
return renderTemplateFormPage(template, 'edit', message, data && data.canvasSizes ? data.canvasSizes : [], currentUser);
|
||||
return renderView('templates/form', buildTemplateFormViewModel(template, message, data && data.canvasSizes ? data.canvasSizes : [], currentUser, true));
|
||||
};
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
// Shared template form view-model builder.
|
||||
|
||||
const { getRegionEditorScripts } = require('../../../lib/region-scripts');
|
||||
|
||||
function buildDefaultTemplate() {
|
||||
return {
|
||||
id: null,
|
||||
name: '',
|
||||
canvas_size_id: null,
|
||||
canvas_size_width: 1920,
|
||||
canvas_size_height: 1080,
|
||||
background_color: '#111111',
|
||||
background_image_path: '',
|
||||
region_usage: [],
|
||||
regions: []
|
||||
};
|
||||
}
|
||||
|
||||
function resolveTemplateCanvasSize(template, canvasSizes) {
|
||||
const currentWidth = Math.max(1, Number(template.canvas_size_width || 1920));
|
||||
const currentHeight = Math.max(1, Number(template.canvas_size_height || 1080));
|
||||
const sizeId = template.canvas_size_id ? Number(template.canvas_size_id) : null;
|
||||
const matched = (canvasSizes || []).find((size) => Number(size.id) === sizeId)
|
||||
|| (canvasSizes || []).find((size) => Number(size.width) === currentWidth && Number(size.height) === currentHeight);
|
||||
|
||||
if (matched) {
|
||||
return Object.assign({}, template, {
|
||||
canvas_size_id: matched.id,
|
||||
canvas_size_label: matched.name,
|
||||
canvas_size_width: matched.width,
|
||||
canvas_size_height: matched.height,
|
||||
background_color: String(template.background_color || '#111111').trim() || '#111111'
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign({}, template, {
|
||||
canvas_size_id: '',
|
||||
canvas_size_label: 'Current size (' + currentWidth + 'x' + currentHeight + ')',
|
||||
canvas_size_width: currentWidth,
|
||||
canvas_size_height: currentHeight,
|
||||
background_color: String(template.background_color || '#111111').trim() || '#111111'
|
||||
});
|
||||
}
|
||||
|
||||
function buildTemplateFormViewModel(template, message, canvasSizes, currentUser, isEdit) {
|
||||
const current = resolveTemplateCanvasSize(template || buildDefaultTemplate(), canvasSizes || []);
|
||||
const assetVersion = Date.now().toString(36);
|
||||
|
||||
return {
|
||||
title: isEdit ? 'Edit template' : 'Create Template',
|
||||
active: 'templates',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
template: current,
|
||||
inUse: Boolean(current.inUse),
|
||||
isEdit: Boolean(isEdit),
|
||||
showSaveSecondaryActions: Boolean(isEdit),
|
||||
deleteDisabled: !isEdit || Boolean(current.inUse),
|
||||
formAction: isEdit && current.id ? '/templates/' + current.id : '/templates',
|
||||
formAttrs: 'data-template-has-slides="' + (current.slide_count ? 'true' : 'false') + '" data-async-save' + (isEdit ? ' data-async-save-close-url="/templates"' : ' data-async-save-new-redirect="response-url"') + ' data-async-save-new-url="/templates/new"',
|
||||
cancelUrl: '/templates',
|
||||
deleteUrl: isEdit && current.id ? '/templates/' + current.id + '/delete' : '',
|
||||
canvasSizes: canvasSizes || [],
|
||||
scripts: ['js/lib/modal.js?v=' + assetVersion].concat(getRegionEditorScripts(assetVersion), ['js/templates/template-designer-utils.js?v=' + assetVersion, 'js/templates/template-designer.js?v=' + assetVersion])
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildTemplateFormViewModel: buildTemplateFormViewModel
|
||||
};
|
||||
Reference in New Issue
Block a user