���� ������������������������������������ /////////////// Tool Tip ////////////// $(document).ready(function(){ $('[data-toggle="tooltip"]').tooltip(); }); let currentRow; let undoStack = []; let hasUndone = false; let cropFrom = false; let initialCanvasState = null; let isSelectMode = false; let isShapeMode = true; // Default mode when modal opens let currentZoom = 1; const ZOOM_STEP = 0.1; const MAX_ZOOM = 3; const MIN_ZOOM = 0.5; function triggerFileInput(dropArea) { const fileInput = $(dropArea).siblings('input[type="file"]')[0]; fileInput.click(); } function addRow() { const row = `
Drag & Drop Image Here
Image Preview `; $('#imageTableBody').append(row); } function removeRow(button) { const rows = $('#imageTableBody tr'); if (rows.length > 1) { $(button).closest('tr').remove(); } else { toastr.warning("The last row cannot be removed!"); } } function moveRow(button, direction) { const row = $(button).closest('tr'); if (direction === 'up' && row.index() > 0) { row.insertBefore(row.prev()); } else if (direction === 'down') { row.insertAfter(row.next()); } } function clearImage(button) { const container = $(button).closest('td'); const fileInput = container.find('input[type="file"]'); const imgPreview = container.find('.image-preview'); const clearButton = container.find('.clear-image-btn'); const dragDropArea = container.find('.drag-drop-area'); // Clear the file input fileInput.val(''); // Hide the image preview and clear button imgPreview.attr('src', '#').hide(); clearButton.hide(); // Make sure drag-drop area is visible and not highlighted dragDropArea.removeClass('highlight').show(); // Show success message toastr.success('Image cleared successfully'); } function previewImage(event, input) { const container = $(input).closest('td'); const imgPreview = container.find('.image-preview'); const clearButton = container.find('.clear-image-btn'); const dragDropArea = container.find('.drag-drop-area'); if (input.files && input.files[0]) { const reader = new FileReader(); reader.onload = function (e) { imgPreview.attr('src', e.target.result).show(); clearButton.show(); dragDropArea.removeClass('highlight'); // Just remove highlight, don't hide } reader.readAsDataURL(input.files[0]); } } function highlightDropArea(dropArea) { $(dropArea).addClass('highlight'); } function removeHighlight(dropArea) { $(dropArea).removeClass('highlight'); } function saveImages() { $(document).find("button.btn-secondary").each(function(index){ if($(this).hasClass("currentActive")){ $(this).removeClass("currentActive"); } }); const saveButton = $('#saveImagesButton'); saveButton.prop('disabled', true); saveButton.text('Submitting...'); let formData = new FormData(); let pid = $('#pid').val(); // Create a minimal white background image as base64 const whiteImageBase64 = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII='; // Convert base64 to blob const byteString = atob(whiteImageBase64.split(',')[1]); const ab = new ArrayBuffer(byteString.length); const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } const defaultFile = new File([ab], 'White-Background-PNG.png', { type: 'image/png' }); $('#imageTableBody tr').each(function () { const fileInput = $(this).find('.image-file')[0].files[0]; const caption = $(this).find('input[type="text"]').val(); const imageSize = $(this).find('.image-size').val(); const pins = $(this).data('pins') || []; const id = $(this).data('image-id'); const imageOrder = $(this).index(); // Use fileInput if it exists, otherwise use defaultFile formData.append('images[]', fileInput || defaultFile); formData.append('captions[]', caption); formData.append('pins[]', JSON.stringify(pins)); formData.append('ids[]', id); formData.append('pid', pid); formData.append('imageOrder[]', imageOrder); formData.append('imageSizes[]', imageSize); }); $.ajax({ url: 'save_images.php', method: 'POST', data: formData, processData: false, contentType: false, success: function (response) { saveButton.text('Images Saved'); toastr.success('Images saved successfully! Please submit the form to view processed images.'); window.parent.postMessage({ action: 'submitForm' }, '*'); }, error: function (xhr, status, error) { saveButton.prop('disabled', false); saveButton.text('Save All Images'); toastr.error('Error saving images: ' + error); } }); // Reset states $(document).find("button.btn-secondary").each(function(index){ if($(this).hasClass("currentActive")){ $(this).removeClass("currentActive"); } }); isLineDrawing = false; isDrawing = false; cropFrom = false; disableAllDrawingModes(); canvas.isDrawingMode = false; } $(document).ready(function () { ////////////// If any button is active and any mode is enable then disable it ////////// $(document).find("button.btn-secondary").each(function(index){ // console.log("%cREADY ABC", "color:green;font-size:25px;"); // console.log("This is button Index "+index); if($(this).hasClass("currentActive")){ $(this).removeClass("currentActive"); } }); isLineDrawing = false; isDrawing = false; cropFrom = false; disableAllDrawingModes(); canvas.isDrawingMode = false; cropFrom = false; $(document).on('dragover', '.drag-drop-area', function (event) { event.preventDefault(); highlightDropArea(this); }); $(document).on('dragleave', '.drag-drop-area', function () { removeHighlight(this); }); $(document).on('drop', '.drag-drop-area', function (event) { handleDrop(event, this); }); $(document).on('paste', '.drag-drop-area', function (event) { handlePaste(event, this); }); }); function highlightDropArea(dropArea) { $(dropArea).addClass('highlight'); } function removeHighlight(dropArea) { $(dropArea).removeClass('highlight'); } function handleDrop(event, dropArea) { event.preventDefault(); removeHighlight(dropArea); const files = event.originalEvent.dataTransfer.files; if (files.length > 0) { const input = $(dropArea).siblings('input[type="file"]')[0]; const dataTransfer = new DataTransfer(); dataTransfer.items.add(files[0]); // Simulate file input input.files = dataTransfer.files; previewImage(event, input); } } function handlePaste(event, dropArea) { const items = event.originalEvent.clipboardData.items; for (let i = 0; i < items.length; i++) { const item = items[i]; if (item.type.startsWith('image/')) { event.preventDefault(); highlightDropArea(dropArea); // Highlight the area const file = item.getAsFile(); const input = $(dropArea).siblings('input[type="file"]')[0]; const dataTransfer = new DataTransfer(); dataTransfer.items.add(file); input.files = dataTransfer.files; // Simulate file input previewImage(event, input); } } } let canvas = new fabric.Canvas('canvas'); let fabricImage = null; let originalImageSrc = null; function editImage(icon) { ////////////// If any button is active and any mode is enable then disable it ////////// $(document).find("button.btn-secondary").each(function(index){ if($(this).hasClass("currentActive")){ $(this).removeClass("currentActive"); } }); isLineDrawing = false; isDrawing = false; cropFrom = false; disableAllDrawingModes(); canvas.isDrawingMode = false; cropFrom = false; currentRow = $(icon).closest('tr'); const imageSrc = currentRow.find('.image-preview').attr('src'); // Get the description cell and extract preferred size const descriptionCell = currentRow.find('td').filter(function() { return $(this).text().includes('Preferred Image Size:'); }); // Extract preferred size let preferredSize = ''; if (descriptionCell.length > 0) { const sizeMatch = descriptionCell.text().match(/Preferred Image Size: \((.*?)\)/); if (sizeMatch && sizeMatch[1]) { preferredSize = sizeMatch[1]; } } if (imageSrc) { originalImageSrc = imageSrc; $('#editImageModal').modal({ backdrop: 'static', keyboard: false }); // Update modal title and preferred size display if (preferredSize) { $('#preferredSizeDisplay').html(`
Preferred Size: ${preferredSize}`); } else { $('#preferredSizeDisplay').html(''); } loadFabricImage(imageSrc); } } function loadFabricImage(src) { fabric.Image.fromURL(src, function(img) { // Reset zoom level currentZoom = 1; const originalWidth = img.width; const originalHeight = img.height; const maxWidth = 1000; if (originalWidth > maxWidth) { const aspectRatio = originalHeight / originalWidth; canvas.setWidth(maxWidth); canvas.setHeight(maxWidth * aspectRatio); } else { canvas.setWidth(originalWidth); canvas.setHeight(originalHeight); } canvas.clear(); fabricImage = img; // Set enhanced quality properties img.set({ left: 0, top: 0, scaleX: canvas.width / originalWidth, scaleY: canvas.height / originalHeight, selectable: false, evented: false, objectCaching: false }); // Set canvas quality settings canvas.set({ imageSmoothingEnabled: true, imageSmoothingQuality: 'high' }); canvas.add(fabricImage); fabricImage.sendToBack(); canvas.renderAll(); // Force a high-quality render setTimeout(() => { canvas.requestRenderAll(); }, 100); initialCanvasState = { state: JSON.stringify(canvas.toJSON()), width: canvas.getWidth(), height: canvas.getHeight(), zoom: 1 }; // Update dimension display updateDimensionDisplay(); }, { crossOrigin: 'anonymous', quality: 1.0, enableRetinaScaling: true }); } function updateModalImage(event) { const input = event.target; if (input.files && input.files[0]) { const reader = new FileReader(); reader.onload = function(e) { loadFabricImage(e.target.result); }; reader.readAsDataURL(input.files[0]); } } let cropMode = false; // Tracks whether crop mode is active let cropRect; function cropImage() { disableShapeMode(); if (!cropMode) { // First click - initialize crop mode and create crop rectangle disableAllDrawingModes(); disableAllButtonsExcept('cropButton'); pushUndoState(); cropRect = new fabric.Rect({ left: 50, top: 50, width: 200, height: 200, fill: 'transparent', stroke: 'red', strokeWidth: 2, selectable: true, }); canvas.add(cropRect); canvas.setActiveObject(cropRect); canvas.renderAll(); cropMode = true; // Change the crop button text and add a cancel button $('#cropButton').html(' Apply Crop'); // Add cancel button if it doesn't exist if (!$('#cancelCropButton').length) { $('#cropButton').after(` `); } toastr.info('Select area to crop. Click Apply Crop when ready, or Cancel to abort.'); } else { // Second click - perform the crop if (fabricImage && cropRect) { const rect = cropRect.getBoundingRect(); // Store all objects except the background image and crop rectangle const objectsToKeep = canvas.getObjects().filter(obj => obj !== fabricImage && obj !== cropRect ); const cropX = (rect.left - fabricImage.left) / fabricImage.scaleX; const cropY = (rect.top - fabricImage.top) / fabricImage.scaleY; const cropWidth = rect.width / fabricImage.scaleX; const cropHeight = rect.height / fabricImage.scaleY; const croppedCanvas = document.createElement('canvas'); croppedCanvas.width = cropWidth; croppedCanvas.height = cropHeight; const ctx = croppedCanvas.getContext('2d'); ctx.drawImage( fabricImage.getElement(), cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight ); const croppedImageDataURL = croppedCanvas.toDataURL(); fabric.Image.fromURL(croppedImageDataURL, function (croppedImage) { canvas.clear(); let finalWidth = croppedImage.width; let finalHeight = croppedImage.height; const maxWidth = 1000; if (finalWidth > maxWidth) { const scaleFactor = maxWidth / finalWidth; finalWidth = maxWidth; finalHeight *= scaleFactor; } canvas.setWidth(finalWidth); canvas.setHeight(finalHeight); // Add the cropped background image croppedImage.set({ left: 0, top: 0, scaleX: canvas.width / croppedImage.width, scaleY: canvas.height / croppedImage.height, selectable: false, evented: false }); canvas.add(croppedImage); fabricImage = croppedImage; // Restore all other objects with adjusted positions objectsToKeep.forEach(obj => { // Calculate new positions relative to crop area const newLeft = ((obj.left - rect.left) / rect.width) * canvas.width; const newTop = ((obj.top - rect.top) / rect.height) * canvas.height; // Scale the object proportionally const scaleX = canvas.width / rect.width; const scaleY = canvas.height / rect.height; obj.set({ left: newLeft, top: newTop, scaleX: obj.scaleX * scaleX, scaleY: obj.scaleY * scaleY }); canvas.add(obj); }); canvas.renderAll(); pushUndoState(); // Update dimension display after cropping updateDimensionDisplay(); // After successful crop, restore the button $('#cropButton').html(' Crop'); $('#cancelCropButton').remove(); cropMode = false; cropRect = null; enableAllButtons(); toastr.success('Image cropped successfully'); }); } } } // Add new function to handle crop cancellation function cancelCrop() { if (cropRect) { canvas.remove(cropRect); canvas.renderAll(); } // Reset the crop button $('#cropButton').html(' Crop'); $('#cancelCropButton').remove(); cropMode = false; cropRect = null; enableAllButtons(); toastr.info('Crop operation cancelled'); } // Update the disableAllButtonsExcept function to handle the cancel button function disableAllButtonsExcept(buttonId) { document.querySelectorAll('.btn-secondary').forEach(button => { if (button.id !== buttonId && button.id !== 'cancelCropButton') { button.disabled = true; } }); document.getElementById(buttonId).disabled = false; } function enableAllButtons() { document.querySelectorAll('.btn-secondary').forEach(button => { button.disabled = false; }); } function flipImage() { disableAllDrawingModes() cropFrom = false; if (fabricImage) { // console.log("Before flip: ", fabricImage); // console.log("Before flip: ", fabricImage.flipX); pushUndoState(); fabricImage.flipX = !fabricImage.flipX; // console.log("After flip: ", fabricImage); // console.log("After flip: ", fabricImage.flipX); fabricImage.dirty = true; canvas.renderAll(); } cropFrom = true; } function rotateImage() { disableAllDrawingModes(); if (fabricImage) { pushUndoState(); fabricImage.rotate((fabricImage.angle + 90) % 360); canvas.renderAll(); } cropFrom = true; } function saveImageChanges() { // Reset zoom before saving if (currentZoom !== 1) { currentZoom = 1; applyZoom(); } cropFrom = false; // Store all objects including pins const objects = canvas.getObjects(); const pins = objects.filter(obj => obj.type === 'pin').map(pin => ({ left: (pin.left / canvas.width) * 100, top: (pin.top / canvas.height) * 100 })); const dataURL = canvas.toDataURL(); currentRow.find('.image-preview').attr('src', dataURL); const fileInput = currentRow.find('input[type="file"]')[0]; const blob = dataURLtoBlob(dataURL); const newFile = new File([blob], 'edited-image.png', { type: 'image/png' }); const dataTransfer = new DataTransfer(); dataTransfer.items.add(newFile); fileInput.files = dataTransfer.files; // Save pins data to the row currentRow.data('pins', pins); $('#editImageModal').modal('hide'); toastr.success('Image and pins saved successfully'); } // Helper function to convert base64 to Blob function dataURLtoBlob(dataURL) { const byteString = atob(dataURL.split(',')[1]); const mimeString = dataURL.split(',')[0].split(':')[1].split(';')[0]; const ab = new ArrayBuffer(byteString.length); const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } return new Blob([ab], { type: mimeString }); } // Add this after the canvas initialization let selectedColor = '#000000'; // Default color // Update the color selection handler $(document).ready(function() { // Add click handler for color boxes $('.color-box').click(function() { // Remove selected class from all boxes $('.color-box').removeClass('selected'); // Add selected class to clicked box $(this).addClass('selected'); // Update the selected color selectedColor = $(this).data('color'); // Update active object if exists if (canvas.getActiveObject()) { const activeObject = canvas.getActiveObject(); if (activeObject.type === 'text') { activeObject.set('fill', selectedColor); } else { activeObject.set('stroke', selectedColor); } canvas.renderAll(); } // Update free drawing brush if active if (canvas.isDrawingMode) { canvas.freeDrawingBrush.color = selectedColor; } }); }); // Update the toggleFreeDrawing function function toggleFreeDrawing() { disableAllDrawingModes(); canvas.isDrawingMode = !canvas.isDrawingMode; if (canvas.isDrawingMode) { $('#freeDrawButton').addClass('currentActive'); canvas.freeDrawingBrush.color = selectedColor; canvas.freeDrawingBrush.width = 5; // Make existing paths selectable canvas.getObjects('path').forEach(path => { path.set({ selectable: true, evented: true, hasControls: true, hasBorders: true }); }); toastr.success('Free Drawing Mode Enabled'); } else { $('#freeDrawButton').removeClass('currentActive'); toastr.success('Free Drawing Mode Disabled'); } canvas.renderAll(); } // Update addRectangle function function addRectangle() { disableAllDrawingModes(); isShapeMode = true; // Enable shape mode $('#rectangleButton').addClass('currentActive'); const canvasCenter = { x: canvas.width / 2, y: canvas.height / 2 }; const offsetX = Math.floor(Math.random() * 60) - 30; const offsetY = Math.floor(Math.random() * 60) - 30; const rect = new fabric.Rect({ left: canvasCenter.x - 40 + offsetX, top: canvasCenter.y - 30 + offsetY, fill: 'transparent', width: 80, height: 60, stroke: selectedColor, strokeWidth: 2, selectable: true, // Always selectable when added evented: true, hasControls: true, hasBorders: true }); canvas.add(rect); canvas.setActiveObject(rect); canvas.renderAll(); } // Update addCircle function function addCircle() { disableAllDrawingModes(); isShapeMode = true; // Enable shape mode $('#circleButton').addClass('currentActive'); const canvasCenter = { x: canvas.width / 2, y: canvas.height / 2 }; const offsetX = Math.floor(Math.random() * 60) - 30; const offsetY = Math.floor(Math.random() * 60) - 30; const circle = new fabric.Circle({ left: canvasCenter.x - 30 + offsetX, top: canvasCenter.y - 30 + offsetY, radius: 30, fill: 'transparent', stroke: selectedColor, strokeWidth: 2, selectable: true, // Always selectable when added evented: true, hasControls: true, hasBorders: true }); canvas.add(circle); canvas.setActiveObject(circle); canvas.renderAll(); } // Update addText function function addText() { disableAllDrawingModes(); $('#textButton').addClass('currentActive'); const text = prompt('Enter the text you want to add:'); if (text) { const offsetX = Math.floor(Math.random() * 60) - 30; const offsetY = Math.floor(Math.random() * 60) - 30; const textObj = new fabric.Text(text, { left: canvas.width / 2 + offsetX, top: canvas.height / 2 + offsetY, fill: selectedColor, fontSize: 24, fontFamily: 'Arial', originX: 'center', originY: 'center', selectable: true, evented: true, hasControls: true, hasBorders: true }); canvas.add(textObj); canvas.setActiveObject(textObj); canvas.renderAll(); } } let isLineDrawing = false; let isDrawing = false; let line = null; function toggleLineDrawing() { disableAllDrawingModes(); isLineDrawing = !isLineDrawing; if (isLineDrawing) { $('#lineButton').addClass('currentActive'); canvas.defaultCursor = 'crosshair'; canvas.selection = false; // Disable canvas selection when in line drawing mode // Add drawing event listeners canvas.on('mouse:down', startDrawingLine); canvas.on('mouse:move', continueDrawingLine); canvas.on('mouse:up', finishDrawingLine); // Add object:moving event listener to disable line drawing mode canvas.on('object:moving', function() { if (isLineDrawing) { disableLineDrawing(); $('#lineButton').removeClass('currentActive'); canvas.selection = true; toastr.info('Line Drawing Mode disabled due to object movement'); } }); toastr.success('Line Drawing Mode Enabled - Click and drag to draw lines. Click the Line button again to exit.'); } else { disableLineDrawing(); canvas.selection = true; // Re-enable canvas selection when exiting line drawing mode toastr.success('Line Drawing Mode Disabled'); } canvas.renderAll(); } function startDrawingLine(o) { if (!isLineDrawing || isDrawing) return; isDrawing = true; const pointer = canvas.getPointer(o.e); line = new fabric.Line([pointer.x, pointer.y, pointer.x, pointer.y], { strokeWidth: 2, stroke: selectedColor, selectable: false, evented: false, hasControls: false, hasBorders: false, hoverCursor: 'pointer', perPixelTargetFind: false, strokeLineCap: 'round', strokeLineJoin: 'round', targetFindTolerance: 10, padding: 5 }); canvas.add(line); canvas.selection = false; } function continueDrawingLine(o) { if (!isDrawing || !line) return; const pointer = canvas.getPointer(o.e); line.set({ x2: pointer.x, y2: pointer.y }); canvas.renderAll(); } function finishDrawingLine() { if (!isDrawing || !line) return; isDrawing = false; line.set({ selectable: true, evented: true, hasControls: true, hasBorders: true, perPixelTargetFind: false, targetFindTolerance: 10, padding: 5, hoverCursor: 'pointer' }); // Add hover effect line.on('mouseover', function() { this.set({ strokeWidth: 4 }); canvas.renderAll(); }); line.on('mouseout', function() { this.set({ strokeWidth: 2 }); canvas.renderAll(); }); canvas.selection = true; line.setCoords(); canvas.discardActiveObject(); canvas.renderAll(); pushUndoState(); line = null; } function disableLineDrawing() { $('#lineButton').removeClass('currentActive'); // Remove drawing event listeners canvas.off('mouse:down', startDrawingLine); canvas.off('mouse:move', continueDrawingLine); canvas.off('mouse:up', finishDrawingLine); canvas.off('object:moving'); // Remove the moving event listener // Ensure all objects remain selectable canvas.getObjects().forEach(obj => { if (obj !== fabricImage) { obj.set({ selectable: true, evented: true, hasControls: true, hasBorders: true }); } }); canvas.defaultCursor = 'default'; isLineDrawing = false; isDrawing = false; line = null; } // Update disableAllDrawingModes to keep objects selectable function disableAllDrawingModes() { canvas.isDrawingMode = false; if (isLineDrawing) { disableLineDrawing(); } isLineDrawing = false; isDrawing = false; cropFrom = false; // Remove active class from all buttons $('.btn-secondary').removeClass('currentActive'); // Keep all objects selectable canvas.getObjects().forEach(obj => { if (obj !== fabricImage) { obj.set({ selectable: true, evented: true, hasControls: true, hasBorders: true }); } }); canvas.selection = true; canvas.defaultCursor = 'default'; canvas.renderAll(); } // Add this to handle switching between tools $('.btn-secondary').click(function() { if (this.id !== 'lineButton') { isLineDrawing = false; canvas.off('mouse:down', startDrawingLine); canvas.off('mouse:move', continueDrawingLine); canvas.off('mouse:up', finishDrawingLine); } }); let pinsData = []; function addPin() { disableAllDrawingModes(); $('#pinButton').addClass('currentActive'); // Remove URL prompt and directly add the pin fabric.Image.fromURL('location-pin.png', function(pinIcon) { pinIcon.scale(0.06); const offsetX = Math.floor(Math.random() * 60) - 30; const offsetY = Math.floor(Math.random() * 60) - 30; pinIcon.set({ left: canvas.width / 2 + offsetX, top: canvas.height / 2 + offsetY, originX: 'center', originY: 'bottom', selectable: true, evented: true, hasControls: true, hasBorders: true, type: 'pin' // Add type identifier }); canvas.add(pinIcon); canvas.setActiveObject(pinIcon); canvas.renderAll(); pushUndoState(); // Add to undo stack }); } function disableAllDrawingModes() { canvas.isDrawingMode = false; isLineDrawing = false; isDrawing = false; cropFrom = false; // Remove active class from all buttons $('.btn-secondary').removeClass('currentActive'); // Make all objects (except background image) selectable canvas.getObjects().forEach(obj => { if (obj !== fabricImage) { obj.set({ selectable: true, evented: true, hasControls: true, hasBorders: true }); } }); canvas.selection = true; canvas.defaultCursor = 'default'; canvas.renderAll(); } let isDropAreaSelected = false; // Track the selection state $(document).on('click', '.select-area-btn', function() { var currentRow = $(this).closest('tr'); var dropArea = currentRow.find('.drag-drop-area')[0]; var instructions = currentRow.find('.paste-instructions'); highlightDropArea(dropArea); instructions.show(); $(document).off('paste'); $(document).on('paste', function(event) { handlePaste(event, dropArea); }); }); function pushUndoState() { const currentState = JSON.stringify(canvas.toJSON()); if (undoStack.length > 0) { const lastState = undoStack[undoStack.length - 1]; if (lastState.state === currentState) { return; } } if (hasUndone) { undoStack = undoStack.slice(0, undoStack.length); hasUndone = false; } undoStack.push({ state: currentState, width: canvas.getWidth(), height: canvas.getHeight(), }); console.log('Undo state saved:', undoStack); } function undo() { disableAllDrawingModes(); if (undoStack.length > 0) { const lastState = undoStack.pop(); // Remove and retrieve the last state const previousState = undoStack[undoStack.length - 1]; // Peek the next state if (previousState) { canvas.clear(); canvas.setWidth(previousState.width); canvas.setHeight(previousState.height); canvas.loadFromJSON(previousState.state, () => { canvas.getObjects().forEach(obj => { obj.set({ selectable: false, // Ensure objects are not selectable evented: false, // Disable event handlers }); // Set fabricImage if the object is an image if (obj.type === 'image') { fabricImage = obj; } }); canvas.renderAll(); toastr.success('Undo successful.'); }); } else if (initialCanvasState) { // Restore the initial canvas state canvas.clear(); canvas.setWidth(initialCanvasState.width); canvas.setHeight(initialCanvasState.height); canvas.loadFromJSON(initialCanvasState.state, () => { canvas.getObjects().forEach(obj => { obj.set({ selectable: false, evented: false, }); if (obj.type === 'image') { fabricImage = obj; } }); canvas.renderAll(); toastr.warning('No actions to undo.'); }); } else { // If no initial state, just clear the canvas canvas.clear(); toastr.warning('No actions to undo.'); } } else { if (initialCanvasState) { // Restore to initial state if undo stack is empty canvas.clear(); canvas.setWidth(initialCanvasState.width); canvas.setHeight(initialCanvasState.height); canvas.loadFromJSON(initialCanvasState.state, () => { canvas.getObjects().forEach(obj => { obj.set({ selectable: false, evented: false, }); if (obj.type === 'image') { fabricImage = obj; } }); canvas.renderAll(); toastr.warning('No actions to undo.'); }); } else { toastr.warning('No actions to undo.'); } } } $(document).ready(function () { $(document).on('click', 'button.close', function() { $(document).find("button.btn-secondary").each(function(index){ // console.log("%cREADY ABC", "color:green;font-size:25px;"); // console.log("This is button Index "+index); if($(this).hasClass("currentActive")){ $(this).removeClass("currentActive"); } }); isLineDrawing = false; isDrawing = false; cropFrom = false; disableAllDrawingModes(); canvas.isDrawingMode = false; cropFrom = false; }); /////// Remove All Active Class On Load ///// $(document).on('click', 'button.btn-secondary', function() { // console.log("%cREADY XYZ", "color:yellow;font-size:25px;"); // console.log(cropFrom); if(!cropFrom){ $(document).find("button.btn-secondary").each(function(index){ // console.log("%cREADY ABC", "color:green;font-size:25px;"); // console.log("This is button Index "+index); if($(this).hasClass("currentActive")){ $(this).removeClass("currentActive"); } }); $(this).addClass("currentActive"); } else{ // console.log("%cREADY BBBB", "color:blue;font-size:25px;"); // console.log(cropFrom); $(document).find("button.btn-secondary").each(function(index){ // console.log("%cREADY ABC", "color:green;font-size:25px;"); // console.log("This is button Index "+index); if($(this).hasClass("currentActive")){ $(this).removeClass("currentActive"); } }); cropFrom = false; } }); }); // Add this function to handle selection mode function toggleSelectMode() { disableAllDrawingModes(); isSelectMode = true; // Enable selection mode canvas.selection = true; canvas.discardActiveObject(); // Clear any active selection // Make all objects except background image selectable canvas.getObjects().forEach(obj => { if (obj !== fabricImage) { obj.set({ selectable: true, evented: true, hasControls: true, hasBorders: true }); } }); // Enable canvas interactivity canvas.interactive = true; canvas.defaultCursor = 'pointer'; $('#selectButton').addClass('currentActive'); $('#deselectButton').removeClass('currentActive'); toastr.success('Select Mode Enabled - Click on shapes to select them'); canvas.renderAll(); } function deselectAll() { disableAllDrawingModes(); isSelectMode = false; // Disable selection mode canvas.selection = false; canvas.discardActiveObject(); // Make all objects non-selectable canvas.getObjects().forEach(obj => { if (obj !== fabricImage) { obj.set({ selectable: false, evented: false, hasControls: false, hasBorders: false }); } }); canvas.defaultCursor = 'default'; $('#selectButton').removeClass('currentActive'); $('#deselectButton').addClass('currentActive'); toastr.info('All objects deselected'); canvas.renderAll(); } function deleteSelected() { const activeObject = canvas.getActiveObject(); if (activeObject) { if (activeObject !== fabricImage) { // Disable all drawing modes first canvas.isDrawingMode = false; isLineDrawing = false; isDrawing = false; // Remove all active classes from buttons $('.btn-secondary').removeClass('currentActive'); // Remove drawing event listeners canvas.off('mouse:down', startDrawingLine); canvas.off('mouse:move', continueDrawingLine); canvas.off('mouse:up', finishDrawingLine); // Delete the object canvas.remove(activeObject); canvas.discardActiveObject(); // Keep all remaining objects selectable canvas.getObjects().forEach(obj => { if (obj !== fabricImage) { obj.set({ selectable: true, evented: true, hasControls: true, hasBorders: true }); } }); canvas.renderAll(); pushUndoState(); // Save state for undo toastr.success('Object deleted successfully'); } else { toastr.warning('Cannot delete the background image'); } } else { toastr.warning('Please select an object to delete'); } } // Add this to handle object selection events canvas.on('selection:created', function(options) { if (isSelectMode && options.target) { options.target.set({ hasControls: true, hasBorders: true }); } }); canvas.off('selection:cleared'); // Add this function to disable shape mode when switching to other tools function disableShapeMode() { isShapeMode = false; canvas.getObjects().forEach(obj => { if (obj !== fabricImage) { obj.set({ selectable: false, evented: false, hasControls: false, hasBorders: false }); } }); canvas.renderAll(); } // When the modal opens, set shape mode as default $('#editImageModal').on('shown.bs.modal', function () { isShapeMode = true; disableAllDrawingModes(); }); // Add canvas event listener for newly added free drawing paths canvas.on('path:created', function(e) { const path = e.path; path.set({ selectable: true, evented: true, hasControls: true, hasBorders: true }); }); function zoomIn() { disableAllDrawingModes(); if (currentZoom < MAX_ZOOM) { currentZoom += ZOOM_STEP; applyZoom(); // Disable resize button and update its tooltip const resizeBtn = $('.dimension-display .btn'); resizeBtn.prop('disabled', true); resizeBtn.attr('data-original-title', 'Resize is disabled while zooming. Reset zoom to enable resize.'); $('[data-toggle="tooltip"]').tooltip('dispose').tooltip(); } } function zoomOut() { disableAllDrawingModes(); if (currentZoom > MIN_ZOOM) { currentZoom -= ZOOM_STEP; applyZoom(); // Disable resize button and update its tooltip const resizeBtn = $('.dimension-display .btn'); resizeBtn.prop('disabled', true); resizeBtn.attr('data-original-title', 'Resize is disabled while zooming. Reset zoom to enable resize.'); $('[data-toggle="tooltip"]').tooltip('dispose').tooltip(); } } function resetZoom() { console.log("Reset Zoom"); disableAllDrawingModes(); if (currentZoom !== 1) { currentZoom = 1; // Set canvas dimensions back to the current initial state canvas.setWidth(initialCanvasState.width); canvas.setHeight(initialCanvasState.height); // Load the current state canvas.loadFromJSON(initialCanvasState.state, () => { canvas.getObjects().forEach(obj => { if (obj.type === 'image') { fabricImage = obj; obj.set({ selectable: false, evented: false }); } else { obj.set({ selectable: true, evented: true, hasControls: true, hasBorders: true }); } }); // Enable resize button and update its tooltip const resizeBtn = $('.dimension-display .btn'); resizeBtn.prop('disabled', false); resizeBtn.attr('data-original-title', 'Image resize cannot be undone'); $('[data-toggle="tooltip"]').tooltip('dispose').tooltip(); }); } else { // Even if zoom is already at 1, ensure resize button is enabled const resizeBtn = $('.dimension-display .btn'); resizeBtn.prop('disabled', false); resizeBtn.attr('data-original-title', 'Image resize cannot be undone'); $('[data-toggle="tooltip"]').tooltip('dispose').tooltip(); } } function applyZoom() { // Store current dimensions const currentWidth = canvas.getWidth(); const currentHeight = canvas.getHeight(); // Calculate new dimensions const scaledWidth = initialCanvasState.width * currentZoom; const scaledHeight = initialCanvasState.height * currentZoom; // Set new dimensions canvas.setWidth(scaledWidth); canvas.setHeight(scaledHeight); // Scale all objects canvas.getObjects().forEach(obj => { const originalScaleX = obj.scaleX; const originalScaleY = obj.scaleY; const originalLeft = obj.left; const originalTop = obj.top; obj.set({ scaleX: originalScaleX * (scaledWidth / currentWidth), scaleY: originalScaleY * (scaledHeight / currentHeight), left: originalLeft * (scaledWidth / currentWidth), top: originalTop * (scaledHeight / currentHeight) }); }); canvas.renderAll(); updateDimensionDisplay(); } // Add this function to reset the image to original state function resetImage() { disableAllDrawingModes(); if (initialCanvasState) { // Reset zoom level currentZoom = 1; // Reset canvas dimensions to initial state canvas.setWidth(initialCanvasState.width); canvas.setHeight(initialCanvasState.height); // Load the initial state canvas.loadFromJSON(initialCanvasState.state, () => { // Ensure the background image is not selectable canvas.getObjects().forEach(obj => { if (obj.type === 'image') { fabricImage = obj; obj.set({ selectable: false, evented: false }); } else { // Keep other objects selectable obj.set({ selectable: true, evented: true, hasControls: true, hasBorders: true }); } }); canvas.renderAll(); // toastr.success('Image reset to original position'); }); } else { // toastr.warning('No initial state available to reset to'); } } // Update dimension display function updateDimensionDisplay() { const dimensionHtml = `
px
×
px
`; // Replace the existing dimension display with the new one $('.dimension-display').replaceWith(dimensionHtml); // Initialize tooltips $('[data-toggle="tooltip"]').tooltip(); // Update the values $('#imageWidth').val(Math.round(canvas.width)); $('#imageHeight').val(Math.round(canvas.height)); } // Function to resize image function resizeImage() { const newWidth = parseInt($('#imageWidth').val()); const newHeight = parseInt($('#imageHeight').val()); if (!newWidth || !newHeight || newWidth <= 0 || newHeight <= 0) { toastr.error('Please enter valid dimensions'); return; } if (newWidth > 3000 || newHeight > 3000) { toastr.error('Maximum dimensions are 3000x3000 pixels'); return; } pushUndoState(); // Store all objects except the background image const objects = canvas.getObjects(); const annotations = objects.filter(obj => obj !== fabricImage).map(obj => { return { object: obj, originalLeft: obj.left / canvas.width, originalTop: obj.top / canvas.height, originalScaleX: obj.scaleX, originalScaleY: obj.scaleY }; }); const originalImgSrc = fabricImage.getSrc(); const img = new Image(); img.src = originalImgSrc; img.onload = function() { const scale = Math.min(newWidth / img.width, newHeight / img.height); const oldCanvasWidth = canvas.width; const oldCanvasHeight = canvas.height; canvas.setWidth(img.width * scale); canvas.setHeight(img.height * scale); canvas.clear(); // Add the resized background image fabric.Image.fromURL(originalImgSrc, function(img) { img.set({ originX: 'center', originY: 'center', left: canvas.width / 2, top: canvas.height / 2, scaleX: scale, scaleY: scale, selectable: false, evented: false, objectCaching: false }); canvas.set({ imageSmoothingEnabled: true, imageSmoothingQuality: 'high' }); canvas.add(img); fabricImage = img; // Restore all annotations with adjusted positions and scales annotations.forEach(annotation => { const obj = annotation.object; const widthRatio = canvas.width / oldCanvasWidth; const heightRatio = canvas.height / oldCanvasHeight; obj.set({ left: annotation.originalLeft * canvas.width, top: annotation.originalTop * canvas.height, scaleX: annotation.originalScaleX * widthRatio, scaleY: annotation.originalScaleY * heightRatio }); if (obj.type === 'text') { const avgRatio = (widthRatio + heightRatio) / 2; obj.set({ fontSize: obj.fontSize * avgRatio }); } canvas.add(obj); }); // Update initialCanvasState with the new resized state initialCanvasState = { state: JSON.stringify(canvas.toJSON()), width: canvas.width, height: canvas.height, zoom: 1 }; setTimeout(() => { canvas.renderAll(); }, 50); toastr.success('Image resized successfully'); }, { crossOrigin: 'anonymous', quality: 1.0, enableRetinaScaling: false }); }; } // Add event listeners for dimension inputs $(document).ready(function() { // ... existing ready code ... // Add input validation for dimensions $('#imageWidth, #imageHeight').on('input', function() { this.value = this.value.replace(/[^0-9]/g, ''); }); }); // function resetZoom() { // disableAllDrawingModes(); // if (currentZoom !== 1) { // currentZoom = 1; // applyZoom(); // // toastr.info('Zoom reset to 100%'); // } // } // Add CSS for the input groups const styles = ` .input-group-text { background-color: #e9ecef; border: 1px solid #ced4da; padding: 0.375rem 0.75rem; font-size: 0.9rem; color: #495057; } .input-group input { border-right: none; } .input-group-append { margin-left: -1px; } .dimension-display .input-group { display: inline-flex; vertical-align: middle; } `; // Add the styles to the document $('