Save worktree changes
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
const { renderView } = require('../../../view');
|
||||
|
||||
module.exports = function renderPlaylistFormPage(message, currentUser) {
|
||||
return renderView('playlists/add', {
|
||||
title: 'Create playlist',
|
||||
active: 'playlists',
|
||||
playlist: {
|
||||
fade_between_slides: false
|
||||
},
|
||||
message: message,
|
||||
currentUser: currentUser || null
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
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 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;
|
||||
}
|
||||
|
||||
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) || '',
|
||||
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)
|
||||
}));
|
||||
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),
|
||||
playlistSlides: playlistSlides,
|
||||
slides: data.slides || [],
|
||||
hasSlides: (data.slides || []).length > 0,
|
||||
hasSelectableSlides: selectableSlides.length > 0,
|
||||
hasAddableSlides: hasAddableSlides,
|
||||
selectableSlides: selectableSlides,
|
||||
playlistCanvasSignature: playlistCanvasSignature,
|
||||
playlistCanvasMismatch: playlistCanvasMismatch,
|
||||
scripts: ['js/vendor/sortable.min.js', 'js/playlists/playlist-schedule.js']
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
const { renderView } = require('../../../view');
|
||||
|
||||
module.exports = function renderPlaylistsPage(data, message, currentUser) {
|
||||
const playlistSlides = data.playlistSlides || [];
|
||||
const playlists = (data.playlists || []).map((playlist) => {
|
||||
const slideCount = playlistSlides.filter((item) => item.playlist_id === playlist.id).length;
|
||||
return Object.assign({}, playlist, {
|
||||
slideCount: slideCount,
|
||||
slideCountIsOne: slideCount === 1
|
||||
});
|
||||
});
|
||||
|
||||
return renderView('playlists/list', {
|
||||
title: 'Playlists',
|
||||
active: 'playlists',
|
||||
message: message,
|
||||
currentUser: currentUser || null,
|
||||
playlists: playlists
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
module.exports = function registerPlaylistsRoutes(app, deps) {
|
||||
const pool = deps.pool;
|
||||
const common = deps.common;
|
||||
const pages = deps.pages;
|
||||
const requirePermission = deps.requirePermission;
|
||||
|
||||
function requireQueryPermission(readPermissionKey, editPermissionKey) {
|
||||
return function (req, res, next) {
|
||||
const permissionKey = req.query && req.query.edit ? editPermissionKey : readPermissionKey;
|
||||
return requirePermission(permissionKey)(req, res, next);
|
||||
};
|
||||
}
|
||||
|
||||
app.get('/playlists', requireQueryPermission('playlists.read', 'playlists.update'), async function (req, res, next) {
|
||||
try {
|
||||
const data = await common.fetchAdminData(pool);
|
||||
if (req.query.edit) {
|
||||
const playlist = await common.fetchPlaylistById(pool, Number(req.query.edit));
|
||||
if (!playlist) {
|
||||
return res.status(404).send('Playlist not found');
|
||||
}
|
||||
return res.send(pages.renderPlaylistEditPage(playlist, data, req.query.message ? String(req.query.message) : '', req.currentUser));
|
||||
}
|
||||
res.send(pages.renderPlaylistsPage(data, req.query.message ? String(req.query.message) : '', req.currentUser));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/playlists/new', requirePermission('playlists.create'), function (req, res) {
|
||||
res.send(pages.renderPlaylistFormPage(req.query.message ? String(req.query.message) : '', req.currentUser));
|
||||
});
|
||||
|
||||
app.get('/playlists/:id/edit', requirePermission('playlists.update'), async function (req, res, next) {
|
||||
try {
|
||||
const playlist = await common.fetchPlaylistById(pool, Number(req.params.id));
|
||||
if (!playlist) {
|
||||
return res.status(404).send('Playlist not found');
|
||||
}
|
||||
const data = await common.fetchAdminData(pool);
|
||||
res.send(pages.renderPlaylistEditPage(playlist, data, req.query.message ? String(req.query.message) : '', req.currentUser));
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
const { renderFragment } = require('../../../view');
|
||||
|
||||
const DAY_OPTIONS = [
|
||||
{ value: 0, label: 'Sun' },
|
||||
{ value: 1, label: 'Mon' },
|
||||
{ value: 2, label: 'Tue' },
|
||||
{ value: 3, label: 'Wed' },
|
||||
{ value: 4, label: 'Thu' },
|
||||
{ value: 5, label: 'Fri' },
|
||||
{ value: 6, label: 'Sat' }
|
||||
];
|
||||
|
||||
const DEFAULT_START_TIME = '00:00';
|
||||
const DEFAULT_END_TIME = '23:59';
|
||||
|
||||
function formatDateTimeInput(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}T${hours}:${minutes}`;
|
||||
}
|
||||
|
||||
function formatTimeInput(value) {
|
||||
if (!value) {
|
||||
return '';
|
||||
}
|
||||
return String(value).slice(0, 5);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
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 : '');
|
||||
|
||||
return renderFragment('playlists/slide-config', {
|
||||
title: 'Slide schedule',
|
||||
framePopupCard: true,
|
||||
hideFrameHeader: true,
|
||||
hideThemeToggle: true,
|
||||
frameBodyClass: 'admin-shell-body--frame-plain',
|
||||
message: message,
|
||||
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/playlists/playlist-schedule.js']
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user