diff options
Diffstat (limited to 'src/components/ui/imageUpload/index.tsx')
-rw-r--r-- | src/components/ui/imageUpload/index.tsx | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/src/components/ui/imageUpload/index.tsx b/src/components/ui/imageUpload/index.tsx new file mode 100644 index 000000000..7a2ca747f --- /dev/null +++ b/src/components/ui/imageUpload/index.tsx | |||
@@ -0,0 +1,167 @@ | |||
1 | import { Component, InputHTMLAttributes } from 'react'; | ||
2 | import { observer } from 'mobx-react'; | ||
3 | import classnames from 'classnames'; | ||
4 | import Dropzone from 'react-dropzone'; | ||
5 | import { mdiDelete, mdiFileImage } from '@mdi/js'; | ||
6 | import prettyBytes from 'pretty-bytes'; | ||
7 | import { noop } from 'lodash'; | ||
8 | import { isWindows } from '../../../environment'; | ||
9 | import Icon from '../icon'; | ||
10 | import { IFormField } from '../typings/generic'; | ||
11 | |||
12 | interface IProps extends InputHTMLAttributes<HTMLInputElement>, IFormField { | ||
13 | className: string; | ||
14 | multiple: boolean; | ||
15 | textDelete: string; | ||
16 | textUpload: string; | ||
17 | textMaxFileSize: string; | ||
18 | textMaxFileSizeError: string; | ||
19 | maxSize?: number; | ||
20 | maxFiles?: number; | ||
21 | messages: any; | ||
22 | set?: (value: string) => void; | ||
23 | } | ||
24 | |||
25 | interface IState { | ||
26 | path: string | null; | ||
27 | errorState: boolean; | ||
28 | errorMessage: { message: string }; | ||
29 | } | ||
30 | |||
31 | // TODO - drag and drop image for recipe add/edit not working from 6.2.0 need to look at it | ||
32 | @observer | ||
33 | class ImageUpload extends Component<IProps, IState> { | ||
34 | constructor(props: IProps) { | ||
35 | super(props); | ||
36 | |||
37 | this.state = { | ||
38 | path: null, | ||
39 | errorState: false, | ||
40 | errorMessage: { | ||
41 | message: '', | ||
42 | }, | ||
43 | }; | ||
44 | } | ||
45 | |||
46 | onDropAccepted(acceptedFiles): void { | ||
47 | const { onDrop = noop, set = noop } = this.props; | ||
48 | this.setState({ errorState: false }); | ||
49 | |||
50 | for (const file of acceptedFiles) { | ||
51 | const imgPath: string = isWindows | ||
52 | ? file.path.replace(/\\/g, '/') | ||
53 | : file.path; | ||
54 | this.setState({ path: imgPath }); | ||
55 | onDrop(file); | ||
56 | } | ||
57 | |||
58 | set(''); | ||
59 | } | ||
60 | |||
61 | onDropRejected(rejectedFiles): void { | ||
62 | for (const file of rejectedFiles) { | ||
63 | for (const error of file.errors) { | ||
64 | if (error.code === 'file-too-large') { | ||
65 | this.setState({ errorState: true }); | ||
66 | this.setState({ | ||
67 | errorMessage: { | ||
68 | message: this.props.textMaxFileSizeError, | ||
69 | }, | ||
70 | }); | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | } | ||
75 | |||
76 | render() { | ||
77 | const { | ||
78 | className, | ||
79 | multiple = false, | ||
80 | textDelete, | ||
81 | textUpload, | ||
82 | textMaxFileSize, | ||
83 | value, | ||
84 | maxSize = Number.POSITIVE_INFINITY, | ||
85 | maxFiles = 0, | ||
86 | label = '', | ||
87 | set = noop, | ||
88 | } = this.props; | ||
89 | |||
90 | const cssClasses = classnames({ | ||
91 | 'image-upload__dropzone': true, | ||
92 | [`${className}`]: className, | ||
93 | }); | ||
94 | |||
95 | const maxSizeParse: number = | ||
96 | maxSize === undefined || maxSize === Number.POSITIVE_INFINITY | ||
97 | ? 0 | ||
98 | : maxSize; | ||
99 | |||
100 | return ( | ||
101 | <div className="image-upload-wrapper"> | ||
102 | <label className="franz-form__label" htmlFor="iconUpload"> | ||
103 | {label} | ||
104 | </label> | ||
105 | <div className="image-upload"> | ||
106 | {(value && value !== 'delete') || this.state.path ? ( | ||
107 | <> | ||
108 | <div | ||
109 | className="image-upload__preview" | ||
110 | style={{ | ||
111 | backgroundImage: `url("${this.state.path || value}")`, | ||
112 | }} | ||
113 | /> | ||
114 | <div className="image-upload__action"> | ||
115 | <button | ||
116 | type="button" | ||
117 | onClick={() => { | ||
118 | if (value) { | ||
119 | set('delete'); | ||
120 | } else { | ||
121 | this.setState({ path: null }); | ||
122 | } | ||
123 | }} | ||
124 | > | ||
125 | <Icon icon={mdiDelete} /> | ||
126 | <p>{textDelete}</p> | ||
127 | </button> | ||
128 | <div className="image-upload__action-background" /> | ||
129 | </div> | ||
130 | </> | ||
131 | ) : ( | ||
132 | <Dropzone | ||
133 | onDropAccepted={this.onDropAccepted.bind(this)} | ||
134 | onDropRejected={this.onDropRejected.bind(this)} | ||
135 | multiple={multiple} | ||
136 | accept="image/jpeg, image/png, image/svg+xml" | ||
137 | minSize={0} | ||
138 | maxSize={maxSize} | ||
139 | maxFiles={maxFiles} | ||
140 | > | ||
141 | {({ getRootProps, getInputProps }) => ( | ||
142 | <div {...getRootProps()} className={cssClasses}> | ||
143 | <Icon icon={mdiFileImage} /> | ||
144 | <p>{textUpload}</p> | ||
145 | <input {...getInputProps()} /> | ||
146 | </div> | ||
147 | )} | ||
148 | </Dropzone> | ||
149 | )} | ||
150 | </div> | ||
151 | {maxSizeParse !== 0 && ( | ||
152 | <span className="image-upload-wrapper__file-size"> | ||
153 | {textMaxFileSize}{' '} | ||
154 | {prettyBytes(maxSizeParse, { maximumFractionDigits: 1 })} | ||
155 | </span> | ||
156 | )} | ||
157 | {this.state.errorState && ( | ||
158 | <span className="image-upload-wrapper__file-size-error"> | ||
159 | {this.state.errorMessage.message} | ||
160 | </span> | ||
161 | )} | ||
162 | </div> | ||
163 | ); | ||
164 | } | ||
165 | } | ||
166 | |||
167 | export default ImageUpload; | ||