229 lines
8.1 KiB
JavaScript
229 lines
8.1 KiB
JavaScript
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 directMediaPath = String(slide.media_path || '').trim();
|
|
if (String(slide.media_type || '').trim().toLowerCase() === 'video' && directMediaPath) {
|
|
return directMediaPath;
|
|
}
|
|
|
|
const contentJson = slide.content_json;
|
|
if (!contentJson) {
|
|
return directMediaPath;
|
|
}
|
|
|
|
try {
|
|
const parsed = typeof contentJson === 'string' ? JSON.parse(contentJson) : contentJson;
|
|
if (!parsed || typeof parsed !== 'object') {
|
|
return directMediaPath;
|
|
}
|
|
|
|
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() : directMediaPath;
|
|
} catch (_error) {
|
|
return directMediaPath;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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: String(item.media_type || '').trim().toLowerCase() === 'video' || 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: String(slide.media_type || '').trim().toLowerCase() === 'video' || 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,
|
|
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', 'js/vendor/sortable.min.js?v=20260726.7', 'js/playlists/playlist-schedule.js?v=20260726.7']
|
|
});
|
|
};
|