/* Application principale - Routeur SPA et logique globale */ let config = {}; let modeActuel = 'simple'; let photosSession = []; // Photos de la session en cours let photoFinale = null; // Photo finale (avec effets) let ecranActuel = 'accueil'; let _veilleTimer = null; let _enVeille = false; let _spotsAllumes = false; const _shutterSound = new Audio('/sounds/shutter.wav'); _shutterSound.volume = 0.8; // --- Initialisation --- async function init() { config = await apiGet('/api/config'); appliquerConfig(); setupEcranAccueil(); setupModes(); setupOnglets(); chargerBoothInfo(); verifierPhotostationDisponible(); } function appliquerConfig() { const event = config.evenement || {}; const fonc = config.fonctionnalites || {}; // Theme const theme = event.theme || 'base'; document.documentElement.setAttribute('data-theme', theme); // Couleurs (seulement si theme base, sinon le theme CSS gere) if (theme === 'base') { document.documentElement.style.setProperty('--primaire', event.couleur_primaire || '#e91e63'); document.documentElement.style.setProperty('--secondaire', event.couleur_secondaire || '#ffffff'); } // Logo selon le theme const logo = document.getElementById('accueil-logo'); if (logo) { const logos = { sikapics: '/assets/logos/sikapics.png' }; if (logos[theme]) { logo.src = logos[theme]; logo.classList.remove('cache'); } else { logo.classList.add('cache'); } } // Nom evenement const h1 = document.getElementById('nom-evenement'); if (h1) h1.textContent = event.nom || 'Photobooth'; // Visibilite des modes (restreint par formats evenement) const fmtsEvent = event.formats_actifs; const stripDispo = !fmtsEvent || fmtsEvent.includes('strip'); toggleVisible('btn-multi', fonc.multi_shot && stripDispo); toggleVisible('btn-imprimer', fonc.impression); toggleVisible('btn-email', fonc.email); const boothActif = config.booth?.actif; toggleVisible('btn-qr', fonc.qr_code || boothActif); // Media d'accueil (video/gif en boucle) appliquerMediaAccueil(event.media_accueil); } function appliquerMediaAccueil(media) { const video = document.getElementById('accueil-video'); const gif = document.getElementById('accueil-gif'); const animDefaut = document.getElementById('accueil-animation-defaut'); // Reset video.classList.add('cache'); gif.classList.add('cache'); video.pause(); video.src = ''; gif.src = ''; if (!media) { // Pas de media, afficher l'animation par defaut animDefaut.classList.remove('cache'); return; } animDefaut.classList.add('cache'); const ext = media.split('.').pop().toLowerCase(); if (ext === 'gif') { gif.src = `/assets/animations/${media}`; gif.classList.remove('cache'); } else { video.src = `/assets/animations/${media}`; video.classList.remove('cache'); video.play().catch(() => {}); } } // --- Navigation --- function allerA(ecran) { const tous = document.querySelectorAll('.ecran'); tous.forEach(e => e.classList.remove('actif')); const cible = document.getElementById('ecran-' + ecran); if (cible) { cible.classList.add('actif'); ecranActuel = ecran; } // Masquer le popup QR a chaque changement d'ecran fermerQR(); // Actions au changement d'ecran if (ecran === 'accueil') { photosSession = []; photoFinale = null; arreterPreview(); majCompteurAccueil(); eteindreSpots(); lancerTimerVeille(); } else { arreterTimerVeille(); reveillerEclairage(); } if (ecran === 'capture') { lancerCapture(); } else if (ecran === 'partage') { majBoutonImprimerCompteur(); } else if (ecran === 'galerie') { chargerGalerie(); } else if (ecran === 'admin') { chargerAdmin(); demarrerTimeoutAdmin(); } else { arreterTimeoutAdmin(); } } function demarrerTimeoutAdmin() {} function arreterTimeoutAdmin() {} function recommencer() { photosSession = []; photoFinale = null; allerA('mode'); } // --- Ecran accueil --- function setupEcranAccueil() { const accueil = document.getElementById('ecran-accueil'); // Toucher pour commencer (ignorer le bouton admin et le popup) accueil.addEventListener('click', (e) => { if (e.target.closest('.btn-admin')) return; if (e.target.closest('.btn-photostation')) return; if (_enVeille) reveillerEclairage(); allerA('mode'); }); } // --- Admin : icone + mot de passe --- const MDP_ADMIN = '1234'; const PIN_PHOTOSTATION = '1234'; let _pinPhotostationSaisi = ''; function ouvrirAdmin() { const popup = document.getElementById('popup-mdp'); const input = document.getElementById('input-mdp-admin'); popup.classList.remove('cache'); input.value = ''; document.getElementById('mdp-erreur').classList.add('cache'); } function fermerPopupMdp() { document.getElementById('popup-mdp').classList.add('cache'); document.getElementById('input-mdp-admin').value = ''; } function paveTouche(chiffre) { const input = document.getElementById('input-mdp-admin'); input.value += chiffre; } function paveEffacer() { const input = document.getElementById('input-mdp-admin'); input.value = input.value.slice(0, -1); } // --- Photostation --- async function verifierPhotostationDisponible() { const data = await apiGet('/api/photostation/disponible').catch(() => null); if (data?.disponible) { document.getElementById('btn-photostation').classList.remove('cache'); } } function ouvrirPhotostation() { _pinPhotostationSaisi = ''; _majAffichagePin(); document.getElementById('pin-photostation-erreur').classList.add('cache'); document.getElementById('popup-pin-photostation').classList.remove('cache'); } function fermerPopupPhotostation() { document.getElementById('popup-pin-photostation').classList.add('cache'); _pinPhotostationSaisi = ''; } function pinPhotostationTouche(c) { if (_pinPhotostationSaisi.length >= 4) return; _pinPhotostationSaisi += c; _majAffichagePin(); if (_pinPhotostationSaisi.length === 4) { setTimeout(_validerPinPhotostation, 150); } } function pinPhotostationEffacer() { _pinPhotostationSaisi = _pinPhotostationSaisi.slice(0, -1); _majAffichagePin(); } function _majAffichagePin() { const n = _pinPhotostationSaisi.length; const el = document.getElementById('pin-photostation-affichage'); el.textContent = '●'.repeat(n) + '○'.repeat(4 - n); } async function _validerPinPhotostation() { if (_pinPhotostationSaisi !== PIN_PHOTOSTATION) { document.getElementById('pin-photostation-erreur').classList.remove('cache'); _pinPhotostationSaisi = ''; _majAffichagePin(); return; } fermerPopupPhotostation(); afficherStatut('Lancement de la station d\'impression...', 'succes'); await apiPost('/api/photostation/lancer', {}); } function validerMdpAdmin() { const input = document.getElementById('input-mdp-admin'); if (input.value === MDP_ADMIN) { fermerPopupMdp(); allerA('admin-choix'); } else { document.getElementById('mdp-erreur').classList.remove('cache'); input.value = ''; } } function ouvrirWizard() { allerA('wizard'); wizardInit(); } // Valider avec Entree document.addEventListener('keydown', (e) => { if (e.key === 'Enter' && !document.getElementById('popup-mdp').classList.contains('cache')) { validerMdpAdmin(); } }); // --- Modes --- let cadreChoisi = null; // cadre sélectionné par l'utilisateur avant capture function setupModes() { document.querySelectorAll('#ecran-mode .btn-mode').forEach(btn => { btn.addEventListener('click', () => { modeActuel = btn.dataset.mode; cadreChoisi = null; allerA('capture'); }); }); } async function chargerCadresChoix() { const cfg = await apiGet('/api/config'); const fmt = (cfg.impression?.format || '15x20').replace('-2up', ''); const data = await apiGet(`/api/cadres-impression/${fmt}`).catch(() => null); if (!data || data.disponibles.length === 0) return false; const grille = document.getElementById('grille-cadres-choix'); // Conserver "Sans cadre" en premier, supprimer les anciens cadres const aucun = grille.querySelector('.cadre-choix-aucun'); grille.innerHTML = ''; if (aucun) grille.appendChild(aucun); for (const nom of data.disponibles) { const div = document.createElement('div'); div.className = 'cadre-choix-item'; div.innerHTML = `
${nom}
${nom.replace('.png','')}`; div.onclick = () => { cadreChoisi = nom; allerA('capture'); }; grille.appendChild(div); } return true; } function lancerSansCadre() { cadreChoisi = null; allerA('capture'); } // --- Rubriques + Onglets admin --- let rubriqueActive = 'general'; function changerRubrique(nom) { rubriqueActive = nom; document.querySelectorAll('.rubrique').forEach(r => r.classList.toggle('actif', r.dataset.rubrique === nom)); document.querySelectorAll('.admin-onglets').forEach(g => g.classList.toggle('cache', g.id !== 'onglets-' + nom)); document.querySelectorAll('.admin-panneau').forEach(p => p.classList.remove('actif')); document.querySelectorAll('.onglet').forEach(o => o.classList.remove('actif')); const groupe = document.getElementById('onglets-' + nom); if (groupe) { const premier = groupe.querySelector('.onglet'); if (premier) { premier.click(); } } } function setupOnglets() { document.querySelectorAll('.onglet').forEach(onglet => { onglet.addEventListener('click', () => { const groupe = onglet.closest('.admin-onglets'); if (groupe) groupe.querySelectorAll('.onglet').forEach(o => o.classList.remove('actif')); document.querySelectorAll('.admin-panneau').forEach(p => p.classList.remove('actif')); onglet.classList.add('actif'); const panneau = document.getElementById('panneau-' + onglet.dataset.onglet); if (panneau) panneau.classList.add('actif'); }); }); } // --- Utilitaires --- async function apiGet(url) { const resp = await fetch(url); return resp.json(); } async function apiPost(url, data = {}) { const resp = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data), }); return resp.json(); } function toggleVisible(id, visible) { const el = document.getElementById(id); if (el) { if (visible === false) el.classList.add('cache'); else el.classList.remove('cache'); } } async function chargerBoothInfo() { const booth = config.booth || {}; const el = document.getElementById('booth-info'); if (!booth.actif || !booth.url) { el.classList.add('cache'); return; } try { const info = await apiGet('/api/booth/info'); if (info.password) { const eventId = (config.evenement || {}).event_id || booth.event_id || 'default'; const qrUrl = `${booth.url}/g/${eventId}?p=${info.password}`; document.getElementById('booth-qr').src = `/api/qr?url=${encodeURIComponent(qrUrl)}`; el.classList.remove('cache'); } } catch (e) { el.classList.add('cache'); } } function fermerQR() { document.getElementById('popup-qr').classList.add('cache'); } function afficherStatut(message, type = 'succes') { const el = document.getElementById('statut-partage'); el.textContent = message; el.className = 'statut-partage ' + type; el.classList.remove('cache'); setTimeout(() => el.classList.add('cache'), 4000); } // WebSocket : mise a jour config en temps reel wsOnMessage('config_maj', (msg) => { config = msg.config; appliquerConfig(); if (ecranActuel === 'accueil') lancerTimerVeille(); }); // === Erreur equipement : overlay intelligent avec escalade === let _erreurCameraTimer = null; let _erreurCameraCount = 0; let _erreurCameraPhase = 0; // 0=auto-fix, 1=patience, 2=operator, 3=restart function _showErreurEquipement(icon, titre, msg, phase) { const el = document.getElementById('erreur-equipement'); document.getElementById('erreur-equip-icon').textContent = icon; document.getElementById('erreur-equip-titre').textContent = titre; document.getElementById('erreur-equip-msg').textContent = msg; const spinner = document.getElementById('erreur-equip-spinner'); const steps = document.getElementById('erreur-equip-steps'); const btnRetry = document.getElementById('erreur-equip-btn-retry'); const btnVideo = document.getElementById('erreur-equip-btn-video'); const btnRestart = document.getElementById('erreur-equip-btn-restart'); spinner.classList.toggle('cache', phase >= 2); steps.classList.toggle('cache', phase < 2); btnRetry.classList.toggle('cache', phase < 1); btnVideo.classList.toggle('cache', phase < 2); btnRestart.classList.toggle('cache', phase < 2); if (phase >= 2) { steps.innerHTML = '
    ' + '
  1. Verifiez que l\'appareil photo est allume (bouton ON)
  2. ' + '
  3. Verifiez le cable USB entre l\'appareil et la borne
  4. ' + '
  5. Eteignez et rallumez l\'appareil photo
  6. ' + '
  7. Si le probleme persiste, redemarrez la borne
  8. ' + '
'; } el.classList.remove('cache'); } function fermerErreurEquipement() { document.getElementById('erreur-equipement').classList.add('cache'); _erreurCameraCount = 0; _erreurCameraPhase = 0; allerA('accueil'); } function erreurEquipRetry() { document.getElementById('erreur-equip-titre').textContent = 'Verification en cours...'; document.getElementById('erreur-equip-msg').textContent = 'Un instant...'; document.getElementById('erreur-equip-spinner').classList.remove('cache'); document.getElementById('erreur-equip-steps').classList.add('cache'); document.getElementById('erreur-equip-btn-retry').classList.add('cache'); if (_erreurEquipType === 'imprimante') { fetch('/api/imprimante/reactiver', { method: 'POST' }).catch(() => {}); } else { fetch('/api/camera/reconnecter', { method: 'POST', headers: {'Content-Type':'application/json'}, body: '{}' }).catch(() => {}); } } let _erreurEquipType = 'camera'; function erreurEquipVideo() { document.getElementById('erreur-equipement').classList.add('cache'); const situationMap = { camera: 'camera_hs', imprimante: window._imprimanteErreurSituation || 'depannage_imprimante' }; const situation = situationMap[_erreurEquipType] || 'camera_hs'; if (typeof lancerWizardSituation === 'function') { lancerWizardSituation(situation, _erreurEquipType); } } function erreurEquipRestart() { _showErreurEquipement('⏳', 'Redemarrage en cours', 'La borne redemarre, patientez 30 secondes...', 0); document.getElementById('erreur-equip-btn-accueil').classList.add('cache'); fetch('/api/systeme/redemarrer', { method: 'POST' }).catch(() => {}); } wsOnMessage('camera_erreur', (data) => { if (window.location.pathname === '/admin') return; _erreurCameraCount++; _erreurEquipType = 'camera'; if (_erreurCameraCount <= 2) { _erreurCameraPhase = 0; _showErreurEquipement('📷', 'Preparation de l\'appareil photo', 'Un instant, reconnexion automatique...', 0); } else if (_erreurCameraCount <= 5) { _erreurCameraPhase = 1; _showErreurEquipement('📷', 'L\'appareil photo ne repond pas', 'Le systeme essaie de le reconnecter. Vous pouvez aussi reessayer manuellement.', 1); } else { _erreurCameraPhase = 2; _showErreurEquipement('📷', 'Appareil photo injoignable', 'Suivez ces etapes pour le remettre en marche :', 2); } if (_erreurCameraTimer) clearTimeout(_erreurCameraTimer); _erreurCameraTimer = setTimeout(() => { location.reload(); }, 180000); }); wsOnMessage('shutter', () => { _shutterSound.currentTime = 0; _shutterSound.play().catch(() => {}); }); wsOnMessage('camera_ok', () => { document.getElementById('erreur-equipement').classList.add('cache'); _erreurCameraCount = 0; _erreurCameraPhase = 0; if (_erreurCameraTimer) { clearTimeout(_erreurCameraTimer); _erreurCameraTimer = null; } }); wsOnMessage('imprimante_erreur', (data) => { if (window.location.pathname === '/admin') return; _erreurEquipType = 'imprimante'; const titre = data.titre || 'Probleme imprimante'; const msg = data.message || 'L\'imprimante ne fonctionne pas correctement.'; const etapes = data.etapes || []; const situation = data.situation || 'depannage_imprimante'; _showErreurEquipement('🖨', titre, msg, 2); const steps = document.getElementById('erreur-equip-steps'); if (etapes.length) { steps.innerHTML = '
    ' + etapes.map(e => '
  1. ' + e + '
  2. ').join('') + '
'; steps.classList.remove('cache'); } document.getElementById('erreur-equip-btn-retry').classList.remove('cache'); document.getElementById('erreur-equip-btn-retry').textContent = 'Verifier'; document.getElementById('erreur-equip-btn-retry').onclick = function() { fetch('/api/imprimante/reactiver', { method: 'POST' }).catch(() => {}); _showErreurEquipement('🖨', 'Verification en cours...', 'Un instant...', 0); }; document.getElementById('erreur-equip-btn-video').classList.remove('cache'); document.getElementById('erreur-equip-btn-video').onclick = function() { document.getElementById('erreur-equipement').classList.add('cache'); if (typeof lancerWizardSituation === 'function') lancerWizardSituation(situation, 'imprimante'); }; window._imprimanteErreurSituation = situation; }); wsOnMessage('imprimante_ok', () => { if (_erreurEquipType === 'imprimante') { document.getElementById('erreur-equipement').classList.add('cache'); } document.getElementById('printer-erreur').classList.add('cache'); }); function _afficherErreurImprimante(msg) { const el = document.getElementById('printer-erreur'); document.getElementById('printer-erreur-msg').textContent = msg; el.classList.remove('cache'); } async function _pollStatutImprimante() { try { const r = await apiGet('/api/imprimante/statut-detail'); const el = document.getElementById('printer-erreur'); if (r.statut && !r.statut.includes('stopped') && !r.statut.includes('disabled')) { el.classList.add('cache'); if (_erreurEquipType === 'imprimante') { document.getElementById('erreur-equipement').classList.add('cache'); } } } catch(e) {} } setInterval(_pollStatutImprimante, 30000); // === WIZARD === let wizardStep = 1; const WIZARD_TOTAL = 6; let wizardData = { nom: '', theme: 'base', modes: { simple: true, multi: true }, nbPhotos: 4, fonctions: {}, }; function wizardInit() { wizardStep = 1; wizardData.nom = config.evenement?.nom || ''; wizardData.theme = config.evenement?.theme || 'base'; wizardData.nbPhotos = config.multi_shot?.nombre_photos || 4; document.getElementById('wiz-nom').value = wizardData.nom; document.getElementById('wiz-nb-photos').textContent = wizardData.nbPhotos; wizardAfficherStep(); } function wizardAfficherStep() { document.querySelectorAll('.wizard-step').forEach(s => s.classList.remove('actif')); const step = document.querySelector(`.wizard-step[data-step="${wizardStep}"]`); if (step) step.classList.add('actif'); // Progress dots const prog = document.getElementById('wizard-progress'); prog.innerHTML = ''; for (let i = 1; i <= WIZARD_TOTAL; i++) { const dot = document.createElement('div'); dot.className = 'wizard-dot' + (i === wizardStep ? ' actif' : '') + (i < wizardStep ? ' fait' : ''); prog.appendChild(dot); } } function wizardSuivant() { // Sauvegarder l'etape courante if (wizardStep === 1) wizardData.nom = document.getElementById('wiz-nom').value; if (wizardStep === 4) wizardData.nbPhotos = parseInt(document.getElementById('wiz-nb-photos').textContent); wizardStep++; if (wizardStep > WIZARD_TOTAL) wizardStep = WIZARD_TOTAL; // Resume if (wizardStep === WIZARD_TOTAL) { const modes = Object.keys(wizardData.modes).filter(k => wizardData.modes[k]); const foncs = Object.keys(wizardData.fonctions).filter(k => wizardData.fonctions[k]); document.getElementById('wiz-resume').innerHTML = `
Evenement : ${wizardData.nom || 'Photobooth'}
Theme : ${wizardData.theme}
Modes : ${modes.join(', ') || 'photo'}
Photos/pellicule : ${wizardData.nbPhotos}
Partage : ${foncs.join(', ') || 'aucun'}
`; } wizardAfficherStep(); } function wizardChoisir(btn, key) { btn.closest('.wizard-choix').querySelectorAll('.wizard-card').forEach(c => c.classList.remove('actif')); btn.classList.add('actif'); wizardData[key] = btn.dataset.val; } function wizardToggle(btn) { btn.classList.toggle('actif'); const val = btn.dataset.val; const isActive = btn.classList.contains('actif'); if (['simple', 'multi'].includes(val)) { wizardData.modes[val] = isActive; } else { wizardData.fonctions[val] = isActive; } } function wizardNbPhotos(delta) { const el = document.getElementById('wiz-nb-photos'); let n = parseInt(el.textContent) + delta; n = Math.max(2, Math.min(8, n)); el.textContent = n; wizardData.nbPhotos = n; } async function wizardTerminer() { // Sauvegarder toute la config await apiPost('/api/config', { evenement: { nom: wizardData.nom || 'Photobooth', theme: wizardData.theme, }, fonctionnalites: { photo_simple: wizardData.modes.simple !== false, multi_shot: wizardData.modes.multi !== false, impression: !!wizardData.fonctions.impression, email: !!wizardData.fonctions.email, qr_code: !!wizardData.fonctions.qr, }, multi_shot: { nombre_photos: wizardData.nbPhotos, mode: 'strip', }, booth: { actif: !!wizardData.fonctions.galerie_live, }, }); config = await apiGet('/api/config'); appliquerConfig(); chargerBoothInfo(); allerA('accueil'); } // --- Veille eclairage --- function lancerTimerVeille() { arreterTimerVeille(); const delai = (config.relais?.veille_delai || 0) * 60000; if (delai <= 0) return; _veilleTimer = setTimeout(() => { _enVeille = true; _spotsAllumes = false; fetch('/api/relais/veille', { method: 'POST' }).catch(() => {}); }, delai); } function arreterTimerVeille() { if (_veilleTimer) { clearTimeout(_veilleTimer); _veilleTimer = null; } } function reveillerEclairage() { if (!_enVeille && _spotsAllumes) return; _enVeille = false; _spotsAllumes = true; arreterTimerVeille(); fetch('/api/relais/reveil', { method: 'POST' }).catch(() => {}); } function eteindreSpots() { _spotsAllumes = false; fetch('/api/relais/veille', { method: 'POST' }).catch(() => {}); } // Demarrage document.addEventListener('DOMContentLoaded', () => { init().then(() => { if (window.location.pathname === '/admin') { allerA('admin-choix'); } }); });