Files
Automata/packages/editor-ui/src/components/RunDataSchema.vue
Iván Ovejero a1259898c0 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>
2022-12-14 14:43:02 +01:00

174 lines
4.0 KiB
Vue

<script lang="ts" setup>
import { computed, ref } from 'vue';
import { INodeUi, Schema } from '@/Interface';
import RunDataSchemaItem from '@/components/RunDataSchemaItem.vue';
import Draggable from '@/components/Draggable.vue';
import { useNDVStore } from '@/stores/ndv';
import { useWebhooksStore } from '@/stores/webhooks';
import { runExternalHook } from '@/mixins/externalHooks';
import { telemetry } from '@/plugins/telemetry';
import { IDataObject } from 'n8n-workflow';
import { getSchema, isEmpty, mergeDeep } from '@/utils';
import { i18n } from '@/plugins/i18n';
type Props = {
data: IDataObject[];
mappingEnabled: boolean;
distanceFromActive: number;
runIndex: number;
totalRuns: number;
node: INodeUi | null;
};
const props = withDefaults(defineProps<Props>(), {
distanceFromActive: 0,
});
const draggingPath = ref<string>('');
const ndvStore = useNDVStore();
const webhooksStore = useWebhooksStore();
const schema = computed<Schema>(() => {
const [head, ...tail] = props.data;
return getSchema(mergeDeep([head, ...tail, head]));
});
const isDataEmpty = computed(() => {
return isEmpty(props.data);
});
const onDragStart = (el: HTMLElement) => {
if (el && el.dataset?.path) {
draggingPath.value = el.dataset.path;
}
ndvStore.resetMappingTelemetry();
};
const onDragEnd = (el: HTMLElement) => {
draggingPath.value = '';
setTimeout(() => {
const mappingTelemetry = ndvStore.mappingTelemetry;
const telemetryPayload = {
src_node_type: props.node?.type,
src_field_name: el.dataset.name || '',
src_nodes_back: props.distanceFromActive,
src_run_index: props.runIndex,
src_runs_total: props.totalRuns,
src_field_nest_level: el.dataset.depth || 0,
src_view: 'schema',
src_element: el,
success: false,
...mappingTelemetry,
};
runExternalHook('runDataJson.onDragEnd', webhooksStore, telemetryPayload);
telemetry.track('User dragged data for mapping', telemetryPayload);
}, 1000); // ensure dest data gets set if drop
};
</script>
<template>
<div :class="$style.schemaWrapper">
<n8n-info-tip v-if="isDataEmpty">{{
i18n.baseText('dataMapping.schemaView.emptyData')
}}</n8n-info-tip>
<draggable
v-else
type="mapping"
targetDataKey="mappable"
:disabled="!mappingEnabled"
@dragstart="onDragStart"
@dragend="onDragEnd"
>
<template #preview="{ canDrop, el }">
<div
v-if="el"
:class="[$style.dragPill, canDrop ? $style.droppablePill : $style.defaultPill]"
v-html="el.outerHTML"
/>
</template>
<template>
<div :class="$style.schema">
<run-data-schema-item
:schema="schema"
:level="0"
:parent="null"
:subKey="`${schema.type}-0-0`"
:mappingEnabled="mappingEnabled"
:draggingPath="draggingPath"
:distanceFromActive="distanceFromActive"
:node="node"
/>
</div>
</template>
</draggable>
</div>
</template>
<style lang="scss" module>
.schemaWrapper {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
overflow: auto;
line-height: 1.5;
word-break: normal;
height: 100%;
width: 100%;
background-color: var(--color-background-base);
> div[class*='info'] {
padding: 0 var(--spacing-s);
}
}
.schema {
display: inline-block;
padding: 0 var(--spacing-s) var(--spacing-s);
}
.dragPill {
display: inline-flex;
height: 24px;
padding: 0 var(--spacing-3xs);
border: 1px solid var(--color-foreground-light);
border-radius: 4px;
background: var(--color-background-xlight);
font-size: var(--font-size-2xs);
color: var(--color-text-base);
white-space: nowrap;
align-items: center;
span {
display: flex;
height: 100%;
align-items: center;
}
}
.droppablePill {
&,
span span {
color: var(--color-success);
border-color: var(--color-success-light);
background-color: white;
}
}
.defaultPill {
transform: translate(-50%, -100%);
box-shadow: 0 2px 6px rgba(68, 28, 23, 0.2);
&,
span span {
color: var(--color-primary);
border-color: var(--color-primary-tint-1);
background-color: var(--color-primary-tint-3);
}
}
</style>