Admin backoffice : rubriques General/Evenement + videos situation + surprise + rembobinage ruban
- Reorganisation menu admin en 2 rubriques (General / Evenement) - Destinations deplace dans Evenement - Nouveau panneau Videos : upload par situation (montage, depannage, bourrage, rechargement) - Nouveau panneau Surprise : photo/video affichee ~1s avant capture pour provoquer sourire - Toggle rembobinage ruban Mitsubishi (StpiDecklist) pour economiser le ruban sur petits formats - Historique email cloisonne par evenement - Memoire projet consolidee dans memoire.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -66,6 +66,7 @@ async function chargerMateriel() {
|
||||
const orient = (imp.orientations || {})[fmt] || 'portrait';
|
||||
document.querySelector(`input[name="admin-orientation"][value="${orient}"]`).checked = true;
|
||||
setValue('admin-copies-max', imp.copies_max || 5);
|
||||
document.getElementById('tog-rembobinage-ruban').checked = imp.rembobinage_ruban || false;
|
||||
}
|
||||
|
||||
async function rafraichirImprimantes() {
|
||||
@@ -149,6 +150,7 @@ async function sauvegarderMateriel() {
|
||||
format: getValue('admin-format-papier'),
|
||||
copies_max: parseInt(getValue('admin-copies-max')) || 5,
|
||||
orientations: _getOrientations(),
|
||||
rembobinage_ruban: document.getElementById('tog-rembobinage-ruban').checked,
|
||||
},
|
||||
});
|
||||
afficherStatut('Materiel sauvegarde', 'succes');
|
||||
@@ -823,9 +825,23 @@ async function uploaderMediaAccueil() {
|
||||
const formData = new FormData();
|
||||
formData.append('fichier', input.files[0]);
|
||||
await fetch('/api/upload/animation', { method: 'POST', body: formData });
|
||||
const nomFichier = input.files[0].name;
|
||||
input.value = '';
|
||||
await rafraichirMediaAccueil();
|
||||
afficherStatut('Media importe', 'succes');
|
||||
document.getElementById('admin-media-accueil').value = nomFichier;
|
||||
|
||||
// Applique automatiquement la video comme media d'accueil de l'evenement en cours
|
||||
// (sinon il faut la re-selectionner dans le menu ET cliquer sur "Enregistrer" separement)
|
||||
if (eventEditId) {
|
||||
await apiPost('/api/config', { evenement: { media_accueil: nomFichier, event_id: eventEditId } });
|
||||
await fetch(`/api/evenements/${eventEditId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ media_accueil: nomFichier }),
|
||||
});
|
||||
appliquerConfig();
|
||||
}
|
||||
afficherStatut('Media importe et applique', 'succes');
|
||||
}
|
||||
|
||||
async function sauvegarderEvenement() {
|
||||
@@ -925,16 +941,19 @@ async function uploaderCadreEvent() {
|
||||
const input = document.getElementById('input-cadre-event');
|
||||
if (!input.files.length) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('fichier', input.files[0]);
|
||||
const nomFichier = input.files[0].name;
|
||||
for (const fmt of FORMATS_CADRE_EVENT) {
|
||||
const fd = new FormData();
|
||||
fd.append('fichier', input.files[0]);
|
||||
await fetch(`/api/evenements/${eventEditId}/upload-cadre/${fmt}`, { method: 'POST', body: fd });
|
||||
}
|
||||
input.value = '';
|
||||
await chargerCadresEvent();
|
||||
afficherStatut('Cadre importe (10x15 + 15x20)', 'succes');
|
||||
|
||||
// Applique automatiquement le cadre importe (sinon il reste inactif tant qu'on ne clique pas sur sa vignette)
|
||||
const modeSelect = document.getElementById('mode-cadre-event');
|
||||
if (modeSelect && modeSelect.value === 'aucun') modeSelect.value = 'impose';
|
||||
await selectionnerCadreEvent(nomFichier);
|
||||
afficherStatut('Cadre importe et applique (10x15 + 15x20)', 'succes');
|
||||
}
|
||||
|
||||
async function supprimerCadreEvent(nom) {
|
||||
@@ -1381,3 +1400,130 @@ async function chargerDiagCamera() {
|
||||
el.textContent = 'ERREUR : ' + e.message;
|
||||
}
|
||||
}
|
||||
|
||||
// === VIDEOS DE SITUATION ===
|
||||
|
||||
async function chargerVideosAdmin() {
|
||||
try {
|
||||
const data = await apiGet('/api/videos');
|
||||
const situations = ['montage', 'depannage_imprimante', 'bourrage', 'rechargement'];
|
||||
for (const sit of situations) {
|
||||
const el = document.getElementById('video-sit-' + sit);
|
||||
if (!el) continue;
|
||||
const v = data.videos && data.videos[sit];
|
||||
if (v) {
|
||||
el.innerHTML = `<a href="#" onclick="previewVideoSituation('${sit}');return false">${_escHtml(v)}</a>`;
|
||||
} else {
|
||||
el.textContent = 'Aucune video';
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Erreur chargement videos:', e);
|
||||
}
|
||||
}
|
||||
|
||||
async function uploaderVideoSituation(situation) {
|
||||
const input = document.getElementById('input-video-' + situation);
|
||||
if (!input || !input.files.length) return;
|
||||
const fd = new FormData();
|
||||
fd.append('video', input.files[0]);
|
||||
fd.append('situation', situation);
|
||||
try {
|
||||
const resp = await fetch('/api/videos/upload', { method: 'POST', body: fd });
|
||||
if (resp.ok) {
|
||||
input.value = '';
|
||||
chargerVideosAdmin();
|
||||
afficherStatut('Video importee', 'succes');
|
||||
}
|
||||
} catch (e) {
|
||||
afficherStatut('Erreur upload video', 'erreur');
|
||||
}
|
||||
}
|
||||
|
||||
async function supprimerVideoSituation(situation) {
|
||||
try {
|
||||
await fetch('/api/videos/' + situation, { method: 'DELETE' });
|
||||
chargerVideosAdmin();
|
||||
afficherStatut('Video supprimee', 'succes');
|
||||
} catch (e) {
|
||||
afficherStatut('Erreur suppression video', 'erreur');
|
||||
}
|
||||
}
|
||||
|
||||
function previewVideoSituation(situation) {
|
||||
const preview = document.getElementById('video-situation-preview');
|
||||
if (preview) {
|
||||
preview.src = '/api/videos/' + situation + '/stream';
|
||||
preview.load();
|
||||
}
|
||||
}
|
||||
|
||||
// === SURPRISE (photo/video avant capture) ===
|
||||
|
||||
async function chargerSurpriseConfig() {
|
||||
const surprise = config.surprise || {};
|
||||
const tog = document.getElementById('tog-surprise-actif');
|
||||
if (tog) tog.checked = !!surprise.actif;
|
||||
const delai = document.getElementById('admin-surprise-delai');
|
||||
if (delai) delai.value = surprise.delai_ms || 1000;
|
||||
const typeRadios = document.querySelectorAll('input[name="surprise-type"]');
|
||||
typeRadios.forEach(r => r.checked = r.value === (surprise.type || 'photo'));
|
||||
const current = document.getElementById('surprise-current');
|
||||
if (current) current.textContent = surprise.fichier || 'Aucun media configure';
|
||||
majSurprisePreview(surprise);
|
||||
}
|
||||
|
||||
function majSurprisePreview(surprise) {
|
||||
const container = document.getElementById('surprise-preview');
|
||||
if (!container) return;
|
||||
if (!surprise || !surprise.fichier) {
|
||||
container.innerHTML = '<span style="color:#666">Aucun apercu</span>';
|
||||
return;
|
||||
}
|
||||
const url = '/api/surprise/media';
|
||||
if (surprise.type === 'video') {
|
||||
container.innerHTML = `<video src="${url}" style="width:100%;max-height:300px;border-radius:8px" controls></video>`;
|
||||
} else {
|
||||
container.innerHTML = `<img src="${url}" style="width:100%;max-height:300px;border-radius:8px;object-fit:contain">`;
|
||||
}
|
||||
}
|
||||
|
||||
async function uploaderSurprise() {
|
||||
const input = document.getElementById('input-surprise-media');
|
||||
if (!input || !input.files.length) return;
|
||||
const fd = new FormData();
|
||||
fd.append('media', input.files[0]);
|
||||
try {
|
||||
const resp = await fetch('/api/surprise/upload', { method: 'POST', body: fd });
|
||||
if (resp.ok) {
|
||||
const data = await resp.json();
|
||||
input.value = '';
|
||||
config = await apiGet('/api/config');
|
||||
chargerSurpriseConfig();
|
||||
afficherStatut('Media surprise importe', 'succes');
|
||||
}
|
||||
} catch (e) {
|
||||
afficherStatut('Erreur upload surprise', 'erreur');
|
||||
}
|
||||
}
|
||||
|
||||
async function supprimerSurprise() {
|
||||
try {
|
||||
await fetch('/api/surprise/media', { method: 'DELETE' });
|
||||
config = await apiGet('/api/config');
|
||||
chargerSurpriseConfig();
|
||||
afficherStatut('Surprise supprimee', 'succes');
|
||||
} catch (e) {
|
||||
afficherStatut('Erreur suppression surprise', 'erreur');
|
||||
}
|
||||
}
|
||||
|
||||
async function sauvegarderSurprise() {
|
||||
const actif = document.getElementById('tog-surprise-actif')?.checked || false;
|
||||
const delai = parseInt(document.getElementById('admin-surprise-delai')?.value) || 1000;
|
||||
const type = document.querySelector('input[name="surprise-type"]:checked')?.value || 'photo';
|
||||
await apiPost('/api/config', {
|
||||
surprise: { actif, delai_ms: delai, type }
|
||||
});
|
||||
afficherStatut('Surprise sauvegardee', 'succes');
|
||||
}
|
||||
|
||||
@@ -298,12 +298,28 @@ function lancerSansCadre() {
|
||||
allerA('capture');
|
||||
}
|
||||
|
||||
// --- Onglets admin ---
|
||||
// --- 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', () => {
|
||||
document.querySelectorAll('.onglet').forEach(o => o.classList.remove('actif'));
|
||||
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);
|
||||
|
||||
@@ -9,6 +9,27 @@ let captureEnCours = false; // true pendant tout le flux lancerCapture()
|
||||
|
||||
const EMOJI_CAR = { 3: '🤪', 2: '😱', 1: '🔥' };
|
||||
|
||||
// --- Surprise avant capture ---
|
||||
|
||||
async function afficherSurprise() {
|
||||
const surprise = (config || {}).surprise;
|
||||
if (!surprise || !surprise.actif || !surprise.fichier) return;
|
||||
const delai = surprise.delai_ms || 1000;
|
||||
|
||||
const overlay = document.getElementById('surprise-overlay');
|
||||
if (!overlay) return;
|
||||
|
||||
if (surprise.type === 'video') {
|
||||
overlay.innerHTML = '<video src="/api/surprise/media" autoplay muted playsinline style="max-width:100%;max-height:100%;object-fit:contain"></video>';
|
||||
} else {
|
||||
overlay.innerHTML = '<img src="/api/surprise/media" style="max-width:100%;max-height:100%;object-fit:contain">';
|
||||
}
|
||||
overlay.classList.remove('cache');
|
||||
|
||||
await new Promise(r => setTimeout(r, delai));
|
||||
overlay.classList.add('cache');
|
||||
}
|
||||
|
||||
// --- Preview live ---
|
||||
|
||||
function afficherErreurCapture(titre, detail = '') {
|
||||
@@ -253,6 +274,9 @@ async function lancerCapture() {
|
||||
lancerPreview(); // Miroir live pendant le compte à rebours
|
||||
await compteARebours();
|
||||
|
||||
// Surprise : afficher media juste avant capture
|
||||
await afficherSurprise();
|
||||
|
||||
// Flash blanc immédiat + lancer capture en parallèle
|
||||
const flash = document.getElementById('flash-blanc');
|
||||
flash.classList.remove('cache');
|
||||
|
||||
Reference in New Issue
Block a user