Flash Canon configurable, choix de cadre a l'impression, fixes capture/reconnexion DSLR

- Flash integre Canon activable/desactivable depuis l'admin (configurer_flash)
- Popup de choix de cadre juste avant impression, layout sans defilement (flex nowrap + aspect-ratio)
- Capture : ignore camera_erreur pendant une capture en cours (laisse l'API trancher)
- surveiller_dslr : grace period preview augmentee a 2s, reapplique la config camera apres reconnexion
- start.sh simplifie (Chromium gere par kiosk-session.sh), remote debugging port 9222 active
This commit is contained in:
2026-06-21 21:59:36 +02:00
parent c7225cc9f6
commit fe0a559c85
10 changed files with 217 additions and 60 deletions

View File

@@ -54,6 +54,7 @@ async function chargerMateriel() {
if (cam.appareil) selectApp.value = cam.appareil;
setValue('admin-car', cam.compte_a_rebours || 3);
document.getElementById('tog-flash-integre').checked = cam.flash_integre !== false;
// Imprimantes
await rafraichirImprimantes();
@@ -135,10 +136,12 @@ async function rafraichirStatutImprimante() {
}
async function sauvegarderMateriel() {
const flashIntegre = document.getElementById('tog-flash-integre').checked;
await apiPost('/api/config', {
camera: {
appareil: getValue('admin-appareil'),
compte_a_rebours: parseInt(getValue('admin-car')) || 3,
flash_integre: flashIntegre,
},
impression: {
imprimante: getValue('admin-imprimante'),

View File

@@ -247,11 +247,10 @@ let cadreChoisi = null; // cadre sélectionné par l'utilisateur avant capture
function setupModes() {
document.querySelectorAll('#ecran-mode .btn-mode').forEach(btn => {
btn.addEventListener('click', async () => {
btn.addEventListener('click', () => {
modeActuel = btn.dataset.mode;
cadreChoisi = null;
const hasCadres = await chargerCadresChoix();
allerA(hasCadres ? 'cadre-choix' : 'capture');
allerA('capture');
});
});
}

View File

@@ -2,8 +2,10 @@
let previewActif = false;
let previewTimeoutId = null;
let previewInterval = null;
const PREVIEW_TIMEOUT_MS = 3000;
let captureAbortee = false;
let captureEnCours = false; // true pendant tout le flux lancerCapture()
const EMOJI_CAR = { 3: '🤪', 2: '😱', 1: '🔥' };
@@ -73,13 +75,16 @@ wsOnMessage('preview', (msg) => {
resetPreviewTimeout();
});
// Erreur camera : abort capture + retour accueil si on est en train de capturer
// Erreur camera pendant preview idle : retour accueil
// Si on est en cours de capture (compte à rebours ou attente API), on ignore :
// l'API /api/capturer retournera elle-même une erreur si le DSLR est vraiment mort.
wsOnMessage('camera_erreur', () => {
captureAbortee = true;
arreterPreview();
if (ecranActuel === 'capture') {
// Retour accueil automatique en 4s (afficherErreurCapture le fait)
afficherErreurCapture('Appareil photo déconnecté', 'Reconnexion en cours...');
if (!captureEnCours) {
captureAbortee = true;
if (ecranActuel === 'capture') {
afficherErreurCapture('Appareil photo déconnecté', 'Reconnexion en cours...');
}
}
});
@@ -91,6 +96,7 @@ wsOnMessage('camera_ok', () => {
// --- Capture ---
async function lancerCapture() {
captureEnCours = true;
captureAbortee = false;
photosSession = [];
photoImpression = null;
@@ -105,10 +111,12 @@ async function lancerCapture() {
try {
const statut = await apiGet('/api/camera/statut');
if (!statut.connectee || statut.mode === 'erreur') {
captureEnCours = false;
afficherErreurCapture('Appareil photo non disponible', 'Verifiez la connexion USB du DSLR');
return;
}
} catch (e) {
captureEnCours = false;
afficherErreurCapture('Impossible de joindre le serveur', 'Verifiez que le photobooth est bien demarre');
return;
}
@@ -117,20 +125,14 @@ async function lancerCapture() {
const compteurEl = document.getElementById('capture-compteur');
for (let i = 0; i < nbPhotos; i++) {
if (captureAbortee) return;
if (nbPhotos > 1) {
compteurEl.textContent = `Photo ${i + 1} / ${nbPhotos}`;
}
lancerPreview(); // Miroir live pendant le compte à rebours
await compteARebours();
arreterPreview(); // Libère le bus USB gphoto2 pour la capture
if (captureAbortee) return;
afficherFlash();
document.getElementById('capture-en-cours').classList.remove('cache');
arreterPreview();
await pause(150);
const promesseCapture = apiPost('/api/capturer').catch(() => null);
let resultat = await promesseCapture;
@@ -140,16 +142,19 @@ async function lancerCapture() {
if (!resultat) {
erreurReseau = true;
} else if (resultat.erreur === 'Limite de photos atteinte') {
captureEnCours = false;
afficherErreurCapture('Limite de photos atteinte', 'La session est terminee');
return;
}
if (erreurReseau) {
captureEnCours = false;
afficherErreurCapture('Erreur reseau', 'Le serveur ne repond pas — redemarrez le photobooth');
return;
}
if (!resultat || resultat.erreur) {
captureEnCours = false;
const msg = resultat?.erreur || 'Erreur inconnue';
afficherErreurCapture('Echec de la capture', msg.includes('Echec') ? 'Verifiez le DSLR et reessayez' : msg);
return;
@@ -160,6 +165,7 @@ async function lancerCapture() {
}
}
captureEnCours = false;
await traiterCapture();
}

View File

@@ -3,15 +3,66 @@
let nbExemplaires = 1;
let copiesMax = 5;
function ouvrirImpression() {
async function ouvrirImpression() {
copiesMax = config.impression?.copies_max || 5;
nbExemplaires = 1;
document.getElementById('nb-exemplaires').textContent = nbExemplaires;
document.getElementById('form-impression').classList.remove('cache');
cadreChoisi = null;
const hasCadres = await chargerCadresChoixImpression();
if (hasCadres) {
document.getElementById('popup-cadre-impression').classList.remove('cache');
} else {
document.getElementById('form-impression').classList.remove('cache');
}
}
function fermerImpression() {
document.getElementById('form-impression').classList.add('cache');
cadreChoisi = null;
}
function confirmerCadreImpression() {
document.getElementById('popup-cadre-impression').classList.add('cache');
document.getElementById('form-impression').classList.remove('cache');
}
function annulerCadreImpression() {
document.getElementById('popup-cadre-impression').classList.add('cache');
cadreChoisi = null;
}
async function chargerCadresChoixImpression() {
const fmt = (config.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-impression');
grille.innerHTML = '';
const divAucun = document.createElement('div');
divAucun.className = 'cadre-choix-item cadre-choix-aucun cadre-selectionne';
divAucun.innerHTML = '<div class="cadre-choix-vide"></div><span>Sans cadre</span>';
divAucun.onclick = () => {
cadreChoisi = null;
grille.querySelectorAll('.cadre-choix-item').forEach(el => el.classList.remove('cadre-selectionne'));
divAucun.classList.add('cadre-selectionne');
};
grille.appendChild(divAucun);
for (const nom of data.disponibles) {
const div = document.createElement('div');
div.className = 'cadre-choix-item';
div.innerHTML = `<div class="cadre-thumb-wrap"><img src="/assets/cadres/${fmt}/${nom}" alt="${nom}"></div><span>${nom.replace('.png','')}</span>`;
div.onclick = () => {
cadreChoisi = nom;
grille.querySelectorAll('.cadre-choix-item').forEach(el => el.classList.remove('cadre-selectionne'));
div.classList.add('cadre-selectionne');
};
grille.appendChild(div);
}
return true;
}
function changerExemplaires(delta) {