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:
commit
53c039e206
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,144 @@
|
||||||
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;
|
||||||
|
meta?: Partial<WrappedFieldMetaProps>;
|
||||||
|
showLineNumbers?: boolean;
|
||||||
|
allowTabIndent?: boolean;
|
||||||
};
|
};
|
||||||
theme?: "LIGHT" | "DARK";
|
|
||||||
|
type Props = ReduxStateProps &
|
||||||
|
DynamicAutocompleteInputProps & {
|
||||||
|
input: Partial<WrappedFieldInputProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
class DynamicAutocompleteInput extends Component<Props> {
|
class DynamicAutocompleteInput extends Component<Props> {
|
||||||
|
|
@ -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}>
|
||||||
|
<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>
|
</Wrapper>
|
||||||
|
</ErrorTooltip>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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"}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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} />;
|
||||||
|
|
|
||||||
|
|
@ -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} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user