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;
+}