Init photobooth - borne photo evenementielle RPi4
Backend FastAPI complet : camera gphoto2, filtres/overlays Pillow, chroma key OpenCV, multi-shot/collage, GIF, impression CUPS, email SMTP, QR code. Frontend web vanilla (HTML/CSS/JS) pour Chromium kiosk. Menu admin cache (appui long coin ecran). Toutes les fonctionnalites activables/desactivables. Scripts install + systemd pour RPi4. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
172
frontend/js/app.js
Normal file
172
frontend/js/app.js
Normal file
@@ -0,0 +1,172 @@
|
||||
/* 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';
|
||||
|
||||
// --- Initialisation ---
|
||||
|
||||
async function init() {
|
||||
config = await apiGet('/api/config');
|
||||
appliquerConfig();
|
||||
setupEcranAccueil();
|
||||
setupModes();
|
||||
setupOnglets();
|
||||
}
|
||||
|
||||
function appliquerConfig() {
|
||||
const event = config.evenement || {};
|
||||
const fonc = config.fonctionnalites || {};
|
||||
|
||||
// Couleurs
|
||||
document.documentElement.style.setProperty('--primaire', event.couleur_primaire || '#e91e63');
|
||||
document.documentElement.style.setProperty('--secondaire', event.couleur_secondaire || '#ffffff');
|
||||
|
||||
// Nom evenement
|
||||
const h1 = document.getElementById('nom-evenement');
|
||||
if (h1) h1.textContent = event.nom || 'Photobooth';
|
||||
|
||||
// Visibilite des modes
|
||||
toggleVisible('btn-multi', fonc.multi_shot);
|
||||
toggleVisible('btn-gif', fonc.gif);
|
||||
toggleVisible('btn-imprimer', fonc.impression);
|
||||
toggleVisible('btn-email', fonc.email);
|
||||
toggleVisible('btn-qr', fonc.qr_code);
|
||||
}
|
||||
|
||||
// --- 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;
|
||||
}
|
||||
|
||||
// Actions au changement d'ecran
|
||||
if (ecran === 'accueil') {
|
||||
photosSession = [];
|
||||
photoFinale = null;
|
||||
arreterPreview();
|
||||
} else if (ecran === 'capture') {
|
||||
lancerCapture();
|
||||
} else if (ecran === 'galerie') {
|
||||
chargerGalerie();
|
||||
} else if (ecran === 'admin') {
|
||||
chargerAdmin();
|
||||
}
|
||||
}
|
||||
|
||||
function recommencer() {
|
||||
photosSession = [];
|
||||
photoFinale = null;
|
||||
allerA('mode');
|
||||
}
|
||||
|
||||
// --- Ecran accueil ---
|
||||
|
||||
function setupEcranAccueil() {
|
||||
const accueil = document.getElementById('ecran-accueil');
|
||||
|
||||
// Toucher pour commencer
|
||||
accueil.addEventListener('click', (e) => {
|
||||
// Ignorer si c'est la zone admin
|
||||
if (e.target.closest('.zone-admin')) return;
|
||||
allerA('mode');
|
||||
});
|
||||
|
||||
// Zone admin : appui long 3s
|
||||
const zoneAdmin = document.getElementById('zone-admin');
|
||||
let timerAdmin = null;
|
||||
|
||||
zoneAdmin.addEventListener('touchstart', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
timerAdmin = setTimeout(() => allerA('admin'), 3000);
|
||||
});
|
||||
|
||||
zoneAdmin.addEventListener('touchend', () => {
|
||||
if (timerAdmin) clearTimeout(timerAdmin);
|
||||
});
|
||||
|
||||
// Support souris aussi (pour dev)
|
||||
zoneAdmin.addEventListener('mousedown', (e) => {
|
||||
e.stopPropagation();
|
||||
timerAdmin = setTimeout(() => allerA('admin'), 3000);
|
||||
});
|
||||
|
||||
zoneAdmin.addEventListener('mouseup', () => {
|
||||
if (timerAdmin) clearTimeout(timerAdmin);
|
||||
});
|
||||
}
|
||||
|
||||
// --- Modes ---
|
||||
|
||||
function setupModes() {
|
||||
document.querySelectorAll('.btn-mode').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
modeActuel = btn.dataset.mode;
|
||||
allerA('capture');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// --- Onglets admin ---
|
||||
|
||||
function setupOnglets() {
|
||||
document.querySelectorAll('.onglet').forEach(onglet => {
|
||||
onglet.addEventListener('click', () => {
|
||||
document.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');
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
// Demarrage
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
Reference in New Issue
Block a user