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

384 lines
11 KiB
JavaScript
Raw Permalink 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 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 = `
<form id="photoUploadForm">
<h2 id="photoUploadTitle" style="margin:0 0 18px;font-size:1.2rem">
Carica una foto
</h2>
<label for="photoUploadUser" style="display:block;margin-bottom:6px">
Utente
</label>
<input id="photoUploadUser" type="text" readonly
style="box-sizing:border-box;width:100%;padding:10px;margin-bottom:16px">
<label for="photoUploadFolder" style="display:block;margin-bottom:6px">
Cartella di destinazione
</label>
<select id="photoUploadFolder" required
style="box-sizing:border-box;width:100%;padding:10px;margin-bottom:12px">
</select>
<div id="photoUploadNewFolderBox" hidden>
<label for="photoUploadNewFolder" style="display:block;margin-bottom:6px">
Nome della nuova cartella
</label>
<input id="photoUploadNewFolder" type="text"
maxlength="200" autocomplete="off"
placeholder="Inserisci il nome"
style="box-sizing:border-box;width:100%;padding:10px;margin-bottom:12px">
</div>
<p id="photoUploadDialogError" role="alert"
style="color:#c62828;margin:4px 0 12px"></p>
<div style="display:flex;justify-content:flex-end;gap:8px;margin-top:12px">
<button type="button" id="photoUploadCancel">Annulla</button>
<button type="submit" id="photoUploadContinue">Scegli foto</button>
</div>
</form>
`;
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;
}
});
})();