refactor(editor): Migrate FixedCollectionParameter to composition API (#11555)

Co-authored-by: Elias Meire <elias@meire.dev>
This commit is contained in:
Shireen Missi 2024-11-06 14:40:49 +00:00 committed by GitHub
parent 93fae5d8a7
commit 499c54b29a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 386 additions and 259 deletions

View file

@ -22,6 +22,7 @@ import DraggableTarget from './DraggableTarget.vue';
import { dropInExpressionEditor } from '@/plugins/codemirror/dragAndDrop';
import { APP_MODALS_ELEMENT_ID } from '@/constants';
import { N8nInput, N8nText } from 'n8n-design-system';
type Props = {
parameter: INodeProperties;

View file

@ -0,0 +1,110 @@
import { createComponentRenderer } from '@/__tests__/render';
import { cleanupAppModals, createAppModals, SETTINGS_STORE_DEFAULT_STATE } from '@/__tests__/utils';
import FixedCollectionParameter, { type Props } from '@/components/FixedCollectionParameter.vue';
import { STORES } from '@/constants';
import { createTestingPinia } from '@pinia/testing';
import userEvent from '@testing-library/user-event';
import { setActivePinia } from 'pinia';
describe('FixedCollectionParameter.vue', () => {
const pinia = createTestingPinia({
initialState: {
[STORES.SETTINGS]: {
settings: SETTINGS_STORE_DEFAULT_STATE.settings,
},
},
});
setActivePinia(pinia);
const props: Props = {
parameter: {
displayName: 'Routing Rules',
name: 'rules',
placeholder: 'Add Routing Rule',
type: 'fixedCollection',
typeOptions: {
multipleValues: true,
sortable: true,
},
default: '',
options: [
{
name: 'values',
displayName: 'Values',
values: [
{
displayName: 'Output Name',
name: 'outputKey',
type: 'string',
default: 'Default Output Name',
},
],
},
],
},
path: 'parameters.rules',
nodeValues: {
parameters: {
rules: { values: [{ outputKey: 'Test Output Name' }] },
},
},
values: {
values: [{ outputKey: 'Test Output Name' }],
},
isReadOnly: false,
};
const renderComponent = createComponentRenderer(FixedCollectionParameter, { props });
beforeEach(() => {
createAppModals();
});
afterEach(() => {
cleanupAppModals();
});
it('renders the component', () => {
const { getByTestId } = renderComponent();
expect(getByTestId('fixed-collection-rules')).toBeInTheDocument();
expect(getByTestId('fixed-collection-add')).toBeInTheDocument();
expect(getByTestId('fixed-collection-delete')).toBeInTheDocument();
expect(getByTestId('parameter-item')).toBeInTheDocument();
});
it('computes placeholder text correctly', () => {
const { getByTestId } = renderComponent();
expect(getByTestId('fixed-collection-add')).toHaveTextContent('Add Routing Rule');
});
it('emits valueChanged event on option creation', async () => {
const { getByTestId, emitted } = renderComponent();
await userEvent.click(getByTestId('fixed-collection-add'));
expect(emitted('valueChanged')).toEqual([
[
{
name: 'parameters.rules.values',
value: [{ outputKey: 'Test Output Name' }, { outputKey: 'Default Output Name' }],
},
],
]);
});
it('emits valueChanged event on option deletion', async () => {
const { getByTestId, emitted } = renderComponent({
props: {
...props,
values: {
values: [{ outputKey: 'Test' }],
},
},
});
await userEvent.click(getByTestId('fixed-collection-delete'));
expect(emitted('valueChanged')).toEqual([
[
{
name: 'parameters.rules.values',
value: undefined,
},
],
]);
});
});

View file

@ -1,159 +1,173 @@
<script lang="ts">
import { defineComponent } from 'vue';
import type { PropType } from 'vue';
<script lang="ts" setup>
import type { IUpdateInformation } from '@/Interface';
import type { INodeParameters, INodeProperties, INodePropertyCollection } from 'n8n-workflow';
import type { INodeParameters, INodeProperties, NodeParameterValueType } from 'n8n-workflow';
import { deepCopy, isINodePropertyCollectionList } from 'n8n-workflow';
import { get } from 'lodash-es';
export default defineComponent({
name: 'FixedCollectionParameter',
props: {
nodeValues: {
type: Object as PropType<INodeParameters>,
required: true,
},
parameter: {
type: Object as PropType<INodeProperties>,
required: true,
},
path: {
type: String,
required: true,
},
values: {
type: Object as PropType<Record<string, INodeParameters[]>>,
default: () => ({}),
},
isReadOnly: {
type: Boolean,
default: false,
},
},
data() {
return {
selectedOption: undefined,
mutableValues: {} as Record<string, INodeParameters[]>,
};
},
computed: {
getPlaceholderText(): string {
const placeholder = this.$locale.nodeText().placeholder(this.parameter, this.path);
return placeholder ? placeholder : this.$locale.baseText('fixedCollectionParameter.choose');
},
getProperties(): INodePropertyCollection[] {
import { computed, ref, watch, onBeforeMount } from 'vue';
import { useI18n } from '@/composables/useI18n';
import {
N8nIconButton,
N8nSelect,
N8nOption,
N8nInputLabel,
N8nText,
N8nButton,
} from 'n8n-design-system';
import ParameterInputList from './ParameterInputList.vue';
const locale = useI18n();
export type Props = {
nodeValues: INodeParameters;
parameter: INodeProperties;
path: string;
values?: Record<string, INodeParameters[]>;
isReadOnly?: boolean;
};
type ValueChangedEvent = {
name: string;
value: NodeParameterValueType;
type?: 'optionsOrderChanged';
};
const props = withDefaults(defineProps<Props>(), {
values: () => ({}),
isReadOnly: false,
});
const emit = defineEmits<{
valueChanged: [value: ValueChangedEvent];
}>();
const getPlaceholderText = computed(() => {
const placeholder = locale.nodeText().placeholder(props.parameter, props.path);
return placeholder ? placeholder : locale.baseText('fixedCollectionParameter.choose');
});
const mutableValues = ref({} as Record<string, INodeParameters[]>);
const selectedOption = ref<string | null | undefined>(null);
const propertyNames = computed(() => {
return new Set(Object.keys(mutableValues.value || {}));
});
const getProperties = computed(() => {
const returnProperties = [];
let tempProperties;
for (const name of this.propertyNames) {
tempProperties = this.getOptionProperties(name);
for (const name of propertyNames.value) {
tempProperties = getOptionProperties(name);
if (tempProperties !== undefined) {
returnProperties.push(tempProperties);
}
}
return returnProperties;
},
multipleValues(): boolean {
return !!this.parameter.typeOptions?.multipleValues;
},
parameterOptions(): INodePropertyCollection[] {
if (this.multipleValues && isINodePropertyCollectionList(this.parameter.options)) {
return this.parameter.options;
});
const multipleValues = computed(() => {
return !!props.parameter.typeOptions?.multipleValues;
});
const parameterOptions = computed(() => {
if (!isINodePropertyCollectionList(props.parameter.options)) return [];
if (multipleValues.value) {
return props.parameter.options;
}
return (this.parameter.options as INodePropertyCollection[]).filter((option) => {
return !this.propertyNames.includes(option.name);
return (props.parameter.options ?? []).filter((option) => {
return !propertyNames.value.has(option.name);
});
});
const sortable = computed(() => {
return !!props.parameter.typeOptions?.sortable;
});
watch(
() => props.values,
(newValues: Record<string, INodeParameters[]>) => {
mutableValues.value = deepCopy(newValues);
},
propertyNames(): string[] {
return Object.keys(this.mutableValues || {});
},
sortable(): boolean {
return !!this.parameter.typeOptions?.sortable;
},
},
watch: {
values: {
handler(newValues: Record<string, INodeParameters[]>) {
this.mutableValues = deepCopy(newValues);
},
deep: true,
},
},
created() {
this.mutableValues = deepCopy(this.values);
},
methods: {
deleteOption(optionName: string, index?: number) {
const currentOptionsOfSameType = this.mutableValues[optionName];
{ deep: true },
);
onBeforeMount(() => {
mutableValues.value = deepCopy(props.values);
});
const deleteOption = (optionName: string, index?: number) => {
const currentOptionsOfSameType = mutableValues.value[optionName];
if (!currentOptionsOfSameType || currentOptionsOfSameType.length > 1) {
// it's not the only option of this type, so just remove it.
this.$emit('valueChanged', {
name: this.getPropertyPath(optionName, index),
emit('valueChanged', {
name: getPropertyPath(optionName, index),
value: undefined,
});
} else {
// it's the only option, so remove the whole type
this.$emit('valueChanged', {
name: this.getPropertyPath(optionName),
emit('valueChanged', {
name: getPropertyPath(optionName),
value: undefined,
});
}
},
getPropertyPath(name: string, index?: number) {
return `${this.path}.${name}` + (index !== undefined ? `[${index}]` : '');
},
getOptionProperties(optionName: string): INodePropertyCollection | undefined {
if (isINodePropertyCollectionList(this.parameter.options)) {
for (const option of this.parameter.options) {
};
const getPropertyPath = (name: string, index?: number) => {
return `${props.path}.${name}` + (index !== undefined ? `[${index}]` : '');
};
const getOptionProperties = (optionName: string) => {
if (isINodePropertyCollectionList(props.parameter.options)) {
for (const option of props.parameter.options) {
if (option.name === optionName) {
return option;
}
}
}
return undefined;
},
moveOptionDown(optionName: string, index: number) {
if (Array.isArray(this.mutableValues[optionName])) {
this.mutableValues[optionName].splice(
};
const moveOptionDown = (optionName: string, index: number) => {
if (Array.isArray(mutableValues.value[optionName])) {
mutableValues.value[optionName].splice(
index + 1,
0,
this.mutableValues[optionName].splice(index, 1)[0],
mutableValues.value[optionName].splice(index, 1)[0],
);
}
const parameterData = {
name: this.getPropertyPath(optionName),
value: this.mutableValues[optionName],
const parameterData: ValueChangedEvent = {
name: getPropertyPath(optionName),
value: mutableValues.value[optionName],
type: 'optionsOrderChanged',
};
this.$emit('valueChanged', parameterData);
},
moveOptionUp(optionName: string, index: number) {
if (Array.isArray(this.mutableValues[optionName])) {
this.mutableValues?.[optionName].splice(
emit('valueChanged', parameterData);
};
const moveOptionUp = (optionName: string, index: number) => {
if (Array.isArray(mutableValues.value[optionName])) {
mutableValues.value?.[optionName].splice(
index - 1,
0,
this.mutableValues[optionName].splice(index, 1)[0],
mutableValues.value[optionName].splice(index, 1)[0],
);
}
const parameterData = {
name: this.getPropertyPath(optionName),
value: this.mutableValues[optionName],
const parameterData: ValueChangedEvent = {
name: getPropertyPath(optionName),
value: mutableValues.value[optionName],
type: 'optionsOrderChanged',
};
this.$emit('valueChanged', parameterData);
},
optionSelected(optionName: string) {
const option = this.getOptionProperties(optionName);
emit('valueChanged', parameterData);
};
const optionSelected = (optionName: string) => {
const option = getOptionProperties(optionName);
if (option === undefined) {
return;
}
const name = `${this.path}.${option.name}`;
const name = `${props.path}.${option.name}`;
const newParameterValue: INodeParameters = {};
@ -169,14 +183,11 @@ export default defineComponent({
optionParameter.typeOptions.multipleValues === true
) {
// Multiple values are allowed so append option to array
const multiValue = get(this.nodeValues, [this.path, optionParameter.name], []);
const multiValue = get(props.nodeValues, [props.path, optionParameter.name], []);
if (Array.isArray(optionParameter.default)) {
multiValue.push(...deepCopy(optionParameter.default));
} else if (
optionParameter.default !== '' &&
typeof optionParameter.default !== 'object'
) {
} else if (optionParameter.default !== '' && typeof optionParameter.default !== 'object') {
multiValue.push(deepCopy(optionParameter.default));
}
@ -187,9 +198,9 @@ export default defineComponent({
}
}
let newValue;
if (this.multipleValues) {
newValue = get(this.nodeValues, name, []) as INodeParameters[];
let newValue: NodeParameterValueType;
if (multipleValues.value) {
newValue = get(props.nodeValues, name, []) as INodeParameters[];
newValue.push(newParameterValue);
} else {
@ -201,26 +212,25 @@ export default defineComponent({
value: newValue,
};
this.$emit('valueChanged', parameterData);
this.selectedOption = undefined;
},
valueChanged(parameterData: IUpdateInformation) {
this.$emit('valueChanged', parameterData);
},
},
});
emit('valueChanged', parameterData);
selectedOption.value = undefined;
};
const valueChanged = (parameterData: IUpdateInformation) => {
emit('valueChanged', parameterData);
};
</script>
<template>
<div
class="fixed-collection-parameter"
:data-test-id="`fixed-collection-${parameter.name}`"
:data-test-id="`fixed-collection-${props.parameter?.name}`"
@keydown.stop
>
<div v-if="getProperties.length === 0" class="no-items-exist">
<n8n-text size="small">{{
$locale.baseText('fixedCollectionParameter.currentlyNoItemsExist')
}}</n8n-text>
<N8nText size="small">{{
locale.baseText('fixedCollectionParameter.currentlyNoItemsExist')
}}</N8nText>
</div>
<div
@ -228,9 +238,9 @@ export default defineComponent({
:key="property.name"
class="fixed-collection-parameter-property"
>
<n8n-input-label
<N8nInputLabel
v-if="property.displayName !== '' && parameter.options && parameter.options.length !== 1"
:label="$locale.nodeText().inputLabelDisplayName(property, path)"
:label="locale.nodeText().inputLabelDisplayName(property, path)"
:underline="true"
size="small"
color="text-dark"
@ -245,32 +255,33 @@ export default defineComponent({
:class="index ? 'border-top-dashed parameter-item-wrapper ' : 'parameter-item-wrapper'"
>
<div v-if="!isReadOnly" class="delete-option">
<n8n-icon-button
<N8nIconButton
type="tertiary"
text
size="mini"
icon="trash"
:title="$locale.baseText('fixedCollectionParameter.deleteItem')"
data-test-id="fixed-collection-delete"
:title="locale.baseText('fixedCollectionParameter.deleteItem')"
@click="deleteOption(property.name, index)"
></n8n-icon-button>
<n8n-icon-button
></N8nIconButton>
<N8nIconButton
v-if="sortable && index !== 0"
type="tertiary"
text
size="mini"
icon="angle-up"
:title="$locale.baseText('fixedCollectionParameter.moveUp')"
:title="locale.baseText('fixedCollectionParameter.moveUp')"
@click="moveOptionUp(property.name, index)"
></n8n-icon-button>
<n8n-icon-button
></N8nIconButton>
<N8nIconButton
v-if="sortable && index !== mutableValues[property.name].length - 1"
type="tertiary"
text
size="mini"
icon="angle-down"
:title="$locale.baseText('fixedCollectionParameter.moveDown')"
:title="locale.baseText('fixedCollectionParameter.moveDown')"
@click="moveOptionDown(property.name, index)"
></n8n-icon-button>
></N8nIconButton>
</div>
<Suspense>
<ParameterInputList
@ -288,14 +299,15 @@ export default defineComponent({
<div v-else class="parameter-item">
<div class="parameter-item-wrapper">
<div v-if="!isReadOnly" class="delete-option">
<n8n-icon-button
<N8nIconButton
type="tertiary"
text
size="mini"
icon="trash"
:title="$locale.baseText('fixedCollectionParameter.deleteItem')"
data-test-id="fixed-collection-delete"
:title="locale.baseText('fixedCollectionParameter.deleteItem')"
@click="deleteOption(property.name)"
></n8n-icon-button>
></N8nIconButton>
</div>
<ParameterInputList
:parameters="property.values"
@ -311,7 +323,7 @@ export default defineComponent({
</div>
<div v-if="parameterOptions.length > 0 && !isReadOnly" class="controls">
<n8n-button
<N8nButton
v-if="parameter.options && parameter.options.length === 1"
type="tertiary"
block
@ -320,20 +332,20 @@ export default defineComponent({
@click="optionSelected(parameter.options[0].name)"
/>
<div v-else class="add-option">
<n8n-select
<N8nSelect
v-model="selectedOption"
:placeholder="getPlaceholderText"
size="small"
filterable
@update:model-value="optionSelected"
>
<n8n-option
<N8nOption
v-for="item in parameterOptions"
:key="item.name"
:label="$locale.nodeText().collectionOptionDisplayName(parameter, item, path)"
:label="locale.nodeText().collectionOptionDisplayName(parameter, item, path)"
:value="item.name"
></n8n-option>
</n8n-select>
></N8nOption>
</N8nSelect>
</div>
</div>
</div>

View file

@ -8,6 +8,8 @@ import { useI18n } from '@/composables/useI18n';
import type { IUpdateInformation } from '@/Interface';
import CollectionParameter from '@/components/CollectionParameter.vue';
import ParameterInputFull from '@/components/ParameterInputFull.vue';
import { N8nButton, N8nInputLabel, N8nText } from 'n8n-design-system';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
defineOptions({ name: 'MultipleParameter' });
@ -42,7 +44,7 @@ watch(
);
const addButtonText = computed(() => {
if (!props.parameter.typeOptions || !props.parameter.typeOptions.multipleValueButtonText) {
if (!props.parameter.typeOptions?.multipleValueButtonText) {
return i18n.baseText('multipleParameter.addItem');
}
@ -105,7 +107,7 @@ const valueChanged = (parameterData: IUpdateInformation) => {
<template>
<div class="duplicate-parameter" @keydown.stop>
<n8n-input-label
<N8nInputLabel
:label="i18n.nodeText().inputLabelDisplayName(parameter, path)"
:tooltip-text="i18n.nodeText().inputLabelDescription(parameter, path)"
:underline="true"
@ -120,20 +122,20 @@ const valueChanged = (parameterData: IUpdateInformation) => {
:class="parameter.type"
>
<div v-if="!isReadOnly" class="delete-item clickable">
<font-awesome-icon
<FontAwesomeIcon
icon="trash"
:title="i18n.baseText('multipleParameter.deleteItem')"
@click="deleteItem(index)"
/>
<div v-if="sortable">
<font-awesome-icon
<FontAwesomeIcon
v-if="index !== 0"
icon="angle-up"
class="clickable"
:title="i18n.baseText('multipleParameter.moveUp')"
@click="moveOptionUp(index)"
/>
<font-awesome-icon
<FontAwesomeIcon
v-if="index !== mutableValues.length - 1"
icon="angle-down"
class="clickable"
@ -173,11 +175,11 @@ const valueChanged = (parameterData: IUpdateInformation) => {
v-if="(mutableValues && mutableValues.length === 0) || isReadOnly"
class="no-items-exist"
>
<n8n-text size="small">{{
<N8nText size="small">{{
i18n.baseText('multipleParameter.currentlyNoItemsExist')
}}</n8n-text>
}}</N8nText>
</div>
<n8n-button
<N8nButton
v-if="!isReadOnly"
type="tertiary"
block

View file

@ -61,7 +61,7 @@ import { useNodeTypesStore } from '@/stores/nodeTypes.store';
import { useSettingsStore } from '@/stores/settings.store';
import { useWorkflowsStore } from '@/stores/workflows.store';
import { isCredentialOnlyNodeType } from '@/utils/credentialOnlyNodes';
import { N8nInput, N8nSelect } from 'n8n-design-system';
import { N8nIcon, N8nInput, N8nInputNumber, N8nOption, N8nSelect } from 'n8n-design-system';
import type { EventBus } from 'n8n-design-system/utils';
import { createEventBus } from 'n8n-design-system/utils';
import { useRouter } from 'vue-router';
@ -1172,7 +1172,7 @@ onUpdated(async () => {
@update:model-value="valueChangedDebounced"
>
<template #suffix>
<n8n-icon
<N8nIcon
v-if="!editorIsReadOnly"
data-test-id="code-editor-fullscreen-button"
icon="external-link-alt"
@ -1195,7 +1195,7 @@ onUpdated(async () => {
@update:model-value="valueChangedDebounced"
>
<template #suffix>
<n8n-icon
<N8nIcon
data-test-id="code-editor-fullscreen-button"
icon="external-link-alt"
size="xsmall"
@ -1216,7 +1216,7 @@ onUpdated(async () => {
@update:model-value="valueChangedDebounced"
>
<template #suffix>
<n8n-icon
<N8nIcon
data-test-id="code-editor-fullscreen-button"
icon="external-link-alt"
size="xsmall"
@ -1237,7 +1237,7 @@ onUpdated(async () => {
@update:model-value="valueChangedDebounced"
>
<template #suffix>
<n8n-icon
<N8nIcon
v-if="!editorIsReadOnly"
data-test-id="code-editor-fullscreen-button"
icon="external-link-alt"
@ -1258,7 +1258,7 @@ onUpdated(async () => {
@update:model-value="valueChangedDebounced"
>
<template #suffix>
<n8n-icon
<N8nIcon
data-test-id="code-editor-fullscreen-button"
icon="external-link-alt"
size="xsmall"
@ -1302,7 +1302,7 @@ onUpdated(async () => {
@blur="onBlur"
>
<template #suffix>
<n8n-icon
<N8nIcon
v-if="!isReadOnly && !isSecretParameter"
icon="external-link-alt"
size="xsmall"
@ -1422,9 +1422,9 @@ onUpdated(async () => {
@focus="setFocus"
@blur="onBlur"
>
<n8n-option
<N8nOption
v-for="option in parameterOptions"
:key="option.value"
:key="option.value.toString()"
:value="option.value"
:label="getOptionsOptionDisplayName(option)"
>
@ -1441,7 +1441,7 @@ onUpdated(async () => {
v-n8n-html="getOptionsOptionDescription(option)"
></div>
</div>
</n8n-option>
</N8nOption>
</N8nSelect>
<N8nSelect
@ -1460,9 +1460,9 @@ onUpdated(async () => {
@focus="setFocus"
@blur="onBlur"
>
<n8n-option
<N8nOption
v-for="option in parameterOptions"
:key="option.value"
:key="option.value.toString()"
:value="option.value"
:label="getOptionsOptionDisplayName(option)"
>
@ -1474,7 +1474,7 @@ onUpdated(async () => {
v-n8n-html="getOptionsOptionDescription(option)"
></div>
</div>
</n8n-option>
</N8nOption>
</N8nSelect>
<!-- temporary state of booleans while data is mapped -->

View file

@ -13,6 +13,7 @@ import { hasExpressionMapping, hasOnlyListMode, isValueExpression } from '@/util
import { isResourceLocatorValue } from '@/utils/typeGuards';
import { createEventBus } from 'n8n-design-system/utils';
import type { INodeProperties, IParameterLabel, NodeParameterValueType } from 'n8n-workflow';
import { N8nInputLabel } from 'n8n-design-system';
type Props = {
parameter: INodeProperties;
@ -191,7 +192,7 @@ function onDrop(newParamValue: string) {
</script>
<template>
<n8n-input-label
<N8nInputLabel
:class="[$style.wrapper]"
:label="hideLabel ? '' : i18n.nodeText().inputLabelDisplayName(parameter, path)"
:tooltip-text="hideLabel ? '' : i18n.nodeText().inputLabelDescription(parameter, path)"
@ -262,7 +263,7 @@ function onDrop(newParamValue: string) {
@menu-expanded="onMenuExpanded"
/>
</div>
</n8n-input-label>
</N8nInputLabel>
</template>
<style lang="scss" module>

View file

@ -35,6 +35,7 @@ import {
import { get, set } from 'lodash-es';
import { useRouter } from 'vue-router';
import { captureException } from '@sentry/vue';
import { N8nNotice, N8nIconButton, N8nInputLabel, N8nText, N8nIcon } from 'n8n-design-system';
const LazyFixedCollectionParameter = defineAsyncComponent(
async () => await import('./FixedCollectionParameter.vue'),
@ -498,7 +499,7 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
@value-changed="valueChanged"
/>
<n8n-notice
<N8nNotice
v-else-if="parameter.type === 'notice'"
:class="['parameter-item', parameter.typeOptions?.containerClass ?? '']"
:content="$locale.nodeText().inputLabelDisplayName(parameter, path)"
@ -519,7 +520,7 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
v-else-if="['collection', 'fixedCollection'].includes(parameter.type)"
class="multi-parameter"
>
<n8n-icon-button
<N8nIconButton
v-if="hideDelete !== true && !isReadOnly && !parameter.isNodeSetting"
type="tertiary"
text
@ -528,8 +529,8 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
class="delete-option"
:title="$locale.baseText('parameterInputList.delete')"
@click="deleteOption(parameter.name)"
></n8n-icon-button>
<n8n-input-label
></N8nIconButton>
<N8nInputLabel
:label="$locale.nodeText().inputLabelDisplayName(parameter, path)"
:tooltip-text="$locale.nodeText().inputLabelDescription(parameter, path)"
size="small"
@ -558,16 +559,16 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
/>
</template>
<template #fallback>
<n8n-text size="small" class="async-notice">
<n8n-icon icon="sync-alt" size="xsmall" :spin="true" />
<N8nText size="small" class="async-notice">
<N8nIcon icon="sync-alt" size="xsmall" :spin="true" />
{{ $locale.baseText('parameterInputList.loadingFields') }}
</n8n-text>
</N8nText>
</template>
</Suspense>
<n8n-text v-else size="small" color="danger" class="async-notice">
<n8n-icon icon="exclamation-triangle" size="xsmall" />
<N8nText v-else size="small" color="danger" class="async-notice">
<N8nIcon icon="exclamation-triangle" size="xsmall" />
{{ $locale.baseText('parameterInputList.loadingError') }}
</n8n-text>
</N8nText>
</div>
<ResourceMapper
v-else-if="parameter.type === 'resourceMapper'"
@ -602,7 +603,7 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
v-else-if="displayNodeParameter(parameter) && credentialsParameterIndex !== index"
class="parameter-item"
>
<n8n-icon-button
<N8nIconButton
v-if="hideDelete !== true && !isReadOnly && !parameter.isNodeSetting"
type="tertiary"
text
@ -611,7 +612,7 @@ function getParameterValue<T extends NodeParameterValueType = NodeParameterValue
class="delete-option"
:title="$locale.baseText('parameterInputList.delete')"
@click="deleteOption(parameter.name)"
></n8n-icon-button>
></N8nIconButton>
<ParameterInputFull
:parameter="parameter"