Canon: fiabilite veille/reveil + detection PTP freeze + UX countdown + fix copies impression

- Canon PTP freeze: 5 keepalive FAIL consecutifs → deconnexion forcee
- Canon veille: reset timestamps reconnexion (pas de boucle de mort)
- Canon reveil: guard _reveil_en_cours dans thread preview + surveiller_dslr
- Canon capture: attente auto reveil (60s max) au lieu de 503 immediat
- Canon reconnexion quand _echecs_connexion == 0
- UX: sequence "Attention!" → "Preparez-vous!" → 3 → 2 → 1
- UX: pre-messages uniquement photo simple + 1ere photo pellicule
- UX: pas de compteur "Photo 1/1" en mode simple
- UX: pas de cadre propose pour pellicule
- UX: taille texte countdown reduite pour Surface (scale-factor 3)
- Impression: N jobs lp separes au lieu de lp -n (Mitsubishi ignore -n)
- Bouton Terminer grise pendant impression

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 18:54:10 +02:00
parent 4e267492c2
commit 583b89f0bd
8 changed files with 179 additions and 85 deletions

View File

@@ -1766,6 +1766,13 @@ h3 {
color: var(--primaire);
}
.anim-texte {
font-size: clamp(0.9rem, 2.5vw, 1.6rem);
max-width: 80vw;
text-align: center;
line-height: 1.2;
}
/* === Animations compte a rebours === */
/* Classique */
.anim-classique { animation: anim-pop 0.5s ease; }

View File

@@ -246,7 +246,7 @@
</div>
<div id="statut-partage" class="statut-partage cache"></div>
<div class="partage-bas">
<button class="btn-action" onclick="allerA('accueil')">Terminer</button>
<button class="btn-action" id="btn-terminer" onclick="allerA('accueil')">Terminer</button>
<button class="btn-secondaire" onclick="recommencer()">Nouvelle photo</button>
</div>
</section>

View File

@@ -269,11 +269,14 @@ async function lancerCapture() {
for (let i = 0; i < nbPhotos; i++) {
if (nbPhotos > 1) {
compteurEl.textContent = `Photo ${i + 1} / ${nbPhotos}`;
} else {
compteurEl.textContent = '';
}
lancerPreview(); // Miroir live pendant le compte à rebours
wsEnvoyer({type: 'prepare_capture'});
await compteARebours();
const avecPreMessages = (i === 0);
await compteARebours(avecPreMessages);
// Surprise : afficher media juste avant capture
await afficherSurprise();
@@ -285,30 +288,29 @@ async function lancerCapture() {
flash.offsetHeight;
flash.style.animation = '';
const promesseCapture = apiPost('/api/capturer').catch(() => null);
arreterPreview();
let resultat = await promesseCapture;
flash.classList.add('cache');
document.getElementById('capture-en-cours').classList.add('cache');
let erreurReseau = false;
let resultat = await apiPost('/api/capturer').catch(() => null);
if (!resultat) {
erreurReseau = true;
}
if (erreurReseau) {
if (!resultat && !resultat?.erreur) {
flash.classList.add('cache');
document.getElementById('capture-en-cours').classList.add('cache');
captureEnCours = false;
afficherErreurCapture('Erreur reseau', 'Le serveur ne repond pas — redemarrez le photobooth');
return;
}
if (!resultat || resultat.erreur) {
if (resultat?.erreur) {
flash.classList.add('cache');
document.getElementById('capture-en-cours').classList.add('cache');
captureEnCours = false;
const msg = resultat?.erreur || 'Erreur inconnue';
const msg = resultat.erreur;
afficherErreurCapture('Echec de la capture', msg.includes('Echec') ? 'Verifiez le DSLR et reessayez' : msg);
return;
}
flash.classList.add('cache');
document.getElementById('capture-en-cours').classList.add('cache');
if (resultat.nom) {
photosSession.push(resultat.nom);
}
@@ -338,7 +340,7 @@ function choisirAnimationAleatoire() {
return pool[Math.floor(Math.random() * pool.length)];
}
async function compteARebours() {
async function compteARebours(avecPreMessages = true) {
const conteneur = document.getElementById('compte-a-rebours');
const chiffre = document.getElementById('chiffre-car');
const duree = config.camera?.compte_a_rebours || 3;
@@ -354,6 +356,17 @@ async function compteARebours() {
// Animation CSS classique
conteneur.classList.remove('cache');
if (avecPreMessages) {
const preMessages = ['Attention !', 'Préparez-vous !'];
for (const msg of preMessages) {
chiffre.textContent = msg;
chiffre.className = 'anim-chiffre anim-texte';
void chiffre.offsetWidth;
chiffre.classList.add('anim-' + anim.id);
await pause(1000);
}
}
for (let i = duree; i > 0; i--) {
if (anim.id === 'emoji') {
chiffre.textContent = EMOJI_CAR[i] || i;

View File

@@ -9,6 +9,11 @@ async function ouvrirImpression() {
document.getElementById('nb-exemplaires').textContent = nbExemplaires;
cadreChoisi = null;
if (modeActuel === 'multi') {
document.getElementById('form-impression').classList.remove('cache');
return;
}
const hasCadres = await chargerCadresChoixImpression();
if (hasCadres) {
document.getElementById('popup-cadre-impression').classList.remove('cache');
@@ -101,6 +106,8 @@ function changerExemplaires(delta) {
async function lancerImpression() {
if (!photoFinale) return;
fermerImpression();
const btnTerminer = document.getElementById('btn-terminer');
if (btnTerminer) { btnTerminer.disabled = true; btnTerminer.style.opacity = '0.4'; }
// Pour les strips, imprimer la version 2 bandes sur 10x15
const fichierImpression = photoImpression || photoFinale;
afficherStatut(`Impression de ${nbExemplaires} exemplaire(s)...`, 'succes');
@@ -110,6 +117,7 @@ async function lancerImpression() {
cadre: cadreChoisi || undefined,
format_papier: formatImpression || undefined,
});
if (btnTerminer) { btnTerminer.disabled = false; btnTerminer.style.opacity = ''; }
if (resultat.attente) {
afficherStatutEco('⏳', resultat.message || 'Votre photo sortira avec la suivante !', 'attente-eco');
} else if (resultat.jumeau && resultat.succes) {