diff --git a/package.json b/package.json index b9fc2280..699323b5 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "query-string": "^6.13.1", "react": "^16.11.0", "react-background-slider": "^1.2.0", - "react-cropper": "^1.3.0", + "react-cropper": "2.1.7", "react-dom": "^16.11.0", "react-images-upload": "^1.2.7", "react-photo-gallery": "^8.0.0", diff --git a/src/Components/Upload/ImageCropper/index.jsx b/src/Components/Upload/ImageCropper/index.jsx new file mode 100644 index 00000000..9fc4f1bd --- /dev/null +++ b/src/Components/Upload/ImageCropper/index.jsx @@ -0,0 +1,111 @@ +import React, { useRef, useState, useEffect, useCallback } from 'react'; +import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; + +import NoStyleButton from 'Components/Form/NoStyleButton'; + +import Cropper from 'react-cropper'; +import 'cropperjs/dist/cropper.css'; + +function ImageCropper(props) { + const { imgHandler } = props; + const cropperRef = useRef(null); + const user = useSelector((state) => state.authReducer.user); + + const [pictureImg, setPictureImg] = useState(null); + const [pictureImgUrl, setPictureImgUrl] = useState(null); + + const handlePictureImg = async (e) => { + if (e.target.files[0] !== undefined) { + setPictureImg(e.target.files[0]); + setPictureImgUrl(URL.createObjectURL(e.target.files[0])); + } + }; + + const handlePostUpload = useCallback(async () => { + if (user && cropperRef.current) { + const imgElement = cropperRef.current; + const { cropper } = imgElement; + if (pictureImg && cropper) { + const { name } = pictureImg; + const canvas = cropper.getCroppedCanvas(); + if (canvas) { + canvas.toBlob( + async (croppedImg) => { + const image = new FormData(); + image.append('img', croppedImg, name); + imgHandler(image); + }, + undefined, + 1, + ); + } + } else { + // TO DO :: notify uploading image ! + } + } else { + // TO DO :: implement login modal ! + } + }, [imgHandler, user, pictureImg]); + + const removePicture = () => { + setPictureImg(null); + setPictureImgUrl(null); + }; + + useEffect(() => { + handlePostUpload(); + }, [pictureImg, handlePostUpload]); + + return ( + <> +
+
+
+ {pictureImg && ( + <> + + + X + + + )} + {!pictureImg && ( +
+
+ +
+
+ )} +
+
+
+ + ); +} + +ImageCropper.propTypes = { + imgHandler: PropTypes.func.isRequired, +}; + +export default ImageCropper; diff --git a/src/Components/Upload/ImageCropper/index.scss b/src/Components/Upload/ImageCropper/index.scss new file mode 100644 index 00000000..c3b6047e --- /dev/null +++ b/src/Components/Upload/ImageCropper/index.scss @@ -0,0 +1,81 @@ +/* + ImageCropper Component Styling +*/ + +.imageCropper { + width: 100%; + height: 100%; + border-radius: 18px; + button:focus { + outline: none; + } + &__creator { + margin-bottom: 1rem; + height: 100%; + &-previewPic { + height: 100%; + position: relative; + display: flex; + margin: auto; + border: 2px dotted #ccd0d5; + background-color: rgba(255, 244, 201, 0.3); + + &__remove { + margin: 0.5rem; + z-index: 1; + position: absolute; + top: 0; + left: 0; + color: $main-color; + font-weight: bolder; + font-size: 1.5rem; + } + + &__empty { + position: relative; + width: 100%; + background-color: $background-color; + opacity: 50%; + &-uploadPic { + z-index: 1; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + label { + margin: 0; + cursor: pointer; + font-family: 'GothamRound'; + color: $main-color; + + &:hover { + animation: buttonHover 0.3s ease; + animation-fill-mode: forwards; + border-radius: 18px; + padding: 1rem; + background-color: $main-color; + color: white; + } + } + } + } + + .cropper { + margin: auto; + max-width: 80%; + max-height: 100%; + } + } + } +} + +@keyframes buttonHover { + 0% { + transform: scale(1); + } + + 100% { + transform: scale(1.3); + } +} diff --git a/src/Components/Upload/UploadPost/index.jsx b/src/Components/Upload/UploadPost/index.jsx index 87375fe2..71c78edb 100644 --- a/src/Components/Upload/UploadPost/index.jsx +++ b/src/Components/Upload/UploadPost/index.jsx @@ -5,16 +5,15 @@ import * as PostAction from 'Redux/post'; import PropTypes from 'prop-types'; -import Cropper from 'react-cropper'; -import 'cropperjs/dist/cropper.css'; -import { Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; -import Button from '@material-ui/core/Button'; -import TextField from '@material-ui/core/TextField'; - import RoundLoader from 'Components/Loader/Round'; +import NoStyleButton from 'Components/Form/NoStyleButton'; +import ImageCropper from 'Components/Upload/ImageCropper'; import plusButton from 'Assets/images/plus.svg'; +import { Modal } from 'reactstrap'; +import TextField from '@material-ui/core/TextField'; + const defaultProps = { user: null, token: null, @@ -48,54 +47,40 @@ class Upload extends Component { constructor(props) { super(props); this.state = { - pictureImg: null, - pictureImgUrl: null, - content: '', + imageFormData: null, + title: '', + description: '', modal: false, }; } - handlePictureImg = async (e) => { - if (e.target.files[0] !== undefined) { - await new Promise((accept) => - this.setState({ pictureImg: e.target.files[0] }, accept), - ); - const { pictureImg } = this.state; - this.setState({ - pictureImgUrl: URL.createObjectURL(pictureImg), - }); - } + handlePictureWithCropper = (imageFormData) => { + this.setState({ + imageFormData, + }); + }; + + handleTitle = (e) => { + this.setState({ title: e.target.value }); }; - handleContent = (e) => { - this.setState({ content: e.target.value }); + handleDescription = (e) => { + this.setState({ description: e.target.value }); }; handlePostUpload = async () => { const { user, uploadPost, token } = this.props; - const { pictureImgUrl, pictureImg, content } = this.state; + const { imageFormData, title, description } = this.state; if (user) { - if (pictureImgUrl) { - this.cropper.getCroppedCanvas({ imageSmoothingQuality: 'high' }).toBlob( - async (croppedImg) => { - const image = new FormData(); - const params = new URLSearchParams(); - image.append('img', croppedImg, pictureImg.name); - params.append('title', content); - params.append('user_id', user.id); - params.append('description', content); - params.append('category_id', 5); - params.append('is_public', true); - const postUpload = await uploadPost(image, params, token); - if (postUpload) window.location.reload(); - // TO DO :: notify failed to upload image - }, - undefined, - 1, - ); - } else { - // TO DO :: notify uploading image ! - } + const params = new URLSearchParams(); + + params.append('title', title); + params.append('user_id', user.id); + params.append('description', description); + params.append('category_id', 5); + params.append('is_public', true); + const postUpload = await uploadPost(imageFormData, params, token); + if (postUpload) window.location.reload(); } else { // TO DO :: implement login modal ! } @@ -107,84 +92,66 @@ class Upload extends Component { }; render() { - const { content, modal, pictureImgUrl } = this.state; + const { content, modal } = this.state; const { isUploading } = this.props; return ( <> {isUploading && } - - - - Upload Your Masterpiece! - - -
-
-
- + <> + + +
+
+
+ Share Your Masterpiece with Blecther. +
+
+ This will be automatically uploaded to my feed and displayed + to Blecther home.
- {pictureImgUrl && ( -
- { - this.cropper = cropper; - }} - // Cropper.js options - center - /> +
+
+
+ +
+
+
+
+
+ Brief Introduction about Your Masterpiece
- )} -
+
+
+ +
Upload
+
+
- - - - - - + + ); } diff --git a/src/Components/Upload/UploadPost/index.scss b/src/Components/Upload/UploadPost/index.scss index 6ac37c91..8b75db89 100644 --- a/src/Components/Upload/UploadPost/index.scss +++ b/src/Components/Upload/UploadPost/index.scss @@ -2,69 +2,92 @@ Upload Post Component Styling */ -.postUpload { - width: 500px; - button:focus { - outline: none; - } +.uploadPost { + background-color: white; + z-index: 1005; + width: 70%; + height: 70%; + position: fixed; + transform: translate(-50%, -50%); + top: 50%; + left: 50%; + border-radius: 18px; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + overflow: hidden; + &__header { + width: 100%; + height: 15%; + display: flex; + flex-flow: column wrap; + align-items: center; + justify-content: center; + background-color: $background-color; + &__title { + color: $main-color; + font-size: 1.9rem; + } - .backBtn { - width: 35px; - cursor: pointer; + &__sub { + font-family: 'AppleSDGothicNeo'; + } } - &__creator { - &-uploadPic { - width: 80%; - margin: auto; - margin-bottom: 5px; - - label { - width: 100%; - margin: 0; - cursor: pointer; - } + &__content { + width: 90%; + height: 50%; + flex-direction: column; + justify-content: space-around; + align-items: center; + &__image { + height: 100%; } + } + + &__footer { + width: 100%; + margin-bottom: 3%; + display: flex; + flex-direction: row; + justify-content: space-around; + align-items: center; - &-previewPic { + &__input { + width: 40%; + height: 40%; display: flex; flex-direction: column; - align-items: center; justify-content: center; - width: 80%; - min-height: 300px; - margin: auto; - border: 2px dotted #ccd0d5; - border-radius: 4px; - margin-bottom: 20px; - - .cropper { - width: 100%; - max-height: 300px; + & input { + width: max-content; } - } - &-content { - width: 80%; - margin: auto; + &__text { + text-align: center; + color: $main-color; + font-size: 1.3rem; + } } - } - - &__sketcher { - &-uploadPic { + &__buttons { + width: fit-content; + height: 100%; display: flex; - - div { - width: 50%; - border: 1px solid #ccd0d5; - border-radius: 4px; + flex-direction: column; + justify-content: space-around; + & .noStyleButton { + height: fit-content; + padding: 1rem 4rem; + border-radius: 26px; + border: 2px solid $main-color; text-align: center; + + &:hover { + color: white; + background-color: $main-color; + } } } } - - &__upload { - margin-top: 20px; - text-align: center; - } } diff --git a/src/index.scss b/src/index.scss index 88d16b2c..1a2dbe5b 100644 --- a/src/index.scss +++ b/src/index.scss @@ -22,6 +22,7 @@ @import 'Components/Sign/SignUpContainer'; @import 'Components/Sign/SignInModal'; @import 'Components/Upload/UploadPost'; +@import 'Components/Upload/ImageCropper'; @import 'Components/Search/'; @import 'Components/Post/Post'; @import 'Components/Post/PostList'; diff --git a/yarn.lock b/yarn.lock index 76d96c98..d1c58534 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2307,11 +2307,6 @@ babel-code-frame@^6.22.0: esutils "^2.0.2" js-tokens "^3.0.2" -babel-core@7.0.0-bridge.0: - version "7.0.0-bridge.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" - integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== - babel-eslint@10.1.0, babel-eslint@^10.1.0: version "10.1.0" resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-10.1.0.tgz#6968e568a910b78fb3779cdd8b6ac2f479943232" @@ -3421,10 +3416,10 @@ create-react-context@^0.3.0: gud "^1.0.0" warning "^4.0.3" -cropperjs@^1.5.5: - version "1.5.7" - resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.7.tgz#b65019725bae1c6285e881fb661b2141fa57025b" - integrity sha512-sGj+G/ofKh+f6A4BtXLJwtcKJgMUsXYVUubfTo9grERiDGXncttefmue/fyQFvn8wfdyoD1KhDRYLfjkJFl0yw== +cropperjs@^1.5.11: + version "1.5.11" + resolved "https://registry.yarnpkg.com/cropperjs/-/cropperjs-1.5.11.tgz#502ae6d8ca098b124de6813601cca70015879fc0" + integrity sha512-SJUeBBhtNBnnn+UrLKluhFRIXLJn7XFPv8QN1j49X5t+BIMwkgvDev541f96bmu8Xe0TgCx3gON22KmY/VddaA== cropperjs@^1.5.9: version "1.5.9" @@ -9335,14 +9330,12 @@ react-background-slider@^1.2.0: css-vendor "^1.2.1" prop-types "^15.7.2" -react-cropper@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/react-cropper/-/react-cropper-1.3.0.tgz#7de0c09a440d62f72c0df5f104d4ad7598cc3bf0" - integrity sha512-A14BUmeOD84ulNp/eChHOBuChVYatPodhc43wZzO9ZSFoM2Ta8O9P+ZXURyseF/R8v32a6YyMVcBUShj7XqcbA== +react-cropper@2.1.7: + version "2.1.7" + resolved "https://registry.yarnpkg.com/react-cropper/-/react-cropper-2.1.7.tgz#f9f8127b9516fecc44f918dd331107bfc32adfaf" + integrity sha512-dGp2l2wSh4KubWx3PQZIP27fV4shPYjIvm972IqJZdzi/fpDsB6s3GKEH54mk0gy18+80d3WnlgAP2xJ/o8+yw== dependencies: - babel-core "7.0.0-bridge.0" - cropperjs "^1.5.5" - prop-types "^15.5.8" + cropperjs "^1.5.11" react-dev-utils@^10.2.1: version "10.2.1"