Backoffice complet : materiel, compteur, destinations, cadres, animations
- Onglet Materiel : choix appareil photo + imprimante - Compteur avec limite configurable (399/400) affiche sur l'accueil - Destinations multiples : memoire interne, cle USB, FTP, site web, email - Choix sauvegarder toutes les photos ou seulement les imprimees - Gestion des cadres : activation/desactivation + import - 7 animations de compte a rebours : classique, explosion, rebond, rotation 3D, fondu, emoji fun, tremblement - Preview des animations dans le backoffice - Module destinations.py (copie USB, envoi FTP, compteur) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
114
backend/destinations.py
Normal file
114
backend/destinations.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import ftplib
|
||||||
|
import logging
|
||||||
|
import shutil
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from backend.config import charger_config, mettre_a_jour_config
|
||||||
|
|
||||||
|
log = logging.getLogger("photobooth.destinations")
|
||||||
|
|
||||||
|
|
||||||
|
def distribuer_photo(chemin_photo: Path, imprimee: bool = False):
|
||||||
|
"""Copie la photo vers toutes les destinations activees."""
|
||||||
|
config = charger_config()
|
||||||
|
dest = config.get("destinations", {})
|
||||||
|
|
||||||
|
# Verifier si on sauvegarde tout ou seulement les imprimees
|
||||||
|
if not dest.get("sauvegarder_tout", True) and not imprimee:
|
||||||
|
log.info(f"Photo non imprimee, pas de distribution : {chemin_photo.name}")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Cle USB
|
||||||
|
if dest.get("cle_usb", False):
|
||||||
|
copier_usb(chemin_photo, dest.get("chemin_usb", "/media/usb"))
|
||||||
|
|
||||||
|
# FTP
|
||||||
|
if dest.get("ftp", False):
|
||||||
|
envoyer_ftp(chemin_photo, dest)
|
||||||
|
|
||||||
|
# Incrementer le compteur
|
||||||
|
compteur = config.get("compteur", {})
|
||||||
|
if compteur.get("actif", False):
|
||||||
|
compteur["photos_prises"] = compteur.get("photos_prises", 0) + 1
|
||||||
|
mettre_a_jour_config({"compteur": compteur})
|
||||||
|
|
||||||
|
|
||||||
|
def copier_usb(chemin_photo: Path, chemin_usb: str):
|
||||||
|
"""Copie une photo sur la cle USB."""
|
||||||
|
dossier_usb = Path(chemin_usb)
|
||||||
|
if not dossier_usb.exists():
|
||||||
|
log.warning(f"Cle USB non trouvee : {chemin_usb}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
dossier_dest = dossier_usb / "photobooth"
|
||||||
|
dossier_dest.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
shutil.copy2(chemin_photo, dossier_dest / chemin_photo.name)
|
||||||
|
log.info(f"Photo copiee sur USB : {chemin_photo.name}")
|
||||||
|
return True
|
||||||
|
except OSError as e:
|
||||||
|
log.error(f"Erreur copie USB : {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def envoyer_ftp(chemin_photo: Path, config_dest: dict):
|
||||||
|
"""Envoie une photo par FTP."""
|
||||||
|
host = config_dest.get("ftp_host", "")
|
||||||
|
port = config_dest.get("ftp_port", 21)
|
||||||
|
user = config_dest.get("ftp_user", "")
|
||||||
|
password = config_dest.get("ftp_password", "")
|
||||||
|
chemin_distant = config_dest.get("ftp_chemin", "/photobooth")
|
||||||
|
|
||||||
|
if not host:
|
||||||
|
log.warning("FTP non configure")
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with ftplib.FTP() as ftp:
|
||||||
|
ftp.connect(host, port, timeout=10)
|
||||||
|
ftp.login(user, password)
|
||||||
|
# Creer le dossier si necessaire
|
||||||
|
try:
|
||||||
|
ftp.mkd(chemin_distant)
|
||||||
|
except ftplib.error_perm:
|
||||||
|
pass
|
||||||
|
ftp.cwd(chemin_distant)
|
||||||
|
with open(chemin_photo, "rb") as f:
|
||||||
|
ftp.storbinary(f"STOR {chemin_photo.name}", f)
|
||||||
|
log.info(f"Photo envoyee par FTP : {chemin_photo.name}")
|
||||||
|
return True
|
||||||
|
except (ftplib.all_errors, OSError) as e:
|
||||||
|
log.error(f"Erreur FTP : {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def detecter_usb() -> list[str]:
|
||||||
|
"""Detecte les cles USB montees."""
|
||||||
|
chemins_possibles = [Path("/media"), Path("/mnt")]
|
||||||
|
usb_trouvees = []
|
||||||
|
for racine in chemins_possibles:
|
||||||
|
if racine.exists():
|
||||||
|
for sous_dossier in racine.iterdir():
|
||||||
|
if sous_dossier.is_mount() or (sous_dossier.is_dir() and any(sous_dossier.iterdir())):
|
||||||
|
usb_trouvees.append(str(sous_dossier))
|
||||||
|
return usb_trouvees
|
||||||
|
|
||||||
|
|
||||||
|
def compteur_restant() -> dict:
|
||||||
|
"""Retourne l'etat du compteur."""
|
||||||
|
config = charger_config()
|
||||||
|
compteur = config.get("compteur", {})
|
||||||
|
limite = compteur.get("limite", 400)
|
||||||
|
prises = compteur.get("photos_prises", 0)
|
||||||
|
return {
|
||||||
|
"actif": compteur.get("actif", False),
|
||||||
|
"limite": limite,
|
||||||
|
"photos_prises": prises,
|
||||||
|
"restantes": max(0, limite - prises),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def reset_compteur():
|
||||||
|
"""Remet le compteur a zero."""
|
||||||
|
mettre_a_jour_config({"compteur": {"photos_prises": 0}})
|
||||||
@@ -17,7 +17,7 @@ from backend.camera import camera
|
|||||||
from backend.gallery import lister_photos, compter_photos, supprimer_photo, vider_galerie
|
from backend.gallery import lister_photos, compter_photos, supprimer_photo, vider_galerie
|
||||||
from backend.effects import appliquer_filtre, appliquer_overlay, chroma_key, lister_overlays, lister_fonds, FILTRES
|
from backend.effects import appliquer_filtre, appliquer_overlay, chroma_key, lister_overlays, lister_fonds, FILTRES
|
||||||
from backend.collage import creer_strip, creer_collage
|
from backend.collage import creer_strip, creer_collage
|
||||||
|
from backend.destinations import distribuer_photo, detecter_usb, compteur_restant, reset_compteur
|
||||||
from backend.printer import lister_imprimantes, imprimer
|
from backend.printer import lister_imprimantes, imprimer
|
||||||
from backend.mailer import envoyer_photo
|
from backend.mailer import envoyer_photo
|
||||||
from backend.qrcode_gen import generer_qr, qr_galerie
|
from backend.qrcode_gen import generer_qr, qr_galerie
|
||||||
@@ -73,10 +73,19 @@ async def api_config_update(modifications: dict):
|
|||||||
|
|
||||||
@app.post("/api/capturer")
|
@app.post("/api/capturer")
|
||||||
async def api_capturer():
|
async def api_capturer():
|
||||||
|
# Verifier le compteur
|
||||||
|
etat = compteur_restant()
|
||||||
|
if etat["actif"] and etat["restantes"] <= 0:
|
||||||
|
return JSONResponse({"erreur": "Limite de photos atteinte"}, status_code=403)
|
||||||
|
|
||||||
chemin = camera.capturer()
|
chemin = camera.capturer()
|
||||||
if chemin is None:
|
if chemin is None:
|
||||||
return JSONResponse({"erreur": "Echec capture"}, status_code=500)
|
return JSONResponse({"erreur": "Echec capture"}, status_code=500)
|
||||||
nom = chemin.name
|
nom = chemin.name
|
||||||
|
|
||||||
|
# Distribuer vers les destinations
|
||||||
|
distribuer_photo(chemin, imprimee=False)
|
||||||
|
|
||||||
await diffuser_ws({"type": "photo_capturee", "nom": nom})
|
await diffuser_ws({"type": "photo_capturee", "nom": nom})
|
||||||
return {"nom": nom, "chemin": f"/data/photos/{nom}"}
|
return {"nom": nom, "chemin": f"/data/photos/{nom}"}
|
||||||
|
|
||||||
@@ -206,6 +215,8 @@ async def api_imprimer(donnees: dict):
|
|||||||
if not chemin.exists():
|
if not chemin.exists():
|
||||||
return JSONResponse({"erreur": "Photo introuvable"}, status_code=404)
|
return JSONResponse({"erreur": "Photo introuvable"}, status_code=404)
|
||||||
ok = imprimer(chemin)
|
ok = imprimer(chemin)
|
||||||
|
if ok:
|
||||||
|
distribuer_photo(chemin, imprimee=True)
|
||||||
return {"succes": ok}
|
return {"succes": ok}
|
||||||
|
|
||||||
|
|
||||||
@@ -278,6 +289,60 @@ async def api_upload_fond(fichier: UploadFile = File(...)):
|
|||||||
return {"nom": fichier.filename}
|
return {"nom": fichier.filename}
|
||||||
|
|
||||||
|
|
||||||
|
# --- API Compteur ---
|
||||||
|
|
||||||
|
@app.get("/api/compteur")
|
||||||
|
async def api_compteur_etat():
|
||||||
|
return compteur_restant()
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/compteur/reset")
|
||||||
|
async def api_compteur_reset():
|
||||||
|
reset_compteur()
|
||||||
|
return compteur_restant()
|
||||||
|
|
||||||
|
|
||||||
|
# --- API Destinations ---
|
||||||
|
|
||||||
|
@app.get("/api/usb/detecter")
|
||||||
|
async def api_detecter_usb():
|
||||||
|
return detecter_usb()
|
||||||
|
|
||||||
|
|
||||||
|
# --- API Cadres actifs ---
|
||||||
|
|
||||||
|
@app.get("/api/cadres")
|
||||||
|
async def api_cadres():
|
||||||
|
config = charger_config()
|
||||||
|
tous = lister_overlays()
|
||||||
|
actifs = config.get("cadres", {}).get("actifs", [])
|
||||||
|
return {"tous": tous, "actifs": actifs}
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/cadres")
|
||||||
|
async def api_cadres_update(donnees: dict):
|
||||||
|
actifs = donnees.get("actifs", [])
|
||||||
|
mettre_a_jour_config({"cadres": {"actifs": actifs}})
|
||||||
|
return {"actifs": actifs}
|
||||||
|
|
||||||
|
|
||||||
|
# --- API Animations compte a rebours ---
|
||||||
|
|
||||||
|
ANIMATIONS_CAR = {
|
||||||
|
"classique": "Classique (chiffres simples)",
|
||||||
|
"explosion": "Explosion (chiffres qui eclatent)",
|
||||||
|
"rebond": "Rebond (chiffres qui rebondissent)",
|
||||||
|
"rotation": "Rotation 3D",
|
||||||
|
"fondu": "Fondu enchaine",
|
||||||
|
"emoji": "Emoji fun",
|
||||||
|
"shake": "Tremblement",
|
||||||
|
}
|
||||||
|
|
||||||
|
@app.get("/api/animations")
|
||||||
|
async def api_animations():
|
||||||
|
return ANIMATIONS_CAR
|
||||||
|
|
||||||
|
|
||||||
# --- API Systeme ---
|
# --- API Systeme ---
|
||||||
|
|
||||||
@app.post("/api/systeme/redemarrer")
|
@app.post("/api/systeme/redemarrer")
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
"evenement": {
|
"evenement": {
|
||||||
"nom": "Mon Evenement",
|
"nom": "Mon Evenement",
|
||||||
"logo": null,
|
"logo": null,
|
||||||
"overlay": null,
|
|
||||||
"couleur_primaire": "#e91e63",
|
"couleur_primaire": "#e91e63",
|
||||||
"couleur_secondaire": "#ffffff"
|
"couleur_secondaire": "#ffffff"
|
||||||
},
|
},
|
||||||
@@ -15,19 +14,43 @@
|
|||||||
"impression": true,
|
"impression": true,
|
||||||
"email": true,
|
"email": true,
|
||||||
"qr_code": true,
|
"qr_code": true,
|
||||||
"galerie": true,
|
"galerie": true
|
||||||
"compteur": true
|
|
||||||
},
|
},
|
||||||
"camera": {
|
"camera": {
|
||||||
|
"appareil": null,
|
||||||
"iso": "auto",
|
"iso": "auto",
|
||||||
"balance_blancs": "auto",
|
"balance_blancs": "auto",
|
||||||
"compte_a_rebours": 3
|
"compte_a_rebours": 3,
|
||||||
|
"animation_compte_a_rebours": "classique"
|
||||||
|
},
|
||||||
|
"compteur": {
|
||||||
|
"actif": true,
|
||||||
|
"limite": 400,
|
||||||
|
"photos_prises": 0
|
||||||
|
},
|
||||||
|
"destinations": {
|
||||||
|
"memoire_interne": true,
|
||||||
|
"cle_usb": false,
|
||||||
|
"chemin_usb": "/media/usb",
|
||||||
|
"ftp": false,
|
||||||
|
"ftp_host": "",
|
||||||
|
"ftp_port": 21,
|
||||||
|
"ftp_user": "",
|
||||||
|
"ftp_password": "",
|
||||||
|
"ftp_chemin": "/photobooth",
|
||||||
|
"site_web": false,
|
||||||
|
"site_web_url": "",
|
||||||
|
"email_auto": false,
|
||||||
|
"sauvegarder_tout": true
|
||||||
},
|
},
|
||||||
"impression": {
|
"impression": {
|
||||||
"imprimante": null,
|
"imprimante": null,
|
||||||
"copies": 1,
|
"copies": 1,
|
||||||
"format": "10x15"
|
"format": "10x15"
|
||||||
},
|
},
|
||||||
|
"cadres": {
|
||||||
|
"actifs": []
|
||||||
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"smtp_host": "",
|
"smtp_host": "",
|
||||||
"smtp_port": 587,
|
"smtp_port": 587,
|
||||||
|
|||||||
@@ -572,11 +572,291 @@ html, body {
|
|||||||
border-color: var(--primaire);
|
border-color: var(--primaire);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* === Compteur accueil === */
|
||||||
|
.compteur-accueil {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
background: rgba(0,0,0,0.4);
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
border-radius: 2rem;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--texte-secondaire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Admin extras === */
|
||||||
|
.btn-petit {
|
||||||
|
padding: 0.4rem 1rem !important;
|
||||||
|
font-size: 0.85rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.champ-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sous-config {
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
border-left: 3px solid var(--primaire);
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--primaire);
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aide {
|
||||||
|
color: var(--texte-secondaire);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statut-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.2rem 0.8rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
background: rgba(76, 175, 80, 0.2);
|
||||||
|
color: var(--succes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compteur display */
|
||||||
|
.compteur-display {
|
||||||
|
text-align: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
background: var(--fond);
|
||||||
|
border-radius: var(--rayon);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compteur-gros {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.compteur-nombre {
|
||||||
|
font-size: 4rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primaire);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compteur-nombre.petit {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--texte-secondaire);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compteur-sep {
|
||||||
|
font-size: 2rem;
|
||||||
|
color: var(--texte-secondaire);
|
||||||
|
}
|
||||||
|
|
||||||
|
.compteur-label {
|
||||||
|
color: var(--texte-secondaire);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Radio buttons */
|
||||||
|
.radio-groupe {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio input { display: none; }
|
||||||
|
|
||||||
|
.radio-mark {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
border: 2px solid #555;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio input:checked + .radio-mark {
|
||||||
|
border-color: var(--primaire);
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio input:checked + .radio-mark::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 4px; left: 4px;
|
||||||
|
width: 10px; height: 10px;
|
||||||
|
background: var(--primaire);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Liste cadres */
|
||||||
|
.liste-cadres {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cadre-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.6rem 1rem;
|
||||||
|
background: var(--fond);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cadre-item img {
|
||||||
|
width: 60px;
|
||||||
|
height: 40px;
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-fichier {
|
||||||
|
background: var(--fond);
|
||||||
|
border: 2px dashed #444;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 0.8rem;
|
||||||
|
color: var(--texte);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Liste animations */
|
||||||
|
.liste-animations {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.8rem 1rem;
|
||||||
|
background: var(--fond);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-option.actif {
|
||||||
|
border-color: var(--primaire);
|
||||||
|
background: rgba(233, 30, 99, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-option:active {
|
||||||
|
transform: scale(0.98);
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-animation-box {
|
||||||
|
width: 200px;
|
||||||
|
height: 200px;
|
||||||
|
background: rgba(0,0,0,0.6);
|
||||||
|
border-radius: var(--rayon);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.anim-chiffre {
|
||||||
|
font-size: 8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primaire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Animations compte a rebours === */
|
||||||
|
/* Classique */
|
||||||
|
.anim-classique { animation: anim-pop 0.5s ease; }
|
||||||
|
|
||||||
|
/* Explosion */
|
||||||
|
.anim-explosion { animation: anim-explosion 0.6s ease; }
|
||||||
|
@keyframes anim-explosion {
|
||||||
|
0% { transform: scale(0); opacity: 0; }
|
||||||
|
50% { transform: scale(1.8); opacity: 1; }
|
||||||
|
70% { transform: scale(0.9); }
|
||||||
|
100% { transform: scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rebond */
|
||||||
|
.anim-rebond { animation: anim-rebond 0.7s ease; }
|
||||||
|
@keyframes anim-rebond {
|
||||||
|
0% { transform: translateY(-200px) scale(0.5); opacity: 0; }
|
||||||
|
40% { transform: translateY(20px) scale(1.1); opacity: 1; }
|
||||||
|
60% { transform: translateY(-10px) scale(0.95); }
|
||||||
|
80% { transform: translateY(5px) scale(1.02); }
|
||||||
|
100% { transform: translateY(0) scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Rotation 3D */
|
||||||
|
.anim-rotation { animation: anim-rotation 0.6s ease; perspective: 500px; }
|
||||||
|
@keyframes anim-rotation {
|
||||||
|
0% { transform: rotateY(90deg) scale(0.5); opacity: 0; }
|
||||||
|
60% { transform: rotateY(-10deg) scale(1.1); opacity: 1; }
|
||||||
|
100% { transform: rotateY(0deg) scale(1); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Fondu */
|
||||||
|
.anim-fondu { animation: anim-fondu 0.8s ease; }
|
||||||
|
@keyframes anim-fondu {
|
||||||
|
0% { opacity: 0; transform: scale(2); filter: blur(20px); }
|
||||||
|
100% { opacity: 1; transform: scale(1); filter: blur(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Emoji fun */
|
||||||
|
.anim-emoji { animation: anim-emoji 0.6s ease; }
|
||||||
|
@keyframes anim-emoji {
|
||||||
|
0% { transform: scale(0) rotate(-180deg); }
|
||||||
|
60% { transform: scale(1.3) rotate(10deg); }
|
||||||
|
100% { transform: scale(1) rotate(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Shake */
|
||||||
|
.anim-shake { animation: anim-shake 0.6s ease; }
|
||||||
|
@keyframes anim-shake {
|
||||||
|
0% { transform: scale(0.3); opacity: 0; }
|
||||||
|
20% { transform: scale(1.1) rotate(-5deg); opacity: 1; }
|
||||||
|
40% { transform: scale(1) rotate(5deg); }
|
||||||
|
60% { transform: scale(1.05) rotate(-3deg); }
|
||||||
|
80% { transform: scale(1) rotate(2deg); }
|
||||||
|
100% { transform: scale(1) rotate(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pop (classique redefini) */
|
||||||
|
@keyframes anim-pop {
|
||||||
|
0% { transform: scale(0.3); opacity: 0; }
|
||||||
|
60% { transform: scale(1.2); }
|
||||||
|
100% { transform: scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Textes emoji pour le mode emoji */
|
||||||
|
.emoji-3::before { content: ""; }
|
||||||
|
.emoji-2::before { content: ""; }
|
||||||
|
.emoji-1::before { content: ""; }
|
||||||
|
|
||||||
/* === Utilitaires === */
|
/* === Utilitaires === */
|
||||||
.cache {
|
.cache {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.texte-secondaire {
|
||||||
|
color: var(--texte-secondaire);
|
||||||
|
}
|
||||||
|
|
||||||
/* Scrollbar tactile */
|
/* Scrollbar tactile */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
<div class="accueil-animation">
|
<div class="accueil-animation">
|
||||||
<div class="cercle-pulse"></div>
|
<div class="cercle-pulse"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="compteur-accueil" class="compteur-accueil cache">
|
||||||
|
<span id="compteur-restant">400</span> / <span id="compteur-limite">400</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Zone admin : coin bas-droit, appui long 3s -->
|
<!-- Zone admin : coin bas-droit, appui long 3s -->
|
||||||
<div id="zone-admin" class="zone-admin"></div>
|
<div id="zone-admin" class="zone-admin"></div>
|
||||||
@@ -58,10 +61,8 @@
|
|||||||
<img id="photo-resultat" src="" alt="Photo">
|
<img id="photo-resultat" src="" alt="Photo">
|
||||||
</div>
|
</div>
|
||||||
<div id="barre-filtres" class="barre-filtres">
|
<div id="barre-filtres" class="barre-filtres">
|
||||||
<!-- Rempli dynamiquement -->
|
|
||||||
</div>
|
</div>
|
||||||
<div id="barre-overlays" class="barre-overlays cache">
|
<div id="barre-overlays" class="barre-overlays cache">
|
||||||
<!-- Rempli dynamiquement -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="preview-actions">
|
<div class="preview-actions">
|
||||||
<button class="btn-action" onclick="allerA('partage')">Valider</button>
|
<button class="btn-action" onclick="allerA('partage')">Valider</button>
|
||||||
@@ -89,17 +90,14 @@
|
|||||||
<span>QR Code</span>
|
<span>QR Code</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Formulaire email -->
|
|
||||||
<div id="form-email" class="form-email cache">
|
<div id="form-email" class="form-email cache">
|
||||||
<input type="email" id="input-email" placeholder="votre@email.com" autocomplete="off">
|
<input type="email" id="input-email" placeholder="votre@email.com" autocomplete="off">
|
||||||
<button class="btn-action" onclick="envoyerEmail()">Envoyer</button>
|
<button class="btn-action" onclick="envoyerEmail()">Envoyer</button>
|
||||||
<button class="btn-secondaire" onclick="fermerEmail()">Annuler</button>
|
<button class="btn-secondaire" onclick="fermerEmail()">Annuler</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- QR Code -->
|
|
||||||
<div id="zone-qr" class="zone-qr cache">
|
<div id="zone-qr" class="zone-qr cache">
|
||||||
<img id="img-qr" src="" alt="QR Code">
|
<img id="img-qr" src="" alt="QR Code">
|
||||||
</div>
|
</div>
|
||||||
<!-- Message statut -->
|
|
||||||
<div id="statut-partage" class="statut-partage cache"></div>
|
<div id="statut-partage" class="statut-partage cache"></div>
|
||||||
<div class="partage-bas">
|
<div class="partage-bas">
|
||||||
<button class="btn-action" onclick="allerA('accueil')">Terminer</button>
|
<button class="btn-action" onclick="allerA('accueil')">Terminer</button>
|
||||||
@@ -110,43 +108,154 @@
|
|||||||
<!-- Galerie -->
|
<!-- Galerie -->
|
||||||
<section id="ecran-galerie" class="ecran">
|
<section id="ecran-galerie" class="ecran">
|
||||||
<h2>Galerie</h2>
|
<h2>Galerie</h2>
|
||||||
<div id="grille-galerie" class="grille-galerie">
|
<div id="grille-galerie" class="grille-galerie"></div>
|
||||||
<!-- Rempli dynamiquement -->
|
|
||||||
</div>
|
|
||||||
<button class="btn-retour" onclick="allerA('accueil')">Retour</button>
|
<button class="btn-retour" onclick="allerA('accueil')">Retour</button>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Menu Admin -->
|
<!-- Menu Admin / Backoffice -->
|
||||||
<section id="ecran-admin" class="ecran">
|
<section id="ecran-admin" class="ecran">
|
||||||
<div class="admin-header">
|
<div class="admin-header">
|
||||||
<h2>Administration</h2>
|
<h2>Parametres</h2>
|
||||||
<button class="btn-fermer" onclick="allerA('accueil')">×</button>
|
<button class="btn-fermer" onclick="allerA('accueil')">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="admin-contenu">
|
<div class="admin-contenu">
|
||||||
<!-- Onglets -->
|
|
||||||
<div class="admin-onglets">
|
<div class="admin-onglets">
|
||||||
<button class="onglet actif" data-onglet="fonctionnalites">Fonctions</button>
|
<button class="onglet actif" data-onglet="materiel">Materiel</button>
|
||||||
|
<button class="onglet" data-onglet="compteur-admin">Compteur</button>
|
||||||
|
<button class="onglet" data-onglet="destinations-admin">Destinations</button>
|
||||||
|
<button class="onglet" data-onglet="cadres-admin">Cadres</button>
|
||||||
|
<button class="onglet" data-onglet="animations-admin">Animation 3-2-1</button>
|
||||||
<button class="onglet" data-onglet="evenement">Evenement</button>
|
<button class="onglet" data-onglet="evenement">Evenement</button>
|
||||||
<button class="onglet" data-onglet="camera-admin">Camera</button>
|
<button class="onglet" data-onglet="fonctionnalites">Fonctions</button>
|
||||||
<button class="onglet" data-onglet="impression-admin">Impression</button>
|
|
||||||
<button class="onglet" data-onglet="email-admin">Email</button>
|
<button class="onglet" data-onglet="email-admin">Email</button>
|
||||||
<button class="onglet" data-onglet="galerie-admin">Galerie</button>
|
<button class="onglet" data-onglet="galerie-admin">Galerie</button>
|
||||||
<button class="onglet" data-onglet="systeme">Systeme</button>
|
<button class="onglet" data-onglet="systeme">Systeme</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Panneau Fonctionnalites -->
|
<!-- Panneau Materiel (Camera + Imprimante) -->
|
||||||
<div class="admin-panneau actif" id="panneau-fonctionnalites">
|
<div class="admin-panneau actif" id="panneau-materiel">
|
||||||
|
<h3>Appareil photo</h3>
|
||||||
|
<div class="champ">
|
||||||
|
<label>Statut</label>
|
||||||
|
<span id="admin-camera-statut" class="statut-badge">--</span>
|
||||||
|
<button class="btn-secondaire btn-petit" onclick="reconnecterCamera()">Reconnecter</button>
|
||||||
|
</div>
|
||||||
|
<div class="champ">
|
||||||
|
<label>Appareil detecte</label>
|
||||||
|
<select id="admin-appareil"><option value="">Detection...</option></select>
|
||||||
|
</div>
|
||||||
|
<div class="champ">
|
||||||
|
<label>Compte a rebours (secondes)</label>
|
||||||
|
<input type="number" id="admin-car" min="1" max="10" value="3">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Imprimante</h3>
|
||||||
|
<div class="champ">
|
||||||
|
<label>Imprimante</label>
|
||||||
|
<select id="admin-imprimante"><option value="">Detection...</option></select>
|
||||||
|
<button class="btn-secondaire btn-petit" onclick="rafraichirImprimantes()">Rafraichir</button>
|
||||||
|
</div>
|
||||||
|
<div class="champ">
|
||||||
|
<label>Copies par impression</label>
|
||||||
|
<input type="number" id="admin-copies" min="1" max="5" value="1">
|
||||||
|
</div>
|
||||||
|
<button class="btn-action" onclick="sauvegarderMateriel()">Sauvegarder</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Panneau Compteur -->
|
||||||
|
<div class="admin-panneau" id="panneau-compteur-admin">
|
||||||
|
<h3>Compteur de photos</h3>
|
||||||
|
<div class="champ">
|
||||||
|
<label class="toggle"><input type="checkbox" id="tog-compteur-actif"><span class="toggle-slider"></span> Compteur actif</label>
|
||||||
|
</div>
|
||||||
|
<div class="compteur-display">
|
||||||
|
<div class="compteur-gros">
|
||||||
|
<span id="admin-compteur-restant" class="compteur-nombre">400</span>
|
||||||
|
<span class="compteur-sep">/</span>
|
||||||
|
<span id="admin-compteur-limite-display" class="compteur-nombre petit">400</span>
|
||||||
|
</div>
|
||||||
|
<span class="compteur-label">photos restantes</span>
|
||||||
|
</div>
|
||||||
|
<div class="champ">
|
||||||
|
<label>Limite totale</label>
|
||||||
|
<input type="number" id="admin-compteur-limite" min="1" max="9999" value="400">
|
||||||
|
</div>
|
||||||
|
<div class="champ-row">
|
||||||
|
<button class="btn-action" onclick="sauvegarderCompteur()">Sauvegarder</button>
|
||||||
|
<button class="btn-danger" onclick="resetCompteur()">Remettre a zero</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Panneau Destinations -->
|
||||||
|
<div class="admin-panneau" id="panneau-destinations-admin">
|
||||||
|
<h3>Ou sauvegarder les photos ?</h3>
|
||||||
<div class="toggle-groupe">
|
<div class="toggle-groupe">
|
||||||
<label class="toggle"><input type="checkbox" id="tog-photo-simple" checked><span class="toggle-slider"></span> Photo simple</label>
|
<label class="toggle"><input type="checkbox" id="tog-dest-memoire" checked><span class="toggle-slider"></span> Memoire interne</label>
|
||||||
<label class="toggle"><input type="checkbox" id="tog-multi-shot"><span class="toggle-slider"></span> Multi-shot</label>
|
<label class="toggle"><input type="checkbox" id="tog-dest-usb"><span class="toggle-slider"></span> Cle USB</label>
|
||||||
<label class="toggle"><input type="checkbox" id="tog-filtres"><span class="toggle-slider"></span> Filtres</label>
|
</div>
|
||||||
<label class="toggle"><input type="checkbox" id="tog-overlays"><span class="toggle-slider"></span> Overlays / Cadres</label>
|
<div id="dest-usb-details" class="sous-config cache">
|
||||||
<label class="toggle"><input type="checkbox" id="tog-chroma-key"><span class="toggle-slider"></span> Chroma key (fond vert)</label>
|
<div class="champ">
|
||||||
<label class="toggle"><input type="checkbox" id="tog-impression"><span class="toggle-slider"></span> Impression</label>
|
<label>Chemin USB</label>
|
||||||
<label class="toggle"><input type="checkbox" id="tog-email"><span class="toggle-slider"></span> Email</label>
|
<div class="champ-row">
|
||||||
<label class="toggle"><input type="checkbox" id="tog-qr-code"><span class="toggle-slider"></span> QR Code</label>
|
<select id="admin-chemin-usb"><option value="/media/usb">/media/usb</option></select>
|
||||||
<label class="toggle"><input type="checkbox" id="tog-galerie"><span class="toggle-slider"></span> Galerie</label>
|
<button class="btn-secondaire btn-petit" onclick="detecterUSB()">Detecter</button>
|
||||||
<label class="toggle"><input type="checkbox" id="tog-compteur"><span class="toggle-slider"></span> Compteur</label>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="toggle-groupe">
|
||||||
|
<label class="toggle"><input type="checkbox" id="tog-dest-ftp"><span class="toggle-slider"></span> Serveur FTP</label>
|
||||||
|
</div>
|
||||||
|
<div id="dest-ftp-details" class="sous-config cache">
|
||||||
|
<div class="champ"><label>Hote FTP</label><input type="text" id="admin-ftp-host" placeholder="ftp.exemple.com"></div>
|
||||||
|
<div class="champ"><label>Port</label><input type="number" id="admin-ftp-port" value="21"></div>
|
||||||
|
<div class="champ"><label>Utilisateur</label><input type="text" id="admin-ftp-user"></div>
|
||||||
|
<div class="champ"><label>Mot de passe</label><input type="password" id="admin-ftp-pass"></div>
|
||||||
|
<div class="champ"><label>Chemin distant</label><input type="text" id="admin-ftp-chemin" value="/photobooth"></div>
|
||||||
|
</div>
|
||||||
|
<div class="toggle-groupe">
|
||||||
|
<label class="toggle"><input type="checkbox" id="tog-dest-web"><span class="toggle-slider"></span> Site internet</label>
|
||||||
|
</div>
|
||||||
|
<div id="dest-web-details" class="sous-config cache">
|
||||||
|
<div class="champ"><label>URL d'upload</label><input type="text" id="admin-web-url" placeholder="https://..."></div>
|
||||||
|
</div>
|
||||||
|
<div class="toggle-groupe">
|
||||||
|
<label class="toggle"><input type="checkbox" id="tog-dest-email-auto"><span class="toggle-slider"></span> Email automatique</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Quelles photos sauvegarder ?</h3>
|
||||||
|
<div class="radio-groupe">
|
||||||
|
<label class="radio"><input type="radio" name="sauv-mode" id="sauv-tout" value="tout" checked><span class="radio-mark"></span> Toutes les photos</label>
|
||||||
|
<label class="radio"><input type="radio" name="sauv-mode" id="sauv-imprimees" value="imprimees"><span class="radio-mark"></span> Uniquement les photos imprimees</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn-action" onclick="sauvegarderDestinations()">Sauvegarder</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Panneau Cadres -->
|
||||||
|
<div class="admin-panneau" id="panneau-cadres-admin">
|
||||||
|
<h3>Cadres disponibles</h3>
|
||||||
|
<p class="aide">Cochez les cadres a proposer aux utilisateurs.</p>
|
||||||
|
<div id="liste-cadres" class="liste-cadres">
|
||||||
|
<p class="texte-secondaire">Aucun cadre importe</p>
|
||||||
|
</div>
|
||||||
|
<h3>Importer un cadre</h3>
|
||||||
|
<div class="champ">
|
||||||
|
<input type="file" id="input-cadre" accept=".png" class="input-fichier">
|
||||||
|
<button class="btn-action" onclick="uploaderCadre()">Importer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Panneau Animations -->
|
||||||
|
<div class="admin-panneau" id="panneau-animations-admin">
|
||||||
|
<h3>Animation du compte a rebours</h3>
|
||||||
|
<p class="aide">Choisissez le style d'animation pour le 3... 2... 1...</p>
|
||||||
|
<div id="liste-animations" class="liste-animations">
|
||||||
|
</div>
|
||||||
|
<div class="animation-preview">
|
||||||
|
<h4>Apercu</h4>
|
||||||
|
<div id="preview-animation" class="preview-animation-box">
|
||||||
|
<span id="preview-anim-chiffre" class="anim-chiffre">3</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn-secondaire" onclick="testerAnimation()">Tester</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -167,53 +276,28 @@
|
|||||||
<button class="btn-action" onclick="sauvegarderEvenement()">Sauvegarder</button>
|
<button class="btn-action" onclick="sauvegarderEvenement()">Sauvegarder</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Panneau Camera -->
|
<!-- Panneau Fonctionnalites -->
|
||||||
<div class="admin-panneau" id="panneau-camera-admin">
|
<div class="admin-panneau" id="panneau-fonctionnalites">
|
||||||
<div class="champ">
|
<div class="toggle-groupe">
|
||||||
<label>Statut</label>
|
<label class="toggle"><input type="checkbox" id="tog-photo-simple" checked><span class="toggle-slider"></span> Photo simple</label>
|
||||||
<span id="admin-camera-statut">--</span>
|
<label class="toggle"><input type="checkbox" id="tog-multi-shot"><span class="toggle-slider"></span> Multi-shot</label>
|
||||||
<button class="btn-secondaire" onclick="reconnecterCamera()">Reconnecter</button>
|
<label class="toggle"><input type="checkbox" id="tog-filtres"><span class="toggle-slider"></span> Filtres</label>
|
||||||
</div>
|
<label class="toggle"><input type="checkbox" id="tog-overlays"><span class="toggle-slider"></span> Overlays / Cadres</label>
|
||||||
<div class="champ">
|
<label class="toggle"><input type="checkbox" id="tog-chroma-key"><span class="toggle-slider"></span> Chroma key (fond vert)</label>
|
||||||
<label>Compte a rebours (secondes)</label>
|
<label class="toggle"><input type="checkbox" id="tog-impression"><span class="toggle-slider"></span> Impression</label>
|
||||||
<input type="number" id="admin-car" min="1" max="10" value="3">
|
<label class="toggle"><input type="checkbox" id="tog-email"><span class="toggle-slider"></span> Email</label>
|
||||||
</div>
|
<label class="toggle"><input type="checkbox" id="tog-qr-code"><span class="toggle-slider"></span> QR Code</label>
|
||||||
</div>
|
<label class="toggle"><input type="checkbox" id="tog-galerie"><span class="toggle-slider"></span> Galerie</label>
|
||||||
|
|
||||||
<!-- Panneau Impression -->
|
|
||||||
<div class="admin-panneau" id="panneau-impression-admin">
|
|
||||||
<div class="champ">
|
|
||||||
<label>Imprimante</label>
|
|
||||||
<select id="admin-imprimante"><option value="">Chargement...</option></select>
|
|
||||||
</div>
|
|
||||||
<div class="champ">
|
|
||||||
<label>Copies</label>
|
|
||||||
<input type="number" id="admin-copies" min="1" max="5" value="1">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Panneau Email -->
|
<!-- Panneau Email -->
|
||||||
<div class="admin-panneau" id="panneau-email-admin">
|
<div class="admin-panneau" id="panneau-email-admin">
|
||||||
<div class="champ">
|
<div class="champ"><label>Serveur SMTP</label><input type="text" id="admin-smtp-host" placeholder="smtp.gmail.com"></div>
|
||||||
<label>Serveur SMTP</label>
|
<div class="champ"><label>Port</label><input type="number" id="admin-smtp-port" value="587"></div>
|
||||||
<input type="text" id="admin-smtp-host" placeholder="smtp.gmail.com">
|
<div class="champ"><label>Utilisateur</label><input type="text" id="admin-smtp-user" placeholder="user@gmail.com"></div>
|
||||||
</div>
|
<div class="champ"><label>Mot de passe</label><input type="password" id="admin-smtp-pass"></div>
|
||||||
<div class="champ">
|
<div class="champ"><label>Expediteur</label><input type="text" id="admin-expediteur" placeholder="photobooth@event.com"></div>
|
||||||
<label>Port</label>
|
|
||||||
<input type="number" id="admin-smtp-port" value="587">
|
|
||||||
</div>
|
|
||||||
<div class="champ">
|
|
||||||
<label>Utilisateur</label>
|
|
||||||
<input type="text" id="admin-smtp-user" placeholder="user@gmail.com">
|
|
||||||
</div>
|
|
||||||
<div class="champ">
|
|
||||||
<label>Mot de passe</label>
|
|
||||||
<input type="password" id="admin-smtp-pass">
|
|
||||||
</div>
|
|
||||||
<div class="champ">
|
|
||||||
<label>Expediteur</label>
|
|
||||||
<input type="text" id="admin-expediteur" placeholder="photobooth@event.com">
|
|
||||||
</div>
|
|
||||||
<button class="btn-action" onclick="sauvegarderEmail()">Sauvegarder</button>
|
<button class="btn-action" onclick="sauvegarderEmail()">Sauvegarder</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* Module administration - Menu cache */
|
/* Module administration - Backoffice complet */
|
||||||
|
|
||||||
// Mapping toggle ID -> cle config
|
// Mapping toggle ID -> cle config fonctionnalites
|
||||||
const TOGGLES_MAP = {
|
const TOGGLES_MAP = {
|
||||||
'tog-photo-simple': 'photo_simple',
|
'tog-photo-simple': 'photo_simple',
|
||||||
'tog-multi-shot': 'multi_shot',
|
'tog-multi-shot': 'multi_shot',
|
||||||
@@ -11,36 +11,53 @@ const TOGGLES_MAP = {
|
|||||||
'tog-email': 'email',
|
'tog-email': 'email',
|
||||||
'tog-qr-code': 'qr_code',
|
'tog-qr-code': 'qr_code',
|
||||||
'tog-galerie': 'galerie',
|
'tog-galerie': 'galerie',
|
||||||
'tog-compteur': 'compteur',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function chargerAdmin() {
|
async function chargerAdmin() {
|
||||||
config = await apiGet('/api/config');
|
config = await apiGet('/api/config');
|
||||||
const fonc = config.fonctionnalites || {};
|
chargerMateriel();
|
||||||
const event = config.evenement || {};
|
chargerCompteur();
|
||||||
const cam = config.camera || {};
|
chargerDestinations();
|
||||||
const imp = config.impression || {};
|
chargerCadres();
|
||||||
const email = config.email || {};
|
chargerAnimations();
|
||||||
const qr = config.qr_code || {};
|
chargerFonctionnalites();
|
||||||
|
chargerEmailAdmin();
|
||||||
// Toggles fonctionnalites
|
chargerGalerieAdmin();
|
||||||
for (const [id, cle] of Object.entries(TOGGLES_MAP)) {
|
|
||||||
const el = document.getElementById(id);
|
|
||||||
if (el) el.checked = fonc[cle] !== false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evenement
|
// === MATERIEL ===
|
||||||
setValue('admin-nom-event', event.nom);
|
|
||||||
setValue('admin-couleur-primaire', event.couleur_primaire || '#e91e63');
|
async function chargerMateriel() {
|
||||||
setValue('admin-couleur-secondaire', event.couleur_secondaire || '#ffffff');
|
const cam = config.camera || {};
|
||||||
|
const imp = config.impression || {};
|
||||||
|
|
||||||
// Camera
|
// Camera
|
||||||
const statut = await apiGet('/api/camera/statut');
|
const statut = await apiGet('/api/camera/statut');
|
||||||
document.getElementById('admin-camera-statut').textContent =
|
const badge = document.getElementById('admin-camera-statut');
|
||||||
statut.connectee ? 'Connectee' : 'Deconnectee';
|
badge.textContent = statut.connectee ? 'Connectee' : 'Deconnectee';
|
||||||
|
badge.style.background = statut.connectee ? 'rgba(76,175,80,0.2)' : 'rgba(244,67,54,0.2)';
|
||||||
|
badge.style.color = statut.connectee ? 'var(--succes)' : 'var(--danger)';
|
||||||
|
|
||||||
|
// Liste appareils
|
||||||
|
const selectApp = document.getElementById('admin-appareil');
|
||||||
|
selectApp.innerHTML = '';
|
||||||
|
for (const a of statut.appareils) {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = a;
|
||||||
|
opt.textContent = a;
|
||||||
|
selectApp.appendChild(opt);
|
||||||
|
}
|
||||||
|
if (cam.appareil) selectApp.value = cam.appareil;
|
||||||
|
|
||||||
setValue('admin-car', cam.compte_a_rebours || 3);
|
setValue('admin-car', cam.compte_a_rebours || 3);
|
||||||
|
|
||||||
// Impression
|
// Imprimantes
|
||||||
|
await rafraichirImprimantes();
|
||||||
|
if (imp.imprimante) document.getElementById('admin-imprimante').value = imp.imprimante;
|
||||||
|
setValue('admin-copies', imp.copies || 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rafraichirImprimantes() {
|
||||||
const imprimantes = await apiGet('/api/imprimantes');
|
const imprimantes = await apiGet('/api/imprimantes');
|
||||||
const select = document.getElementById('admin-imprimante');
|
const select = document.getElementById('admin-imprimante');
|
||||||
select.innerHTML = '';
|
select.innerHTML = '';
|
||||||
@@ -50,23 +67,266 @@ async function chargerAdmin() {
|
|||||||
opt.textContent = `${p.nom} (${p.statut})`;
|
opt.textContent = `${p.nom} (${p.statut})`;
|
||||||
select.appendChild(opt);
|
select.appendChild(opt);
|
||||||
}
|
}
|
||||||
if (imp.imprimante) select.value = imp.imprimante;
|
}
|
||||||
setValue('admin-copies', imp.copies || 1);
|
|
||||||
|
|
||||||
// Email
|
async function reconnecterCamera() {
|
||||||
setValue('admin-smtp-host', email.smtp_host);
|
const resultat = await apiPost('/api/camera/reconnecter');
|
||||||
setValue('admin-smtp-port', email.smtp_port || 587);
|
chargerMateriel();
|
||||||
setValue('admin-smtp-user', email.smtp_user);
|
}
|
||||||
setValue('admin-smtp-pass', email.smtp_password);
|
|
||||||
setValue('admin-expediteur', email.expediteur);
|
|
||||||
|
|
||||||
// Galerie
|
async function sauvegarderMateriel() {
|
||||||
const compteur = await apiGet('/api/galerie/compteur');
|
await apiPost('/api/config', {
|
||||||
document.getElementById('admin-nb-photos').textContent = compteur.photos_prises || 0;
|
camera: {
|
||||||
document.getElementById('admin-nb-exports').textContent = compteur.exports || 0;
|
appareil: getValue('admin-appareil'),
|
||||||
setValue('admin-url-galerie', qr.url_galerie);
|
compte_a_rebours: parseInt(getValue('admin-car')) || 3,
|
||||||
|
},
|
||||||
|
impression: {
|
||||||
|
imprimante: getValue('admin-imprimante'),
|
||||||
|
copies: parseInt(getValue('admin-copies')) || 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
afficherStatut('Materiel sauvegarde', 'succes');
|
||||||
|
}
|
||||||
|
|
||||||
// Ecouter les changements de toggles
|
// === COMPTEUR ===
|
||||||
|
|
||||||
|
async function chargerCompteur() {
|
||||||
|
const etat = await apiGet('/api/compteur');
|
||||||
|
document.getElementById('tog-compteur-actif').checked = etat.actif;
|
||||||
|
document.getElementById('admin-compteur-restant').textContent = etat.restantes;
|
||||||
|
document.getElementById('admin-compteur-limite-display').textContent = etat.limite;
|
||||||
|
setValue('admin-compteur-limite', etat.limite);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sauvegarderCompteur() {
|
||||||
|
await apiPost('/api/config', {
|
||||||
|
compteur: {
|
||||||
|
actif: document.getElementById('tog-compteur-actif').checked,
|
||||||
|
limite: parseInt(getValue('admin-compteur-limite')) || 400,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
chargerCompteur();
|
||||||
|
afficherStatut('Compteur sauvegarde', 'succes');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resetCompteur() {
|
||||||
|
if (confirm('Remettre le compteur a zero ?')) {
|
||||||
|
await apiPost('/api/compteur/reset');
|
||||||
|
chargerCompteur();
|
||||||
|
afficherStatut('Compteur remis a zero', 'succes');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// === DESTINATIONS ===
|
||||||
|
|
||||||
|
function chargerDestinations() {
|
||||||
|
const dest = config.destinations || {};
|
||||||
|
|
||||||
|
document.getElementById('tog-dest-memoire').checked = dest.memoire_interne !== false;
|
||||||
|
document.getElementById('tog-dest-usb').checked = dest.cle_usb || false;
|
||||||
|
document.getElementById('tog-dest-ftp').checked = dest.ftp || false;
|
||||||
|
document.getElementById('tog-dest-web').checked = dest.site_web || false;
|
||||||
|
document.getElementById('tog-dest-email-auto').checked = dest.email_auto || false;
|
||||||
|
|
||||||
|
// Sous-configs visibles si actives
|
||||||
|
toggleSousConfig('tog-dest-usb', 'dest-usb-details');
|
||||||
|
toggleSousConfig('tog-dest-ftp', 'dest-ftp-details');
|
||||||
|
toggleSousConfig('tog-dest-web', 'dest-web-details');
|
||||||
|
|
||||||
|
setValue('admin-ftp-host', dest.ftp_host);
|
||||||
|
setValue('admin-ftp-port', dest.ftp_port || 21);
|
||||||
|
setValue('admin-ftp-user', dest.ftp_user);
|
||||||
|
setValue('admin-ftp-pass', dest.ftp_password);
|
||||||
|
setValue('admin-ftp-chemin', dest.ftp_chemin || '/photobooth');
|
||||||
|
setValue('admin-web-url', dest.site_web_url);
|
||||||
|
|
||||||
|
// Radio sauvegarder tout / imprimees
|
||||||
|
if (dest.sauvegarder_tout === false) {
|
||||||
|
document.getElementById('sauv-imprimees').checked = true;
|
||||||
|
} else {
|
||||||
|
document.getElementById('sauv-tout').checked = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listeners pour afficher/cacher sous-configs
|
||||||
|
['tog-dest-usb', 'tog-dest-ftp', 'tog-dest-web'].forEach(id => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
el.onchange = () => {
|
||||||
|
const map = { 'tog-dest-usb': 'dest-usb-details', 'tog-dest-ftp': 'dest-ftp-details', 'tog-dest-web': 'dest-web-details' };
|
||||||
|
toggleSousConfig(id, map[id]);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSousConfig(toggleId, detailsId) {
|
||||||
|
const checked = document.getElementById(toggleId).checked;
|
||||||
|
const el = document.getElementById(detailsId);
|
||||||
|
if (checked) el.classList.remove('cache');
|
||||||
|
else el.classList.add('cache');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function detecterUSB() {
|
||||||
|
const usbs = await apiGet('/api/usb/detecter');
|
||||||
|
const select = document.getElementById('admin-chemin-usb');
|
||||||
|
select.innerHTML = '';
|
||||||
|
if (usbs.length === 0) {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = '';
|
||||||
|
opt.textContent = 'Aucune cle detectee';
|
||||||
|
select.appendChild(opt);
|
||||||
|
} else {
|
||||||
|
for (const u of usbs) {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = u;
|
||||||
|
opt.textContent = u;
|
||||||
|
select.appendChild(opt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sauvegarderDestinations() {
|
||||||
|
await apiPost('/api/config', {
|
||||||
|
destinations: {
|
||||||
|
memoire_interne: document.getElementById('tog-dest-memoire').checked,
|
||||||
|
cle_usb: document.getElementById('tog-dest-usb').checked,
|
||||||
|
chemin_usb: getValue('admin-chemin-usb') || '/media/usb',
|
||||||
|
ftp: document.getElementById('tog-dest-ftp').checked,
|
||||||
|
ftp_host: getValue('admin-ftp-host'),
|
||||||
|
ftp_port: parseInt(getValue('admin-ftp-port')) || 21,
|
||||||
|
ftp_user: getValue('admin-ftp-user'),
|
||||||
|
ftp_password: getValue('admin-ftp-pass'),
|
||||||
|
ftp_chemin: getValue('admin-ftp-chemin') || '/photobooth',
|
||||||
|
site_web: document.getElementById('tog-dest-web').checked,
|
||||||
|
site_web_url: getValue('admin-web-url'),
|
||||||
|
email_auto: document.getElementById('tog-dest-email-auto').checked,
|
||||||
|
sauvegarder_tout: document.getElementById('sauv-tout').checked,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
afficherStatut('Destinations sauvegardees', 'succes');
|
||||||
|
}
|
||||||
|
|
||||||
|
// === CADRES ===
|
||||||
|
|
||||||
|
async function chargerCadres() {
|
||||||
|
const data = await apiGet('/api/cadres');
|
||||||
|
const liste = document.getElementById('liste-cadres');
|
||||||
|
liste.innerHTML = '';
|
||||||
|
|
||||||
|
if (data.tous.length === 0) {
|
||||||
|
liste.innerHTML = '<p class="texte-secondaire">Aucun cadre importe</p>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const nom of data.tous) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'cadre-item';
|
||||||
|
|
||||||
|
const cb = document.createElement('input');
|
||||||
|
cb.type = 'checkbox';
|
||||||
|
cb.checked = data.actifs.includes(nom);
|
||||||
|
cb.onchange = () => majCadresActifs();
|
||||||
|
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = `/assets/overlays/${nom}`;
|
||||||
|
img.alt = nom;
|
||||||
|
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.textContent = nom.replace('.png', '');
|
||||||
|
|
||||||
|
div.appendChild(cb);
|
||||||
|
div.appendChild(img);
|
||||||
|
div.appendChild(span);
|
||||||
|
liste.appendChild(div);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function majCadresActifs() {
|
||||||
|
const items = document.querySelectorAll('.cadre-item input[type="checkbox"]');
|
||||||
|
const noms = document.querySelectorAll('.cadre-item span');
|
||||||
|
const actifs = [];
|
||||||
|
items.forEach((cb, i) => {
|
||||||
|
if (cb.checked) actifs.push(noms[i].textContent + '.png');
|
||||||
|
});
|
||||||
|
await apiPost('/api/cadres', { actifs });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploaderCadre() {
|
||||||
|
const input = document.getElementById('input-cadre');
|
||||||
|
if (!input.files.length) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('fichier', input.files[0]);
|
||||||
|
|
||||||
|
await fetch('/api/upload/overlay', { method: 'POST', body: formData });
|
||||||
|
input.value = '';
|
||||||
|
chargerCadres();
|
||||||
|
afficherStatut('Cadre importe', 'succes');
|
||||||
|
}
|
||||||
|
|
||||||
|
// === ANIMATIONS ===
|
||||||
|
|
||||||
|
let animationChoisie = 'classique';
|
||||||
|
|
||||||
|
async function chargerAnimations() {
|
||||||
|
const anims = await apiGet('/api/animations');
|
||||||
|
animationChoisie = config.camera?.animation_compte_a_rebours || 'classique';
|
||||||
|
|
||||||
|
const liste = document.getElementById('liste-animations');
|
||||||
|
liste.innerHTML = '';
|
||||||
|
|
||||||
|
for (const [id, nom] of Object.entries(anims)) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.className = 'anim-option' + (id === animationChoisie ? ' actif' : '');
|
||||||
|
div.textContent = nom;
|
||||||
|
div.onclick = () => choisirAnimation(id, div);
|
||||||
|
liste.appendChild(div);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function choisirAnimation(id, el) {
|
||||||
|
animationChoisie = id;
|
||||||
|
document.querySelectorAll('.anim-option').forEach(e => e.classList.remove('actif'));
|
||||||
|
el.classList.add('actif');
|
||||||
|
|
||||||
|
await apiPost('/api/config', {
|
||||||
|
camera: { animation_compte_a_rebours: id },
|
||||||
|
});
|
||||||
|
|
||||||
|
testerAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
const EMOJI_MAP = { 3: '🤪', 2: '😱', 1: '🔥' };
|
||||||
|
|
||||||
|
function testerAnimation() {
|
||||||
|
const chiffre = document.getElementById('preview-anim-chiffre');
|
||||||
|
let count = 3;
|
||||||
|
|
||||||
|
function afficher() {
|
||||||
|
if (animationChoisie === 'emoji') {
|
||||||
|
chiffre.textContent = EMOJI_MAP[count] || count;
|
||||||
|
} else {
|
||||||
|
chiffre.textContent = count;
|
||||||
|
}
|
||||||
|
chiffre.className = 'anim-chiffre anim-' + animationChoisie;
|
||||||
|
// Force reflow pour relancer l'animation
|
||||||
|
chiffre.style.animation = 'none';
|
||||||
|
chiffre.offsetHeight;
|
||||||
|
chiffre.className = 'anim-chiffre anim-' + animationChoisie;
|
||||||
|
|
||||||
|
count--;
|
||||||
|
if (count > 0) setTimeout(afficher, 800);
|
||||||
|
}
|
||||||
|
|
||||||
|
afficher();
|
||||||
|
}
|
||||||
|
|
||||||
|
// === FONCTIONNALITES ===
|
||||||
|
|
||||||
|
function chargerFonctionnalites() {
|
||||||
|
const fonc = config.fonctionnalites || {};
|
||||||
|
for (const [id, cle] of Object.entries(TOGGLES_MAP)) {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) el.checked = fonc[cle] !== false;
|
||||||
|
}
|
||||||
setupToggleListeners();
|
setupToggleListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +334,6 @@ function setupToggleListeners() {
|
|||||||
for (const [id, cle] of Object.entries(TOGGLES_MAP)) {
|
for (const [id, cle] of Object.entries(TOGGLES_MAP)) {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (!el) continue;
|
if (!el) continue;
|
||||||
// Retirer les anciens listeners en clonant
|
|
||||||
const nouveau = el.cloneNode(true);
|
const nouveau = el.cloneNode(true);
|
||||||
el.parentNode.replaceChild(nouveau, el);
|
el.parentNode.replaceChild(nouveau, el);
|
||||||
nouveau.addEventListener('change', () => {
|
nouveau.addEventListener('change', () => {
|
||||||
@@ -83,6 +342,15 @@ function setupToggleListeners() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === EVENEMENT ===
|
||||||
|
|
||||||
|
function chargerEvenementAdmin() {
|
||||||
|
const event = config.evenement || {};
|
||||||
|
setValue('admin-nom-event', event.nom);
|
||||||
|
setValue('admin-couleur-primaire', event.couleur_primaire || '#e91e63');
|
||||||
|
setValue('admin-couleur-secondaire', event.couleur_secondaire || '#ffffff');
|
||||||
|
}
|
||||||
|
|
||||||
async function sauvegarderEvenement() {
|
async function sauvegarderEvenement() {
|
||||||
await apiPost('/api/config', {
|
await apiPost('/api/config', {
|
||||||
evenement: {
|
evenement: {
|
||||||
@@ -90,13 +358,21 @@ async function sauvegarderEvenement() {
|
|||||||
couleur_primaire: getValue('admin-couleur-primaire'),
|
couleur_primaire: getValue('admin-couleur-primaire'),
|
||||||
couleur_secondaire: getValue('admin-couleur-secondaire'),
|
couleur_secondaire: getValue('admin-couleur-secondaire'),
|
||||||
},
|
},
|
||||||
camera: {
|
|
||||||
compte_a_rebours: parseInt(getValue('admin-car')) || 3,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
afficherStatut('Evenement sauvegarde', 'succes');
|
afficherStatut('Evenement sauvegarde', 'succes');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === EMAIL ===
|
||||||
|
|
||||||
|
function chargerEmailAdmin() {
|
||||||
|
const email = config.email || {};
|
||||||
|
setValue('admin-smtp-host', email.smtp_host);
|
||||||
|
setValue('admin-smtp-port', email.smtp_port || 587);
|
||||||
|
setValue('admin-smtp-user', email.smtp_user);
|
||||||
|
setValue('admin-smtp-pass', email.smtp_password);
|
||||||
|
setValue('admin-expediteur', email.expediteur);
|
||||||
|
}
|
||||||
|
|
||||||
async function sauvegarderEmail() {
|
async function sauvegarderEmail() {
|
||||||
await apiPost('/api/config', {
|
await apiPost('/api/config', {
|
||||||
email: {
|
email: {
|
||||||
@@ -110,45 +386,43 @@ async function sauvegarderEmail() {
|
|||||||
afficherStatut('Email sauvegarde', 'succes');
|
afficherStatut('Email sauvegarde', 'succes');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === GALERIE ===
|
||||||
|
|
||||||
|
async function chargerGalerieAdmin() {
|
||||||
|
const compteur = await apiGet('/api/galerie/compteur');
|
||||||
|
document.getElementById('admin-nb-photos').textContent = compteur.photos_prises || 0;
|
||||||
|
document.getElementById('admin-nb-exports').textContent = compteur.exports || 0;
|
||||||
|
setValue('admin-url-galerie', (config.qr_code || {}).url_galerie);
|
||||||
|
}
|
||||||
|
|
||||||
async function sauvegarderGalerie() {
|
async function sauvegarderGalerie() {
|
||||||
await apiPost('/api/config', {
|
await apiPost('/api/config', {
|
||||||
qr_code: { url_galerie: getValue('admin-url-galerie') },
|
qr_code: { url_galerie: getValue('admin-url-galerie') },
|
||||||
impression: {
|
|
||||||
imprimante: getValue('admin-imprimante'),
|
|
||||||
copies: parseInt(getValue('admin-copies')) || 1,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
afficherStatut('Configuration sauvegardee', 'succes');
|
afficherStatut('Configuration sauvegardee', 'succes');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reconnecterCamera() {
|
|
||||||
const resultat = await apiPost('/api/camera/reconnecter');
|
|
||||||
document.getElementById('admin-camera-statut').textContent =
|
|
||||||
resultat.connectee ? 'Connectee' : 'Deconnectee';
|
|
||||||
}
|
|
||||||
|
|
||||||
function confirmerViderGalerie() {
|
function confirmerViderGalerie() {
|
||||||
if (confirm('Supprimer TOUTES les photos ? Cette action est irreversible.')) {
|
if (confirm('Supprimer TOUTES les photos ? Cette action est irreversible.')) {
|
||||||
apiPost('/api/galerie/vider').then(() => {
|
apiPost('/api/galerie/vider').then(() => {
|
||||||
afficherStatut('Galerie videe', 'succes');
|
afficherStatut('Galerie videe', 'succes');
|
||||||
chargerAdmin();
|
chargerGalerieAdmin();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === SYSTEME ===
|
||||||
|
|
||||||
function confirmerRedemarrage() {
|
function confirmerRedemarrage() {
|
||||||
if (confirm('Redemarrer le systeme ?')) {
|
if (confirm('Redemarrer le systeme ?')) apiPost('/api/systeme/redemarrer');
|
||||||
apiPost('/api/systeme/redemarrer');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function confirmerExtinction() {
|
function confirmerExtinction() {
|
||||||
if (confirm('Eteindre le systeme ?')) {
|
if (confirm('Eteindre le systeme ?')) apiPost('/api/systeme/eteindre');
|
||||||
apiPost('/api/systeme/eteindre');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utilitaires
|
// === Utilitaires ===
|
||||||
|
|
||||||
function setValue(id, val) {
|
function setValue(id, val) {
|
||||||
const el = document.getElementById(id);
|
const el = document.getElementById(id);
|
||||||
if (el) el.value = val || '';
|
if (el) el.value = val || '';
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ function allerA(ecran) {
|
|||||||
photosSession = [];
|
photosSession = [];
|
||||||
photoFinale = null;
|
photoFinale = null;
|
||||||
arreterPreview();
|
arreterPreview();
|
||||||
|
majCompteurAccueil();
|
||||||
} else if (ecran === 'capture') {
|
} else if (ecran === 'capture') {
|
||||||
lancerCapture();
|
lancerCapture();
|
||||||
} else if (ecran === 'galerie') {
|
} else if (ecran === 'galerie') {
|
||||||
|
|||||||
@@ -3,16 +3,17 @@
|
|||||||
let previewActif = false;
|
let previewActif = false;
|
||||||
let previewInterval = null;
|
let previewInterval = null;
|
||||||
|
|
||||||
|
const EMOJI_CAR = { 3: '🤪', 2: '😱', 1: '🔥' };
|
||||||
|
|
||||||
// --- Preview live ---
|
// --- Preview live ---
|
||||||
|
|
||||||
function lancerPreview() {
|
function lancerPreview() {
|
||||||
if (previewActif) return;
|
if (previewActif) return;
|
||||||
previewActif = true;
|
previewActif = true;
|
||||||
|
|
||||||
// Demander des previews via WebSocket
|
|
||||||
previewInterval = setInterval(() => {
|
previewInterval = setInterval(() => {
|
||||||
if (previewActif) wsEnvoyer({ type: 'preview' });
|
if (previewActif) wsEnvoyer({ type: 'preview' });
|
||||||
}, 100); // ~10 fps
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
function arreterPreview() {
|
function arreterPreview() {
|
||||||
@@ -23,7 +24,6 @@ function arreterPreview() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recevoir les previews
|
|
||||||
wsOnMessage('preview', (msg) => {
|
wsOnMessage('preview', (msg) => {
|
||||||
if (!previewActif) return;
|
if (!previewActif) return;
|
||||||
const img = document.getElementById('img-preview');
|
const img = document.getElementById('img-preview');
|
||||||
@@ -35,6 +35,7 @@ wsOnMessage('preview', (msg) => {
|
|||||||
async function lancerCapture() {
|
async function lancerCapture() {
|
||||||
photosSession = [];
|
photosSession = [];
|
||||||
lancerPreview();
|
lancerPreview();
|
||||||
|
majCompteurAccueil();
|
||||||
|
|
||||||
const nbPhotos = getNombrePhotos();
|
const nbPhotos = getNombrePhotos();
|
||||||
const compteurEl = document.getElementById('capture-compteur');
|
const compteurEl = document.getElementById('capture-compteur');
|
||||||
@@ -44,27 +45,28 @@ async function lancerCapture() {
|
|||||||
compteurEl.textContent = `Photo ${i + 1} / ${nbPhotos}`;
|
compteurEl.textContent = `Photo ${i + 1} / ${nbPhotos}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compte a rebours
|
|
||||||
await compteARebours();
|
await compteARebours();
|
||||||
|
|
||||||
// Flash
|
|
||||||
afficherFlash();
|
afficherFlash();
|
||||||
|
|
||||||
// Capture
|
|
||||||
arreterPreview();
|
arreterPreview();
|
||||||
const resultat = await apiPost('/api/capturer');
|
const resultat = await apiPost('/api/capturer');
|
||||||
|
|
||||||
|
if (resultat.erreur) {
|
||||||
|
// Limite atteinte ou erreur
|
||||||
|
allerA('accueil');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (resultat.nom) {
|
if (resultat.nom) {
|
||||||
photosSession.push(resultat.nom);
|
photosSession.push(resultat.nom);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reprendre le preview si encore des photos a prendre
|
|
||||||
if (i < nbPhotos - 1) {
|
if (i < nbPhotos - 1) {
|
||||||
lancerPreview();
|
lancerPreview();
|
||||||
await pause(500);
|
await pause(500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traitement selon le mode
|
|
||||||
await traiterCapture();
|
await traiterCapture();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,15 +80,24 @@ async function compteARebours() {
|
|||||||
const conteneur = document.getElementById('compte-a-rebours');
|
const conteneur = document.getElementById('compte-a-rebours');
|
||||||
const chiffre = document.getElementById('chiffre-car');
|
const chiffre = document.getElementById('chiffre-car');
|
||||||
const duree = config.camera?.compte_a_rebours || 3;
|
const duree = config.camera?.compte_a_rebours || 3;
|
||||||
|
const anim = config.camera?.animation_compte_a_rebours || 'classique';
|
||||||
|
|
||||||
conteneur.classList.remove('cache');
|
conteneur.classList.remove('cache');
|
||||||
|
|
||||||
for (let i = duree; i > 0; i--) {
|
for (let i = duree; i > 0; i--) {
|
||||||
|
// Contenu selon le type d'animation
|
||||||
|
if (anim === 'emoji') {
|
||||||
|
chiffre.textContent = EMOJI_CAR[i] || i;
|
||||||
|
} else {
|
||||||
chiffre.textContent = i;
|
chiffre.textContent = i;
|
||||||
// Re-trigger animation
|
}
|
||||||
|
|
||||||
|
// Appliquer l'animation
|
||||||
|
chiffre.className = 'anim-chiffre anim-' + anim;
|
||||||
chiffre.style.animation = 'none';
|
chiffre.style.animation = 'none';
|
||||||
chiffre.offsetHeight; // force reflow
|
chiffre.offsetHeight;
|
||||||
chiffre.style.animation = 'pop 0.5s ease';
|
chiffre.className = 'anim-chiffre anim-' + anim;
|
||||||
|
|
||||||
await pause(1000);
|
await pause(1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +120,6 @@ async function traiterCapture() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (modeActuel === 'multi') {
|
if (modeActuel === 'multi') {
|
||||||
// Creer le strip/collage
|
|
||||||
const mode = config.multi_shot?.mode || 'strip';
|
const mode = config.multi_shot?.mode || 'strip';
|
||||||
let resultat;
|
let resultat;
|
||||||
if (mode === 'strip') {
|
if (mode === 'strip') {
|
||||||
@@ -122,7 +132,6 @@ async function traiterCapture() {
|
|||||||
afficherPreviewPhoto('/data/exports/' + resultat.nom);
|
afficherPreviewPhoto('/data/exports/' + resultat.nom);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Photo simple - aller au preview avec filtres
|
|
||||||
photoFinale = photosSession[0];
|
photoFinale = photosSession[0];
|
||||||
afficherPreviewPhoto('/data/photos/' + photosSession[0]);
|
afficherPreviewPhoto('/data/photos/' + photosSession[0]);
|
||||||
}
|
}
|
||||||
@@ -134,17 +143,28 @@ function afficherPreviewPhoto(chemin) {
|
|||||||
document.getElementById('photo-resultat').src = chemin;
|
document.getElementById('photo-resultat').src = chemin;
|
||||||
document.getElementById('photo-partage').src = chemin;
|
document.getElementById('photo-partage').src = chemin;
|
||||||
|
|
||||||
// Charger les filtres si photo simple
|
|
||||||
if (modeActuel === 'simple' && config.fonctionnalites?.filtres) {
|
if (modeActuel === 'simple' && config.fonctionnalites?.filtres) {
|
||||||
chargerFiltres();
|
chargerFiltres();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charger les overlays
|
|
||||||
if (config.fonctionnalites?.overlays) {
|
if (config.fonctionnalites?.overlays) {
|
||||||
chargerOverlays();
|
chargerOverlays();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Compteur accueil ---
|
||||||
|
|
||||||
|
async function majCompteurAccueil() {
|
||||||
|
const etat = await apiGet('/api/compteur');
|
||||||
|
const el = document.getElementById('compteur-accueil');
|
||||||
|
if (etat.actif) {
|
||||||
|
el.classList.remove('cache');
|
||||||
|
document.getElementById('compteur-restant').textContent = etat.restantes;
|
||||||
|
document.getElementById('compteur-limite').textContent = etat.limite;
|
||||||
|
} else {
|
||||||
|
el.classList.add('cache');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function pause(ms) {
|
function pause(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user