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:
@@ -1342,6 +1342,64 @@ h3 {
|
||||
color: var(--texte-secondaire);
|
||||
}
|
||||
|
||||
.cadre-choix-item.cadre-selectionne {
|
||||
border-color: var(--primaire);
|
||||
}
|
||||
|
||||
/* Popup sélection cadre au moment de l'impression */
|
||||
.popup-box-cadre {
|
||||
max-width: 92vw;
|
||||
width: 680px;
|
||||
max-height: 90vh;
|
||||
padding: 1.2rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.grille-cadres-popup {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
gap: 0.6rem;
|
||||
padding: 0.3rem;
|
||||
}
|
||||
|
||||
.grille-cadres-popup .cadre-choix-item {
|
||||
flex: 0 1 78px;
|
||||
min-width: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.grille-cadres-popup .cadre-choix-item .cadre-thumb-wrap {
|
||||
width: 100%;
|
||||
aspect-ratio: 2 / 3;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.grille-cadres-popup .cadre-choix-item .cadre-thumb-wrap img {
|
||||
width: 250%;
|
||||
height: 250%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.grille-cadres-popup .cadre-choix-aucun .cadre-choix-vide {
|
||||
width: 100%;
|
||||
aspect-ratio: 2 / 3;
|
||||
height: auto;
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.grille-cadres-popup .cadre-choix-item span {
|
||||
font-size: 0.68rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.cadre-choix-aucun .cadre-choix-vide {
|
||||
width: 140px;
|
||||
height: 210px;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||
<meta name="google" content="notranslate">
|
||||
<meta http-equiv="Content-Language" content="fr">
|
||||
<link rel="stylesheet" href="/css/style.css">
|
||||
<link rel="stylesheet" href="/css/style.css?v=2">
|
||||
<link rel="stylesheet" href="/css/themes.css">
|
||||
</head>
|
||||
<body>
|
||||
@@ -144,7 +144,6 @@
|
||||
<span>QR Code</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Choix exemplaires impression -->
|
||||
<div id="form-impression" class="form-impression cache">
|
||||
<div class="exemplaires-choix">
|
||||
<button class="btn-exemplaire" onclick="changerExemplaires(-1)">-</button>
|
||||
@@ -379,6 +378,9 @@
|
||||
<label>Compte a rebours (secondes)</label>
|
||||
<input type="number" id="admin-car" min="1" max="10" value="3">
|
||||
</div>
|
||||
<div class="champ">
|
||||
<label class="toggle"><input type="checkbox" id="tog-flash-integre" checked><span class="toggle-slider"></span> Flash intégré Canon (décocher = flash Off)</label>
|
||||
</div>
|
||||
|
||||
<h3>Imprimante</h3>
|
||||
<div class="champ">
|
||||
@@ -672,6 +674,18 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Popup sélection cadre impression -->
|
||||
<div id="popup-cadre-impression" class="popup-overlay cache">
|
||||
<div class="popup-box popup-box-cadre">
|
||||
<h3>Cadre d'impression</h3>
|
||||
<div id="grille-cadres-impression" class="grille-cadres-choix grille-cadres-popup"></div>
|
||||
<div class="popup-actions">
|
||||
<button class="btn-action" onclick="confirmerCadreImpression()">Confirmer</button>
|
||||
<button class="btn-secondaire" onclick="annulerCadreImpression()">Annuler</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Popup PIN photostation -->
|
||||
<div id="popup-pin-photostation" class="popup-overlay cache">
|
||||
<div class="popup-box popup-box-pave">
|
||||
@@ -722,12 +736,12 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/websocket.js"></script>
|
||||
<script src="/js/app.js"></script>
|
||||
<script src="/js/camera.js"></script>
|
||||
<script src="/js/effects.js"></script>
|
||||
<script src="/js/gallery.js"></script>
|
||||
<script src="/js/share.js"></script>
|
||||
<script src="/js/admin.js"></script>
|
||||
<script src="/js/websocket.js?v=3"></script>
|
||||
<script src="/js/app.js?v=4"></script>
|
||||
<script src="/js/camera.js?v=6"></script>
|
||||
<script src="/js/effects.js?v=3"></script>
|
||||
<script src="/js/gallery.js?v=3"></script>
|
||||
<script src="/js/share.js?v=5"></script>
|
||||
<script src="/js/admin.js?v=4"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user