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:
@@ -6468,6 +6468,50 @@ def upload_calendar_credentials():
|
||||
print(error_details)
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
@api_v3.route('/plugins/calendar/list-calendars', methods=['GET'])
|
||||
def list_calendar_calendars():
|
||||
"""Return Google Calendars accessible with the currently authenticated credentials."""
|
||||
if not api_v3.plugin_manager:
|
||||
return jsonify({'status': 'error', 'message': 'Plugin manager not available'}), 500
|
||||
plugin = api_v3.plugin_manager.get_plugin('calendar')
|
||||
if not plugin:
|
||||
return jsonify({'status': 'error', 'message': 'Calendar plugin is not running. Enable it and save config first.'}), 404
|
||||
if not hasattr(plugin, 'get_calendars'):
|
||||
return jsonify({'status': 'error', 'message': 'Installed plugin version does not support calendar listing — update the plugin.'}), 400
|
||||
try:
|
||||
raw = plugin.get_calendars()
|
||||
import collections.abc
|
||||
if not isinstance(raw, (list, tuple)):
|
||||
logger.error('list_calendar_calendars: get_calendars() returned non-sequence type %r', type(raw))
|
||||
return jsonify({'status': 'error', 'message': 'Unable to load calendars from the plugin. Please check plugin configuration and try again.'}), 500
|
||||
calendars = []
|
||||
for cal in raw:
|
||||
if not isinstance(cal, collections.abc.Mapping):
|
||||
logger.warning('list_calendar_calendars: skipping malformed calendar entry (type=%r): %r', type(cal), cal)
|
||||
continue
|
||||
cal_id = cal.get('id') or cal.get('calendarId', '')
|
||||
if not isinstance(cal_id, str):
|
||||
cal_id = str(cal_id) if cal_id else ''
|
||||
if not cal_id:
|
||||
logger.warning('list_calendar_calendars: skipping calendar entry with empty id: %r', cal)
|
||||
continue
|
||||
summary = cal.get('summary', '')
|
||||
if not isinstance(summary, str):
|
||||
summary = str(summary) if summary else ''
|
||||
calendars.append({
|
||||
'id': cal_id,
|
||||
'summary': summary,
|
||||
'primary': bool(cal.get('primary', False)),
|
||||
})
|
||||
return jsonify({'status': 'success', 'calendars': calendars})
|
||||
except (ValueError, TypeError, KeyError):
|
||||
logger.exception('list_calendar_calendars: error normalising calendar data for plugin=calendar')
|
||||
return jsonify({'status': 'error', 'message': 'Unable to load calendars from the plugin. Please check plugin configuration and try again.'}), 500
|
||||
except Exception:
|
||||
logger.exception('list_calendar_calendars: unexpected error for plugin=calendar')
|
||||
return jsonify({'status': 'error', 'message': 'Unable to load calendars from the plugin. Please check plugin configuration and try again.'}), 500
|
||||
|
||||
|
||||
@api_v3.route('/plugins/assets/delete', methods=['POST'])
|
||||
def delete_plugin_asset():
|
||||
"""Delete an asset file for a plugin"""
|
||||
|
||||
Reference in New Issue
Block a user