Ajoute un accès direct à la galerie en plus de la caméra

Deux boutons séparés (deux inputs file) plutôt qu'un seul avec
capture=environment qui masquait l'option galerie — retour de Kévin
après test réel. État de sélection centralisé dans state.selectedFile.
This commit is contained in:
2026-09-16 13:12:38 +02:00
parent 62a15ffe2d
commit 7afd3b6a18
3 changed files with 87 additions and 9 deletions
+19 -5
View File
@@ -36,11 +36,15 @@
<form id="upload-form" class="upload-form" hidden novalidate> <form id="upload-form" class="upload-form" hidden novalidate>
<div class="field"> <div class="field">
<label for="photo-input">Ta photo</label> <label id="photo-field-label">Ta photo</label>
<!-- capture="environment" : ouvre directement l'appareil photo <!-- Deux entrées séparées : "photo-input" (capture="environment")
(caméra arrière) sur mobile plutôt que la galerie — décision ouvre directement l'appareil photo, "photo-input-gallery" ouvre
actée avec Kévin le 2026-09-16, remplace le choix initial qui le sélecteur de fichiers classique. Décision actée avec Kévin le
laissait le choix caméra/galerie. --> 2026-09-16 : les deux doivent être accessibles en un tap. -->
<div class="photo-source-buttons">
<label for="photo-input" class="btn-secondary">📷 Prendre une photo</label>
<label for="photo-input-gallery" class="btn-secondary">🖼️ Depuis la galerie</label>
</div>
<input <input
type="file" type="file"
id="photo-input" id="photo-input"
@@ -48,7 +52,17 @@
accept="image/*,.heic,.heif" accept="image/*,.heic,.heif"
capture="environment" capture="environment"
required required
class="visually-hidden"
> >
<input
type="file"
id="photo-input-gallery"
accept="image/*,.heic,.heif"
class="visually-hidden"
tabindex="-1"
aria-hidden="true"
>
<p id="file-chosen-label" class="file-chosen-label">Aucun fichier choisi</p>
</div> </div>
<img id="preview" class="preview" alt="Aperçu de la photo choisie" hidden> <img id="preview" class="preview" alt="Aperçu de la photo choisie" hidden>
+44
View File
@@ -112,6 +112,50 @@ input[type="file"] {
padding: 0.55rem 0.5rem; padding: 0.55rem 0.5rem;
} }
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.photo-source-buttons {
display: flex;
gap: 0.6rem;
}
.btn-secondary {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
font-weight: 600;
font-size: 0.9rem;
padding: 0.85rem 0.6rem;
min-height: 48px;
border: 1px solid var(--color-accent);
border-radius: var(--radius);
color: var(--color-accent);
background: #fff;
cursor: pointer;
}
.btn-secondary:active {
transform: translateY(1px);
}
.file-chosen-label {
font-size: 0.85rem;
color: var(--color-muted);
margin: 0;
}
textarea { textarea {
resize: vertical; resize: vertical;
min-height: 4.5rem; min-height: 4.5rem;
+24 -4
View File
@@ -40,6 +40,8 @@ const successBlock = document.getElementById("success-block");
const addAnotherBtn = document.getElementById("add-another-btn"); const addAnotherBtn = document.getElementById("add-another-btn");
const form = document.getElementById("upload-form"); const form = document.getElementById("upload-form");
const photoInput = document.getElementById("photo-input"); const photoInput = document.getElementById("photo-input");
const photoInputGallery = document.getElementById("photo-input-gallery");
const fileChosenLabel = document.getElementById("file-chosen-label");
const previewImg = document.getElementById("preview"); const previewImg = document.getElementById("preview");
const nomInput = document.getElementById("nom-input"); const nomInput = document.getElementById("nom-input");
const messageInput = document.getElementById("message-input"); const messageInput = document.getElementById("message-input");
@@ -413,17 +415,20 @@ async function insertPhotoRow(eventId, path, nom, message) {
let previewObjectUrl = null; let previewObjectUrl = null;
photoInput.addEventListener("change", () => { // Deux inputs (caméra directe / galerie) partagent la même logique de
// sélection — état centralisé dans state.selectedFile plutôt que de lire
// photoInput.files[0] un peu partout, pour rester correct quel que soit
// l'input utilisé.
function handleFileSelected(file) {
// Nouveau fichier choisi -> on invalide toute compression déjà faite. // Nouveau fichier choisi -> on invalide toute compression déjà faite.
state.compressedBlob = null; state.compressedBlob = null;
state.selectedFile = photoInput.files[0] || null; state.selectedFile = file || null;
if (previewObjectUrl) { if (previewObjectUrl) {
URL.revokeObjectURL(previewObjectUrl); URL.revokeObjectURL(previewObjectUrl);
previewObjectUrl = null; previewObjectUrl = null;
} }
const file = state.selectedFile;
if (file && file.type && file.type.startsWith("image/")) { if (file && file.type && file.type.startsWith("image/")) {
previewObjectUrl = URL.createObjectURL(file); previewObjectUrl = URL.createObjectURL(file);
previewImg.src = previewObjectUrl; previewImg.src = previewObjectUrl;
@@ -435,6 +440,20 @@ photoInput.addEventListener("change", () => {
// Pas d'aperçu fiable pour les HEIC sur la plupart des navigateurs. // Pas d'aperçu fiable pour les HEIC sur la plupart des navigateurs.
previewImg.hidden = true; previewImg.hidden = true;
} }
fileChosenLabel.textContent = file ? file.name : "Aucun fichier choisi";
}
photoInput.addEventListener("change", () => {
handleFileSelected(photoInput.files[0] || null);
// Un seul fichier actif à la fois : on vide l'autre input pour éviter
// toute ambiguïté si l'utilisateur change de méthode après coup.
photoInputGallery.value = "";
});
photoInputGallery.addEventListener("change", () => {
handleFileSelected(photoInputGallery.files[0] || null);
photoInput.value = "";
}); });
async function submitPhoto() { async function submitPhoto() {
@@ -444,7 +463,7 @@ async function submitPhoto() {
return; return;
} }
const file = photoInput.files[0]; const file = state.selectedFile;
if (!file) return showError(appError("file-missing")); if (!file) return showError(appError("file-missing"));
if (!isAcceptableImageFile(file)) return showError(appError("file-not-image")); if (!isAcceptableImageFile(file)) return showError(appError("file-not-image"));
if (file.size > MAX_INPUT_BYTES) return showError(appError("file-too-large")); if (file.size > MAX_INPUT_BYTES) return showError(appError("file-too-large"));
@@ -487,6 +506,7 @@ addAnotherBtn.addEventListener("click", () => {
form.reset(); form.reset();
state.selectedFile = null; state.selectedFile = null;
state.compressedBlob = null; state.compressedBlob = null;
fileChosenLabel.textContent = "Aucun fichier choisi";
previewImg.hidden = true; previewImg.hidden = true;
if (previewObjectUrl) { if (previewObjectUrl) {
URL.revokeObjectURL(previewObjectUrl); URL.revokeObjectURL(previewObjectUrl);