(() => { "use strict"; const button = document.getElementById("uploadPhotoBtn"); if (!button) { console.warn("[photoUpload] Pulsante upload non trovato"); return; } let uploading = false; function authHeaders() { const token = localStorage.getItem("token"); return token ? { Authorization: `Bearer ${token}` } : {}; } async function readResponse(response) { const contentType = response.headers.get("content-type") || ""; if (contentType.includes("application/json")) { return response.json(); } const text = await response.text(); throw new Error(text || `Errore HTTP ${response.status}`); } async function api(url, options = {}) { const response = await fetch(url, { credentials: "include", ...options, headers: { Accept: "application/json", ...authHeaders(), ...(options.headers || {}), }, }); const result = await readResponse(response); if (!response.ok || !result.ok) { throw new Error(result.error || `Errore HTTP ${response.status}`); } return result; } function validFolder(value) { return Boolean(value) && value !== "." && value !== ".." && !value.includes("/") && !value.includes("\\") && !value.includes("\0"); } function getStatus() { let status = document.getElementById("photoUploadClientStatus"); if (!status) { status = document.createElement("span"); status.id = "photoUploadClientStatus"; status.setAttribute("role", "status"); status.setAttribute("aria-live", "polite"); status.style.display = "none"; document.body.appendChild(status); } status.style.display = "none"; return status; } function chooseFile() { return new Promise((resolve) => { const input = document.createElement("input"); input.type = "file"; input.accept = "image/*"; input.multiple = true; input.style.display = "none"; document.body.appendChild(input); let finished = false; function finish(files) { if (finished) return; finished = true; input.remove(); window.removeEventListener("focus", onFocus); resolve(files); } function onFocus() { setTimeout(() => { if (!input.files?.length) finish([]); }, 500); } input.addEventListener("change", () => { finish(Array.from(input.files || [])); }, { once: true }); window.addEventListener("focus", onFocus, { once: true }); input.click(); }); } function createDialog() { const dialog = document.createElement("dialog"); dialog.setAttribute("aria-labelledby", "photoUploadTitle"); dialog.style.cssText = [ "width:min(92vw,420px)", "max-width:420px", "border:0", "border-radius:12px", "padding:22px", "box-shadow:0 8px 36px #0004", "color:inherit", ].join(";"); dialog.innerHTML = `
`; document.body.appendChild(dialog); const form = dialog.querySelector("#photoUploadForm"); const select = dialog.querySelector("#photoUploadFolder"); const newBox = dialog.querySelector("#photoUploadNewFolderBox"); const newInput = dialog.querySelector("#photoUploadNewFolder"); const error = dialog.querySelector("#photoUploadDialogError"); select.addEventListener("change", () => { const isNew = select.value === "__new__"; newBox.hidden = !isNew; newInput.required = isNew; error.textContent = ""; if (isNew) newInput.focus(); }); dialog.querySelector("#photoUploadCancel").addEventListener("click", () => { dialog.close("cancel"); }); dialog.addEventListener("click", (event) => { if (event.target === dialog) dialog.close("cancel"); }); return { dialog, form, select, newBox, newInput, error, userInput: dialog.querySelector("#photoUploadUser"), continueButton: dialog.querySelector("#photoUploadContinue"), }; } async function openUploadDialog() { const targetsResult = await api("/photos/upload-targets"); // La destinazione è sempre l'utente autenticato, non un device. const target = targetsResult.targets?.find( (item) => item.type === "user" && String(item.value).toLowerCase() === String(targetsResult.user).toLowerCase() ); if (!target) { throw new Error("Non è disponibile una destinazione personale autorizzata."); } const foldersResult = await api( `/photos/upload-folders?source=${encodeURIComponent(target.value)}` ); const ui = createDialog(); ui.userInput.value = targetsResult.user; const newOption = document.createElement("option"); newOption.value = "__new__"; newOption.textContent = "+ Nuova cartella…"; ui.select.appendChild(newOption); for (const folder of foldersResult.folders || []) { const option = document.createElement("option"); option.value = folder; option.textContent = folder; ui.select.appendChild(option); } // Seleziona inizialmente la prima cartella esistente, se disponibile. // La voce per creare una cartella resta sempre in cima. if (foldersResult.folders?.length) { ui.select.value = foldersResult.folders[0]; } else { ui.select.value = "__new__"; ui.newBox.hidden = false; ui.newInput.required = true; } ui.dialog.showModal(); return new Promise((resolve, reject) => { let finished = false; function finish(value) { if (finished) return; finished = true; ui.dialog.close(); ui.dialog.remove(); resolve(value); } ui.dialog.addEventListener("close", () => { if (!finished) finish(null); }, { once: true }); ui.form.addEventListener("submit", (event) => { event.preventDefault(); ui.error.textContent = ""; let folder = ui.select.value; if (folder === "__new__") { folder = ui.newInput.value.trim(); if (!validFolder(folder)) { ui.error.textContent = "Inserisci un nome di cartella valido, senza / o \\."; ui.newInput.focus(); return; } } if (!folder) { ui.error.textContent = "Seleziona una cartella."; return; } finish({ source: target.value, folder }); }); // Il reject resta disponibile per eventuali errori futuri nel dialogo. void reject; }); } button.addEventListener("click", async () => { if (uploading) return; const status = getStatus(); const setStatus = (message) => { status.textContent = message; if (window.operationBanner) { const lower = String(message).toLowerCase(); const error = lower.startsWith("errore:"); const done = lower.includes("upload completato"); const busy = !error && !done && (lower.includes("carico") || lower.includes("caricamento")); window.operationBanner.show(message, { type: error ? "error" : done ? "success" : "upload", busy, sticky: busy }); } }; uploading = true; button.disabled = true; try { setStatus("Carico le cartelle..."); const destination = await openUploadDialog(); if (!destination) { setStatus("Upload annullato."); return; } setStatus("Seleziona una o più foto..."); const files = await chooseFile(); if (!files.length) { setStatus("Nessuna foto selezionata."); return; } const invalidFile = files.find( file => file.type && !file.type.startsWith("image/") ); if (invalidFile) { throw new Error(`Il file "${invalidFile.name}" non è un'immagine.`); } const formData = new FormData(); formData.append("source", destination.source); formData.append("folder", destination.folder); for (const file of files) { formData.append("photo", file, file.name); } setStatus(`Caricamento di ${files.length} foto...`); const result = await api("/photos/upload", { method: "POST", body: formData, }); const uploadedCount = Array.isArray(result.files) ? result.files.length : files.length; setStatus( `${uploadedCount} foto caricate. Attendo l'indicizzazione...` ); // Mantiene invariato il comportamento precedente della galleria. await new Promise((resolve) => setTimeout(resolve, 2000)); if (typeof window.refreshGallery === "function") { window.refreshGallery(); } setStatus( "Upload completato. L'indicizzazione potrebbe richiedere qualche istante." ); if (window.operationBanner) { window.operationBanner.show( "Upload completato. L'indicizzazione potrebbe richiedere qualche istante.", { type: "success", busy: false, sticky: false, duration: 3500 } ); } } catch (error) { console.error("[photoUpload] Errore:", error); setStatus(`Errore: ${error.message || "Upload non riuscito"}`); } finally { uploading = false; button.disabled = false; } }); })();