mirror of
https://github.com/n8n-io/n8n.git
synced 2024-11-14 16:44:07 -08:00
refactor(editor): Convert MultipleParameter to composition API (no-changelog) (#11568)
This commit is contained in:
parent
2ed4ce3d7a
commit
cb28b5cd60
44
packages/editor-ui/src/components/MultipleParameter.test.ts
Normal file
44
packages/editor-ui/src/components/MultipleParameter.test.ts
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import { createTestingPinia } from '@pinia/testing';
|
||||||
|
|
||||||
|
import { createComponentRenderer } from '@/__tests__/render';
|
||||||
|
import MultipleParameter from './MultipleParameter.vue';
|
||||||
|
|
||||||
|
describe('MultipleParameter', () => {
|
||||||
|
const renderComponent = createComponentRenderer(MultipleParameter, {
|
||||||
|
props: {
|
||||||
|
path: 'parameters.additionalFields',
|
||||||
|
parameter: {
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Currency',
|
||||||
|
name: 'currency',
|
||||||
|
type: 'string',
|
||||||
|
default: 'USD',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
nodeValues: {
|
||||||
|
parameters: {
|
||||||
|
additionalFields: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
values: [],
|
||||||
|
isReadOnly: false,
|
||||||
|
},
|
||||||
|
pinia: createTestingPinia({ initialState: {} }),
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render correctly', () => {
|
||||||
|
const wrapper = renderComponent();
|
||||||
|
|
||||||
|
expect(wrapper.html()).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,133 +1,113 @@
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineComponent } from 'vue';
|
import { ref, watch, computed } from 'vue';
|
||||||
import type { PropType } from 'vue';
|
import { get } from 'lodash-es';
|
||||||
import type { IUpdateInformation } from '@/Interface';
|
|
||||||
import type { INodeParameters, INodeProperties } from 'n8n-workflow';
|
import type { INodeParameters, INodeProperties } from 'n8n-workflow';
|
||||||
import { deepCopy } from 'n8n-workflow';
|
import { deepCopy } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { useI18n } from '@/composables/useI18n';
|
||||||
|
import type { IUpdateInformation } from '@/Interface';
|
||||||
import CollectionParameter from '@/components/CollectionParameter.vue';
|
import CollectionParameter from '@/components/CollectionParameter.vue';
|
||||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||||
|
|
||||||
import { get } from 'lodash-es';
|
defineOptions({ name: 'MultipleParameter' });
|
||||||
|
|
||||||
export default defineComponent({
|
const props = withDefaults(
|
||||||
name: 'MultipleParameter',
|
defineProps<{
|
||||||
components: {
|
nodeValues: INodeParameters;
|
||||||
CollectionParameter,
|
parameter: INodeProperties;
|
||||||
ParameterInputFull,
|
path: string;
|
||||||
|
values?: INodeParameters[];
|
||||||
|
isReadOnly?: boolean;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
values: () => [] as INodeParameters[],
|
||||||
|
isReadOnly: false,
|
||||||
},
|
},
|
||||||
props: {
|
);
|
||||||
nodeValues: {
|
|
||||||
type: Object as PropType<INodeParameters>,
|
const emit = defineEmits<{
|
||||||
required: true,
|
valueChanged: [parameterData: IUpdateInformation];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
|
const mutableValues = ref<INodeParameters[]>(deepCopy(props.values));
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.values,
|
||||||
|
(newValues) => {
|
||||||
|
mutableValues.value = deepCopy(newValues);
|
||||||
},
|
},
|
||||||
parameter: {
|
{ deep: true },
|
||||||
type: Object as PropType<INodeProperties>,
|
);
|
||||||
required: true,
|
|
||||||
},
|
const addButtonText = computed(() => {
|
||||||
path: {
|
if (!props.parameter.typeOptions || !props.parameter.typeOptions.multipleValueButtonText) {
|
||||||
type: String,
|
return i18n.baseText('multipleParameter.addItem');
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
values: {
|
|
||||||
type: Array as PropType<INodeParameters[]>,
|
|
||||||
default: () => [],
|
|
||||||
},
|
|
||||||
isReadOnly: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
mutableValues: [] as INodeParameters[],
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
addButtonText(): string {
|
|
||||||
if (
|
|
||||||
!this.parameter.typeOptions ||
|
|
||||||
(this.parameter.typeOptions && !this.parameter.typeOptions.multipleValueButtonText)
|
|
||||||
) {
|
|
||||||
return this.$locale.baseText('multipleParameter.addItem');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$locale.nodeText().multipleValueButtonText(this.parameter);
|
return i18n.nodeText().multipleValueButtonText(props.parameter);
|
||||||
},
|
});
|
||||||
hideDelete(): boolean {
|
|
||||||
return this.parameter.options?.length === 1;
|
|
||||||
},
|
|
||||||
sortable(): boolean {
|
|
||||||
return !!this.parameter.typeOptions?.sortable;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
values: {
|
|
||||||
handler(newValues: INodeParameters[]) {
|
|
||||||
this.mutableValues = deepCopy(newValues);
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.mutableValues = deepCopy(this.values);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
addItem() {
|
|
||||||
const name = this.getPath();
|
|
||||||
const currentValue = get(this.nodeValues, name, []) as INodeParameters[];
|
|
||||||
|
|
||||||
currentValue.push(deepCopy(this.parameter.default as INodeParameters));
|
const hideDelete = computed(() => props.parameter.options?.length === 1);
|
||||||
|
|
||||||
|
const sortable = computed(() => !!props.parameter.typeOptions?.sortable);
|
||||||
|
|
||||||
|
const addItem = () => {
|
||||||
|
const name = getPath();
|
||||||
|
const currentValue = get(props.nodeValues, name, []) as INodeParameters[];
|
||||||
|
|
||||||
|
currentValue.push(deepCopy(props.parameter.default as INodeParameters));
|
||||||
|
|
||||||
const parameterData = {
|
const parameterData = {
|
||||||
name,
|
name,
|
||||||
value: currentValue,
|
value: currentValue,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$emit('valueChanged', parameterData);
|
emit('valueChanged', parameterData);
|
||||||
},
|
};
|
||||||
deleteItem(index: number) {
|
|
||||||
|
const deleteItem = (index: number) => {
|
||||||
const parameterData = {
|
const parameterData = {
|
||||||
name: this.getPath(index),
|
name: getPath(index),
|
||||||
value: undefined,
|
value: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$emit('valueChanged', parameterData);
|
emit('valueChanged', parameterData);
|
||||||
},
|
|
||||||
getPath(index?: number): string {
|
|
||||||
return this.path + (index !== undefined ? `[${index}]` : '');
|
|
||||||
},
|
|
||||||
moveOptionDown(index: number) {
|
|
||||||
this.mutableValues.splice(index + 1, 0, this.mutableValues.splice(index, 1)[0]);
|
|
||||||
|
|
||||||
const parameterData = {
|
|
||||||
name: this.path,
|
|
||||||
value: this.mutableValues,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$emit('valueChanged', parameterData);
|
const getPath = (index?: number) => {
|
||||||
},
|
return props.path + (index !== undefined ? `[${index}]` : '');
|
||||||
moveOptionUp(index: number) {
|
|
||||||
this.mutableValues.splice(index - 1, 0, this.mutableValues.splice(index, 1)[0]);
|
|
||||||
|
|
||||||
const parameterData = {
|
|
||||||
name: this.path,
|
|
||||||
value: this.mutableValues,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
this.$emit('valueChanged', parameterData);
|
const moveOptionDown = (index: number) => {
|
||||||
},
|
mutableValues.value.splice(index + 1, 0, mutableValues.value.splice(index, 1)[0]);
|
||||||
valueChanged(parameterData: IUpdateInformation) {
|
|
||||||
this.$emit('valueChanged', parameterData);
|
emit('valueChanged', {
|
||||||
},
|
name: props.path,
|
||||||
},
|
value: mutableValues.value,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const moveOptionUp = (index: number) => {
|
||||||
|
mutableValues.value.splice(index - 1, 0, mutableValues.value.splice(index, 1)[0]);
|
||||||
|
|
||||||
|
emit('valueChanged', {
|
||||||
|
name: props.path,
|
||||||
|
value: mutableValues.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const valueChanged = (parameterData: IUpdateInformation) => {
|
||||||
|
emit('valueChanged', parameterData);
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="duplicate-parameter" @keydown.stop>
|
<div class="duplicate-parameter" @keydown.stop>
|
||||||
<n8n-input-label
|
<n8n-input-label
|
||||||
:label="$locale.nodeText().inputLabelDisplayName(parameter, path)"
|
:label="i18n.nodeText().inputLabelDisplayName(parameter, path)"
|
||||||
:tooltip-text="$locale.nodeText().inputLabelDescription(parameter, path)"
|
:tooltip-text="i18n.nodeText().inputLabelDescription(parameter, path)"
|
||||||
:underline="true"
|
:underline="true"
|
||||||
size="small"
|
size="small"
|
||||||
color="text-dark"
|
color="text-dark"
|
||||||
|
@ -142,7 +122,7 @@ export default defineComponent({
|
||||||
<div v-if="!isReadOnly" class="delete-item clickable">
|
<div v-if="!isReadOnly" class="delete-item clickable">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
icon="trash"
|
icon="trash"
|
||||||
:title="$locale.baseText('multipleParameter.deleteItem')"
|
:title="i18n.baseText('multipleParameter.deleteItem')"
|
||||||
@click="deleteItem(index)"
|
@click="deleteItem(index)"
|
||||||
/>
|
/>
|
||||||
<div v-if="sortable">
|
<div v-if="sortable">
|
||||||
|
@ -150,14 +130,14 @@ export default defineComponent({
|
||||||
v-if="index !== 0"
|
v-if="index !== 0"
|
||||||
icon="angle-up"
|
icon="angle-up"
|
||||||
class="clickable"
|
class="clickable"
|
||||||
:title="$locale.baseText('multipleParameter.moveUp')"
|
:title="i18n.baseText('multipleParameter.moveUp')"
|
||||||
@click="moveOptionUp(index)"
|
@click="moveOptionUp(index)"
|
||||||
/>
|
/>
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
v-if="index !== mutableValues.length - 1"
|
v-if="index !== mutableValues.length - 1"
|
||||||
icon="angle-down"
|
icon="angle-down"
|
||||||
class="clickable"
|
class="clickable"
|
||||||
:title="$locale.baseText('multipleParameter.moveDown')"
|
:title="i18n.baseText('multipleParameter.moveDown')"
|
||||||
@click="moveOptionDown(index)"
|
@click="moveOptionDown(index)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -194,7 +174,7 @@ export default defineComponent({
|
||||||
class="no-items-exist"
|
class="no-items-exist"
|
||||||
>
|
>
|
||||||
<n8n-text size="small">{{
|
<n8n-text size="small">{{
|
||||||
$locale.baseText('multipleParameter.currentlyNoItemsExist')
|
i18n.baseText('multipleParameter.currentlyNoItemsExist')
|
||||||
}}</n8n-text>
|
}}</n8n-text>
|
||||||
</div>
|
</div>
|
||||||
<n8n-button
|
<n8n-button
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`MultipleParameter > should render correctly 1`] = `
|
||||||
|
"<div data-v-a47e4507="" class="duplicate-parameter">
|
||||||
|
<div data-v-a47e4507="" class="container" data-test-id="input-label"><label class="n8n-input-label inputLabel heading underline small">
|
||||||
|
<div class="title"><span class="n8n-text text-dark size-small bold">Additional Fields <!--v-if--></span></div>
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
|
<!--v-if-->
|
||||||
|
</label></div>
|
||||||
|
<div data-v-a47e4507="" class="add-item-wrapper">
|
||||||
|
<div data-v-a47e4507="" class="no-items-exist"><span data-v-a47e4507="" class="n8n-text size-small regular">Currently no items exist</span></div><button data-v-a47e4507="" class="button button tertiary medium block" aria-live="polite">
|
||||||
|
<!--v-if--><span>Add item</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>"
|
||||||
|
`;
|
Loading…
Reference in a new issue