UNPKG

react-crop-upload-pictures

Version:
283 lines 18.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const react_1 = tslib_1.__importStar(require("react")); require("../assets/style/index.scss"); const react_fontawesome_1 = require("@fortawesome/react-fontawesome"); const free_solid_svg_icons_1 = require("@fortawesome/free-solid-svg-icons"); const free_brands_svg_icons_1 = require("@fortawesome/free-brands-svg-icons"); const react_bootstrap_1 = require("react-bootstrap"); const react_drag_reorder_1 = require("react-drag-reorder"); const crop_1 = tslib_1.__importDefault(require("./crop")); const socialMediaImportPopup_1 = tslib_1.__importDefault(require("./socialMediaImportPopup")); const ERROR = { NOT_SUPPORTED_EXTENSION: 'NOT_SUPPORTED_EXTENSION', FILE_SIZE_TOO_LARGE: 'FILE_SIZE_TOO_LARGE', DIMENSION_IMAGE: "DIMENSION_IMAGE" }; const UploadPictures = (0, react_1.forwardRef)(({ title = null, imgExtension = ['.jpg', '.jpeg', '.gif', '.png'], maxFileSize = 5242880, height = "200px", width = "200px", classStyle = "", iconSize = "lg", drag = false, crop = false, multiple = true, aspect = 4 / 3, dragDescription = "You can drag pictures to rearrange their order", instructions = null, errorMessages = { NOT_SUPPORTED_EXTENSION: 'The following files are of unsupported types: ', FILE_SIZE_TOO_LARGE: 'The following files are too large and cannot be imported:', DIMENSION_IMAGE: "The following images need to be cropped: " }, handleClose = () => { }, setPhotosCallback = () => { }, openedSocialOverride = false, }, ref) => { (0, react_1.useImperativeHandle)(ref, () => ({ getErrors() { return { NOT_SUPPORTED_EXTENSION: pictures.map(el => el.NOT_SUPPORTED_EXTENSION), FILE_SIZE_TOO_LARGE: pictures.map(el => el.FILE_SIZE_TOO_LARGE), DIMENSION_IMAGE: pictures.map(el => el.DIMENSION_IMAGE), }; }, getPictures() { return pictures.map(el => el.contents?.file); }, })); const [pictures, setPictures] = (0, react_1.useState)([]); const [socialRawMediaPictures, setSocialMediaRawPictures] = (0, react_1.useState)([]); const [socialMediaSelectedPictures, setSocialMediaSelectedPictures] = (0, react_1.useState)([]); const [srcCrop, setSrcCrop] = (0, react_1.useState)(false); const [openCrop, setOpenCrop] = (0, react_1.useState)(false); const [indexCrop, setIndexCrop] = (0, react_1.useState)(false); const [openedSocial, setOpenedSocial] = (0, react_1.useState)(openedSocialOverride); const [loading, setLoading] = (0, react_1.useState)(false); (0, react_1.useEffect)(() => { if (setPhotosCallback) { setPhotosCallback(pictures); } }, [pictures?.length]); (0, react_1.useEffect)(() => { if (window.location.search.includes("?code")) { setOpenedSocial('instagram'); } }, []); const hasExtension = (fileName) => { const pattern = '(' + imgExtension.join('|').replace(/\./g, '\\.') + ')$'; return new RegExp(pattern, 'i').test(fileName); }; const readFile = (file) => { return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = function (e) { let dataURL = e.target.result; dataURL = dataURL.replace(";base64", `;name=${file.name};base64`); resolve({ file, dataURL }); }; reader.readAsDataURL(file); }); }; const onFileChange = (0, react_1.useCallback)(event => { // Clear prev error files setPictures(pictures => pictures.filter(e => !!e?.contents)); let allFilePromises = []; if (event.target.files) { Array.from(event.target.files).forEach((file) => { const errors = { NOT_SUPPORTED_EXTENSION: false, FILE_SIZE_TOO_LARGE: false, DIMENSION_IMAGE: false, }; if (!hasExtension(file.name)) { errors.NOT_SUPPORTED_EXTENSION = { type: ERROR.NOT_SUPPORTED_EXTENSION, filename: file.name }; } if (file.size > maxFileSize) { errors.FILE_SIZE_TOO_LARGE = { type: ERROR.FILE_SIZE_TOO_LARGE, filename: file.name }; } const newFile = new Promise(async (resolve) => resolve({ ...errors, contents: !errors.FILE_SIZE_TOO_LARGE && !errors.NOT_SUPPORTED_EXTENSION ? await readFile(file) : null, })); allFilePromises.push(newFile); }); Promise.all(allFilePromises).then(newFilesData => { newFilesData.forEach((newFileData, index) => { debugger; if (!newFileData.contents || newFileData.contents === null) { setPictures(pictures => [...pictures, newFileData]); return; } newFileData.contents.file.src = newFileData.contents.dataURL; var image = new Image(); image.src = newFileData.contents.file.src; image.index = index; image.addEventListener('load', () => { const { width, height } = image; // check aspect ratio of the image if (aspect !== (width / height)) { newFileData.contents.file.needsCropping = true; newFileData.DIMENSION_IMAGE = { index: index + pictures?.length, type: ERROR.DIMENSION_IMAGE, filename: newFileData.contents.file.name }; } setPictures(pictures => [...pictures, newFileData]); }); }); }); } }, [pictures?.length, aspect, hasExtension, maxFileSize]); const remove = (index) => { let newList = pictures.filter((_, i) => i !== index); setPictures(newList); }; const getChangedPos = (currentPos, newPos) => { let newList = [...pictures]; let pic = newList.splice(currentPos, 1); newList.splice(newPos, 0, pic[0]); setPictures(newList); }; const imageUrlToBase64 = async (url) => { const data = await fetch(url); const blob = await data.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.readAsDataURL(blob); reader.onloadend = () => { const base64data = reader.result; resolve(base64data); }; reader.onerror = reject; }); }; const setSocialMediaPhotosCallback = (socialPics, selected) => { setSocialMediaRawPictures(socialPics); setSocialMediaSelectedPictures(selected); }; const saveSocialPictures = (0, react_1.useCallback)(async () => { const selectedPictures = socialRawMediaPictures.filter((_, index) => socialMediaSelectedPictures.includes(index)); const results = await Promise.all(selectedPictures.map(async (item, index) => { const src = await imageUrlToBase64(item?.src); let needsCropping = false; let dimensionError = false; if (aspect !== (width / height)) { dimensionError = { index: index + pictures?.length, type: ERROR.DIMENSION_IMAGE, filename: '' }; needsCropping = true; } return { contents: { file: { src: src, needsCropping: needsCropping, }, }, NOT_SUPPORTED_EXTENSION: false, FILE_SIZE_TOO_LARGE: false, DIMENSION_IMAGE: dimensionError, }; })); if (results) { setPictures([...pictures, ...results]); } setOpenedSocial(false); setSocialMediaRawPictures([]); setSocialMediaSelectedPictures([]); }, [pictures, socialMediaSelectedPictures, socialRawMediaPictures]); const DraggableRender = (0, react_1.useCallback)(() => { return (react_1.default.createElement(react_drag_reorder_1.Draggable, { onPosChange: getChangedPos }, pictures.length > 0 && pictures.map((picture, index) => { debugger; return !picture.FILE_SIZE_TOO_LARGE && !picture.NOT_SUPPORTED_EXTENSION && picture.contents && picture.contents !== null && (react_1.default.createElement("div", { className: `${picture.contents?.file?.needsCropping ? "border border-warning" : ""} position-relative p-0 mx-2 drager-pictures`, key: index, style: { width: width } }, react_1.default.createElement(Actions, { index: index, iconSize: iconSize, remove: remove, cropPicture: cropPicture, crop: crop }), react_1.default.createElement(ImageDisplay, { picture: picture.contents?.file, height: height, width: width, className: "mb-4" }))); }))); }, [pictures]); const ImagesRender = (0, react_1.useCallback)(() => { return (react_1.default.createElement("div", { className: "row d-flex justify-content-center" }, pictures && pictures.map((picture, index) => (react_1.default.createElement("div", { className: "position-relative p-0 mx-2", key: index, style: { width: width, height: height } }, react_1.default.createElement(Actions, { index: index, iconSize: iconSize, remove: remove, cropPicture: cropPicture, crop: crop }), react_1.default.createElement(ImageDisplay, { picture: picture.contents?.file, height: height, width: width, className: "mb-4" })))))); }, [pictures]); const cropPicture = (index) => { let picture = pictures.find((_, i) => i === index); setSrcCrop(picture); setOpenCrop(true); setIndexCrop(index); }; const saveCroppedPicture = (picture) => { let newPicture = { ...picture }; newPicture.contents.file.needsCropping = false; newPicture.DIMENSION_IMAGE = false; setPictures(items => { const res = items.map((item, i) => i === indexCrop ? newPicture : item); setPhotosCallback(res); return res; }); setSrcCrop(false); setOpenCrop(false); setIndexCrop(false); }; const tooLargeErrors = pictures.map(el => (el.FILE_SIZE_TOO_LARGE)).filter(e => !!e); const notSupportedErrors = pictures.map(el => (el.NOT_SUPPORTED_EXTENSION)).filter(e => !!e); const dimensionErrors = pictures.map((el, index) => (el.DIMENSION_IMAGE ? { ...el.DIMENSION_IMAGE, index: index } : false)).filter(e => !!e); const shownPictures = pictures.filter(pic => !!pic?.contents); return (react_1.default.createElement(react_1.default.Fragment, null, crop && react_1.default.createElement(crop_1.default, { picture: srcCrop, isOpen: openCrop, setOpenCrop: setOpenCrop, saveCroppedPicture: saveCroppedPicture, iconSize: iconSize, aspect: aspect }), react_1.default.createElement("div", { ref: ref }, react_1.default.createElement("div", { className: classStyle }, react_1.default.createElement("div", { className: "upload-content" }, title !== null ? react_1.default.createElement("div", { className: "upload-header" }, react_1.default.createElement("h1", { className: "upload-title fs-5" }, title)) : "", react_1.default.createElement("div", { className: "mb-5 upload-body" }, tooLargeErrors?.length ? (react_1.default.createElement("div", { className: "alert alert-danger", role: "alert" }, react_1.default.createElement("strong", null, errorMessages[ERROR.FILE_SIZE_TOO_LARGE]), tooLargeErrors.map((error) => error.filename).join(', '))) : "", notSupportedErrors?.length ? (react_1.default.createElement("div", { className: "alert alert-danger", role: "alert" }, react_1.default.createElement("strong", null, errorMessages[ERROR.NOT_SUPPORTED_EXTENSION]), notSupportedErrors.map((error) => error.filename).join(', '))) : "", dimensionErrors?.length ? (react_1.default.createElement("div", { className: "alert alert-warning", role: "alert" }, react_1.default.createElement("strong", null, errorMessages[ERROR.DIMENSION_IMAGE]), dimensionErrors.map((el) => '[' + el.index + ']').join(', '))) : "", drag && shownPictures.length === 0 && instructions && react_1.default.createElement("div", { className: "mb-2" }, react_1.default.createElement("div", { className: "alert alert-info", role: "alert", dangerouslySetInnerHTML: { __html: instructions } })), react_1.default.createElement("div", { className: "row mb-5" }, react_1.default.createElement("div", { className: "col" }, react_1.default.createElement("input", { onChange: onFileChange, className: "form-control", type: "file", id: "formFile", multiple: multiple })), react_1.default.createElement("div", { className: "col-auto ml-auto social-buttons" }, react_1.default.createElement("button", { onClick: () => setOpenedSocial('facebook'), className: "facebook-import-button" }, react_1.default.createElement(react_fontawesome_1.FontAwesomeIcon, { icon: free_brands_svg_icons_1.faFacebook, size: iconSize, className: "facebook-import-button-icon" })), react_1.default.createElement("button", { onClick: () => setOpenedSocial('instagram'), className: "instagram-import-button" }, react_1.default.createElement(react_fontawesome_1.FontAwesomeIcon, { icon: free_brands_svg_icons_1.faInstagram, size: iconSize, className: "instagram-import-button-icon" })))), loading && (react_1.default.createElement("div", { className: "d-flex justify-content-center mt-3" }, react_1.default.createElement("div", { className: "spinner-border", role: "status" }, react_1.default.createElement("span", { className: "sr-only" }, "Loading...")))), drag && shownPictures.length > 1 && react_1.default.createElement("div", { className: "mb-2" }, react_1.default.createElement("div", { className: "alert alert-info", role: "alert" }, dragDescription)), react_1.default.createElement("div", { className: "row d-flex justify-content-center space-photos" }, drag && shownPictures.length > 0 && (react_1.default.createElement(DraggableRender, null)), !drag && shownPictures.length > 0 && (react_1.default.createElement(ImagesRender, null))))), crop && react_1.default.createElement("div", { className: (openCrop ? "modal-backdrop fade show" : "") }))), !!openedSocial && (react_1.default.createElement(react_bootstrap_1.Modal, { show: !!openedSocial, onHide: () => setOpenedSocial(false), size: "xl", keyboard: true }, react_1.default.createElement(react_bootstrap_1.Modal.Header, { closeButton: true }, react_1.default.createElement(react_bootstrap_1.Modal.Title, null, multiple ? "Select Images" : "Select an Image")), react_1.default.createElement(react_bootstrap_1.Modal.Body, null, react_1.default.createElement(socialMediaImportPopup_1.default, { ref: ref, modalSource: openedSocial, height: "100px", width: "100px", iconSize: "lg", handleClose: () => setOpenedSocial(false), setPhotosCallback: setSocialMediaPhotosCallback, multiple: multiple })), react_1.default.createElement(react_bootstrap_1.Modal.Footer, null, react_1.default.createElement(react_bootstrap_1.Button, { variant: "secondary", onClick: () => setOpenedSocial(false) }, react_1.default.createElement(react_fontawesome_1.FontAwesomeIcon, { icon: free_solid_svg_icons_1.faXmark, size: iconSize })), react_1.default.createElement(react_bootstrap_1.Button, { variant: "primary", className: "text-white", onClick: saveSocialPictures, disabled: !socialMediaSelectedPictures?.length, "data-bs-toggle": "tooltip", "data-bs-placement": "top", title: "Tooltip on top" }, react_1.default.createElement(react_fontawesome_1.FontAwesomeIcon, { icon: free_solid_svg_icons_1.faFileImport, size: iconSize }))))))); }); function ImageDisplay({ picture, width, height }) { return react_1.default.createElement("img", { src: picture?.src, style: { height: height, width: width, }, className: "mb-4", alt: "" }); } function Actions({ index, iconSize, remove, cropPicture, crop }) { return (react_1.default.createElement("div", { className: "d-flex w-100 justify-content-between p-0 pb-3" }, crop && react_1.default.createElement(react_fontawesome_1.FontAwesomeIcon, { role: "button", onClick: () => cropPicture(index), icon: free_solid_svg_icons_1.faCropSimple, size: iconSize, className: "my-auto" }), react_1.default.createElement("b", { className: "fs-5 my-auto" }, "[", index, "]"), react_1.default.createElement(react_fontawesome_1.FontAwesomeIcon, { role: "button", onClick: () => remove(index), icon: free_solid_svg_icons_1.faXmark, size: iconSize, className: "my-auto" }))); } exports.default = UploadPictures; //# sourceMappingURL=index.js.map