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,131 @@
import { EditorView, Decoration, DecorationSet } from '@codemirror/view';
import { StateField, StateEffect } from '@codemirror/state';
import { tags } from '@lezer/highlight';
import { syntaxHighlighting, HighlightStyle } from '@codemirror/language';
import type { ColoringStateEffect, Plaintext, Resolvable, Resolved } from '@/types/expressions';
const cssClasses = {
validResolvable: 'cm-valid-resolvable',
invalidResolvable: 'cm-invalid-resolvable',
brokenResolvable: 'cm-broken-resolvable',
plaintext: 'cm-plaintext',
};
const resolvablesTheme = EditorView.theme({
['.' + cssClasses.validResolvable]: {
color: 'var(--color-valid-resolvable-foreground)',
backgroundColor: 'var(--color-valid-resolvable-background)',
},
['.' + cssClasses.invalidResolvable]: {
color: 'var(--color-invalid-resolvable-foreground)',
backgroundColor: 'var(--color-invalid-resolvable-background)',
},
});
const marks = {
valid: Decoration.mark({ class: cssClasses.validResolvable }),
invalid: Decoration.mark({ class: cssClasses.invalidResolvable }),
};
const coloringStateEffects = {
addColorEffect: StateEffect.define<ColoringStateEffect.Value>({
map: ({ from, to, kind, error }, change) => ({
from: change.mapPos(from),
to: change.mapPos(to),
kind,
error,
}),
}),
removeColorEffect: StateEffect.define<ColoringStateEffect.Value>({
map: ({ from, to }, change) => ({
from: change.mapPos(from),
to: change.mapPos(to),
}),
}),
};
const coloringStateField = StateField.define<DecorationSet>({
provide: (stateField) => EditorView.decorations.from(stateField),
create() {
return Decoration.none;
},
update(colorings, transaction) {
colorings = colorings.map(transaction.changes); // recalculate positions for new doc
for (const txEffect of transaction.effects) {
if (txEffect.is(coloringStateEffects.removeColorEffect)) {
colorings = colorings.update({
filter: (from, to) => txEffect.value.from !== from && txEffect.value.to !== to,
});
}
if (txEffect.is(coloringStateEffects.addColorEffect)) {
colorings = colorings.update({
filter: (from, to) => txEffect.value.from !== from && txEffect.value.to !== to,
});
const decoration = txEffect.value.error ? marks.invalid : marks.valid;
if (txEffect.value.from === 0 && txEffect.value.to === 0) continue;
colorings = colorings.update({
add: [decoration.range(txEffect.value.from, txEffect.value.to)],
});
}
}
return colorings;
},
});
function addColor(view: EditorView, segments: Array<Resolvable | Resolved>) {
const effects: Array<StateEffect<unknown>> = segments.map(({ from, to, kind, error }) =>
coloringStateEffects.addColorEffect.of({ from, to, kind, error }),
);
if (effects.length === 0) return;
if (!view.state.field(coloringStateField, false)) {
effects.push(StateEffect.appendConfig.of([coloringStateField, resolvablesTheme]));
}
view.dispatch({ effects });
}
function removeColor(view: EditorView, segments: Plaintext[]) {
const effects: Array<StateEffect<unknown>> = segments.map(({ from, to }) =>
coloringStateEffects.removeColorEffect.of({ from, to }),
);
if (effects.length === 0) return;
if (!view.state.field(coloringStateField, false)) {
effects.push(StateEffect.appendConfig.of([coloringStateField, resolvablesTheme]));
}
view.dispatch({ effects });
}
const resolvableStyle = syntaxHighlighting(
HighlightStyle.define([
{
tag: tags.content,
class: cssClasses.plaintext,
},
{
tag: tags.className,
class: cssClasses.brokenResolvable,
},
/**
* CSS classes for valid and invalid resolvables
* dynamically applied based on state fields
*/
]),
);
export const highlighter = {
addColor,
removeColor,
resolvableStyle,
};