feat(editor): Add plus handle design with ability to add connected nodes in new canvas (no-changelog) (#10097)

This commit is contained in:
Alex Grozav
2024-07-18 19:01:14 +03:00
committed by GitHub
parent 7a135df768
commit 11db5a5b51
29 changed files with 665 additions and 369 deletions

View File

@@ -6,7 +6,10 @@ import { CanvasConnectionMode } from '@/types';
import { createCanvasConnectionHandleString } from '@/utils/canvasUtilsV2';
describe('useNodeConnections', () => {
const defaultConnections = { input: {}, output: {} };
const defaultConnections = {
[CanvasConnectionMode.Input]: {},
[CanvasConnectionMode.Output]: {},
};
describe('mainInputs', () => {
it('should return main inputs when provided with main inputs', () => {
const inputs = ref<CanvasNodeData['inputs']>([
@@ -73,13 +76,13 @@ describe('useNodeConnections', () => {
const inputs = ref<CanvasNodeData['inputs']>([]);
const outputs = ref<CanvasNodeData['outputs']>([]);
const connections = ref<CanvasNodeData['connections']>({
input: {
[CanvasConnectionMode.Input]: {
[NodeConnectionType.Main]: [
[{ node: 'node1', type: NodeConnectionType.Main, index: 0 }],
[{ node: 'node2', type: NodeConnectionType.Main, index: 0 }],
],
},
output: {},
[CanvasConnectionMode.Output]: {},
});
const { mainInputConnections } = useNodeConnections({
@@ -89,7 +92,9 @@ describe('useNodeConnections', () => {
});
expect(mainInputConnections.value.length).toBe(2);
expect(mainInputConnections.value).toEqual(connections.value.input[NodeConnectionType.Main]);
expect(mainInputConnections.value).toEqual(
connections.value[CanvasConnectionMode.Input][NodeConnectionType.Main],
);
});
});
@@ -139,8 +144,8 @@ describe('useNodeConnections', () => {
const inputs = ref<CanvasNodeData['inputs']>([]);
const outputs = ref<CanvasNodeData['outputs']>([]);
const connections = ref<CanvasNodeData['connections']>({
input: {},
output: {
[CanvasConnectionMode.Input]: {},
[CanvasConnectionMode.Output]: {
[NodeConnectionType.Main]: [
[{ node: 'node1', type: NodeConnectionType.Main, index: 0 }],
[{ node: 'node2', type: NodeConnectionType.Main, index: 0 }],
@@ -156,7 +161,7 @@ describe('useNodeConnections', () => {
expect(mainOutputConnections.value.length).toBe(2);
expect(mainOutputConnections.value).toEqual(
connections.value.output[NodeConnectionType.Main],
connections.value[CanvasConnectionMode.Output][NodeConnectionType.Main],
);
});
});

View File

@@ -122,11 +122,11 @@ describe('useCanvasMapping', () => {
},
],
connections: {
input: {},
output: {},
[CanvasConnectionMode.Input]: {},
[CanvasConnectionMode.Output]: {},
},
render: {
type: 'default',
type: CanvasNodeRenderType.Default,
options: {
configurable: false,
configuration: false,
@@ -205,10 +205,14 @@ describe('useCanvasMapping', () => {
workflowObject: ref(workflowObject) as Ref<Workflow>,
});
expect(mappedNodes.value[0]?.data?.connections.output).toHaveProperty(
expect(mappedNodes.value[0]?.data?.connections[CanvasConnectionMode.Output]).toHaveProperty(
NodeConnectionType.Main,
);
expect(mappedNodes.value[0]?.data?.connections.output[NodeConnectionType.Main][0][0]).toEqual(
expect(
mappedNodes.value[0]?.data?.connections[CanvasConnectionMode.Output][
NodeConnectionType.Main
][0][0],
).toEqual(
expect.objectContaining({
node: setNode.name,
type: NodeConnectionType.Main,
@@ -216,8 +220,14 @@ describe('useCanvasMapping', () => {
}),
);
expect(mappedNodes.value[1]?.data?.connections.input).toHaveProperty(NodeConnectionType.Main);
expect(mappedNodes.value[1]?.data?.connections.input[NodeConnectionType.Main][0][0]).toEqual(
expect(mappedNodes.value[1]?.data?.connections[CanvasConnectionMode.Input]).toHaveProperty(
NodeConnectionType.Main,
);
expect(
mappedNodes.value[1]?.data?.connections[CanvasConnectionMode.Input][
NodeConnectionType.Main
][0][0],
).toEqual(
expect.objectContaining({
node: manualTriggerNode.name,
type: NodeConnectionType.Main,

View File

@@ -18,7 +18,7 @@ import type {
CanvasNodeDefaultRender,
CanvasNodeStickyNoteRender,
} from '@/types';
import { CanvasNodeRenderType } from '@/types';
import { CanvasConnectionMode, CanvasNodeRenderType } from '@/types';
import {
mapLegacyConnectionsToCanvasConnections,
mapLegacyEndpointsToCanvasConnectionPort,
@@ -263,8 +263,8 @@ export function useCanvasMapping({
inputs: nodeInputsById.value[node.id] ?? [],
outputs: nodeOutputsById.value[node.id] ?? [],
connections: {
input: inputConnections,
output: outputConnections,
[CanvasConnectionMode.Input]: inputConnections,
[CanvasConnectionMode.Output]: outputConnections,
},
issues: {
items: nodeIssuesById.value[node.id],

View File

@@ -1,7 +1,8 @@
import { useCanvasNode } from '@/composables/useCanvasNode';
import { inject, ref } from 'vue';
import type { CanvasNodeInjectionData } from '../types';
import { CanvasNodeRenderType } from '../types';
import type { CanvasNodeData, CanvasNodeInjectionData } from '../types';
import { CanvasConnectionMode, CanvasNodeRenderType } from '../types';
import { NodeConnectionType } from 'n8n-workflow';
vi.mock('vue', async () => {
const actual = await vi.importActual('vue');
@@ -18,7 +19,10 @@ describe('useCanvasNode', () => {
expect(result.label.value).toBe('');
expect(result.inputs.value).toEqual([]);
expect(result.outputs.value).toEqual([]);
expect(result.connections.value).toEqual({ input: {}, output: {} });
expect(result.connections.value).toEqual({
[CanvasConnectionMode.Input]: {},
[CanvasConnectionMode.Output]: {},
});
expect(result.isDisabled.value).toBe(false);
expect(result.isSelected.value).toBeUndefined();
expect(result.pinnedDataCount.value).toBe(0);
@@ -41,9 +45,12 @@ describe('useCanvasNode', () => {
type: 'nodeType1',
typeVersion: 1,
disabled: true,
inputs: [{ type: 'main', index: 0 }],
outputs: [{ type: 'main', index: 0 }],
connections: { input: { '0': [] }, output: {} },
inputs: [{ type: NodeConnectionType.Main, index: 0 }],
outputs: [{ type: NodeConnectionType.Main, index: 0 }],
connections: {
[CanvasConnectionMode.Input]: { '0': [] },
[CanvasConnectionMode.Output]: {},
},
issues: { items: ['issue1'], visible: true },
execution: { status: 'running', waiting: 'waiting', running: true },
runData: { count: 1, visible: true },
@@ -56,7 +63,7 @@ describe('useCanvasNode', () => {
trigger: false,
},
},
}),
} satisfies CanvasNodeData),
id: ref('1'),
label: ref('Node 1'),
selected: ref(true),
@@ -68,9 +75,12 @@ describe('useCanvasNode', () => {
expect(result.label.value).toBe('Node 1');
expect(result.name.value).toBe('Node 1');
expect(result.inputs.value).toEqual([{ type: 'main', index: 0 }]);
expect(result.outputs.value).toEqual([{ type: 'main', index: 0 }]);
expect(result.connections.value).toEqual({ input: { '0': [] }, output: {} });
expect(result.inputs.value).toEqual([{ type: NodeConnectionType.Main, index: 0 }]);
expect(result.outputs.value).toEqual([{ type: NodeConnectionType.Main, index: 0 }]);
expect(result.connections.value).toEqual({
[CanvasConnectionMode.Input]: { '0': [] },
[CanvasConnectionMode.Output]: {},
});
expect(result.isDisabled.value).toBe(true);
expect(result.isSelected.value).toBe(true);
expect(result.pinnedDataCount.value).toBe(1);

View File

@@ -6,7 +6,7 @@
import { CanvasNodeKey } from '@/constants';
import { computed, inject } from 'vue';
import type { CanvasNodeData } from '@/types';
import { CanvasNodeRenderType } from '@/types';
import { CanvasNodeRenderType, CanvasConnectionMode } from '@/types';
export function useCanvasNode() {
const node = inject(CanvasNodeKey);
@@ -20,7 +20,7 @@ export function useCanvasNode() {
disabled: false,
inputs: [],
outputs: [],
connections: { input: {}, output: {} },
connections: { [CanvasConnectionMode.Input]: {}, [CanvasConnectionMode.Output]: {} },
issues: { items: [], visible: false },
pinnedData: { count: 0, visible: false },
execution: {

View File

@@ -0,0 +1,25 @@
/**
* Canvas V2 Only
* @TODO Remove this notice when Canvas V2 is the only one in use
*/
import { CanvasNodeHandleKey } from '@/constants';
import { computed, inject } from 'vue';
import { NodeConnectionType } from 'n8n-workflow';
import { CanvasConnectionMode } from '@/types';
export function useCanvasNodeHandle() {
const handle = inject(CanvasNodeHandleKey);
const label = computed(() => handle?.label.value ?? '');
const connected = computed(() => handle?.connected.value ?? false);
const type = computed(() => handle?.type.value ?? NodeConnectionType.Main);
const mode = computed(() => handle?.mode.value ?? CanvasConnectionMode.Input);
return {
label,
connected,
type,
mode,
};
}

View File

@@ -1,4 +1,5 @@
import type { CanvasNodeData } from '@/types';
import { CanvasConnectionMode } from '@/types';
import type { MaybeRef } from 'vue';
import { computed, unref } from 'vue';
import { NodeConnectionType } from 'n8n-workflow';
@@ -31,7 +32,7 @@ export function useNodeConnections({
);
const mainInputConnections = computed(
() => unref(connections).input[NodeConnectionType.Main] ?? [],
() => unref(connections)[CanvasConnectionMode.Input][NodeConnectionType.Main] ?? [],
);
/**
@@ -47,7 +48,7 @@ export function useNodeConnections({
);
const mainOutputConnections = computed(
() => unref(connections).output[NodeConnectionType.Main] ?? [],
() => unref(connections)[CanvasConnectionMode.Output][NodeConnectionType.Main] ?? [],
);
/**