refactor(editor): Refactor VariableSelector and VariableSelectorItem to composition API (no-changelog) (#10030)

This commit is contained in:
oleg
2024-07-12 12:03:30 +02:00
committed by GitHub
parent e4e66ab7da
commit 3b2d76e358
2 changed files with 920 additions and 925 deletions

View File

@@ -1,32 +1,7 @@
<template> <script setup lang="ts">
<div class="variable-selector-wrapper" @keydown.stop> import { ref, computed } from 'vue';
<div class="input-wrapper"> import { useRouter } from 'vue-router';
<n8n-input import { storeToRefs } from 'pinia';
ref="inputField"
v-model="variableFilter"
:placeholder="$locale.baseText('variableSelector.variableFilter')"
size="small"
type="text"
></n8n-input>
</div>
<div class="result-wrapper">
<VariableSelectorItem
v-for="option in currentResults"
:key="option.key"
:item="option"
:extend-all="extendAll"
:redact-values="redactValues"
@item-selected="forwardItemSelected"
></VariableSelectorItem>
</div>
</div>
</template>
<script lang="ts">
/* eslint-disable prefer-spread */
import { defineComponent } from 'vue';
import { mapStores } from 'pinia';
import { PLACEHOLDER_FILLED_AT_EXECUTION_TIME, STICKY_NODE_TYPE } from '@/constants'; import { PLACEHOLDER_FILLED_AT_EXECUTION_TIME, STICKY_NODE_TYPE } from '@/constants';
import type { import type {
@@ -43,66 +18,49 @@ import type {
import { NodeConnectionType, WorkflowDataProxy } from 'n8n-workflow'; import { NodeConnectionType, WorkflowDataProxy } from 'n8n-workflow';
import VariableSelectorItem from '@/components/VariableSelectorItem.vue'; import VariableSelectorItem from '@/components/VariableSelectorItem.vue';
import type { INodeUi, IVariableItemSelected, IVariableSelectorOption } from '@/Interface'; import type { IVariableItemSelected, IVariableSelectorOption } from '@/Interface';
import { useWorkflowsStore } from '@/stores/workflows.store'; import { useWorkflowsStore } from '@/stores/workflows.store';
import { useRootStore } from '@/stores/root.store';
import { useNDVStore } from '@/stores/ndv.store'; import { useNDVStore } from '@/stores/ndv.store';
import { useRouter } from 'vue-router';
import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers'; import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
import { useI18n } from '@/composables/useI18n';
import { escapeMappingString } from '@/utils/mappingUtils'; import { escapeMappingString } from '@/utils/mappingUtils';
// Node types that should not be displayed in variable selector // Node types that should not be displayed in variable selector
const SKIPPED_NODE_TYPES = [STICKY_NODE_TYPE]; const SKIPPED_NODE_TYPES = [STICKY_NODE_TYPE];
export default defineComponent({ const props = defineProps<{
name: 'VariableSelector', path: string;
components: { redactValues: boolean;
VariableSelectorItem, }>();
},
props: ['path', 'redactValues'], const emit = defineEmits<{
setup() { itemSelected: [value: IVariableItemSelected];
}>();
const router = useRouter(); const router = useRouter();
const i18n = useI18n();
const workflowHelpers = useWorkflowHelpers({ router }); const workflowHelpers = useWorkflowHelpers({ router });
return { const workflowsStore = useWorkflowsStore();
workflowHelpers, const ndvStore = useNDVStore();
};
},
data() {
return {
variableFilter: '',
selectorOpenInputIndex: null as number | null,
};
},
computed: {
...mapStores(useNDVStore, useRootStore, useWorkflowsStore),
activeNode(): INodeUi | null {
const activeNode = this.ndvStore.activeNode!;
if (!activeNode) {
return null;
}
return this.workflow.getParentMainInputNode(activeNode);
},
extendAll(): boolean {
if (this.variableFilter) {
return true;
}
return false; const { activeNode } = storeToRefs(ndvStore);
},
currentResults(): IVariableSelectorOption[] { const variableFilter = ref('');
return this.getFilterResults(this.variableFilter.toLowerCase(), 0);
}, const extendAll = computed(() => !!variableFilter.value);
workflow(): Workflow {
return this.workflowHelpers.getCurrentWorkflow(); const currentResults = computed(() => getFilterResults(variableFilter.value.toLowerCase(), 0));
},
}, const workflow = computed(() => workflowHelpers.getCurrentWorkflow());
methods: {
forwardItemSelected(eventData: IVariableItemSelected) { // Helper functions
this.$emit('itemSelected', eventData);
}, /**
sortOptions(options: IVariableSelectorOption[] | null): IVariableSelectorOption[] | null { * Sorts the options alphabetically. Categories get sorted before items.
*/
function sortOptions(options: IVariableSelectorOption[] | null): IVariableSelectorOption[] | null {
if (options === null) { if (options === null) {
return null; return null;
} }
@@ -127,15 +85,19 @@ export default defineComponent({
} }
return 0; return 0;
}); });
}, }
removeEmptyEntries(
/**
* Removes all empty entries from the list
*/
function removeEmptyEntries(
inputData: IVariableSelectorOption[] | IVariableSelectorOption | null, inputData: IVariableSelectorOption[] | IVariableSelectorOption | null,
): IVariableSelectorOption[] | IVariableSelectorOption | null { ): IVariableSelectorOption[] | IVariableSelectorOption | null {
if (Array.isArray(inputData)) { if (Array.isArray(inputData)) {
const newItems: IVariableSelectorOption[] = []; const newItems: IVariableSelectorOption[] = [];
let tempItem: IVariableSelectorOption; let tempItem: IVariableSelectorOption;
inputData.forEach((item) => { inputData.forEach((item) => {
tempItem = this.removeEmptyEntries(item) as IVariableSelectorOption; tempItem = removeEmptyEntries(item) as IVariableSelectorOption;
if (tempItem !== null) { if (tempItem !== null) {
newItems.push(tempItem); newItems.push(tempItem);
} }
@@ -144,7 +106,7 @@ export default defineComponent({
} }
if (inputData?.options) { if (inputData?.options) {
const newOptions = this.removeEmptyEntries(inputData.options); const newOptions = removeEmptyEntries(inputData.options);
if (Array.isArray(newOptions) && newOptions.length) { if (Array.isArray(newOptions) && newOptions.length) {
// Has still options left so return // Has still options left so return
inputData.options = newOptions; inputData.options = newOptions;
@@ -159,9 +121,12 @@ export default defineComponent({
// Is an item no category // Is an item no category
return inputData; return inputData;
} }
}, }
// Normalizes the path so compare paths which have use dots or brakets
getPathNormalized(path: string | undefined): string { /**
* Normalizes the path so compare paths which have use dots or brakets
*/
function getPathNormalized(path: string | undefined): string {
if (path === undefined) { if (path === undefined) {
return ''; return '';
} }
@@ -197,8 +162,9 @@ export default defineComponent({
} }
return finalArray.join('|'); return finalArray.join('|');
}, }
jsonDataToFilterOption(
function jsonDataToFilterOption(
inputData: IDataObject | GenericValue | IDataObject[] | GenericValue[] | null, inputData: IDataObject | GenericValue | IDataObject[] | GenericValue[] | null,
parentPath: string, parentPath: string,
propertyName: string, propertyName: string,
@@ -231,9 +197,8 @@ export default defineComponent({
const arrayData: IVariableSelectorOption[] = []; const arrayData: IVariableSelectorOption[] = [];
for (let i = 0; i < inputData.length; i++) { for (let i = 0; i < inputData.length; i++) {
arrayData.push.apply( arrayData.push(
arrayData, ...jsonDataToFilterOption(
this.jsonDataToFilterOption(
inputData[i], inputData[i],
newParentPath, newParentPath,
newPropertyName, newPropertyName,
@@ -256,9 +221,8 @@ export default defineComponent({
const tempValue: IVariableSelectorOption[] = []; const tempValue: IVariableSelectorOption[] = [];
for (const key of Object.keys(inputData)) { for (const key of Object.keys(inputData)) {
tempValue.push.apply( tempValue.push(
tempValue, ...jsonDataToFilterOption(
this.jsonDataToFilterOption(
(inputData as IDataObject)[key], (inputData as IDataObject)[key],
fullpath, fullpath,
key, key,
@@ -273,7 +237,7 @@ export default defineComponent({
if (tempValue.length) { if (tempValue.length) {
returnData.push({ returnData.push({
name: displayName || propertyName, name: displayName || propertyName,
options: this.sortOptions(tempValue), options: sortOptions(tempValue),
key: fullpath, key: fullpath,
allowParentSelect: true, allowParentSelect: true,
dataType: 'object', dataType: 'object',
@@ -281,11 +245,12 @@ export default defineComponent({
} }
} else { } else {
if (filterText !== undefined && propertyName.toLowerCase().indexOf(filterText) === -1) { if (filterText !== undefined && propertyName.toLowerCase().indexOf(filterText) === -1) {
// If filter is set apply it
return returnData; return returnData;
} }
// Skip is currently only needed for leafs so only check here // Skip is currently only needed for leafs so only check here
if (this.getPathNormalized(skipKey) !== this.getPathNormalized(fullpath)) { if (getPathNormalized(skipKey) !== getPathNormalized(fullpath)) {
returnData.push({ returnData.push({
name: propertyName, name: propertyName,
key: fullpath, key: fullpath,
@@ -295,7 +260,7 @@ export default defineComponent({
} }
return returnData; return returnData;
}, }
/** /**
* Get the node's output using runData * Get the node's output using runData
@@ -309,7 +274,7 @@ export default defineComponent({
* @param {number} [outputIndex=0] The index of the output * @param {number} [outputIndex=0] The index of the output
* @param {boolean} [useShort=false] Use short notation $json vs. $('NodeName').json * @param {boolean} [useShort=false] Use short notation $json vs. $('NodeName').json
*/ */
getNodeRunDataOutput( function getNodeRunDataOutput(
nodeName: string, nodeName: string,
runData: IRunData, runData: IRunData,
filterText: string, filterText: string,
@@ -338,29 +303,29 @@ export default defineComponent({
return null; return null;
} }
if (!runData[nodeName][runIndex].data!.hasOwnProperty(inputName)) { if (!runData[nodeName][runIndex].data.hasOwnProperty(inputName)) {
// No data found for inputName // No data found for inputName
return null; return null;
} }
if (runData[nodeName][runIndex].data![inputName].length <= outputIndex) { if (runData[nodeName][runIndex].data[inputName].length <= outputIndex) {
// No data found for output Index // No data found for output Index
return null; return null;
} }
// The data should be identical no matter to which node it gets so always select the first one // The data should be identical no matter to which node it gets so always select the first one
if ( if (
runData[nodeName][runIndex].data![inputName][outputIndex] === null || runData[nodeName][runIndex].data[inputName][outputIndex] === null ||
runData[nodeName][runIndex].data![inputName][outputIndex]!.length <= itemIndex runData[nodeName][runIndex].data[inputName][outputIndex].length <= itemIndex
) { ) {
// No data found for node connection found // No data found for node connection found
return null; return null;
} }
const outputData = runData[nodeName][runIndex].data![inputName][outputIndex]![itemIndex]; const outputData = runData[nodeName][runIndex].data[inputName][outputIndex][itemIndex];
return this.getNodeOutput(nodeName, outputData, filterText, useShort); return getNodeOutput(nodeName, outputData, filterText, useShort);
}, }
/** /**
* Get the node's output using pinData * Get the node's output using pinData
@@ -370,7 +335,7 @@ export default defineComponent({
* @param {string} filterText Filter text for parameters * @param {string} filterText Filter text for parameters
* @param {boolean} [useShort=false] Use short notation $json vs. $('NodeName').json * @param {boolean} [useShort=false] Use short notation $json vs. $('NodeName').json
*/ */
getNodePinDataOutput( function getNodePinDataOutput(
nodeName: string, nodeName: string,
pinData: IPinData[string], pinData: IPinData[string],
filterText: string, filterText: string,
@@ -378,8 +343,8 @@ export default defineComponent({
): IVariableSelectorOption[] | null { ): IVariableSelectorOption[] | null {
const outputData = pinData.map((data) => ({ json: data }) as INodeExecutionData)[0]; const outputData = pinData.map((data) => ({ json: data }) as INodeExecutionData)[0];
return this.getNodeOutput(nodeName, outputData, filterText, useShort); return getNodeOutput(nodeName, outputData, filterText, useShort);
}, }
/** /**
* Returns the node's output data * Returns the node's output data
@@ -389,7 +354,7 @@ export default defineComponent({
* @param {string} filterText Filter text for parameters * @param {string} filterText Filter text for parameters
* @param {boolean} [useShort=false] Use short notation * @param {boolean} [useShort=false] Use short notation
*/ */
getNodeOutput( function getNodeOutput(
nodeName: string, nodeName: string,
outputData: INodeExecutionData, outputData: INodeExecutionData,
filterText: string, filterText: string,
@@ -405,9 +370,8 @@ export default defineComponent({
const jsonDataOptions: IVariableSelectorOption[] = []; const jsonDataOptions: IVariableSelectorOption[] = [];
for (const propertyName of Object.keys(outputData.json)) { for (const propertyName of Object.keys(outputData.json)) {
jsonDataOptions.push.apply( jsonDataOptions.push(
jsonDataOptions, ...jsonDataToFilterOption(
this.jsonDataToFilterOption(
outputData.json[propertyName], outputData.json[propertyName],
jsonPropertyPrefix, jsonPropertyPrefix,
propertyName, propertyName,
@@ -419,7 +383,7 @@ export default defineComponent({
if (jsonDataOptions.length) { if (jsonDataOptions.length) {
returnData.push({ returnData.push({
name: 'JSON', name: 'JSON',
options: this.sortOptions(jsonDataOptions), options: sortOptions(jsonDataOptions),
}); });
} }
} }
@@ -456,7 +420,7 @@ export default defineComponent({
binaryData.push({ binaryData.push({
name: dataPropertyName, name: dataPropertyName,
key: `${binaryPropertyPrefix}.${dataPropertyName}`, key: `${binaryPropertyPrefix}.${dataPropertyName}`,
options: this.sortOptions(binaryPropertyData), options: sortOptions(binaryPropertyData),
allowParentSelect: true, allowParentSelect: true,
}); });
} }
@@ -465,15 +429,16 @@ export default defineComponent({
returnData.push({ returnData.push({
name: 'Binary', name: 'Binary',
key: binaryPropertyPrefix, key: binaryPropertyPrefix,
options: this.sortOptions(binaryData), options: sortOptions(binaryData),
allowParentSelect: true, allowParentSelect: true,
}); });
} }
} }
return returnData; return returnData;
}, }
getNodeContext(
function getNodeContext(
workflow: Workflow, workflow: Workflow,
runExecutionData: IRunExecutionData | null, runExecutionData: IRunExecutionData | null,
parentNode: string[], parentNode: string[],
@@ -485,16 +450,16 @@ export default defineComponent({
const runIndex = 0; const runIndex = 0;
const returnData: IVariableSelectorOption[] = []; const returnData: IVariableSelectorOption[] = [];
if (this.activeNode === null) { if (activeNode.value === null) {
return returnData; return returnData;
} }
const nodeConnection = this.workflow.getNodeConnectionIndexes( const nodeConnection = workflow.getNodeConnectionIndexes(
this.activeNode.name, activeNode.value.name,
parentNode[0], parentNode[0],
inputName, inputName,
); );
const connectionInputData = this.workflowHelpers.connectionInputData( const connectionInputData = workflowHelpers.connectionInputData(
parentNode, parentNode,
nodeName, nodeName,
inputName, inputName,
@@ -532,7 +497,6 @@ export default defineComponent({
); );
const proxy = dataProxy.getDataProxy(); const proxy = dataProxy.getDataProxy();
// @ts-ignore
const nodeContext = proxy.$node[nodeName].context as IContextObject; const nodeContext = proxy.$node[nodeName].context as IContextObject;
for (const key of Object.keys(nodeContext)) { for (const key of Object.keys(nodeContext)) {
if (filterText !== undefined && key.toLowerCase().indexOf(filterText) === -1) { if (filterText !== undefined && key.toLowerCase().indexOf(filterText) === -1) {
@@ -543,13 +507,13 @@ export default defineComponent({
returnData.push({ returnData.push({
name: key, name: key,
key: `$('${escapeMappingString(nodeName)}').context['${escapeMappingString(key)}']`, key: `$('${escapeMappingString(nodeName)}').context['${escapeMappingString(key)}']`,
// @ts-ignore
value: nodeContext[key], value: nodeContext[key],
}); });
} }
return returnData; return returnData;
}, }
/** /**
* Returns all the node parameters with values * Returns all the node parameters with values
* *
@@ -558,13 +522,13 @@ export default defineComponent({
* @param {string} [skipParameter] Parameter to skip * @param {string} [skipParameter] Parameter to skip
* @param {string} [filterText] Filter text for parameters * @param {string} [filterText] Filter text for parameters
*/ */
getNodeParameters( function getNodeParameters(
nodeName: string, nodeName: string,
path: string, path: string,
skipParameter?: string, skipParameter?: string,
filterText?: string, filterText?: string,
): IVariableSelectorOption[] | null { ): IVariableSelectorOption[] | null {
const node = this.workflow.getNode(nodeName); const node = workflow.value.getNode(nodeName);
if (node === null) { if (node === null) {
return null; return null;
} }
@@ -581,9 +545,8 @@ export default defineComponent({
continue; continue;
} }
returnParameters.push.apply( returnParameters.push(
returnParameters, ...jsonDataToFilterOption(
this.jsonDataToFilterOption(
node.parameters[parameterName], node.parameters[parameterName],
path, path,
parameterName, parameterName,
@@ -596,17 +559,18 @@ export default defineComponent({
} }
return returnParameters; return returnParameters;
}, }
getFilterResults(filterText: string, itemIndex: number): IVariableSelectorOption[] {
function getFilterResults(filterText: string, itemIndex: number): IVariableSelectorOption[] {
const inputName = NodeConnectionType.Main; const inputName = NodeConnectionType.Main;
if (this.activeNode === null) { if (activeNode.value === null) {
return []; return [];
} }
const executionData = this.workflowsStore.getWorkflowExecution; const executionData = workflowsStore.getWorkflowExecution;
let parentNode = this.workflow.getParentNodes(this.activeNode.name, inputName, 1); let parentNode = workflow.value.getParentNodes(activeNode.value.name, inputName, 1);
let runData = this.workflowsStore.getWorkflowRunData; let runData = workflowsStore.getWorkflowRunData;
if (runData === null) { if (runData === null) {
runData = {}; runData = {};
@@ -625,17 +589,17 @@ export default defineComponent({
if (executionData?.data !== undefined) { if (executionData?.data !== undefined) {
const runExecutionData: IRunExecutionData = executionData.data; const runExecutionData: IRunExecutionData = executionData.data;
tempOptions = this.getNodeContext( tempOptions = getNodeContext(
this.workflow, workflow.value,
runExecutionData, runExecutionData,
parentNode, parentNode,
this.activeNode.name, activeNode.value.name,
filterText, filterText,
) as IVariableSelectorOption[]; ) as IVariableSelectorOption[];
if (tempOptions.length) { if (tempOptions.length) {
currentNodeData.push({ currentNodeData.push({
name: 'Context', name: 'Context',
options: this.sortOptions(tempOptions), options: sortOptions(tempOptions),
} as IVariableSelectorOption); } as IVariableSelectorOption);
} }
} }
@@ -645,27 +609,29 @@ export default defineComponent({
if (parentNode.length) { if (parentNode.length) {
// If the node has an input node add the input data // If the node has an input node add the input data
let ndvInputNodeName = this.ndvStore.ndvInputNodeName; let ndvInputNodeName = ndvStore.ndvInputNodeName;
if (!ndvInputNodeName) { if (!ndvInputNodeName) {
// If no input node is set use the first parent one // If no input node is set use the first parent one
// this is imporant for config-nodes which do not have // this is important for config-nodes which do not have
// a main input // a main input
ndvInputNodeName = parentNode[0]; ndvInputNodeName = parentNode[0];
} }
const activeInputParentNode = parentNode.find((node) => node === ndvInputNodeName)!; const activeInputParentNode = parentNode.find((node) => node === ndvInputNodeName);
if (!activeInputParentNode) {
return [];
}
// Check from which output to read the data. // Check from which output to read the data.
// Depends on how the nodes are connected. // Depends on how the nodes are connected.
// (example "IF" node. If node is connected to "true" or to "false" output) // (example "IF" node. If node is connected to "true" or to "false" output)
const nodeConnection = this.workflow.getNodeConnectionIndexes( const nodeConnection = workflow.value.getNodeConnectionIndexes(
this.activeNode.name, activeNode.value.name,
activeInputParentNode, activeInputParentNode,
inputName, inputName,
); );
const outputIndex = nodeConnection === undefined ? 0 : nodeConnection.sourceIndex; const outputIndex = nodeConnection === undefined ? 0 : nodeConnection.sourceIndex;
tempOutputData = this.getNodeRunDataOutput( tempOutputData = getNodeRunDataOutput(
activeInputParentNode, activeInputParentNode,
runData, runData,
filterText, filterText,
@@ -683,18 +649,18 @@ export default defineComponent({
}, },
]; ];
parentNode.forEach((parentNodeName) => { parentNode.forEach((parentNodeName) => {
const pinData = this.workflowsStore.pinDataByNodeName(parentNodeName); const pinData = workflowsStore.pinDataByNodeName(parentNodeName);
if (pinData) { if (pinData) {
const output = this.getNodePinDataOutput(parentNodeName, pinData, filterText, true); const output = getNodePinDataOutput(parentNodeName, pinData, filterText, true);
pinDataOptions[0].options = pinDataOptions[0].options!.concat( if (pinDataOptions[0].options) {
output?.[0]?.options ?? [], pinDataOptions[0].options = pinDataOptions[0].options.concat(output?.[0]?.options ?? []);
); }
} }
}); });
if (pinDataOptions[0].options!.length > 0) { if ((pinDataOptions[0]?.options ?? []).length > 0) {
if (tempOutputData) { if (tempOutputData) {
const jsonTempOutputData = tempOutputData.find((tempData) => tempData.name === 'JSON'); const jsonTempOutputData = tempOutputData.find((tempData) => tempData.name === 'JSON');
@@ -703,7 +669,7 @@ export default defineComponent({
jsonTempOutputData.options = []; jsonTempOutputData.options = [];
} }
(pinDataOptions[0].options || []).forEach((pinDataOption) => { (pinDataOptions[0].options ?? []).forEach((pinDataOption) => {
const existingOptionIndex = jsonTempOutputData.options!.findIndex( const existingOptionIndex = jsonTempOutputData.options!.findIndex(
(option) => option.name === pinDataOption.name, (option) => option.name === pinDataOption.name,
); );
@@ -726,7 +692,7 @@ export default defineComponent({
// Data is reasonable small (< 100kb) so add it // Data is reasonable small (< 100kb) so add it
currentNodeData.push({ currentNodeData.push({
name: 'Input Data', name: 'Input Data',
options: this.sortOptions(tempOutputData), options: sortOptions(tempOutputData),
}); });
} else { } else {
// Data is to large so do not add // Data is to large so do not add
@@ -743,16 +709,16 @@ export default defineComponent({
} }
const initialPath = '$parameter'; const initialPath = '$parameter';
let skipParameter = this.path; let skipParameter = props.path;
if (skipParameter.startsWith('parameters.')) { if (skipParameter.startsWith('parameters.')) {
skipParameter = initialPath + skipParameter.substring(10); skipParameter = initialPath + skipParameter.substring(10);
} }
currentNodeData.push({ currentNodeData.push({
name: this.$locale.baseText('variableSelector.parameters'), name: i18n.baseText('variableSelector.parameters'),
options: this.sortOptions( options: sortOptions(
this.getNodeParameters( getNodeParameters(
this.activeNode.name, activeNode.value.name,
initialPath, initialPath,
skipParameter, skipParameter,
filterText, filterText,
@@ -761,8 +727,8 @@ export default defineComponent({
}); });
returnData.push({ returnData.push({
name: this.$locale.baseText('variableSelector.currentNode'), name: i18n.baseText('variableSelector.currentNode'),
options: this.sortOptions(currentNodeData), options: sortOptions(currentNodeData),
}); });
// Add the input data // Add the input data
@@ -772,9 +738,9 @@ export default defineComponent({
// ----------------------------------------- // -----------------------------------------
const allNodesData: IVariableSelectorOption[] = []; const allNodesData: IVariableSelectorOption[] = [];
let nodeOptions: IVariableSelectorOption[]; let nodeOptions: IVariableSelectorOption[];
const upstreamNodes = this.workflow.getParentNodes(this.activeNode.name, inputName); const upstreamNodes = workflow.value.getParentNodes(activeNode.value.name, inputName);
const workflowNodes = Object.entries(this.workflow.nodes); const workflowNodes = Object.entries(workflow.value.nodes);
// Sort the nodes according to their position relative to the current node // Sort the nodes according to their position relative to the current node
workflowNodes.sort((a, b) => { workflowNodes.sort((a, b) => {
@@ -785,7 +751,7 @@ export default defineComponent({
// Add the parameters of all nodes // Add the parameters of all nodes
// TODO: Later have to make sure that no parameters can be referenced which have expression which use input-data (for nodes which are not parent nodes) // TODO: Later have to make sure that no parameters can be referenced which have expression which use input-data (for nodes which are not parent nodes)
if (nodeName === this.activeNode.name) { if (nodeName === activeNode.value.name) {
// Skip the current node as this one get added separately // Skip the current node as this one get added separately
continue; continue;
} }
@@ -796,9 +762,9 @@ export default defineComponent({
nodeOptions = [ nodeOptions = [
{ {
name: this.$locale.baseText('variableSelector.parameters'), name: i18n.baseText('variableSelector.parameters'),
options: this.sortOptions( options: sortOptions(
this.getNodeParameters( getNodeParameters(
nodeName, nodeName,
`$('${escapeMappingString(nodeName)}').params`, `$('${escapeMappingString(nodeName)}').params`,
undefined, undefined,
@@ -811,9 +777,9 @@ export default defineComponent({
if (executionData?.data !== undefined) { if (executionData?.data !== undefined) {
const runExecutionData: IRunExecutionData = executionData.data; const runExecutionData: IRunExecutionData = executionData.data;
parentNode = this.workflow.getParentNodes(nodeName, inputName, 1); parentNode = workflow.value.getParentNodes(nodeName, inputName, 1);
tempOptions = this.getNodeContext( tempOptions = getNodeContext(
this.workflow, workflow.value,
runExecutionData, runExecutionData,
parentNode, parentNode,
nodeName, nodeName,
@@ -822,8 +788,8 @@ export default defineComponent({
if (tempOptions.length) { if (tempOptions.length) {
nodeOptions = [ nodeOptions = [
{ {
name: this.$locale.baseText('variableSelector.context'), name: i18n.baseText('variableSelector.context'),
options: this.sortOptions(tempOptions), options: sortOptions(tempOptions),
} as IVariableSelectorOption, } as IVariableSelectorOption,
]; ];
} }
@@ -831,48 +797,75 @@ export default defineComponent({
if (upstreamNodes.includes(nodeName)) { if (upstreamNodes.includes(nodeName)) {
// If the node is an upstream node add also the output data which can be referenced // If the node is an upstream node add also the output data which can be referenced
const pinData = this.workflowsStore.pinDataByNodeName(nodeName); const pinData = workflowsStore.pinDataByNodeName(nodeName);
tempOutputData = pinData tempOutputData = pinData
? this.getNodePinDataOutput(nodeName, pinData, filterText) ? getNodePinDataOutput(nodeName, pinData, filterText)
: this.getNodeRunDataOutput(nodeName, runData, filterText, itemIndex); : getNodeRunDataOutput(nodeName, runData, filterText, itemIndex);
if (tempOutputData) { if (tempOutputData) {
nodeOptions.push({ nodeOptions.push({
name: this.$locale.baseText('variableSelector.outputData'), name: i18n.baseText('variableSelector.outputData'),
options: this.sortOptions(tempOutputData), options: sortOptions(tempOutputData),
} as IVariableSelectorOption); } as IVariableSelectorOption);
} }
} }
const shortNodeType = this.$locale.shortNodeType(node.type); const shortNodeType = i18n.shortNodeType(node.type);
allNodesData.push({ allNodesData.push({
name: this.$locale.headerText({ name: i18n.headerText({
key: `headers.${shortNodeType}.displayName`, key: `headers.${shortNodeType}.displayName`,
fallback: nodeName, fallback: nodeName,
}), }),
options: this.sortOptions(nodeOptions), options: sortOptions(nodeOptions),
}); });
} }
returnData.push({ returnData.push({
name: this.$locale.baseText('variableSelector.nodes'), name: i18n.baseText('variableSelector.nodes'),
options: allNodesData, options: allNodesData,
}); });
// Remove empty entries and return // Remove empty entries and return
returnData = this.removeEmptyEntries(returnData) as IVariableSelectorOption[] | null; returnData = removeEmptyEntries(returnData) as IVariableSelectorOption[] | null;
if (returnData === null) { if (returnData === null) {
return []; return [];
} }
return returnData; return returnData;
}, }
},
}); function forwardItemSelected(eventData: IVariableItemSelected) {
emit('itemSelected', eventData);
}
</script> </script>
<template>
<div class="variable-selector-wrapper" @keydown.stop>
<div class="input-wrapper">
<n8n-input
ref="inputField"
v-model="variableFilter"
:placeholder="i18n.baseText('variableSelector.variableFilter')"
size="small"
type="text"
></n8n-input>
</div>
<div class="result-wrapper">
<VariableSelectorItem
v-for="option in currentResults"
:key="option.key"
:item="option"
:extend-all="extendAll"
:redact-values="redactValues"
@item-selected="forwardItemSelected"
></VariableSelectorItem>
</div>
</div>
</template>
<style scoped lang="scss"> <style scoped lang="scss">
.variable-selector-wrapper { .variable-selector-wrapper {
border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px;

View File

@@ -59,68 +59,70 @@
</div> </div>
</template> </template>
<script lang="ts"> <script setup lang="ts">
import { defineComponent } from 'vue'; import { ref, computed, onMounted } from 'vue';
import type { IVariableSelectorOption, IVariableItemSelected } from '@/Interface'; import type { IVariableSelectorOption, IVariableItemSelected } from '@/Interface';
export default defineComponent({ const props = defineProps<{
name: 'VariableSelectorItem', allowParentSelect?: boolean;
props: ['allowParentSelect', 'extendAll', 'item', 'redactValues'], extendAll?: boolean;
data() { item: IVariableSelectorOption;
return { redactValues?: boolean;
extended: false, }>();
};
}, const emit = defineEmits<{
computed: { itemSelected: [value: IVariableItemSelected];
itemAddOperations() { }>();
const extended = ref(false);
const itemAddOperations = computed(() => {
const returnOptions = [ const returnOptions = [
{ {
command: 'raw', command: 'raw',
displayName: 'Raw value', displayName: 'Raw value',
}, },
]; ];
if (this.item.dataType === 'array') { if (props.item.dataType === 'array') {
returnOptions.push({ returnOptions.push(
{
command: 'arrayLength', command: 'arrayLength',
displayName: 'Length', displayName: 'Length',
}); },
returnOptions.push({ {
command: 'arrayValues', command: 'arrayValues',
displayName: 'Values', displayName: 'Values',
}); },
} else if (this.item.dataType === 'object') { );
returnOptions.push({ } else if (props.item.dataType === 'object') {
returnOptions.push(
{
command: 'objectKeys', command: 'objectKeys',
displayName: 'Keys', displayName: 'Keys',
}); },
returnOptions.push({ {
command: 'objectValues', command: 'objectValues',
displayName: 'Values', displayName: 'Values',
}); },
);
} }
return returnOptions; return returnOptions;
}, });
},
mounted() { onMounted(() => {
if (this.extended) return; if (extended.value) return;
const shouldAutoExtend = const shouldAutoExtend =
[ ['Current Node', 'Input Data', 'Binary', 'JSON'].includes(props.item.name) &&
this.$locale.baseText('variableSelectorItem.currentNode'), props.item.key === undefined;
this.$locale.baseText('variableSelectorItem.inputData'),
this.$locale.baseText('variableSelectorItem.binary'),
this.$locale.baseText('variableSelectorItem.json'),
].includes(this.item.name) && this.item.key === undefined;
if (shouldAutoExtend) { if (shouldAutoExtend) {
this.extended = true; extended.value = true;
} }
}, });
methods: {
optionSelected(command: string, item: IVariableSelectorOption) { const optionSelected = (command: string, item: IVariableSelectorOption) => {
// By default it is raw let variable = item.key ?? '';
let variable = item.key;
if (command === 'arrayValues') { if (command === 'arrayValues') {
variable = `${item.key}.join(', ')`; variable = `${item.key}.join(', ')`;
} else if (command === 'arrayLength') { } else if (command === 'arrayLength') {
@@ -130,16 +132,16 @@ export default defineComponent({
} else if (command === 'objectValues') { } else if (command === 'objectValues') {
variable = `Object.values(${item.key}).join(', ')`; variable = `Object.values(${item.key}).join(', ')`;
} }
this.$emit('itemSelected', { variable }); emit('itemSelected', { variable });
}, };
selectItem(item: IVariableSelectorOption) {
this.$emit('itemSelected', { variable: item.key }); const selectItem = (item: IVariableSelectorOption) => {
}, emit('itemSelected', { variable: item.key ?? '' });
forwardItemSelected(eventData: IVariableItemSelected) { };
this.$emit('itemSelected', eventData);
}, const forwardItemSelected = (eventData: IVariableItemSelected) => {
}, emit('itemSelected', eventData);
}); };
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">