From f11aae1df118cf93ca69da5e58c8e9a7188b454f Mon Sep 17 00:00:00 2001 From: Kwon Date: Mon, 29 Mar 2021 01:52:24 +0900 Subject: [PATCH 1/2] Apply Upload view #72 --- package.json | 2 +- src/Components/Upload/ImageCropper/index.jsx | 120 ++++++++++++++ src/Components/Upload/ImageCropper/index.scss | 59 +++++++ src/Components/Upload/UploadPost/index.jsx | 153 ++++++------------ src/Components/Upload/UploadPost/index.scss | 103 ++++++------ src/index.scss | 1 + yarn.lock | 25 ++- 7 files changed, 292 insertions(+), 171 deletions(-) create mode 100644 src/Components/Upload/ImageCropper/index.jsx create mode 100644 src/Components/Upload/ImageCropper/index.scss 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..3ca7aa05 --- /dev/null +++ b/src/Components/Upload/ImageCropper/index.jsx @@ -0,0 +1,120 @@ +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'; + +import Button from '@material-ui/core/Button'; + +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, pictureImgUrl, 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..ddb6fa1d --- /dev/null +++ b/src/Components/Upload/ImageCropper/index.scss @@ -0,0 +1,59 @@ +/* + ImageCropper Component Styling +*/ + +.imageCropper { + width: 100%; + border-radius: 18px; + button:focus { + outline: none; + } + &__creator { + margin-bottom: 1rem; + &-previewPic { + 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; + height: 400px; + 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; + } + } + } + + .cropper { + margin: auto; + max-width: 80%; + max-height: 400px; + } + } + } +} diff --git a/src/Components/Upload/UploadPost/index.jsx b/src/Components/Upload/UploadPost/index.jsx index 87375fe2..d9f36ccf 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,23 +47,16 @@ class Upload extends Component { constructor(props) { super(props); this.state = { - pictureImg: null, - pictureImgUrl: null, + imageFormData: null, content: '', 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, + }); }; handleContent = (e) => { @@ -73,29 +65,17 @@ class Upload extends Component { handlePostUpload = async () => { const { user, uploadPost, token } = this.props; - const { pictureImgUrl, pictureImg, content } = this.state; + const { imageFormData, content } = 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', 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(imageFormData, params, token); + if (postUpload) window.location.reload(); } else { // TO DO :: implement login modal ! } @@ -107,84 +87,55 @@ 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! - - -
-
-
- + <> + + +
+
+
+ Upload Your Masterpiece!
- {pictureImgUrl && ( -
- { - this.cropper = cropper; - }} - // Cropper.js options - center - /> -
- )} -
+
+
+
+ +
+
+
+ +
+
+ upload! +
+
+
+
- - - - - - + + ); } diff --git a/src/Components/Upload/UploadPost/index.scss b/src/Components/Upload/UploadPost/index.scss index 6ac37c91..227ae2b4 100644 --- a/src/Components/Upload/UploadPost/index.scss +++ b/src/Components/Upload/UploadPost/index.scss @@ -2,69 +2,66 @@ Upload Post Component Styling */ -.postUpload { - width: 500px; - button:focus { - outline: none; - } - - .backBtn { - width: 35px; - cursor: pointer; - } - - &__creator { - &-uploadPic { - width: 80%; - margin: auto; - margin-bottom: 5px; - - label { - width: 100%; - margin: 0; - cursor: pointer; - } +.uploadPost { + background-color: white; + z-index: 1005; + width: 80%; + height: 85%; + position: fixed; + transform: translate(-50%, -50%); + top: 50%; + left: 50%; + border-radius: 25px; + 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; } + } - &-previewPic { + &__content { + width: 90%; + @include content-center; + &__border { + width: 100%; 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; - } } - &-content { - width: 80%; - margin: auto; + &__input { + width: 100%; } } - &__sketcher { - &-uploadPic { + &__footer { + width: 30%; + border-radius: 58px; + border: 1px solid $light-gray; + margin-bottom: 1rem; + &__upload { display: flex; - - div { - width: 50%; - border: 1px solid #ccd0d5; - border-radius: 4px; - text-align: center; + flex-direction: row; + align-items: center; + img { + margin-left: 0; + } + &__text { + font-size: 2rem; + margin-right: auto; + margin-left: auto; + color: $header-gray; } } } - - &__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" From 38cb02ec34b2c37d87ab3c46fb8a16dbd15767f4 Mon Sep 17 00:00:00 2001 From: Kwon Date: Sun, 4 Apr 2021 18:23:58 +0900 Subject: [PATCH 2/2] Modify Upload View #72 --- src/Components/Upload/ImageCropper/index.jsx | 33 +++------ src/Components/Upload/ImageCropper/index.scss | 26 ++++++- src/Components/Upload/UploadPost/index.jsx | 62 ++++++++++------ src/Components/Upload/UploadPost/index.scss | 74 +++++++++++++------ 4 files changed, 125 insertions(+), 70 deletions(-) diff --git a/src/Components/Upload/ImageCropper/index.jsx b/src/Components/Upload/ImageCropper/index.jsx index 3ca7aa05..9fc4f1bd 100644 --- a/src/Components/Upload/ImageCropper/index.jsx +++ b/src/Components/Upload/ImageCropper/index.jsx @@ -7,8 +7,6 @@ import NoStyleButton from 'Components/Form/NoStyleButton'; import Cropper from 'react-cropper'; import 'cropperjs/dist/cropper.css'; -import Button from '@material-ui/core/Button'; - function ImageCropper(props) { const { imgHandler } = props; const cropperRef = useRef(null); @@ -48,7 +46,7 @@ function ImageCropper(props) { } else { // TO DO :: implement login modal ! } - }, [imgHandler, pictureImgUrl, user, pictureImg]); + }, [imgHandler, user, pictureImg]); const removePicture = () => { setPictureImg(null); @@ -85,24 +83,17 @@ function ImageCropper(props) { {!pictureImg && (
- +
)} diff --git a/src/Components/Upload/ImageCropper/index.scss b/src/Components/Upload/ImageCropper/index.scss index ddb6fa1d..c3b6047e 100644 --- a/src/Components/Upload/ImageCropper/index.scss +++ b/src/Components/Upload/ImageCropper/index.scss @@ -4,13 +4,16 @@ .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; @@ -30,7 +33,6 @@ &__empty { position: relative; - height: 400px; width: 100%; background-color: $background-color; opacity: 50%; @@ -40,11 +42,21 @@ 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; + } } } } @@ -52,8 +64,18 @@ .cropper { margin: auto; max-width: 80%; - max-height: 400px; + 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 d9f36ccf..71c78edb 100644 --- a/src/Components/Upload/UploadPost/index.jsx +++ b/src/Components/Upload/UploadPost/index.jsx @@ -48,7 +48,8 @@ class Upload extends Component { super(props); this.state = { imageFormData: null, - content: '', + title: '', + description: '', modal: false, }; } @@ -59,19 +60,23 @@ class Upload extends Component { }); }; - handleContent = (e) => { - this.setState({ content: e.target.value }); + handleTitle = (e) => { + this.setState({ title: e.target.value }); + }; + + handleDescription = (e) => { + this.setState({ description: e.target.value }); }; handlePostUpload = async () => { const { user, uploadPost, token } = this.props; - const { imageFormData, content } = this.state; + const { imageFormData, title, description } = this.state; if (user) { const params = new URLSearchParams(); - params.append('title', content); + params.append('title', title); params.append('user_id', user.id); - params.append('description', content); + params.append('description', description); params.append('category_id', 5); params.append('is_public', true); const postUpload = await uploadPost(imageFormData, params, token); @@ -104,34 +109,45 @@ class Upload extends Component {
- Upload Your Masterpiece! + Share Your Masterpiece with Blecther. +
+
+ This will be automatically uploaded to my feed and displayed + to Blecther home.
-
+
-
+
+
+
+
+ Brief Introduction about Your Masterpiece +
+
-
-
- -
-
- upload! -
-
-
+
+ +
Upload
+
+
diff --git a/src/Components/Upload/UploadPost/index.scss b/src/Components/Upload/UploadPost/index.scss index 227ae2b4..8b75db89 100644 --- a/src/Components/Upload/UploadPost/index.scss +++ b/src/Components/Upload/UploadPost/index.scss @@ -5,13 +5,13 @@ .uploadPost { background-color: white; z-index: 1005; - width: 80%; - height: 85%; + width: 70%; + height: 70%; position: fixed; transform: translate(-50%, -50%); top: 50%; left: 50%; - border-radius: 25px; + border-radius: 18px; display: flex; flex-direction: column; justify-content: space-between; @@ -29,38 +29,64 @@ color: $main-color; font-size: 1.9rem; } + + &__sub { + font-family: 'AppleSDGothicNeo'; + } } &__content { width: 90%; - @include content-center; - &__border { - width: 100%; - display: flex; - } - - &__input { - width: 100%; + height: 50%; + flex-direction: column; + justify-content: space-around; + align-items: center; + &__image { + height: 100%; } } &__footer { - width: 30%; - border-radius: 58px; - border: 1px solid $light-gray; - margin-bottom: 1rem; - &__upload { + width: 100%; + margin-bottom: 3%; + display: flex; + flex-direction: row; + justify-content: space-around; + align-items: center; + + &__input { + width: 40%; + height: 40%; display: flex; - flex-direction: row; - align-items: center; - img { - margin-left: 0; + flex-direction: column; + justify-content: center; + & input { + width: max-content; } + &__text { - font-size: 2rem; - margin-right: auto; - margin-left: auto; - color: $header-gray; + text-align: center; + color: $main-color; + font-size: 1.3rem; + } + } + &__buttons { + width: fit-content; + height: 100%; + display: flex; + 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; + } } } }