mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
feat(web): add Google Calendar picker widget for dynamic multi-calendar selection (#274)
* fix(install): add --prefer-binary to pip installs to avoid /tmp exhaustion timezonefinder (~54 MB) includes large timezone polygon data files that pip unpacks into /tmp during installation. On Raspberry Pi, the default tmpfs /tmp size (often ~half of RAM) can be too small, causing the install to fail with an out-of-space error. Adding --prefer-binary tells pip to prefer pre-built binary wheels over source distributions. Since timezonefinder and most other packages publish wheels on PyPI (and piwheels.org has ARM wheels), this avoids the large temporary /tmp extraction and speeds up installs generally. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(timezone): use America/New_York instead of EST for ESPN API date queries EST is a fixed UTC-5 offset that does not observe daylight saving time, causing the ESPN API date to be off by one hour during EDT (March–November). America/New_York correctly handles DST transitions. The ESPN scoreboard API anchors its schedule calendar to Eastern US time, so this Eastern timezone is intentionally kept for the API date — it is not user-configurable. Game time display is converted separately to the user's configured timezone. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(web): add Google Calendar picker widget for dynamic calendar selection Adds a new google-calendar-picker widget and API endpoint that lets users load their available Google Calendars by name and check the ones they want, instead of manually typing calendar IDs. - GET /api/v3/plugins/calendar/list-calendars — calls plugin.get_calendars() and returns all accessible calendars with id, summary, and primary flag - google-calendar-picker.js — new widget: "Load My Calendars" button renders a checklist; selections update a hidden comma-separated input for form submit - plugin_config.html — handles x-widget: google-calendar-picker in array branch - base.html — loads the new widget script Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(web): address PR review findings in google-calendar-picker - api_v3.py: replace broad except block with specific exception handling, log full traceback via module logger, normalize/validate get_calendars() output to stable {id,summary,primary} objects, return opaque user-friendly error message instead of leaking str(e) - google-calendar-picker.js: fix button label only updating to "Refresh Calendars" on success (restore original label on error); update summary paragraph via syncHiddenAndSummary() on every checkbox change so UI stays in sync with hidden input; pass summary element through loadCalendars and renderCheckboxes instead of re-querying DOM - plugin_config.html: bound initWidget retry loop with MAX_RETRIES=40 to prevent infinite timers; normalize legacy comma-separated string values to arrays before passing to widget.render so pre-existing config populates correctly - install_dependencies_apt.py: update install_via_pip docstring to document both --break-system-packages and --prefer-binary flags Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(web): harden list_calendar_calendars input validation - Remove unused `as e` binding from ValueError/TypeError/KeyError except clause - Replace hasattr(__iter__) with isinstance(list|tuple) so non-sequence returns are rejected before iteration - Validate each calendar entry is a collections.abc.Mapping; skip and warn on malformed items rather than propagating a TypeError - Coerce id/summary to str safely if not already strings Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(web): skip calendar entries with empty id in list_calendar_calendars After coercing cal_id to str, check it is non-empty before appending to the calendars list so entries with no usable id are never forwarded to the client. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Chuck <chuck@example.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4992,6 +4992,7 @@
|
||||
<script src="{{ url_for('static', filename='v3/js/widgets/checkbox-group.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='v3/js/widgets/custom-feeds.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='v3/js/widgets/array-table.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='v3/js/widgets/google-calendar-picker.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='v3/js/widgets/day-selector.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='v3/js/widgets/time-range.js') }}" defer></script>
|
||||
<script src="{{ url_for('static', filename='v3/js/widgets/schedule-picker.js') }}" defer></script>
|
||||
|
||||
@@ -304,6 +304,43 @@
|
||||
{# Sentinel hidden input with bracket notation to allow clearing array to [] when all unchecked #}
|
||||
{# This ensures the field is always submitted, even when all checkboxes are unchecked #}
|
||||
<input type="hidden" name="{{ full_key }}[]" value="">
|
||||
{% elif x_widget == 'google-calendar-picker' %}
|
||||
{# Google Calendar picker — dynamically loads calendars from the API #}
|
||||
{# Normalise: if value is a string (legacy comma-separated), split it; otherwise fall back to default or [] #}
|
||||
{% if value is not none and value is string and value %}
|
||||
{% set array_value = value.split(',') | map('trim') | list %}
|
||||
{% elif value is not none and value is iterable and value is not string %}
|
||||
{% set array_value = value %}
|
||||
{% elif prop.default is defined and prop.default is string and prop.default %}
|
||||
{% set array_value = prop.default.split(',') | map('trim') | list %}
|
||||
{% elif prop.default is defined and prop.default is iterable and prop.default is not string %}
|
||||
{% set array_value = prop.default %}
|
||||
{% else %}
|
||||
{% set array_value = [] %}
|
||||
{% endif %}
|
||||
<div id="{{ field_id }}_container" class="google-calendar-picker-container mt-1"></div>
|
||||
<script>
|
||||
(function() {
|
||||
var MAX_RETRIES = 40;
|
||||
var retries = 0;
|
||||
function initWidget() {
|
||||
if (retries >= MAX_RETRIES) { return; }
|
||||
retries++;
|
||||
if (!window.LEDMatrixWidgets) { setTimeout(initWidget, 50); return; }
|
||||
var widget = window.LEDMatrixWidgets.get('google-calendar-picker');
|
||||
if (!widget) { setTimeout(initWidget, 50); return; }
|
||||
var container = document.getElementById('{{ field_id }}_container');
|
||||
if (!container) return;
|
||||
var value = {{ array_value|tojson|safe }};
|
||||
widget.render(container, {}, value, { fieldId: '{{ field_id }}', pluginId: '{{ plugin_id }}', name: '{{ full_key }}' });
|
||||
}
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initWidget);
|
||||
} else {
|
||||
setTimeout(initWidget, 50);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
{% elif x_widget == 'day-selector' %}
|
||||
{# Day selector widget for selecting days of the week #}
|
||||
{% set array_value = value if value is not none and value is iterable and value is not string else (prop.default if prop.default is defined and prop.default is iterable and prop.default is not string else []) %}
|
||||
|
||||
Reference in New Issue
Block a user