mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-10-20 21:49:16 +00:00
* feat(schedule): per-plugin schedules and hide-while-live Users asked to turn off news and stocks on gameday so only scores show. Each plugin can now have its own schedule (global or per-day window in which it may show) plus hide_when_live, which hides it whenever any plugin reports a live game. A hidden plugin is skipped in the rotation, the live takeover and the Vegas ticker; it stays loaded, and on-demand still shows it. - src/plugin_schedule.py: the window rule, now shared with the display schedule (_check_schedule calls schedule_state) - core config key plugin_schedules; GET/POST /api/v3/config/plugin-schedules - Schedule page: Plugin Schedules card, one picker per enabled plugin Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(schedule): tolerate wrong-shaped schedules, quiet skipped modes - schedule_state treats a non-string mode, non-dict days or a non-dict day entry as missing instead of raising in the display loop - the run loop no longer warns "No plugin manager found" for a mode it skips: _plugin_to_draw / _plugin_for_mode already log why (a scheduled-off plugin at DEBUG) - Schedule page DOM test: every form is marked data-reports-result, not exactly two (each plugin adds one) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(schedule): guard plugin_schedules shape and the cross-thread cache - a plugin_schedules that is not a dict (list, string) hides nothing instead of raising in the display loop - the hidden-set cache keys on the schedules themselves and is published as one tuple, so a Vegas-thread call still holding the pre-reload config cannot leave its answer standing after a reload; the separate invalidation in _refresh_config_cache is gone Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(schedule): no live hold on a scheduled-off mode; blank an empty rotation - _still_live ignores a plugin its schedule hides, so a rotation landing on a scheduled-off live mode advances instead of holding a mode it skips - once a whole rotation has had nothing to show (every plugin empty or scheduled off), the panel is cleared once instead of leaving the last screen up Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(schedule): push the empty-rotation blank to the web preview clear() alone blanked the matrix but left the preview snapshot on the last frame (seen on ledpi with every plugin scheduled off). Follow it with update_display(), as the scheduled-off blank does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(schedule): write the empty-rotation blank to the preview The preview throttle skipped the blank frame (written 12 s after the last snapshot), and write_owed_snapshot() only runs after a display() call, which an all-empty rotation never makes: on ledpi the preview kept the startup splash while the panel was dark. Flush the owed snapshot on each paused empty pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(schedule): quiet the once-a-second logs when nothing can show With every plugin scheduled off (seen on ledpi), the loop retries once a second and journaled three to four lines each time: Vegas's "No plugins available" WARNING, the coordinator's "Failed to initialize stream manager" ERROR, the 0-plugin rotation summary and "Switching to mode". Vegas now warns once until its list fills again, the coordinator line is DEBUG (initialize only fails for an empty list), and the mode-switch line is DEBUG once a whole rotation has been empty. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(schedule): end the scroll before the empty blank; Vegas sees hidden changes sooner - the empty-rotation blank is controller-drawn, so it ends the last scroll's state first (_end_scroll_before_core_screen), as the scheduled-off blank does - StreamManager.refresh() rebuilds the plugin list when the hidden set changed, even inside its 30 s interval, so a newly hidden plugin leaves at the next scroll group rather than staying for later ones Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
568 lines
26 KiB
JavaScript
568 lines
26 KiB
JavaScript
/**
|
|
* LEDMatrix Schedule Picker Widget
|
|
*
|
|
* Composite widget combining enable toggle, mode switch (global/per-day),
|
|
* and time range configurations. Composes day-selector and time-range widgets.
|
|
*
|
|
* Can be used standalone in schedule.html or by plugins via x-widget: "schedule-picker".
|
|
*
|
|
* Schema example:
|
|
* {
|
|
* "schedule": {
|
|
* "type": "object",
|
|
* "x-widget": "schedule-picker",
|
|
* "x-options": {
|
|
* "showModeToggle": true, // Allow switching global/per-day
|
|
* "showEnableToggle": true, // Show enabled checkbox
|
|
* "compactMode": false, // Compact layout for embedded use
|
|
* "defaultMode": "global" // Default mode: "global" or "per_day"
|
|
* }
|
|
* }
|
|
* }
|
|
*
|
|
* API-compatible output format:
|
|
* {
|
|
* enabled: boolean,
|
|
* mode: "global" | "per_day",
|
|
* start_time: "HH:MM", // if global mode
|
|
* end_time: "HH:MM", // if global mode
|
|
* days: { // if per_day mode
|
|
* monday: { enabled: boolean, start_time: "HH:MM", end_time: "HH:MM" },
|
|
* ...
|
|
* }
|
|
* }
|
|
*
|
|
* @module SchedulePickerWidget
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
const DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
|
|
|
const DAY_LABELS = {
|
|
monday: 'Monday',
|
|
tuesday: 'Tuesday',
|
|
wednesday: 'Wednesday',
|
|
thursday: 'Thursday',
|
|
friday: 'Friday',
|
|
saturday: 'Saturday',
|
|
sunday: 'Sunday'
|
|
};
|
|
const DAY_LABEL_MAP = new Map(Object.entries(DAY_LABELS));
|
|
|
|
// Use BaseWidget utilities if available
|
|
const base = window.BaseWidget ? new window.BaseWidget('SchedulePicker', '1.0.0') : null;
|
|
|
|
function sanitizeId(id) {
|
|
if (base) return base.sanitizeId(id);
|
|
return String(id).replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
}
|
|
|
|
function triggerChange(fieldId, value) {
|
|
if (base) {
|
|
base.triggerChange(fieldId, value);
|
|
} else {
|
|
const event = new CustomEvent('widget-change', {
|
|
detail: { fieldId, value },
|
|
bubbles: true,
|
|
cancelable: true
|
|
});
|
|
document.dispatchEvent(event);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate default schedule config
|
|
*/
|
|
function getDefaultSchedule() {
|
|
const days = {};
|
|
DAYS.forEach(day => {
|
|
days[day] = {
|
|
enabled: true,
|
|
start_time: '07:00',
|
|
end_time: '23:00'
|
|
};
|
|
});
|
|
return {
|
|
enabled: false,
|
|
mode: 'global',
|
|
start_time: '07:00',
|
|
end_time: '23:00',
|
|
days: days
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Coerce a value to boolean, handling string 'true'/'false' values
|
|
* that may come from config files or form submissions.
|
|
*/
|
|
function coerceToBoolean(value) {
|
|
if (typeof value === 'boolean') {
|
|
return value;
|
|
}
|
|
if (typeof value === 'string') {
|
|
const trimmed = value.trim().toLowerCase();
|
|
return trimmed === 'true' || trimmed === '1' || trimmed === 'on';
|
|
}
|
|
return Boolean(value);
|
|
}
|
|
|
|
/**
|
|
* Normalize mode value to handle both 'per_day' and 'per-day' variants.
|
|
*/
|
|
function normalizeMode(mode) {
|
|
if (!mode || typeof mode !== 'string') {
|
|
return 'global';
|
|
}
|
|
// Normalize: replace hyphens with underscores and check for per_day
|
|
const normalized = mode.trim().toLowerCase().replace(/-/g, '_');
|
|
return normalized === 'per_day' ? 'per_day' : 'global';
|
|
}
|
|
|
|
/**
|
|
* Merge user value with defaults
|
|
*/
|
|
function normalizeSchedule(value) {
|
|
const defaults = getDefaultSchedule();
|
|
if (!value || typeof value !== 'object') {
|
|
return defaults;
|
|
}
|
|
|
|
const schedule = {
|
|
enabled: coerceToBoolean(value.enabled),
|
|
mode: normalizeMode(value.mode),
|
|
start_time: value.start_time || defaults.start_time,
|
|
end_time: value.end_time || defaults.end_time,
|
|
days: {}
|
|
};
|
|
|
|
// Merge days
|
|
DAYS.forEach(day => {
|
|
const dayConfig = (value.days && value.days[day]) || defaults.days[day];
|
|
// Use coerceToBoolean but default to true if enabled is undefined
|
|
const dayEnabled = dayConfig.enabled === undefined ? true : coerceToBoolean(dayConfig.enabled);
|
|
schedule.days[day] = {
|
|
enabled: dayEnabled,
|
|
start_time: dayConfig.start_time || defaults.days[day].start_time,
|
|
end_time: dayConfig.end_time || defaults.days[day].end_time
|
|
};
|
|
});
|
|
|
|
return schedule;
|
|
}
|
|
|
|
window.LEDMatrixWidgets.register('schedule-picker', {
|
|
name: 'Schedule Picker Widget',
|
|
version: '1.0.0',
|
|
|
|
/**
|
|
* Render the schedule picker widget
|
|
* @param {HTMLElement} container - Container element
|
|
* @param {Object} config - Schema configuration
|
|
* @param {Object} value - Schedule configuration object
|
|
* @param {Object} options - Additional options (fieldId, pluginId)
|
|
*/
|
|
render: function(container, config, value, options) {
|
|
const fieldId = sanitizeId(options.fieldId || container.id || 'schedule');
|
|
const xOptions = config['x-options'] || config['x_options'] || {};
|
|
const showModeToggle = xOptions.showModeToggle !== false;
|
|
const showEnableToggle = xOptions.showEnableToggle !== false;
|
|
|
|
const schedule = normalizeSchedule(value);
|
|
|
|
let html = `<div id="${fieldId}_widget" class="schedule-picker-widget" data-field-id="${fieldId}">`;
|
|
|
|
// Hidden inputs for API-compatible form submission
|
|
html += this._renderHiddenInputs(fieldId, schedule);
|
|
|
|
// Enable toggle
|
|
if (showEnableToggle) {
|
|
html += `
|
|
<div class="bg-blue-50 rounded-lg p-4 mb-4">
|
|
<label class="flex items-center cursor-pointer">
|
|
<input type="checkbox"
|
|
id="${fieldId}_enabled"
|
|
${schedule.enabled ? 'checked' : ''}
|
|
onchange="window.LEDMatrixWidgets.getHandlers('schedule-picker').onEnabledChange('${fieldId}', this.checked)"
|
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
|
<span class="ml-2 text-sm font-medium text-gray-900">Enable Schedule</span>
|
|
</label>
|
|
<p class="mt-1 text-sm text-gray-600">${window.LEDEscape.html(xOptions.enableDescription || 'When enabled, the display will only operate during specified hours.')}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Mode selection
|
|
if (showModeToggle) {
|
|
html += `
|
|
<div class="bg-gray-50 rounded-lg p-4 mb-4">
|
|
<h3 class="text-md font-medium text-gray-900 mb-4">Schedule Mode</h3>
|
|
<div class="space-y-3">
|
|
<label class="flex items-center cursor-pointer">
|
|
<input type="radio"
|
|
name="${fieldId}_mode"
|
|
value="global"
|
|
id="${fieldId}_mode_global"
|
|
${schedule.mode === 'global' ? 'checked' : ''}
|
|
onchange="window.LEDMatrixWidgets.getHandlers('schedule-picker').onModeChange('${fieldId}', 'global')"
|
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
|
|
<span class="ml-2 text-sm font-medium text-gray-900">Global Schedule</span>
|
|
</label>
|
|
<p class="ml-6 text-sm text-gray-600">Use the same start and end time for all days of the week</p>
|
|
|
|
<label class="flex items-center cursor-pointer mt-4">
|
|
<input type="radio"
|
|
name="${fieldId}_mode"
|
|
value="per_day"
|
|
id="${fieldId}_mode_per_day"
|
|
${schedule.mode === 'per_day' ? 'checked' : ''}
|
|
onchange="window.LEDMatrixWidgets.getHandlers('schedule-picker').onModeChange('${fieldId}', 'per_day')"
|
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300">
|
|
<span class="ml-2 text-sm font-medium text-gray-900">Per-Day Schedule</span>
|
|
</label>
|
|
<p class="ml-6 text-sm text-gray-600">Set different times for each day of the week</p>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
// Global schedule section
|
|
const globalDisplay = schedule.mode === 'global' ? 'block' : 'none';
|
|
html += `
|
|
<div id="${fieldId}_global_section" class="bg-gray-50 rounded-lg p-4 mb-4" style="display: ${globalDisplay};">
|
|
<h3 class="text-md font-medium text-gray-900 mb-4">Global Times</h3>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div class="form-group">
|
|
<label for="${fieldId}_global_start" class="block text-sm font-medium text-gray-700">Start Time</label>
|
|
<input type="time"
|
|
id="${fieldId}_global_start"
|
|
value="${window.LEDEscape.html(schedule.start_time)}"
|
|
onchange="window.LEDMatrixWidgets.getHandlers('schedule-picker').onGlobalTimeChange('${fieldId}')"
|
|
class="form-control mt-1">
|
|
<p class="mt-1 text-sm text-gray-600">When to start displaying content (HH:MM)</p>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="${fieldId}_global_end" class="block text-sm font-medium text-gray-700">End Time</label>
|
|
<input type="time"
|
|
id="${fieldId}_global_end"
|
|
value="${window.LEDEscape.html(schedule.end_time)}"
|
|
onchange="window.LEDMatrixWidgets.getHandlers('schedule-picker').onGlobalTimeChange('${fieldId}')"
|
|
class="form-control mt-1">
|
|
<p class="mt-1 text-sm text-gray-600">When to stop displaying content (HH:MM)</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Per-day schedule section
|
|
const perDayDisplay = schedule.mode === 'per_day' ? 'block' : 'none';
|
|
html += `
|
|
<div id="${fieldId}_perday_section" style="display: ${perDayDisplay};">
|
|
<h3 class="text-md font-medium text-gray-900 mb-4">Day-Specific Times</h3>
|
|
<div class="bg-gray-50 rounded-lg p-4">
|
|
<div class="overflow-x-auto">
|
|
<table class="min-w-full divide-y divide-gray-200">
|
|
<thead>
|
|
<tr class="bg-gray-100">
|
|
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-700 uppercase">Day</th>
|
|
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-700 uppercase">Enabled</th>
|
|
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-700 uppercase">Start</th>
|
|
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-700 uppercase">End</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
`;
|
|
|
|
// Render each day row
|
|
DAYS.forEach(day => {
|
|
const dayConfig = schedule.days[day];
|
|
const dayLabel = window.LEDEscape.html(DAY_LABEL_MAP.get(day) || day);
|
|
const disabled = !dayConfig.enabled;
|
|
const disabledClass = disabled ? 'bg-gray-100' : '';
|
|
|
|
html += `
|
|
<tr class="hover:bg-gray-50" id="${fieldId}_row_${day}">
|
|
<td class="px-3 py-2 whitespace-nowrap">
|
|
<span class="text-sm font-medium text-gray-900">${dayLabel}</span>
|
|
</td>
|
|
<td class="px-3 py-2 whitespace-nowrap">
|
|
<input type="checkbox"
|
|
id="${fieldId}_${day}_enabled"
|
|
aria-label="${dayLabel} enabled"
|
|
${dayConfig.enabled ? 'checked' : ''}
|
|
onchange="window.LEDMatrixWidgets.getHandlers('schedule-picker').onDayEnabledChange('${fieldId}', '${day}', this.checked)"
|
|
class="h-4 w-4 text-blue-600 focus:ring-blue-500 border-gray-300 rounded">
|
|
</td>
|
|
<td class="px-3 py-2 whitespace-nowrap">
|
|
<input type="time"
|
|
id="${fieldId}_${day}_start"
|
|
aria-label="${dayLabel} start time"
|
|
value="${window.LEDEscape.html(dayConfig.start_time)}"
|
|
${disabled ? 'disabled' : ''}
|
|
onchange="window.LEDMatrixWidgets.getHandlers('schedule-picker').onDayTimeChange('${fieldId}', '${day}')"
|
|
class="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 ${disabledClass}">
|
|
</td>
|
|
<td class="px-3 py-2 whitespace-nowrap">
|
|
<input type="time"
|
|
id="${fieldId}_${day}_end"
|
|
aria-label="${dayLabel} end time"
|
|
value="${window.LEDEscape.html(dayConfig.end_time)}"
|
|
${disabled ? 'disabled' : ''}
|
|
onchange="window.LEDMatrixWidgets.getHandlers('schedule-picker').onDayTimeChange('${fieldId}', '${day}')"
|
|
class="block w-full px-2 py-1 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-blue-500 focus:border-blue-500 ${disabledClass}">
|
|
</td>
|
|
</tr>
|
|
`;
|
|
});
|
|
|
|
html += `
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
html += '</div>';
|
|
|
|
container.innerHTML = html;
|
|
},
|
|
|
|
/**
|
|
* Render hidden inputs for form submission
|
|
* These match the existing API format
|
|
*/
|
|
_renderHiddenInputs: function(fieldId, schedule) {
|
|
let html = '';
|
|
|
|
// Enabled state (hidden input ensures value is always sent, even when checkbox is unchecked)
|
|
html += `<input type="hidden" id="${fieldId}_enabled_hidden" name="enabled" value="${schedule.enabled}">`;
|
|
|
|
// Mode indicator (for the widget to track internally)
|
|
html += `<input type="hidden" id="${fieldId}_mode_value" name="mode" value="${schedule.mode}">`;
|
|
|
|
// Global times (used when mode is global)
|
|
html += `<input type="hidden" id="${fieldId}_start_time_hidden" name="start_time" value="${window.LEDEscape.html(schedule.start_time)}">`;
|
|
html += `<input type="hidden" id="${fieldId}_end_time_hidden" name="end_time" value="${window.LEDEscape.html(schedule.end_time)}">`;
|
|
|
|
// Per-day values (used when mode is per_day)
|
|
DAYS.forEach(day => {
|
|
const dayConfig = schedule.days[day];
|
|
html += `<input type="hidden" id="${fieldId}_${day}_enabled_hidden" name="${day}_enabled" value="${dayConfig.enabled}">`;
|
|
html += `<input type="hidden" id="${fieldId}_${day}_start_hidden" name="${day}_start" value="${window.LEDEscape.html(dayConfig.start_time)}">`;
|
|
html += `<input type="hidden" id="${fieldId}_${day}_end_hidden" name="${day}_end" value="${window.LEDEscape.html(dayConfig.end_time)}">`;
|
|
});
|
|
|
|
return html;
|
|
},
|
|
|
|
/**
|
|
* Get current schedule value
|
|
* @param {string} fieldId - Field ID
|
|
* @returns {Object} Schedule configuration object
|
|
*/
|
|
getValue: function(fieldId) {
|
|
const safeId = sanitizeId(fieldId);
|
|
const widget = document.getElementById(`${safeId}_widget`);
|
|
if (!widget) return getDefaultSchedule();
|
|
|
|
const enabledCheckbox = document.getElementById(`${safeId}_enabled`);
|
|
const modeGlobal = document.getElementById(`${safeId}_mode_global`);
|
|
const globalStart = document.getElementById(`${safeId}_global_start`);
|
|
const globalEnd = document.getElementById(`${safeId}_global_end`);
|
|
|
|
const schedule = {
|
|
enabled: enabledCheckbox ? enabledCheckbox.checked : false,
|
|
mode: (modeGlobal && modeGlobal.checked) ? 'global' : 'per_day',
|
|
start_time: globalStart ? globalStart.value : '07:00',
|
|
end_time: globalEnd ? globalEnd.value : '23:00',
|
|
days: {}
|
|
};
|
|
|
|
DAYS.forEach(day => {
|
|
const dayEnabled = document.getElementById(`${safeId}_${day}_enabled`);
|
|
const dayStart = document.getElementById(`${safeId}_${day}_start`);
|
|
const dayEnd = document.getElementById(`${safeId}_${day}_end`);
|
|
|
|
schedule.days[day] = {
|
|
enabled: dayEnabled ? dayEnabled.checked : true,
|
|
start_time: dayStart ? dayStart.value : '07:00',
|
|
end_time: dayEnd ? dayEnd.value : '23:00'
|
|
};
|
|
});
|
|
|
|
return schedule;
|
|
},
|
|
|
|
/**
|
|
* Set schedule value
|
|
* @param {string} fieldId - Field ID
|
|
* @param {Object} value - Schedule configuration object
|
|
*/
|
|
setValue: function(fieldId, value) {
|
|
const safeId = sanitizeId(fieldId);
|
|
const schedule = normalizeSchedule(value);
|
|
|
|
// Set enabled
|
|
const enabledCheckbox = document.getElementById(`${safeId}_enabled`);
|
|
if (enabledCheckbox) enabledCheckbox.checked = schedule.enabled;
|
|
|
|
// Set mode
|
|
const modeGlobal = document.getElementById(`${safeId}_mode_global`);
|
|
const modePerDay = document.getElementById(`${safeId}_mode_per_day`);
|
|
if (modeGlobal) modeGlobal.checked = schedule.mode === 'global';
|
|
if (modePerDay) modePerDay.checked = schedule.mode === 'per_day';
|
|
|
|
// Set global times
|
|
const globalStart = document.getElementById(`${safeId}_global_start`);
|
|
const globalEnd = document.getElementById(`${safeId}_global_end`);
|
|
if (globalStart) globalStart.value = schedule.start_time;
|
|
if (globalEnd) globalEnd.value = schedule.end_time;
|
|
|
|
// Set per-day values
|
|
DAYS.forEach(day => {
|
|
const dayConfig = schedule.days[day];
|
|
const dayEnabled = document.getElementById(`${safeId}_${day}_enabled`);
|
|
const dayStart = document.getElementById(`${safeId}_${day}_start`);
|
|
const dayEnd = document.getElementById(`${safeId}_${day}_end`);
|
|
|
|
if (dayEnabled) dayEnabled.checked = dayConfig.enabled;
|
|
if (dayStart) {
|
|
dayStart.value = dayConfig.start_time;
|
|
dayStart.disabled = !dayConfig.enabled;
|
|
dayStart.classList.toggle('bg-gray-100', !dayConfig.enabled);
|
|
}
|
|
if (dayEnd) {
|
|
dayEnd.value = dayConfig.end_time;
|
|
dayEnd.disabled = !dayConfig.enabled;
|
|
dayEnd.classList.toggle('bg-gray-100', !dayConfig.enabled);
|
|
}
|
|
});
|
|
|
|
// Update visibility
|
|
this.handlers.onModeChange(fieldId, schedule.mode);
|
|
|
|
// Update hidden inputs
|
|
this._updateHiddenInputs(fieldId);
|
|
},
|
|
|
|
/**
|
|
* Update all hidden inputs to match current state
|
|
*/
|
|
_updateHiddenInputs: function(fieldId) {
|
|
const safeId = sanitizeId(fieldId);
|
|
const schedule = this.getValue(fieldId);
|
|
|
|
// Enabled
|
|
const enabledHidden = document.getElementById(`${safeId}_enabled_hidden`);
|
|
if (enabledHidden) enabledHidden.value = schedule.enabled;
|
|
|
|
// Mode
|
|
const modeHidden = document.getElementById(`${safeId}_mode_value`);
|
|
if (modeHidden) modeHidden.value = schedule.mode;
|
|
|
|
// Global times
|
|
const startHidden = document.getElementById(`${safeId}_start_time_hidden`);
|
|
const endHidden = document.getElementById(`${safeId}_end_time_hidden`);
|
|
if (startHidden) startHidden.value = schedule.start_time;
|
|
if (endHidden) endHidden.value = schedule.end_time;
|
|
|
|
// Per-day values
|
|
DAYS.forEach(day => {
|
|
const dayConfig = schedule.days[day];
|
|
const enabledHidden = document.getElementById(`${safeId}_${day}_enabled_hidden`);
|
|
const startHidden = document.getElementById(`${safeId}_${day}_start_hidden`);
|
|
const endHidden = document.getElementById(`${safeId}_${day}_end_hidden`);
|
|
|
|
if (enabledHidden) enabledHidden.value = dayConfig.enabled;
|
|
if (startHidden) startHidden.value = dayConfig.start_time;
|
|
if (endHidden) endHidden.value = dayConfig.end_time;
|
|
});
|
|
},
|
|
|
|
handlers: {
|
|
/**
|
|
* Handle enabled toggle change
|
|
*/
|
|
onEnabledChange: function(fieldId, enabled) {
|
|
const widget = window.LEDMatrixWidgets.get('schedule-picker');
|
|
widget._updateHiddenInputs(fieldId);
|
|
triggerChange(fieldId, widget.getValue(fieldId));
|
|
},
|
|
|
|
/**
|
|
* Handle mode switch
|
|
*/
|
|
onModeChange: function(fieldId, mode) {
|
|
const safeId = sanitizeId(fieldId);
|
|
const globalSection = document.getElementById(`${safeId}_global_section`);
|
|
const perDaySection = document.getElementById(`${safeId}_perday_section`);
|
|
|
|
if (globalSection) globalSection.style.display = mode === 'global' ? 'block' : 'none';
|
|
if (perDaySection) perDaySection.style.display = mode === 'per_day' ? 'block' : 'none';
|
|
|
|
const widget = window.LEDMatrixWidgets.get('schedule-picker');
|
|
widget._updateHiddenInputs(fieldId);
|
|
triggerChange(fieldId, widget.getValue(fieldId));
|
|
},
|
|
|
|
/**
|
|
* Handle global time change
|
|
*/
|
|
onGlobalTimeChange: function(fieldId) {
|
|
const widget = window.LEDMatrixWidgets.get('schedule-picker');
|
|
widget._updateHiddenInputs(fieldId);
|
|
triggerChange(fieldId, widget.getValue(fieldId));
|
|
},
|
|
|
|
/**
|
|
* Handle day enabled change
|
|
*/
|
|
onDayEnabledChange: function(fieldId, day, enabled) {
|
|
const safeId = sanitizeId(fieldId);
|
|
const dayStart = document.getElementById(`${safeId}_${day}_start`);
|
|
const dayEnd = document.getElementById(`${safeId}_${day}_end`);
|
|
|
|
if (dayStart) {
|
|
dayStart.disabled = !enabled;
|
|
dayStart.classList.toggle('bg-gray-100', !enabled);
|
|
// Set default value only when enabling and input is empty
|
|
if (enabled && !dayStart.value) {
|
|
dayStart.value = '07:00';
|
|
}
|
|
}
|
|
|
|
if (dayEnd) {
|
|
dayEnd.disabled = !enabled;
|
|
dayEnd.classList.toggle('bg-gray-100', !enabled);
|
|
// Set default value only when enabling and input is empty
|
|
if (enabled && !dayEnd.value) {
|
|
dayEnd.value = '23:00';
|
|
}
|
|
}
|
|
|
|
const widget = window.LEDMatrixWidgets.get('schedule-picker');
|
|
widget._updateHiddenInputs(fieldId);
|
|
triggerChange(fieldId, widget.getValue(fieldId));
|
|
},
|
|
|
|
/**
|
|
* Handle day time change
|
|
*/
|
|
onDayTimeChange: function(fieldId, day) {
|
|
const widget = window.LEDMatrixWidgets.get('schedule-picker');
|
|
widget._updateHiddenInputs(fieldId);
|
|
triggerChange(fieldId, widget.getValue(fieldId));
|
|
}
|
|
}
|
|
});
|
|
|
|
// Expose constants for external use
|
|
window.LEDMatrixWidgets.get('schedule-picker').DAYS = DAYS;
|
|
window.LEDMatrixWidgets.get('schedule-picker').DAY_LABELS = DAY_LABELS;
|
|
window.LEDMatrixWidgets.get('schedule-picker').getDefaultSchedule = getDefaultSchedule;
|
|
window.LEDMatrixWidgets.get('schedule-picker').normalizeSchedule = normalizeSchedule;
|
|
|
|
})();
|