Skip to content

Commit badebd3

Browse files
committed
Dropzone component + fixes
1 parent e4725a2 commit badebd3

File tree

13 files changed

+282
-23
lines changed

13 files changed

+282
-23
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ All Fields except `<Info />` accept an `attr` property to bind them to a key in
8181
* `<Numeric />`: renders as a text field with arrows and units, useful for numeric values
8282
* `<Radio />`: renders as a button group, useful for mutually-exclusive low-cardinality enumerable values
8383
* `<Dropdown />`: renders as a dropdown menu useful for mutually-exclusive high-cardinality enumerable values
84+
* `<Dropzone/>`: renders a dropzone component to drag and drop files to load
8485
* `<ColorPicker />`: renders as a popup color-picker, useful for CSS color hex value strings
8586
* `<ColorscalePicker />`: npm module [react-colorscales](https://github.com/plotly/react-colorscales)
8687
* `<Flaglist />`: renders as a list of checkboxes, useful for `+`-joined flag lists like `data[].mode`

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"raf": "^3.4.0",
2121
"react-color": "^2.13.8",
2222
"react-colorscales": "^0.4.2",
23+
"react-dropzone": "^4.2.9",
2324
"react-plotly.js": "^2.0.0",
2425
"react-rangeslider": "^2.2.0",
2526
"react-select": "^1.0.0-rc.10",

src/components/containers/ImageAccordion.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ class ImageAccordion extends Component {
3131

3232
const key = `images[${imageIndex}]`;
3333
const value = {
34-
text: `${_('Image')} ${imageIndex}`,
34+
text: `${_('Image')} ${imageIndex + 1}`,
3535
sizex: 0.1,
3636
sizey: 0.1,
3737
x: 0.5,

src/components/containers/TraceRequiredPanel.js

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,31 +11,35 @@ class TraceRequiredPanel extends Component {
1111

1212
render() {
1313
const {localize: _, children, ...rest} = this.props;
14-
const emptyPanelMessage = {
14+
let showPanel = true;
15+
const emptyPanelMessage = {heading: '', message: ''};
16+
17+
const noTraceMessage = {
1518
heading: _("Looks like there aren't any traces defined yet."),
1619
message: _("Go to the 'Create' tab to define traces."),
1720
};
1821

19-
let showPanel = false;
22+
const conditions = [() => this.hasTrace()].concat(
23+
this.props.extraConditions ? this.props.extraConditions : []
24+
);
2025

21-
if (this.props.visible) {
22-
if (this.hasTrace()) {
23-
showPanel = true;
24-
}
26+
const messages = [noTraceMessage].concat(
27+
this.props.extraEmptyPanelMessages
28+
? this.props.extraEmptyPanelMessages
29+
: []
30+
);
2531

26-
if (this.props.extraConditions) {
27-
this.props.extraConditions.forEach((condition, index) => {
28-
if (!condition()) {
29-
showPanel = false;
30-
emptyPanelMessage.heading = this.props.extraEmptyPanelMessages[
31-
index
32-
].heading;
33-
emptyPanelMessage.message = this.props.extraEmptyPanelMessages[
34-
index
35-
].message;
36-
}
37-
});
38-
}
32+
if (this.props.visible) {
33+
conditions.forEach((condition, index) => {
34+
if (!showPanel) {
35+
return;
36+
}
37+
if (!condition()) {
38+
showPanel = false;
39+
emptyPanelMessage.heading = messages[index].heading;
40+
emptyPanelMessage.message = messages[index].message;
41+
}
42+
});
3943

4044
if (showPanel) {
4145
return <LayoutPanel {...rest}>{children}</LayoutPanel>;

src/components/fields/Dropzone.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import Drop from '../widgets/Dropzone';
2+
import Field from './Field';
3+
import PropTypes from 'prop-types';
4+
import React, {Component} from 'react';
5+
import {connectToContainer} from 'lib';
6+
7+
export class UnconnectedDropzone extends Component {
8+
render() {
9+
return (
10+
<Field {...this.props}>
11+
<Drop
12+
value={this.props.fullValue}
13+
onUpdate={this.props.updatePlot}
14+
fileType={this.props.fileType}
15+
/>
16+
</Field>
17+
);
18+
}
19+
}
20+
21+
UnconnectedDropzone.propTypes = {
22+
value: PropTypes.any,
23+
onUpdate: PropTypes.func,
24+
...Field.propTypes,
25+
};
26+
27+
export default connectToContainer(UnconnectedDropzone);

src/components/fields/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import AxesSelector from './AxesSelector';
33
import ColorPicker from './Color';
44
import ColorscalePicker from './Colorscale';
55
import Dropdown from './Dropdown';
6+
import Dropzone from './Dropzone';
67
import FontSelector from './FontSelector';
78
import Flaglist from './Flaglist';
89
import Info from './Info';
@@ -79,4 +80,5 @@ export {
7980
AxisOverlayDropdown,
8081
AxisSide,
8182
UpdateMenuButtons,
83+
Dropzone,
8284
};

src/components/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
TraceOrientation,
3939
TraceSelector,
4040
UpdateMenuButtons,
41+
Dropzone,
4142
} from './fields';
4243

4344
import {
@@ -123,4 +124,5 @@ export {
123124
TraceTypeSection,
124125
SectionHeader,
125126
UpdateMenuButtons,
127+
Dropzone,
126128
};

src/components/widgets/Dropzone.js

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import React, {Component} from 'react';
2+
import PropTypes from 'prop-types';
3+
import Drop from 'react-dropzone';
4+
import {localize} from 'lib';
5+
6+
class Dropzone extends Component {
7+
constructor(props) {
8+
super(props);
9+
const _ = props.localize;
10+
11+
this.state = {
12+
content: '',
13+
};
14+
15+
this.validFiletypes = {
16+
image: _('jpeg/jpg, svg, png, gif, bmp, webp'),
17+
};
18+
19+
this.onDrop = this.onDrop.bind(this);
20+
this.renderSuccess = this.renderSuccess.bind(this);
21+
}
22+
23+
renderSuccess(value) {
24+
const _ = this.props.localize;
25+
26+
if (this.props.fileType === 'image') {
27+
return (
28+
<div
29+
className="dropzone-container__image"
30+
style={{backgroundImage: `url(${value})`}}
31+
/>
32+
);
33+
}
34+
35+
return (
36+
<div className="dropzone-container__message">{_('File loaded!')}</div>
37+
);
38+
}
39+
40+
componentWillMount() {
41+
const _ = this.props.localize;
42+
43+
if (this.props.fileType && this.props.value && this.props.value !== '') {
44+
this.setState({content: this.renderSuccess(this.props.value)});
45+
return;
46+
}
47+
48+
this.setState({
49+
content: (
50+
<div className="dropzone-container__message">
51+
<p>
52+
{_('Drop ') +
53+
(this.props.fileType ? this.props.fileType : 'file') +
54+
_(' to upload.')}
55+
</p>
56+
57+
{this.props.fileType === 'image' ? (
58+
<p>
59+
{_('Supported formats are: ') +
60+
this.validFiletypes[this.props.fileType] +
61+
'.'}
62+
</p>
63+
) : null}
64+
</div>
65+
),
66+
});
67+
}
68+
69+
onLoad(e) {
70+
const _ = this.props.localize;
71+
const parsingError = (
72+
<div className="dropzone-container__message">
73+
<p>{_('Yikes! An error occurred while parsing this file.')}</p>
74+
<p>
75+
{this.props.fileType
76+
? _('Try again with a supported file format: ') +
77+
this.validFiletypes[this.props.fileType] +
78+
'.'
79+
: _('Try again.')}
80+
</p>
81+
</div>
82+
);
83+
84+
if (this.props.fileType === 'image') {
85+
try {
86+
this.props.onUpdate(e.target.result);
87+
this.setState({
88+
content: this.renderSuccess(e.target.result),
89+
});
90+
} catch (error) {
91+
console.warn(error); // eslint-disable-line
92+
this.setState({
93+
content: parsingError,
94+
});
95+
}
96+
}
97+
}
98+
99+
onDrop(file) {
100+
const _ = this.props.localize;
101+
const reader = new FileReader();
102+
103+
const invalidFileTypeMessage = this.props.fileType ? (
104+
<div className="dropzone-container__message">
105+
<p>
106+
{_(
107+
`Yikes! This doesn't look like a valid ${
108+
this.props.fileType
109+
} to us. `
110+
)}
111+
</p>
112+
<p>
113+
{_('Try again with a ') +
114+
this.validFiletypes[this.props.fileType] +
115+
'.'}
116+
</p>
117+
</div>
118+
) : (
119+
_('Unsupported file format!')
120+
);
121+
122+
if (file.length > 1) {
123+
this.setState({
124+
content: (
125+
<div className="dropzone-container__message">
126+
<p>{_('Yikes! You can only upload one file at a time.')}</p>
127+
<p>
128+
{_(
129+
'To upload multiple files, create multiple files and upload them individually.'
130+
)}
131+
</p>
132+
</div>
133+
),
134+
});
135+
return;
136+
}
137+
138+
this.setState({content: _('Loading...')});
139+
140+
reader.onload = e => this.onLoad(e);
141+
142+
if (this.props.fileType === 'image') {
143+
if (
144+
['.jpeg', '.jpg', '.svg', '.png', '.gif', '.bmp', '.webp'].some(ext =>
145+
file[0].name.endsWith(ext)
146+
)
147+
) {
148+
reader.readAsDataURL(file[0]);
149+
} else {
150+
this.setState({
151+
content: invalidFileTypeMessage,
152+
});
153+
}
154+
}
155+
}
156+
157+
render() {
158+
return (
159+
<Drop
160+
ref="dzone"
161+
onDrop={this.onDrop}
162+
disableClick={true}
163+
className="dropzone-container"
164+
activeClassName="dropzone-container--active"
165+
>
166+
<div className="dropzone-container__content">{this.state.content}</div>
167+
</Drop>
168+
);
169+
}
170+
}
171+
172+
Dropzone.propTypes = {
173+
localize: PropTypes.func,
174+
fileType: PropTypes.string,
175+
onUpdate: PropTypes.func,
176+
value: PropTypes.any,
177+
};
178+
179+
export default localize(Dropzone);

src/default_panels/StyleColorbarsPanel.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ const StyleColorBarsPanel = ({localize: _}) => {
3434
})}
3535
{['', 'marker.'].map(prefix => {
3636
return (
37-
<Panel showExpandCollapse={false}>
37+
<Panel showExpandCollapse={false} key={prefix + ' panel'}>
3838
<Fold name={_('Title')}>
3939
<TextEditor attr={prefix + 'colorbar.title'} />
4040

src/default_panels/StyleImagesPanel.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
33
import {
44
ImageAccordion,
55
Radio,
6-
TextEditor,
6+
Dropzone,
77
PositioningNumeric,
88
Section,
99
PositioningRef,
@@ -22,7 +22,8 @@ const StyleImagesPanel = ({localize: _}) => (
2222
{label: _('Hide'), value: false},
2323
]}
2424
/>
25-
<TextEditor attr="source" label={_('Source')} show />
25+
26+
<Dropzone attr="source" fileType="image" show />
2627

2728
<Dropdown
2829
label={_('Aspect Ratio')}
@@ -32,6 +33,7 @@ const StyleImagesPanel = ({localize: _}) => (
3233
{label: _('Fill'), value: 'fill'},
3334
{label: _('Stretch'), value: 'stretch'},
3435
]}
36+
clearable={false}
3537
/>
3638
<PositioningNumeric attr="sizex" label={_('Width')} />
3739
<PositioningNumeric attr="sizey" label={_('Height')} />

src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
ErrorBars,
3636
DataSelector,
3737
Dropdown,
38+
Dropzone,
3839
Flaglist,
3940
Fold,
4041
FontSelector,
@@ -96,6 +97,7 @@ export {
9697
ErrorBars,
9798
DataSelector,
9899
Dropdown,
100+
Dropzone,
99101
EDITOR_ACTIONS,
100102
Flaglist,
101103
Fold,

0 commit comments

Comments
 (0)