UNPKG

aico-image-editor

Version:

Combine multiple image into and create single combined image

2,369 lines 94.1 kB
// import Alpine from 'alpinejs'
//import persist from '@alpinejs/persist';

let persist;
if (typeof window.Alpine.$persist === 'undefined') {
    persist = require('@alpinejs/persist')
    Alpine.plugin(persist.default)
}

// note- as fabric is now in externals, 
//externals script directly gives fabric and not as {fabric} under object
// which modular system does so it will be like this
// and not like this - import {fabric} from 'fabric';
import fabric from 'fabric';
import canvasSize from 'canvas-size';



require('./roundedBackgroundTextbox');


import chroma from 'chroma-js';
//history
import {canvasHistory} from './history/history';
import { AddCommand } from './history/addCommand';
import { RemoveCommand } from './history/removeCommand';
import { TransformCommand } from './history/transformCommand';


window.canvasHistory = canvasHistory;

// Alpine.plugin(persist)

//

import {getObjectControlImages, getProductImages} from './getImages';
import { addCropRectangleControls } from './utilities/cropControls';


import { productPicturesSubTab } from './components/pictureTab/pictureTab';

let webglBackend, canvas2dBackend;


Alpine.store('canvas', {
    products: [
        {    
            id: 2,
            name: 'gr-5', 
            productImageUrl: 'images/gr-5/product-gr5.svg', 
            overlapImageUrl: 'images/gr-5/product-gr5-overlap.svg',
            innerImageUrl: 'images/gr-5/product-gr5-inner.svg',
            innerImageStrokeOnlyUrl: 'images/gr-5/product-gr5-inner-strokeonly.svg', 
            tagImageUrl: 'images/gr-5/gr5-tag.svg',
            tagCloneImageUrl: 'images/gr-5/gr5-tag-cloned.svg',
            tagImageLeft: 4.12,
            tagImageTop: 37.25,
            tagImageWidth: 32.06,
            tagImageHeight: 45.50
       },
        {    id: 0,
             name: 'gr-6', 
             productImageUrl: 'images/gr-6/product-gr6.svg', 
             overlapImageUrl: 'images/gr-6/product-gr6-overlap.svg',
             overlapStickerImageUrl: 'images/gr-6/product-gr6-overlap-sticker.svg',
             innerImageUrl: getProductImages().gr6InnerImage,
             innerImageStrokeOnlyUrl: 'images/gr-6/product-gr6-inner-strokeonly.svg', 
             tagImageUrl: 'images/gr-6/gr6-tag.svg',
             tagCloneImageUrl: 'images/gr-6/gr6-tag-cloned.svg',
             leftUpperTextImage: 'images/gr-6/pdf-left-text.png',
             rightUpperTextImage: 'images/gr-6/pdf-right-text.png',
             bottomTextImageUrl: 'images/gr-6/pdf-bottom-text.svg',
             bottomTextImage: 'images/gr-6/pdf-bottom-text.png',
             plusicon: 'images/gr-6/pluspdf.png',
             tagImageLeft: 6.20,
             tagImageTop: 38.96,
             tagImageWidth: 24.48,
             tagImageHeight: 50.37
             
        },
        {   id: 1, 
            name: 'gr-7', 
            productImageUrl: 'images/gr-7/product-gr7.svg', 
            overlapImageUrl: 'images/gr-7/product-gr7-overlap.svg',
            overlapStickerImageUrl: 'images/gr-7/product-gr7-overlap-sticker.svg', 
            innerImageUrl: 'images/gr-7/product-gr7-inner.svg',
            innerImageStrokeOnlyUrl: 'images/gr-7/product-gr7-inner-strokeonly.svg', 
            tagImageUrl: 'images/gr-7/gr7-tag.svg',
            tagCloneImageUrl: 'images/gr-7/gr7-tag-cloned.svg',
            leftUpperTextImage:'images/gr-7/pdf-left-text.png',
            rightUpperTextImage: 'images/gr-7/pdf-right-text.png',
            bottomTextImageUrl: 'images/gr-7/pdf-bottom-text.svg',
            bottomTextImage: 'images/gr-7/pdf-bottom-text.png',
            plusicon: 'images/gr-7/pluspdf.png',
            tagImageLeft: 5.24,
            tagImageTop: 50.32,
            tagImageWidth: 24.27,
            tagImageHeight: 39.61
        }
    ],
    apiConfig: {
        apiUrl: '',
        apiToken: '',
    },
    currentLocale: 'de',
    canvasData: Alpine.$persist(''),
    isConfiguratorIdChanged: false,
    productId: Alpine.$persist(null),
    configuratorId: Alpine.$persist(null),
    selectedProduct: (function() {
        const urlParams = new URLSearchParams(window.location.search);
        const myProduct = urlParams.get('productKey');
        return myProduct || 'gr-6';
    })(),
    init() {
        fabric.perfLimitSizeTotal = 225000000;
        
    },
    isServerLoaderCanvasVisible: false,
    isServerLoaderTabVisible: false,
    isColorBlockLoaderVisible: false,
    isColorPickerBlockDisabled: true,
    isObjectFilterControlsDisabled: true,
    isCurveBlockDisabled: true,
    isObjectDeselected: true,
    isCanvasResetBtnDisabled:true,
    isFontStyleSaveBtnDisabled: true,
    isTextObjectSelected: false,
    isTextBackgroundButtonDisabled: true,
    isInside3dView: false,
    textErrorMessage:false,
    fontSuccessMessage:false,
    isSaveButtonDisabled: false,
    enable3d(formData) {
        const self = this, canvas =  window.__canvas;
        const product = this.getSelectedProductObj();
        this.isInside3dView = true;

        const clippedPathImageHeight = canvas.clipPath.getScaledHeight();
        const differenceInHeight = (canvas.height - clippedPathImageHeight)/2;

        

        window.__canvas.clone(function(clonedCanvas) {
            clonedCanvas.clipPath = null;

            window.clonedCanvas = clonedCanvas;
            const clonedCanvasObjects = clonedCanvas.getObjects();

            clonedCanvasObjects.forEach(function(clonedCanvasObject,i) {
                
                clonedCanvasObject.set({
                    clipPath: null,
                    
                })
                if(clonedCanvasObject.excludeFromExportIn3D) {
                    clonedCanvas.remove(clonedCanvasObject)
                }
            })
            

            // clonedCanvasObjects.forEach(function(clonedCanvasObject,i) {

            //     console.log(clonedCanvasObject.top);

            //     const percentageFromClippedPath =  (clonedCanvasObject.top/clippedPathImageHeight)*100,
            //         topFromInnerShape = clonedCanvasObject.top - differenceInHeight,
            //         percentageFromInnerShape = (topFromInnerShape/clippedPathImageHeight)*100;
            //         console.log(percentageFromInnerShape);

            //     newObjTop = (percentageFromInnerShape*canvas.height)/100;

            //     console.log(newObjTop)

            //     clonedCanvasObject.set({
            //         clipPath: null,
            //         top: newObjTop,
            //     })
            //     clonedCanvas.renderAll()
                
            // })

            fabric.Image.fromURL(product.tagCloneImageUrl,function(img) {
                
                //img.scaleToWidth(clonedCanvas.width * (product.tagImageWidth/100));
                img.scaleToHeight(clonedCanvas.height/clonedCanvas.viewportTransform[0] * (product.tagImageHeight/100));
                img.set({
                    left: clonedCanvas.width * product.tagImageLeft/100,
                    top: clonedCanvas.height/clonedCanvas.viewportTransform[0] - img.getScaledHeight() - 20
                })
                img.set({zIndex: 1000})
                clonedCanvas.add(img)

                const url = clonedCanvas.toDataURL({
                    format: 'jpeg',
                    enableRetinaScaling: true,
                    multiplier: 3
                });
                

                if(self.isInside3dView) {
                    window.dispatchEvent(new CustomEvent('toggle-3d', { detail: {
                        url: url,
                        topBottomTextureUrl: clonedCanvas.backgroundImage?.src,
                        formData: formData
                    }, bubbles: true }));
                }
            }, {crossOrigin: 'anonymous'})

        },['excludeFromExportIn3D','name', 'filters', 'viewportTransform'])

    },
    toPDF(formData) {
        const self = this, canvas =  window.__canvas;
        const product = this.getSelectedProductObj();
        window.__canvas.clone(function(clonedPDFCanvas) {
            fabric.Image.fromURL(product.overlapStickerImageUrl,function(overlapStickerImg) {
                self.fitImageInCanvas(overlapStickerImg, clonedPDFCanvas);
                self.setCenter(overlapStickerImg);
                self.preventInteraction(overlapStickerImg)
                overlapStickerImg.set({zIndex: 1002})
                clonedPDFCanvas.add(overlapStickerImg)
                    
                clonedPDFCanvas.renderAll()
                
                const url = clonedPDFCanvas.toDataURL({
                    format: 'png',
                    enableRetinaScaling: true,
                    multiplier: 3
                });

                const { jsPDF } = window.jspdf;
                var pdf = new jsPDF({
                    orientation: 'landscape',
                    unit: 'px',
                    format: [935, 875],
                    compress: true,
                });

                pdf.setFillColor(241,241,242);
                pdf.rect(0, 0, 935, 935, 'F');
            
                pdf.addImage(product.plusicon, 'PNG', 15, 15, 30, 30,'','FAST');
                pdf.addImage(product.plusicon, 'PNG', 890, 15, 30, 30,'','FAST');
                pdf.addImage(product.plusicon, 'PNG', 15, 830, 30, 30,'','FAST');
                pdf.addImage(product.plusicon, 'PNG', 890, 830, 30, 30,'','FAST');
                
                pdf.addImage(product.rightUpperTextImage, 'PNG', 740, 105, 124, 67,'','FAST');
                pdf.addImage(product.leftUpperTextImage, 'PNG', 50, 80, 138, 105,'','FAST');
                
                //draw black lines
                pdf.setLineWidth(0.1);
                pdf.setDrawColor(0,0,0); // draw black lines
                
                pdf.line(0, 437.5, 29, 437.5); // horizontal line
                pdf.line(906, 437.5, 935, 437.5);

                pdf.line(467, 0, 467, 29); // vertical line
                pdf.line(467, 846, 467, 877);


                //console.log(pdf.internal.pageSize.getWidth());

                pdf.addImage(url, 'JPEG', 23, 0, 888, 888,'','FAST');
                pdf.addImage(product.bottomTextImage, 'PNG', 0, 0, 895, 895,'','FAST');
                //window.open(pdf.output('bloburl'), '_blank');
                
                
                const blob = pdf.output('blob');
                const fileName = 'productPDF-' + self.configuratorId; // Specify your file name here
                const file = new File([blob], fileName, {type: 'application/pdf'});
                formData.append('pdf', file);
                self.enable3d(formData);
                //pdf.save("download.pdf");
                
                //document.getElementById('canvasConvertedPDFImage').src= url
            }, {crossOrigin: 'anonymous'})
            
        }, ['excludeFromExportIn3D','name', 'filters', 'viewportTransform'])
    },  
    disable3d() {
        this.isInside3dView = false;
    },
    getSelectedProductObj() {
        return this.products.find(product => product.name === this.selectedProduct);
    },
    fitImageInCanvas(img,canvas) {
        //same logic as fitImageAndCanvasToContainer method except that here,
        // parent will be canvas itself and canvas will not be resized anymore

        const canvasWidth = canvas.width;
        const canvasHeight = canvas.height;

        const imgWidth = img.width;
        const imgHeight = img.height;

        // find the minimum of scale factor to which image should be scaled
        // scaleFactor can be also lesser than 1, in which case image would be scaled down
        const scaleFactor = Math.min(canvasWidth / imgWidth, canvasHeight / imgHeight);

        // and then scale image to that factor and hence the other dimension will be automatically scaled in propotion
        img.scaleToWidth(imgWidth * scaleFactor);
       
        // this is for filling image into the canvas instead of fitting.
        // img.scaleToWidth(canvasWidth);
        // if(img.getScaledHeight() < canvasHeight) {
        //     img.scaleToHeight(canvasHeight);
        // }

        img.set({
            left: (canvasWidth - img.getScaledWidth()) / 2,
            top: (canvasHeight - img.getScaledHeight()) / 2,
        });
        canvas.renderAll();
    },
    setObjectFullSize() {
        if(__canvas.getActiveObject()) {
            this.fitImageInCanvas(__canvas.getActiveObject(), __canvas)
        }
        this.setVerticalCenter();
        this.setHorizontalCenter();
        
    },
    
    setCenter(obj) {
        //obj.center();
        window.__canvas.viewportCenterObject(obj)
        
    },
    preventInteraction(obj) {
        obj.set({
            // hasControls: false,
            // hasBorders: false,
            // lockMovementX: true,
            // lockMovementY: true,
            // lockScalingX: true,
            // lockScalingY: true,
            // lockRotation: true,
            selectable: false,
            evented: false,
            
        })
    },
    setCanvasClipPath(canvas) {
        const self = this, product = this.getSelectedProductObj();
        fabric.Image.fromURL(product.productImageUrl,function(img) {
            self.fitImageInCanvas(img,canvas);
            canvas.set({
                clipPath: img
            })
            
            //canvas.add(img).renderAll()
            self.setCenter(img);
        }, {crossOrigin: 'anonymous'});

        //this is for showing inner path
        fabric.Image.fromURL(product.innerImageStrokeOnlyUrl,function(img) {
            self.fitImageInCanvas(img, canvas);
            self.setCenter(img);
            self.preventInteraction(img)
            img.set({
                name: 'initOperationObj',
                excludeFromExportIn3D: true,
                //excludeFromExport: true, 
                zIndex: 1001
            })
            canvas.add(img)
            
        }, {crossOrigin: 'anonymous'})
        
    },
    addProductTagImage(canvas) {
        const self = this, product = this.getSelectedProductObj();
        fabric.Image.fromURL(product.tagImageUrl,function(img) {
            self.fitImageInCanvas(img,canvas);
            
            self.preventInteraction(img)
            img.set({
                name: 'initOperationObj',
                excludeFromExportIn3D: true,
                //excludeFromExport: true,
                zIndex: 1000
            })
            canvas.add(img)
        }, {crossOrigin: 'anonymous'})
    },
    addOverlapImage(canvas) {
        const self = this, product = this.getSelectedProductObj();
        fabric.Image.fromURL(product.overlapImageUrl,function(img) {
            self.fitImageInCanvas(img, canvas);
            self.setCenter(img);
            self.preventInteraction(img)
            img.set({
                name: 'initOperationObj',
                excludeFromExportIn3D: true,
                //excludeFromExport: true, 
                zIndex: 1002
            })
            canvas.add(img)
        }, {crossOrigin: 'anonymous'})
        
    },
    removeObjectControls(obj) {
        obj.setControlsVisibility({
            ml: false,
            mt: false,
            mr: false,
            mb: false,
            // bl: false,
            // br: false,
            // tl: false,
            // tr: false
        })
    },
    getObjectsClipPathImg(canvas) {
        const self = this, product = this.getSelectedProductObj();
        return new Promise((resolve,reject) => {
            try {
                fabric.Image.fromURL(product.innerImageUrl,function(img, isError) {
                    if(isError) {
                        reject('image could not be loaded');
                        return;
                    }
                    self.fitImageInCanvas(img,canvas);
                    img.set({
                        absolutePositioned: true
                    })
                    self.setCenter(img);
                    self.preventInteraction(img)
                    
                    resolve(img);
                
                }, {crossOrigin: 'anonymous'})
            } catch {
                reject('image could not be loaded')
            }
        })
    },
    layers: [],
    currentLayerId: 0,
    selectedObjectLayerIndex: 0,
    activeObjectLayerId: null,
    updateActiveObjectLayerId(layerId) {
        this.activeObjectLayerId = layerId;
    },
    setActiveObjectFromLayerId(layerId) {
        //first update activeObjectLayerId
        this.updateActiveObjectLayerId(layerId)

        /// and then set active object in fabric from that layerId
        const activeObject = __canvas.getObjects().find(obj => obj.id === layerId);
        __canvas.setActiveObject(activeObject).renderAll();
    },
    objectAddEvents(canvas) {
        const self = this;
        canvas.on('object:added',function(options) {
            const currentObj = options.target;
            if(currentObj.name !== 'initOperationObj') {
                if(!currentObj.addedViaHistory) {
                    if(!(currentObj.id)) {
                        currentObj.set({
                            id: 'layer-' + self.currentLayerId,
                            originX:'right',
                            originY: 'top',
                            idNumeric: self.currentLayerId,
                            objid: self.selectedObjectLayerIndex
                        });

                        self.removeObjectControls(currentObj)
                        if(currentObj.name === 'motive' || currentObj.name === 'symbol') {
                            currentObj.scaleToWidth(canvas.width/10 <= 50 ? 50 : canvas.width/10);

                            self.layers.unshift({
                                id: currentObj.id,
                                idNumeric: self.currentLayerId,
                                imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
                                type: currentObj.name,
                                objid: self.selectedObjectLayerIndex,
                                visible: currentObj.get('visible')
                            })
                            self.currentLayerId++;
                        }

                        if(currentObj.name === 'mainPicture' || currentObj.name === 'subPicture') {
                            self.fitImageInCanvas(currentObj,canvas);

                            self.layers.unshift({
                                id: currentObj.id,
                                idNumeric: self.currentLayerId,
                                imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
                                type: currentObj.name,
                                objid: self.selectedObjectLayerIndex,
                                visible: currentObj.get('visible')
                            })
                            self.currentLayerId++;
                        }
                        
                        if(currentObj.name === 'label') {
                            if(currentObj.labelType !== 'template') {
                                const size = Math.max(parseInt(canvas.width/25), 22);
                                currentObj.set("fontSize", size)
                                currentObj.fontFamily = 'Times New Roman';
                            }
                            currentObj.fill = 'rgb(0,0,0)';
                            self.layers.unshift({
                                id: currentObj.id,
                                idNumeric: self.currentLayerId,
                                type: currentObj.name,
                                text: currentObj.text,
                                objid: self.selectedObjectLayerIndex,
                                visible: currentObj.get('visible')
                            })
                            self.currentLayerId++;

                            if(!currentObj.stateProperties.includes('backgroundMode')) {
                                currentObj.stateProperties.push('backgroundMode')
                            }

                        }
                        self.setCenter(currentObj)
                        currentObj.set({ 
                            zIndex: self.currentLayerId 
                        });

                        self.sortObjects(canvas);
                        canvasHistory.add(new AddCommand(currentObj,canvas));
                        
                        
                        if(!(currentObj.name === 'symbol' || currentObj.name === 'label')) {
                            if(!currentObj.stateProperties.includes('filters')) {
                                currentObj.stateProperties.push('filters');
                            }
                        }
                        if(!currentObj.stateProperties.includes('selectable')) {
                            currentObj.stateProperties.push('selectable')
                        }
                        self.selectedObjectLayerIndex = self.layers.length;
                        self.isSaveButtonDisabled = false;
                    } else {
                        if(currentObj.name === 'motive' || currentObj.name === 'symbol' || 
                            currentObj.name === 'mainPicture' || currentObj.name === 'subPicture') {
                            self.layers.unshift({
                                id: currentObj.id,
                                idNumeric: currentObj.idNumeric,
                                imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
                                type: currentObj.name,
                                visible: currentObj.get('visible')
                            })
                        }
                        
                        // if it is symbol object which is being restored now from json, check whether
                        // it was already saved with svgPathColor in json and if not only, then add that property
                        // to it's stateProperties array so it can be saved again to server with that prop 
                        if(currentObj.name === 'symbol' && !currentObj.stateProperties.includes('svgPathColor')) {
                            currentObj.stateProperties.push('svgPathColor')
                        }
                        
                        if(!(currentObj.name === 'symbol' || currentObj.name === 'label') && !currentObj.stateProperties.includes('filters')) {
                            currentObj.stateProperties.push('filters')
                        }
                                
                        if(currentObj.name === 'label') {
                            if(!currentObj.stateProperties.includes('backgroundMode')) {
                                currentObj.stateProperties.push('backgroundMode')
                            }
                            self.layers.unshift({
                                id: currentObj.id,
                                idNumeric: currentObj.idNumeric,
                                text: currentObj.text,
                                type: currentObj.name,
                                visible: currentObj.get('visible')
                            })
                        }
                        if(!currentObj.stateProperties.includes('selectable')) {
                            currentObj.stateProperties.push('selectable')
                        }
                        
                    }
                    currentObj.setCoords().saveState();
                    if(currentObj.filters) {
                        self.saveStateForFilters(currentObj)
                    }
                } else {
                    //console.log(currentObj.zIndex)
                    if(currentObj.name === 'motive' || currentObj.name === 'symbol' || 
                        currentObj.name === 'mainPicture' || currentObj.name === 'subPicture') {
                        self.layers.unshift({
                            id: currentObj.id,
                            idNumeric: currentObj.idNumeric,
                            imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
                            type: currentObj.name,
                            visible: currentObj.get('visible')
                            
                        })
                    }
                    if(currentObj.name === 'label') {
                        self.layers.unshift({
                            id: currentObj.id,
                            idNumeric: currentObj.idNumeric,
                            text: currentObj.text,
                            type: currentObj.name,
                            visible: currentObj.get('visible')
                        })
                    }
                    Alpine.nextTick(() => {
                        if(currentObj.lastLayersArrayBeforeRemoval && currentObj.commandName !== 'AddCommand') {
                            //if there is lastLayersArrayBeforeRemoval associated when adding object from history,
                            // then sort objects as per that array and sort layers in reverse of that
                            sortable.sort(currentObj.lastLayersArrayBeforeRemoval);
                            sortableCanvas.sort(currentObj.lastLayersArrayBeforeRemoval);
                            self.sortFromLayers(currentObj.lastLayersArrayBeforeRemoval.slice().reverse());
                        }
                    })
                    
                }
            }
            //canvas.bringToFront(currentObj).renderAll();
        }) 
        
    },
    objectRemoveEvents(canvas) {
        const self = this;
        canvas.on('object:removed',function(options) {
            const currentObj = options.target;
            const indexOfLayerToRemove = self.layers.findIndex(layer => layer.id === currentObj.id);
            (indexOfLayerToRemove >= 0) && self.layers.splice(indexOfLayerToRemove,1)
            if(!currentObj.removedViaHistory && currentObj.name !== 'initOperationObj') {
                // this condition is here to check if object was removed due to removing manually
                // or removed in event of reloading external data via restoreCanvasData
                // if it was removed via restoreCanvasData operation i.e. if isCanvasDataLoading is set
                // then we don;t need to add remove Caommand in history for that
                if(!(self.isCanvasDataLoading)) {
                    canvasHistory.add(new RemoveCommand(currentObj,canvas));
                }
                self.selectedObjectLayerIndex = self.layers.length;
                self.isSaveButtonDisabled = false;
            }
        })
    },
    saveStateForFilters(currentObj) {
        currentObj.myOwnStateProps = {};
        currentObj.myOwnStateProps.filters = []

        currentObj.filters.forEach(function(filter, filterIndex) {
            currentObj.myOwnStateProps.filters.push(filter)
        })
    },
    objectUpdateEvents(canvas) {
        canvas.on('object:modified', function (options) {
            this.isSaveButtonDisabled = false;
            const currentObj = options.target;

            if(currentObj.name !== 'initOperationObj') {
                // remove duplicate entries in stateProperties
                currentObj.stateProperties = currentObj.stateProperties.filter((value, index, array) => {
                    return array.indexOf(value) === index;
                })

                canvasHistory.add(new TransformCommand(currentObj,canvas));	
                currentObj.saveState()

                // specifically define myOwnStateProps and update filters over there
                if(currentObj.filters) {
                    this.saveStateForFilters(currentObj)
                }
            }
            
        }.bind(this))
        
        
    },
    syncLayerToObject(object) {
        //console.log(object)
        this.layers.find(layer => layer.id === object.id).visible = object.visible;
    },
    objectScaleEvents(canvas) {
        const self = this;
        canvas.on('object:scaling', function (options) {
            const currentObj = options.target;
            self.getSizeData(currentObj);	
        })
    },
    clearSelectionHandler() {
        this.isObjectDeselected = true;
        this.isFontStyleSaveBtnDisabled = true;
        this.isCurveBlockDisabled = true;
        this.isColorPickerBlockDisabled = true;
        this.isObjectFilterControlsDisabled = true;
        this.isTextBackgroundButtonDisabled = true;
        // also update active layer to none by passing empty string so nothing is selected
        this.updateActiveObjectLayerId('')
        this.updateTextarea('');
        this.isTextObjectSelected = false;
    },
    updateSelectionHandler(selectedObject) {
        window.dispatchEvent(new CustomEvent('object-added-to-ai-canvas', {
            detail: {
                object: selectedObject
            }
        }))
        this.updateActiveObjectLayerId(selectedObject.id)
        this.isObjectDeselected = selectedObject.name === 'initOperationObj';
        this.isColorPickerBlockDisabled = !(selectedObject.name === 'symbol' || selectedObject.name === 'label');
        this.isObjectFilterControlsDisabled = (selectedObject.name === 'symbol' || selectedObject.name === 'label');
        this.isTextObjectSelected = selectedObject.name === 'label';
        this.isMarkObjectOptionDisabled = selectedObject.id === this.markedObject?.id;

        this.isFontStyleSaveBtnDisabled = selectedObject.name !== 'label';
        this.isCurveBlockDisabled = (selectedObject.name !== 'label') || (selectedObject._textLines?.length > 1) || selectedObject.textBackgroundColor;
        this.isTextBackgroundButtonDisabled = (selectedObject.name !== 'label') || selectedObject.path;
        if (selectedObject.name === 'label') {
            this.updateTextarea(selectedObject.text);
            window.dispatchEvent(new CustomEvent('object-selected-on-canvas', {
                detail: {
                    objectColor: selectedObject.backgroundMode !== 'foreground' ? chroma(selectedObject.fill).hex() : chroma(selectedObject.textBackgroundColor).hex(),
                    backgroundMode: selectedObject.backgroundMode || 'none',
                }
            }))
        }

        if (selectedObject.name === 'symbol') { 
            window.dispatchEvent(new CustomEvent('object-selected-on-canvas', {
                detail: {
                    objectColor: chroma(selectedObject.svgPathColor).hex(),
                    backgroundMode: selectedObject.backgroundMode || 'none'
                }
            }))
        }


        if (selectedObject) {
            const selectedObjectLayer = sortable.toArray().find(layer => layer.includes(selectedObject.idNumeric));
            this.selectedObjectLayerIndex = sortable.toArray().reverse().indexOf(selectedObjectLayer) + 1;
            this.getSizeData(selectedObject);
        }

        if(!this.isObjectFilterControlsDisabled && selectedObject.filters) {
            
            window.dispatchEvent(new CustomEvent('update-brightness-slider-value', {
                detail: {
                    brightness: selectedObject.filters.find(filter => filter.brightness)?.brightness || 0
                }
            }))
            window.dispatchEvent(new CustomEvent('update-contrast-slider-value', {
                detail: {
                    contrast: selectedObject.filters.find(filter => filter.contrast)?.contrast || 0
                }
            }))
            window.dispatchEvent(new CustomEvent('update-grayscale-switch-value', {
                detail: {
                    grayscale: selectedObject.filters.find(filter => filter.type === 'Grayscale')
                }
            }))
        }
    },
    objectSelectionEvents(canvas) {
        const self = this;
        canvas.on('selection:created', function(options) {
            self.updateSelectionHandler(options.selected[0]);
        }).on('selection:updated', function(options) {
            self.updateSelectionHandler(options.selected[0]);
        }).on('selection:cleared', function(options) {
            self.clearSelectionHandler();
        });         
    },      
    objectModifyEvents(canvas,img) {
        const tempCanvasEl =  fabric.util.createCanvasElement(),
                        
        tempCanvasCtx = tempCanvasEl.getContext('2d',{ willReadFrequently: true }),
        tempCanvas = new fabric.Canvas(tempCanvasEl, {
            preserveObjectStacking: true,
            uniformScaling: true,
            enableRetinaScaling: false,
            width: canvas.width,
            height: canvas.height,
            
        });
        tempCanvas.clipPath = img;
        tempCanvasCtx.fillStyle = 'red';
        tempCanvas.add(img).renderAll()
        
        tempCanvasEl.style.zIndex = -1;
        tempCanvasEl.style.display = 'none';

        //appending in dom is not even necessary to getimagedata from canvas
        document.getElementById('product-canvas-holder').append(tempCanvasEl);    
    

        // using event system from fabric itself we can claim that this code will
        // be only executed once
        canvas.on('object:modified', function (options) {
            const currentObj = options.target;

            //console.log(currentObj.left, currentObj.top)

            //check if any of corners of object are not outside the inner image 
            //i.e by checking if it has pixel data below it
            const allowMove = Object.values(currentObj.aCoords).every((corner) => {
                //console.log(corner.x,corner.y)
                const pxColor = tempCanvasCtx.getImageData(corner.x, corner.y, 1, 1).data;
                return pxColor[3]
            })

            if(!allowMove && currentObj.name !== 'bgimage') {
                //console.log( currentObj._stateProperties)
                currentObj.set( {
                    top:  currentObj._stateProperties.top,
                    left: currentObj._stateProperties.left,
                    angle: currentObj._stateProperties.angle,
                    scaleX: currentObj._stateProperties.scaleX,
                    scaleY: currentObj._stateProperties.scaleY
                })
            }
            currentObj.setCoords();
            currentObj.saveState();
            
        })
    },
    renderIcon(icon) {
        return function renderIcon(ctx, left, top, styleOverride, fabricObject) {
            var size = this.cornerSize;
            ctx.save();
            ctx.translate(left, top);
            ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
            ctx.drawImage(icon, -size/2, -size/2);
            ctx.restore();
          }
    },
    addCustomRotationControl() {
        let self = this;
        return new Promise((resolve,reject) => {
            fabric.Image.fromURL(getObjectControlImages().rotationImage,function(img, isError) {
                if(isError) {
                    reject(isError);
                }
                img.scaleToWidth(20);
                const rotationIconDataUrl = img.toDataURL();
                const  rotationIcon = document.createElement('img');
               
                rotationIcon.src = rotationIconDataUrl;
    
                const mtr = new fabric.Control({
                    x: -0.5,
                    y: 0.5,
                    //offsetY: -16,
                    actionHandler: fabric.controlsUtils.rotationWithSnapping,
                    cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler,
                    withConnection: true,
                    actionName: 'rotate',
                    render: self.renderIcon(rotationIcon),
                    cornerSize: 20
                  });
                  
                fabric.Object.prototype.controls.mtr = mtr;
                resolve();
            }, {crossOrigin: 'anonymous'});
        })
        
    },
    addCustomDeleteControl() {  
        let self = this;
        return new Promise((resolve,reject) => {
            fabric.Image.fromURL(getObjectControlImages().removeImage,function(img, isError) {
                if(isError) {
                    reject(isError);
                }
                img.scaleToWidth(20);
                const deleteIconDataUrl = img.toDataURL();
                const  deleteIcon = document.createElement('img');
    
                deleteIcon.src = deleteIconDataUrl;
    
                fabric.Object.prototype.controls.deleteControl =  new fabric.Control({
                    x: -0.5,
                    y: -0.5,
                    cursorStyle: 'pointer',
                    mouseUpHandler: self.openDeleteModal,
                    render: self.renderIcon(deleteIcon),
                    cornerSize: 20
                })
                resolve();
            }, {crossOrigin: 'anonymous'})
        })
        
    },
    addCustomSizeUpControls() {
        let self = this;
        
        return new Promise((resolve,reject) => {
            fabric.Image.fromURL(getObjectControlImages().sizeDownImage,function(img, isError) {
                if(isError) {
                    reject(isError);
                }
                img.scaleToWidth(20);
                const sizeDownIconDataUrl = img.toDataURL();
                const sizeDownIcon = document.createElement('img');
                sizeDownIcon.src = sizeDownIconDataUrl;

                fabric.Object.prototype.controls.sizeDownControl = new fabric.Control({
                    x: 0.5,
                    y: -0.5,
                    offsetY: 24,
                    cursorStyle: 'pointer',
                    render: self.renderIcon(sizeDownIcon),
                    mouseUpHandler: () => self.decreaseObjectSize(),
                    cornerSize: 20
                })

                resolve()
            }, {crossOrigin: 'anonymous'});
        })
    },
    addCustomSizeDownControls() {
        let self = this;
        
        return new Promise((resolve,reject) => {
            fabric.Image.fromURL(getObjectControlImages().sizeUpImage,function(img, isError) {
                if(isError) {
                    reject(isError);
                }
                img.scaleToWidth(20);
                const sizeUpIconDataUrl = img.toDataURL();
                const sizeUpIcon = document.createElement('img');
                sizeUpIcon.src = sizeUpIconDataUrl;

                fabric.Object.prototype.controls.sizeUpControl = new fabric.Control({
                    x: 0.5,
                    y: -0.5,
                    cursorStyle: 'pointer',
                    render: self.renderIcon(sizeUpIcon),
                    mouseUpHandler: () => self.increaseObjectSize(),
                    cornerSize: 20
                })
                resolve()

            }, {crossOrigin: 'anonymous'});
        })
        
    },
    get isWebglSupported() {
        return fabric.enableGLFiltering &&  fabric.isWebglSupported && fabric.isWebglSupported(fabric.textureSize);
    },
    manualInitFabricFilterBackend() {
        // this will determine fabric.maxTextureSize and so we can only assign this 
        if (this.isWebglSupported) {
            fabric.textureSize = fabric.maxTextureSize > 8192 ? 4096 : 2048;
            webglBackend = new fabric.WebglFilterBackend({ tileSize: fabric.textureSize });
        }
        canvas2dBackend = new fabric.Canvas2dFilterBackend()
    },
    swapFilterBackend(isWebglBackendEnabled) {
        if (isWebglBackendEnabled && webglBackend) {
            fabric.filterBackend = webglBackend;
        } else {
            fabric.filterBackend = canvas2dBackend;
        }
    },
    maxCanvasWidth: 4096,
    maxCanvasHeight: 4096,
    async setMaxCanvasSize() {
        const { success, width, height} = await canvasSize.maxArea();
        if(success) {
            this.maxCanvasHeight = height;
            this.maxCanvasWidth = width;
        }
    },
    initFabricCanvas(el, $watch, $store) {
        // manually init fabric filter backend
        this.manualInitFabricFilterBackend();

        const canvas = window.__canvas = new fabric.Canvas(el, {
            preserveObjectStacking: true,
            uniformScaling: true,
            //stateful: true,
            uniScaleTransform: true,
        });
        //this.enablePanning(canvas)

        //const ratio = canvas.getWidth() / canvas.getHeight();
        
        
        const canvasResizeEL = Alpine.store('elements').canvasResizeEL;
        const offsetWidth = canvasResizeEL.offsetWidth - 2;
        const offsetHeight = canvasResizeEL.offsetWidth - 2;
       
        this.setCanvasDimensions({
            width: offsetWidth,
            height: offsetHeight
        });

        $watch('$store.canvas.configuratorId', (value, oldValue) => {
            if(value !== oldValue) {
                this.isConfiguratorIdChanged = true;
            }
        })
           

        //until eventId is guaranteed to be unique, i can not uncomment this code
        // $watch('$store.canvas.apiConfig.eventId', (value, oldValue) => {
        //     debugger;
        //     if(value !== oldValue) {
        //         this.isConfiguratorIdChanged = true;
        //     }
        // })
        
        
        this.initCanvasObjectsOperations(canvas);
        this.setMaxCanvasSize();
        
        
        
        
    },
    disposeCanvas() {
        window.__canvas.dispose();
    },
    async initCanvasObjectsOperations(canvas) {
        const self = this;
        //this.getObjectsClipPathImg(canvas).then(function(img){
            self.objectAddEvents(canvas);
            self.objectRemoveEvents(canvas);
            self.objectUpdateEvents(canvas);
            self.objectSelectionEvents(canvas);

            //adding custom controls via custom control api
            await this.addControls();
            
            
            //self.objectModifyEvents(canvas,img);
            self.objectScaleEvents(canvas);
            if(self.configuratorId) {

                self.restoreCanvasData(canvas).then(() => {
                    // all data has been loaded so now it's time to add mainPicture above it
                    this.canvasIsReady();
                }).catch((error) => {
                    console.error(error)
                    // in case of unsuccessful restoration also, add mainPicture
                    this.canvasIsReady();
                })
            } else {
                // when configurator id was empty, reset canvas data
                this.resetCanvasData(canvas);
            }
            
            
            
        // }, function(error) {
        //     alert(error);
        // });
    }, 
    async addControls() {
        await this.addCustomRotationControl();
        await this.addCustomDeleteControl();
        await this.addCustomSizeUpControls();
        await this.addCustomSizeDownControls();
    },
    async getDataUrlFromURL(imageURL) {
        let response = await fetch(imageURL);

        let blob = await response.blob();
        let reader = new FileReader();
        let dataURL = await new Promise(resolve => {
            reader.onload = () => resolve(reader.result);
            reader.readAsDataURL(blob);
        });
        return dataURL;
    },
    showCropSelectionRect: false,
    canvasCropWidth: '',
    canvasCropHeight: '',
    finalImageWidth: 2000,
    finalImageHeight: 2000,
    canvasWidth: '',
    canvasHeight: '',
    sizeTemplates: [],
    sizeTemplatesForWorkspace: [],
    
    scalingStretegy: 'fit',
    toggleCropSelectionRect() {
        const cropSelectionRect = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');

        if(cropSelectionRect) {
            cropSelectionRect.set({
                visible: this.showCropSelectionRect,
            })
            
            if(!this.showCropSelectionRect) {
                __canvas.discardActiveObject()
            }
            
            __canvas.set({
                clipPath: this.showCropSelectionRect ? cropSelectionRect : null
            })
        }
        __canvas.renderAll()

    },
    updateCropSelectionRect() {
        const cropSelectionRect = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');

        if(cropSelectionRect) {
            

            if( !(isNaN(this.canvasCropWidth)) && !(isNaN(this.canvasCropHeight)) ) {

                this.canvasCropWidth = parseInt(this.canvasCropWidth)
                this.canvasCropHeight = parseInt(this.canvasCropHeight)

                // height width with viewporttransform offset
                const actualCanvasWidth = __canvas.width / __canvas.viewportTransform[0];
                const actualCanvasHeight = __canvas.height / __canvas.viewportTransform[3]


                if(this.canvasCropWidth > actualCanvasWidth || this.canvasCropHeight > actualCanvasHeight) {
                //if(false) {
                    if(this.canvasCropWidth > this.canvasCropHeight) {
                        cropSelectionRect.set({
                            width: actualCanvasWidth,
                            height: actualCanvasWidth * this.canvasCropHeight / this.canvasCropWidth
                        })
                
                    } else if (this.canvasCropHeight > this.canvasCropWidth) {
                        cropSelectionRect.set({
                            height: actualCanvasHeight,
                            width: actualCanvasWidth * this.canvasCropWidth / this.canvasCropHeight
                        })
                    } else {
                        cropSelectionRect.set({
                            height: actualCanvasWidth,
                            width: actualCanvasHeight
                        })
                    }

                   
                
                } else {
                    cropSelectionRect.set({
                        width: this.canvasCropWidth,
                        height: this.canvasCropHeight
                    })
                }
            }

            // always reset scale of crop rectangle back to 1
            // to have the actual size and not scaled
            cropSelectionRect.scale(1)

            this.setCenter(cropSelectionRect)
        }
        
    },
    async setCanvasBackground(url) {
        const canvas = window.__canvas;
        //const dataURL = await this.getDataUrlFromURL(url);
        
        if(!url) {
            canvas.set({
                backgroundImage: null
            })
            canvas.renderAll();
            return;
        }
        fabric.Image.fromURL(url, (img) => {
            
            //img.set({ crossOrigin: 'Anonymous' });
            img.scaleToWidth(canvas.width/canvas.viewportTransform[0]);
            if(img.getScaledHeight() < (canvas.height/canvas.viewportTransform[3])) {
                img.scaleToHeight(canvas.height/canvas.viewportTransform[3]);
            }
            
            
            const leftOffset = ((canvas.width - img.getScaledWidth()).toFixed(2))/2;
            const topOffset = ((canvas.height - img.getScaledHeight()).toFixed(2))/2;

            let centerPoint = {
                x: (canvas.width/canvas.viewportTransform[0])/2,
                y: (canvas.height/canvas.viewportTransform[3])/2
            }

            //console.log(centerPoint)
            ///console.log(fabric.util.transformPoint(centerPoint, canvas.viewportTransform))
            
            img.set({
                left: centerPoint.x,
                top: centerPoint.y,
                name: 'bgimage',
                originX: 'center',
                originY: 'center',
            });                    
               
            canvas.backgroundImage = img;
            //canvas.add(img);
            this.isSaveButtonDisabled = false;
            canvas.requestRenderAll();
        }, { crossOrigin: 'anonymous' })
    },
    sortFromLayers(layers) {
        
        layers.forEach(function(layer,layerIndex,layers) {
            window.__canvas.getObjects().forEach(function(obj) {
                if(obj.id === layer) {
                  obj.zIndex = layerIndex;
                }
            })
        })
        this.sortObjects(window.__canvas);
        const selectedObject = window.__canvas.getActiveObject();
        if (selectedObject) {
            const selectedObjectLayer = layers.find(layer => layer.includes(selectedObject.id));
            this.selectedObjectLayerIndex = layers.indexOf(selectedObjectLayer) + 1;
        }
    },
    sortObjects(canvas) {
        canvas._objects.sort((a, b) => (a.zIndex > b.zIndex) ? 1 : -1);
        canvas.renderAll();
    },
    setCanvasDimensions(containerSize) {

        const canvas = window.__canvas;
        
        canvas.setDimensions(containerSize);

        const {width, height} = containerSize;
        // this.canvasWidth = width;
        // this.canvasHeight = height;
        
        this.canvasCropWidth = Math.round((width / canvas.viewportTransform[0]) * 90 / 100);
        this.canvasCropHeight = Math.round((height / canvas.viewportTransform[3]) * 90 / 100);
        this.updateCropSelectionRect();

        this.oldContainerWidth = Alpine.store('elements').canvasResizeEL.offsetWidth - 2;
        canvas.renderAll();
    },
    addCropRectangle(canvas) {
        this.showCropSelectionRect = false;
        __canvas.clipPath = null;
        __canvas.renderAll();
        const cropRectangle = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');

        if(cropRectangle) { 
            cropRectangle.set({visible: false})
        }

        if(!cropRectangle) {
            const self = this;
            const cropSelectionRect = new fabric.Rect({
                absolutePositioned: true,
                name: 'initOperationObj',
                altName: 'cropRectangle',
                fill: '#ff0000',
                opacity: 0.01,
                // originX: 'right',
                // originY: 'bottom',
                stroke: 'black',
                visible: false,
                strokeWidth: 0,
                width: Math.round((canvas.width / canvas.viewportTransform[0]) * 90 / 100),
                height: Math.round((canvas.height / canvas.viewportTransform[3]) * 90 / 100),
                strokeUniform: true,
                cornerStyle: 'circle',
                transparentCorners: false,
                zIndex: 1002,
                excludeFromExport: true,
            })

            

            cropSelectionRect.on('modified', function(options) {
                self.canvasCropWidth = Math.round(cropSelectionRect.getScaledWidth());
                self.canvasCropHeight = Math.round(cropSelectionRect.getScaledHeight());
            })

            cropSelectionRect.setControlsVisibility({
                deleteControl: false,
                mtr: false,
                sizeUpControl: false,
                sizeDownControl: false,
            })

            // controls are not working properly
            //addCropRectangleControls(cropSelectionRect)
            this.setCenter(cropSelectionRect)
            
            canvas.add(cropSelectionRect);
        }

    },
        
    oldContainerWidth: '',
    /**
     * 
     * @param {canvas instance} canvas 
     * @param {containersize which was before window resize(oldContainerWidth), this could also come as } sizeBeforeResize 
     * @param {check whether resizeCanvas method is called during resizing window(true) or during restoring data from server(false)} isResizingViaWindow 
     * @param {only present and applicable when method is called during restoring data from server} savedCanvasDim 
     */
    resizeCanvas(canvas, sizeBeforeResize, isResizingViaWindow = true, savedCanvasDim) {
        const canvasResizeEL = Alpine.store('elements').canvasResizeEL;
        let containerWidth = canvasResizeEL.offsetWidth - 2;
       
        
        
        //console.log(containerSize)
        if(containerWidth === 0 || containerWidth < 0) {
            containerWidth = sizeBeforeResize;
        }

        let finalCanvasDim = {
            width:  containerWidth,
            height:  containerWidth,
        }

        const newScale = containerWidth / sizeBeforeResize;
        // if(newScale === 0) {
        //     newScale = 1;
        // }

        // keep in mind that we also call this method after loading data from json
        // for the first time and hence there should be no oldScale present earlier at that point, so it will be 1
        const oldScale = isResizingViaWindow ? canvas.viewportTransform[0] : 1;
        
        //console.log(newScale, oldScale);

        // Store the current filter settings
        const currentFilter = canvas.getContext().filter;
        
        
        // canvas.setZoom(newScale * oldScale); 
        canvas.setViewportTransform([newScale * oldScale, 0,0, newScale * oldScale,0,0]);

        
        if(savedCanvasDim) {
            finalCanvasDim = {
                width: savedCanvasDim?.width * newScale,
                height: savedCanvasDim?.height  * newScale
            }
        }

        this.setCanvasDimensions(finalCanvasDim)

        // restore values in size templates input when restored
        this.canvasWidth = Math.round(finalCanvasDim.width);
        this.canvasHeight = Math.round(finalCanvasDim.height);
        
        
        // Reapply the filter settings
        if (currentFilter) {
            canvas.getContext().filter = currentFilter;
        }
        canvas.renderAll();

        
        //canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), newScale * oldScale)
        //canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);
    },
    zoomCount: 1,
    zoomInCanvas() {
        const canvas = window.__canvas;
        const currentZoom = canvas.getZoom();
        canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), currentZoom + 0.1);
        this.zoomCount++;
    },
    zoomOutCanvas() {
        const canvas = window.__canvas;
        const currentZoom = canvas.getZoom();
        canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), currentZoom - 0.1);
        this.zoomCount--;
    },
    resetCurrentZoom(canvas) {
        while(this.zoomCount !== 1) {
            this.zoomCount > 1 ? this.zoomOutCanvas() :  this.zoomInCanvas()
        }
    },
    isFullScreen: false,
    setFullScreenCanvas() {
        //console.log('called')
        // this.isFullScreen = !this.isFullScreen;
        // window.dispatchEvent(new Event('resize'));
    },
    enablePanning(canvas) {
        canvas.on('mouse:down', function(opt) {
            const evt = opt.e;
            if (evt.altKey === true) {
              this.isDragging = true;
              this.selection = false;
              this.lastPosX = evt.clientX;
              this.lastPosY = evt.clientY;
            }
        });
        canvas.on('mouse:move', function(opt) {
            if (this.isDragging) {
                const e = opt.e;
                var vpt = this.viewportTransform;
                vpt[4] += e.clientX - this.lastPosX;
                vpt[5] += e.clientY - this.lastPosY;
                this.requestRenderAll();
                this.lastPosX = e.clientX;
                this.lastPosY = e.clientY;
            }
        });
        canvas.on('mouse:up', function(opt) {
            // on mouse up we want to recalculate new interaction
            // for all objects, so we call setViewportTransform
            this.setViewportTransform(this.viewportTransform);
            this.isDragging = false;
            this.selection = true;
        });
    },
    async addShapeToCanvas(url, type) {
        const canvas = window.__canvas;
        //const dataURL = await this.getDataUrlFromURL(url);\
        if(type === 'motive') {
            const shapeToAdd =  fabric.Image.fromURL(url,function(img) {
                img.name = type;
                canvas.add(img);
            }, {crossOrigin: 'anonymous'})
        }
        if(type === 'symbol') {
            const shapeToAdd =  fabric.loadSVGFromURL(url,function(objects, options) {
                let svg = fabric.util.groupSVGElements(objects, options);
                
                svg.name = type;

               
                  

                //whenever we need to add custom property to save as a history
                // we need to assign that property(svgPathColor in this case) to the object even before that object has been\
                // added to the canvas and also push that property into stateProperties array of the object
                svg.stateProperties.push('svgPathColor')
                svg.svgPathColor = '#000000';
                
               
                svg.id = null; //we must not need id which was automatically assigned by fabric
                svg.getGroupSVGSrc = url;

                
                
                canvas.add(svg);
            },function() {}, {crossOrigin: 'anonymous'})
        }
    },
    async addPictureToCanvas(url,type) {
        const canvas = window.__canvas;
        //const dataURL = await this.getDataUrlFromURL(url);\
       
        fabric.Image.fromURL(url,function(img) {
            img.name = type;
            canvas.add(img);
        }, {crossOrigin: 'anonymous'})    
        
    },
    canvasIsReady() {
        window.dispatchEvent(new CustomEvent('canvas-is-ready'))
        this.addCropRectangle(__canvas);
        
    },
    addLabelToCanvas(text) {
        const canvas = window.__canvas;
        
        const label = new fabric.Text(text);
        label.name = 'label';
        canvas.add(label);
    },
    addTemplateToCanvas(template) {
        const canvas = window.__canvas;
        
        const label = new fabric.Text(template.text, {
            name: 'label',
            labelType: 'template',
            fontFamily: template.fontFamily,
            fontSize: template.fontSize / __canvas.viewportTransform[0],
            fontWeight: template.fontWeight
        });
        label.name = 'label';
        canvas.add(label);
    },
    increaseObjectSize() {
        const canvas = window.__canvas;
        const activeObj =  canvas.getActiveObject();
        if(activeObj) {
            const newScale = ((activeObj.scaleX) + (activeObj.scaleX/10));
            activeObj.scale(newScale)
            canvas.fire('object:modified', {target: activeObj});
            this.getSizeData(activeObj);
            canvas.renderAll();
        }
    },
    decreaseObjectSize() {
        const canvas = window.__canvas;
        const activeObj =  canvas.getActiveObject();
        if(activeObj) {
            const newScale = ((activeObj.scaleX) - (activeObj.scaleX/10));
            activeObj.scale(newScale);
            canvas.fire('object:modified', {target: activeObj});
            this.getSizeData(activeObj);
            canvas.renderAll();
        }
    },
    
    _curvatureValue: 60,
    get curvatureValue() {
        return this._curvatureValue;
    },
    set curvatureValue(val) {
        this._curvatureValue = Math.min(500, Math.max(0, val))
    },
    isCurveTextboxDisabled: true,
    curveDirection: null,
    applyCurvature() {
        const canvas = window.__canvas;
        const activeObj = canvas.getActiveObject();
        if (activeObj && activeObj.name === 'label') {
            const originalWidth = activeObj.width;
            const originalHeight = activeObj.height;
            
            const inputValue = parseFloat(this.curvatureValue);
            if (inputValue === null || isNaN(inputValue) || inputValue <= 0) {
                activeObj?.set({
                    path: null,
                    selectable: true,
                });
            } else {
                    const canvasCenterX = canvas.width / 2;
                    const canvasCenterY = canvas.height / 2;
                    const curvatureFactor = inputValue / 80;
                    let pathData;
                    if (this.curveDirection === 'upcurve') {
                        const controlPointY = canvasCenterY + originalHeight * curvatureFactor;
                        pathData = `M ${canvasCenterX - originalWidth / 2} ${canvasCenterY}
                                    Q ${canvasCenterX} ${controlPointY}
                                    ${canvasCenterX + originalWidth / 2} ${canvasCenterY}`;
                        
                    } else if (this.curveDirection === 'downcurve') {
                        const controlPointY = canvasCenterY - originalHeight * curvatureFactor;
                        pathData = `M ${canvasCenterX - originalWidth / 2} ${canvasCenterY}
                                    Q ${canvasCenterX} ${controlPointY}
                                    ${canvasCenterX + originalWidth / 2} ${canvasCenterY}`;
                    }
                    const textPath = new fabric.Path(pathData, {
                        strokeWidth: 1,
                        visible: false,
                        
                    });
                    activeObj?.set({
                        path: textPath,
                        pathSide: 'center',
                        pathStartOffset: 0,
                        selectable: true,
                    });
                   
            }   
            
            this.updateSelectionHandler(activeObj)
            // Set the width and height back to the original values
            activeObj?.set({ width: originalWidth, height: originalHeight });
            
            //canvas.requestRenderAll();
            canvas.renderAll();
            canvas.fire('object:modified', {target: activeObj});
        }
    },
    activeObjectWidth: '',
    activeObjectHeight: '',
    getSizeData(activeObj) {
        this.activeObjectWidth = "Width: " + activeObj.getScaledWidth().toFixed(2) + "px";
        this.activeObjectHeight = "Height: " + activeObj.getScaledHeight().toFixed(2) + "px";
    },
    markedObject: null,
    isMarkObjectOptionDisabled: false,
    markObject() {
        const canvas = window.__canvas;
        const activeObj = canvas.getActiveObject();
       
        this.markedObject?.set({
            borderColor: '#b2ccff',
        });
        
        activeObj.set({
            borderColor: 'green',
        });
        canvas.renderAll();
        this.markedObject = activeObj;
        this.isMarkObjectOptionDisabled = true;
        
        
    },
    applySameSizeAsMarkedObject() {
        const canvas = window.__canvas;
        const activeObj = canvas.getActiveObject();
       
        if (this.markedObject && activeObj !== this.markedObject) {
            const markedWidth = this.markedObject.width * this.markedObject.scaleX;
            const markedHeight = this.markedObject.height * this.markedObject.scaleY;
            activeObj.scaleToWidth(markedWidth);
            activeObj.scaleToHeight(markedHeight);
            activeObj.setCoords();
            
            canvas.renderAll();
        } 
        
    },
    async crop($store) {
        const activeObj = __canvas.getActiveObject();
        let fileUrl;
        //let fileUrl = 'http://127.0.0.1/storage/product-configurators/PiSa7SPjMd8rkMX9FqXwkwsG5EXaZuDZowG21taD.png';
        if(activeObj.name !== 'symbol') {
            fileUrl = __canvas.getActiveObject().getSrc();
        } else {
            fileUrl = __canvas.getActiveObject().getGroupSVGSrc;
        }

        const fileName = fileUrl.split('/').pop();

        const response = await fetch(fileUrl);
        const blob = await response?.blob();
        const contentType = response?.headers?.get('Content-Type')
        const file = new File([blob], fileName, {type: contentType, lastModified: Date.now()});

        $store.cropperStore.prepareCropperImage({
            originalImageFile: file,
            fileName:  file.name,
            url: fileUrl,
            cropperData: null,
            uploadWithoutConfig: true,
            fileType: __canvas.getActiveObject().name === 'mainPicture' ? 'mainPictures' : 'shapes',
            el: __canvas.getActiveObject().name === 'mainPicture' ? $store.elements.pictureUploadEL : $store.elements.shapeUploadEL
        })
    },
    replaceActiveObjectUrl(url) {
        const activeObj = __canvas.getActiveObject();
       
        activeObj.setSrc(url, function(img) {
            __canvas.renderAll();
        }, {crossOrigin: 'anonymous'});
    },
    updateTextarea(text) {
        const canvas = window.__canvas;
        const activeObj = canvas.getActiveObject();
        window.dispatchEvent(new CustomEvent('update-text-area', {
            detail: {
                text: text
            }
        }))
    },
    updateObjectText(value) {
        const canvas = window.__canvas;
        const activeObj = canvas.getActiveObject();
        if (activeObj && activeObj.name === 'label') {
            activeObj?.set({
                path: null,
                selectable: true,
                text: value
            });
            canvas.renderAll();
        }
    },
    _shadowDistance: 0,
    get shadowDistance() {
        return this._shadowDistance;
    },
    set shadowDistance(val) {
        this._shadowDistance = val;
    },
    _blurDistance: 0,
    get blurDistance() {
        return this._blurDistance;
    },
    set blurDistance(val) {
        this._blurDistance = val;
    },
    shadowEffect(action) {
        const canvas = window.__canvas;
        const activeObj = canvas.getActiveObject();

        const shadowValue = parseInt(this.shadowDistance, 10);
        const blurValue = parseInt(this.blurDistance,10);
        const shadow = new fabric.Shadow({
            color: 'rgba(0,0,0,0.6)',
            blur: blurValue,
            offsetX: 4,
            offsetY: shadowValue,
        });
        activeObj.set({
            shadow: action === 'apply' ? shadow : null,
        });
        
        canvas.requestRenderAll();
    },
        
    setHorizontalCenter() {
        const canvas = window.__canvas;
        const activeObj =  canvas.getActiveObject();
        if(activeObj) {
            canvas.viewportCenterObjectH(activeObj);
            canvas.fire('object:modified', {target: activeObj});
        }
        //activeObj?.centerH();
    },
    setVerticalCenter() {
        const canvas = window.__canvas;
        const activeObj =  canvas.getActiveObject();
        if(activeObj) {
            canvas.viewportCenterObjectV(activeObj);
            canvas.fire('object:modified', {target: activeObj});
        }
        //activeObj?.centerV();
    },
    setAngle(angle) {
        const canvas = window.__canvas;
        const activeObj =  canvas.getActiveObject();
        activeObj?.rotate(angle);
        canvas.renderAll();
    },
    openDeleteModal() {
        Alpine.store('modal').openModal(Alpine.store('elements').deleteObjectModalEL)
    },
    removeObject() {
        const canvas = window.__canvas, self = this;
        const activeObjects =  canvas.getActiveObjects();
        canvas.discardActiveObject();
        activeObjects.forEach(function(activeObject) {
            //append last position of layers as property along with object called lastLayersArrayBeforeRemoval
            activeObject.lastLayersArrayBeforeRemoval = sortable.toArray().filter(item => item !== 'ignore');
            canvas.remove(activeObject);
            canvas.renderAll()
        })
    },
    toggleObjectById(id, $nextTick) {
        const objectToToggle = __canvas.getObjects().find(obj => obj.id === id);
        const isVisible = objectToToggle.get('visible')
        objectToToggle.set({
            visible: !isVisible,
            selectable: !isVisible,
            
        })
        if(!(objectToToggle.get('visible'))) {
            $nextTick(() => {
                __canvas.discardActiveObject().renderAll();
            })
        }
        this.layers.find(layer => layer.id === id).visible = !isVisible;
        __canvas.fire('object:modified', {target: objectToToggle});   
        __canvas.renderAll()
        

    },
   
    setLabelBackgroundColor(labelBackgroundObj) {
        const canvas = window.__canvas;
        const activeObj =  canvas?.getActiveObject();
        activeObj.set({
            fill: labelBackgroundObj.color,
            textBackgroundColor: labelBackgroundObj.textBackgroundColor,
            bgCornerRadius: 10,
            backgroundMode: labelBackgroundObj.backgroundMode
        })
        this.updateSelectionHandler(activeObj)
        canvas.fire('object:modified', {target: activeObj});    
        canvas.renderAll()
        
        
    },
    setLabelColor(color) {
        const canvas = window.__canvas;
        const activeObj =  canvas?.getActiveObject(),
            activeObjName = activeObj?.name;
        if(activeObjName === 'label') {
            activeObj.set({
                fill: color
            });
            canvas.fire('object:modified', {target: activeObj});                
            canvas.renderAll()
        }
        if(activeObjName === 'symbol') {
            let objects = activeObj._objects;

            // first store reverse order of previous and next color values 
            // and savestate to store them in _stateProperties for history prevState 
           ;
            objects?.filter(object => object.type === 'path').forEach(function(object) {
                if(object.fill !== '#fff' || object.fill === '#ffffff' || object.fill === 'rgb(255,255,255)') {
                    object.set({fill: color})
                }
            })

            // after changing path color, reverse the next and previous to
            // store them in history state object via get method

            activeObj.svgPathColor = color;
            // finally trigger history update via modifying object
            canvas.fire('object:modified', {target: activeObj});  
            canvas.renderAll()
        }    
    },
    setCanvasBackgroundColor(color) {
        __canvas.set({
            backgroundImage: null,
            backgroundColor: color
        });
        __canvas.renderAll();
    },
    setMotiveOrPictureBackgroundColor(color) {
        const activeObj = __canvas.getActiveObject()

        if(activeObj && (activeObj.name === 'motive' || activeObj.name === 'mainPicture' || activeObj.name === 'subPicture')) {
            activeObj.set({
                backgroundColor: color
            })
            __canvas.renderAll();
        }
    },
    setCanvasGradient(colorStops, angle) {
        // colorStops.forEach(function(colorStop,index) {
        //     console.log(colorStop, angle)
        // })
        const canvas = window.__canvas;
        
        const gradientCoords = this.calculateGradientCoords(angle, canvas.width, canvas.height);

        const gradient = new fabric.Gradient({
            type: "linear",
            coords: gradientCoords,
            colorStops: colorStops,
        });
        canvas.set({
            backgroundImage: null,
            backgroundColor: gradient
        })
       

       
        canvas.renderAll();

        
    },
    calculateGradientCoords(angle, canvasWidth, canvasHeight) {
        // Convert angle to radians for calculations
        const angleRad = (angle * Math.PI) / 180;
      
        // Calculate the direction vector components based on the angle
        const dx = Math.cos(angleRad);
        const dy = Math.sin(angleRad);
      
        // Determine the length of the gradient line. This could be the diagonal of the canvas
        // for angles that are not aligned with the axes, to ensure the gradient covers the entire area.
        const gradientLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);
      
        // Calculate the start and end points of the gradient line based on the direction vector
        // and the gradient length. We center the gradient on the canvas.
        const centerX = canvasWidth / 2;
        const centerY = canvasHeight / 2;
      
        const x1 = centerX - dx * gradientLength / 2;
        const y1 = centerY - dy * gradientLength / 2;
      
        const x2 = centerX + dx * gradientLength / 2;
        const y2 = centerY + dy * gradientLength / 2;
      
        return { x1, y1, x2, y2 };
    },
    setLabelFont(font) {
        const canvas = window.__canvas;
        const activeObj =  canvas.getActiveObject(),
            activeObjType = activeObj?.type;
       
        if(activeObjType === 'text') {
            activeObj.set({
                fontFamily:font
            });
            canvas.fire('object:modified', {target: activeObj});
            canvas.renderAll()
        }
    },
    saveCanvasData(bundleFile) {
        
        const canvas = window.__canvas;

        const brightnessMatch = canvas.getContext().filter.match(/brightness\((\d+(\.\d+)?)\)\s*/);
        const contrastMatch = canvas.getContext().filter.match(/contrast\((\d+(\.\d+)?)\)\s*/)
        const grayScaleMatch = canvas.getContext().filter.match(/grayscale\s*\((\d+(?:\.\d+)?|\d+%)\)\s*/)

        this.isServerLoaderCanvasVisible = true;
        

        const currentJSON = {
            savedCanvasSize: this.oldContainerWidth,
            savedCanvasDim: {width: canvas.getWidth(), height: canvas.getHeight()},
            savedViewportTransform: canvas.viewportTransform,
            canvasBrightness: brightnessMatch ? parseFloat(brightnessMatch[1]) : null,
            canvasContrast: contrastMatch ? parseFloat(contrastMatch[1]) : null,
            canvasGrayScale: grayScaleMatch ? parseFloat(grayScaleMatch[1]) : null,
            sizeTemplates: this.sizeTemplates
        };
        const canvasJSON =  canvas.toJSON([
            'id','idNumeric','backgroundImage', 'name', 'labelType', 'selectable',
            'evented','zIndex','excludeFromExportIn3D','svgPathColor','getGroupSVGSrc', 'filters', 'backgroundMode']);

        this.canvasData =  Object.assign(currentJSON, canvasJSON)
        const formData = new FormData();
        
        formData.append('configuratorId', this.configuratorId || null)
        formData.append('product3dImage', bundleFile)
        
        const blobdata = JSON.stringify(this.canvasData);
        formData.append('canvasData', blobdata)
        
        let endpointUrl;
        // formData.get('configuratorId') and this.configuratorId both conditions are same because we already appended that to formdata above
        if(this.configuratorId) {
              // if configuratorId already exists it means this is an update endpoint to update existing configurator
              formData.append('_method', 'PUT');
              endpointUrl = `${this.apiConfig.apiUrl}/api/v1/product-configurators/${formData.get('configuratorId')}`;
        } else {
            // this url is for first time save endpoint if configuratorId does not exist yet
            // also append productId to formdata if it is first time save to make connection between productId and configuratorId
            endpointUrl = `${this.apiConfig.apiUrl}/api/v1/product-configurators`;
            formData.append('productId', this.productId || null);
        }
      

        return fetch(endpointUrl, {
            method: "POST",
            body: formData,
            "timeout": 0,
            "headers": {
                "Authorization": `Bearer ${this.apiConfig.apiToken}`,
            },
            redirect: 'follow'
        }).then((response) => {
            return response.json()
        })
        .then((data) => {
            Alpine.store('canvas').isServerLoaderCanvasVisible = false;
            if(data?.data) {
                Alpine.store('canvas').configuratorId = data.data.toString();

                this.isSaveButtonDisabled = true;
                window.dispatchEvent(new CustomEvent('canvas-data-saved', {
                    detail: {
                        configuratorId: data.data
                    }
                }))
                
                
                // even if configuratorId might be changed, 
                //we don;t want to set isConfiguratorIdChanged to true at this point
                // due to fact that if someone opens modal again, again he will see the same thing
                // inshort - isConfiguratorIdChanged should be only be possible to true from livewire
                this.isConfiguratorIdChanged = false;
            }
            return data;
        })
        .catch((error) => {
            console.error(error);
        });
        
    },
    isCanvasDataLoading: false,
    resetCanvasData(canvas) {
        __canvas.setBackgroundColor('transparent')
        this.isCanvasDataLoading = true;
        const objectsToRemove = canvas.getObjects().filter(obj => obj.name !== 'initOperationObj')
        canvas.remove(...objectsToRemove);
        canvas.backgroundImage = null;
        canvasHistory.clear();
        canvas.requestRenderAll(); // Refresh/render the canvas
        this.isCanvasDataLoading = false;

        this.isCanvasResetBtnDisabled = true;
        this.isSaveButtonDisabled = true;

        // due to the fact that mainPicture is already updated (when configuratorId is changed) in it's array before opening modal,
        // we can use this method to find the one from it which was not manually uploaded (i.e. which does not have isRemovable property)
        // but rather was just updated from consumer side
        this.canvasIsReady();
    },
   
    async restoreCanvasData(canvas) {
        this.isServerLoaderCanvasVisible = true;
        canvasHistory.clear();
        this.isConfiguratorIdChanged = false;
        
        
        
        return new Promise((resolve, reject) => {
            fetch(`${this.apiConfig.apiUrl}/api/v1/product-configurators/${this.configuratorId}?isGlobal=true`, {
                method: "GET",
                headers: {
                    "Authorization": `Bearer ${this.apiConfig.apiToken}`,
                },
                redirect: 'follow'
            }).then((response) => {
                if (response.ok) {
                    return response.json()
                } else {
                    throw new Error('Network response was not OK');
                }
            })
            .then((data) => {
                
                if(data.data) {
                    //console.log(data.data)
                    this.isServerLoaderCanvasVisible = false;
                    const loadingData = data?.data?.canvasData?.savedCanvasSize ? data.data.canvasData : '';
                    this.isCanvasDataLoading = !!loadingData;

                    // update background, color, shape, font and mainPictures
                    // updatre canvas contrast/brightness which is not depedent on loaded data
                    this.updateAssets(data)
                    
                    if(loadingData) {
                        canvas.loadFromJSON(loadingData,function() {
                            this.isCanvasDataLoading = false;
                            this.sortObjects(canvas);

                            //whenever reloading again from server resize whole canvas so that it 
                            // is auto adjustred to the width for which it was made for
                            let savedCanvasDim;
                            if(loadingData.savedCanvasDim) {
                                savedCanvasDim = {
                                    width: loadingData.savedCanvasDim.width / loadingData.savedViewportTransform[0],
                                    height: loadingData.savedCanvasDim.height / loadingData.savedViewportTransform[3]
                                }
                            }
                            this.resizeCanvas(canvas, loadingData.savedCanvasSize / loadingData.savedViewportTransform[0], false, savedCanvasDim)
                            
                            //get array of id of all canvas objects whose id is nummeric 
                            // and find maximum of them to apply it to maximum
                            const allNumericObjects = canvas.getObjects().filter(obj => typeof obj.idNumeric === 'number').map(obj => obj.idNumeric);
                            if(allNumericObjects?.length) {
                                const maxObjID = Math.max(...(allNumericObjects))
                                this.currentLayerId = maxObjID + 1;
                            }
                            // if(typeof object.idNumeric === 'number') {
                            //     this.currentLayerId = object.idNumeric + 1;
                            // }
                            this.isSaveButtonDisabled = true;
                            resolve(data);
                        }.bind(this), function (o, object) {
                            
                        });
                    } else {
                        // it might be possible that configurator exists but have no canvasData
                        // so in this case as well, we need to reset everything on canvas
                        this.resetCanvasData(canvas);

                        // else directly resolve promise with returned data
                        resolve(data);
                    }
                    
                } else {
                    this.isServerLoaderCanvasVisible = false;    
                    reject('data could not be loaded');
                }
                
            })
            .catch((error) => {
                this.isServerLoaderCanvasVisible = false;
                console.error(error);
                reject(error)
            });
        })
    },
    updateAssets(data) {
        let sizeTemplates = [];
        if(data?.data?.history && data.data.history.length) {
            data.data.history.forEach((record) => {
                if(record.canvasData.sizeTemplates) {
                    let sizeTemplatesForRecord = record.canvasData.sizeTemplates;
                    sizeTemplatesForRecord.forEach((sizeTemplateForRecord) => {
                        sizeTemplates.push(sizeTemplateForRecord)
                    })
                }
            })
        }

        if(data.data.backgrounds) {
            window.dispatchEvent(new CustomEvent('backgrounds-added-from-api', { 
                    detail: {
                        backgrounds: data.data.backgrounds
                    }, 
                    bubbles: true 
                }
            ));
        }

        if(data.data.shapes) {
            window.dispatchEvent(new CustomEvent('shapes-added-from-api', { 
                    detail: {
                        shapes: data.data.shapes
                    }, 
                    bubbles: true 
                }
            ));
        }
        
        if(data.data.colors) {
            window.dispatchEvent(new CustomEvent('colors-added-from-api', { 
                    detail: {
                        colors: data.data.colors
                    }, 
                    bubbles: true 
                }
            ));
        }

        if(data.data.fonts) {
            window.dispatchEvent(new CustomEvent('fonts-added-from-api', { 
                    detail: {
                        fonts: data.data.fonts
                    }, 
                    bubbles: true 
                }
            ));
        }

        if(data.data.mainPictures) {
            window.dispatchEvent(new CustomEvent('mainPictures-added-from-api', { 
                    detail: {
                        mainPictures: data.data.mainPictures
                    }, 
                    bubbles: true 
                }
            ));
        }
        
        window.dispatchEvent(new CustomEvent('canvas-data-restored', { 
                detail: {
                    history: data.data.history,
                    canvasBrightness: data?.data?.canvasData?.canvasBrightness,
                    canvasContrast: data?.data?.canvasData?.canvasContrast,
                    canvasGrayScale: data?.data?.canvasData?.canvasGrayScale,
                    sizeTemplates: sizeTemplates
                }, 
            }
        ));
    },
    drawingCanvas: null,
    savedImageDownloadUrl: '',
    savedImageFileSize: '',
    savedImageQuality: 100,
    savedImageFormat: 'png',
    resetScaleForFabricCanvas() {
        __canvas.setDimensions({
            width: __canvas.getWidth() / __canvas.viewportTransform[0],
            height: __canvas.getHeight() / __canvas.viewportTransform[3]
        })
        __canvas.setViewportTransform([1,0,0,1,0,0])
    },
    scaleFabricCanvas(factor) {
        __canvas.setViewportTransform([factor, 0, 0, factor, 0, 0])
        __canvas.setDimensions({
            width: __canvas.getWidth() * factor,
            height: __canvas.getHeight() * factor
        })
    },
    updateDownloadUrl() {
        const self = this;
        const existingFilter = __canvas.getElement().getContext('2d').filter;
        this.resetCurrentZoom(__canvas)
        
        // store original transform before resetting it to normal
        const currentVpt = __canvas.viewportTransform;
        const currentCanvasDimension = {
            width: __canvas.getWidth(),
            height: __canvas.getHeight()
        }
        
        
        this.resetScaleForFabricCanvas()

        this.finalImageWidth =  parseInt(this.finalImageWidth);
        this.finalImageHeight = parseInt(this.finalImageHeight);


        // discard all active objects before drawing on new canvas
        __canvas.discardActiveObject();
        __canvas.renderAll();

        if(!this.drawingCanvas) {
            this.drawingCanvas = document.createElement("canvas");
        }

        const drawingContext = this.drawingCanvas.getContext("2d");
        this.drawingCanvas.width = this.finalImageWidth;
        this.drawingCanvas.height = this.finalImageHeight;
        drawingContext.filter = existingFilter;
         
        drawingContext.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height)

        if(!this.showCropSelectionRect) {
            let drawWidth, drawHeight;


            const canvasWidth = __canvas.width
            const canvasHeight = __canvas.height;


            const widthFactor = this.finalImageWidth/canvasWidth;
            const heightFactor = this.finalImageHeight/canvasHeight;
            let factor = this.scalingStretegy === 'fit' ? Math.min(widthFactor, heightFactor) : Math.max(widthFactor, heightFactor);

            // we also need max factor here to account for the maximum canvas size that can be available
            const maxFactor = Math.min(this.maxCanvasWidth/__canvas.getWidth(), this.maxCanvasHeight/__canvas.getHeight());
            
            factor = Math.min(factor, maxFactor)
            this.scaleFabricCanvas(factor)

            
            if(this.scalingStretegy === 'fit') {
                // check if width factor is greater than height, minimum division is height 
                // then drawHeight will be finalImageHeight and 
                // drawWidth will be calculated by multiplying canvasCropWidth with same heightFactor
                if(widthFactor > heightFactor) {
                    drawWidth = canvasWidth * heightFactor;
                    drawHeight = this.finalImageHeight;
                } else {
                    drawWidth = this.finalImageWidth;
                    drawHeight = canvasHeight * widthFactor;
                }
            } else {
                if(widthFactor > heightFactor) {
                    drawWidth = this.finalImageWidth;
                    drawHeight = canvasHeight * widthFactor;
                } else {
                    drawWidth = canvasWidth * heightFactor;
                    drawHeight = this.finalImageHeight;
                }
            }

            
           
            __canvas.renderAll();

            if(this.scalingStretegy !== 'stretch') {
                drawingContext.drawImage(__canvas.getElement(), (this.finalImageWidth - drawWidth) / 2, (this.finalImageHeight - drawHeight) / 2, drawWidth, drawHeight );
            }
             else {
                drawingContext.drawImage(__canvas.getElement(), 0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
            }

            
        } else {
            let drawWidth, drawHeight;

            const cropSelectionRect = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');

            const canvasCropWidth = cropSelectionRect.getScaledWidth()
            const canvasCropHeight = cropSelectionRect.getScaledHeight()

            const widthFactor = this.finalImageWidth/canvasCropWidth;
            const heightFactor = this.finalImageHeight/canvasCropHeight;
            let factor = this.scalingStretegy === 'fit' ? Math.min(widthFactor, heightFactor) : Math.max(widthFactor, heightFactor);

            // we also need max factor here to account for the maximum canvas size that can be available
            const maxFactor = Math.min(this.maxCanvasWidth/__canvas.getWidth(), this.maxCanvasHeight/__canvas.getHeight());
            

            factor = Math.min(factor, maxFactor)

            this.scaleFabricCanvas(factor)

           
            
            
            if(this.scalingStretegy === 'fit') {
                // check if width factor is greater than height, minimum division is height 
                // then drawHeight will be finalImageHeight and 
                // drawWidth will be calculated by multiplying canvasCropWidth with same heightFactor
                if(widthFactor > heightFactor) {
                    drawWidth = canvasCropWidth * heightFactor;
                    drawHeight = this.finalImageHeight;
                } else {
                    drawWidth = this.finalImageWidth;
                    drawHeight = canvasCropHeight * widthFactor;
                }
            } else {
                if(widthFactor > heightFactor) {
                    drawWidth = this.finalImageWidth;
                    drawHeight = canvasCropHeight * widthFactor;
                } else {
                    drawWidth = canvasCropWidth * heightFactor;
                    drawHeight = this.finalImageHeight;
                }
            }

            __canvas.renderAll();
            
            //__canvas.getElement().getContext('2d').scale(3 ,3);
            if(this.scalingStretegy !== 'stretch') {
                drawingContext.drawImage(__canvas.getElement(), cropSelectionRect.left * factor , cropSelectionRect.top * factor, canvasCropWidth * factor, canvasCropHeight * factor, 
                    (this.finalImageWidth - drawWidth) / 2, (this.finalImageHeight - drawHeight) / 2,  drawWidth, drawHeight);
            } else {
                drawingContext.drawImage(__canvas.getElement(), cropSelectionRect.left * factor, cropSelectionRect.top * factor, canvasCropWidth * factor, canvasCropHeight * factor, 
                    0, 0,  this.drawingCanvas.width, this.drawingCanvas.height);
            }
            
        }

        __canvas.setDimensions(currentCanvasDimension)
        __canvas.setViewportTransform(currentVpt);
        __canvas.renderAll();
        __canvas.getElement().getContext('2d').filter = existingFilter;

        this.savedImageDownloadUrl = this.drawingCanvas.toDataURL(`image/${this.savedImageFormat}`, this.savedImageQuality/100);
    },
    async getBundleFileWithSize() {
        this.updateDownloadUrl();

        // saving procedure starts from this line after successfully getting dataurl
        const configuratorId = this.configuratorId;
        const locale = this.currentLocale;
        const eventId = this.apiConfig.eventId;

        try {
            const response = await fetch(this.savedImageDownloadUrl);
            if (!response.ok) {
                throw new Error(`HTTP error! status: ${response.status}`);
            }
            const blob = await response.blob();
            const bundleFile = new File([blob], `bundleimage-${configuratorId}--${eventId}-${locale}.${this.savedImageFormat}`, {
                type: `image/${this.savedImageFormat}`,
                lastModified: Date.now()
            });
            this.savedImageFileSize = (bundleFile.size / (1024 * 1024));
            return bundleFile;
            
        } catch(error) {
            console.error('Error downloading or creating file:', error);
            throw error;
        }

    },
    async saveCanvasImage() {
        const bundleFile = await this.getBundleFileWithSize();

        if(this.productId || this.configuratorId) {
            this.saveCanvasData(bundleFile).then(function(data) {
                this.triggerBundleFileEvent(bundleFile)
            }.bind(this))
        } else {
            this.triggerBundleFileEvent(bundleFile)
        }     
        
    },
    triggerBundleFileEvent(bundleFile) {
        const $modalStore = Alpine.store('modal');
        const $elementsStore = Alpine.store('elements');
        $modalStore.closeModal($elementsStore.containerModalEL);
        
        window.dispatchEvent(new CustomEvent(`bundle-image-processed-${this.apiConfig.eventId}`, {
            detail: {
                bundleFile: bundleFile
            },
            bubbles: true 
        }))
    },
    
    disableUndoRedoButtons() {
        Alpine.store('elements').historyUndo.disabled = !canvasHistory.getIndex();
        Alpine.store('elements').historyRedo.disabled = !(canvasHistory.getIndex() < canvasHistory.commands.length);
    },
    aiImageSrc: '',

})