[explore] viz type selector as modal (#2787)
* [explore] viz type selector as modal * Addressing comments * Adressing comments
This commit is contained in:
parent
3a4cd3ae24
commit
66403f1876
|
|
@ -7,6 +7,7 @@ import HiddenControl from './controls/HiddenControl';
|
|||
import SelectControl from './controls/SelectControl';
|
||||
import TextAreaControl from './controls/TextAreaControl';
|
||||
import TextControl from './controls/TextControl';
|
||||
import VizTypeControl from './controls/VizTypeControl';
|
||||
|
||||
const controlMap = {
|
||||
CheckboxControl,
|
||||
|
|
@ -15,6 +16,7 @@ const controlMap = {
|
|||
SelectControl,
|
||||
TextAreaControl,
|
||||
TextControl,
|
||||
VizTypeControl,
|
||||
};
|
||||
const controlTypes = Object.keys(controlMap);
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,108 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Label, Row, Col, FormControl, Modal } from 'react-bootstrap';
|
||||
import visTypes from '../../stores/visTypes';
|
||||
import ControlHeader from '../ControlHeader';
|
||||
|
||||
const propTypes = {
|
||||
description: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
name: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func,
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
onChange: () => {},
|
||||
};
|
||||
|
||||
export default class VizTypeControl extends React.PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showModal: false,
|
||||
filter: '',
|
||||
};
|
||||
this.toggleModal = this.toggleModal.bind(this);
|
||||
this.changeSearch = this.changeSearch.bind(this);
|
||||
}
|
||||
onChange(vizType) {
|
||||
this.props.onChange(vizType);
|
||||
this.setState({ showModal: false });
|
||||
}
|
||||
toggleModal() {
|
||||
this.setState({ showModal: !this.state.showModal });
|
||||
}
|
||||
changeSearch(event) {
|
||||
this.setState({ filter: event.target.value });
|
||||
}
|
||||
renderVizType(vizType) {
|
||||
const vt = vizType;
|
||||
return (
|
||||
<div
|
||||
className={`viztype-selector-container ${vt === this.props.value ? 'selected' : ''}`}
|
||||
onClick={this.onChange.bind(this, vt)}
|
||||
>
|
||||
<img
|
||||
alt={`viz-type-${vt}`}
|
||||
width="100%"
|
||||
className={`viztype-selector ${this.props.value === vt ? 'selected' : ''}`}
|
||||
src={`/static/assets/images/viz_thumbnails/${vt}.png`}
|
||||
/>
|
||||
<div className="viztype-label">
|
||||
<strong>{visTypes[vt].label}</strong>
|
||||
</div>
|
||||
</div>);
|
||||
}
|
||||
render() {
|
||||
const filter = this.state.filter;
|
||||
const filteredVizTypes = Object.keys(visTypes)
|
||||
.filter(vt => filter.length === 0 || visTypes[vt].label.toLowerCase().includes(filter));
|
||||
|
||||
const imgPerRow = 4;
|
||||
const rows = [];
|
||||
for (let i = 0; i <= filteredVizTypes.length; i += imgPerRow) {
|
||||
rows.push(
|
||||
<Row>
|
||||
{filteredVizTypes.slice(i, i + imgPerRow).map(vt => (
|
||||
<Col md={3} key={`grid-col-${vt}`}>
|
||||
{this.renderVizType(vt)}
|
||||
</Col>
|
||||
))}
|
||||
</Row>);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<ControlHeader
|
||||
{...this.props}
|
||||
rightNode={
|
||||
<a onClick={this.toggleModal}>edit</a>
|
||||
}
|
||||
/>
|
||||
<Label onClick={this.toggleModal} style={{ cursor: 'pointer' }}>
|
||||
{visTypes[this.props.value].label}
|
||||
</Label>
|
||||
<Modal show={this.state.showModal} onHide={this.toggleModal} bsSize="lg">
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Select a visualization type</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<div>
|
||||
<FormControl
|
||||
id="formControlsText"
|
||||
type="text"
|
||||
bsSize="sm"
|
||||
value={this.state.filter}
|
||||
placeholder="Search / Filter"
|
||||
onChange={this.changeSearch}
|
||||
/>
|
||||
</div>
|
||||
{rows}
|
||||
</Modal.Body>
|
||||
</Modal>
|
||||
</div>);
|
||||
}
|
||||
}
|
||||
|
||||
VizTypeControl.propTypes = propTypes;
|
||||
VizTypeControl.defaultProps = defaultProps;
|
||||
|
|
@ -34,3 +34,28 @@
|
|||
.background-transparent {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.viztype-label {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.viztype-selector-container {
|
||||
cursor: pointer;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
padding: 5px;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
|
||||
.viztype-selector-container:hover {
|
||||
border: 1px solid #000;
|
||||
}
|
||||
|
||||
.viztype-selector-container.selected {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.viztype-selector-container.selected {
|
||||
cursor: not-allowed;
|
||||
border: 1px solid #aaa;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import { formatSelectOptionsForRange, formatSelectOptions } from '../../modules/utils';
|
||||
import visTypes from './visTypes';
|
||||
import * as v from '../validators';
|
||||
|
||||
const D3_FORMAT_DOCS = 'D3 format syntax: https://github.com/d3/d3-format';
|
||||
|
|
@ -48,15 +47,9 @@ export const controls = {
|
|||
},
|
||||
|
||||
viz_type: {
|
||||
type: 'SelectControl',
|
||||
type: 'VizTypeControl',
|
||||
label: 'Visualization Type',
|
||||
clearable: false,
|
||||
default: 'table',
|
||||
choices: Object.keys(visTypes).map(vt => [
|
||||
vt,
|
||||
visTypes[vt].label,
|
||||
`/static/assets/images/viz_thumbnails/${vt}.png`,
|
||||
]),
|
||||
description: 'The type of visualization to display',
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ const visTypes = {
|
|||
},
|
||||
|
||||
dual_line: {
|
||||
label: 'Time Series - Dual Axis Line Chart',
|
||||
label: 'Dual Axis Line Chart',
|
||||
requiresTime: true,
|
||||
controlPanelSections: [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"test": "mocha --require ignore-styles --compilers js:babel-core/register --require spec/helpers/browser.js --recursive spec/**/*_spec.*",
|
||||
"cover": "babel-node ./node_modules/.bin/istanbul cover _mocha -- --require spec/helpers/browser.js --recursive spec/**/*_spec.*",
|
||||
"cover": "babel-node node_modules/.bin/babel-istanbul cover _mocha -- --require spec/helpers/browser.js --recursive spec/**/*_spec.*",
|
||||
"dev": "NODE_ENV=dev webpack --watch --colors --progress --debug --output-pathinfo --devtool inline-source-map",
|
||||
"prod": "NODE_ENV=production node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js -p --colors --progress",
|
||||
"build": "NODE_ENV=production webpack --colors --progress",
|
||||
|
|
@ -59,6 +59,7 @@
|
|||
"immutability-helper": "^2.0.0",
|
||||
"immutable": "^3.8.1",
|
||||
"jquery": "^3.2.1",
|
||||
"jsdom": "9.12.0",
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"mapbox-gl": "^0.26.0",
|
||||
"moment": "^2.14.1",
|
||||
|
|
@ -98,9 +99,10 @@
|
|||
"devDependencies": {
|
||||
"babel-cli": "^6.14.0",
|
||||
"babel-core": "^6.10.4",
|
||||
"babel-istanbul": "^0.12.2",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-plugin-css-modules-transform": "^1.1.0",
|
||||
"babel-polyfill": "^6.14.0",
|
||||
"babel-polyfill": "^6.23.0",
|
||||
"babel-preset-airbnb": "^2.1.1",
|
||||
"babel-preset-es2015": "^6.14.0",
|
||||
"babel-preset-react": "^6.11.1",
|
||||
|
|
@ -119,7 +121,7 @@
|
|||
"ignore-styles": "^5.0.1",
|
||||
"imports-loader": "^0.7.1",
|
||||
"istanbul": "^1.0.0-alpha",
|
||||
"jsdom": "^9.12.0",
|
||||
"jsdom": "9.12.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^2.6.1",
|
||||
"less-loader": "^4.0.3",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import React from 'react';
|
||||
import sinon from 'sinon';
|
||||
import { expect } from 'chai';
|
||||
import { describe, it, beforeEach } from 'mocha';
|
||||
import { shallow } from 'enzyme';
|
||||
import { Modal } from 'react-bootstrap';
|
||||
import VizTypeControl from '../../../../javascripts/explore/components/controls/VizTypeControl';
|
||||
|
||||
const defaultProps = {
|
||||
name: 'viz_type',
|
||||
label: 'Visualization Type',
|
||||
value: 'table',
|
||||
onChange: sinon.spy(),
|
||||
};
|
||||
|
||||
describe('VizTypeControl', () => {
|
||||
let wrapper;
|
||||
|
||||
beforeEach(() => {
|
||||
wrapper = shallow(<VizTypeControl {...defaultProps} />);
|
||||
});
|
||||
|
||||
it('renders a Modal', () => {
|
||||
expect(wrapper.find(Modal)).to.have.lengthOf(1);
|
||||
});
|
||||
|
||||
it('calls onChange when toggled', () => {
|
||||
const select = wrapper.find('.viztype-selector-container').first();
|
||||
select.simulate('click');
|
||||
expect(defaultProps.onChange.called).to.equal(true);
|
||||
});
|
||||
it('filters images based on text input', () => {
|
||||
expect(wrapper.find('img').length).to.be.above(20);
|
||||
wrapper.setState({ filter: 'time' });
|
||||
expect(wrapper.find('img').length).to.be.below(10);
|
||||
});
|
||||
});
|
||||
|
|
@ -422,3 +422,8 @@ hr {
|
|||
}
|
||||
}
|
||||
.space-loop(6);
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue