added level to collahge

This commit is contained in:
2025-09-12 18:09:01 +02:00
parent ce8c95921f
commit 1510ef03f1
3 changed files with 137 additions and 244 deletions
File diff suppressed because one or more lines are too long
+108 -234
View File
@@ -7,7 +7,6 @@ document.addEventListener("DOMContentLoaded", function () {
return; return;
} }
try { try {
console.log("Caricamento contenuto per iddatadb:", iddatadb);
const response = await fetch( const response = await fetch(
`photos_popup.php?iddatadb=${iddatadb}`, `photos_popup.php?iddatadb=${iddatadb}`,
); );
@@ -56,22 +55,18 @@ document.addEventListener("DOMContentLoaded", function () {
return; return;
} }
// Funzione per avviare la webcam con un deviceId specifico
async function startWebcam(deviceId = null) { async function startWebcam(deviceId = null) {
try { try {
// Ferma il flusso video esistente, se presente
if (stream) { if (stream) {
stream.getTracks().forEach((track) => track.stop()); stream.getTracks().forEach((track) => track.stop());
stream = null; stream = null;
webcamVideo.srcObject = null; webcamVideo.srcObject = null;
} }
// Configura i vincoli per getUserMedia
const constraints = { const constraints = {
video: deviceId ? { deviceId: { exact: deviceId } } : true, video: deviceId ? { deviceId: { exact: deviceId } } : true,
}; };
// Avvia il flusso video
stream = await navigator.mediaDevices.getUserMedia(constraints); stream = await navigator.mediaDevices.getUserMedia(constraints);
webcamVideo.srcObject = stream; webcamVideo.srcObject = stream;
webcamArea.style.display = "block"; webcamArea.style.display = "block";
@@ -86,21 +81,17 @@ document.addEventListener("DOMContentLoaded", function () {
} }
} }
// Funzione per popolare il dropdown delle webcam
async function populateWebcamSelect() { async function populateWebcamSelect() {
try { try {
// Richiedi i permessi per accedere ai dispositivi
await navigator.mediaDevices.getUserMedia({ video: true }); await navigator.mediaDevices.getUserMedia({ video: true });
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter( const videoDevices = devices.filter(
(device) => device.kind === "videoinput", (device) => device.kind === "videoinput",
); );
// Svuota il dropdown
webcamSelect.innerHTML = webcamSelect.innerHTML =
'<option value="">Select a webcam</option>'; '<option value="">Select a webcam</option>';
// Popola il dropdown con le webcam disponibili
videoDevices.forEach((device) => { videoDevices.forEach((device) => {
const option = document.createElement("option"); const option = document.createElement("option");
option.value = device.deviceId; option.value = device.deviceId;
@@ -109,11 +100,9 @@ document.addEventListener("DOMContentLoaded", function () {
webcamSelect.appendChild(option); webcamSelect.appendChild(option);
}); });
// Mostra il dropdown solo se ci sono più webcam
webcamSelect.style.display = webcamSelect.style.display =
videoDevices.length > 1 ? "block" : "none"; videoDevices.length > 1 ? "block" : "none";
// Avvia la webcam predefinita se ce n'è almeno una
if (videoDevices.length > 0) { if (videoDevices.length > 0) {
await startWebcam(videoDevices[0].deviceId); await startWebcam(videoDevices[0].deviceId);
} else { } else {
@@ -129,12 +118,10 @@ document.addEventListener("DOMContentLoaded", function () {
} }
} }
// Apri la webcam e popola il dropdown
openWebcamBtn.addEventListener("click", async () => { openWebcamBtn.addEventListener("click", async () => {
await populateWebcamSelect(); await populateWebcamSelect();
}); });
// Gestisci il cambio della webcam selezionata
webcamSelect.addEventListener("change", async (e) => { webcamSelect.addEventListener("change", async (e) => {
const deviceId = e.target.value; const deviceId = e.target.value;
if (deviceId) { if (deviceId) {
@@ -142,7 +129,6 @@ document.addEventListener("DOMContentLoaded", function () {
} }
}); });
// Chiudi la webcam
closeWebcamBtn.addEventListener("click", () => { closeWebcamBtn.addEventListener("click", () => {
if (stream) { if (stream) {
stream.getTracks().forEach((track) => track.stop()); stream.getTracks().forEach((track) => track.stop());
@@ -154,7 +140,6 @@ document.addEventListener("DOMContentLoaded", function () {
dropArea.style.display = "block"; dropArea.style.display = "block";
}); });
// Cattura la foto
captureBtn.addEventListener("click", () => { captureBtn.addEventListener("click", () => {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.width = webcamVideo.videoWidth; canvas.width = webcamVideo.videoWidth;
@@ -171,7 +156,6 @@ document.addEventListener("DOMContentLoaded", function () {
); );
const loader = document.getElementById("loader"); const loader = document.getElementById("loader");
if (loader) { if (loader) {
console.log("Mostro loader per upload webcam");
loader.style.display = "flex"; loader.style.display = "flex";
} }
await handleFiles([file], iddatadb); await handleFiles([file], iddatadb);
@@ -187,7 +171,6 @@ document.addEventListener("DOMContentLoaded", function () {
}); });
} }
// Funzione per gestire il caricamento dei file
async function handleFiles(files, iddatadb) { async function handleFiles(files, iddatadb) {
const loader = document.getElementById("loader"); const loader = document.getElementById("loader");
if (!loader) { if (!loader) {
@@ -206,7 +189,6 @@ document.addEventListener("DOMContentLoaded", function () {
continue; continue;
} }
console.log("Inizio upload del file:", file.name);
loader.style.display = "flex"; loader.style.display = "flex";
const formData = new FormData(); const formData = new FormData();
@@ -219,9 +201,6 @@ document.addEventListener("DOMContentLoaded", function () {
}); });
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
console.log(
"Upload completato con successo, ricarico popup",
);
loadPopupContent(iddatadb); loadPopupContent(iddatadb);
} else { } else {
alert("Errore durante il caricamento: " + result.message); alert("Errore durante il caricamento: " + result.message);
@@ -229,13 +208,11 @@ document.addEventListener("DOMContentLoaded", function () {
} catch (error) { } catch (error) {
alert("Errore durante il caricamento: " + error.message); alert("Errore durante il caricamento: " + error.message);
} finally { } finally {
console.log("Nascondo loader dopo upload");
loader.style.display = "none"; loader.style.display = "none";
} }
} }
} }
// Funzione per attaccare gli event listener al contenuto del popup
function attachPhotoEventListeners(iddatadb) { function attachPhotoEventListeners(iddatadb) {
const dropArea = document.getElementById("dropArea"); const dropArea = document.getElementById("dropArea");
const photoInput = document.getElementById("photoInput"); const photoInput = document.getElementById("photoInput");
@@ -250,9 +227,6 @@ document.addEventListener("DOMContentLoaded", function () {
return; return;
} }
console.log("Event listener associati per iddatadb:", iddatadb);
// Gestione drag-and-drop
const preventDefaults = (e) => { const preventDefaults = (e) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@@ -281,7 +255,6 @@ document.addEventListener("DOMContentLoaded", function () {
dropArea.addEventListener( dropArea.addEventListener(
"drop", "drop",
(e) => { (e) => {
console.log("Evento drop attivato");
const files = e.dataTransfer.files; const files = e.dataTransfer.files;
if (files.length > 0) { if (files.length > 0) {
handleFiles(files, iddatadb); handleFiles(files, iddatadb);
@@ -293,7 +266,6 @@ document.addEventListener("DOMContentLoaded", function () {
dropArea.addEventListener( dropArea.addEventListener(
"click", "click",
() => { () => {
console.log("Click su dropArea, apro input file");
photoInput.click(); photoInput.click();
}, },
false, false,
@@ -302,18 +274,15 @@ document.addEventListener("DOMContentLoaded", function () {
photoInput.addEventListener( photoInput.addEventListener(
"change", "change",
(e) => { (e) => {
console.log("Evento change su photoInput");
const files = e.target.files; const files = e.target.files;
if (files.length > 0) { if (files.length > 0) {
handleFiles(files, iddatadb); handleFiles(files, iddatadb);
} }
// Resetta l'input per consentire il caricamento dello stesso file
e.target.value = ""; e.target.value = "";
}, },
false, false,
); );
// Gestione rimozione foto
document.querySelectorAll(".delete-photo-btn").forEach((button) => { document.querySelectorAll(".delete-photo-btn").forEach((button) => {
button.addEventListener("click", async function () { button.addEventListener("click", async function () {
const photoId = this.getAttribute("data-photo-id"); const photoId = this.getAttribute("data-photo-id");
@@ -329,9 +298,6 @@ document.addEventListener("DOMContentLoaded", function () {
}); });
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
console.log(
"Foto eliminata con successo, ricarico popup",
);
loadPopupContent(iddatadb); loadPopupContent(iddatadb);
} else { } else {
alert( alert(
@@ -348,21 +314,17 @@ document.addEventListener("DOMContentLoaded", function () {
}); });
}); });
// Gestione ingrandimento immagini
document.querySelectorAll(".thumbnail").forEach((img) => { document.querySelectorAll(".thumbnail").forEach((img) => {
img.addEventListener("click", function () { img.addEventListener("click", function () {
console.log("Click su thumbnail, apro modale immagine");
const enlargedImage = document.getElementById("enlargedImage"); const enlargedImage = document.getElementById("enlargedImage");
enlargedImage.src = this.src; enlargedImage.src = this.src;
document.getElementById("imageModal").style.display = "block"; document.getElementById("imageModal").style.display = "block";
}); });
}); });
// Gestione chiusura modale immagine
const imageCloseBtn = document.querySelector(".image-modal-close"); const imageCloseBtn = document.querySelector(".image-modal-close");
if (imageCloseBtn) { if (imageCloseBtn) {
imageCloseBtn.addEventListener("click", () => { imageCloseBtn.addEventListener("click", () => {
console.log("Chiusura modale immagine");
document.getElementById("imageModal").style.display = "none"; document.getElementById("imageModal").style.display = "none";
}); });
} }
@@ -370,57 +332,42 @@ document.addEventListener("DOMContentLoaded", function () {
.getElementById("imageModal") .getElementById("imageModal")
.addEventListener("click", function (event) { .addEventListener("click", function (event) {
if (event.target === this) { if (event.target === this) {
console.log(
"Chiusura modale immagine cliccando sullo sfondo",
);
this.style.display = "none"; this.style.display = "none";
} }
}); });
// Inizializza la gestione della webcam
setupWebcam(iddatadb); setupWebcam(iddatadb);
// Gestione bottone Crea Collage
const createCollageBtn = document.getElementById("createCollageBtn"); const createCollageBtn = document.getElementById("createCollageBtn");
if (createCollageBtn) { if (createCollageBtn) {
createCollageBtn.addEventListener("click", () => { createCollageBtn.addEventListener("click", () => {
console.log("Apertura modale collage");
document.getElementById("collageModal").style.display = "block"; document.getElementById("collageModal").style.display = "block";
initCollageCanvas(); initCollageCanvas();
}); });
} }
// Chiusura modale collage
const closeCollageBtn = document.querySelector(".close-collage"); const closeCollageBtn = document.querySelector(".close-collage");
if (closeCollageBtn) { if (closeCollageBtn) {
closeCollageBtn.addEventListener("click", () => { closeCollageBtn.addEventListener("click", () => {
console.log("Chiusura modale collage");
document.getElementById("collageModal").style.display = "none"; document.getElementById("collageModal").style.display = "none";
if (isCropping) { if (isCropping) {
console.log(
"Chiusura modale durante ritaglio, esco dalla modalità ritaglio",
);
exitCropMode(); exitCropMode();
} }
if (isRemovingBackground) { if (isRemovingBackground) {
console.log(
"Chiusura modale durante rimozione sfondo, esco dalla modalità",
);
exitBackgroundRemovalMode(); exitBackgroundRemovalMode();
} }
}); });
} }
// Inizializza canvas con Fabric.js
let canvas; let canvas;
let cropRect = null; let cropRect = null;
let isCropping = false; let isCropping = false;
let croppedImage = null; // Memorizza l'immagine da ritagliare let croppedImage = null;
let isApplyingCrop = false; // Flag per prevenire duplicazioni let isApplyingCrop = false;
let isRemovingBackground = false; // Flag per modalità rimozione sfondo let isRemovingBackground = false;
let backgroundRemovalImage = null; // Immagine in modalità rimozione sfondo let backgroundRemovalImage = null;
let history = []; // Pila per salvare gli stati del canvas let history = [];
const maxHistory = 20; // Limite massimo di stati nella pila const maxHistory = 20;
function initCollageCanvas() { function initCollageCanvas() {
if (typeof fabric === "undefined") { if (typeof fabric === "undefined") {
@@ -434,7 +381,6 @@ document.addEventListener("DOMContentLoaded", function () {
backgroundColor: "#fff", backgroundColor: "#fff",
selection: true, selection: true,
}); });
// Imposta stile globale per i controlli
fabric.Object.prototype.set({ fabric.Object.prototype.set({
cornerColor: "black", cornerColor: "black",
cornerStrokeColor: "black", cornerStrokeColor: "black",
@@ -442,63 +388,103 @@ document.addEventListener("DOMContentLoaded", function () {
borderColor: "black", borderColor: "black",
transparentCorners: false, transparentCorners: false,
}); });
// Abilita ridimensionamento e trascinamento
canvas.on("object:modified", () => { canvas.on("object:modified", () => {
console.log("Oggetto modificato nel canvas");
saveCanvasState(); saveCanvasState();
updateLayersPanel();
canvas.renderAll(); canvas.renderAll();
}); });
// Gestisci selezione per abilitare/disabilitare pulsanti canvas.on("object:added", () => {
updateLayersPanel();
});
canvas.on("object:removed", () => {
updateLayersPanel();
});
canvas.on("selection:created", () => { canvas.on("selection:created", () => {
console.log("Evento selection:created triggerato");
updateButtons(); updateButtons();
}); });
canvas.on("selection:updated", () => { canvas.on("selection:updated", () => {
console.log("Evento selection:updated triggerato");
updateButtons(); updateButtons();
}); });
canvas.on("selection:cleared", () => { canvas.on("selection:cleared", () => {
console.log("Evento selection:cleared triggerato");
if (!isCropping && !isRemovingBackground) { if (!isCropping && !isRemovingBackground) {
updateButtons(); updateButtons();
} else if (isCropping && cropRect) { } else if (isCropping && cropRect) {
console.log( canvas.setActiveObject(cropRect);
"Ignoro selection:cleared perché in modalità ritaglio",
);
canvas.setActiveObject(cropRect); // Ripristina selezione del rettangolo
canvas.renderAll(); canvas.renderAll();
} else if (isRemovingBackground) { } else if (isRemovingBackground) {
console.log( canvas.setActiveObject(backgroundRemovalImage);
"Ignoro selection:cleared perché in modalità rimozione sfondo",
);
canvas.setActiveObject(backgroundRemovalImage); // Ripristina selezione dell'immagine
canvas.renderAll(); canvas.renderAll();
} }
}); });
// Gestisci click sul canvas per la rimozione dello sfondo
canvas.on("mouse:down", (event) => { canvas.on("mouse:down", (event) => {
if (isRemovingBackground && backgroundRemovalImage) { if (isRemovingBackground && backgroundRemovalImage) {
console.log(
"Click sul canvas in modalità rimozione sfondo",
);
handleBackgroundColorSelection(event); handleBackgroundColorSelection(event);
} }
}); });
// Salva lo stato iniziale del canvas
saveCanvasState(); saveCanvasState();
// Forza un aggiornamento iniziale dei pulsanti updateLayersPanel();
updateButtons(); updateButtons();
} }
// Salva lo stato del canvas nella pila function updateLayersPanel() {
function saveCanvasState() { const layersList = document.getElementById("layersList");
if (isCropping || isRemovingBackground) { if (!layersList) {
console.log( console.error("Elemento layersList non trovato");
"Non salvo lo stato perché in modalità ritaglio o rimozione sfondo", return;
); }
layersList.innerHTML = "";
const images = canvas.getObjects("image");
images.forEach((img, index) => {
let thumbSrc;
try {
const thumbCanvas = document.createElement("canvas");
thumbCanvas.width = 50;
thumbCanvas.height = 50;
const thumbFabric = new fabric.Canvas(thumbCanvas);
const clonedImg = fabric.util.object.clone(img);
const scaleFactor = Math.min(
50 / img.width,
50 / img.height,
);
clonedImg.scaleX = scaleFactor;
clonedImg.scaleY = scaleFactor;
clonedImg.left = (50 - img.width * scaleFactor) / 2;
clonedImg.top = (50 - img.height * scaleFactor) / 2;
clonedImg.setCoords();
thumbFabric.add(clonedImg);
thumbFabric.renderAll();
thumbSrc = thumbCanvas.toDataURL("image/png");
thumbFabric.dispose();
} catch (error) {
console.warn(
"Errore nella generazione della thumbnail per immagine",
index + 1,
error,
);
thumbSrc = img.getSrc(); // Fallback all'URL originale
}
const layerItem = document.createElement("li");
const thumbImg = document.createElement("img");
thumbImg.src = thumbSrc;
thumbImg.title = `Layer ${index + 1}`;
thumbImg.addEventListener("click", () => {
canvas.setActiveObject(img);
canvas.renderAll();
});
layerItem.appendChild(thumbImg);
layersList.appendChild(layerItem);
});
}
function saveCanvasState() {
if (isCropping || isRemovingBackground) {
return; return;
} }
console.log("Salvataggio stato del canvas");
const state = JSON.stringify( const state = JSON.stringify(
canvas.toJSON([ canvas.toJSON([
"cornerColor", "cornerColor",
@@ -510,26 +496,22 @@ document.addEventListener("DOMContentLoaded", function () {
); );
history.push(state); history.push(state);
if (history.length > maxHistory) { if (history.length > maxHistory) {
history.shift(); // Rimuovi lo stato più vecchio se superato il limite history.shift();
} }
console.log("Stato salvato, lunghezza pila:", history.length);
updateButtons(); updateButtons();
} }
// Ripristina l'ultimo stato del canvas
function undo() { function undo() {
if (history.length <= 1) { if (history.length <= 1) {
console.log("Nessuno stato da annullare");
return; return;
} }
console.log("Annullamento ultima azione"); history.pop();
history.pop(); // Rimuovi lo stato corrente
const previousState = history[history.length - 1]; const previousState = history[history.length - 1];
if (previousState) { if (previousState) {
canvas.clear(); canvas.clear();
canvas.loadFromJSON(previousState, () => { canvas.loadFromJSON(previousState, () => {
canvas.renderAll(); canvas.renderAll();
console.log("Stato ripristinato"); updateLayersPanel();
updateButtons(); updateButtons();
}); });
} else { } else {
@@ -537,11 +519,11 @@ document.addEventListener("DOMContentLoaded", function () {
canvas.clear(); canvas.clear();
canvas.setBackgroundColor("#fff"); canvas.setBackgroundColor("#fff");
canvas.renderAll(); canvas.renderAll();
updateLayersPanel();
updateButtons(); updateButtons();
} }
} }
// Aggiorna stato dei pulsanti
function updateButtons() { function updateButtons() {
const cropBtn = document.getElementById("cropImageBtn"); const cropBtn = document.getElementById("cropImageBtn");
const applyCropBtn = document.getElementById("applyCropBtn"); const applyCropBtn = document.getElementById("applyCropBtn");
@@ -555,20 +537,7 @@ document.addEventListener("DOMContentLoaded", function () {
"backgroundRemovalInstruction", "backgroundRemovalInstruction",
); );
const activeObject = canvas.getActiveObject(); const activeObject = canvas.getActiveObject();
console.log(
"updateButtons: activeObject =",
activeObject ? activeObject.type : null,
"isCropping =",
isCropping,
"isRemovingBackground =",
isRemovingBackground,
"history.length =",
history.length,
);
if (isCropping && cropRect) { if (isCropping && cropRect) {
console.log(
"Modo ritaglio attivo, applyCropBtn e cancelCropBtn abilitati",
);
cropBtn.disabled = true; cropBtn.disabled = true;
applyCropBtn.disabled = false; applyCropBtn.disabled = false;
cancelCropBtn.disabled = false; cancelCropBtn.disabled = false;
@@ -577,9 +546,6 @@ document.addEventListener("DOMContentLoaded", function () {
undoBtn.disabled = true; undoBtn.disabled = true;
instruction.style.display = "none"; instruction.style.display = "none";
} else if (isRemovingBackground && backgroundRemovalImage) { } else if (isRemovingBackground && backgroundRemovalImage) {
console.log(
"Modo rimozione sfondo attivo, removeBackgroundBtn disabilitato",
);
cropBtn.disabled = true; cropBtn.disabled = true;
applyCropBtn.disabled = true; applyCropBtn.disabled = true;
cancelCropBtn.disabled = true; cancelCropBtn.disabled = true;
@@ -593,9 +559,6 @@ document.addEventListener("DOMContentLoaded", function () {
!isCropping && !isCropping &&
!isRemovingBackground !isRemovingBackground
) { ) {
console.log(
"Abilitazione cropImageBtn, removeBackgroundBtn e removeImageBtn",
);
cropBtn.disabled = false; cropBtn.disabled = false;
applyCropBtn.disabled = true; applyCropBtn.disabled = true;
cancelCropBtn.disabled = true; cancelCropBtn.disabled = true;
@@ -604,7 +567,6 @@ document.addEventListener("DOMContentLoaded", function () {
undoBtn.disabled = history.length <= 1; undoBtn.disabled = history.length <= 1;
instruction.style.display = "none"; instruction.style.display = "none";
} else { } else {
console.log("Disabilitazione tutti i pulsanti");
cropBtn.disabled = true; cropBtn.disabled = true;
applyCropBtn.disabled = true; applyCropBtn.disabled = true;
cancelCropBtn.disabled = true; cancelCropBtn.disabled = true;
@@ -615,7 +577,6 @@ document.addEventListener("DOMContentLoaded", function () {
} }
} }
// Entra in modalità ritaglio
function enterCropMode() { function enterCropMode() {
const activeObject = canvas.getActiveObject(); const activeObject = canvas.getActiveObject();
if (!activeObject || activeObject.type !== "image") { if (!activeObject || activeObject.type !== "image") {
@@ -623,14 +584,9 @@ document.addEventListener("DOMContentLoaded", function () {
alert("Seleziona un'immagine prima di attivare il ritaglio!"); alert("Seleziona un'immagine prima di attivare il ritaglio!");
return; return;
} }
console.log(
"Entrata in modalità ritaglio per immagine:",
activeObject,
);
isCropping = true; isCropping = true;
croppedImage = activeObject; // Memorizza l'immagine da ritagliare croppedImage = activeObject;
canvas.discardActiveObject(); // Deseleziona l'immagine canvas.discardActiveObject();
// Crea un rettangolo di ritaglio
cropRect = new fabric.Rect({ cropRect = new fabric.Rect({
left: activeObject.left, left: activeObject.left,
top: activeObject.top, top: activeObject.top,
@@ -653,13 +609,10 @@ document.addEventListener("DOMContentLoaded", function () {
canvas.setActiveObject(cropRect); canvas.setActiveObject(cropRect);
canvas.renderAll(); canvas.renderAll();
updateButtons(); updateButtons();
console.log("Rettangolo di ritaglio creato e applicato");
} }
// Esci dalla modalità ritaglio
function exitCropMode() { function exitCropMode() {
if (cropRect) { if (cropRect) {
console.log("Rimozione rettangolo di ritaglio");
canvas.remove(cropRect); canvas.remove(cropRect);
cropRect = null; cropRect = null;
} }
@@ -669,20 +622,12 @@ document.addEventListener("DOMContentLoaded", function () {
canvas.discardActiveObject(); canvas.discardActiveObject();
canvas.renderAll(); canvas.renderAll();
updateButtons(); updateButtons();
console.log("Uscita dalla modalità ritaglio");
} }
// Applica il ritaglio
function applyCrop() { function applyCrop() {
if (isApplyingCrop) { if (isApplyingCrop) {
console.log("applyCrop già in esecuzione, ignoro chiamata");
return; return;
} }
console.log("applyCrop chiamato, stato:", {
isCropping,
cropRect: !!cropRect,
croppedImage: !!croppedImage,
});
if (!isCropping || !cropRect || !croppedImage) { if (!isCropping || !cropRect || !croppedImage) {
console.warn("Condizioni per il ritaglio non soddisfatte", { console.warn("Condizioni per il ritaglio non soddisfatte", {
isCropping, isCropping,
@@ -696,20 +641,12 @@ document.addEventListener("DOMContentLoaded", function () {
return; return;
} }
isApplyingCrop = true; isApplyingCrop = true;
console.log("Applicazione ritaglio all'immagine:", croppedImage);
const img = croppedImage; const img = croppedImage;
const cropX = (cropRect.left - img.left) / img.scaleX; const cropX = (cropRect.left - img.left) / img.scaleX;
const cropY = (cropRect.top - img.top) / img.scaleY; const cropY = (cropRect.top - img.top) / img.scaleY;
const cropWidth = (cropRect.width * cropRect.scaleX) / img.scaleX; const cropWidth = (cropRect.width * cropRect.scaleX) / img.scaleX;
const cropHeight = (cropRect.height * cropRect.scaleY) / img.scaleY; const cropHeight = (cropRect.height * cropRect.scaleY) / img.scaleY;
console.log("Parametri di ritaglio:", {
cropX,
cropY,
cropWidth,
cropHeight,
});
// Crea un'immagine ritagliata
fabric.Image.fromURL( fabric.Image.fromURL(
img.getSrc(), img.getSrc(),
(newImg) => { (newImg) => {
@@ -730,20 +667,19 @@ document.addEventListener("DOMContentLoaded", function () {
borderColor: "black", borderColor: "black",
transparentCorners: false, transparentCorners: false,
}); });
canvas.remove(img); // Rimuovi l'immagine originale canvas.remove(img);
canvas.remove(cropRect); // Rimuovi il rettangolo di ritaglio canvas.remove(cropRect);
canvas.add(newImg); // Aggiungi l'immagine ritagliata canvas.add(newImg);
canvas.setActiveObject(newImg); canvas.setActiveObject(newImg);
exitCropMode(); exitCropMode();
saveCanvasState(); // Salva lo stato dopo il ritaglio saveCanvasState();
updateLayersPanel();
canvas.renderAll(); canvas.renderAll();
console.log("Ritaglio applicato con successo");
}, },
{ crossOrigin: "anonymous" }, { crossOrigin: "anonymous" },
); );
} }
// Entra in modalità rimozione sfondo
function enterBackgroundRemovalMode() { function enterBackgroundRemovalMode() {
const activeObject = canvas.getActiveObject(); const activeObject = canvas.getActiveObject();
if (!activeObject || activeObject.type !== "image") { if (!activeObject || activeObject.type !== "image") {
@@ -755,29 +691,19 @@ document.addEventListener("DOMContentLoaded", function () {
); );
return; return;
} }
console.log(
"Entrata in modalità rimozione sfondo per immagine:",
activeObject,
);
isRemovingBackground = true; isRemovingBackground = true;
backgroundRemovalImage = activeObject; backgroundRemovalImage = activeObject;
updateButtons(); updateButtons();
console.log(
"Modalità rimozione sfondo attivata, clicca sull'immagine per selezionare il colore",
);
} }
// Esci dalla modalità rimozione sfondo
function exitBackgroundRemovalMode() { function exitBackgroundRemovalMode() {
isRemovingBackground = false; isRemovingBackground = false;
backgroundRemovalImage = null; backgroundRemovalImage = null;
canvas.discardActiveObject(); canvas.discardActiveObject();
canvas.renderAll(); canvas.renderAll();
updateButtons(); updateButtons();
console.log("Uscita dalla modalità rimozione sfondo");
} }
// Gestisci la selezione del colore dello sfondo
function handleBackgroundColorSelection(event) { function handleBackgroundColorSelection(event) {
if (!isRemovingBackground || !backgroundRemovalImage) { if (!isRemovingBackground || !backgroundRemovalImage) {
console.warn( console.warn(
@@ -788,14 +714,12 @@ document.addEventListener("DOMContentLoaded", function () {
const pointer = canvas.getPointer(event.e); const pointer = canvas.getPointer(event.e);
const img = backgroundRemovalImage; const img = backgroundRemovalImage;
// Crea una canvas temporanea per ottenere i dati dei pixel
const tempCanvas = document.createElement("canvas"); const tempCanvas = document.createElement("canvas");
tempCanvas.width = img.width; tempCanvas.width = img.width;
tempCanvas.height = img.height; tempCanvas.height = img.height;
const ctx = tempCanvas.getContext("2d"); const ctx = tempCanvas.getContext("2d");
ctx.drawImage(img.getElement(), 0, 0, img.width, img.height); ctx.drawImage(img.getElement(), 0, 0, img.width, img.height);
// Calcola la posizione relativa del click rispetto all'immagine
const imgLeft = img.left; const imgLeft = img.left;
const imgTop = img.top; const imgTop = img.top;
const scaleX = img.scaleX; const scaleX = img.scaleX;
@@ -803,22 +727,17 @@ document.addEventListener("DOMContentLoaded", function () {
const x = (pointer.x - imgLeft) / scaleX; const x = (pointer.x - imgLeft) / scaleX;
const y = (pointer.y - imgTop) / scaleY; const y = (pointer.y - imgTop) / scaleY;
// Ottieni il colore del pixel cliccato
const pixelData = ctx.getImageData(x, y, 1, 1).data; const pixelData = ctx.getImageData(x, y, 1, 1).data;
const targetColor = { const targetColor = {
r: pixelData[0], r: pixelData[0],
g: pixelData[1], g: pixelData[1],
b: pixelData[2], b: pixelData[2],
}; };
console.log("Colore dello sfondo selezionato:", targetColor);
// Rimuovi il colore dello sfondo
removeBackground(img, targetColor); removeBackground(img, targetColor);
} }
// Rimuovi il colore dello sfondo
function removeBackground(img, targetColor) { function removeBackground(img, targetColor) {
console.log("Inizio rimozione sfondo con colore:", targetColor);
const tempCanvas = document.createElement("canvas"); const tempCanvas = document.createElement("canvas");
tempCanvas.width = img.width; tempCanvas.width = img.width;
tempCanvas.height = img.height; tempCanvas.height = img.height;
@@ -832,27 +751,24 @@ document.addEventListener("DOMContentLoaded", function () {
tempCanvas.height, tempCanvas.height,
); );
const data = imageData.data; const data = imageData.data;
const tolerance = 50; // Tolleranza per colori simili const tolerance = 50;
// Scansiona i pixel e rendi trasparenti quelli che corrispondono al colore target
for (let i = 0; i < data.length; i += 4) { for (let i = 0; i < data.length; i += 4) {
const r = data[i]; const r = data[i];
const g = data[i + 1]; const g = data[i + 1];
const b = data[i + 2]; const b = data[i + 2];
// Verifica se il colore è simile al targetColor
if ( if (
Math.abs(r - targetColor.r) <= tolerance && Math.abs(r - targetColor.r) <= tolerance &&
Math.abs(g - targetColor.g) <= tolerance && Math.abs(g - targetColor.g) <= tolerance &&
Math.abs(b - targetColor.b) <= tolerance Math.abs(b - targetColor.b) <= tolerance
) { ) {
data[i + 3] = 0; // Imposta l'alpha a 0 (trasparente) data[i + 3] = 0;
} }
} }
ctx.putImageData(imageData, 0, 0); ctx.putImageData(imageData, 0, 0);
const newImageUrl = tempCanvas.toDataURL("image/png"); const newImageUrl = tempCanvas.toDataURL("image/png");
// Crea una nuova immagine con lo sfondo rimosso
fabric.Image.fromURL( fabric.Image.fromURL(
newImageUrl, newImageUrl,
(newImg) => { (newImg) => {
@@ -869,19 +785,18 @@ document.addEventListener("DOMContentLoaded", function () {
borderColor: "black", borderColor: "black",
transparentCorners: false, transparentCorners: false,
}); });
canvas.remove(img); // Rimuovi l'immagine originale canvas.remove(img);
canvas.add(newImg); // Aggiungi l'immagine con sfondo rimosso canvas.add(newImg);
canvas.setActiveObject(newImg); canvas.setActiveObject(newImg);
exitBackgroundRemovalMode(); exitBackgroundRemovalMode();
saveCanvasState(); // Salva lo stato dopo la rimozione dello sfondo saveCanvasState();
updateLayersPanel();
canvas.renderAll(); canvas.renderAll();
console.log("Sfondo rimosso con successo");
}, },
{ crossOrigin: "anonymous" }, { crossOrigin: "anonymous" },
); );
} }
// Rimuovi l'immagine selezionata
function removeImage() { function removeImage() {
const activeObject = canvas.getActiveObject(); const activeObject = canvas.getActiveObject();
if (!activeObject || activeObject.type !== "image") { if (!activeObject || activeObject.type !== "image") {
@@ -889,15 +804,13 @@ document.addEventListener("DOMContentLoaded", function () {
alert("Seleziona un'immagine da rimuovere!"); alert("Seleziona un'immagine da rimuovere!");
return; return;
} }
console.log("Rimozione immagine selezionata:", activeObject);
canvas.remove(activeObject); canvas.remove(activeObject);
canvas.discardActiveObject(); canvas.discardActiveObject();
saveCanvasState(); // Salva lo stato dopo la rimozione saveCanvasState();
updateLayersPanel();
canvas.renderAll(); canvas.renderAll();
console.log("Immagine rimossa con successo");
} }
// Aggiungi foto selezionate al canvas
const addToCanvasBtn = document.getElementById("addToCanvasBtn"); const addToCanvasBtn = document.getElementById("addToCanvasBtn");
if (addToCanvasBtn) { if (addToCanvasBtn) {
addToCanvasBtn.addEventListener("click", () => { addToCanvasBtn.addEventListener("click", () => {
@@ -914,11 +827,11 @@ document.addEventListener("DOMContentLoaded", function () {
imgPath, imgPath,
(img) => { (img) => {
img.set({ img.set({
left: Math.random() * 600, // Posizione random iniziale left: Math.random() * 600,
top: Math.random() * 400, top: Math.random() * 400,
scaleX: 0.5, // Scala iniziale scaleX: 0.5,
scaleY: 0.5, scaleY: 0.5,
hasControls: true, // Abilita resize/rotate hasControls: true,
hasBorders: true, hasBorders: true,
cornerColor: "black", cornerColor: "black",
cornerStrokeColor: "black", cornerStrokeColor: "black",
@@ -928,25 +841,22 @@ document.addEventListener("DOMContentLoaded", function () {
}); });
canvas.add(img); canvas.add(img);
canvas.renderAll(); canvas.renderAll();
console.log(
"Immagine aggiunta al canvas:",
imgPath,
);
}, },
{ crossOrigin: "anonymous" }, { crossOrigin: "anonymous" },
); );
}); });
// Deseleziona checkbox dopo aggiunta
checkboxes.forEach((cb) => (cb.checked = false)); checkboxes.forEach((cb) => (cb.checked = false));
saveCanvasState(); // Salva lo stato dopo l'aggiunta saveCanvasState();
}); });
} }
// Salva collage
const saveCollageBtn = document.getElementById("saveCollageBtn"); const saveCollageBtn = document.getElementById("saveCollageBtn");
if (saveCollageBtn) { if (saveCollageBtn) {
saveCollageBtn.addEventListener("click", async () => { saveCollageBtn.addEventListener("click", async () => {
if (canvas.getObjects().length === 0) { if (
canvas.getObjects().length === 0 &&
!canvas.backgroundImage
) {
alert("Il canvas è vuoto! Aggiungi almeno una foto."); alert("Il canvas è vuoto! Aggiungi almeno una foto.");
return; return;
} }
@@ -959,29 +869,12 @@ document.addEventListener("DOMContentLoaded", function () {
type: "image/jpeg", type: "image/jpeg",
}); });
// Upload come nuova foto
await handleFiles([file], iddatadb); await handleFiles([file], iddatadb);
// Chiudi modale e ricarica popup
document.getElementById("collageModal").style.display = "none"; document.getElementById("collageModal").style.display = "none";
loadPopupContent(iddatadb); loadPopupContent(iddatadb);
console.log("Collage salvato e modale chiuso");
}); });
} }
// Pulisci canvas
const clearCanvasBtn = document.getElementById("clearCanvasBtn");
if (clearCanvasBtn) {
clearCanvasBtn.addEventListener("click", () => {
canvas.clear();
canvas.setBackgroundColor("#fff");
history = []; // Resetta la pila di stati
saveCanvasState(); // Salva lo stato vuoto
canvas.renderAll();
console.log("Canvas pulito");
});
}
// Gestione livelli delle immagini
const bringToFrontBtn = document.getElementById("bringToFrontBtn"); const bringToFrontBtn = document.getElementById("bringToFrontBtn");
if (bringToFrontBtn) { if (bringToFrontBtn) {
bringToFrontBtn.addEventListener("click", () => { bringToFrontBtn.addEventListener("click", () => {
@@ -989,8 +882,8 @@ document.addEventListener("DOMContentLoaded", function () {
if (activeObject) { if (activeObject) {
canvas.bringToFront(activeObject); canvas.bringToFront(activeObject);
canvas.renderAll(); canvas.renderAll();
saveCanvasState(); // Salva lo stato dopo il cambio di livello saveCanvasState();
console.log("Oggetto portato in primo piano"); updateLayersPanel();
} else { } else {
alert("Seleziona un'immagine sul canvas!"); alert("Seleziona un'immagine sul canvas!");
} }
@@ -1004,8 +897,8 @@ document.addEventListener("DOMContentLoaded", function () {
if (activeObject) { if (activeObject) {
canvas.sendToBack(activeObject); canvas.sendToBack(activeObject);
canvas.renderAll(); canvas.renderAll();
saveCanvasState(); // Salva lo stato dopo il cambio di livello saveCanvasState();
console.log("Oggetto mandato in fondo"); updateLayersPanel();
} else { } else {
alert("Seleziona un'immagine sul canvas!"); alert("Seleziona un'immagine sul canvas!");
} }
@@ -1019,8 +912,8 @@ document.addEventListener("DOMContentLoaded", function () {
if (activeObject) { if (activeObject) {
canvas.bringForward(activeObject); canvas.bringForward(activeObject);
canvas.renderAll(); canvas.renderAll();
saveCanvasState(); // Salva lo stato dopo il cambio di livello saveCanvasState();
console.log("Oggetto spostato avanti di un livello"); updateLayersPanel();
} else { } else {
alert("Seleziona un'immagine sul canvas!"); alert("Seleziona un'immagine sul canvas!");
} }
@@ -1034,19 +927,17 @@ document.addEventListener("DOMContentLoaded", function () {
if (activeObject) { if (activeObject) {
canvas.sendBackwards(activeObject); canvas.sendBackwards(activeObject);
canvas.renderAll(); canvas.renderAll();
saveCanvasState(); // Salva lo stato dopo il cambio di livello saveCanvasState();
console.log("Oggetto spostato indietro di un livello"); updateLayersPanel();
} else { } else {
alert("Seleziona un'immagine sul canvas!"); alert("Seleziona un'immagine sul canvas!");
} }
}); });
} }
// Gestione ritaglio immagini
const cropImageBtn = document.getElementById("cropImageBtn"); const cropImageBtn = document.getElementById("cropImageBtn");
if (cropImageBtn) { if (cropImageBtn) {
cropImageBtn.addEventListener("click", () => { cropImageBtn.addEventListener("click", () => {
console.log("Pulsante Ritaglia cliccato");
enterCropMode(); enterCropMode();
}); });
} }
@@ -1054,7 +945,6 @@ document.addEventListener("DOMContentLoaded", function () {
const applyCropBtn = document.getElementById("applyCropBtn"); const applyCropBtn = document.getElementById("applyCropBtn");
if (applyCropBtn) { if (applyCropBtn) {
applyCropBtn.addEventListener("click", () => { applyCropBtn.addEventListener("click", () => {
console.log("Pulsante Applica Ritaglio cliccato");
applyCrop(); applyCrop();
}); });
} }
@@ -1062,49 +952,39 @@ document.addEventListener("DOMContentLoaded", function () {
const cancelCropBtn = document.getElementById("cancelCropBtn"); const cancelCropBtn = document.getElementById("cancelCropBtn");
if (cancelCropBtn) { if (cancelCropBtn) {
cancelCropBtn.addEventListener("click", () => { cancelCropBtn.addEventListener("click", () => {
console.log("Pulsante Annulla Ritaglio cliccato");
exitCropMode(); exitCropMode();
}); });
} }
// Gestione rimozione sfondo
const removeBackgroundBtn = document.getElementById( const removeBackgroundBtn = document.getElementById(
"removeBackgroundBtn", "removeBackgroundBtn",
); );
if (removeBackgroundBtn) { if (removeBackgroundBtn) {
removeBackgroundBtn.addEventListener("click", () => { removeBackgroundBtn.addEventListener("click", () => {
console.log("Pulsante Rimuovi Sfondo cliccato");
enterBackgroundRemovalMode(); enterBackgroundRemovalMode();
}); });
} }
// Gestione rimozione immagine
const removeImageBtn = document.getElementById("removeImageBtn"); const removeImageBtn = document.getElementById("removeImageBtn");
if (removeImageBtn) { if (removeImageBtn) {
removeImageBtn.addEventListener("click", () => { removeImageBtn.addEventListener("click", () => {
console.log("Pulsante Rimuovi Immagine cliccato");
removeImage(); removeImage();
}); });
} }
// Gestione undo
const undoBtn = document.getElementById("undoBtn"); const undoBtn = document.getElementById("undoBtn");
if (undoBtn) { if (undoBtn) {
undoBtn.addEventListener("click", () => { undoBtn.addEventListener("click", () => {
console.log("Pulsante Annulla cliccato");
undo(); undo();
}); });
} }
// Assicurati che il loader sia nascosto all'apertura del popup
const loader = document.getElementById("loader"); const loader = document.getElementById("loader");
if (loader) { if (loader) {
console.log("Nascondo loader all'apertura del popup");
loader.style.display = "none"; loader.style.display = "none";
} }
} }
// Gestione del pulsante Photos
const photosButtons = document.querySelectorAll(".photos-btn"); const photosButtons = document.querySelectorAll(".photos-btn");
const photosModal = document.getElementById("photosModal"); const photosModal = document.getElementById("photosModal");
const closeBtn = document.querySelector(".close-btn"); const closeBtn = document.querySelector(".close-btn");
@@ -1112,10 +992,6 @@ document.addEventListener("DOMContentLoaded", function () {
if (photosButtons.length && photosModal && closeBtn) { if (photosButtons.length && photosModal && closeBtn) {
photosButtons.forEach((button) => { photosButtons.forEach((button) => {
button.addEventListener("click", function () { button.addEventListener("click", function () {
console.log(
"Pulsante Photos cliccato per iddatadb:",
this.getAttribute("data-iddatadb"),
);
const iddatadb = this.getAttribute("data-iddatadb"); const iddatadb = this.getAttribute("data-iddatadb");
loadPopupContent(iddatadb); loadPopupContent(iddatadb);
photosModal.style.display = "block"; photosModal.style.display = "block";
@@ -1124,7 +1000,6 @@ document.addEventListener("DOMContentLoaded", function () {
}); });
closeBtn.addEventListener("click", function () { closeBtn.addEventListener("click", function () {
console.log("Chiusura modale photos");
photosModal.style.display = "none"; photosModal.style.display = "none";
document.querySelector(".overlay").style.display = "none"; document.querySelector(".overlay").style.display = "none";
document.body.style.pointerEvents = "auto"; document.body.style.pointerEvents = "auto";
@@ -1132,7 +1007,6 @@ document.addEventListener("DOMContentLoaded", function () {
window.addEventListener("click", function (event) { window.addEventListener("click", function (event) {
if (event.target === photosModal) { if (event.target === photosModal) {
console.log("Chiusura modale photos cliccando sullo sfondo");
photosModal.style.display = "none"; photosModal.style.display = "none";
document.querySelector(".overlay").style.display = "none"; document.querySelector(".overlay").style.display = "none";
document.body.style.pointerEvents = "auto"; document.body.style.pointerEvents = "auto";
+28 -9
View File
@@ -21,7 +21,6 @@ use Endroid\QrCode\Writer\PngWriter;
try { try {
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../../'); $dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/../../');
$dotenv->load(); $dotenv->load();
error_log("File .env caricato correttamente da " . __DIR__ . '/../../.env');
} catch (Exception $e) { } catch (Exception $e) {
error_log("Errore nel caricamento del file .env: " . $e->getMessage()); error_log("Errore nel caricamento del file .env: " . $e->getMessage());
echo json_encode(['error' => 'Errore nel caricamento del file di configurazione']); echo json_encode(['error' => 'Errore nel caricamento del file di configurazione']);
@@ -176,7 +175,7 @@ $result->saveToFile($qrCodeFile);
<!-- Modale per collage --> <!-- Modale per collage -->
<div id="collageModal" class="modal" style="display: none; position: fixed; z-index: 1002; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.8);"> <div id="collageModal" class="modal" style="display: none; position: fixed; z-index: 1002; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.8);">
<div class="modal-content" style="background: white; margin: 5% auto; padding: 20px; width: 80%; max-width: 1200px; position: relative;"> <div class="modal-content" style="background: white; margin: 5% auto; padding: 20px; width: 80%; max-width: 1200px; position: relative;">
<span class="close-collage" style="position: absolute; top: 10px; right: 20px; font-size: 30px; cursor: pointer;">&times;</span> <span class="close-collage" style="position: absolute; top: 10px; right: 20px; font-size: 30px; font-weight: bold; color: #333; cursor: pointer; z-index: 1003; background: #fff; padding: 5px 10px; border-radius: 50%;">&times;</span>
<h3>Crea Collage</h3> <h3>Crea Collage</h3>
<!-- Lista foto selezionabili --> <!-- Lista foto selezionabili -->
@@ -195,10 +194,15 @@ $result->saveToFile($qrCodeFile);
<!-- Canvas per editing --> <!-- Canvas per editing -->
<canvas id="collageCanvas" width="800" height="600" style="border: 1px solid #ccc; margin-top: 20px;"></canvas> <canvas id="collageCanvas" width="800" height="600" style="border: 1px solid #ccc; margin-top: 20px;"></canvas>
<!-- Pannello dei livelli -->
<div id="layersPanel" style="width: 120px; max-height: 600px; overflow-y: auto; background: #f8f9fa; padding: 10px; position: absolute; right: 0; top: 60px;">
<h4 style="margin: 0 0 10px 0; font-size: 16px;">Livelli</h4>
<ul id="layersList" style="list-style: none; padding: 0;"></ul>
</div>
<!-- Bottoni azioni --> <!-- Bottoni azioni -->
<div style="margin-top: 20px; display: flex; flex-wrap: wrap; gap: 5px;"> <div style="margin-top: 20px; display: flex; flex-wrap: wrap; gap: 5px;">
<button id="saveCollageBtn" title="Salva il collage"><i class="fas fa-save"></i></button> <button id="saveCollageBtn" title="Salva il collage"><i class="fas fa-save"></i></button>
<!-- <button id="clearCanvasBtn" title="Pulisci il canvas"><i class="fas fa-trash"></i></button> -->
<button id="bringToFrontBtn" title="Porta in primo piano"><i class="fas fa-arrow-up"></i></button> <button id="bringToFrontBtn" title="Porta in primo piano"><i class="fas fa-arrow-up"></i></button>
<button id="sendToBackBtn" title="Manda in fondo"><i class="fas fa-arrow-down"></i></button> <button id="sendToBackBtn" title="Manda in fondo"><i class="fas fa-arrow-down"></i></button>
<button id="bringForwardBtn" title="Sposta avanti di un livello"><i class="fas fa-arrow-circle-up"></i></button> <button id="bringForwardBtn" title="Sposta avanti di un livello"><i class="fas fa-arrow-circle-up"></i></button>
@@ -311,11 +315,6 @@ $result->saveToFile($qrCodeFile);
color: white; color: white;
} }
#clearCanvasBtn {
background: #dc3545;
color: white;
}
#bringToFrontBtn, #bringToFrontBtn,
#sendToBackBtn, #sendToBackBtn,
#bringForwardBtn, #bringForwardBtn,
@@ -323,7 +322,6 @@ $result->saveToFile($qrCodeFile);
background: #007bff; background: #007bff;
color: white; color: white;
padding: 8px; padding: 8px;
/* Pulsanti solo icona più piccoli */
} }
#cropImageBtn, #cropImageBtn,
@@ -344,4 +342,25 @@ $result->saveToFile($qrCodeFile);
#collageModal button i { #collageModal button i {
font-size: 16px; font-size: 16px;
} }
/* Stile per il pannello dei livelli */
#layersPanel {
z-index: 1002;
}
#layersPanel li {
margin-bottom: 5px;
}
#layersPanel img {
width: 50px;
height: 50px;
border: 2px solid #ccc;
cursor: pointer;
object-fit: cover;
}
#layersPanel img:hover {
border-color: #007bff;
}
</style> </style>