(function () {
// Shared constants used across the playlist schedule editor.
var DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
var PICKER_THUMB_EMPTY_LABEL = 'No thumbnail';
var PICKER_ASSIGNED_BADGE_LABEL = 'Already in playlist';
var PICKER_CARD_CLASS = 'playlist-slide-picker-card';
var PICKER_MEDIA_CLASS = 'playlist-slide-picker-media';
var PICKER_TITLE_CLASS = 'playlist-slide-picker-title';
var PICKER_CHECK_CLASS = 'playlist-slide-picker-check bi bi-check2-circle';
var PICKER_BADGE_CLASS = 'playlist-slide-picker-badge badge text-bg-primary';
var SCHEDULE_ROW_FIELD_NAMES = {
rulesContainer: '[data-schedule-rules-container]',
summaryNode: '.playlist-schedule-summary'
};
// Schedule helpers.
function formatDays(daysValue) {
var days = [];
if (Array.isArray(daysValue)) {
days = daysValue;
} else if (daysValue) {
try {
var parsedDays = JSON.parse(daysValue);
days = Array.isArray(parsedDays) ? parsedDays : [];
} catch (_error) {
days = [];
}
}
return days
.map(function (day) {
return DAY_NAMES[Number(day)] || '';
})
.filter(Boolean)
.join(', ');
}
function normalizeScheduleRuleValue(rule) {
var input = rule && typeof rule === 'object' ? rule : {};
var startDatetime = String(input.start_datetime || input.startDateTime || input.startDateTimeValue || '').trim();
var endDatetime = String(input.end_datetime || input.endDateTime || input.endDateTimeValue || '').trim();
var startTime = String(input.start_time || input.startTime || input.startTimeValue || '').trim();
var endTime = String(input.end_time || input.endTime || input.endTimeValue || '').trim();
var days = [];
if (Array.isArray(input.days)) {
days = input.days;
} else if (Array.isArray(input.dayOptions)) {
days = input.dayOptions.filter(function (dayOption) {
return Boolean(dayOption && dayOption.checked);
}).map(function (dayOption) {
return dayOption.value;
});
} else if (input.daysCsv !== undefined && input.daysCsv !== null && String(input.daysCsv).trim()) {
days = String(input.daysCsv).split(',');
} else if (input.days !== undefined && input.days !== null && String(input.days).trim()) {
try {
var parsedDays = JSON.parse(String(input.days));
days = Array.isArray(parsedDays) ? parsedDays : [];
} catch (_error) {
days = String(input.days).split(',');
}
}
days = Array.from(new Set(days.map(function (day) {
return Number(day);
}).filter(function (day) {
return Number.isInteger(day) && day >= 0 && day <= 6;
}))).sort(function (left, right) {
return left - right;
});
if (startDatetime || endDatetime) {
if (!startDatetime || !endDatetime) {
return null;
}
}
if (startTime || endTime) {
if (!startTime || !endTime) {
return null;
}
}
if (!startDatetime && !endDatetime && !startTime && !endTime && !days.length) {
return null;
}
var normalized = {};
if (startDatetime) {
normalized.start_datetime = startDatetime;
}
if (endDatetime) {
normalized.end_datetime = endDatetime;
}
if (startTime) {
normalized.start_time = startTime;
}
if (endTime) {
normalized.end_time = endTime;
}
if (days.length) {
normalized.days = days;
}
return normalized;
}
function parseScheduleRulesValue(value) {
var rawRules = [];
if (Array.isArray(value)) {
rawRules = value;
} else {
var raw = String(value || '').trim();
if (!raw) {
return [];
}
try {
var parsed = JSON.parse(raw);
rawRules = Array.isArray(parsed) ? parsed : [];
} catch (_error) {
return [];
}
}
return rawRules.map(function (rule) {
return normalizeScheduleRuleValue(rule);
}).filter(Boolean);
}
function formatScheduleRuleSummary(rule) {
var normalized = normalizeScheduleRuleValue(rule);
var left = [];
var right = [];
if (!normalized) {
return 'Always visible';
}
if (normalized.start_datetime && normalized.end_datetime) {
left.push('Dates ' + normalized.start_datetime.replace('T', ' ').slice(0, 16) + ' to ' + normalized.end_datetime.replace('T', ' ').slice(0, 16));
} else {
left.push('Any date');
}
var days = formatDays(normalized.days || []);
if (days) {
right.push(days);
}
if (normalized.start_time && normalized.end_time) {
right.push(normalized.start_time.slice(0, 5) + '-' + normalized.end_time.slice(0, 5));
}
if (right.length) {
return left[0] + ': ' + right.join(' ');
}
return left[0];
}
function formatScheduleRulesSummary(value) {
var rules = parseScheduleRulesValue(value);
if (!rules.length) {
return 'Always visible';
}
return rules.length === 1 ? '1 rule' : String(rules.length) + ' rules';
}
function getScheduleRowFields(row) {
return {
rulesContainer: row ? row.querySelector(SCHEDULE_ROW_FIELD_NAMES.rulesContainer) : null,
summaryNode: row ? row.querySelector(SCHEDULE_ROW_FIELD_NAMES.summaryNode) : null
};
}
function buildScheduleRuleHiddenInputs(rule, rowKey, position, formId) {
var normalizedRule = normalizeScheduleRuleValue(rule) || {};
var daysCsv = Array.isArray(normalizedRule.days) ? normalizedRule.days.join(',') : '';
var formAttr = formId ? ' form="' + formId + '"' : '';
return '' +
'' +
'' +
'' +
'' +
'' +
'' +
'';
}
function buildScheduleRulesMarkup(values, rowKey, formId) {
var rules = Array.isArray(values) ? values : [];
return rules.map(function (rule, index) {
return buildScheduleRuleHiddenInputs(rule, rowKey, index, formId);
}).join('');
}
function parseLocalDate(value) {
var date = new Date(String(value || '').trim());
if (Number.isNaN(date.getTime())) {
return null;
}
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}
function isDayInDateRange(startDatetime, endDatetime, dayIndex) {
var startDate = parseLocalDate(startDatetime);
var endDate = parseLocalDate(endDatetime);
var currentDate;
if (!startDate || !endDate) {
return false;
}
for (currentDate = new Date(startDate.getTime()); currentDate.getTime() <= endDate.getTime(); currentDate.setDate(currentDate.getDate() + 1)) {
if (currentDate.getDay() === Number(dayIndex)) {
return true;
}
}
return false;
}
function getTimeParts(value) {
var match = String(value || '').trim().match(/^(\d{2}):(\d{2})(?::\d{2})?$/);
if (!match) {
return null;
}
return {
hours: Number(match[1]),
minutes: Number(match[2])
};
}
function combineDateAndTime(baseDate, timeValue) {
var timeParts = getTimeParts(timeValue);
if (!baseDate || !timeParts) {
return null;
}
return new Date(
baseDate.getFullYear(),
baseDate.getMonth(),
baseDate.getDate(),
timeParts.hours,
timeParts.minutes,
0,
0
);
}
function hasScheduleRuleOverlap(startDatetime, endDatetime, startTime, endTime, selectedDays) {
var startDateTime = new Date(String(startDatetime || '').trim());
var endDateTime = new Date(String(endDatetime || '').trim());
var startDate = parseLocalDate(startDatetime);
var endDate = parseLocalDate(endDatetime);
var allowedDays = Array.isArray(selectedDays) && selectedDays.length ? selectedDays : [0, 1, 2, 3, 4, 5, 6];
var currentDate;
if (!startDate || !endDate || Number.isNaN(startDateTime.getTime()) || Number.isNaN(endDateTime.getTime())) {
return false;
}
for (currentDate = new Date(startDate.getTime()); currentDate.getTime() <= endDate.getTime(); currentDate.setDate(currentDate.getDate() + 1)) {
if (allowedDays.indexOf(currentDate.getDay()) === -1) {
continue;
}
var dayStart = combineDateAndTime(currentDate, startTime);
var dayEnd = combineDateAndTime(currentDate, endTime);
if (!dayStart || !dayEnd) {
continue;
}
if (dayEnd >= startDateTime && dayStart <= endDateTime) {
return true;
}
}
return false;
}
function getDateRangeDayIndices(startDatetime, endDatetime) {
var startDate = parseLocalDate(startDatetime);
var endDate = parseLocalDate(endDatetime);
var dayIndices = [];
var currentDate;
if (!startDate || !endDate) {
return dayIndices;
}
for (currentDate = new Date(startDate.getTime()); currentDate.getTime() <= endDate.getTime(); currentDate.setDate(currentDate.getDate() + 1)) {
if (dayIndices.indexOf(currentDate.getDay()) === -1) {
dayIndices.push(currentDate.getDay());
}
}
return dayIndices;
}
function setDayInputError(card, dayInput, message) {
var dayInputs = card ? Array.prototype.slice.call(card.querySelectorAll('[data-schedule-rule-day]')) : [];
var dayLabels = card ? Array.prototype.slice.call(card.querySelectorAll('.schedule-day-button')) : [];
var feedbackNode = card ? card.querySelector('[data-schedule-rule-feedback]') : null;
var dayIndex = dayInputs.indexOf(dayInput);
if (dayInput) {
dayInput.setCustomValidity(message);
dayInput.classList.add('is-invalid');
dayInput.setAttribute('aria-invalid', 'true');
}
if (dayIndex !== -1 && dayLabels[dayIndex]) {
dayLabels[dayIndex].classList.add('is-invalid');
dayLabels[dayIndex].setAttribute('aria-invalid', 'true');
}
if (feedbackNode) {
var messageNode = document.createElement('div');
messageNode.textContent = message;
feedbackNode.appendChild(messageNode);
feedbackNode.hidden = false;
}
}
function appendRuleFeedbackMessage(card, message) {
var feedbackNode = card ? card.querySelector('[data-schedule-rule-feedback]') : null;
if (!feedbackNode) {
return;
}
var messageNode = document.createElement('div');
messageNode.textContent = message;
feedbackNode.appendChild(messageNode);
feedbackNode.hidden = false;
}
function validateScheduleRuleCard(card) {
var fields = getRuleFields(card);
var dayInputs = Array.prototype.slice.call(fields.days || []);
var dayLabels = getRuleDayLabels(card);
var checkedDayInputs = dayInputs.filter(function (input) {
return Boolean(input && input.checked);
});
var checkedDayIndices = checkedDayInputs.map(function (input) {
return Number(input && input.value);
});
var startDatetime = String(fields.startDatetime && fields.startDatetime.value || '').trim();
var endDatetime = String(fields.endDatetime && fields.endDatetime.value || '').trim();
var startTime = String(fields.startTime && fields.startTime.value || '').trim();
var endTime = String(fields.endTime && fields.endTime.value || '').trim();
clearRuleValidity(card);
if ((startDatetime || endDatetime) && (!startDatetime || !endDatetime)) {
setRuleError(!startDatetime ? fields.startDatetime : fields.endDatetime, 'Start and end datetimes must both be set for this rule.');
return false;
}
if ((startTime || endTime) && (!startTime || !endTime)) {
setRuleError(!startTime ? fields.startTime : fields.endTime, 'Start and end times must both be set for this rule.');
return false;
}
if (startDatetime && endDatetime && new Date(endDatetime) <= new Date(startDatetime)) {
setRuleError(fields.endDatetime, 'End datetime must be after start datetime.');
return false;
}
if (startTime && endTime && endTime <= startTime) {
setRuleError(fields.endTime, 'End time must be after start time.');
if (fields.startTime) {
fields.startTime.classList.add('is-invalid');
fields.startTime.setAttribute('aria-invalid', 'true');
}
return false;
}
if (startDatetime && endDatetime) {
var dateRangeDayIndices = getDateRangeDayIndices(startDatetime, endDatetime);
var dayIndicesToCheck = checkedDayIndices.length ? checkedDayIndices : [0, 1, 2, 3, 4, 5, 6];
var invalidDayInputs = checkedDayInputs.filter(function (input) {
return dateRangeDayIndices.indexOf(Number(input && input.value)) === -1;
});
invalidDayInputs.forEach(function (input) {
var dayIndex = dayInputs.indexOf(input);
if (dayIndex !== -1 && dayLabels[dayIndex]) {
dayLabels[dayIndex].classList.add('is-invalid');
dayLabels[dayIndex].setAttribute('aria-invalid', 'true');
}
});
if (checkedDayInputs.length && invalidDayInputs.length === checkedDayInputs.length) {
setDayInputError(card, invalidDayInputs[0] || checkedDayInputs[0] || dayInputs[0], 'Selected days must overlap the date range.');
return false;
}
if (startTime && endTime && !hasScheduleRuleOverlap(startDatetime, endDatetime, startTime, endTime, dayIndicesToCheck)) {
setRuleError(fields.endTime, 'Start and end times must overlap the date range.');
if (fields.startTime) {
fields.startTime.classList.add('is-invalid');
fields.startTime.setAttribute('aria-invalid', 'true');
}
return false;
}
}
return true;
}
function getScheduleRuleValidationIssue(card) {
var fields = getRuleFields(card);
var dayInputs = Array.prototype.slice.call(fields.days || []);
var checkedDayInputs = dayInputs.filter(function (input) {
return Boolean(input && input.checked);
});
var checkedDayIndices = checkedDayInputs.map(function (input) {
return Number(input && input.value);
});
var startDatetime = String(fields.startDatetime && fields.startDatetime.value || '').trim();
var endDatetime = String(fields.endDatetime && fields.endDatetime.value || '').trim();
var startTime = String(fields.startTime && fields.startTime.value || '').trim();
var endTime = String(fields.endTime && fields.endTime.value || '').trim();
if (startDatetime || endDatetime) {
if (!startDatetime || !endDatetime) {
return {
type: 'datetime-pair',
target: !startDatetime ? fields.startDatetime : fields.endDatetime,
message: 'Start and end datetimes must both be set for this rule.'
};
}
}
if (startTime || endTime) {
if (!startTime || !endTime) {
return {
type: 'time-pair',
target: !startTime ? fields.startTime : fields.endTime,
message: 'Start and end times must both be set for this rule.'
};
}
}
if (startDatetime && endDatetime && new Date(endDatetime) <= new Date(startDatetime)) {
return {
type: 'datetime-order',
target: fields.endDatetime,
message: 'End datetime must be after start datetime.'
};
}
if (startTime && endTime && endTime <= startTime) {
return {
type: 'time-order',
target: fields.endTime,
message: 'End time must be after start time.'
};
}
if (startDatetime && endDatetime) {
var dateRangeDayIndices = getDateRangeDayIndices(startDatetime, endDatetime);
var dayIndicesToCheck = checkedDayIndices.length ? checkedDayIndices : [0, 1, 2, 3, 4, 5, 6];
var invalidDayInputs = checkedDayInputs.filter(function (input) {
return dateRangeDayIndices.indexOf(Number(input && input.value)) === -1;
});
if (checkedDayInputs.length && invalidDayInputs.length === checkedDayInputs.length) {
return {
type: 'day-overlap',
target: checkedDayInputs[0] || dayInputs[0] || fields.endDatetime,
message: 'Selected days must overlap the date range.'
};
}
if (startTime && endTime && !hasScheduleRuleOverlap(startDatetime, endDatetime, startTime, endTime, dayIndicesToCheck)) {
return {
type: 'time-overlap',
target: fields.endTime,
secondaryTarget: fields.startTime,
message: 'Start and end times must overlap the date range.'
};
}
}
return null;
}
function applyScheduleRuleValidationIssue(card, issue) {
var fields = getRuleFields(card);
var dayLabels = getRuleDayLabels(card);
clearRuleValidity(card);
if (!issue) {
return true;
}
if (issue.type === 'time-overlap') {
setRuleError(issue.target || fields.endTime, issue.message);
if (issue.secondaryTarget) {
issue.secondaryTarget.classList.add('is-invalid');
issue.secondaryTarget.setAttribute('aria-invalid', 'true');
}
return false;
}
if (issue.type === 'day-overlap') {
var dayInputs = Array.prototype.slice.call(fields.days || []);
var dayIndex = dayInputs.indexOf(issue.target);
setRuleError(issue.target, issue.message);
if (dayIndex !== -1 && dayLabels[dayIndex]) {
dayLabels[dayIndex].classList.add('is-invalid');
dayLabels[dayIndex].setAttribute('aria-invalid', 'true');
}
return false;
}
setRuleError(issue.target, issue.message);
return false;
}
// Video duration helpers.
function getVideoDurationButtonLabel(isPressed) {
return isPressed ? 'Disable use video duration' : 'Use video duration';
}
function buildVideoDurationButtonContent(isPressed) {
return '' +
'' +
'' + getVideoDurationButtonLabel(isPressed) + '';
}
function buildVideoDurationButtonMarkup(isPressed, enabled) {
if (!enabled) {
return '';
}
return '';
}
function getDisableAudioButtonLabel(isPressed) {
return isPressed ? 'Enable audio' : 'Mute slide';
}
function buildDisableAudioButtonContent(isPressed) {
return '' +
'' +
'' + getDisableAudioButtonLabel(isPressed) + '';
}
function buildDisableAudioButtonMarkup(isPressed, enabled) {
if (!enabled) {
return '';
}
return '';
}
function syncDisableAudioButtonState(button, isPressed) {
if (!button) {
return;
}
button.classList.toggle('btn-outline-secondary', Boolean(isPressed));
button.classList.toggle('btn-secondary', !Boolean(isPressed));
button.setAttribute('aria-pressed', isPressed ? 'true' : 'false');
button.setAttribute('aria-label', getDisableAudioButtonLabel(isPressed));
button.setAttribute('title', getDisableAudioButtonLabel(isPressed));
button.innerHTML = buildDisableAudioButtonContent(isPressed);
}
function syncVideoDurationButtonState(button, isPressed) {
if (!button) {
return;
}
button.classList.toggle('btn-outline-info', !Boolean(isPressed));
button.classList.toggle('btn-info', Boolean(isPressed));
button.setAttribute('aria-pressed', isPressed ? 'true' : 'false');
button.setAttribute('aria-label', getVideoDurationButtonLabel(isPressed));
button.setAttribute('title', getVideoDurationButtonLabel(isPressed));
button.innerHTML = buildVideoDurationButtonContent(isPressed);
}
// Slide picker helpers.
function getSlidePickerEmptyMessage(query, showAssignedSlides, assignedCount, visibleCount) {
if (!showAssignedSlides && assignedCount && visibleCount === 0) {
return query ? 'No available slides match your search.' : 'No available slides are currently left for this playlist.';
}
return query ? 'No slides match your search.' : 'No slides are currently available for this playlist.';
}
function updateSlidePickerCardState(card, isAssigned, isCanvasMismatch) {
if (!card) {
return;
}
card.setAttribute('data-is-assigned', isAssigned ? 'true' : 'false');
card.classList.toggle('is-assigned', isAssigned);
card.classList.toggle('is-mismatch', isCanvasMismatch);
card.classList.toggle('is-hidden', false);
}
function createElementWithClass(tagName, className, textContent) {
var element = document.createElement(tagName);
if (className) {
element.className = className;
}
if (textContent !== undefined) {
element.textContent = textContent;
}
return element;
}
function escapeHtmlAttribute(value) {
return String(value === undefined || value === null ? '' : value)
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(//g, '>');
}
function buildScheduleSummaryChipMarkup(summary) {
return '' + escapeHtmlAttribute(String(summary || 'Always visible')) + '';
}
function buildSlidePickerThumbPlaceholder() {
var placeholder = createElementWithClass('div', 'playlist-slide-picker-thumb-placeholder');
var icon = createElementWithClass('i', 'bi bi-image playlist-slide-picker-thumb-placeholder-icon');
var label = createElementWithClass('span', 'playlist-slide-picker-thumb-placeholder-label', PICKER_THUMB_EMPTY_LABEL);
icon.setAttribute('aria-hidden', 'true');
placeholder.appendChild(icon);
placeholder.appendChild(label);
return placeholder;
}
function buildSlidePickerThumbImage(slide) {
var image = createElementWithClass('img', 'playlist-slide-picker-thumb-image');
image.loading = 'lazy';
image.alt = String(slide.title || 'Slide');
image.src = String(slide.thumbnail_path || '');
image.addEventListener('error', function () {
if (image.parentNode) {
image.parentNode.replaceChild(buildSlidePickerThumbPlaceholder(), image);
}
});
return image;
}
function buildSlidePickerMedia(slide) {
var media = createElementWithClass('div', PICKER_MEDIA_CLASS);
var check = createElementWithClass('span', PICKER_CHECK_CLASS);
var badge = createElementWithClass('span', PICKER_BADGE_CLASS, PICKER_ASSIGNED_BADGE_LABEL);
check.setAttribute('aria-hidden', 'true');
badge.setAttribute('aria-hidden', 'true');
media.appendChild(slide && slide.thumbnail_path ? buildSlidePickerThumbImage(slide) : buildSlidePickerThumbPlaceholder());
media.appendChild(check);
media.appendChild(badge);
return media;
}
// Playlist row helpers.
function getPlaylistSlideRow(target) {
return target && target.closest ? target.closest('tr[data-playlist-slide-row]') : null;
}
function getPlaylistSlideRowKey(row) {
return String(row && row.getAttribute('data-row-key') || '');
}
function isDurationControl(target) {
return Boolean(target && target.closest && target.closest('.playlist-use-video-duration'));
}
function isAudioControl(target) {
return Boolean(target && target.closest && target.closest('.playlist-disable-audio'));
}
function getDurationPointerState(target, type) {
var row = getPlaylistSlideRow(target);
var pointerType = type === 'button' ? 'button' : 'input';
if (!row) {
return null;
}
return {
type: pointerType,
rowKey: getPlaylistSlideRowKey(row)
};
}
function buildPlaylistOrderCellMarkup() {
return '' +
'
' +
'' +
' ' +
'' +
'' +
'' +
' ' +
' ' +
' ' +
' | ';
}
function buildPlaylistSlideCellMarkup(values, thumbnailStyle, thumbnailMarkup) {
return '' +
'' +
'' +
' ' + thumbnailMarkup + ' ' +
' ' + values.title + ' ' +
' ' +
'' +
'' +
' | ';
}
function buildPlaylistScheduleCellMarkup(values, rowKey) {
return '' +
'' +
' ' + buildScheduleSummaryChipMarkup(values.summary || 'Always visible') + ' ' +
'' +
buildScheduleRulesMarkup(values.scheduleRules, rowKey, 'playlist-edit-form') +
' ' +
' | ';
}
function buildPlaylistDurationCellMarkup(values, durationActionMarkup) {
return '' +
'' +
' ' +
'' +
'' +
' ' +
' ' +
' ' +
(values.useVideoDuration ? ' ' : '') +
durationActionMarkup +
' | ';
}
function buildPlaylistActionsCellMarkup(playlistId, rowKey) {
var scheduleUrl = '/playlists/new/slides/0/config';
var scheduleButtonMarkup = '';
return '' +
'' +
scheduleButtonMarkup +
'' +
' | ';
}
function collectScheduleParams(row, rowKey) {
var params = new URLSearchParams();
var fields = row ? Array.prototype.slice.call(row.querySelectorAll('[name^="schedule_rule_"]')) : [];
params.set('row_key', String(rowKey || ''));
fields.forEach(function (field) {
if (!field || !field.name) {
return;
}
params.append(field.name, String(field.value || ''));
});
return params;
}
function openScheduleModal(url) {
var dialog = document.getElementById('slide-schedule-dialog');
var content = document.getElementById('slide-schedule-content');
if (!dialog || !content) {
return;
}
content.innerHTML = '';
if (typeof dialog.showModal === 'function') {
dialog.showModal();
} else {
dialog.setAttribute('open', 'open');
}
fetch(url, { credentials: 'same-origin' }).then(function (response) {
if (!response.ok) {
return response.text().then(function (text) {
throw new Error(text || 'Unable to open schedule editor.');
});
}
return response.text();
}).then(function (html) {
content.innerHTML = html;
if (typeof window.initPlaylistScheduleForm === 'function') {
window.initPlaylistScheduleForm(content);
}
}).catch(function (error) {
content.innerHTML = '' + escapeHtmlAttribute(String(error && error.message ? error.message : 'Unable to open schedule editor.')) + '
';
});
}
function closeScheduleModal(options) {
var dialog = document.getElementById('slide-schedule-dialog');
var content = document.getElementById('slide-schedule-content');
var shouldDiscardDraft = Boolean(options && options.discardDraft);
var shouldPreserveChanges = Boolean(options && options.preserveChanges);
if (shouldDiscardDraft && !shouldPreserveChanges && typeof window.restorePlaylistScheduleDraft === 'function') {
window.restorePlaylistScheduleDraft();
}
if (shouldDiscardDraft && typeof window.clearPlaylistScheduleDraft === 'function') {
window.clearPlaylistScheduleDraft();
}
if (content) {
content.innerHTML = '';
}
if (!dialog) {
return;
}
if (typeof dialog.close === 'function') {
dialog.close();
} else {
dialog.removeAttribute('open');
}
}
function initPlaylistScheduleModal() {
window.openScheduleModal = openScheduleModal;
window.closeScheduleModal = closeScheduleModal;
}
// Keep schedule rule markup in sync with each rendered playlist row.
function hydrateScheduleRowContainers(root) {
var scope = root || document;
var rows = Array.prototype.slice.call(scope.querySelectorAll('[data-playlist-slide-row]'));
rows.forEach(function (row) {
var fields = getScheduleRowFields(row);
var data = fields.rulesContainer ? fields.rulesContainer.getAttribute('data-schedule-rules') : '';
var rules = [];
if (!fields.rulesContainer) {
return;
}
if (!data && fields.rulesContainer.children.length) {
return;
}
if (data) {
try {
rules = JSON.parse(data);
} catch (_error) {
rules = [];
}
}
if (!fields.rulesContainer.children.length) {
fields.rulesContainer.innerHTML = buildScheduleRulesMarkup(rules, row.getAttribute('data-row-key') || '', 'playlist-edit-form');
}
});
}
function setRowSchedule(row, values) {
var rowKey = String(values && values.row_key ? values.row_key : row && row.getAttribute('data-row-key') || '');
var fields = getScheduleRowFields(row);
var rules = Array.isArray(values && values.scheduleRules) ? values.scheduleRules : [];
var summary = String(values && values.summary ? values.summary : formatScheduleRulesSummary(rules) || 'Always visible');
if (!row) {
return;
}
if (fields.summaryNode) {
fields.summaryNode.innerHTML = buildScheduleSummaryChipMarkup(summary || 'Always visible');
}
if (fields.rulesContainer) {
fields.rulesContainer.setAttribute('data-schedule-rules', JSON.stringify(rules).replace(/' +
'' +
'' +
'
' +
'
' +
'
' +
'
' +
'
' +
'';
}
// Modal form state, validation, and submit handling.
function initPlaylistScheduleForm(root) {
var scope = root || document;
var form = scope.querySelector('form[action*="/config"], form[data-schedule-draft="true"]');
var scheduleRuleList = scope.querySelector('#schedule-rule-list');
var effectiveSummary = scope.querySelector('#schedule-effective-summary');
var addRuleButton = scope.querySelector('#schedule-add-rule');
var scheduleRuleTemplate = scope.querySelector('#schedule-rule-card-template');
if (!form || !scheduleRuleList) {
return;
}
var isDraftMode = Boolean(form.getAttribute('data-schedule-draft') === 'true');
var rowKeyInput = form.querySelector('[name="row_key"]');
var draftStore = window.__playlistScheduleDraftStore = window.__playlistScheduleDraftStore || {};
var draftSnapshotStore = window.__playlistScheduleDraftSnapshotStore = window.__playlistScheduleDraftSnapshotStore || {};
var cancelButton = scope.querySelector('[data-schedule-cancel]');
// Draft state is keyed by the row being edited so cancel/save can restore it later.
function getScheduleDraftKey() {
return String(rowKeyInput && rowKeyInput.value ? rowKeyInput.value : '').trim();
}
function readRuleDraftFromCard(card) {
var fields = getRuleFields(card);
var days = [];
Array.prototype.forEach.call(fields.days || [], function (checkbox) {
if (checkbox.checked) {
days.push(Number(checkbox.value));
}
});
days = Array.from(new Set(days)).sort(function (left, right) {
return left - right;
});
return {
start_datetime: String(fields.startDatetime && fields.startDatetime.value || ''),
end_datetime: String(fields.endDatetime && fields.endDatetime.value || ''),
start_time: String(fields.startTime && fields.startTime.value || ''),
end_time: String(fields.endTime && fields.endTime.value || ''),
days: days
};
}
function captureScheduleDraftState() {
return getRuleCards().map(function (card) {
return {
rule: readRuleDraftFromCard(card),
isCollapsed: Boolean(card && card.classList && card.classList.contains('collapsed-card'))
};
});
}
function renderScheduleDraftState(ruleStates) {
var states = Array.isArray(ruleStates) ? ruleStates : [];
scheduleRuleList.innerHTML = '';
states.forEach(function (ruleState, index) {
var card = createRuleCardElement(ruleState && ruleState.rule ? ruleState.rule : {}, index + 1, { isCollapsed: Boolean(ruleState && ruleState.isCollapsed) });
if (card) {
scheduleRuleList.appendChild(card);
}
});
refreshRuleNumbers();
syncScheduleRulesField();
}
function restoreScheduleDraft() {
var draftKey = getScheduleDraftKey();
var snapshot = draftKey && Object.prototype.hasOwnProperty.call(draftSnapshotStore, draftKey) ? draftSnapshotStore[draftKey] : null;
if (!draftKey || !Array.isArray(snapshot)) {
return;
}
renderScheduleDraftState(snapshot);
}
function getScheduleDraftRules() {
var draftKey = getScheduleDraftKey();
if (!draftKey || !Object.prototype.hasOwnProperty.call(draftStore, draftKey)) {
return null;
}
return Array.isArray(draftStore[draftKey]) ? draftStore[draftKey] : [];
}
function persistScheduleDraft() {
var draftKey = getScheduleDraftKey();
if (!draftKey) {
return;
}
draftStore[draftKey] = getRuleCards().map(function (card) {
return {
rule: readRuleDraftFromCard(card),
isCollapsed: Boolean(card && card.classList && card.classList.contains('collapsed-card'))
};
});
}
function clearScheduleDraft() {
var draftKey = getScheduleDraftKey();
if (draftKey && Object.prototype.hasOwnProperty.call(draftStore, draftKey)) {
delete draftStore[draftKey];
}
if (draftKey && Object.prototype.hasOwnProperty.call(draftSnapshotStore, draftKey)) {
delete draftSnapshotStore[draftKey];
}
}
window.clearPlaylistScheduleDraft = clearScheduleDraft;
window.restorePlaylistScheduleDraft = restoreScheduleDraft;
var draftKey = getScheduleDraftKey();
if (draftKey && !Object.prototype.hasOwnProperty.call(draftSnapshotStore, draftKey)) {
draftSnapshotStore[draftKey] = captureScheduleDraftState();
}
function getRuleCards() {
return Array.prototype.slice.call(scheduleRuleList.querySelectorAll('[data-schedule-rule-card]'));
}
function getRuleFields(card) {
return {
startDatetime: card ? card.querySelector('[data-schedule-rule-start-datetime]') : null,
endDatetime: card ? card.querySelector('[data-schedule-rule-end-datetime]') : null,
startTime: card ? card.querySelector('[data-schedule-rule-start-time]') : null,
endTime: card ? card.querySelector('[data-schedule-rule-end-time]') : null,
days: card ? card.querySelectorAll('[data-schedule-rule-day]') : []
};
}
function getRuleSummaryNode(card) {
return card ? card.querySelector('[data-schedule-rule-summary]') : null;
}
function getRuleNumberNode(card) {
return card ? card.querySelector('[data-schedule-rule-number]') : null;
}
function getRuleRowKeyNode(card) {
return card ? card.querySelector('[data-schedule-rule-row-key]') : null;
}
function getRulePositionNode(card) {
return card ? card.querySelector('[data-schedule-rule-position]') : null;
}
function getRemoveRuleButton(card) {
return card ? card.querySelector('[data-remove-schedule-rule]') : null;
}
function getRuleDayLabels(card) {
return card ? Array.prototype.slice.call(card.querySelectorAll('.schedule-day-button')) : [];
}
function getCollapseRuleButton(card) {
return card ? card.querySelector('[data-schedule-rule-collapse], [data-lte-toggle="card-collapse"]') : null;
}
function getCollapseRuleIcon(card) {
return card ? card.querySelector('[data-schedule-rule-collapse-icon]') : null;
}
function syncRuleCollapseButtonState(card) {
var button = getCollapseRuleButton(card);
var icon = getCollapseRuleIcon(card);
var numberNode = getRuleNumberNode(card);
var ruleIndex = numberNode ? String(numberNode.textContent || '') : '';
var isCollapsed = Boolean(card && card.classList && card.classList.contains('collapsed-card'));
var label = (isCollapsed ? 'Expand' : 'Collapse') + ' rule ' + ruleIndex;
if (!button) {
return;
}
button.setAttribute('aria-expanded', isCollapsed ? 'false' : 'true');
button.setAttribute('aria-label', label);
button.setAttribute('title', label);
if (icon) {
icon.classList.remove('bi-dash-lg', 'bi-plus-lg');
icon.classList.add(isCollapsed ? 'bi-plus-lg' : 'bi-dash-lg');
}
var visibleLabel = button.querySelector('.visually-hidden');
if (visibleLabel) {
visibleLabel.textContent = label;
}
}
function clearRuleValidity(card) {
var fields = getRuleFields(card);
var dayInputs = Array.prototype.slice.call(fields.days || []);
var dayLabels = getRuleDayLabels(card);
var feedbackNode = card ? card.querySelector('[data-schedule-rule-feedback]') : null;
[fields.startDatetime, fields.endDatetime, fields.startTime, fields.endTime].forEach(function (input) {
if (input) {
input.setCustomValidity('');
input.classList.remove('is-invalid');
input.removeAttribute('aria-invalid');
}
});
dayInputs.forEach(function (input) {
input.setCustomValidity('');
input.classList.remove('is-invalid');
input.removeAttribute('aria-invalid');
});
dayLabels.forEach(function (label) {
label.classList.remove('is-invalid');
label.removeAttribute('aria-invalid');
});
if (feedbackNode) {
feedbackNode.innerHTML = '';
feedbackNode.hidden = true;
}
}
function setRuleError(input, message) {
if (!input) {
return;
}
input.setCustomValidity(message);
if (String(input.value || '').trim()) {
input.classList.add('is-invalid');
input.setAttribute('aria-invalid', 'true');
}
appendRuleFeedbackMessage(input.closest ? input.closest('[data-schedule-rule-card]') : null, message);
}
function validateScheduleRuleCard(card) {
var fields = getRuleFields(card);
var dayInputs = Array.prototype.slice.call(fields.days || []);
var dayLabels = getRuleDayLabels(card);
var checkedDayInputs = dayInputs.filter(function (input) {
return Boolean(input && input.checked);
});
var checkedDayIndices = checkedDayInputs.map(function (input) {
return Number(input && input.value);
});
var startDatetime = String(fields.startDatetime && fields.startDatetime.value || '').trim();
var endDatetime = String(fields.endDatetime && fields.endDatetime.value || '').trim();
var startTime = String(fields.startTime && fields.startTime.value || '').trim();
var endTime = String(fields.endTime && fields.endTime.value || '').trim();
var isValid = true;
clearRuleValidity(card);
if ((startDatetime || endDatetime) && (!startDatetime || !endDatetime)) {
setRuleError(!startDatetime ? fields.startDatetime : fields.endDatetime, 'Start and end datetimes must both be set for this rule.');
isValid = false;
}
if ((startTime || endTime) && (!startTime || !endTime)) {
setRuleError(!startTime ? fields.startTime : fields.endTime, 'Start and end times must both be set for this rule.');
isValid = false;
}
if (startDatetime && endDatetime && new Date(endDatetime) <= new Date(startDatetime)) {
setRuleError(fields.endDatetime, 'End datetime must be after start datetime.');
isValid = false;
}
if (startTime && endTime && endTime <= startTime) {
setRuleError(fields.endTime, 'End time must be after start time.');
if (fields.startTime) {
fields.startTime.classList.add('is-invalid');
fields.startTime.setAttribute('aria-invalid', 'true');
}
isValid = false;
}
if (startDatetime && endDatetime) {
var dateRangeDayIndices = getDateRangeDayIndices(startDatetime, endDatetime);
var dayIndicesToCheck = checkedDayIndices.length ? checkedDayIndices : [0, 1, 2, 3, 4, 5, 6];
var invalidDayInputs = checkedDayInputs.filter(function (input) {
return dateRangeDayIndices.indexOf(Number(input && input.value)) === -1;
});
invalidDayInputs.forEach(function (input) {
var dayIndex = dayInputs.indexOf(input);
if (dayIndex !== -1 && dayLabels[dayIndex]) {
dayLabels[dayIndex].classList.add('is-invalid');
dayLabels[dayIndex].setAttribute('aria-invalid', 'true');
}
});
if (checkedDayInputs.length && invalidDayInputs.length === checkedDayInputs.length) {
setDayInputError(card, checkedDayInputs[0] || dayInputs[0], 'Selected days must overlap the date range.');
isValid = false;
}
if (startTime && endTime && !hasScheduleRuleOverlap(startDatetime, endDatetime, startTime, endTime, dayIndicesToCheck)) {
setRuleError(fields.endTime, 'Start and end times must overlap the date range.');
if (fields.startTime) {
fields.startTime.classList.add('is-invalid');
fields.startTime.setAttribute('aria-invalid', 'true');
}
isValid = false;
}
}
return isValid;
}
function updateLiveRuleValidity(card) {
validateScheduleRuleCard(card);
}
function validateRuleCard(card) {
return validateScheduleRuleCard(card);
}
function readRuleFromCard(card) {
var fields = getRuleFields(card);
var days = [];
Array.prototype.forEach.call(fields.days || [], function (checkbox) {
if (checkbox.checked) {
days.push(Number(checkbox.value));
}
});
days = Array.from(new Set(days)).sort(function (left, right) {
return left - right;
});
return normalizeScheduleRuleValue({
start_datetime: fields.startDatetime && fields.startDatetime.value || '',
end_datetime: fields.endDatetime && fields.endDatetime.value || '',
start_time: fields.startTime && fields.startTime.value || '',
end_time: fields.endTime && fields.endTime.value || '',
days: days
});
}
function updateRuleCardSummary(card) {
var summaryNode = getRuleSummaryNode(card);
var rule = readRuleFromCard(card);
if (summaryNode) {
summaryNode.textContent = formatScheduleRuleSummary(rule);
}
return rule;
}
function refreshRuleNumbers() {
getRuleCards().forEach(function (card, index) {
var numberNode = getRuleNumberNode(card);
var rowKeyNode = getRuleRowKeyNode(card);
var positionNode = getRulePositionNode(card);
var removeButton = getRemoveRuleButton(card);
var ruleIndex = index + 1;
if (numberNode) {
numberNode.textContent = String(ruleIndex);
}
if (rowKeyNode) {
rowKeyNode.value = String(getScheduleDraftKey() || '');
}
if (positionNode) {
positionNode.value = String(index);
}
if (removeButton) {
removeButton.disabled = false;
}
card.setAttribute('data-schedule-rule-key', 'schedule-rule-' + ruleIndex);
});
}
function renderEffectiveSummary(rules) {
if (!effectiveSummary) {
return;
}
effectiveSummary.innerHTML = '';
if (!rules.length) {
effectiveSummary.textContent = 'Always visible';
return;
}
var list = document.createElement('ul');
list.className = 'mb-0';
rules.forEach(function (rule, index) {
var item = document.createElement('li');
item.textContent = 'Rule ' + (index + 1) + ': ' + formatScheduleRuleSummary(rule);
list.appendChild(item);
});
effectiveSummary.appendChild(list);
}
// Rebuild the hidden form fields and keep the live summary chip current.
function syncScheduleRulesField() {
var rules = [];
getRuleCards().forEach(function (card) {
var rule = readRuleFromCard(card);
if (rule) {
rules.push(rule);
}
});
renderEffectiveSummary(rules);
persistScheduleDraft();
if (window.parent && typeof window.parent.applyPlaylistScheduleConfig === 'function') {
window.parent.applyPlaylistScheduleConfig({
row_key: String(rowKeyInput && rowKeyInput.value ? rowKeyInput.value : ''),
scheduleRules: rules,
summary: formatScheduleRulesSummary(rules)
});
}
return rules;
}
function getRuleCardTemplate() {
if (!scheduleRuleTemplate || !scheduleRuleTemplate.content) {
return null;
}
return scheduleRuleTemplate.content.firstElementChild;
}
function updateRuleCardDayOptions(card, ruleKey, normalizedRule) {
var dayInputs = card ? Array.prototype.slice.call(card.querySelectorAll('[data-schedule-rule-day]')) : [];
var dayLabels = card ? Array.prototype.slice.call(card.querySelectorAll('.schedule-day-button')) : [];
dayInputs.forEach(function (input, dayIndex) {
var label = dayLabels[dayIndex];
var isChecked = Array.isArray(normalizedRule.days) && normalizedRule.days.indexOf(dayIndex) !== -1;
var inputId = ruleKey + '-day-' + dayIndex;
input.id = inputId;
input.value = String(dayIndex);
input.checked = isChecked;
input.autocomplete = 'off';
input.setAttribute('data-schedule-rule-day', '');
if (label) {
label.setAttribute('for', inputId);
}
});
}
function createRuleCardElement(rule, ruleIndex, options) {
var ruleKey = 'schedule-rule-' + ruleIndex + '-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8);
var normalizedRule = normalizeScheduleRuleValue(rule) || {};
var cardTemplate = getRuleCardTemplate();
var isCollapsed = Boolean(options && options.isCollapsed);
var card;
if (!cardTemplate) {
return null;
}
card = cardTemplate.cloneNode(true);
card.setAttribute('data-schedule-rule-card', '');
card.setAttribute('data-schedule-rule-key', ruleKey);
card.querySelector('[data-schedule-rule-number]').textContent = String(ruleIndex);
card.querySelector('[data-schedule-rule-summary]').textContent = formatScheduleRuleSummary(normalizedRule);
card.querySelector('[data-schedule-rule-row-key]').value = String(getScheduleDraftKey() || '');
card.querySelector('[data-schedule-rule-position]').value = String(Math.max(0, ruleIndex - 1));
card.querySelector('[data-schedule-rule-start-datetime]').value = String(normalizedRule.start_datetime || '');
card.querySelector('[data-schedule-rule-end-datetime]').value = String(normalizedRule.end_datetime || '');
card.querySelector('[data-schedule-rule-start-time]').value = String(normalizedRule.start_time || '');
card.querySelector('[data-schedule-rule-end-time]').value = String(normalizedRule.end_time || '');
card.querySelector('[data-remove-schedule-rule]').setAttribute('data-confirm-message', 'Remove this rule?');
updateRuleCardDayOptions(card, ruleKey, normalizedRule);
if (isCollapsed) {
card.classList.add('collapsed-card');
} else {
card.classList.remove('collapsed-card');
}
try {
updateLiveRuleValidity(card);
} catch (_error) {
clearRuleValidity(card);
}
syncRuleCollapseButtonState(card);
return card;
}
function addRule(rule, options) {
var ruleCount = getRuleCards().length + 1;
var card = createRuleCardElement(rule || {}, ruleCount, options);
if (!card) {
scheduleRuleList.insertAdjacentHTML('beforeend', buildScheduleRuleCardMarkup(rule || {}, ruleCount, Boolean(options && options.isCollapsed)));
card = scheduleRuleList.lastElementChild;
}
if (!card) {
return null;
}
if (card.parentNode !== scheduleRuleList) {
scheduleRuleList.appendChild(card);
}
refreshRuleNumbers();
syncScheduleRulesField();
return card;
}
function handleCardChange(card) {
updateLiveRuleValidity(card);
updateRuleCardSummary(card);
syncScheduleRulesField();
}
function handleRuleRemoval(card) {
if (!card) {
return;
}
card.remove();
refreshRuleNumbers();
syncScheduleRulesField();
}
// Block saves when any rule is incomplete or out of order.
function validateScheduleForm() {
var isValid = true;
getRuleCards().forEach(function (card) {
if (!validateRuleCard(card)) {
isValid = false;
}
});
if (isValid) {
syncScheduleRulesField();
}
return isValid && form.checkValidity();
}
// Persist the modal form back to the server for non-draft rows.
function saveScheduleForm() {
return fetch(form.action, {
method: 'POST',
credentials: 'same-origin',
body: new URLSearchParams(new FormData(form))
}).then(function (response) {
if (!response.ok) {
return response.text().then(function (text) {
throw new Error(text || 'Unable to save schedule.');
});
}
return response;
});
}
scheduleRuleList.addEventListener('input', function (event) {
var card = event.target && event.target.closest ? event.target.closest('[data-schedule-rule-card]') : null;
if (!card) {
return;
}
handleCardChange(card);
});
scheduleRuleList.addEventListener('change', function (event) {
var card = event.target && event.target.closest ? event.target.closest('[data-schedule-rule-card]') : null;
if (!card) {
return;
}
handleCardChange(card);
});
scheduleRuleList.addEventListener('click', function (event) {
var removeButton = event.target && event.target.closest ? event.target.closest('[data-remove-schedule-rule]') : null;
var collapseButton = event.target && event.target.closest ? event.target.closest('[data-schedule-rule-collapse], [data-lte-toggle="card-collapse"]') : null;
if (collapseButton) {
window.setTimeout(function () {
syncRuleCollapseButtonState(collapseButton.closest('[data-schedule-rule-card]'));
persistScheduleDraft();
}, 0);
return;
}
if (!removeButton) {
return;
}
event.preventDefault();
var confirmMessage = removeButton.getAttribute('data-confirm-message');
if (confirmMessage && !window.confirm(confirmMessage)) {
return;
}
handleRuleRemoval(removeButton.closest('[data-schedule-rule-card]'));
});
if (addRuleButton) {
addRuleButton.addEventListener('click', function () {
addRule({}, { isCollapsed: false });
});
}
if (cancelButton) {
cancelButton.addEventListener('click', function () {
if (typeof window.closeScheduleModal === 'function') {
window.closeScheduleModal({ discardDraft: true });
}
});
}
var draftRules = getScheduleDraftRules();
if (draftRules !== null) {
renderScheduleDraftState(draftRules);
} else if (!getRuleCards().length) {
syncScheduleRulesField();
} else {
refreshRuleNumbers();
getRuleCards().forEach(function (card) {
syncRuleCollapseButtonState(card);
updateLiveRuleValidity(card);
updateRuleCardSummary(card);
});
syncScheduleRulesField();
}
form.addEventListener('submit', function (event) {
event.preventDefault();
if (!validateScheduleForm()) {
form.reportValidity();
return;
}
var rules = syncScheduleRulesField();
var values = {
row_key: String(rowKeyInput && rowKeyInput.value ? rowKeyInput.value : ''),
scheduleRules: rules,
summary: ''
};
values.summary = formatScheduleRulesSummary(values.scheduleRules);
if (isDraftMode) {
clearScheduleDraft();
if (window.parent && typeof window.parent.applyPlaylistScheduleConfig === 'function') {
window.parent.applyPlaylistScheduleConfig(values);
}
if (typeof window.closeScheduleModal === 'function') {
window.closeScheduleModal({ discardDraft: true, preserveChanges: true });
}
return;
}
saveScheduleForm().then(function () {
clearScheduleDraft();
if (window.parent && typeof window.parent.applyPlaylistScheduleConfig === 'function') {
window.parent.applyPlaylistScheduleConfig(values);
if (typeof window.parent.closeScheduleModal === 'function') {
window.parent.closeScheduleModal({ discardDraft: true, preserveChanges: true });
}
return;
}
if (typeof window.closeScheduleModal === 'function') {
window.closeScheduleModal({ discardDraft: true, preserveChanges: true });
}
}).catch(function (error) {
window.alert(String(error && error.message ? error.message : 'Unable to save schedule.'));
});
});
}
// Playlist editor behavior.
// Playlist table editing, picker modal, and row sync logic.
function initPlaylistEditStaging() {
var tbody = document.getElementById('playlist-items-body');
var form = document.getElementById('playlist-edit-form');
var addSlideModal = document.getElementById('playlist-add-slide-modal');
var addSlideGrid = document.getElementById('playlist-slide-picker-grid');
var addSlideSearch = document.getElementById('playlist-slide-picker-search');
var addSlideShowAssigned = document.getElementById('playlist-slide-picker-show-assigned');
var addSlideEmpty = document.getElementById('playlist-slide-picker-empty');
var addSlideConfirm = document.getElementById('playlist-confirm-add-slides');
var addSlideCount = document.getElementById('playlist-slide-picker-selected-count');
var addSlideData = document.getElementById('playlist-add-slide-data');
var addSlideOpenButton = document.getElementById('playlist-open-slide-modal');
var canvasSizeSelect = document.getElementById('playlist-canvas-size');
var canvasSizeLockInput = document.getElementById('playlist-canvas-size-lock');
var slidePickerCards = [];
var slidePickerSelection = new Set();
var slidePickerSlides = [];
var lastDurationPointerDown = null;
var lastDurationPointerUp = null;
if (!tbody || !form) {
return;
}
if (addSlideData) {
try {
var parsedSlides = JSON.parse(addSlideData.textContent || '[]');
slidePickerSlides = Array.isArray(parsedSlides) ? parsedSlides : [];
} catch (_error) {
slidePickerSlides = [];
}
}
// Selection state helpers for the picker modal.
function setCardSelected(card, selected) {
if (!card) {
return;
}
card.classList.toggle('is-selected', Boolean(selected));
card.setAttribute('aria-pressed', Boolean(selected) ? 'true' : 'false');
}
function setCardVisible(card, visible) {
if (!card) {
return;
}
card.classList.toggle('is-hidden', !visible);
}
function getCanvasSizeEditable() {
return Boolean(canvasSizeSelect && String(canvasSizeSelect.getAttribute('data-playlist-canvas-editable') || '') === 'true');
}
function getLockedCanvasId() {
var canvasId = Number(canvasSizeSelect && canvasSizeSelect.value ? canvasSizeSelect.value : 0) || null;
if (canvasId) {
return canvasId;
}
var canvasIds = {};
getRows().forEach(function (row) {
var rowCanvasId = Number(row.getAttribute('data-canvas-id') || 0) || null;
if (rowCanvasId) {
canvasIds[rowCanvasId] = true;
}
});
var canvasKeys = Object.keys(canvasIds);
if (canvasKeys.length === 1) {
return Number(canvasKeys[0]) || null;
}
return null;
}
function syncPlaylistCanvasLock() {
if (!canvasSizeSelect) {
return;
}
var shouldLock = !getCanvasSizeEditable() || getRows().length > 0;
var lockedCanvasId = shouldLock ? getLockedCanvasId() : null;
if (shouldLock && !canvasSizeSelect.value && lockedCanvasId) {
canvasSizeSelect.value = String(lockedCanvasId);
}
canvasSizeSelect.disabled = shouldLock;
if (canvasSizeLockInput) {
canvasSizeLockInput.disabled = !shouldLock;
canvasSizeLockInput.value = shouldLock ? String(canvasSizeSelect.value || lockedCanvasId || '') : '';
}
if (tbody && tbody.setAttribute) {
tbody.setAttribute('data-playlist-canvas-id', String(shouldLock ? (canvasSizeSelect.value || lockedCanvasId || '') : getPlaylistCanvasId() || ''));
}
}
function syncAddSlideButtonState() {
if (!addSlideOpenButton) {
return;
}
addSlideOpenButton.disabled = !Boolean(getPlaylistCanvasId());
}
function clearModalSelection() {
slidePickerSelection.clear();
slidePickerCards.forEach(function (card) {
setCardSelected(card, false);
});
if (addSlideSearch) {
addSlideSearch.value = '';
}
syncSlidePickerState();
}
function attachSlideThumbFallbacks(root) {
if (typeof window.attachSlideThumbFallbacks === 'function') {
window.attachSlideThumbFallbacks(root || tbody);
}
}
// Picker card rendering.
function createPickerCard(slide) {
var button = document.createElement('button');
var searchText = String(slide && slide.title ? slide.title : '').toLowerCase();
button.type = 'button';
button.className = PICKER_CARD_CLASS;
button.setAttribute('aria-pressed', 'false');
button.setAttribute('data-slide-id', String(slide.id || ''));
button.setAttribute('data-canvas-id', String(slide && slide.canvasSizeId ? slide.canvasSizeId : ''));
button.setAttribute('data-search-text', searchText);
button.setAttribute('data-is-assigned', slide && slide.isAssigned ? 'true' : 'false');
if (slide && slide.isAssigned) {
button.classList.add('is-assigned');
}
var media = buildSlidePickerMedia(slide);
var title = document.createElement('div');
title.className = PICKER_TITLE_CLASS;
title.textContent = String(slide && slide.title ? slide.title : 'Slide');
button.appendChild(media);
button.appendChild(title);
return button;
}
// Picker filtering and modal controls.
function setShowAssignedState(showAssignedSlides) {
if (!addSlideShowAssigned) {
return;
}
addSlideShowAssigned.setAttribute('aria-pressed', showAssignedSlides ? 'true' : 'false');
addSlideShowAssigned.classList.toggle('btn-secondary', showAssignedSlides);
addSlideShowAssigned.classList.toggle('btn-outline-secondary', !showAssignedSlides);
}
function renderSlidePicker() {
if (!addSlideGrid) {
return;
}
addSlideGrid.innerHTML = '';
slidePickerCards = slidePickerSlides.map(function (slide) {
var card = createPickerCard(slide);
addSlideGrid.appendChild(card);
return card;
});
syncSlidePickerState();
}
function getPlaylistCanvasId() {
if (canvasSizeSelect && canvasSizeSelect.value) {
return Number(canvasSizeSelect.value) || null;
}
if (canvasSizeSelect && (!canvasSizeSelect.disabled || !canvasSizeSelect.value)) {
return canvasSizeLockInput && canvasSizeLockInput.value ? Number(canvasSizeLockInput.value) || null : null;
}
if (canvasSizeLockInput && canvasSizeLockInput.value) {
return Number(canvasSizeLockInput.value) || null;
}
return Number(tbody && tbody.getAttribute ? tbody.getAttribute('data-playlist-canvas-id') : 0) || null;
}
function syncPlaylistCanvasId() {
if (tbody && tbody.setAttribute) {
tbody.setAttribute('data-playlist-canvas-id', String(getPlaylistCanvasId() || ''));
}
syncAddSlideOptions();
syncSlidePickerState();
syncPlaylistCanvasLock();
syncAddSlideButtonState();
}
function syncSlidePickerState() {
var query = String(addSlideSearch && addSlideSearch.value ? addSlideSearch.value : '').trim().toLowerCase();
var showAssignedSlides = Boolean(addSlideShowAssigned && addSlideShowAssigned.getAttribute('aria-pressed') === 'true');
var visibleCount = 0;
var assignedCount = 0;
var allowedCanvasId = getPlaylistCanvasId();
var activeCanvasIds = {};
if (!allowedCanvasId) {
getRows().forEach(function (row) {
var canvasId = Number(row.getAttribute('data-canvas-id') || 0) || null;
if (canvasId) {
activeCanvasIds[canvasId] = true;
}
});
if (!Object.keys(activeCanvasIds).length) {
slidePickerCards.forEach(function (card) {
if (slidePickerSelection.has(String(card.getAttribute('data-slide-id') || ''))) {
var selectedCanvasId = Number(card.getAttribute('data-canvas-id') || 0) || null;
if (selectedCanvasId) {
activeCanvasIds[selectedCanvasId] = true;
}
}
});
}
var canvasKeys = Object.keys(activeCanvasIds);
if (canvasKeys.length === 1) {
allowedCanvasId = Number(canvasKeys[0]) || null;
}
}
slidePickerCards.forEach(function (card) {
var searchText = String(card.getAttribute('data-search-text') || '');
var isAssigned = card.getAttribute('data-is-assigned') === 'true';
var canvasId = Number(card.getAttribute('data-canvas-id') || 0) || null;
var isCanvasMismatch = Boolean(allowedCanvasId && canvasId && canvasId !== allowedCanvasId);
var matchesSearch = !query || searchText.indexOf(query) !== -1;
var matchesAssignment = showAssignedSlides || !isAssigned;
var matches = matchesSearch && matchesAssignment && !isCanvasMismatch;
updateSlidePickerCardState(card, isAssigned, isCanvasMismatch);
setCardVisible(card, matches);
if (isAssigned) {
assignedCount += 1;
}
if (matches) {
visibleCount += 1;
}
});
if (addSlideEmpty) {
addSlideEmpty.textContent = getSlidePickerEmptyMessage(query, showAssignedSlides, assignedCount, visibleCount);
addSlideEmpty.classList.toggle('is-hidden', visibleCount !== 0);
}
if (addSlideCount) {
addSlideCount.textContent = String(slidePickerSelection.size);
}
if (addSlideConfirm) {
addSlideConfirm.disabled = slidePickerSelection.size === 0;
}
setShowAssignedState(showAssignedSlides);
}
function toggleCardSelection(card) {
var slideId = String(card && card.getAttribute('data-slide-id') ? card.getAttribute('data-slide-id') : '');
var selected;
if (!slideId || String(card && card.getAttribute('data-is-assigned') || '') === 'true') {
return;
}
selected = !slidePickerSelection.has(slideId);
if (selected) {
slidePickerSelection.add(slideId);
} else {
slidePickerSelection.delete(slideId);
}
setCardSelected(card, selected);
syncSlidePickerState();
}
function addSelectedSlides() {
var currentTbody = document.getElementById('playlist-items-body') || tbody;
var selectedSlides = slidePickerSlides.filter(function (slide) {
return slidePickerSelection.has(String(slide.id || '')) && !slide.isAssigned;
});
if (!selectedSlides.length) {
return;
}
if (currentTbody && currentTbody.querySelector('.playlist-empty-row')) {
currentTbody.querySelector('.playlist-empty-row').remove();
}
selectedSlides.forEach(function (slide) {
var row = createRow({
row_key: 'new-' + Date.now() + '-' + slide.id,
slide_id: String(slide.id),
thumbnail_path: String(slide.thumbnail_path || ''),
canvas_width: slide.canvas_width,
canvas_height: slide.canvas_height,
canvas_id: slide.canvasSizeId,
showVideoDurationButton: slide.showVideoDurationButton,
showMuteButton: slide.showMuteButton,
videoSourcePath: slide.videoSourcePath,
videoDurationSeconds: slide.videoDurationSeconds,
disableAudio: slide.disableAudio,
title: slide.title || 'Slide',
duration_seconds: 10,
durationSeconds: 10,
scheduleRules: [],
summary: 'Always visible'
});
if (currentTbody) {
currentTbody.appendChild(row);
}
});
attachSlideThumbFallbacks(currentTbody || tbody);
updateRowOrder(true);
if (!window.pulseModal || !window.pulseModal.hide(addSlideModal)) {
addSlideModal.classList.remove('show');
addSlideModal.setAttribute('aria-hidden', 'true');
}
}
attachSlideThumbFallbacks(tbody);
function getRows() {
var currentTbody = document.getElementById('playlist-items-body') || tbody;
return currentTbody ? Array.prototype.slice.call(currentTbody.querySelectorAll('tr[data-playlist-slide-row]:not([data-playlist-ghost-row])')) : [];
}
function findRowByKey(rowKey) {
var rows = getRows();
for (var i = 0; i < rows.length; i += 1) {
if (rows[i].getAttribute('data-row-key') === rowKey) {
return rows[i];
}
}
return null;
}
function updateEmptyState() {
var currentTbody = document.getElementById('playlist-items-body') || tbody;
var rows = getRows();
var placeholder = currentTbody ? currentTbody.querySelector('.playlist-empty-row') : null;
if (rows.length) {
if (placeholder) {
placeholder.remove();
}
return;
}
if (!placeholder) {
if (currentTbody) {
currentTbody.innerHTML = '| No slides assigned yet. |
';
}
}
}
function markPlaylistDirty() {
if (form && form.dataset) {
form.dataset.dirty = 'true';
}
}
// Rebuild hidden schedule inputs after the async save swaps the playlist tbody.
function syncScheduleRowInputs(row) {
var fields = getScheduleRowFields(row);
var rowKey = String(row && row.getAttribute('data-row-key') || '').trim();
var serializedRules = fields.rulesContainer ? fields.rulesContainer.getAttribute('data-schedule-rules') : '';
var rules = [];
if (!fields.rulesContainer || !rowKey) {
return;
}
if (serializedRules) {
try {
rules = JSON.parse(serializedRules);
} catch (_error) {
rules = [];
}
} else {
rules = [];
}
if (!Array.isArray(rules)) {
rules = [];
}
fields.rulesContainer.innerHTML = buildScheduleRulesMarkup(rules, rowKey, 'playlist-edit-form');
if (fields.summaryNode) {
fields.summaryNode.innerHTML = buildScheduleSummaryChipMarkup(formatScheduleRulesSummary(rules) || 'Always visible');
}
}
function syncScheduleDraftRows() {
getRows().forEach(function (row) {
syncScheduleRowInputs(row);
});
}
// Filter the slide picker against the current playlist rows and canvas lock.
function syncAddSlideOptions() {
var activeSlideIds = {};
var activeCanvasIds = {};
var allowedCanvasId = getPlaylistCanvasId();
getRows().forEach(function (row) {
var slideId = String(row.getAttribute('data-slide-id') || '');
var canvasId = Number(row.getAttribute('data-canvas-id') || 0) || null;
if (slideId) {
activeSlideIds[slideId] = true;
}
if (!allowedCanvasId && canvasId) {
activeCanvasIds[canvasId] = true;
}
});
if (!allowedCanvasId) {
slidePickerCards.forEach(function (card) {
if (slidePickerSelection.has(String(card.getAttribute('data-slide-id') || ''))) {
var selectedCanvasId = Number(card.getAttribute('data-canvas-id') || 0) || null;
if (selectedCanvasId) {
activeCanvasIds[selectedCanvasId] = true;
}
}
});
var canvasKeys = Object.keys(activeCanvasIds);
if (canvasKeys.length === 1) {
allowedCanvasId = Number(canvasKeys[0]) || null;
}
}
slidePickerSlides.forEach(function (slide) {
var slideId = String(slide.id || '');
var card = slidePickerCards.find(function (item) {
return String(item.getAttribute('data-slide-id') || '') === slideId;
});
var canvasId = Number(slide.canvasSizeId || 0) || null;
var isAssigned = Boolean(activeSlideIds[slideId]);
var isCanvasMismatch = Boolean(allowedCanvasId && canvasId && canvasId !== allowedCanvasId);
slide.isAssigned = isAssigned;
updateSlidePickerCardState(card, isAssigned, isCanvasMismatch);
if (isAssigned) {
slidePickerSelection.delete(slideId);
if (card) {
setCardSelected(card, false);
}
}
});
syncSlidePickerState();
}
// Recompute row numbers and derived picker state after drag/drop or adds.
function updateRowOrder(markDirty) {
var rows = getRows();
rows.forEach(function (row, index) {
var orderNumber = row.querySelector('.playlist-order-number');
var moveUpButton = row.querySelector('[data-playlist-row-move="up"]');
var moveDownButton = row.querySelector('[data-playlist-row-move="down"]');
var moveUpIcon = row.querySelector('[data-playlist-row-move-icon="up"]');
var moveDownIcon = row.querySelector('[data-playlist-row-move-icon="down"]');
var isFirst = index === 0;
var isLast = index === rows.length - 1;
if (orderNumber) {
orderNumber.textContent = String(index + 1);
}
if (moveUpButton) {
moveUpButton.disabled = isFirst;
moveUpButton.setAttribute('aria-disabled', isFirst ? 'true' : 'false');
}
if (moveUpIcon) {
moveUpIcon.classList.toggle('bi-caret-up-fill', !isFirst);
moveUpIcon.classList.toggle('bi-caret-up', isFirst);
}
if (moveDownButton) {
moveDownButton.disabled = isLast;
moveDownButton.setAttribute('aria-disabled', isLast ? 'true' : 'false');
}
if (moveDownIcon) {
moveDownIcon.classList.toggle('bi-caret-down-fill', !isLast);
moveDownIcon.classList.toggle('bi-caret-down', isLast);
}
});
syncAddSlideOptions();
syncPlaylistCanvasLock();
syncAddSlideButtonState();
updateEmptyState();
if (markDirty) {
markPlaylistDirty();
}
}
form.addEventListener('submit', function () {
syncScheduleDraftRows();
}, true);
function bindPlaylistTableDrag(tbodyElement) {
if (!tbodyElement || typeof window.initPlaylistTableDrag !== 'function') {
return;
}
window.initPlaylistTableDrag({
tbody: tbodyElement,
onOrderChanged: updateRowOrder
});
}
function movePlaylistRowByOffset(row, offset) {
var currentTbody = document.getElementById('playlist-items-body') || tbody;
var rows = getRows();
var currentIndex = rows.indexOf(row);
var targetIndex;
var referenceRow;
if (!currentTbody || !row || currentIndex === -1) {
return false;
}
targetIndex = currentIndex + Number(offset || 0);
if (targetIndex < 0 || targetIndex >= rows.length) {
return false;
}
referenceRow = rows[targetIndex];
if (targetIndex > currentIndex) {
referenceRow = referenceRow ? referenceRow.nextSibling : null;
}
if (referenceRow) {
currentTbody.insertBefore(row, referenceRow);
} else {
currentTbody.appendChild(row);
}
updateRowOrder(true);
return true;
}
function populatePlaylistSlideRow(row, values, rowKey) {
var canvasWidth = Number(values.canvas_width);
var canvasHeight = Number(values.canvas_height);
var thumbnailPath = String(values.thumbnail_path || '');
var scheduleRules = Array.isArray(values.scheduleRules) ? values.scheduleRules : [];
var useVideoDuration = Boolean(values.useVideoDuration);
var showVideoDurationButton = Boolean(values.showVideoDurationButton);
var disableAudio = values.disableAudio === undefined || values.disableAudio === null ? true : Boolean(values.disableAudio);
var showMuteButton = Boolean(values.showMuteButton);
var durationSource = values.durationSeconds !== undefined && values.durationSeconds !== null ? values.durationSeconds : values.duration_seconds;
var durationSeconds = String(durationSource !== undefined && durationSource !== null ? durationSource : 10);
var summary = String(values.summary || formatScheduleRulesSummary(scheduleRules) || 'Always visible');
var durationButton = row ? row.querySelector('[data-use-video-duration-button]') : null;
var muteButton = row ? row.querySelector('[data-disable-audio-button]') : null;
var durationInput = row ? row.querySelector('[data-playlist-duration-input]') : null;
var useVideoDurationInput = row ? row.querySelector('[data-playlist-use-video-duration-input]') : null;
var disableAudioInput = row ? row.querySelector('[data-playlist-disable-audio-input]') : null;
var durationMirror = row ? row.querySelector('[data-playlist-duration-mirror]') : null;
var thumb = row ? row.querySelector('[data-playlist-slide-thumb]') : null;
var titleNode = row ? row.querySelector('[data-playlist-slide-title]') : null;
var slideIdInput = row ? row.querySelector('[data-playlist-slide-id-input]') : null;
var rowKeyInput = row ? row.querySelector('[data-playlist-row-key-input]') : null;
var scheduleSummaryChip = row ? row.querySelector('[data-schedule-summary-chip]') : null;
var rulesContainer = row ? row.querySelector('[data-schedule-rules-container]') : null;
if (!row) {
return;
}
row.setAttribute('data-row-key', rowKey);
row.setAttribute('data-slide-id', String(values.slide_id || ''));
row.setAttribute('data-canvas-id', String(values.canvas_id || ''));
row.setAttribute('data-video-source-path', String(values.videoSourcePath || ''));
row.setAttribute('data-video-duration-seconds', String(values.videoDurationSeconds || ''));
row.setAttribute('data-use-video-duration-state', useVideoDuration ? 'true' : 'false');
row.setAttribute('data-disable-audio-state', disableAudio ? 'true' : 'false');
if (Number.isFinite(canvasWidth) && Number.isFinite(canvasHeight) && canvasWidth > 0 && canvasHeight > 0 && thumb) {
thumb.style.setProperty('--playlist-slide-thumb-aspect-ratio', canvasWidth + ' / ' + canvasHeight);
}
if (thumb) {
thumb.innerHTML = thumbnailPath
? '
'
: '';
}
if (titleNode) {
titleNode.textContent = String(values.title || 'Slide');
}
if (slideIdInput) {
slideIdInput.value = String(values.slide_id || '');
}
if (rowKeyInput) {
rowKeyInput.value = String(rowKey || '');
}
if (scheduleSummaryChip) {
scheduleSummaryChip.textContent = summary;
}
if (rulesContainer) {
rulesContainer.setAttribute('data-schedule-rules', JSON.stringify(scheduleRules).replace(/ 0) {
input.value = String(storedDuration);
}
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
}
function setDisableAudioToggleState(row, button, isPressed) {
var flag = row ? row.querySelector('[name="disable_audio[]"]') : null;
syncDisableAudioButtonState(button, isPressed);
if (flag) {
flag.value = isPressed ? '1' : '0';
}
if (row) {
row.setAttribute('data-disable-audio-state', isPressed ? 'true' : 'false');
}
}
function applyDisableAudioToRow(row, button) {
if (!row) {
return;
}
setDisableAudioToggleState(row, button, !(button && button.getAttribute('aria-pressed') === 'true'));
markPlaylistDirty();
}
// Central click handler for row actions so drag, remove, schedule, and duration toggle stay coordinated.
document.addEventListener('click', function (event) {
if (event.target && event.target.matches('[name="duration_seconds[]"]')) {
event.stopPropagation();
return;
}
var moveButton = event.target.closest('[data-playlist-row-move]');
var removeButton = event.target.closest('[data-playlist-remove-row]');
var durationButton = event.target.closest('.playlist-use-video-duration');
var muteButton = event.target.closest('.playlist-disable-audio');
var row = getPlaylistSlideRow(event.target);
if (!row) {
return;
}
if (moveButton) {
event.preventDefault();
event.stopPropagation();
movePlaylistRowByOffset(row, moveButton.getAttribute('data-playlist-row-move') === 'up' ? -1 : 1);
return;
}
if (removeButton) {
event.preventDefault();
row.remove();
updateRowOrder(true);
return;
}
if (durationButton) {
var rowKey = getPlaylistSlideRowKey(row);
var pressedOnInput = lastDurationPointerDown && lastDurationPointerDown.type === 'input' && lastDurationPointerDown.rowKey === rowKey;
var releasedOnButton = lastDurationPointerUp && lastDurationPointerUp.type === 'button' && lastDurationPointerUp.rowKey === rowKey;
if (event.detail > 0 && (pressedOnInput || !releasedOnButton)) {
lastDurationPointerDown = null;
lastDurationPointerUp = null;
event.preventDefault();
return;
}
event.preventDefault();
void applyVideoDurationToRow(row, durationButton);
lastDurationPointerDown = null;
lastDurationPointerUp = null;
return;
}
if (muteButton) {
event.preventDefault();
void applyDisableAudioToRow(row, muteButton);
return;
}
var scheduleButton = event.target.closest('[data-schedule-config]');
if (scheduleButton) {
event.preventDefault();
if (typeof window.openScheduleModal === 'function') {
var scheduleParams = collectScheduleParams(row, scheduleButton.getAttribute('data-schedule-config-row') || row.getAttribute('data-row-key') || '');
window.openScheduleModal(scheduleButton.getAttribute('data-schedule-config') + '?' + scheduleParams.toString());
}
}
lastDurationPointerDown = null;
lastDurationPointerUp = null;
});
// Track the pointer source so clicks on the duration toggle do not fight text selection.
document.addEventListener('mousedown', function (event) {
var row = getPlaylistSlideRow(event.target);
var dragHandle = event.target && event.target.closest ? event.target.closest('[data-playlist-drag-handle]') : null;
var durationInput = event.target && event.target.matches('[name="duration_seconds[]"]') ? event.target : null;
var durationToggle = isDurationControl(event.target) ? event.target.closest('.playlist-use-video-duration') : null;
var muteToggle = isAudioControl(event.target) ? event.target.closest('.playlist-disable-audio') : null;
if (row && !dragHandle) {
event.stopPropagation();
}
if (durationInput) {
lastDurationPointerDown = getDurationPointerState(event.target, 'input');
event.stopPropagation();
return;
}
if (durationToggle) {
lastDurationPointerDown = getDurationPointerState(durationToggle, 'button');
return;
}
if (muteToggle) {
event.stopPropagation();
return;
}
lastDurationPointerDown = null;
});
// Mirror the pointer-up side of the duration toggle click detection.
document.addEventListener('mouseup', function (event) {
var durationInput = event.target && event.target.matches('[name="duration_seconds[]"]') ? event.target : null;
var durationToggle = isDurationControl(event.target) ? event.target.closest('.playlist-use-video-duration') : null;
var muteToggle = isAudioControl(event.target) ? event.target.closest('.playlist-disable-audio') : null;
var row = getPlaylistSlideRow(event.target);
if (durationInput) {
lastDurationPointerUp = getDurationPointerState(row, 'input');
return;
}
if (durationToggle) {
lastDurationPointerUp = getDurationPointerState(row, 'button');
return;
}
if (muteToggle) {
lastDurationPointerUp = null;
return;
}
lastDurationPointerUp = null;
});
// Keep the row dirty state and mirrored duration input in sync while the user edits.
document.addEventListener('input', function (event) {
if (event.target && event.target.matches('[name="duration_seconds[]"]')) {
var editedRow = getPlaylistSlideRow(event.target);
var editedButton = editedRow ? editedRow.querySelector('.playlist-use-video-duration') : null;
var editedMuteButton = editedRow ? editedRow.querySelector('.playlist-disable-audio') : null;
var editedMirror = editedRow ? editedRow.querySelector('[data-video-duration-mirror]') : null;
var useVideoDuration = Boolean(editedButton && editedButton.getAttribute('aria-pressed') === 'true');
var disableAudio = Boolean(editedMuteButton && editedMuteButton.getAttribute('aria-pressed') === 'true');
if (editedRow) {
editedRow.setAttribute('data-use-video-duration-state', useVideoDuration ? 'true' : 'false');
editedRow.setAttribute('data-disable-audio-state', disableAudio ? 'true' : 'false');
}
if (useVideoDuration && editedMirror) {
editedMirror.value = event.target.value;
}
if (editedMirror && !useVideoDuration) {
editedMirror.parentNode.removeChild(editedMirror);
}
markPlaylistDirty();
}
});
bindPlaylistTableDrag(tbody);
hydrateScheduleRowContainers(tbody);
initPlaylistScheduleModal();
if (addSlideModal && addSlideGrid) {
renderSlidePicker();
addSlideGrid.addEventListener('click', function (event) {
var card = event.target.closest('.playlist-slide-picker-card');
if (!card) {
return;
}
event.preventDefault();
toggleCardSelection(card);
});
if (addSlideSearch) {
addSlideSearch.addEventListener('input', syncSlidePickerState);
}
if (addSlideShowAssigned) {
addSlideShowAssigned.addEventListener('click', function () {
var nextState = addSlideShowAssigned.getAttribute('aria-pressed') !== 'true';
setShowAssignedState(nextState);
syncSlidePickerState();
});
}
if (addSlideConfirm) {
addSlideConfirm.addEventListener('click', function () {
addSelectedSlides();
});
}
if (canvasSizeSelect) {
canvasSizeSelect.addEventListener('change', function () {
syncPlaylistCanvasId();
});
}
addSlideModal.addEventListener('shown.bs.modal', function () {
syncSlidePickerState();
if (addSlideSearch) {
addSlideSearch.focus();
}
});
addSlideModal.addEventListener('hidden.bs.modal', function () {
clearModalSelection();
});
}
window.rebindPlaylistTableDrag = function (tbodyElement) {
bindPlaylistTableDrag(tbodyElement);
};
updateRowOrder();
syncPlaylistCanvasLock();
syncAddSlideButtonState();
}
window.initPlaylistScheduleForm = initPlaylistScheduleForm;
initPlaylistScheduleForm();
initPlaylistEditStaging();
}());