����
������������������������������������
/////////////// 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
Press Ctrl + V to paste images into the highlighted area.
|
|
`;
$('#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 = `
`;
// 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
$('