Merge branch 'fix/codemirror-fixes' into 'release'

Fixes for Dynamic Input styling

See merge request theappsmith/internal-tools-client!212
This commit is contained in:
Hetu Nandu 2020-01-02 13:36:35 +00:00
commit 53c039e206
18 changed files with 270 additions and 84 deletions

View File

@ -14,9 +14,14 @@ type DropdownProps = {
meta: WrappedFieldMetaProps; meta: WrappedFieldMetaProps;
onCreateOption: (inputValue: string) => void; onCreateOption: (inputValue: string) => void;
formatCreateLabel?: (value: string) => React.ReactNode; formatCreateLabel?: (value: string) => React.ReactNode;
noOptionsMessage?: (obj: { inputValue: string }) => string;
}; };
const selectStyles = { const selectStyles = {
placeholder: (provided: any) => ({
...provided,
color: "#a3b3bf",
}),
singleValue: (provided: any) => ({ singleValue: (provided: any) => ({
...provided, ...provided,
backgroundColor: "rgba(104,113,239,0.1)", backgroundColor: "rgba(104,113,239,0.1)",
@ -67,9 +72,11 @@ class CreatableDropdown extends React.Component<DropdownProps> {
onCreateOption, onCreateOption,
input, input,
formatCreateLabel, formatCreateLabel,
noOptionsMessage,
} = this.props; } = this.props;
const optionalProps: Partial<DropdownProps> = {}; const optionalProps: Partial<DropdownProps> = {};
if (formatCreateLabel) optionalProps.formatCreateLabel = formatCreateLabel; if (formatCreateLabel) optionalProps.formatCreateLabel = formatCreateLabel;
if (noOptionsMessage) optionalProps.noOptionsMessage = noOptionsMessage;
return ( return (
<Creatable <Creatable
placeholder={placeholder} placeholder={placeholder}

View File

@ -14,6 +14,10 @@ type DropdownProps = {
}; };
const selectStyles = { const selectStyles = {
placeholder: (provided: any) => ({
...provided,
color: "#a3b3bf",
}),
control: (styles: any, state: any) => ({ control: (styles: any, state: any) => ({
...styles, ...styles,
width: 100, width: 100,

View File

@ -11,7 +11,7 @@ import { formatBytes } from "utils/helpers";
import { APIEditorRouteParams } from "constants/routes"; import { APIEditorRouteParams } from "constants/routes";
import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer"; import { ApiPaneReduxState } from "reducers/uiReducers/apiPaneReducer";
import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen"; import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen";
import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput"; import CodeEditor from "components/editorComponents/CodeEditor";
const ResponseWrapper = styled.div` const ResponseWrapper = styled.div`
position: relative; position: relative;
@ -138,12 +138,13 @@ const ApiResponseView = (props: Props) => {
key: "body", key: "body",
title: "Response Body", title: "Response Body",
panelComponent: ( panelComponent: (
<DynamicAutocompleteInput <CodeEditor
input={{ input={{
value: response.body value: response.body
? JSON.stringify(response.body, null, 2) ? JSON.stringify(response.body, null, 2)
: "", : "",
}} }}
height={700}
/> />
), ),
}, },

View File

@ -21,19 +21,19 @@ interface Props {
class CodeEditor extends React.Component<Props> { class CodeEditor extends React.Component<Props> {
textArea = React.createRef<HTMLTextAreaElement>(); textArea = React.createRef<HTMLTextAreaElement>();
editor: any; editor: any;
constructor(props: Props) {
super(props);
}
componentDidMount(): void { componentDidMount(): void {
if (this.textArea.current) { if (this.textArea.current) {
const readOnly = !this.props.input.onChange;
this.editor = cm.fromTextArea(this.textArea.current, { this.editor = cm.fromTextArea(this.textArea.current, {
mode: { name: "javascript", json: true }, mode: { name: "javascript", json: true },
value: this.props.input.value, value: this.props.input.value,
readOnly,
lineNumbers: true, lineNumbers: true,
tabSize: 2, tabSize: 2,
indentWithTabs: true, indentWithTabs: true,
lineWrapping: true, lineWrapping: true,
}); });
this.editor.setSize(null, this.props.height);
} }
} }

View File

@ -1,43 +1,146 @@
import React, { Component } from "react"; import React, { Component } from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { AppState } from "reducers"; import { AppState } from "reducers";
import styled from "styled-components"; import styled, { createGlobalStyle } from "styled-components";
import CodeMirror, { EditorConfiguration } from "codemirror"; import CodeMirror, { EditorConfiguration, LineHandle } from "codemirror";
import "codemirror/lib/codemirror.css"; import "codemirror/lib/codemirror.css";
import "codemirror/theme/monokai.css"; import "codemirror/theme/monokai.css";
import "codemirror/addon/hint/show-hint"; import "codemirror/addon/hint/show-hint";
import "codemirror/addon/hint/show-hint.css";
import "codemirror/addon/hint/javascript-hint"; import "codemirror/addon/hint/javascript-hint";
import "codemirror/addon/display/placeholder";
import { import {
getNameBindingsWithData, getNameBindingsWithData,
NameBindingsWithData, NameBindingsWithData,
} from "selectors/nameBindingsWithDataSelector"; } from "selectors/nameBindingsWithDataSelector";
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";
require("codemirror/mode/javascript/javascript"); require("codemirror/mode/javascript/javascript");
const Wrapper = styled.div<{ height?: number; theme?: "LIGHT" | "DARK" }>` const HintStyles = createGlobalStyle`
border: ${props => props.theme !== "DARK" && "1px solid #d0d7dd"}; .CodeMirror-hints {
position: absolute;
z-index: 10;
overflow: hidden;
list-style: none;
margin: 0;
padding: 5px;
font-size: 90%;
font-family: monospace;
max-height: 20em;
width: 200px;
overflow-y: auto;
background: #FFFFFF;
border: 1px solid #EBEFF2;
box-shadow: 0px 2px 4px rgba(67, 70, 74, 0.14);
border-radius: 4px;
}
.CodeMirror-hint {
height: 32px;
padding: 3px;
margin: 0;
white-space: pre;
color: #2E3D49;
cursor: pointer;
display: flex;
align-items: center;
font-size: 14px;
}
li.CodeMirror-hint-active {
background: #E9FAF3;
border-radius: 4px;
}
`;
const Wrapper = styled.div<{
borderStyle?: THEME;
hasError: boolean;
}>`
border: 1px solid;
border-color: ${props =>
props.hasError
? props.theme.colors.error
: props.borderStyle !== THEMES.DARK
? "#d0d7dd"
: "transparent"};
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
flex: 1; flex: 1;
flex-direction: column; flex-direction: row;
position: relative; position: relative;
text-transform: none; text-transform: none;
min-height: 32px; min-height: 32px;
height: ${props => (props.height ? `${props.height}px` : "32px")}; overflow: hidden;
height: auto;
&& {
.binding-highlight {
color: ${props =>
props.borderStyle === THEMES.DARK ? "#f7c75b" : "#ffb100"};
font-weight: 700;
}
.CodeMirror {
flex: 1;
line-height: 21px;
z-index: 0;
border-radius: 4px;
height: auto;
}
.CodeMirror pre.CodeMirror-placeholder {
color: #a3b3bf;
}
}
`; `;
const IconContainer = styled.div`
.bp3-icon {
border-radius: 4px 0 0 4px;
margin: 0;
height: 32px;
width: 30px;
display: flex;
align-items: center;
justify-content: center;
background-color: #eef2f5;
svg {
height: 20px;
width: 20px;
path {
fill: #979797;
}
}
}
`;
const THEMES = {
LIGHT: "LIGHT",
DARK: "DARK",
};
type THEME = "LIGHT" | "DARK";
interface ReduxStateProps { interface ReduxStateProps {
dynamicData: NameBindingsWithData; dynamicData: NameBindingsWithData;
} }
type Props = ReduxStateProps & { export type DynamicAutocompleteInputProps = {
input: { placeholder?: string;
value: string; leftIcon?: Function;
onChange?: (value: string) => void; initialHeight: number;
}; theme?: THEME;
theme?: "LIGHT" | "DARK"; meta?: Partial<WrappedFieldMetaProps>;
showLineNumbers?: boolean;
allowTabIndent?: boolean;
}; };
type Props = ReduxStateProps &
DynamicAutocompleteInputProps & {
input: Partial<WrappedFieldInputProps>;
};
class DynamicAutocompleteInput extends Component<Props> { class DynamicAutocompleteInput extends Component<Props> {
textArea = React.createRef<HTMLTextAreaElement>(); textArea = React.createRef<HTMLTextAreaElement>();
editor: any; editor: any;
@ -47,30 +150,40 @@ class DynamicAutocompleteInput extends Component<Props> {
const options: EditorConfiguration = {}; const options: EditorConfiguration = {};
if (this.props.theme === "DARK") options.theme = "monokai"; if (this.props.theme === "DARK") options.theme = "monokai";
if (!this.props.input.onChange) options.readOnly = true; if (!this.props.input.onChange) options.readOnly = true;
if (this.props.showLineNumbers) options.lineNumbers = true;
const extraKeys: Record<string, any> = {
"Ctrl-Space": "autocomplete",
};
if (!this.props.allowTabIndent) extraKeys["Tab"] = false;
this.editor = CodeMirror.fromTextArea(this.textArea.current, { this.editor = CodeMirror.fromTextArea(this.textArea.current, {
mode: { name: "javascript", globalVars: true }, mode: { name: "javascript", globalVars: true },
viewportMargin: 10,
value: this.props.input.value, value: this.props.input.value,
tabSize: 2, tabSize: 2,
indentWithTabs: true, indentWithTabs: true,
lineWrapping: true, lineWrapping: true,
extraKeys: { "Ctrl-Space": "autocomplete" },
showHint: true, showHint: true,
extraKeys,
...options, ...options,
}); });
this.editor.on("change", this.handleChange); this.editor.on("change", _.debounce(this.handleChange, 200));
this.editor.on("keyup", this.handleAutocompleteVisibility); this.editor.on("cursorActivity", this.handleAutocompleteVisibility);
this.editor.setOption("hintOptions", { this.editor.setOption("hintOptions", {
completeSingle: false, completeSingle: false,
globalScope: this.props.dynamicData, globalScope: this.props.dynamicData,
}); });
this.editor.eachLine(this.highlightBindings);
} }
} }
componentDidUpdate(): void { componentDidUpdate(): void {
if (this.editor) { if (this.editor) {
const editorValue = this.editor.getValue(); const editorValue = this.editor.getValue();
const inputValue = this.props.input.value; let inputValue = this.props.input.value;
if (inputValue && inputValue !== editorValue) { if (typeof inputValue === "object") {
inputValue = JSON.stringify(inputValue, null, 2);
}
if (!!inputValue && inputValue !== editorValue) {
this.editor.setValue(inputValue); this.editor.setValue(inputValue);
} }
} }
@ -81,21 +194,72 @@ class DynamicAutocompleteInput extends Component<Props> {
if (this.props.input.onChange) { if (this.props.input.onChange) {
this.props.input.onChange(value); this.props.input.onChange(value);
} }
this.editor.eachLine(this.highlightBindings);
}; };
handleAutocompleteVisibility = (cm: any, event: any) => { handleAutocompleteVisibility = (cm: any) => {
if (!cm.state.completionActive && event.keyCode !== 13) { let cursorBetweenBinding = false;
const cursor = this.editor.getCursor();
const value = this.editor.getValue();
let cumulativeCharCount = 0;
parseDynamicString(value).forEach(segment => {
const start = cumulativeCharCount;
const dynamicStart = segment.indexOf("{{");
const dynamicDoesStart = dynamicStart > -1;
const dynamicEnd = segment.indexOf("}}");
const dynamicDoesEnd = dynamicEnd > -1;
const dynamicStartIndex = dynamicStart + start + 1;
const dynamicEndIndex = dynamicEnd + start + 1;
if (
dynamicDoesStart &&
cursor.ch > dynamicStartIndex &&
((dynamicDoesEnd && cursor.ch < dynamicEndIndex) ||
(!dynamicDoesEnd && cursor.ch > dynamicStartIndex))
) {
cursorBetweenBinding = true;
}
cumulativeCharCount = start + segment.length;
});
const shouldShow = cursorBetweenBinding && !cm.state.completionActive;
if (shouldShow) {
cm.showHint(cm); cm.showHint(cm);
} }
}; };
highlightBindings = (line: LineHandle) => {
const lineNo = this.editor.getLineNumber(line);
let match;
while ((match = AUTOCOMPLETE_MATCH_REGEX.exec(line.text)) != null) {
const start = match.index;
const end = AUTOCOMPLETE_MATCH_REGEX.lastIndex;
this.editor.markText(
{ ch: start, line: lineNo },
{ ch: end, line: lineNo },
{
className: "binding-highlight",
},
);
}
};
render() { render() {
const { input, theme } = this.props; const { input, meta, theme } = this.props;
const height = this.editor ? this.editor.doc.height + 20 : null; const hasError = !!(meta && meta.error);
return ( return (
<Wrapper height={height} theme={theme}> <ErrorTooltip message={meta ? meta.error : ""} isOpen={hasError}>
<textarea ref={this.textArea} defaultValue={input.value} /> <Wrapper borderStyle={theme} hasError={hasError}>
</Wrapper> <HintStyles />
<IconContainer>
{this.props.leftIcon && <this.props.leftIcon />}
</IconContainer>
<textarea
ref={this.textArea}
{..._.omit(this.props.input, ["onChange", "value"])}
defaultValue={input.value}
placeholder={this.props.placeholder}
/>
</Wrapper>
</ErrorTooltip>
); );
} }
} }

View File

@ -3,8 +3,10 @@ import { Popover } from "@blueprintjs/core";
import styled from "styled-components"; import styled from "styled-components";
const Wrapper = styled.div` const Wrapper = styled.div`
flex: 1;
.bp3-popover-target { .bp3-popover-target {
width: 100%; width: 100%;
height: 100%;
} }
.bp3-popover { .bp3-popover {
.bp3-popover-arrow { .bp3-popover-arrow {

View File

@ -37,11 +37,12 @@ const DatasourcesField = (
component={CreatableDropdown} component={CreatableDropdown}
isLoading={props.datasources.loading} isLoading={props.datasources.loading}
options={options} options={options}
placeholder="Data Source" placeholder="https://<base-url>.com"
onCreateOption={props.createDatasource} onCreateOption={props.createDatasource}
format={(value: string) => _.find(options, { value })} format={(value: string) => _.find(options, { value })}
parse={(option: { value: string }) => (option ? option.value : null)} parse={(option: { value: string }) => (option ? option.value : null)}
formatCreateLabel={(value: string) => `Create data source "${value}"`} formatCreateLabel={(value: string) => `Create data source "${value}"`}
noOptionsMessage={() => "No data sources created"}
/> />
); );
}; };

View File

@ -1,10 +1,11 @@
import React from "react"; import React from "react";
import { Field, BaseFieldProps } from "redux-form"; import { Field, BaseFieldProps } from "redux-form";
import { TextInputProps } from "components/designSystems/appsmith/TextInputComponent"; import DynamicAutocompleteInput, {
import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput"; DynamicAutocompleteInputProps,
} from "components/editorComponents/DynamicAutocompleteInput";
class DynamicTextField extends React.Component< class DynamicTextField extends React.Component<
BaseFieldProps & TextInputProps BaseFieldProps & DynamicAutocompleteInputProps
> { > {
render() { render() {
return <Field component={DynamicAutocompleteInput} {...this.props} />; return <Field component={DynamicAutocompleteInput} {...this.props} />;

View File

@ -1,8 +1,12 @@
import React from "react"; import React from "react";
import { Field } from "redux-form"; import { Field } from "redux-form";
import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput"; import DynamicAutocompleteInput, {
DynamicAutocompleteInputProps,
} from "components/editorComponents/DynamicAutocompleteInput";
const JSONEditorField = (props: { name: string }) => { type Props = { name: string } & DynamicAutocompleteInputProps;
const JSONEditorField = (props: Props) => {
return <Field name={props.name} component={DynamicAutocompleteInput} />; return <Field name={props.name} component={DynamicAutocompleteInput} />;
}; };

View File

@ -32,8 +32,16 @@ const KeyValueRow = (props: Props & WrappedFieldArrayProps) => {
{props.fields.map((field: any, index: number) => ( {props.fields.map((field: any, index: number) => (
<FormRowWithLabel key={index}> <FormRowWithLabel key={index}>
{index === 0 && <FormLabel>{props.label}</FormLabel>} {index === 0 && <FormLabel>{props.label}</FormLabel>}
<DynamicTextField name={`${field}.key`} placeholder="Key" /> <DynamicTextField
<DynamicTextField name={`${field}.value`} placeholder="Value" /> name={`${field}.key`}
placeholder="Key"
initialHeight={32}
/>
<DynamicTextField
name={`${field}.value`}
placeholder="Value"
initialHeight={32}
/>
{index === props.fields.length - 1 ? ( {index === props.fields.length - 1 ? (
<Icon <Icon
icon="plus" icon="plus"

View File

@ -1,8 +1,9 @@
import React from "react"; import React, { ChangeEvent } from "react";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import { ControlWrapper } from "./StyledControls"; import { ControlWrapper } from "./StyledControls";
import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput"; import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocompleteInput";
import { EventOrValueHandler } from "redux-form";
class CodeEditorControl extends BaseControl<ControlProps> { class CodeEditorControl extends BaseControl<ControlProps> {
render() { render() {
return ( return (
@ -11,12 +12,15 @@ class CodeEditorControl extends BaseControl<ControlProps> {
<DynamicAutocompleteInput <DynamicAutocompleteInput
theme={"DARK"} theme={"DARK"}
input={{ value: this.props.propertyValue, onChange: this.onChange }} input={{ value: this.props.propertyValue, onChange: this.onChange }}
initialHeight={32}
/> />
</ControlWrapper> </ControlWrapper>
); );
} }
onChange = (value: string) => { onChange: EventOrValueHandler<ChangeEvent<any>> = (
value: string | ChangeEvent,
) => {
this.updateProperty(this.props.propertyName, value); this.updateProperty(this.props.propertyName, value);
}; };

View File

@ -8,12 +8,7 @@ import DynamicAutocompleteInput from "components/editorComponents/DynamicAutocom
class InputTextControl extends BaseControl<InputControlProps> { class InputTextControl extends BaseControl<InputControlProps> {
render() { render() {
const { const { validationMessage, propertyValue, isValid, label } = this.props;
// validationMessage,
propertyValue,
// isValid,
label,
} = this.props;
return ( return (
<ControlWrapper> <ControlWrapper>
<label>{label}</label> <label>{label}</label>
@ -23,7 +18,12 @@ class InputTextControl extends BaseControl<InputControlProps> {
value: propertyValue, value: propertyValue,
onChange: this.onTextChange, onChange: this.onTextChange,
}} }}
meta={{
error: isValid ? "" : validationMessage,
touched: true,
}}
theme={"DARK"} theme={"DARK"}
initialHeight={32}
/> />
</StyledDynamicInput> </StyledDynamicInput>
</ControlWrapper> </ControlWrapper>

View File

@ -2,6 +2,5 @@
// TODO (hetu): Remove useless escapes and re-enable the above lint rule // TODO (hetu): Remove useless escapes and re-enable the above lint rule
export type NamePathBindingMap = Record<string, string>; export type NamePathBindingMap = Record<string, string>;
export const DATA_BIND_REGEX = /(.*?){{(\s*(.*?)\s*)}}(.*?)/g; export const DATA_BIND_REGEX = /(.*?){{(\s*(.*?)\s*)}}(.*?)/g;
export const DATA_PATH_REGEX = /[\w\.\[\]\d]+/; export const AUTOCOMPLETE_MATCH_REGEX = /{{\s*.*?\s*}}/g;
export const DATA_BIND_AUTOCOMPLETE = /({{)(.*)(}{0,2}?)/;
/* eslint-enable no-useless-escape */ /* eslint-enable no-useless-escape */

View File

@ -25,4 +25,13 @@ export const FormIcons: {
<Icon icon={IconNames.PLUS} color={props.color} iconSize={props.height} /> <Icon icon={IconNames.PLUS} color={props.color} iconSize={props.height} />
</IconWrapper> </IconWrapper>
), ),
SLASH_ICON: (props: IconProps) => (
<IconWrapper {...props}>
<Icon
icon={IconNames.SLASH}
color={props.color}
iconSize={props.height}
/>
</IconWrapper>
),
}; };

View File

@ -101,7 +101,7 @@ const PropertyPaneConfigResponse = {
controlType: "INPUT_TEXT", controlType: "INPUT_TEXT",
}, },
{ {
id: "4.1", id: "4.2",
propertyName: "defaultImage", propertyName: "defaultImage",
label: "Default Image", label: "Default Image",
placeholderText: "Enter URL", placeholderText: "Enter URL",
@ -412,18 +412,12 @@ const PropertyPaneConfigResponse = {
children: [ children: [
{ {
id: "12.1", id: "12.1",
propertyName: "tableActions",
label: "Record action",
controlType: "RECORD_ACTION_SELECTOR",
},
{
id: "12.2",
propertyName: "onRowSelected", propertyName: "onRowSelected",
label: "onRowSelected", label: "onRowSelected",
controlType: "ACTION_SELECTOR", controlType: "ACTION_SELECTOR",
}, },
{ {
id: "12.3", id: "12.2",
propertyName: "onPageChange", propertyName: "onPageChange",
label: "onPageChange", label: "onPageChange",
controlType: "ACTION_SELECTOR", controlType: "ACTION_SELECTOR",

View File

@ -11,10 +11,10 @@ import DynamicTextField from "components/editorComponents/form/fields/DynamicTex
import DropdownField from "components/editorComponents/form/fields/DropdownField"; import DropdownField from "components/editorComponents/form/fields/DropdownField";
import DatasourcesField from "components/editorComponents/form/fields/DatasourcesField"; import DatasourcesField from "components/editorComponents/form/fields/DatasourcesField";
import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray"; import KeyValueFieldArray from "components/editorComponents/form/fields/KeyValueFieldArray";
import JSONEditorField from "components/editorComponents/form/fields/JSONEditorField";
import ApiResponseView from "components/editorComponents/ApiResponseView"; import ApiResponseView from "components/editorComponents/ApiResponseView";
import { API_EDITOR_FORM_NAME } from "constants/forms"; import { API_EDITOR_FORM_NAME } from "constants/forms";
import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen"; import LoadingOverlayScreen from "components/editorComponents/LoadingOverlayScreen";
import { FormIcons } from "icons/FormIcons";
const Form = styled.form` const Form = styled.form`
display: flex; display: flex;
@ -65,8 +65,6 @@ const ActionButton = styled(BaseButton)`
const JSONEditorFieldWrapper = styled.div` const JSONEditorFieldWrapper = styled.div`
margin: 5px; margin: 5px;
border: 1px solid #d0d7dd;
border-radius: 4px;
`; `;
const DatasourceWrapper = styled.div` const DatasourceWrapper = styled.div`
@ -141,10 +139,10 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
<DatasourcesField name="datasource.id" pluginId={pluginId} /> <DatasourcesField name="datasource.id" pluginId={pluginId} />
</DatasourceWrapper> </DatasourceWrapper>
<DynamicTextField <DynamicTextField
placeholder="API Path" placeholder="v1/method"
name="actionConfiguration.path" name="actionConfiguration.path"
leftIcon="slash" leftIcon={FormIcons.SLASH_ICON}
showError initialHeight={32}
/> />
</FormRow> </FormRow>
</MainConfiguration> </MainConfiguration>
@ -162,7 +160,12 @@ const ApiEditorForm: React.FC<Props> = (props: Props) => {
<React.Fragment> <React.Fragment>
<FormLabel>{"Post Body"}</FormLabel> <FormLabel>{"Post Body"}</FormLabel>
<JSONEditorFieldWrapper> <JSONEditorFieldWrapper>
<JSONEditorField name="actionConfiguration.body" /> <DynamicTextField
name="actionConfiguration.body"
initialHeight={300}
showLineNumbers
allowTabIndent
/>
</JSONEditorFieldWrapper> </JSONEditorFieldWrapper>
</React.Fragment> </React.Fragment>
)} )}

View File

@ -1,25 +1,10 @@
import _ from "lodash"; import _ from "lodash";
import { WidgetProps } from "widgets/BaseWidget"; import { WidgetProps } from "widgets/BaseWidget";
import { import { DATA_BIND_REGEX } from "constants/BindingsConstants";
DATA_BIND_AUTOCOMPLETE,
DATA_BIND_REGEX,
} from "constants/BindingsConstants";
import ValidationFactory from "./ValidationFactory"; import ValidationFactory from "./ValidationFactory";
import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton"; import JSExecutionManagerSingleton from "jsExecution/JSExecutionManagerSingleton";
import { NameBindingsWithData } from "selectors/nameBindingsWithDataSelector"; import { NameBindingsWithData } from "selectors/nameBindingsWithDataSelector";
export const isDynamicAutocompleteMatch = (value: string): boolean =>
DATA_BIND_AUTOCOMPLETE.test(value);
export const getDynamicAutocompleteSearchTerm = (value: string): string => {
const bindings = value.match(DATA_BIND_AUTOCOMPLETE) || [];
if (bindings.length > 0) {
return bindings[2];
} else {
return "";
}
};
export const isDynamicValue = (value: string): boolean => export const isDynamicValue = (value: string): boolean =>
DATA_BIND_REGEX.test(value); DATA_BIND_REGEX.test(value);