gallery_server_client_s190js/public/js/admin/photoUpload.js
2026-10-26 11:00:14 +01:00

395 lines
9.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

(() => {
"use strict";
const targetSelect = document.getElementById("photoUploadTarget");
const folderInput = document.getElementById("photoUploadFolder");
const uploadButton = document.getElementById("btnUploadPhoto");
const fileInput = document.getElementById("photoUploadInput");
const statusBox = document.getElementById("photoUploadStatus");
if (
!targetSelect ||
!folderInput ||
!uploadButton ||
!fileInput ||
!statusBox
) {
console.warn("[photoUpload] Elementi interfaccia upload non trovati");
return;
}
let pendingUpload = null;
let uploading = false;
function setStatus(message, type = "info") {
statusBox.textContent = message || "";
statusBox.className = `upload-${type}`;
if (window.operationBanner && message) {
const bannerType = type === "error" ? "error" :
type === "success" ? "success" : "upload";
const busy = type === "info" &&
/in corso|caricamento destinazioni/i.test(message);
window.operationBanner.show(message, {
type: bannerType,
busy,
sticky: busy
});
}
}
function normalize(value) {
return String(value ?? "").trim();
}
function getAuthorizationHeaders() {
/*
* Se l'autenticazione utilizza cookie/sessione,
* non serve aggiungere Authorization.
*
* Se utilizzi JWT in localStorage, modifica i nomi
* qui sotto in base al tuo progetto.
*/
const token =
localStorage.getItem("token") ||
localStorage.getItem("jwtToken") ||
localStorage.getItem("accessToken");
if (!token) {
return {};
}
return {
Authorization: `Bearer ${token}`,
};
}
async function readJsonResponse(response) {
const contentType = response.headers.get("content-type") || "";
if (contentType.includes("application/json")) {
return response.json();
}
const text = await response.text();
return {
ok: false,
error: text || `Errore HTTP ${response.status}`,
};
}
function setUploadEnabled() {
const hasTarget = Boolean(normalize(targetSelect.value));
const hasFolder = Boolean(normalize(folderInput.value));
uploadButton.disabled = uploading || !hasTarget || !hasFolder;
}
async function loadUploadTargets() {
targetSelect.disabled = true;
uploadButton.disabled = true;
targetSelect.innerHTML =
'<option value="">Caricamento destinazioni...</option>';
// Il caricamento iniziale non deve mostrare un banner.
try {
const response = await fetch("/photos/upload-targets", {
method: "GET",
credentials: "include",
headers: {
Accept: "application/json",
...getAuthorizationHeaders(),
},
});
const result = await readJsonResponse(response);
if (!response.ok || !result.ok) {
throw new Error(
result.error ||
`Impossibile caricare le destinazioni (${response.status})`,
);
}
const targets = Array.isArray(result.targets) ? result.targets : [];
targetSelect.replaceChildren();
const placeholder = document.createElement("option");
placeholder.value = "";
placeholder.textContent = "Seleziona utente o device";
targetSelect.appendChild(placeholder);
for (const target of targets) {
const value = normalize(target.value);
if (!value) {
continue;
}
const option = document.createElement("option");
option.value = value;
option.textContent =
target.type === "device"
? `${target.label || value} (device)`
: `${target.label || value} (utente)`;
option.dataset.targetType = target.type || "";
targetSelect.appendChild(option);
}
targetSelect.disabled = targets.length === 0;
if (targets.length === 0) {
setStatus("Nessuna destinazione autorizzata per l'upload.", "error");
} else {
// Nessun banner automatico all’apertura della pagina.
}
setUploadEnabled();
} catch (error) {
console.error("[photoUpload] Errore upload-targets:", error);
targetSelect.innerHTML =
'<option value="">Destinazioni non disponibili</option>';
targetSelect.disabled = true;
uploadButton.disabled = true;
setStatus(error.message, "error");
}
}
uploadButton.addEventListener("click", () => {
if (uploading) {
return;
}
const source = normalize(targetSelect.value);
const folder = normalize(folderInput.value);
if (!source) {
setStatus("Seleziona prima l'utente o il device.", "error");
return;
}
if (!folder) {
setStatus("Inserisci prima il nome della cartella.", "error");
folderInput.focus();
return;
}
if (
folder === "." ||
folder === ".." ||
folder.includes("/") ||
folder.includes("\\") ||
folder.includes("\0")
) {
setStatus("Nome cartella non valido. Non usare /, \\ oppure ..", "error");
folderInput.focus();
return;
}
pendingUpload = {
source,
folder,
};
fileInput.value = "";
setStatus(
`Destinazione: ${source}\n` +
`Cartella: ${folder}\n` +
"Apertura del selettore file...",
"info",
);
/*
* Deve essere chiamato direttamente nel gestore del click.
* Non inserire await, setTimeout o prompt prima di questo punto.
*/
fileInput.click();
});
fileInput.addEventListener("change", async () => {
const files = Array.from(fileInput.files || []);
console.log(
"[photoUpload] File selezionati:",
files.map((file) => ({
name: file.name,
type: file.type,
size: file.size,
})),
);
if (files.length === 0) {
pendingUpload = null;
setStatus("Nessun file selezionato.", "error");
return;
}
if (!pendingUpload) {
setStatus(
"Destinazione upload non disponibile. Ripeti la selezione.",
"error",
);
fileInput.value = "";
return;
}
const { source, folder } = pendingUpload;
pendingUpload = null;
uploading = true;
targetSelect.disabled = true;
folderInput.disabled = true;
setUploadEnabled();
const totalSelectedBytes = files.reduce(
(total, file) => total + file.size,
0,
);
const totalSelectedMb = (totalSelectedBytes / 1024 / 1024).toFixed(1);
setStatus(
`Upload di ${files.length} file in corso...\n` +
`Dimensione totale: ${totalSelectedMb} MB\n` +
`Destinazione: ${source}\n` +
`Cartella: ${folder}`,
"info",
);
try {
const formData = new FormData();
formData.append("source", source);
formData.append("folder", folder);
/*
* Tutti i file usano il campo "photo".
* Il server li riceve con:
* uploadMedia.array("photo", MEDIA_UPLOAD_MAX_FILES)
*/
for (const file of files) {
formData.append("photo", file, file.name);
}
console.log("[photoUpload] Invio richiesta", {
source,
folder,
fileCount: files.length,
totalSelectedBytes,
});
const response = await fetch("/photos/upload", {
method: "POST",
credentials: "include",
/*
* Non aggiungere Content-Type.
* Il browser genera automaticamente il boundary
* multipart/form-data.
*/
headers: {
Accept: "application/json",
...getAuthorizationHeaders(),
},
body: formData,
});
const result = await readJsonResponse(response);
console.log("[photoUpload] Risposta server:", {
status: response.status,
result,
});
if (!response.ok || !result.ok) {
throw new Error(
result.error || `Upload non riuscito (${response.status})`,
);
}
const returnedFiles = Array.isArray(result.files) ? result.files : [];
const renamedFiles = returnedFiles.filter((file) => {
return file.renamed;
});
let message =
"Upload completato correttamente.\n" +
`File caricati: ${result.fileCount ?? returnedFiles.length}\n` +
`Foto: ${result.photoCount ?? 0}\n` +
`Video: ${result.videoCount ?? 0}\n` +
`Destinazione: ${result.source ?? source}\n` +
`Cartella: ${result.folder ?? folder}`;
if (renamedFiles.length > 0) {
message +=
"\nFile rinominati perché già esistenti: " + renamedFiles.length;
for (const file of renamedFiles) {
message += `\n${file.originalFileName} -> ${file.fileName}`;
}
}
if (result.watcherPending) {
message += "\nIl watcher elaborerà automaticamente tutti i media.";
}
setStatus(message, "success");
} catch (error) {
console.error("[photoUpload] Errore upload:", error);
setStatus(`Upload fallito: ${error.message}`, "error");
} finally {
uploading = false;
targetSelect.disabled = false;
folderInput.disabled = false;
/*
* Permette di selezionare nuovamente gli stessi file.
*/
fileInput.value = "";
setUploadEnabled();
}
});
folderInput.addEventListener("input", () => {
setUploadEnabled();
});
targetSelect.addEventListener("change", () => {
setUploadEnabled();
const source = normalize(targetSelect.value);
if (source) {
setStatus(`Destinazione selezionata: ${source}`, "info");
} else {
setStatus("Seleziona l'utente o il device di destinazione.", "info");
}
});
loadUploadTargets();
})();