feat(editor): Inline expression editor (#4814)

* WIP

* 🔥 Remove unneeded watch

*  Further setup

*  Fix import

*  Minor tweaks

* 🔥 Remove logging

* 🎨 Add some styling

* 🎨 More styling changes

* 🐛 Fix wrong marking of stale data

* 🎨 Prevent fx on dragging

* 🔥 Remove logging

*  Refine draggable target offsets

* refactor(editor): Consolidate expression management logic (#4836)

*  Extract `ExpressionFunctionIcon`

*  Simplify syntax

*  Move to mixin

* 🎨 Format

* 📘 Unify types

*  Dedup double brace handler

*  Consolidate resolvable highlighter

* 🎨 Format

*  Consolidate language pack

* ✏️ Add comment

*  Move completions to plugins

*  Partially deduplicate themes

* refactor(editor): Apply styling feedback to inline expression editor (#4846)

* 🎨 Adjust styling for expression parameter input

* 🎨 Style outputs differently

*  Set single line for RLC

* 🎨 Style both openers identically

* 🐛 Prevent defocus on resize

*  Adjust line height

* 🎨 Adjust border with for expression input

*  Fix font family for inline output

*  Set up telemetry

*  Complete telemetry

*  Simplify event source

*  Set monospaced font for inline output

* 🎨 Hide cursor on schema pill drop

* 🧪 Update snapshots

*  Consolidate editor styles

* ✏️ Add tech debt comments

*  Improve naming

*  Improve inside resolvable detection

*  Improve var naming

* 🔥 Remove outdated comment

* 🚚 Move constant to data

* ✏️ Clarify comments

* 🔥 Remove outdated comments

* 🔥 Remove unneeded try-catch

* 🔥 Remove unneeded method

* 🔥 Remove unneeded check

* 🔥 Remove `openExpression` check

* 🔥 Remove unused timeout

* 🔥 Remove commented out sections

*  Use Pinia naming convention

*  Re-evaluate on change of `ndvInputData`

* 🐛 Fix handling of `0` in number-type input

* 🐛 Surface focus and blur for mapping hints

* 🔥 Remove logging

* ✏️ Reword error

*  Change kebab-case to PascalCase

*  Refactor state fields for clarity

*  Support double bracing on selection

* 🎨 More styling

*  Miscellaneous cleanup

*  Disregard error on drop

* 🎨 Fix schema pill styling

* 🎨 More `background` to `background-color` fixes

* 🧪 Update snapshots

* 🎨 Replace non-existing var with white

* 🧪 Update snapshot

* 📦 Integrate `codemirror-lang-n8n-expression`

* 🎨 Fix formatting

* 🧪 Re-update test snapshots

* 🧪 Update selectors for inline editor

* 🔥 Remove unused test ID

* 📘 Add type for `currentNodePaneType`

*  Refactor mixin to util

*  Use `:global`

* 🔥 Remove comment

*  Add watch

*  Change import style

* 👕 Fix lint

*  Refactor preventing blur on resize

* 🔥 Remove comment

* 🧪 Re-update snapshots

* 🎨 Prettify

* 👕 Fix lint

* 🔥 Remove comment

Co-authored-by: Mutasem <mutdmour@gmail.com>
This commit is contained in:
Iván Ovejero
2022-12-14 14:43:02 +01:00
committed by GitHub
parent f73267ffa5
commit a1259898c0
36 changed files with 1285 additions and 593 deletions

View File

@@ -12,11 +12,7 @@
@closeDialog="closeExpressionEditDialog"
@valueChanged="expressionUpdated"
></expression-edit>
<div
class="parameter-input ignore-key-press"
:style="parameterInputWrapperStyle"
@click="openExpressionEdit"
>
<div class="parameter-input ignore-key-press" :style="parameterInputWrapperStyle">
<resource-locator
v-if="isResourceLocatorParameter"
ref="resourceLocator"
@@ -32,19 +28,21 @@
:node="node"
:path="path"
@input="valueChanged"
@modalOpenerClick="openExpressionEditorModal"
@focus="setFocus"
@blur="onBlur"
@drop="onResourceLocatorDrop"
/>
<n8n-input
v-else-if="isValueExpression || droppable || forceShowExpression"
:size="inputSize"
:type="getStringInputType"
:rows="getArgument('rows')"
<ExpressionParameterInput
v-else-if="isValueExpression || forceShowExpression"
:value="expressionDisplayValue"
:title="displayTitle"
:readOnly="isReadOnly"
@keydown.stop
:isReadOnly="isReadOnly"
@valueChanged="expressionUpdated"
@modalOpenerClick="openExpressionEditorModal"
@focus="setFocus"
@blur="onBlur"
ref="inputField"
/>
<div
v-else-if="
@@ -99,6 +97,7 @@
v-else
v-model="tempValue"
ref="inputField"
class="input-with-opener"
:size="inputSize"
:type="getStringInputType"
:rows="getArgument('rows')"
@@ -113,15 +112,19 @@
:placeholder="getPlaceholder()"
>
<template #suffix>
<div class="expand-input-icon-container">
<font-awesome-icon
v-if="!isReadOnly"
icon="expand-alt"
class="edit-window-button clickable"
:title="$locale.baseText('parameterInput.openEditWindow')"
@click="displayEditDialog()"
/>
</div>
<n8n-icon
v-if="!isReadOnly"
icon="external-link-alt"
size="xsmall"
class="edit-window-button textarea-modal-opener"
:class="{
focused: isFocused,
invalid: !isFocused && getIssues.length > 0 && !isValueExpression,
}"
:title="$locale.baseText('parameterInput.openEditWindow')"
@click="displayEditDialog()"
@focus="setFocus"
/>
</template>
</n8n-input>
</div>
@@ -329,6 +332,7 @@ import ScopesNotice from '@/components/ScopesNotice.vue';
import ParameterOptions from '@/components/ParameterOptions.vue';
import ParameterIssues from '@/components/ParameterIssues.vue';
import ResourceLocator from '@/components/ResourceLocator/ResourceLocator.vue';
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
// @ts-ignore
import PrismEditor from 'vue-prism-editor';
import TextEdit from '@/components/TextEdit.vue';
@@ -362,6 +366,7 @@ export default mixins(
CodeEdit,
CodeNodeEditor,
ExpressionEdit,
ExpressionParameterInput,
NodeCredentials,
CredentialsSelect,
PrismEditor,
@@ -464,6 +469,7 @@ export default mixins(
},
],
},
isFocused: false,
};
},
watch: {
@@ -718,10 +724,12 @@ export default mixins(
classes['parameter-value-container'] = true;
}
if (this.isValueExpression || this.forceShowExpression) {
classes['expression'] = true;
}
if (!this.droppable && !this.activeDrop && (this.getIssues.length || this.errorHighlight)) {
if (
!this.droppable &&
!this.activeDrop &&
(this.getIssues.length > 0 || this.errorHighlight) &&
!this.isValueExpression
) {
classes['has-issues'] = true;
}
@@ -890,26 +898,20 @@ export default mixins(
: value;
this.valueChanged(val);
},
openExpressionEdit() {
if (this.isValueExpression) {
this.expressionEditDialogVisible = true;
this.trackExpressionEditOpen();
return;
}
openExpressionEditorModal() {
if (!this.isValueExpression) return;
this.expressionEditDialogVisible = true;
this.trackExpressionEditOpen();
},
onBlur() {
this.$emit('blur');
this.isFocused = false;
},
onResourceLocatorDrop(data: string) {
this.$emit('drop', data);
},
setFocus() {
if (this.isValueExpression) {
this.expressionEditDialogVisible = true;
this.trackExpressionEditOpen();
return;
}
if (['json'].includes(this.parameter.type) && this.getArgument('alwaysOpenEditWindow')) {
this.displayEditDialog();
return;
@@ -931,6 +933,7 @@ export default mixins(
if (this.$refs.inputField && this.$refs.inputField.$el) {
// @ts-ignore
this.$refs.inputField.focus();
this.isFocused = true;
}
});
@@ -1014,8 +1017,6 @@ export default mixins(
if (command === 'resetValue') {
this.valueChanged(this.parameter.default);
} else if (command === 'openExpression') {
this.expressionEditDialogVisible = true;
} else if (command === 'addExpression') {
if (this.isResourceLocatorParameter) {
if (isResourceLocatorValue(this.value)) {
@@ -1023,19 +1024,23 @@ export default mixins(
} else {
this.valueChanged({ __rl: true, value: `=${this.value}`, mode: '' });
}
} else if (
this.parameter.type === 'number' &&
(!this.value || this.value === '[Object: null]')
) {
this.valueChanged('={{ 0 }}');
} else if (this.parameter.type === 'number' || this.parameter.type === 'boolean') {
this.valueChanged(`={{${this.value}}}`);
this.valueChanged(`={{ ${this.value} }}`);
} else {
this.valueChanged(`=${this.value}`);
}
setTimeout(() => {
this.expressionEditDialogVisible = true;
this.trackExpressionEditOpen();
}, 375);
this.setFocus();
} else if (command === 'removeExpression') {
let value: NodeParameterValueType = this.expressionEvaluated;
this.isFocused = false;
if (this.parameter.type === 'multiOptions' && typeof value === 'string') {
value = (value || '')
.split(',')
@@ -1201,24 +1206,13 @@ export default mixins(
background-color: #f0f0f0;
}
.expression {
textarea,
input {
cursor: pointer !important;
}
--input-border-color: var(--color-secondary-tint-1);
--input-background-color: var(--color-secondary-tint-3);
--input-font-color: var(--color-secondary);
}
.droppable {
--input-border-color: var(--color-secondary);
--input-background-color: var(--color-foreground-xlight);
--input-border-style: dashed;
textarea,
input {
input,
.cm-editor {
border-width: 1.5px;
}
}
@@ -1276,4 +1270,39 @@ export default mixins(
height: 100%;
align-items: center;
}
.input-with-opener > .el-input__suffix {
right: 0;
}
.textarea-modal-opener {
position: absolute;
right: 0;
bottom: 0;
background-color: white;
padding: 3px;
line-height: 9px;
border: var(--border-base);
border-top-left-radius: var(--border-radius-base);
border-bottom-right-radius: var(--border-radius-base);
cursor: pointer;
svg {
width: 9px !important;
height: 9px;
transform: rotate(270deg);
&:hover {
color: var(--color-primary);
}
}
}
.focused {
border-color: var(--color-secondary);
}
.invalid {
border-color: var(--color-danger);
}
</style>