/**
* Google Calendar Picker Widget
*
* Renders a dynamic multi-select checklist of Google Calendars fetched from
* /api/v3/plugins/calendar/list-calendars. Selected IDs are stored in a hidden
* comma-separated input so the existing backend parser works unchanged.
*
* @module GoogleCalendarPickerWidget
*/
(function () {
'use strict';
if (typeof window.LEDMatrixWidgets === 'undefined') {
console.error('[GoogleCalendarPickerWidget] LEDMatrixWidgets registry not found. Load registry.js first.');
return;
}
window.LEDMatrixWidgets.register('google-calendar-picker', {
name: 'Google Calendar Picker Widget',
version: '1.0.0',
/**
* Render the widget into container.
* @param {HTMLElement} container
* @param {Object} config - schema config (unused)
* @param {Array} value - current array of selected calendar IDs
* @param {Object} options - { fieldId, pluginId, name }
*/
render: function (container, config, value, options) {
const fieldId = options.fieldId;
const name = options.name;
const currentIds = Array.isArray(value) ? value : ['primary'];
// Hidden input — this is what the form submits
const hiddenInput = document.createElement('input');
hiddenInput.type = 'hidden';
hiddenInput.id = fieldId + '_hidden';
hiddenInput.name = name;
hiddenInput.value = currentIds.join(', ');
// Current selection summary — kept in sync whenever the hidden value changes
const summary = document.createElement('p');
summary.id = fieldId + '_summary';
summary.className = 'text-xs text-gray-400 mt-1';
summary.textContent = 'Currently selected: ' + currentIds.join(', ');
// "Load My Calendars" button
const btn = document.createElement('button');
btn.type = 'button';
btn.id = fieldId + '_load_btn';
btn.className = 'px-3 py-1.5 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded-md flex items-center gap-1.5';
btn.innerHTML = ' Load My Calendars';
btn.addEventListener('click', function () {
loadCalendars(fieldId, hiddenInput, listContainer, btn, summary);
});
// Status / list area
const listContainer = document.createElement('div');
listContainer.id = fieldId + '_list';
listContainer.className = 'mt-2';
container.appendChild(btn);
container.appendChild(summary);
container.appendChild(listContainer);
container.appendChild(hiddenInput);
},
getValue: function (fieldId) {
const hidden = document.getElementById(fieldId + '_hidden');
if (!hidden || !hidden.value.trim()) return [];
return hidden.value.split(',').map(s => s.trim()).filter(Boolean);
},
setValue: function (fieldId, values) {
const hidden = document.getElementById(fieldId + '_hidden');
if (hidden) {
hidden.value = (Array.isArray(values) ? values : []).join(', ');
}
},
handlers: {}
});
/**
* Fetch calendar list from backend and render checkboxes.
*/
function loadCalendars(fieldId, hiddenInput, listContainer, btn, summary) {
btn.disabled = true;
btn.innerHTML = ' Loading...';
listContainer.innerHTML = '';
fetch('/api/v3/plugins/calendar/list-calendars')
.then(function (r) { return r.json(); })
.then(function (data) {
btn.disabled = false;
if (data.status !== 'success') {
btn.innerHTML = ' Load My Calendars';
showError(listContainer, data.message || 'Failed to load calendars.');
return;
}
btn.innerHTML = ' Refresh Calendars';
renderCheckboxes(fieldId, data.calendars, hiddenInput, listContainer, summary);
})
.catch(function (err) {
btn.disabled = false;
btn.innerHTML = ' Load My Calendars';
showError(listContainer, 'Request failed: ' + err.message);
});
}
/**
* Render a checklist of calendars, pre-checking those already in the hidden input.
*/
function renderCheckboxes(fieldId, calendars, hiddenInput, listContainer, summary) {
listContainer.innerHTML = '';
if (!calendars || calendars.length === 0) {
showError(listContainer, 'No calendars found on this account.');
return;
}
const wrapper = document.createElement('div');
wrapper.className = 'mt-2 space-y-1.5 border border-gray-700 rounded-md p-3 bg-gray-800';
// Track selected IDs — seed from the hidden input so manually-typed IDs are preserved
let selectedIds = hiddenInput.value.split(',').map(function (s) { return s.trim(); }).filter(Boolean);
function syncHiddenAndSummary() {
hiddenInput.value = selectedIds.join(', ');
summary.textContent = 'Currently selected: ' + (selectedIds.length ? selectedIds.join(', ') : '(none)');
}
calendars.forEach(function (cal) {
const isChecked = selectedIds.includes(cal.id);
const label = document.createElement('label');
label.className = 'flex items-center gap-2 cursor-pointer';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'h-4 w-4 text-blue-600 border-gray-300 rounded';
checkbox.value = cal.id;
checkbox.checked = isChecked;
checkbox.addEventListener('change', function () {
if (checkbox.checked) {
if (!selectedIds.includes(cal.id)) selectedIds.push(cal.id);
} else {
selectedIds = selectedIds.filter(function (id) { return id !== cal.id; });
}
// Ensure at least one calendar is selected
if (selectedIds.length === 0) {
checkbox.checked = true;
selectedIds.push(cal.id);
if (window.showNotification) {
window.showNotification('At least one calendar must be selected.', 'warning');
}
}
syncHiddenAndSummary();
});
const nameSpan = document.createElement('span');
nameSpan.className = 'text-sm text-gray-200 flex-1';
nameSpan.textContent = cal.summary + (cal.primary ? ' (primary)' : '');
const idSpan = document.createElement('span');
idSpan.className = 'text-xs text-gray-500 font-mono truncate max-w-xs';
idSpan.textContent = cal.id;
idSpan.title = cal.id;
label.appendChild(checkbox);
label.appendChild(nameSpan);
label.appendChild(idSpan);
wrapper.appendChild(label);
});
listContainer.appendChild(wrapper);
}
function showError(container, message) {
container.innerHTML = '';
const p = document.createElement('p');
p.className = 'text-xs text-red-400 mt-1 flex items-center gap-1';
p.innerHTML = ' ' + escapeHtml(message);
container.appendChild(p);
}
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
console.log('[GoogleCalendarPickerWidget] registered');
})();