feat: Schalter platziert die Willkommensseite sofort
Die Willkommensseite ist je Standort identisch. Bisher musste man nach dem Einschalten des Schalters "Willkommensseite hier anzeigen" erneut generieren, damit sie auf dem Screen ankam - ein Handgriff ohne anderes Ergebnis, und ohne Rueckmeldung, dass er noetig ist. - sync_welcome_for_screen() kopiert beim Einschalten eine vorhandene welcome.html in den Screen und setzt sie vorne in die Playlist; beim Ausschalten werden Eintrag und Datei entfernt. Aufgerufen aus update_screen(), nur wenn sich der Schalter wirklich aendert. - save_welcome_page() legt zusaetzlich eine Standort-Kopie unter media/<site>/welcome.html an, die nie geloescht wird. Ohne sie waere nach dem Abschalten aller Screens keine Quelle mehr vorhanden und der Schalter wuerde wieder stillschweigend nichts tun. Nebenbei behoben: beim Generieren wurde welcome.html aus abgewaehlten Screens nur aus der Playlist entfernt, die Datei blieb liegen. Bei leerer Playlist spielt player() noch nicht verzeichnete Dateien aus dem Ordner ab - die Begruessung waere dort also weiter zu sehen gewesen. README, AGENTS.md und Hilfeseite nachgezogen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -124,6 +124,9 @@ Standorte (sites) sind die oberste Organisationsebene und gruppieren Screens.
|
||||
- **Logo-Upload**: Pro Kunde kann im Willkommensseiten-Formular eine Bilddatei hochgeladen werden (`logo_file_1..3`); ein Upload hat Vorrang vor `search_customer_logo`. Ablage als `media/<site>/logos/logo_<slot>.png`, Auslieferung über `/media/<site>/logos/<datei>`. Die URL traegt einen `?v=<timestamp>`, damit Player kein altes Logo aus dem Cache zeigen.
|
||||
- `remove_logo_background()` arbeitet mit Pillow (Flood Fill von den Raendern, weiche Kante). **Bewusst kein ML-Modell** wie rembg: der Container laeuft mit read-only Root-FS ohne Modell-Cache. Flood Fill statt globalem Farbvergleich ist wesentlich - sonst verschwindet auch Weiss *innerhalb* des Logos. Bereits transparente Bilder werden unveraendert durchgereicht; bei unruhigem Bildrand bleibt das Bild unangetastet und die Funktion gibt einen Hinweis zurueck.
|
||||
- `welcome_data` in der Site-Config wird **feldweise** gespeichert (Slot 1-3, auch leere), nicht nur die erfolgreichen Kunden - sonst verschiebt sich die Zuordnung von Name, Logo und Ansprechpartner beim naechsten Aufruf. Ein zuvor hochgeladenes Logo wird beim erneuten Generieren wiederverwendet, solange der Kundenname im selben Feld unveraendert ist.
|
||||
- **Der Schalter `welcome_enabled` wirkt sofort**: `update_screen` ruft bei einer Aenderung `sync_welcome_for_screen()`, das die vorhandene `welcome.html` in den Screen kopiert und vorne einfuegt bzw. beim Abschalten Eintrag und Datei entfernt. Kein erneutes Generieren noetig - die Seite ist je Standort identisch. Die Funktion aendert nur das uebergebene config-Dict, gespeichert wird vom Aufrufer.
|
||||
- `save_welcome_page` legt zusaetzlich eine Standort-Kopie unter `media/<site>/welcome.html` an, die **nie geloescht** wird. Ohne sie waere nach dem Abschalten aller Screens keine Quelle mehr da und der Schalter wuerde stillschweigend nichts tun.
|
||||
- Beim Generieren wird `welcome.html` aus abgewaehlten Screens auch als **Datei** entfernt, nicht nur aus der Playlist: bei leerer Playlist spielt `player()` sonst noch nicht verzeichnete Dateien aus dem Ordner ab.
|
||||
- Weder `save_welcome_page` noch `add_customer_to_playlists` legen Screens an. Das frühere `screens.setdefault("lobby", {})` erzeugte an Standorten ohne Lobby stillschweigend einen Screen.
|
||||
- `add_screen` legt ein Verzeichnis unter `media/<site>/<screen>/` an und einen Config-Eintrag.
|
||||
- `delete_screen` entfernt den Screen aus der Config und löscht das Verzeichnis rekursiv.
|
||||
|
||||
@@ -294,7 +294,9 @@ http://localhost:5005/willkommen?site=stuttgart
|
||||
|
||||
Maximal 3 Kunden eingeben → Logos werden via OpenAI + Brandfetch gesucht → `welcome.html` wird in den Playlists aller Zielscreens vorne eingefügt. Pro Standort kann ein eigenes Hintergrundbild hochgeladen werden (Fallback auf `static/wallpaper.png`).
|
||||
|
||||
**Zielscreens:** Jeder Screen mit `welcome_enabled` (Schalter „Willkommensseite hier anzeigen" im Tab Einstellungen). Besitzt kein Screen des Standorts den Schlüssel, greift das alte Verhalten und die Seite geht nur in `lobby`. Wird ein Screen später abgewählt, verschwindet die Willkommensseite dort beim nächsten Generieren.
|
||||
**Zielscreens:** Jeder Screen mit `welcome_enabled` (Schalter „Willkommensseite hier anzeigen" im Tab Einstellungen). Besitzt kein Screen des Standorts den Schlüssel, greift das alte Verhalten und die Seite geht nur in `lobby`.
|
||||
|
||||
**Der Schalter wirkt sofort:** Beim Einschalten wird eine bereits vorhandene Willkommensseite unmittelbar in die Playlist des Screens gesetzt, beim Ausschalten wieder entfernt (samt Datei) – ohne erneutes Generieren. Die Seite ist je Standort identisch, ein zweiter Handgriff wäre also nur Aufwand ohne anderes Ergebnis. Beim Generieren wird zusätzlich eine Standort-Kopie unter `media/<site>/welcome.html` abgelegt, die nie gelöscht wird; dadurch findet der Schalter auch dann noch eine Quelle, wenn die Seite zwischenzeitlich auf allen Screens abgeschaltet war.
|
||||
|
||||
**Logo-Upload:** Findet die automatische Suche kein Logo (oder passt es nicht), kann pro Kunde eine Bilddatei hochgeladen werden. Ein Upload hat Vorrang vor der Suche. Die Datei wird als `media/<site>/logos/logo_<slot>.png` abgelegt und über `/media/<site>/logos/<datei>` ausgeliefert.
|
||||
|
||||
@@ -509,6 +511,8 @@ Optional pro Eintrag. Ohne `schedule` läuft der Eintrag immer (sofern `enabled`
|
||||
- `welcome_target_screens(config, site)` – Zielscreens der Willkommensseite (Fallback `lobby`)
|
||||
- `save_uploaded_logo(file, site, slot, freistellen=True)` – hochgeladenes Logo freistellen und als PNG ablegen
|
||||
- `remove_logo_background(image)` – Hintergrund per Flood Fill entfernen, gibt `(bild, hinweis)` zurück
|
||||
- `sync_welcome_for_screen(config, site, screen, enabled)` – Willkommensseite beim Umlegen des Schalters platzieren bzw. entfernen
|
||||
- `find_welcome_source(site, config)` – vorhandene `welcome.html` als Kopiervorlage finden
|
||||
- `prio_redirect(site)` – Redirect-Pfad zur Priority-Seite
|
||||
- `get_background_url(site)` – URL zum benutzerdefinierten Hintergrundbild oder `None`
|
||||
- `load_users()` / `save_users()` / `get_user()` – User aus `users.json`
|
||||
|
||||
10
app.py
10
app.py
@@ -1591,7 +1591,15 @@ def update_screen(site, screen):
|
||||
cfg["newsticker_text"] = request.form.get("newsticker_text", "")
|
||||
cfg["newsticker_enabled"] = "newsticker_enabled" in request.form
|
||||
cfg["stay_on_first"] = "stay_on_first" in request.form
|
||||
cfg["welcome_enabled"] = "welcome_enabled" in request.form
|
||||
welcome_an = "welcome_enabled" in request.form
|
||||
vorher = cfg.get("welcome_enabled")
|
||||
cfg["welcome_enabled"] = welcome_an
|
||||
|
||||
# Beim Umlegen des Schalters die Willkommensseite sofort platzieren bzw.
|
||||
# entfernen - sie ist je Standort identisch, ein erneutes Generieren
|
||||
# waere also nur ein zusaetzlicher Handgriff ohne anderes Ergebnis.
|
||||
if vorher != welcome_an:
|
||||
generate_welcome_page.sync_welcome_for_screen(config, site, screen, welcome_an)
|
||||
|
||||
save_config(config)
|
||||
return redirect(f"/admin/{site}")
|
||||
|
||||
@@ -499,6 +499,12 @@ def save_welcome_page(customer_names, logo_urls, contacts=None, site="stuttgart"
|
||||
html_content = generate_welcome_html(customer_names, logo_urls, contacts=contacts,
|
||||
site=site, background_url=background_url)
|
||||
|
||||
# Standort-Kopie zuerst - sie ueberlebt das Abschalten aller Screens
|
||||
master = _welcome_master_path(site)
|
||||
os.makedirs(os.path.dirname(master), exist_ok=True)
|
||||
with open(master, "w", encoding="utf-8") as f:
|
||||
f.write(html_content)
|
||||
|
||||
for screen in screens:
|
||||
welcome_dir = os.path.join(MEDIA_DIR, site, screen)
|
||||
os.makedirs(welcome_dir, exist_ok=True)
|
||||
@@ -530,6 +536,87 @@ def _strip_welcome(playlist, filename):
|
||||
]
|
||||
|
||||
|
||||
def _welcome_path(site, screen):
|
||||
return os.path.join(MEDIA_DIR, site, screen, WELCOME_FILENAME)
|
||||
|
||||
|
||||
def _welcome_master_path(site):
|
||||
"""Standort-Kopie der Willkommensseite. Sie liegt neben den Screen-Ordnern,
|
||||
wird beim Generieren mitgeschrieben und nie geloescht - damit der Schalter
|
||||
eines Screens die Seite auch dann noch platzieren kann, wenn sie zuvor
|
||||
auf allen Screens abgeschaltet war."""
|
||||
return os.path.join(MEDIA_DIR, site, WELCOME_FILENAME)
|
||||
|
||||
|
||||
def find_welcome_source(site, config):
|
||||
"""Sucht eine vorhandene welcome.html für einen Standort.
|
||||
Die Seite ist je Standort identisch, jede Kopie taugt als Quelle.
|
||||
Gibt den Pfad zurück oder None, wenn noch nie eine erzeugt wurde."""
|
||||
master = _welcome_master_path(site)
|
||||
if os.path.exists(master):
|
||||
return master
|
||||
|
||||
screens = config.get("sites", {}).get(site, {}).get("screens", {})
|
||||
for screen in sorted(screens):
|
||||
pfad = _welcome_path(site, screen)
|
||||
if os.path.exists(pfad):
|
||||
return pfad
|
||||
return None
|
||||
|
||||
|
||||
def _remove_welcome_file(site, screen):
|
||||
"""Löscht die welcome.html eines Screens, falls vorhanden."""
|
||||
pfad = _welcome_path(site, screen)
|
||||
if os.path.exists(pfad):
|
||||
try:
|
||||
os.remove(pfad)
|
||||
except OSError as e:
|
||||
print(f"⚠️ Generate - welcome.html in {site}/{screen} nicht löschbar: {e}")
|
||||
|
||||
|
||||
def sync_welcome_for_screen(config, site, screen, enabled):
|
||||
"""Bringt einen einzelnen Screen mit dem Schalter "Willkommensseite hier
|
||||
anzeigen" in Einklang - ohne dass neu generiert werden muss.
|
||||
|
||||
enabled=True : vorhandene welcome.html hineinkopieren und vorne einfügen
|
||||
enabled=False: Eintrag entfernen und die Kopie löschen
|
||||
|
||||
Verändert nur das übergebene config-Dict; das Speichern übernimmt der
|
||||
Aufrufer. Gibt True zurück, wenn sich an der Playlist etwas geändert hat."""
|
||||
screen_cfg = config.get("sites", {}).get(site, {}).get("screens", {}).get(screen)
|
||||
if screen_cfg is None:
|
||||
return False
|
||||
|
||||
playlist = screen_cfg.setdefault("playlist", [])
|
||||
vorher = list(playlist)
|
||||
|
||||
if not enabled:
|
||||
_strip_welcome(playlist, WELCOME_FILENAME)
|
||||
_remove_welcome_file(site, screen)
|
||||
return playlist != vorher
|
||||
|
||||
quelle = find_welcome_source(site, config)
|
||||
if not quelle:
|
||||
# Noch keine Willkommensseite erzeugt - dann gibt es nichts zu platzieren.
|
||||
return False
|
||||
|
||||
ziel = _welcome_path(site, screen)
|
||||
if os.path.abspath(quelle) != os.path.abspath(ziel):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(ziel), exist_ok=True)
|
||||
with open(quelle, "rb") as q, open(ziel, "wb") as z:
|
||||
z.write(q.read())
|
||||
except OSError as e:
|
||||
print(f"❌ Generate - welcome.html nach {site}/{screen} nicht kopierbar: {e}")
|
||||
return False
|
||||
|
||||
# Vorhandenen Eintrag entfernen und vorne neu einfügen
|
||||
_strip_welcome(playlist, WELCOME_FILENAME)
|
||||
playlist.insert(0, {"name": WELCOME_FILENAME, "enabled": True})
|
||||
print(f"✅ Generate - Willkommensseite in {site}/{screen} platziert")
|
||||
return True
|
||||
|
||||
|
||||
def add_customer_to_playlists(html_filename, site="stuttgart", screens=None, valid_until=None):
|
||||
"""Setzt die Willkommensseite an Position 0 der Playlist jedes Zielscreens.
|
||||
|
||||
@@ -570,6 +657,11 @@ def add_customer_to_playlists(html_filename, site="stuttgart", screens=None, val
|
||||
_strip_welcome(playlist, html_filename)
|
||||
if name in targets:
|
||||
playlist.insert(0, dict(entry))
|
||||
else:
|
||||
# Auch die Datei entfernen - sonst bliebe auf einem abgewaehlten
|
||||
# Screen eine verwaiste Kopie liegen, die bei leerer Playlist
|
||||
# sogar noch abgespielt wuerde.
|
||||
_remove_welcome_file(site, name)
|
||||
|
||||
save_config(config)
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@
|
||||
<ul>
|
||||
<li>Aufruf über <code>/willkommen?site=<standort></code> (oder Menüpunkt)</li>
|
||||
<li><strong>Zielscreens:</strong> Die generierte <code>welcome.html</code> wird automatisch an den Anfang der Playlist <em>jedes</em> Screens gesetzt, bei dem im Tab "Einstellungen" die Option <em>Willkommensseite hier anzeigen</em> aktiv ist. So lässt sich dieselbe Begrüßung auf mehreren Screens zeigen, ohne sie mehrfach anzulegen. Ist an einem Standort nichts eingestellt, geht sie wie bisher in die Lobby.</li>
|
||||
<li>Wird ein Screen später abgewählt, verschwindet die Willkommensseite dort beim nächsten Generieren automatisch.</li>
|
||||
<li><strong>Der Schalter wirkt sofort:</strong> Schaltest du ihn bei einem weiteren Screen ein, erscheint eine bereits erzeugte Willkommensseite dort umgehend in der Playlist – ohne dass du sie neu generieren musst. Schaltest du ihn aus, verschwindet sie dort wieder.</li>
|
||||
<li><strong>Anzeigen bis:</strong> Optionales Datum im Formular. Ist es gesetzt, verschwindet die Willkommensseite nach diesem Tag auf allen Screens von selbst – kein manuelles Aufräumen nach dem Besuch. Ohne Angabe bleibt sie stehen.</li>
|
||||
<li><strong>Logo hochladen:</strong> Findet die automatische Suche kein Logo oder passt es nicht, kann pro Kunde eine eigene Bilddatei hochgeladen werden. Ein Upload hat immer Vorrang vor der automatischen Suche.</li>
|
||||
<li><strong>Freistellen:</strong> Der Hintergrund eines hochgeladenen Logos wird automatisch entfernt, damit es auf der Willkommensseite nicht in einem weissen Kasten steht. Das gelingt am besten bei einem Logo auf einfarbigem Grund – ein PNG mit bereits transparentem Hintergrund wird unverändert übernommen. Bei unruhigem Bildrand bleibt das Bild unangetastet und es erscheint ein Hinweis. Mit „Hintergrund behalten" lässt sich das Freistellen abschalten.</li>
|
||||
|
||||
Reference in New Issue
Block a user