From 263b531ee0c1ded9b709da70b98d45ee5271b26f Mon Sep 17 00:00:00 2001 From: "jampick@microsoft.com" Date: Sun, 5 Apr 2026 08:06:59 -0700 Subject: [PATCH 1/3] feat: unify printing on/off with quiet hours into single print mode Replace separate "PRINTING: ON/OFF" toggle and "Enable quiet hours" checkbox with a single 3-state print_mode selector: Always On, Scheduled (quiet hours), or Off. Quiet hours settings are greyed out when not in Scheduled mode, simplifying the UI. Includes backward-compatible migration from legacy enabled/quiet_enabled config fields to the new print_mode field. Fixes #70 Co-Authored-By: Claude Opus 4.6 (1M context) --- pi/appliance.py | 14 +++- pi/webapp/server.py | 25 +++--- pi/webapp/templates/index.html | 138 +++++++++++++-------------------- printpulse/pi_launcher.py | 9 ++- tests/test_appliance.py | 48 +++++++++++- tests/test_server.py | 2 +- 6 files changed, 134 insertions(+), 102 deletions(-) diff --git a/pi/appliance.py b/pi/appliance.py index 8ef2c08..6b3c06b 100644 --- a/pi/appliance.py +++ b/pi/appliance.py @@ -27,11 +27,10 @@ def default_config() -> dict: "max_prints": 3, "theme": "green", "printer_device": "/dev/usb/lp0", - "quiet_enabled": True, + "print_mode": "scheduled", # "on", "scheduled", or "off" "quiet_start": "22:00", "quiet_end": "08:00", "quiet_wake_mode": "latest", - "enabled": True, "auth_user": "", "auth_hash": "", "secret_key": "", @@ -50,6 +49,17 @@ def load_config() -> dict: saved = json.load(f) # Merge saved values over defaults so new keys get defaults merged = {**defaults, **saved} + # Migrate legacy enabled/quiet_enabled to print_mode + if "print_mode" not in saved: + if not saved.get("enabled", True): + merged["print_mode"] = "off" + elif saved.get("quiet_enabled", True): + merged["print_mode"] = "scheduled" + else: + merged["print_mode"] = "on" + # Clean up legacy keys + merged.pop("enabled", None) + merged.pop("quiet_enabled", None) return merged except Exception: return defaults diff --git a/pi/webapp/server.py b/pi/webapp/server.py index c2b3c5c..891d9a9 100644 --- a/pi/webapp/server.py +++ b/pi/webapp/server.py @@ -534,8 +534,11 @@ def validate_save_input(form) -> tuple[dict | None, list[str]]: errors.append(f"Invalid theme. Must be one of: {', '.join(sorted(_VALID_THEMES))}.") theme = "green" - # --- Quiet hours --- - quiet_enabled = form.get("quiet_enabled") == "1" + # --- Print mode (unified on/scheduled/off) --- + print_mode = form.get("print_mode", "scheduled") + if print_mode not in ("on", "scheduled", "off"): + errors.append("Print mode must be 'on', 'scheduled', or 'off'.") + print_mode = "scheduled" quiet_start = form.get("quiet_start", "22:00").strip() quiet_end = form.get("quiet_end", "08:00").strip() @@ -588,7 +591,7 @@ def validate_save_input(form) -> tuple[dict | None, list[str]]: "max_prints": max_prints, "theme": theme, "printer_device": printer_device, - "quiet_enabled": quiet_enabled, + "print_mode": print_mode, "quiet_start": quiet_start, "quiet_end": quiet_end, "quiet_wake_mode": quiet_wake_mode, @@ -627,11 +630,11 @@ def _quiet_hours_active() -> dict: from datetime import datetime, time as dtime config = load_config() - enabled = config.get("quiet_enabled", False) + print_mode = config.get("print_mode", "scheduled") start_str = config.get("quiet_start", "22:00") end_str = config.get("quiet_end", "08:00") - if not enabled: + if print_mode != "scheduled": return {"enabled": False, "active": False, "start": start_str, "end": end_str} now = datetime.now().time() @@ -701,7 +704,7 @@ def save(): config["max_prints"] = validated["max_prints"] config["theme"] = validated["theme"] config["printer_device"] = validated["printer_device"] - config["quiet_enabled"] = validated["quiet_enabled"] + config["print_mode"] = validated["print_mode"] config["quiet_start"] = validated["quiet_start"] config["quiet_end"] = validated["quiet_end"] config["quiet_wake_mode"] = validated["quiet_wake_mode"] @@ -876,22 +879,24 @@ def status_api(): "printer": _printer_detected(), "auto_update": _auto_update_state.copy(), "quiet_hours": _quiet_hours_active(), - "enabled": cfg.get("enabled", True), + "print_mode": cfg.get("print_mode", "scheduled"), }) @app.route("/toggle_enabled", methods=["POST"]) @require_auth def toggle_enabled(): - """Toggle the print enabled/disabled state.""" + """Cycle print mode: on -> scheduled -> off -> on.""" client_ip = request.remote_addr or "unknown" if _check_rate_limit(f"toggle:{client_ip}"): abort(429) config = load_config() - config["enabled"] = not config.get("enabled", True) + cycle = {"on": "scheduled", "scheduled": "off", "off": "on"} + current = config.get("print_mode", "scheduled") + config["print_mode"] = cycle.get(current, "on") save_config(config) - logger.info("Printing %s via toggle button.", "enabled" if config["enabled"] else "disabled") + logger.info("Print mode changed to '%s' via toggle.", config["print_mode"]) return redirect(url_for("index")) diff --git a/pi/webapp/templates/index.html b/pi/webapp/templates/index.html index 0fdeb32..01c9951 100644 --- a/pi/webapp/templates/index.html +++ b/pi/webapp/templates/index.html @@ -52,43 +52,6 @@ font-size: 0.85em; } - .print-toggle { - display: flex; - justify-content: center; - margin-bottom: 20px; - } - - .print-toggle button { - font-size: 1.2em; - padding: 14px 40px; - letter-spacing: 2px; - font-weight: bold; - border-width: 3px; - transition: all 0.15s ease; - } - - .print-toggle .toggle-on { - border-color: #33ff33; - color: #33ff33; - text-shadow: 0 0 8px #33ff33; - } - .print-toggle .toggle-on:hover { - background: #33ff33; - color: #0a0a0a; - text-shadow: none; - } - - .print-toggle .toggle-off { - border-color: #ff3333; - color: #ff3333; - text-shadow: 0 0 8px #ff3333; - } - .print-toggle .toggle-off:hover { - background: #ff3333; - color: #0a0a0a; - text-shadow: none; - } - .status-bar { display: flex; justify-content: center; @@ -247,18 +210,6 @@

[ PRINTPULSE ]

Appliance Configuration
- - -
@@ -273,10 +224,10 @@

[ PRINTPULSE ]

id="prt-dot"> Printer: {% if printer_ok %}connected{% else %}not found{% endif %}
-
- - Quiet: {% if quiet_hours.active %}active ({{ quiet_hours.start }}–{{ quiet_hours.end }}){% else %}off{% endif %} +
@@ -331,14 +282,19 @@

[ PRINTPULSE ]

-
// QUIET HOURS
-
- - +
// PRINTING
+ + + -
+
@@ -423,6 +379,25 @@

[ PRINTPULSE ]