Skip to content

Commit c8bfcd6

Browse files
authored
Merge pull request #418 from plotly/dropzone-and-fixes
Dropzone component + fixes
2 parents e4725a2 + 3d092c7 commit c8bfcd6

File tree

15 files changed

+327
-41
lines changed

15 files changed

+327
-41
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",

scripts/translationKeys/combined-translation-keys.txt

Lines changed: 23 additions & 9 deletions
Large diffs are not rendered by default.

scripts/translationKeys/translation-keys.txt

Lines changed: 23 additions & 9 deletions
Large diffs are not rendered by default.

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: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
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 the ') +
53+
(this.props.fileType ? this.props.fileType : _('file')) +
54+
_(
55+
' to upload here or click to choose a file from your computer.'
56+
)}
57+
</p>
58+
59+
{this.props.fileType === 'image' ? (
60+
<p>
61+
{_('Supported formats are: ') +
62+
this.validFiletypes[this.props.fileType] +
63+
'.'}
64+
</p>
65+
) : null}
66+
</div>
67+
),
68+
});
69+
}
70+
71+
onLoad(e) {
72+
const _ = this.props.localize;
73+
const parsingError = (
74+
<div className="dropzone-container__message">
75+
<p>{_('Yikes! An error occurred while parsing this file.')}</p>
76+
<p>
77+
{this.props.fileType
78+
? _('Try again with a supported file format: ') +
79+
this.validFiletypes[this.props.fileType] +
80+
'.'
81+
: _('Try again.')}
82+
</p>
83+
</div>
84+
);
85+
86+
if (this.props.fileType === 'image') {
87+
try {
88+
this.props.onUpdate(e.target.result);
89+
this.setState({
90+
content: this.renderSuccess(e.target.result),
91+
});
92+
} catch (error) {
93+
console.warn(error); // eslint-disable-line
94+
this.setState({
95+
content: parsingError,
96+
});
97+
}
98+
}
99+
}
100+
101+
onDrop(file) {
102+
const _ = this.props.localize;
103+
const reader = new FileReader();
104+
105+
const invalidFileTypeMessage = this.props.fileType ? (
106+
<div className="dropzone-container__message">
107+
<p>
108+
{_("Yikes! This doesn't look like a valid ") +
109+
this.props.fileType +
110+
_('to us. ')}
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+
className="dropzone-container"
163+
activeClassName="dropzone-container--active"
164+
>
165+
<div className="dropzone-container__content">{this.state.content}</div>
166+
</Drop>
167+
);
168+
}
169+
}
170+
171+
Dropzone.propTypes = {
172+
localize: PropTypes.func,
173+
fileType: PropTypes.string,
174+
onUpdate: PropTypes.func,
175+
value: PropTypes.any,
176+
};
177+
178+
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)