2024-03-26 07:23:30 -07:00
|
|
|
<script setup lang="ts">
|
|
|
|
import { computed, ref, watch } from 'vue';
|
|
|
|
|
|
|
|
import ExpressionFunctionIcon from '@/components/ExpressionFunctionIcon.vue';
|
|
|
|
import InlineExpressionEditorInput from '@/components/InlineExpressionEditor/InlineExpressionEditorInput.vue';
|
|
|
|
import InlineExpressionEditorOutput from '@/components/InlineExpressionEditor/InlineExpressionEditorOutput.vue';
|
|
|
|
import { useNDVStore } from '@/stores/ndv.store';
|
|
|
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
|
|
|
import { createExpressionTelemetryPayload } from '@/utils/telemetryUtils';
|
|
|
|
|
|
|
|
import { useTelemetry } from '@/composables/useTelemetry';
|
|
|
|
import type { Segment } from '@/types/expressions';
|
|
|
|
import { createEventBus, type EventBus } from 'n8n-design-system/utils';
|
|
|
|
import type { IDataObject } from 'n8n-workflow';
|
|
|
|
import type { EditorState, SelectionRange } from '@codemirror/state';
|
|
|
|
|
|
|
|
const isFocused = ref(false);
|
|
|
|
const segments = ref<Segment[]>([]);
|
|
|
|
const editorState = ref<EditorState>();
|
|
|
|
const selection = ref<SelectionRange>();
|
|
|
|
const inlineInput = ref<InstanceType<typeof InlineExpressionEditorInput>>();
|
|
|
|
|
|
|
|
type Props = {
|
|
|
|
path: string;
|
|
|
|
modelValue: string;
|
2024-05-21 06:04:20 -07:00
|
|
|
rows?: number;
|
|
|
|
additionalExpressionData?: IDataObject;
|
|
|
|
eventBus?: EventBus;
|
|
|
|
isReadOnly?: boolean;
|
|
|
|
isAssignment?: boolean;
|
2024-03-26 07:23:30 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
|
|
|
rows: 5,
|
|
|
|
isAssignment: false,
|
2024-05-21 06:04:20 -07:00
|
|
|
isReadOnly: false,
|
2024-03-26 07:23:30 -07:00
|
|
|
additionalExpressionData: () => ({}),
|
|
|
|
eventBus: () => createEventBus(),
|
|
|
|
});
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
(event: 'modal-opener-click'): void;
|
|
|
|
(event: 'update:model-value', value: string): void;
|
|
|
|
(event: 'focus'): void;
|
|
|
|
(event: 'blur'): void;
|
|
|
|
}>();
|
|
|
|
|
|
|
|
const telemetry = useTelemetry();
|
|
|
|
const ndvStore = useNDVStore();
|
|
|
|
const workflowsStore = useWorkflowsStore();
|
|
|
|
|
|
|
|
const isDragging = computed(() => ndvStore.isDraggableDragging);
|
|
|
|
|
|
|
|
function focus() {
|
|
|
|
if (inlineInput.value) {
|
|
|
|
inlineInput.value.focus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onFocus() {
|
|
|
|
isFocused.value = true;
|
|
|
|
emit('focus');
|
|
|
|
}
|
|
|
|
|
|
|
|
function onBlur(event?: FocusEvent | KeyboardEvent) {
|
|
|
|
if (
|
|
|
|
event?.target instanceof Element &&
|
|
|
|
Array.from(event.target.classList).some((_class) => _class.includes('resizer'))
|
|
|
|
) {
|
|
|
|
return; // prevent blur on resizing
|
|
|
|
}
|
|
|
|
|
|
|
|
const wasFocused = isFocused.value;
|
|
|
|
|
|
|
|
isFocused.value = false;
|
|
|
|
|
|
|
|
if (wasFocused) {
|
|
|
|
emit('blur');
|
|
|
|
|
|
|
|
const telemetryPayload = createExpressionTelemetryPayload(
|
|
|
|
segments.value,
|
|
|
|
props.modelValue,
|
|
|
|
workflowsStore.workflowId,
|
2024-04-03 04:43:14 -07:00
|
|
|
ndvStore.pushRef,
|
2024-03-26 07:23:30 -07:00
|
|
|
ndvStore.activeNode?.type ?? '',
|
|
|
|
);
|
|
|
|
|
|
|
|
telemetry.track('User closed Expression Editor', telemetryPayload);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function onValueChange({ value, segments: newSegments }: { value: string; segments: Segment[] }) {
|
|
|
|
segments.value = newSegments;
|
|
|
|
|
|
|
|
if (isDragging.value) return;
|
|
|
|
if (value === '=' + props.modelValue) return; // prevent report on change of target item
|
|
|
|
|
|
|
|
emit('update:model-value', value);
|
|
|
|
}
|
|
|
|
|
|
|
|
function onSelectionChange({
|
|
|
|
state: newState,
|
|
|
|
selection: newSelection,
|
|
|
|
}: {
|
|
|
|
state: EditorState;
|
|
|
|
selection: SelectionRange;
|
|
|
|
}) {
|
|
|
|
editorState.value = newState;
|
|
|
|
selection.value = newSelection;
|
|
|
|
}
|
|
|
|
|
|
|
|
watch(isDragging, (newIsDragging) => {
|
|
|
|
if (newIsDragging) {
|
|
|
|
onBlur();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
defineExpose({ focus });
|
|
|
|
</script>
|
|
|
|
|
2022-12-14 05:43:02 -08:00
|
|
|
<template>
|
2023-07-28 00:51:07 -07:00
|
|
|
<div
|
|
|
|
v-on-click-outside="onBlur"
|
2023-12-28 00:49:58 -08:00
|
|
|
:class="$style['expression-parameter-input']"
|
2023-07-28 00:51:07 -07:00
|
|
|
@keydown.tab="onBlur"
|
|
|
|
>
|
2024-02-06 09:34:34 -08:00
|
|
|
<div
|
|
|
|
:class="[
|
|
|
|
$style['all-sections'],
|
|
|
|
{ [$style.focused]: isFocused, [$style.assignment]: isAssignment },
|
|
|
|
]"
|
|
|
|
>
|
2023-12-13 05:45:22 -08:00
|
|
|
<div :class="[$style['prepend-section'], 'el-input-group__prepend']">
|
2024-02-06 09:34:34 -08:00
|
|
|
<span v-if="isAssignment">=</span>
|
|
|
|
<ExpressionFunctionIcon v-else />
|
2022-12-14 05:43:02 -08:00
|
|
|
</div>
|
|
|
|
<InlineExpressionEditorInput
|
2023-12-28 00:49:58 -08:00
|
|
|
ref="inlineInput"
|
|
|
|
:model-value="modelValue"
|
2024-03-15 10:40:37 -07:00
|
|
|
:path="path"
|
2023-12-28 00:49:58 -08:00
|
|
|
:is-read-only="isReadOnly"
|
2024-02-06 09:34:34 -08:00
|
|
|
:rows="rows"
|
2023-12-28 00:49:58 -08:00
|
|
|
:additional-data="additionalExpressionData"
|
2024-03-13 04:57:08 -07:00
|
|
|
:event-bus="eventBus"
|
2022-12-14 05:43:02 -08:00
|
|
|
@focus="onFocus"
|
|
|
|
@blur="onBlur"
|
2024-03-26 07:23:30 -07:00
|
|
|
@update:model-value="onValueChange"
|
|
|
|
@update:selection="onSelectionChange"
|
2022-12-14 05:43:02 -08:00
|
|
|
/>
|
2024-01-29 07:34:10 -08:00
|
|
|
<n8n-button
|
2022-12-14 05:43:02 -08:00
|
|
|
v-if="!isDragging"
|
2024-01-29 07:34:10 -08:00
|
|
|
square
|
|
|
|
outline
|
|
|
|
type="tertiary"
|
2022-12-14 05:43:02 -08:00
|
|
|
icon="external-link-alt"
|
|
|
|
size="xsmall"
|
|
|
|
:class="$style['expression-editor-modal-opener']"
|
|
|
|
data-test-id="expander"
|
2024-03-26 07:23:30 -07:00
|
|
|
@click="emit('modal-opener-click')"
|
2022-12-14 05:43:02 -08:00
|
|
|
/>
|
|
|
|
</div>
|
2023-06-22 07:47:28 -07:00
|
|
|
<InlineExpressionEditorOutput
|
2024-03-26 07:23:30 -07:00
|
|
|
:unresolved-expression="modelValue"
|
|
|
|
:selection="selection"
|
|
|
|
:editor-state="editorState"
|
2023-06-22 07:47:28 -07:00
|
|
|
:segments="segments"
|
2023-12-28 00:49:58 -08:00
|
|
|
:is-read-only="isReadOnly"
|
2023-06-22 07:47:28 -07:00
|
|
|
:visible="isFocused"
|
|
|
|
/>
|
2022-12-14 05:43:02 -08:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<style lang="scss" module>
|
|
|
|
.expression-parameter-input {
|
|
|
|
position: relative;
|
|
|
|
|
2023-11-01 05:33:36 -07:00
|
|
|
:global(.cm-editor) {
|
|
|
|
background-color: var(--color-code-background);
|
|
|
|
}
|
|
|
|
|
2022-12-14 05:43:02 -08:00
|
|
|
.all-sections {
|
|
|
|
height: 30px;
|
|
|
|
display: inline-table;
|
|
|
|
width: 100%;
|
|
|
|
}
|
|
|
|
|
|
|
|
.prepend-section {
|
|
|
|
padding: 0;
|
|
|
|
padding-top: 2px;
|
|
|
|
width: 22px;
|
|
|
|
text-align: center;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-06 09:34:34 -08:00
|
|
|
.assignment {
|
|
|
|
.prepend-section {
|
|
|
|
vertical-align: top;
|
|
|
|
padding-top: 4px;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-14 05:43:02 -08:00
|
|
|
.expression-editor-modal-opener {
|
|
|
|
position: absolute;
|
|
|
|
right: 0;
|
|
|
|
bottom: 0;
|
2023-11-01 05:33:36 -07:00
|
|
|
background-color: var(--color-code-background);
|
2022-12-14 05:43:02 -08:00
|
|
|
padding: 3px;
|
|
|
|
line-height: 9px;
|
2024-01-29 07:34:10 -08:00
|
|
|
border: var(--input-border-color, var(--border-color-base))
|
|
|
|
var(--input-border-style, var(--border-style-base))
|
|
|
|
var(--input-border-width, var(--border-width-base));
|
2022-12-14 05:43:02 -08:00
|
|
|
cursor: pointer;
|
2024-02-06 09:34:34 -08:00
|
|
|
border-radius: 0;
|
|
|
|
border-top-left-radius: var(--border-radius-base);
|
2022-12-14 05:43:02 -08:00
|
|
|
|
2024-01-29 07:34:10 -08:00
|
|
|
&:hover {
|
|
|
|
border: var(--input-border-color, var(--border-color-base))
|
|
|
|
var(--input-border-style, var(--border-style-base))
|
|
|
|
var(--input-border-width, var(--border-width-base));
|
|
|
|
}
|
|
|
|
|
2022-12-14 05:43:02 -08:00
|
|
|
svg {
|
|
|
|
width: 9px !important;
|
|
|
|
height: 9px;
|
|
|
|
transform: rotate(270deg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.focused > .prepend-section {
|
|
|
|
border-color: var(--color-secondary);
|
|
|
|
border-bottom-left-radius: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.focused :global(.cm-editor) {
|
|
|
|
border-color: var(--color-secondary);
|
|
|
|
}
|
|
|
|
|
|
|
|
.focused > .expression-editor-modal-opener {
|
|
|
|
border-color: var(--color-secondary);
|
|
|
|
border-bottom-right-radius: 0;
|
2023-11-01 05:33:36 -07:00
|
|
|
background-color: var(--color-code-background);
|
2022-12-14 05:43:02 -08:00
|
|
|
}
|
|
|
|
</style>
|