diff --git a/common/lib/expression_form_handlers.js b/common/lib/expression_form_handlers.js new file mode 100644 index 0000000000000..2c6226555bfe2 --- /dev/null +++ b/common/lib/expression_form_handlers.js @@ -0,0 +1,10 @@ +export class ExpressionFormHandlers { + constructor() { + this.destroy = () => {}; + this.done = () => {}; + } + + onDestroy(fn) { + this.destroy = fn; + } +} diff --git a/public/components/arg_form/arg_form.js b/public/components/arg_form/arg_form.js index b53c4c36df70f..2c9a2cd5cd6ac 100644 --- a/public/components/arg_form/arg_form.js +++ b/public/components/arg_form/arg_form.js @@ -1,46 +1,13 @@ -import React, { createElement } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { ErrorBoundary } from '../enhance/error_boundary'; -import { RenderError } from '../../../common/lib/errors'; import { ArgLabel } from './arg_label'; import { ArgSimpleForm } from './arg_simple_form'; +import { ArgTemplateForm } from './arg_template_form'; import { SimpleFailure } from './simple_failure'; import { AdvancedFailure } from './advanced_failure'; import './arg_form.less'; -const getTemplates = ( - err, - { argTypeInstance, label, setLabel, resetErrorState, templateProps } -) => { - const { template, simpleTemplate } = argTypeInstance.argType; - const argumentProps = { - ...templateProps, - defaultValue: argTypeInstance.default, - // Provide templates with a renderError method, and wrap the error in a known error type - // to stop Kibana's window.error from being called - // see window_error_handler.js for details - renderError: msg => { - throw new RenderError(msg || 'Render failed'); - }, - setLabel, - resetErrorState, - label, - }; - - // if provided any kind of error, render the fallback failure forms - if (err) { - return { - simpleForm: createElement(SimpleFailure, argumentProps), - advancedForm: createElement(AdvancedFailure, argumentProps), - }; - } - - return { - simpleForm: simpleTemplate && createElement(simpleTemplate, argumentProps), - advancedForm: template && createElement(template, argumentProps), - }; -}; - // This is what is being generated by render() from the Arg class. It is called in FunctionForm export const ArgForm = props => { const { @@ -52,27 +19,53 @@ export const ArgForm = props => { expand, setExpand, onValueRemove, + workpad, + renderError, + setRenderError, } = props; return ( {({ error, resetErrorState }) => { - const { simpleForm, advancedForm } = getTemplates(error, { - argTypeInstance, - label, + const { template, simpleTemplate } = argTypeInstance.argType; + + const hasError = Boolean(error) || renderError; + + const argumentProps = { + ...templateProps, + defaultValue: argTypeInstance.default, + + renderError: () => { + // TODO: don't do this + // It's an ugly hack to avoid React's render cycle and ensure the error happens on the next tick + // This is important; Otherwise we end up updating state in the middle of a render cycle + Promise.resolve().then(() => { + // Provide templates with a renderError method, and wrap the error in a known error type + // to stop Kibana's window.error from being called + // see window_error_handler.js for details, + setRenderError(true); + }); + }, + error: hasError, setLabel, - resetErrorState, - templateProps, - }); - const expandableLabel = Boolean(simpleForm && advancedForm); + resetErrorState: () => { + resetErrorState(); + setRenderError(false); + }, + label, + workpad, + }; + + const expandableLabel = hasError || Boolean(simpleTemplate && template); + const showAdvanced = hasError ? expand : template && (expand || !simpleTemplate); return (
@@ -82,15 +75,27 @@ export const ArgForm = props => { valueMissing={valueMissing} onRemove={onValueRemove} > - {simpleForm} +
- {advancedForm} +
); @@ -100,6 +105,7 @@ export const ArgForm = props => { }; ArgForm.propTypes = { + workpad: PropTypes.object.isRequired, argTypeInstance: PropTypes.object, templateProps: PropTypes.object, valueMissing: PropTypes.bool, @@ -108,4 +114,6 @@ ArgForm.propTypes = { expand: PropTypes.bool, setExpand: PropTypes.func, onValueRemove: PropTypes.func, + renderError: PropTypes.bool.isRequired, + setRenderError: PropTypes.func.isRequired, }; diff --git a/public/components/arg_form/arg_template_form.js b/public/components/arg_form/arg_template_form.js new file mode 100644 index 0000000000000..2fb28c6c7d6fc --- /dev/null +++ b/public/components/arg_form/arg_template_form.js @@ -0,0 +1,80 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { compose, withPropsOnChange, withProps } from 'recompose'; +import { RenderToDom } from '../render_to_dom'; +import { ExpressionFormHandlers } from '../../../common/lib/expression_form_handlers'; +import './arg_form.less'; + +class ArgTemplateFormComponent extends React.Component { + static propTypes = { + template: PropTypes.func, + argumentProps: PropTypes.shape({ + valueMissing: PropTypes.bool, + label: PropTypes.string, + setLabel: PropTypes.func.isRequired, + expand: PropTypes.bool, + setExpand: PropTypes.func, + onValueRemove: PropTypes.func, + resetErrorState: PropTypes.func.isRequired, + }), + handlers: PropTypes.object.isRequired, + error: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]).isRequired, + errorTemplate: PropTypes.oneOfType([PropTypes.element, PropTypes.func]).isRequired, + }; + + static domNode = null; + + componentWillUpdate(prevProps) { + //see if error state changed + if (this.props.error !== prevProps.error) this.props.handlers.destroy(); + } + componentDidUpdate() { + if (this.props.error) return this.renderErrorTemplate(); + this.renderTemplate(this.domNode); + } + + componentWillUnmount() { + this.props.handlers.destroy(); + } + + renderTemplate = domNode => { + const { template, argumentProps, handlers } = this.props; + if (template) { + return template(domNode, argumentProps, handlers); + } + }; + + renderErrorTemplate = () => { + const { errorTemplate, argumentProps } = this.props; + return React.createElement(errorTemplate, argumentProps); + }; + + render() { + const { template, error } = this.props; + + if (error) return this.renderErrorTemplate(); + + if (!template) return null; + + return ( + { + this.domNode = domNode; + this.renderTemplate(domNode); + }} + /> + ); + } +} + +export const ArgTemplateForm = compose( + withPropsOnChange( + () => false, + () => ({ + expressionFormHandlers: new ExpressionFormHandlers(), + }) + ), + withProps(({ handlers, expressionFormHandlers }) => ({ + handlers: Object.assign(expressionFormHandlers, handlers), + })) +)(ArgTemplateFormComponent); diff --git a/public/components/arg_form/index.js b/public/components/arg_form/index.js index 9e8d69129170d..25c7799e3f61a 100644 --- a/public/components/arg_form/index.js +++ b/public/components/arg_form/index.js @@ -1,12 +1,22 @@ import PropTypes from 'prop-types'; -import { compose, withState } from 'recompose'; +import { connect } from 'react-redux'; +import { compose, withState, lifecycle } from 'recompose'; +import { getWorkpadInfo } from '../../state/selectors/workpad'; import { ArgForm as Component } from './arg_form'; export const ArgForm = compose( withState('label', 'setLabel', ({ label, argTypeInstance }) => { return label || argTypeInstance.displayName || argTypeInstance.name; }), - withState('expand', 'setExpand', ({ argTypeInstance }) => argTypeInstance.expanded) + withState('expand', 'setExpand', ({ argTypeInstance }) => argTypeInstance.expanded), + withState('renderError', 'setRenderError', false), + lifecycle({ + componentWillUpdate(prevProps) { + if (prevProps.templateProps.argValue !== this.props.templateProps.argValue) + this.props.setRenderError(false); + }, + }), + connect(state => ({ workpad: getWorkpadInfo(state) })) )(Component); ArgForm.propTypes = { diff --git a/public/components/color_manager/color_manager.js b/public/components/color_manager/color_manager.js index d9a6c38c8d92b..d16ab3066bf5d 100644 --- a/public/components/color_manager/color_manager.js +++ b/public/components/color_manager/color_manager.js @@ -19,17 +19,21 @@ export const ColorManager = ({ value, addColor, removeColor, onChange }) => ( onChange={e => onChange(e.target.value)} /> - addColor(value)} className="canvas__color-manager--add fa fa-plus-circle" /> - removeColor(value)} - className="canvas__color-manager--remove fa fa-times-circle" - /> + {addColor && ( + addColor(value)} className="canvas__color-manager--add fa fa-plus-circle" /> + )} + {removeColor && ( + removeColor(value)} + className="canvas__color-manager--remove fa fa-times-circle" + /> + )} ); ColorManager.propTypes = { value: PropTypes.string, - addColor: PropTypes.func.isRequired, - removeColor: PropTypes.func.isRequired, + addColor: PropTypes.func, + removeColor: PropTypes.func, onChange: PropTypes.func.isRequired, }; diff --git a/public/components/color_picker/index.js b/public/components/color_picker/index.js index 013491ef06a8d..0a3b3b13d2080 100644 --- a/public/components/color_picker/index.js +++ b/public/components/color_picker/index.js @@ -1,16 +1,5 @@ -import { connect } from 'react-redux'; -import { getWorkpadColors } from '../../state/selectors/workpad'; -import { addColor, removeColor } from '../../state/actions/workpad'; +import { pure } from 'recompose'; import { ColorPicker as Component } from './color_picker'; -const mapStateToProps = state => ({ - colors: getWorkpadColors(state), -}); - -const mapDispatchToProps = { - addColor, - removeColor, -}; - -export const ColorPicker = connect(mapStateToProps, mapDispatchToProps)(Component); +export const ColorPicker = pure(Component); diff --git a/public/components/color_picker_mini/color_picker_mini.js b/public/components/color_picker_mini/color_picker_mini.js index ce606b0e26341..8a50bc64abbfb 100644 --- a/public/components/color_picker_mini/color_picker_mini.js +++ b/public/components/color_picker_mini/color_picker_mini.js @@ -4,11 +4,16 @@ import { Popover, OverlayTrigger } from 'react-bootstrap'; import { ColorPicker } from '../color_picker'; import { ColorDot } from '../color_dot'; import './color_picker_mini.less'; +import { WorkpadColorPicker } from '../workpad_color_picker/'; -export const ColorPickerMini = ({ onChange, value, placement }) => { +export const ColorPickerMini = ({ onChange, value, placement, colors }) => { const picker = ( - + {colors ? ( + + ) : ( + + )} ); @@ -24,6 +29,7 @@ export const ColorPickerMini = ({ onChange, value, placement }) => { }; ColorPickerMini.propTypes = { + colors: PropTypes.array, value: PropTypes.string, onChange: PropTypes.func, placement: PropTypes.string, diff --git a/public/components/render_to_dom/render_to_dom.js b/public/components/render_to_dom/render_to_dom.js index bfaf66e452b41..132480d6a8a44 100644 --- a/public/components/render_to_dom/render_to_dom.js +++ b/public/components/render_to_dom/render_to_dom.js @@ -19,13 +19,14 @@ export class RenderToDom extends React.Component { } render() { + const { domNode, setDomNode, style } = this.props; const linkRef = refNode => { - if (!this.props.domNode && refNode) { + if (!domNode && refNode) { // Initialize the domNode property. This should only happen once, even if config changes. - this.props.setDomNode(refNode); + setDomNode(refNode); } }; - return
; + return
; } } diff --git a/public/components/text_style_picker/text_style_picker.js b/public/components/text_style_picker/text_style_picker.js index c13c43c171554..219d27123b688 100644 --- a/public/components/text_style_picker/text_style_picker.js +++ b/public/components/text_style_picker/text_style_picker.js @@ -15,6 +15,7 @@ export const TextStylePicker = ({ underline, italic, onChange, + colors, }) => { function doChange(propName, value) { onChange({ @@ -48,7 +49,11 @@ export const TextStylePicker = ({ doChange('family', value)} /> - doChange('color', value)} /> + doChange('color', value)} + colors={colors} + />
@@ -68,4 +68,5 @@ BorderForm.propTypes = { radius: PropTypes.string, onChange: PropTypes.func.isRequired, className: PropTypes.string, + colors: PropTypes.array.isRequired, }; diff --git a/public/expression_types/arg_types/container_style/container_style.js b/public/expression_types/arg_types/container_style/container_style.js index e0f4e1a6f79e7..5f1593994351e 100644 --- a/public/expression_types/arg_types/container_style/container_style.js +++ b/public/expression_types/arg_types/container_style/container_style.js @@ -1,8 +1,9 @@ import { withHandlers } from 'recompose'; import { get } from 'lodash'; import { set } from 'object-path-immutable'; -import { simpleTemplate } from './simple_template'; -import { extendedTemplate } from './extended_template'; +import { templateFromReactComponent } from '../../../lib/template_from_react_component'; +import { SimpleTemplate } from './simple_template'; +import { ExtendedTemplate } from './extended_template'; import './container_style.less'; @@ -23,6 +24,6 @@ export const containerStyle = () => ({ name: 'containerStyle', displayName: 'Image Upload', help: 'Select or upload an image', - template: wrap(extendedTemplate), - simpleTemplate: wrap(simpleTemplate), + simpleTemplate: templateFromReactComponent(wrap(SimpleTemplate)), + template: templateFromReactComponent(wrap(ExtendedTemplate)), }); diff --git a/public/expression_types/arg_types/container_style/extended_template.js b/public/expression_types/arg_types/container_style/extended_template.js index fbf7baab084ff..aeeef593befc6 100644 --- a/public/expression_types/arg_types/container_style/extended_template.js +++ b/public/expression_types/arg_types/container_style/extended_template.js @@ -5,7 +5,7 @@ import { AppearanceForm } from './appearance_form'; import './container_style.less'; -export const extendedTemplate = ({ getArgValue, setArgValue }) => ( +export const ExtendedTemplate = ({ getArgValue, setArgValue, workpad }) => (
@@ -23,16 +23,20 @@ export const extendedTemplate = ({ getArgValue, setArgValue }) => ( value={getArgValue('border', '')} radius={getArgValue('borderRadius')} onChange={setArgValue} + colors={workpad.colors} />
); -extendedTemplate.displayName = 'ContainerStyleArgExtendedInput'; +ExtendedTemplate.displayName = 'ContainerStyleArgExtendedInput'; -extendedTemplate.propTypes = { +ExtendedTemplate.propTypes = { onValueChange: PropTypes.func.isRequired, argValue: PropTypes.any.isRequired, getArgValue: PropTypes.func.isRequired, setArgValue: PropTypes.func.isRequired, + workpad: PropTypes.shape({ + colors: PropTypes.array.isRequired, + }).isRequired, }; diff --git a/public/expression_types/arg_types/container_style/simple_template.js b/public/expression_types/arg_types/container_style/simple_template.js index f91ec0d9e4fb9..a6d7bb88e2dff 100644 --- a/public/expression_types/arg_types/container_style/simple_template.js +++ b/public/expression_types/arg_types/container_style/simple_template.js @@ -2,20 +2,24 @@ import React from 'react'; import PropTypes from 'prop-types'; import { ColorPickerMini } from '../../../components/color_picker_mini'; -export const simpleTemplate = ({ getArgValue, setArgValue }) => ( +export const SimpleTemplate = ({ getArgValue, setArgValue, workpad }) => (
setArgValue('backgroundColor', color)} + colors={workpad.colors} />
); -simpleTemplate.displayName = 'ContainerStyleArgSimpleInput'; +SimpleTemplate.displayName = 'ContainerStyleArgSimpleInput'; -simpleTemplate.propTypes = { +SimpleTemplate.propTypes = { onValueChange: PropTypes.func.isRequired, argValue: PropTypes.any.isRequired, getArgValue: PropTypes.func.isRequired, setArgValue: PropTypes.func.isRequired, + workpad: PropTypes.shape({ + colors: PropTypes.array.isRequired, + }), }; diff --git a/public/expression_types/arg_types/datacolumn/datacolumn.js b/public/expression_types/arg_types/datacolumn/datacolumn.js index 975dbf8e3580e..ea769caaaa45e 100644 --- a/public/expression_types/arg_types/datacolumn/datacolumn.js +++ b/public/expression_types/arg_types/datacolumn/datacolumn.js @@ -5,6 +5,7 @@ import { FormControl } from 'react-bootstrap'; import { sortBy } from 'lodash'; import { createStatefulPropHoc } from '../../../components/enhance/stateful_prop'; import { getType } from '../../../../common/lib/get_type'; +import { templateFromReactComponent } from '../../../lib/template_from_react_component'; import { SimpleMathFunction } from './simple_math_function'; import { getFormObject } from './get_form_object'; import './datacolumn.less'; @@ -19,8 +20,10 @@ const DatacolumnArgInput = ({ setMathFunction, renderError, }) => { - if (mathValue.error) return renderError(); - + if (mathValue.error) { + renderError(); + return null; + } const inputRefs = {}; const valueNotSet = val => !val || val.length === 0; const updateFunctionValue = () => { @@ -45,7 +48,6 @@ const DatacolumnArgInput = ({ }; const column = columns.map(col => col.name).find(colName => colName === mathValue.column) || ''; - return (
({ mathValue: (argValue => { if (getType(argValue) !== 'string') return { error: 'argValue is not a string type' }; @@ -101,7 +103,7 @@ const simpleTemplate = compose( }) )(DatacolumnArgInput); -simpleTemplate.propTypes = { +EnhancedDatacolumnArgInput.propTypes = { argValue: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, columns: PropTypes.array.isRequired, }; @@ -111,5 +113,5 @@ export const datacolumn = () => ({ displayName: 'Column', help: 'Select the data column', default: '""', - simpleTemplate, + simpleTemplate: templateFromReactComponent(EnhancedDatacolumnArgInput), }); diff --git a/public/expression_types/arg_types/font/extended_template.js b/public/expression_types/arg_types/font/extended_template.js index a0d33e3b83212..913a3ed31dc18 100644 --- a/public/expression_types/arg_types/font/extended_template.js +++ b/public/expression_types/arg_types/font/extended_template.js @@ -3,8 +3,8 @@ import PropTypes from 'prop-types'; import { get, mapValues, set } from 'lodash'; import { TextStylePicker } from '../../../components/text_style_picker'; -export const extendedTemplate = props => { - const { onValueChange, argValue } = props; +export const ExtendedTemplate = props => { + const { onValueChange, argValue, workpad } = props; const chain = get(argValue, 'chain.0', {}); const chainArgs = get(chain, 'arguments', {}); @@ -28,17 +28,20 @@ export const extendedTemplate = props => { underline={spec.underline || false} italic={spec.italic || false} onChange={handleChange} + colors={workpad.colors} />
); }; -extendedTemplate.displayName = 'FontArgExtendedInput'; - -extendedTemplate.propTypes = { +ExtendedTemplate.propTypes = { onValueChange: PropTypes.func.isRequired, argValue: PropTypes.any.isRequired, typeInstance: PropTypes.object, - labels: PropTypes.array.isRequired, renderError: PropTypes.func, + workpad: PropTypes.shape({ + colors: PropTypes.array.isRequired, + }).isRequired, }; + +ExtendedTemplate.displayName = 'FontArgExtendedInput'; diff --git a/public/expression_types/arg_types/font/font.js b/public/expression_types/arg_types/font/font.js index f5584bec48903..d4b9aa30115ac 100644 --- a/public/expression_types/arg_types/font/font.js +++ b/public/expression_types/arg_types/font/font.js @@ -1,11 +1,12 @@ -import { extendedTemplate } from './extended_template'; +import { templateFromReactComponent } from '../../../lib/template_from_react_component'; +import { ExtendedTemplate } from './extended_template'; import './font.less'; export const font = () => ({ name: 'font', displayName: 'Text Settings', help: 'Set the font, size and color', - template: extendedTemplate, + template: templateFromReactComponent(ExtendedTemplate), default: '{font size=12 family="\'Open Sans\', Helvetica, Arial, sans-serif" color="#000000" align=left}', }); diff --git a/public/expression_types/arg_types/font/simple_template.js b/public/expression_types/arg_types/font/simple_template.js index 77bf38ef1a233..6aa18edef39af 100644 --- a/public/expression_types/arg_types/font/simple_template.js +++ b/public/expression_types/arg_types/font/simple_template.js @@ -2,15 +2,15 @@ import React from 'react'; import PropTypes from 'prop-types'; //import { get } from 'lodash'; -export const simpleTemplate = (/*props*/) => { +export const SimpleTemplate = (/*props*/) => { // const { argValue, onValueChange, labels } = props; return
Oh look, free beer
; }; -simpleTemplate.displayName = 'FontArgSimpleInput'; +SimpleTemplate.displayName = 'FontArgSimpleInput'; -simpleTemplate.propTypes = { +SimpleTemplate.propTypes = { onValueChange: PropTypes.func.isRequired, argValue: PropTypes.any.isRequired, renderError: PropTypes.func, diff --git a/public/expression_types/arg_types/image_upload.js b/public/expression_types/arg_types/image_upload.js index f16f52095d7db..7ca648e0a8eea 100644 --- a/public/expression_types/arg_types/image_upload.js +++ b/public/expression_types/arg_types/image_upload.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { FormGroup, ControlLabel } from 'react-bootstrap'; import { withState } from 'recompose'; import { encode } from '../../../common/lib/dataurl'; +import { templateFromReactComponent } from '../../lib/template_from_react_component'; const ImageUploadArgInput = ({ typeInstance, @@ -57,9 +58,11 @@ ImageUploadArgInput.propTypes = { isLoading: PropTypes.bool, }; +const EnhancedImageUpload = withState('isLoading', 'setLoading', false)(ImageUploadArgInput); + export const imageUpload = () => ({ name: 'imageUpload', displayName: 'Image Upload', help: 'Select or upload an image', - template: withState('isLoading', 'setLoading', false)(ImageUploadArgInput), + template: templateFromReactComponent(EnhancedImageUpload), }); diff --git a/public/expression_types/arg_types/number.js b/public/expression_types/arg_types/number.js index 174e074985ab1..3c5d906a0dc01 100644 --- a/public/expression_types/arg_types/number.js +++ b/public/expression_types/arg_types/number.js @@ -4,6 +4,7 @@ import { compose, withProps } from 'recompose'; import { Form, FormGroup, FormControl, Button } from 'react-bootstrap'; import { get } from 'lodash'; import { createStatefulPropHoc } from '../../components/enhance/stateful_prop'; +import { templateFromReactComponent } from '../../lib/template_from_react_component'; // This is basically a direct copy of the string input, but with some Number() goodness maybe you think that's cheating and it should be // abstracted. If you can think of a 3rd or 4th usage for that abstraction, cool, do it, just don't make it more confusing. Copying is the @@ -29,13 +30,12 @@ const NumberArgInput = ({ updateValue, value, confirm, commit }) => ( NumberArgInput.propTypes = { updateValue: PropTypes.func.isRequired, - value: PropTypes.string.isRequired, + value: PropTypes.number.isRequired, confirm: PropTypes.string, commit: PropTypes.func.isRequired, }; -// TODO: Here's where you would put that max/min support stuff -const template = compose( +const EnhancedNumberArgInput = compose( withProps(({ onValueChange, typeInstance, argValue }) => ({ confirm: get(typeInstance, 'options.confirm'), commit: onValueChange, @@ -44,7 +44,7 @@ const template = compose( createStatefulPropHoc('value') )(NumberArgInput); -template.propTypes = { +EnhancedNumberArgInput.propTypes = { argValue: PropTypes.any.isRequired, onValueChange: PropTypes.func.isRequired, typeInstance: PropTypes.object.isRequired, @@ -54,5 +54,5 @@ export const number = () => ({ name: 'number', displayName: 'number', help: 'Input a number', - simpleTemplate: template, + simpleTemplate: templateFromReactComponent(EnhancedNumberArgInput), }); diff --git a/public/expression_types/arg_types/palette.js b/public/expression_types/arg_types/palette.js index 478a9e619f8a5..dc2d8d9337c7a 100644 --- a/public/expression_types/arg_types/palette.js +++ b/public/expression_types/arg_types/palette.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { get } from 'lodash'; import { PalettePicker } from '../../components/palette_picker'; import { getType } from '../../../common/lib/get_type'; +import { templateFromReactComponent } from '../../lib/template_from_react_component'; const PaletteArgInput = ({ onValueChange, argValue, renderError }) => { // Why is this neccesary? Does the dialog really need to know what parameter it is setting? @@ -62,5 +63,5 @@ export const palette = () => ({ help: 'Color palette selector', default: '{palette #882E72 #B178A6 #D6C1DE #1965B0 #5289C7 #7BAFDE #4EB265 #90C987 #CAE0AB #F7EE55 #F6C141 #F1932D #E8601C #DC050C}', - simpleTemplate: PaletteArgInput, + simpleTemplate: templateFromReactComponent(PaletteArgInput), }); diff --git a/public/expression_types/arg_types/select.js b/public/expression_types/arg_types/select.js index 7b1a2aa981128..7706557b7bf02 100644 --- a/public/expression_types/arg_types/select.js +++ b/public/expression_types/arg_types/select.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { FormControl } from 'react-bootstrap'; +import { templateFromReactComponent } from '../../lib/template_from_react_component'; const getArgValueString = argValue => { if (typeof argValue === 'object' && argValue !== null) return argValue.value; @@ -47,5 +48,5 @@ export const select = () => ({ name: 'select', displayName: 'Select', help: 'Select from multiple options in a drop down', - simpleTemplate: SelectArgInput, + simpleTemplate: templateFromReactComponent(SelectArgInput), }); diff --git a/public/expression_types/arg_types/series_style/extended_template.js b/public/expression_types/arg_types/series_style/extended_template.js index 3ebb45155c705..4a7dce3dcae3b 100644 --- a/public/expression_types/arg_types/series_style/extended_template.js +++ b/public/expression_types/arg_types/series_style/extended_template.js @@ -5,7 +5,7 @@ import { get } from 'lodash'; import { set, del } from 'object-path-immutable'; import { LabeledInput } from '../../../components/labeled_input'; -export const extendedTemplate = props => { +export const ExtendedTemplate = props => { const { typeInstance, onValueChange, labels, argValue } = props; const chain = get(argValue, 'chain.0', {}); const chainArgs = get(chain, 'arguments', {}); @@ -87,9 +87,9 @@ export const extendedTemplate = props => { ); }; -extendedTemplate.displayName = 'SeriesStyleArgAdvancedInput'; +ExtendedTemplate.displayName = 'SeriesStyleArgAdvancedInput'; -extendedTemplate.propTypes = { +ExtendedTemplate.propTypes = { onValueChange: PropTypes.func.isRequired, argValue: PropTypes.any.isRequired, typeInstance: PropTypes.object, diff --git a/public/expression_types/arg_types/series_style/series_style.js b/public/expression_types/arg_types/series_style/series_style.js index 9fffa7c304a94..70e7271034a08 100644 --- a/public/expression_types/arg_types/series_style/series_style.js +++ b/public/expression_types/arg_types/series_style/series_style.js @@ -1,11 +1,12 @@ import PropTypes from 'prop-types'; import { lifecycle } from 'recompose'; import { get } from 'lodash'; -import { simpleTemplate } from './simple_template'; -import { extendedTemplate } from './extended_template'; +import { templateFromReactComponent } from '../../../lib/template_from_react_component'; +import { SimpleTemplate } from './simple_template'; +import { ExtendedTemplate } from './extended_template'; import './series_style.less'; -const wrappedTemplate = lifecycle({ +const EnhancedExtendedTemplate = lifecycle({ formatLabel(label) { if (typeof label !== 'string') this.props.renderError(); return `Style: ${label}`; @@ -20,9 +21,9 @@ const wrappedTemplate = lifecycle({ this.props.setLabel(this.formatLabel(newLabel)); } }, -})(extendedTemplate); +})(ExtendedTemplate); -wrappedTemplate.propTypes = { +EnhancedExtendedTemplate.propTypes = { argValue: PropTypes.any.isRequired, setLabel: PropTypes.func.isRequired, label: PropTypes.string, @@ -32,6 +33,6 @@ export const seriesStyle = () => ({ name: 'seriesStyle', displayName: 'Series Style', help: 'Set the style for a selected named series', - template: wrappedTemplate, - simpleTemplate, + template: templateFromReactComponent(EnhancedExtendedTemplate), + simpleTemplate: templateFromReactComponent(SimpleTemplate), }); diff --git a/public/expression_types/arg_types/series_style/simple_template.js b/public/expression_types/arg_types/series_style/simple_template.js index 59446c69bb574..b30cc335a9405 100644 --- a/public/expression_types/arg_types/series_style/simple_template.js +++ b/public/expression_types/arg_types/series_style/simple_template.js @@ -5,8 +5,8 @@ import { set, del } from 'object-path-immutable'; import { ColorPickerMini } from '../../../components/color_picker_mini'; import { Tooltip } from '../../../components/tooltip'; -export const simpleTemplate = props => { - const { argValue, onValueChange, labels } = props; +export const SimpleTemplate = props => { + const { argValue, onValueChange, labels, workpad } = props; const chain = get(argValue, 'chain.0', {}); const chainArgs = get(chain, 'arguments', {}); const color = get(chainArgs, 'color.0', ''); @@ -36,7 +36,11 @@ export const simpleTemplate = props => {
handlePlain('color', '')} className="fa fa-times-circle clickable" />
- handlePlain('color', val)} /> + handlePlain('color', val)} + colors={workpad.colors} + />
)} @@ -51,10 +55,13 @@ export const simpleTemplate = props => { ); }; -simpleTemplate.displayName = 'SeriesStyleArgSimpleInput'; +SimpleTemplate.displayName = 'SeriesStyleArgSimpleInput'; -simpleTemplate.propTypes = { +SimpleTemplate.propTypes = { onValueChange: PropTypes.func.isRequired, argValue: PropTypes.any.isRequired, labels: PropTypes.array, + workpad: PropTypes.shape({ + colors: PropTypes.array.isRequired, + }).isRequired, }; diff --git a/public/expression_types/arg_types/shape.js b/public/expression_types/arg_types/shape.js index 3fe13f8d578ed..acefb57a3fe00 100644 --- a/public/expression_types/arg_types/shape.js +++ b/public/expression_types/arg_types/shape.js @@ -1,6 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { ShapePicker } from '../../components/shape_picker/shape_picker.js'; +import { templateFromReactComponent } from '../../lib/template_from_react_component.js'; const ShapeArgInput = ({ argValue, onValueChange }) => { const onChange = val => onValueChange(val); @@ -22,5 +23,5 @@ export const shape = () => ({ displayName: 'Shape', help: 'Shape selector', default: 'circle', - simpleTemplate: ShapeArgInput, + simpleTemplate: templateFromReactComponent(ShapeArgInput), }); diff --git a/public/expression_types/arg_types/string.js b/public/expression_types/arg_types/string.js index 08a3f4f68a4dd..a23505fe881e6 100644 --- a/public/expression_types/arg_types/string.js +++ b/public/expression_types/arg_types/string.js @@ -4,6 +4,7 @@ import { compose, withProps } from 'recompose'; import { Form, FormGroup, FormControl, Button } from 'react-bootstrap'; import { get } from 'lodash'; import { createStatefulPropHoc } from '../../components/enhance/stateful_prop'; +import { templateFromReactComponent } from '../../lib/template_from_react_component'; const StringArgInput = ({ updateValue, value, confirm, commit }) => (
@@ -31,7 +32,7 @@ StringArgInput.propTypes = { commit: PropTypes.func.isRequired, }; -const template = compose( +const EnhancedStringArgInput = compose( withProps(({ onValueChange, typeInstance, argValue }) => ({ confirm: get(typeInstance, 'options.confirm'), commit: onValueChange, @@ -40,7 +41,7 @@ const template = compose( createStatefulPropHoc('value') )(StringArgInput); -template.propTypes = { +EnhancedStringArgInput.propTypes = { argValue: PropTypes.any.isRequired, onValueChange: PropTypes.func.isRequired, typeInstance: PropTypes.object.isRequired, @@ -50,5 +51,5 @@ export const string = () => ({ name: 'string', displayName: 'string', help: 'Input short strings', - simpleTemplate: template, + simpleTemplate: templateFromReactComponent(EnhancedStringArgInput), }); diff --git a/public/expression_types/arg_types/textarea.js b/public/expression_types/arg_types/textarea.js index 4a31358301696..77b6183bcc0e3 100644 --- a/public/expression_types/arg_types/textarea.js +++ b/public/expression_types/arg_types/textarea.js @@ -4,10 +4,13 @@ import { compose, withProps } from 'recompose'; import { FormGroup, FormControl, Button } from 'react-bootstrap'; import { get } from 'lodash'; import { createStatefulPropHoc } from '../../components/enhance/stateful_prop'; +import { templateFromReactComponent } from '../../lib/template_from_react_component'; const TextAreaArgInput = ({ updateValue, value, confirm, commit, renderError }) => { - if (typeof value !== 'string') renderError(); - + if (typeof value !== 'string') { + renderError(); + return null; + } return (
@@ -37,7 +40,7 @@ TextAreaArgInput.propTypes = { renderError: PropTypes.func, }; -const template = compose( +const EnhancedTextAreaArgInput = compose( withProps(({ onValueChange, typeInstance, argValue }) => ({ confirm: get(typeInstance, 'options.confirm'), commit: onValueChange, @@ -46,15 +49,16 @@ const template = compose( createStatefulPropHoc('value') )(TextAreaArgInput); -template.propTypes = { +EnhancedTextAreaArgInput.propTypes = { argValue: PropTypes.any.isRequired, onValueChange: PropTypes.func.isRequired, typeInstance: PropTypes.object.isRequired, + renderError: PropTypes.func.isRequired, }; export const textarea = () => ({ name: 'textarea', displayName: 'textarea', help: 'Input long strings', - template: template, + template: templateFromReactComponent(EnhancedTextAreaArgInput), }); diff --git a/public/lib/template_from_react_component.js b/public/lib/template_from_react_component.js new file mode 100644 index 0000000000000..d5d63b64e59d1 --- /dev/null +++ b/public/lib/template_from_react_component.js @@ -0,0 +1,17 @@ +import React from 'react'; +import ReactDom from 'react-dom'; + +export const templateFromReactComponent = Component => { + return async (domNode, config, handlers) => { + try { + ReactDom.render(React.createElement(Component, config), domNode); + handlers.done(); + } catch (e) { + config.renderError(); + } + + handlers.onDestroy(() => { + ReactDom.unmountComponentAtNode(domNode); + }); + }; +}; diff --git a/public/state/selectors/workpad.js b/public/state/selectors/workpad.js index 928f774995c11..b4eb7c04aa5a1 100644 --- a/public/state/selectors/workpad.js +++ b/public/state/selectors/workpad.js @@ -21,6 +21,9 @@ export function getWorkpadPersisted(state) { assets: getAssets(state), }; } +export function getWorkpadInfo(state) { + return omit(getWorkpad(state), ['pages']); +} // page getters export function getSelectedPageIndex(state) {