1556 lines
61 KiB
JavaScript
1556 lines
61 KiB
JavaScript
$(document).ready(function () {
|
|
// ===================
|
|
// GLOBAL STATE
|
|
// ===================
|
|
let photoData = {
|
|
naturalWidth: 0,
|
|
naturalHeight: 0,
|
|
displayWidth: 0,
|
|
displayHeight: 0,
|
|
scale: 1,
|
|
};
|
|
|
|
let photoAnnotations = {};
|
|
let partColors = {};
|
|
let partSizes = {}; // memorizza la dimensione specifica per parte
|
|
let selectedPartNumber = null;
|
|
let unsavedChanges = false;
|
|
let fabricCanvas = null;
|
|
let descriptionTextbox = null;
|
|
let markerObjects = {};
|
|
let nextMarkerId = 1;
|
|
let partsListData = [];
|
|
// DIMENSIONE GLOBALE MARKER
|
|
let globalMarkerSize = 16;
|
|
|
|
// ===================
|
|
// MODAL INITIALIZATION
|
|
// ===================
|
|
window.initAnnotationsModal = function (iddatadb, idquotations, trfHeader) {
|
|
console.log("initAnnotationsModal chiamato con:", {
|
|
iddatadb,
|
|
idquotations,
|
|
trfHeader,
|
|
});
|
|
|
|
$("#annotationsModal").attr("data-iddatadb", iddatadb);
|
|
|
|
if (!iddatadb && !idquotations) {
|
|
const errorMsg = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore: ID TRF mancante. Impossibile inizializzare il modale delle annotazioni.</div>',
|
|
);
|
|
$("#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();
|
|
// Inizializza slider dimensione marker
|
|
$("#markerSizeSlider").val(globalMarkerSize);
|
|
$("#markerSizeValue").text(globalMarkerSize + "px");
|
|
|
|
// 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 = {};
|
|
globalMarkerSize = 16;
|
|
$("#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();
|
|
});
|
|
|
|
// SLIDER DIMENSIONE MARKER
|
|
$(document)
|
|
.off("input", "#markerSizeSlider")
|
|
.on("input", "#markerSizeSlider", function () {
|
|
globalMarkerSize = parseInt($(this).val());
|
|
$("#markerSizeValue").text(globalMarkerSize + "px");
|
|
|
|
partsListData.forEach(function (part) {
|
|
if (part && part.part_number != null) {
|
|
partSizes[part.part_number] = globalMarkerSize;
|
|
}
|
|
});
|
|
|
|
$("#partsListAnnotations .marker-size-slider").val(
|
|
globalMarkerSize,
|
|
);
|
|
$("#partsListAnnotations .marker-size-value").text(
|
|
globalMarkerSize + "px",
|
|
);
|
|
|
|
updateMarkers();
|
|
markUnsaved();
|
|
});
|
|
|
|
// ===================
|
|
// 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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Nessuna foto trovata per questo elemento.</div>',
|
|
);
|
|
$("#annotationsModal .modal-body").prepend(errorMsg);
|
|
setTimeout(function () {
|
|
errorMsg.fadeOut(500, function () {
|
|
$(this).remove();
|
|
});
|
|
}, 5000);
|
|
}
|
|
} else {
|
|
const errorMsg = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">' +
|
|
(response.message ||
|
|
"Errore nel caricamento della foto.") +
|
|
"</div>",
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore nel caricamento della foto: ' +
|
|
error +
|
|
" (" +
|
|
xhr.status +
|
|
")</div>",
|
|
);
|
|
$("#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 = $(
|
|
'<select id="photoSelectorAnnotations" class="form-control"></select>',
|
|
);
|
|
photos.forEach((photo, index) => {
|
|
const photoName = photo.split("/").pop();
|
|
const option = $("<option></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";
|
|
photoAnnotations[currentPhoto].markers.push({
|
|
id: nextMarkerId++,
|
|
partNumber: selectedPartNumber,
|
|
x,
|
|
y,
|
|
color: partColor,
|
|
});
|
|
|
|
console.log("Marker aggiunto/spostato:", {
|
|
partNumber: selectedPartNumber,
|
|
x,
|
|
y,
|
|
color: partColor,
|
|
});
|
|
updateMarkers();
|
|
markUnsaved();
|
|
});
|
|
|
|
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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore: Pulsante #downloadPhotoBtnAnnotations non trovato nel DOM.</div>',
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Nessuna foto caricata da scaricare.</div>',
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore: Impossibile caricare l\'immagine per il download.</div>',
|
|
);
|
|
$("#annotationsModal .modal-body").prepend(errorMsg);
|
|
setTimeout(function () {
|
|
errorMsg.fadeOut(500, function () {
|
|
$(this).remove();
|
|
});
|
|
}, 5000);
|
|
};
|
|
},
|
|
);
|
|
|
|
// ===================
|
|
// TORNA AL MODALE PARTI (modal_partsTable.php)
|
|
// ===================
|
|
$(document)
|
|
.off("click.backToParts", "#backToPartsBtnAnnotations")
|
|
.on("click.backToParts", "#backToPartsBtnAnnotations", function (e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
if (
|
|
unsavedChanges &&
|
|
!confirm(
|
|
"Hai modifiche non salvate. Vuoi davvero tornare al modale delle parti?",
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const iddatadb = $("#annotationsModal").data("iddatadb");
|
|
const idquotations = $("#annotationsModal").data("idquotations");
|
|
const trfHeader = $("#trfHeaderAnnotations").text();
|
|
|
|
// CHIUDI MODALE ANNOTAZIONI IN MODO SICURO
|
|
const annotationsModalElement =
|
|
document.getElementById("annotationsModal");
|
|
if (annotationsModalElement) {
|
|
const modalInstance = bootstrap.Modal.getInstance(
|
|
annotationsModalElement,
|
|
);
|
|
if (modalInstance) {
|
|
modalInstance.hide();
|
|
} else {
|
|
$(annotationsModalElement).modal("hide"); // fallback jQuery
|
|
}
|
|
}
|
|
console.log("Torno a modal_partsTable.php con:", {
|
|
iddatadb,
|
|
idquotations,
|
|
trfHeader,
|
|
});
|
|
// CARICA E APRI MODALE PARTI
|
|
$.get(
|
|
"modal_partsTable.php",
|
|
{
|
|
iddatadb: iddatadb || "",
|
|
idquotations: idquotations || "",
|
|
trfHeader: trfHeader,
|
|
},
|
|
function (data) {
|
|
// Rimuovi vecchio modale (con ID corretto)
|
|
$("#partsTableModal").remove();
|
|
$(".modal-backdrop").remove();
|
|
|
|
// Aggiungi nuovo modale
|
|
$("body").append(data);
|
|
|
|
// Apri con Bootstrap 5
|
|
const partsModalElement =
|
|
document.getElementById("partsTableModal");
|
|
if (partsModalElement) {
|
|
const modal = new bootstrap.Modal(partsModalElement, {
|
|
backdrop: true,
|
|
keyboard: true,
|
|
focus: true,
|
|
});
|
|
modal.show();
|
|
} else {
|
|
let iddatadb =
|
|
$("#annotationsModal").attr("data-iddatadb");
|
|
|
|
$(
|
|
"button.parts-btn[data-iddatadb='" +
|
|
iddatadb +
|
|
"']",
|
|
).trigger("click");
|
|
}
|
|
},
|
|
).fail(function (xhr) {
|
|
console.error("Errore caricamento modale parti:", xhr);
|
|
alert(
|
|
"Errore 404: modal_partsTable.php non trovato o errore server.",
|
|
);
|
|
});
|
|
});
|
|
// ===================
|
|
// 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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore: Elemento #partsListAnnotations non trovato nel DOM.</div>',
|
|
);
|
|
$("#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) =>
|
|
`<div class="color-option" style="background-color: ${color}; width: 20px; height: 20px; display: inline-block; margin: 2px; cursor: pointer; pointer-events: auto;" data-color="${color}"></div>`,
|
|
)
|
|
.join("");
|
|
const listItem = `
|
|
<li class="list-group-item" data-part-number="${partNumber}">
|
|
<span class="part-info" style="cursor: pointer;">${partNumber} - ${partDescription}</span>
|
|
<div class="color-picker-container" style="display:inline-block; position: relative; overflow: visible; z-index: 2000; margin-left:5px;">
|
|
<div class="color-option selected-color" style="background-color: ${partColor}; width: 20px; height: 20px; display: inline-block; cursor: pointer; pointer-events: auto;"></div>
|
|
<div class="color-picker" style="display: none; position: absolute; right: 0; z-index: 2000; background: #fff; border: 1px solid #ccc; padding: 5px;">${colorOptions}</div>
|
|
</div>
|
|
<div style="display:inline-flex; align-items:center; gap:4px; margin-left:5px;">
|
|
<input type="range" class="marker-size-slider"
|
|
min="12" max="48" step="2"
|
|
value="${partSizes[partNumber] || globalMarkerSize}"
|
|
style="width:70px;">
|
|
<span class="marker-size-value" style="font-size:0.8rem;">${partSizes[partNumber] || globalMarkerSize}px</span>
|
|
</div>
|
|
</li>`;
|
|
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);
|
|
const partNumber = $listItem.data("part-number");
|
|
|
|
if (
|
|
selectedPartNumber == partNumber &&
|
|
$listItem.hasClass("active")
|
|
) {
|
|
selectedPartNumber = null;
|
|
$listItem.removeClass("active");
|
|
|
|
return;
|
|
}
|
|
|
|
selectedPartNumber = partNumber;
|
|
$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();
|
|
});
|
|
|
|
// === Gestione cambio colore ===
|
|
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,
|
|
);
|
|
|
|
// Salva il nuovo colore
|
|
partColors[partNumber] = color;
|
|
|
|
// Aggiorna il colore visivo nel selettore
|
|
$listItem
|
|
.find(".selected-color")
|
|
.css("background-color", color);
|
|
|
|
let currentPhoto = $("#samplePhotoAnnotations").attr("src");
|
|
let annotations = photoAnnotations[currentPhoto];
|
|
|
|
if (annotations) {
|
|
annotations.markers.forEach(function (m) {
|
|
if (m.partNumber == partNumber) {
|
|
m.color = color;
|
|
let group = markerObjects[m.id];
|
|
|
|
if (group) {
|
|
let circle = group.item(0);
|
|
|
|
circle.set("fill", color);
|
|
circle.set("stroke", color);
|
|
}
|
|
}
|
|
});
|
|
fabricCanvas.renderAll();
|
|
}
|
|
|
|
// Chiudi la palette e aggiorna canvas
|
|
$this.closest(".color-picker").hide();
|
|
updateMarkers();
|
|
markUnsaved();
|
|
});
|
|
|
|
// === Slider locale per dimensione marker ===
|
|
partsListElement
|
|
.off("input.localMarkerSize")
|
|
.on("input.localMarkerSize", ".marker-size-slider", function (e) {
|
|
e.stopPropagation();
|
|
e.preventDefault();
|
|
|
|
// Riferimenti alla parte e valore scelto
|
|
const $slider = $(this);
|
|
const partNumber = $slider.closest("li").data("part-number");
|
|
const newSize = parseInt($slider.val());
|
|
|
|
// Aggiorna il valore visivo accanto allo slider
|
|
$slider.siblings(".marker-size-value").text(newSize + "px");
|
|
|
|
// Memorizza la nuova dimensione per quella parte
|
|
partSizes[partNumber] = newSize;
|
|
|
|
// Aggiorna i marker sul canvas
|
|
updateMarkers();
|
|
|
|
// Segnala modifiche non salvate
|
|
markUnsaved();
|
|
|
|
console.log(
|
|
`Dimensione marker aggiornata per parte ${partNumber}: ${newSize}px`,
|
|
);
|
|
});
|
|
|
|
$(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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore: Checkbox #showMixPartsAnnotations non trovato nel DOM.</div>',
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore: ID TRF mancante per il caricamento delle parti.</div>',
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">' +
|
|
(response.message ||
|
|
"Nessuna parte trovata per questo elemento.") +
|
|
"</div>",
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore nel caricamento delle parti: ' +
|
|
error +
|
|
" (" +
|
|
xhr.status +
|
|
")</div>",
|
|
);
|
|
$("#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 markerId in markerObjects) {
|
|
fabricCanvas.remove(markerObjects[markerId]);
|
|
delete markerObjects[markerId];
|
|
}
|
|
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 size = partSizes[marker.partNumber] || globalMarkerSize;
|
|
const radius = size / 2;
|
|
const fontSize = Math.max(10, Math.round(size * 0.65));
|
|
|
|
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.markerId = marker.id;
|
|
group.partNumber = marker.partNumber;
|
|
|
|
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.id] = 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: Math.max(16, Math.round(globalMarkerSize * 0.8)),
|
|
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 markerId in markerObjects) {
|
|
fabricCanvas.remove(markerObjects[markerId]);
|
|
delete markerObjects[markerId];
|
|
}
|
|
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.id]) {
|
|
fabricCanvas.remove(markerObjects[lastMarker.id]);
|
|
delete markerObjects[lastMarker.id];
|
|
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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore: Pulsante #addDescriptionsBtnAnnotations non trovato nel DOM.</div>',
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore: Pulsante #removeAnnotationsBtnAnnotations non trovato nel DOM.</div>',
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Nessuna foto caricata da salvare.</div>',
|
|
);
|
|
$("#annotationsModal .modal-body").prepend(errorMsg);
|
|
setTimeout(function () {
|
|
errorMsg.fadeOut(500, function () {
|
|
$(this).remove();
|
|
});
|
|
}, 5000);
|
|
return;
|
|
}
|
|
|
|
if (!fabricCanvas) {
|
|
const errorMsg = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore: Canvas Fabric.js non inizializzato.</div>',
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore: l\'immagine non è caricata correttamente.</div>',
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore durante la creazione dell\'immagine finale.</div>',
|
|
);
|
|
$("#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 = $(
|
|
'<div class="alert alert-success temp-alert" role="alert">Foto salvata con successo: ' +
|
|
response.file_path +
|
|
"</div>",
|
|
);
|
|
$(
|
|
"#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 = $(
|
|
"<option></option>",
|
|
)
|
|
.val(newPhotoPath)
|
|
.text(
|
|
`Photo ${optionCount + 1} - ${newPhotoName}`,
|
|
);
|
|
photoSelector.append(newOption);
|
|
photoSelector
|
|
.val(newPhotoPath)
|
|
.trigger("change");
|
|
}
|
|
|
|
unsavedChanges = false;
|
|
} else {
|
|
const errorMsg = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio della foto: ' +
|
|
response.message +
|
|
"</div>",
|
|
);
|
|
$(
|
|
"#annotationsModal .modal-body",
|
|
).prepend(errorMsg);
|
|
setTimeout(function () {
|
|
errorMsg.fadeOut(
|
|
500,
|
|
function () {
|
|
$(this).remove();
|
|
},
|
|
);
|
|
}, 5000);
|
|
}
|
|
},
|
|
error: function (xhr, status, error) {
|
|
const errorMsg = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore nel salvataggio della foto: ' +
|
|
error +
|
|
" (" +
|
|
xhr.status +
|
|
")</div>",
|
|
);
|
|
$(
|
|
"#annotationsModal .modal-body",
|
|
).prepend(errorMsg);
|
|
setTimeout(function () {
|
|
errorMsg.fadeOut(500, function () {
|
|
$(this).remove();
|
|
});
|
|
}, 5000);
|
|
},
|
|
});
|
|
}, "image/png");
|
|
});
|
|
};
|
|
} catch (e) {
|
|
const errorMsg = $(
|
|
'<div class="alert alert-danger temp-alert" role="alert">Errore durante la generazione dell\'immagine: ' +
|
|
e.message +
|
|
"</div>",
|
|
);
|
|
$("#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.");
|
|
}
|
|
}
|
|
});
|