Calendar mode polish batch + Today & Tomorrow view
Responds to post-launch feedback on calendar mode: configurable week-start day for week/month views, crisper non-antialiased text (threshold-masked instead of drawn straight, so Floyd-Steinberg dithering doesn't speckle glyph edges), a color-coded/proportionally filled battery icon on the manage overlay, word-wrapped placeholder text so "Calendar isn't set up yet" no longer clips in portrait, photo inlay support extended from agenda-only to every view, and a fix so manage-overlay face labels reposition correctly when a photo inlay is active (they previously assumed the photo filled the whole canvas). Also adds a fourth calendar view, "Today & Tomorrow" -- a two-day agenda that reuses the same per-day row-layout helper the single-day agenda view already has.
This commit is contained in:
@@ -99,6 +99,7 @@ def api_config_save(
|
||||
mode: str | None = Form(None),
|
||||
calendar_view: str | None = Form(None),
|
||||
calendar_photo_inlay: bool | None = Form(None),
|
||||
calendar_week_start: int | None = Form(None),
|
||||
frame: Frame = Depends(require_frame_control),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
@@ -174,6 +175,8 @@ def api_config_save(
|
||||
cfg.calendar_view = new_view
|
||||
if calendar_photo_inlay is not None:
|
||||
cfg.calendar_photo_inlay = calendar_photo_inlay
|
||||
if calendar_week_start is not None:
|
||||
cfg.calendar_week_start = max(0, min(6, calendar_week_start))
|
||||
cfg.stats_config_saves += 1
|
||||
return {"status": "saved"}
|
||||
|
||||
@@ -436,13 +439,13 @@ def api_calendar_included(
|
||||
|
||||
|
||||
def _calendar_photo_inlay(frame: Frame, db: Session):
|
||||
"""The agenda view's optional photo-inlay source image, or None if
|
||||
inlay is off, not agenda view, or the frame's photos-mode album isn't
|
||||
"""The photo-inlay's source image (any view now, not just agenda), or
|
||||
None if inlay is off or the frame's photos-mode album isn't
|
||||
configured. Shared shape between the live render (routers/device.py's
|
||||
_render_calendar_mode) and this preview endpoint; small enough that
|
||||
duplicating rather than factoring out is fine, since the two call
|
||||
sites differ slightly in error handling."""
|
||||
if not (frame.calendar_view == "agenda" and frame.calendar_photo_inlay):
|
||||
if not frame.calendar_photo_inlay:
|
||||
return None
|
||||
url, key = immich_creds(frame)
|
||||
if not (url and key and frame.album_id):
|
||||
@@ -477,6 +480,7 @@ def api_preview_calendar(frame: Frame = Depends(require_frame_view), db: Session
|
||||
png = calendar_render.render_calendar_preview_png(
|
||||
events, view=view, browse_offset=frame.calendar_browse_offset, orientation=frame.orientation,
|
||||
palette_rgb=frame.palette_rgb, timezone=frame.timezone, photo_inlay=photo_inlay, fetch_summary=summary,
|
||||
week_start=frame.calendar_week_start,
|
||||
)
|
||||
return Response(content=png, media_type="image/png")
|
||||
|
||||
|
||||
@@ -244,15 +244,29 @@ def _format_taken_at(exif: dict) -> str | None:
|
||||
def _manage_content_asset_id(frame: Frame) -> str | None:
|
||||
"""Whether frame.current_asset_id refers to a photo actually visible
|
||||
right now, for whichever mode is active -- always true in photos
|
||||
mode; only true in calendar mode when the agenda view's photo inlay
|
||||
is on (otherwise current_asset_id could be stale, left over from
|
||||
whenever photos mode last ran, and showing its location/date/share
|
||||
info on a manage overlay over a view with no visible photo at all
|
||||
would be actively misleading, not just unhelpful)."""
|
||||
relevant = frame.mode != "calendar" or (frame.calendar_view == "agenda" and frame.calendar_photo_inlay)
|
||||
mode; only true in calendar mode when that view's photo inlay is on
|
||||
(otherwise current_asset_id could be stale, left over from whenever
|
||||
photos mode last ran, and showing its location/date/share info on a
|
||||
manage overlay over a view with no visible photo at all would be
|
||||
actively misleading, not just unhelpful)."""
|
||||
relevant = frame.mode != "calendar" or frame.calendar_photo_inlay
|
||||
return frame.current_asset_id if relevant and frame.current_asset_id else None
|
||||
|
||||
|
||||
def _manage_content_region(frame: Frame) -> tuple[int, int, int, int] | None:
|
||||
"""Where the photo behind _manage_content_asset_id actually landed in
|
||||
the logical canvas -- None (the whole canvas) in photos mode, or
|
||||
calendar_render.inlay_region(...) when a calendar view's photo inlay
|
||||
is what's showing. Needed so face labels (and, if ever added, other
|
||||
photo-relative overlay positioning) land on the actual inlaid photo
|
||||
instead of where a full-panel photo would have been."""
|
||||
if frame.mode == "calendar" and frame.calendar_photo_inlay:
|
||||
from ..calendar_render import inlay_region
|
||||
|
||||
return inlay_region(frame.orientation)
|
||||
return None
|
||||
|
||||
|
||||
def build_manage_content(db: Session, frame: Frame, request) -> dict:
|
||||
"""Gathers everything manage_overlay.compose() needs -- what used to
|
||||
be two separate device-facing endpoints (/frame/photo-info,
|
||||
@@ -293,7 +307,10 @@ def build_manage_content(db: Session, frame: Frame, request) -> dict:
|
||||
preview_bytes = client.download_asset_preview(asset_id)
|
||||
from ..face_labels import compute_face_labels
|
||||
|
||||
content["face_labels"] = compute_face_labels(preview_bytes, faces, frame.display_mode, frame.orientation)
|
||||
content["face_labels"] = compute_face_labels(
|
||||
preview_bytes, faces, frame.display_mode, frame.orientation,
|
||||
region=_manage_content_region(frame),
|
||||
)
|
||||
except httpx.HTTPError as e:
|
||||
logger.warning("Could not download asset %s for face-label mapping: %s", asset_id, e)
|
||||
|
||||
|
||||
@@ -144,7 +144,8 @@ def _render_calendar_mode(db: Session, frame: Frame, request: Request, manage: d
|
||||
locked.calendar_browse_offset = 0
|
||||
browse_offset = locked.calendar_browse_offset
|
||||
view = locked.calendar_view if locked.calendar_view in calendar_render.CALENDAR_VIEWS else "agenda"
|
||||
inlay_wanted = locked.calendar_photo_inlay and view == "agenda"
|
||||
week_start = locked.calendar_week_start
|
||||
inlay_wanted = locked.calendar_photo_inlay
|
||||
|
||||
events, summary = get_or_refresh_calendar_events(db, frame)
|
||||
|
||||
@@ -169,7 +170,7 @@ def _render_calendar_mode(db: Session, frame: Frame, request: Request, manage: d
|
||||
return calendar_render.render_calendar(
|
||||
events, view=view, browse_offset=browse_offset, orientation=frame.orientation,
|
||||
palette_rgb=frame.palette_rgb, timezone=frame.timezone,
|
||||
photo_inlay=photo_inlay, fetch_summary=summary, manage=manage,
|
||||
photo_inlay=photo_inlay, fetch_summary=summary, manage=manage, week_start=week_start,
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user