profile-plus
Version:
### IOS
306 lines • 16.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProfilePictureUploader = void 0;
const camera_1 = require("@capacitor/camera");
const core_1 = require("@capacitor/core");
const useAuth_1 = require("lincd-auth/hooks/useAuth");
const Button_1 = require("lincd-mui-base/components/Button");
const Modal_1 = require("lincd-mui-base/components/Modal");
const ImageObject_1 = require("lincd-schema/shapes/ImageObject");
const ImageResize_1 = require("lincd-server-utils/utils/ImageResize");
const Server_1 = require("lincd-server-utils/utils/Server");
const ClassNames_1 = require("lincd/utils/ClassNames");
const react_1 = __importStar(require("react"));
const package_js_1 = require("../package.js");
const Person_js_1 = require("../shapes/Person.js");
const helper_js_1 = require("../utils/helper.js");
const ProfilePictureUploader_module_css_1 = __importDefault(require("./ProfilePictureUploader.module.css"));
const Spinner_js_1 = __importDefault(require("./Spinner.js"));
const Query = Person_js_1.Person.query((p) => [p.profilePicture.cropped.contentUrl]);
exports.ProfilePictureUploader = (0, package_js_1.linkedComponent)(Query, ({ profilePicture, thumbnailHeight, componentHeight, thumbnailWidth, aspectRatio = 3 / 5, className, property = 'profilePicture', confirmText = 'Save', renderAction, uploadIcon, onUpdate, }) => {
var _a;
let auth = (0, useAuth_1.useAuth)();
let user = auth.user;
// crop state
const [cropModalOpen, setCropModalOpen] = (0, react_1.useState)(false);
const [crop, setCrop] = (0, react_1.useState)({ x: 0, y: 0 });
const [zoom, setZoom] = (0, react_1.useState)(1);
const [cropArea, setCropArea] = (0, react_1.useState)(null);
const [cropLoading, setCropLoading] = (0, react_1.useState)(false);
const [Cropper, setCropper] = (0, react_1.useState)(null);
// uploader state
const [loadingImage, setLoadingImage] = (0, react_1.useState)(false);
const [image, setImage] = (0, react_1.useState)();
const [imageWebPath, setImageWebPath] = (0, react_1.useState)();
const [fileName, setFileName] = (0, react_1.useState)('');
const [file, setFile] = (0, react_1.useState)(); // state for FileTransfer
const [uploadError, setUploadError] = (0, react_1.useState)(false);
// dynamically construct the property name based on property
const croppedImageUrl = (_a = profilePicture === null || profilePicture === void 0 ? void 0 : profilePicture.cropped) === null || _a === void 0 ? void 0 : _a.contentUrl;
const [croppedImage, setCroppedImage] = (0, react_1.useState)(croppedImageUrl || '');
// android development path fix
let croppedImageSrc = (0, ImageResize_1.getResizedImagePath)((0, helper_js_1.replaceLocalhostWithSiteRoot)(croppedImage), thumbnailWidth ? thumbnailWidth * 2 : NaN, thumbnailHeight ? thumbnailHeight * 2 : thumbnailWidth ? NaN : 300);
// Dynamically load react-easy-crop when modal opens to avoid import cycle
(0, react_1.useEffect)(() => {
if (cropModalOpen && !Cropper) {
Promise.resolve().then(() => __importStar(require('react-easy-crop'))).then((module) => {
setCropper(() => module.default);
})
.catch((error) => {
console.error('Failed to load react-easy-crop:', error);
});
}
}, [cropModalOpen, Cropper]);
// 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 = (0, react_1.useCallback)((croppedAreaPercentage, croppedArea) => {
setCropArea(croppedArea);
}, []);
// handle capacitor camera
const handleCapacitorCamera = () => __awaiter(void 0, void 0, void 0, function* () {
try {
const imageOptions = {
quality: 90,
allowEditing: false,
width: 1024,
height: 1024,
resultType: core_1.Capacitor.isNativePlatform()
? camera_1.CameraResultType.Uri
: camera_1.CameraResultType.DataUrl,
source: core_1.Capacitor.isNativePlatform()
? camera_1.CameraSource.Prompt
: camera_1.CameraSource.Photos,
};
return yield camera_1.Camera.getPhoto(imageOptions);
}
catch (error) {
if (error.message !== 'User cancelled photos app') {
console.error('error in handleCapacitorCamera:', error);
}
}
});
// handle the web platform image processing
const handleWebImageUpload = (file) => __awaiter(void 0, void 0, void 0, function* () {
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 = (0, helper_js_1.generateRandomName)(file.format);
setFileName(filename);
// create an ImageObject and save it
const imageObj = yield ImageObject_1.ImageObject.fromDataURL(dataUrl, filename);
// update profile picture's image reference
if (imageObj) {
//the backend may rename the image during upload, so we need the updated file name
setFileName(imageObj.contentUrl.split('/').pop());
const personUpdate = yield Person_js_1.Person.update(user, {
profilePicture: {
image: imageObj,
},
});
}
else {
console.error('Something went wrong uploading image to the server.');
}
}
catch (err) {
console.error('Error in handleWebImage:', err);
// show the alert error message
setUploadError(true);
alert(`Hmm, things are a little crowded right now. We were not able to upload the image. Please try again in a moment.`);
// hide the crop modal
setCropModalOpen(false);
}
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 = () => __awaiter(void 0, void 0, void 0, function* () {
const fileTransfer = new FileTransfer();
const token = yield auth.getAccessToken();
// generate a unique file name for the image
// example: originalName.jpg -> originalName_1633024800000_abcd12.jpg
const filename = (0, helper_js_1.generateUniqueFileName)(file.path.substring(file.path.lastIndexOf('/') + 1));
setFileName(filename);
console.log(`filename: ${filename}`);
const options = {
fileKey: 'file',
fileName: filename,
mimeType: 'image/jpeg',
chunkedMode: false, // Change this based on your requirements
headers: {
Authorization: 'Bearer ' + token,
},
};
const uploadUrl = `${process.env.SITE_ROOT}/call/${package_js_1.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_1.Server.call(package_js_1.packageName, 'cropImage', featuredImage, cropArea, property, fileName).then((croppedImageUrl) => {
setLoadingImage(false);
setCroppedImage(croppedImageUrl);
if (onUpdate) {
onUpdate(croppedImageUrl);
}
});
};
// main function to handle file change uploader
const handleFileChange = () => __awaiter(void 0, void 0, void 0, function* () {
setUploadError(false);
try {
const file = yield handleCapacitorCamera();
// start loading when upload image
setLoadingImage(true);
// make sure file is already exist before do upload
if (!file) {
console.error('Photo is not defined. User may have cancelled.');
return;
}
// check use which platform for upload the image
if (core_1.Capacitor.getPlatform() === 'web') {
yield 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 (core_1.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) => {
setUploadError(true);
alert(`Hmm, things are a little crowded right now. We were not able to upload the image. Please try again in a moment.`);
throw err;
})
.finally(() => {
setLoadingImage(false);
});
}
else {
// start to crop image for web platform
onCropUploadImage();
}
};
const renderActionView = () => {
return react_1.default.createElement("div", { onClick: handleFileChange }, renderAction);
};
return (react_1.default.createElement("div", { className: ProfilePictureUploader_module_css_1.default.Root },
react_1.default.createElement(Modal_1.Modal, { isOpen: cropModalOpen, backdrop: "rgba(255, 255, 255, 1)", onClose: handleCloseModal, renderContent: react_1.default.createElement("div", { className: ProfilePictureUploader_module_css_1.default.modalWrapper },
react_1.default.createElement("div", null, Cropper ? (react_1.default.createElement(Cropper, { image: core_1.Capacitor.isNativePlatform() ? imageWebPath : image, crop: crop, zoom: zoom, aspect: aspectRatio, onCropChange: setCrop, onZoomChange: setZoom, onCropComplete: onCropComplete })) : (react_1.default.createElement(Spinner_js_1.default, null))),
react_1.default.createElement("div", { className: ProfilePictureUploader_module_css_1.default.modalAction },
react_1.default.createElement(Button_1.Button, { color: "secondary", onClick: onCropCancel }, "Cancel"),
react_1.default.createElement(Button_1.Button, { color: "primary", onClick: handleCropImage, disabled: !Cropper }, cropLoading ? `Uploading...` : confirmText))) }),
react_1.default.createElement("div", { className: (0, ClassNames_1.cl)(ProfilePictureUploader_module_css_1.default.uploaderContainer, className), style: {
aspectRatio: aspectRatio ? aspectRatio : 'inherit',
height: thumbnailHeight ? thumbnailHeight + 'px' : 'auto',
width: thumbnailWidth ? thumbnailWidth + 'px' : 'auto',
} }, renderAction ? (renderActionView()) : (react_1.default.createElement(react_1.default.Fragment, null, !loadingImage && croppedImage ? (react_1.default.createElement("img", { src: croppedImageSrc, alt: fileName, className: (0, ClassNames_1.cl)(ProfilePictureUploader_module_css_1.default.imageUpload), style: {
height: thumbnailHeight ? thumbnailHeight + 'px' : 'auto',
aspectRatio: aspectRatio ? aspectRatio : 'inherit',
}, onClick: handleFileChange })) : (react_1.default.createElement("div", { className: (0, ClassNames_1.cl)(ProfilePictureUploader_module_css_1.default.uploadButton), onClick: handleFileChange, style: {
aspectRatio: aspectRatio ? aspectRatio : 'inherit',
} }, uploadError ? (react_1.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", fill: "none", stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", className: "lucide lucide-circle-alert-icon lucide-circle-alert", viewBox: "0 0 24 24" },
react_1.default.createElement("circle", { cx: "12", cy: "12", r: "10" }),
react_1.default.createElement("path", { d: "M12 8v4M12 16h.01" }))) : loadingImage ? (react_1.default.createElement(Spinner_js_1.default, null)) : uploadIcon ? (uploadIcon) : (react_1.default.createElement("svg", { viewBox: "0 -960 960 960", height: "48", width: "48" },
react_1.default.createElement("path", { fill: "currentColor", d: "M450-450H200v-60h250v-250h60v250h250v60H510v250h-60v-250Z" }))))))))));
});
//# sourceMappingURL=ProfilePictureUploader.js.map