feat: enhance color handling in templates and dashboard with hex support

This commit is contained in:
2026-05-10 12:55:39 +02:00
parent 8f8c3655db
commit 565c4078bb
5 changed files with 39 additions and 8 deletions
+17 -2
View File
@@ -32,6 +32,17 @@ def get_html_template(content) -> str:
return HTML_TEMPLATE.format(content=content) return HTML_TEMPLATE.format(content=content)
def _as_hex_color(value: int | str | None) -> str:
if isinstance(value, int):
return f"#{value:06X}"
if isinstance(value, str):
try:
return f"#{parse_color(value):06X}"
except ValueError:
return "#000000"
return "#000000"
def create_app( def create_app(
*, *,
get_state: Callable[[], dict], get_state: Callable[[], dict],
@@ -77,13 +88,14 @@ def create_app(
blocks = [] blocks = []
for key, tpl in templates.items(): for key, tpl in templates.items():
text = (tpl.get("text") or "").replace("'", "'") text = (tpl.get("text") or "").replace("'", "'")
color = tpl.get("color") color_hex = _as_hex_color(tpl.get("color"))
image_url = tpl.get("image_url") or "" image_url = tpl.get("image_url") or ""
blocks.append( blocks.append(
"<fieldset style='margin-bottom:16px;'>" "<fieldset style='margin-bottom:16px;'>"
f"<legend><strong>{key}</strong></legend>" f"<legend><strong>{key}</strong></legend>"
f"<label>Text<br><textarea name='{key}__text' rows='3' style='width:100%'>{text}</textarea></label><br>" f"<label>Text<br><textarea name='{key}__text' rows='3' style='width:100%'>{text}</textarea></label><br>"
f"<label>Color<br><input name='{key}__color' value='{color}' style='width:200px'></label><br>" f"<label>Color<br><input type='color' name='{key}__color_picker' value='{color_hex}' oninput=\"this.form['{key}__color'].value=this.value\"></label><br>"
f"<label>Color value<br><input name='{key}__color' value='{color_hex}' style='width:200px'></label><br>"
f"<label>Image URL (optional)<br><input name='{key}__image_url' value='{image_url}' style='width:100%'></label>" f"<label>Image URL (optional)<br><input name='{key}__image_url' value='{image_url}' style='width:100%'></label>"
"</fieldset>" "</fieldset>"
) )
@@ -108,6 +120,9 @@ def create_app(
for key in current.keys(): for key in current.keys():
text = request.form.get(f"{key}__text", "").strip() text = request.form.get(f"{key}__text", "").strip()
color_raw = request.form.get(f"{key}__color", "").strip() color_raw = request.form.get(f"{key}__color", "").strip()
if not color_raw:
color_raw = request.form.get(
f"{key}__color_picker", "").strip()
image_url = request.form.get(f"{key}__image_url", "").strip() image_url = request.form.get(f"{key}__image_url", "").strip()
if not text: if not text:
+5 -5
View File
@@ -1,24 +1,24 @@
{ {
"420": { "420": {
"color": 3066993, "color": "#2ECC71",
"image_url": "https://copyparty.allucanget.biz/img/weed.png", "image_url": "https://copyparty.allucanget.biz/img/weed.png",
"text": "Blaze it!" "text": "Blaze it!"
}, },
"halftime": { "halftime": {
"color": 3066993, "color": "#2ECC71",
"image_url": "https://copyparty.allucanget.biz/img/weed.png", "image_url": "https://copyparty.allucanget.biz/img/weed.png",
"text": "Half-time!" "text": "Half-time!"
}, },
"reminder": { "reminder": {
"color": 15105570, "color": "#E67E22",
"text": "This is your 5 minute reminder to 420!" "text": "This is your 5 minute reminder to 420!"
}, },
"reminder_halftime": { "reminder_halftime": {
"color": 15105570, "color": "#E67E22",
"text": "Half-time in 5 minutes!" "text": "Half-time in 5 minutes!"
}, },
"test": { "test": {
"color": 3447003, "color": "#3498DB",
"text": "This is a test notification." "text": "This is a test notification."
} }
} }
+12 -1
View File
@@ -49,6 +49,11 @@ def _normalize_templates(raw: dict) -> dict[str, dict]:
color = incoming.get("color") color = incoming.get("color")
if isinstance(color, int): if isinstance(color, int):
out[key]["color"] = color out[key]["color"] = color
elif isinstance(color, str):
try:
out[key]["color"] = parse_color(color)
except ValueError:
pass
image_url = incoming.get("image_url") image_url = incoming.get("image_url")
if isinstance(image_url, str) and image_url.strip(): if isinstance(image_url, str) and image_url.strip():
@@ -74,10 +79,16 @@ def load_templates(path: str | Path) -> dict[str, dict]:
def save_templates(path: str | Path, templates: dict) -> None: def save_templates(path: str | Path, templates: dict) -> None:
p = Path(path) p = Path(path)
normalized = _normalize_templates(templates) normalized = _normalize_templates(templates)
serialized = deepcopy(normalized)
for tpl in serialized.values():
color = tpl.get("color")
if isinstance(color, int):
tpl["color"] = f"#{color:06X}"
p.parent.mkdir(parents=True, exist_ok=True) p.parent.mkdir(parents=True, exist_ok=True)
tmp = p.with_suffix(p.suffix + ".tmp") tmp = p.with_suffix(p.suffix + ".tmp")
tmp.write_text(json.dumps(normalized, indent=2, tmp.write_text(json.dumps(serialized, indent=2,
sort_keys=True) + "\n", encoding="utf-8") sort_keys=True) + "\n", encoding="utf-8")
tmp.replace(p) tmp.replace(p)
+2
View File
@@ -72,6 +72,8 @@ def test_admin_get_renders_template_form(monkeypatch):
assert "Admin: templates" in body assert "Admin: templates" in body
assert "name='420__text'" in body assert "name='420__text'" in body
assert "name='420__color'" in body assert "name='420__color'" in body
assert "name='420__color_picker'" in body
assert "type='color'" in body
assert "name='420__image_url'" in body assert "name='420__image_url'" in body
+3
View File
@@ -20,6 +20,9 @@ def test_save_and_load_templates_roundtrip(tmp_path):
} }
save_templates(path, data) save_templates(path, data)
raw = path.read_text(encoding="utf-8")
assert '"color": "#00007B"' in raw
loaded = load_templates(path) loaded = load_templates(path)
assert loaded["420"]["text"] == "Custom" assert loaded["420"]["text"] == "Custom"
assert loaded["420"]["color"] == 123 assert loaded["420"]["color"] == 123