mirror of
https://github.com/n8n-io/n8n.git
synced 2025-02-21 02:56:40 -08:00
feat(editor): Add item selector to expression output (#9281)
This commit is contained in:
parent
1c1e4443f4
commit
dc5994b185
|
@ -75,7 +75,7 @@ describe('Resource Locator', () => {
|
||||||
|
|
||||||
ndv.actions.setInvalidExpression({ fieldName: 'fieldId' });
|
ndv.actions.setInvalidExpression({ fieldName: 'fieldId' });
|
||||||
|
|
||||||
ndv.getters.inputDataContainer().click(); // remove focus from input, hide expression preview
|
ndv.getters.inputPanel().click(); // remove focus from input, hide expression preview
|
||||||
|
|
||||||
ndv.getters.resourceLocatorInput('rlc').click();
|
ndv.getters.resourceLocatorInput('rlc').click();
|
||||||
|
|
||||||
|
|
|
@ -325,7 +325,7 @@ describe('NDV', () => {
|
||||||
|
|
||||||
ndv.actions.setInvalidExpression({ fieldName: 'fieldId', delay: 200 });
|
ndv.actions.setInvalidExpression({ fieldName: 'fieldId', delay: 200 });
|
||||||
|
|
||||||
ndv.getters.inputDataContainer().click(); // remove focus from input, hide expression preview
|
ndv.getters.inputPanel().click(); // remove focus from input, hide expression preview
|
||||||
|
|
||||||
ndv.getters.parameterInput('remoteOptions').click();
|
ndv.getters.parameterInput('remoteOptions').click();
|
||||||
|
|
||||||
|
@ -712,4 +712,31 @@ describe('NDV', () => {
|
||||||
workflowPage.getters.successToast().should('exist');
|
workflowPage.getters.successToast().should('exist');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow selecting item for expressions', () => {
|
||||||
|
workflowPage.actions.visit();
|
||||||
|
|
||||||
|
cy.createFixtureWorkflow('Test_workflow_3.json', `My test workflow`);
|
||||||
|
workflowPage.actions.openNode('Set');
|
||||||
|
|
||||||
|
ndv.actions.typeIntoParameterInput('value', '='); // switch to expressions
|
||||||
|
ndv.actions.typeIntoParameterInput('value', '{{', {
|
||||||
|
parseSpecialCharSequences: false,
|
||||||
|
});
|
||||||
|
ndv.actions.typeIntoParameterInput('value', '$json.input[0].count');
|
||||||
|
ndv.getters.inlineExpressionEditorOutput().should('have.text', '0');
|
||||||
|
|
||||||
|
ndv.actions.expressionSelectNextItem();
|
||||||
|
ndv.getters.inlineExpressionEditorOutput().should('have.text', '1');
|
||||||
|
ndv.getters.inlineExpressionEditorItemInput().should('have.value', '1');
|
||||||
|
ndv.getters.inlineExpressionEditorItemNextButton().should('be.disabled');
|
||||||
|
|
||||||
|
ndv.actions.expressionSelectPrevItem();
|
||||||
|
ndv.getters.inlineExpressionEditorOutput().should('have.text', '0');
|
||||||
|
ndv.getters.inlineExpressionEditorItemInput().should('have.value', '0');
|
||||||
|
ndv.getters.inlineExpressionEditorItemPrevButton().should('be.disabled');
|
||||||
|
|
||||||
|
ndv.actions.expressionSelectItem(1);
|
||||||
|
ndv.getters.inlineExpressionEditorOutput().should('have.text', '1');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -40,6 +40,12 @@ export class NDV extends BasePage {
|
||||||
this.getters.inputTableRow(row).find('td').eq(col),
|
this.getters.inputTableRow(row).find('td').eq(col),
|
||||||
inlineExpressionEditorInput: () => cy.getByTestId('inline-expression-editor-input'),
|
inlineExpressionEditorInput: () => cy.getByTestId('inline-expression-editor-input'),
|
||||||
inlineExpressionEditorOutput: () => cy.getByTestId('inline-expression-editor-output'),
|
inlineExpressionEditorOutput: () => cy.getByTestId('inline-expression-editor-output'),
|
||||||
|
inlineExpressionEditorItemInput: () =>
|
||||||
|
cy.getByTestId('inline-expression-editor-item-input').find('input'),
|
||||||
|
inlineExpressionEditorItemPrevButton: () =>
|
||||||
|
cy.getByTestId('inline-expression-editor-item-prev'),
|
||||||
|
inlineExpressionEditorItemNextButton: () =>
|
||||||
|
cy.getByTestId('inline-expression-editor-item-next'),
|
||||||
nodeParameters: () => cy.getByTestId('node-parameters'),
|
nodeParameters: () => cy.getByTestId('node-parameters'),
|
||||||
parameterInput: (parameterName: string) => cy.getByTestId(`parameter-input-${parameterName}`),
|
parameterInput: (parameterName: string) => cy.getByTestId(`parameter-input-${parameterName}`),
|
||||||
parameterInputIssues: (parameterName: string) =>
|
parameterInputIssues: (parameterName: string) =>
|
||||||
|
@ -290,6 +296,15 @@ export class NDV extends BasePage {
|
||||||
.click({ force: true });
|
.click({ force: true });
|
||||||
this.getters.parameterInput('operation').find('input').should('have.value', operation);
|
this.getters.parameterInput('operation').find('input').should('have.value', operation);
|
||||||
},
|
},
|
||||||
|
expressionSelectItem: (index: number) => {
|
||||||
|
this.getters.inlineExpressionEditorItemInput().type(`{selectall}${index}`);
|
||||||
|
},
|
||||||
|
expressionSelectNextItem: () => {
|
||||||
|
this.getters.inlineExpressionEditorItemNextButton().click();
|
||||||
|
},
|
||||||
|
expressionSelectPrevItem: () => {
|
||||||
|
this.getters.inlineExpressionEditorItemPrevButton().click();
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,3 +95,24 @@ Sizes.args = {
|
||||||
placeholder: 'placeholder...',
|
placeholder: 'placeholder...',
|
||||||
controls: false,
|
controls: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ControlsTemplate: StoryFn = (args, { argTypes }) => ({
|
||||||
|
setup: () => ({ args }),
|
||||||
|
props: Object.keys(argTypes),
|
||||||
|
components: {
|
||||||
|
N8nInputNumber,
|
||||||
|
},
|
||||||
|
template:
|
||||||
|
'<div> <n8n-input-number style="margin-bottom:10px" v-bind="args" v-model="val" @update:modelValue="onUpdateModelValue" /> <n8n-input-number style="margin-bottom:10px" v-bind="args" size="medium" v-model="val" @update:modelValue="onUpdateModelValue" /> <n8n-input-number style="margin-bottom:10px" v-bind="args" size="small" v-model="val" @update:modelValue="onUpdateModelValue" /> <n8n-input-number style="margin-bottom:10px" v-bind="args" v-model="val" size="mini" @update:modelValue="onUpdateModelValue" /> <n8n-input-number controls-position="right" style="margin-bottom:10px" v-bind="args" v-model="val" @update:modelValue="onUpdateModelValue" /> <n8n-input-number controls-position="right" style="margin-bottom:10px" v-bind="args" size="medium" v-model="val" @update:modelValue="onUpdateModelValue" /> <n8n-input-number controls-position="right" style="margin-bottom:10px" v-bind="args" size="small" v-model="val" @update:modelValue="onUpdateModelValue" /> <n8n-input-number controls-position="right" style="margin-bottom:10px" v-bind="args" v-model="val" size="mini" @update:modelValue="onUpdateModelValue" /> </div>',
|
||||||
|
methods,
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
val: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Controls = ControlsTemplate.bind({});
|
||||||
|
Controls.args = {
|
||||||
|
placeholder: 'placeholder...',
|
||||||
|
};
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { InputSize } from '@/types';
|
||||||
import { ElInputNumber } from 'element-plus';
|
import { ElInputNumber } from 'element-plus';
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
type InputNumberProps = {
|
||||||
name: 'N8nInputNumber',
|
size?: InputSize;
|
||||||
components: {
|
min?: number;
|
||||||
ElInputNumber,
|
max?: number;
|
||||||
},
|
step?: number;
|
||||||
props: {
|
precision?: number;
|
||||||
...ElInputNumber.props,
|
};
|
||||||
},
|
|
||||||
});
|
defineProps<InputNumberProps>();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
@ -88,6 +88,10 @@ const classes = computed(() => {
|
||||||
color: var(--color-primary);
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.secondary {
|
||||||
|
color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.text-dark {
|
.text-dark {
|
||||||
color: var(--color-text-dark);
|
color: var(--color-text-dark);
|
||||||
}
|
}
|
||||||
|
|
|
@ -466,6 +466,7 @@ $input-small-height: 30px !default;
|
||||||
$input-mini-font-size: 12px;
|
$input-mini-font-size: 12px;
|
||||||
/// height||Other|4
|
/// height||Other|4
|
||||||
$input-mini-height: 26px !default;
|
$input-mini-height: 26px !default;
|
||||||
|
$input-number-control-border-radius: 3px;
|
||||||
|
|
||||||
/* Cascader
|
/* Cascader
|
||||||
-------------------------- */
|
-------------------------- */
|
||||||
|
|
|
@ -32,12 +32,16 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@include mixins.e((increase, decrease)) {
|
@include mixins.e((increase, decrease)) {
|
||||||
|
--disabled-color: var(--color-text-light);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
|
bottom: 1px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
width: var.$input-height;
|
width: var.$input-height;
|
||||||
height: auto;
|
height: auto;
|
||||||
text-align: center;
|
|
||||||
background: var.$background-color-base;
|
background: var.$background-color-base;
|
||||||
color: var(--color-text-dark);
|
color: var(--color-text-dark);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -59,13 +63,15 @@
|
||||||
|
|
||||||
@include mixins.e(increase) {
|
@include mixins.e(increase) {
|
||||||
right: 1px;
|
right: 1px;
|
||||||
border-radius: 0 var(--border-radius-base) var(--border-radius-base) 0;
|
border-radius: 0 var.$input-number-control-border-radius var.$input-number-control-border-radius
|
||||||
|
0;
|
||||||
border-left: var(--border-base);
|
border-left: var(--border-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
@include mixins.e(decrease) {
|
@include mixins.e(decrease) {
|
||||||
left: 1px;
|
left: 1px;
|
||||||
border-radius: var(--border-radius-base) 0 0 var(--border-radius-base);
|
border-radius: var.$input-number-control-border-radius 0 0
|
||||||
|
var.$input-number-control-border-radius;
|
||||||
border-right: var(--border-base);
|
border-right: var(--border-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +88,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@include mixins.m(medium) {
|
@include mixins.m(medium) {
|
||||||
line-height: #{var.$input-medium-height - 2};
|
line-height: #{var.$input-medium-height - 4};
|
||||||
|
|
||||||
@include mixins.e((increase, decrease)) {
|
@include mixins.e((increase, decrease)) {
|
||||||
width: var.$input-medium-height;
|
width: var.$input-medium-height;
|
||||||
|
@ -114,7 +120,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@include mixins.m(mini) {
|
@include mixins.m(mini) {
|
||||||
line-height: #{var.$input-mini-height - 2};
|
line-height: #{var.$input-mini-height - 4};
|
||||||
|
|
||||||
@include mixins.e((increase, decrease)) {
|
@include mixins.e((increase, decrease)) {
|
||||||
width: var.$input-mini-height;
|
width: var.$input-mini-height;
|
||||||
|
@ -134,20 +140,33 @@
|
||||||
@include mixins.when(without-controls) {
|
@include mixins.when(without-controls) {
|
||||||
.el-input__inner {
|
.el-input__inner {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding-left: 12px;
|
padding-left: var(--spacing-2xs);
|
||||||
padding-right: 12px;
|
padding-right: var(--spacing-2xs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@include mixins.when(controls-right) {
|
@include mixins.when(controls-right) {
|
||||||
.el-input__inner {
|
.el-input__inner {
|
||||||
padding-left: 15px;
|
padding-left: var(--spacing-2xs);
|
||||||
padding-right: #{var.$input-height + 10};
|
padding-right: #{var.$input-height + 4};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-input-number--medium .el-input__inner {
|
||||||
|
padding-right: #{var.$input-medium-height + 4};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-input-number--small .el-input__inner {
|
||||||
|
padding-right: #{var.$input-small-height + 4};
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-input-number--mini .el-input__inner {
|
||||||
|
padding-left: var(--spacing-4xs);
|
||||||
|
padding-right: #{var.$input-mini-height + 4};
|
||||||
}
|
}
|
||||||
|
|
||||||
@include mixins.e((increase, decrease)) {
|
@include mixins.e((increase, decrease)) {
|
||||||
height: auto;
|
height: calc((100% - 1px) / 2);
|
||||||
line-height: #{(var.$input-height - 2) * 0.5};
|
bottom: auto;
|
||||||
|
|
||||||
[class*='el-icon'] {
|
[class*='el-icon'] {
|
||||||
transform: scale(0.8);
|
transform: scale(0.8);
|
||||||
|
@ -155,7 +174,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@include mixins.e(increase) {
|
@include mixins.e(increase) {
|
||||||
border-radius: 0 var(--border-radius-base) 0 0;
|
border-radius: 0 var.$input-number-control-border-radius 0 0;
|
||||||
border-bottom: var(--border-base);
|
border-bottom: var(--border-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,28 +185,7 @@
|
||||||
left: auto;
|
left: auto;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-left: var(--border-base);
|
border-left: var(--border-base);
|
||||||
border-radius: 0 0 var(--border-radius-base) 0;
|
border-radius: 0 0 var.$input-number-control-border-radius 0;
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='medium'] {
|
|
||||||
[class*='increase'],
|
|
||||||
[class*='decrease'] {
|
|
||||||
line-height: #{(var.$input-medium-height - 2) * 0.5};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='small'] {
|
|
||||||
[class*='increase'],
|
|
||||||
[class*='decrease'] {
|
|
||||||
line-height: #{(var.$input-small-height - 2) * 0.5};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&[class*='mini'] {
|
|
||||||
[class*='increase'],
|
|
||||||
[class*='decrease'] {
|
|
||||||
line-height: #{(var.$input-mini-height - 2) * 0.5};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1253,6 +1253,7 @@ export interface NDVState {
|
||||||
focusedInputPath: string;
|
focusedInputPath: string;
|
||||||
mappingTelemetry: { [key: string]: string | number | boolean };
|
mappingTelemetry: { [key: string]: string | number | boolean };
|
||||||
hoveringItem: null | TargetItem;
|
hoveringItem: null | TargetItem;
|
||||||
|
expressionOutputItemIndex: number;
|
||||||
draggable: {
|
draggable: {
|
||||||
isDragging: boolean;
|
isDragging: boolean;
|
||||||
type: string;
|
type: string;
|
||||||
|
@ -1261,6 +1262,7 @@ export interface NDVState {
|
||||||
activeTarget: { id: string; stickyPosition: null | XYPosition } | null;
|
activeTarget: { id: string; stickyPosition: null | XYPosition } | null;
|
||||||
};
|
};
|
||||||
isMappingOnboarded: boolean;
|
isMappingOnboarded: boolean;
|
||||||
|
isTableHoverOnboarded: boolean;
|
||||||
isAutocompleteOnboarded: boolean;
|
isAutocompleteOnboarded: boolean;
|
||||||
highlightDraggables: boolean;
|
highlightDraggables: boolean;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,9 +48,7 @@ const telemetry = useTelemetry();
|
||||||
const ndvStore = useNDVStore();
|
const ndvStore = useNDVStore();
|
||||||
const workflowsStore = useWorkflowsStore();
|
const workflowsStore = useWorkflowsStore();
|
||||||
|
|
||||||
const hoveringItemNumber = computed(() => ndvStore.hoveringItemNumber);
|
|
||||||
const isDragging = computed(() => ndvStore.isDraggableDragging);
|
const isDragging = computed(() => ndvStore.isDraggableDragging);
|
||||||
const noInputData = computed(() => ndvStore.hasInputData);
|
|
||||||
|
|
||||||
function focus() {
|
function focus() {
|
||||||
if (inlineInput.value) {
|
if (inlineInput.value) {
|
||||||
|
@ -166,9 +164,7 @@ defineExpose({ focus });
|
||||||
:editor-state="editorState"
|
:editor-state="editorState"
|
||||||
:segments="segments"
|
:segments="segments"
|
||||||
:is-read-only="isReadOnly"
|
:is-read-only="isReadOnly"
|
||||||
:no-input-data="noInputData"
|
|
||||||
:visible="isFocused"
|
:visible="isFocused"
|
||||||
:hovering-item-number="hoveringItemNumber"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -103,6 +103,9 @@ onMounted(() => {
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
highlighter.addColor(editor.value as EditorView, resolvedSegments.value);
|
||||||
|
highlighter.removeColor(editor.value as EditorView, plaintextSegments.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
|
|
@ -6,20 +6,20 @@ import type { Segment } from '@/types/expressions';
|
||||||
import ExpressionOutput from './ExpressionOutput.vue';
|
import ExpressionOutput from './ExpressionOutput.vue';
|
||||||
import InlineExpressionTip from './InlineExpressionTip.vue';
|
import InlineExpressionTip from './InlineExpressionTip.vue';
|
||||||
import { outputTheme } from './theme';
|
import { outputTheme } from './theme';
|
||||||
|
import { computed, onBeforeUnmount } from 'vue';
|
||||||
|
import { useNDVStore } from '@/stores/ndv.store';
|
||||||
|
import { N8nTooltip } from 'n8n-design-system/components';
|
||||||
|
|
||||||
interface InlineExpressionEditorOutputProps {
|
interface InlineExpressionEditorOutputProps {
|
||||||
segments: Segment[];
|
segments: Segment[];
|
||||||
hoveringItemNumber: number;
|
|
||||||
unresolvedExpression?: string;
|
unresolvedExpression?: string;
|
||||||
editorState?: EditorState;
|
editorState?: EditorState;
|
||||||
selection?: SelectionRange;
|
selection?: SelectionRange;
|
||||||
visible?: boolean;
|
visible?: boolean;
|
||||||
noInputData?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
withDefaults(defineProps<InlineExpressionEditorOutputProps>(), {
|
withDefaults(defineProps<InlineExpressionEditorOutputProps>(), {
|
||||||
visible: false,
|
visible: false,
|
||||||
noInputData: false,
|
|
||||||
editorState: undefined,
|
editorState: undefined,
|
||||||
selection: undefined,
|
selection: undefined,
|
||||||
unresolvedExpression: undefined,
|
unresolvedExpression: undefined,
|
||||||
|
@ -27,19 +27,95 @@ withDefaults(defineProps<InlineExpressionEditorOutputProps>(), {
|
||||||
|
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const theme = outputTheme();
|
const theme = outputTheme();
|
||||||
|
const ndvStore = useNDVStore();
|
||||||
|
|
||||||
|
const hideTableHoverHint = computed(() => ndvStore.isTableHoverOnboarded);
|
||||||
|
const hoveringItem = computed(() => ndvStore.getHoveringItem);
|
||||||
|
const hoveringItemIndex = computed(() => hoveringItem.value?.itemIndex);
|
||||||
|
const isHoveringItem = computed(() => Boolean(hoveringItem.value));
|
||||||
|
const itemsLength = computed(() => ndvStore.ndvInputDataWithPinnedData.length);
|
||||||
|
const itemIndex = computed(() => hoveringItemIndex.value ?? ndvStore.expressionOutputItemIndex);
|
||||||
|
const max = computed(() => Math.max(itemsLength.value - 1, 0));
|
||||||
|
const isItemIndexEditable = computed(() => !isHoveringItem.value && itemsLength.value > 0);
|
||||||
|
const canSelectPrevItem = computed(() => isItemIndexEditable.value && itemIndex.value !== 0);
|
||||||
|
const canSelectNextItem = computed(
|
||||||
|
() => isItemIndexEditable.value && itemIndex.value < itemsLength.value - 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
function updateItemIndex(index: number) {
|
||||||
|
ndvStore.expressionOutputItemIndex = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextItem() {
|
||||||
|
ndvStore.expressionOutputItemIndex = ndvStore.expressionOutputItemIndex + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function prevItem() {
|
||||||
|
ndvStore.expressionOutputItemIndex = ndvStore.expressionOutputItemIndex - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
ndvStore.expressionOutputItemIndex = 0;
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div :class="visible ? $style.dropdown : $style.hidden">
|
<div v-if="visible" :class="$style.dropdown" title="">
|
||||||
<n8n-text v-if="!noInputData" size="small" compact :class="$style.header">
|
<div :class="$style.header">
|
||||||
{{ i18n.baseText('parameterInput.resultForItem') }} {{ hoveringItemNumber }}
|
<n8n-text bold size="small" compact>
|
||||||
|
{{ i18n.baseText('parameterInput.result') }}
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
|
|
||||||
|
<div :class="$style.item">
|
||||||
|
<n8n-text size="small" color="text-base" compact>
|
||||||
|
{{ i18n.baseText('parameterInput.item') }}
|
||||||
|
</n8n-text>
|
||||||
|
|
||||||
|
<div :class="$style.controls">
|
||||||
|
<N8nInputNumber
|
||||||
|
data-test-id="inline-expression-editor-item-input"
|
||||||
|
size="mini"
|
||||||
|
:controls="false"
|
||||||
|
:class="[$style.input, { [$style.hovering]: isHoveringItem }]"
|
||||||
|
:min="0"
|
||||||
|
:max="max"
|
||||||
|
:model-value="itemIndex"
|
||||||
|
@update:model-value="updateItemIndex"
|
||||||
|
></N8nInputNumber>
|
||||||
|
<N8nIconButton
|
||||||
|
data-test-id="inline-expression-editor-item-prev"
|
||||||
|
icon="chevron-left"
|
||||||
|
type="tertiary"
|
||||||
|
text
|
||||||
|
size="mini"
|
||||||
|
:disabled="!canSelectPrevItem"
|
||||||
|
@click="prevItem"
|
||||||
|
></N8nIconButton>
|
||||||
|
|
||||||
|
<N8nTooltip placement="right" :disabled="hideTableHoverHint">
|
||||||
|
<template #content>
|
||||||
|
<div>{{ i18n.baseText('parameterInput.hoverTableItemTip') }}</div>
|
||||||
|
</template>
|
||||||
|
<N8nIconButton
|
||||||
|
data-test-id="inline-expression-editor-item-next"
|
||||||
|
icon="chevron-right"
|
||||||
|
type="tertiary"
|
||||||
|
text
|
||||||
|
size="mini"
|
||||||
|
:disabled="!canSelectNextItem"
|
||||||
|
@click="nextItem"
|
||||||
|
></N8nIconButton>
|
||||||
|
</N8nTooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<n8n-text :class="$style.body">
|
<n8n-text :class="$style.body">
|
||||||
<ExpressionOutput
|
<ExpressionOutput
|
||||||
data-test-id="inline-expression-editor-output"
|
data-test-id="inline-expression-editor-output"
|
||||||
:segments="segments"
|
:segments="segments"
|
||||||
:extensions="theme"
|
:extensions="theme"
|
||||||
></ExpressionOutput>
|
>
|
||||||
|
</ExpressionOutput>
|
||||||
</n8n-text>
|
</n8n-text>
|
||||||
<div :class="$style.footer">
|
<div :class="$style.footer">
|
||||||
<InlineExpressionTip
|
<InlineExpressionTip
|
||||||
|
@ -52,10 +128,6 @@ const theme = outputTheme();
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dropdown {
|
.dropdown {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -73,7 +145,6 @@ const theme = outputTheme();
|
||||||
background-color: var(--color-code-background);
|
background-color: var(--color-code-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header,
|
|
||||||
.body {
|
.body {
|
||||||
padding: var(--spacing-3xs);
|
padding: var(--spacing-3xs);
|
||||||
}
|
}
|
||||||
|
@ -83,12 +154,22 @@ const theme = outputTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-2xs);
|
||||||
color: var(--color-text-dark);
|
color: var(--color-text-dark);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
padding-left: var(--spacing-2xs);
|
padding: 0 var(--spacing-2xs);
|
||||||
padding-top: var(--spacing-2xs);
|
padding-top: var(--spacing-2xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-4xs);
|
||||||
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
padding-left: var(--spacing-2xs);
|
padding-left: var(--spacing-2xs);
|
||||||
|
@ -98,5 +179,33 @@ const theme = outputTheme();
|
||||||
padding-top: var(--spacing-2xs);
|
padding-top: var(--spacing-2xs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
--input-height: 22px;
|
||||||
|
--input-width: 26px;
|
||||||
|
--input-border-top-left-radius: var(--border-radius-base);
|
||||||
|
--input-border-bottom-left-radius: var(--border-radius-base);
|
||||||
|
--input-border-top-right-radius: var(--border-radius-base);
|
||||||
|
--input-border-bottom-right-radius: var(--border-radius-base);
|
||||||
|
max-width: var(--input-width);
|
||||||
|
line-height: calc(var(--input-height) - var(--spacing-4xs));
|
||||||
|
|
||||||
|
&.hovering {
|
||||||
|
--input-font-color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.el-input__inner) {
|
||||||
|
height: var(--input-height);
|
||||||
|
min-height: var(--input-height);
|
||||||
|
line-height: var(--input-height);
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 var(--spacing-4xs);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -8,6 +8,7 @@ describe('InlineExpressionEditorOutput.vue', () => {
|
||||||
pinia: createTestingPinia(),
|
pinia: createTestingPinia(),
|
||||||
props: {
|
props: {
|
||||||
hoveringItemNumber: 0,
|
hoveringItemNumber: 0,
|
||||||
|
visible: true,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
from: 0,
|
from: 0,
|
||||||
|
@ -56,6 +57,7 @@ describe('InlineExpressionEditorOutput.vue', () => {
|
||||||
pinia: createTestingPinia(),
|
pinia: createTestingPinia(),
|
||||||
props: {
|
props: {
|
||||||
hoveringItemNumber: 0,
|
hoveringItemNumber: 0,
|
||||||
|
visible: true,
|
||||||
segments: [
|
segments: [
|
||||||
{
|
{
|
||||||
kind: 'plaintext',
|
kind: 'plaintext',
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
:segments="segments"
|
:segments="segments"
|
||||||
:is-read-only="isReadOnly"
|
:is-read-only="isReadOnly"
|
||||||
:visible="hasFocus"
|
:visible="hasFocus"
|
||||||
:hovering-item-number="hoveringItemNumber"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -25,7 +24,6 @@ import {
|
||||||
tabKeyMap,
|
tabKeyMap,
|
||||||
} from '@/plugins/codemirror/keymap';
|
} from '@/plugins/codemirror/keymap';
|
||||||
import { n8nAutocompletion } from '@/plugins/codemirror/n8nLang';
|
import { n8nAutocompletion } from '@/plugins/codemirror/n8nLang';
|
||||||
import { useNDVStore } from '@/stores/ndv.store';
|
|
||||||
import { ifNotIn } from '@codemirror/autocomplete';
|
import { ifNotIn } from '@codemirror/autocomplete';
|
||||||
import { history, toggleComment } from '@codemirror/commands';
|
import { history, toggleComment } from '@codemirror/commands';
|
||||||
import { LanguageSupport, bracketMatching, foldGutter, indentOnInput } from '@codemirror/language';
|
import { LanguageSupport, bracketMatching, foldGutter, indentOnInput } from '@codemirror/language';
|
||||||
|
@ -143,11 +141,6 @@ const {
|
||||||
skipSegments: ['Statement', 'CompositeIdentifier', 'Parens', 'Brackets'],
|
skipSegments: ['Statement', 'CompositeIdentifier', 'Parens', 'Brackets'],
|
||||||
isReadOnly: props.isReadOnly,
|
isReadOnly: props.isReadOnly,
|
||||||
});
|
});
|
||||||
const ndvStore = useNDVStore();
|
|
||||||
|
|
||||||
const hoveringItemNumber = computed(() => {
|
|
||||||
return ndvStore.hoveringItemNumber;
|
|
||||||
});
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { createComponentRenderer } from '@/__tests__/render';
|
||||||
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
|
import ExpressionParameterInput from '@/components/ExpressionParameterInput.vue';
|
||||||
import { type TestingPinia, createTestingPinia } from '@pinia/testing';
|
import { type TestingPinia, createTestingPinia } from '@pinia/testing';
|
||||||
import userEvent from '@testing-library/user-event';
|
import userEvent from '@testing-library/user-event';
|
||||||
|
import { waitFor } from '@testing-library/vue';
|
||||||
import { setActivePinia } from 'pinia';
|
import { setActivePinia } from 'pinia';
|
||||||
|
|
||||||
describe('ExpressionParameterInput', () => {
|
describe('ExpressionParameterInput', () => {
|
||||||
|
@ -57,6 +58,7 @@ describe('ExpressionParameterInput', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
const editor = container.querySelector('.cm-content') as HTMLDivElement;
|
const editor = container.querySelector('.cm-content') as HTMLDivElement;
|
||||||
expect(editor).toBeInTheDocument();
|
expect(editor).toBeInTheDocument();
|
||||||
expect(editor.getAttribute('contenteditable')).toEqual('false');
|
expect(editor.getAttribute('contenteditable')).toEqual('false');
|
||||||
|
@ -64,3 +66,4 @@ describe('ExpressionParameterInput', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { createTestingPinia } from '@pinia/testing';
|
||||||
import SqlEditor from '@/components/SqlEditor/SqlEditor.vue';
|
import SqlEditor from '@/components/SqlEditor/SqlEditor.vue';
|
||||||
import { renderComponent } from '@/__tests__/render';
|
import { renderComponent } from '@/__tests__/render';
|
||||||
import { waitFor } from '@testing-library/vue';
|
import { waitFor } from '@testing-library/vue';
|
||||||
|
import { userEvent } from '@testing-library/user-event';
|
||||||
import { setActivePinia } from 'pinia';
|
import { setActivePinia } from 'pinia';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useWorkflowsStore } from '@/stores/workflows.store';
|
import { useWorkflowsStore } from '@/stores/workflows.store';
|
||||||
|
@ -20,6 +21,10 @@ const DEFAULT_SETUP = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function focusEditor(container: Element) {
|
||||||
|
await waitFor(() => expect(container.querySelector('.cm-line')).toBeInTheDocument());
|
||||||
|
await userEvent.click(container.querySelector('.cm-line') as Element);
|
||||||
|
}
|
||||||
const nodes = [
|
const nodes = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
|
@ -70,7 +75,7 @@ describe('SqlEditor.vue', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders basic query', async () => {
|
it('renders basic query', async () => {
|
||||||
const { getByTestId } = renderComponent(SqlEditor, {
|
const { getByTestId, container } = renderComponent(SqlEditor, {
|
||||||
...DEFAULT_SETUP,
|
...DEFAULT_SETUP,
|
||||||
props: {
|
props: {
|
||||||
...DEFAULT_SETUP.props,
|
...DEFAULT_SETUP.props,
|
||||||
|
@ -78,6 +83,7 @@ describe('SqlEditor.vue', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await focusEditor(container);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(getByTestId(EXPRESSION_OUTPUT_TEST_ID)).toHaveTextContent('SELECT * FROM users'),
|
expect(getByTestId(EXPRESSION_OUTPUT_TEST_ID)).toHaveTextContent('SELECT * FROM users'),
|
||||||
);
|
);
|
||||||
|
@ -85,7 +91,7 @@ describe('SqlEditor.vue', () => {
|
||||||
|
|
||||||
it('renders basic query with expression', async () => {
|
it('renders basic query with expression', async () => {
|
||||||
mockResolveExpression().mockReturnValueOnce('users');
|
mockResolveExpression().mockReturnValueOnce('users');
|
||||||
const { getByTestId } = renderComponent(SqlEditor, {
|
const { getByTestId, container } = renderComponent(SqlEditor, {
|
||||||
...DEFAULT_SETUP,
|
...DEFAULT_SETUP,
|
||||||
props: {
|
props: {
|
||||||
...DEFAULT_SETUP.props,
|
...DEFAULT_SETUP.props,
|
||||||
|
@ -93,6 +99,7 @@ describe('SqlEditor.vue', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await focusEditor(container);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(getByTestId(EXPRESSION_OUTPUT_TEST_ID)).toHaveTextContent('SELECT * FROM users'),
|
expect(getByTestId(EXPRESSION_OUTPUT_TEST_ID)).toHaveTextContent('SELECT * FROM users'),
|
||||||
);
|
);
|
||||||
|
@ -100,13 +107,15 @@ describe('SqlEditor.vue', () => {
|
||||||
|
|
||||||
it('renders resolved expressions with dot between resolvables', async () => {
|
it('renders resolved expressions with dot between resolvables', async () => {
|
||||||
mockResolveExpression().mockReturnValueOnce('public.users');
|
mockResolveExpression().mockReturnValueOnce('public.users');
|
||||||
const { getByTestId } = renderComponent(SqlEditor, {
|
const { getByTestId, container } = renderComponent(SqlEditor, {
|
||||||
...DEFAULT_SETUP,
|
...DEFAULT_SETUP,
|
||||||
props: {
|
props: {
|
||||||
...DEFAULT_SETUP.props,
|
...DEFAULT_SETUP.props,
|
||||||
modelValue: 'SELECT * FROM {{ $json.schema }}.{{ $json.table }}',
|
modelValue: 'SELECT * FROM {{ $json.schema }}.{{ $json.table }}',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await focusEditor(container);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(getByTestId(EXPRESSION_OUTPUT_TEST_ID)).toHaveTextContent(
|
expect(getByTestId(EXPRESSION_OUTPUT_TEST_ID)).toHaveTextContent(
|
||||||
'SELECT * FROM public.users',
|
'SELECT * FROM public.users',
|
||||||
|
@ -120,7 +129,7 @@ describe('SqlEditor.vue', () => {
|
||||||
.mockReturnValueOnce('users')
|
.mockReturnValueOnce('users')
|
||||||
.mockReturnValueOnce('id')
|
.mockReturnValueOnce('id')
|
||||||
.mockReturnValueOnce(0);
|
.mockReturnValueOnce(0);
|
||||||
const { getByTestId } = renderComponent(SqlEditor, {
|
const { getByTestId, container } = renderComponent(SqlEditor, {
|
||||||
...DEFAULT_SETUP,
|
...DEFAULT_SETUP,
|
||||||
props: {
|
props: {
|
||||||
...DEFAULT_SETUP.props,
|
...DEFAULT_SETUP.props,
|
||||||
|
@ -128,6 +137,8 @@ describe('SqlEditor.vue', () => {
|
||||||
'SELECT * FROM {{ $json.schema }}.{{ $json.table }} WHERE {{ $json.id }} > {{ $json.limit - 10 }}',
|
'SELECT * FROM {{ $json.schema }}.{{ $json.table }} WHERE {{ $json.id }} > {{ $json.limit - 10 }}',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await focusEditor(container);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(getByTestId(EXPRESSION_OUTPUT_TEST_ID)).toHaveTextContent(
|
expect(getByTestId(EXPRESSION_OUTPUT_TEST_ID)).toHaveTextContent(
|
||||||
'SELECT * FROM public.users WHERE id > 0',
|
'SELECT * FROM public.users WHERE id > 0',
|
||||||
|
@ -141,7 +152,7 @@ describe('SqlEditor.vue', () => {
|
||||||
.mockReturnValueOnce('users')
|
.mockReturnValueOnce('users')
|
||||||
.mockReturnValueOnce(0)
|
.mockReturnValueOnce(0)
|
||||||
.mockReturnValueOnce(false);
|
.mockReturnValueOnce(false);
|
||||||
const { getByTestId } = renderComponent(SqlEditor, {
|
const { getByTestId, container } = renderComponent(SqlEditor, {
|
||||||
...DEFAULT_SETUP,
|
...DEFAULT_SETUP,
|
||||||
props: {
|
props: {
|
||||||
...DEFAULT_SETUP.props,
|
...DEFAULT_SETUP.props,
|
||||||
|
@ -149,6 +160,8 @@ describe('SqlEditor.vue', () => {
|
||||||
'SELECT * FROM {{ $json.schema }}.{{ $json.table }}\n WHERE id > {{ $json.limit - 10 }}\n AND active = {{ $json.active }};',
|
'SELECT * FROM {{ $json.schema }}.{{ $json.table }}\n WHERE id > {{ $json.limit - 10 }}\n AND active = {{ $json.active }};',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await focusEditor(container);
|
||||||
await waitFor(() =>
|
await waitFor(() =>
|
||||||
expect(getByTestId(EXPRESSION_OUTPUT_TEST_ID)).toHaveTextContent(
|
expect(getByTestId(EXPRESSION_OUTPUT_TEST_ID)).toHaveTextContent(
|
||||||
'SELECT * FROM public.users WHERE id > 0 AND active = false;',
|
'SELECT * FROM public.users WHERE id > 0 AND active = false;',
|
||||||
|
|
|
@ -105,7 +105,7 @@ export const useExpressionEditor = ({
|
||||||
const { from, to, text, token } = segment;
|
const { from, to, text, token } = segment;
|
||||||
|
|
||||||
if (token === 'Resolvable') {
|
if (token === 'Resolvable') {
|
||||||
const { resolved, error, fullError } = resolve(text, hoveringItem.value);
|
const { resolved, error, fullError } = resolve(text, targetItem.value);
|
||||||
acc.push({
|
acc.push({
|
||||||
kind: 'resolvable',
|
kind: 'resolvable',
|
||||||
from,
|
from,
|
||||||
|
@ -253,7 +253,7 @@ export const useExpressionEditor = ({
|
||||||
return end !== undefined && expressionExtensionNames.value.has(end);
|
return end !== undefined && expressionExtensionNames.value.has(end);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolve(resolvable: string, hoverItem: TargetItem | null) {
|
function resolve(resolvable: string, target: TargetItem | null) {
|
||||||
const result: { resolved: unknown; error: boolean; fullError: Error | null } = {
|
const result: { resolved: unknown; error: boolean; fullError: Error | null } = {
|
||||||
resolved: undefined,
|
resolved: undefined,
|
||||||
error: false,
|
error: false,
|
||||||
|
@ -268,7 +268,7 @@ export const useExpressionEditor = ({
|
||||||
let opts;
|
let opts;
|
||||||
if (ndvStore.isInputParentOfActiveNode) {
|
if (ndvStore.isInputParentOfActiveNode) {
|
||||||
opts = {
|
opts = {
|
||||||
targetItem: hoverItem ?? undefined,
|
targetItem: target ?? undefined,
|
||||||
inputNodeName: ndvStore.ndvInputNodeName,
|
inputNodeName: ndvStore.ndvInputNodeName,
|
||||||
inputRunIndex: ndvStore.ndvInputRunIndex,
|
inputRunIndex: ndvStore.ndvInputRunIndex,
|
||||||
inputBranchIndex: ndvStore.ndvInputBranchIndex,
|
inputBranchIndex: ndvStore.ndvInputBranchIndex,
|
||||||
|
@ -306,8 +306,21 @@ export const useExpressionEditor = ({
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hoveringItem = computed(() => {
|
const targetItem = computed<TargetItem | null>(() => {
|
||||||
|
if (ndvStore.hoveringItem) {
|
||||||
return ndvStore.hoveringItem;
|
return ndvStore.hoveringItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ndvStore.expressionOutputItemIndex && ndvStore.ndvInputNodeName) {
|
||||||
|
return {
|
||||||
|
nodeName: ndvStore.ndvInputNodeName,
|
||||||
|
runIndex: ndvStore.ndvInputRunIndex ?? 0,
|
||||||
|
outputIndex: ndvStore.ndvInputBranchIndex ?? 0,
|
||||||
|
itemIndex: ndvStore.expressionOutputItemIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
const resolvableSegments = computed<Resolvable[]>(() => {
|
const resolvableSegments = computed<Resolvable[]>(() => {
|
||||||
|
@ -372,14 +385,12 @@ export const useExpressionEditor = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
[
|
[() => workflowsStore.getWorkflowExecution, () => workflowsStore.getWorkflowRunData],
|
||||||
() => workflowsStore.getWorkflowExecution,
|
|
||||||
() => workflowsStore.getWorkflowRunData,
|
|
||||||
() => ndvStore.hoveringItemNumber,
|
|
||||||
],
|
|
||||||
debouncedUpdateSegments,
|
debouncedUpdateSegments,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(targetItem, updateSegments);
|
||||||
|
|
||||||
watch(resolvableSegments, updateHighlighting);
|
watch(resolvableSegments, updateHighlighting);
|
||||||
|
|
||||||
function setCursorPosition(pos: number | 'lastExpression' | 'end'): void {
|
function setCursorPosition(pos: number | 'lastExpression' | 'end'): void {
|
||||||
|
|
|
@ -401,6 +401,7 @@ export const LOCAL_STORAGE_PIN_DATA_DISCOVERY_NDV_FLAG = 'N8N_PIN_DATA_DISCOVERY
|
||||||
export const LOCAL_STORAGE_PIN_DATA_DISCOVERY_CANVAS_FLAG = 'N8N_PIN_DATA_DISCOVERY_CANVAS';
|
export const LOCAL_STORAGE_PIN_DATA_DISCOVERY_CANVAS_FLAG = 'N8N_PIN_DATA_DISCOVERY_CANVAS';
|
||||||
export const LOCAL_STORAGE_MAPPING_IS_ONBOARDED = 'N8N_MAPPING_ONBOARDED';
|
export const LOCAL_STORAGE_MAPPING_IS_ONBOARDED = 'N8N_MAPPING_ONBOARDED';
|
||||||
export const LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED = 'N8N_AUTOCOMPLETE_ONBOARDED';
|
export const LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED = 'N8N_AUTOCOMPLETE_ONBOARDED';
|
||||||
|
export const LOCAL_STORAGE_TABLE_HOVER_IS_ONBOARDED = 'N8N_TABLE_HOVER_ONBOARDED';
|
||||||
export const LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH = 'N8N_MAIN_PANEL_RELATIVE_WIDTH';
|
export const LOCAL_STORAGE_MAIN_PANEL_RELATIVE_WIDTH = 'N8N_MAIN_PANEL_RELATIVE_WIDTH';
|
||||||
export const LOCAL_STORAGE_ACTIVE_MODAL = 'N8N_ACTIVE_MODAL';
|
export const LOCAL_STORAGE_ACTIVE_MODAL = 'N8N_ACTIVE_MODAL';
|
||||||
export const LOCAL_STORAGE_THEME = 'N8N_THEME';
|
export const LOCAL_STORAGE_THEME = 'N8N_THEME';
|
||||||
|
|
|
@ -1244,7 +1244,9 @@
|
||||||
"parameterInput.inputField": "input field",
|
"parameterInput.inputField": "input field",
|
||||||
"parameterInput.dragTipAfterPill": "from the left to use it here.",
|
"parameterInput.dragTipAfterPill": "from the left to use it here.",
|
||||||
"parameterInput.learnMore": "Learn more",
|
"parameterInput.learnMore": "Learn more",
|
||||||
"parameterInput.resultForItem": "Result for Item",
|
"parameterInput.result": "Result",
|
||||||
|
"parameterInput.item": "Item",
|
||||||
|
"parameterInput.hoverTableItemTip": "You can also do this by hovering over input/output items in the table view",
|
||||||
"parameterInput.emptyString": "[empty]",
|
"parameterInput.emptyString": "[empty]",
|
||||||
"parameterInput.customApiCall": "Custom API Call",
|
"parameterInput.customApiCall": "Custom API Call",
|
||||||
"parameterInput.error": "ERROR",
|
"parameterInput.error": "ERROR",
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { useStorage } from '@/composables/useStorage';
|
||||||
import {
|
import {
|
||||||
LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED,
|
LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED,
|
||||||
LOCAL_STORAGE_MAPPING_IS_ONBOARDED,
|
LOCAL_STORAGE_MAPPING_IS_ONBOARDED,
|
||||||
|
LOCAL_STORAGE_TABLE_HOVER_IS_ONBOARDED,
|
||||||
STORES,
|
STORES,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import type { INodeExecutionData, INodeIssues } from 'n8n-workflow';
|
import type { INodeExecutionData, INodeIssues } from 'n8n-workflow';
|
||||||
|
@ -47,6 +48,7 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
focusedInputPath: '',
|
focusedInputPath: '',
|
||||||
mappingTelemetry: {},
|
mappingTelemetry: {},
|
||||||
hoveringItem: null,
|
hoveringItem: null,
|
||||||
|
expressionOutputItemIndex: 0,
|
||||||
draggable: {
|
draggable: {
|
||||||
isDragging: false,
|
isDragging: false,
|
||||||
type: '',
|
type: '',
|
||||||
|
@ -55,6 +57,7 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
activeTarget: null,
|
activeTarget: null,
|
||||||
},
|
},
|
||||||
isMappingOnboarded: useStorage(LOCAL_STORAGE_MAPPING_IS_ONBOARDED).value === 'true',
|
isMappingOnboarded: useStorage(LOCAL_STORAGE_MAPPING_IS_ONBOARDED).value === 'true',
|
||||||
|
isTableHoverOnboarded: useStorage(LOCAL_STORAGE_TABLE_HOVER_IS_ONBOARDED).value === 'true',
|
||||||
isAutocompleteOnboarded: useStorage(LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED).value === 'true',
|
isAutocompleteOnboarded: useStorage(LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED).value === 'true',
|
||||||
highlightDraggables: false,
|
highlightDraggables: false,
|
||||||
}),
|
}),
|
||||||
|
@ -79,14 +82,20 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return executionData.data?.resultData?.runData?.[inputNodeName]?.[inputRunIndex]?.data
|
return (
|
||||||
?.main?.[inputBranchIndex];
|
executionData.data?.resultData?.runData?.[inputNodeName]?.[inputRunIndex]?.data?.main?.[
|
||||||
|
inputBranchIndex
|
||||||
|
] ?? []
|
||||||
|
);
|
||||||
|
},
|
||||||
|
ndvInputDataWithPinnedData(): INodeExecutionData[] {
|
||||||
|
const data = this.ndvInputData;
|
||||||
|
return this.ndvInputNodeName
|
||||||
|
? useWorkflowsStore().pinDataByNodeName(this.ndvInputNodeName) ?? data
|
||||||
|
: data;
|
||||||
},
|
},
|
||||||
hasInputData(): boolean {
|
hasInputData(): boolean {
|
||||||
const data = this.ndvInputData;
|
return this.ndvInputDataWithPinnedData.length > 0;
|
||||||
const pinData =
|
|
||||||
this.ndvInputNodeName && useWorkflowsStore().pinDataByNodeName(this.ndvInputNodeName);
|
|
||||||
return !!(data && data.length > 0) || !!(pinData && pinData.length > 0);
|
|
||||||
},
|
},
|
||||||
getPanelDisplayMode() {
|
getPanelDisplayMode() {
|
||||||
return (panel: NodePanelType) => this[panel].displayMode;
|
return (panel: NodePanelType) => this[panel].displayMode;
|
||||||
|
@ -237,6 +246,7 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
this.mappingTelemetry = {};
|
this.mappingTelemetry = {};
|
||||||
},
|
},
|
||||||
setHoveringItem(item: null | NDVState['hoveringItem']): void {
|
setHoveringItem(item: null | NDVState['hoveringItem']): void {
|
||||||
|
if (item) this.setTableHoverOnboarded();
|
||||||
this.hoveringItem = item;
|
this.hoveringItem = item;
|
||||||
},
|
},
|
||||||
setNDVBranchIndex(e: { pane: 'input' | 'output'; branchIndex: number }): void {
|
setNDVBranchIndex(e: { pane: 'input' | 'output'; branchIndex: number }): void {
|
||||||
|
@ -249,6 +259,10 @@ export const useNDVStore = defineStore(STORES.NDV, {
|
||||||
this.isMappingOnboarded = true;
|
this.isMappingOnboarded = true;
|
||||||
useStorage(LOCAL_STORAGE_MAPPING_IS_ONBOARDED).value = 'true';
|
useStorage(LOCAL_STORAGE_MAPPING_IS_ONBOARDED).value = 'true';
|
||||||
},
|
},
|
||||||
|
setTableHoverOnboarded() {
|
||||||
|
this.isTableHoverOnboarded = true;
|
||||||
|
useStorage(LOCAL_STORAGE_TABLE_HOVER_IS_ONBOARDED).value = 'true';
|
||||||
|
},
|
||||||
setAutocompleteOnboarded() {
|
setAutocompleteOnboarded() {
|
||||||
this.isAutocompleteOnboarded = true;
|
this.isAutocompleteOnboarded = true;
|
||||||
useStorage(LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED).value = 'true';
|
useStorage(LOCAL_STORAGE_AUTOCOMPLETE_IS_ONBOARDED).value = 'true';
|
||||||
|
|
Loading…
Reference in a new issue