2020-05-20 15:44:52 +00:00
|
|
|
import React, { Component, lazy, Suspense } from "react";
|
2019-12-06 13:16:08 +00:00
|
|
|
import { connect } from "react-redux";
|
|
|
|
|
import { AppState } from "reducers";
|
2020-01-02 13:36:35 +00:00
|
|
|
import styled, { createGlobalStyle } from "styled-components";
|
|
|
|
|
import CodeMirror, { EditorConfiguration, LineHandle } from "codemirror";
|
2019-12-30 07:35:16 +00:00
|
|
|
import "codemirror/lib/codemirror.css";
|
|
|
|
|
import "codemirror/theme/monokai.css";
|
|
|
|
|
import "codemirror/addon/hint/show-hint";
|
2020-01-02 13:36:35 +00:00
|
|
|
import "codemirror/addon/display/placeholder";
|
2020-02-04 10:40:55 +00:00
|
|
|
import "codemirror/addon/edit/closebrackets";
|
|
|
|
|
import "codemirror/addon/display/autorefresh";
|
2020-05-05 07:50:30 +00:00
|
|
|
import "codemirror/addon/mode/multiplex";
|
2020-05-20 11:30:53 +00:00
|
|
|
import "codemirror/addon/tern/tern.css";
|
2020-02-18 10:41:52 +00:00
|
|
|
import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors";
|
2020-01-02 13:36:35 +00:00
|
|
|
import { AUTOCOMPLETE_MATCH_REGEX } from "constants/BindingsConstants";
|
|
|
|
|
import ErrorTooltip from "components/editorComponents/ErrorTooltip";
|
2020-04-14 12:34:14 +00:00
|
|
|
import HelperTooltip from "components/editorComponents/HelperTooltip";
|
2020-01-02 13:36:35 +00:00
|
|
|
import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form";
|
|
|
|
|
import _ from "lodash";
|
|
|
|
|
import { parseDynamicString } from "utils/DynamicBindingUtils";
|
2020-02-18 10:41:52 +00:00
|
|
|
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
2020-02-24 12:58:16 +00:00
|
|
|
import { Theme } from "constants/DefaultTheme";
|
2020-03-06 04:59:24 +00:00
|
|
|
import AnalyticsUtil from "utils/AnalyticsUtil";
|
2020-05-20 11:30:53 +00:00
|
|
|
import TernServer from "utils/autocomplete/TernServer";
|
|
|
|
|
import KeyboardShortcuts from "constants/KeyboardShortcuts";
|
2020-05-20 15:44:52 +00:00
|
|
|
const LightningMenu = lazy(() =>
|
|
|
|
|
import("components/editorComponents/LightningMenu"),
|
|
|
|
|
);
|
2019-12-30 07:35:16 +00:00
|
|
|
require("codemirror/mode/javascript/javascript");
|
2020-05-05 07:50:30 +00:00
|
|
|
require("codemirror/mode/sql/sql");
|
|
|
|
|
require("codemirror/addon/hint/sql-hint");
|
|
|
|
|
|
|
|
|
|
CodeMirror.defineMode("sql-js", function(config) {
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
return CodeMirror.multiplexingMode(
|
|
|
|
|
CodeMirror.getMode(config, "text/x-sql"),
|
|
|
|
|
{
|
|
|
|
|
open: "{{",
|
|
|
|
|
close: "}}",
|
|
|
|
|
mode: CodeMirror.getMode(config, {
|
|
|
|
|
name: "javascript",
|
|
|
|
|
globalVars: true,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
// .. more multiplexed styles can follow here
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
CodeMirror.defineMode("js-js", function(config) {
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
return CodeMirror.multiplexingMode(
|
|
|
|
|
CodeMirror.getMode(config, { name: "javascript", json: true }),
|
|
|
|
|
{
|
|
|
|
|
open: "{{",
|
|
|
|
|
close: "}}",
|
|
|
|
|
mode: CodeMirror.getMode(config, {
|
|
|
|
|
name: "javascript",
|
|
|
|
|
globalVars: true,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
// .. more multiplexed styles can follow here
|
|
|
|
|
);
|
|
|
|
|
});
|
2019-12-06 13:16:08 +00:00
|
|
|
|
2020-02-24 12:58:16 +00:00
|
|
|
const getBorderStyle = (
|
|
|
|
|
props: { theme: Theme } & {
|
|
|
|
|
editorTheme?: THEME;
|
|
|
|
|
hasError: boolean;
|
|
|
|
|
singleLine: boolean;
|
|
|
|
|
isFocused: boolean;
|
2020-04-14 12:34:14 +00:00
|
|
|
disabled?: boolean;
|
2020-02-24 12:58:16 +00:00
|
|
|
},
|
|
|
|
|
) => {
|
|
|
|
|
if (props.hasError) return props.theme.colors.error;
|
|
|
|
|
if (props.editorTheme !== THEMES.DARK) {
|
|
|
|
|
if (props.isFocused) return props.theme.colors.inputActiveBorder;
|
|
|
|
|
return props.theme.colors.border;
|
|
|
|
|
}
|
|
|
|
|
return "transparent";
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-02 13:36:35 +00:00
|
|
|
const HintStyles = createGlobalStyle`
|
|
|
|
|
.CodeMirror-hints {
|
|
|
|
|
position: absolute;
|
2020-03-27 10:37:29 +00:00
|
|
|
z-index: 20;
|
2020-01-02 13:36:35 +00:00
|
|
|
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;
|
|
|
|
|
}
|
2020-05-20 11:30:53 +00:00
|
|
|
.CodeMirror-Tern-completion {
|
|
|
|
|
padding-left: 22px !important;
|
|
|
|
|
}
|
|
|
|
|
.CodeMirror-Tern-completion:before {
|
|
|
|
|
left: 4px !important;
|
|
|
|
|
bottom: 7px !important;
|
|
|
|
|
line-height: 15px !important;
|
|
|
|
|
}
|
2020-01-02 13:36:35 +00:00
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const Wrapper = styled.div<{
|
2020-02-24 12:58:16 +00:00
|
|
|
editorTheme?: THEME;
|
2020-01-02 13:36:35 +00:00
|
|
|
hasError: boolean;
|
2020-02-21 07:57:28 +00:00
|
|
|
singleLine: boolean;
|
2020-02-24 12:58:16 +00:00
|
|
|
isFocused: boolean;
|
2020-04-14 12:34:14 +00:00
|
|
|
disabled?: boolean;
|
2020-05-07 11:00:26 +00:00
|
|
|
setMaxHeight?: boolean;
|
2020-01-02 13:36:35 +00:00
|
|
|
}>`
|
2020-02-24 12:58:16 +00:00
|
|
|
${props =>
|
|
|
|
|
props.singleLine && props.isFocused
|
|
|
|
|
? `
|
2020-03-03 06:51:59 +00:00
|
|
|
z-index: 5;
|
2020-02-24 12:58:16 +00:00
|
|
|
position: absolute;
|
|
|
|
|
right: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
top: 0;
|
|
|
|
|
`
|
|
|
|
|
: `z-index: 0; position: relative`}
|
|
|
|
|
background-color: ${props =>
|
2020-04-14 12:34:14 +00:00
|
|
|
props.editorTheme === THEMES.DARK ? "#272822" : "#fff"};
|
|
|
|
|
background-color: ${props => props.disabled && "#eef2f5"};
|
2020-01-02 13:36:35 +00:00
|
|
|
border: 1px solid;
|
2020-02-24 12:58:16 +00:00
|
|
|
border-color: ${getBorderStyle};
|
2019-12-30 07:35:16 +00:00
|
|
|
border-radius: 4px;
|
2019-12-06 13:16:08 +00:00
|
|
|
display: flex;
|
|
|
|
|
flex: 1;
|
2020-01-02 13:36:35 +00:00
|
|
|
flex-direction: row;
|
2019-12-06 13:16:08 +00:00
|
|
|
text-transform: none;
|
2019-12-30 07:35:16 +00:00
|
|
|
min-height: 32px;
|
2020-01-02 13:36:35 +00:00
|
|
|
overflow: hidden;
|
|
|
|
|
height: auto;
|
2020-05-07 11:00:26 +00:00
|
|
|
${props =>
|
|
|
|
|
props.setMaxHeight &&
|
|
|
|
|
props.isFocused &&
|
|
|
|
|
`
|
|
|
|
|
z-index: 5;
|
|
|
|
|
position: absolute;
|
|
|
|
|
right: 0;
|
|
|
|
|
left: 0;
|
|
|
|
|
top: 0;
|
|
|
|
|
`}
|
|
|
|
|
${props => props.setMaxHeight && !props.isFocused && `max-height: 30px;`}
|
2020-01-02 13:36:35 +00:00
|
|
|
&& {
|
|
|
|
|
.binding-highlight {
|
|
|
|
|
color: ${props =>
|
2020-02-24 12:58:16 +00:00
|
|
|
props.editorTheme === THEMES.DARK ? "#f7c75b" : "#ffb100"};
|
2020-01-02 13:36:35 +00:00
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
.CodeMirror {
|
|
|
|
|
flex: 1;
|
|
|
|
|
line-height: 21px;
|
|
|
|
|
z-index: 0;
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
height: auto;
|
|
|
|
|
}
|
2020-04-14 12:34:14 +00:00
|
|
|
${props =>
|
|
|
|
|
props.disabled &&
|
|
|
|
|
`
|
|
|
|
|
.CodeMirror-cursor {
|
|
|
|
|
display: none !important;
|
|
|
|
|
}
|
|
|
|
|
`}
|
2020-01-02 13:36:35 +00:00
|
|
|
.CodeMirror pre.CodeMirror-placeholder {
|
|
|
|
|
color: #a3b3bf;
|
|
|
|
|
}
|
2020-02-21 07:57:28 +00:00
|
|
|
${props =>
|
|
|
|
|
props.singleLine &&
|
|
|
|
|
`
|
2020-02-24 12:58:16 +00:00
|
|
|
.CodeMirror-hscrollbar {
|
2020-02-21 07:57:28 +00:00
|
|
|
-ms-overflow-style: none;
|
|
|
|
|
&::-webkit-scrollbar {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
`}
|
2020-01-02 13:36:35 +00:00
|
|
|
}
|
2020-04-14 12:34:14 +00:00
|
|
|
&& {
|
|
|
|
|
.CodeMirror-lines {
|
|
|
|
|
background-color: ${props => props.disabled && "#eef2f5"};
|
|
|
|
|
cursor: ${props => (props.disabled ? "not-allowed" : "text")}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.bp3-popover-target {
|
|
|
|
|
padding-right: 10px;
|
|
|
|
|
padding-top: 5px;
|
|
|
|
|
}
|
|
|
|
|
.leftImageStyles {
|
|
|
|
|
width: 20px;
|
|
|
|
|
height: 20px;
|
|
|
|
|
margin: 5px;
|
|
|
|
|
}
|
|
|
|
|
.linkStyles {
|
|
|
|
|
margin: 5px;
|
|
|
|
|
margin-right: 11px;
|
|
|
|
|
}
|
2020-01-02 13:36:35 +00:00
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const IconContainer = styled.div`
|
|
|
|
|
.bp3-icon {
|
|
|
|
|
border-radius: 4px 0 0 4px;
|
|
|
|
|
margin: 0;
|
2020-01-08 09:19:00 +00:00
|
|
|
height: 30px;
|
2020-01-02 13:36:35 +00:00
|
|
|
width: 30px;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
background-color: #eef2f5;
|
|
|
|
|
svg {
|
|
|
|
|
height: 20px;
|
|
|
|
|
width: 20px;
|
|
|
|
|
path {
|
|
|
|
|
fill: #979797;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-04-14 12:34:14 +00:00
|
|
|
.bp3-popover-target {
|
|
|
|
|
padding-right: 10px;
|
|
|
|
|
}
|
2019-12-06 13:16:08 +00:00
|
|
|
`;
|
|
|
|
|
|
2020-05-20 15:44:52 +00:00
|
|
|
const DynamicAutocompleteInputWrapper = styled.div`
|
|
|
|
|
width: 100%;
|
2020-05-21 04:22:57 +00:00
|
|
|
height: 100%;
|
|
|
|
|
flex: 1;
|
2020-05-20 15:44:52 +00:00
|
|
|
position: relative;
|
|
|
|
|
& > span:first-of-type {
|
|
|
|
|
position: absolute;
|
|
|
|
|
right: 0;
|
|
|
|
|
top: 2px;
|
|
|
|
|
width: 14px;
|
|
|
|
|
z-index: 10;
|
|
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
2020-01-02 13:36:35 +00:00
|
|
|
const THEMES = {
|
|
|
|
|
LIGHT: "LIGHT",
|
|
|
|
|
DARK: "DARK",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
type THEME = "LIGHT" | "DARK";
|
|
|
|
|
|
2020-05-04 09:03:41 +00:00
|
|
|
const AUTOCOMPLETE_CLOSE_KEY_CODES = ["Enter", "Tab", "Escape"];
|
|
|
|
|
|
2019-12-06 13:16:08 +00:00
|
|
|
interface ReduxStateProps {
|
2020-02-18 10:41:52 +00:00
|
|
|
dynamicData: DataTree;
|
2019-12-06 13:16:08 +00:00
|
|
|
}
|
|
|
|
|
|
2020-01-02 13:36:35 +00:00
|
|
|
export type DynamicAutocompleteInputProps = {
|
|
|
|
|
placeholder?: string;
|
|
|
|
|
leftIcon?: Function;
|
2020-04-14 12:34:14 +00:00
|
|
|
rightIcon?: Function;
|
|
|
|
|
description?: string;
|
2020-01-06 06:32:10 +00:00
|
|
|
height?: number;
|
2020-01-02 13:36:35 +00:00
|
|
|
theme?: THEME;
|
|
|
|
|
meta?: Partial<WrappedFieldMetaProps>;
|
|
|
|
|
showLineNumbers?: boolean;
|
|
|
|
|
allowTabIndent?: boolean;
|
2020-02-21 07:57:28 +00:00
|
|
|
singleLine: boolean;
|
2020-05-05 07:50:30 +00:00
|
|
|
mode?: string | object;
|
|
|
|
|
className?: string;
|
2020-04-14 12:34:14 +00:00
|
|
|
leftImage?: string;
|
2020-05-05 07:50:30 +00:00
|
|
|
disabled?: boolean;
|
2020-04-14 12:34:14 +00:00
|
|
|
link?: string;
|
2020-05-05 07:50:30 +00:00
|
|
|
baseMode?: string | object;
|
2020-05-07 11:00:26 +00:00
|
|
|
setMaxHeight?: boolean;
|
2020-05-20 15:44:52 +00:00
|
|
|
showLightningMenu?: boolean;
|
2019-12-06 13:16:08 +00:00
|
|
|
};
|
|
|
|
|
|
2020-01-02 13:36:35 +00:00
|
|
|
type Props = ReduxStateProps &
|
|
|
|
|
DynamicAutocompleteInputProps & {
|
|
|
|
|
input: Partial<WrappedFieldInputProps>;
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-27 13:53:33 +00:00
|
|
|
type State = {
|
|
|
|
|
isFocused: boolean;
|
2020-04-03 05:15:57 +00:00
|
|
|
autoCompleteVisible: boolean;
|
2020-01-27 13:53:33 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
class DynamicAutocompleteInput extends Component<Props, State> {
|
2019-12-30 07:35:16 +00:00
|
|
|
textArea = React.createRef<HTMLTextAreaElement>();
|
|
|
|
|
editor: any;
|
2020-05-20 11:30:53 +00:00
|
|
|
ternServer?: TernServer = undefined;
|
2019-12-30 07:35:16 +00:00
|
|
|
|
2020-01-27 13:53:33 +00:00
|
|
|
constructor(props: Props) {
|
|
|
|
|
super(props);
|
|
|
|
|
this.state = {
|
|
|
|
|
isFocused: false,
|
2020-04-03 05:15:57 +00:00
|
|
|
autoCompleteVisible: false,
|
2020-01-27 13:53:33 +00:00
|
|
|
};
|
2020-05-22 10:16:53 +00:00
|
|
|
this.updatePropertyValue = this.updatePropertyValue.bind(this);
|
2020-01-27 13:53:33 +00:00
|
|
|
}
|
|
|
|
|
|
2019-12-06 13:16:08 +00:00
|
|
|
componentDidMount(): void {
|
2020-05-19 17:24:38 +00:00
|
|
|
if (this.textArea.current) {
|
2019-12-30 07:35:16 +00:00
|
|
|
const options: EditorConfiguration = {};
|
2020-05-20 15:44:52 +00:00
|
|
|
//use this for lightning menu theme
|
2019-12-30 07:35:16 +00:00
|
|
|
if (this.props.theme === "DARK") options.theme = "monokai";
|
2020-04-14 12:34:14 +00:00
|
|
|
if (!this.props.input.onChange || this.props.disabled) {
|
|
|
|
|
options.readOnly = true;
|
|
|
|
|
options.scrollbarStyle = "null";
|
|
|
|
|
}
|
2020-01-02 13:36:35 +00:00
|
|
|
if (this.props.showLineNumbers) options.lineNumbers = true;
|
2020-05-20 11:30:53 +00:00
|
|
|
const extraKeys: Record<string, any> = {};
|
2020-01-02 13:36:35 +00:00
|
|
|
if (!this.props.allowTabIndent) extraKeys["Tab"] = false;
|
2020-05-19 17:24:38 +00:00
|
|
|
this.editor = CodeMirror.fromTextArea(this.textArea.current, {
|
2020-05-05 07:50:30 +00:00
|
|
|
mode: this.props.mode || { name: "javascript", globalVars: true },
|
2020-01-02 13:36:35 +00:00
|
|
|
viewportMargin: 10,
|
2019-12-30 07:35:16 +00:00
|
|
|
tabSize: 2,
|
|
|
|
|
indentWithTabs: true,
|
2020-02-24 12:58:16 +00:00
|
|
|
lineWrapping: !this.props.singleLine,
|
2020-01-02 13:36:35 +00:00
|
|
|
extraKeys,
|
2020-02-04 10:40:55 +00:00
|
|
|
autoCloseBrackets: true,
|
2019-12-30 07:35:16 +00:00
|
|
|
...options,
|
|
|
|
|
});
|
2020-05-05 07:50:30 +00:00
|
|
|
|
2020-02-20 15:03:14 +00:00
|
|
|
this.editor.on("change", _.debounce(this.handleChange, 300));
|
2020-05-08 06:05:03 +00:00
|
|
|
this.editor.on("keyup", this.handleAutocompleteHide);
|
2020-02-24 12:58:16 +00:00
|
|
|
this.editor.on("focus", this.handleEditorFocus);
|
|
|
|
|
this.editor.on("blur", this.handleEditorBlur);
|
2020-01-06 06:32:10 +00:00
|
|
|
if (this.props.height) {
|
|
|
|
|
this.editor.setSize(0, this.props.height);
|
2020-02-20 15:03:14 +00:00
|
|
|
} else {
|
|
|
|
|
this.editor.setSize(0, "auto");
|
2020-01-06 06:32:10 +00:00
|
|
|
}
|
2020-01-02 13:36:35 +00:00
|
|
|
this.editor.eachLine(this.highlightBindings);
|
2020-02-04 10:11:33 +00:00
|
|
|
// Set value of the editor
|
2020-02-04 10:40:55 +00:00
|
|
|
let inputValue = this.props.input.value || "";
|
2020-02-04 10:11:33 +00:00
|
|
|
if (typeof inputValue === "object") {
|
|
|
|
|
inputValue = JSON.stringify(inputValue, null, 2);
|
|
|
|
|
}
|
|
|
|
|
this.editor.setValue(inputValue);
|
2020-05-20 11:30:53 +00:00
|
|
|
this.startAutocomplete();
|
2019-12-06 13:16:08 +00:00
|
|
|
}
|
2019-12-30 07:35:16 +00:00
|
|
|
}
|
2019-12-06 13:16:08 +00:00
|
|
|
|
2020-01-27 13:53:33 +00:00
|
|
|
componentDidUpdate(prevProps: Props): void {
|
2019-12-30 07:35:16 +00:00
|
|
|
if (this.editor) {
|
2020-03-03 06:51:59 +00:00
|
|
|
this.editor.refresh();
|
2020-02-24 12:58:16 +00:00
|
|
|
if (!this.state.isFocused) {
|
2020-05-05 07:50:30 +00:00
|
|
|
const currentMode = this.editor.getOption("mode");
|
2020-02-24 12:58:16 +00:00
|
|
|
const editorValue = this.editor.getValue();
|
|
|
|
|
let inputValue = this.props.input.value;
|
|
|
|
|
// Safe update of value of the editor when value updated outside the editor
|
|
|
|
|
if (typeof inputValue === "object") {
|
|
|
|
|
inputValue = JSON.stringify(inputValue, null, 2);
|
|
|
|
|
}
|
|
|
|
|
if ((!!inputValue || inputValue === "") && inputValue !== editorValue) {
|
|
|
|
|
this.editor.setValue(inputValue);
|
|
|
|
|
}
|
2020-05-05 07:50:30 +00:00
|
|
|
|
|
|
|
|
if (currentMode !== this.props.mode) {
|
|
|
|
|
this.editor.setOption("mode", this.props?.mode);
|
|
|
|
|
}
|
2020-02-24 12:58:16 +00:00
|
|
|
} else {
|
|
|
|
|
// Update the dynamic bindings for autocomplete
|
|
|
|
|
if (prevProps.dynamicData !== this.props.dynamicData) {
|
2020-05-20 11:30:53 +00:00
|
|
|
if (this.ternServer) {
|
|
|
|
|
// const dataTreeDef = dataTreeTypeDefCreator(this.props.dynamicData);
|
|
|
|
|
// this.ternServer.updateDef("dataTree", dataTreeDef);
|
|
|
|
|
} else {
|
|
|
|
|
this.editor.setOption("hintOptions", {
|
|
|
|
|
completeSingle: false,
|
|
|
|
|
globalScope: this.props.dynamicData,
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-02-24 12:58:16 +00:00
|
|
|
}
|
2020-01-27 13:53:33 +00:00
|
|
|
}
|
2019-12-06 13:16:08 +00:00
|
|
|
}
|
2019-12-30 07:35:16 +00:00
|
|
|
}
|
|
|
|
|
|
2020-05-20 11:30:53 +00:00
|
|
|
startAutocomplete() {
|
|
|
|
|
try {
|
|
|
|
|
this.ternServer = new TernServer(this.props.dynamicData);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e);
|
|
|
|
|
}
|
|
|
|
|
if (this.ternServer) {
|
|
|
|
|
this.editor.setOption("extraKeys", {
|
|
|
|
|
[KeyboardShortcuts.CodeEditor.OpenAutocomplete]: (
|
|
|
|
|
cm: CodeMirror.Editor,
|
|
|
|
|
) => {
|
|
|
|
|
if (this.ternServer) this.ternServer.complete(cm);
|
|
|
|
|
},
|
|
|
|
|
[KeyboardShortcuts.CodeEditor.ShowTypeAndInfo]: (cm: any) => {
|
|
|
|
|
if (this.ternServer) this.ternServer.showType(cm);
|
|
|
|
|
},
|
|
|
|
|
[KeyboardShortcuts.CodeEditor.OpenDocsLink]: (cm: any) => {
|
|
|
|
|
if (this.ternServer) this.ternServer.showDocs(cm);
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
// start normal autocomplete
|
|
|
|
|
this.editor.setOption("extraKeys", {
|
|
|
|
|
[KeyboardShortcuts.CodeEditor.OpenAutocomplete]: "autocomplete",
|
|
|
|
|
});
|
|
|
|
|
this.editor.setOption("showHint", true);
|
|
|
|
|
this.editor.setOption("hintOptions", {
|
|
|
|
|
completeSingle: false,
|
|
|
|
|
globalScope: this.props.dynamicData,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
this.editor.on("cursorActivity", this.handleAutocompleteVisibility);
|
|
|
|
|
}
|
|
|
|
|
|
2020-02-24 12:58:16 +00:00
|
|
|
handleEditorFocus = () => {
|
|
|
|
|
this.setState({ isFocused: true });
|
|
|
|
|
this.editor.refresh();
|
|
|
|
|
if (this.props.singleLine) {
|
|
|
|
|
this.editor.setOption("lineWrapping", true);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
handleEditorBlur = () => {
|
2020-03-03 06:51:59 +00:00
|
|
|
this.handleChange();
|
2020-02-24 12:58:16 +00:00
|
|
|
this.setState({ isFocused: false });
|
|
|
|
|
if (this.props.singleLine) {
|
|
|
|
|
this.editor.setOption("lineWrapping", false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2020-03-06 04:59:24 +00:00
|
|
|
handleChange = (instance?: any, changeObj?: any) => {
|
2019-12-30 07:35:16 +00:00
|
|
|
const value = this.editor.getValue();
|
2020-03-06 04:59:24 +00:00
|
|
|
if (changeObj && changeObj.origin === "complete") {
|
|
|
|
|
AnalyticsUtil.logEvent("AUTO_COMPLETE_SELECT", {
|
|
|
|
|
searchString: changeObj.text[0],
|
|
|
|
|
});
|
|
|
|
|
}
|
2020-01-08 09:19:00 +00:00
|
|
|
const inputValue = this.props.input.value;
|
|
|
|
|
if (this.props.input.onChange && value !== inputValue) {
|
2019-12-30 07:35:16 +00:00
|
|
|
this.props.input.onChange(value);
|
2019-12-06 13:16:08 +00:00
|
|
|
}
|
2020-01-02 13:36:35 +00:00
|
|
|
this.editor.eachLine(this.highlightBindings);
|
2019-12-06 13:16:08 +00:00
|
|
|
};
|
|
|
|
|
|
2020-05-08 06:05:03 +00:00
|
|
|
handleAutocompleteVisibility = (cm: any) => {
|
2020-02-03 11:49:20 +00:00
|
|
|
if (this.state.isFocused) {
|
|
|
|
|
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;
|
|
|
|
|
});
|
2020-05-04 09:03:41 +00:00
|
|
|
|
2020-05-20 11:30:53 +00:00
|
|
|
const shouldShow = cursorBetweenBinding;
|
2020-05-04 09:03:41 +00:00
|
|
|
|
2020-05-05 07:50:30 +00:00
|
|
|
if (this.props.baseMode) {
|
|
|
|
|
// https://github.com/codemirror/CodeMirror/issues/5249#issue-295565980
|
|
|
|
|
cm.doc.modeOption = this.props.baseMode;
|
|
|
|
|
}
|
2020-02-03 11:49:20 +00:00
|
|
|
if (shouldShow) {
|
2020-03-06 04:59:24 +00:00
|
|
|
AnalyticsUtil.logEvent("AUTO_COMPELTE_SHOW", {});
|
2020-04-03 05:15:57 +00:00
|
|
|
this.setState({
|
|
|
|
|
autoCompleteVisible: true,
|
|
|
|
|
});
|
2020-05-20 11:30:53 +00:00
|
|
|
if (this.ternServer) {
|
|
|
|
|
this.ternServer.complete(cm);
|
|
|
|
|
} else {
|
|
|
|
|
cm.showHint(cm);
|
|
|
|
|
}
|
2020-04-03 05:15:57 +00:00
|
|
|
} else {
|
|
|
|
|
this.setState({
|
|
|
|
|
autoCompleteVisible: false,
|
|
|
|
|
});
|
2020-05-20 11:30:53 +00:00
|
|
|
cm.closeHint();
|
2020-01-02 13:36:35 +00:00
|
|
|
}
|
2019-12-06 13:16:08 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2020-05-08 06:05:03 +00:00
|
|
|
handleAutocompleteHide = (cm: any, event: KeyboardEvent) => {
|
|
|
|
|
if (AUTOCOMPLETE_CLOSE_KEY_CODES.includes(event.code)) {
|
|
|
|
|
cm.closeHint();
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2020-01-02 13:36:35 +00:00
|
|
|
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",
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2020-05-22 10:20:10 +00:00
|
|
|
updatePropertyValue(value: string, cursor?: number) {
|
2020-05-20 15:44:52 +00:00
|
|
|
this.editor.setValue(value);
|
|
|
|
|
this.editor.focus();
|
2020-05-22 07:29:15 +00:00
|
|
|
if (cursor === undefined) {
|
|
|
|
|
cursor = value.length - 2;
|
|
|
|
|
}
|
2020-05-20 15:44:52 +00:00
|
|
|
this.editor.setCursor({
|
2020-05-21 14:06:17 +00:00
|
|
|
line: 0,
|
2020-05-20 15:44:52 +00:00
|
|
|
ch: cursor,
|
|
|
|
|
});
|
2020-05-22 10:16:53 +00:00
|
|
|
}
|
2020-05-20 15:44:52 +00:00
|
|
|
|
2019-12-06 13:16:08 +00:00
|
|
|
render() {
|
2020-05-07 11:00:26 +00:00
|
|
|
const {
|
|
|
|
|
input,
|
|
|
|
|
meta,
|
|
|
|
|
theme,
|
|
|
|
|
singleLine,
|
|
|
|
|
disabled,
|
|
|
|
|
className,
|
|
|
|
|
setMaxHeight,
|
2020-05-20 15:44:52 +00:00
|
|
|
showLightningMenu,
|
2020-05-07 11:00:26 +00:00
|
|
|
} = this.props;
|
2020-01-02 13:36:35 +00:00
|
|
|
const hasError = !!(meta && meta.error);
|
2020-01-03 13:40:31 +00:00
|
|
|
let showError = false;
|
|
|
|
|
if (this.editor) {
|
2020-04-03 05:15:57 +00:00
|
|
|
showError =
|
|
|
|
|
hasError && this.state.isFocused && !this.state.autoCompleteVisible;
|
2020-01-03 13:40:31 +00:00
|
|
|
}
|
2020-05-21 04:22:57 +00:00
|
|
|
const hideLightningMenu = false;
|
2019-12-06 13:16:08 +00:00
|
|
|
return (
|
2020-05-20 15:44:52 +00:00
|
|
|
<DynamicAutocompleteInputWrapper>
|
2020-05-21 04:22:57 +00:00
|
|
|
{!hideLightningMenu &&
|
|
|
|
|
(showLightningMenu === undefined || showLightningMenu === true) && (
|
|
|
|
|
<LightningMenu
|
2020-05-22 05:29:54 +00:00
|
|
|
themeType={this.props.theme === "DARK" ? "dark" : "light"}
|
2020-05-21 04:22:57 +00:00
|
|
|
updatePropertyValue={this.updatePropertyValue}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
2020-05-20 15:44:52 +00:00
|
|
|
<ErrorTooltip message={meta ? meta.error : ""} isOpen={showError}>
|
|
|
|
|
<Wrapper
|
|
|
|
|
editorTheme={theme}
|
|
|
|
|
hasError={hasError}
|
|
|
|
|
singleLine={singleLine}
|
|
|
|
|
isFocused={this.state.isFocused}
|
|
|
|
|
disabled={disabled}
|
|
|
|
|
className={className}
|
|
|
|
|
setMaxHeight={setMaxHeight}
|
|
|
|
|
>
|
|
|
|
|
<HintStyles />
|
|
|
|
|
<IconContainer>
|
|
|
|
|
{this.props.leftIcon && <this.props.leftIcon />}
|
|
|
|
|
</IconContainer>
|
|
|
|
|
|
|
|
|
|
{this.props.leftImage && (
|
|
|
|
|
<img
|
|
|
|
|
src={this.props.leftImage}
|
|
|
|
|
alt="img"
|
|
|
|
|
className="leftImageStyles"
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<textarea
|
|
|
|
|
ref={this.textArea}
|
|
|
|
|
{..._.omit(this.props.input, ["onChange", "value"])}
|
|
|
|
|
defaultValue={input.value}
|
|
|
|
|
placeholder={this.props.placeholder}
|
2020-04-14 12:34:14 +00:00
|
|
|
/>
|
2020-05-20 15:44:52 +00:00
|
|
|
{this.props.link && (
|
|
|
|
|
<React.Fragment>
|
|
|
|
|
<a
|
|
|
|
|
href={this.props.link}
|
|
|
|
|
target="_blank"
|
|
|
|
|
className="linkStyles"
|
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
|
>
|
|
|
|
|
API documentation
|
|
|
|
|
</a>
|
|
|
|
|
</React.Fragment>
|
|
|
|
|
)}
|
|
|
|
|
{this.props.rightIcon && (
|
|
|
|
|
<HelperTooltip
|
|
|
|
|
description={this.props.description}
|
|
|
|
|
rightIcon={this.props.rightIcon}
|
|
|
|
|
/>
|
|
|
|
|
)}
|
|
|
|
|
</Wrapper>
|
|
|
|
|
</ErrorTooltip>
|
|
|
|
|
</DynamicAutocompleteInputWrapper>
|
2019-12-06 13:16:08 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const mapStateToProps = (state: AppState): ReduxStateProps => ({
|
2020-02-18 10:41:52 +00:00
|
|
|
dynamicData: getDataTreeForAutocomplete(state),
|
2019-12-06 13:16:08 +00:00
|
|
|
});
|
|
|
|
|
|
2020-05-19 17:24:38 +00:00
|
|
|
export default connect(mapStateToProps)(DynamicAutocompleteInput);
|