Foto del Campione
-
-
+
-
+
@@ -99,7 +100,10 @@
#partsModal {
z-index: 1060 !important;
- /* Aumentato per superare il modale foto (z-index: 1050) */
+ }
+
+ #partsModal .modal-backdrop {
+ z-index: 1055 !important;
}
#partsModal .modal-content {
@@ -132,12 +136,33 @@
position: absolute;
background: rgba(255, 255, 255, 0.8);
padding: 5px;
- font-size: 10px;
font-family: Arial, sans-serif;
color: #000000;
cursor: move;
user-select: none;
z-index: 1000;
+ min-width: 100px;
+ min-height: 50px;
+ overflow: visible;
+ border: 1px solid #ccc;
+ }
+
+ .draggable-description.active-interaction {
+ border: 2px dashed #000;
+ }
+
+ .draggable-description div {
+ white-space: nowrap;
+ }
+
+ .resize-handle {
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ width: 10px;
+ height: 10px;
+ background: #888;
+ cursor: se-resize;
}
.draggable-marker {
@@ -167,21 +192,17 @@
font-size: 0.8rem;
}
- /* Normale Save button */
#savePhotoBtn {
transition: all 0.3s ease-in-out;
}
- /* Unsaved changes */
#savePhotoBtn.unsaved {
background-color: #dc3545 !important;
- /* Rosso */
border-color: #dc3545 !important;
color: white !important;
animation: pulse 1.2s infinite;
}
- /* Animazione pulsante */
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(220, 53, 69, 0.7);
@@ -195,4 +216,37 @@
box-shadow: 0 0 0 0 rgba(220, 53, 69, 0);
}
}
+
+ .color-picker-container {
+ position: relative;
+ display: inline-block;
+ }
+
+ .color-picker {
+ display: none;
+ position: absolute;
+ top: 25px;
+ left: 0;
+ background: #fff;
+ border: 1px solid #ccc;
+ padding: 5px;
+ z-index: 1002;
+ box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
+ flex-wrap: wrap;
+ width: 120px;
+ }
+
+ .color-option {
+ width: 24px;
+ height: 24px;
+ margin: 3px;
+ border: 1px solid #000;
+ cursor: pointer;
+ display: inline-block;
+ }
+
+ .color-option:hover {
+ border: 2px solid #000;
+ margin: 2px;
+ }
\ No newline at end of file
diff --git a/public/userarea/parts.js b/public/userarea/parts.js
index 24bea23..891067a 100644
--- a/public/userarea/parts.js
+++ b/public/userarea/parts.js
@@ -12,10 +12,13 @@ $(document).ready(function () {
scale: 1,
};
- let photoAnnotations = {}; // Annotazioni per ogni foto
- let partColors = {}; // Colori associati ai numeri delle parti
- let selectedPartNumber = null; // Numero della parte selezionata
- let unsavedChanges = false; // Flag per modifiche non salvate
+ let photoAnnotations = {};
+ let partColors = {};
+ let selectedPartNumber = null;
+ let unsavedChanges = false;
+ let fabricCanvas = null;
+ let descriptionTextbox = null;
+ let markerObjects = {};
// ===================
// VOICE RECOGNITION SETUP
@@ -24,7 +27,7 @@ $(document).ready(function () {
window.SpeechRecognition || window.webkitSpeechRecognition;
let recognition = null;
let isVoiceActive = false;
- const magicWord = "salva"; // Parola magica per aggiungere nuova riga
+ const magicWord = "salva";
if (SpeechRecognition) {
recognition = new SpeechRecognition();
@@ -166,10 +169,39 @@ $(document).ready(function () {
}
});
+ $("#partsModal").on("hidden.bs.modal", function () {
+ // Reset global state to initial values
+ photoData = {
+ naturalWidth: 0,
+ naturalHeight: 0,
+ displayWidth: 0,
+ displayHeight: 0,
+ scale: 1,
+ };
+ photoAnnotations = {};
+ partColors = {};
+ selectedPartNumber = null;
+ unsavedChanges = false;
+ if (fabricCanvas) {
+ fabricCanvas.off(); // Rimuove tutti gli eventi
+ fabricCanvas.dispose();
+ fabricCanvas = null;
+ }
+ descriptionTextbox = null;
+ markerObjects = {};
+ // Clear UI elements
+ $("#photoSelectorContainer").empty().hide();
+ $("#samplePhoto").attr("src", "");
+ $("#partsTableBody").empty();
+ // Remove any temporary messages
+ $(".temp-alert").remove();
+ });
+
// ===================
// PHOTO LOADERS
// ===================
function loadPhoto(iddatadb, idquotations) {
+ const currentPhoto = $("#samplePhoto").attr("src");
const endpoint = idquotations
? "load_photo_quotation.php"
: "load_photo.php";
@@ -183,7 +215,7 @@ $(document).ready(function () {
success: function (response) {
if (response.success) {
if (response.photos && response.photos.length > 1) {
- showPhotoSelector(response.photos);
+ showPhotoSelector(response.photos, currentPhoto);
} else if (
response.photos &&
response.photos.length === 1
@@ -191,24 +223,50 @@ $(document).ready(function () {
loadSinglePhoto(response.photos[0]);
} else {
$("#samplePhoto").attr("src", "");
- alert("Nessuna foto trovata per questo elemento.");
+ const errorMsg = $(
+ '
Nessuna foto trovata per questo elemento.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
}
} else {
- alert(
- response.message ||
- "Errore nel caricamento della foto.",
+ const errorMsg = $(
+ '
' +
+ (response.message ||
+ "Errore nel caricamento della foto.") +
+ "
",
);
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
}
},
error: function (xhr, status, error) {
- alert(
- `Errore nel caricamento della foto: ${error} (${xhr.status})`,
+ const errorMsg = $(
+ '
Errore nel caricamento della foto: ' +
+ error +
+ " (" +
+ xhr.status +
+ ")
",
);
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
},
});
}
- function showPhotoSelector(photos) {
+ function showPhotoSelector(photos, selected = null) {
const selectorContainer = $("#photoSelectorContainer");
selectorContainer.empty().show();
const selector = $(
@@ -225,9 +283,11 @@ $(document).ready(function () {
loadSinglePhoto($(this).val());
});
selectorContainer.append(selector);
- if (photos.length > 0) {
- selector.val(photos[0]);
- loadSinglePhoto(photos[0]);
+ const photoToSelect =
+ selected && photos.includes(selected) ? selected : photos[0];
+ if (photoToSelect) {
+ selector.val(photoToSelect);
+ loadSinglePhoto(photoToSelect);
}
}
@@ -235,6 +295,7 @@ $(document).ready(function () {
const img = $("#samplePhoto");
img.off("load").attr("src", photoPath);
img.on("load", function () {
+ console.log("Foto caricata:", photoPath);
const canvas = document.getElementById("photoCanvas");
const ctx = canvas.getContext("2d");
const naturalWidth = img[0].naturalWidth;
@@ -257,21 +318,159 @@ $(document).ready(function () {
canvas.style.width = `${photoData.displayWidth}px`;
canvas.style.height = `${photoData.displayHeight}px`;
- $("#markerContainer").css({
- width: `${photoData.displayWidth}px`,
- height: `${photoData.displayHeight}px`,
- });
- $("#descriptionList").css({
- maxWidth: `${Math.max(200, Math.round(photoData.displayWidth * 0.35))}px`,
- });
-
ctx.clearRect(0, 0, naturalWidth, naturalHeight);
ctx.drawImage(img[0], 0, 0, naturalWidth, naturalHeight);
- updateMarkers();
- updateDescriptions();
+
+ // === FIX PER FABRIC.JS ===
+
+ // 1. Disponi completamente il canvas esistente
+ if (fabricCanvas) {
+ fabricCanvas.off(); // Rimuove tutti gli eventi esistenti
+ fabricCanvas.dispose();
+ fabricCanvas = null;
+ }
+
+ // 2. Resetta completamente l'elemento canvas overlay
+ const overlayCanvas = document.getElementById("overlayCanvas");
+
+ // Rimuovi l'elemento esistente e creane uno nuovo
+ const canvasContainer = overlayCanvas.parentElement;
+ const newOverlayCanvas = document.createElement("canvas");
+ newOverlayCanvas.id = "overlayCanvas";
+ newOverlayCanvas.width = photoData.displayWidth;
+ newOverlayCanvas.height = photoData.displayHeight;
+ newOverlayCanvas.style.width = `${photoData.displayWidth}px`;
+ newOverlayCanvas.style.height = `${photoData.displayHeight}px`;
+
+ // Mantieni gli stili CSS esistenti
+ newOverlayCanvas.style.position = "absolute";
+ newOverlayCanvas.style.top = "0";
+ newOverlayCanvas.style.left = "0";
+ newOverlayCanvas.style.zIndex = "10";
+
+ // Sostituisci l'elemento
+ canvasContainer.removeChild(overlayCanvas);
+ canvasContainer.appendChild(newOverlayCanvas);
+
+ // 3. Aspetta un tick per assicurarsi che il DOM sia aggiornato
+ setTimeout(() => {
+ // 4. Inizializza nuovo canvas Fabric.js
+ fabricCanvas = new fabric.Canvas("overlayCanvas", {
+ selection: true,
+ preserveObjectStacking: true,
+ width: photoData.displayWidth,
+ height: photoData.displayHeight,
+ });
+
+ // 5. Forza il ridimensionamento del canvas
+ fabricCanvas.setDimensions({
+ width: photoData.displayWidth,
+ height: photoData.displayHeight,
+ });
+
+ // 6. Registra gli eventi
+ fabricCanvas.on("mouse:down", function (options) {
+ console.log("Mouse down event triggered on canvas");
+ if (selectedPartNumber === null) {
+ console.log("Nessun partNumber selezionato");
+ return;
+ }
+ if (options.target) {
+ console.log("Click su oggetto esistente, ignorato");
+ return;
+ }
+ const pointer = fabricCanvas.getPointer(options.e);
+ const x = pointer.x / photoData.scale;
+ const y = pointer.y / photoData.scale;
+ const currentPhoto = $("#samplePhoto").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,
+ });
+ }
+
+ updateMarkers();
+ markUnsaved();
+ selectedPartNumber = null;
+ $("#partsList li").removeClass("active");
+ console.log(
+ "Marker aggiunto per partNumber:",
+ selectedPartNumber,
+ );
+ });
+
+ // 7. Forza focus e render
+ fabricCanvas.upperCanvasEl.focus();
+ fabricCanvas.renderAll();
+
+ // 8. Aggiorna marker e descrizioni esistenti per questa foto
+ updateMarkers();
+ updateDescriptions();
+
+ console.log("Canvas Fabric.js reinizializzato per:", photoPath);
+ console.log(
+ "Dimensioni canvas:",
+ photoData.displayWidth,
+ "x",
+ photoData.displayHeight,
+ );
+ console.log("Scale factor:", photoData.scale);
+ }, 10); // Piccolo delay per assicurare che il DOM sia pronto
});
}
+ // ===================
+ // DOWNLOAD PHOTO
+ // ===================
+ $("#downloadPhotoBtn").on("click", function () {
+ const photoSrc = $("#samplePhoto").attr("src");
+ if (!photoSrc) {
+ const errorMsg = $(
+ '
Nessuna foto caricata da scaricare.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ return;
+ }
+ const photoName = photoSrc.split("/").pop();
+ const link = document.createElement("a");
+ link.href = photoSrc;
+ link.download = photoName;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ });
+
// ===================
// PARTS TABLE
// ===================
@@ -316,6 +515,9 @@ $(document).ready(function () {
);
addNewRow(maxPartNumber + 1);
updatePartsList();
+ if (photoAnnotations[$("#samplePhoto").attr("src")]?.hasDescriptions) {
+ updateDescriptions();
+ }
});
$(document).on("click", ".add-mix-row", function (e) {
@@ -329,6 +531,9 @@ $(document).ready(function () {
);
addNewRow(maxPartNumber + 1, true);
updatePartsList();
+ if (photoAnnotations[$("#samplePhoto").attr("src")]?.hasDescriptions) {
+ updateDescriptions();
+ }
});
$(document).on("click", ".remove-row", function (e) {
@@ -352,23 +557,59 @@ $(document).ready(function () {
if (response.success) {
$row.remove();
delete partColors[partNumber];
+ if (markerObjects[partNumber]) {
+ fabricCanvas.remove(markerObjects[partNumber]);
+ delete markerObjects[partNumber];
+ fabricCanvas.renderAll();
+ }
updateRowButtons();
updatePartsList();
- clearCanvasMarkers(false);
markUnsaved();
} else {
- alert(`Errore nell'eliminazione: ${response.message}`);
+ const errorMsg = $(
+ '
Errore nell\'eliminazione: ' +
+ response.message +
+ "
",
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
}
},
error: function (xhr, status, error) {
- alert(`Errore nell'eliminazione: ${error} (${xhr.status})`);
+ const errorMsg = $(
+ '
Errore nell\'eliminazione: ' +
+ error +
+ " (" +
+ xhr.status +
+ ")
",
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
},
});
} else {
$row.remove();
delete partColors[partNumber];
+ if (markerObjects[partNumber]) {
+ fabricCanvas.remove(markerObjects[partNumber]);
+ delete markerObjects[partNumber];
+ fabricCanvas.renderAll();
+ }
updateRowButtons();
updatePartsList();
+ if (
+ photoAnnotations[$("#samplePhoto").attr("src")]?.hasDescriptions
+ ) {
+ updateDescriptions();
+ }
markUnsaved();
}
});
@@ -421,27 +662,49 @@ $(document).ready(function () {
}
setTimeout(() => $saveStatus.hide(), 2000);
updatePartsList();
+ if (
+ photoAnnotations[$("#samplePhoto").attr("src")]
+ ?.hasDescriptions
+ ) {
+ updateDescriptions();
+ }
} else {
- alert(`Errore nel salvataggio: ${response.message}`);
+ const errorMsg = $(
+ '
Errore nel salvataggio: ' +
+ response.message +
+ "
",
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
}
},
error: function (xhr, status, error) {
$saveLoading.hide();
- alert(
- `Errore nel salvataggio delle parti: ${error} (${xhr.status})`,
+ const errorMsg = $(
+ '
Errore nel salvataggio delle parti: ' +
+ error +
+ " (" +
+ xhr.status +
+ ")
",
);
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
},
});
}
});
- $(document).on("change", ".part-color", function () {
- const partNumber = $(this).closest("li").data("part-number");
- partColors[partNumber] = $(this).val();
- updateMarkers();
- markUnsaved();
- });
-
+ // ===================
+ // LOAD EXISTING PARTS
+ // ===================
function loadExistingParts(iddatadb, idquotations) {
const endpoint = idquotations
? "load_parts_quotation.php"
@@ -484,17 +747,41 @@ $(document).ready(function () {
updatePartsList();
},
error: function (xhr, status, error) {
- alert(
- `Errore nel caricamento delle parti: ${error} (${xhr.status})`,
+ const errorMsg = $(
+ '
Errore nel caricamento delle parti: ' +
+ error +
+ " (" +
+ xhr.status +
+ ")
",
);
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
addNewRow(1);
},
});
}
+ // ===================
+ // PARTS LIST
+ // ===================
function updatePartsList() {
const showMixParts = $("#showMixParts").is(":checked");
$("#partsList").empty();
+ const predefinedColors = [
+ "#ff0000", // Rosso
+ "#0000ff", // Blu
+ "#00ff00", // Verde
+ "#01832cff", // Giallo
+ "#ff00ff", // Magenta
+ "#00ffff", // Ciano
+ "#800080", // Viola
+ "#ffa500", // Arancione
+ ];
+
$("#partsTableBody tr").each(function () {
const partNumber = $(this).find(".part-number").val();
const partDescription = $(this).find(".part-description").val();
@@ -506,20 +793,117 @@ $(document).ready(function () {
partDescription &&
(showMixParts || !partDescription.startsWith("Mix"))
) {
+ const colorOptions = predefinedColors
+ .map(
+ (color) =>
+ `
`,
+ )
+ .join("");
const listItem = `
${partNumber} - ${partDescription}
`;
$("#partsList").append(listItem);
}
});
- updateMarkers();
+
+ $(".selected-color").on("click", function (e) {
+ e.stopPropagation();
+ const $picker = $(this).siblings(".color-picker");
+ $(".color-picker").not($picker).hide();
+ $picker.toggle();
+ });
+
+ $(".color-option").on("click", function (e) {
+ e.stopPropagation();
+ const $this = $(this);
+ const color = $this.data("color");
+ const $listItem = $this.closest("li");
+ const partNumber = $listItem.data("part-number");
+ partColors[partNumber] = color;
+ $listItem.find(".selected-color").css("background-color", color);
+ $this.closest(".color-picker").hide();
+ updateMarkers();
+ markUnsaved();
+ });
+
+ $(document).on("click", function (e) {
+ if (!$(e.target).closest(".color-picker-container").length) {
+ $(".color-picker").hide();
+ }
+ });
}
+ $(document).on("click", ".add-to-mix-btn", function () {
+ const $listItem = $(this).closest("li");
+ const partDescription = $listItem.text().split(" - ")[1].trim();
+ const $mixRow = $("#partsTableBody tr")
+ .filter(function () {
+ return $(this)
+ .find(".part-description")
+ .val()
+ .startsWith("Mix");
+ })
+ .last();
+
+ if ($mixRow.length === 0) {
+ const errorMsg = $(
+ '
Crea prima una riga Mix usando il pulsante \'M\'.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ return;
+ }
+
+ const $descriptionInput = $mixRow.find(".part-description");
+ let currentDescription = $descriptionInput.val().trim();
+ if (currentDescription === "Mix") {
+ currentDescription = `Mix ${partDescription}`;
+ } else if (!currentDescription.includes(partDescription)) {
+ currentDescription += ` + ${partDescription}`;
+ } else {
+ return;
+ }
+
+ $descriptionInput.val(currentDescription);
+ $descriptionInput.trigger("blur");
+ updatePartsList();
+ if (photoAnnotations[$("#samplePhoto").attr("src")]?.hasDescriptions) {
+ updateDescriptions();
+ }
+ });
+
+ $("#partsList").on("click", "li", function (e) {
+ if (
+ $(e.target).hasClass("add-to-mix-btn") ||
+ $(e.target).hasClass("color-option") ||
+ $(e.target).closest(".color-picker-container").length
+ )
+ return;
+ selectedPartNumber = $(this).data("part-number");
+ $(this).addClass("active").siblings().removeClass("active");
+ console.log("PartNumber selezionato:", selectedPartNumber);
+ });
+
+ $("#showMixParts").on("change", function () {
+ updatePartsList();
+ updateMarkers();
+ if (photoAnnotations[$("#samplePhoto").attr("src")]?.hasDescriptions) {
+ updateDescriptions();
+ }
+ });
+
function renumberParts() {
const $rows = $("#partsTableBody tr");
const iddatadb = $("#partsModal").data("iddatadb");
@@ -531,6 +915,7 @@ $(document).ready(function () {
? { idquotations: idquotations }
: { iddatadb: iddatadb };
let newPartColors = {};
+ let newMarkerObjects = {};
let partsData = $rows
.map(function (index) {
@@ -546,6 +931,9 @@ $(document).ready(function () {
partsData.forEach((part, index) => {
const newNumber = index + 1;
newPartColors[newNumber] = partColors[part.partNumber] || "#ff0000";
+ if (markerObjects[part.partNumber]) {
+ newMarkerObjects[newNumber] = markerObjects[part.partNumber];
+ }
part.partNumber = newNumber;
});
@@ -555,8 +943,6 @@ $(document).ready(function () {
.val(index + 1);
});
- partColors = newPartColors;
-
const currentPhoto = $("#samplePhoto").attr("src");
if (photoAnnotations[currentPhoto]) {
photoAnnotations[currentPhoto].markers.forEach((marker) => {
@@ -565,11 +951,14 @@ $(document).ready(function () {
)?.partNumber;
if (newPartNumber) {
marker.partNumber = newPartNumber;
- marker.color = partColors[newPartNumber];
+ marker.color = newPartColors[newPartNumber];
}
});
}
+ partColors = newPartColors;
+ markerObjects = newMarkerObjects;
+
const partsToSave = partsData.map((part) => ({
id: part.partId || null,
part_number: part.partNumber,
@@ -604,125 +993,65 @@ $(document).ready(function () {
});
updatePartsList();
updateMarkers();
- updateDescriptions();
+ if (photoAnnotations[currentPhoto]?.hasDescriptions) {
+ updateDescriptions();
+ }
markUnsaved();
} else {
- alert(
- `Errore nella rinumerazione delle parti: ${response.message}`,
+ const errorMsg = $(
+ '
Errore nella rinumerazione delle parti: ' +
+ response.message +
+ "
",
);
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
}
},
error: function (xhr, status, error) {
- alert(
- `Errore nella rinumerazione delle parti: ${error} (${xhr.status})`,
+ const errorMsg = $(
+ '
Errore nella rinumerazione delle parti: ' +
+ error +
+ " (" +
+ xhr.status +
+ ")
",
);
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
},
});
}
- $(document).on("click", ".add-to-mix-btn", function () {
- const $listItem = $(this).closest("li");
- const partDescription = $listItem.text().split(" - ")[1].trim();
- const $mixRow = $("#partsTableBody tr")
- .filter(function () {
- return $(this)
- .find(".part-description")
- .val()
- .startsWith("Mix");
- })
- .last();
-
- if ($mixRow.length === 0) {
- alert("Crea prima una riga Mix usando il pulsante 'M'.");
- return;
- }
-
- const $descriptionInput = $mixRow.find(".part-description");
- let currentDescription = $descriptionInput.val().trim();
- if (currentDescription === "Mix") {
- currentDescription = `Mix ${partDescription}`;
- } else if (!currentDescription.includes(partDescription)) {
- currentDescription += ` + ${partDescription}`;
- } else {
- return;
- }
-
- $descriptionInput.val(currentDescription);
- $descriptionInput.trigger("blur");
- updatePartsList();
- });
-
- $("#partsList").on("click", "li", function (e) {
- if (
- $(e.target).hasClass("add-to-mix-btn") ||
- $(e.target).hasClass("part-color")
- )
- return;
- selectedPartNumber = $(this).data("part-number");
- $(this).addClass("active").siblings().removeClass("active");
- });
-
- $("#showMixParts").on("change", updatePartsList);
$("#renumberPartsBtn").on("click", renumberParts);
// ===================
// MARKERS & DESCRIPTIONS
// ===================
- const canvas = document.getElementById("photoCanvas");
- const ctx = canvas.getContext("2d");
-
- $("#markerContainer").on("click", function (e) {
- if (selectedPartNumber === null) return;
- const rect = canvas.getBoundingClientRect();
- const x = (e.clientX - rect.left) / photoData.scale;
- const y = (e.clientY - rect.top) / photoData.scale;
- const currentPhoto = $("#samplePhoto").attr("src");
-
- if (!photoAnnotations[currentPhoto]) {
- photoAnnotations[currentPhoto] = {
- markers: [],
- hasDescriptions: false,
- descriptionPosition: { x: 10, y: 10 },
- };
- }
-
- 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,
- });
- }
-
- updateMarkers();
- updateDescriptions();
- markUnsaved();
- selectedPartNumber = null;
- $("#partsList li").removeClass("active");
- });
-
function updateMarkers() {
- const markerContainer = $("#markerContainer");
- markerContainer.empty().css({
- width: `${photoData.displayWidth}px`,
- height: `${photoData.displayHeight}px`,
- });
+ console.log("Aggiornamento marker");
+ // Rimuovi marker esistenti
+ for (let partNumber in markerObjects) {
+ fabricCanvas.remove(markerObjects[partNumber]);
+ delete markerObjects[partNumber];
+ }
+ markerObjects = {};
const currentPhoto = $("#samplePhoto").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 = $("#showMixParts").is(":checked");
@@ -738,84 +1067,96 @@ $(document).ready(function () {
)
return;
- const scaledX = marker.x * photoData.scale;
- const scaledY = marker.y * photoData.scale;
+ const radius = 12;
+ const fontSize = 16;
const markerColor =
marker.color || partColors[marker.partNumber] || "#ff0000";
- const $marker = $(
- `
${marker.partNumber}
`,
- ).css({
- left: scaledX - 8 + "px",
- top: scaledY - 8 + "px",
+
+ // Crea cerchio
+ 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,
});
- markerContainer.append($marker);
- makeDraggable($marker, marker);
- });
- }
- function makeDraggable($element, item) {
- let isDragging = false;
- let startLeft = 0;
- let startTop = 0;
- let initialX = 0;
- let initialY = 0;
+ // Crea testo
+ 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,
+ });
- $element.on("mousedown", function (e) {
- e.preventDefault();
- isDragging = true;
- startLeft = parseFloat($element.css("left")) || 0;
- startTop = parseFloat($element.css("top")) || 0;
- initialX = e.clientX - startLeft;
- initialY = e.clientY - startTop;
- $element.css("z-index", 1001);
+ // Raggruppa cerchio e testo
+ 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 () {
+ console.log("Marker spostato:", marker.partNumber);
+ marker.x = this.left / photoData.scale;
+ marker.y = this.top / photoData.scale;
+ markUnsaved();
+ });
+
+ fabricCanvas.add(group);
+ markerObjects[marker.partNumber] = group;
});
- $(document).on("mousemove.dragMarker", function (e) {
- if (!isDragging) return;
- let currentX = e.clientX - initialX;
- let currentY = e.clientY - initialY;
- const maxX = photoData.displayWidth - $element.width();
- const maxY = photoData.displayHeight - $element.height();
- currentX = Math.max(0, Math.min(currentX, maxX));
- currentY = Math.max(0, Math.min(currentY, maxY));
- $element.css({ left: currentX + "px", top: currentY + "px" });
-
- if (item && item.partNumber) {
- item.x = (currentX + 8) / photoData.scale;
- item.y = (currentY + 8) / photoData.scale;
- } else {
- const currentPhoto = $("#samplePhoto").attr("src");
- if (photoAnnotations[currentPhoto]) {
- photoAnnotations[currentPhoto].descriptionPosition.x =
- (currentX + 5) / photoData.scale;
- photoAnnotations[currentPhoto].descriptionPosition.y =
- (currentY + 5) / photoData.scale;
- }
- }
- markUnsaved();
- });
-
- $(document).on("mouseup.dragMarker", function () {
- if (!isDragging) return;
- isDragging = false;
- $element.css("z-index", 1000);
- $(document).off("mousemove.dragMarker mouseup.dragMarker");
- });
+ fabricCanvas.renderAll();
+ console.log(
+ "Marker aggiornati, totale:",
+ Object.keys(markerObjects).length,
+ );
}
function updateDescriptions() {
+ console.log("Aggiornamento descrizioni");
const currentPhoto = $("#samplePhoto").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 = $("#showMixParts").is(":checked");
- const descriptionList = $("#descriptionList");
- descriptionList.empty();
if (!annotations.hasDescriptions) {
- descriptionList.hide();
+ if (descriptionTextbox) {
+ fabricCanvas.remove(descriptionTextbox);
+ descriptionTextbox = null;
+ fabricCanvas.renderAll();
+ }
return;
}
@@ -832,18 +1173,90 @@ $(document).ready(function () {
}
});
- descriptionList.css({
- display: "block",
- top: annotations.descriptionPosition.y * photoData.scale + "px",
- left: annotations.descriptionPosition.x * photoData.scale + "px",
+ const text = partsList.join("\n");
+
+ // Rimuovi il textbox esistente
+ 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: "rgba(255, 255, 255, 0.8)",
+ fontFamily: "Arial",
+ fontSize: 20,
+ 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,
});
- partsList.forEach((part) =>
- descriptionList.append(`
${part}
`),
- );
- makeDraggable(descriptionList);
+
+ descriptionTextbox.on("scaling", function () {
+ console.log("Descrizione ridimensionata");
+ fitFontToBox(this);
+ annotations.descriptionSize.width = this.width * this.scaleX;
+ annotations.descriptionSize.height = this.height * this.scaleY;
+ markUnsaved();
+ });
+
+ descriptionTextbox.on("moving", function () {
+ console.log("Descrizione spostata");
+ annotations.descriptionPosition.x = this.left / photoData.scale;
+ annotations.descriptionPosition.y = this.top / photoData.scale;
+ markUnsaved();
+ });
+
+ fabricCanvas.add(descriptionTextbox);
+ fitFontToBox(descriptionTextbox);
+ fabricCanvas.renderAll();
+ console.log("Descrizioni aggiornate");
+ }
+
+ function fitFontToBox(textbox) {
+ let fontSize = 20;
+ 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("Pulizia marker e descrizioni");
const currentPhoto = $("#samplePhoto").attr("src");
if (clearDescriptions && photoAnnotations[currentPhoto]) {
photoAnnotations[currentPhoto].hasDescriptions = false;
@@ -851,8 +1264,22 @@ $(document).ready(function () {
x: 10,
y: 10,
};
+ photoAnnotations[currentPhoto].descriptionSize = {
+ width: photoData.displayWidth * 0.3,
+ height: photoData.displayHeight * 0.3,
+ };
+ if (descriptionTextbox) {
+ fabricCanvas.remove(descriptionTextbox);
+ descriptionTextbox = null;
+ }
}
- $("#markerContainer").empty();
+
+ for (let partNumber in markerObjects) {
+ fabricCanvas.remove(markerObjects[partNumber]);
+ delete markerObjects[partNumber];
+ }
+ markerObjects = {};
+
const canvas = document.getElementById("photoCanvas");
const ctx = canvas.getContext("2d");
canvas.width = photoData.naturalWidth;
@@ -870,29 +1297,40 @@ $(document).ready(function () {
);
}
updateMarkers();
- markUnsaved();
+ if (photoAnnotations[currentPhoto]?.hasDescriptions) {
+ updateDescriptions();
+ }
}
function undoLastMarker() {
+ console.log("Annullamento ultimo marker");
const currentPhoto = $("#samplePhoto").attr("src");
if (
photoAnnotations[currentPhoto] &&
photoAnnotations[currentPhoto].markers.length > 0
) {
- photoAnnotations[currentPhoto].markers.pop();
- updateMarkers();
- updateDescriptions();
+ const lastMarker = photoAnnotations[currentPhoto].markers.pop();
+ if (markerObjects[lastMarker.partNumber]) {
+ fabricCanvas.remove(markerObjects[lastMarker.partNumber]);
+ delete markerObjects[lastMarker.partNumber];
+ fabricCanvas.renderAll();
+ }
markUnsaved();
}
}
$("#addDescriptionsBtn").on("click", function () {
+ console.log("Aggiunta descrizioni");
const currentPhoto = $("#samplePhoto").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;
@@ -910,139 +1348,233 @@ $(document).ready(function () {
// SAVE PHOTO
// ===================
$("#savePhotoBtn").on("click", function () {
- const canvas = document.getElementById("photoCanvas");
- const ctx = canvas.getContext("2d");
- const img = $("#samplePhoto");
- const naturalWidth = img[0].naturalWidth;
- const naturalHeight = img[0].naturalHeight;
- canvas.width = naturalWidth;
- canvas.height = naturalHeight;
- ctx.drawImage(img[0], 0, 0, naturalWidth, naturalHeight);
-
- const currentPhoto = $("#samplePhoto").attr("src");
- const annotations = photoAnnotations[currentPhoto] || {
- markers: [],
- hasDescriptions: false,
- descriptionPosition: { x: 10, y: 10 },
- };
- const showMixParts = $("#showMixParts").is(":checked");
-
- if (annotations.hasDescriptions) {
- const partsList = [];
- $("#partsTableBody tr").each(function () {
- const partNumber = $(this).find(".part-number").val();
- const partDescription = $(this).find(".part-description").val();
- if (
- partNumber &&
- partDescription &&
- (showMixParts || !partDescription.startsWith("Mix"))
- ) {
- partsList.push(`${partNumber} ${partDescription}`);
- }
- });
-
- if (partsList.length > 0) {
- const fontSize = Math.round(naturalWidth * 0.02);
- ctx.font = fontSize + "px Arial";
- const textHeight = fontSize + 8;
- const boxWidth = Math.round(naturalWidth * 0.28);
- const boxHeight = partsList.length * textHeight + 25;
- const x = annotations.descriptionPosition.x;
- const y = annotations.descriptionPosition.y;
-
- ctx.save();
- ctx.shadowColor = "rgba(0,0,0,0.3)";
- ctx.shadowBlur = 8;
- ctx.shadowOffsetX = 3;
- ctx.shadowOffsetY = 3;
- ctx.fillStyle = "rgba(255, 255, 255, 0.9)";
- ctx.beginPath();
- ctx.roundRect(x, y, boxWidth, boxHeight, 12);
- ctx.fill();
- ctx.restore();
-
- ctx.fillStyle = "#111111";
- partsList.forEach((part, index) => {
- ctx.fillText(part, x + 15, y + 35 + index * textHeight);
+ console.log("Inizio salvataggio foto");
+ if (!$("#samplePhoto").attr("src")) {
+ const errorMsg = $(
+ '
Nessuna foto caricata da salvare.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
});
- }
+ }, 5000);
+ return;
}
- annotations.markers.forEach((marker) => {
- const partRow = $("#partsTableBody tr").filter(function () {
- return $(this).find(".part-number").val() == marker.partNumber;
+ const canvas = document.getElementById("photoCanvas");
+ const ctx = canvas.getContext("2d");
+ const img = $("#samplePhoto")[0];
+
+ // Verifica che l'immagine sia caricata
+ if (!img.complete || img.naturalWidth === 0) {
+ const errorMsg = $(
+ '
Errore: l\'immagine non è caricata correttamente.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ return;
+ }
+
+ // Imposta le dimensioni del canvas
+ canvas.width = photoData.naturalWidth;
+ canvas.height = photoData.naturalHeight;
+ ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
+
+ // Renderizza Fabric overlay scalato su photoCanvas
+ try {
+ const fabricDataURL = fabricCanvas.toDataURL({
+ format: "png",
+ multiplier: 1 / photoData.scale,
});
- const partDescription = partRow.find(".part-description").val();
- if (
- !showMixParts &&
- partDescription &&
- partDescription.startsWith("Mix")
- )
- return;
+ const tempImg = new Image();
+ tempImg.src = fabricDataURL;
+ tempImg.onload = function () {
+ ctx.drawImage(tempImg, 0, 0, canvas.width, canvas.height);
+ finalizeSave();
+ };
+ tempImg.onerror = function () {
+ const errorMsg = $(
+ '
Errore durante il rendering dell\'overlay Fabric.js.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ };
+ } catch (e) {
+ console.error("Errore in Fabric.js toDataURL:", e);
+ const errorMsg = $(
+ '
Errore durante la generazione dell\'immagine annotata.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ return;
+ }
- const x = marker.x;
- const y = marker.y;
- const radius = Math.max(5, Math.round(naturalWidth * 0.025));
- const fontSize = Math.max(8, Math.round(radius * 0.9));
- const markerColor =
- marker.color || partColors[marker.partNumber] || "#ff0000";
+ // 1. PRIMA - Modifica la funzione finalizeSave per pulire la foto originale:
+ function finalizeSave() {
+ try {
+ const dataURL = canvas.toDataURL("image/png");
+ const timestamp = new Date()
+ .toISOString()
+ .replace(/[:.]/g, "-");
+ const iddatadb = $("#partsModal").data("iddatadb");
+ const idquotations = $("#partsModal").data("idquotations");
+ const id = iddatadb || idquotations;
+ const endpoint = idquotations
+ ? "save_annotated_photo_quotation.php"
+ : "save_annotated_photo.php";
+ const dataParam = idquotations
+ ? { idquotations: id }
+ : { iddatadb: id };
+ const finalName = `photo_${id}_${timestamp}.png`;
- ctx.beginPath();
- ctx.arc(x, y, radius, 0, 2 * Math.PI);
- ctx.fillStyle = markerColor;
- ctx.fill();
- ctx.lineWidth = 3;
- ctx.strokeStyle = markerColor;
- ctx.stroke();
- ctx.fillStyle = "#ffffff";
- ctx.font = `bold ${fontSize}px Arial`;
- ctx.textAlign = "center";
- ctx.textBaseline = "middle";
- ctx.fillText(marker.partNumber || "", x, y);
- });
+ $.ajax({
+ url: endpoint,
+ method: "POST",
+ data: {
+ dataURL: dataURL,
+ filename: finalName,
+ ...dataParam,
+ },
+ success: function (response) {
+ if (response.success) {
+ // Mostra messaggio non-blocking
+ const successMsg = $(
+ '
Foto salvata con successo: ' +
+ response.file_path +
+ "
",
+ );
+ $("#partsModal .modal-body").prepend(successMsg);
+ setTimeout(function () {
+ successMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
- const dataURL = canvas.toDataURL("image/png");
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
- const iddatadb = $("#partsModal").data("iddatadb");
- const idquotations = $("#partsModal").data("idquotations");
- const id = iddatadb || idquotations;
- const endpoint = idquotations
- ? "save_annotated_photo_quotation.php"
- : "save_annotated_photo.php";
- const dataParam = idquotations
- ? { idquotations: id }
- : { iddatadb: id };
- const defaultName = `photo_${id}_${timestamp}.png`;
+ // AGGIORNA IL DROPDOWN E TORNA ALLA PRIMA FOTO
+ const photoSelector = $("#photoSelector");
+ if (photoSelector.length > 0) {
+ const newPhotoPath = response.file_path;
+ const newPhotoName = newPhotoPath
+ .split("/")
+ .pop();
+ const optionCount =
+ photoSelector.find("option").length;
- const newName = prompt(
- "Inserisci il nome del file (senza estensione):",
- defaultName.split(".png")[0],
- );
- if (newName) {
- const finalName = `${newName}_${timestamp}.png`;
- $.ajax({
- url: endpoint,
- method: "POST",
- data: { dataURL: dataURL, filename: finalName, ...dataParam },
- success: function (response) {
- if (response.success) {
- alert(
- `Foto salvata con successo: ${response.file_path}`,
+ // Aggiungi la nuova opzione al dropdown
+ const newOption = $("
")
+ .val(newPhotoPath)
+ .text(
+ `Photo ${optionCount + 1} - ${newPhotoName}`,
+ );
+ photoSelector.append(newOption);
+
+ // Seleziona la prima foto del dropdown
+ const firstPhotoPath = photoSelector
+ .find("option:first")
+ .val();
+ photoSelector.val(firstPhotoPath);
+
+ // Carica effettivamente la prima foto
+ loadSinglePhoto(firstPhotoPath);
+
+ console.log(
+ "Foto aggiunta al dropdown e ritornato alla prima:",
+ firstPhotoPath,
+ );
+ } else {
+ // Se non c'è dropdown, ricarica la foto corrente per vedere la versione pulita
+ const currentPhoto =
+ $("#samplePhoto").attr("src");
+ if (currentPhoto) {
+ loadSinglePhoto(currentPhoto);
+ }
+ }
+
+ // PULISCI LA FOTO ORIGINALE (come richiesto)
+ const currentPhoto = $("#samplePhoto").attr("src");
+
+ // Inizializza annotazioni vuote per la nuova foto salvata
+ const newPhoto = response.file_path;
+ photoAnnotations[newPhoto] = {
+ markers: [],
+ hasDescriptions: false,
+ descriptionPosition: { x: 10, y: 10 },
+ descriptionSize: {
+ width: photoData.displayWidth * 0.3,
+ height: photoData.displayHeight * 0.3,
+ },
+ };
+
+ // SVUOTA LE ANNOTAZIONI DELLA FOTO ORIGINALE
+ photoAnnotations[currentPhoto] = {
+ markers: [],
+ hasDescriptions: false,
+ descriptionPosition: { x: 10, y: 10 },
+ descriptionSize: {
+ width: photoData.displayWidth * 0.3,
+ height: photoData.displayHeight * 0.3,
+ },
+ };
+
+ // PULISCI IL CANVAS COMPLETAMENTE
+ clearCanvasMarkers(true); // Pulisce marker e descrizioni
+ clearUnsaved();
+ console.log("Foto salvata e originale pulita");
+ } else {
+ const errorMsg = $(
+ '
Errore: ' +
+ (response.message || "Errore sconosciuto") +
+ "
",
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ }
+ },
+ error: function (xhr, status, error) {
+ const errorMsg = $(
+ '
Errore nel salvataggio della foto: ' +
+ error +
+ " (" +
+ xhr.status +
+ ")
",
);
- $("#samplePhoto").attr("src", response.file_path);
- loadPhoto(iddatadb, idquotations);
- clearCanvasMarkers(false);
- clearUnsaved();
- } else {
- alert(`Errore: ${response.message}`);
- }
- },
- error: function (xhr, status, error) {
- alert(
- `Errore nel salvataggio della foto: ${error} (${xhr.status})`,
- );
- },
- });
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ },
+ });
+ } catch (e) {
+ console.error("Errore in canvas.toDataURL:", e);
+ const errorMsg = $(
+ '
Errore durante la creazione dell\'immagine finale.
',
+ );
+ $("#partsModal .modal-body").prepend(errorMsg);
+ setTimeout(function () {
+ errorMsg.fadeOut(500, function () {
+ $(this).remove();
+ });
+ }, 5000);
+ }
}
});
@@ -1058,7 +1590,7 @@ $(document).ready(function () {
function clearUnsaved() {
unsavedChanges = false;
- $("#savePhotoBtn").removeClass("unsaved").text("Salva Foto con Nome");
+ $("#savePhotoBtn").removeClass("unsaved").text("Salva Foto");
}
$(document).on("input change", "#partsTableBody input", markUnsaved);