1 | import React, { Component, createRef } from 'react';
|
2 | import PropTypes from 'prop-types';
|
3 | import UploadCore from '@availity/upload-core';
|
4 | import { FormFeedback } from 'reactstrap';
|
5 | import Dropzone from 'react-dropzone';
|
6 | import map from 'lodash.map';
|
7 | import uuid from 'uuid/v4';
|
8 | import FilePickerBtn from './FilePickerBtn';
|
9 | import FileList from './FileList';
|
10 | import './styles.scss';
|
11 |
|
12 | const validationAttrs = ['min', 'max', 'required'];
|
13 |
|
14 | class Upload extends Component {
|
15 | constructor(props) {
|
16 | super(props);
|
17 |
|
18 | this.state = {
|
19 | files: [],
|
20 | };
|
21 | }
|
22 |
|
23 | input = createRef();
|
24 |
|
25 | files = [];
|
26 |
|
27 | error = null;
|
28 |
|
29 | removeFile = fileId => {
|
30 | this.error = null;
|
31 | this.setState(({ files }) => {
|
32 | const newFiles = files.filter(file => file.id !== fileId);
|
33 | if (newFiles.length !== files.length) {
|
34 | this.files = newFiles;
|
35 |
|
36 | if (this.props.onFileRemove)
|
37 | this.props.onFileRemove(this.files, fileId);
|
38 | return {
|
39 | files: newFiles,
|
40 | };
|
41 | }
|
42 | return null;
|
43 | });
|
44 | };
|
45 |
|
46 | setFiles = files => {
|
47 | let selectedFiles = [];
|
48 | for (let i = 0; i < files.length; i++) {
|
49 | selectedFiles[i] = files[i];
|
50 | }
|
51 | if (
|
52 | this.props.max &&
|
53 | selectedFiles.length + this.state.files.length > this.props.max
|
54 | ) {
|
55 | selectedFiles = selectedFiles.slice(
|
56 | 0,
|
57 | Math.max(0, this.props.max - this.state.files.length)
|
58 | );
|
59 | }
|
60 | this.files = this.files.concat(
|
61 | selectedFiles.map(file => {
|
62 | const upload = new UploadCore(file, {
|
63 | bucketId: this.props.bucketId,
|
64 | customerId: this.props.customerId,
|
65 | clientId: this.props.clientId,
|
66 | fileTypes: this.props.allowedFileTypes,
|
67 | maxSize: this.props.maxSize,
|
68 | allowedFileNameCharacters: this.props.allowedFileNameCharacters,
|
69 | });
|
70 | upload.id = `${upload.id}-${uuid()}`;
|
71 | upload.start();
|
72 | if (this.props.onFileUpload) this.props.onFileUpload(upload);
|
73 | return upload;
|
74 | })
|
75 | );
|
76 | this.error = null;
|
77 | this.setState({ files: this.files });
|
78 | };
|
79 |
|
80 | handleFileInputChange = event => {
|
81 | this.setFiles(event.target.files);
|
82 | };
|
83 |
|
84 | onDrop = (acceptedFiles, rejectedFiles) => {
|
85 | if (rejectedFiles && rejectedFiles.length > 0) {
|
86 | const fileNames = map(rejectedFiles, 'name');
|
87 | this.error = `Could not attach ${fileNames.slice().join(', ')}`;
|
88 | }
|
89 |
|
90 | this.setFiles(acceptedFiles);
|
91 | };
|
92 |
|
93 | reset = () => {
|
94 | this.files = [];
|
95 | this.setState({ files: [] });
|
96 | this.error = null;
|
97 | };
|
98 |
|
99 | componentDidMount() {
|
100 | if (this.context.FormCtrl && this.props.name) {
|
101 | this.updateValidations();
|
102 | }
|
103 | }
|
104 |
|
105 | updateValidations(props = this.props) {
|
106 | this.validations = { ...props.validate };
|
107 |
|
108 | Object.keys(props)
|
109 | .filter(val => validationAttrs.indexOf(val) > -1)
|
110 | .forEach(attr => {
|
111 | if (props[attr]) {
|
112 | this.validations[attr] = this.validations[attr] || {
|
113 | value: props[attr],
|
114 | };
|
115 | } else {
|
116 | delete this.validations[attr];
|
117 | }
|
118 | });
|
119 |
|
120 | this.context.FormCtrl.register(this);
|
121 | this.validate();
|
122 | }
|
123 |
|
124 | validate() {
|
125 | if (this.context.FormCtrl && this.props.name) {
|
126 | this.context.FormCtrl.validate(this.props.name);
|
127 | }
|
128 | }
|
129 |
|
130 | componentWillUnmount() {
|
131 | if (this.context.FormCtrl && this.props.name)
|
132 | this.context.FormCtrl.unregister(this);
|
133 | }
|
134 |
|
135 | getValue() {
|
136 | if (!this.files) return [];
|
137 | return this.files;
|
138 | }
|
139 |
|
140 | render() {
|
141 | const {
|
142 | btnText,
|
143 | max,
|
144 | multiple,
|
145 | allowedFileTypes,
|
146 | maxSize,
|
147 | children,
|
148 | showFileDrop,
|
149 | } = this.props;
|
150 | const { files } = this.state;
|
151 |
|
152 | let fileAddArea;
|
153 | const text = btnText || (
|
154 | <>
|
155 | <i className="icon icon-plus-circle" title="Add File Icon" />
|
156 | {files.length === 0 ? 'Add File' : 'Add Another File Attachment'}
|
157 | </>
|
158 | );
|
159 |
|
160 | if (!max || files.length < max) {
|
161 | if (showFileDrop) {
|
162 | fileAddArea = (
|
163 | <div>
|
164 | <Dropzone
|
165 | onDrop={this.onDrop}
|
166 | multiple={multiple}
|
167 | maxSize={maxSize}
|
168 | className="file-drop"
|
169 | activeClassName="file-drop-active"
|
170 | >
|
171 | {({ getRootProps, getInputProps }) => (
|
172 | <section>
|
173 | <div {...getRootProps()}>
|
174 | <input data-testid="file-picker" {...getInputProps()} />
|
175 | <p>
|
176 | <strong>Drag and Drop</strong>
|
177 | </p>
|
178 | {text}
|
179 | </div>
|
180 | </section>
|
181 | )}
|
182 | </Dropzone>
|
183 | <FormFeedback valid={!this.error} className="d-block">
|
184 | {this.error}
|
185 | </FormFeedback>
|
186 | </div>
|
187 | );
|
188 | } else {
|
189 | fileAddArea = (
|
190 | <FilePickerBtn
|
191 | data-testid="file-picker"
|
192 | onChange={this.handleFileInputChange}
|
193 | color={files.length === 0 ? 'light' : 'link'}
|
194 | multiple={multiple}
|
195 | allowedFileTypes={allowedFileTypes}
|
196 | maxSize={maxSize}
|
197 | >
|
198 | {text}
|
199 | </FilePickerBtn>
|
200 | );
|
201 | }
|
202 | }
|
203 |
|
204 | return (
|
205 | <>
|
206 | <FileList files={files} onRemoveFile={this.removeFile}>
|
207 | {children}
|
208 | </FileList>
|
209 | {fileAddArea}
|
210 | </>
|
211 | );
|
212 | }
|
213 | }
|
214 |
|
215 | Upload.propTypes = {
|
216 | btnText: PropTypes.node,
|
217 | bucketId: PropTypes.string.isRequired,
|
218 | customerId: PropTypes.string.isRequired,
|
219 | clientId: PropTypes.string.isRequired,
|
220 | allowedFileNameCharacters: PropTypes.string,
|
221 | allowedFileTypes: PropTypes.arrayOf(PropTypes.string),
|
222 | onFileUpload: PropTypes.func,
|
223 | onFileRemove: PropTypes.func,
|
224 | maxSize: PropTypes.number,
|
225 | max: PropTypes.number,
|
226 | multiple: PropTypes.bool,
|
227 | children: PropTypes.func,
|
228 | name: PropTypes.string,
|
229 | showFileDrop: PropTypes.bool,
|
230 | };
|
231 |
|
232 | Upload.defaultProps = {
|
233 | multiple: true,
|
234 | showFileDrop: false,
|
235 | };
|
236 |
|
237 | export default Upload;
|