From 6ceb2aa32f86ef79311117f2bd7225a0bb9364f2 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Wed, 2 Sep 2026 23:42:21 -0400 Subject: [PATCH] docs(calendar): replace the ASCII mock-ups with real renders The README described the display with three hand-drawn text sketches: 03/15 2:30pm Team Meeting They are now real plugin output at the true panel size -- a timed event, an all-day event, a long title being ellipsized, and the empty state -- plus a panel-size sheet. Also documents google_auth, the one setting the reference did not mention. It is not a value you type: it is the web UI's "Connect Your Google Account" button (x-widget: google-oauth). The two combined customization rows are split so each key appears on its own, which takes the plugin to 13 of 13 settings documented by key. Rendering these needed the shots "attrs" seam from #386, which is not on main yet; the identical change to sitecustomize.py and render_docs_assets.py is included here so this branch can render on its own. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- docs/assets/calendar/event-kinds.png | Bin 0 -> 18589 bytes docs/assets/calendar/hero.png | Bin 0 -> 1853 bytes docs/assets/calendar/panel-sizes.png | Bin 0 -> 14454 bytes docs/assets/calendar/shots.json | 54 ++++++++++++++++ plugins.json | 4 +- plugins/calendar/README.md | 48 +++++++------- plugins/calendar/manifest.json | 10 ++- scripts/docs_render_support/sitecustomize.py | 62 +++++++++++++++++++ scripts/render_docs_assets.py | 6 +- 10 files changed, 157 insertions(+), 29 deletions(-) create mode 100644 docs/assets/calendar/event-kinds.png create mode 100644 docs/assets/calendar/hero.png create mode 100644 docs/assets/calendar/panel-sizes.png create mode 100644 docs/assets/calendar/shots.json diff --git a/README.md b/README.md index 9fae8532..3b8f138c 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \ |--------|-------------|---------| | [Simple Clock](./plugins/clock-simple/) | Time and date display | clock-simple on an LED panel | | [7-Segment Clock](./plugins/7-segment-clock/) | Retro-style 7-segment clock with customizable colors | 7-segment-clock on an LED panel | -| [Google Calendar](./plugins/calendar/) | Upcoming events from Google Calendar | | +| [Google Calendar](./plugins/calendar/) | Upcoming events from Google Calendar | calendar on an LED panel | | [Geochron World Clock](./plugins/geochron/) | World map with the real-time day/night terminator | | ### Weather (2) diff --git a/docs/assets/calendar/event-kinds.png b/docs/assets/calendar/event-kinds.png new file mode 100644 index 0000000000000000000000000000000000000000..4d7ee9bb67550f91a45306b51c824d68882cbbd3 GIT binary patch literal 18589 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 literal 0 HcmV?d00001 diff --git a/docs/assets/calendar/hero.png b/docs/assets/calendar/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..4f4fe3a3af89b2d4cb1a7efa9556cf1cd0fadfc1 GIT binary patch literal 1853 zcmc&!YgE!_82{&LYOS=U5w6mc%@WpSkxot0)J(LH6mQi(E26w#$RtU?N^~yMnUHBJ zQCgIh1x5!!&9(KC;nIfWiE5Rv>Myw5rBd!Fa~E>9W;y=(D8 zvxNWvEJk^_`2fIIx-0P5Ku>o%_GXU(fc|`x+Yaoh_TjGZpRxEA`eQ_5jo%Z|{l;Xv z;)u^t69Zb;{mPwmQ~y9rXmT4Q?P{yV@YlJQmoD1@UFvq!uwCkE9Js=awah<^76m(4 z?%Ow*As|!hQ;BBvirNtk#oYYZYkL{;cJ=b%kYO) zHV9eiA_hpN_E)tp?2bgXc(Ny`vBB{LL5sE^y^kZi`Q3f3svsis#)|OpqUP=uMzj;!1qjh*NsAH520g zCre-D#f>}-#d}@qn|&OdQ0#v>I38sk5EHW=y>-ZmvIWaiIOc(}z#dRJk>giNLhiS| zO`dXQA0SB7i}O#93ztL)w_$6T7pojvJT(^_?oVkIf()bIR|z7jr!iC7t`6>yVnhue z(Ik??)_Nxaz+$23t;WPc(`gCOH`~?PYD&l3@Q#q|>w2*9?CWGDHKR(^DP_-WB!O zvtosNY(X1@X$lJ_tG(FRa)FDqRW?ZzZOD9Tcm%#WP2GvG(yl4kE-j;9*rO!b_Fj0e34;1x&ZLuv59 z2FFgEnJHy)BAQICCGY(j(z$PsmXS;{`F2W0r}fPg(KIA#48QZ;{7+PWWZpuqcil4I zZai9~Typ`6*odunqM{GGw-6U6l47OTX4H>NU)$JXt@h+)5wf4ByzgwV>&?gTWrcP( z<=Nha^{Wr#1nrk&kqkvRZ#ZMd7jXmqJl<;$b25s3XSl)&TS&p3R*9=R)VtW4{R2BX z%7!s2VSjC2^J5V++G!RS^pB!{!p1*E&1;1}2FlKPpjAAbCOZUV|k32;h&7Fz+MWyQ= zk}mm%XFe<9HP>`y{gyCGZyD{UL@_NRA_H(pxLS*v!<<{sQ!Tv<8m~0AGg40&4dptu z2(=kmtvO9mV6x@-!N|yfy@HeW_J!y_Dd;)AujM}Wi0qJvwM=x_73e`_T;Ei<-dsGP zAc22KvfW3U5xY$YPU2iL&5HW{ztXJ}G^uD&`qr}!!ol3?X5q$jJ&vp0yJy5N)V*4H z?NIS)c!J)io%vCTMuXXmCBTb26?1I2FIDG%d$&1nMnB)rgN8M})%jrv)K0Y9J(ojg F{{n*}<8%N3 literal 0 HcmV?d00001 diff --git a/docs/assets/calendar/panel-sizes.png b/docs/assets/calendar/panel-sizes.png new file mode 100644 index 0000000000000000000000000000000000000000..153a5f1f4d6f2539672d690e35cb38725c3bf680 GIT binary patch literal 14454 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 OL22o0X8im7wf_Y@b-d{S literal 0 HcmV?d00001 diff --git a/docs/assets/calendar/shots.json b/docs/assets/calendar/shots.json new file mode 100644 index 00000000..6f64fb8d --- /dev/null +++ b/docs/assets/calendar/shots.json @@ -0,0 +1,54 @@ +{ + "plugin": "calendar", + "defaults": { + "width": 128, + "height": 32, + "scale": 6, + "freeze_time": "2026-09-02T14:05:00+00:00", + "skip_update": true, + "config": { + "enabled": true + }, + "attrs": { + "events": [ + {"summary": "Team Standup", + "start": {"dateTime": "2026-09-03T09:30:00+00:00"}} + ] + } + }, + "shots": [ + { "name": "hero" }, + { "name": "timed", "standalone": false }, + { "name": "all-day", "standalone": false, + "attrs": { "events": [{"summary": "Ada's Birthday", "start": {"date": "2026-09-05"}}] } }, + { "name": "long-title", "standalone": false, + "attrs": { "events": [{"summary": "Quarterly Planning Review With Engineering", "start": {"dateTime": "2026-09-04T13:00:00+00:00"}}] } }, + { "name": "no-events", "standalone": false, "attrs": { "events": [] } }, + { "name": "size-64x32", "width": 64, "standalone": false }, + { "name": "size-128x32", "width": 128, "standalone": false }, + { "name": "size-128x64", "width": 128, "height": 64, "standalone": false }, + { "name": "size-256x32", "width": 256, "standalone": false } + ], + "composites": [ + { + "name": "event-kinds", + "columns": 1, + "cells": [ + {"shot": "timed", "label": "Timed event", "sublabel": "date, then start time"}, + {"shot": "all-day", "label": "All-day event", "sublabel": "date, then \"All Day\""}, + {"shot": "long-title", "label": "Long title", "sublabel": "wraps, then ellipsizes"}, + {"shot": "no-events", "label": "Nothing upcoming"} + ] + }, + { + "name": "panel-sizes", + "columns": 2, + "cells": [ + {"shot": "size-64x32", "label": "64 x 32"}, + {"shot": "size-128x32", "label": "128 x 32"}, + {"shot": "size-128x64", "label": "128 x 64"}, + {"shot": "size-256x32", "label": "256 x 32"} + ] + } + ] +} diff --git a/plugins.json b/plugins.json index f900d57c..fad62363 100644 --- a/plugins.json +++ b/plugins.json @@ -121,10 +121,10 @@ "plugin_path": "plugins/calendar", "stars": 0, "downloads": 0, - "last_updated": "2026-08-13", + "last_updated": "2026-09-02", "verified": true, "screenshot": "", - "latest_version": "1.2.3" + "latest_version": "1.2.4" }, { "id": "christmas-countdown", diff --git a/plugins/calendar/README.md b/plugins/calendar/README.md index 6ceb255e..17a80f77 100644 --- a/plugins/calendar/README.md +++ b/plugins/calendar/README.md @@ -15,6 +15,12 @@ Display upcoming events from Google Calendar with automatic updates, event rotation, and timezone support. +![An upcoming event on a 128x32 panel: the date and start time on one line, the +event title below it](../../docs/assets/calendar/hero.png) + +*Every image in this README is real plugin output, rendered at the true panel +size and scaled up so the pixels stay pixels.* + ## Features - **Google Calendar Integration**: OAuth2 authentication @@ -104,6 +110,7 @@ widget and first-time authentication) and rarely need editing by hand. |-----|---------|-------| | `enabled` | `false` | Enable or disable the plugin | | `credentials_file` | `"credentials.json"` | Google OAuth credentials file (uploaded via the config UI) | +| `google_auth` | `""` | Not a setting you type into. It is the **Step 2: Connect Your Google Account** button in the web UI (`x-widget: google-oauth`), which runs the consent flow described above. Google redirects to a page that fails to load — that is expected; copy the address back into the field to finish. | | `token_file` | `"token.pickle"` | Where the OAuth token is stored after first-time auth (auto-created) | | `calendars` | `["primary"]` | Calendar IDs to display — use the calendar picker after authenticating, or `"primary"` for your default | | `max_events` | `3` | Maximum upcoming events to show (1–10) | @@ -111,34 +118,29 @@ widget and first-time authentication) and rarely need editing by hand. | `event_rotation_interval` | `10` | Seconds between rotating displayed events (min 5) | | `display_duration` | `30` | Total seconds on screen before moving to the next plugin (min 5) | | `update_interval` | `3600` | Seconds between refreshes from Google (60–86400) | -| `customization.datetime_text.font` / `.font_size` | `PressStart2P-Regular.ttf` / `8` | Font family + size (4–16px) for the event date/time line | -| `customization.title_text.font` / `.font_size` | `PressStart2P-Regular.ttf` / `8` | Font family + size (4–16px) for the event title | +| `customization.datetime_text.font` | `PressStart2P-Regular.ttf` | Font for the date/time line | +| `customization.datetime_text.font_size` | `8` | Size of the date/time line in pixels (4–16) | +| `customization.title_text.font` | `PressStart2P-Regular.ttf` | Font for the event title | +| `customization.title_text.font_size` | `8` | Size of the event title in pixels (4–16) | ## Display Format -### Timed Event -``` - 03/15 2:30pm - - Team Meeting - -``` +The date sits on the top line with the start time beside it, and the event title +fills the space below, wrapped across as many lines as fit. -### All-Day Event -``` - 03/20 All Day - - Birthday Party - -``` +![Timed, all-day, long-title and empty screens](../../docs/assets/calendar/event-kinds.png) -### Long Title (Wrapped) -``` - 03/22 10:00am - - Project Review - and Planning -``` +- A **timed** event shows its start time next to the date. +- An **all-day** event shows `All Day` in place of the time. Set + `show_all_day_events` to `false` to leave these out. +- A **long title** wraps, and the last visible line is ellipsized rather than + simply stopping, so a title that did not fit is recognisable as truncated. +- When nothing upcoming is left, the panel shows `No Events`. + +The layout is the same on every panel; the taller the panel, the more lines of +title fit before ellipsis. + +![The same event on four panel sizes](../../docs/assets/calendar/panel-sizes.png) ## Multiple Calendars diff --git a/plugins/calendar/manifest.json b/plugins/calendar/manifest.json index 30ab02a5..986893d8 100644 --- a/plugins/calendar/manifest.json +++ b/plugins/calendar/manifest.json @@ -1,7 +1,7 @@ { "id": "calendar", "name": "Google Calendar", - "version": "1.2.3", + "version": "1.2.4", "author": "ChuckBuilds", "description": "Display upcoming events from Google Calendar with date, time, and event details. Shows next 1-3 events with automatic rotation and timezone support.", "category": "productivity", @@ -35,6 +35,12 @@ } ], "versions": [ + { + "version": "1.2.4", + "released": "2026-09-02", + "notes": "Documentation. The README's three ASCII mock-ups of the display are replaced with real rendered screenshots of the same four screens (timed, all-day, a long title being ellipsized, and the empty state) plus a panel-size sheet. Documents google_auth, which is the web UI's Connect Your Google Account button rather than a value you type, and splits the two combined font rows so each customization key appears on its own. All 13 schema settings are now documented by key.", + "ledmatrix_min_version": "3.3.0" + }, { "version": "1.2.3", "released": "2026-08-31", @@ -106,7 +112,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-08-13", + "last_updated": "2026-09-02", "stars": 0, "downloads": 0, "verified": true, diff --git a/scripts/docs_render_support/sitecustomize.py b/scripts/docs_render_support/sitecustomize.py index 20844d5e..90fe76c1 100644 --- a/scripts/docs_render_support/sitecustomize.py +++ b/scripts/docs_render_support/sitecustomize.py @@ -52,3 +52,65 @@ def today(cls): _docs_http_replay.install() except Exception: # never let doc tooling break the render it is measuring pass + + +# Some plugins hold their interesting state in memory, put there by an event +# the renderer cannot produce -- an MQTT message, a webhook, a button press. +# No configuration reaches that state, so a documentation render would only +# ever show the idle frame. LEDMATRIX_DOCS_ATTRS names attributes to set on the +# plugin instance once the core's loader has built it, which is the same thing +# the event would have done, without the plugin knowing it is being rendered. +_ATTRS = os.environ.get("LEDMATRIX_DOCS_ATTRS") +if _ATTRS: + import json as _json + import sys as _sys + + _WANTED = _json.loads(_ATTRS) + _TARGET = "src.plugin_system.plugin_loader" + + def _apply(instance): + for name, value in _WANTED.items(): + if isinstance(value, list) and len(value) == 3 and all( + isinstance(v, int) for v in value): + value = tuple(value) # colours are tuples everywhere in the core + setattr(instance, name, value) + return instance + + class _PatchingLoader: + """Wraps the real loader so the module is patched right after it runs.""" + + def __init__(self, inner): + self._inner = inner + + def create_module(self, spec): + return self._inner.create_module(spec) + + def exec_module(self, module): + self._inner.exec_module(module) + real = module.PluginLoader.load_plugin + + def load_plugin(self, *args, **kwargs): + instance, mod = real(self, *args, **kwargs) + return _apply(instance), mod + + module.PluginLoader.load_plugin = load_plugin + + def __getattr__(self, name): + return getattr(self._inner, name) + + class _AttrFinder: + """Hands back the real spec with its loader wrapped, once.""" + + def find_spec(self, name, path=None, target=None): + if name != _TARGET: + return None + index = _sys.meta_path.index(self) + for finder in _sys.meta_path[index + 1:]: + find = getattr(finder, "find_spec", None) + spec = find(name, path, target) if find else None + if spec is not None and spec.loader is not None: + spec.loader = _PatchingLoader(spec.loader) + return spec + return None + + _sys.meta_path.insert(0, _AttrFinder()) diff --git a/scripts/render_docs_assets.py b/scripts/render_docs_assets.py index 42a1d93d..36c7bd4b 100644 --- a/scripts/render_docs_assets.py +++ b/scripts/render_docs_assets.py @@ -263,12 +263,16 @@ def render_shot( # clock, a countdown, or a "starts in 2h" line differs on every run. freeze_time = shot.get("freeze_time", defaults.get("freeze_time")) http_replay = shot.get("http_replay", defaults.get("http_replay")) - if freeze_time or http_replay: + # Runtime state that an event would normally have set; see the shim. + attrs = shot.get("attrs", defaults.get("attrs")) + if freeze_time or http_replay or attrs: support_dir = str(Path(__file__).resolve().parent / "docs_render_support") existing = env.get("PYTHONPATH") env["PYTHONPATH"] = f"{support_dir}{os.pathsep}{existing}" if existing else support_dir if freeze_time: env["LEDMATRIX_DOCS_FREEZE_TIME"] = str(freeze_time) + if attrs: + env["LEDMATRIX_DOCS_ATTRS"] = json.dumps(attrs) if http_replay: replay_path = (shot_list_dir / http_replay).resolve() if not replay_path.is_file():