feat(editor): Add plus handle design with ability to add connected nodes in new canvas (no-changelog) (#10097)
This commit is contained in:
@@ -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],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: {
|
||||
|
||||
25
packages/editor-ui/src/composables/useCanvasNodeHandle.ts
Normal file
25
packages/editor-ui/src/composables/useCanvasNodeHandle.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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] ?? [],
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user