UNPKG

profile-plus

Version:

269 lines 12.4 kB
import React, { useCallback, useEffect, useState } from 'react'; import style from './ProfilePictureUploader.module.scss'; import { useAuth } from 'lincd-auth/hooks/useAuth'; import { Modal } from 'lincd-mui-base/components/Modal'; import Spinner from './Spinner.js'; import { Camera, CameraResultType, CameraSource } from '@capacitor/camera'; import { Capacitor } from '@capacitor/core'; import { ImageObject } from 'lincd-schema/shapes/ImageObject'; import { ProfilePicture } from 'profile-pics/shapes/ProfilePicture'; import Cropper from 'react-easy-crop'; import { Button } from 'lincd-mui-base/components/Button'; import { Server } from 'lincd-server-utils/utils/Server'; import { packageName } from '../package.js'; import { generateRandomName, replaceLocalhostWithSiteRoot, } from '../utils/helper.js'; import { cl } from 'lincd/utils/ClassNames'; import { getResizedImagePath } from 'lincd-server-utils/utils/ImageResize'; export const ProfilePictureUploader = ({ thumbnailHeight, componentHeight, thumbnailWidth, aspectRatio = 3 / 5, className, property = 'profilePicture', confirmText = 'Save', renderAction, uploadIcon, onUpdate, }) => { var _a, _b; let auth = useAuth(); let user = auth.user; // crop state const [cropModalOpen, setCropModalOpen] = useState(false); const [crop, setCrop] = useState({ x: 0, y: 0 }); const [zoom, setZoom] = useState(1); const [cropArea, setCropArea] = useState(null); const [cropLoading, setCropLoading] = useState(false); // uploader state const [loadingImage, setLoadingImage] = useState(false); const [image, setImage] = useState(); const [imageWebPath, setImageWebPath] = useState(); const [fileName, setFileName] = useState(''); const [file, setFile] = useState(); // state for FileTransfer // dynamically construct the property name based on property const [croppedImage, setCroppedImage] = useState(((_b = (_a = user === null || user === void 0 ? void 0 : user[property]) === null || _a === void 0 ? void 0 : _a.cropped) === null || _b === void 0 ? void 0 : _b.contentUrl) || ''); useEffect(() => { //make sure the image is loaded setLoadingImage(true); Server.call(packageName, 'loadProfilePicture', property) .then((croppedImage) => { if (croppedImage) { setCroppedImage(croppedImage.contentUrl); } setLoadingImage(false); }) .catch((err) => { console.error('Error in loadProfilePicture:', err); setLoadingImage(false); }); }, []); // android development path fix let croppedImageSrc = getResizedImagePath(replaceLocalhostWithSiteRoot(croppedImage), thumbnailWidth ? thumbnailWidth * 2 : NaN, thumbnailHeight ? thumbnailHeight * 2 : thumbnailWidth ? NaN : 300); // close crop modal const handleCloseModal = () => { setCropModalOpen(false); }; // set user profilePicture property const onCancel = () => { user[property] = null; }; // handle cancel crop button const onCropCancel = () => { setImage(null); handleCloseModal(); setLoadingImage(false); if (onCancel) { onCancel(); } }; // handle crop when completed const onCropComplete = useCallback((croppedAreaPercentage, croppedArea) => { setCropArea(croppedArea); }, []); // handle capacitor camera const handleCapacitorCamera = async () => { try { const imageOptions = { quality: 90, allowEditing: false, width: 1024, height: 1024, resultType: Capacitor.isNativePlatform() ? CameraResultType.Uri : CameraResultType.DataUrl, source: Capacitor.isNativePlatform() ? CameraSource.Prompt : CameraSource.Photos, }; return await Camera.getPhoto(imageOptions); } catch (error) { console.error('error in handleCapacitorCamera:', error); } }; // handle the web platform image processing const handleWebImageUpload = async (file) => { try { const dataUrl = file.dataUrl; // set the image in the state setImage(dataUrl); // show crop image modal setCropModalOpen(true); // generate a random filename with the correct format const filename = generateRandomName(file.format); setFileName(filename); // create an ImageObject and save it const imageObj = await ImageObject.fromDataURL(dataUrl, filename); // check user has profile picture and save image to user profile picture and image object if (!user.profilePicture || !user[property]) { const profilePicture = new ProfilePicture(); if (property) { user[property] = profilePicture; } else { user.profilePicture = profilePicture; } profilePicture.save(); } // update profile picture's image reference if (imageObj) { if (property) { const propertyId = user[property]; if (propertyId) { propertyId.image = imageObj; } else { console.error(`Property '${property}' does not exist on user.`); } } else { user.profilePicture.image = imageObj; } imageObj.save(); } else { console.error('Something went wrong uploading image to the server.'); } } catch (err) { console.error('Error in handleWebImage:', err); } finally { setLoadingImage(false); } }; // handle the native platform image uploading const handleNativeImageUpload = (file) => { setCropModalOpen(true); setImageWebPath(file.webPath); setFile(file); }; // upload image using Cordova FileTransfer only for Native Platform const onFileTransferUpload = async () => { const fileTransfer = new FileTransfer(); const token = await auth.getAccessToken(); const options = { fileKey: 'file', fileName: file.path.substring(file.path.lastIndexOf('/') + 1), mimeType: 'image/jpeg', chunkedMode: false, // Change this based on your requirements headers: { Authorization: 'Bearer ' + token, }, }; const uploadUrl = `${process.env.SITE_ROOT}/call/${packageName}/uploadImage?property=${property}`; return new Promise((resolve, reject) => { fileTransfer.upload(file.path, encodeURI(uploadUrl), (success) => { const imageUrl = JSON.parse(success.response); resolve(imageUrl); }, (error) => { reject(new Error('Upload error: ' + JSON.stringify(error))); }, options); }); }; // handle cropping image call from backend const onCropUploadImage = (uploadImage) => { // set featureImage from uploadImage param or image state // image state use for web platform and uploadImage param use for native platform const featuredImage = uploadImage || image; // before crop, we need to make sure featuredImage is exist if (!featuredImage) { throw new Error('Please upload your image before crop'); } // cropped image from backend) Server.call(packageName, 'cropImage', featuredImage, cropArea, property, fileName).then(([person, croppedImageUrl]) => { setLoadingImage(false); setCroppedImage(croppedImageUrl); if (onUpdate) { onUpdate(croppedImageUrl); } }); }; // main function to handle file change uploader const handleFileChange = async () => { try { const file = await handleCapacitorCamera(); // start loading when upload image setLoadingImage(true); // make sure file is already exist before do upload if (!file) { throw new Error('File is not defined'); } // check use which platform for upload the image if (Capacitor.getPlatform() === 'web') { await handleWebImageUpload(file); } else { handleNativeImageUpload(file); } } catch (err) { console.error('Error in handleFileChange:', err); } finally { setLoadingImage(false); } }; // handle crop image for native app const handleCropImage = () => { // setCropLoading(true); setLoadingImage(true); handleCloseModal(); // check crop image base on platform // for native apps, we upload image use Cordova FileTransfer first and then crop // for web, on this function only do cropping image if (Capacitor.isNativePlatform()) { onFileTransferUpload() .then((imageUpload) => { // make sure image already success upload to server before crop if (!imageUpload) { throw new Error('Upload image failed'); } // start to crop image onCropUploadImage(imageUpload); }) .catch((err) => { throw new Error('Upload image failed' + err); }) .finally(() => { setLoadingImage(false); }); } else { // start to crop image for web platform onCropUploadImage(); } }; const renderActionView = () => { return React.createElement("div", { onClick: handleFileChange }, renderAction); }; return (React.createElement("div", { className: style.Root }, React.createElement(Modal, { isOpen: cropModalOpen, backdrop: "rgba(255, 255, 255, 1)", onClose: handleCloseModal, renderContent: React.createElement("div", { className: style.modalWrapper }, React.createElement("div", null, React.createElement(Cropper, { image: Capacitor.isNativePlatform() ? imageWebPath : image, crop: crop, zoom: zoom, aspect: aspectRatio, onCropChange: setCrop, onZoomChange: setZoom, onCropComplete: onCropComplete })), React.createElement("div", { className: style.modalAction }, React.createElement(Button, { color: "secondary", onClick: onCropCancel }, "Cancel"), React.createElement(Button, { color: "primary", onClick: handleCropImage }, cropLoading ? `Uploading...` : confirmText))) }), React.createElement("div", { className: cl(style.uploaderContainer, className), style: { aspectRatio: aspectRatio ? aspectRatio : 'inherit', height: thumbnailHeight ? thumbnailHeight + 'px' : 'auto', width: thumbnailWidth ? thumbnailWidth + 'px' : 'auto', } }, renderAction ? (renderActionView()) : (React.createElement(React.Fragment, null, !loadingImage && croppedImage ? (React.createElement("img", { src: croppedImageSrc, alt: fileName, className: cl(style.imageUpload), style: { height: thumbnailHeight ? thumbnailHeight + 'px' : 'auto', aspectRatio: aspectRatio ? aspectRatio : 'inherit', }, onClick: handleFileChange })) : (React.createElement("div", { className: cl(style.uploadButton), onClick: handleFileChange, style: { aspectRatio: aspectRatio ? aspectRatio : 'inherit', } }, loadingImage ? (React.createElement(Spinner, null)) : uploadIcon ? (uploadIcon) : (React.createElement("svg", { viewBox: "0 -960 960 960", height: "48", width: "48" }, React.createElement("path", { fill: "currentColor", d: "M450-450H200v-60h250v-250h60v250h250v60H510v250h-60v-250Z" })))))))))); }; //# sourceMappingURL=ProfilePictureUploader.js.map