profile-plus
Version:
269 lines • 12.4 kB
JavaScript
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