Fix manual switch reverting instantly, add weather forecast
- scheduler: "Show X now" was a true one-shot -- reverted on the very next 5s tick, causing the flash-to-X-then-flash-back-to-clock behavior. Now pins for 5 minutes (still preemptable by Navidrome priority, which stays authoritative). Added a "Resume automatic" action/button and status display showing what's pinned and until when. - weather: daily forecast strip (3/5/7 days, selectable in settings) below current conditions, via Open-Meteo's daily= params in the same request. Fixed a bad symbol choice along the way -- DejaVu Sans has no glyph for the partly-cloudy emoji, was rendering as a tofu box; swapped to the plain cloud symbol, verified glyph coverage for the whole WMO code table.
This commit is contained in:
1 parent
bfe4616f20
commit
a19a7ea49e
7 files changed
+147
-40
No files matched your search
+10
@@ -36,6 +36,11 @@ def create_app(config, screens: dict, scheduler) -> Flask:
|
||||
scheduler.request_switch(screen_id)
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@app.route("/actions/resume", methods=["POST"])
|
||||
def resume_rotation():
|
||||
scheduler.request_resume_rotation()
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@app.route("/settings/navidrome", methods=["GET", "POST"])
|
||||
def settings_navidrome():
|
||||
if request.method == "POST":
|
||||
@@ -60,6 +65,9 @@ def create_app(config, screens: dict, scheduler) -> Flask:
|
||||
city = request.form.get("city", "").strip()
|
||||
lat_raw = request.form.get("latitude", "").strip()
|
||||
lon_raw = request.form.get("longitude", "").strip()
|
||||
forecast_days = int(request.form.get("forecast_days", 3) or 3)
|
||||
if forecast_days not in (3, 5, 7):
|
||||
forecast_days = 3
|
||||
|
||||
if city:
|
||||
try:
|
||||
@@ -73,6 +81,7 @@ def create_app(config, screens: dict, scheduler) -> Flask:
|
||||
"city": result["label"],
|
||||
"latitude": result["latitude"],
|
||||
"longitude": result["longitude"],
|
||||
"forecast_days": forecast_days,
|
||||
})
|
||||
return redirect(url_for("settings_weather", saved=1))
|
||||
else:
|
||||
@@ -80,6 +89,7 @@ def create_app(config, screens: dict, scheduler) -> Flask:
|
||||
"city": "",
|
||||
"latitude": float(lat_raw) if lat_raw else None,
|
||||
"longitude": float(lon_raw) if lon_raw else None,
|
||||
"forecast_days": forecast_days,
|
||||
})
|
||||
return redirect(url_for("settings_weather", saved=1))
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
label { display: block; font-weight: 600; margin-bottom: 0.35rem; font-size: 0.85rem; }
|
||||
.hint { font-size: 0.78rem; color: var(--text-muted); margin: 0.35rem 0 0; }
|
||||
|
||||
input[type=text], input[type=password], input[type=number] {
|
||||
input[type=text], input[type=password], input[type=number], select, input[type=file] {
|
||||
width: 100%;
|
||||
padding: 0.6rem 0.7rem;
|
||||
font-size: 0.95rem;
|
||||
|
||||
@@ -22,6 +22,10 @@
|
||||
<span class="badge {{ 'on' if rotation.enabled else 'off' }}">{{ "on" if rotation.enabled else "off" }}</span>
|
||||
{{ rotation.screens | join(" → ") }} (every {{ rotation.interval_minutes }} min)
|
||||
</dd>
|
||||
{% if status.manual_screen %}
|
||||
<dt>Manual pin</dt>
|
||||
<dd><span class="badge on">{{ screens[status.manual_screen].display_name }}</span> until {{ status.manual_until }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
@@ -36,6 +40,11 @@
|
||||
<button class="secondary" type="submit">Show {{ screen.display_name }}</button>
|
||||
</form>
|
||||
{% endfor %}
|
||||
{% if status.manual_screen %}
|
||||
<form class="inline" method="post" action="{{ url_for('resume_rotation') }}">
|
||||
<button class="secondary" type="submit">Resume automatic</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -43,7 +43,7 @@
|
||||
<div class="section-label">Display options</div>
|
||||
<div class="field">
|
||||
<label for="fit">Fit</label>
|
||||
<select id="fit" name="fit" style="width:100%; padding:0.6rem 0.7rem; font-size:0.95rem; font-family:inherit; background:var(--bg); color:var(--text); border:1px solid var(--border); border-radius:8px;">
|
||||
<select id="fit" name="fit">
|
||||
<option value="cover" {{ "selected" if settings.fit == "cover" }}>Cover -- fill the screen, cropping edges</option>
|
||||
<option value="contain" {{ "selected" if settings.fit == "contain" }}>Contain -- show the whole image, letterboxed</option>
|
||||
</select>
|
||||
|
||||
@@ -19,6 +19,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="section-label">Forecast</div>
|
||||
<div class="field">
|
||||
<label for="forecast_days">Days to show</label>
|
||||
<select id="forecast_days" name="forecast_days">
|
||||
<option value="3" {{ "selected" if settings.forecast_days == 3 or not settings.forecast_days }}>3 days</option>
|
||||
<option value="5" {{ "selected" if settings.forecast_days == 5 }}>5 days</option>
|
||||
<option value="7" {{ "selected" if settings.forecast_days == 7 }}>7 days</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="section-label">Or enter coordinates manually</div>
|
||||
<p class="hint" style="margin: -0.25rem 0 1rem;">Leave the city field above empty to use these instead.</p>
|
||||
|
||||
Reference in new issue
Block a user