test-dont-get-it
Version:
React.js component for uploading images to the server
928 lines (881 loc) • 21.5 kB
JSX
/* @flow */
import React, { Component } from "react";
import PropTypes from "prop-types";
import fetch from "isomorphic-fetch";
import autobind from "autobind-decorator";
import classnames from "classnames";
import Dropzone from "react-dropzone";
import Button from "react-progress-button-for-images-uploader";
import "babel-core/register";
import "babel-polyfill";
console.log("test-test");
export default class ImagesUploader extends Component {
/* eslint-disable react/sort-comp */
state: {
imagePreviewUrls: Array<string>,
loadState: string,
optimisticPreviews: Array<string>,
displayNotification: boolean,
};
input: ?HTMLInputElement;
static propTypes = {
url: PropTypes.string.isRequired,
dataName: PropTypes.string,
headers: PropTypes.object,
classNamespace: PropTypes.string,
inputId: PropTypes.string,
label: PropTypes.string,
images: PropTypes.array,
disabled: PropTypes.bool,
onLoadStart: PropTypes.func,
onLoadEnd: PropTypes.func,
deleteImage: PropTypes.func,
clickImage: PropTypes.func,
optimisticPreviews: PropTypes.bool,
multiple: PropTypes.bool,
image: PropTypes.string,
notification: PropTypes.string,
max: PropTypes.number,
color: PropTypes.string,
disabledColor: PropTypes.string,
borderColor: PropTypes.string,
disabledBorderColor: PropTypes.string,
notificationBgColor: PropTypes.string,
notificationColor: PropTypes.string,
deleteElement: PropTypes.oneOfType([
PropTypes.string,
PropTypes.element,
]),
plusElement: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
classNames: PropTypes.shape({
container: PropTypes.string,
label: PropTypes.string,
deletePreview: PropTypes.string,
loadContainer: PropTypes.string,
dropzone: PropTypes.string,
pseudobutton: PropTypes.string,
pseudobuttonContent: PropTypes.string,
imgPreview: PropTypes.string,
fileInput: PropTypes.string,
emptyPreview: PropTypes.string,
filesInputContainer: PropTypes.string,
notification: PropTypes.string,
}),
styles: PropTypes.shape({
container: PropTypes.object,
label: PropTypes.object,
deletePreview: PropTypes.object,
loadContainer: PropTypes.object,
dropzone: PropTypes.object,
pseudobutton: PropTypes.object,
pseudobuttonContent: PropTypes.object,
imgPreview: PropTypes.object,
fileInput: PropTypes.object,
emptyPreview: PropTypes.object,
filesInputContainer: PropTypes.object,
notification: PropTypes.object,
}),
};
static defaultProps = {
dataName: "imageFiles",
headers: {},
classNames: {},
styles: {},
multiple: true,
color: "#142434",
disabledColor: "#bec3c7",
borderColor: "#a9bac8",
disabledBorderColor: "#bec3c7",
notificationBgColor: "rgba(0, 0, 0, 0.3)",
notificationColor: "#fafafa",
classNamespace: "iu-",
};
constructor(props: Object) {
super(props);
let imagePreviewUrls = [];
if (this.props.images && this.props.multiple !== false) {
imagePreviewUrls = this.props.images || [];
}
if (this.props.image && this.props.multiple === false) {
imagePreviewUrls = [this.props.image];
}
this.state = {
imagePreviewUrls,
loadState: "",
optimisticPreviews: [],
displayNotification: false,
};
this.input = null;
}
/* eslint-enable react/sort-comp */
componentWillMount() {
// support SSR rendering.
// we should not use document on server, so just omit
// these calls
if (typeof document !== "undefined") {
document.addEventListener(
"dragover",
(event) => {
// prevent default to allow drop
event.preventDefault();
},
false
);
document.addEventListener(
"drop",
(event) => {
// prevent default to allow drop
event.preventDefault();
},
false
);
}
}
componentWillReceiveProps(nextProps: Object) {
if (
!this.props.images &&
nextProps.images &&
nextProps.multiple !== false
) {
this.setState({
imagePreviewUrls: nextProps.images,
});
}
if (
!this.props.image &&
nextProps.image &&
nextProps.multiple === false
) {
this.setState({
imagePreviewUrls: [nextProps.image],
});
}
}
clickImage(key: number, url: string) {
const clickImageCallback = this.props.clickImage;
if (clickImageCallback && typeof clickImageCallback === "function") {
clickImageCallback(key, url);
}
}
deleteImage(key: number, url: string) {
if (!this.props.disabled) {
const imagePreviewUrls = this.state.imagePreviewUrls;
imagePreviewUrls.splice(key, 1);
this.setState({
imagePreviewUrls,
});
if (
this.props.deleteImage &&
typeof this.props.deleteImage === "function"
) {
this.props.deleteImage(key, url);
}
}
}
buildPreviews(
urls: Array<string>,
optimisticUrls?: Array<string>,
inButton?: boolean
) {
const {
classNamespace,
disabled,
classNames,
styles,
color,
disabledColor,
borderColor,
disabledBorderColor,
notificationBgColor,
notificationColor,
deleteElement,
plusElement,
} = this.props;
if (
(!urls || urls.length < 1) &&
(!optimisticUrls || optimisticUrls.length < 1)
) {
return (
<div
className={
classNames.emptyPreview ||
`${classNamespace}emptyPreview`
}
style={styles.emptyPreview}
/>
);
}
let previews = [];
const multiple = this.props.multiple;
if (
urls &&
urls.length > 0 &&
!(multiple === false && optimisticUrls && optimisticUrls.length > 0)
) {
previews = urls.map((url, key) => {
if (url) {
let imgPreviewStyle = {
backgroundImage: `url(${url})`,
borderColor: disabled
? disabledBorderColor
: borderColor,
};
if (this.props.size) {
imgPreviewStyle = {
...imgPreviewStyle,
...{
width: this.props.size,
height: this.props.size,
},
...(styles.imagePreview || {}),
};
}
const deletePreviewStyle = {
...{
color: disabled ? disabledColor : color,
borderColor: disabled
? disabledBorderColor
: borderColor,
},
...(styles.deletePreview || {}),
};
return (
<div
className={
classNames.imgPreview ||
`${classNamespace}imgPreview`
}
key={key}
style={imgPreviewStyle}
onClick={(e) => {
e.preventDefault();
this.clickImage(key, url);
}}
>
{!inButton ? (
<div
className={
classNames.deletePreview ||
`${classNamespace}deletePreview`
}
style={deletePreviewStyle}
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
this.deleteImage(key, url);
}}
>
{deleteElement || (
<svg
xmlns="http://www.w3.org/2000/svg"
width="7.969"
height="8"
viewBox="0 0 7.969 8"
>
<path
id="X_Icon"
data-name="X Icon"
style={{
fill: disabled
? disabledColor
: color,
fillRule: "evenodd",
}}
/* eslint-disable max-len */
d="M562.036,606l2.849-2.863a0.247,0.247,0,0,0,0-.352l-0.7-.706a0.246,0.246,0,0,0-.352,0l-2.849,2.862-2.849-2.862a0.247,0.247,0,0,0-.352,0l-0.7.706a0.249,0.249,0,0,0,0,.352L559.927,606l-2.849,2.862a0.25,0.25,0,0,0,0,.353l0.7,0.706a0.249,0.249,0,0,0,.352,0l2.849-2.862,2.849,2.862a0.249,0.249,0,0,0,.352,0l0.7-.706a0.25,0.25,0,0,0,0-.353Z"
/* eslint-enable max-len */
transform="translate(-557 -602)"
/>
</svg>
)}
</div>
) : (
<div
className={
classNames.notification ||
`${classNamespace}notification`
}
style={
styles.notification
? {
...styles.notification,
...{
display: this.state
.displayNotification
? "block"
: "none",
backgroundColor: notificationBgColor,
color: notificationColor,
},
}
: {
display: this.state
.displayNotification
? "block"
: "none",
backgroundColor: notificationBgColor,
color: notificationColor,
}
}
>
<span>
{this.props.notification ||
this.buildPlus(
disabled,
notificationColor,
disabledColor,
plusElement
)}
</span>
</div>
)}
</div>
);
}
return null;
});
}
if (optimisticUrls && optimisticUrls.length > 0) {
const length = previews.length;
previews = previews.concat(
optimisticUrls.map((url, key) => {
if (url) {
let imgPreviewStyle = {
backgroundImage: `url(${url})`,
borderColor: disabled
? disabledBorderColor
: borderColor,
};
if (this.props.size) {
imgPreviewStyle = {
...imgPreviewStyle,
...{
width: this.props.size,
height: this.props.size,
},
...(styles.imgPreview || {}),
};
}
return (
<div
className={
classNames.imgPreview ||
`${classNamespace}imgPreview`
}
key={length + key}
style={imgPreviewStyle}
/>
);
}
return null;
})
);
}
return previews;
}
async loadImages(files: FileList, url: string, onLoadEnd?: Function): any {
if (url) {
try {
const imageFormData = new FormData();
for (let i = 0; i < files.length; i++) {
imageFormData.append(
this.props.dataName,
files[i],
files[i].name
);
}
let response = await fetch(url, {
method: "POST",
credentials: "include",
body: imageFormData,
headers: this.props.headers,
});
if (response && response.status && response.status === 200) {
response = await response.json();
const multiple = this.props.multiple;
if (
response instanceof Array ||
typeof response === "string"
) {
let imagePreviewUrls = [];
if (multiple === false) {
imagePreviewUrls =
response instanceof Array
? response
: [response];
} else {
imagePreviewUrls = this.state.imagePreviewUrls.concat(
response
);
}
this.setState({
imagePreviewUrls,
optimisticPreviews: [],
loadState: "success",
});
if (onLoadEnd && typeof onLoadEnd === "function") {
onLoadEnd(false, response);
}
} else {
const err = {
message: "invalid response type",
response,
fileName: "ImagesUploader",
};
this.setState({
loadState: "error",
optimisticPreviews: [],
});
if (onLoadEnd && typeof onLoadEnd === "function") {
onLoadEnd(err);
}
}
} else {
const err = {
message: "server error",
status: response ? response.status : false,
response,
fileName: "ImagesUploader",
};
this.setState({
loadState: "error",
optimisticPreviews: [],
});
if (onLoadEnd && typeof onLoadEnd === "function") {
onLoadEnd(err);
}
}
} catch (err) {
if (onLoadEnd && typeof onLoadEnd === "function") {
onLoadEnd(err);
}
this.setState({
loadState: "error",
optimisticPreviews: [],
});
}
}
}
handleImageChange(e: Object) {
e.preventDefault();
const filesList = e.target.files;
// Return when cancel button click but onChange event trigger
console.log("eeeeeeeeeeee");
if (filesList.length === 0) {
return;
}
const {
onLoadStart,
onLoadEnd,
url,
optimisticPreviews,
multiple,
} = this.props;
if (onLoadStart && typeof onLoadStart === "function") {
onLoadStart();
}
this.setState({
loadState: "loading",
});
if (
this.props.max &&
filesList.length + this.state.imagePreviewUrls.length >
this.props.max
) {
const err = {
message: "exceeded the number",
};
this.setState({
loadState: "error",
optimisticPreviews: [],
});
if (onLoadEnd && typeof onLoadEnd === "function") {
onLoadEnd(err);
}
return;
}
for (let i = 0; i < filesList.length; i++) {
const file = filesList[i];
if (optimisticPreviews) {
const reader = new FileReader();
reader.onload = (upload) => {
if (multiple === false) {
this.setState({
optimisticPreviews: [upload.target.result],
});
} else {
const prevOptimisticPreviews = this.state
.optimisticPreviews;
this.setState({
optimisticPreviews: prevOptimisticPreviews.concat(
upload.target.result
),
});
}
};
reader.readAsDataURL(file);
}
if (!file.type.match("image.*")) {
const err = {
message: "file type error",
type: file.type,
fileName: "ImagesUploader",
};
if (onLoadEnd && typeof onLoadEnd === "function") {
onLoadEnd(err);
}
this.setState({
loadState: "error",
});
return;
}
}
if (url) {
this.loadImages(filesList, url, onLoadEnd);
}
}
handleFileDrop(files: FileList) {
if (!this.props.disabled) {
this.handleImageChange({
preventDefault: () => true,
target: {
files,
},
});
}
}
/* eslint-disable max-len, no-undef */
buildPlus(
disabled: boolean,
color: string,
disabledColor: string,
plusElement?: string | React$Element<*>
) {
return (
plusElement || (
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"
style={{
width: 35,
fill: disabled ? disabledColor : color,
}}
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 1000 1000"
enableBackground="new 0 0 1000 1000"
xmlSpace="preserve"
>
<g>
<path d="M500,10c13.5,0,25.1,4.8,34.7,14.4C544.2,33.9,549,45.5,549,59v392h392c13.5,0,25.1,4.8,34.7,14.4c9.6,9.6,14.4,21.1,14.4,34.7c0,13.5-4.8,25.1-14.4,34.6c-9.6,9.6-21.1,14.4-34.7,14.4H549v392c0,13.5-4.8,25.1-14.4,34.7c-9.6,9.6-21.1,14.4-34.7,14.4c-13.5,0-25.1-4.8-34.7-14.4c-9.6-9.6-14.4-21.1-14.4-34.7V549H59c-13.5,0-25.1-4.8-34.7-14.4C14.8,525.1,10,513.5,10,500c0-13.5,4.8-25.1,14.4-34.7C33.9,455.8,45.5,451,59,451h392V59c0-13.5,4.8-25.1,14.4-34.7C474.9,14.8,486.5,10,500,10L500,10z" />
</g>
</svg>
)
);
}
/* eslint-enable max-len, no-undef */
buildButtonContent() {
const {
multiple,
classNamespace,
disabled,
classNames,
styles,
color,
disabledColor,
plusElement,
} = this.props;
const pseudobuttonContentStyle = {
...{
color: disabled ? disabledColor : color,
},
...styles.pseudobuttonContent,
};
if (multiple !== false) {
return (
<span
className={
classNames.pseudobuttonContent ||
`${classNamespace}pseudobuttonContent`
}
style={pseudobuttonContentStyle}
>
{this.buildPlus(
disabled,
color,
disabledColor,
plusElement
)}
</span>
);
}
const { imagePreviewUrls, optimisticPreviews } = this.state;
if (
(!imagePreviewUrls || imagePreviewUrls.length < 1) &&
(!optimisticPreviews || optimisticPreviews.length < 1)
) {
return (
<span
className={
classNames.pseudobuttonContent ||
`${classNamespace}pseudobuttonContent`
}
style={pseudobuttonContentStyle}
>
{this.buildPlus(
disabled,
color,
disabledColor,
plusElement
)}
</span>
);
}
return this.buildPreviews(imagePreviewUrls, optimisticPreviews, true);
}
buildClose() {
const {
multiple,
classNamespace,
disabled,
classNames,
styles,
color,
disabledColor,
borderColor,
disabledBorderColor,
deleteElement,
} = this.props;
if (multiple !== false) {
return null;
}
const { imagePreviewUrls } = this.state;
if (!imagePreviewUrls || imagePreviewUrls.length < 1) {
return null;
}
const deletePreviewStyle = {
...{
color: disabled ? disabledColor : color,
borderColor: disabled ? disabledBorderColor : borderColor,
},
...(styles.deletePreview || {}),
};
return (
<div
className={
classNames.deletePreview || `${classNamespace}deletePreview`
}
style={deletePreviewStyle}
onClick={(e) => {
e.preventDefault();
this.deleteImage(0);
}}
>
{deleteElement || (
<svg
xmlns="http://www.w3.org/2000/svg"
width="7.969"
height="8"
viewBox="0 0 7.969 8"
>
<path
id="X_Icon"
data-name="X Icon"
style={{
fill: disabled ? disabledColor : color,
fillRrule: "evenodd",
}}
/* eslint-disable max-len */
d="M562.036,606l2.849-2.863a0.247,0.247,0,0,0,0-.352l-0.7-.706a0.246,0.246,0,0,0-.352,0l-2.849,2.862-2.849-2.862a0.247,0.247,0,0,0-.352,0l-0.7.706a0.249,0.249,0,0,0,0,.352L559.927,606l-2.849,2.862a0.25,0.25,0,0,0,0,.353l0.7,0.706a0.249,0.249,0,0,0,.352,0l2.849-2.862,2.849,2.862a0.249,0.249,0,0,0,.352,0l0.7-.706a0.25,0.25,0,0,0,0-.353Z"
/* eslint-enable max-len */
transform="translate(-557 -602)"
/>
</svg>
)}
</div>
);
}
showNotification() {
const { multiple, disabled } = this.props;
const { imagePreviewUrls } = this.state;
if (
!disabled &&
multiple === false &&
imagePreviewUrls &&
imagePreviewUrls.length > 0
) {
this.setState({
displayNotification: true,
});
}
}
hideNotification() {
const { multiple } = this.props;
if (multiple === false) {
this.setState({
displayNotification: false,
});
}
}
render() {
const { imagePreviewUrls, loadState, optimisticPreviews } = this.state;
const {
inputId,
disabled,
multiple,
label,
size,
classNamespace,
classNames,
styles,
color,
disabledColor,
borderColor,
disabledBorderColor,
} = this.props;
const containerClassNames = classnames({
[classNames.container || `${classNamespace}container`]: true,
disabled,
});
const loadContainerStyle = {
...(size
? {
width: size,
height: size,
}
: {}),
...{
color: disabled ? disabledColor : color,
},
...(styles.loadContainer || {}),
};
const pseudobuttonStyle = {
...(size
? {
width: size,
height: size,
}
: {}),
...{
color: disabled ? disabledColor : color,
},
...(styles.pseudobuttonStyle || {}),
};
const labelStyle = {
...{
color: disabled ? disabledColor : color,
},
...(styles.label || {}),
};
const dropzoneStyle = {
...{
borderColor: disabled ? disabledBorderColor : borderColor,
},
...(styles.dropzone || {}),
};
return (
<div className={containerClassNames} style={styles.container || {}}>
<label
className={classNames.label || `${classNamespace}label`}
style={labelStyle}
htmlFor={inputId || "filesInput"}
>
{label || null}
</label>
<div
className={
classNames.filesInputContainer ||
`${classNamespace}filesInputContainer`
}
style={styles.filesInputContainer}
>
<div
className={
classNames.loadContainer ||
`${classNamespace}loadContainer`
}
style={loadContainerStyle}
>
{this.buildClose()}
<Dropzone
onDrop={this.handleFileDrop}
disableClick
accept="image/*"
className={
classNames.dropzone ||
`${classNamespace}dropzone`
}
style={dropzoneStyle}
multiple={
/* eslint-disable no-unneeded-ternary */
multiple === false ? false : true
/* eslint-enable no-unneeded-ternary */
}
>
<Button
state={loadState}
type="button"
classNamespace={`${classNamespace}button-`}
className={
classNames.pseudobutton ||
`${classNamespace}pseudobutton`
}
style={pseudobuttonStyle}
onClick={(e) => {
e.preventDefault();
if (this.input) {
this.input.click();
}
}}
onMouseOver={this.showNotification}
onMouseLeave={this.hideNotification}
onDragOver={this.showNotification}
onDragLeave={this.hideNotification}
>
{this.buildButtonContent()}
</Button>
</Dropzone>
</div>
<input
name={inputId || "filesInput"}
id={inputId || "filesInput"}
className={
classNames.fileInput || `${classNamespace}fileInput`
}
style={{
...{
display: "none",
},
...(styles.fileInput || {}),
}}
ref={(ref) => {
this.input = ref;
}}
type="file"
accept="image/*"
multiple={multiple === false ? false : "multiple"}
disabled={disabled || loadState === "loading"}
onChange={this.handleImageChange}
/>
</div>
{multiple !== false
? this.buildPreviews(
imagePreviewUrls,
this.props.optimisticPreviews && optimisticPreviews
)
: null}
</div>
);
}
}