docbrown/app/javascript/article-form/components/ArticleCoverImage.jsx
Nick Taylor f0963c1bfa
Drag and drop an image in the editor (#10145)
* Refactored drag n drop functionality to a hook and component.

* Add the snackbar to the article editor.

* Now you can drag and drop images in the post body content.

* Cleaning up a few things.

* Now post cover images can be dragged and dropped.

* Removed unused empty function.

* Added @testing-library/preact-hooks so that we can test hooks.

* Some renaming, small refactor.

* Added tests

* Added more tests.

* Added more tests.

* Added message about dropping only one image in post body.

* Added message about dropping only one image for cover image.

* put onDrop into it's own meethod.

* More tests.

* Added some API documentation.

* Updated editor help with drag and drop image help.

* Changed wording.

* Now the alt text for markdown removes the file extension.

* Updated help wording. Thanks @rhymes

* Now the image markdown adds a new line at the end as we currently do not support inline images.
2020-09-04 14:12:59 -04:00

139 lines
4 KiB
JavaScript

import { h, Component, Fragment } from 'preact';
import PropTypes from 'prop-types';
import { addSnackbarItem } from '../../Snackbar';
import { generateMainImage } from '../actions';
import { validateFileInputs } from '../../packs/validateFileInputs';
import { onDragOver, onDragExit } from './dragAndDropHelpers';
import { Button } from '@crayons';
import { Spinner } from '@crayons/Spinner/Spinner';
import { DragAndDropZone } from '@utilities/dragAndDrop';
export class ArticleCoverImage extends Component {
state = {
uploadError: false,
uploadErrorMessage: null,
uploadingImage: false,
};
onImageUploadSuccess = (...args) => {
this.props.onMainImageUrlChange(...args);
this.setState({ uploadingImage: false });
};
handleMainImageUpload = (event) => {
event.preventDefault();
this.setState({ uploadingImage: true });
this.clearUploadError();
if (validateFileInputs()) {
const { files: image } =
event instanceof DragEvent ? event.dataTransfer : event.target;
const payload = { image, wrap_cloudinary: true };
generateMainImage(payload, this.onImageUploadSuccess, this.onUploadError);
}
};
clearUploadError = () => {
this.setState({
uploadError: false,
uploadErrorMessage: null,
});
};
onUploadError = (error) => {
this.setState({
uploadingImage: false,
uploadError: true,
uploadErrorMessage: error.message,
});
};
triggerMainImageRemoval = (e) => {
e.preventDefault();
const { onMainImageUrlChange } = this.props;
onMainImageUrlChange({
links: [],
});
};
onDropImage = (event) => {
onDragExit(event);
if (event.dataTransfer.files.length > 1) {
addSnackbarItem({
message: 'Only one image can be dropped at a time.',
addCloseButton: true,
});
return;
}
this.handleMainImageUpload(event);
};
render() {
const { mainImage } = this.props;
const { uploadError, uploadErrorMessage, uploadingImage } = this.state;
const uploadLabel = mainImage ? 'Change' : 'Add a cover image';
return (
<DragAndDropZone
onDragOver={onDragOver}
onDragExit={onDragExit}
onDrop={this.onDropImage}
>
<div className="crayons-article-form__cover" role="presentation">
{!uploadingImage && mainImage && (
<img
src={mainImage}
className="crayons-article-form__cover__image"
width="250"
height="105"
alt="Post cover"
/>
)}
<div className="flex items-center">
{uploadingImage ? (
<span class="lh-base pl-1 border-0 py-2 inline-block">
<Spinner /> Uploading...
</span>
) : (
<Fragment>
<Button variant="outlined" className="mr-2 whitespace-nowrap">
<label htmlFor="cover-image-input">{uploadLabel}</label>
<input
id="cover-image-input"
type="file"
onChange={this.handleMainImageUpload}
accept="image/*"
className="w-100 h-100 absolute left-0 right-0 top-0 bottom-0 overflow-hidden opacity-0 cursor-pointer"
data-max-file-size-mb="25"
/>
</Button>
{mainImage && (
<Button
variant="ghost-danger"
onClick={this.triggerMainImageRemoval}
>
Remove
</Button>
)}
</Fragment>
)}
</div>
{uploadError && (
<p className="articleform__uploaderror">{uploadErrorMessage}</p>
)}
</div>
</DragAndDropZone>
);
}
}
ArticleCoverImage.propTypes = {
mainImage: PropTypes.string.isRequired,
onMainImageUrlChange: PropTypes.func.isRequired,
};
ArticleCoverImage.displayName = 'ArticleCoverImage';