feat(editor): Params pane collection improvements (#11607)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions
Benchmark Docker Image CI / build (push) Waiting to run

Co-authored-by: Elias Meire <elsmr@users.noreply.github.com>
This commit is contained in:
Shireen Missi 2024-12-18 20:39:10 +00:00 committed by GitHub
parent 7ce4e8d169
commit 6e44c71c9c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 310 additions and 208 deletions

View file

@ -44,8 +44,7 @@ describe('n8n Form Trigger', () => {
':nth-child(3) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item', ':nth-child(3) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item',
) )
.find('input[placeholder*="e.g. What is your name?"]') .find('input[placeholder*="e.g. What is your name?"]')
.type('Test Field 3') .type('Test Field 3');
.blur();
cy.get( cy.get(
':nth-child(3) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(2) > .parameter-item', ':nth-child(3) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(2) > .parameter-item',
).click(); ).click();
@ -56,27 +55,24 @@ describe('n8n Form Trigger', () => {
':nth-child(4) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item', ':nth-child(4) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(1) > .parameter-item',
) )
.find('input[placeholder*="e.g. What is your name?"]') .find('input[placeholder*="e.g. What is your name?"]')
.type('Test Field 4') .type('Test Field 4');
.blur();
cy.get( cy.get(
':nth-child(4) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(2) > .parameter-item', ':nth-child(4) > .border-top-dashed > .parameter-input-list-wrapper > :nth-child(2) > .parameter-item',
).click(); ).click();
getVisibleSelect().contains('Dropdown').click(); getVisibleSelect().contains('Dropdown').click();
cy.get( cy.contains('button', 'Add Field Option').click();
'.border-top-dashed > :nth-child(2) > :nth-child(3) > .multi-parameter > .fixed-collection-parameter > :nth-child(2) > .button', cy.contains('label', 'Field Options')
).click(); .parent()
cy.get( .nextAll()
':nth-child(4) > :nth-child(1) > :nth-child(2) > :nth-child(3) > .multi-parameter > .fixed-collection-parameter > .fixed-collection-parameter-property > :nth-child(1) > :nth-child(1)', .find('[data-test-id="parameter-input-field"]')
) .eq(0)
.find('input') .type('Option 1');
.type('Option 1') cy.contains('label', 'Field Options')
.blur(); .parent()
cy.get( .nextAll()
':nth-child(4) > :nth-child(1) > :nth-child(2) > :nth-child(3) > .multi-parameter > .fixed-collection-parameter > .fixed-collection-parameter-property > :nth-child(1) > :nth-child(2)', .find('[data-test-id="parameter-input-field"]')
) .eq(1)
.find('input') .type('Option 2');
.type('Option 2')
.blur();
//add optional submitted message //add optional submitted message
cy.get('.param-options').click(); cy.get('.param-options').click();
@ -94,7 +90,6 @@ describe('n8n Form Trigger', () => {
.children() .children()
.children() .children()
.first() .first()
.clear()
.type('Your test form was successfully submitted'); .type('Your test form was successfully submitted');
ndv.getters.backToCanvas().click(); ndv.getters.backToCanvas().click();

View file

@ -65,26 +65,6 @@ describe('NDV', () => {
cy.shouldNotHaveConsoleErrors(); cy.shouldNotHaveConsoleErrors();
}); });
it('should disconect Switch outputs if rules order was changed', () => {
cy.createFixtureWorkflow('NDV-test-switch_reorder.json', 'NDV test switch reorder');
workflowPage.actions.zoomToFit();
workflowPage.actions.executeWorkflow();
workflowPage.actions.openNode('Merge');
ndv.getters.outputPanel().contains('2 items').should('exist');
cy.contains('span', 'first').should('exist');
ndv.getters.backToCanvas().click();
workflowPage.actions.openNode('Switch');
cy.get('.cm-line').realMouseMove(100, 100);
cy.get('.fa-angle-down').first().click();
ndv.getters.backToCanvas().click();
workflowPage.actions.executeWorkflow();
workflowPage.actions.openNode('Merge');
ndv.getters.outputPanel().contains('2 items').should('exist');
cy.contains('span', 'zero').should('exist');
});
it('should show correct validation state for resource locator params', () => { it('should show correct validation state for resource locator params', () => {
workflowPage.actions.addNodeToCanvas('Typeform', true, true); workflowPage.actions.addNodeToCanvas('Typeform', true, true);
ndv.getters.container().should('be.visible'); ndv.getters.container().should('be.visible');

View file

@ -462,6 +462,9 @@
--color-configurable-node-name: var(--color-text-dark); --color-configurable-node-name: var(--color-text-dark);
--color-secondary-link: var(--prim-color-secondary-tint-200); --color-secondary-link: var(--prim-color-secondary-tint-200);
--color-secondary-link-hover: var(--prim-color-secondary-tint-100); --color-secondary-link-hover: var(--prim-color-secondary-tint-100);
//Params
--color-icon-base: var(--color-text-light);
--color-icon-hover: var(--prim-color-primary);
--color-menu-background: var(--prim-gray-740); --color-menu-background: var(--prim-gray-740);
--color-menu-hover-background: var(--prim-gray-670); --color-menu-hover-background: var(--prim-gray-670);

View file

@ -621,6 +621,10 @@
--spacing-3xl: 4rem; --spacing-3xl: 4rem;
--spacing-4xl: 8rem; --spacing-4xl: 8rem;
--spacing-5xl: 16rem; --spacing-5xl: 16rem;
//Params
--color-icon-base: var(--color-text-light);
--color-icon-hover: var(--prim-color-primary);
} }
:root { :root {

View file

@ -82,6 +82,7 @@
"vue-router": "catalog:frontend", "vue-router": "catalog:frontend",
"vue-virtual-scroller": "2.0.0-beta.8", "vue-virtual-scroller": "2.0.0-beta.8",
"vue3-touch-events": "^4.1.3", "vue3-touch-events": "^4.1.3",
"vuedraggable": "4.1.0",
"xss": "catalog:" "xss": "catalog:"
}, },
"devDependencies": { "devDependencies": {

View file

@ -152,6 +152,14 @@ const onBlur = (): void => {
}" }"
data-test-id="assignment" data-test-id="assignment"
> >
<N8nIconButton
v-if="!isReadOnly"
type="tertiary"
text
size="mini"
icon="grip-vertical"
:class="[$style.iconButton, $style.defaultTopPadding, 'drag-handle']"
></N8nIconButton>
<n8n-icon-button <n8n-icon-button
v-if="!isReadOnly" v-if="!isReadOnly"
type="tertiary" type="tertiary"
@ -159,7 +167,7 @@ const onBlur = (): void => {
size="mini" size="mini"
icon="trash" icon="trash"
data-test-id="assignment-remove" data-test-id="assignment-remove"
:class="$style.remove" :class="[$style.iconButton, $style.extraTopPadding]"
@click="onRemove" @click="onRemove"
></n8n-icon-button> ></n8n-icon-button>
@ -241,7 +249,7 @@ const onBlur = (): void => {
} }
&:hover { &:hover {
.remove { .iconButton {
opacity: 1; opacity: 1;
} }
} }
@ -269,12 +277,19 @@ const onBlur = (): void => {
} }
} }
.remove { .iconButton {
position: absolute; position: absolute;
left: 0; left: 0;
top: var(--spacing-l);
opacity: 0; opacity: 0;
transition: opacity 100ms ease-in; transition: opacity 100ms ease-in;
color: var(--icon-base-color);
}
.extraTopPadding {
top: calc(20px + var(--spacing-l));
}
.defaultTopPadding {
top: var(--spacing-l);
} }
.status { .status {

View file

@ -15,6 +15,7 @@ import ParameterOptions from '../ParameterOptions.vue';
import Assignment from './Assignment.vue'; import Assignment from './Assignment.vue';
import { inputDataToAssignments, typeFromExpression } from './utils'; import { inputDataToAssignments, typeFromExpression } from './utils';
import { propertyNameFromExpression } from '@/utils/mappingUtils'; import { propertyNameFromExpression } from '@/utils/mappingUtils';
import Draggable from 'vuedraggable';
interface Props { interface Props {
parameter: INodeProperties; parameter: INodeProperties;
@ -133,19 +134,27 @@ function optionSelected(action: string) {
</n8n-input-label> </n8n-input-label>
<div :class="$style.content"> <div :class="$style.content">
<div :class="$style.assignments"> <div :class="$style.assignments">
<div v-for="(assignment, index) of state.paramValue.assignments" :key="assignment.id"> <Draggable
<Assignment v-model="state.paramValue.assignments"
:model-value="assignment" item-key="id"
:index="index" handle=".drag-handle"
:path="`${path}.${index}`" :drag-class="$style.dragging"
:issues="getIssues(index)" :ghost-class="$style.ghost"
:class="$style.assignment" >
:is-read-only="isReadOnly" <template #item="{ index, element: assignment }">
@update:model-value="(value) => onAssignmentUpdate(index, value)" <Assignment
@remove="() => onAssignmentRemove(index)" :model-value="assignment"
> :index="index"
</Assignment> :path="`${path}.${index}`"
</div> :issues="getIssues(index)"
:class="$style.assignment"
:is-read-only="isReadOnly"
@update:model-value="(value) => onAssignmentUpdate(index, value)"
@remove="() => onAssignmentRemove(index)"
>
</Assignment>
</template>
</Draggable>
</div> </div>
<div <div
v-if="!isReadOnly" v-if="!isReadOnly"
@ -265,4 +274,18 @@ function optionSelected(action: string) {
.icon { .icon {
font-size: var(--font-size-2xl); font-size: var(--font-size-2xl);
} }
.ghost,
.dragging {
border-radius: var(--border-radius-base);
padding-right: var(--spacing-xs);
padding-bottom: var(--spacing-xs);
}
.ghost {
background-color: var(--color-background-base);
opacity: 0.5;
}
.dragging {
background-color: var(--color-background-xlight);
opacity: 0.7;
}
</style> </style>

View file

@ -33,6 +33,7 @@ interface Props {
canRemove?: boolean; canRemove?: boolean;
readOnly?: boolean; readOnly?: boolean;
index?: number; index?: number;
canDrag?: boolean;
} }
const props = withDefaults(defineProps<Props>(), { const props = withDefaults(defineProps<Props>(), {
@ -41,6 +42,7 @@ const props = withDefaults(defineProps<Props>(), {
fixedLeftValue: false, fixedLeftValue: false,
readOnly: false, readOnly: false,
index: 0, index: 0,
canDrag: true,
}); });
const emit = defineEmits<{ const emit = defineEmits<{
@ -152,6 +154,15 @@ const onBlur = (): void => {
}" }"
data-test-id="filter-condition" data-test-id="filter-condition"
> >
<N8nIconButton
v-if="canDrag && !readOnly"
type="tertiary"
text
size="mini"
icon="grip-vertical"
:title="i18n.baseText('filter.dragCondition')"
:class="[$style.iconButton, $style.defaultTopPadding, 'drag-handle']"
></N8nIconButton>
<n8n-icon-button <n8n-icon-button
v-if="canRemove && !readOnly" v-if="canRemove && !readOnly"
type="tertiary" type="tertiary"
@ -160,7 +171,7 @@ const onBlur = (): void => {
icon="trash" icon="trash"
data-test-id="filter-remove-condition" data-test-id="filter-remove-condition"
:title="i18n.baseText('filter.removeCondition')" :title="i18n.baseText('filter.removeCondition')"
:class="$style.remove" :class="[$style.iconButton, $style.extraTopPadding]"
@click="onRemove" @click="onRemove"
></n8n-icon-button> ></n8n-icon-button>
<InputTriple> <InputTriple>
@ -248,7 +259,7 @@ const onBlur = (): void => {
} }
&:hover { &:hover {
.remove { .iconButton {
opacity: 1; opacity: 1;
} }
} }
@ -261,13 +272,21 @@ const onBlur = (): void => {
.statusIcon { .statusIcon {
padding-left: var(--spacing-4xs); padding-left: var(--spacing-4xs);
padding-right: var(--spacing-4xs);
} }
.remove { .iconButton {
position: absolute; position: absolute;
left: 0; left: 0;
top: var(--spacing-l);
opacity: 0; opacity: 0;
transition: opacity 100ms ease-in; transition: opacity 100ms ease-in;
color: var(--icon-base-color);
}
.defaultTopPadding {
top: var(--spacing-m);
}
.extraTopPadding {
top: calc(14px + var(--spacing-m));
} }
</style> </style>

View file

@ -23,6 +23,7 @@ import Condition from './Condition.vue';
import CombinatorSelect from './CombinatorSelect.vue'; import CombinatorSelect from './CombinatorSelect.vue';
import { resolveParameter } from '@/composables/useWorkflowHelpers'; import { resolveParameter } from '@/composables/useWorkflowHelpers';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import Draggable from 'vuedraggable';
interface Props { interface Props {
parameter: INodeProperties; parameter: INodeProperties;
@ -161,30 +162,41 @@ function getIssues(index: number): string[] {
</n8n-input-label> </n8n-input-label>
<div :class="$style.content"> <div :class="$style.content">
<div :class="$style.conditions"> <div :class="$style.conditions">
<div v-for="(condition, index) of state.paramValue.conditions" :key="condition.id"> <Draggable
<CombinatorSelect item-key="id"
v-if="index !== 0" v-model="state.paramValue.conditions"
:read-only="index !== 1 || readOnly" handle=".drag-handle"
:options="allowedCombinators" :drag-class="$style.dragging"
:selected="state.paramValue.combinator" :ghost-class="$style.ghost"
:class="$style.combinator" >
@combinator-change="onCombinatorChange" <template #item="{ index, element: condition }">
/> <div>
<CombinatorSelect
v-if="index !== 0"
:read-only="index !== 1 || readOnly"
:options="allowedCombinators"
:selected="state.paramValue.combinator"
:class="$style.combinator"
@combinator-change="onCombinatorChange"
/>
<Condition <Condition
:condition="condition" :condition="condition"
:index="index" :index="index"
:options="state.paramValue.options" :options="state.paramValue.options"
:fixed-left-value="!!parameter.typeOptions?.filter?.leftValue" :fixed-left-value="!!parameter.typeOptions?.filter?.leftValue"
:read-only="readOnly" :read-only="readOnly"
:can-remove="index !== 0 || state.paramValue.conditions.length > 1" :can-remove="index !== 0 || state.paramValue.conditions.length > 1"
:path="`${path}.${index}`" :can-drag="index !== 0 || state.paramValue.conditions.length > 1"
:issues="getIssues(index)" :path="`${path}.${index}`"
:class="$style.condition" :issues="getIssues(index)"
@update="(value) => onConditionUpdate(index, value)" :class="$style.condition"
@remove="() => onConditionRemove(index)" @update="(value) => onConditionUpdate(index, value)"
></Condition> @remove="() => onConditionRemove(index)"
</div> ></Condition>
</div>
</template>
</Draggable>
</div> </div>
<div v-if="!singleCondition && !readOnly" :class="$style.addConditionWrapper"> <div v-if="!singleCondition && !readOnly" :class="$style.addConditionWrapper">
<n8n-button <n8n-button
@ -224,6 +236,7 @@ function getIssues(index: number): string[] {
.condition { .condition {
padding-left: var(--spacing-l); padding-left: var(--spacing-l);
padding-bottom: var(--spacing-xs);
} }
.single { .single {
@ -266,4 +279,20 @@ function getIssues(index: number): string[] {
outline: none; outline: none;
} }
} }
.ghost,
.dragging {
border-radius: var(--border-radius-base);
padding-right: var(--spacing-xs);
}
.ghost {
background-color: var(--color-background-base);
opacity: 0.5;
}
.dragging {
background-color: var(--color-background-xlight);
opacity: 0.7;
}
.dragging > .combinator {
display: none;
}
</style> </style>

View file

@ -17,6 +17,7 @@ import {
N8nButton, N8nButton,
} from 'n8n-design-system'; } from 'n8n-design-system';
import ParameterInputList from './ParameterInputList.vue'; import ParameterInputList from './ParameterInputList.vue';
import Draggable from 'vuedraggable';
const locale = useI18n(); const locale = useI18n();
@ -126,42 +127,6 @@ const getOptionProperties = (optionName: string) => {
return undefined; return undefined;
}; };
const moveOptionDown = (optionName: string, index: number) => {
if (Array.isArray(mutableValues.value[optionName])) {
mutableValues.value[optionName].splice(
index + 1,
0,
mutableValues.value[optionName].splice(index, 1)[0],
);
}
const parameterData: ValueChangedEvent = {
name: getPropertyPath(optionName),
value: mutableValues.value[optionName],
type: 'optionsOrderChanged',
};
emit('valueChanged', parameterData);
};
const moveOptionUp = (optionName: string, index: number) => {
if (Array.isArray(mutableValues.value[optionName])) {
mutableValues.value?.[optionName].splice(
index - 1,
0,
mutableValues.value[optionName].splice(index, 1)[0],
);
}
const parameterData: ValueChangedEvent = {
name: getPropertyPath(optionName),
value: mutableValues.value[optionName],
type: 'optionsOrderChanged',
};
emit('valueChanged', parameterData);
};
const optionSelected = (optionName: string) => { const optionSelected = (optionName: string) => {
const option = getOptionProperties(optionName); const option = getOptionProperties(optionName);
if (option === undefined) { if (option === undefined) {
@ -219,6 +184,15 @@ const optionSelected = (optionName: string) => {
const valueChanged = (parameterData: IUpdateInformation) => { const valueChanged = (parameterData: IUpdateInformation) => {
emit('valueChanged', parameterData); emit('valueChanged', parameterData);
}; };
const onDragChange = (optionName: string) => {
const parameterData: ValueChangedEvent = {
name: getPropertyPath(optionName),
value: mutableValues.value[optionName],
type: 'optionsOrderChanged',
};
emit('valueChanged', parameterData);
};
</script> </script>
<template> <template>
@ -246,59 +220,61 @@ const valueChanged = (parameterData: IUpdateInformation) => {
color="text-dark" color="text-dark"
/> />
<div v-if="multipleValues"> <div v-if="multipleValues">
<div <Draggable
v-for="(_, index) in mutableValues[property.name]" v-model="mutableValues[property.name]"
:key="property.name + index" handle=".drag-handle"
class="parameter-item" drag-class="dragging"
ghost-class="ghost"
chosen-class="chosen"
@change="onDragChange(property.name)"
> >
<div <template #item="{ index }">
:class="index ? 'border-top-dashed parameter-item-wrapper ' : 'parameter-item-wrapper'" <div :key="property.name + '-' + index" class="parameter-item">
> <div
<div v-if="!isReadOnly" class="delete-option"> :class="
<N8nIconButton index ? 'border-top-dashed parameter-item-wrapper ' : 'parameter-item-wrapper'
type="tertiary" "
text >
size="mini" <div v-if="!isReadOnly" class="icon-button default-top-padding">
icon="trash" <N8nIconButton
data-test-id="fixed-collection-delete" v-if="sortable"
:title="locale.baseText('fixedCollectionParameter.deleteItem')" type="tertiary"
@click="deleteOption(property.name, index)" text
></N8nIconButton> size="mini"
<N8nIconButton icon="grip-vertical"
v-if="sortable && index !== 0" :title="locale.baseText('fixedCollectionParameter.dragItem')"
type="tertiary" class="drag-handle"
text ></N8nIconButton>
size="mini" </div>
icon="angle-up" <div v-if="!isReadOnly" class="icon-button extra-top-padding">
:title="locale.baseText('fixedCollectionParameter.moveUp')" <N8nIconButton
@click="moveOptionUp(property.name, index)" type="tertiary"
></N8nIconButton> text
<N8nIconButton size="mini"
v-if="sortable && index !== mutableValues[property.name].length - 1" icon="trash"
type="tertiary" data-test-id="fixed-collection-delete"
text :title="locale.baseText('fixedCollectionParameter.deleteItem')"
size="mini" @click="deleteOption(property.name, index)"
icon="angle-down" ></N8nIconButton>
:title="locale.baseText('fixedCollectionParameter.moveDown')" </div>
@click="moveOptionDown(property.name, index)" <Suspense>
></N8nIconButton> <ParameterInputList
:parameters="property.values"
:node-values="nodeValues"
:path="getPropertyPath(property.name, index)"
:hide-delete="true"
:is-read-only="isReadOnly"
@value-changed="valueChanged"
/>
</Suspense>
</div>
</div> </div>
<Suspense> </template>
<ParameterInputList </Draggable>
:parameters="property.values"
:node-values="nodeValues"
:path="getPropertyPath(property.name, index)"
:hide-delete="true"
:is-read-only="isReadOnly"
@value-changed="valueChanged"
/>
</Suspense>
</div>
</div>
</div> </div>
<div v-else class="parameter-item"> <div v-else class="parameter-item">
<div class="parameter-item-wrapper"> <div class="parameter-item-wrapper">
<div v-if="!isReadOnly" class="delete-option"> <div v-if="!isReadOnly" class="icon-button">
<N8nIconButton <N8nIconButton
type="tertiary" type="tertiary"
text text
@ -355,7 +331,7 @@ const valueChanged = (parameterData: IUpdateInformation) => {
.fixed-collection-parameter { .fixed-collection-parameter {
padding-left: var(--spacing-s); padding-left: var(--spacing-s);
.delete-option { .icon-button {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -390,21 +366,36 @@ const valueChanged = (parameterData: IUpdateInformation) => {
.fixed-collection-parameter-property { .fixed-collection-parameter-property {
margin: var(--spacing-xs) 0; margin: var(--spacing-xs) 0;
margin-bottom: 0;
} }
.parameter-item:hover > .parameter-item-wrapper > .delete-option { .parameter-item:hover > .parameter-item-wrapper > .icon-button {
opacity: 1; opacity: 1;
} }
.parameter-item { .parameter-item {
position: relative; position: relative;
padding: 0 0 0 1em; padding: 0 0 var(--spacing-s) var(--spacing-s);
+ .parameter-item { + .parameter-item {
.parameter-item-wrapper { .parameter-item-wrapper {
.delete-option { .default-top-padding {
top: 14px; top: calc(1.2 * var(--spacing-s));
} }
.extra-top-padding {
top: calc(2.2 * var(--spacing-s));
}
}
}
}
.parameter-item:first-of-type {
.parameter-item-wrapper {
.default-top-padding {
top: var(--spacing-3xs);
}
.extra-top-padding {
top: var(--spacing-l);
} }
} }
} }
@ -416,4 +407,20 @@ const valueChanged = (parameterData: IUpdateInformation) => {
.no-items-exist { .no-items-exist {
margin: var(--spacing-xs) 0; margin: var(--spacing-xs) 0;
} }
.ghost,
.dragging {
border-radius: var(--border-radius-base);
padding-right: var(--spacing-xs);
}
.ghost {
background-color: var(--color-background-base);
opacity: 0.5;
}
.dragging {
background-color: var(--color-background-xlight);
.parameter-item-wrapper {
border: none;
}
opacity: 0.7;
}
</style> </style>

View file

@ -531,16 +531,6 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
v-else-if="['collection', 'fixedCollection'].includes(parameter.type)" v-else-if="['collection', 'fixedCollection'].includes(parameter.type)"
class="multi-parameter" class="multi-parameter"
> >
<N8nIconButton
v-if="hideDelete !== true && !isReadOnly && !parameter.isNodeSetting"
type="tertiary"
text
size="mini"
icon="trash"
class="delete-option"
:title="i18n.baseText('parameterInputList.delete')"
@click="deleteOption(parameter.name)"
></N8nIconButton>
<N8nInputLabel <N8nInputLabel
:label="i18n.nodeText().inputLabelDisplayName(parameter, path)" :label="i18n.nodeText().inputLabelDisplayName(parameter, path)"
:tooltip-text="i18n.nodeText().inputLabelDescription(parameter, path)" :tooltip-text="i18n.nodeText().inputLabelDescription(parameter, path)"
@ -580,6 +570,16 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
<N8nIcon icon="exclamation-triangle" size="xsmall" /> <N8nIcon icon="exclamation-triangle" size="xsmall" />
{{ i18n.baseText('parameterInputList.loadingError') }} {{ i18n.baseText('parameterInputList.loadingError') }}
</N8nText> </N8nText>
<N8nIconButton
v-if="hideDelete !== true && !isReadOnly && !parameter.isNodeSetting"
type="tertiary"
text
size="mini"
icon="trash"
class="icon-button"
:title="i18n.baseText('parameterInputList.delete')"
@click="deleteOption(parameter.name)"
></N8nIconButton>
</div> </div>
<ResourceMapper <ResourceMapper
v-else-if="parameter.type === 'resourceMapper'" v-else-if="parameter.type === 'resourceMapper'"
@ -620,7 +620,7 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
text text
size="mini" size="mini"
icon="trash" icon="trash"
class="delete-option" class="icon-button"
:title="i18n.baseText('parameterInputList.delete')" :title="i18n.baseText('parameterInputList.delete')"
@click="deleteOption(parameter.name)" @click="deleteOption(parameter.name)"
></N8nIconButton> ></N8nIconButton>
@ -650,12 +650,18 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
<style lang="scss"> <style lang="scss">
.parameter-input-list-wrapper { .parameter-input-list-wrapper {
.delete-option { .icon-button {
position: absolute; position: absolute;
opacity: 0; opacity: 0;
top: 0; top: 0;
left: calc(-1 * var(--spacing-2xs)); left: calc(-0.5 * var(--spacing-2xs));
transition: opacity 100ms ease-in; transition: opacity 100ms ease-in;
Button {
color: var(--color-icon-base);
}
}
.icon-button > Button:hover {
color: var(--color-icon-hover);
} }
.indent > div { .indent > div {
@ -675,8 +681,8 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
position: relative; position: relative;
margin: var(--spacing-xs) 0; margin: var(--spacing-xs) 0;
} }
.parameter-item:hover > .delete-option, .parameter-item:hover > .icon-button,
.multi-parameter:hover > .delete-option { .multi-parameter:hover > .icon-button {
opacity: 1; opacity: 1;
} }

View file

@ -856,6 +856,7 @@
"fixedCollectionParameter.choose": "Choose...", "fixedCollectionParameter.choose": "Choose...",
"fixedCollectionParameter.currentlyNoItemsExist": "Currently no items exist", "fixedCollectionParameter.currentlyNoItemsExist": "Currently no items exist",
"fixedCollectionParameter.deleteItem": "Delete item", "fixedCollectionParameter.deleteItem": "Delete item",
"fixedCollectionParameter.dragItem": "Drag item",
"fixedCollectionParameter.moveDown": "Move down", "fixedCollectionParameter.moveDown": "Move down",
"fixedCollectionParameter.moveUp": "Move up", "fixedCollectionParameter.moveUp": "Move up",
"forgotPassword": "Forgot my password", "forgotPassword": "Forgot my password",
@ -2706,6 +2707,7 @@
"filter.combinator.and": "AND", "filter.combinator.and": "AND",
"filter.addCondition": "Add condition", "filter.addCondition": "Add condition",
"filter.removeCondition": "Remove condition", "filter.removeCondition": "Remove condition",
"filter.dragCondition": "Drag condition",
"filter.maxConditions": "Maximum conditions reached", "filter.maxConditions": "Maximum conditions reached",
"filter.condition.resolvedTrue": "This condition is true for the first input item", "filter.condition.resolvedTrue": "This condition is true for the first input item",
"filter.condition.resolvedFalse": "This condition is false for the first input item", "filter.condition.resolvedFalse": "This condition is false for the first input item",

View file

@ -283,7 +283,7 @@ importers:
version: 4.0.7 version: 4.0.7
axios: axios:
specifier: 'catalog:' specifier: 'catalog:'
version: 1.7.4 version: 1.7.4(debug@4.3.7)
dotenv: dotenv:
specifier: 8.6.0 specifier: 8.6.0
version: 8.6.0 version: 8.6.0
@ -354,7 +354,7 @@ importers:
dependencies: dependencies:
axios: axios:
specifier: 'catalog:' specifier: 'catalog:'
version: 1.7.4 version: 1.7.4(debug@4.3.7)
packages/@n8n/codemirror-lang: packages/@n8n/codemirror-lang:
dependencies: dependencies:
@ -428,7 +428,7 @@ importers:
version: 3.666.0(@aws-sdk/client-sts@3.666.0) version: 3.666.0(@aws-sdk/client-sts@3.666.0)
'@getzep/zep-cloud': '@getzep/zep-cloud':
specifier: 1.0.12 specifier: 1.0.12
version: 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu)) version: 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i))
'@getzep/zep-js': '@getzep/zep-js':
specifier: 0.9.0 specifier: 0.9.0
version: 0.9.0 version: 0.9.0
@ -455,7 +455,7 @@ importers:
version: 0.3.1(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) version: 0.3.1(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)
'@langchain/community': '@langchain/community':
specifier: 0.3.15 specifier: 0.3.15
version: 0.3.15(vc5hvyy27o4cmm4jplsptc2fqm) version: 0.3.15(v4qhcw25bevfr6xzz4fnsvjiqe)
'@langchain/core': '@langchain/core':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) version: 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
@ -542,7 +542,7 @@ importers:
version: 23.0.1 version: 23.0.1
langchain: langchain:
specifier: 0.3.6 specifier: 0.3.6
version: 0.3.6(e4rnrwhosnp2xiru36mqgdy2bu) version: 0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i)
lodash: lodash:
specifier: 'catalog:' specifier: 'catalog:'
version: 4.17.21 version: 4.17.21
@ -801,7 +801,7 @@ importers:
version: 1.11.0 version: 1.11.0
axios: axios:
specifier: 'catalog:' specifier: 'catalog:'
version: 1.7.4 version: 1.7.4(debug@4.3.7)
bcryptjs: bcryptjs:
specifier: 2.4.3 specifier: 2.4.3
version: 2.4.3 version: 2.4.3
@ -1135,7 +1135,7 @@ importers:
version: 1.11.0 version: 1.11.0
axios: axios:
specifier: 'catalog:' specifier: 'catalog:'
version: 1.7.4 version: 1.7.4(debug@4.3.7)
chardet: chardet:
specifier: 2.0.0 specifier: 2.0.0
version: 2.0.0 version: 2.0.0
@ -1431,7 +1431,7 @@ importers:
version: 10.11.0(vue@3.5.13(typescript@5.7.2)) version: 10.11.0(vue@3.5.13(typescript@5.7.2))
axios: axios:
specifier: 'catalog:' specifier: 'catalog:'
version: 1.7.4 version: 1.7.4(debug@4.3.7)
bowser: bowser:
specifier: 2.11.0 specifier: 2.11.0
version: 2.11.0 version: 2.11.0
@ -1534,6 +1534,9 @@ importers:
vue3-touch-events: vue3-touch-events:
specifier: ^4.1.3 specifier: ^4.1.3
version: 4.1.3 version: 4.1.3
vuedraggable:
specifier: 4.1.0
version: 4.1.0(vue@3.5.13(typescript@5.7.2))
xss: xss:
specifier: 'catalog:' specifier: 'catalog:'
version: 1.0.15 version: 1.0.15
@ -1926,7 +1929,7 @@ importers:
version: 0.15.2 version: 0.15.2
axios: axios:
specifier: 'catalog:' specifier: 'catalog:'
version: 1.7.4 version: 1.7.4(debug@4.3.7)
callsites: callsites:
specifier: 3.1.0 specifier: 3.1.0
version: 3.1.0 version: 3.1.0
@ -11834,6 +11837,9 @@ packages:
resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==} resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==}
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
sortablejs@1.14.0:
resolution: {integrity: sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==}
source-map-js@1.2.0: source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -13059,6 +13065,11 @@ packages:
typescript: typescript:
optional: true optional: true
vuedraggable@4.1.0:
resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
peerDependencies:
vue: ^3.0.1
w3c-keyname@2.2.6: w3c-keyname@2.2.6:
resolution: {integrity: sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==} resolution: {integrity: sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==}
@ -15652,7 +15663,7 @@ snapshots:
'@gar/promisify@1.1.3': '@gar/promisify@1.1.3':
optional: true optional: true
'@getzep/zep-cloud@1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu))': '@getzep/zep-cloud@1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i))':
dependencies: dependencies:
form-data: 4.0.0 form-data: 4.0.0
node-fetch: 2.7.0(encoding@0.1.13) node-fetch: 2.7.0(encoding@0.1.13)
@ -15661,7 +15672,7 @@ snapshots:
zod: 3.23.8 zod: 3.23.8
optionalDependencies: optionalDependencies:
'@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) '@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
langchain: 0.3.6(e4rnrwhosnp2xiru36mqgdy2bu) langchain: 0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i)
transitivePeerDependencies: transitivePeerDependencies:
- encoding - encoding
@ -16125,7 +16136,7 @@ snapshots:
- aws-crt - aws-crt
- encoding - encoding
'@langchain/community@0.3.15(vc5hvyy27o4cmm4jplsptc2fqm)': '@langchain/community@0.3.15(v4qhcw25bevfr6xzz4fnsvjiqe)':
dependencies: dependencies:
'@ibm-cloud/watsonx-ai': 1.1.2 '@ibm-cloud/watsonx-ai': 1.1.2
'@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) '@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
@ -16135,7 +16146,7 @@ snapshots:
flat: 5.0.2 flat: 5.0.2
ibm-cloud-sdk-core: 5.1.0 ibm-cloud-sdk-core: 5.1.0
js-yaml: 4.1.0 js-yaml: 4.1.0
langchain: 0.3.6(e4rnrwhosnp2xiru36mqgdy2bu) langchain: 0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i)
langsmith: 0.2.3(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) langsmith: 0.2.3(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
uuid: 10.0.0 uuid: 10.0.0
zod: 3.23.8 zod: 3.23.8
@ -16148,7 +16159,7 @@ snapshots:
'@aws-sdk/client-s3': 3.666.0 '@aws-sdk/client-s3': 3.666.0
'@aws-sdk/credential-provider-node': 3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@aws-sdk/client-sts@3.666.0) '@aws-sdk/credential-provider-node': 3.666.0(@aws-sdk/client-sso-oidc@3.666.0(@aws-sdk/client-sts@3.666.0))(@aws-sdk/client-sts@3.666.0)
'@azure/storage-blob': 12.18.0(encoding@0.1.13) '@azure/storage-blob': 12.18.0(encoding@0.1.13)
'@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu)) '@getzep/zep-cloud': 1.0.12(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)(langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i))
'@getzep/zep-js': 0.9.0 '@getzep/zep-js': 0.9.0
'@google-ai/generativelanguage': 2.6.0(encoding@0.1.13) '@google-ai/generativelanguage': 2.6.0(encoding@0.1.13)
'@google-cloud/storage': 7.12.1(encoding@0.1.13) '@google-cloud/storage': 7.12.1(encoding@0.1.13)
@ -17159,7 +17170,7 @@ snapshots:
'@rudderstack/rudder-sdk-node@2.0.9(tslib@2.6.2)': '@rudderstack/rudder-sdk-node@2.0.9(tslib@2.6.2)':
dependencies: dependencies:
axios: 1.7.4 axios: 1.7.4(debug@4.3.7)
axios-retry: 3.7.0 axios-retry: 3.7.0
component-type: 1.2.1 component-type: 1.2.1
join-component: 1.1.0 join-component: 1.1.0
@ -19456,7 +19467,7 @@ snapshots:
'@babel/runtime': 7.24.7 '@babel/runtime': 7.24.7
is-retry-allowed: 2.2.0 is-retry-allowed: 2.2.0
axios@1.7.4: axios@1.7.4(debug@4.3.7):
dependencies: dependencies:
follow-redirects: 1.15.6(debug@4.3.6) follow-redirects: 1.15.6(debug@4.3.6)
form-data: 4.0.0 form-data: 4.0.0
@ -19464,9 +19475,9 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
axios@1.7.4(debug@4.3.7): axios@1.7.7:
dependencies: dependencies:
follow-redirects: 1.15.6(debug@4.3.7) follow-redirects: 1.15.6(debug@4.3.6)
form-data: 4.0.0 form-data: 4.0.0
proxy-from-env: 1.1.0 proxy-from-env: 1.1.0
transitivePeerDependencies: transitivePeerDependencies:
@ -23332,7 +23343,7 @@ snapshots:
kuler@2.0.0: {} kuler@2.0.0: {}
langchain@0.3.6(e4rnrwhosnp2xiru36mqgdy2bu): langchain@0.3.6(4axcxpjbcq5bce7ff6ajxrpp4i):
dependencies: dependencies:
'@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)) '@langchain/core': 0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))
'@langchain/openai': 0.3.14(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) '@langchain/openai': 0.3.14(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)
@ -23356,7 +23367,7 @@ snapshots:
'@langchain/groq': 0.1.2(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13) '@langchain/groq': 0.1.2(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))(encoding@0.1.13)
'@langchain/mistralai': 0.2.0(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))) '@langchain/mistralai': 0.2.0(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))
'@langchain/ollama': 0.1.2(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8))) '@langchain/ollama': 0.1.2(@langchain/core@0.3.19(openai@4.73.1(encoding@0.1.13)(zod@3.23.8)))
axios: 1.7.4 axios: 1.7.4(debug@4.3.7)
cheerio: 1.0.0 cheerio: 1.0.0
handlebars: 4.7.8 handlebars: 4.7.8
transitivePeerDependencies: transitivePeerDependencies:
@ -26189,6 +26200,8 @@ snapshots:
smart-buffer: 4.2.0 smart-buffer: 4.2.0
optional: true optional: true
sortablejs@1.14.0: {}
source-map-js@1.2.0: {} source-map-js@1.2.0: {}
source-map-js@1.2.1: {} source-map-js@1.2.1: {}
@ -27476,6 +27489,11 @@ snapshots:
optionalDependencies: optionalDependencies:
typescript: 5.7.2 typescript: 5.7.2
vuedraggable@4.1.0(vue@3.5.13(typescript@5.7.2)):
dependencies:
sortablejs: 1.14.0
vue: 3.5.13(typescript@5.7.2)
w3c-keyname@2.2.6: {} w3c-keyname@2.2.6: {}
w3c-xmlserializer@3.0.0: w3c-xmlserializer@3.0.0: