"""Calendar widget: merged-event agenda/week/month view into the widget's own region -- the widget-system analogue of routers/device.py's old _render_calendar_mode/_advance_calendar_mode/_back_calendar_mode. render() builds directly at whatever target box it's asked for -- calendar_render.py's layout math picks from discrete size tiers (see its own _size_tier) rather than always laying out at full panel size and resizing after the fact, so a small placed calendar widget gets an actually-legible small-size layout instead of a shrunk-down full-size one. "Photo inlay" (calendar mode's old half-and-half photo split) has no widget-system equivalent -- place an independent photo widget alongside instead; arbitrary placement is strictly more flexible than one fixed split ever was.""" from __future__ import annotations from PIL import Image from sqlalchemy.orm import Session from zoneinfo import ZoneInfo from ..calendar_render import _build from ..db import widget_locked from ..models import CalendarWidgetConfig, Frame, Widget from ..routers.common import ( get_or_refresh_calendar_events_for_widget, get_or_refresh_weather_for_widget, ) ACTION_LABELS = {"advance": "Next period", "back": "Previous period"} def render(db: Session, frame: Frame, widget: Widget, target_w: int, target_h: int, is_normal_wake: bool = True) -> Image.Image: """is_normal_wake=True (an ordinary /frame/image GET, not a button press) resets browse_offset back to "today" if it had drifted -- mirrors the old _render_calendar_mode's identical behavior. A button press explicitly moved the browse position on purpose, so it passes is_normal_wake=False to render its own already-updated offset instead of immediately snapping back to 0.""" if is_normal_wake: with widget_locked(db, frame.id, widget.id) as (_, _, locked_cfg): if locked_cfg.browse_offset != 0: locked_cfg.browse_offset = 0 cfg = db.get(CalendarWidgetConfig, widget.id) events, fetch_summary = get_or_refresh_calendar_events_for_widget(db, frame, widget) weather_cities = get_or_refresh_weather_for_widget(db, frame, widget) if cfg.weather_enabled else None if cfg.render_style == "modern": # Local import: html_render pulls in Playwright, a real headless- # Chromium dependency -- every other widget type, and this one's # own classic path, should never pay for it. from .. import calendar_html_render tz = ZoneInfo(frame.timezone) if frame.timezone else ZoneInfo("UTC") return calendar_html_render.build( events, cfg.view, cfg.browse_offset, target_w, target_h, tz, cfg.week_start, frame.palette_rgb, weather_cities, cfg.weather_units, cfg.week_days, cfg.week_layout, cfg.week_start_offset, frame.theme, ) return _build( events, view=cfg.view, browse_offset=cfg.browse_offset, target_w=target_w, target_h=target_h, timezone=frame.timezone, fetch_summary=fetch_summary, week_start=cfg.week_start, palette_rgb=frame.palette_rgb, weather_cities=weather_cities, weather_units=cfg.weather_units, week_days=cfg.week_days, week_layout=cfg.week_layout, week_start_offset=cfg.week_start_offset, ) def _advance(db: Session, frame: Frame, widget: Widget) -> None: with widget_locked(db, frame.id, widget.id) as (_, _, cfg): cfg.browse_offset += 1 def _back(db: Session, frame: Frame, widget: Widget) -> None: with widget_locked(db, frame.id, widget.id) as (_, _, cfg): cfg.browse_offset -= 1 ACTIONS = {"advance": _advance, "back": _back}