(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 normalizeDateTimeValue(value) {
if (value instanceof Date || Object.prototype.toString.call(value) === '[object Date]') {
if (Number.isNaN(value.getTime())) {
return '';
}
var pad = function (part) { return String(part).padStart(2, '0'); };
return value.getFullYear() + '-' + pad(value.getMonth() + 1) + '-' + pad(value.getDate())
+ 'T' + pad(value.getHours()) + ':' + pad(value.getMinutes());
}
return String(value || '').trim();
}
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 = normalizeDateTimeValue(input.start_datetime || input.startDateTime || input.startDateTimeValue);
var endDatetime = normalizeDateTimeValue(input.end_datetime || input.endDateTime || input.endDateTimeValue);
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);
} else if (normalized.start_time && normalized.end_time) {
right.push('Any day');
}
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';
}
if (rules.length >= 3) {
return String(rules.length) + ' rules';
}
var summaries = rules.map(function (rule) {
return formatScheduleRuleSummary(rule).replace(/^Any date: /, '');
});
if (summaries.some(function (summary) { return summary.length > 60; })) {
return rules.length === 1 ? '1 rule' : String(rules.length) + ' rules';
}
return summaries.map(function (summary, index) {
return 'Rule ' + String(index + 1) + ': ' + summary;
}).join('\n');
}
function parseScheduleBoundary(value) {
if (!value) return null;
var date = value instanceof Date ? value : new Date(value);
return Number.isNaN(date.getTime()) ? null : date;
}
function startOfScheduleDay(date) {
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
}
function getScheduleBoundaryMinute(date) {
return date.getHours() * 60 + date.getMinutes() + date.getSeconds() / 60 + date.getMilliseconds() / 60000;
}
function getScheduleTimeIntervals(rule, day) {
var dayStart = startOfScheduleDay(day);
var dayEnd = new Date(dayStart.getTime());
dayEnd.setDate(dayEnd.getDate() + 1);
dayEnd = new Date(dayEnd.getTime() - 1);
if (rule.start_datetime) {
var ruleStart = parseScheduleBoundary(rule.start_datetime);
if (!ruleStart || dayEnd < ruleStart) return [];
}
if (rule.end_datetime) {
var ruleEnd = parseScheduleBoundary(rule.end_datetime);
if (!ruleEnd || dayStart > ruleEnd) return [];
}
if (Array.isArray(rule.days) && rule.days.length && rule.days.indexOf(day.getDay()) === -1) {
return [];
}
var lowerMinute = 0;
var upperMinute = 1440;
if (rule.start_datetime) {
var startDate = parseScheduleBoundary(rule.start_datetime);
if (startDate && startOfScheduleDay(startDate).getTime() === dayStart.getTime()) {
lowerMinute = Math.max(lowerMinute, getScheduleBoundaryMinute(startDate));
}
}
if (rule.end_datetime) {
var endDate = parseScheduleBoundary(rule.end_datetime);
if (endDate && startOfScheduleDay(endDate).getTime() === dayStart.getTime()) {
upperMinute = Math.min(upperMinute, getScheduleBoundaryMinute(endDate));
}
}
if (lowerMinute > upperMinute) return [];
var intervals = [];
if (!rule.start_time || !rule.end_time) {
intervals.push([0, 1440]);
} else {
var startTime = normalizeScheduleRuleValue({ start_time: rule.start_time, end_time: rule.end_time });
if (!startTime) return [];
var startMinute = Number(String(rule.start_time).slice(0, 2)) * 60 + Number(String(rule.start_time).slice(3, 5));
var endMinute = Number(String(rule.end_time).slice(0, 2)) * 60 + Number(String(rule.end_time).slice(3, 5));
if (startMinute <= endMinute) {
intervals.push([startMinute, endMinute]);
} else {
intervals.push([0, endMinute], [startMinute, 1440]);
}
}
return intervals.map(function (interval) {
return [Math.max(interval[0], lowerMinute), Math.min(interval[1], upperMinute)];
}).filter(function (interval) {
return interval[0] <= interval[1];
});
}
function scheduleRulePairCanOverlap(leftRule, rightRule) {
var startDates = [parseScheduleBoundary(leftRule.start_datetime), parseScheduleBoundary(rightRule.start_datetime)].filter(Boolean);
var endDates = [parseScheduleBoundary(leftRule.end_datetime), parseScheduleBoundary(rightRule.end_datetime)].filter(Boolean);
var startDate = startDates.reduce(function (latest, date) {
return !latest || date > latest ? date : latest;
}, null);
var endDate = endDates.reduce(function (earliest, date) {
return !earliest || date < earliest ? date : earliest;
}, null);
if (startDate && endDate && startDate > endDate) {
return false;
}
var firstDay = startDate ? startOfScheduleDay(startDate) : endDate ? startOfScheduleDay(endDate) : startOfScheduleDay(new Date());
if (!startDate && endDate) {
firstDay.setDate(firstDay.getDate() - 7);
}
var lastDay = endDate ? startOfScheduleDay(endDate) : startOfScheduleDay(firstDay);
if (!endDate) {
lastDay.setDate(lastDay.getDate() + 7);
}
var spanDays = Math.floor((Date.UTC(lastDay.getFullYear(), lastDay.getMonth(), lastDay.getDate())
- Date.UTC(firstDay.getFullYear(), firstDay.getMonth(), firstDay.getDate())) / 86400000);
var candidateOffsets = [];
var lastOffset = Math.max(0, spanDays);
if (lastOffset <= 16) {
for (var offset = 0; offset <= lastOffset; offset += 1) candidateOffsets.push(offset);
} else {
for (var startOffset = 0; startOffset <= 7; startOffset += 1) candidateOffsets.push(startOffset);
for (var endOffset = lastOffset - 7; endOffset <= lastOffset; endOffset += 1) candidateOffsets.push(endOffset);
}
var checkedDays = new Set();
for (var index = 0; index < candidateOffsets.length; index += 1) {
var day = new Date(firstDay.getTime());
day.setDate(day.getDate() + candidateOffsets[index]);
var dayKey = [day.getFullYear(), day.getMonth(), day.getDate()].join('-');
if (checkedDays.has(dayKey)) continue;
checkedDays.add(dayKey);
var leftIntervals = getScheduleTimeIntervals(leftRule, day);
var rightIntervals = getScheduleTimeIntervals(rightRule, day);
for (var leftIndex = 0; leftIndex < leftIntervals.length; leftIndex += 1) {
for (var rightIndex = 0; rightIndex < rightIntervals.length; rightIndex += 1) {
if (Math.max(leftIntervals[leftIndex][0], rightIntervals[rightIndex][0])
<= Math.min(leftIntervals[leftIndex][1], rightIntervals[rightIndex][1])) {
return true;
}
}
}
}
return false;
}
function scheduleRuleSetsCanOverlap(slideRulesValue, groupRulesValue) {
var slideRules = parseScheduleRulesValue(slideRulesValue);
var groupRules = parseScheduleRulesValue(groupRulesValue);
if (!slideRules.length || !groupRules.length) {
return true;
}
return slideRules.some(function (slideRule) {
return groupRules.some(function (groupRule) {
return scheduleRulePairCanOverlap(slideRule, groupRule);
});
});
}
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) {
var groupKey = String(values.groupKey || '');
var groupName = escapeHtmlAttribute(String(values.groupName || ''));
var groupPickCount = Math.max(1, Number(values.groupPickCount) || 1);
var groupScheduleRules = Array.isArray(values.groupScheduleRules) ? values.groupScheduleRules : [];
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, isGroupSchedule) {
var params = new URLSearchParams();
var fields = !isGroupSchedule && 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 || ''));
});
if (isGroupSchedule && row) {
var rulesInput = row.querySelector('[data-playlist-group-schedule-rules]');
var rules = [];
try {
rules = JSON.parse(rulesInput && rulesInput.value || '[]');
} catch (_error) {
rules = [];
}
(Array.isArray(rules) ? rules : []).forEach(function (rule, index) {
params.append('schedule_rule_row_key[]', String(rowKey || ''));
params.append('schedule_rule_position[]', String(index));
params.append('schedule_rule_start_datetime[]', String(rule && rule.start_datetime || ''));
params.append('schedule_rule_end_datetime[]', String(rule && rule.end_datetime || ''));
params.append('schedule_rule_start_time[]', String(rule && rule.start_time || ''));
params.append('schedule_rule_end_time[]', String(rule && rule.end_time || ''));
params.append('schedule_rule_days_csv[]', Array.isArray(rule && rule.days) ? rule.days.join(',') : '');
});
}
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 syncRuleCollapseButtonState(card) {
var button = getCollapseRuleButton(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);
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);
});
function handleRuleCollapseComplete(event) {
var card = event.target && event.target.closest ? event.target.closest('[data-schedule-rule-card]') : null;
if (!card) {
return;
}
syncRuleCollapseButtonState(card);
persistScheduleDraft();
}
scheduleRuleList.addEventListener('collapsed.lte.card-widget', handleRuleCollapseComplete);
scheduleRuleList.addEventListener('expanded.lte.card-widget', handleRuleCollapseComplete);
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) {
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 createGroupButton = document.getElementById('playlist-create-group');
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;
var defaultSlideDuration = Number(tbody.getAttribute('data-default-slide-duration')) || 10;
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: defaultSlideDuration,
durationSeconds: defaultSlideDuration,
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 getGroupHeader(groupKey) {
return Array.prototype.slice.call((document.getElementById('playlist-items-body') || tbody).querySelectorAll('[data-playlist-group-row]')).find(function (header) {
return String(header.getAttribute('data-group-key') || '') === String(groupKey || '');
}) || null;
}
function getGroupRows(groupKey) {
return getRows().filter(function (row) {
var keyInput = row.querySelector('[data-playlist-group-key]');
return keyInput && String(keyInput.value || '') === String(groupKey || '');
});
}
function updateGroupScheduleWarnings() {
getRows().forEach(function (row) {
var groupKeyInput = row.querySelector('[data-playlist-group-key]');
var groupKey = String(groupKeyInput && groupKeyInput.value || '');
var scheduleContainer = row.querySelector('[data-schedule-rules-container]');
var scheduleSummary = row.querySelector('.playlist-schedule-summary');
var groupHeader = groupKey ? getGroupHeader(groupKey) : null;
var groupRulesInput = groupHeader && groupHeader.querySelector('[data-playlist-group-schedule-rules]');
var slideRules = parseScheduleRulesValue(scheduleContainer && scheduleContainer.getAttribute('data-schedule-rules') || '[]');
var groupRules = parseScheduleRulesValue(groupRulesInput && groupRulesInput.value || '[]');
var hasConflict = Boolean(groupKey && slideRules.length && groupRules.length
&& !scheduleRuleSetsCanOverlap(slideRules, groupRules));
var indicator = scheduleSummary && scheduleSummary.querySelector('[data-group-schedule-conflict]');
row.classList.toggle('playlist-schedule-conflict-row', hasConflict);
if (!scheduleSummary) {
return;
}
if (!hasConflict) {
if (indicator) indicator.remove();
return;
}
if (!indicator) {
indicator = document.createElement('span');
indicator.className = 'chip playlist-schedule-conflict-indicator';
indicator.setAttribute('data-group-schedule-conflict', '');
indicator.setAttribute('role', 'img');
scheduleSummary.appendChild(indicator);
}
indicator.setAttribute('aria-label', 'Never shown: slide and group schedules do not overlap');
indicator.title = 'This slide and its group have no overlapping schedule, so it will never display.';
indicator.innerHTML = ' Never shown';
});
}
window.refreshPlaylistGroupScheduleWarnings = updateGroupScheduleWarnings;
function getGroupHeaders() {
var currentTbody = document.getElementById('playlist-items-body') || tbody;
return currentTbody ? Array.prototype.slice.call(currentTbody.querySelectorAll('[data-playlist-group-row]')) : [];
}
function createGroupHeader(values) {
var group = values || {};
var groupKey = String(group.groupKey || '');
var groupName = String(group.groupName || 'New group');
var pickCount = Math.max(1, Number(group.groupPickCount) || 1);
var rules = Array.isArray(group.groupScheduleRules) ? group.groupScheduleRules : [];
var header = document.createElement('tr');
header.className = 'playlist-group-block-row';
header.setAttribute('data-playlist-group-row', '');
header.setAttribute('data-group-key', groupKey);
header.setAttribute('data-group-mode', group.groupMode === 'repeat' ? 'repeat' : 'select');
header.innerHTML = '' +
'' +
' ' +
'' +
'' +
'' +
' ' +
' | ' +
'' +
' ' +
' ' +
' ' +
' | ' +
'
| ' +
'Repeat | ' +
'' +
'' +
'' +
' | ';
header.querySelector('[data-playlist-group-name]').value = groupName;
header.querySelector('[data-playlist-group-pick-count]').value = String(pickCount);
header.querySelector('[data-playlist-group-repeat-count]').value = String(Math.min(100, Math.max(1, Number(group.groupRepeatCount) || 1)));
syncPlaylistGroupModeControls(header);
var shuffleButton = header.querySelector('[data-playlist-group-shuffle]');
var shuffleEnabled = Boolean(group.groupShuffle);
shuffleButton.setAttribute('aria-pressed', shuffleEnabled ? 'true' : 'false');
shuffleButton.setAttribute('aria-label', shuffleEnabled ? 'Disable shuffle' : 'Enable shuffle');
shuffleButton.title = shuffleEnabled ? 'Disable shuffle' : 'Enable shuffle';
shuffleButton.classList.toggle('btn-secondary', shuffleEnabled);
shuffleButton.classList.toggle('btn-outline-secondary', !shuffleEnabled);
shuffleButton.querySelector('i').className = shuffleEnabled ? 'bi bi-shuffle' : 'bi bi-chevron-double-right';
header.querySelector('[data-playlist-group-schedule-rules]').value = JSON.stringify(rules);
header.querySelector('[data-group-schedule-summary]').textContent = formatScheduleRulesSummary(rules) || 'Always visible';
return header;
}
function syncPlaylistGroupModeControls(header) {
var mode = header.getAttribute('data-group-mode') === 'repeat' ? 'repeat' : 'select';
Array.prototype.forEach.call(header.querySelectorAll('[data-playlist-group-mode]'), function (button) {
var active = button.getAttribute('data-playlist-group-mode') === mode;
button.setAttribute('aria-pressed', active ? 'true' : 'false');
button.classList.toggle('btn-secondary', active);
button.classList.toggle('btn-outline-secondary', !active);
});
header.querySelector('[data-playlist-group-select-controls]').hidden = mode === 'repeat';
header.querySelector('[data-playlist-group-repeat-controls]').hidden = mode !== 'repeat';
}
function syncGroupRowMetadata(header) {
if (!header) {
return;
}
var groupKey = String(header.getAttribute('data-group-key') || '');
var groupMode = header.getAttribute('data-group-mode') === 'repeat' ? 'repeat' : 'select';
var repeatInput = header.querySelector('[data-playlist-group-repeat-count]');
var repeatCount = Math.min(100, Math.max(1, Math.floor(Number(repeatInput && repeatInput.value) || 1)));
if (repeatInput) repeatInput.value = String(repeatCount);
syncPlaylistGroupModeControls(header);
var nameInput = header.querySelector('[data-playlist-group-name]');
var pickInput = header.querySelector('[data-playlist-group-pick-count]');
var shuffleButton = header.querySelector('[data-playlist-group-shuffle]');
var shuffleEnabled = shuffleButton && shuffleButton.getAttribute('aria-pressed') === 'true';
var rulesInput = header.querySelector('[data-playlist-group-schedule-rules]');
var groupName = String(nameInput && nameInput.value || '').trim() || 'Group';
var members = getGroupRows(groupKey);
var maximum = Math.max(1, members.length);
var pickCount = Math.min(maximum, Math.max(1, Math.floor(Number(pickInput && pickInput.value) || 1)));
if (pickInput) {
pickInput.max = String(maximum);
pickInput.disabled = members.length === 0;
pickInput.value = String(pickCount);
}
var scheduleRules = [];
try {
scheduleRules = JSON.parse(rulesInput && rulesInput.value || '[]');
} catch (_error) {
scheduleRules = [];
}
if (!Array.isArray(scheduleRules)) {
scheduleRules = [];
}
getGroupRows(groupKey).forEach(function (row) {
var keyField = row.querySelector('[data-playlist-group-key]');
var nameField = row.querySelector('[data-playlist-group-name-value]');
var pickField = row.querySelector('[data-playlist-group-pick-count-value]');
var shuffleField = row.querySelector('[data-playlist-group-shuffle-value]');
var modeField = row.querySelector('[data-playlist-group-mode-value]');
var repeatField = row.querySelector('[data-playlist-group-repeat-count-value]');
var scheduleField = row.querySelector('[data-playlist-group-schedule-rules]');
row.classList.add('playlist-group-child');
row.setAttribute('data-group-key', groupKey);
if (keyField) keyField.value = groupKey;
if (nameField) nameField.value = groupName;
if (pickField) pickField.value = String(pickCount);
if (shuffleField) shuffleField.value = shuffleEnabled ? '1' : '0';
if (modeField) modeField.value = groupMode;
if (repeatField) repeatField.value = String(repeatCount);
if (scheduleField) scheduleField.value = JSON.stringify(scheduleRules);
});
}
function initializePlaylistGroups(currentTbody) {
var target = currentTbody || document.getElementById('playlist-items-body') || tbody;
var rows = target ? Array.prototype.slice.call(target.querySelectorAll('tr[data-playlist-slide-row]:not([data-playlist-ghost-row])')) : [];
var groupValues = new Map();
var renderedKeys = new Set();
var orderedNodes = [];
rows.forEach(function (row) {
var keyField = row.querySelector('[data-playlist-group-key]');
var groupKey = String(keyField && keyField.value || '');
if (!groupKey || groupValues.has(groupKey)) {
return;
}
var nameField = row.querySelector('[data-playlist-group-name-value]');
var pickField = row.querySelector('[data-playlist-group-pick-count-value]');
var rulesField = row.querySelector('[data-playlist-group-schedule-rules]');
var rules = [];
try {
rules = JSON.parse(rulesField && rulesField.value || '[]');
} catch (_error) {
rules = [];
}
groupValues.set(groupKey, {
groupKey: groupKey,
groupName: String(nameField && nameField.value || 'Group'),
groupPickCount: Number(pickField && pickField.value) || 1,
groupShuffle: String((row.querySelector('[data-playlist-group-shuffle-value]') || {}).value || '0') === '1',
groupMode: String((row.querySelector('[data-playlist-group-mode-value]') || {}).value || 'select'),
groupRepeatCount: Number((row.querySelector('[data-playlist-group-repeat-count-value]') || {}).value) || 1,
groupScheduleRules: Array.isArray(rules) ? rules : []
});
});
rows.forEach(function (row) {
var keyField = row.querySelector('[data-playlist-group-key]');
var groupKey = String(keyField && keyField.value || '');
if (!groupKey) {
row.classList.remove('playlist-group-child');
orderedNodes.push(row);
return;
}
if (renderedKeys.has(groupKey)) {
return;
}
renderedKeys.add(groupKey);
orderedNodes.push(createGroupHeader(groupValues.get(groupKey)));
rows.filter(function (member) {
var memberKey = member.querySelector('[data-playlist-group-key]');
return String(memberKey && memberKey.value || '') === groupKey;
}).forEach(function (member) {
member.classList.add('playlist-group-child');
orderedNodes.push(member);
});
});
var emptyRow = target && target.querySelector('.playlist-empty-row');
if (emptyRow) {
orderedNodes.push(emptyRow);
}
if (target) {
var groupBlockFragment = document.createDocumentFragment();
orderedNodes.forEach(function (node) {
groupBlockFragment.appendChild(node);
});
target.replaceChildren(groupBlockFragment);
}
getGroupHeaders().forEach(syncGroupRowMetadata);
updateGroupScheduleWarnings();
}
function getTopLevelPlaylistBlocks() {
var currentTbody = document.getElementById('playlist-items-body') || tbody;
var children = currentTbody ? Array.prototype.slice.call(currentTbody.children) : [];
var blocks = [];
for (var index = 0; index < children.length; index += 1) {
var node = children[index];
if (node.classList.contains('playlist-empty-row')) {
continue;
}
if (!node.matches('[data-playlist-group-row]')) {
blocks.push({ key: String(node.getAttribute('data-group-key') || ''), nodes: [node] });
continue;
}
var groupKey = String(node.getAttribute('data-group-key') || '');
var groupNodes = [node];
var nextIndex = index + 1;
while (nextIndex < children.length && children[nextIndex].matches('[data-playlist-slide-row], [data-playlist-group-empty]')
&& String(children[nextIndex].getAttribute('data-group-key') || '') === groupKey) {
groupNodes.push(children[nextIndex]);
nextIndex += 1;
}
blocks.push({ key: groupKey, nodes: groupNodes });
index = nextIndex - 1;
}
return blocks;
}
function moveTopLevelBlock(blockNode, offset) {
var currentTbody = document.getElementById('playlist-items-body') || tbody;
var blocks = getTopLevelPlaylistBlocks();
var currentIndex = blocks.findIndex(function (block) { return block.nodes[0] === blockNode; });
var targetIndex = currentIndex + Number(offset || 0);
if (!currentTbody || currentIndex < 0 || targetIndex < 0 || targetIndex >= blocks.length) {
return false;
}
var movingNodes = blocks[currentIndex].nodes;
var referenceNode = offset < 0
? blocks[targetIndex].nodes[0]
: blocks[targetIndex].nodes[blocks[targetIndex].nodes.length - 1].nextSibling;
var movingBlock = document.createDocumentFragment();
movingNodes.forEach(function (node) { movingBlock.appendChild(node); });
if (referenceNode) currentTbody.insertBefore(movingBlock, referenceNode);
else currentTbody.appendChild(movingBlock);
updateRowOrder(true);
return true;
}
function moveGroupBlock(header, offset) {
return moveTopLevelBlock(header, offset);
}
function moveGroupBlockRelative(header, targetBlock, placeAfter, markDirty) {
var currentTbody = document.getElementById('playlist-items-body') || tbody;
var blocks = getTopLevelPlaylistBlocks();
var sourceBlock = blocks.find(function (block) { return block.nodes[0] === header; });
if (!currentTbody || !sourceBlock || !targetBlock || sourceBlock === targetBlock) {
return false;
}
var sourceNextSibling = sourceBlock.nodes[sourceBlock.nodes.length - 1].nextSibling;
var targetNextSibling = targetBlock.nodes[targetBlock.nodes.length - 1].nextSibling;
if ((!placeAfter && sourceNextSibling === targetBlock.nodes[0])
|| (placeAfter && targetNextSibling === sourceBlock.nodes[0])) {
return false;
}
var previousRects = new Map();
Array.prototype.forEach.call(currentTbody.children, function (node) {
if (node.matches('[data-playlist-group-row], [data-playlist-slide-row]')) {
previousRects.set(node, node.getBoundingClientRect());
}
});
var referenceNode = placeAfter
? targetNextSibling
: targetBlock.nodes[0];
var movingBlock = document.createDocumentFragment();
sourceBlock.nodes.forEach(function (node) { movingBlock.appendChild(node); });
if (referenceNode) currentTbody.insertBefore(movingBlock, referenceNode);
else currentTbody.appendChild(movingBlock);
updateRowOrder(markDirty !== false);
if (typeof window.animatePlaylistRowReorder === 'function') {
window.animatePlaylistRowReorder(previousRects, sourceBlock.nodes);
}
return true;
}
function bindGroupBlockDrag(targetTbody) {
if (!targetTbody || targetTbody.dataset.groupBlockDragBound === 'true') {
return;
}
targetTbody.dataset.groupBlockDragBound = 'true';
var draggedHeader = null;
var dragPreview = null;
var draggedMembers = [];
var originalNodes = [];
var placeholderNodes = [];
var placeholderHeader = null;
var groupDragLayer = null;
var originalNextSibling = null;
var groupDragMoved = false;
var groupDropCompleted = false;
var groupDragCleanupTimer = null;
var groupPointerState = null;
Array.prototype.forEach.call(document.querySelectorAll('.playlist-group-drag-preview'), function (preview) {
preview.remove();
});
Array.prototype.forEach.call(document.querySelectorAll('.playlist-group-original-drag-layer'), function (layer) {
layer.remove();
});
function createGroupDragPreview(header, point) {
var groupKey = String(header.getAttribute('data-group-key') || '');
var preview = document.createElement('div');
var groupRows = getGroupRows(groupKey).slice(0, 3);
var heading = document.createElement('div');
var icon = document.createElement('i');
var name = document.createElement('strong');
var memberList = document.createElement('div');
var groupNameInput = header.querySelector('[data-playlist-group-name]');
preview.className = 'playlist-group-drag-preview';
preview.style.setProperty('--playlist-group-accent', header.style.getPropertyValue('--playlist-group-accent'));
preview.style.setProperty('--playlist-group-bg', header.style.getPropertyValue('--playlist-group-bg'));
preview.setAttribute('aria-hidden', 'true');
preview.style.position = 'fixed';
preview.style.top = '0';
preview.style.left = '0';
preview.style.width = Math.min(460, Math.max(300, window.innerWidth * 0.4)) + 'px';
preview.style.pointerEvents = 'none';
preview.style.zIndex = '1200';
preview.style.transform = 'translate3d(' + Math.max(0, Number(point && point.x || 0) + 14) + 'px, ' + Math.max(0, Number(point && point.y || 0) + 14) + 'px, 0)';
heading.className = 'playlist-group-drag-preview-heading';
icon.className = 'bi bi-collection-fill playlist-group-icon';
icon.setAttribute('aria-hidden', 'true');
name.textContent = String(groupNameInput && groupNameInput.value || 'Group');
heading.appendChild(icon);
heading.appendChild(name);
memberList.className = 'playlist-group-drag-preview-members';
groupRows.forEach(function (row) {
var member = document.createElement('div');
var thumbnail = row.querySelector('[data-playlist-slide-thumb-image]');
var title = row.querySelector('[data-playlist-slide-title]');
member.className = 'playlist-group-drag-preview-member';
if (thumbnail && thumbnail.getAttribute('src')) {
var image = document.createElement('img');
image.alt = '';
image.src = thumbnail.getAttribute('src');
member.appendChild(image);
} else {
var placeholder = document.createElement('span');
placeholder.className = 'playlist-group-drag-preview-placeholder';
placeholder.innerHTML = '';
member.appendChild(placeholder);
}
var memberTitle = document.createElement('span');
memberTitle.textContent = String(title && title.textContent || 'Slide');
member.appendChild(memberTitle);
memberList.appendChild(member);
});
if (getGroupRows(groupKey).length > groupRows.length) {
var remaining = document.createElement('div');
remaining.className = 'playlist-group-drag-preview-more';
remaining.textContent = '+ ' + String(getGroupRows(groupKey).length - groupRows.length) + ' more';
memberList.appendChild(remaining);
}
preview.appendChild(heading);
preview.appendChild(memberList);
document.body.appendChild(preview);
return preview;
}
function cloneGroupPlaceholderNode(node) {
var clone = node.cloneNode(true);
clone.classList.add('playlist-group-block-placeholder');
clone.setAttribute('aria-hidden', 'true');
clone.style.pointerEvents = 'none';
if (clone.matches('[data-playlist-slide-row]')) {
clone.setAttribute('data-playlist-ghost-row', 'true');
}
Array.prototype.forEach.call(clone.querySelectorAll('[id]'), function (element) {
element.removeAttribute('id');
});
Array.prototype.forEach.call(clone.querySelectorAll('input, select, textarea, button'), function (control) {
control.disabled = true;
control.removeAttribute('name');
control.removeAttribute('form');
control.setAttribute('tabindex', '-1');
});
return clone;
}
function createGroupDragLayer(nodes) {
var layer = document.createElement('table');
var layerBody = document.createElement('tbody');
layer.className = 'playlist-group-original-drag-layer';
layer.setAttribute('aria-hidden', 'true');
layer.style.position = 'fixed';
layer.style.top = '0';
layer.style.left = '-10000px';
layer.style.width = String(targetTbody.getBoundingClientRect().width) + 'px';
layer.style.pointerEvents = 'none';
layer.style.opacity = '0';
layer.style.tableLayout = 'fixed';
layer.style.zIndex = '-1';
nodes.forEach(function (node) {
layerBody.appendChild(node);
});
layer.appendChild(layerBody);
document.body.appendChild(layer);
return layer;
}
function getGroupDragPoint(event) {
var touch = event.touches && event.touches[0]
? event.touches[0]
: event.changedTouches && event.changedTouches[0]
? event.changedTouches[0]
: null;
return {
x: touch ? touch.clientX : event.clientX,
y: touch ? touch.clientY : event.clientY
};
}
function beginGroupPointerDrag(point) {
if (!groupPointerState || groupPointerState.active) {
return false;
}
draggedHeader = groupPointerState.header;
var sourceBlock = getTopLevelPlaylistBlocks().find(function (block) { return block.nodes[0] === draggedHeader; });
if (!sourceBlock) {
draggedHeader = null;
return false;
}
originalNodes = sourceBlock.nodes.slice();
originalNextSibling = originalNodes[originalNodes.length - 1].nextSibling;
dragPreview = createGroupDragPreview(draggedHeader, point);
placeholderNodes = originalNodes.map(cloneGroupPlaceholderNode);
placeholderHeader = placeholderNodes[0];
var placeholderBlock = document.createDocumentFragment();
placeholderNodes.forEach(function (node) { placeholderBlock.appendChild(node); });
targetTbody.insertBefore(placeholderBlock, draggedHeader);
draggedMembers = originalNodes.filter(function (node) { return node.matches('[data-playlist-slide-row]'); });
originalNodes.forEach(function (node) { node.classList.add('playlist-group-block-dragging'); });
groupDragLayer = createGroupDragLayer(originalNodes);
groupPointerState.active = true;
groupDragMoved = false;
groupDropCompleted = false;
groupDragCleanupTimer = window.setTimeout(finishGroupDrag, 120000);
return true;
}
function moveGroupPlaceholder(clientY) {
var blocks = getTopLevelPlaylistBlocks();
var sourceBlock = blocks.find(function (block) { return block.nodes[0] === placeholderHeader; });
if (!sourceBlock) {
return;
}
var destination = null;
var placeAfter = false;
for (var index = 0; index < blocks.length; index += 1) {
var block = blocks[index];
if (block === sourceBlock) {
continue;
}
var firstRect = block.nodes[0].getBoundingClientRect();
var lastRect = block.nodes[block.nodes.length - 1].getBoundingClientRect();
var midpoint = (firstRect.top + lastRect.bottom) / 2;
if (clientY < midpoint) {
destination = block;
placeAfter = false;
break;
}
destination = block;
placeAfter = true;
}
if (destination && moveGroupBlockRelative(placeholderHeader, destination, placeAfter, false)) {
groupDragMoved = true;
}
}
function handleGroupPointerMove(event) {
if (!groupPointerState) {
return;
}
var point = getGroupDragPoint(event);
if (!groupPointerState.active) {
var threshold = 4;
if (Math.abs(point.x - groupPointerState.startPoint.x) < threshold
&& Math.abs(point.y - groupPointerState.startPoint.y) < threshold) {
return;
}
if (!beginGroupPointerDrag(groupPointerState.currentPoint || point)) {
return;
}
}
event.preventDefault();
if (dragPreview) {
dragPreview.style.transform = 'translate3d(' + Math.max(0, point.x + 14) + 'px, ' + Math.max(0, point.y + 14) + 'px, 0)';
}
moveGroupPlaceholder(point.y);
}
function handleGroupPointerEnd(event, shouldCancel) {
if (!groupPointerState) {
return;
}
if (groupPointerState.active) {
groupDropCompleted = !shouldCancel && groupDragMoved;
finishGroupDrag();
} else {
groupPointerState = null;
}
}
function finishGroupDrag() {
var hasGroupDrag = Boolean(originalNodes.length || placeholderNodes.length || groupDragLayer);
var shouldCommit = groupDropCompleted && groupDragMoved;
if (groupDragCleanupTimer !== null) {
window.clearTimeout(groupDragCleanupTimer);
groupDragCleanupTimer = null;
}
if (placeholderNodes.length && placeholderHeader && placeholderHeader.parentNode && !shouldCommit) {
var resetPlaceholder = document.createDocumentFragment();
placeholderNodes.forEach(function (node) { resetPlaceholder.appendChild(node); });
if (originalNextSibling && originalNextSibling.parentNode === targetTbody) {
targetTbody.insertBefore(resetPlaceholder, originalNextSibling);
} else {
targetTbody.appendChild(resetPlaceholder);
}
}
if (originalNodes.length && placeholderHeader && placeholderHeader.parentNode) {
var restoredBlock = document.createDocumentFragment();
originalNodes.forEach(function (node) {
node.classList.remove('playlist-group-block-dragging');
restoredBlock.appendChild(node);
});
placeholderHeader.parentNode.insertBefore(restoredBlock, placeholderHeader);
}
placeholderNodes.forEach(function (node) {
if (node.parentNode) node.parentNode.removeChild(node);
});
if (groupDragLayer && groupDragLayer.parentNode) groupDragLayer.parentNode.removeChild(groupDragLayer);
if (draggedHeader) draggedHeader.classList.remove('playlist-group-block-dragging');
draggedMembers.forEach(function (row) { row.classList.remove('playlist-group-block-dragging'); });
draggedMembers = [];
Array.prototype.forEach.call(document.querySelectorAll('.playlist-group-drag-preview'), function (preview) {
preview.remove();
});
Array.prototype.forEach.call(document.querySelectorAll('.playlist-group-drop-target'), function (target) {
target.classList.remove('playlist-group-drop-target');
});
dragPreview = null;
draggedHeader = null;
originalNodes = [];
placeholderNodes = [];
placeholderHeader = null;
groupDragLayer = null;
originalNextSibling = null;
groupDragMoved = false;
groupDropCompleted = false;
groupPointerState = null;
if (hasGroupDrag) updateRowOrder(shouldCommit);
}
function handleGroupPointerStart(event) {
var handle = event.target && event.target.closest ? event.target.closest('[data-playlist-group-drag-handle]') : null;
var header = handle ? handle.closest('[data-playlist-group-row]') : null;
var point = getGroupDragPoint(event);
if (!header || groupPointerState || !point) {
return;
}
if (event.type === 'mousedown' && event.button !== 0) {
return;
}
event.preventDefault();
event.stopPropagation();
groupPointerState = {
header: header,
startPoint: point,
currentPoint: point,
pointerId: event.pointerId,
active: false
};
}
function handleGroupPointerMove(event) {
if (!groupPointerState) {
return;
}
if (groupPointerState.pointerId !== undefined && event.pointerId !== undefined
&& groupPointerState.pointerId !== event.pointerId) {
return;
}
var point = getGroupDragPoint(event);
groupPointerState.currentPoint = point;
if (!groupPointerState.active) {
var threshold = 4;
if (Math.abs(point.x - groupPointerState.startPoint.x) < threshold
&& Math.abs(point.y - groupPointerState.startPoint.y) < threshold) {
return;
}
if (!beginGroupPointerDrag(point)) {
return;
}
}
event.preventDefault();
if (dragPreview) {
dragPreview.style.transform = 'translate3d(' + Math.max(0, point.x + 14) + 'px, ' + Math.max(0, point.y + 14) + 'px, 0)';
}
moveGroupPlaceholder(point.y);
}
function handleGroupPointerEnd(event, shouldCancel) {
if (!groupPointerState) {
return;
}
if (groupPointerState.pointerId !== undefined && event && event.pointerId !== undefined
&& groupPointerState.pointerId !== event.pointerId) {
return;
}
if (groupPointerState.active) {
groupDropCompleted = !shouldCancel && groupDragMoved;
finishGroupDrag();
return;
}
groupPointerState = null;
}
if (typeof window !== 'undefined' && window.PointerEvent) {
targetTbody.addEventListener('pointerdown', handleGroupPointerStart);
document.addEventListener('pointermove', handleGroupPointerMove, { passive: false });
document.addEventListener('pointerup', function (event) { handleGroupPointerEnd(event, false); });
document.addEventListener('pointercancel', function (event) { handleGroupPointerEnd(event, true); });
} else {
targetTbody.addEventListener('mousedown', handleGroupPointerStart);
document.addEventListener('mousemove', handleGroupPointerMove);
document.addEventListener('mouseup', function (event) { handleGroupPointerEnd(event, false); });
targetTbody.addEventListener('touchstart', handleGroupPointerStart, { passive: false });
document.addEventListener('touchmove', handleGroupPointerMove, { passive: false });
document.addEventListener('touchend', function (event) { handleGroupPointerEnd(event, false); });
document.addEventListener('touchcancel', function (event) { handleGroupPointerEnd(event, true); });
}
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape') handleGroupPointerEnd(event, true);
});
document.addEventListener('visibilitychange', function () {
if (document.hidden) handleGroupPointerEnd(null, true);
});
window.addEventListener('blur', function () { handleGroupPointerEnd(null, true); });
}
function removePlaylistGroup(header) {
if (!header) {
return;
}
var groupKey = String(header.getAttribute('data-group-key') || '');
getGroupRows(groupKey).forEach(function (row) {
row.remove();
});
header.remove();
updateRowOrder(true);
}
function setSlideGroupFields(row, targetGroupKey) {
var groupKey = String(targetGroupKey || '');
var keyField = row && row.querySelector('[data-playlist-group-key]');
var nameField = row && row.querySelector('[data-playlist-group-name-value]');
var pickField = row && row.querySelector('[data-playlist-group-pick-count-value]');
var shuffleField = row && row.querySelector('[data-playlist-group-shuffle-value]');
var modeField = row && row.querySelector('[data-playlist-group-mode-value]');
var repeatField = row && row.querySelector('[data-playlist-group-repeat-count-value]');
var scheduleField = row && row.querySelector('[data-playlist-group-schedule-rules]');
var header = groupKey ? getGroupHeader(groupKey) : null;
if (groupKey && !header) {
return false;
}
if (keyField) keyField.value = groupKey;
row.setAttribute('data-group-key', groupKey);
row.classList.toggle('playlist-group-child', Boolean(groupKey));
if (groupKey) {
var groupNameInput = header.querySelector('[data-playlist-group-name]');
var groupPickInput = header.querySelector('[data-playlist-group-pick-count]');
var groupScheduleInput = header.querySelector('[data-playlist-group-schedule-rules]');
if (nameField) nameField.value = String(groupNameInput && groupNameInput.value || '').trim() || 'Group';
if (pickField) pickField.value = String(Math.max(1, Number(groupPickInput && groupPickInput.value) || 1));
var groupShuffleButton = header.querySelector('[data-playlist-group-shuffle]');
if (shuffleField) shuffleField.value = groupShuffleButton && groupShuffleButton.getAttribute('aria-pressed') === 'true' ? '1' : '0';
if (modeField) modeField.value = header.getAttribute('data-group-mode') === 'repeat' ? 'repeat' : 'select';
var groupRepeatInput = header.querySelector('[data-playlist-group-repeat-count]');
if (repeatField) repeatField.value = String(Number(groupRepeatInput && groupRepeatInput.value) || 1);
if (scheduleField) scheduleField.value = String(groupScheduleInput && groupScheduleInput.value || '[]');
} else {
if (nameField) nameField.value = '';
if (pickField) pickField.value = '1';
if (shuffleField) shuffleField.value = '0';
if (modeField) modeField.value = 'select';
if (repeatField) repeatField.value = '1';
if (scheduleField) scheduleField.value = '[]';
}
return true;
}
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 || getGroupHeaders().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();
}
function updatePlaylistGroupEmptyRows() {
var currentTbody = document.getElementById('playlist-items-body') || tbody;
if (!currentTbody) return;
Array.prototype.forEach.call(currentTbody.querySelectorAll('[data-playlist-group-empty]'), function (row) {
row.remove();
});
getGroupHeaders().forEach(function (header) {
var groupKey = String(header.getAttribute('data-group-key') || '');
if (getGroupRows(groupKey).length) return;
var emptyRow = document.createElement('tr');
emptyRow.className = 'playlist-group-child playlist-group-empty';
emptyRow.setAttribute('data-playlist-group-empty', '');
emptyRow.setAttribute('data-group-key', groupKey);
emptyRow.innerHTML = ' | ';
header.after(emptyRow);
});
}
function updatePlaylistGroupColours(rows, blocks) {
var palette = ['success', 'warning', 'info', 'danger'];
var usedColours = new Set();
blocks.forEach(function (block) {
var colour = block.nodes[0].getAttribute('data-group-colour');
if (palette.indexOf(colour) !== -1) usedColours.add(colour);
});
rows.forEach(function (row) {
row.style.removeProperty('--playlist-group-accent');
row.style.removeProperty('--playlist-group-bg');
});
var groupIndex = 0;
blocks.forEach(function (block) {
var header = block.nodes[0];
if (!header.matches('[data-playlist-group-row]')) return;
var colour = header.getAttribute('data-group-colour');
if (palette.indexOf(colour) === -1) {
colour = palette.find(function (candidate) { return !usedColours.has(candidate); }) || palette[groupIndex % palette.length];
header.setAttribute('data-group-colour', colour);
}
usedColours.add(colour);
groupIndex += 1;
block.nodes.forEach(function (row) {
row.style.setProperty('--playlist-group-accent', 'var(--bs-' + colour + ')');
row.style.setProperty('--playlist-group-bg', 'var(--bs-' + colour + '-bg-subtle)');
});
});
}
// Recompute row numbers and derived picker state after drag/drop or adds.
function updateRowOrder(markDirty) {
var rows = getRows();
updatePlaylistGroupEmptyRows();
getGroupHeaders().forEach(syncGroupRowMetadata);
var blocks = getTopLevelPlaylistBlocks();
updatePlaylistGroupColours(rows, blocks);
rows.forEach(function (row) {
var rowGroupKey = String((row.querySelector('[data-playlist-group-key]') || {}).value || '');
var siblingRows = rows.filter(function (candidate) {
return String((candidate.querySelector('[data-playlist-group-key]') || {}).value || '') === rowGroupKey;
});
var siblingIndex = siblingRows.indexOf(row);
var blockIndex = blocks.findIndex(function (block) {
return rowGroupKey ? block.key === rowGroupKey : block.nodes[0] === row;
});
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 = rowGroupKey ? siblingIndex === 0 : blockIndex === 0;
var isLast = rowGroupKey ? siblingIndex === siblingRows.length - 1 : blockIndex === blocks.length - 1;
if (orderNumber) {
orderNumber.textContent = String(blockIndex + 1) + (rowGroupKey ? '.' + String(siblingIndex + 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);
}
});
blocks.forEach(function (block, index) {
var header = block.nodes[0];
if (!header || !header.matches('[data-playlist-group-row]')) {
return;
}
var orderNumber = header.querySelector('[data-playlist-group-order]');
if (orderNumber) orderNumber.textContent = String(index + 1);
var moveUpButton = header.querySelector('[data-playlist-group-move="up"]');
var moveDownButton = header.querySelector('[data-playlist-group-move="down"]');
var moveUpIcon = header.querySelector('[data-playlist-group-move-icon="up"]');
var moveDownIcon = header.querySelector('[data-playlist-group-move-icon="down"]');
if (moveUpButton) moveUpButton.disabled = index === 0;
if (moveDownButton) moveDownButton.disabled = index === blocks.length - 1;
if (moveUpIcon) {
moveUpIcon.classList.toggle('bi-caret-up-fill', index !== 0);
moveUpIcon.classList.toggle('bi-caret-up', index === 0);
}
if (moveDownIcon) {
moveDownIcon.classList.toggle('bi-caret-down-fill', index !== blocks.length - 1);
moveDownIcon.classList.toggle('bi-caret-down', index === blocks.length - 1);
}
});
syncAddSlideOptions();
updateGroupScheduleWarnings();
syncPlaylistCanvasLock();
syncAddSlideButtonState();
updateEmptyState();
if (markDirty) {
markPlaylistDirty();
}
}
form.addEventListener('submit', function () {
getGroupHeaders().forEach(syncGroupRowMetadata);
syncScheduleDraftRows();
}, true);
function bindPlaylistTableDrag(tbodyElement) {
if (!tbodyElement || typeof window.initPlaylistTableDrag !== 'function') {
return;
}
window.initPlaylistTableDrag({
tbody: tbodyElement,
onOrderChanged: updateRowOrder,
onRowDrop: function (row, targetGroupKey) {
if (setSlideGroupFields(row, targetGroupKey)) {
var groupHeader = targetGroupKey ? getGroupHeader(targetGroupKey) : null;
if (groupHeader) {
var previousMember = groupHeader;
getGroupRows(targetGroupKey).forEach(function (member) {
previousMember.after(member);
previousMember = member;
});
}
markPlaylistDirty();
}
}
});
}
function movePlaylistRowByOffset(row, offset) {
var currentTbody = document.getElementById('playlist-items-body') || tbody;
var groupKey = String((row && row.querySelector('[data-playlist-group-key]') || {}).value || '');
if (!groupKey) {
return moveTopLevelBlock(row, offset);
}
var rows = getRows().filter(function (candidate) {
return String((candidate.querySelector('[data-playlist-group-key]') || {}).value || '') === groupKey;
});
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 groupKeyInput = row ? row.querySelector('[data-playlist-group-key]') : null;
var groupNameInput = row ? row.querySelector('[data-playlist-group-name-value]') : null;
var groupPickCountInput = row ? row.querySelector('[data-playlist-group-pick-count-value]') : null;
var groupShuffleInput = row ? row.querySelector('[data-playlist-group-shuffle-value]') : null;
var groupModeInput = row ? row.querySelector('[data-playlist-group-mode-value]') : null;
var groupRepeatInput = row ? row.querySelector('[data-playlist-group-repeat-count-value]') : null;
var groupScheduleInput = row ? row.querySelector('[data-playlist-group-schedule-rules]') : null;
var groupKey = String(values.groupKey || '');
var groupScheduleRules = Array.isArray(values.groupScheduleRules) ? values.groupScheduleRules : [];
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-group-key', groupKey);
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 (groupKeyInput) {
groupKeyInput.value = groupKey;
}
if (groupNameInput) {
groupNameInput.value = String(values.groupName || '');
}
if (groupPickCountInput) {
groupPickCountInput.value = String(Math.max(1, Number(values.groupPickCount) || 1));
}
if (groupShuffleInput) {
groupShuffleInput.value = values.groupShuffle ? '1' : '0';
}
if (groupModeInput) groupModeInput.value = values.groupMode === 'repeat' ? 'repeat' : 'select';
if (groupRepeatInput) groupRepeatInput.value = String(Math.min(100, Math.max(1, Number(values.groupRepeatCount) || 1)));
if (groupScheduleInput) {
groupScheduleInput.value = JSON.stringify(groupScheduleRules);
}
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;
}
if (createGroupButton && event.target.closest('#playlist-create-group')) {
event.preventDefault();
var nextGroupKey = 'group-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
var emptyPlaylistRow = tbody.querySelector('.playlist-empty-row');
if (emptyPlaylistRow) emptyPlaylistRow.remove();
var newGroupHeader = createGroupHeader({ groupKey: nextGroupKey, groupName: 'New group', groupPickCount: 1, groupScheduleRules: [] });
tbody.appendChild(newGroupHeader);
updateRowOrder(true);
var newGroupName = newGroupHeader.querySelector('[data-playlist-group-name]');
if (newGroupName) {
newGroupName.focus();
newGroupName.select();
}
return;
}
var groupHeader = event.target.closest('tr[data-playlist-group-row]');
var groupModeButton = event.target.closest('[data-playlist-group-mode]');
if (groupHeader && groupModeButton) {
event.preventDefault();
groupHeader.setAttribute('data-group-mode', groupModeButton.getAttribute('data-playlist-group-mode') === 'repeat' ? 'repeat' : 'select');
syncGroupRowMetadata(groupHeader);
markPlaylistDirty();
return;
}
var groupShuffleButton = event.target.closest('[data-playlist-group-shuffle]');
if (groupHeader && groupShuffleButton) {
event.preventDefault();
var shuffleEnabled = groupShuffleButton.getAttribute('aria-pressed') !== 'true';
groupShuffleButton.setAttribute('aria-pressed', shuffleEnabled ? 'true' : 'false');
groupShuffleButton.setAttribute('aria-label', shuffleEnabled ? 'Disable shuffle' : 'Enable shuffle');
groupShuffleButton.title = shuffleEnabled ? 'Disable shuffle' : 'Enable shuffle';
groupShuffleButton.classList.toggle('btn-secondary', shuffleEnabled);
groupShuffleButton.classList.toggle('btn-outline-secondary', !shuffleEnabled);
groupShuffleButton.querySelector('i').className = shuffleEnabled ? 'bi bi-shuffle' : 'bi bi-chevron-double-right';
syncGroupRowMetadata(groupHeader);
markPlaylistDirty();
return;
}
var groupMoveButton = event.target.closest('[data-playlist-group-move]');
var groupRemoveButton = event.target.closest('[data-playlist-group-remove]');
var groupScheduleButton = event.target.closest('[data-group-schedule-config]');
if (groupHeader && groupMoveButton) {
event.preventDefault();
moveGroupBlock(groupHeader, groupMoveButton.getAttribute('data-playlist-group-move') === 'up' ? -1 : 1);
return;
}
if (groupHeader && groupRemoveButton) {
event.preventDefault();
removePlaylistGroup(groupHeader);
return;
}
if (groupHeader && groupScheduleButton && !groupScheduleButton.disabled) {
event.preventDefault();
if (typeof window.openScheduleModal === 'function') {
var groupKey = String(groupHeader.getAttribute('data-group-key') || '');
var groupScheduleParams = collectScheduleParams(groupHeader, 'group:' + groupKey, true);
window.openScheduleModal(groupScheduleButton.getAttribute('data-group-schedule-config') + '?' + groupScheduleParams.toString());
}
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();
return;
}
if (event.target && event.target.matches('[data-playlist-group-name]')) {
var editedGroupHeader = event.target.closest('[data-playlist-group-row]');
if (!editedGroupHeader) {
return;
}
syncGroupRowMetadata(editedGroupHeader);
markPlaylistDirty();
return;
}
if (event.target && event.target.matches('[data-playlist-group-pick-count], [data-playlist-group-repeat-count]')) {
var quotaGroupHeader = event.target.closest('[data-playlist-group-row]');
if (quotaGroupHeader) {
syncGroupRowMetadata(quotaGroupHeader);
markPlaylistDirty();
}
}
});
document.addEventListener('change', function (event) {
if (!event.target) {
return;
}
if (event.target.matches('[data-playlist-group-name]')) {
var groupNameHeader = event.target.closest('[data-playlist-group-row]');
if (groupNameHeader) {
if (!String(event.target.value || '').trim()) event.target.value = 'Group';
syncGroupRowMetadata(groupNameHeader);
markPlaylistDirty();
}
return;
}
if (event.target.matches('[data-playlist-group-pick-count], [data-playlist-group-repeat-count]')) {
var groupQuotaHeader = event.target.closest('[data-playlist-group-row]');
if (groupQuotaHeader) {
event.target.value = String(Math.max(1, Math.floor(Number(event.target.value) || 1)));
syncGroupRowMetadata(groupQuotaHeader);
markPlaylistDirty();
}
return;
}
});
initializePlaylistGroups(tbody);
bindGroupBlockDrag(tbody);
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) {
initializePlaylistGroups(tbodyElement);
bindGroupBlockDrag(tbodyElement);
bindPlaylistTableDrag(tbodyElement);
hydrateScheduleRowContainers(tbodyElement);
updateRowOrder();
};
updateRowOrder();
syncPlaylistCanvasLock();
syncAddSlideButtonState();
}
window.initPlaylistScheduleForm = initPlaylistScheduleForm;
initPlaylistScheduleForm();
initPlaylistEditStaging();
}());