interface SignData{
   base: string,
   base_product_id: string,
   base_template_id: string,
   content: string,
   dim: object,
   double_sided: string,
   font_data: object,
   font_url : string, 
   name : string,
   price : string,
   sign: object,
   site_url :string,
   sliders : string[],
   slugSign : string,
   template_data: object[],
   template_pricing:  object[]
}


jQuery(document).ready(function($){
    function deepCopy(data){
        return JSON.parse(JSON.stringify(data));
    }

    var USER_UPLOAD_DIR = sign_data.site_url + "/wp-content/uploads/user-uploads";
    var saveCustomProperties = ['signstudio_standard', 'id', 'stdnCustom', 'realSize', 'cstBorder', 'singleColor', 'templateName', 'thumbailShadow', 'user_image_id', 'svgUrl', 'removeOnSave'];
    let backgroundCanvas;
    let foregroundCanvas;
    //used to keep object variables when storing temps 
    fabric.Object.prototype.toObject = (function(toObject) {
        return function(propertiesToInclude) {
            propertiesToInclude = (propertiesToInclude || []).concat(saveCustomProperties);
            var originalObject = toObject.call(this, propertiesToInclude);
            var customProps = {};
            saveCustomProperties.forEach(function(prop) {
                if (this[prop] !== undefined) {
                    customProps[prop] = this[prop];
                }
            }, this);
            return fabric.util.object.extend(originalObject, customProps);
        };
    })(fabric.Object.prototype.toObject);

    class APIClient{
        constructor(ajaxob){
            this.root = ajaxob.root;
            this.nonce = ajaxob.nonce;
            if(typeof customer_site !== 'undefined' && customer_site){
                this.clientRoot = customer_site.root;
                this.clientNonce = customer_site.nonce;
            }
        }

        request(method, url, data = null, extraOptions = {}, contentType = false){
            const options = {
                method: method,
                url: this.root + url,
                headers: {'X-WP-Nonce': this.nonce},
                ...extraOptions,
            }

            if(data){
                if(contentType){
                    if(contentType != 'none'){
                        options.contentType = contentType;
                    }
                }else{
                  options.contentType = "application/json";
                  options.data = (typeof data === 'string') ? data : JSON.stringify(data);
                }
                options.data = data;
            }

            return new Promise((resolve, reject) => {
                $.ajax(options)
                    .done(response => {
                        resolve(response);
                    })
                    .fail((xhr, status, error) => {
                        reject(new Error(`API request failed: ${error}`));
                    });
            });
        }

        clientRequest(method, url, data = null, extraOptions = {}, contentType = false){
            const options = {
                method: method,
                url: this.clientRoot + url,
                headers: {'X-WP-Nonce': this.clientNonce},
                ...extraOptions,
            }

            if(data){
                if(contentType){
                    if(contentType !== 'none'){
                        options.contentType = contentType;
                    }
                } else {
                    options.contentType = "application/json";
                    options.data = JSON.stringify(data);
                }
            }

            return new Promise((resolve, reject) => {
                $.ajax(options)
                    .done(response => {
                        resolve(response);
                    })
                    .fail((xhr, status, error) => {
                        reject(new Error(`API request failed: ${error}`));
                    });
            });
        }

        async getSignFromCode(data){
            return this.request('GET', 'skyltar/v1/loadSignFromCode', data);
        }
        async sendFinishedDesign(data){
            return this.request('POST', 'skyltar/v1/sendFinisedDesign', data);
        }
        async userUpload(data){
            return this.request('POST', 'skyltar/v1/user_upload', data, {cache: false, processData: false, contentType: false}, 'none');
        }

        async getUserImages(){
            return this.request('GET', 'skyltar/v1/user_images');
        }

        async customerCreateSign(data){
            return this.clientRequest('POST', 'signstudio_extend/v1/new_sign', data);
        }
        async manageUserImages(data){
            return this.request('POST', 'skyltar/v1/manage_user_images', data);
        }
    }


   class OnCanvasGUIHandler{
      private canvas;
      private backgroundCanvas;
      private foregroundCanvas;
      private signOperations;
      private isMobile: boolean;
      private zoomedHeight: number;
      private zoomedWidth: number;

      private lastZoomValue: number;
      private animationFrameId: number;
      private timeoutId: any;

      private zoomConfig: object;

      constructor(canvas, signOperations, backgroundCanvas, foregroundCanvas){
         this.canvas = canvas;
         this.backgroundCanvas = backgroundCanvas;
         this.foregroundCanvas = foregroundCanvas;
         this.signOperations = signOperations;
         this.isMobile = false;
         this.zoomedHeight = 1;
         this.zoomedWidth = 1;

         this.lastZoomValue = 100;
         this.animationFrameId = null;
         this.timeoutId = null;
         this.handleZoom = this.handleZoom.bind(this);

         // Configuration options
         this.zoomConfig = {
            delay: 10,
            animationDuration: 150,
            useRequestAnimationFrame: true
         };

         this.initialize();

         this.allCoords;

      }

      initialize(){
         this.$window = $(window);
         this.getWindowHeight();

         this.objectControlElement = $(".object-controls"); 
         this.$magenetSnappingBtn = $('.magnet-snapping');
         this.$zoomRange = $( "#zoomrange");
         this.$zoomRangeText = $('#zoom-per span');
         this.$zoomRangeFullScreen = $("#zoomrange-full");
         this.$zoomRangeFullScreenText = $('#zoom-per-full span');
         this.$designArea = $('.design-area');
         this.$canvasContainer = $(".canvas-container");
         this.$canvasWrap = $('#canvas-wrap');
         this.$toolWrap = $('#toolwrap');
         this.$flipButton = $(".option-flip");
         this.binding();
      }

      binding(){
         this.$magenetSnappingBtn.on('click', () => {
            this.updateMagneticSnapping();
         });

         this.$window.on('resize', () => {
            this.windowResize();
         });
      }

      getWindowHeight(){
         this.$window.on('load', () => {
            // Code that needs to wait for the page to fully load goes here
            //this.vh = window.innerHeight * 0.01;
            //document.documentElement.style.setProperty('--vh', `${vh}px`);
         });
      }

      renderCanvas(){
         this.updateCanvasSize();
         //this.zoomCanvas();
         this.signOperations.requestCenterCanvas();
         //this.centerCanvasView();
         this.canvas.renderAll();
         this.menuCanvasSizeControl();
      }

      updateMagneticSnapping(){
         if (this.$magenetSnappingBtn.is('.active')) {
            this.$magenetSnappingBtn.removeClass('active');
            this.signOperations.setMagnetSnapping(false);
         } else {
            this.$magenetSnappingBtn.addClass('active');
            this.signOperations.setMagnetSnapping(true);
         }
      }


      updateDimensionInformation(){
         let distance = 55;
         let distance2 = 40;

         if(this.$window.width()<768){
            distance = 55;
            distance2 = 10;
         }

         //updates top nav size
         $('.top-tool-bar-width span').html(handleRealDim.getDim('width')+"mm");
         $('.top-tool-bar-height span').html(handleRealDim.getDim('height')+"mm");

         var maxDim = handleRealDim.getDim('width');
         if (handleRealDim.getDim('height') < handleRealDim.getDim('width'))
            maxDim = handleRealDim.getDim('height');

         var maxBorderThickness = Math.floor(maxDim / 4);
         $(".thicc-slider").attr("max", maxBorderThickness);
      }

      updateHeightText(height){
         this.$heightInfoWrpText.text(height + " mm");
      }

      updateWidthText(width){
         this.$widthInfoWrpText.text(width + " mm");
      }

      centerCanvasView(lastRun = false) {
         let backgroundShape = this.signOperations.getBackgroundShape();
         if (!backgroundShape) return;

         let objWidth = backgroundShape.getScaledWidth();
         let objHeight = backgroundShape.getScaledHeight();

         /*
         let dimensionsLines = this.signOperations.getDimensionLines();

         if(dimensionsLines.width){
            let lineGroupWidth = dimensionsLines.width.getScaledHeight();
            let additionalHeight = lineGroupWidth + dimensionsLines.width.top + lineGroupWidth/2 - objHeight;
            objHeight += additionalHeight;
         }
         if(dimensionsLines.height){
            let lineGroupHeight = dimensionsLines.height.getScaledWidth();
            let additionalWidth = lineGroupHeight + dimensionsLines.height.left + lineGroupHeight/2 - objWidth;
            objWidth += additionalWidth;
         }

*/
         let bgCanvasDim = $.canvasBgManager.getDim();
         let fgCanvasDim = $.canvasFgManager.getDim();

         let bgWidth = objWidth;
         let bgHeight = objHeight;

         let bgCW = objWidth;
         let bgCH = objHeight;
         let fgCW = objWidth;
         let fgCH = objHeight;

         if (bgCanvasDim) {
            if (bgCanvasDim.width > objWidth) bgWidth = bgCanvasDim.width;
            if (bgCanvasDim.height > objHeight) bgHeight = bgCanvasDim.height;
            bgCW = (objWidth > bgCanvasDim.width) ? objWidth : bgCanvasDim.width;
            bgCH = (objHeight > bgCanvasDim.height) ? objHeight : bgCanvasDim.height;
         }

         if (fgCanvasDim) {
            if (fgCanvasDim.width > bgWidth) bgWidth = fgCanvasDim.width;
            if (fgCanvasDim.height > bgHeight) bgHeight = fgCanvasDim.height;
            fgCW = (objWidth > fgCanvasDim.width) ? objWidth : fgCanvasDim.width;
            fgCH = (objHeight > fgCanvasDim.height) ? objHeight : fgCanvasDim.height;
         }

         let absolutCanvas = {
            left: Math.min(bgCanvasDim.left, fgCanvasDim.left, backgroundShape.left),
            right: Math.max(backgroundShape.width, fgCanvasDim.right, bgCanvasDim.right),
            top: Math.min(bgCanvasDim.top, fgCanvasDim.top, backgroundShape.top),
            bottom: Math.max(backgroundShape.height, fgCanvasDim.bottom, bgCanvasDim.bottom),
         }

         absolutCanvas['width'] = absolutCanvas['right'] - absolutCanvas['left'];
         absolutCanvas['height'] = absolutCanvas['bottom'] - absolutCanvas['top'];

         let zoomWidth = this.canvas.width / (bgWidth);
         let zoomHeight = this.canvas.height / (bgHeight);
         zoomWidth = this.canvas.width / (absolutCanvas['width']);
         zoomHeight = this.canvas.height / (absolutCanvas['height']);


         let newzoom = Math.min(zoomWidth, zoomHeight);


         if(backgroundShape.strokeWidth && backgroundShape.strokeWidth > 0){
            backgroundShape.strokeWidth = 1 / newzoom;
         }

         newzoom *= 0.95;


         let panX = 0
         let panY = 0

         //get center
         panX = ((this.canvas.getWidth() / newzoom / 2) - (absolutCanvas.width / 2) - absolutCanvas.left) * newzoom
         panY = ((this.canvas.getHeight() / newzoom / 2) - (absolutCanvas.height / 2) - absolutCanvas.top) * newzoom

         //panX = ((this.canvas.getWidth() / newzoom / 2) - (objWidth / 2)) * newzoom
         //panY = ((this.canvas.getHeight() / newzoom / 2) - (objHeight / 2)) * newzoom

         this.canvas.setViewportTransform([newzoom, 0, 0, newzoom, panX, panY]);
         this.backgroundCanvas.setViewportTransform([newzoom, 0, 0, newzoom, panX, panY]);
         this.foregroundCanvas.setViewportTransform([newzoom, 0, 0, newzoom, panX, panY]);

         $('#zoom-per span').text(95);
         this.$zoomRange.slider({ value: 95 });
         this.$zoomRangeFullScreenText.slider({ value: 95 });

         this.updateDimensionInformation();
         this.canvas.requestRenderAll();
      }


      checkforCursorType(event){
         let backgroundShape = this.signOperations.getBackgroundShape();
         //checks for panning
         if(event.target != null && event.target.selectable == false){
            let yRatio = (backgroundShape.height+10)*canvas.getZoom()/canvas.getHeight();
            let xRatio = (backgroundShape.width+10)*canvas.getZoom()/canvas.getWidth();
            if(xRatio > 1 || yRatio > 1){
               event.target.hoverCursor = "grab";
               return true;
            }else{
               return event.target.hoverCursor = "default";
            }
         }else{
            if (event.target == null) return;

            if (event.target.type == "i-text") {
               event.target.hoverCursor = "pointer";
            }else if(event.target.selectable == true){
               event.target.hoverCursor = "move";
            }
         }
      }

      updateObjectControl(dontdisplay){
         let object = this.canvas.getActiveObject();
         //objectControlElement set in init 
         if(object){
            if(dontdisplay == true){
               this.objectControlElement.addClass('dont-display');
            }else{
               this.objectControlElement.removeClass('dont-display');
               let box = object.getBoundingRect();
               let vpt = this.canvas.viewportTransform;
               let zoom = this.canvas.getZoom();

               let controlWidthHalf = this.objectControlElement.outerWidth(true)/2;
               let top = box.top + box.height+ 10;
               let left = box.left + box.width/2 - controlWidthHalf;
               let right = box.left + box.width/2 + controlWidthHalf; 

               let canvasLeft = this.$canvasContainer.offset().left;
               let canvasWrpRight = this.$canvasWrap.outerWidth();

               if(this.isMobile){
                  if (left < 0 && (canvasLeft + left) < 10){
                     left = 10 - canvasLeft;
                  }else if(right > canvasWrpRight-10){
                     left = canvasWrpRight-controlWidthHalf*2;
                  }
               }

               if(top > this.$canvasWrap.height()){
                  top = this.$canvasWrap.height();
               }

               this.objectControlElement.css({
                  'left': left +"px",
                  'top' :top +"px"
               });
            }
         }else{
            this.objectControlElement.addClass('dont-display');
         }
      }
      removeHelperLines(){
         this.canvas.remove(helperLine);
         this.canvas.remove(helperLineV);
         this.canvas.remove(helperLineObjV);
         this.canvas.remove(helperLineObjH);
      }

      zoomCanvas() {
         return;
         let backgroundShape = this.signOperations.getBackgroundShape();
         let canvas = this.canvas;

         let bgWidth = backgroundShape.width;
         let bgHeight = backgroundShape.height;

         let bgCanvasDim = $.canvasBgManager.getDim();
         let fgCanvasDim = $.canvasFgManager.getDim();

         if (bgCanvasDim) {
            if (bgCanvasDim.width > bgWidth) bgWidth = bgCanvasDim.width;
            if (bgCanvasDim.height > bgHeight) bgHeight = bgCanvasDim.height;
         }

         if (fgCanvasDim) {
            if (fgCanvasDim.width > bgWidth) bgWidth = fgCanvasDim.width;
            if (fgCanvasDim.height > bgHeight) bgHeight = fgCanvasDim.height;
         }


         //set to 85% as we dont have the function to get the widht and height of dimension displays
         let zoomWidth = canvas.width / (bgWidth + 10);
         let zoomHeight = canvas.height / (bgWidth + 10);


         var newzoom = 0;

         if (zoomWidth < zoomHeight) {
            newzoom = zoomWidth;
         } else {
            newzoom = zoomHeight;
         }


         let zoomPoint = new fabric.Point(canvas.width / 2, canvas.height / 2);

         canvas.zoomToPoint(zoomPoint, newzoom);
         if (bgCanvasDim) {
            this.backgroundCanvas.zoomToPoint(zoomPoint, newzoom);
         }
         this.foregroundCanvas.zoomToPoint(zoomPoint, newzoom);
         canvas.requestRenderAll();

         this.$zoomRange.slider({ range: "min", value: 85 });
         this.$zoomRangeText.text(85);

         this.$zoomRangeFullScreen.slider({ range: "min", value: 85 });
         this.$zoomRangeFullScreenText.text(85);

         this.updateObjectControl();
      }


      userZoom(zoom = false, updatesliders = false) {
         if (!zoom) zoom = parseInt($('#zoom-per span').text());

         if(zoom < 50) zoom = 50;
         if(zoom > 500) zoom = 500;

         let backgroundShape = this.signOperations.getBackgroundShape();
         if (!backgroundShape) return;
         let canvas = this.canvas;
         let zoomPercentage = 100 / zoom;
         this.$zoomRangeText.text(Math.round(zoom));
         this.$zoomRangeFullScreenText.text(Math.round(zoom));

         if (updatesliders) {
            this.$zoomRange.slider({ value: zoom });
            this.$zoomRangeFullScreenText.slider({ value: zoom });
         }

         let rect = backgroundShape.getBoundingRect(true, true);
         let objWidth = rect.width;
         let objHeight = rect.height;

         let bgCanvasDim = $.canvasBgManager.getDim();
         let fgCanvasDim = $.canvasFgManager.getDim();

         let bgWidth = objWidth;
         let bgHeight = objHeight;

         if (bgCanvasDim) {
            if (bgCanvasDim.width > bgWidth) bgWidth = bgCanvasDim.width;
            if (bgCanvasDim.height > bgHeight) bgHeight = bgCanvasDim.height;
         }

         if (fgCanvasDim) {
            if (fgCanvasDim.width > bgWidth) bgWidth = fgCanvasDim.width;
            if (fgCanvasDim.height > bgHeight) bgHeight = fgCanvasDim.height;
         }

         //absolute values from center 
         let absolutCanvas = {
            left: Math.min(bgCanvasDim.left, fgCanvasDim.left, backgroundShape.left),
            right: Math.max(backgroundShape.width, fgCanvasDim.right, bgCanvasDim.right),
            top: Math.min(bgCanvasDim.top, fgCanvasDim.top, backgroundShape.top),
            bottom: Math.max(backgroundShape.height, fgCanvasDim.bottom, bgCanvasDim.bottom),
         }

         absolutCanvas['width'] = absolutCanvas['right'] - absolutCanvas['left'];
         absolutCanvas['height'] = absolutCanvas['bottom'] - absolutCanvas['top'];
         let zoomWidth = canvas.width / (absolutCanvas.width);
         let zoomHeight = canvas.height / (absolutCanvas.height);

         let newzoom = 0;

         if (zoomWidth < zoomHeight) {
            newzoom = zoomWidth;
         } else {
            newzoom = zoomHeight;
         }

         let setZoom = (newzoom * (zoom / 100));
         let zoomingIn = (canvas.getZoom() < setZoom);
         //let newZoomPoint = new fabric.Point(canvas.width / 2, canvas.height / 2);
         let vpt = canvas.viewportTransform;

         let xVPT = false;
         let yVPT = false;

         let screenX = 0;
         let screenY = 0;

         let canvasWidth = canvas.getWidth();
         let canvasHeight = canvas.getHeight();

         let zoomedWidth = absolutCanvas.width * setZoom;
         //zoomedWidth = objWidth * setZoom;

         let centerX = absolutCanvas.left + absolutCanvas.width / 2;
         screenX = (centerX * vpt[0]) + vpt[4];

         if (zoomedWidth > canvasWidth) {
            if (!zoomingIn) { //IF we are zooming in we do not need to care about keeping edges in view
               let right = vpt[4] + (absolutCanvas.right) * setZoom;
               let left = vpt[4] + (absolutCanvas.left) * setZoom;

               let bothOutOfBoundX = (left < 0 && right > canvasWidth);

               //if not both is outof bound take closes to edge to zero
               if (!bothOutOfBoundX && right < canvasWidth) {
                  //out of bound left side
                  xVPT = vpt[4] + (canvasWidth - right);
               } else if (!bothOutOfBoundX && vpt[4] >= left) {
                  //out of bound left side
                  xVPT = vpt[4] - left;
               }
            }
         } else {
            xVPT = ((canvasWidth / setZoom / 2) - (absolutCanvas.width / 2) - absolutCanvas.left) * setZoom;
            xVPT = ((canvasWidth / setZoom / 2) - (objWidth / 2)) * setZoom;
            //let orginVPTx = ((canvasWidth) - (objWidth*setZoom))/2;
            //screenX = canvas.width/2*setZoom;
            //xVPT = orginVPTx;
         }

         let zoomedHeight = absolutCanvas.height * setZoom;
         // zoomedHeight = objWidth * setZoom;


         let centerY = (absolutCanvas.height / 2) + absolutCanvas.top;
         screenY = (centerY * vpt[0]) + vpt[5];

         if (zoomedHeight > canvasHeight) {
            if (!zoomingIn) {
               let bottom = vpt[5] + (absolutCanvas.bottom) * setZoom;
               let top = vpt[5] + (absolutCanvas.top) * setZoom;
               let bothOutOfBound = (top < 0 && bottom > canvasHeight);

               if (!bothOutOfBound && top <= 0 && bottom < canvasHeight) { //if bottom is out of bound
                  yVPT = vpt[5] + (canvasHeight - bottom);
               } else if (!bothOutOfBound && top >= 0 && bottom > canvasHeight) { // if top is out of bound
                  yVPT = vpt[5] - top;
               }
            }
         } else {
            yVPT = ((canvasHeight / setZoom / 2) - (absolutCanvas.height / 2) - absolutCanvas.top) * setZoom;
            yVPT = ((canvasHeight / setZoom / 2) - (objHeight / 2)) * setZoom;
            //let orginVPTy = ((canvasHeight)-(zoomedHeight))/2;
            //yVPT = orginVPTy;
         }


         let newZoomPoint = new fabric.Point(screenX, screenY);
         canvas.zoomToPoint(newZoomPoint, setZoom);
         this.backgroundCanvas.zoomToPoint(newZoomPoint, setZoom)
         this.foregroundCanvas.zoomToPoint(newZoomPoint, setZoom)
         if (!zoomingIn && (yVPT || xVPT || yVPT === 0 || xVPT === 0)) {
            vpt = canvas.viewportTransform;

            if (yVPT || yVPT === 0) {
               vpt[5] = yVPT;
            }
            if (xVPT || xVPT === 0) {
               vpt[4] = xVPT;
            }
            canvas.setViewportTransform(vpt);
            this.backgroundCanvas.setViewportTransform(vpt);
            this.foregroundCanvas.setViewportTransform(vpt);
         }

         //doesnt allow left of canvas to be outside
         canvas.requestRenderAll();
         this.backgroundCanvas.requestRenderAll();
         this.foregroundCanvas.requestRenderAll();
         onCanvasGUIHandler.updateDimensionInformation();
      }


      animateZoom(startZoom, targetZoom, startTime, duration) {
         const currentTime = Date.now();
         const elapsed = currentTime - startTime;

         if (elapsed < duration) {
            const progress = elapsed / duration;
            const easedProgress = 0.5 * (1 - Math.cos(Math.PI * progress));
            const currentZoom = startZoom + (targetZoom - startZoom) * easedProgress;

            this.userZoom(currentZoom);

            this.animationFrameId = requestAnimationFrame(() => 
               this.animateZoom(startZoom, targetZoom, startTime, duration)
            );
         } else {
            this.userZoom(targetZoom);
            this.animationFrameId = null;
         }
      }

      handleZoom(newZoomValue) {
         // Cancel any ongoing animations
         if (this.animationFrameId) {
            cancelAnimationFrame(this.animationFrameId);
            this.animationFrameId = null;
         }
         if (this.timeoutId) {
            clearTimeout(this.timeoutId);
            this.timeoutId = null;
         }

         // Debounce the zoom action
         this.timeoutId = setTimeout(() => {
            if (this.zoomConfig.useRequestAnimationFrame) {
               this.animateZoom(
                  this.lastZoomValue, 
                  newZoomValue, 
                  Date.now(), 
                  this.zoomConfig.animationDuration
               );
            } else {
               this.userZoom(newZoomValue);
            }
            this.lastZoomValue = newZoomValue;
         }, this.zoomConfig.delay);
      }


      menuCanvasSizeControl(hide){
         if(this.isMobile) return;
         if(!hide){
            hide = (this.$toolWrap.hasClass('dont-display')) ? true : false;
         }

         //let zoom = this.$zoomRangeText.text();
         if(hide){
            this.$designArea.css('padding-left', 0+"px");
         }else{
            let pad = this.$toolWrap.outerWidth() + 40;
            this.$designArea.css('padding-left', pad+"px");
         }
         this.updateCanvasSize();
         let zoom = $('#zoom-per span').text(); 

         let canvasZoom = this.canvas.getZoom();
         //let backgroundShape = this.canvas.getBackgroundShape();
         signOperation.requestCenterCanvas();
         //	this.userZoom(zoom);
      }

      getBackgroundLeft(){
         let object = this.signOperations.getBackgroundShape();;
         let zoom = this.signOperations.getCanvas().getZoom();
         let transform = this.canvas.viewportTransform;
         let objectLeft = object.left
         let objectLeftPx = objectLeft * zoom + transform[4];
         return objectLeftPx;
      }

      updateCanvasSize() {
         this.canvas.setHeight(0);
         this.canvas.setWidth(0);
         let height = this.$canvasContainer.outerHeight();
         let width = this.$canvasContainer.outerWidth();
         this.canvas.setHeight(height);
         this.canvas.setWidth(width);

         MAX_SIGN_HEIGHT = this.canvas.getHeight();
         MAX_SIGN_WIDTH = this.canvas.getWidth();
         this.backgroundCanvas.setHeight(height).setWidth(width);
         this.foregroundCanvas.setHeight(height).setWidth(width);

      }

      updateFlipButton(shape = false) {
         if(!shape){
            shape = this.canvas.getActiveObject();
         }
         if (shape["flipX"]) {
            this.$flipButton.addClass("active");
         } else {
            this.$flipButton.removeClass("active");
         }
      }


      windowResize(){
         toolMenu.resizeCanvasToOverFold();
         const MIN_KEYBOARD_HEIGHT = 250 // N.B.! this might not always be correct
         let isKeyboardOpen = false;
         this.isMobile = (this.$window.innerWidth() < 768) ? true : false;

         //alert(screen.height);
         //let isKeyboardOpen = isMobile && ($(window).innerHeight() - MIN_KEYBOARD_HEIGHT > $(window).height());
         if(this.isMobile){
            //isKeyboardOpen = isMobile &&  screen.height - MIN_KEYBOARD_HEIGHT >  visualViewport.height;
            if($(document.activeElement).prop('type') === 'textarea') {
               isKeyboardOpen = true;
            }
         }

         //this.vh = this.$window.innerHeight() * 0.01;
         //document.documentElement.style.setProperty('--vh', `${this.vh}px`);

         if(!isKeyboardOpen){
            this.renderCanvas();
         }
      }



      calculateAndDrawHelperLines(object, direction, backgroundShape, moving, magnetSnapping) {
         let objBounding = object.getBoundingRect(true,true);
         let objectCenterV = object.getCenterPoint().x;
         let objectCenterH = object.getCenterPoint().y;
         let objLeft = objBounding.left;
         let objWidth = objBounding.width;
         let objRight = objBounding.left + objWidth;
         let objTop = objBounding.top;
         let objHeight = objBounding.height;
         let objBottom = objBounding.top + objHeight;
         let objectMarCor = (objHeight > objWidth) ? objWidth*0.08 : objHeight*0.08;
         let zoom = this.canvas.getZoom();
         let newCoords = {"x" :0, "y":0}
         let objectCoords = {'lefts' : objLeft, 
            'rights' : objRight,
            'tops' : objTop,
            'bottoms' : objBottom,
            'centersV' : objectCenterV,
            'centersH' : objectCenterH}
         var centerLine  = backgroundShape.getCenterPoint().x;

         var distanceFromHorizontalCenter = Math.abs(centerLine - objectCenterV);

         this.allCoords = {lefts : [], tops : [], rights :[], bottoms : [], centersV: [], centersH: []}

         const auto_size_h = handleRealDim.getDim('auto_size_h');
         const auto_size_w = handleRealDim.getDim('auto_size_w');
         //only check objects once if no auto size
         if(!moving && auto_size_h != true && auto_size_w != true){
            //get all objects for snapping to right, middle, left
            let allObjects = this.canvas.getObjects();
            let exludeObj = ['bg', 
               "custom_sign_background_prod", 
               "custom_sign_background",
               "helperlineH",
               "helperlineV",
               "helperLineObjV",
               "helperLineObjH"]

            $.each(allObjects, (index, obj) => {
               if(object != obj && obj.signstudio_standard != true && !exludeObj.includes(obj.name)){
                  let bounding = obj.getBoundingRect(true,true);
                  this.allCoords.lefts.push(bounding.left);
                  this.allCoords.tops.push(bounding.top);
                  this.allCoords.rights.push(bounding.left + bounding.width);
                  this.allCoords.bottoms.push(bounding.top + bounding.height);
                  this.allCoords.centersV.push(bounding.left + bounding.width/2);
                  this.allCoords.centersH.push(bounding.top + bounding.height/2);
               }
            });
         }


         let lineThickness = 1 / zoom;

         if (distanceFromHorizontalCenter < 8 && auto_size_h != true) {

            if (helperLineV.canvas == null){
               this.canvas.add(helperLineV);
            } 
            helperLineV.set({ left: centerLine-(lineThickness/2), width: lineThickness, height: backgroundShape.height});
            if(magnetSnapping){
               //centerObjectVertically(object);
               signOperation.positionVertical(object);
            }
         } else {
            this.canvas.remove(helperLineV);
         }

         var centerLine  = backgroundShape.getCenterPoint().y;
         var distanceFromVerticalCenter = Math.abs(centerLine - objectCenterH);

         if (distanceFromVerticalCenter < 8 && auto_size_w != true) {

            if (helperLine.canvas == null){
               this.canvas.add(helperLine);
            } 
            helperLine.set({ top: centerLine-(lineThickness/2), height: lineThickness, width:backgroundShape.width});
            if(magnetSnapping){
               signOperation.positionHorizontal(object);
               //centerObjectHorizontally(object);
            }
         } else {
            this.canvas.remove(helperLine);
         }

         if(magnetSnapping){
            //align with objects
            let checkDirections = [];
            if(direction.x != 0){
               checkDirections.push('lefts','rights', 'centersV');
            }
            if(direction.y != 0){
               checkDirections.push('tops','bottoms','centersH');
            }


            let snapToObj = {'lefts' : {'state' : false, 'distance': 0, 'coords': 0, 'align' : 'x', 'func' : 'left'}, 
               'rights' : {'state' : false, 'distance': 0, 'coords': 0, 'align' : 'x', 'func' : 'right'},
               'centersV' : {'state' : false, 'distance': 0, 'coords': 0, 'align' : 'x', 'func' : 'cenV'}, 
               'tops' :{'state' : false, 'distance': 0, 'coords': 0, 'align' : 'y', 'func' :'top' },
               'bottoms' : {'state' : false, 'distance': 0 , 'coords': 0, 'align' : 'y', 'func' : 'bot'},
               'centersH' :{'state' : false, 'distance': 0, 'coords': 0, 'align' : 'y', 'func':'cenH'}}

            $.each(checkDirections, (ind, obj) => {
               snapToObj[obj].state = this.allCoords[obj].some((value) => {
                  snapToObj[obj].distance = Math.abs(value - objectCoords[obj]);
                  snapToObj[obj].coords = value;
                  return (Math.abs(value - objectCoords[obj]) <= objectMarCor);	   
               });
            });

            let lowestXDistance = Object.values(snapToObj).filter(obj => obj.state == true && obj.align == 'x');
            (lowestXDistance.length !== 0) ? lowestXDistance = lowestXDistance.reduce((prev,curr) => (prev.distance < curr.distance) ? prev : curr) : false;

            let lowestYDistance = Object.values(snapToObj).filter(obj => obj.state == true && obj.align == 'y');
            (lowestYDistance.length !== 0) ? lowestYDistance = lowestYDistance.reduce((prev,curr) => (prev.distance < curr.distance) ? prev : curr) : false;

            //snapping is being done
            if(Object.keys(lowestXDistance).length > 0){
               let left = 0; 
               switch(lowestXDistance.func){
                  case 'left':
                     left = lowestXDistance.coords + objWidth/2;
                     break;
                  case 'right':
                     left = lowestXDistance.coords - objWidth/2;
                     break;
                  case 'cenV':
                     left = lowestXDistance.coords;
                     break;	
               }
               if (helperLineObjV.canvas == null){
                  canvas.add(helperLineObjV);
               }
               helperLineObjV.set({x1 : lowestXDistance.coords, 
                  y1: 0, 
                  x2: lowestXDistance.coords, 
                  y2: backgroundShape.height})
               let LcentY = object.getCenterPoint().y
               newCoords = {"x" :left, "y":LcentY}
               object.setPositionByOrigin(newCoords, 'center', 'center');
               this.canvas.requestRenderAll();
            }else{
               this.canvas.remove(helperLineObjV);
            }

            if(Object.keys(lowestYDistance).length > 0){
               let top = 0;
               switch(lowestYDistance.func){
                  case 'top':
                     top = lowestYDistance.coords + objHeight/2;
                     break;
                  case 'bot':
                     top = lowestYDistance.coords - objHeight/2;
                     break;
                  case 'cenH':
                     top = lowestYDistance.coords;	
                     break;
               }
               if (helperLineObjH.canvas == null){
                  this.canvas.add(helperLineObjH);
               }
               helperLineObjH.set({x1 : 0, 
                  y1: lowestYDistance.coords, 
                  x2: backgroundShape.width, 
                  y2: lowestYDistance.coords});
               //
               //object.set("top", top).setCoords();
               let LcentX = object.getCenterPoint().x
               newCoords = {"x" :LcentX, "y":top}
               object.setPositionByOrigin(newCoords, 'center', 'center');
               this.canvas.renderAll();
            }else{
               this.canvas.remove(helperLineObjH);
            }
         }
         this.canvas.renderAll();
      }

   }

   let onCanvasGUIHandler: OnCanvasGUIHandler;


   class HandleRealDimensionsClass {
      private realDimensions: object;
      private sideDims: string[];
      private MIN_WIDTH: number;
      private MIN_HEIGHT: number;

      constructor() {
         this.realDimensions = {
            width: 500,
            height: 500,
            material: 1,
            border: 0,
            cornerRadius: null,
            background: "#fff",
            shape: "rect",
            borderColor: "#fff",
            mounting: "none",
            mountingLayout: false,
            mountdim: false,
            engraving: false,
            mount_amount: 0,
            mountImage: false,
            surface: "blank",
            special_sign: false,
            double_sided: false,
            special: false,
            auto_size_h: false,
            auto_size_w: false,
            rotated: false,
         };

         this.sideDims = ['border', 'background', 'borderColor'];

         this.MIN_WIDTH = 30;
         this.MIN_HEIGHT = 30;
      }

      updateDim(key: string, value: any) {
         if (key in this.realDimensions) {
            let currentDim = this.realDimensions;

            /*
            if (currentDim.hasOwnProperty('double_sided') && (currentDim.double_sided && currentDim.double_sided == 1)) {
               if (signSidesManager.getActiveSide() !== 1 && this.sideDims.includes(key)) {
                  currentDim = signSidesManager.getSideDim();
               } else {
                  //should be side one 
                  if (signSidesManager.getSideDim() !== this.realDimensions) {
                     signSidesManager.connectSideOneDim(this.realDimensions);
                  }
               }
            }
            */
            currentDim[key] = value;
         } else {
            console.warn(`Key: ${key} not initialized in realDimensions. Consider adding it first.`);
         }
      }

      updateDimsObj(dimensionsObj) {
         Object.entries(dimensionsObj).forEach(([key, value]) => {
            this.updateDim(key, value);
         });
      }

      setRealdimensions(value) {
         if(typeof value === 'object'){
            this.realDimensions = { ...this.realDimensions, ...value };
         }
      }

      getDim(key:string | string[]) {
         if (!key) return false;

         if (Array.isArray(key)) {
            let dimObject = {};
            for (let i = 0; i < key.length; i++) {
               let type = key[i]
               dimObject[type] = this.realDimensions[type];
            }
            return dimObject;
         } else {
            return this.realDimensions[key];
         }
      }
      getAllDim() {
         return this.realDimensions;
      }

      hasDim(key) {
         return key in this.realDimensions;
      }

      updateWidth(value) {
         value = parseInt(value);
         if (value < this.MIN_WIDTH) {
            value = this.MIN_WIDTH;
         }
         this.updateDim('width', value);
      }

      updateHeight(value) {
         value = parseInt(value);
         if (value < this.MIN_HEIGHT) {
            value = this.MIN_HEIGHT;
         }
         this.updateDim('height', value);
      }
   }

   let handleRealDim: HandleRealDimensionsClass;

    class TemporaryCanvas {
        constructor(widthPx = false, heightPx = false, bg=false) {
            this.canvas = new fabric.Canvas(document.createElement('canvas'));
            this.width = 0;
            this.height = 0;
            this.background = null;

            if (widthPx && heightPx) {
                this.setCanvasSize(widthPx, heightPx);
            }

            if(bg){
                this.addBg(bg);
            }
        }

        getCanvas() {
            return this.canvas;
        }

        setCanvasSize(widthPx, heightPx) {
            this.canvas.setDimensions({ width: widthPx, height: heightPx });
            this.width = widthPx;
            this.height = heightPx;
        }

        addBg(color){
            if(this.background){
                this.canvas.remove(this.background);
            }

            if(!this.width || !this.height){
                console.log('cant set background no size');
                return;
            }

            const rect = new fabric.Rect({
                left: 0,
                top: 0,
                width: this.width,
                height: this.height,
                fill: color,
                selectable: false,
                evented: false,
                excludeFromExport: false
            });

            this.background = rect;
            this.canvas.add(rect);
            this.canvas.sendToBack(rect);
        }



        getSvg() {
            return this.canvas.toSVG();
        }

        getPng(){
            try {
                let dataURL = this.canvas.toDataURL({
                    format: "png",
                    left: 0,
                    quality: 1,
                    top: 0,
                    width: this.width ,
                    height: this.height ,
                });

                return dataURL;

            }catch(error){
                console.log('Error', error);
                return false;
            }
        }

        initPNGDownload(dataURL){
            function dataURLToBlob(dataURL) {
                const arr = dataURL.split(',');
                const mime = arr[0].match(/:(.*?);/)[1];
                const bstr = atob(arr[1]);
                let n = bstr.length;
                const u8arr = new Uint8Array(n);
                while (n--) {
                    u8arr[n] = bstr.charCodeAt(n);
                }
                return new Blob([u8arr], { type: mime });
            }

            // Test download
            const blob = dataURLToBlob(dataURL);
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'fabric-export.png';
            a.click();
        }

        dispose() {
            this.canvas.dispose();
        }

        async addContent(jsonContent, maxWidth = false, maxHeight = false, addBgShadow = false) {
            if (!jsonContent) {
                console.log('No content provided for adding to tempcanvas');
                return;
            }

            // Set max dimensions
            if (!maxWidth || maxWidth > this.width) {
                maxWidth = this.width;
            }
            if (!maxHeight || maxHeight > this.height) {
                maxHeight = this.height;
            }

            // Parse the JSON content
            const parsedContent = typeof jsonContent === 'string' ? JSON.parse(jsonContent) : jsonContent;

            // Wrap enlivenObjects in a Promise
            return new Promise((resolve, reject) => {
                fabric.util.enlivenObjects(parsedContent.objects, (objects) => {
                    let extrasOffsetSign = { left: 0, top: 0}

                    try {
                        // Calculate the bounding box of all objects
                        let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;

                        let transparentAdded = false;


                        objects.forEach(obj => {

                            const bounds = obj.getBoundingRect(true, true);

                            if(addBgShadow){

                                if(obj?.id){
                                    if((obj.id === "backgroundShape" ||
                                        obj.id === "backgroundInnerShape" ||
                                        obj.id === 'addShadow') 
                                        && obj.fill !== 'transparent' 
                                        && !transparentAdded
                                    ){

                                        const scaledWidth = bounds.width;
                                        const scaledHeight = bounds.height;
                                        const diagonal = Math.sqrt(scaledWidth * scaledWidth + scaledHeight * scaledHeight);

                                        const shadowSize = diagonal * 0.01; // Direct percentage

                                        transparentAdded = true;
                                        obj.set('shadow', new fabric.Shadow({
                                            color: 'rgba(0,0,0,0.4)',
                                            blur: shadowSize * 2.5,
                                            offsetX: shadowSize,
                                            offsetY: shadowSize * 1.2
                                        }));

                                    }
                                }

                            }

                            if(obj?.name && obj.name === 'signGroup' && obj?.clipPath){
                                //we are going to find the extra or less left
                                let signCoords = {left: 0, top: 0, right: 0, bottom: 0};
                                let signCoordsDiff = {left: 0, top: 0, right: 0, bottom: 0};
                                let cropObject = false;

                                $.each(obj._objects,(i,ob) => {
                                    //get background and extracts corrct left and top offset
                                    const bb = ob.getBoundingRect(true, true);

                                    if(!cropObject && ob?.fill && ob?.fill !== "transparent"){
                                        cropObject = ob;

                                        signCoords = {
                                            left: bb.left,
                                            top: bb.top,
                                            right: bb.left + bb.width,
                                            bottom: bb.top + bb.height
                                        }

                                        signCoordsDiff = {...signCoords}

                                        return;
                                    }

                                    if(cropObject){
                                        signCoordsDiff = {
                                            left: Math.min( bb.left, signCoordsDiff.left),
                                            top: Math.min( bb.top, signCoordsDiff.top),
                                            right: Math.max( (bb.left + bb.width), signCoordsDiff.right),
                                            bottom: Math.max( (bb.top + bb.height), signCoordsDiff.bottom),
                                        }
                                    }
                                });

                                const leftDiff = signCoords.left - signCoordsDiff.left;
                                const topDiff = signCoords.top - signCoordsDiff.top;
                                const rightDiff = signCoordsDiff.right - signCoords.right;
                                const bottomDiff = signCoordsDiff.bottom - signCoords.bottom;

                                extrasOffsetSign = {
                                    left: leftDiff - rightDiff,
                                    top: topDiff - bottomDiff,
                                }

                            }

                            //devided by two due to center postion..I think
                            const adjL = bounds.left + extrasOffsetSign.left/2;
                            const adjT = bounds.top + extrasOffsetSign.top/2;

                            minX = Math.min(minX, adjL);
                            minY = Math.min(minY, adjT);
                            maxX = Math.max(maxX, adjL + bounds.width);
                            maxY = Math.max(maxY, adjT + bounds.height);
                        });

                        const contentWidth = maxX - minX;
                        const contentHeight = maxY - minY;

                        // Calculate scaling factor to fit within maxWidth/maxHeight
                        const scaleX = maxWidth / contentWidth;
                        const scaleY = maxHeight / contentHeight;
                        const scale = Math.min(scaleX, scaleY, 1); // Don't scale up, only down

                        // Calculate center position
                        const centerX = this.width / 2;
                        const centerY = this.height / 2;

                        // Calculate offset to center the content
                        const scaledWidth = contentWidth * scale;
                        const scaledHeight = contentHeight * scale;
                        const offsetX = centerX - scaledWidth / 2 - (minX * scale);
                        const offsetY = centerY - scaledHeight / 2 - (minY * scale);

                        // Apply transformations and add objects to canvas
                        objects.forEach(obj => {
                            // Scale the object
                            obj.scaleX = (obj.scaleX || 1) * scale;
                            obj.scaleY = (obj.scaleY || 1) * scale;

                            // Reposition to center
                            obj.left = (obj.left * scale) + offsetX;
                            obj.top = (obj.top * scale) + offsetY;

                            // Add to canvas
                            this.canvas.add(obj);
                        });

                        // Render the canvas
                        this.canvas.renderAll();

                        // Resolve the promise
                        resolve(objects);
                    } catch (error) {
                        reject(error);
                    }
                });
            });
        }
    }

    class ManagingSign{

      private apiClient: APIClient;
      private sign_data: SignData;
      private base_id: string;
      private base_template;
      private uploadImageManager: uploadImageManager;
      private control_templates;
      private svgChecks;
      private isMobile: boolean;
      private imageObjects;
      private convertedsign: object;
      private originalObjects: boolean;
      private dim: object;
      private template;
      private templateObjects: object;
      private originalTempalteObjects: object; 
      private templateData: object;
      private templateInputs: object;
      private canvas;
      private canvasContainer: boolean;
      private signRatio: number;
      private signResizeTemp: boolean;
      private originalWidth: number;
      private originalHeight: number;
      private originalBorder: number;
      private changedSizeScales: boolean;
      private backgroundShape;
      private backgroundInnerShape;
      private bg_group_special;
      private bgGroupSpecialProduction;
      private templateImage;
      private $textInputs: JQuery;
      private $imageButton: JQuery;
      private imageUpload: JQuery;
      private $changeTemplate: JQuery;
      private $templateToolsWrp: JQuery;
      private $buyButtons: JQuery;
      private $addToCart: JQuery;
      private $goToCheckOut: JQuery;
      private $goToStudio: JQuery;
      private isProductProcessing: false;
      private backgroundCanvas;
      private foregroundCanvas;

        constructor(apiClient: APIClient, uploadImageManager: UploadImage, sign_data : SignData, control_templates, svgCheck){
            this.apiClient = apiClient;
            this.sign_data = sign_data;
            this.base_id = sign_data.base_id;
            this.base_template = sign_data.base_template;
            this.uploadImageManager = uploadImageManager;
            this.control_templates = control_templates;
            this.svgCheck = svgCheck;
            this.isMobile = false;
            this.imageObjects = (this.sign_data.sliders) ? this.sign_data.sliders : [];
            this.addImageSliders();
            this.convertedsign = this.sign_data.sign;
            this.originalObjects = false;
            this.dim = this.sign_data.dim;
            this.template = this.reconstructTemplateData(this.sign_data.template_data);
            this.templateObjects = {};
            this.originalTempalteObjects = {}; 
            this.templateData = {};
            this.templateInputs = {};
            this.canvas;
            this.canvasContainer = false;
            this.signRatio = this.dim['height'] / this.dim['width'];
            this.signResizeTemp = false;
            this.originalWidth = parseInt(this.dim['width']);
            this.originalHeight = parseInt(this.dim['height']);
            this.originalBorder= parseInt( this.dim['border']);
            this.changedSizeScales = false;
            this.backgroundShape;
            this.backgroundInnerShape;
            this.bg_group_special;
            this.bgGroupSpecialProduction;
            this.templateImage;
            this.$textInputs = $('.quick-text');
            this.$imageButton = $('.quick-upload');
            this.imageUpload = $('#upload-image');
            this.$changeTemplate = $('.quick-template');
            this.$templateToolsWrp = $('#quick-right-spacing');
            this.$buyButtons = $('#quick-buy-button-wrp');
            this.$addToCart = $('#quick-add-to-cart');
            this.$goToCheckOut = $('#quick-to-checkout');
            this.$goToStudio = $('#quick-to-studio');
            this.isProductProcessing = false;

            //Canvasbinds
            this.cropClick = false;
            this.originalPosition = false;
            this.fakeControlBox = false;

            handleRealDim.setRealdimensions(this.dim);

            this.bindDom();
            this.loadCanvas();
            this.updateCanvasSize();
            this.processSign(true);
            this.canvas.requestRenderAll();
            this.bindCanvas();
        }

      getBackgroundShape(){
         return this.backgroundShape;
      }

        reconstructTemplateData(templateArray){
            if(!templateArray){
                return {};
            }
            let newTemplateObject = {};
            this.templateInputOrder = [];
            for(let i =0; i < templateArray.length; i++){
                let obj = templateArray[i];
                let id = obj.id;
                this.templateInputOrder.push(id);
                newTemplateObject[id] = obj;
            }
            return newTemplateObject; 
        }

        bindDom(){
            let self = this;
            //unbinds to remove double click
            this.imageUpload.on('change', (event, data = false) => {
                let fileList;
                if(data){
                    fileList = data;
                }else{
                    fileList = event.target.files;
                }
                let belong = this.templateImage;
                let container;

                if(!belong) return;

                if (fileList.length > 0) {
                    if(belong){
                        //add spinner to show procces
                        let container = $(`.quick-upload-user-images[data-belong="${belong}"]`);
                        container.show();
                        this.uploadImageManager.addGallerySpinner(container);
                    }
                    //check that imagegallery has been added
                    if($('.quick-upload-user-images-cnt',container).html().length === 0){
                        this.uploadImageManager.addImagesToGallery(belong);
                    }
                    this.uploadImageManager.uploadImage(fileList, belong);
                }
            });

            this.$changeTemplate.on('click', (e) =>{
                let id, prodID = false;
                id = $(e.currentTarget).attr('data-id');
                prodID = $(e.currentTarget).attr('data-prod-id');
                id = (id) ? id : false;
                prodID = (prodID) ? prodID : false;
                this.changeTemplate(id,prodID);
            });

            this.$addToCart.on('click', (e) => {
                this.addTocart($(e.currentTarget));
            });
            this.$goToCheckOut.on('click', (e) => {
                this.goToCheckOut($(e.currentTarget));
            });
            this.$goToStudio.on('click', (e) =>{
                this.goToStudio($(e.currentTarget));
            })

            this.$templateToolsWrp.on('click', '.uploaded-files-btn', (e) => {
                let belong = $(e.currentTarget).data('belong');
                if(!belong) return;

                let container = $(`.quick-upload-user-images[data-belong="${belong}"]`);

                if(container.is(":visible")){
                    container.hide();
                }else{
                    if($('.quick-upload-user-images-cnt',container).html().length === 0){
                        this.uploadImageManager.addImagesToGallery(belong);
                    }else{
                        container.show();
                    }
                }
            });

            this.$templateToolsWrp.on('dragover', '.quick-upload', function (e) {
                e.stopPropagation();// Prevent default behavior (prevent file from being opened)
                e.preventDefault();
            });

            this.$templateToolsWrp.on('dragover', '.quick-upload', function (e) {
                e.stopPropagation();
                e.preventDefault();
            });

            $('#quick-right').on('dragover', function (e) {
                e.stopPropagation();
                e.preventDefault();
            });

            $('#quick-right').on('drop', (e) =>{
                e.preventDefault();
                e.stopPropagation();
            });

            this.$templateToolsWrp.on('drop',  '.quick-upload',(e) => {
                //Drag and drop images 
                e.preventDefault();
                e.stopPropagation();
                let files = e.originalEvent.dataTransfer.files;
                let belong = $(e.currentTarget).data('belong');

                if (files.length) { 
                    this.templateImage = belong;
                    this.imageUpload.files = files;
                    $(this.imageUpload).trigger('change', [files]);
                }
            });


            this.$templateToolsWrp.on('click', '.quick-upload-user-images li', (e) => {
                let object = $(e.currentTarget);
                let url = object.data('target');
                this.templateImage = object.data('belong'); 
                this.storeUserTemplateInput(this.template[this.templateImage].title, url, 'image');
                this.changeImage(url);
            });

            this.$templateToolsWrp.on('click', '.quick-upload-user-images li .del', (e) => {
                //deete upload
                e.preventDefault();
                e.stopPropagation();
                this.delteImage($(e.currentTarget));
            });

            this.$templateToolsWrp.on('click', '.quick-order-control', (e)=>{ let element = $(e.currentTarget); let dir = element.data('dir');
                let belong = element.data('belong'); 

                this.changeOrderOfItem(dir, belong);
            });

            this.$templateToolsWrp.off('click', '.quick-upload');
            this.$templateToolsWrp.off('input', '.quick-text');

            this.$templateToolsWrp.on('input', '.quick-text', (e) =>{
                let target = $(e.target);
                let inputVal = target.val();
                let id = target.attr('data-belong');
                let name = target.data('name'); 
                this.updateTemplateText(inputVal,id);
                this.storeUserTemplateInput(name, inputVal, 'text');
            });
            this.$templateToolsWrp.on('click', '.quick-upload', (e) => {
                this.templateImage = $(e.currentTarget).attr('data-belong');
                this.imageUpload.click();
            });

            this.$templateToolsWrp.on('click', '.quick-change-sign-size', (e) => {
                let width = $(e.currentTarget).attr('data-width');
                let height = $(e.currentTarget).attr('data-height');
                this.resizeSignToNewSize(width, height);
            });

            this.$templateToolsWrp.on('click', '.quick-side-option', (e) => {
                let type = $(e.currentTarget).attr('data-type');
                this.updateDoubleSided(type);
            });

            window.addEventListener('beforeunload', function (e) {
                // If the product is still processing, show the confirmation dialog
                if (self.isProductProcessing) {
                    var confirmationMessage = 'Din skylt är inte klar, är du säker på att du vill lämna sidan?';

                    // Some browsers may not display this message and will show a default message instead
                    e.returnValue = confirmationMessage; // Gecko and Trident
                    return confirmationMessage; // Gecko and WebKit
                }
            });

            $(document).on('mousedown', (e) => {
                //check if on canvas, if not deselect active object
                if(!$(e.target).hasClass('upper-canvas')){
                    if(this.canvas.getActiveObject()){
                        this.canvas.discardActiveObject().requestRenderAll();    
                    }
                }
            })

            window.addEventListener('resize', () => {
                this.windowResize();
            });
        }

        bindCanvas(){
            let self = this;
            let previousTarget = false;
            this.canvas.on({
                "selection:created" : (e) =>{
                    let object = e.selected[0];
                },
                "selection:cleared": (e) => {
                    this.objectCleared(e);
                    previousTarget = true;
                },
                'mouse:down' : (e) => {
                    if(this.isMobile){
                        if(!e.target.selectable && !this.canvas.getActiveObject() && !previousTarget){
                            let $canvasContainer = this.getCanvasConatiner();
                            let $overlay = $('.mobile-canvas-overlay');
                            $canvasContainer.css({
                                'pointer-events': 'none',
                            });
                            $overlay.css('display', 'flex');
                            //this.mobileScroll = window.scrollY;
                            // this.getCanvasConatiner().css('pointer-events','none');

                            setTimeout(() => {
                                $overlay.fadeOut(500, function() {
                                    $canvasContainer.css({
                                        'pointer-events': '',
                                    });
                                });
                            }, 2000); // Revert after 0.5 seconds
                        }
                        previousTarget = false;
                    }
                },
                'mouse:down:before': (e) => {
                    let object = e.target;
                    if (!object) return;
                    // Store the initial position
                    this.originalPosition = {
                        left: object.left,
                        top: object.top
                    };

                    if(object && object.clipPath && object.type === 'image' && (!object.clipPath || this.cropClick === object.clipPath )){
                        if(!this.cropClick) this.cropClick = object.clipPath;
                        this.toggleImageCrop(object);
                    }

                },'selection:updated': (e) => {
                    this.objectUpdated(e);
                },'mouse:up': (e) => {
                    // Clear the original position after the move is complete
                    let object = e.currentTarget;
                    if(object && object.type === 'image' && this.cropClick){
                        this.toggleImageCrop(object, true);
                    }
                    this.originalPosition = null;

                    if(this.isMobile && this.mobileScroll){
                        this.mobileScroll = false;
                    }

                },
            });

            let hammer = new Hammer(this.canvas.upperCanvasEl);
            hammer.get('pinch').set({
                enable: true
            });

            let initialScale = 1;

            hammer.on('pinch', (e) => {
                // Check if there is an active object selected
                const activeObject = this.canvas.getActiveObject();
                if (activeObject) {
                    // Calculate the new scale based on the initial scale and the pinch scale factor
                    const newScale = initialScale * e.scale;

                    let originX = activeObject.originX;
                    let originY = activeObject.originY;
                    activeObject.set({
                        originX: 'center',
                        originY: 'center'
                    });
                    // Apply the new scale to the active object
                    activeObject.scale(newScale);
                    activeObject.set({
                        originX:originX,
                        originY:originY
                    });
                    this.canvas.requestRenderAll();
                }
            });

            $(document).keydown((e) => this.onKeyDown(e));
        }


        onKeyDown(e){
            this.keyDown = e.keyCode;
            if(e.ctrlKey || e.metaKey){	
                switch (e.which) {
                    case 79: // O
                        this.testFunction();
                        break;
                }
            }
        }

        getCanvasConatiner(){
            if(!this.canvasContainer){
                this.canvasContainer = $('.canvas-container');
            }
            return this.canvasContainer;
        }


        objectUpdated(e){
            let deselection = e.deselected;
            let newSelection = e.selected;

            //check crop click, if it was then revert the crop and then select the other object
            //remove cropping
            if(deselection[0] && (
                (deselection[0].name === "userImage" && deselection[0].clipPath) ||
                    (deselection[0].hasOwnProperty('belong') && deselection[0].belong !== newSelection[0])
            )){
                this.revertCrop(deselection[0]);
            }
            let obj = this.canvas.getActiveObject();
            //add cropping if it has none
            if(obj && (obj.type === 'image' || obj.type === 'path' ) && obj.clipPath && !this.cropClick){
                //this.toggleImageCrop(obj);
            }
        }

        objectMoving(e){
            let obj = e.target;
            if(obj.hasOwnProperty('name') && obj.name === 'userImage'){
                //make sure image is not out side clippath 
                this.maintainImageInCrop(obj);
            }
        }

        objectCleared(e){
            let object = (e.deselected) ? e.deselected[0] : false;
            this.revertCrop(object);
        }

        revertCrop(object){
            if(object.type === 'image' && this.cropClick){
                this.toggleImageCrop(object, true);
                //this.removeFakeBoxForClipping();
            }
            this.cropClick = false;
        }

        maintainImageInCrop(obj){
            // Makes sure a part of the image (center) is always inside the clipPath

            if(!obj || !obj.clipPath) return;
            let clipPath = obj.clipPath;
            // Get the bounding rect of the clipPath
            var clipPathBounds = clipPath.getCenterPoint(true, true);
            let objRect = obj.getBoundingRect(true,true);
            // Calculate the center of the object
            var objectCorners = {
                lx : objRect.left,
                rx : objRect.left + objRect.width,
                ty : objRect.top,
                by : objRect.top + objRect.height
            };

            let left = false;
            let top = false;

            if (objectCorners.lx > clipPathBounds.x) left =  clipPathBounds.x;
            if (objectCorners.rx < clipPathBounds.x) left =  clipPathBounds.x - objRect.width;

            if (objectCorners.ty > clipPathBounds.y) top =  clipPathBounds.y;
            if (objectCorners.by < clipPathBounds.y) top = clipPathBounds.y -  objRect.height;

            if(left || top){
                let centerPoint = {};
                centerPoint.x = (left) ? left : objRect.left;
                centerPoint.y = (top) ? top : objRect.top;
                obj.setPositionByOrigin(centerPoint, 'left', 'top');
            }

            // Apply the changes
            obj.setCoords();
            this.canvas.requestRenderAll();
        }

        testFunction(){
            //   console.log(this.template);
            //    console.dir(this.canvas.getObjects());
            console.log(this.canvas.getActiveObject());
        }

      loadCanvas(){
         fabric.Object.prototype.transparentCorners = false;
         fabric.Object.prototype.objectCaching = false;

         fabric.Object.prototype.toObject = (function(toObject) {
            return function(properties) {
               return fabric.util.object.extend(toObject.call(this, properties), {
                  name: this.name,
                  selectable: this.selectable
               });
            };
         })(fabric.Object.prototype.toObject);

         fabric.Object.prototype.set({
            transparentCorners: false,
            cornerColor: '#4169e1',
            cornerStrokeColor: '#fff',
            cornerStyle: 'circle',
            borderColor: '#4169e1',
            cornerSize: 12,
            padding: 0,
         });

         this.canvas = new fabric.Canvas("quickedit-canvas", {
            preserveObjectStacking: true,
            controlsAboveOverlay: true,
            fireRightClick: true,
            fireMiddleClick: true, 
            stopContextMenu: true,
            isDrawingMode: false,
            allowTouchScrolling: true,
            uniformScaling: true,
            selection : false,
         });


         var bgCanvasHTML = document.getElementById("background");
         this.backgroundCanvas = new fabric.Canvas(bgCanvasHTML, {
            interactive: false, 
            containerClass: ' canvas-container overlay-canvases',
         });

         var fgCanvasHTML = document.getElementById("foreground");
         this.foregroundCanvas = new fabric.Canvas(fgCanvasHTML, {
            interactive: false, 
            containerClass: ' canvas-container overlay-canvases',
         });

      }

        async processSign(){
            const objects = this.convertedsign['objects'];
            const keys = Object.keys(objects);
            this.assignIdsToObjects(objects, keys);
            await this.preLoadFonts(objects);
            await this.enlivenObjectsAndAddToCanvas(objects);
            this.adjustObjectsSizeAndPosition(true);
            this.updatePreviousTemplateInputs();
         
            $.canvasFgManager = new ManageForegroundCanvas(this.foregroundCanvas, this.canvas);
            $.canvasBgManager = new ManageBackgroundCanvas(this.backgroundCanvas, this.canvas);
            onCanvasGUIHandler = new OnCanvasGUIHandler(this.canvas, this, this.backgroundCanvas, this.foregroundCanvas);
            this.zoomCanvas();
            //onCanvasGUIHandler.centerCanvasView();
            this.centerCanvasView();
            this.updatePrice();
        }

        assignIdsToObjects(objects, keys) {
            keys.forEach((key, i) => {
                const obj = objects[key];

                if(obj.name == "bg"){
                    obj.id = i === 0 ? 'backgroundShape' : 'backgroundInnerShape';
                    obj.signstudio_standard = true;
                }

                const customSignBackgroundIds = {
                    "custom_sign_background": 'custom_sign_background',
                    "custom_sign_background_prod": 'custom_sign_background_prod'
                };

                if (customSignBackgroundIds.hasOwnProperty(obj.name)) {
                    obj.id = customSignBackgroundIds[obj.name];
                    obj.signstudio_standard = true;
                }
            });
        }

        async preLoadFonts(objects){
            for (let v of objects) {
                if (v.type === 'i-text') {
                    let fontObject = { family: v.fontFamily, weight: v.fontWeight, style: v.fontStyle };

                    try {
                        await $.manageFonts.loadFont(fontObject);
                        // Font is loaded, continue with your application logic
                    } catch (error) {
                        console.error('Error loading font:', error);
                    }
                }
            }
        }

        getFontFormatFromObject(obj = false){
            if(!obj) return;


            let prefix = (obj.fontStyle === "italic") ? 'i' : 'n';
            let suffix = (obj.fontWeight === "bold" ) ? '7' : '4';
            let fontFamily = obj.fontFamily + ':' + prefix + suffix;
            return fontFamily;
        }

        async loadFont(fontFamily) {

            let data = {
                families: [fontFamily],
                urls: [this.sign_data.content + "assets/css/fonts.css"]
            }

            return new Promise((resolve, reject) => {
                WebFont.load({
                    //google: {
                    //    families: [fontFamily]
                    //},
                    custom : data ,
                    active: function() {

                        resolve();
                    },
                    inactive: function() {
                        reject(new Error(`Font ${fontFamily} could not be loaded`));
                    }
                });
            });
        }

        enlivenObjectsAndAddToCanvas(objects) {
            this.originalObjects = {};
            return new Promise((resolve, reject) => {
                fabric.util.enlivenObjects(objects, (enlivenedObjects) => {
                    try {
                        let index = 0;
                        enlivenedObjects.forEach(obj => {
                            obj.perPixelTargetFind = true;
                            let tempObj = obj;

                            if(obj.hasOwnProperty('templateName')){
                                if(obj.templateName.includes('image')){
                                    obj.setControlsVisibility({
                                        mb: false, 
                                        ml: false, 
                                        mr: false, 
                                        mt: false, 
                                        bl: true,
                                        br: true, 
                                        tl: true,
                                        tr: true,
                                    });
                                    this.templateObjects[obj.templateName] = obj;
                                    //Check if the object exists in the tempalate inputs, if so replace it 
                                } else if(obj.templateName.includes('text')){
                                    obj.selectable = false;
                                    let align = this.template[obj.templateName].textAlign;
                                    let title = this.template[obj.templateName].title;
                                    let fontCorrection = obj.scaleX/1;
                                    obj.fontSize = obj.fontSize*fontCorrection;
                                    obj.scaleX = 1;
                                    obj.scaleY = 1;
                                    obj.setCoords();

                                    switch(align){
                                        case 'center':
                                            let center = obj.getCenterPoint();
                                            obj.set({
                                                originX: 'center',
                                                textAlign: 'center',
                                                left: center.x
                                            });
                                            break;
                                        case 'right':
                                            //let right = obj.getCenterPoint().x + obj.width/2;
                                            //obj.set({
                                            //   textAlign: 'right',
                                            //   originX: 'right',
                                            //   left: right
                                            //});
                                            break;
                                    }

                                    this.templateObjects[obj.templateName] = obj;
                                }
                            } else {
                                obj.selectable = false;
                            }
                            this.canvas.add(tempObj);
                            //this.canvas.requestRenderAll();
                            index += 1;
                        });

                        //const newObj = this.canvas.getObjects();
                        this.assignObjectReferences(enlivenedObjects);
                        this.setCanvasClipPath(enlivenedObjects);
                        //this.canvas.requestRenderAll();
                        this.originalObjects = JSON.parse(JSON.stringify(enlivenedObjects.map(obj => obj.toObject())));
                        this.originalTempalteObjects = JSON.parse(JSON.stringify(this.templateObjects));
                        //check if we need top update templateObjects from previous inputs, we do it after as we want to store
                        //the original state in advanve
                        resolve();
                    } catch (error) {
                        reject(error);
                    }
                });
            });
        }


        assignObjectReferences(newObj) {
            newObj.forEach(obj => {
                switch (obj.id) {
                    case 'backgroundShape':
                        this.backgroundShape = obj;
                        break;
                    case 'backgroundInnerShape':
                        this.backgroundInnerShape = obj;
                        break;
                    case 'custom_sign_background':
                        this.bg_group_special = obj;
                        break;
                    case 'custom_sign_background_prod':
                        this.bgGroupSpecialProduction = obj;
                        break;
                    default:
                        break;
                }
            });
        }

        setCanvasClipPath(newObj) {
            this.canvas.clipPath = this.backgroundShape;

            if (this.bg_group_special) {
                this.canvas.clipPath = this.bg_group_special;
            }
        }

        /*
         updateCanvasSize(){
            this.canvas.setHeight(0);
            this.canvas.setWidth(0);

            let width = $(".canvas-container").outerWidth();
            alert(width);
            let height = (width*this.signRatio > 1) ? width*this.signRatio : width;

            this.canvas.setHeight(height);
            this.canvas.setWidth(width);
         }*/

        updateCanvasSize() {
            this.canvas.setHeight(0);
            this.canvas.setWidth(0);

            // Determine if we are in a mobile environment based on the window width
            if(window.innerWidth < 768){
                this.isMobile = window.innerWidth;
            }else{
                this.isMobile = false;
            }

            // Get the available width from the canvas container

            let width = $(".quick-sticky-wrp").outerWidth();

            // Calculate the desired height based on the sign ratio
            let height = width * this.signRatio;

            // If we're on a mobile device and the calculated height is greater than 80vh,
            // adjust the width and height to ensure the height does not exceed 80vh
            if (this.isMobile) {
                const maxHeight = window.innerHeight * 0.8; // 80% of the viewport height

                if (height > maxHeight) {
                    // Adjust the height to be within the 80vh limit
                    height = maxHeight;
                    // Adjust the width according to the sign ratio to maintain aspect ratio
                    width = height / this.signRatio;
                }
            }

            this.canvas.setHeight(height);
            this.canvas.setWidth(width);
        }



        zoomCanvas(){
            let zoomWidth = this.canvas.width / (this.backgroundShape.width);
            let zoomHeight = this.canvas.height / (this.backgroundShape.height);

            let newzoom = 0;

            if(zoomWidth < zoomHeight){
                newzoom = zoomWidth;
            }else{
                newzoom = zoomHeight;
            }
            this.canvas.zoomToPoint(new fabric.Point(this.canvas.width / 2, this.canvas.height / 2), newzoom);
            this.canvas.requestRenderAll();
        }

        centerCanvasView(){
            let origZoom = this.canvas.getZoom()
            let origX = this.canvas.viewportTransform[4]
            let origY = this.canvas.viewportTransform[5]
            let object = this.backgroundShape;
            let objWidth = this.backgroundShape.getScaledWidth()
            let objHeight = this.backgroundShape.getScaledHeight()

            let zoom = this.canvas.getZoom()

            let panX = 0
            let panY = 0

            panX = ((this.canvas.getWidth() / zoom / 2) - (object.aCoords.tl.x) - (objWidth / 2)) * zoom;
            panY = ((this.canvas.getHeight() / zoom / 2) - (object.aCoords.tl.y) - (objHeight / 2)) * zoom;

            this.canvas.setViewportTransform([origZoom, 0, 0, origZoom, panX, panY]);
            this.canvas.requestRenderAll();
        }

        windowResize(){
            if(!this.isMobile && window.innerWidth !== this.isMobile){
                this.updateCanvasSize();
                this.zoomCanvas();
                this.centerCanvasView();
            }
        }

        saveDesign(){
            const { canvas } = this;
            let canvasJSON = canvas.toJSON(saveCustomProperties);

            let signDistanceToLeft = this.backgroundShape.left;
            let signDistanceToTop = this.backgroundShape.top;

            for (var i = 0; i < canvasJSON.objects.length; i++) {
                var obj = canvasJSON.objects[i];
                obj.left -= signDistanceToLeft;
                obj.top -= signDistanceToTop;
            }

            canvasJSON.objects[0].shadow = null;
            canvasJSON.objects[0].stroke = null;

            var savedSign = {
                dim: this.dim,
                data: canvasJSON
            };

            return savedSign;
        }

        async getCompleteSignAsync(){
            let self = this;
            var completeSign = {};

            try{
                completeSign.sign = this.saveDesign();

                //const saveCanvas = new fabric.Canvas("save");
                const saveCanvas = new fabric.Canvas(null, {containerClass: 'save'});
                var canvasJSON = completeSign.sign.data;


                let signSVG = await this.generateSVG(canvasJSON, true);
                completeSign.svg = signSVG.svgData;
                completeSign.thumbnailExport = signSVG.thumbnailExport;

                if (signSVG.hasOwnProperty('svgProd')) {
                    completeSign.svgProd = signSVG.svgProd;
                }

                return completeSign;
            }catch(error){
                console.error("Error in getCompleteSignAsync:", error);
                throw error; // or handle the error as you see fit
            }
        }

        async generateSVG(canvasJSON, raw = false) {
            //let saveCanvas = new fabric.Canvas("save");
            let saveCanvas = new fabric.Canvas(null, { containerClass: 'save' });

            let returnObject = {};

            // Load canvas from JSON and wait for it to complete
            await new Promise((resolve) => {
                saveCanvas.loadFromJSON(canvasJSON, () => {
                    saveCanvas.setWidth(
                        canvasJSON.objects[0].width * canvasJSON.objects[0].scaleX
                    );
                    saveCanvas.setHeight(
                        canvasJSON.objects[0].height * canvasJSON.objects[0].scaleY
                    );
                    resolve();
                });
            });

            // Replace images with SVGs
            //await this.replaceImagesWithSVGs(saveCanvas);

            // Generate SVG data
            returnObject['svgData'] = saveCanvas.toSVG();

            if(raw){
                returnObject['thumbnailExport'] = await this.getThumbnailSVG(saveCanvas);
            }

            // Additional processing for special signs
            if (handleRealDim.getDim('special_sign') === true) {
                let customBG = saveCanvas.getItemByAttr('name', 'custom_sign_background');
                if (customBG && customBG.type !== "path") {
                    customBG._objects.forEach(obj => {
                        customBG.remove(obj);
                    });
                }
                saveCanvas.remove(customBG);
                returnObject['svgProd'] = saveCanvas.toSVG();
            }

            // Dispose of the canvas
            saveCanvas.dispose();
            return returnObject;
        }

        async getThumbnailSVG(mainCanvas = false){
            try{
                if(!mainCanvas){
                    mainCanvas = this.canvas;
                }
                //check that export canvas is inited
                let bgDims = $.canvasBgManager.getDim(true);
                let fgDims = $.canvasFgManager.getDim(true);

                let bgCanvas = $.canvasBgManager.getCanvas(); 
                let fgCanvas = $.canvasFgManager.getCanvas(); 


                let newDims = {left: 0, right: 0, top : 0, bottom : 0};
                let newHeight, newWidth;

                for(const dir of Object.keys(newDims)){
                    let less = false; 
                    if(dir == 'left' || dir == 'top'){
                        less = true; 
                    }

                    if(less){
                        if(bgDims[dir] < newDims[dir]) newDims[dir] = bgDims[dir]; 
                        if(fgDims[dir] < newDims[dir]) newDims[dir] = fgDims[dir]; 
                    }else{
                        if(bgDims[dir] > newDims[dir]) newDims[dir] = bgDims[dir]; 
                        if(fgDims[dir] > newDims[dir]) newDims[dir] = fgDims[dir]; 
                    }
                }


                newWidth = Math.abs(newDims.left) + newDims.right;
                newHeight = Math.abs(newDims.top) + newDims.bottom;

                const renderCanvasInstance = new TemporaryCanvas(newWidth, newHeight);
                const renderCanvas = renderCanvasInstance.getCanvas();

                function copyCanvasContents(sourceCanvas, targetCanvas, offsetX, offsetY, isSign = false) {
                    let cropObject = false;
                    let layerGroup = new fabric.Group([], {left: 0, top:0});
                    let extrasOffsetSign = {left: 0, top: 0};

                    //manage crop offsets for sign
                    if(isSign){
                        let signCoords = {left: 0, top: 0, right: 0, bottom: 0};
                        let signCoordsDiff = {left: 0, top: 0, right: 0, bottom: 0};
                        sourceCanvas.forEachObject(function(obj) {
                            //get background and extracts corrct left and top offset
                            const bb = obj.getBoundingRect(true, true);

                            if(!cropObject && obj?.fill && obj?.fill !== "transparent"){
                                cropObject = obj;

                                signCoords = {
                                    left: bb.left,
                                    top: bb.top,
                                    right: bb.left + bb.width,
                                    bottom: bb.top + bb.height
                                }

                                signCoordsDiff = {...signCoords}

                                return;
                            }

                            if(cropObject){
                                signCoordsDiff = {
                                    left: Math.min( bb.left, signCoordsDiff.left),
                                    top: Math.min( bb.top, signCoordsDiff.top),
                                    right: Math.max( (bb.left + bb.width), signCoordsDiff.right),
                                    bottom: Math.max( (bb.top + bb.height), signCoordsDiff.bottom),
                                }
                            }
                        });

                        const leftDiff = signCoords.left - signCoordsDiff.left;
                        const topDiff = signCoords.top - signCoordsDiff.top;
                        const rightDiff = signCoordsDiff.right - signCoords.right;
                        const bottomDiff = signCoordsDiff.bottom - signCoords.bottom;

                        extrasOffsetSign = {
                            left: leftDiff - rightDiff,
                            top: topDiff - bottomDiff,
                        }

                        cropObject = false;
                    }

                    sourceCanvas.forEachObject(function(obj) {
                        var clonedObj = fabric.util.object.clone(obj);

                        clonedObj.set({
                            left: clonedObj.left + offsetX,
                            top: clonedObj.top + offsetY ,
                            objectCaching: false // Disable caching for performance
                        });

                        clonedObj.setCoords();
                        if(isSign && !cropObject && obj?.fill && obj?.fill !== "transparent"){
                            //adding crop
                            //crop
                            cropObject = fabric.util.object.clone(obj);
                            delete cropObject.id;
                            //shadow
                            let shadow = fabric.util.object.clone(obj);
                            shadow.id = 'addShadow'
                            targetCanvas.add(shadow);
                        }

                        layerGroup.addWithUpdate(clonedObj);
                    });

                    if(cropObject){
                        layerGroup.clipPath = cropObject;
                        //cropObject.absolutePositioned = true;
                        cropObject.left = - (cropObject.width - extrasOffsetSign.left) / 2;
                        cropObject.top = - (cropObject.height - extrasOffsetSign.top) / 2;
                        layerGroup.set('name', 'signGroup');
                    }

                    targetCanvas.add(layerGroup);
                    targetCanvas.requestRenderAll();
                }

                let offesetX = Math.abs(newDims.left);
                let offesetY = Math.abs(newDims.top);


                copyCanvasContents(bgCanvas, renderCanvas, offesetX, offesetY); 
                copyCanvasContents(mainCanvas, renderCanvas, offesetX, offesetY, true); 
                copyCanvasContents(fgCanvas, renderCanvas, offesetX, offesetY); 

                let exportCanvasJSON = renderCanvas.toJSON(saveCustomProperties);


                let testSVG = renderCanvas.toSVG();

                //thumbnail svg
                const canvasClass = new TemporaryCanvas(1100, 1100, '#ffffff'); 

                await canvasClass.addContent(exportCanvasJSON, 1000, 1000, true);

                let returnData = {type: 'png'};

                //first we try to get the png
                let exportCanvas = canvasClass.getPng();

                if(!exportCanvas){
                    //if it doesnt work we send the svg instead 'slower' as we will need to replace
                    //urls with base64 or svgs
                    returnData.type = 'svg';
                    exportCanvas = canvasClass.getSvg();
                }


                canvasClass.dispose();
                renderCanvasInstance.dispose();

                returnData['data'] = exportCanvas;

                return returnData;
            }catch(error){
                console.log(error);
            }
        }

        getAjaxParams(){
            let params = {
                'data' : false,
                'addToCart' : false,
                'preview': false,
                'admin' : false, 
                'share' : false,
                'favorite': false,
                'image':false,
                'mountingHoles' : false,
                'base' : this.sign_data.base,
            }
            params = JSON.parse(JSON.stringify(params));

            if(typeof customer_site !== 'undefined' && customer_site && customer_site.unique_key){
                params['unique_key'] = customer_site.unique_key;
            }
            return params;
        }

        getUserTemplateSetting(){
            let newTemplateSettings = [];
            $.each(this.template, function(key, value){
                let tempsettings = {};
                tempsettings = value;
                let includesText = key.includes('text');
                if(includesText){
                    tempsettings['default'] = $(`.quick-text[data-belong=${key}]`).val();
                }
                newTemplateSettings.push(tempsettings);
            });

            return newTemplateSettings;
        }

        createSign(event, element = false){
            this.isProductProcessing = true;
            self = this;
            this.getCompleteSignAsync().then(result=>{
                let completeSign = result;
                let params = self.getAjaxParams();
                params.data = completeSign;
                params.addToCart = true;
                params.base = this.sign_data.base;
                params.mountingHoles = self.dim.mountAmount;
                params.names = {product_name: self.sign_data.name};
                params.fromQuick = true;
                params.data.sign.template = self.getUserTemplateSetting(); 
                params.base_product_id = this.base_id;
                params.base_template_id = this.base_template;

                this.apiClient.sendFinishedDesign(JSON.stringify(params))
                    .then(async succ => {
                        if(typeof customer_site !== 'undefined' && customer_site){
                            succ.addToCart = true;
                            await self.apiClient.customerCreateSign(succ);
                        }
                        if(event == 'add'){
                            updateCartDisplay(succ.post_id);
                        }else if(event == 'checkout'){ 
                            window.location.replace(`${this.sign_data.site_url}/checkout/`);
                            updateCartDisplay(succ.post_id);
                        }


                        this.isProductProcessing = false;
                        $.stdnLoadingSpinner.removeLoading(element);  

                        this.isProductProcessing = false;
                    }).catch(error => {
                        this.isProductProcessing = false;
                        $.stdnLoadingSpinner.removeLoading(element);  
                        console.log(error);
                    });
            }).catch(error => {
                    this.isProductProcessing = false;
                    $.stdnLoadingSpinner.removeLoading(element);  
                    console.log(error);
                });
        }

        addTocart(element){
            console.log($.stdnLoadingSpinner);
            $.stdnLoadingSpinner.addLoading(element);  
            this.createSign('add',element);
        }
        goToCheckOut(element){
            $.stdnLoadingSpinner.addLoading(element);  
            this.createSign('checkout', element);
        }

        goToStudio(element){
            let sign = JSON.stringify(this.saveDesign());
            localStorage.setItem("designFromQuick", sign);
            let templateBase= (this.$goToStudio.data('val')) ? this.$goToStudio.data('val') : false;
            let tempalteBaseString = (templateBase) ? `&templateBase=${templateBase}` : ''; 
            if(typeof customer_site !== 'undefined' && customer_site){
                window.location.href = `${customer_site.url}?quick=new${tempalteBaseString}`;
            }else{
                window.location.href = `${this.sign_data.site_url}/skyltstudion/?quick=new${tempalteBaseString}`;
            }
        }

        changeTemplate(id = false, prodId = false){
            //get template 
            let code = false;
            if(id){
                code = {'getTemplate': id}
            }else if(prodId){
                code = {'getTemplateProd': prodId}
            }

            this.base_template = (id) ? id : prodId;
            if(code){
                this.apiClient.getSignFromCode({'code': code}).then(result => {
                    //change value 
                    this.updateSignCluster(result);
                });
            }
        }

        changeOrderOfItem(dir, belong) {
            let self = this;
            // Identify the object to be moved.
            let objectToMove = this.templateObjects[belong];

            if(!objectToMove){
                console.error("Object not found: ", belong);
                return;
            }

            // Get its current index in the stack.
            let currentIndex = self.canvas.getObjects().indexOf(objectToMove);

            // Calculate the new index based on the direction.
            let newIndex = dir === "up" ? currentIndex + 1 : currentIndex - 1;

            // Ensure the new index is within the acceptable range.
            if(newIndex < 2 || newIndex >= self.canvas.getObjects().length) {
                return;
            }

            // Move the object to the new index.
            self.canvas.moveTo(objectToMove, newIndex);

            // Rerender the canvas to visually update the stack order.
            self.canvas.requestRenderAll();
        }

        updateSignCluster(newSign){
            this.changeSignData(newSign);
            this.clearCanvas();
            this.updateCanvasSize();
            this.processSign(true);
            this.canvas.requestRenderAll();
            this.clearTools();
            this.generateTools();
            this.updatePrice();
        }

        changeSignData(sign){
            this.convertedsign = sign.data;
            this.dim = sign.dim;
            this.template = this.reconstructTemplateData(sign.templateData.template);
            this.templateData = sign.templateData;
            this.originalWidth = this.dim['width'];
            this.originalHeight = this.dim['height'];
            this.originalBorder = this.dim['border'];
            this.templateObjects = {}; 
            this.imageObjects = [];
            this.signResizeTemp = false;
            this.updateSignRatio(this.dim['width'],this.dim['height']);
        }

        updateSignRatio(width,height){
            this.signRatio = height / width;
        }

        adjustObjectsSizeAndPosition(changeTemplate = false) {
            if(changeTemplate == true) this.firstRun = true;
            const newObj = this.canvas.getObjects();
            let scaleXmm, scaleYmm, reduceTop, reduceLeft;
            let bg = false;
            let objectID;
            if(changeTemplate){
                this.templateInputsToUpdate = [];
            }

            this.changedSizeScales = {};

            newObj.forEach((obj, i) => {
                if (obj.name === "bg") {
                    if(i === 0){
                        scaleXmm = fabric.util.parseUnit(this.dim.width + "mm") / (obj.width * obj.scaleX);
                        scaleYmm = fabric.util.parseUnit(this.dim.height + "mm") / (obj.height * obj.scaleY);
                        reduceTop = obj.top;
                        reduceLeft = obj.left;

                        obj.set({
                            'width': fabric.util.parseUnit(this.dim.width + "mm"),
                            'height': fabric.util.parseUnit(this.dim.height + "mm"),
                            'signstudio_standard': true,
                            'left': 0,
                            'top': 0,
                            'scaleX': 1,
                            'scaleY': 1,
                        }).setCoords();
                        bg = obj;
                    }else{
                        if(this.signResizeTemp){
                            obj.set(this.correctPositionBorderSizeChange(obj, scaleXmm, scaleYmm, bg));
                        }else{
                            obj.set(this.correctPosition(obj, i ,scaleXmm, scaleYmm, reduceTop, reduceLeft, true)).setCoords();
                        }

                    }
                }

                if (obj.name === "custom_sign_background" || obj.name === "custom_sign_background_prod") {
                    obj.setCoords();
                }

                let customShapes = ['bg','custom_sign_background','custom_sign_background_prod'];
                if(!customShapes.includes(obj.name)){
                    obj.set(this.correctPosition(obj, i,scaleXmm,scaleYmm,reduceTop,reduceLeft)).setCoords();
                    if(obj.hasOwnProperty('templateName')){
                        objectID = obj.templateName;
                        let templateSettings = this.template[objectID];
                        if(templateSettings){
                            let title = templateSettings.title;
                            if(obj.type === 'i-text'){
                                this.updateFixedTextSize(templateSettings,obj, objectID, false,true)
                                //Check if the object exists in the tempalate inputs, if so replace it
                                if(this.templateInputs.hasOwnProperty(title) && changeTemplate){
                                    this.templateInputsToUpdate.push({
                                        templateName : obj.templateName,
                                        title : title,
                                        obj : obj,
                                        type : 'text',
                                        value: this.templateInputs[title].value,
                                    });
                                }
                            }else{

                                //can only be image if not i-text for now
                                if(this.templateInputs.hasOwnProperty(title) && changeTemplate){
                                    this.templateInputsToUpdate.push({
                                        templateName : obj.templateName,
                                        title : title,
                                        obj : obj,
                                        type : 'image',
                                        value: this.templateInputs[title].value,
                                    });
                                }

                            }
                        }
                    }
                }
                this.firstRun = false;
                this.canvas.requestRenderAll();
            });
        }

        async updatePreviousTemplateInputs(){
            if(!this.templateInputsToUpdate) return;

            let objects = this.templateInputsToUpdate;
            for(let i = 0; i < objects.length; i++){
                let obj = objects[i];

                if(obj.type === 'text'){
                    obj.obj.set('text', obj.value);
                    let textSettings = this.template[obj.templateName];
                    this.updateFixedTextSize(textSettings,obj.obj, obj.templateName, false ,false)
                    //console.log([textSettings,obj.obj, obj.index, originalObjects,true]);
                }else if(obj.type === 'image'){
                    this.templateImage = obj.templateName;
                    await this.changeImage(obj.value);
                }
            }
        }

        correctPosition(obj, index, scaleX, scaleY, rTop, rLeft, fixed) {
            let newLeft = obj.left * scaleX - rLeft * scaleX;
            let newTop = obj.top * scaleY - rTop * scaleY;
            let originalObject = false;
            let ogToNewRatioW, ogToNewRatioH;  
            let templateObject = false;

            scaleX = fixed ? 1 : obj.scaleX * scaleX;
            scaleY = fixed ? 1 : obj.scaleY * scaleY;

            if(this.signResizeTemp && !fixed){
                originalObject = this.originalObjects[index];
                if(originalObject){
                    let {newWidth,  newHeight } = this.signResizeTemp;

                    let originalScaleX = originalObject.scaleX;
                    let originalScaleY = originalObject.scaleY;


                    let templateName = (originalObject.hasOwnProperty('templateName')) ? originalObject.templateName : false;
                    if(templateName && this.templateObjects[templateName].hasOwnProperty('changeTop')){
                        templateObject = this.templateObjects[templateName];
                        originalScaleX = templateObject.changeScale; 
                        originalScaleY = templateObject.changeScale; 
                    }

                    ogToNewRatioW = newWidth/this.originalWidth;
                    ogToNewRatioH = newHeight/ this.originalHeight;

                    scaleX = originalScaleX*ogToNewRatioW;
                    scaleY = originalScaleY*ogToNewRatioH;

                    let originalObjLeft = originalObject.left;
                    let originalObjTop = originalObject.top;

                    if(templateObject){
                        originalObjLeft = templateObject.changeLeft;
                        originalObjTop = templateObject.changeTop;
                    }

                    newLeft = originalObjLeft*ogToNewRatioW;
                    newTop = originalObjTop*ogToNewRatioH;

                    let scaleRatio = 1;
                    if(ogToNewRatioW < ogToNewRatioH){
                        //let addDistance = (obj.height*scaleY - obj.height*scaleX)/2;
                        //console.log(addDistance);
                        //newTop += (addDistance);
                        scaleRatio = originalScaleY / originalScaleX;
                        scaleY = scaleX;
                        scaleY *= scaleRatio;
                    }else if(ogToNewRatioW > ogToNewRatioH){
                        //let addDistance = (obj.width*scaleX - obj.width*scaleY)/2;
                        //newLeft += (addDistance);
                        scaleRatio = originalScaleX / originalScaleY;
                        scaleX = scaleY;                   
                        scaleX *= scaleRatio;
                    }

                    //Check if has clippath if so we need to update the clippath as well
                    if(obj.clipPath){
                        let clip = obj.clipPath;
                        let originalClip = this.originalObjects[index].clipPath;


                        let originalClipScaleX = originalClip.scaleX;
                        let originalClipScaleY = originalClip.scaleY;

                        let clipScaleX = originalClipScaleX*ogToNewRatioW;
                        let clipScaleY = originalClipScaleY*ogToNewRatioH;

                        let newClipLeft = originalClip.left*ogToNewRatioW;
                        let newClipTop = originalClip.top*ogToNewRatioH;

                        // let clipAddDistance = 0;
                        let scaleRatioClip = 1;
                        if(ogToNewRatioW < ogToNewRatioH){
                            //clipAddDistance = (clip.height*clipScaleY - clip.height*clipScaleX)/2;
                            // newClipTop += (clipAddDistance);
                            scaleRatioClip = originalClipScaleY / originalClipScaleX ;
                            clipScaleY = clipScaleX;
                            clipScaleY *= scaleRatioClip;
                        }else if(ogToNewRatioW > ogToNewRatioH){
                            //clipAddDistance = (clip.width*clipScaleX - clip.width*clipScaleY)/2;
                            // newClipLeft += (clipAddDistance);
                            scaleRatioClip = originalClipScaleX /originalClipScaleY;
                            clipScaleX = clipScaleY;
                            clipScaleX *= scaleRatioClip;
                        }
                        clip.set({
                            left: newClipLeft,
                            top: newClipTop,
                            scaleX : clipScaleX,
                            scaleY : clipScaleY,
                            width: clip.width,
                            height: clip.height
                        });
                    }
                };
            }

            let returnSetting = {
                'left': newLeft,
                'top': newTop,
                'scaleX': scaleX,
                'scaleY': scaleY,
                'width' : obj.width,
                'height' : obj.height,
            };
            if(obj.hasOwnProperty('templateName')){
                this.changedSizeScales[obj.templateName] = returnSetting;
            }

            if (fixed) {
                returnSetting.signstudio_standard = true;
                returnSetting.height = obj.height * obj.scaleY * scaleY;
                returnSetting.width = obj.width * obj.scaleX * scaleX;
            }
            return returnSetting;
        }

        correctPositionBorderSizeChange(obj, scaleX, scaleY, bg) {
            let border = fabric.util.parseUnit(this.originalBorder+"mm");
            let ogWidht = fabric.util.parseUnit(this.originalWidth+'mm');
            let ogHeight =  fabric.util.parseUnit(this.originalHeight+'mm');

            let widthChange = bg.width / ogWidht;
            let heightChange = bg.height / ogHeight;

            let borderChange = (widthChange < heightChange) ? widthChange : heightChange;
            let newBorder = border*borderChange;

            let newWidth = bg.width - (newBorder*2);
            let newHeight = bg.height - (newBorder*2);

            newWidth = Math.max(newWidth, 0);
            newHeight = Math.max(newHeight, 0);

            const left = (bg.width - newWidth) / 2;
            const top = (bg.height - newHeight) / 2;

            let returnSetting = {
                left: left,
                top: top,
                width: newWidth,
                height: newHeight,
                scaleX: 1, // scaleX is set to 1 because we've already applied scaling to width and height
                scaleY: 1, // scaleY is set to 1 for the same reason
            };
            return returnSetting;

        }


        rotateImage(value, objectID){
            let object = this.templateObjects[objectID];
            object.rotate(value);
            object.setCoords();
            this.canvas.requestRenderAll();
        }
        scaleImage(value, objectID){
            let object = this.templateObjects[objectID];
            if(!object.hasOwnProperty('originalScaleFactor')){
                object.originalScaleFactor = object.scaleX;
            }
            let center = object.getCenterPoint();
            object.set({
                originX: 'center',
                originY: 'center',
                left: center.x,
                top: center.y
            });
            object.scale(value*object.originalScaleFactor);
            object.setCoords();
            this.canvas.requestRenderAll();
        }

        async changeImage(url) {
            let self = this;

            // Function to load an SVG and resolve with the group
            function loadSVG(url) {
                return new Promise((resolve, reject) => {
                    fabric.loadSVGFromURL(url, (objects, options) => {
                        let svg = fabric.util.groupSVGElements(objects, options);
                        resolve(svg);
                    });
                });
            }

            // Function to load an image and resolve with the image
            function loadImage(url) {
                return new Promise((resolve, reject) => {
                    fabric.Image.fromURL(url, function(img) {
                        resolve(img);
                    }, { crossOrigin: 'anonymous' });
                });
            }

            if (url.endsWith(".svg")) {
                // Check size and validate svg
                let allowed = await this.checkSvgSingleColor(url);
                if (allowed) {
                    let svg = await loadSVG(url);
                    self.modifieImage(svg);
                    self.canvas.requestRenderAll();
                } else {
                    let img = await loadImage(url);
                    self.modifieImage(img);
                    self.canvas.requestRenderAll();
                }
            } else {
                let img = await loadImage(url);
                self.modifieImage(img);
                self.canvas.requestRenderAll();
            }
        }

        async checkSvgSingleColor(url){
            if(!this.svgCheck){
                this.svgCheck = new SvgCheck;
            }

            // Fetch the SVG file from the URL
            let response = await fetch(url);
            if (!response.ok) {
                alert("Hittade inte filen");
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            // Convert response to Blob
            let blob = await response.blob();
            const maxSize = 500 * 1024; // 500 KB in bytes
            if (blob.size > maxSize) {
                console.log("Filen är för stor");
                return false;
            }
            // Validate the SVG
            let status = await this.svgCheck.readSVGFile(blob);
            return status;
        }

        toggleImageCrop(object, revert = false){

            if(revert){
                this.canvas.remove(this.fakeControlBox);
                object.clipPath = this.cropClick;
                object.opacity = 1;
            }else{
                this.fakeControlBox = fabric.util.object.clone(this.cropClick);
                this.fakeControlBox.set({
                    stroke: 'black',
                    strokeWidth: 2,
                    fill: 'transparent',
                    belong: object,
                    opacity:0.8,
                    evented: false, // Make it non-interactive
                    selectable: false,
                });
                this.canvas.add(this.fakeControlBox);
                object.clipPath = null;
                object.opacity= 0.40;
            }
        }

        modifieImage(img){
            //first get previous image size and center
            let oldImage = this.templateObjects[this.templateImage];
            if(!oldImage) return;      

            let oldWidht;
            let oldHeight;
            //Get largest convinence
            if(oldImage.hasOwnProperty('clipPath')){
                let rectClip = oldImage.clipPath.getBoundingRect(true,true);
                oldWidht = rectClip.width;
                oldHeight = rectClip.height;
            }else{
                oldWidht = oldImage.width*oldImage.scaleX;
                oldHeight = oldImage.height*oldImage.scaleY;
            }

            //get old size and position
            //oldWidht = oldImage.width*oldImage.scaleX;
            //oldHeight = oldImage.height*oldImage.scaleY;

            let center = oldImage.getCenterPoint();

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

            let newscale;

            let oldImageIndex = this.canvas.getObjects().indexOf(oldImage);

            if(imgWidth < imgHeight){
                newscale = oldWidht/imgWidth;
            }else{
                newscale = oldHeight/imgHeight;
            }


            this.canvas.add(img);
            img.scale(newscale);

            img.templateName = this.templateImage;
            img.set({
                originX: 'center',
                originY: 'center',
                left: center.x,
                top: center.y,
                name: 'userImage',
            });
            img.setCoords();

            img.setControlsVisibility({
                mb: false, 
                ml: false, 
                mr: false, 
                mt: false, 
                bl: true,
                br: true, 
                tl: true,
                tr: true,
            });

            // Update the reference in the template objects
            this.templateObjects[this.templateImage] = img;

            this.canvas.moveTo(img, oldImageIndex);

            if (oldImage.clipPath) {
                img.clipPath = oldImage.clipPath;
            }

            this.canvas.remove(oldImage);
            img.selectable = true;
            img.perPixelTargetFind = true;
            this.canvas.requestRenderAll();
        }

        updateTemplateText(inputVal, objectID, update = false) {
            let object = this.templateObjects[objectID];
            let textSettings = this.template[objectID];
            let originalData = this.getOriginalData(objectID);
            let changeX = 1, changeY = 1;

            if(!update){
                object.set('text', inputVal);
            }else{
                //object.fontSize = object.fontSize/object.scaleX;
            }

            /* object.scaleX = 1;
         object.scaleY = 1;
         object.setCoords();

         if(this.changedSizeScales){
            changeX = this.changedSizeScales[objectID].scaleX;
            changeY = this.changedSizeScales[objectID].scaleY;
            object.fontSize = originalData.fontSize*changeX;
            object.setCoords();
         }
         */
            if(textSettings.fixedSize == 1){
                this.updateFixedTextSize(textSettings,object,objectID,originalData);
            }

            // If there's no text, reset to the original font size

            if (!update && inputVal === '') {
                object.scaleX = 1;
                object.scaleY = 1;
                object.setCoords();
            }

            object.setCoords(); // Update coor
            this.canvas.requestRenderAll();
        }

        updateFixedTextSize(textSettings, object, objectID, originalData = false, changeSize = false){
            if(textSettings.fixedSize !== 1) return;

            let changeX = 1, changeY = 1, xCompensation = 1, yCompensation = 1;
            if(!originalData){
                originalData = this.getOriginalData(objectID);
            }

            if(this.changedSizeScales){
                changeX = this.changedSizeScales[objectID].scaleX;
                changeY = this.changedSizeScales[objectID].scaleY;
                object.fontSize = originalData.fontSize*changeX;
                object.scaleX = 1;
                object.scaleY = 1;
                object.top =  this.changedSizeScales[objectID].top; 
            }
            if(changeSize){
                object.setCoords();
                this.canvas.requestRenderAll();
            }
            object.set('text', object.text); // needed to update coords

            //this.canvas.requestRenderAll();
            let boundingRect = object.getBoundingRect(true,true);
            let objectWidth = object.getScaledWidth();
            let objectHeight = object.getScaledHeight();


            if(textSettings.width === "" || textSettings.width == 0){
                textSettings.width = 0;
            }

            if(textSettings.height === "" || textSettings.height == 0){
                textSettings.height = 0;
            }

            let textSettingsWidth = fabric.util.parseUnit(textSettings.width+'mm')*changeX;
            let textSettingsHeight = fabric.util.parseUnit(textSettings.height+'mm')*changeY;


            if (textSettingsWidth !== 0 && objectWidth > textSettingsWidth) {
                //let newScale = textSettingsWidth / objectWidth;
                let newScale = textSettingsWidth / objectWidth;
                object.scaleX = newScale;
                object.scaleY = newScale;
                // Keep the baseline aligned after scalin
                //let newTop = originalData.top*changeY + (originalData.height*originalData.scaleY*changeY - objectHeight*newScale)/2;
                let objectAngle = object.angle;
                object.setCoords();
                if(objectAngle == 0){
                    let newTop = originalData.top*changeY;
                    let baseLine = (originalData.height*originalData.scaleY*changeY-objectHeight*newScale*yCompensation)/2;
                    object.set({
                        top: this.changedSizeScales[objectID].top +  baseLine,
                    }).setCoords();
                }else{

                }
            }


            this.canvas.requestRenderAll();
        }

        async delteImage(element){
            let li = element.closest('li');
            let id = element.data('target');
            let result = false;

            let loggedIn = this.uploadImageManager.getLoggedinStatus();

            if(loggedIn){
                let data = JSON.stringify({'action': 'del', 'image_id': id});
                result = await this.apiClient.manageUserImages(data);
            }

            let stored = JSON.parse(localStorage.getItem("designUploads"));
            for(let i = 0; i < stored.length; i++){
                if(stored[i].id == id){
                    stored.splice(i, 1);
                    break;
                }
            }
            localStorage.setItem("designUploads", JSON.stringify(stored)); // update the stored data


            li.fadeOut(function() {
                li.remove();
            });

        }


        getOriginalData(objID){
            let data = {};
            if(this.originalTempalteObjects.hasOwnProperty(objID)){
                let item = this.originalTempalteObjects[objID]
                data = {
                    width : item.width,
                    height : item.height,
                    top :  item.top,
                    left : item.left,
                    scaleX : item.scaleX,
                    scaleY : item.scaleY,
                    fontSize : item.fontSize,
                }
            }
            return data;
        }

        clearCanvas(){
            this.canvas.clear();
            this.canvas.setBackgroundImage('', this.canvas.requestRenderAll.bind(this.canvas));
            this.canvas.requestRenderAll();
        }
        clearTools(){
            $('.quick-tool-specific', this.$templateToolsWrp).remove();
        }

        getCurrentPrice(){
            let templatePrices = this.sign_data.template_pricing;

            let {width, height, double_sided} = this.dim;
            let key = width +"x"+ height;

            if(templatePrices.hasOwnProperty(key)){
                let priceObject = templatePrices[key];
                if(double_sided){
                    return priceObject['dubbelsidig'].price;
                }else{
                    return priceObject['enkelsidig'].price;
                }
            }
        }

        generateTools(){
            let html = "";
            let self = this;
            let sideControlladded = false
            for(const templateId of this.templateInputOrder){
                let value = this.template[templateId];
                let title = value.title;
                if(templateId.includes('image')){
                    html += self.addImageControls(title, templateId);
                }else if (templateId.includes('text')){
                    let inputValue = value.default;
                    if(self.templateInputs.hasOwnProperty(title)){
                        inputValue  = self.templateInputs[title].value;
                    }
                    html += self.addTextControls(title,templateId, inputValue);
                }
            }


            //get current price to add price difference on size and side options 

            if(Object.keys(this.templateData.sizes).length > 0){
                html += this.addSizeControls();
                sideControlladded = true;
            }


            if(this.sign_data.double_sided){
                html += this.addSideControl();
            }


            if(this.imageObjects.length > 0){}
            this.$buyButtons.before(html)

            if(sideControlladded){
                $('#quick-size-options').find(`button[data-width="${this.dim['width']}"][data-height="${this.dim['height']}"]`).addClass('active');
            }

            this.addImageSliders();
        }

        updatePrice(){
            let {width, height, double_sided} = this.dim;
            let size = width + 'x' + height;
            let side = (double_sided == 1) ? 'dubbelsidig' : 'enkelsidig';
            let price = 0;
            if (this.sign_data.template_pricing && 
                this.sign_data.template_pricing[size] && 
                this.sign_data.template_pricing[size][side] && 
                'price' in this.sign_data.template_pricing[size][side]) {
                price = this.sign_data.template_pricing[size][side]['price'];
                $('.quick-price-span').html(price);
            } else {
                price = this.sign_data.price;   
            }
            $('.quick-price-span').html(price);
        }

        addImageControls(title, id) {
            var count = this.imageObjects.length + 1;
            this.imageObjects.push(count);

            var templateStr = control_templates.image;
            var html = templateStr.replace(/\[TITLE\]/g, title)
            .replace(/\[ID\]/g, id)
            .replace(/\[NAME\]/g, title)
            .replace(/\[COUNT\]/g, count);
            return html;
        }
        addTextControls(title, id, deafult){
            let templateStr = control_templates.text;
            let html = templateStr.replace(/\[TITLE\]/g, title)
            .replace(/\[VALUE\]/g, deafult)
            .replace(/\[ID\]/g, id)
            .replace(/\[NAME\]/g, title);
            //let html = `<input type='text' class='quick-text' value='${deafult}' data-belong='${id}'>`;
            return html;
        }

        addSizeControls(price = ""){
            let templateStr = this.control_templates.sizes;
            let buttons ="";
            let sizes = this.templateData.sizes;
            sizes[this.originalWidth+'x'+this.originalHeight] = [];

            function generateSizeButton(width, height, active = false){
                let html = control_templates.size_btn;
                html = html.replace(/\[WIDTH\]/g, width)
                    .replace(/\[HEIGHT\]/g, height)
                if(active){
                    html = html.replace(/class="/, 'class="active ');
                }
                return html;
            }

            let sortedKeys = Object.keys(sizes).sort((a, b) => {
                let [widthA, heightA] = a.split('x').map(Number);
                let [widthB, heightB] = b.split('x').map(Number);

                // First compare by width
                if (widthA !== widthB) {
                    return widthA - widthB;
                }
                // If widths are equal, compare by height
                return heightA - heightB;
            });

            $.each(sortedKeys, (index, key) => {
                let [width, height] = key.split('x');
                let active = (width == this.originalWidth && height == this.originalHeight) ? true :false;

                buttons += generateSizeButton(width, height, active);
            });



            let html = templateStr.replace(/\[BUTTONS\]/g, buttons);
            return html;
        }

        addSideControl(){
            let templateStr = this.control_templates.sides;
            return templateStr;
        }

        constructTitleAndContent(title, content) {
            var html = '<div class="quick-tool-wrap quick-tool-specific">';
            html += '<div class="quick-tool-title"><h4>' + title + '</h4></div>';
            html += '<div class="quick-tool-content">' + content + '</div>';
            html += '</div>';

            return html;
        }

        addImageSliders(){
            const self = this;
            let sliders = this.imageObjects;
            if(sliders.length > 0){
                sliders.forEach(obj => {
                    // add rotation
                    $( `#rotation-range-${obj}` ).slider({
                        range: "min",
                        min: -180,
                        max: 180,
                        value: 0,
                        slide: function( event, ui ) {
                            let val = ui.value;
                            let id = $(event.target).attr('data-belong');
                            self.rotateImage(val,id);
                        }
                    }); 

                    // add scale
                    $( `#scale-range-${obj}` ).slider({
                        range: "min",
                        min: 0.2,
                        max: 2,
                        step:0.01,
                        value: 1,
                        slide: function( event, ui ) {
                            let val = ui.value;
                            let id = $(event.target).attr('data-belong');
                            self.scaleImage(val,id)
                        }
                    });  
                });
            }
        }

        resizeSignToNewSize(newWidth, newHeight) {
            // Assuming 'canvas' is your Fabric.js canvas instance
            if(newWidth === this.dim.width && newHeight === this.dim.height) return;

            if(!this.signResizeTemp){
                this.signResizeTemp = {
                    oldWidth : this.dim.width,
                    oldHeight : this.dim.height,
                    newWidth : newWidth,
                    newHeight : newHeight,
                    originalWidth : this.dim.width,
                    originalHeight : this.dim.height,
                }
            }else{
                this.signResizeTemp.oldWidth = this.dim.width;
                this.signResizeTemp.oldHeight = this.dim.height;
                this.signResizeTemp.newWidth = newWidth;
                this.signResizeTemp.newHeight = newHeight;
            }

            this.dim.width = newWidth;
            this.dim.height = newHeight;

            this.updateSizeActive();
            this.updateSignRatio(newWidth, newHeight);
            this.updateCanvasSize();
            this.changeImageBeforeSizeChange();
            this.adjustObjectsSizeAndPosition();
            this.zoomCanvas();
            this.centerCanvasView();
            this.updatePrice();
        }


        changeImageBeforeSizeChange(){
            let ratio = false;
            let scaleDir = false;
            this.canvas.getObjects().forEach((obj) => {
                if(obj.type == 'image' && obj.hasOwnProperty('templateName')){
                    if(!ratio){
                        let xScale =  this.signResizeTemp.originalWidth / this.signResizeTemp.oldWidth; 
                        let yScale = this.signResizeTemp.originalHeight / this.signResizeTemp.oldHeight;
                        ratio = Math.min(xScale,yScale);
                        scaleDir = (xScale !== ratio) ? 'scaleX' : 'scaleY';
                    }

                    let templateName = obj.templateName;
                    for(let i = 0; i < this.originalObjects.length; i++){
                        let oObj = this.originalObjects[i];
                        if(oObj.hasOwnProperty('templateName')){
                        }
                        if(oObj.hasOwnProperty('templateName') && oObj.templateName == templateName){
                            let tempObj = this.templateObjects[templateName];
                            tempObj.changeTop = ratio * tempObj.top;
                            tempObj.changeLeft = ratio * tempObj.left;

                            //change scale 
                            let type = (scaleDir === 'scaleX') ? 'width' : 'height';

                            let tmpLenght = (tempObj[scaleDir]*ratio);
                            let ogLength = (oObj[type]*oObj[scaleDir]);
                            tempObj.changeScale = tempObj[scaleDir]*ratio;
                        }
                    }
                }
            });
        }



        updateSizeActive(){
            $('#quick-size-options button').removeClass('active');
            $('#quick-size-options').find(`button[data-width="${this.dim['width']}"][data-height="${this.dim['height']}"]`).addClass('active');
        }

        updateDoubleSided(type){
            let wrp = $('#quick-side-options');
            $('button', wrp).removeClass('active');
            if(type === 'double'){
                wrp.find('button[data-type="double"]').addClass('active');
                this.dim.double_sided = 1;
            }else{
                wrp.find('button[data-type="single"]').addClass('active');
                this.dim.double_sided = 0;
            }
            this.updatePrice();
        }

        storeUserTemplateInput(name = false, value = false, type = false){
            if(!name || !value) return;
            this.templateInputs[name] = {value : value, type : type}
        }

    }

    let signManager: ManagingSign;

    class UploadImage{
        constructor(apiClient, userUploadDir){
            this.apiClient = apiClient;
            this.userUploadDir = userUploadDir;
            this.images = false;
            this.loaded = false;
            this.loggedIn = false; 
            this.setStandards();
        }

        getLoggedinStatus(){
            return this.loggedIn;
        }
        setStandards(){
            this.validMimeTypes = {
                'jpg': 'image/jpg',
                'jpg': 'image/jpeg',
                'jpeg': 'image/jpeg',
                'jpeg': 'image/jpg',
                'gif': 'image/gif',
                'png': 'image/png',
                'svg': 'image/svg+xml',
                'svgz': 'image/svg',
                'eps': 'application/postscript',
                'pdf': 'application/pdf'
            };
        }
        uploadImage(files, belong = false){
            let data = new FormData();

            for (const [key, value] of Object.entries(files)) {
                data.append(key, value);
            }

            this.apiClient.userUpload(data)
                .then(result => {
                    if (typeof result.error === "undefined") {
                        this.addNewImageToGallery(result.files, belong);
                    } else {
                        $(`.quick-upload-user-images[data-belong="${belong}"]`).unblock();
                        console.log("ERRORS: " + result.error);
                    }

                    $(".symbol-upload-form .loader").addClass("hidden");
                })
                .catch(errorData => {
                    $(".symbol-upload-form .loader").addClass("hidden");
                });
        }

        addNewImageToGallery(files, belong = false){
            // get all files
            let uploads = [];
            let cahceLength = 0;

            for (var i = 0; i < files.length; i++) {
                let transformedObject = {
                    'name': files[i].image_name,
                    'id': files[i].id,
                    'path': files[i].path
                };
                if(this.loggedIn == false){
                    delete transformedObject.id;
                }
                uploads.push(transformedObject);
            }

            if(this.loggedIn == false){
                //store to cache for not logged in users 
                let cacheUploads = uploads;
                let stored = localStorage.getItem("designUploads");

                if (stored) {
                    cacheUploads = JSON.parse(stored).concat(uploads);
                }

                let maxId = cacheUploads.reduce((max, upload) => Math.max(max, upload.id || 0), 0);

                // Add id to each upload that doesn't already have one
                for(let i = 0; i < uploads.length; i++){
                    maxId++;
                    uploads[i].id = maxId;
                }

                localStorage.setItem("designUploads", JSON.stringify(cacheUploads));
            }

            //Now we add it to the gallery cahce is only for non logged in users
            $(".symbol-upload-form").addClass("has-files");

            let newImages = "";

            for(let i = 0; i < uploads.length; i++){
                let path = USER_UPLOAD_DIR + uploads[i].path;
                let imageNameWithoutExt = uploads[i].name.split('.').slice(0, -1).join('.');
                newImages += this.generateImageInGallery(path, uploads[i].id,uploads[i].name, imageNameWithoutExt, belong);
            }

            let noImageMessage =$(".quick-upload-user-images-cnt span.no-images"); 
            if (noImageMessage.length > 0) noImageMessage.remove();

            $(".quick-upload-user-images-cnt").prepend(newImages);

            if(belong){
                if(uploads.length === 1){
                    let uploadedImageButton = $(`.quick-upload-user-images[data-belong="${belong}"] li[data-item="${uploads[0].id}"]`);
                    if(uploadedImageButton) uploadedImageButton.trigger('click');
                }
                $(`.quick-upload-user-images[data-belong="${belong}"]`).unblock();
                //$(`.quick-upload-user-images[data-belong="${belong}"]`).show();
            }
        }

        async updateImagesFromServer(belong) {
            //check for first run
            if(this.images === false){
                this.images = [];
                try {
                    const owned = await this.apiClient.getUserImages();
                    if(owned.hasOwnProperty('notLoggedIn')){
                        this.getImagesFromLocalStorage();
                    }else if(owned && owned.length > 0){
                        this.loggedIn = true;
                        for(let image of owned) {
                            const index = this.images.findIndex(item => item.path === image.path);
                            if(index === -1) {
                                this.images.push({ path: image.path, name: image.image_name, id: image.id });
                            }
                        }
                    }else{
                        this.loggedIn = true;
                    }

                    return this.renderImages(belong);
                } catch(error) {
                    console.error('Error:', error);
                    throw error;
                }
            }else if(this.images){
                return this.renderImages(belong);
            }
        }

        getImagesFromLocalStorage() {
            let images = localStorage.getItem("designUploads");
            if (images) {
                // TEMPORARY CODE TO REMOVE BAD FORMATTING CAN BE REMOVE LATER...  2023-07-20
                images = JSON.parse(images);
                // Filter out images that don't have 'id' property or 'id' is not a number
                images = images.filter(image => typeof image.id === 'number');
                // Update the local storage
                localStorage.setItem("designUploads", JSON.stringify(images));
            } else {
                images = [];
            }
            this.images = images;
        }

        renderImages(belong) {
            let dom = "";
            for (let i = 0; i < this.images.length; i++) {
                let path = this.userUploadDir + this.images[i].path;
                let imageNameWithoutExt = this.images[i]['name'].split('.').slice(0, -1).join('.');
                let id = this.images[i].id ? this.images[i].id : i;					
                dom += this.generateImageInGallery(path,id,this.images[i].name, imageNameWithoutExt, belong);
            }
            this.loaded = true;
            if(dom === ""){
                dom = "<span class='no-images'>Du har inga bilder uppladade</span>";
            }
            return dom;
        } 

        generateImageInGallery(path, id, name, imageNameWithoutExt, belong){
            let image = 
                `<li data-target="${path}" data-name="${name}" data-item="${id}" data-belong="${belong}" draggable="true">
<div class="actions">
<button class="del" data-action="del" data-target='${id}'>
<i class="material-icons">-</i>
</button>
</div>
<img src="${path}">
<div class="upload-name">${imageNameWithoutExt}</div>
</li>`;
            return image;
        }

        addGallerySpinner(container){
            container.show();

            let data= {
                message: "", // Optional: if you want to show a custom loading image
                overlayCSS: {
                    background: '#fff',
                    opacity: 0.6
                },
                css: {
                    cursor: 'wait',
                    border: 'none',
                },
                fadeIn : 300,
            };
            container.block(data);  
        }

        addImagesToGallery(belong){
            let container = $(`.quick-upload-user-images[data-belong="${belong}"]`);
            this.addGallerySpinner(container);

            this.updateImagesFromServer(belong).then(images => {
                $('.quick-upload-user-images-cnt',container).html(images);
                //container.show();
                container.unblock();
            }).catch(error => {
                    console.error(error);  
                    container.unblock();
                });
        }
    }

    class SvgCheck {
        constructor() {

        }

        readSVGFile(file) {
            return new Promise((resolve, reject) => {
                const reader = new FileReader();
                reader.onload = (event) => {
                    const svgString = event.target.result;
                    const isValid = this.validate(svgString);
                    resolve(isValid); // resolve the promise with the validation result
                };
                reader.onerror = (error) => {
                    reject(error); // reject the promise if reading the file fails
                };
                reader.readAsText(file);
            });
        }

        validate(svgString) {
            const parser = new DOMParser();
            const doc = parser.parseFromString(svgString, 'image/svg+xml');
            const styleColors = this.extractColorsFromStyles(doc);

            //const paths = doc.querySelectorAll('path');
            const paths = doc.querySelectorAll('path, rect, circle, ellipse, line, polyline, polygon', 'g');
            const filteredPaths = Array.from(paths).filter(path => !path.closest('#boolean-operations'));


            const allowedElements = doc.documentElement.querySelectorAll('path, g, defs, style, clipPath, rect, circle, ellipse, line, polyline, polygon, title');
            const totalElements = doc.documentElement.querySelectorAll('*');


            if (totalElements.length !== allowedElements.length) {
                return false;
            }


            let firstFillColor = true;
            let isSingleColor = true;
            let fillColor;

            let colors = new Set();
            for(let index = 0; index < paths.length; index++) {
                const path = paths[index];
                //console.log(path);
                fillColor = this.validateElementColor(path, styleColors);
                //console.log(fillColor);
                if(index === 0) {
                    firstFillColor = fillColor;
                }else{
                    if(fillColor !== firstFillColor) {
                        isSingleColor = false;
                        break;
                    }
                }
            }

            if (!isSingleColor) {
                return false;
            }

            return true;
        }

        extractColorsFromStyles(svgDoc) {
            let styleColors = {};

            const styles = svgDoc.querySelectorAll('style');
            styles.forEach(style => {
                const cssRules = style.sheet.cssRules;
                for (let rule of cssRules) {
                    // Assume the rule targets fill and stroke properties
                    const color = rule.style.fill || rule.style.stroke;
                    if (color) {
                        rule.selectorText.split(",").forEach(selector => {
                            styleColors[selector.trim()] = color;
                        });
                    }
                }
            });

            return styleColors;
        }

        validateElementColor(element, styleColors) {
            let fillColor = element.getAttribute('fill');

            if (!fillColor) {
                // Check class and id for a color
                if (element.className.baseVal) {
                    fillColor = styleColors['.' + element.className.baseVal];
                }
                if (!fillColor && element.id) {
                    fillColor = styleColors['#' + element.id];
                }
            }

            return fillColor;
        }
    }

    class LoadingSpinner {
        constructor() {
            this.loadingBlack = '<div class="lds-ring black"><div></div><div></div><div></div><div></div></div>';
            this.loadingWhite = '<div class="lds-ring"><div></div><div></div><div></div><div></div></div>';
            console.log('im initatied');
        }

        addLoading(element, white) {
            if (element.find('.pre-load').length > 0) {
                // Spinner already exists, don't add another
                return;
            }
            let inner = element.html();
            let loading = this.loadingBlack;
            if (white) {
                loading = this.loadingWhite;
            }
            element.html(loading + `<div class="pre-load" style="display:none;">${inner}</div>`)
        }

        removeLoading(element) {
            if(!element) return;
            let original = $('.pre-load', element).html();
            element.html(original);
        }
    }

    class ManageFonts{

        constructor(fontSettings, fontUrl){
            this.loadedFonts = {};
            this.fontSettings = fontSettings;
            this.fontUrl = fontUrl;
        }

        async loadFont(fontFamily) {
            if (!Array.isArray(fontFamily)) fontFamily = [fontFamily];

            // Use Promise.all to wait for all fonts to load
            return Promise.all(fontFamily.map(fontSettings => this.loadSingleFont(fontSettings)));
        }

        async loadNewFont(family, args = { preferredWeight: '400', preferredStyle: 'normal' }) {
            if (!this.fontSettings[family]) {
                throw new Error(`Font family ${family} is not available.`);
            }

            let fontSettings = this.findClosestFontSettings(family, args);
            if (!fontSettings) {
                throw new Error(`No suitable font settings found for ${family}.`);
            }

            try {
                await this.loadSingleFont(fontSettings);
                return fontSettings;
            } catch (error) {
                console.error(`Failed to load font: ${fontSettings.family} ${fontSettings.weight} ${fontSettings.style}`, error);
                throw error;
            }
        }

        findClosestFontSettings(family, args) {
            const { preferredWeight, preferredStyle } = args;
            let fontObject = this.fontSettings[family];

            // First, try to match the preferred weight and style exactly
            if (fontObject[preferredWeight] && fontObject[preferredWeight][preferredStyle]) {
                return { family, weight: preferredWeight, style: preferredStyle };
            }

            // If not found, try any style with the preferred weight
            for (let weight in fontObject) {
                if (fontObject[weight][preferredStyle]) {
                    return { family, weight, style: preferredStyle };
                }
            }

            // Lastly, return any available weight and style
            for (let weight in fontObject) {
                for (let style in fontObject[weight]) {
                    return { family, weight, style };
                }
            }

            return null; // No match found
        }

        async loadSingleFont(fontOptions) {
            if(fontOptions.weight == 'normal') fontOptions.weight = 400;
            if(fontOptions.weight == 'bold') fontOptions.weight = 700;
            fontOptions.style = fontOptions.style.charAt(0).toUpperCase() + fontOptions.style.slice(1);

            const { family, weight, style } = fontOptions;

            // Check if the font has already been loaded
            if (this.isFontLoaded(family, weight, style)) {
                return;
            }

            let args = { preferredWeight: weight, preferredStyle: style}; 
            fontOptions = this.findClosestFontSettings(family, args);

            // Construct the FontFace and load it
            let url = this.getFontPath(fontOptions);
            fabric.util.clearFabricFontCache(family);
            const fontFace = new FontFace(family, `url(${url})`, { weight: weight, style: style });
            document.fonts.add(fontFace);

            try {
                await fontFace.load();
                this.markFontAsLoaded(family, weight, style);
            } catch (error) {
                console.error(`Failed to load font: ${family} ${weight} ${style}`, error);
                this.markFontAsLoaded(family, weight, style);
                throw error; // rethrow to be caught by the caller if needed
            }
        }

        isFontLoaded(family, weight, style) {
            return !!this.loadedFonts[family]?.[weight]?.[style];
        }

        markFontAsLoaded(family, weight, style) {
            if (!this.loadedFonts[family]) this.loadedFonts[family] = {};
            if (!this.loadedFonts[family][weight]) this.loadedFonts[family][weight] = {};
            this.loadedFonts[family][weight][style] = true;
        }

        getFontPath(fontOptions){
            console.log(fontOptions);

            const { family, weight, style } = fontOptions;

            this.markFontAsLoaded(family, weight, style);
            let url =this.fontUrl + this.fontSettings[family][weight][style]; 
            return encodeURI(url);
        }

        getCurrentFontSettings(fontFamily){
            return this.fontSettings[fontFamily];
        }
        checkForSingleStyle(family, weight){
            if(this.fontSettings[family]){
                if(this.fontSettings[family][weight]){
                    let fontStyles = [];
                    for(let style in this.fontSettings[family][weight]){
                        fontStyles.push(style);
                    }
                    if(fontStyles.length == 1){
                        return fontStyles[0];
                    }
                }
            }

            return false;
        }
    }


    function calculateCanvasDimensions(canvas, clean = false) {
        let backgroundShape = signManager.getBackgroundShape();
        if (!backgroundShape) return;

        let rect = backgroundShape.getBoundingRect(true, true);
        let lefts = [rect.left];
        let rights = [rect.left + rect.width];
        let tops = [rect.top];
        let bottoms = [rect.top + rect.height];

        if (canvas) {
            let objects = canvas.getObjects();
            if (objects.length) {
                objects.forEach((obj) => {
                    if (clean && obj?.excludeFromExport && obj.excludeFromExport === true) {
                        return;
                    }

                    let boundingRect = obj.getBoundingRect(true, true);
                    lefts.push(boundingRect.left);
                    rights.push(boundingRect.left + boundingRect.width);
                    tops.push(boundingRect.top);
                    bottoms.push(boundingRect.top + boundingRect.height);
                });
            }
        }

        const coords = {
            left: Math.min(...lefts),
            top: Math.min(...tops),
            right: Math.max(...rights),
            bottom: Math.max(...bottoms),
        };

        const width = coords.right - coords.left;
        const height = coords.bottom - coords.top;

        return { ...coords, width: width, height: height };
    }



    class ManageBackgroundCanvas {
        constructor(backgroundCanvas, canvas) {
            this.backgroundCanvas = backgroundCanvas;
            this.canvas = canvas;
            this.dim = false;
            this.bgs = false;
            this.hashedBGs = false;
        }

        getCanvas() {
            return this.backgroundCanvas;
        }

        getDim(getClean = false) {
            if (!this.dim || getClean) {
                return this.updateDim(getClean);
            }
            return this.dim;
        }

        updateDim(clean = false) {
            const newCoords = calculateCanvasDimensions(this.backgroundCanvas, clean);

            if (clean) {
                return newCoords;
            } else {
                this.dim = newCoords;
            }

            return this.dim;
        }

        setBgs(objects) {
            this.bgs = objects;
        }

        updateBackground() {
            let objects = this.bgs;
            let newHash = this.hashObjectArray(objects);
            //if no images to add and no images on canvas do nothing
            if (newHash === this.hashedBGs) {
                return;
            } else {
                if (objects.length === 0) {
                    this.removeBackgrund();
                    this.updateDim();
                    this.hashedBGs = newHash;
                    //            onCanvasGUIHandler.addCanvasShadow();
                    //           onCanvasGUIHandler.centerCanvasView();
                    signOperation.requestCenterCanvas();
                } else {
                    this.removeBackgrund();
                    let updated = this.addBackground(objects[0]);
                    if (updated) {
                        onCanvasGUIHandler.removeCanvasShadow();
                        this.hashedBGs = newHash;
                    }
                }
            }
        }

        hashObjectArray(objArray) {
            // Simple hashing function - for illustration purposes
            // Convert the entire array to a JSON string first
            return JSON.stringify(objArray).split('').reduce((prev, curr) => ((prev << 5) - prev) + curr.charCodeAt(0), 0);
        }

        removeBackgrund() {
            let objects = this.backgroundCanvas.getObjects();
            for (let i = 0; i < objects.length; i++) {
                let curr = objects[i];
                if (curr.name === 'product-bg') this.backgroundCanvas.remove(curr);
            }
            this.backgroundCanvas.requestRenderAll();
        }

        addBackground(item) {
            let backgroundShape = signOperation.getBackgroundShape();
            if (!backgroundShape) {
                return false;
            }
            let url = my_base_params.image_content + "/" + item.image;
            let width = fabric.util.parseUnit(item.width + 'mm');
            let height = fabric.util.parseUnit(item.height + 'mm');
            let mt = fabric.util.parseUnit(item.mt + 'mm');
            let mr = fabric.util.parseUnit(item.mr + 'mm');
            let mb = fabric.util.parseUnit(item.mb + 'mm');
            let ml = fabric.util.parseUnit(item.ml + 'mm');

            let bgWidth = backgroundShape.getScaledWidth();
            let bgHeight = backgroundShape.getScaledHeight();

            let left = (bgWidth - width) / 2; // Default to center if margins are 0
            let top = (bgHeight - height) / 2; // Default to center if margins are 0

            // Adjust for margins
            if (mt !== 0) top = mt;
            if (mb !== 0) top = 0 - mb;
            if (ml !== 0) left = ml;
            if (mr !== 0) left = 0 - mr;

            fabric.Image.fromURL(url, (img) => {
                // Set position and scale
                img.set({
                    left: parseInt(left),
                    top: parseInt(top),
                    originX: 'left',
                    originY: 'top',
                    name: 'product-bg',
                });

                img.scaleToWidth(width);
                img.scaleToHeight(height);

                // Additional settings (if needed)
                // Add to canvas
                this.backgroundCanvas.add(img);
                this.updateDim();
                //onCanvasGUIHandler.centerCanvasView();
                //
                signOperation.requestCenterCanvas();
            }, {
                    crossOrigin: 'anonymous' // If the image is from a different domain
                });
            return true;
        }

        test() {
            let width = fabric.util.parseUnit(1300 + "mm");
            let height = fabric.util.parseUnit(1300 + "mm");
            let backgroundShape = signOperation.getBackgroundShape();
            let bgWidth = backgroundShape.getScaledWidth();
            let bgHeight = backgroundShape.getScaledHeight();
            let left = (bgWidth - width);
            let top = (bgHeight - height);
            let rect = new fabric.Rect({
                width: width,
                height: height,
                left: left,
                top: top,
                fill: '#fff',
                stroke: '#000',
                strokeWidth: 20
            });
            this.backgroundCanvas.add(rect);
            this.updateDim();
        }
    }





    class ManageForegroundCanvas {
        constructor(foregroundCanvas, canvas) {
            this.foregroundCanvas = foregroundCanvas;
            this.canvas = canvas;
            this.dim = false;
        }

        getCanvas() {
            return this.foregroundCanvas;
        }

        getDim(getClean = false) {
            if (!this.dim || getClean) {
                return this.updateDim(getClean);
            }
            return this.dim;
        }

        updateDim(clean = false) {
            const newCoords = calculateCanvasDimensions(this.backgroundCanvas, clean);

            if (clean) {
                return newCoords;
            } else {
                this.dim = newCoords;
            }

            return this.dim;
        }
    }

    function init(){
        let userUploadDir = sign_data.site_url + "/wp-content/uploads/user-uploads";
        const apiClient = new APIClient(ajaxob);
        const uploadImageManager = new UploadImage(apiClient,userUploadDir);
        const svgCheck = new SvgCheck();
        $.manageFonts = new ManageFonts(sign_data.font_data, sign_data.font_url);
        handleRealDim = new HandleRealDimensionsClass();
        signManager = new ManagingSign(apiClient, uploadImageManager, sign_data, control_templates, svgCheck);

        $.stdnLoadingSpinner = new LoadingSpinner();
    }

    if (document.readyState === 'complete') {
        init();
    } else {
        window.addEventListener('load', function() {
            init();
        });
    }
});

