UNPKG

aico-image-editor

Version:

Combine multiple image into and create single combined image

297 lines (273 loc) 12 kB
import Cropper from 'cropperjs'; const elementStore = Alpine.store('elements'); Alpine.store('pictureCropperModalStore', { cropperPictureImg: null, cropperData: null, originalPictureFile: null, editingPictureId: null, shouldPushRounded: false, dataURLtoBlob(dataurl) { var arr = dataurl.split(','), mime = arr[0].match(/:(.*?);/)[1], bstr = atob(arr[1]), n = bstr.length, u8arr = new Uint8Array(n); while(n--){ u8arr[n] = bstr.charCodeAt(n); } return new Blob([u8arr], {type:mime}); }, async getFileFromUrl(url, name, defaultType = 'image/jpeg') { const response = await fetch(url); const data = await response.blob(); return new File([data], name, { type: data.type || defaultType, }); }, async editExistingPicture(picture) { //const originalBackgroundFile = await this.getFileFromUrl(background.originalUrl); this.editingPictureId = picture.id; const cropperInitObj = { fileName: picture.name, url: picture.cropperData.originalFileDataUrl, //url: picture.originalUrl, cropperData: picture.cropperData } this.displaySelectedPicture(cropperInitObj); }, async displaySelectedPicture(cropperInitObj) { this.destroyPictureCropper() const img = document.createElement('img'); img.id = 'js-picture-cropper'; img.src = cropperInitObj.url; img.alt = cropperInitObj.fileName; const imgcrop = elementStore.pictureCropEL; imgcrop.innerHTML = ''; imgcrop.appendChild(img); // this represents current shape image element which was created above this.cropperPictureImg = img; this.cropperData = cropperInitObj.cropperData; if(cropperInitObj.originalPictureFile) { this.originalPictureFile = cropperInitObj.originalPictureFile; } if(elementStore.cropPictureModalEL.classList.contains('show')) { this.initPictureCropper(img); } else { bootstrap.Modal.getOrCreateInstance(elementStore.cropPictureModalEL).show() } }, initPictureCropper(img) { let self = this; //console.log('cropper is being init now') const input = elementStore.pictureUploadEL; input.value = ''; //this.aspectRatio = '1'; window.dispatchEvent(new CustomEvent('pictures-cleard')) //destroy old cropper before initializing new one new Cropper(img, { crop: function (event) { self.cropBoxWidth = Math.round(event.detail.width); self.cropBoxHeight = Math.round(event.detail.height); }, //aspectRatio: parseFloat(self.aspectRatio), preview: elementStore.cropperPicturePreviewEL, autoCropArea: 1, // data:{ //define cropbox size // width: 240, // height: 240, // }, ready: function () { if(self.cropperData) { this.cropper.setData(self.cropperData); if(self.cropperData?.aspectRatio) { self.aspectRatio = self.cropperData.aspectRatio; } if(self.cropperData?.shouldPushRounded) { self.setRadius(); } else { self.setSquare(); } if(self.cropperData?.zoomMode) { self.zoomMode = self.cropperData.zoomMode; } if(self.cropperData?.dragMode) { self.dragMode = self.cropperData.dragMode; } } else { self.resetCropperControls() } // set cropper instance's aspect ratio sane as alpine js object;s aspect ratio property but it should be in number this.aspectRatio = parseFloat(self.aspectRatio); // set dragmode of cropper instance to alpine js dragmode after in by calling this method self.setDragMode(self.dragMode); } }); // no need to do squaer after init //this.setSquare(); }, cropBoxWidth: 0, cropBoxHeight: 0, resetCropperControls() { this.setAspectRatio('NaN'); this.setSquare(); this.setDragMode('move'); this.zoomMode = 'zoomIn' }, dragMode: 'move', setDragMode(dragMode) { this.dragMode = dragMode; const cropperInstance = this.cropperPictureImg.cropper; cropperInstance.setDragMode(dragMode) }, zoomMode: 'zoomIn', zoom(zoomValue, zoomMode ) { this.zoomMode = zoomMode; const cropperInstance = this.cropperPictureImg.cropper; cropperInstance.zoom(zoomValue) }, shouldPushRounded: false, setRadius() { this.shouldPushRounded = true; elementStore.cropperPicturePreviewEL.classList.add("rounded"); elementStore.cropPictureModalEL.querySelector(".cropper-view-box")?.classList.add("rounded"); elementStore.cropPictureModalEL.querySelector(".cropper-face")?.classList.add("rounded"); }, setSquare() { this.shouldPushRounded = false; elementStore.cropperPicturePreviewEL.classList.remove("rounded"); elementStore.cropPictureModalEL.querySelector(".cropper-view-box")?.classList.remove("rounded"); elementStore.cropPictureModalEL.querySelector(".cropper-face")?.classList.remove("rounded"); }, aspectRatio: 'NaN', setAspectRatio(aspectRatio) { this.aspectRatio = aspectRatio; if(aspectRatio) { const cropperInstance = this.cropperPictureImg.cropper; cropperInstance.setAspectRatio(aspectRatio) } }, clear() { const cropperInstance = this.cropperPictureImg.cropper; cropperInstance.clear() }, destroyPictureCropper() { this.cropperPictureImg?.cropper?.destroy(); }, async crop() { return new Promise((resolve, reject) => { const cropperInstance = this.cropperPictureImg.cropper; const input = elementStore.pictureUploadEL; let self = this; const croppedCanvas = cropperInstance.getCroppedCanvas(); let finalCanvas = croppedCanvas; if (self.shouldPushRounded) { finalCanvas = self.getRoundedCanvas(croppedCanvas); } finalCanvas.toBlob(blob => { if (blob) { const dt = new DataTransfer(); dt.items.add(new File([blob], self.cropperPictureImg.alt, { type: blob.type })); input.files = dt.files; window.dispatchEvent(new CustomEvent('picture-uploaded', { detail: { name: self.cropperPictureImg.alt, label: self.cropperPictureImg.alt, url: URL.createObjectURL(blob), class: self.shouldPushRounded ? 'rounded' : '', type: blob.type, } })); resolve(dt.files); } else { reject("Failed to generate blob"); } }); }); }, async processUpload($store) { let self = this; await this.crop(); const input = elementStore.pictureUploadEL; const cropperData = Object.assign( {originalFileDataUrl: self.cropperPictureImg.src, wasAspectRatioSaved: self.shouldCurrentAspectRatioSaved} , self.cropperPictureImg?.cropper.getData()); if(input.files.length) { const uploadObj = { files: input.files, action: `${$store.canvas.apiConfig.apiUrl}/api/v1/product-configurators/${$store.canvas.configuratorId || null}/images`, type: 'mainPictures[]', imageType: 'MAIN_IMAGE', originalFile: this.originalPictureFile, cropperData: cropperData } const data = await $store.uploadStore.uploadFilesToServer(uploadObj); data?.data?.images?.forEach((mainPicture) => { window.dispatchEvent(new CustomEvent('main-pictures-added-from-api', { detail: { mainPictures: [mainPicture] } })); }); //productBlockVisibleMobile = true; } }, async processUpdate($store) { let self = this; const files = await this.crop(); const input = elementStore.pictureUploadEL; input.files = files; const cropperData = Object.assign({ originalFileDataUrl: self.cropperPictureImg.src, wasAspectRatioSaved: self.shouldCurrentAspectRatioSaved, aspectRatio: self.aspectRatio, shouldPushRounded: self.shouldPushRounded, zoomMode: self.zoomMode, dragMode: self.dragMode } ,self.cropperPictureImg?.cropper.getData()); if(input.files.length) { const editUploadObj = { files: input.files, action: `${$store.canvas.apiConfig.apiUrl}/api/v1/product-configurators/images/${this.editingPictureId}`, type: 'image', cropperData: cropperData } const data = await $store.uploadStore.updateImageInServer(editUploadObj); //here for update case background is directly returned as data.data and no further background property under that if(data?.data) { window.dispatchEvent(new CustomEvent('main-pictures-updated-from-api', { detail: { mainPictures: [data?.data] } })); } //productBlockVisibleMobile = true; } }, getRoundedCanvas(sourceCanvas,aspectRatio) { var canvas = document.createElement('canvas'); var context = canvas.getContext('2d'); var width = sourceCanvas.width; var height = sourceCanvas.height; if (width > 0 && height > 0) { if (width / height > aspectRatio) { var newWidth = height * aspectRatio; var xOffset = (width - newWidth) / 2; canvas.width = newWidth; canvas.height = height; context.drawImage(sourceCanvas, xOffset, 0, newWidth, height, 0, 0, newWidth, height); } else { canvas.width = width; canvas.height = height; context.drawImage(sourceCanvas, 0, 0, width, height, 0, 0, width, height); } context.imageSmoothingEnabled = true; context.globalCompositeOperation = 'destination-in'; var centerX = canvas.width / 2; var centerY = canvas.height / 2; var radiusX = canvas.width / 2; var radiusY = canvas.height / 2; context.beginPath(); context.ellipse(centerX, centerY, radiusX, radiusY, 0, 0, 2 * Math.PI); context.fill(); } return canvas; }, shouldCurrentAspectRatioSaved: false, })