Release 1.6
This commit is contained in:
@@ -1161,6 +1161,64 @@ def update_display(display_id: int):
|
||||
return None
|
||||
return v
|
||||
|
||||
def _normalize_css_color(val: str | None) -> str | None:
|
||||
"""Accept a limited set of CSS color inputs (primarily hex + a few keywords).
|
||||
|
||||
This is used to avoid storing arbitrary CSS strings while still being user friendly.
|
||||
"""
|
||||
|
||||
v = (val or "").strip()
|
||||
if not v:
|
||||
return None
|
||||
|
||||
low = v.lower()
|
||||
if low in {"white", "black", "red", "green", "blue", "yellow", "orange", "purple", "gray", "grey"}:
|
||||
return low
|
||||
|
||||
# Hex colors: #RGB, #RRGGBB, #RRGGBBAA
|
||||
if low.startswith("#"):
|
||||
h = low[1:]
|
||||
if len(h) in {3, 6, 8} and all(c in "0123456789abcdef" for c in h):
|
||||
return "#" + h
|
||||
return None
|
||||
|
||||
def _normalize_percent(val) -> int | None:
|
||||
if val in (None, ""):
|
||||
return None
|
||||
try:
|
||||
n = int(val)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
return min(100, max(0, n))
|
||||
|
||||
def _normalize_speed(val) -> int | None:
|
||||
if val in (None, ""):
|
||||
return None
|
||||
try:
|
||||
n = int(val)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
return min(100, max(1, n))
|
||||
|
||||
def _normalize_font_family(val: str | None) -> str | None:
|
||||
v = (val or "").strip()
|
||||
if not v:
|
||||
return None
|
||||
# keep it short and avoid quotes/newlines that could be abused in CSS.
|
||||
v = v.replace("\n", " ").replace("\r", " ").replace('"', "").replace("'", "")
|
||||
v = " ".join(v.split())
|
||||
return v[:120] if v else None
|
||||
|
||||
def _normalize_font_size_px(val) -> int | None:
|
||||
if val in (None, ""):
|
||||
return None
|
||||
try:
|
||||
n = int(val)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
# reasonable bounds for signage displays
|
||||
return min(200, max(10, n))
|
||||
|
||||
# Inputs from either form or JSON
|
||||
payload = request.get_json(silent=True) if request.is_json else None
|
||||
|
||||
@@ -1210,6 +1268,75 @@ def update_display(display_id: int):
|
||||
if raw is not None:
|
||||
display.show_overlay = (raw or "").strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
# Ticker tape settings
|
||||
if request.is_json:
|
||||
if payload is None:
|
||||
return _json_error("Invalid JSON")
|
||||
|
||||
if "ticker_enabled" in payload:
|
||||
raw = payload.get("ticker_enabled")
|
||||
if isinstance(raw, bool):
|
||||
display.ticker_enabled = raw
|
||||
elif raw in (1, 0):
|
||||
display.ticker_enabled = bool(raw)
|
||||
else:
|
||||
s = ("" if raw is None else str(raw)).strip().lower()
|
||||
display.ticker_enabled = s in {"1", "true", "yes", "on"}
|
||||
|
||||
if "ticker_rss_url" in payload:
|
||||
u = (payload.get("ticker_rss_url") or "").strip() or None
|
||||
# Keep within column limit and avoid whitespace-only.
|
||||
if u is not None:
|
||||
u = u[:1000]
|
||||
display.ticker_rss_url = u
|
||||
|
||||
if "ticker_color" in payload:
|
||||
display.ticker_color = _normalize_css_color(payload.get("ticker_color"))
|
||||
|
||||
if "ticker_bg_color" in payload:
|
||||
display.ticker_bg_color = _normalize_css_color(payload.get("ticker_bg_color"))
|
||||
|
||||
if "ticker_bg_opacity" in payload:
|
||||
display.ticker_bg_opacity = _normalize_percent(payload.get("ticker_bg_opacity"))
|
||||
|
||||
if "ticker_font_family" in payload:
|
||||
display.ticker_font_family = _normalize_font_family(payload.get("ticker_font_family"))
|
||||
|
||||
if "ticker_font_size_px" in payload:
|
||||
display.ticker_font_size_px = _normalize_font_size_px(payload.get("ticker_font_size_px"))
|
||||
|
||||
if "ticker_speed" in payload:
|
||||
display.ticker_speed = _normalize_speed(payload.get("ticker_speed"))
|
||||
else:
|
||||
# Form POST implies full update
|
||||
raw = request.form.get("ticker_enabled")
|
||||
if raw is not None:
|
||||
display.ticker_enabled = (raw or "").strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
if "ticker_rss_url" in request.form:
|
||||
u = (request.form.get("ticker_rss_url") or "").strip() or None
|
||||
if u is not None:
|
||||
u = u[:1000]
|
||||
display.ticker_rss_url = u
|
||||
|
||||
if "ticker_color" in request.form:
|
||||
display.ticker_color = _normalize_css_color(request.form.get("ticker_color"))
|
||||
|
||||
if "ticker_bg_color" in request.form:
|
||||
display.ticker_bg_color = _normalize_css_color(request.form.get("ticker_bg_color"))
|
||||
|
||||
if "ticker_bg_opacity" in request.form:
|
||||
display.ticker_bg_opacity = _normalize_percent(request.form.get("ticker_bg_opacity"))
|
||||
|
||||
if "ticker_font_family" in request.form:
|
||||
display.ticker_font_family = _normalize_font_family(request.form.get("ticker_font_family"))
|
||||
|
||||
if "ticker_font_size_px" in request.form:
|
||||
display.ticker_font_size_px = _normalize_font_size_px(request.form.get("ticker_font_size_px"))
|
||||
|
||||
if "ticker_speed" in request.form:
|
||||
display.ticker_speed = _normalize_speed(request.form.get("ticker_speed"))
|
||||
|
||||
# Playlist assignment
|
||||
if request.is_json:
|
||||
if "playlist_id" in payload:
|
||||
@@ -1251,6 +1378,14 @@ def update_display(display_id: int):
|
||||
"description": display.description,
|
||||
"transition": display.transition,
|
||||
"show_overlay": bool(display.show_overlay),
|
||||
"ticker_enabled": bool(display.ticker_enabled),
|
||||
"ticker_rss_url": display.ticker_rss_url,
|
||||
"ticker_color": display.ticker_color,
|
||||
"ticker_bg_color": display.ticker_bg_color,
|
||||
"ticker_bg_opacity": display.ticker_bg_opacity,
|
||||
"ticker_font_family": display.ticker_font_family,
|
||||
"ticker_font_size_px": display.ticker_font_size_px,
|
||||
"ticker_speed": display.ticker_speed,
|
||||
"assigned_playlist_id": display.assigned_playlist_id,
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user