mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-12 15:44:06 -08:00
refactor(editor): Migrate FixedCollectionParameter to composition API (#11555)
Co-authored-by: Elias Meire <elias@meire.dev>
This commit is contained in:
parent
93fae5d8a7
commit
499c54b29a
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
});
|
|
@ -1,226 +1,236 @@
|
|||
<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[] {
|
||||
const returnProperties = [];
|
||||
let tempProperties;
|
||||
for (const name of this.propertyNames) {
|
||||
tempProperties = this.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;
|
||||
}
|
||||
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';
|
||||
|
||||
return (this.parameter.options as INodePropertyCollection[]).filter((option) => {
|
||||
return !this.propertyNames.includes(option.name);
|
||||
});
|
||||
},
|
||||
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];
|
||||
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),
|
||||
value: undefined,
|
||||
});
|
||||
} else {
|
||||
// it's the only option, so remove the whole type
|
||||
this.$emit('valueChanged', {
|
||||
name: this.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) {
|
||||
if (option.name === optionName) {
|
||||
return option;
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
moveOptionDown(optionName: string, index: number) {
|
||||
if (Array.isArray(this.mutableValues[optionName])) {
|
||||
this.mutableValues[optionName].splice(
|
||||
index + 1,
|
||||
0,
|
||||
this.mutableValues[optionName].splice(index, 1)[0],
|
||||
);
|
||||
}
|
||||
const locale = useI18n();
|
||||
|
||||
const parameterData = {
|
||||
name: this.getPropertyPath(optionName),
|
||||
value: this.mutableValues[optionName],
|
||||
type: 'optionsOrderChanged',
|
||||
};
|
||||
export type Props = {
|
||||
nodeValues: INodeParameters;
|
||||
parameter: INodeProperties;
|
||||
path: string;
|
||||
values?: Record<string, INodeParameters[]>;
|
||||
isReadOnly?: boolean;
|
||||
};
|
||||
|
||||
this.$emit('valueChanged', parameterData);
|
||||
},
|
||||
moveOptionUp(optionName: string, index: number) {
|
||||
if (Array.isArray(this.mutableValues[optionName])) {
|
||||
this.mutableValues?.[optionName].splice(
|
||||
index - 1,
|
||||
0,
|
||||
this.mutableValues[optionName].splice(index, 1)[0],
|
||||
);
|
||||
}
|
||||
type ValueChangedEvent = {
|
||||
name: string;
|
||||
value: NodeParameterValueType;
|
||||
type?: 'optionsOrderChanged';
|
||||
};
|
||||
|
||||
const parameterData = {
|
||||
name: this.getPropertyPath(optionName),
|
||||
value: this.mutableValues[optionName],
|
||||
type: 'optionsOrderChanged',
|
||||
};
|
||||
|
||||
this.$emit('valueChanged', parameterData);
|
||||
},
|
||||
optionSelected(optionName: string) {
|
||||
const option = this.getOptionProperties(optionName);
|
||||
if (option === undefined) {
|
||||
return;
|
||||
}
|
||||
const name = `${this.path}.${option.name}`;
|
||||
|
||||
const newParameterValue: INodeParameters = {};
|
||||
|
||||
for (const optionParameter of option.values) {
|
||||
if (
|
||||
optionParameter.type === 'fixedCollection' &&
|
||||
optionParameter.typeOptions !== undefined &&
|
||||
optionParameter.typeOptions.multipleValues === true
|
||||
) {
|
||||
newParameterValue[optionParameter.name] = {};
|
||||
} else if (
|
||||
optionParameter.typeOptions !== undefined &&
|
||||
optionParameter.typeOptions.multipleValues === true
|
||||
) {
|
||||
// Multiple values are allowed so append option to array
|
||||
const multiValue = get(this.nodeValues, [this.path, optionParameter.name], []);
|
||||
|
||||
if (Array.isArray(optionParameter.default)) {
|
||||
multiValue.push(...deepCopy(optionParameter.default));
|
||||
} else if (
|
||||
optionParameter.default !== '' &&
|
||||
typeof optionParameter.default !== 'object'
|
||||
) {
|
||||
multiValue.push(deepCopy(optionParameter.default));
|
||||
}
|
||||
|
||||
newParameterValue[optionParameter.name] = multiValue;
|
||||
} else {
|
||||
// Add a new option
|
||||
newParameterValue[optionParameter.name] = deepCopy(optionParameter.default);
|
||||
}
|
||||
}
|
||||
|
||||
let newValue;
|
||||
if (this.multipleValues) {
|
||||
newValue = get(this.nodeValues, name, []) as INodeParameters[];
|
||||
|
||||
newValue.push(newParameterValue);
|
||||
} else {
|
||||
newValue = newParameterValue;
|
||||
}
|
||||
|
||||
const parameterData = {
|
||||
name,
|
||||
value: newValue,
|
||||
};
|
||||
|
||||
this.$emit('valueChanged', parameterData);
|
||||
this.selectedOption = undefined;
|
||||
},
|
||||
valueChanged(parameterData: IUpdateInformation) {
|
||||
this.$emit('valueChanged', parameterData);
|
||||
},
|
||||
},
|
||||
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 propertyNames.value) {
|
||||
tempProperties = getOptionProperties(name);
|
||||
if (tempProperties !== undefined) {
|
||||
returnProperties.push(tempProperties);
|
||||
}
|
||||
}
|
||||
return returnProperties;
|
||||
});
|
||||
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 (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);
|
||||
},
|
||||
{ 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.
|
||||
emit('valueChanged', {
|
||||
name: getPropertyPath(optionName, index),
|
||||
value: undefined,
|
||||
});
|
||||
} else {
|
||||
// it's the only option, so remove the whole type
|
||||
emit('valueChanged', {
|
||||
name: getPropertyPath(optionName),
|
||||
value: undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
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 option = getOptionProperties(optionName);
|
||||
if (option === undefined) {
|
||||
return;
|
||||
}
|
||||
const name = `${props.path}.${option.name}`;
|
||||
|
||||
const newParameterValue: INodeParameters = {};
|
||||
|
||||
for (const optionParameter of option.values) {
|
||||
if (
|
||||
optionParameter.type === 'fixedCollection' &&
|
||||
optionParameter.typeOptions !== undefined &&
|
||||
optionParameter.typeOptions.multipleValues === true
|
||||
) {
|
||||
newParameterValue[optionParameter.name] = {};
|
||||
} else if (
|
||||
optionParameter.typeOptions !== undefined &&
|
||||
optionParameter.typeOptions.multipleValues === true
|
||||
) {
|
||||
// Multiple values are allowed so append option to array
|
||||
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') {
|
||||
multiValue.push(deepCopy(optionParameter.default));
|
||||
}
|
||||
|
||||
newParameterValue[optionParameter.name] = multiValue;
|
||||
} else {
|
||||
// Add a new option
|
||||
newParameterValue[optionParameter.name] = deepCopy(optionParameter.default);
|
||||
}
|
||||
}
|
||||
|
||||
let newValue: NodeParameterValueType;
|
||||
if (multipleValues.value) {
|
||||
newValue = get(props.nodeValues, name, []) as INodeParameters[];
|
||||
|
||||
newValue.push(newParameterValue);
|
||||
} else {
|
||||
newValue = newParameterValue;
|
||||
}
|
||||
|
||||
const parameterData = {
|
||||
name,
|
||||
value: newValue,
|
||||
};
|
||||
|
||||
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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue