New widget type with four display modes -- current conditions, an hourly forecast strip, a multi-day forecast, and several cities' current day side by side -- backed by a pluggable provider registry (app/weather/, mirroring the app/widgets/ dispatch pattern): Open-Meteo (worldwide) and NWS (US-only) both wired up now, Environment Canada documented as the next one to add given its more involved station/grid-lookup API. The calendar widget's existing embedded weather strip is untouched and still Open-Meteo-only; this lifts the same underlying icon-drawing primitives (now shared via app/weather_render.py, calendar_render.py still imports draw_weather_row unchanged) into a widget that can be placed and sized on its own. Icons are redrawn in the panel's actual ink colors (yellow sun/bolt, blue rain/snow) instead of flat black, and build_multi_city's icon/font sizing now scales with how many cities need to fit rather than the box's height alone -- both fixed after catching them via live browser verification, along with a mode-switch cache-shape crash and a mobile-width dialog overflow. New WeatherWidgetConfig table (migration 24), grid footprint, widget module, common.py fetch/cache helper, router endpoints (location set/ clear, city add/remove, preview), dialog template + JS, and full test coverage (providers, widget render, HTTP endpoints, migration replay). docs/widgets.md and CLAUDE.md's TODO updated accordingly.
50 lines
2.1 KiB
Python
50 lines
2.1 KiB
Python
"""Registry mapping a Widget's widget_type to its render/action module --
|
|
the widget-system's analogue of routers/device.py's old RENDERERS/
|
|
ADVANCE_RENDERERS/BACK_RENDERERS dicts, generalized from "one mode owns
|
|
the whole panel" to "each widget renders into its own region and
|
|
optionally responds to named button actions."
|
|
|
|
Each module in this package exposes:
|
|
|
|
render(db, frame, widget, target_w, target_h, is_normal_wake=True) -> Image.Image
|
|
An RGB image exactly target_w x target_h, unquantized -- the
|
|
widget's content composed into its own region. Never returns
|
|
packed panel bytes or raises for a foreseeable failure (a
|
|
widget's own fetch hiccup shows a small placeholder instead) --
|
|
image_pipeline.render_panel composites every widget's own
|
|
render() result onto one shared canvas and quantizes/packs the
|
|
whole thing once (see its own docstring). is_normal_wake
|
|
distinguishes an ordinary /frame/image GET from a button-
|
|
triggered re-render -- only app/widgets/calendar.py's render()
|
|
actually uses it (resetting browse_offset back to "today" on a
|
|
normal wake), but every module accepts it for one uniform call
|
|
signature regardless.
|
|
|
|
ACTIONS: dict[str, Callable[[Session, Frame, Widget], None]]
|
|
Named button actions this widget type supports (e.g. "advance",
|
|
"back", "check_now") -- see models.FrameButtonAction. Each
|
|
function mutates the widget's own state via db.widget_locked
|
|
internally; none return a value or re-render themselves --
|
|
whatever dispatches a button press (routers/device.py, once the
|
|
widget-system cutover lands) re-renders the whole panel once
|
|
after running every assigned action.
|
|
|
|
ACTION_LABELS: dict[str, str]
|
|
Human-readable labels for ACTIONS' keys, for the button-
|
|
assignment UI.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from . import calendar, photos, static_image, tasks, text, weather, whiteboard
|
|
|
|
WIDGET_TYPES = {
|
|
"photos": photos,
|
|
"calendar": calendar,
|
|
"whiteboard": whiteboard,
|
|
"tasks": tasks,
|
|
"static": static_image,
|
|
"text": text,
|
|
"weather": weather,
|
|
}
|