UNPKG

react-dnd-crop

Version:
287 lines (281 loc) 10.5 kB
import React, { Component } from "react"; import { swap, filterWithIndex, validateImage, base64StringtoFile, replaceWithIndex, validateImageMimeType } from "./helper"; import { style } from "./style"; import Modal from "../Modal/Modal"; import CancelIcon from "../icons/cancel.svg"; import CropIcon from "../icons/crop.svg"; import ImageIcon from "../icons/image.svg"; import "./Dnd.css"; class Dnd extends Component { constructor(props) { super(props); let cropRatio = props.cropRatio ? props.cropRatio : { unit: "%", width: 30, aspect: 4 / 3 }; this.state = { files: [], fixedDataUrl: [], imageUrls: [], finalUrl: [], draggingElmIndex: null, cropImgSrc: null, currentCropImageIndex: null, open: false, url: [], cropRatio: cropRatio, mimeType: props.mimeType || "png/jpg/jpeg/gif", errorMessage: props.errorMessages || {} }; this.INPUT_REF = React.createRef(); this.onImageBrowse = this.onImageBrowse.bind(this); this.openModal = this.openModal.bind(this); this.onImageDrop = this.onImageDrop.bind(this); this.onDrop = this.onDrop.bind(this); this.callback = this.callback.bind(this); this.getImageDataUrl = this.getImageDataUrl.bind(this); this.onInputChange = this.onInputChange.bind(this); this.removeImage = this.removeImage.bind(this); this.checkValidImages = this.checkValidImages.bind(this); this.convertImageForupload = this.convertImageForupload.bind(this); this.getcropImage = this.getcropImage.bind(this); this.cancelCrop = this.cancelCrop.bind(this); this.handleClearToDefault = this.handleClearToDefault.bind(this); this.onDragStart = this.onDragStart.bind(this); } preventDefaults(e) { e.preventDefault(); e.stopPropagation(); } openModal(index) { let { fixedDataUrl } = this.state; let cropImgSrc = fixedDataUrl.find((_, i) => i === index); this.setState({ cropImgSrc: cropImgSrc, open: true, currentCropImageIndex: index }); } handleClearToDefault() { this.setState({ open: false }); } async onDrop(e, index) { e.stopPropagation(); let { draggingElmIndex, files, imageUrls, fixedDataUrl } = this.state; files = swap(files, draggingElmIndex, index); imageUrls = swap(imageUrls, draggingElmIndex, index); fixedDataUrl = swap(fixedDataUrl, draggingElmIndex, index); await this.setState({ files: files, imageUrls, fixedDataUrl, draggingElmIndex: null }); } onDragStart(_, index) { this.setState({ draggingElmIndex: index }); } // image Events onImageBrowse() { let DRAG_AND_DROP_AREA_INPUT = this.INPUT_REF.current; if (DRAG_AND_DROP_AREA_INPUT && document.createEvent) { var evt = document.createEvent("MouseEvents"); evt.initEvent("click", true, false); DRAG_AND_DROP_AREA_INPUT.dispatchEvent(evt); } } callback(payload) { if (this.props.callback) return this.props.callback(payload); console.log("Provide a callback"); } onImageDrop(e) { this.preventDefaults(e); let { files, imageUrls, mimeType, errorMessage, cropRatio, freeCrop } = this.state, validUpload = true; let newFiles = e.dataTransfer.files; if (imageUrls.length + newFiles.length > (this.props.maxImageUpload || 12)) return this.callback({ error: errorMessage.maxImageUpload || `You Cant Upload More than ${this.props.maxImageUpload || 12} Files` }); [...newFiles].forEach(file => { if (!validateImageMimeType(file, mimeType)) { validUpload = false; this.callback({ error: errorMessage.imageFormat || "Only Jpeg/Jpg/Png/Gif Allowed" }); return false; } }); if (validUpload) { this.setState({ files: [...files, ...newFiles] }); [...newFiles].forEach(file => validateImage(file, this.getImageDataUrl, cropRatio, freeCrop)); } } getImageDataUrl(isValid, url) { if (!this.props.crop) isValid = true; let { imageUrls, fixedDataUrl, errorMessage } = this.state; if (imageUrls.length + 1 > (this.props.maxImageUpload || 12)) return this.callback({ error: errorMessage.maxImageUpload || `You Cant Upload More than ${this.props.maxImageUpload || 12} Files` }); this.setState({ fixedDataUrl: [...fixedDataUrl, url], imageUrls: [...imageUrls, { url: url, isValid }] }); } async onInputChange(e) { let { files, imageUrls, mimeType, errorMessage, cropRatio } = this.state; let file = e.target.files[0]; e.target.value = null; if (!file) return; files = [...files, file]; if (!validateImageMimeType(file, mimeType)) return this.callback({ error: errorMessage.imageFormat || "Only Jpeg/Jpg/Png/Gif Allowed" }); if (imageUrls.length + 1 > this.props.maxImageUpload) return this.callback({ error: errorMessage.maxImageUpload || `You Cant Upload More than ${this.props.maxImageUpload} Files` }); this.setState({ files }, () => { validateImage(file, this.getImageDataUrl, cropRatio, this.props.freeCrop); }); this.convertImageForupload(); } async removeImage(index) { let { files, imageUrls, fixedDataUrl } = this.state; files = filterWithIndex(files, index); fixedDataUrl = filterWithIndex(fixedDataUrl, index); imageUrls = filterWithIndex(imageUrls, index); await this.setState({ files, imageUrls, fixedDataUrl }); } checkValidImages() { let { imageUrls } = this.state; for (let i = 0; i < imageUrls.length; i++) if (!imageUrls[i].isValid) return false; return true; } convertImageForupload() { let { imageUrls, files, errorMessage } = this.state; let fileObject = []; imageUrls.forEach((url, index) => { let file = base64StringtoFile(url.url, files[index].name); fileObject.push(file); }); let isUploadValid = this.checkValidImages(); if (!isUploadValid) return this.callback({ error: errorMessage.aspectRatio || "Image is not in the proper Aspect Ratio" }); return this.callback({ fileObject, imageUrls }); } getcropImage(url) { let { currentCropImageIndex, imageUrls, files } = this.state; let file = base64StringtoFile(url, files[currentCropImageIndex].name); imageUrls = replaceWithIndex(imageUrls, currentCropImageIndex, { url: url, isValid: true }); files = replaceWithIndex(files, currentCropImageIndex, file); this.setState({ files, imageUrls, currentCropImageIndex: null, open: false }); this.convertImageForupload(); } cancelCrop() { this.setState({ open: false }); } render() { let { imageUrls, open, cropImgSrc } = this.state; let styles = style(this.props); return ( <div onMouseOut={this.convertImageForupload}> <input ref={this.INPUT_REF} type="file" id="DRAG_AND_DROP_AREA_INPUT" onChange={this.onInputChange} /> <div id="DRAG_AND_DROP_AREA" style={styles.DRAG_AND_DROP_AREA} onDragOver={e => this.preventDefaults(e)} onDrop={this.onImageDrop} onClick={this.onImageBrowse} > {imageUrls.length === 0 ? null : ( <span id="DRAG_AND_DROP_MESSAGE" style={styles.DRAG_AND_DROP_MESSAGE}> {this.props.message || "Hold and Drag to rearrange the order"} </span> )} {imageUrls.length === 0 ? ( <div id="DRAG_AND_DROP_PLACEHOLDER"> {this.props.icons && this.props.icons.labelIcon ? this.props.icons.labelIcon : <img src={ImageIcon} width="30" height="30" />} <h1>{this.props.label || "Drag and drop images here"}</h1> <p>{this.props.subLabel || "or click to upload"}</p> </div> ) : ( <div> <div id="DRAG_AND_DROP_ZONE"> {imageUrls.map((item, index) => ( <div id="DRAG_AND_DROP_AREA_ITEM" key={index} draggable onClick={e => this.preventDefaults(e)} onDragStart={e => this.onDragStart(e, index)} onDragOver={e => this.preventDefaults(e)} onDrop={e => this.onDrop(e, index)} style={item.isValid ? styles.DRAG_AND_DROP_AREA_ITEM : styles.DRAG_AND_DROP_AREA_ITEM_ERROR} > <div id="DRAG_AND_DROP_AREA_ITEM_IMAGE_CONTAINER" onDragOver={e => this.preventDefaults(e)} style={{ backgroundImage: `url(${item.url})` }} > <span id="DRAG_AND_DROP_AREA_ITEM_CANCEL_ICON" style={styles.DRAG_AND_DROP_AREA_ITEM_CANCEL_ICON} onClick={() => this.removeImage(index)} > {this.props.icons && this.props.icons.cancelIcon ? ( this.props.icons.cancelIcon ) : ( <img src={CancelIcon} width="15" height="15" /> )} </span> {this.props.crop ? ( <span id="DRAG_AND_DROP_AREA_ITEM_CROP_ICON" style={styles.DRAG_AND_DROP_AREA_ITEM_CROP_ICON} onClick={() => this.openModal(index)} > {this.props.icons && this.props.icons.cropIcon ? ( this.props.icons.cropIcon ) : ( <img src={CropIcon} width="15" height="15" /> )} </span> ) : ( "" )} </div> </div> ))} </div> </div> )} </div> <Modal isOpen={open} {...this.props} onRequestClose={this.handleClearToDefault} getcropImage={this.getcropImage} cancelCrop={this.cancelCrop} imageSrc={cropImgSrc} /> </div> ); } } export default Dnd;