$(document).ready(function () { // =================== // GLOBAL STATE // =================== let photoData = { naturalWidth: 0, naturalHeight: 0, displayWidth: 0, displayHeight: 0, scale: 1, }; let photoAnnotations = {}; let partColors = {}; let selectedPartNumber = null; let unsavedChanges = false; let fabricCanvas = null; let descriptionTextbox = null; let markerObjects = {}; let partsListData = []; // =================== // MODAL INITIALIZATION // =================== window.initAnnotationsModal = function (iddatadb, idquotations, trfHeader) { console.log("initAnnotationsModal chiamato con:", { iddatadb, idquotations, trfHeader, }); if (!iddatadb && !idquotations) { const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } $("#trfHeaderAnnotations").text(trfHeader || "N/D"); $("#annotationsModal") .data("iddatadb", iddatadb || null) .data("idquotations", idquotations || null); loadPhoto(iddatadb, idquotations); loadExistingParts(iddatadb, idquotations); const modalElement = document.getElementById("annotationsModal"); if (!modalElement) { console.error("Elemento #annotationsModal non trovato nel DOM."); alert( "Errore: Il modale delle annotazioni non è presente nel DOM.", ); return; } let modal = bootstrap.Modal.getInstance(modalElement); if (!modal) { modal = new bootstrap.Modal(modalElement, { backdrop: true, keyboard: true, focus: true, }); } modal.show(); // Debug: Verifica presenza elementi DOM console.log( "Presenza #partsListAnnotations:", $("#partsListAnnotations").length, ); console.log( "Presenza #showMixPartsAnnotations:", $("#showMixPartsAnnotations").length, ); console.log( "Presenza #addDescriptionsBtnAnnotations:", $("#addDescriptionsBtnAnnotations").length, ); console.log( "Presenza #removeAnnotationsBtnAnnotations:", $("#removeAnnotationsBtnAnnotations").length, ); console.log( "Presenza #downloadPhotoBtnAnnotations:", $("#downloadPhotoBtnAnnotations").length, ); console.log( "Presenza #backToPartsBtnAnnotations:", $("#backToPartsBtnAnnotations").length, ); console.log( "Presenza #overlayCanvasAnnotations:", $("#overlayCanvasAnnotations").length, ); }; $("#annotationsModal").on("hide.bs.modal", function (e) { if ( unsavedChanges && !confirm("Hai modifiche non salvate. Vuoi davvero uscire?") ) { e.preventDefault(); } }); $("#annotationsModal").on("hidden.bs.modal", function () { photoData = { naturalWidth: 0, naturalHeight: 0, displayWidth: 0, displayHeight: 0, scale: 1, }; photoAnnotations = {}; partColors = {}; selectedPartNumber = null; unsavedChanges = false; partsListData = []; if (fabricCanvas) { fabricCanvas.off(); fabricCanvas.dispose(); fabricCanvas = null; } descriptionTextbox = null; markerObjects = {}; $("#photoSelectorContainerAnnotations").empty().hide(); $("#samplePhotoAnnotations").attr("src", ""); $("#partsListAnnotations").empty(); $(".temp-alert").remove(); const modalElement = document.getElementById("annotationsModal"); const modal = bootstrap.Modal.getInstance(modalElement); if (modal) { modal.dispose(); } $(".modal-backdrop").remove(); $("body").removeClass("modal-open"); $("body").css("padding-right", ""); $(":focus").blur(); }); // =================== // PHOTO LOADERS // =================== function loadPhoto(iddatadb, idquotations) { const currentPhoto = $("#samplePhotoAnnotations").attr("src"); const endpoint = idquotations ? "load_photo_quotation.php" : "load_photo.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; $.ajax({ url: endpoint, method: "GET", data: data, success: function (response) { console.log("Risposta da load_photo:", response); if (response.success) { if (response.photos && response.photos.length > 1) { showPhotoSelector(response.photos, currentPhoto); } else if ( response.photos && response.photos.length === 1 ) { loadSinglePhoto(response.photos[0]); } else { $("#samplePhotoAnnotations").attr("src", ""); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } } else { const errorMsg = $( '", ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }, error: function (xhr, status, error) { console.error("Errore AJAX in loadPhoto:", { status, error, responseText: xhr.responseText, }); const errorMsg = $( '", ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } function showPhotoSelector(photos, selected = null) { const selectorContainer = $("#photoSelectorContainerAnnotations"); selectorContainer.empty().show(); const selector = $( '', ); photos.forEach((photo, index) => { const photoName = photo.split("/").pop(); const option = $("") .val(photo) .text(`Photo ${index + 1} - ${photoName}`); selector.append(option); }); selector.on("change", function () { loadSinglePhoto($(this).val()); }); selectorContainer.append(selector); const photoToSelect = selected && photos.includes(selected) ? selected : photos[0]; if (photoToSelect) { selector.val(photoToSelect); loadSinglePhoto(photoToSelect); } } function loadSinglePhoto(photoPath) { const img = $("#samplePhotoAnnotations"); img.off("load").attr("src", photoPath); img.on("load", function () { console.log("Foto caricata:", photoPath); const canvas = document.getElementById("photoCanvasAnnotations"); const ctx = canvas.getContext("2d"); const naturalWidth = img[0].naturalWidth; const naturalHeight = img[0].naturalHeight; const parent = $(canvas).parent(); const maxW = parent.width(); const maxH = parent.height(); const scale = Math.min(maxW / naturalWidth, maxH / naturalHeight); photoData = { naturalWidth, naturalHeight, displayWidth: Math.max(1, Math.round(naturalWidth * scale)), displayHeight: Math.max(1, Math.round(naturalHeight * scale)), scale, }; canvas.width = naturalWidth; canvas.height = naturalHeight; canvas.style.width = `${photoData.displayWidth}px`; canvas.style.height = `${photoData.displayHeight}px`; ctx.clearRect(0, 0, naturalWidth, naturalHeight); ctx.drawImage(img[0], 0, 0, naturalWidth, naturalHeight); if (fabricCanvas) { fabricCanvas.off(); fabricCanvas.dispose(); fabricCanvas = null; } const overlayCanvas = document.getElementById( "overlayCanvasAnnotations", ); const canvasContainer = overlayCanvas.parentElement; const newOverlayCanvas = document.createElement("canvas"); newOverlayCanvas.id = "overlayCanvasAnnotations"; newOverlayCanvas.width = photoData.displayWidth; newOverlayCanvas.height = photoData.displayHeight; newOverlayCanvas.style.width = `${photoData.displayWidth}px`; newOverlayCanvas.style.height = `${photoData.displayHeight}px`; newOverlayCanvas.style.position = "absolute"; newOverlayCanvas.style.top = "0"; newOverlayCanvas.style.left = "0"; newOverlayCanvas.style.zIndex = "1000"; canvasContainer.removeChild(overlayCanvas); canvasContainer.appendChild(newOverlayCanvas); setTimeout(() => { fabricCanvas = new fabric.Canvas("overlayCanvasAnnotations", { selection: true, preserveObjectStacking: true, width: photoData.displayWidth, height: photoData.displayHeight, }); fabricCanvas.setDimensions({ width: photoData.displayWidth, height: photoData.displayHeight, }); fabricCanvas.on("mouse:down", function (options) { console.log( "Evento mouse:down su canvas, selectedPartNumber:", selectedPartNumber, ); if (selectedPartNumber === null) { console.log( "Nessuna parte selezionata, ignoro il click.", ); return; } if (options.target) { console.log("Click su un oggetto esistente, ignoro."); return; } const pointer = fabricCanvas.getPointer(options.e); const x = pointer.x / photoData.scale; const y = pointer.y / photoData.scale; const currentPhoto = $("#samplePhotoAnnotations").attr( "src", ); if (!photoAnnotations[currentPhoto]) { photoAnnotations[currentPhoto] = { markers: [], hasDescriptions: false, descriptionPosition: { x: 10, y: 10 }, descriptionSize: { width: photoData.displayWidth * 0.3, height: photoData.displayHeight * 0.3, }, }; } const partColor = partColors[selectedPartNumber] || "#ff0000"; const existingMarker = photoAnnotations[ currentPhoto ].markers.find((m) => m.partNumber == selectedPartNumber); if (existingMarker) { existingMarker.x = x; existingMarker.y = y; existingMarker.color = partColor; } else { photoAnnotations[currentPhoto].markers.push({ partNumber: selectedPartNumber, x, y, color: partColor, }); } console.log("Marker aggiunto/spostato:", { partNumber: selectedPartNumber, x, y, color: partColor, }); updateMarkers(); markUnsaved(); selectedPartNumber = null; $("#partsListAnnotations li").removeClass("active"); }); fabricCanvas.upperCanvasEl.focus(); fabricCanvas.renderAll(); updateMarkers(); updateDescriptions(); }, 10); }); } // =================== // DOWNLOAD PHOTO // =================== $(document) .off("click.downloadPhoto", "#downloadPhotoBtnAnnotations") .on( "click.downloadPhoto", "#downloadPhotoBtnAnnotations", function (e) { e.preventDefault(); e.stopPropagation(); console.log( "Evento click su #downloadPhotoBtnAnnotations, ID elemento:", $(this).attr("id"), ); if (!$("#downloadPhotoBtnAnnotations").length) { console.error( "Pulsante #downloadPhotoBtnAnnotations non trovato nel DOM.", ); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } const photoSrc = $("#samplePhotoAnnotations").attr("src"); console.log("URL immagine per il download:", photoSrc); if (!photoSrc) { console.error("Nessuna foto caricata da scaricare."); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } // Verifica se l'URL è valido const img = new Image(); img.src = photoSrc; img.onload = function () { const photoName = photoSrc.split("/").pop() || "downloaded_photo.png"; console.log("Nome file per il download:", photoName); const link = document.createElement("a"); link.href = photoSrc; link.download = photoName; document.body.appendChild(link); link.click(); document.body.removeChild(link); console.log("Download avviato per:", photoName); }; img.onerror = function () { console.error( "Errore: Impossibile caricare l'immagine per il download:", photoSrc, ); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }; }, ); // =================== // BACK TO PARTS MODAL // =================== $(document) .off("click.backToParts", "#backToPartsBtnAnnotations") .on("click.backToParts", "#backToPartsBtnAnnotations", function (e) { e.preventDefault(); e.stopPropagation(); console.log( "Evento click su #backToPartsBtnAnnotations, ID elemento:", $(this).attr("id"), ); if (!$("#backToPartsBtnAnnotations").length) { console.error( "Pulsante #backToPartsBtnAnnotations non trovato nel DOM.", ); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } // Controlla modifiche non salvate if ( unsavedChanges && !confirm( "Hai modifiche non salvate. Vuoi davvero tornare al modale delle parti?", ) ) { console.log( "Tornare al modale delle parti annullato a causa di modifiche non salvate.", ); return; } // Chiudi annotationsModal const annotationsModalElement = document.getElementById("annotationsModal"); const annotationsModal = bootstrap.Modal.getInstance( annotationsModalElement, ); if (annotationsModal) { annotationsModal.hide(); } else { console.error( "Impossibile trovare l'istanza del modale #annotationsModal.", ); } // Apri partsModal const iddatadb = $("#annotationsModal").data("iddatadb"); const idquotations = $("#annotationsModal").data("idquotations"); const trfHeader = $("#trfHeaderAnnotations").text(); console.log("Apertura partsModal con:", { iddatadb, idquotations, trfHeader, }); const partsModalElement = document.getElementById("partsModal"); if (!partsModalElement) { console.error("Elemento #partsModal non trovato nel DOM."); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } let partsModal = bootstrap.Modal.getInstance(partsModalElement); if (!partsModal) { partsModal = new bootstrap.Modal(partsModalElement, { backdrop: true, keyboard: true, focus: true, }); } // Inizializza il modale delle parti if (typeof window.initPartsModal === "function") { window.initPartsModal(iddatadb, idquotations, trfHeader); partsModal.show(); console.log("partsModal aperto con successo."); } else { console.error("Funzione initPartsModal non definita."); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }); // =================== // PARTS LIST // =================== function updatePartsList() { console.log( "updatePartsList chiamato con partsListData:", partsListData, ); const showMixParts = $("#showMixPartsAnnotations").is(":checked"); console.log("Stato showMixPartsAnnotations:", showMixParts); const partsListElement = $("#partsListAnnotations"); if (!partsListElement.length) { console.error( "Elemento #partsListAnnotations non trovato nel DOM.", ); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } partsListElement.empty(); const predefinedColors = [ "#ff0000", // Rosso "#0000ff", // Blu "#00ff00", // Verde "#ffff00", // Giallo "#ff00ff", // Magenta "#00ffff", // Ciano "#800080", // Viola "#ffa500", // Arancione ]; partsListData.forEach((part) => { const partNumber = part.part_number; const partDescription = part.part_description; const partColor = partColors[partNumber] || (partDescription.toLowerCase().startsWith("mix") ? "#0000ff" : "#ff0000"); if ( partNumber && partDescription && (showMixParts || !partDescription.toLowerCase().startsWith("mix")) ) { const colorOptions = predefinedColors .map( (color) => `
`, ) .join(""); const listItem = `
  • ${partNumber} - ${partDescription}
  • `; partsListElement.append(listItem); } }); console.log( "Elementi aggiunti a #partsListAnnotations:", partsListElement.find("li").length, ); console.log("HTML di #partsListAnnotations:", partsListElement.html()); // Associa evento di selezione all'intera riga partsListElement .off("click.partsList") .on("click.partsList", ".list-group-item", function (e) { e.stopPropagation(); e.preventDefault(); // Ignora il click se è sulla paletta dei colori if ($(e.target).closest(".color-picker-container").length) { console.log( "Click sulla paletta dei colori, ignoro selezione parte.", ); return; } const $listItem = $(this); selectedPartNumber = $listItem.data("part-number"); $listItem.addClass("active").siblings().removeClass("active"); console.log( "Parte selezionata tramite riga:", selectedPartNumber, ); }); // Associa eventi alla paletta dei colori partsListElement .off("click.selectedColor") .on("click.selectedColor", ".selected-color", function (e) { e.stopPropagation(); e.preventDefault(); const $picker = $(this).siblings(".color-picker"); console.log( "Cliccato .selected-color, mostro/nascondo paletta:", $picker.is(":visible"), ); $(".color-picker").not($picker).hide(); $picker.toggle(); }); partsListElement .off("click.colorOption") .on("click.colorOption", ".color-option", function (e) { e.stopPropagation(); e.preventDefault(); const $this = $(this); const color = $this.data("color"); const $listItem = $this.closest("li"); const partNumber = $listItem.data("part-number"); console.log( "Cliccato .color-option, colore:", color, "per parte:", partNumber, ); partColors[partNumber] = color; $listItem .find(".selected-color") .css("background-color", color); $this.closest(".color-picker").hide(); updateMarkers(); markUnsaved(); }); $(document) .off("click.colorPicker") .on("click.colorPicker", function (e) { if (!$(e.target).closest(".color-picker-container").length) { console.log("Cliccato fuori, nascondo tutte le palette."); $(".color-picker").hide(); } }); } // Delegazione evento per il checkbox $(document) .off("change.showMix", "#showMixPartsAnnotations") .on("change.showMix", "#showMixPartsAnnotations", function (e) { e.preventDefault(); e.stopPropagation(); console.log( "Evento change su #showMixPartsAnnotations, ID elemento:", $(this).attr("id"), ); const isChecked = $(this).is(":checked"); console.log( "Checkbox #showMixPartsAnnotations cambiato:", isChecked, ); if (!$("#showMixPartsAnnotations").length) { console.error( "Checkbox #showMixPartsAnnotations non trovato nel DOM.", ); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } updatePartsList(); updateMarkers(); if ( photoAnnotations[$("#samplePhotoAnnotations").attr("src")] ?.hasDescriptions ) { updateDescriptions(); } }); // =================== // LOAD EXISTING PARTS // =================== function loadExistingParts(iddatadb, idquotations) { console.log("loadExistingParts chiamato con:", { iddatadb, idquotations, }); if (!iddatadb && !idquotations) { const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } const endpoint = idquotations ? "load_parts_quotation.php" : "load_parts.php"; const data = idquotations ? { idquotations: idquotations } : { iddatadb: iddatadb }; $.ajax({ url: endpoint, method: "GET", data: data, success: function (response) { console.log("Risposta da load_parts:", response); partsListData = []; if ( response.success && response.parts && response.parts.length > 0 ) { partsListData = response.parts; response.parts.forEach((part) => { const defaultColor = part.part_description .toLowerCase() .startsWith("mix") ? "#0000ff" : "#ff0000"; partColors[part.part_number] = defaultColor; }); updatePartsList(); // Forza aggiornamento iniziale del checkbox setTimeout(() => { $("#showMixPartsAnnotations").trigger("change.showMix"); }, 100); } else { const errorMsg = $( '", ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }, error: function (xhr, status, error) { console.error("Errore AJAX in loadExistingParts:", { status, error, responseText: xhr.responseText, }); const errorMsg = $( '", ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); } // =================== // MARKERS & DESCRIPTIONS // =================== function updateMarkers() { console.log( "updateMarkers chiamato, markerObjects:", Object.keys(markerObjects), ); for (let partNumber in markerObjects) { fabricCanvas.remove(markerObjects[partNumber]); delete markerObjects[partNumber]; } markerObjects = {}; const currentPhoto = $("#samplePhotoAnnotations").attr("src"); const annotations = photoAnnotations[currentPhoto] || { markers: [], hasDescriptions: false, descriptionPosition: { x: 10, y: 10 }, descriptionSize: { width: photoData.displayWidth * 0.3, height: photoData.displayHeight * 0.3, }, }; const showMixParts = $("#showMixPartsAnnotations").is(":checked"); annotations.markers.forEach((marker) => { const part = partsListData.find( (p) => p.part_number == marker.partNumber, ); const partDescription = part ? part.part_description : ""; if ( !showMixParts && partDescription && partDescription.toLowerCase().startsWith("mix") ) { console.log("Ignoro marker per parte Mix:", marker.partNumber); return; } const radius = 12; const fontSize = 16; const markerColor = marker.color || partColors[marker.partNumber] || "#ff0000"; const circle = new fabric.Circle({ radius: radius, fill: markerColor, stroke: markerColor, strokeWidth: 1, left: marker.x * photoData.scale, top: marker.y * photoData.scale, originX: "center", originY: "center", selectable: true, hasControls: false, lockScalingX: true, lockScalingY: true, lockRotation: true, }); const text = new fabric.Text(marker.partNumber.toString(), { fontFamily: "Arial", fontSize: fontSize, fontWeight: "bold", fill: "#ffffff", left: marker.x * photoData.scale, top: marker.y * photoData.scale, originX: "center", originY: "middle", selectable: true, hasControls: false, lockScalingX: true, lockScalingY: true, lockRotation: true, }); const group = new fabric.Group([circle, text], { left: marker.x * photoData.scale, top: marker.y * photoData.scale, originX: "center", originY: "center", selectable: true, hasControls: false, lockScalingX: true, lockScalingY: true, lockRotation: true, }); group.on("moving", function () { marker.x = this.left / photoData.scale; marker.y = this.top / photoData.scale; console.log("Marker spostato:", { partNumber: marker.partNumber, x: marker.x, y: marker.y, }); markUnsaved(); }); fabricCanvas.add(group); markerObjects[marker.partNumber] = group; }); fabricCanvas.renderAll(); console.log( "Canvas aggiornato, numero di marker:", Object.keys(markerObjects).length, ); } function updateDescriptions() { console.log("updateDescriptions chiamato"); const currentPhoto = $("#samplePhotoAnnotations").attr("src"); const annotations = photoAnnotations[currentPhoto] || { markers: [], hasDescriptions: false, descriptionPosition: { x: 10, y: 10 }, descriptionSize: { width: photoData.displayWidth * 0.3, height: photoData.displayHeight * 0.3, }, }; const showMixParts = $("#showMixPartsAnnotations").is(":checked"); if (!annotations.hasDescriptions) { if (descriptionTextbox) { fabricCanvas.remove(descriptionTextbox); descriptionTextbox = null; fabricCanvas.renderAll(); } console.log( "Nessuna descrizione attiva, rimuovo textbox se presente.", ); return; } const partsList = partsListData .filter( (part) => showMixParts || !part.part_description.toLowerCase().startsWith("mix"), ) .map((part) => `${part.part_number} ${part.part_description}`); const text = partsList.join("\n"); console.log("Testo descrizione generato:", text); if (descriptionTextbox) { fabricCanvas.remove(descriptionTextbox); descriptionTextbox = null; } descriptionTextbox = new fabric.Textbox(text, { left: annotations.descriptionPosition.x * photoData.scale, top: annotations.descriptionPosition.y * photoData.scale, width: annotations.descriptionSize.width, scaleX: 1, scaleY: 1, backgroundColor: "transparent", fontFamily: "Arial", fontSize: 24, fill: "#000000", padding: 10, editable: false, selectable: true, hasControls: true, borderColor: "#ccc", cornerColor: "#888", cornerSize: 10, transparentCorners: false, lockRotation: true, lockScalingFlip: true, minScaleLimit: 0.1, }); descriptionTextbox.on("scaling", function () { fitFontToBox(this); annotations.descriptionSize.width = this.width * this.scaleX; annotations.descriptionSize.height = this.height * this.scaleY; console.log( "Descrizione ridimensionata:", annotations.descriptionSize, ); markUnsaved(); }); descriptionTextbox.on("moving", function () { annotations.descriptionPosition.x = this.left / photoData.scale; annotations.descriptionPosition.y = this.top / photoData.scale; console.log( "Descrizione spostata:", annotations.descriptionPosition, ); markUnsaved(); }); fabricCanvas.add(descriptionTextbox); fitFontToBox(descriptionTextbox); fabricCanvas.renderAll(); console.log("Descrizione aggiunta al canvas:", text); } function fitFontToBox(textbox) { let fontSize = 24; textbox.set("fontSize", fontSize); textbox.setCoords(); while ( (textbox.textLines.length * textbox.fontSize * 1.2 > textbox.height * textbox.scaleY || textbox._getTransformedDimensions().x > textbox.width * textbox.scaleX) && fontSize > 8 ) { fontSize -= 1; textbox.set("fontSize", fontSize); textbox.setCoords(); } while ( textbox.textLines.length * textbox.fontSize * 1.2 < textbox.height * textbox.scaleY && textbox._getTransformedDimensions().x < textbox.width * textbox.scaleX && fontSize < 32 ) { fontSize += 1; textbox.set("fontSize", fontSize); textbox.setCoords(); } } function clearCanvasMarkers(clearDescriptions = true) { console.log( "clearCanvasMarkers chiamato, clearDescriptions:", clearDescriptions, ); const currentPhoto = $("#samplePhotoAnnotations").attr("src"); if (clearDescriptions && photoAnnotations[currentPhoto]) { photoAnnotations[currentPhoto].hasDescriptions = false; photoAnnotations[currentPhoto].descriptionPosition = { x: 10, y: 10, }; photoAnnotations[currentPhoto].descriptionSize = { width: photoData.displayWidth * 0.3, height: photoData.displayHeight * 0.3, }; if (descriptionTextbox) { fabricCanvas.remove(descriptionTextbox); descriptionTextbox = null; } } for (let partNumber in markerObjects) { fabricCanvas.remove(markerObjects[partNumber]); delete markerObjects[partNumber]; } markerObjects = {}; const canvas = document.getElementById("photoCanvasAnnotations"); const ctx = canvas.getContext("2d"); canvas.width = photoData.naturalWidth; canvas.height = photoData.naturalHeight; canvas.style.width = `${photoData.displayWidth}px`; canvas.style.height = `${photoData.displayHeight}px`; ctx.clearRect(0, 0, canvas.width, canvas.height); if ($("#samplePhotoAnnotations")[0].naturalWidth) { ctx.drawImage( $("#samplePhotoAnnotations")[0], 0, 0, canvas.width, canvas.height, ); } updateMarkers(); if (photoAnnotations[currentPhoto]?.hasDescriptions) { updateDescriptions(); } markUnsaved(); } function undoLastMarker() { console.log("undoLastMarker chiamato"); const currentPhoto = $("#samplePhotoAnnotations").attr("src"); if ( photoAnnotations[currentPhoto] && photoAnnotations[currentPhoto].markers.length > 0 ) { const lastMarker = photoAnnotations[currentPhoto].markers.pop(); if (markerObjects[lastMarker.partNumber]) { fabricCanvas.remove(markerObjects[lastMarker.partNumber]); delete markerObjects[lastMarker.partNumber]; fabricCanvas.renderAll(); } console.log("Ultimo marker rimosso:", lastMarker); markUnsaved(); } } // Delegazione evento per il pulsante "Descrizioni" $(document) .off("click.addDescriptions", "#addDescriptionsBtnAnnotations") .on( "click.addDescriptions", "#addDescriptionsBtnAnnotations", function (e) { e.preventDefault(); e.stopPropagation(); console.log( "Evento click su #addDescriptionsBtnAnnotations, ID elemento:", $(this).attr("id"), ); if (!$("#addDescriptionsBtnAnnotations").length) { console.error( "Pulsante #addDescriptionsBtnAnnotations non trovato nel DOM.", ); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } const currentPhoto = $("#samplePhotoAnnotations").attr("src"); if (!photoAnnotations[currentPhoto]) { photoAnnotations[currentPhoto] = { markers: [], hasDescriptions: false, descriptionPosition: { x: 10, y: 10 }, descriptionSize: { width: photoData.displayWidth * 0.3, height: photoData.displayHeight * 0.3, }, }; } photoAnnotations[currentPhoto].hasDescriptions = true; updateDescriptions(); markUnsaved(); }, ); // Delegazione evento per il pulsante "Rimuovi" $(document) .off("click.removeAnnotations", "#removeAnnotationsBtnAnnotations") .on( "click.removeAnnotations", "#removeAnnotationsBtnAnnotations", function (e) { e.preventDefault(); e.stopPropagation(); console.log( "Evento click su #removeAnnotationsBtnAnnotations, ID elemento:", $(this).attr("id"), ); if (!$("#removeAnnotationsBtnAnnotations").length) { console.error( "Pulsante #removeAnnotationsBtnAnnotations non trovato nel DOM.", ); const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } clearCanvasMarkers(true); }, ); // Delegazione evento per il pulsante "Undo" $(document) .off("click.undoMarker", "#undoMarkerBtnAnnotations") .on("click.undoMarker", "#undoMarkerBtnAnnotations", function (e) { e.preventDefault(); e.stopPropagation(); console.log( "Evento click su #undoMarkerBtnAnnotations, ID elemento:", $(this).attr("id"), ); undoLastMarker(); }); // =================== // SAVE PHOTO // =================== $(document) .off("click.savePhoto", "#savePhotoBtnAnnotations") .on("click.savePhoto", "#savePhotoBtnAnnotations", function (e) { e.preventDefault(); e.stopPropagation(); console.log( "Evento click su #savePhotoBtnAnnotations, ID elemento:", $(this).attr("id"), ); if (!$("#samplePhotoAnnotations").attr("src")) { const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } if (!fabricCanvas) { const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } const canvas = document.getElementById("photoCanvasAnnotations"); const ctx = canvas.getContext("2d"); const img = $("#samplePhotoAnnotations")[0]; if (!img.complete || img.naturalWidth === 0) { const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } canvas.width = photoData.naturalWidth; canvas.height = photoData.naturalHeight; ctx.drawImage(img, 0, 0, canvas.width, canvas.height); try { const fabricDataURL = fabricCanvas.toDataURL({ format: "png", multiplier: 1 / photoData.scale, }); const tempImg = new Image(); tempImg.src = fabricDataURL; tempImg.onload = function () { ctx.drawImage(tempImg, 0, 0, canvas.width, canvas.height); fetch(fabricDataURL) .then((res) => res.blob()) .then((blob) => { canvas.toBlob(function (blob) { if (!blob) { const errorMsg = $( '', ); $("#annotationsModal .modal-body").prepend( errorMsg, ); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); return; } const timestamp = new Date() .toISOString() .replace(/[:.]/g, "-"); const iddatadb = $("#annotationsModal").data("iddatadb"); const idquotations = $("#annotationsModal").data("idquotations"); const id = iddatadb || idquotations; const endpoint = idquotations ? "save_annotated_photo_quotation.php" : "save_annotated_photo.php"; const finalName = `photo_${id}_${timestamp}.png`; const formData = new FormData(); formData.append("file", blob, finalName); formData.append("filename", finalName); formData.append( idquotations ? "idquotations" : "iddatadb", id, ); $.ajax({ url: endpoint, method: "POST", data: formData, processData: false, contentType: false, success: function (response) { if (response.success) { const successMsg = $( '", ); $( "#annotationsModal .modal-body", ).prepend(successMsg); setTimeout(function () { successMsg.fadeOut( 500, function () { $(this).remove(); }, ); }, 5000); const photoSelector = $( "#photoSelectorAnnotations", ); if (photoSelector.length > 0) { const newPhotoPath = response.file_path; const newPhotoName = newPhotoPath .split("/") .pop(); const optionCount = photoSelector.find( "option", ).length; const newOption = $( "", ) .val(newPhotoPath) .text( `Photo ${optionCount + 1} - ${newPhotoName}`, ); photoSelector.append(newOption); photoSelector .val(newPhotoPath) .trigger("change"); } unsavedChanges = false; } else { const errorMsg = $( '", ); $( "#annotationsModal .modal-body", ).prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut( 500, function () { $(this).remove(); }, ); }, 5000); } }, error: function (xhr, status, error) { const errorMsg = $( '", ); $( "#annotationsModal .modal-body", ).prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); }, }); }, "image/png"); }); }; } catch (e) { const errorMsg = $( '", ); $("#annotationsModal .modal-body").prepend(errorMsg); setTimeout(function () { errorMsg.fadeOut(500, function () { $(this).remove(); }); }, 5000); } }); function markUnsaved() { if (!unsavedChanges) { unsavedChanges = true; console.log("Modifiche non salvate rilevate."); } } });