Formats activables par evenement + cadre impose exclut strips
- Toggles formats (10x15, 15x20, strip) par evenement - Strip desactive masque le bouton pellicule sur l'accueil - Cadre impose/defaut s'applique uniquement sur 10x15 et 15x20 (pas strip) - Upload cadre evenement le copie dans les deux formats automatiquement - Section cadres admin simplifiee (un seul bloc au lieu de 3) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,7 @@ def creer_evenement(nom: str, **kwargs) -> dict:
|
||||
"couleur_primaire": kwargs.get("couleur_primaire", "#e91e63"),
|
||||
"couleur_secondaire": kwargs.get("couleur_secondaire", "#ffffff"),
|
||||
"media_accueil": kwargs.get("media_accueil"),
|
||||
"formats_actifs": kwargs.get("formats_actifs", ["10x15", "15x20", "strip"]),
|
||||
"cadres": {},
|
||||
}
|
||||
_chemin_event(event_id).write_text(json.dumps(event, ensure_ascii=False, indent=2), "utf-8")
|
||||
@@ -97,6 +98,7 @@ def activer_evenement(event_id: str) -> dict | None:
|
||||
"couleur_primaire": event.get("couleur_primaire", "#e91e63"),
|
||||
"couleur_secondaire": event.get("couleur_secondaire", "#ffffff"),
|
||||
"media_accueil": event.get("media_accueil"),
|
||||
"formats_actifs": event.get("formats_actifs", ["10x15", "15x20", "strip"]),
|
||||
}
|
||||
})
|
||||
log.info(f"Evenement active : {event['nom']} ({event_id})")
|
||||
@@ -135,7 +137,10 @@ def set_cadre_event(event_id: str, format_papier: str, mode: str, cadre: str | N
|
||||
def cadre_actif_pour_impression(format_papier: str) -> tuple[str | None, str]:
|
||||
"""Retourne (nom_cadre, mode) en tenant compte de l'evenement actif.
|
||||
mode: 'aucun', 'disponible', 'impose', 'defaut'
|
||||
Strips exclus du cadre impose/defaut.
|
||||
"""
|
||||
if format_papier == "strip":
|
||||
return None, "aucun"
|
||||
config = charger_config()
|
||||
event_id = config.get("evenement", {}).get("event_id")
|
||||
if not event_id:
|
||||
|
||||
@@ -626,8 +626,16 @@
|
||||
</div>
|
||||
<button class="btn-action" onclick="sauvegarderEvenement()">Sauvegarder</button>
|
||||
|
||||
<h3>Cadres evenement</h3>
|
||||
<p class="aide">Cadres specifiques a cet evenement (n'apparaissent pas dans les autres).</p>
|
||||
<h3>Formats d'impression</h3>
|
||||
<p class="aide">Formats papier disponibles pour cet evenement.</p>
|
||||
<div class="toggle-groupe" id="event-formats-toggles">
|
||||
<label class="toggle"><input type="checkbox" id="tog-event-fmt-10x15"><span class="toggle-slider"></span> 10×15 cm</label>
|
||||
<label class="toggle"><input type="checkbox" id="tog-event-fmt-15x20"><span class="toggle-slider"></span> 15×20 cm</label>
|
||||
<label class="toggle"><input type="checkbox" id="tog-event-fmt-strip"><span class="toggle-slider"></span> Strip / Pellicule</label>
|
||||
</div>
|
||||
|
||||
<h3>Cadre evenement</h3>
|
||||
<p class="aide">Cadre specifique a cet evenement. Applique sur tous les formats sauf strips.</p>
|
||||
<div id="cadres-event-formats"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -760,11 +768,11 @@
|
||||
</div>
|
||||
|
||||
<script src="/js/websocket.js?v=3"></script>
|
||||
<script src="/js/app.js?v=5"></script>
|
||||
<script src="/js/camera.js?v=12"></script>
|
||||
<script src="/js/app.js?v=6"></script>
|
||||
<script src="/js/camera.js?v=13"></script>
|
||||
<script src="/js/effects.js?v=4"></script>
|
||||
<script src="/js/gallery.js?v=3"></script>
|
||||
<script src="/js/share.js?v=7"></script>
|
||||
<script src="/js/admin.js?v=6"></script>
|
||||
<script src="/js/admin.js?v=7"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -762,6 +762,12 @@ function afficherDetailEvent(event) {
|
||||
const select = document.getElementById('admin-media-accueil');
|
||||
if (event.media_accueil) select.value = event.media_accueil;
|
||||
});
|
||||
|
||||
const fmts = event.formats_actifs || ['10x15', '15x20', 'strip'];
|
||||
document.getElementById('tog-event-fmt-10x15').checked = fmts.includes('10x15');
|
||||
document.getElementById('tog-event-fmt-15x20').checked = fmts.includes('15x20');
|
||||
document.getElementById('tog-event-fmt-strip').checked = fmts.includes('strip');
|
||||
|
||||
chargerCadresEvent();
|
||||
}
|
||||
|
||||
@@ -809,12 +815,18 @@ async function uploaderMediaAccueil() {
|
||||
}
|
||||
|
||||
async function sauvegarderEvenement() {
|
||||
const formats_actifs = [];
|
||||
if (document.getElementById('tog-event-fmt-10x15').checked) formats_actifs.push('10x15');
|
||||
if (document.getElementById('tog-event-fmt-15x20').checked) formats_actifs.push('15x20');
|
||||
if (document.getElementById('tog-event-fmt-strip').checked) formats_actifs.push('strip');
|
||||
|
||||
const donnees = {
|
||||
theme: document.getElementById('admin-theme').value,
|
||||
nom: getValue('admin-nom-event'),
|
||||
couleur_primaire: getValue('admin-couleur-primaire'),
|
||||
couleur_secondaire: getValue('admin-couleur-secondaire'),
|
||||
media_accueil: getValue('admin-media-accueil') || null,
|
||||
formats_actifs,
|
||||
};
|
||||
|
||||
if (eventEditId) {
|
||||
@@ -834,79 +846,88 @@ async function sauvegarderEvenement() {
|
||||
|
||||
// === CADRES EVENEMENT ===
|
||||
|
||||
const FORMATS_EVENT = ['strip', '10x15', '15x20'];
|
||||
const FORMATS_CADRE_EVENT = ['10x15', '15x20'];
|
||||
|
||||
async function chargerCadresEvent() {
|
||||
if (!eventEditId) return;
|
||||
const container = document.getElementById('cadres-event-formats');
|
||||
container.innerHTML = '';
|
||||
|
||||
for (const fmt of FORMATS_EVENT) {
|
||||
const data = await apiGet(`/api/evenements/${eventEditId}/cadres/${fmt}`);
|
||||
const section = document.createElement('div');
|
||||
section.className = 'cadre-event-section';
|
||||
// Charger cadres du premier format pour afficher le mode + liste
|
||||
const data = await apiGet(`/api/evenements/${eventEditId}/cadres/15x20`);
|
||||
const data2 = await apiGet(`/api/evenements/${eventEditId}/cadres/10x15`);
|
||||
const tous = [...new Set([...(data.disponibles || []), ...(data2.disponibles || [])])];
|
||||
|
||||
let cadresHtml = '';
|
||||
for (const nom of data.disponibles) {
|
||||
const selected = data.cadre === nom ? ' cadre-selectionne' : '';
|
||||
cadresHtml += `<div class="cadre-event-item${selected}" onclick="selectionnerCadreEvent('${fmt}','${nom}')">
|
||||
<img src="/data/evenements/${eventEditId}/cadres/${fmt}/${nom}" alt="${nom}">
|
||||
<span>${nom.replace('.png','')}</span>
|
||||
<button class="btn-danger btn-icone" onclick="event.stopPropagation();supprimerCadreEvent('${fmt}','${nom}')">✕</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
section.innerHTML = `
|
||||
<h4>Format ${fmt}</h4>
|
||||
<div class="champ">
|
||||
<label>Mode cadre</label>
|
||||
<select id="mode-cadre-event-${fmt}" onchange="changerModeCadreEvent('${fmt}')">
|
||||
<option value="aucun" ${data.mode === 'aucun' ? 'selected' : ''}>Aucun</option>
|
||||
<option value="disponible" ${data.mode === 'disponible' ? 'selected' : ''}>Disponible (choix utilisateur)</option>
|
||||
<option value="defaut" ${data.mode === 'defaut' ? 'selected' : ''}>Par defaut (pre-selectionne)</option>
|
||||
<option value="impose" ${data.mode === 'impose' ? 'selected' : ''}>Impose (toujours applique)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="cadres-event-grille">${cadresHtml || '<p class="texte-secondaire">Aucun cadre</p>'}</div>
|
||||
<input type="file" id="input-cadre-event-${fmt}" accept=".png" class="input-fichier">
|
||||
<button class="btn-secondaire btn-petit" onclick="uploaderCadreEvent('${fmt}')">Importer cadre ${fmt}</button>
|
||||
`;
|
||||
container.appendChild(section);
|
||||
let cadresHtml = '';
|
||||
for (const nom of tous) {
|
||||
const selected = data.cadre === nom ? ' cadre-selectionne' : '';
|
||||
const src15 = `/data/evenements/${eventEditId}/cadres/15x20/${nom}`;
|
||||
const src10 = `/data/evenements/${eventEditId}/cadres/10x15/${nom}`;
|
||||
cadresHtml += `<div class="cadre-event-item${selected}" onclick="selectionnerCadreEvent('${nom}')">
|
||||
<img src="${src15}" onerror="this.src='${src10}'" alt="${nom}">
|
||||
<span>${nom.replace('.png','')}</span>
|
||||
<button class="btn-danger btn-icone" onclick="event.stopPropagation();supprimerCadreEvent('${nom}')">✕</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="champ">
|
||||
<label>Mode cadre</label>
|
||||
<select id="mode-cadre-event" onchange="changerModeCadreEvent()">
|
||||
<option value="aucun" ${data.mode === 'aucun' ? 'selected' : ''}>Aucun</option>
|
||||
<option value="disponible" ${data.mode === 'disponible' ? 'selected' : ''}>Disponible (choix utilisateur)</option>
|
||||
<option value="defaut" ${data.mode === 'defaut' ? 'selected' : ''}>Par defaut (pre-selectionne)</option>
|
||||
<option value="impose" ${data.mode === 'impose' ? 'selected' : ''}>Impose (toujours applique)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="cadres-event-grille">${cadresHtml || '<p class="texte-secondaire">Aucun cadre importe</p>'}</div>
|
||||
<input type="file" id="input-cadre-event" accept=".png" class="input-fichier">
|
||||
<button class="btn-secondaire btn-petit" onclick="uploaderCadreEvent()">Importer cadre</button>
|
||||
`;
|
||||
}
|
||||
|
||||
async function selectionnerCadreEvent(fmt, nom) {
|
||||
async function selectionnerCadreEvent(nom) {
|
||||
if (!eventEditId) return;
|
||||
const mode = document.getElementById(`mode-cadre-event-${fmt}`).value;
|
||||
await apiPost(`/api/evenements/${eventEditId}/cadres/${fmt}`, { mode, cadre: nom });
|
||||
const mode = document.getElementById('mode-cadre-event').value;
|
||||
for (const fmt of FORMATS_CADRE_EVENT) {
|
||||
await apiPost(`/api/evenements/${eventEditId}/cadres/${fmt}`, { mode, cadre: nom });
|
||||
}
|
||||
await chargerCadresEvent();
|
||||
}
|
||||
|
||||
async function changerModeCadreEvent(fmt) {
|
||||
async function changerModeCadreEvent() {
|
||||
if (!eventEditId) return;
|
||||
const mode = document.getElementById(`mode-cadre-event-${fmt}`).value;
|
||||
const event = await apiGet(`/api/evenements/${eventEditId}`);
|
||||
const cadre = (event.cadres || {})[fmt]?.cadre || null;
|
||||
await apiPost(`/api/evenements/${eventEditId}/cadres/${fmt}`, { mode, cadre });
|
||||
afficherStatut(`Mode cadre ${fmt} : ${mode}`, 'succes');
|
||||
const mode = document.getElementById('mode-cadre-event').value;
|
||||
const ev = await apiGet(`/api/evenements/${eventEditId}`);
|
||||
const cadre = (ev.cadres || {})['15x20']?.cadre || (ev.cadres || {})['10x15']?.cadre || null;
|
||||
for (const fmt of FORMATS_CADRE_EVENT) {
|
||||
await apiPost(`/api/evenements/${eventEditId}/cadres/${fmt}`, { mode, cadre });
|
||||
}
|
||||
afficherStatut(`Mode cadre : ${mode}`, 'succes');
|
||||
}
|
||||
|
||||
async function uploaderCadreEvent(fmt) {
|
||||
async function uploaderCadreEvent() {
|
||||
if (!eventEditId) return;
|
||||
const input = document.getElementById(`input-cadre-event-${fmt}`);
|
||||
const input = document.getElementById('input-cadre-event');
|
||||
if (!input.files.length) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('fichier', input.files[0]);
|
||||
await fetch(`/api/evenements/${eventEditId}/upload-cadre/${fmt}`, { method: 'POST', body: formData });
|
||||
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 ${fmt} importe`, 'succes');
|
||||
afficherStatut('Cadre importe (10x15 + 15x20)', 'succes');
|
||||
}
|
||||
|
||||
async function supprimerCadreEvent(fmt, nom) {
|
||||
async function supprimerCadreEvent(nom) {
|
||||
if (!eventEditId) return;
|
||||
await fetch(`/api/evenements/${eventEditId}/cadres/${fmt}/${encodeURIComponent(nom)}`, { method: 'DELETE' });
|
||||
for (const fmt of FORMATS_CADRE_EVENT) {
|
||||
await fetch(`/api/evenements/${eventEditId}/cadres/${fmt}/${encodeURIComponent(nom)}`, { method: 'DELETE' }).catch(() => {});
|
||||
}
|
||||
await chargerCadresEvent();
|
||||
afficherStatut('Cadre supprime', 'succes');
|
||||
}
|
||||
|
||||
@@ -48,8 +48,10 @@ function appliquerConfig() {
|
||||
const h1 = document.getElementById('nom-evenement');
|
||||
if (h1) h1.textContent = event.nom || 'Photobooth';
|
||||
|
||||
// Visibilite des modes
|
||||
toggleVisible('btn-multi', fonc.multi_shot);
|
||||
// Visibilite des modes (restreint par formats evenement)
|
||||
const fmtsEvent = event.formats_actifs;
|
||||
const stripDispo = !fmtsEvent || fmtsEvent.includes('strip');
|
||||
toggleVisible('btn-multi', fonc.multi_shot && stripDispo);
|
||||
toggleVisible('btn-imprimer', fonc.impression);
|
||||
toggleVisible('btn-email', fonc.email);
|
||||
const boothActif = config.booth?.actif;
|
||||
|
||||
@@ -437,6 +437,10 @@ async function majBoutonImprimerCompteur() {
|
||||
}
|
||||
|
||||
async function chargerCadreImpose() {
|
||||
if (modeActuel === 'multi') {
|
||||
afficherCadreOverlay(null);
|
||||
return;
|
||||
}
|
||||
const fmt = (config.impression?.format || '15x20').replace('-2up', '');
|
||||
const data = await apiGet(`/api/cadres-impression/${fmt}`).catch(() => null);
|
||||
if (!data) { afficherCadreOverlay(null); return; }
|
||||
|
||||
Reference in New Issue
Block a user