diff --git a/app/client/package.json b/app/client/package.json index 3f09505ebf..87041d214a 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -46,6 +46,7 @@ "husky": "^3.0.5", "interweave": "^12.1.1", "interweave-autolink": "^4.0.1", + "json-fn": "^1.1.1", "lint-staged": "^9.2.5", "localforage": "^1.7.3", "lodash": "^4.17.11", @@ -74,6 +75,7 @@ "react-select": "^3.0.8", "react-spring": "^8.0.27", "react-tabs": "^3.0.0", + "react-toastify": "^5.5.0", "react-transition-group": "^4.3.0", "redux": "^4.0.1", "redux-form": "^8.2.6", diff --git a/app/client/src/actions/actionActions.ts b/app/client/src/actions/actionActions.ts index 65498fa21f..65625805ad 100644 --- a/app/client/src/actions/actionActions.ts +++ b/app/client/src/actions/actionActions.ts @@ -1,4 +1,4 @@ -import { RestAction, PaginationField } from "api/ActionAPI"; +import { RestAction, PaginationField, ActionResponse } from "api/ActionAPI"; import { ReduxActionTypes, ReduxAction, @@ -130,6 +130,19 @@ export const copyActionError = (payload: { }; }; +export const executeApiActionRequest = (payload: { id: string }) => ({ + type: ReduxActionTypes.EXECUTE_API_ACTION_REQUEST, + payload: payload, +}); + +export const executeApiActionSuccess = (payload: { + id: string; + response: ActionResponse; +}) => ({ + type: ReduxActionTypes.EXECUTE_API_ACTION_SUCCESS, + payload: payload, +}); + export default { createAction: createActionRequest, fetchActions, diff --git a/app/client/src/actions/controlActions.tsx b/app/client/src/actions/controlActions.tsx index 9f704c5847..838fd53148 100644 --- a/app/client/src/actions/controlActions.tsx +++ b/app/client/src/actions/controlActions.tsx @@ -24,4 +24,5 @@ export interface UpdateWidgetPropertyPayload { propertyValue: any; renderMode: RenderMode; dynamicBindings?: Record; + dynamicTriggers?: Record; } diff --git a/app/client/src/actions/widgetActions.tsx b/app/client/src/actions/widgetActions.tsx index 2e22d3589a..0b4685f35b 100644 --- a/app/client/src/actions/widgetActions.tsx +++ b/app/client/src/actions/widgetActions.tsx @@ -3,27 +3,18 @@ import { ReduxAction, ReduxActionErrorTypes, } from "constants/ReduxActionConstants"; -import { PaginationField } from "api/ActionAPI"; - import { - ActionPayload, + ExecuteActionPayload, ExecuteErrorPayload, PageAction, } from "constants/ActionConstants"; export const executeAction = ( - actionPayloads: ActionPayload[], - paginationField?: PaginationField, -): ReduxAction<{ - actions: ActionPayload[]; - paginationField: PaginationField; -}> => { + payload: ExecuteActionPayload, +): ReduxAction => { return { type: ReduxActionTypes.EXECUTE_ACTION, - payload: { - actions: actionPayloads, - paginationField: paginationField, - }, + payload, }; }; diff --git a/app/client/src/assets/icons/alert/error.svg b/app/client/src/assets/icons/alert/error.svg new file mode 100644 index 0000000000..addd38233f --- /dev/null +++ b/app/client/src/assets/icons/alert/error.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/alert/info.svg b/app/client/src/assets/icons/alert/info.svg new file mode 100644 index 0000000000..145547a06f --- /dev/null +++ b/app/client/src/assets/icons/alert/info.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/alert/success.svg b/app/client/src/assets/icons/alert/success.svg new file mode 100644 index 0000000000..c3221e7b06 --- /dev/null +++ b/app/client/src/assets/icons/alert/success.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/assets/icons/alert/warning.svg b/app/client/src/assets/icons/alert/warning.svg new file mode 100644 index 0000000000..6887ec5239 --- /dev/null +++ b/app/client/src/assets/icons/alert/warning.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx b/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx index 2ff368678c..386f410451 100644 --- a/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/ButtonComponent.tsx @@ -139,7 +139,7 @@ const mapButtonStyleToStyleName = (buttonStyle?: ButtonStyle) => { const ButtonContainer = (props: ButtonContainerProps & ButtonStyleProps) => { return ( props.theme.radii[1]}px; box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14); padding: ${props => props.theme.spaces[3]}px; - background:white; + background: white; } && .${Classes.POPOVER_WRAPPER} { @@ -123,7 +123,6 @@ const DropdownContainer = styled.div` max-width: 100%; max-height: auto; } - width: 100%; `; diff --git a/app/client/src/components/designSystems/syncfusion/TableComponent.tsx b/app/client/src/components/designSystems/syncfusion/TableComponent.tsx index c5d20b02a2..72d59700ad 100644 --- a/app/client/src/components/designSystems/syncfusion/TableComponent.tsx +++ b/app/client/src/components/designSystems/syncfusion/TableComponent.tsx @@ -20,7 +20,6 @@ import { import React, { useRef, MutableRefObject, useEffect, memo } from "react"; import styled from "constants/DefaultTheme"; import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl"; -import { ActionPayload } from "constants/ActionConstants"; import { Classes } from "@blueprintjs/core"; import { TablePagination } from "../appsmith/TablePagination"; @@ -32,7 +31,7 @@ export interface TableComponentProps { height: number; width: number; columnActions?: ColumnAction[]; - onCommandClick: (actions: ActionPayload[]) => void; + onCommandClick: (dynamicTrigger: string) => void; disableDrag: (disable: boolean) => void; nextPageClick: Function; prevPageClick: Function; @@ -158,7 +157,7 @@ const TableComponent = memo( const commands: CommandModel[] = (props.columnActions || []).map(action => { return { buttonOption: { content: action.label }, - data: action.actionPayloads, + data: action.dynamicTrigger, }; }); @@ -172,7 +171,7 @@ const TableComponent = memo( action.label.toLowerCase() === _target.title.toLowerCase(), ) .forEach(action => { - props.onCommandClick(action.actionPayloads); + props.onCommandClick(action.dynamicTrigger); }); } } diff --git a/app/client/src/components/editorComponents/DynamicActionCreator.tsx b/app/client/src/components/editorComponents/DynamicActionCreator.tsx new file mode 100644 index 0000000000..10c299fb84 --- /dev/null +++ b/app/client/src/components/editorComponents/DynamicActionCreator.tsx @@ -0,0 +1,445 @@ +import React, { ChangeEvent } from "react"; +import { connect } from "react-redux"; +import { AppState } from "reducers"; +import { DropdownOption } from "widgets/DropdownWidget"; +import _ from "lodash"; +import { ControlWrapper } from "components/propertyControls/StyledControls"; +import { InputText } from "components/propertyControls/InputTextControl"; +import StyledDropdown from "components/editorComponents/StyledDropdown"; + +const ACTION_TRIGGER_REGEX = /^{{([\s\S]*?)\(([\s\S]*?)\)}}$/g; +const ACTION_ANONYMOUS_FUNC_REGEX = /\(\) => ([\s\S]*?)(\([\s\S]*?\))/g; + +const ALERT_STYLE_OPTIONS = [ + { label: "Info", value: "'info'", id: "info" }, + { label: "Success", value: "'success'", id: "success" }, + { label: "Error", value: "'error'", id: "error" }, + { label: "Warning", value: "'warning'", id: "warning" }, +]; + +type ValueChangeHandler = (changeValue: string, currentValue: string) => string; +type ActionCreatorArgumentConfig = { + label: string; + field: string; + valueChangeHandler: ValueChangeHandler; + getSelectedValue: (value: string, returnArguments: boolean) => string; +}; + +interface ActionCreatorDropdownOption extends DropdownOption { + arguments: ActionCreatorArgumentConfig[]; +} + +const handleTopLevelFuncUpdate: ValueChangeHandler = ( + value: string, +): string => { + return value === "none" ? "" : `{{${value}()}}`; +}; + +const handleApiArgSelect = ( + changeValue: string, + currentValue: string, + label: "onSuccess" | "onError", +): string => { + const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)]; + const args = [...matches[0][2].matchAll(ACTION_ANONYMOUS_FUNC_REGEX)]; + let successArg = args[0] ? args[0][0] : "() => {}"; + let errorArg = args[1] ? args[1][0] : "() => {}"; + if (label === "onSuccess") { + successArg = changeValue.endsWith(")") + ? `() => ${changeValue}` + : `() => ${changeValue}()`; + } + if (label === "onError") { + errorArg = changeValue.endsWith(")") + ? `() => ${changeValue}` + : `() => ${changeValue}()`; + } + return currentValue.replace( + ACTION_TRIGGER_REGEX, + `{{$1(${successArg}, ${errorArg})}}`, + ); +}; + +const handlePageNameArgSelect = (changeValue: string, currentValue: string) => { + return currentValue.replace(ACTION_TRIGGER_REGEX, `{{$1(${changeValue})}}`); +}; + +const handleTextArgChange = ( + changeValue: string, + currentValue: string, +): string => { + return currentValue.replace(ACTION_TRIGGER_REGEX, `{{$1('${changeValue}')}}`); +}; + +const handleAlertTextChange = ( + changeValue: string, + currentValue: string, +): string => { + const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)]; + const args = matches[0][2].split(","); + args[0] = `'${changeValue}'`; + return currentValue.replace( + ACTION_TRIGGER_REGEX, + `{{$1(${args.join(",")})}}`, + ); +}; + +const handleAlertTypeChange = ( + changeValue: string, + currentValue: string, +): string => { + const matches = [...currentValue.matchAll(ACTION_TRIGGER_REGEX)]; + const args = matches[0][2].split(","); + args[1] = changeValue; + return currentValue.replace( + ACTION_TRIGGER_REGEX, + `{{$1(${args.join(",")})}}`, + ); +}; + +const getApiArgumentValue = ( + value: string, + label: "onSuccess" | "onError", + returnSubArguments = false, +): string => { + let selectedValue = "none"; + let selectedValueArgs = ""; + const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)]; + if (matches.length) { + const funcArgs = matches[0][2]; + const argIndex = label === "onSuccess" ? 0 : 1; + const args = [...funcArgs.matchAll(ACTION_ANONYMOUS_FUNC_REGEX)]; + const selectedArg = args[argIndex]; + if (selectedArg && selectedArg.length) { + selectedValue = selectedArg[1]; + selectedValueArgs = selectedArg[2]; + } + } + if (returnSubArguments) return selectedValueArgs; + return selectedValue; +}; + +const getPageNameSelectedValue = (value: string) => { + const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)]; + return matches.length ? matches[0][2] : "none"; +}; + +const getTextArgValue = (value: string) => { + const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)]; + if (matches.length) { + const stringValue = matches[0][2]; + return stringValue.substring(1, stringValue.length - 1); + } + return ""; +}; + +const getAlertTextValue = (value: string) => { + const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)]; + if (matches.length) { + const funcArgs = matches[0][2]; + const arg = funcArgs.split(",")[0]; + return arg.substring(1, arg.length - 1); + } + return ""; +}; + +const getAlertTypeValue = (value: string) => { + const matches = [...value.matchAll(ACTION_TRIGGER_REGEX)]; + if (matches.length) { + const funcArgs = matches[0][2]; + const arg = funcArgs.split(",")[1]; + return arg ? arg.trim() : "'primary'"; + } + return ""; +}; + +export const PropertyPaneActionDropdownOptions: ActionCreatorDropdownOption[] = [ + { + label: "No action", + value: "none", + id: "none", + arguments: [], + }, + { + label: "Call API", + value: "api", + id: "api", + arguments: [ + { + label: "onSuccess", + field: "ACTION_SELECTOR_FIELD", + valueChangeHandler: (changeValue, currentValue) => + handleApiArgSelect(changeValue, currentValue, "onSuccess"), + getSelectedValue: (value: string, returnArgs = false) => + getApiArgumentValue(value, "onSuccess", returnArgs), + }, + { + label: "onError", + field: "ACTION_SELECTOR_FIELD", + valueChangeHandler: (changeValue, currentValue) => + handleApiArgSelect(changeValue, currentValue, "onError"), + getSelectedValue: (value: string, returnArgs = false) => + getApiArgumentValue(value, "onError", returnArgs), + }, + ], + }, + { + label: "Navigate to Page", + value: "navigateTo", + id: "navigateTo", + arguments: [ + { + label: "pageName", + field: "PAGE_SELECTOR_FIELD", + valueChangeHandler: handlePageNameArgSelect, + getSelectedValue: getPageNameSelectedValue, + }, + ], + }, + { + label: "Navigate to URL", + value: "navigateToUrl", + id: "navigateToUrl", + arguments: [ + { + label: "URL", + field: "TEXT_FIELD", + valueChangeHandler: handleTextArgChange, + getSelectedValue: getTextArgValue, + }, + ], + }, + { + label: "Show Alert", + value: "showAlert", + id: "showAlert", + arguments: [ + { + label: "text", + field: "TEXT_FIELD", + valueChangeHandler: handleAlertTextChange, + getSelectedValue: getAlertTextValue, + }, + { + label: "type", + field: "ALERT_TYPE_SELECTOR_FIELD", + valueChangeHandler: handleAlertTypeChange, + getSelectedValue: getAlertTypeValue, + }, + ], + }, +]; + +type ReduxStateProps = { + actions: DropdownOption[]; + pageNameDropdown: DropdownOption[]; +}; + +interface Props { + value: string; + onValueChange: (newValue: string) => void; +} + +class DynamicActionCreator extends React.Component { + getTopLevelFuncValue = (value: string) => { + let matches: any[] = []; + if (value) { + matches = value ? [...value.matchAll(ACTION_TRIGGER_REGEX)] : []; + } + let mainFuncSelectedValue = "none"; + if (matches.length) { + mainFuncSelectedValue = matches[0][1] || "none"; + } + return mainFuncSelectedValue; + }; + + handleValueUpdate = ( + updateValueOrEvent: string | ChangeEvent, + valueUpdateHandler: ValueChangeHandler, + ) => { + const { value, onValueChange } = this.props; + let updateValue = updateValueOrEvent; + if (typeof updateValueOrEvent !== "string") { + updateValue = updateValueOrEvent.target.value; + } + const newValue = valueUpdateHandler(updateValue as string, value); + onValueChange(newValue); + }; + + renderSubArgumentFields = ( + argValue: string, + allOptions: ActionCreatorDropdownOption[], + parentChangeHandler: ( + updateValueOrEvent: string | ChangeEvent, + valueUpdateHandler: ValueChangeHandler, + ) => void, + argumentConfig: ActionCreatorArgumentConfig, + ) => { + const subArgValue = argumentConfig.getSelectedValue(argValue, false); + const subArguments = argumentConfig.getSelectedValue(argValue, true); + let selectedOption = allOptions[0]; + allOptions + .filter(o => o.value !== "api") + .forEach(o => { + if (o.value === subArgValue) { + selectedOption = o; + } + }); + const handleValueUpdate = ( + updateValueOrEvent: string | ChangeEvent, + valueUpdateHandler: ValueChangeHandler, + ) => { + let updateValue = updateValueOrEvent; + if (typeof updateValueOrEvent !== "string") { + updateValue = updateValueOrEvent.target.value; + } + const tempArg = `{{${subArgValue}${subArguments}}}`; + const newValue = valueUpdateHandler(updateValue as string, tempArg); + const newArgValue = newValue.substring(2, newValue.length - 2); + parentChangeHandler(newArgValue, argumentConfig.valueChangeHandler); + }; + const subFunctionCall = `{{${subArgValue}${subArguments}}}`; + return this.renderActionArgumentFields( + subFunctionCall, + selectedOption, + allOptions, + handleValueUpdate, + ); + }; + + renderActionArgumentFields = ( + value: string, + selectedOption: ActionCreatorDropdownOption, + allOptions: ActionCreatorDropdownOption[], + handleUpdate: ( + updateValueOrEvent: string | ChangeEvent, + valueUpdateHandler: ValueChangeHandler, + ) => void, + ) => { + return ( +
+ {selectedOption.arguments.map(arg => { + switch (arg.field) { + case "ACTION_SELECTOR_FIELD": + return ( + + + + handleUpdate(value, arg.valueChangeHandler) + } + /> + {this.renderSubArgumentFields( + value, + allOptions, + handleUpdate, + arg, + )} + + ); + case "PAGE_SELECTOR_FIELD": + return ( + + + + handleUpdate(value, arg.valueChangeHandler) + } + /> + + ); + case "TEXT_FIELD": + return ( + + handleUpdate(e, arg.valueChangeHandler)} + isValid={true} + /> + + ); + case "ALERT_TYPE_SELECTOR_FIELD": + return ( + + + + handleUpdate(value, arg.valueChangeHandler) + } + /> + + ); + default: + return null; + } + })} +
+ ); + }; + + render() { + const { actions, value } = this.props; + const topLevelFuncValue = this.getTopLevelFuncValue(value); + const actionOptions = PropertyPaneActionDropdownOptions.map(o => { + if (o.id === "api") { + return { + ...o, + children: actions.map(a => ({ ...o, ...a })), + }; + } else { + return o; + } + }); + let selectedOption = actionOptions[0]; + actionOptions.forEach(o => { + if ( + o.value === topLevelFuncValue || + _.some(o.children, { + value: topLevelFuncValue, + }) + ) { + selectedOption = o; + } + }); + + return ( + + + this.handleValueUpdate(value, handleTopLevelFuncUpdate) + } + /> + {this.renderActionArgumentFields( + value, + selectedOption, + actionOptions, + this.handleValueUpdate, + )} + + ); + } +} + +const mapStateToProps = (state: AppState): ReduxStateProps => ({ + actions: state.entities.actions.map(a => ({ + label: a.config.name, + id: a.config.id, + value: `${a.config.name}.run`, + })), + pageNameDropdown: state.entities.pageList.pages.map(p => ({ + label: p.pageName, + id: p.pageId, + value: `'${p.pageName}'`, + })), +}); + +export default connect(mapStateToProps)(DynamicActionCreator); diff --git a/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx b/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx index aa9d05d788..63ff7f976e 100644 --- a/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx +++ b/app/client/src/components/editorComponents/DynamicAutocompleteInput.tsx @@ -10,15 +10,13 @@ import "codemirror/addon/hint/javascript-hint"; import "codemirror/addon/display/placeholder"; import "codemirror/addon/edit/closebrackets"; import "codemirror/addon/display/autorefresh"; -import { - getNameBindingsForAutocomplete, - NameBindingsWithData, -} from "selectors/nameBindingsWithDataSelector"; +import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors"; import { AUTOCOMPLETE_MATCH_REGEX } from "constants/BindingsConstants"; import ErrorTooltip from "components/editorComponents/ErrorTooltip"; import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form"; import _ from "lodash"; import { parseDynamicString } from "utils/DynamicBindingUtils"; +import { DataTree } from "entities/DataTree/dataTreeFactory"; require("codemirror/mode/javascript/javascript"); const HintStyles = createGlobalStyle` @@ -125,7 +123,7 @@ const THEMES = { type THEME = "LIGHT" | "DARK"; interface ReduxStateProps { - dynamicData: NameBindingsWithData; + dynamicData: DataTree; } export type DynamicAutocompleteInputProps = { @@ -306,7 +304,7 @@ class DynamicAutocompleteInput extends Component { } const mapStateToProps = (state: AppState): ReduxStateProps => ({ - dynamicData: getNameBindingsForAutocomplete(state), + dynamicData: getDataTreeForAutocomplete(state), }); export default connect(mapStateToProps)(DynamicAutocompleteInput); diff --git a/app/client/src/components/editorComponents/EditorContextProvider.tsx b/app/client/src/components/editorComponents/EditorContextProvider.tsx index 6ff121da39..58c5991958 100644 --- a/app/client/src/components/editorComponents/EditorContextProvider.tsx +++ b/app/client/src/components/editorComponents/EditorContextProvider.tsx @@ -9,19 +9,15 @@ import { updateWidget } from "actions/pageActions"; import { executeAction, disableDragAction } from "actions/widgetActions"; import { updateWidgetProperty } from "actions/controlActions"; -import { ActionPayload } from "constants/ActionConstants"; +import { ExecuteActionPayload } from "constants/ActionConstants"; import { RenderModes } from "constants/WidgetConstants"; import { OccupiedSpace } from "constants/editorConstants"; import { getOccupiedSpaces } from "selectors/editorSelectors"; -import { PaginationField } from "api/ActionAPI"; import { updateWidgetMetaProperty } from "actions/metaActions"; export type EditorContextType = { - executeAction?: ( - actionPayloads: ActionPayload[], - paginationField?: PaginationField, - ) => void; + executeAction?: (actionPayloads: ExecuteActionPayload) => void; updateWidget?: ( operation: WidgetOperation, widgetId: string, @@ -99,21 +95,19 @@ const mapDispatchToProps = (dispatch: any) => { RenderModes.CANVAS, ), ), + executeAction: (actionPayload: ExecuteActionPayload) => + dispatch(executeAction(actionPayload)), + updateWidget: ( + operation: WidgetOperation, + widgetId: string, + payload: any, + ) => dispatch(updateWidget(operation, widgetId, payload)), updateWidgetMetaProperty: ( widgetId: string, propertyName: string, propertyValue: any, ) => dispatch(updateWidgetMetaProperty(widgetId, propertyName, propertyValue)), - executeAction: ( - actionPayloads: ActionPayload[], - paginationField?: PaginationField, - ) => dispatch(executeAction(actionPayloads, paginationField)), - updateWidget: ( - operation: WidgetOperation, - widgetId: string, - payload: any, - ) => dispatch(updateWidget(operation, widgetId, payload)), disableDrag: (disable: boolean) => { dispatch(disableDragAction(disable)); }, diff --git a/app/client/src/components/editorComponents/StyledDropdown.tsx b/app/client/src/components/editorComponents/StyledDropdown.tsx new file mode 100644 index 0000000000..6eab9d9ff0 --- /dev/null +++ b/app/client/src/components/editorComponents/StyledDropdown.tsx @@ -0,0 +1,101 @@ +import React from "react"; +import _ from "lodash"; +import { DropdownOption } from "widgets/DropdownWidget"; +import { + StyledDropDown, + StyledDropDownContainer, +} from "components/propertyControls/StyledControls"; +import { + Button, + MenuItem, + PopoverInteractionKind, + PopoverPosition, +} from "@blueprintjs/core"; +import { IconNames } from "@blueprintjs/icons"; + +type ActionTypeDropdownProps = { + options: DropdownOption[]; + selectedValue: string; + onSelect: (value: string) => void; +}; + +class StyledDropdown extends React.Component { + handleSelect = (option: DropdownOption) => { + this.props.onSelect(option.value); + }; + renderItem = (option: DropdownOption) => { + const isSelected = this.isOptionSelected(option); + return ( + this.handleSelect(option)} + text={option.label} + popoverProps={{ + minimal: true, + hoverCloseDelay: 0, + interactionKind: PopoverInteractionKind.HOVER, + position: PopoverPosition.BOTTOM, + modifiers: { + arrow: { + enabled: false, + }, + offset: { + enabled: true, + offset: "-16px, 0", + }, + }, + }} + > + {option.children && option.children.map(this.renderItem)} + + ); + }; + isOptionSelected = (currentOption: DropdownOption) => { + if (currentOption.children) { + return _.some(currentOption.children, { + value: this.props.selectedValue, + }); + } else { + return currentOption.value === this.props.selectedValue; + } + }; + + render() { + const { selectedValue } = this.props; + let selectedOption = this.props.options[0]; + this.props.options.forEach(o => { + if (o.value === selectedValue) { + selectedOption = o; + } else { + const childOption = _.find(o.children, { + value: this.props.selectedValue, + }); + if (childOption) selectedOption = childOption; + } + }); + return ( + + +