From 5cc98bc63d1e6e5777c23914b43a9f0801486746 Mon Sep 17 00:00:00 2001 From: kwajiehao Date: Tue, 24 Dec 2019 10:50:11 +0100 Subject: [PATCH 1/2] feat: update ImageSettingsModal This commit achieves two things: 1. It allows ImageSettingsModal to receive a `toReload` prop since we do not always want the page to reload. In the case of EditPage with the ImageModal, we want users to be able to upload an image and then select it right away. If we do not have a `toReload` prop, the window will reload and the ImageModal will be reset. 2. It adds custom logic for `isPendingUpload`. In cases where the image/file is pending upload, there should not be a delete button in the ImageSettingsModal, and the Save button would not be subject to the usual constraints (requiring a `sha` value before the button becomes active since the create operation does not require a `sha`) --- src/components/ImageSettingsModal.jsx | 54 +++++++++++++++++++-------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/src/components/ImageSettingsModal.jsx b/src/components/ImageSettingsModal.jsx index cfbc4dd80..faa3e5e16 100644 --- a/src/components/ImageSettingsModal.jsx +++ b/src/components/ImageSettingsModal.jsx @@ -39,7 +39,7 @@ export default class ImageSettingsModal extends Component { } renameImage = async () => { - const { match, image, isPendingUpload } = this.props; + const { match, image, isPendingUpload, toReload, onClose } = this.props; const { siteName } = match.params; const { newFileName, @@ -70,8 +70,13 @@ export default class ImageSettingsModal extends Component { withCredentials: true, }); } + // reload after action - window.location.reload(); + if (toReload) { + window.location.reload(); + } else { + onClose(); + } } deleteImage = async () => { @@ -137,20 +142,32 @@ export default class ImageSettingsModal extends Component { />
- - + {isPendingUpload + ? ( + + ) : ( + <> + + + + )}
@@ -172,4 +189,9 @@ ImageSettingsModal.propTypes = { }).isRequired, isPendingUpload: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, + toReload: PropTypes.bool, }; + +ImageSettingsModal.defaultProps = { + toReload: true, +} From c41e29178dbd3fa5a4a7f7f78bd1908cf962fbf8 Mon Sep 17 00:00:00 2001 From: kwajiehao Date: Tue, 24 Dec 2019 11:08:35 +0100 Subject: [PATCH 2/2] feat: refactor ImagesModal to be a PureComponent This commit accomplishes a couple of things: 1. It introduces the functionality to rename image before uploading for the ImageModal (this functionality was firrst introduced for the Images tab in PR #107). 2. It refactors the ImagesModal to be a PureComponent. Previously, the ImageModal retrieved images and saved this images in state. This functionality can be moved to its parent component, EditPage, so that only an `images` prop is passed to the ImageModal. This is beneficial because the ImageModal logic is simple and its output really depends on the images that are passed as props to it. Since the key for the images are the filenames, this means that when we upload a new image, React will perform a shallow compare based on the filenames and only render the new uploaded image instead of re-rendering all the images. --- src/components/ImagesModal.jsx | 74 +++------------------------------- src/layouts/EditPage.jsx | 66 +++++++++++++++++++++++++++++- 2 files changed, 69 insertions(+), 71 deletions(-) diff --git a/src/components/ImagesModal.jsx b/src/components/ImagesModal.jsx index d77fc4817..8d826484f 100644 --- a/src/components/ImagesModal.jsx +++ b/src/components/ImagesModal.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { PureComponent } from 'react'; import axios from 'axios'; import PropTypes from 'prop-types'; import mediaStyles from '../styles/isomer-cms/pages/Media.module.scss'; @@ -25,72 +25,9 @@ export const ImageCard = ({ image, siteName, onClick }) => ( ); -export default class ImagesModal extends Component { - constructor(props) { - super(props); - this.state = { - images: [], - }; - } - - async componentDidMount() { - const { siteName } = this.props; - try { - this.getImage(siteName); - } catch (e) { - console.log(e); - } - } - - getImage = async (siteName) => { - const { data: { images } } = await axios.get(`${process.env.REACT_APP_BACKEND_URL}/sites/${siteName}/images`, { - withCredentials: true, - }); - this.setState({ images }); - } - - uploadImage = async (imageName, imageContent) => { - try { - const { siteName } = this.props; - const params = { - imageName, - content: imageContent, - }; - - // add a loading screen while file is being uploaded - await axios.post(`${process.env.REACT_APP_BACKEND_URL}/sites/${siteName}/images`, params, { - withCredentials: true, - }); - - // trigger a re-render of the modal - const { data: { images } } = await axios.get(`${process.env.REACT_APP_BACKEND_URL}/sites/${siteName}/images`, { - withCredentials: true, - }); - this.setState({ images }); - } catch (err) { - console.log(err); - } - } - - onImageSelect = async (event) => { - const imgReader = new FileReader(); - const imgName = event.target.files[0].name; - imgReader.onload = (() => { - /** Github only requires the content of the image - * imgReader returns `data:image/png;base64, {fileContent}` - * hence the split - */ - - const imgData = imgReader.result.split(',')[1]; - - this.uploadImage(imgName, imgData); - }); - imgReader.readAsDataURL(event.target.files[0]); - } - +export default class ImagesModal extends PureComponent { render() { - const { siteName, onClose, onImageSelect } = this.props; - const { images } = this.state; + const { siteName, images, onClose, onImageSelect, readImageToUpload } = this.props; return (!!images.length && (
@@ -107,7 +44,7 @@ export default class ImagesModal extends Component { onClick={() => document.getElementById('file-upload').click()} />
- ) ); - } + }; } ImageCard.propTypes = { diff --git a/src/layouts/EditPage.jsx b/src/layouts/EditPage.jsx index f0d33304d..1e03c2d1a 100644 --- a/src/layouts/EditPage.jsx +++ b/src/layouts/EditPage.jsx @@ -7,6 +7,7 @@ import marked from 'marked'; import { Base64 } from 'js-base64'; import SimplePage from '../templates/SimplePage'; import ImagesModal from '../components/ImagesModal'; +import ImageSettingsModal from '../components/ImageSettingsModal'; import { frontMatterParser, concatFrontMatterMdBody, prependImageSrc, prettifyPageFileName, } from '../utils'; @@ -37,7 +38,9 @@ export default class EditPage extends Component { sha: null, editorValue: '', frontMatter: '', + images: [], isSelectingImage: false, + pendingImageUpload: null }; this.mdeRef = React.createRef(); } @@ -50,6 +53,7 @@ export default class EditPage extends Component { withCredentials: true, }); const { content, sha } = resp.data; + // split the markdown into front matter and content const { frontMatter, mdBody } = frontMatterParser(Base64.decode(content)); this.setState({ @@ -62,6 +66,13 @@ export default class EditPage extends Component { } } + getImages = async (siteName) => { + const { data: { images } } = await axios.get(`${process.env.REACT_APP_BACKEND_URL}/sites/${siteName}/images`, { + withCredentials: true, + }); + this.setState({ images }); + } + updatePage = async () => { try { const { match } = this.props; @@ -126,10 +137,40 @@ export default class EditPage extends Component { }); } + uploadImage = async (imageName, imageContent) => { + try { + // toggle state so that image renaming modal appears + this.setState({ + pendingImageUpload: { + fileName: imageName, + path: `images%2F${imageName}`, + content: imageContent, + }, + }); + } catch (err) { + console.log(err); + } + } + + readImageToUpload = async (event) => { + const imgReader = new FileReader(); + const imgName = event.target.files[0].name; + imgReader.onload = (() => { + /** Github only requires the content of the image + * imgReader returns `data:image/png;base64, {fileContent}` + * hence the split + */ + + const imgData = imgReader.result.split(',')[1]; + this.uploadImage(imgName, imgData); + }); + imgReader.readAsDataURL(event.target.files[0]); + } + render() { const { match } = this.props; const { siteName, fileName } = match.params; - const { editorValue, isSelectingImage } = this.state; + const { editorValue, images, isSelectingImage, pendingImageUpload } = this.state; return ( <>
this.setState({ isSelectingImage: false })} + images={images} onImageSelect={this.onImageClick} + readImageToUpload={this.readImageToUpload} /> )}
@@ -165,7 +208,10 @@ export default class EditPage extends Component { '|', { name: 'image', - action: () => this.setState({ isSelectingImage: true }), + action: async () => { + await this.getImages(siteName); + this.setState({ isSelectingImage: true }) + }, className: 'fa fa-picture-o', title: 'Insert Image', default: true, @@ -195,6 +241,22 @@ export default class EditPage extends Component { callback={this.deletePage} />
+ { + pendingImageUpload + && ( + { + this.getImages(siteName); + this.setState({ pendingImageUpload: null }); + }} + toReload={false} + /> + ) + } ); }