diff --git a/app/client/src/assets/icons/menu/binding.svg b/app/client/src/assets/icons/menu/binding.svg new file mode 100644 index 0000000000..22692a0acd --- /dev/null +++ b/app/client/src/assets/icons/menu/binding.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/client/src/assets/icons/menu/new-plus.svg b/app/client/src/assets/icons/menu/new-plus.svg new file mode 100644 index 0000000000..5016558969 --- /dev/null +++ b/app/client/src/assets/icons/menu/new-plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/app/client/src/components/editorComponents/CodeEditor/commandsHelper.ts b/app/client/src/components/editorComponents/CodeEditor/commandsHelper.ts index b0d6908898..180e5e579a 100644 --- a/app/client/src/components/editorComponents/CodeEditor/commandsHelper.ts +++ b/app/client/src/components/editorComponents/CodeEditor/commandsHelper.ts @@ -6,6 +6,7 @@ import { generateQuickCommands } from "./generateQuickCommands"; import { Datasource } from "entities/Datasource"; import AnalyticsUtil from "utils/AnalyticsUtil"; import log from "loglevel"; +import { ENTITY_TYPE } from "entities/AppsmithConsole"; export const commandsHelper: HintHelper = (editor, data: any) => { let entitiesForSuggestions = Object.values(data).filter( @@ -19,21 +20,15 @@ export const commandsHelper: HintHelper = (editor, data: any) => { { datasources, executeCommand, - mutedHinting, pluginIdToImageLocation, recentEntities, - updatePropertyValue, + update, }: { - mutedHinting?: boolean; datasources: Datasource[]; executeCommand: (payload: { actionType: string; args?: any }) => void; pluginIdToImageLocation: Record; recentEntities: string[]; - updatePropertyValue: ( - value: string, - cursor?: number, - preventAutoComplete?: boolean, - ) => void; + update: (value: string) => void; }, ): boolean => { const currentEntityType = data[entityName]?.ENTITY_TYPE || "ACTION"; @@ -45,7 +40,8 @@ export const commandsHelper: HintHelper = (editor, data: any) => { const cursorBetweenBinding = checkIfCursorInsideBinding(editor); const value = editor.getValue(); const slashIndex = value.lastIndexOf("/"); - const shouldShowBinding = (!value && !mutedHinting) || slashIndex > -1; + const shouldShowBinding = + slashIndex > -1 || (!value && currentEntityType === ENTITY_TYPE.WIDGET); if (!cursorBetweenBinding && shouldShowBinding) { const searchText = value.substring(slashIndex + 1); const list = generateQuickCommands( @@ -81,16 +77,19 @@ export const commandsHelper: HintHelper = (editor, data: any) => { selectedHint: 1, }; CodeMirror.on(hints, "pick", (selected: CommandsCompletion) => { - const updatedValue = value.slice( - 0, - value.length - searchText.length - 1, - ); - if (selected.action && typeof selected.action === "function") { - updatePropertyValue(updatedValue, updatedValue.length, true); - selected.action(); - } else { - updatePropertyValue(updatedValue + selected.text); - } + update(value.slice(0, slashIndex) + selected.text); + setTimeout(() => { + editor.focus(); + editor.setCursor({ + line: editor.lineCount() - 1, + ch: editor.getLine(editor.lineCount() - 1).length - 2, + }); + if (selected.action && typeof selected.action === "function") { + selected.action(); + } else { + CodeMirror.signal(editor, "postPick"); + } + }); try { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { data, render, ...rest } = selected; diff --git a/app/client/src/components/editorComponents/CodeEditor/generateQuickCommands.tsx b/app/client/src/components/editorComponents/CodeEditor/generateQuickCommands.tsx index 24bf457bb9..e5c36a1deb 100644 --- a/app/client/src/components/editorComponents/CodeEditor/generateQuickCommands.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/generateQuickCommands.tsx @@ -6,7 +6,13 @@ import sortBy from "lodash/sortBy"; import { PluginType } from "entities/Action"; import { ReactComponent as ApisIcon } from "assets/icons/menu/api-colored.svg"; import { ReactComponent as DataSourcesColoredIcon } from "assets/icons/menu/datasource-colored.svg"; +import { ReactComponent as NewPlus } from "assets/icons/menu/new-plus.svg"; +import { ReactComponent as Binding } from "assets/icons/menu/binding.svg"; +enum Shortcuts { + PLUS = "PLUS", + BINDING = "BINDING", +} export const generateQuickCommands = ( entitiesForSuggestions: any[], currentEntityType: string, @@ -25,19 +31,20 @@ export const generateQuickCommands = ( ) => { const suggestionsHeader: CommandsCompletion = commandsHeader("Bind Data"); const createNewHeader: CommandsCompletion = commandsHeader("Create New"); + recentEntities.reverse(); const newBinding: CommandsCompletion = generateCreateNewCommand({ text: "{{}}", displayText: "New Binding", - shortcut: "{{}}", + shortcut: Shortcuts.BINDING, }); - const newDatasource: CommandsCompletion = generateCreateNewCommand({ + const newIntegration: CommandsCompletion = generateCreateNewCommand({ text: "", displayText: "New Datasource", action: () => executeCommand({ - actionType: "NEW_DATASOURCE", + actionType: "NEW_INTEGRATION", }), - shortcut: "datasource.new", + shortcut: Shortcuts.PLUS, }); const suggestions = entitiesForSuggestions.map((suggestion: any) => { const name = suggestion.name || suggestion.widgetName; @@ -45,7 +52,6 @@ export const generateQuickCommands = ( text: currentEntityType === "WIDGET" ? `{{${name}.data}}` : `{{${name}}}`, displayText: `${name}`, className: "CodeMirror-commands", - shortcut: "{{}}", data: suggestion, render: (element: HTMLElement, self: any, data: any) => { const pluginType = data.data.pluginType as PluginType; @@ -65,7 +71,6 @@ export const generateQuickCommands = ( text: "", displayText: `${action.name}`, className: "CodeMirror-commands", - shortcut: `${action.name}.new`, data: action, action: () => executeCommand({ @@ -88,7 +93,7 @@ export const generateQuickCommands = ( suggestions, searchText, recentEntities, - currentEntityType === "WIDGET" ? 2 : 3, + 5, ); suggestionsMatchingSearchText.push( ...matchingCommands([newBinding], searchText, []), @@ -105,7 +110,7 @@ export const generateQuickCommands = ( ); if (currentEntityType === "WIDGET") { createNewCommandsMatchingSearchText.push( - ...matchingCommands([newDatasource], searchText, []), + ...matchingCommands([newIntegration], searchText, []), ); } let list: CommandsCompletion[] = []; @@ -123,13 +128,12 @@ const matchingCommands = ( list: any, searchText: string, recentEntities: string[] = [], - limit = 2, + limit = 5, ) => { list = list.filter((action: any) => { - return ( - action.displayText.toLowerCase().startsWith(searchText.toLowerCase()) || - action.shortcut.toLowerCase().startsWith(searchText.toLowerCase()) - ); + return action.displayText + .toLowerCase() + .startsWith(searchText.toLowerCase()); }); list = sortBy(list, (a: any) => { return ( @@ -171,7 +175,11 @@ const generateCreateNewCommand = ({ action: action, render: (element: HTMLElement, self: any, data: any) => { ReactDOM.render( - , + , element, ); }, @@ -181,7 +189,8 @@ function Command(props: { pluginType?: PluginType; imgSrc?: string; name: string; - shortcut: string; + shortcut: Shortcuts; + customText?: string; }) { return ( @@ -193,9 +202,12 @@ function Command(props: { SAAS: , }[props.pluginType]} {props.imgSrc && } + {props.shortcut && + { [Shortcuts.BINDING]: , [Shortcuts.PLUS]: }[ + props.shortcut + ]} {props.name} - {props.shortcut} ); } diff --git a/app/client/src/components/editorComponents/CodeEditor/index.tsx b/app/client/src/components/editorComponents/CodeEditor/index.tsx index 71d82c6f32..99f02e179c 100644 --- a/app/client/src/components/editorComponents/CodeEditor/index.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/index.tsx @@ -93,7 +93,6 @@ export type EditorStyleProps = { disabled?: boolean; link?: string; showLightningMenu?: boolean; - mutedHinting?: boolean; dataTreePath?: string; evaluatedValue?: any; expected?: string; @@ -227,6 +226,7 @@ class CodeEditor extends Component { editor.on("cursorActivity", this.handleCursorMovement); editor.on("focus", this.onFocusTrigger); editor.on("blur", this.handleEditorBlur); + editor.on("postPick", () => this.handleAutocompleteVisibility(editor)); if (this.props.height) { editor.setSize("100%", this.props.height); } else { @@ -290,11 +290,9 @@ class CodeEditor extends Component { showLightningMenu?: boolean, additionalDynamicData?: Record>, ) { - return (showLightningMenu !== false ? hinting : [bindingHint]).map( - (helper) => { - return helper(editor, dynamicData, additionalDynamicData); - }, - ); + return hinting.map((helper) => { + return helper(editor, dynamicData, additionalDynamicData); + }); } onFocusTrigger = (cm: CodeMirror.Editor) => { @@ -328,10 +326,12 @@ class CodeEditor extends Component { handleEditorFocus = () => { this.setState({ isFocused: true }); if (this.props.size === EditorSize.COMPACT) { - const inputValue = this.props.input.value; - this.editor.setOption("lineWrapping", true); - this.editor.setValue(inputValue); - this.editor.setCursor(inputValue.length); + this.editor.operation(() => { + const inputValue = this.props.input.value; + this.editor.setOption("lineWrapping", true); + this.editor.setValue(inputValue); + this.editor.setCursor(inputValue.length); + }); } if (this.editor.getValue().length === 0) this.handleAutocompleteVisibility(this.editor); @@ -339,12 +339,10 @@ class CodeEditor extends Component { handleEditorBlur = () => { this.handleChange(); - // on blur closing the binding prompt for an editor regardless. this.setState({ isFocused: false }); if (this.props.size === EditorSize.COMPACT) { this.editor.setOption("lineWrapping", false); } - this.editor.setOption("matchBrackets", false); }; @@ -375,7 +373,8 @@ class CodeEditor extends Component { const inputValue = this.props.input.value || ""; if ( this.props.input.onChange && - value !== inputValue && + (value !== inputValue || + _.get(this.editor, "state.completionActive.startLen") === 0) && this.state.isFocused ) { this.props.input.onChange(value); @@ -383,7 +382,8 @@ class CodeEditor extends Component { CodeEditor.updateMarkings(this.editor, this.props.marking); }; - handleAutocompleteVisibility = (cm: CodeMirror.Editor, force?: boolean) => { + handleAutocompleteVisibility = (cm: CodeMirror.Editor) => { + if (!this.state.isFocused) return; const expected = this.props.expected ? this.props.expected : ""; const { entityName } = getEntityNameAndPropertyPath( this.props.dataTreePath || "", @@ -391,17 +391,16 @@ class CodeEditor extends Component { let hinterOpen = false; for (let i = 0; i < this.hinters.length; i++) { hinterOpen = this.hinters[i].showHint(cm, expected, entityName, { - mutedHinting: force ? !force : this.props.mutedHinting, datasources: this.props.datasources.list, pluginIdToImageLocation: this.props.pluginIdToImageLocation, - updatePropertyValue: this.updatePropertyValue.bind(this), recentEntities: this.props.recentEntities, + update: this.props.input.onChange?.bind(this), executeCommand: (payload: any) => { this.props.executeCommand({ ...payload, callback: (binding: string) => { const value = this.editor.getValue() + binding; - this.updatePropertyValue(value); + this.updatePropertyValue(value, value.length); }, }); }, @@ -432,20 +431,13 @@ class CodeEditor extends Component { this.editor.setValue(value); } this.editor.focus(); - if (cursor === undefined) { - if (value) { - cursor = value.length - 2; - } else { - cursor = 1; - } - } this.editor.setCursor({ - line: 0, - ch: cursor, + line: cursor || this.editor.lineCount() - 1, + ch: this.editor.getLine(this.editor.lineCount() - 1).length - 2, }); this.setState({ isFocused: true }, () => { if (preventAutoComplete) return; - this.handleAutocompleteVisibility(this.editor, true); + this.handleAutocompleteVisibility(this.editor); }); } @@ -598,7 +590,10 @@ class CodeEditor extends Component { )} diff --git a/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx b/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx index 957aa985d3..387dc51dad 100644 --- a/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx +++ b/app/client/src/components/editorComponents/form/fields/KeyValueFieldArray.tsx @@ -136,7 +136,6 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) { hoverInteraction name={`${field}.key`} placeholder={`Key ${index + 1}`} - showLightningMenu={false} theme={props.theme} /> @@ -149,7 +148,6 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) { dataTreePath={`${props.dataTreePath}[${index}].value`} expected={FIELD_VALUES.API_ACTION.params} hoverInteraction - mutedHinting name={`${field}.value`} placeholder={`Value ${index + 1}`} theme={props.theme} @@ -169,7 +167,6 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) { ) } expected={FIELD_VALUES.API_ACTION.params} - mutedHinting name={`${field}.value`} placeholder={ props.placeholder diff --git a/app/client/src/components/formControls/DynamicTextFieldControl.tsx b/app/client/src/components/formControls/DynamicTextFieldControl.tsx index b7d3bcef58..ff5a441f4b 100644 --- a/app/client/src/components/formControls/DynamicTextFieldControl.tsx +++ b/app/client/src/components/formControls/DynamicTextFieldControl.tsx @@ -65,7 +65,6 @@ class DynamicTextControl extends BaseControl< configProperty, evaluationSubstitutionType, label, - mutedHinting, placeholderText, responseType, } = this.props; @@ -105,7 +104,6 @@ class DynamicTextControl extends BaseControl< dataTreePath={dataTreePath} evaluationSubstitutionType={evaluationSubstitutionType} mode={mode} - mutedHinting={mutedHinting} name={this.props.configProperty} placeholder={placeholderText} size={EditorSize.EXTENDED} diff --git a/app/client/src/globalStyles/CodmirrorHintStyles.ts b/app/client/src/globalStyles/CodmirrorHintStyles.ts index 6d2231964c..7db244f95f 100644 --- a/app/client/src/globalStyles/CodmirrorHintStyles.ts +++ b/app/client/src/globalStyles/CodmirrorHintStyles.ts @@ -52,6 +52,7 @@ export const CodemirrorHintStyles = createGlobalStyle<{ pointer-events: none !important; font-family: ${(props) => props.theme.fonts.text}; ${(props) => getTypographyByKey(props, "p3")} + font-weight: 600; } .CodeMirror-commands { @@ -86,6 +87,8 @@ export const CodemirrorHintStyles = createGlobalStyle<{ margin-right: 7px; } svg { + height: 12px; + width: 12px; margin-right: 7px; } } diff --git a/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx b/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx index b79361d75b..e1d3b549c6 100644 --- a/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx +++ b/app/client/src/pages/Editor/APIEditor/PostBodyData.tsx @@ -72,7 +72,6 @@ function PostBodyData(props: Props) { dataTreePath={`${dataTreePath}.body`} expected={FIELD_VALUES.API_ACTION.body} mode={EditorModes.JSON_WITH_BINDING} - mutedHinting name="actionConfiguration.body" placeholder={ '{\n "name":"{{ inputName.property }}",\n "preference":"{{ dropdownName.property }}"\n}\n\n\\\\Take widget inputs using {{ }}' diff --git a/app/client/src/pages/Editor/APIEditor/RapidApiEditorForm.tsx b/app/client/src/pages/Editor/APIEditor/RapidApiEditorForm.tsx index ea6a6c6709..382e03c15d 100644 --- a/app/client/src/pages/Editor/APIEditor/RapidApiEditorForm.tsx +++ b/app/client/src/pages/Editor/APIEditor/RapidApiEditorForm.tsx @@ -183,14 +183,12 @@ function RapidApiEditorForm(props: Props) { diff --git a/app/client/src/sagas/ActionSagas.ts b/app/client/src/sagas/ActionSagas.ts index 1695f2a788..3152f1fd5b 100644 --- a/app/client/src/sagas/ActionSagas.ts +++ b/app/client/src/sagas/ActionSagas.ts @@ -749,7 +749,7 @@ function* executeCommand( const pageId = yield select(getCurrentPageId); const applicationId = yield select(getCurrentApplicationId); switch (actionPayload.payload.actionType) { - case "NEW_DATASOURCE": + case "NEW_INTEGRATION": history.push( INTEGRATION_EDITOR_URL(applicationId, pageId, INTEGRATION_TABS.NEW), ); diff --git a/app/client/src/utils/FormControlRegistry.tsx b/app/client/src/utils/FormControlRegistry.tsx index 0579fe2453..c25ae1440d 100644 --- a/app/client/src/utils/FormControlRegistry.tsx +++ b/app/client/src/utils/FormControlRegistry.tsx @@ -76,7 +76,7 @@ class FormControlRegistry { }); FormControlFactory.registerControlBuilder("QUERY_DYNAMIC_TEXT", { buildPropertyControl(controlProps: DynamicTextFieldProps): JSX.Element { - return ; + return ; }, }); FormControlFactory.registerControlBuilder("QUERY_DYNAMIC_INPUT_TEXT", {