aico-image-editor
Version:
Combine multiple image into and create single combined image
2,369 lines • 94.1 kB
JavaScript
// import Alpine from 'alpinejs'
//import persist from '@alpinejs/persist';
let persist;
if (typeof window.Alpine.$persist === 'undefined') {
persist = require('@alpinejs/persist')
Alpine.plugin(persist.default)
}
// note- as fabric is now in externals,
//externals script directly gives fabric and not as {fabric} under object
// which modular system does so it will be like this
// and not like this - import {fabric} from 'fabric';
import fabric from 'fabric';
import canvasSize from 'canvas-size';
require('./roundedBackgroundTextbox');
import chroma from 'chroma-js';
//history
import {canvasHistory} from './history/history';
import { AddCommand } from './history/addCommand';
import { RemoveCommand } from './history/removeCommand';
import { TransformCommand } from './history/transformCommand';
window.canvasHistory = canvasHistory;
// Alpine.plugin(persist)
//
import {getObjectControlImages, getProductImages} from './getImages';
import { addCropRectangleControls } from './utilities/cropControls';
import { productPicturesSubTab } from './components/pictureTab/pictureTab';
let webglBackend, canvas2dBackend;
Alpine.store('canvas', {
products: [
{
id: 2,
name: 'gr-5',
productImageUrl: 'images/gr-5/product-gr5.svg',
overlapImageUrl: 'images/gr-5/product-gr5-overlap.svg',
innerImageUrl: 'images/gr-5/product-gr5-inner.svg',
innerImageStrokeOnlyUrl: 'images/gr-5/product-gr5-inner-strokeonly.svg',
tagImageUrl: 'images/gr-5/gr5-tag.svg',
tagCloneImageUrl: 'images/gr-5/gr5-tag-cloned.svg',
tagImageLeft: 4.12,
tagImageTop: 37.25,
tagImageWidth: 32.06,
tagImageHeight: 45.50
},
{ id: 0,
name: 'gr-6',
productImageUrl: 'images/gr-6/product-gr6.svg',
overlapImageUrl: 'images/gr-6/product-gr6-overlap.svg',
overlapStickerImageUrl: 'images/gr-6/product-gr6-overlap-sticker.svg',
innerImageUrl: getProductImages().gr6InnerImage,
innerImageStrokeOnlyUrl: 'images/gr-6/product-gr6-inner-strokeonly.svg',
tagImageUrl: 'images/gr-6/gr6-tag.svg',
tagCloneImageUrl: 'images/gr-6/gr6-tag-cloned.svg',
leftUpperTextImage: 'images/gr-6/pdf-left-text.png',
rightUpperTextImage: 'images/gr-6/pdf-right-text.png',
bottomTextImageUrl: 'images/gr-6/pdf-bottom-text.svg',
bottomTextImage: 'images/gr-6/pdf-bottom-text.png',
plusicon: 'images/gr-6/pluspdf.png',
tagImageLeft: 6.20,
tagImageTop: 38.96,
tagImageWidth: 24.48,
tagImageHeight: 50.37
},
{ id: 1,
name: 'gr-7',
productImageUrl: 'images/gr-7/product-gr7.svg',
overlapImageUrl: 'images/gr-7/product-gr7-overlap.svg',
overlapStickerImageUrl: 'images/gr-7/product-gr7-overlap-sticker.svg',
innerImageUrl: 'images/gr-7/product-gr7-inner.svg',
innerImageStrokeOnlyUrl: 'images/gr-7/product-gr7-inner-strokeonly.svg',
tagImageUrl: 'images/gr-7/gr7-tag.svg',
tagCloneImageUrl: 'images/gr-7/gr7-tag-cloned.svg',
leftUpperTextImage:'images/gr-7/pdf-left-text.png',
rightUpperTextImage: 'images/gr-7/pdf-right-text.png',
bottomTextImageUrl: 'images/gr-7/pdf-bottom-text.svg',
bottomTextImage: 'images/gr-7/pdf-bottom-text.png',
plusicon: 'images/gr-7/pluspdf.png',
tagImageLeft: 5.24,
tagImageTop: 50.32,
tagImageWidth: 24.27,
tagImageHeight: 39.61
}
],
apiConfig: {
apiUrl: '',
apiToken: '',
},
currentLocale: 'de',
canvasData: Alpine.$persist(''),
isConfiguratorIdChanged: false,
productId: Alpine.$persist(null),
configuratorId: Alpine.$persist(null),
selectedProduct: (function() {
const urlParams = new URLSearchParams(window.location.search);
const myProduct = urlParams.get('productKey');
return myProduct || 'gr-6';
})(),
init() {
fabric.perfLimitSizeTotal = 225000000;
},
isServerLoaderCanvasVisible: false,
isServerLoaderTabVisible: false,
isColorBlockLoaderVisible: false,
isColorPickerBlockDisabled: true,
isObjectFilterControlsDisabled: true,
isCurveBlockDisabled: true,
isObjectDeselected: true,
isCanvasResetBtnDisabled:true,
isFontStyleSaveBtnDisabled: true,
isTextObjectSelected: false,
isTextBackgroundButtonDisabled: true,
isInside3dView: false,
textErrorMessage:false,
fontSuccessMessage:false,
isSaveButtonDisabled: false,
enable3d(formData) {
const self = this, canvas = window.__canvas;
const product = this.getSelectedProductObj();
this.isInside3dView = true;
const clippedPathImageHeight = canvas.clipPath.getScaledHeight();
const differenceInHeight = (canvas.height - clippedPathImageHeight)/2;
window.__canvas.clone(function(clonedCanvas) {
clonedCanvas.clipPath = null;
window.clonedCanvas = clonedCanvas;
const clonedCanvasObjects = clonedCanvas.getObjects();
clonedCanvasObjects.forEach(function(clonedCanvasObject,i) {
clonedCanvasObject.set({
clipPath: null,
})
if(clonedCanvasObject.excludeFromExportIn3D) {
clonedCanvas.remove(clonedCanvasObject)
}
})
// clonedCanvasObjects.forEach(function(clonedCanvasObject,i) {
// console.log(clonedCanvasObject.top);
// const percentageFromClippedPath = (clonedCanvasObject.top/clippedPathImageHeight)*100,
// topFromInnerShape = clonedCanvasObject.top - differenceInHeight,
// percentageFromInnerShape = (topFromInnerShape/clippedPathImageHeight)*100;
// console.log(percentageFromInnerShape);
// newObjTop = (percentageFromInnerShape*canvas.height)/100;
// console.log(newObjTop)
// clonedCanvasObject.set({
// clipPath: null,
// top: newObjTop,
// })
// clonedCanvas.renderAll()
// })
fabric.Image.fromURL(product.tagCloneImageUrl,function(img) {
//img.scaleToWidth(clonedCanvas.width * (product.tagImageWidth/100));
img.scaleToHeight(clonedCanvas.height/clonedCanvas.viewportTransform[0] * (product.tagImageHeight/100));
img.set({
left: clonedCanvas.width * product.tagImageLeft/100,
top: clonedCanvas.height/clonedCanvas.viewportTransform[0] - img.getScaledHeight() - 20
})
img.set({zIndex: 1000})
clonedCanvas.add(img)
const url = clonedCanvas.toDataURL({
format: 'jpeg',
enableRetinaScaling: true,
multiplier: 3
});
if(self.isInside3dView) {
window.dispatchEvent(new CustomEvent('toggle-3d', { detail: {
url: url,
topBottomTextureUrl: clonedCanvas.backgroundImage?.src,
formData: formData
}, bubbles: true }));
}
}, {crossOrigin: 'anonymous'})
},['excludeFromExportIn3D','name', 'filters', 'viewportTransform'])
},
toPDF(formData) {
const self = this, canvas = window.__canvas;
const product = this.getSelectedProductObj();
window.__canvas.clone(function(clonedPDFCanvas) {
fabric.Image.fromURL(product.overlapStickerImageUrl,function(overlapStickerImg) {
self.fitImageInCanvas(overlapStickerImg, clonedPDFCanvas);
self.setCenter(overlapStickerImg);
self.preventInteraction(overlapStickerImg)
overlapStickerImg.set({zIndex: 1002})
clonedPDFCanvas.add(overlapStickerImg)
clonedPDFCanvas.renderAll()
const url = clonedPDFCanvas.toDataURL({
format: 'png',
enableRetinaScaling: true,
multiplier: 3
});
const { jsPDF } = window.jspdf;
var pdf = new jsPDF({
orientation: 'landscape',
unit: 'px',
format: [935, 875],
compress: true,
});
pdf.setFillColor(241,241,242);
pdf.rect(0, 0, 935, 935, 'F');
pdf.addImage(product.plusicon, 'PNG', 15, 15, 30, 30,'','FAST');
pdf.addImage(product.plusicon, 'PNG', 890, 15, 30, 30,'','FAST');
pdf.addImage(product.plusicon, 'PNG', 15, 830, 30, 30,'','FAST');
pdf.addImage(product.plusicon, 'PNG', 890, 830, 30, 30,'','FAST');
pdf.addImage(product.rightUpperTextImage, 'PNG', 740, 105, 124, 67,'','FAST');
pdf.addImage(product.leftUpperTextImage, 'PNG', 50, 80, 138, 105,'','FAST');
//draw black lines
pdf.setLineWidth(0.1);
pdf.setDrawColor(0,0,0); // draw black lines
pdf.line(0, 437.5, 29, 437.5); // horizontal line
pdf.line(906, 437.5, 935, 437.5);
pdf.line(467, 0, 467, 29); // vertical line
pdf.line(467, 846, 467, 877);
//console.log(pdf.internal.pageSize.getWidth());
pdf.addImage(url, 'JPEG', 23, 0, 888, 888,'','FAST');
pdf.addImage(product.bottomTextImage, 'PNG', 0, 0, 895, 895,'','FAST');
//window.open(pdf.output('bloburl'), '_blank');
const blob = pdf.output('blob');
const fileName = 'productPDF-' + self.configuratorId; // Specify your file name here
const file = new File([blob], fileName, {type: 'application/pdf'});
formData.append('pdf', file);
self.enable3d(formData);
//pdf.save("download.pdf");
//document.getElementById('canvasConvertedPDFImage').src= url
}, {crossOrigin: 'anonymous'})
}, ['excludeFromExportIn3D','name', 'filters', 'viewportTransform'])
},
disable3d() {
this.isInside3dView = false;
},
getSelectedProductObj() {
return this.products.find(product => product.name === this.selectedProduct);
},
fitImageInCanvas(img,canvas) {
//same logic as fitImageAndCanvasToContainer method except that here,
// parent will be canvas itself and canvas will not be resized anymore
const canvasWidth = canvas.width;
const canvasHeight = canvas.height;
const imgWidth = img.width;
const imgHeight = img.height;
// find the minimum of scale factor to which image should be scaled
// scaleFactor can be also lesser than 1, in which case image would be scaled down
const scaleFactor = Math.min(canvasWidth / imgWidth, canvasHeight / imgHeight);
// and then scale image to that factor and hence the other dimension will be automatically scaled in propotion
img.scaleToWidth(imgWidth * scaleFactor);
// this is for filling image into the canvas instead of fitting.
// img.scaleToWidth(canvasWidth);
// if(img.getScaledHeight() < canvasHeight) {
// img.scaleToHeight(canvasHeight);
// }
img.set({
left: (canvasWidth - img.getScaledWidth()) / 2,
top: (canvasHeight - img.getScaledHeight()) / 2,
});
canvas.renderAll();
},
setObjectFullSize() {
if(__canvas.getActiveObject()) {
this.fitImageInCanvas(__canvas.getActiveObject(), __canvas)
}
this.setVerticalCenter();
this.setHorizontalCenter();
},
setCenter(obj) {
//obj.center();
window.__canvas.viewportCenterObject(obj)
},
preventInteraction(obj) {
obj.set({
// hasControls: false,
// hasBorders: false,
// lockMovementX: true,
// lockMovementY: true,
// lockScalingX: true,
// lockScalingY: true,
// lockRotation: true,
selectable: false,
evented: false,
})
},
setCanvasClipPath(canvas) {
const self = this, product = this.getSelectedProductObj();
fabric.Image.fromURL(product.productImageUrl,function(img) {
self.fitImageInCanvas(img,canvas);
canvas.set({
clipPath: img
})
//canvas.add(img).renderAll()
self.setCenter(img);
}, {crossOrigin: 'anonymous'});
//this is for showing inner path
fabric.Image.fromURL(product.innerImageStrokeOnlyUrl,function(img) {
self.fitImageInCanvas(img, canvas);
self.setCenter(img);
self.preventInteraction(img)
img.set({
name: 'initOperationObj',
excludeFromExportIn3D: true,
//excludeFromExport: true,
zIndex: 1001
})
canvas.add(img)
}, {crossOrigin: 'anonymous'})
},
addProductTagImage(canvas) {
const self = this, product = this.getSelectedProductObj();
fabric.Image.fromURL(product.tagImageUrl,function(img) {
self.fitImageInCanvas(img,canvas);
self.preventInteraction(img)
img.set({
name: 'initOperationObj',
excludeFromExportIn3D: true,
//excludeFromExport: true,
zIndex: 1000
})
canvas.add(img)
}, {crossOrigin: 'anonymous'})
},
addOverlapImage(canvas) {
const self = this, product = this.getSelectedProductObj();
fabric.Image.fromURL(product.overlapImageUrl,function(img) {
self.fitImageInCanvas(img, canvas);
self.setCenter(img);
self.preventInteraction(img)
img.set({
name: 'initOperationObj',
excludeFromExportIn3D: true,
//excludeFromExport: true,
zIndex: 1002
})
canvas.add(img)
}, {crossOrigin: 'anonymous'})
},
removeObjectControls(obj) {
obj.setControlsVisibility({
ml: false,
mt: false,
mr: false,
mb: false,
// bl: false,
// br: false,
// tl: false,
// tr: false
})
},
getObjectsClipPathImg(canvas) {
const self = this, product = this.getSelectedProductObj();
return new Promise((resolve,reject) => {
try {
fabric.Image.fromURL(product.innerImageUrl,function(img, isError) {
if(isError) {
reject('image could not be loaded');
return;
}
self.fitImageInCanvas(img,canvas);
img.set({
absolutePositioned: true
})
self.setCenter(img);
self.preventInteraction(img)
resolve(img);
}, {crossOrigin: 'anonymous'})
} catch {
reject('image could not be loaded')
}
})
},
layers: [],
currentLayerId: 0,
selectedObjectLayerIndex: 0,
activeObjectLayerId: null,
updateActiveObjectLayerId(layerId) {
this.activeObjectLayerId = layerId;
},
setActiveObjectFromLayerId(layerId) {
//first update activeObjectLayerId
this.updateActiveObjectLayerId(layerId)
/// and then set active object in fabric from that layerId
const activeObject = __canvas.getObjects().find(obj => obj.id === layerId);
__canvas.setActiveObject(activeObject).renderAll();
},
objectAddEvents(canvas) {
const self = this;
canvas.on('object:added',function(options) {
const currentObj = options.target;
if(currentObj.name !== 'initOperationObj') {
if(!currentObj.addedViaHistory) {
if(!(currentObj.id)) {
currentObj.set({
id: 'layer-' + self.currentLayerId,
originX:'right',
originY: 'top',
idNumeric: self.currentLayerId,
objid: self.selectedObjectLayerIndex
});
self.removeObjectControls(currentObj)
if(currentObj.name === 'motive' || currentObj.name === 'symbol') {
currentObj.scaleToWidth(canvas.width/10 <= 50 ? 50 : canvas.width/10);
self.layers.unshift({
id: currentObj.id,
idNumeric: self.currentLayerId,
imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
type: currentObj.name,
objid: self.selectedObjectLayerIndex,
visible: currentObj.get('visible')
})
self.currentLayerId++;
}
if(currentObj.name === 'mainPicture' || currentObj.name === 'subPicture') {
self.fitImageInCanvas(currentObj,canvas);
self.layers.unshift({
id: currentObj.id,
idNumeric: self.currentLayerId,
imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
type: currentObj.name,
objid: self.selectedObjectLayerIndex,
visible: currentObj.get('visible')
})
self.currentLayerId++;
}
if(currentObj.name === 'label') {
if(currentObj.labelType !== 'template') {
const size = Math.max(parseInt(canvas.width/25), 22);
currentObj.set("fontSize", size)
currentObj.fontFamily = 'Times New Roman';
}
currentObj.fill = 'rgb(0,0,0)';
self.layers.unshift({
id: currentObj.id,
idNumeric: self.currentLayerId,
type: currentObj.name,
text: currentObj.text,
objid: self.selectedObjectLayerIndex,
visible: currentObj.get('visible')
})
self.currentLayerId++;
if(!currentObj.stateProperties.includes('backgroundMode')) {
currentObj.stateProperties.push('backgroundMode')
}
}
self.setCenter(currentObj)
currentObj.set({
zIndex: self.currentLayerId
});
self.sortObjects(canvas);
canvasHistory.add(new AddCommand(currentObj,canvas));
if(!(currentObj.name === 'symbol' || currentObj.name === 'label')) {
if(!currentObj.stateProperties.includes('filters')) {
currentObj.stateProperties.push('filters');
}
}
if(!currentObj.stateProperties.includes('selectable')) {
currentObj.stateProperties.push('selectable')
}
self.selectedObjectLayerIndex = self.layers.length;
self.isSaveButtonDisabled = false;
} else {
if(currentObj.name === 'motive' || currentObj.name === 'symbol' ||
currentObj.name === 'mainPicture' || currentObj.name === 'subPicture') {
self.layers.unshift({
id: currentObj.id,
idNumeric: currentObj.idNumeric,
imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
type: currentObj.name,
visible: currentObj.get('visible')
})
}
// if it is symbol object which is being restored now from json, check whether
// it was already saved with svgPathColor in json and if not only, then add that property
// to it's stateProperties array so it can be saved again to server with that prop
if(currentObj.name === 'symbol' && !currentObj.stateProperties.includes('svgPathColor')) {
currentObj.stateProperties.push('svgPathColor')
}
if(!(currentObj.name === 'symbol' || currentObj.name === 'label') && !currentObj.stateProperties.includes('filters')) {
currentObj.stateProperties.push('filters')
}
if(currentObj.name === 'label') {
if(!currentObj.stateProperties.includes('backgroundMode')) {
currentObj.stateProperties.push('backgroundMode')
}
self.layers.unshift({
id: currentObj.id,
idNumeric: currentObj.idNumeric,
text: currentObj.text,
type: currentObj.name,
visible: currentObj.get('visible')
})
}
if(!currentObj.stateProperties.includes('selectable')) {
currentObj.stateProperties.push('selectable')
}
}
currentObj.setCoords().saveState();
if(currentObj.filters) {
self.saveStateForFilters(currentObj)
}
} else {
//console.log(currentObj.zIndex)
if(currentObj.name === 'motive' || currentObj.name === 'symbol' ||
currentObj.name === 'mainPicture' || currentObj.name === 'subPicture') {
self.layers.unshift({
id: currentObj.id,
idNumeric: currentObj.idNumeric,
imageUrl: currentObj.name !== 'symbol' ? currentObj.getSrc() : currentObj.getGroupSVGSrc,
type: currentObj.name,
visible: currentObj.get('visible')
})
}
if(currentObj.name === 'label') {
self.layers.unshift({
id: currentObj.id,
idNumeric: currentObj.idNumeric,
text: currentObj.text,
type: currentObj.name,
visible: currentObj.get('visible')
})
}
Alpine.nextTick(() => {
if(currentObj.lastLayersArrayBeforeRemoval && currentObj.commandName !== 'AddCommand') {
//if there is lastLayersArrayBeforeRemoval associated when adding object from history,
// then sort objects as per that array and sort layers in reverse of that
sortable.sort(currentObj.lastLayersArrayBeforeRemoval);
sortableCanvas.sort(currentObj.lastLayersArrayBeforeRemoval);
self.sortFromLayers(currentObj.lastLayersArrayBeforeRemoval.slice().reverse());
}
})
}
}
//canvas.bringToFront(currentObj).renderAll();
})
},
objectRemoveEvents(canvas) {
const self = this;
canvas.on('object:removed',function(options) {
const currentObj = options.target;
const indexOfLayerToRemove = self.layers.findIndex(layer => layer.id === currentObj.id);
(indexOfLayerToRemove >= 0) && self.layers.splice(indexOfLayerToRemove,1)
if(!currentObj.removedViaHistory && currentObj.name !== 'initOperationObj') {
// this condition is here to check if object was removed due to removing manually
// or removed in event of reloading external data via restoreCanvasData
// if it was removed via restoreCanvasData operation i.e. if isCanvasDataLoading is set
// then we don;t need to add remove Caommand in history for that
if(!(self.isCanvasDataLoading)) {
canvasHistory.add(new RemoveCommand(currentObj,canvas));
}
self.selectedObjectLayerIndex = self.layers.length;
self.isSaveButtonDisabled = false;
}
})
},
saveStateForFilters(currentObj) {
currentObj.myOwnStateProps = {};
currentObj.myOwnStateProps.filters = []
currentObj.filters.forEach(function(filter, filterIndex) {
currentObj.myOwnStateProps.filters.push(filter)
})
},
objectUpdateEvents(canvas) {
canvas.on('object:modified', function (options) {
this.isSaveButtonDisabled = false;
const currentObj = options.target;
if(currentObj.name !== 'initOperationObj') {
// remove duplicate entries in stateProperties
currentObj.stateProperties = currentObj.stateProperties.filter((value, index, array) => {
return array.indexOf(value) === index;
})
canvasHistory.add(new TransformCommand(currentObj,canvas));
currentObj.saveState()
// specifically define myOwnStateProps and update filters over there
if(currentObj.filters) {
this.saveStateForFilters(currentObj)
}
}
}.bind(this))
},
syncLayerToObject(object) {
//console.log(object)
this.layers.find(layer => layer.id === object.id).visible = object.visible;
},
objectScaleEvents(canvas) {
const self = this;
canvas.on('object:scaling', function (options) {
const currentObj = options.target;
self.getSizeData(currentObj);
})
},
clearSelectionHandler() {
this.isObjectDeselected = true;
this.isFontStyleSaveBtnDisabled = true;
this.isCurveBlockDisabled = true;
this.isColorPickerBlockDisabled = true;
this.isObjectFilterControlsDisabled = true;
this.isTextBackgroundButtonDisabled = true;
// also update active layer to none by passing empty string so nothing is selected
this.updateActiveObjectLayerId('')
this.updateTextarea('');
this.isTextObjectSelected = false;
},
updateSelectionHandler(selectedObject) {
window.dispatchEvent(new CustomEvent('object-added-to-ai-canvas', {
detail: {
object: selectedObject
}
}))
this.updateActiveObjectLayerId(selectedObject.id)
this.isObjectDeselected = selectedObject.name === 'initOperationObj';
this.isColorPickerBlockDisabled = !(selectedObject.name === 'symbol' || selectedObject.name === 'label');
this.isObjectFilterControlsDisabled = (selectedObject.name === 'symbol' || selectedObject.name === 'label');
this.isTextObjectSelected = selectedObject.name === 'label';
this.isMarkObjectOptionDisabled = selectedObject.id === this.markedObject?.id;
this.isFontStyleSaveBtnDisabled = selectedObject.name !== 'label';
this.isCurveBlockDisabled = (selectedObject.name !== 'label') || (selectedObject._textLines?.length > 1) || selectedObject.textBackgroundColor;
this.isTextBackgroundButtonDisabled = (selectedObject.name !== 'label') || selectedObject.path;
if (selectedObject.name === 'label') {
this.updateTextarea(selectedObject.text);
window.dispatchEvent(new CustomEvent('object-selected-on-canvas', {
detail: {
objectColor: selectedObject.backgroundMode !== 'foreground' ? chroma(selectedObject.fill).hex() : chroma(selectedObject.textBackgroundColor).hex(),
backgroundMode: selectedObject.backgroundMode || 'none',
}
}))
}
if (selectedObject.name === 'symbol') {
window.dispatchEvent(new CustomEvent('object-selected-on-canvas', {
detail: {
objectColor: chroma(selectedObject.svgPathColor).hex(),
backgroundMode: selectedObject.backgroundMode || 'none'
}
}))
}
if (selectedObject) {
const selectedObjectLayer = sortable.toArray().find(layer => layer.includes(selectedObject.idNumeric));
this.selectedObjectLayerIndex = sortable.toArray().reverse().indexOf(selectedObjectLayer) + 1;
this.getSizeData(selectedObject);
}
if(!this.isObjectFilterControlsDisabled && selectedObject.filters) {
window.dispatchEvent(new CustomEvent('update-brightness-slider-value', {
detail: {
brightness: selectedObject.filters.find(filter => filter.brightness)?.brightness || 0
}
}))
window.dispatchEvent(new CustomEvent('update-contrast-slider-value', {
detail: {
contrast: selectedObject.filters.find(filter => filter.contrast)?.contrast || 0
}
}))
window.dispatchEvent(new CustomEvent('update-grayscale-switch-value', {
detail: {
grayscale: selectedObject.filters.find(filter => filter.type === 'Grayscale')
}
}))
}
},
objectSelectionEvents(canvas) {
const self = this;
canvas.on('selection:created', function(options) {
self.updateSelectionHandler(options.selected[0]);
}).on('selection:updated', function(options) {
self.updateSelectionHandler(options.selected[0]);
}).on('selection:cleared', function(options) {
self.clearSelectionHandler();
});
},
objectModifyEvents(canvas,img) {
const tempCanvasEl = fabric.util.createCanvasElement(),
tempCanvasCtx = tempCanvasEl.getContext('2d',{ willReadFrequently: true }),
tempCanvas = new fabric.Canvas(tempCanvasEl, {
preserveObjectStacking: true,
uniformScaling: true,
enableRetinaScaling: false,
width: canvas.width,
height: canvas.height,
});
tempCanvas.clipPath = img;
tempCanvasCtx.fillStyle = 'red';
tempCanvas.add(img).renderAll()
tempCanvasEl.style.zIndex = -1;
tempCanvasEl.style.display = 'none';
//appending in dom is not even necessary to getimagedata from canvas
document.getElementById('product-canvas-holder').append(tempCanvasEl);
// using event system from fabric itself we can claim that this code will
// be only executed once
canvas.on('object:modified', function (options) {
const currentObj = options.target;
//console.log(currentObj.left, currentObj.top)
//check if any of corners of object are not outside the inner image
//i.e by checking if it has pixel data below it
const allowMove = Object.values(currentObj.aCoords).every((corner) => {
//console.log(corner.x,corner.y)
const pxColor = tempCanvasCtx.getImageData(corner.x, corner.y, 1, 1).data;
return pxColor[3]
})
if(!allowMove && currentObj.name !== 'bgimage') {
//console.log( currentObj._stateProperties)
currentObj.set( {
top: currentObj._stateProperties.top,
left: currentObj._stateProperties.left,
angle: currentObj._stateProperties.angle,
scaleX: currentObj._stateProperties.scaleX,
scaleY: currentObj._stateProperties.scaleY
})
}
currentObj.setCoords();
currentObj.saveState();
})
},
renderIcon(icon) {
return function renderIcon(ctx, left, top, styleOverride, fabricObject) {
var size = this.cornerSize;
ctx.save();
ctx.translate(left, top);
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle));
ctx.drawImage(icon, -size/2, -size/2);
ctx.restore();
}
},
addCustomRotationControl() {
let self = this;
return new Promise((resolve,reject) => {
fabric.Image.fromURL(getObjectControlImages().rotationImage,function(img, isError) {
if(isError) {
reject(isError);
}
img.scaleToWidth(20);
const rotationIconDataUrl = img.toDataURL();
const rotationIcon = document.createElement('img');
rotationIcon.src = rotationIconDataUrl;
const mtr = new fabric.Control({
x: -0.5,
y: 0.5,
//offsetY: -16,
actionHandler: fabric.controlsUtils.rotationWithSnapping,
cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler,
withConnection: true,
actionName: 'rotate',
render: self.renderIcon(rotationIcon),
cornerSize: 20
});
fabric.Object.prototype.controls.mtr = mtr;
resolve();
}, {crossOrigin: 'anonymous'});
})
},
addCustomDeleteControl() {
let self = this;
return new Promise((resolve,reject) => {
fabric.Image.fromURL(getObjectControlImages().removeImage,function(img, isError) {
if(isError) {
reject(isError);
}
img.scaleToWidth(20);
const deleteIconDataUrl = img.toDataURL();
const deleteIcon = document.createElement('img');
deleteIcon.src = deleteIconDataUrl;
fabric.Object.prototype.controls.deleteControl = new fabric.Control({
x: -0.5,
y: -0.5,
cursorStyle: 'pointer',
mouseUpHandler: self.openDeleteModal,
render: self.renderIcon(deleteIcon),
cornerSize: 20
})
resolve();
}, {crossOrigin: 'anonymous'})
})
},
addCustomSizeUpControls() {
let self = this;
return new Promise((resolve,reject) => {
fabric.Image.fromURL(getObjectControlImages().sizeDownImage,function(img, isError) {
if(isError) {
reject(isError);
}
img.scaleToWidth(20);
const sizeDownIconDataUrl = img.toDataURL();
const sizeDownIcon = document.createElement('img');
sizeDownIcon.src = sizeDownIconDataUrl;
fabric.Object.prototype.controls.sizeDownControl = new fabric.Control({
x: 0.5,
y: -0.5,
offsetY: 24,
cursorStyle: 'pointer',
render: self.renderIcon(sizeDownIcon),
mouseUpHandler: () => self.decreaseObjectSize(),
cornerSize: 20
})
resolve()
}, {crossOrigin: 'anonymous'});
})
},
addCustomSizeDownControls() {
let self = this;
return new Promise((resolve,reject) => {
fabric.Image.fromURL(getObjectControlImages().sizeUpImage,function(img, isError) {
if(isError) {
reject(isError);
}
img.scaleToWidth(20);
const sizeUpIconDataUrl = img.toDataURL();
const sizeUpIcon = document.createElement('img');
sizeUpIcon.src = sizeUpIconDataUrl;
fabric.Object.prototype.controls.sizeUpControl = new fabric.Control({
x: 0.5,
y: -0.5,
cursorStyle: 'pointer',
render: self.renderIcon(sizeUpIcon),
mouseUpHandler: () => self.increaseObjectSize(),
cornerSize: 20
})
resolve()
}, {crossOrigin: 'anonymous'});
})
},
get isWebglSupported() {
return fabric.enableGLFiltering && fabric.isWebglSupported && fabric.isWebglSupported(fabric.textureSize);
},
manualInitFabricFilterBackend() {
// this will determine fabric.maxTextureSize and so we can only assign this
if (this.isWebglSupported) {
fabric.textureSize = fabric.maxTextureSize > 8192 ? 4096 : 2048;
webglBackend = new fabric.WebglFilterBackend({ tileSize: fabric.textureSize });
}
canvas2dBackend = new fabric.Canvas2dFilterBackend()
},
swapFilterBackend(isWebglBackendEnabled) {
if (isWebglBackendEnabled && webglBackend) {
fabric.filterBackend = webglBackend;
} else {
fabric.filterBackend = canvas2dBackend;
}
},
maxCanvasWidth: 4096,
maxCanvasHeight: 4096,
async setMaxCanvasSize() {
const { success, width, height} = await canvasSize.maxArea();
if(success) {
this.maxCanvasHeight = height;
this.maxCanvasWidth = width;
}
},
initFabricCanvas(el, $watch, $store) {
// manually init fabric filter backend
this.manualInitFabricFilterBackend();
const canvas = window.__canvas = new fabric.Canvas(el, {
preserveObjectStacking: true,
uniformScaling: true,
//stateful: true,
uniScaleTransform: true,
});
//this.enablePanning(canvas)
//const ratio = canvas.getWidth() / canvas.getHeight();
const canvasResizeEL = Alpine.store('elements').canvasResizeEL;
const offsetWidth = canvasResizeEL.offsetWidth - 2;
const offsetHeight = canvasResizeEL.offsetWidth - 2;
this.setCanvasDimensions({
width: offsetWidth,
height: offsetHeight
});
$watch('$store.canvas.configuratorId', (value, oldValue) => {
if(value !== oldValue) {
this.isConfiguratorIdChanged = true;
}
})
//until eventId is guaranteed to be unique, i can not uncomment this code
// $watch('$store.canvas.apiConfig.eventId', (value, oldValue) => {
// debugger;
// if(value !== oldValue) {
// this.isConfiguratorIdChanged = true;
// }
// })
this.initCanvasObjectsOperations(canvas);
this.setMaxCanvasSize();
},
disposeCanvas() {
window.__canvas.dispose();
},
async initCanvasObjectsOperations(canvas) {
const self = this;
//this.getObjectsClipPathImg(canvas).then(function(img){
self.objectAddEvents(canvas);
self.objectRemoveEvents(canvas);
self.objectUpdateEvents(canvas);
self.objectSelectionEvents(canvas);
//adding custom controls via custom control api
await this.addControls();
//self.objectModifyEvents(canvas,img);
self.objectScaleEvents(canvas);
if(self.configuratorId) {
self.restoreCanvasData(canvas).then(() => {
// all data has been loaded so now it's time to add mainPicture above it
this.canvasIsReady();
}).catch((error) => {
console.error(error)
// in case of unsuccessful restoration also, add mainPicture
this.canvasIsReady();
})
} else {
// when configurator id was empty, reset canvas data
this.resetCanvasData(canvas);
}
// }, function(error) {
// alert(error);
// });
},
async addControls() {
await this.addCustomRotationControl();
await this.addCustomDeleteControl();
await this.addCustomSizeUpControls();
await this.addCustomSizeDownControls();
},
async getDataUrlFromURL(imageURL) {
let response = await fetch(imageURL);
let blob = await response.blob();
let reader = new FileReader();
let dataURL = await new Promise(resolve => {
reader.onload = () => resolve(reader.result);
reader.readAsDataURL(blob);
});
return dataURL;
},
showCropSelectionRect: false,
canvasCropWidth: '',
canvasCropHeight: '',
finalImageWidth: 2000,
finalImageHeight: 2000,
canvasWidth: '',
canvasHeight: '',
sizeTemplates: [],
sizeTemplatesForWorkspace: [],
scalingStretegy: 'fit',
toggleCropSelectionRect() {
const cropSelectionRect = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');
if(cropSelectionRect) {
cropSelectionRect.set({
visible: this.showCropSelectionRect,
})
if(!this.showCropSelectionRect) {
__canvas.discardActiveObject()
}
__canvas.set({
clipPath: this.showCropSelectionRect ? cropSelectionRect : null
})
}
__canvas.renderAll()
},
updateCropSelectionRect() {
const cropSelectionRect = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');
if(cropSelectionRect) {
if( !(isNaN(this.canvasCropWidth)) && !(isNaN(this.canvasCropHeight)) ) {
this.canvasCropWidth = parseInt(this.canvasCropWidth)
this.canvasCropHeight = parseInt(this.canvasCropHeight)
// height width with viewporttransform offset
const actualCanvasWidth = __canvas.width / __canvas.viewportTransform[0];
const actualCanvasHeight = __canvas.height / __canvas.viewportTransform[3]
if(this.canvasCropWidth > actualCanvasWidth || this.canvasCropHeight > actualCanvasHeight) {
//if(false) {
if(this.canvasCropWidth > this.canvasCropHeight) {
cropSelectionRect.set({
width: actualCanvasWidth,
height: actualCanvasWidth * this.canvasCropHeight / this.canvasCropWidth
})
} else if (this.canvasCropHeight > this.canvasCropWidth) {
cropSelectionRect.set({
height: actualCanvasHeight,
width: actualCanvasWidth * this.canvasCropWidth / this.canvasCropHeight
})
} else {
cropSelectionRect.set({
height: actualCanvasWidth,
width: actualCanvasHeight
})
}
} else {
cropSelectionRect.set({
width: this.canvasCropWidth,
height: this.canvasCropHeight
})
}
}
// always reset scale of crop rectangle back to 1
// to have the actual size and not scaled
cropSelectionRect.scale(1)
this.setCenter(cropSelectionRect)
}
},
async setCanvasBackground(url) {
const canvas = window.__canvas;
//const dataURL = await this.getDataUrlFromURL(url);
if(!url) {
canvas.set({
backgroundImage: null
})
canvas.renderAll();
return;
}
fabric.Image.fromURL(url, (img) => {
//img.set({ crossOrigin: 'Anonymous' });
img.scaleToWidth(canvas.width/canvas.viewportTransform[0]);
if(img.getScaledHeight() < (canvas.height/canvas.viewportTransform[3])) {
img.scaleToHeight(canvas.height/canvas.viewportTransform[3]);
}
const leftOffset = ((canvas.width - img.getScaledWidth()).toFixed(2))/2;
const topOffset = ((canvas.height - img.getScaledHeight()).toFixed(2))/2;
let centerPoint = {
x: (canvas.width/canvas.viewportTransform[0])/2,
y: (canvas.height/canvas.viewportTransform[3])/2
}
//console.log(centerPoint)
///console.log(fabric.util.transformPoint(centerPoint, canvas.viewportTransform))
img.set({
left: centerPoint.x,
top: centerPoint.y,
name: 'bgimage',
originX: 'center',
originY: 'center',
});
canvas.backgroundImage = img;
//canvas.add(img);
this.isSaveButtonDisabled = false;
canvas.requestRenderAll();
}, { crossOrigin: 'anonymous' })
},
sortFromLayers(layers) {
layers.forEach(function(layer,layerIndex,layers) {
window.__canvas.getObjects().forEach(function(obj) {
if(obj.id === layer) {
obj.zIndex = layerIndex;
}
})
})
this.sortObjects(window.__canvas);
const selectedObject = window.__canvas.getActiveObject();
if (selectedObject) {
const selectedObjectLayer = layers.find(layer => layer.includes(selectedObject.id));
this.selectedObjectLayerIndex = layers.indexOf(selectedObjectLayer) + 1;
}
},
sortObjects(canvas) {
canvas._objects.sort((a, b) => (a.zIndex > b.zIndex) ? 1 : -1);
canvas.renderAll();
},
setCanvasDimensions(containerSize) {
const canvas = window.__canvas;
canvas.setDimensions(containerSize);
const {width, height} = containerSize;
// this.canvasWidth = width;
// this.canvasHeight = height;
this.canvasCropWidth = Math.round((width / canvas.viewportTransform[0]) * 90 / 100);
this.canvasCropHeight = Math.round((height / canvas.viewportTransform[3]) * 90 / 100);
this.updateCropSelectionRect();
this.oldContainerWidth = Alpine.store('elements').canvasResizeEL.offsetWidth - 2;
canvas.renderAll();
},
addCropRectangle(canvas) {
this.showCropSelectionRect = false;
__canvas.clipPath = null;
__canvas.renderAll();
const cropRectangle = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');
if(cropRectangle) {
cropRectangle.set({visible: false})
}
if(!cropRectangle) {
const self = this;
const cropSelectionRect = new fabric.Rect({
absolutePositioned: true,
name: 'initOperationObj',
altName: 'cropRectangle',
fill: '#ff0000',
opacity: 0.01,
// originX: 'right',
// originY: 'bottom',
stroke: 'black',
visible: false,
strokeWidth: 0,
width: Math.round((canvas.width / canvas.viewportTransform[0]) * 90 / 100),
height: Math.round((canvas.height / canvas.viewportTransform[3]) * 90 / 100),
strokeUniform: true,
cornerStyle: 'circle',
transparentCorners: false,
zIndex: 1002,
excludeFromExport: true,
})
cropSelectionRect.on('modified', function(options) {
self.canvasCropWidth = Math.round(cropSelectionRect.getScaledWidth());
self.canvasCropHeight = Math.round(cropSelectionRect.getScaledHeight());
})
cropSelectionRect.setControlsVisibility({
deleteControl: false,
mtr: false,
sizeUpControl: false,
sizeDownControl: false,
})
// controls are not working properly
//addCropRectangleControls(cropSelectionRect)
this.setCenter(cropSelectionRect)
canvas.add(cropSelectionRect);
}
},
oldContainerWidth: '',
/**
*
* @param {canvas instance} canvas
* @param {containersize which was before window resize(oldContainerWidth), this could also come as } sizeBeforeResize
* @param {check whether resizeCanvas method is called during resizing window(true) or during restoring data from server(false)} isResizingViaWindow
* @param {only present and applicable when method is called during restoring data from server} savedCanvasDim
*/
resizeCanvas(canvas, sizeBeforeResize, isResizingViaWindow = true, savedCanvasDim) {
const canvasResizeEL = Alpine.store('elements').canvasResizeEL;
let containerWidth = canvasResizeEL.offsetWidth - 2;
//console.log(containerSize)
if(containerWidth === 0 || containerWidth < 0) {
containerWidth = sizeBeforeResize;
}
let finalCanvasDim = {
width: containerWidth,
height: containerWidth,
}
const newScale = containerWidth / sizeBeforeResize;
// if(newScale === 0) {
// newScale = 1;
// }
// keep in mind that we also call this method after loading data from json
// for the first time and hence there should be no oldScale present earlier at that point, so it will be 1
const oldScale = isResizingViaWindow ? canvas.viewportTransform[0] : 1;
//console.log(newScale, oldScale);
// Store the current filter settings
const currentFilter = canvas.getContext().filter;
// canvas.setZoom(newScale * oldScale);
canvas.setViewportTransform([newScale * oldScale, 0,0, newScale * oldScale,0,0]);
if(savedCanvasDim) {
finalCanvasDim = {
width: savedCanvasDim?.width * newScale,
height: savedCanvasDim?.height * newScale
}
}
this.setCanvasDimensions(finalCanvasDim)
// restore values in size templates input when restored
this.canvasWidth = Math.round(finalCanvasDim.width);
this.canvasHeight = Math.round(finalCanvasDim.height);
// Reapply the filter settings
if (currentFilter) {
canvas.getContext().filter = currentFilter;
}
canvas.renderAll();
//canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), newScale * oldScale)
//canvas.setViewportTransform([zoom, 0, 0, zoom, 0, 0]);
},
zoomCount: 1,
zoomInCanvas() {
const canvas = window.__canvas;
const currentZoom = canvas.getZoom();
canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), currentZoom + 0.1);
this.zoomCount++;
},
zoomOutCanvas() {
const canvas = window.__canvas;
const currentZoom = canvas.getZoom();
canvas.zoomToPoint(new fabric.Point(canvas.width / 2, canvas.height / 2), currentZoom - 0.1);
this.zoomCount--;
},
resetCurrentZoom(canvas) {
while(this.zoomCount !== 1) {
this.zoomCount > 1 ? this.zoomOutCanvas() : this.zoomInCanvas()
}
},
isFullScreen: false,
setFullScreenCanvas() {
//console.log('called')
// this.isFullScreen = !this.isFullScreen;
// window.dispatchEvent(new Event('resize'));
},
enablePanning(canvas) {
canvas.on('mouse:down', function(opt) {
const evt = opt.e;
if (evt.altKey === true) {
this.isDragging = true;
this.selection = false;
this.lastPosX = evt.clientX;
this.lastPosY = evt.clientY;
}
});
canvas.on('mouse:move', function(opt) {
if (this.isDragging) {
const e = opt.e;
var vpt = this.viewportTransform;
vpt[4] += e.clientX - this.lastPosX;
vpt[5] += e.clientY - this.lastPosY;
this.requestRenderAll();
this.lastPosX = e.clientX;
this.lastPosY = e.clientY;
}
});
canvas.on('mouse:up', function(opt) {
// on mouse up we want to recalculate new interaction
// for all objects, so we call setViewportTransform
this.setViewportTransform(this.viewportTransform);
this.isDragging = false;
this.selection = true;
});
},
async addShapeToCanvas(url, type) {
const canvas = window.__canvas;
//const dataURL = await this.getDataUrlFromURL(url);\
if(type === 'motive') {
const shapeToAdd = fabric.Image.fromURL(url,function(img) {
img.name = type;
canvas.add(img);
}, {crossOrigin: 'anonymous'})
}
if(type === 'symbol') {
const shapeToAdd = fabric.loadSVGFromURL(url,function(objects, options) {
let svg = fabric.util.groupSVGElements(objects, options);
svg.name = type;
//whenever we need to add custom property to save as a history
// we need to assign that property(svgPathColor in this case) to the object even before that object has been\
// added to the canvas and also push that property into stateProperties array of the object
svg.stateProperties.push('svgPathColor')
svg.svgPathColor = '#000000';
svg.id = null; //we must not need id which was automatically assigned by fabric
svg.getGroupSVGSrc = url;
canvas.add(svg);
},function() {}, {crossOrigin: 'anonymous'})
}
},
async addPictureToCanvas(url,type) {
const canvas = window.__canvas;
//const dataURL = await this.getDataUrlFromURL(url);\
fabric.Image.fromURL(url,function(img) {
img.name = type;
canvas.add(img);
}, {crossOrigin: 'anonymous'})
},
canvasIsReady() {
window.dispatchEvent(new CustomEvent('canvas-is-ready'))
this.addCropRectangle(__canvas);
},
addLabelToCanvas(text) {
const canvas = window.__canvas;
const label = new fabric.Text(text);
label.name = 'label';
canvas.add(label);
},
addTemplateToCanvas(template) {
const canvas = window.__canvas;
const label = new fabric.Text(template.text, {
name: 'label',
labelType: 'template',
fontFamily: template.fontFamily,
fontSize: template.fontSize / __canvas.viewportTransform[0],
fontWeight: template.fontWeight
});
label.name = 'label';
canvas.add(label);
},
increaseObjectSize() {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
if(activeObj) {
const newScale = ((activeObj.scaleX) + (activeObj.scaleX/10));
activeObj.scale(newScale)
canvas.fire('object:modified', {target: activeObj});
this.getSizeData(activeObj);
canvas.renderAll();
}
},
decreaseObjectSize() {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
if(activeObj) {
const newScale = ((activeObj.scaleX) - (activeObj.scaleX/10));
activeObj.scale(newScale);
canvas.fire('object:modified', {target: activeObj});
this.getSizeData(activeObj);
canvas.renderAll();
}
},
_curvatureValue: 60,
get curvatureValue() {
return this._curvatureValue;
},
set curvatureValue(val) {
this._curvatureValue = Math.min(500, Math.max(0, val))
},
isCurveTextboxDisabled: true,
curveDirection: null,
applyCurvature() {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
if (activeObj && activeObj.name === 'label') {
const originalWidth = activeObj.width;
const originalHeight = activeObj.height;
const inputValue = parseFloat(this.curvatureValue);
if (inputValue === null || isNaN(inputValue) || inputValue <= 0) {
activeObj?.set({
path: null,
selectable: true,
});
} else {
const canvasCenterX = canvas.width / 2;
const canvasCenterY = canvas.height / 2;
const curvatureFactor = inputValue / 80;
let pathData;
if (this.curveDirection === 'upcurve') {
const controlPointY = canvasCenterY + originalHeight * curvatureFactor;
pathData = `M ${canvasCenterX - originalWidth / 2} ${canvasCenterY}
Q ${canvasCenterX} ${controlPointY}
${canvasCenterX + originalWidth / 2} ${canvasCenterY}`;
} else if (this.curveDirection === 'downcurve') {
const controlPointY = canvasCenterY - originalHeight * curvatureFactor;
pathData = `M ${canvasCenterX - originalWidth / 2} ${canvasCenterY}
Q ${canvasCenterX} ${controlPointY}
${canvasCenterX + originalWidth / 2} ${canvasCenterY}`;
}
const textPath = new fabric.Path(pathData, {
strokeWidth: 1,
visible: false,
});
activeObj?.set({
path: textPath,
pathSide: 'center',
pathStartOffset: 0,
selectable: true,
});
}
this.updateSelectionHandler(activeObj)
// Set the width and height back to the original values
activeObj?.set({ width: originalWidth, height: originalHeight });
//canvas.requestRenderAll();
canvas.renderAll();
canvas.fire('object:modified', {target: activeObj});
}
},
activeObjectWidth: '',
activeObjectHeight: '',
getSizeData(activeObj) {
this.activeObjectWidth = "Width: " + activeObj.getScaledWidth().toFixed(2) + "px";
this.activeObjectHeight = "Height: " + activeObj.getScaledHeight().toFixed(2) + "px";
},
markedObject: null,
isMarkObjectOptionDisabled: false,
markObject() {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
this.markedObject?.set({
borderColor: '#b2ccff',
});
activeObj.set({
borderColor: 'green',
});
canvas.renderAll();
this.markedObject = activeObj;
this.isMarkObjectOptionDisabled = true;
},
applySameSizeAsMarkedObject() {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
if (this.markedObject && activeObj !== this.markedObject) {
const markedWidth = this.markedObject.width * this.markedObject.scaleX;
const markedHeight = this.markedObject.height * this.markedObject.scaleY;
activeObj.scaleToWidth(markedWidth);
activeObj.scaleToHeight(markedHeight);
activeObj.setCoords();
canvas.renderAll();
}
},
async crop($store) {
const activeObj = __canvas.getActiveObject();
let fileUrl;
//let fileUrl = 'http://127.0.0.1/storage/product-configurators/PiSa7SPjMd8rkMX9FqXwkwsG5EXaZuDZowG21taD.png';
if(activeObj.name !== 'symbol') {
fileUrl = __canvas.getActiveObject().getSrc();
} else {
fileUrl = __canvas.getActiveObject().getGroupSVGSrc;
}
const fileName = fileUrl.split('/').pop();
const response = await fetch(fileUrl);
const blob = await response?.blob();
const contentType = response?.headers?.get('Content-Type')
const file = new File([blob], fileName, {type: contentType, lastModified: Date.now()});
$store.cropperStore.prepareCropperImage({
originalImageFile: file,
fileName: file.name,
url: fileUrl,
cropperData: null,
uploadWithoutConfig: true,
fileType: __canvas.getActiveObject().name === 'mainPicture' ? 'mainPictures' : 'shapes',
el: __canvas.getActiveObject().name === 'mainPicture' ? $store.elements.pictureUploadEL : $store.elements.shapeUploadEL
})
},
replaceActiveObjectUrl(url) {
const activeObj = __canvas.getActiveObject();
activeObj.setSrc(url, function(img) {
__canvas.renderAll();
}, {crossOrigin: 'anonymous'});
},
updateTextarea(text) {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
window.dispatchEvent(new CustomEvent('update-text-area', {
detail: {
text: text
}
}))
},
updateObjectText(value) {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
if (activeObj && activeObj.name === 'label') {
activeObj?.set({
path: null,
selectable: true,
text: value
});
canvas.renderAll();
}
},
_shadowDistance: 0,
get shadowDistance() {
return this._shadowDistance;
},
set shadowDistance(val) {
this._shadowDistance = val;
},
_blurDistance: 0,
get blurDistance() {
return this._blurDistance;
},
set blurDistance(val) {
this._blurDistance = val;
},
shadowEffect(action) {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
const shadowValue = parseInt(this.shadowDistance, 10);
const blurValue = parseInt(this.blurDistance,10);
const shadow = new fabric.Shadow({
color: 'rgba(0,0,0,0.6)',
blur: blurValue,
offsetX: 4,
offsetY: shadowValue,
});
activeObj.set({
shadow: action === 'apply' ? shadow : null,
});
canvas.requestRenderAll();
},
setHorizontalCenter() {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
if(activeObj) {
canvas.viewportCenterObjectH(activeObj);
canvas.fire('object:modified', {target: activeObj});
}
//activeObj?.centerH();
},
setVerticalCenter() {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
if(activeObj) {
canvas.viewportCenterObjectV(activeObj);
canvas.fire('object:modified', {target: activeObj});
}
//activeObj?.centerV();
},
setAngle(angle) {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject();
activeObj?.rotate(angle);
canvas.renderAll();
},
openDeleteModal() {
Alpine.store('modal').openModal(Alpine.store('elements').deleteObjectModalEL)
},
removeObject() {
const canvas = window.__canvas, self = this;
const activeObjects = canvas.getActiveObjects();
canvas.discardActiveObject();
activeObjects.forEach(function(activeObject) {
//append last position of layers as property along with object called lastLayersArrayBeforeRemoval
activeObject.lastLayersArrayBeforeRemoval = sortable.toArray().filter(item => item !== 'ignore');
canvas.remove(activeObject);
canvas.renderAll()
})
},
toggleObjectById(id, $nextTick) {
const objectToToggle = __canvas.getObjects().find(obj => obj.id === id);
const isVisible = objectToToggle.get('visible')
objectToToggle.set({
visible: !isVisible,
selectable: !isVisible,
})
if(!(objectToToggle.get('visible'))) {
$nextTick(() => {
__canvas.discardActiveObject().renderAll();
})
}
this.layers.find(layer => layer.id === id).visible = !isVisible;
__canvas.fire('object:modified', {target: objectToToggle});
__canvas.renderAll()
},
setLabelBackgroundColor(labelBackgroundObj) {
const canvas = window.__canvas;
const activeObj = canvas?.getActiveObject();
activeObj.set({
fill: labelBackgroundObj.color,
textBackgroundColor: labelBackgroundObj.textBackgroundColor,
bgCornerRadius: 10,
backgroundMode: labelBackgroundObj.backgroundMode
})
this.updateSelectionHandler(activeObj)
canvas.fire('object:modified', {target: activeObj});
canvas.renderAll()
},
setLabelColor(color) {
const canvas = window.__canvas;
const activeObj = canvas?.getActiveObject(),
activeObjName = activeObj?.name;
if(activeObjName === 'label') {
activeObj.set({
fill: color
});
canvas.fire('object:modified', {target: activeObj});
canvas.renderAll()
}
if(activeObjName === 'symbol') {
let objects = activeObj._objects;
// first store reverse order of previous and next color values
// and savestate to store them in _stateProperties for history prevState
;
objects?.filter(object => object.type === 'path').forEach(function(object) {
if(object.fill !== '#fff' || object.fill === '#ffffff' || object.fill === 'rgb(255,255,255)') {
object.set({fill: color})
}
})
// after changing path color, reverse the next and previous to
// store them in history state object via get method
activeObj.svgPathColor = color;
// finally trigger history update via modifying object
canvas.fire('object:modified', {target: activeObj});
canvas.renderAll()
}
},
setCanvasBackgroundColor(color) {
__canvas.set({
backgroundImage: null,
backgroundColor: color
});
__canvas.renderAll();
},
setMotiveOrPictureBackgroundColor(color) {
const activeObj = __canvas.getActiveObject()
if(activeObj && (activeObj.name === 'motive' || activeObj.name === 'mainPicture' || activeObj.name === 'subPicture')) {
activeObj.set({
backgroundColor: color
})
__canvas.renderAll();
}
},
setCanvasGradient(colorStops, angle) {
// colorStops.forEach(function(colorStop,index) {
// console.log(colorStop, angle)
// })
const canvas = window.__canvas;
const gradientCoords = this.calculateGradientCoords(angle, canvas.width, canvas.height);
const gradient = new fabric.Gradient({
type: "linear",
coords: gradientCoords,
colorStops: colorStops,
});
canvas.set({
backgroundImage: null,
backgroundColor: gradient
})
canvas.renderAll();
},
calculateGradientCoords(angle, canvasWidth, canvasHeight) {
// Convert angle to radians for calculations
const angleRad = (angle * Math.PI) / 180;
// Calculate the direction vector components based on the angle
const dx = Math.cos(angleRad);
const dy = Math.sin(angleRad);
// Determine the length of the gradient line. This could be the diagonal of the canvas
// for angles that are not aligned with the axes, to ensure the gradient covers the entire area.
const gradientLength = Math.sqrt(canvasWidth * canvasWidth + canvasHeight * canvasHeight);
// Calculate the start and end points of the gradient line based on the direction vector
// and the gradient length. We center the gradient on the canvas.
const centerX = canvasWidth / 2;
const centerY = canvasHeight / 2;
const x1 = centerX - dx * gradientLength / 2;
const y1 = centerY - dy * gradientLength / 2;
const x2 = centerX + dx * gradientLength / 2;
const y2 = centerY + dy * gradientLength / 2;
return { x1, y1, x2, y2 };
},
setLabelFont(font) {
const canvas = window.__canvas;
const activeObj = canvas.getActiveObject(),
activeObjType = activeObj?.type;
if(activeObjType === 'text') {
activeObj.set({
fontFamily:font
});
canvas.fire('object:modified', {target: activeObj});
canvas.renderAll()
}
},
saveCanvasData(bundleFile) {
const canvas = window.__canvas;
const brightnessMatch = canvas.getContext().filter.match(/brightness\((\d+(\.\d+)?)\)\s*/);
const contrastMatch = canvas.getContext().filter.match(/contrast\((\d+(\.\d+)?)\)\s*/)
const grayScaleMatch = canvas.getContext().filter.match(/grayscale\s*\((\d+(?:\.\d+)?|\d+%)\)\s*/)
this.isServerLoaderCanvasVisible = true;
const currentJSON = {
savedCanvasSize: this.oldContainerWidth,
savedCanvasDim: {width: canvas.getWidth(), height: canvas.getHeight()},
savedViewportTransform: canvas.viewportTransform,
canvasBrightness: brightnessMatch ? parseFloat(brightnessMatch[1]) : null,
canvasContrast: contrastMatch ? parseFloat(contrastMatch[1]) : null,
canvasGrayScale: grayScaleMatch ? parseFloat(grayScaleMatch[1]) : null,
sizeTemplates: this.sizeTemplates
};
const canvasJSON = canvas.toJSON([
'id','idNumeric','backgroundImage', 'name', 'labelType', 'selectable',
'evented','zIndex','excludeFromExportIn3D','svgPathColor','getGroupSVGSrc', 'filters', 'backgroundMode']);
this.canvasData = Object.assign(currentJSON, canvasJSON)
const formData = new FormData();
formData.append('configuratorId', this.configuratorId || null)
formData.append('product3dImage', bundleFile)
const blobdata = JSON.stringify(this.canvasData);
formData.append('canvasData', blobdata)
let endpointUrl;
// formData.get('configuratorId') and this.configuratorId both conditions are same because we already appended that to formdata above
if(this.configuratorId) {
// if configuratorId already exists it means this is an update endpoint to update existing configurator
formData.append('_method', 'PUT');
endpointUrl = `${this.apiConfig.apiUrl}/api/v1/product-configurators/${formData.get('configuratorId')}`;
} else {
// this url is for first time save endpoint if configuratorId does not exist yet
// also append productId to formdata if it is first time save to make connection between productId and configuratorId
endpointUrl = `${this.apiConfig.apiUrl}/api/v1/product-configurators`;
formData.append('productId', this.productId || null);
}
return fetch(endpointUrl, {
method: "POST",
body: formData,
"timeout": 0,
"headers": {
"Authorization": `Bearer ${this.apiConfig.apiToken}`,
},
redirect: 'follow'
}).then((response) => {
return response.json()
})
.then((data) => {
Alpine.store('canvas').isServerLoaderCanvasVisible = false;
if(data?.data) {
Alpine.store('canvas').configuratorId = data.data.toString();
this.isSaveButtonDisabled = true;
window.dispatchEvent(new CustomEvent('canvas-data-saved', {
detail: {
configuratorId: data.data
}
}))
// even if configuratorId might be changed,
//we don;t want to set isConfiguratorIdChanged to true at this point
// due to fact that if someone opens modal again, again he will see the same thing
// inshort - isConfiguratorIdChanged should be only be possible to true from livewire
this.isConfiguratorIdChanged = false;
}
return data;
})
.catch((error) => {
console.error(error);
});
},
isCanvasDataLoading: false,
resetCanvasData(canvas) {
__canvas.setBackgroundColor('transparent')
this.isCanvasDataLoading = true;
const objectsToRemove = canvas.getObjects().filter(obj => obj.name !== 'initOperationObj')
canvas.remove(...objectsToRemove);
canvas.backgroundImage = null;
canvasHistory.clear();
canvas.requestRenderAll(); // Refresh/render the canvas
this.isCanvasDataLoading = false;
this.isCanvasResetBtnDisabled = true;
this.isSaveButtonDisabled = true;
// due to the fact that mainPicture is already updated (when configuratorId is changed) in it's array before opening modal,
// we can use this method to find the one from it which was not manually uploaded (i.e. which does not have isRemovable property)
// but rather was just updated from consumer side
this.canvasIsReady();
},
async restoreCanvasData(canvas) {
this.isServerLoaderCanvasVisible = true;
canvasHistory.clear();
this.isConfiguratorIdChanged = false;
return new Promise((resolve, reject) => {
fetch(`${this.apiConfig.apiUrl}/api/v1/product-configurators/${this.configuratorId}?isGlobal=true`, {
method: "GET",
headers: {
"Authorization": `Bearer ${this.apiConfig.apiToken}`,
},
redirect: 'follow'
}).then((response) => {
if (response.ok) {
return response.json()
} else {
throw new Error('Network response was not OK');
}
})
.then((data) => {
if(data.data) {
//console.log(data.data)
this.isServerLoaderCanvasVisible = false;
const loadingData = data?.data?.canvasData?.savedCanvasSize ? data.data.canvasData : '';
this.isCanvasDataLoading = !!loadingData;
// update background, color, shape, font and mainPictures
// updatre canvas contrast/brightness which is not depedent on loaded data
this.updateAssets(data)
if(loadingData) {
canvas.loadFromJSON(loadingData,function() {
this.isCanvasDataLoading = false;
this.sortObjects(canvas);
//whenever reloading again from server resize whole canvas so that it
// is auto adjustred to the width for which it was made for
let savedCanvasDim;
if(loadingData.savedCanvasDim) {
savedCanvasDim = {
width: loadingData.savedCanvasDim.width / loadingData.savedViewportTransform[0],
height: loadingData.savedCanvasDim.height / loadingData.savedViewportTransform[3]
}
}
this.resizeCanvas(canvas, loadingData.savedCanvasSize / loadingData.savedViewportTransform[0], false, savedCanvasDim)
//get array of id of all canvas objects whose id is nummeric
// and find maximum of them to apply it to maximum
const allNumericObjects = canvas.getObjects().filter(obj => typeof obj.idNumeric === 'number').map(obj => obj.idNumeric);
if(allNumericObjects?.length) {
const maxObjID = Math.max(...(allNumericObjects))
this.currentLayerId = maxObjID + 1;
}
// if(typeof object.idNumeric === 'number') {
// this.currentLayerId = object.idNumeric + 1;
// }
this.isSaveButtonDisabled = true;
resolve(data);
}.bind(this), function (o, object) {
});
} else {
// it might be possible that configurator exists but have no canvasData
// so in this case as well, we need to reset everything on canvas
this.resetCanvasData(canvas);
// else directly resolve promise with returned data
resolve(data);
}
} else {
this.isServerLoaderCanvasVisible = false;
reject('data could not be loaded');
}
})
.catch((error) => {
this.isServerLoaderCanvasVisible = false;
console.error(error);
reject(error)
});
})
},
updateAssets(data) {
let sizeTemplates = [];
if(data?.data?.history && data.data.history.length) {
data.data.history.forEach((record) => {
if(record.canvasData.sizeTemplates) {
let sizeTemplatesForRecord = record.canvasData.sizeTemplates;
sizeTemplatesForRecord.forEach((sizeTemplateForRecord) => {
sizeTemplates.push(sizeTemplateForRecord)
})
}
})
}
if(data.data.backgrounds) {
window.dispatchEvent(new CustomEvent('backgrounds-added-from-api', {
detail: {
backgrounds: data.data.backgrounds
},
bubbles: true
}
));
}
if(data.data.shapes) {
window.dispatchEvent(new CustomEvent('shapes-added-from-api', {
detail: {
shapes: data.data.shapes
},
bubbles: true
}
));
}
if(data.data.colors) {
window.dispatchEvent(new CustomEvent('colors-added-from-api', {
detail: {
colors: data.data.colors
},
bubbles: true
}
));
}
if(data.data.fonts) {
window.dispatchEvent(new CustomEvent('fonts-added-from-api', {
detail: {
fonts: data.data.fonts
},
bubbles: true
}
));
}
if(data.data.mainPictures) {
window.dispatchEvent(new CustomEvent('mainPictures-added-from-api', {
detail: {
mainPictures: data.data.mainPictures
},
bubbles: true
}
));
}
window.dispatchEvent(new CustomEvent('canvas-data-restored', {
detail: {
history: data.data.history,
canvasBrightness: data?.data?.canvasData?.canvasBrightness,
canvasContrast: data?.data?.canvasData?.canvasContrast,
canvasGrayScale: data?.data?.canvasData?.canvasGrayScale,
sizeTemplates: sizeTemplates
},
}
));
},
drawingCanvas: null,
savedImageDownloadUrl: '',
savedImageFileSize: '',
savedImageQuality: 100,
savedImageFormat: 'png',
resetScaleForFabricCanvas() {
__canvas.setDimensions({
width: __canvas.getWidth() / __canvas.viewportTransform[0],
height: __canvas.getHeight() / __canvas.viewportTransform[3]
})
__canvas.setViewportTransform([1,0,0,1,0,0])
},
scaleFabricCanvas(factor) {
__canvas.setViewportTransform([factor, 0, 0, factor, 0, 0])
__canvas.setDimensions({
width: __canvas.getWidth() * factor,
height: __canvas.getHeight() * factor
})
},
updateDownloadUrl() {
const self = this;
const existingFilter = __canvas.getElement().getContext('2d').filter;
this.resetCurrentZoom(__canvas)
// store original transform before resetting it to normal
const currentVpt = __canvas.viewportTransform;
const currentCanvasDimension = {
width: __canvas.getWidth(),
height: __canvas.getHeight()
}
this.resetScaleForFabricCanvas()
this.finalImageWidth = parseInt(this.finalImageWidth);
this.finalImageHeight = parseInt(this.finalImageHeight);
// discard all active objects before drawing on new canvas
__canvas.discardActiveObject();
__canvas.renderAll();
if(!this.drawingCanvas) {
this.drawingCanvas = document.createElement("canvas");
}
const drawingContext = this.drawingCanvas.getContext("2d");
this.drawingCanvas.width = this.finalImageWidth;
this.drawingCanvas.height = this.finalImageHeight;
drawingContext.filter = existingFilter;
drawingContext.clearRect(0, 0, this.drawingCanvas.width, this.drawingCanvas.height)
if(!this.showCropSelectionRect) {
let drawWidth, drawHeight;
const canvasWidth = __canvas.width
const canvasHeight = __canvas.height;
const widthFactor = this.finalImageWidth/canvasWidth;
const heightFactor = this.finalImageHeight/canvasHeight;
let factor = this.scalingStretegy === 'fit' ? Math.min(widthFactor, heightFactor) : Math.max(widthFactor, heightFactor);
// we also need max factor here to account for the maximum canvas size that can be available
const maxFactor = Math.min(this.maxCanvasWidth/__canvas.getWidth(), this.maxCanvasHeight/__canvas.getHeight());
factor = Math.min(factor, maxFactor)
this.scaleFabricCanvas(factor)
if(this.scalingStretegy === 'fit') {
// check if width factor is greater than height, minimum division is height
// then drawHeight will be finalImageHeight and
// drawWidth will be calculated by multiplying canvasCropWidth with same heightFactor
if(widthFactor > heightFactor) {
drawWidth = canvasWidth * heightFactor;
drawHeight = this.finalImageHeight;
} else {
drawWidth = this.finalImageWidth;
drawHeight = canvasHeight * widthFactor;
}
} else {
if(widthFactor > heightFactor) {
drawWidth = this.finalImageWidth;
drawHeight = canvasHeight * widthFactor;
} else {
drawWidth = canvasWidth * heightFactor;
drawHeight = this.finalImageHeight;
}
}
__canvas.renderAll();
if(this.scalingStretegy !== 'stretch') {
drawingContext.drawImage(__canvas.getElement(), (this.finalImageWidth - drawWidth) / 2, (this.finalImageHeight - drawHeight) / 2, drawWidth, drawHeight );
}
else {
drawingContext.drawImage(__canvas.getElement(), 0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
}
} else {
let drawWidth, drawHeight;
const cropSelectionRect = __canvas.getObjects().find(obj => obj.altName === 'cropRectangle');
const canvasCropWidth = cropSelectionRect.getScaledWidth()
const canvasCropHeight = cropSelectionRect.getScaledHeight()
const widthFactor = this.finalImageWidth/canvasCropWidth;
const heightFactor = this.finalImageHeight/canvasCropHeight;
let factor = this.scalingStretegy === 'fit' ? Math.min(widthFactor, heightFactor) : Math.max(widthFactor, heightFactor);
// we also need max factor here to account for the maximum canvas size that can be available
const maxFactor = Math.min(this.maxCanvasWidth/__canvas.getWidth(), this.maxCanvasHeight/__canvas.getHeight());
factor = Math.min(factor, maxFactor)
this.scaleFabricCanvas(factor)
if(this.scalingStretegy === 'fit') {
// check if width factor is greater than height, minimum division is height
// then drawHeight will be finalImageHeight and
// drawWidth will be calculated by multiplying canvasCropWidth with same heightFactor
if(widthFactor > heightFactor) {
drawWidth = canvasCropWidth * heightFactor;
drawHeight = this.finalImageHeight;
} else {
drawWidth = this.finalImageWidth;
drawHeight = canvasCropHeight * widthFactor;
}
} else {
if(widthFactor > heightFactor) {
drawWidth = this.finalImageWidth;
drawHeight = canvasCropHeight * widthFactor;
} else {
drawWidth = canvasCropWidth * heightFactor;
drawHeight = this.finalImageHeight;
}
}
__canvas.renderAll();
//__canvas.getElement().getContext('2d').scale(3 ,3);
if(this.scalingStretegy !== 'stretch') {
drawingContext.drawImage(__canvas.getElement(), cropSelectionRect.left * factor , cropSelectionRect.top * factor, canvasCropWidth * factor, canvasCropHeight * factor,
(this.finalImageWidth - drawWidth) / 2, (this.finalImageHeight - drawHeight) / 2, drawWidth, drawHeight);
} else {
drawingContext.drawImage(__canvas.getElement(), cropSelectionRect.left * factor, cropSelectionRect.top * factor, canvasCropWidth * factor, canvasCropHeight * factor,
0, 0, this.drawingCanvas.width, this.drawingCanvas.height);
}
}
__canvas.setDimensions(currentCanvasDimension)
__canvas.setViewportTransform(currentVpt);
__canvas.renderAll();
__canvas.getElement().getContext('2d').filter = existingFilter;
this.savedImageDownloadUrl = this.drawingCanvas.toDataURL(`image/${this.savedImageFormat}`, this.savedImageQuality/100);
},
async getBundleFileWithSize() {
this.updateDownloadUrl();
// saving procedure starts from this line after successfully getting dataurl
const configuratorId = this.configuratorId;
const locale = this.currentLocale;
const eventId = this.apiConfig.eventId;
try {
const response = await fetch(this.savedImageDownloadUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const blob = await response.blob();
const bundleFile = new File([blob], `bundleimage-${configuratorId}--${eventId}-${locale}.${this.savedImageFormat}`, {
type: `image/${this.savedImageFormat}`,
lastModified: Date.now()
});
this.savedImageFileSize = (bundleFile.size / (1024 * 1024));
return bundleFile;
} catch(error) {
console.error('Error downloading or creating file:', error);
throw error;
}
},
async saveCanvasImage() {
const bundleFile = await this.getBundleFileWithSize();
if(this.productId || this.configuratorId) {
this.saveCanvasData(bundleFile).then(function(data) {
this.triggerBundleFileEvent(bundleFile)
}.bind(this))
} else {
this.triggerBundleFileEvent(bundleFile)
}
},
triggerBundleFileEvent(bundleFile) {
const $modalStore = Alpine.store('modal');
const $elementsStore = Alpine.store('elements');
$modalStore.closeModal($elementsStore.containerModalEL);
window.dispatchEvent(new CustomEvent(`bundle-image-processed-${this.apiConfig.eventId}`, {
detail: {
bundleFile: bundleFile
},
bubbles: true
}))
},
disableUndoRedoButtons() {
Alpine.store('elements').historyUndo.disabled = !canvasHistory.getIndex();
Alpine.store('elements').historyRedo.disabled = !(canvasHistory.getIndex() < canvasHistory.commands.length);
},
aiImageSrc: '',
})