Move button actions to per-widget config, add hold-for-global-action
Next/back button assignment moves from a frame-level "Button assignments" card into each widget's own gear-icon dialog, prefilled with a sane default at creation (photos/calendar -> advance/back, whiteboard/weather -> check_now, others -> none). At most one binding per (widget, button) now -- cross-widget execution order never mattered since each widget's action only touches its own state. New firmware capability: holding NEXT or BACK past a configurable duration (min 3s, server-side default) triggers a frame-wide action instead of the per-widget short-press one -- cycling saved layouts, refreshing all widgets, or freezing/unfreezing every photo widget (see app/global_actions.py). Firmware next/back checks gain the same hold-duration polling the combo button already had; the threshold comes from the previous wake's /frame/config fetch (persisted in NVS), since this wake's button decision happens before that request. Not done here: firmware/version.txt is intentionally left unbumped -- this hasn't been built or hardware-tested (no ESP-IDF toolchain in this environment), so no firmware release build should be triggered yet.
This commit is contained in:
+54
-23
@@ -24,7 +24,6 @@ from .models import (
|
||||
BatteryLog,
|
||||
CalendarWidgetConfig,
|
||||
Frame,
|
||||
FrameButtonAction,
|
||||
FrameTaskList,
|
||||
PhotoWidgetConfig,
|
||||
ServerSettings,
|
||||
@@ -32,6 +31,7 @@ from .models import (
|
||||
WhiteboardWidgetConfig,
|
||||
Widget,
|
||||
)
|
||||
from .widgets import default_button_actions
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -725,6 +725,55 @@ def _migration_27(conn) -> None:
|
||||
conn.execute(text("ALTER TABLE photo_widget_configs ADD COLUMN locked INTEGER NOT NULL DEFAULT 0"))
|
||||
|
||||
|
||||
def _migration_28(conn) -> None:
|
||||
"""One action per (widget, button) instead of an ordered per-button
|
||||
list -- button-action editing moved from the frame-level "Button
|
||||
assignments" card into each widget's own config dialog (see
|
||||
models.FrameButtonAction's updated docstring, routers/api_widgets.py's
|
||||
api_widget_config_save). Cross-widget execution order never actually
|
||||
mattered (each widget's action only touches its own state), so this
|
||||
only needs to de-dupe down to one row before the new unique index can
|
||||
be created -- MIN(id) per (widget_id, button) survives, arbitrarily
|
||||
but deterministically, since which specific extra binding a user's
|
||||
old list happened to have doesn't matter anymore."""
|
||||
conn.execute(text(
|
||||
"DELETE FROM frame_button_actions WHERE id NOT IN "
|
||||
"(SELECT MIN(id) FROM frame_button_actions GROUP BY widget_id, button)"
|
||||
))
|
||||
conn.execute(text(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS ix_frame_button_actions_widget_button "
|
||||
"ON frame_button_actions (widget_id, button)"
|
||||
))
|
||||
|
||||
|
||||
def _migration_29(conn) -> None:
|
||||
"""Hold-for-global-action (see app/global_actions.py): holding NEXT/
|
||||
BACK past hold_duration_ms triggers a frame-wide action instead of
|
||||
the per-widget one a short press runs. next_hold_action/
|
||||
back_hold_action are NULL (disabled) by default -- existing frames
|
||||
get no new button behavior until someone opts in on the
|
||||
Configuration tab. last_cycled_layout_id tracks where a repeated
|
||||
"cycle saved layouts" hold should resume from.
|
||||
|
||||
Guarded per-column, same reasoning as migration 26/27's own
|
||||
comments: frames is a table test_migrations.py's pre-widget-system
|
||||
replay tests leave un-dropped (unlike calendar/task/widget tables
|
||||
those tests DROP and recreate in an old shape), so it keeps the
|
||||
fresh-install create_all() copy -- which already has these columns
|
||||
-- when those tests replay migrations 17+ from schema_version 16.
|
||||
Without the guard, replaying this migration there re-adds a column
|
||||
that's already there and SQLite raises "duplicate column name"."""
|
||||
existing = {c["name"] for c in inspect(conn).get_columns("frames")}
|
||||
if "hold_duration_ms" not in existing:
|
||||
conn.execute(text("ALTER TABLE frames ADD COLUMN hold_duration_ms INTEGER NOT NULL DEFAULT 3000"))
|
||||
if "next_hold_action" not in existing:
|
||||
conn.execute(text("ALTER TABLE frames ADD COLUMN next_hold_action TEXT"))
|
||||
if "back_hold_action" not in existing:
|
||||
conn.execute(text("ALTER TABLE frames ADD COLUMN back_hold_action TEXT"))
|
||||
if "last_cycled_layout_id" not in existing:
|
||||
conn.execute(text("ALTER TABLE frames ADD COLUMN last_cycled_layout_id INTEGER"))
|
||||
|
||||
|
||||
MIGRATIONS = [
|
||||
(1, _migration_1),
|
||||
(2, _migration_2),
|
||||
@@ -753,6 +802,8 @@ MIGRATIONS = [
|
||||
(25, _migration_25),
|
||||
(26, _migration_26),
|
||||
(27, _migration_27),
|
||||
(28, _migration_28),
|
||||
(29, _migration_29),
|
||||
]
|
||||
|
||||
|
||||
@@ -963,26 +1014,6 @@ def _whiteboard_config_from_frame(frame: Frame, widget_id: int) -> WhiteboardWid
|
||||
)
|
||||
|
||||
|
||||
def _default_button_actions(frame_id: int, widget_id: int, widget_type: str) -> list[FrameButtonAction]:
|
||||
"""NEXT/BACK -> whatever this widget's own advance/back concept is
|
||||
(see app/widgets/ for the actual action registry, built in a later
|
||||
phase) -- reproduces each mode's exact old button behavior for the
|
||||
one auto-migrated widget, so upgrading changes nothing about what the
|
||||
physical buttons do until someone deliberately reassigns them."""
|
||||
if widget_type == "whiteboard":
|
||||
# No real "next"/"back" concept for a static board -- both
|
||||
# buttons already meant "check now" before this migration (see
|
||||
# the old _advance_whiteboard_mode/_back_whiteboard_mode).
|
||||
return [
|
||||
FrameButtonAction(frame_id=frame_id, button="next", widget_id=widget_id, action="check_now"),
|
||||
FrameButtonAction(frame_id=frame_id, button="back", widget_id=widget_id, action="check_now"),
|
||||
]
|
||||
return [
|
||||
FrameButtonAction(frame_id=frame_id, button="next", widget_id=widget_id, action="advance"),
|
||||
FrameButtonAction(frame_id=frame_id, button="back", widget_id=widget_id, action="back"),
|
||||
]
|
||||
|
||||
|
||||
def _maybe_add_legacy_tasks_widget(db, frame: Frame, existing: list[grid.Rect], next_sort_order: int) -> None:
|
||||
"""Only relevant for a database jumping straight from before the
|
||||
widget system existed to after tasks became their own widget type
|
||||
@@ -1032,7 +1063,7 @@ def _backfill_frame_widgets(db, frame: Frame) -> None:
|
||||
db.flush() # assign ids before the FK'd config rows reference them
|
||||
db.add(_calendar_config_from_frame(frame, cal_widget.id))
|
||||
db.add(_photo_config_from_frame(frame, photo_widget.id))
|
||||
db.add_all(_default_button_actions(frame.id, cal_widget.id, "calendar"))
|
||||
db.add_all(default_button_actions(frame.id, cal_widget.id, "calendar"))
|
||||
_maybe_add_legacy_tasks_widget(
|
||||
db, frame, [(0, 0, cols - half, rows), (cols - half, 0, half, rows)], next_sort_order=2
|
||||
)
|
||||
@@ -1048,7 +1079,7 @@ def _backfill_frame_widgets(db, frame: Frame) -> None:
|
||||
db.add(_calendar_config_from_frame(frame, widget.id))
|
||||
elif mode == "whiteboard":
|
||||
db.add(_whiteboard_config_from_frame(frame, widget.id))
|
||||
db.add_all(_default_button_actions(frame.id, widget.id, mode))
|
||||
db.add_all(default_button_actions(frame.id, widget.id, mode))
|
||||
if mode == "calendar":
|
||||
_maybe_add_legacy_tasks_widget(db, frame, [(0, 0, cols, rows)], next_sort_order=1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user