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

@@ -0,0 +1,135 @@
<template>
<div ref="root" class="ph-no-capture"></div>
</template>
<script lang="ts">
import mixins from 'vue-typed-mixins';
import { mapStores } from 'pinia';
import { EditorView } from '@codemirror/view';
import { EditorState } from '@codemirror/state';
import { history } from '@codemirror/commands';
import { useNDVStore } from '@/stores/ndv';
import { workflowHelpers } from '@/mixins/workflowHelpers';
import { expressionManager } from '@/mixins/expressionManager';
import { highlighter } from '@/plugins/codemirror/resolvableHighlighter';
import { n8nLanguageSupport } from '@/plugins/codemirror/n8nLanguageSupport';
import { doubleBraceHandler } from '@/plugins/codemirror/doubleBraceHandler';
import { inputTheme } from './theme';
export default mixins(expressionManager, workflowHelpers).extend({
name: 'InlineExpressionEditorInput',
props: {
value: {
type: String,
},
isReadOnly: {
type: Boolean,
default: false,
},
isSingleLine: {
type: Boolean,
default: false,
},
},
data() {
return {
cursorPosition: 0,
};
},
watch: {
value(newValue) {
const range = this.editor?.state.selection.ranges[0];
if (range !== undefined && range.from !== range.to) return;
try {
this.editor?.dispatch({
changes: {
from: 0,
to: this.editor.state.doc.length,
insert: newValue,
},
selection: { anchor: this.cursorPosition, head: this.cursorPosition },
});
} catch (_) {
// ignore out-of-range selection error on drop
}
},
ndvInputData() {
this.editor?.dispatch({
changes: {
from: 0,
to: this.editor.state.doc.length,
insert: this.value,
},
});
setTimeout(() => {
this.editor?.contentDOM.blur();
});
},
},
computed: {
...mapStores(useNDVStore),
ndvInputData(): object {
return this.ndvStore.ndvInputData;
},
},
mounted() {
const extensions = [
inputTheme({ isSingleLine: this.isSingleLine }),
n8nLanguageSupport(),
history(),
doubleBraceHandler(),
EditorView.lineWrapping,
EditorView.editable.of(!this.isReadOnly),
EditorView.domEventHandlers({
focus: () => {
this.$emit('focus');
},
}),
EditorView.updateListener.of((viewUpdate) => {
if (!this.editor || !viewUpdate.docChanged) return;
highlighter.removeColor(this.editor, this.plaintextSegments);
highlighter.addColor(this.editor, this.resolvableSegments);
this.cursorPosition = viewUpdate.view.state.selection.ranges[0].from;
setTimeout(() => {
this.$emit('change', {
value: this.unresolvedExpression,
segments: this.displayableSegments,
});
}, this.evaluationDelay);
}),
];
this.editor = new EditorView({
parent: this.$refs.root as HTMLDivElement,
state: EditorState.create({
doc: this.value.startsWith('=') ? this.value.slice(1) : this.value,
extensions,
}),
});
highlighter.addColor(this.editor, this.resolvableSegments);
this.$emit('change', {
value: this.unresolvedExpression,
segments: this.displayableSegments,
});
},
destroyed() {
this.editor?.destroy();
},
methods: {
focus() {
this.editor?.focus();
},
},
});
</script>
<style lang="scss"></style>