diff --git a/superset/assets/javascripts/explore/components/controls/TextAreaControl.jsx b/superset/assets/javascripts/explore/components/controls/TextAreaControl.jsx index 04a1e87c45c45..4fe9b7716d891 100644 --- a/superset/assets/javascripts/explore/components/controls/TextAreaControl.jsx +++ b/superset/assets/javascripts/explore/components/controls/TextAreaControl.jsx @@ -1,7 +1,16 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { FormGroup, FormControl } from 'react-bootstrap'; +import { Button, FormGroup, FormControl } from 'react-bootstrap'; +import AceEditor from 'react-ace'; +import 'brace/mode/sql'; +import 'brace/mode/json'; +import 'brace/mode/html'; +import 'brace/mode/markdown'; + +import 'brace/theme/textmate'; + import ControlHeader from '../ControlHeader'; +import ModalTrigger from '../../../components/ModalTrigger'; const propTypes = { name: PropTypes.string.isRequired, @@ -9,6 +18,8 @@ const propTypes = { description: PropTypes.string, onChange: PropTypes.func, value: PropTypes.string, + height: PropTypes.number, + language: PropTypes.oneOf([null, 'json', 'html', 'sql', 'markdown']), }; const defaultProps = { @@ -16,24 +27,60 @@ const defaultProps = { description: null, onChange: () => {}, value: '', + height: 250, }; export default class TextAreaControl extends React.Component { - onChange(event) { + onControlChange(event) { this.props.onChange(event.target.value); } + onAceChange(value) { + this.props.onChange(value); + } + renderEditor(inModal = false) { + if (this.props.language) { + return ( + + ); + } + return ( + + + ); + } render() { + const controlHeader = ; return (
- - - - + {controlHeader} + {this.renderEditor()} + + Edit {this.props.language} in modal + + } + modalBody={this.renderEditor(true)} + />
); } diff --git a/superset/assets/javascripts/explore/stores/controls.jsx b/superset/assets/javascripts/explore/stores/controls.jsx index a8f9bbadf710e..db149729af8e9 100644 --- a/superset/assets/javascripts/explore/stores/controls.jsx +++ b/superset/assets/javascripts/explore/stores/controls.jsx @@ -819,8 +819,10 @@ export const controls = { markup_type: { type: 'SelectControl', label: 'Markup Type', + clearable: false, choices: formatSelectOptions(['markdown', 'html']), default: 'markdown', + validators: [v.nonEmpty], description: 'Pick your favorite markup language', }, @@ -858,6 +860,9 @@ export const controls = { type: 'TextAreaControl', label: 'Code', description: 'Put your code here', + mapStateToProps: state => ({ + language: state.controls ? state.controls.markup_type.value : null, + }), default: '', }, diff --git a/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx b/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx index aa9fc192e50a7..701e2d5151654 100644 --- a/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx +++ b/superset/assets/spec/javascripts/explore/components/TextArea_spec.jsx @@ -5,6 +5,8 @@ import sinon from 'sinon'; import { expect } from 'chai'; import { describe, it, beforeEach } from 'mocha'; import { shallow } from 'enzyme'; +import AceEditor from 'react-ace'; + import TextAreaControl from '../../../../javascripts/explore/components/controls/TextAreaControl'; const defaultProps = { @@ -15,7 +17,6 @@ const defaultProps = { describe('SelectControl', () => { let wrapper; - beforeEach(() => { wrapper = shallow(); }); @@ -29,4 +30,12 @@ describe('SelectControl', () => { select.simulate('change', { target: { value: 'x' } }); expect(defaultProps.onChange.calledWith('x')).to.be.true; }); + + it('renders a AceEditor when language is specified', () => { + const props = Object.assign({}, defaultProps); + props.language = 'markdown'; + wrapper = shallow(); + expect(wrapper.find(FormControl)).to.have.lengthOf(0); + expect(wrapper.find(AceEditor)).to.have.lengthOf(1); + }); }); diff --git a/superset/assets/stylesheets/superset.css b/superset/assets/stylesheets/superset.css index d0b16cad2885b..d4469e447c3a0 100644 --- a/superset/assets/stylesheets/superset.css +++ b/superset/assets/stylesheets/superset.css @@ -225,6 +225,13 @@ div.widget .slice_container { .editable-title input[type="button"]:focus { outline: none; -}.m-r-5 { +} +.m-r-5 { margin-right: 5px; } +.m-t-5 { + margin-top: 5px; +} +.Select-menu-outer { + z-index: 10 !important; +}