mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-28 22:19:41 -08:00
feat(editor): Improve node and edge bring-to-front mechanism on new canvas (#11793)
This commit is contained in:
parent
4dde287cde
commit
b89ca9d482
|
@ -154,6 +154,9 @@ export function createCanvasHandleProvide({
|
|||
isReadOnly?: boolean;
|
||||
isRequired?: boolean;
|
||||
} = {}) {
|
||||
const maxConnections = [NodeConnectionType.Main, NodeConnectionType.AiTool].includes(type)
|
||||
? Infinity
|
||||
: 1;
|
||||
return {
|
||||
[String(CanvasNodeHandleKey)]: {
|
||||
label: ref(label),
|
||||
|
@ -164,6 +167,7 @@ export function createCanvasHandleProvide({
|
|||
isConnecting: ref(isConnecting),
|
||||
isReadOnly: ref(isReadOnly),
|
||||
isRequired: ref(isRequired),
|
||||
maxConnections: ref(maxConnections),
|
||||
runData: ref(runData),
|
||||
} satisfies CanvasNodeHandleInjectionData,
|
||||
};
|
||||
|
|
|
@ -29,10 +29,11 @@ import type { PinDataSource } from '@/composables/usePinnedData';
|
|||
import { isPresent } from '@/utils/typesUtils';
|
||||
import { GRID_SIZE } from '@/utils/nodeViewUtils';
|
||||
import { CanvasKey } from '@/constants';
|
||||
import { onKeyDown, onKeyUp } from '@vueuse/core';
|
||||
import { onKeyDown, onKeyUp, useThrottleFn } from '@vueuse/core';
|
||||
import CanvasArrowHeadMarker from './elements/edges/CanvasArrowHeadMarker.vue';
|
||||
import CanvasBackgroundStripedPattern from './elements/CanvasBackgroundStripedPattern.vue';
|
||||
import { useCanvasTraversal } from '@/composables/useCanvasTraversal';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
|
||||
const $style = useCssModule();
|
||||
|
||||
|
@ -115,6 +116,11 @@ const {
|
|||
onPaneReady,
|
||||
findNode,
|
||||
viewport,
|
||||
onEdgeMouseLeave,
|
||||
onEdgeMouseEnter,
|
||||
onEdgeMouseMove,
|
||||
onNodeMouseEnter,
|
||||
onNodeMouseLeave,
|
||||
} = vueFlow;
|
||||
const {
|
||||
getIncomingNodes,
|
||||
|
@ -297,7 +303,7 @@ function onUpdateNodeParameters(id: string, parameters: Record<string, unknown>)
|
|||
}
|
||||
|
||||
/**
|
||||
* Connections
|
||||
* Connections / Edges
|
||||
*/
|
||||
|
||||
const connectionCreated = ref(false);
|
||||
|
@ -339,6 +345,55 @@ function onClickConnectionAdd(connection: Connection) {
|
|||
|
||||
const arrowHeadMarkerId = ref('custom-arrow-head');
|
||||
|
||||
/**
|
||||
* Edge and Nodes Hovering
|
||||
*/
|
||||
|
||||
const edgesHoveredById = ref<Record<string, boolean>>({});
|
||||
const edgesBringToFrontById = ref<Record<string, boolean>>({});
|
||||
const nodesHoveredById = ref<Record<string, boolean>>({});
|
||||
|
||||
onEdgeMouseEnter(({ edge }) => {
|
||||
edgesBringToFrontById.value = { [edge.id]: true };
|
||||
edgesHoveredById.value = { [edge.id]: true };
|
||||
});
|
||||
|
||||
onEdgeMouseMove(
|
||||
useThrottleFn(({ edge, event }) => {
|
||||
const type = edge.data.source.type;
|
||||
if (type !== NodeConnectionType.AiTool) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!edge.data.maxConnections || edge.data.maxConnections > 1) {
|
||||
const projectedPosition = getProjectedPosition(event);
|
||||
const yDiff = projectedPosition.y - edge.targetY;
|
||||
if (yDiff < 4 * GRID_SIZE) {
|
||||
edgesBringToFrontById.value = { [edge.id]: false };
|
||||
} else {
|
||||
edgesBringToFrontById.value = { [edge.id]: true };
|
||||
}
|
||||
}
|
||||
}, 100),
|
||||
);
|
||||
|
||||
onEdgeMouseLeave(({ edge }) => {
|
||||
edgesBringToFrontById.value = { [edge.id]: false };
|
||||
edgesHoveredById.value = { [edge.id]: false };
|
||||
});
|
||||
|
||||
onNodeMouseEnter(({ node }) => {
|
||||
nodesHoveredById.value = { [node.id]: true };
|
||||
});
|
||||
|
||||
onNodeMouseLeave(({ node }) => {
|
||||
nodesHoveredById.value = { [node.id]: false };
|
||||
});
|
||||
|
||||
function onUpdateEdgeHovered(id: string, hovered: boolean) {
|
||||
edgesHoveredById.value[id] = hovered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executions
|
||||
*/
|
||||
|
@ -375,7 +430,7 @@ const defaultZoom = 1;
|
|||
const zoom = ref(defaultZoom);
|
||||
const isPaneMoving = ref(false);
|
||||
|
||||
function getProjectedPosition(event?: MouseEvent) {
|
||||
function getProjectedPosition(event?: Pick<MouseEvent, 'clientX' | 'clientY'>) {
|
||||
const bounds = viewportRef.value?.getBoundingClientRect() ?? { left: 0, top: 0 };
|
||||
const offsetX = event?.clientX ?? 0;
|
||||
const offsetY = event?.clientY ?? 0;
|
||||
|
@ -590,11 +645,13 @@ provide(CanvasKey, {
|
|||
@move-end="onPaneMoveEnd"
|
||||
@node-drag-stop="onNodeDragStop"
|
||||
>
|
||||
<template #node-canvas-node="canvasNodeProps">
|
||||
<template #node-canvas-node="nodeProps">
|
||||
<Node
|
||||
v-bind="canvasNodeProps"
|
||||
v-bind="nodeProps"
|
||||
:read-only="readOnly"
|
||||
:event-bus="eventBus"
|
||||
:hovered="nodesHoveredById[nodeProps.id]"
|
||||
:bring-to-front="nodesHoveredById[nodeProps.id]"
|
||||
@delete="onDeleteNode"
|
||||
@run="onRunNode"
|
||||
@select="onSelectNode"
|
||||
|
@ -607,13 +664,16 @@ provide(CanvasKey, {
|
|||
/>
|
||||
</template>
|
||||
|
||||
<template #edge-canvas-edge="canvasEdgeProps">
|
||||
<template #edge-canvas-edge="edgeProps">
|
||||
<Edge
|
||||
v-bind="canvasEdgeProps"
|
||||
v-bind="edgeProps"
|
||||
:marker-end="`url(#${arrowHeadMarkerId})`"
|
||||
:read-only="readOnly"
|
||||
:hovered="edgesHoveredById[edgeProps.id]"
|
||||
:bring-to-front="edgesBringToFrontById[edgeProps.id]"
|
||||
@add="onClickConnectionAdd"
|
||||
@delete="onDeleteConnection"
|
||||
@update:hovered="onUpdateEdgeHovered(edgeProps.id, $event)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -30,7 +30,11 @@ beforeEach(() => {
|
|||
|
||||
describe('CanvasEdge', () => {
|
||||
it('should emit delete event when toolbar delete is clicked', async () => {
|
||||
const { emitted, getByTestId } = renderComponent();
|
||||
const { emitted, getByTestId } = renderComponent({
|
||||
props: {
|
||||
hovered: true,
|
||||
},
|
||||
});
|
||||
await userEvent.hover(getByTestId('edge-label-wrapper'));
|
||||
const deleteButton = getByTestId('delete-connection-button');
|
||||
|
||||
|
@ -40,7 +44,11 @@ describe('CanvasEdge', () => {
|
|||
});
|
||||
|
||||
it('should emit add event when toolbar add is clicked', async () => {
|
||||
const { emitted, getByTestId } = renderComponent();
|
||||
const { emitted, getByTestId } = renderComponent({
|
||||
props: {
|
||||
hovered: true,
|
||||
},
|
||||
});
|
||||
await userEvent.hover(getByTestId('edge-label-wrapper'));
|
||||
|
||||
const addButton = getByTestId('add-connection-button');
|
||||
|
|
|
@ -3,40 +3,28 @@
|
|||
import type { CanvasConnectionData } from '@/types';
|
||||
import { isValidNodeConnectionType } from '@/utils/typeGuards';
|
||||
import type { Connection, EdgeProps } from '@vue-flow/core';
|
||||
import { useVueFlow, BaseEdge, EdgeLabelRenderer } from '@vue-flow/core';
|
||||
import { BaseEdge, EdgeLabelRenderer } from '@vue-flow/core';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { computed, useCssModule, ref, toRef } from 'vue';
|
||||
import { computed, useCssModule, toRef } from 'vue';
|
||||
import CanvasEdgeToolbar from './CanvasEdgeToolbar.vue';
|
||||
import { getCustomPath } from './utils/edgePath';
|
||||
|
||||
const emit = defineEmits<{
|
||||
add: [connection: Connection];
|
||||
delete: [connection: Connection];
|
||||
'update:hovered': [hovered: boolean];
|
||||
}>();
|
||||
|
||||
export type CanvasEdgeProps = EdgeProps<CanvasConnectionData> & {
|
||||
readOnly?: boolean;
|
||||
hovered?: boolean;
|
||||
bringToFront?: boolean; // Determines if entire edges layer should be brought to front
|
||||
};
|
||||
|
||||
const props = defineProps<CanvasEdgeProps>();
|
||||
|
||||
const data = toRef(props, 'data');
|
||||
|
||||
const { onEdgeMouseEnter, onEdgeMouseLeave } = useVueFlow();
|
||||
|
||||
const isHovered = ref(false);
|
||||
|
||||
onEdgeMouseEnter(({ edge }) => {
|
||||
if (edge.id !== props.id) return;
|
||||
isHovered.value = true;
|
||||
});
|
||||
|
||||
onEdgeMouseLeave(({ edge }) => {
|
||||
if (edge.id !== props.id) return;
|
||||
isHovered.value = false;
|
||||
});
|
||||
|
||||
const $style = useCssModule();
|
||||
|
||||
const connectionType = computed(() =>
|
||||
|
@ -45,7 +33,7 @@ const connectionType = computed(() =>
|
|||
: NodeConnectionType.Main,
|
||||
);
|
||||
|
||||
const renderToolbar = computed(() => isHovered.value && !props.readOnly);
|
||||
const renderToolbar = computed(() => props.hovered && !props.readOnly);
|
||||
|
||||
const isMainConnection = computed(() => data.value.source.type === NodeConnectionType.Main);
|
||||
|
||||
|
@ -71,12 +59,13 @@ const edgeStyle = computed(() => ({
|
|||
...props.style,
|
||||
...(isMainConnection.value ? {} : { strokeDasharray: '8,8' }),
|
||||
strokeWidth: 2,
|
||||
stroke: isHovered.value ? 'var(--color-primary)' : edgeColor.value,
|
||||
stroke: props.hovered ? 'var(--color-primary)' : edgeColor.value,
|
||||
}));
|
||||
|
||||
const edgeClasses = computed(() => ({
|
||||
[$style.edge]: true,
|
||||
hovered: isHovered.value,
|
||||
hovered: props.hovered,
|
||||
'bring-to-front': props.bringToFront,
|
||||
}));
|
||||
|
||||
const edgeLabelStyle = computed(() => ({
|
||||
|
@ -87,7 +76,7 @@ const edgeToolbarStyle = computed(() => {
|
|||
const [, labelX, labelY] = path.value;
|
||||
return {
|
||||
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
|
||||
...(isHovered.value ? { zIndex: 1 } : {}),
|
||||
...(props.hovered ? { zIndex: 1 } : {}),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -117,6 +106,14 @@ function onAdd() {
|
|||
function onDelete() {
|
||||
emit('delete', connection.value);
|
||||
}
|
||||
|
||||
function onEdgeLabelMouseEnter() {
|
||||
emit('update:hovered', true);
|
||||
}
|
||||
|
||||
function onEdgeLabelMouseLeave() {
|
||||
emit('update:hovered', false);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -137,8 +134,8 @@ function onDelete() {
|
|||
:data-edge-status="status"
|
||||
:style="edgeToolbarStyle"
|
||||
:class="edgeToolbarClasses"
|
||||
@mouseenter="isHovered = true"
|
||||
@mouseleave="isHovered = false"
|
||||
@mouseenter="onEdgeLabelMouseEnter"
|
||||
@mouseleave="onEdgeLabelMouseLeave"
|
||||
>
|
||||
<CanvasEdgeToolbar
|
||||
v-if="renderToolbar"
|
||||
|
|
|
@ -126,6 +126,7 @@ const mode = toRef(props, 'mode');
|
|||
const type = toRef(props, 'type');
|
||||
const index = toRef(props, 'index');
|
||||
const isRequired = toRef(props, 'required');
|
||||
const maxConnections = toRef(props, 'maxConnections');
|
||||
|
||||
provide(CanvasNodeHandleKey, {
|
||||
label,
|
||||
|
@ -137,6 +138,7 @@ provide(CanvasNodeHandleKey, {
|
|||
isConnected,
|
||||
isConnecting,
|
||||
isReadOnly,
|
||||
maxConnections,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -155,6 +157,7 @@ provide(CanvasNodeHandleKey, {
|
|||
<RenderType
|
||||
:class="renderTypeClasses"
|
||||
:is-connected="isConnected"
|
||||
:max-connections="maxConnections"
|
||||
:style="offset"
|
||||
:label="label"
|
||||
@add="onAdd"
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<script lang="ts" setup>
|
||||
import CanvasHandlePlus from '@/components/canvas/elements/handles/render-types/parts/CanvasHandlePlus.vue';
|
||||
import { useCanvasNodeHandle } from '@/composables/useCanvasNodeHandle';
|
||||
import { NodeConnectionType } from 'n8n-workflow';
|
||||
import { computed, ref, useCssModule } from 'vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -10,7 +9,7 @@ const emit = defineEmits<{
|
|||
|
||||
const $style = useCssModule();
|
||||
|
||||
const { label, isConnected, isConnecting, isRequired, type } = useCanvasNodeHandle();
|
||||
const { label, isConnected, isConnecting, isRequired, maxConnections } = useCanvasNodeHandle();
|
||||
|
||||
const handleClasses = 'target';
|
||||
|
||||
|
@ -20,14 +19,12 @@ const classes = computed(() => ({
|
|||
[$style.required]: isRequired.value,
|
||||
}));
|
||||
|
||||
const supportsMultipleConnections = computed(() => type.value === NodeConnectionType.AiTool);
|
||||
|
||||
const isHandlePlusAvailable = computed(
|
||||
() => !isConnected.value || supportsMultipleConnections.value,
|
||||
() => !isConnected.value || !maxConnections.value || maxConnections.value > 1,
|
||||
);
|
||||
|
||||
const isHandlePlusVisible = computed(
|
||||
() => !isConnecting.value || isHovered.value || supportsMultipleConnections.value,
|
||||
() => !isConnecting.value || isHovered.value || !maxConnections.value || maxConnections.value > 1,
|
||||
);
|
||||
|
||||
const isHovered = ref(false);
|
||||
|
|
|
@ -156,18 +156,18 @@ function onClick(event: MouseEvent) {
|
|||
stroke: var(--color-success);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plus {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
.plus {
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
|
||||
path {
|
||||
fill: var(--color-primary);
|
||||
}
|
||||
path {
|
||||
fill: var(--color-primary);
|
||||
}
|
||||
|
||||
rect {
|
||||
stroke: var(--color-primary);
|
||||
rect {
|
||||
stroke: var(--color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
<script lang="ts" setup>
|
||||
import { computed, onBeforeUnmount, onMounted, provide, ref, toRef, watch } from 'vue';
|
||||
import {
|
||||
computed,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
provide,
|
||||
ref,
|
||||
toRef,
|
||||
useCssModule,
|
||||
watch,
|
||||
} from 'vue';
|
||||
import type {
|
||||
CanvasConnectionPort,
|
||||
CanvasElementPortWithRenderData,
|
||||
|
@ -26,6 +35,8 @@ import { createEventBus } from 'n8n-design-system';
|
|||
type Props = NodeProps<CanvasNodeData> & {
|
||||
readOnly?: boolean;
|
||||
eventBus?: EventBus<CanvasEventBusEvents>;
|
||||
hovered?: boolean;
|
||||
bringToFront?: boolean; // Determines if entire nodes layer should be brought to front
|
||||
};
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -40,6 +51,8 @@ const emit = defineEmits<{
|
|||
move: [id: string, position: XYPosition];
|
||||
}>();
|
||||
|
||||
const style = useCssModule();
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const nodeTypesStore = useNodeTypesStore();
|
||||
|
@ -63,6 +76,14 @@ const nodeTypeDescription = computed(() => {
|
|||
return nodeTypesStore.getNodeType(props.data.type, props.data.typeVersion);
|
||||
});
|
||||
|
||||
const classes = computed(() => ({
|
||||
[style.canvasNode]: true,
|
||||
[style.showToolbar]: showToolbar.value,
|
||||
hovered: props.hovered,
|
||||
selected: props.selected,
|
||||
'bring-to-front': props.bringToFront,
|
||||
}));
|
||||
|
||||
/**
|
||||
* Event bus
|
||||
*/
|
||||
|
@ -256,11 +277,7 @@ onBeforeUnmount(() => {
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="[$style.canvasNode, { [$style.showToolbar]: showToolbar }]"
|
||||
data-test-id="canvas-node"
|
||||
:data-node-type="data.type"
|
||||
>
|
||||
<div :class="classes" data-test-id="canvas-node" :data-node-type="data.type">
|
||||
<template
|
||||
v-for="source in mappedOutputs"
|
||||
:key="`${source.handleId}(${source.index + 1}/${mappedOutputs.length})`"
|
||||
|
|
|
@ -514,30 +514,40 @@ export function useCanvasMapping({
|
|||
});
|
||||
|
||||
function getConnectionData(connection: CanvasConnection): CanvasConnectionData {
|
||||
const fromNode = nodes.value.find((node) => node.name === connection.data?.fromNodeName);
|
||||
const { type, index } = parseCanvasConnectionHandleString(connection.sourceHandle);
|
||||
const runDataTotal =
|
||||
nodeExecutionRunDataOutputMapById.value[connection.source]?.[type]?.[index]?.total ?? 0;
|
||||
|
||||
let status: CanvasConnectionData['status'];
|
||||
if (fromNode) {
|
||||
const { type, index } = parseCanvasConnectionHandleString(connection.sourceHandle);
|
||||
const runDataTotal =
|
||||
nodeExecutionRunDataOutputMapById.value[fromNode.id]?.[type]?.[index]?.total ?? 0;
|
||||
|
||||
if (nodeExecutionRunningById.value[fromNode.id]) {
|
||||
status = 'running';
|
||||
} else if (
|
||||
nodePinnedDataById.value[fromNode.id] &&
|
||||
nodeExecutionRunDataById.value[fromNode.id]
|
||||
) {
|
||||
status = 'pinned';
|
||||
} else if (nodeHasIssuesById.value[fromNode.id]) {
|
||||
status = 'error';
|
||||
} else if (runDataTotal > 0) {
|
||||
status = 'success';
|
||||
}
|
||||
if (nodeExecutionRunningById.value[connection.source]) {
|
||||
status = 'running';
|
||||
} else if (
|
||||
nodePinnedDataById.value[connection.source] &&
|
||||
nodeExecutionRunDataById.value[connection.source]
|
||||
) {
|
||||
status = 'pinned';
|
||||
} else if (nodeHasIssuesById.value[connection.source]) {
|
||||
status = 'error';
|
||||
} else if (runDataTotal > 0) {
|
||||
status = 'success';
|
||||
}
|
||||
|
||||
const maxConnections = [
|
||||
...nodeInputsById.value[connection.source],
|
||||
...nodeInputsById.value[connection.target],
|
||||
]
|
||||
.filter((port) => port.type === type)
|
||||
.reduce<number | undefined>((acc, port) => {
|
||||
if (port.maxConnections === undefined) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return Math.min(acc ?? Infinity, port.maxConnections);
|
||||
}, undefined);
|
||||
|
||||
return {
|
||||
...(connection.data as CanvasConnectionData),
|
||||
...(maxConnections ? { maxConnections } : {}),
|
||||
status,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export function useCanvasNodeHandle() {
|
|||
const isConnecting = computed(() => handle?.isConnecting.value ?? false);
|
||||
const isReadOnly = computed(() => handle?.isReadOnly.value);
|
||||
const isRequired = computed(() => handle?.isRequired.value);
|
||||
const maxConnections = computed(() => handle?.maxConnections.value);
|
||||
const type = computed(() => handle?.type.value ?? NodeConnectionType.Main);
|
||||
const mode = computed(() => handle?.mode.value ?? CanvasConnectionMode.Input);
|
||||
const index = computed(() => handle?.index.value ?? 0);
|
||||
|
@ -27,6 +28,7 @@ export function useCanvasNodeHandle() {
|
|||
isConnecting,
|
||||
isReadOnly,
|
||||
isRequired,
|
||||
maxConnections,
|
||||
type,
|
||||
mode,
|
||||
index,
|
||||
|
|
|
@ -77,6 +77,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
.vue-flow__nodes:has(.bring-to-front) {
|
||||
z-index: 2 !important;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selection
|
||||
*/
|
||||
|
@ -92,8 +96,7 @@
|
|||
* Edges
|
||||
*/
|
||||
|
||||
.vue-flow__edges:has(.selected),
|
||||
.vue-flow__edges:has(.hovered),
|
||||
.vue-flow__edges:has(.bring-to-front),
|
||||
.vue-flow__edge-label.selected {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
|
|
@ -118,6 +118,7 @@ export interface CanvasConnectionData {
|
|||
target: CanvasConnectionPort;
|
||||
fromNodeName?: string;
|
||||
status?: 'success' | 'error' | 'pinned' | 'running';
|
||||
maxConnections?: number;
|
||||
}
|
||||
|
||||
export type CanvasConnection = DefaultEdge<CanvasConnectionData>;
|
||||
|
@ -173,6 +174,7 @@ export interface CanvasNodeHandleInjectionData {
|
|||
isConnected: ComputedRef<boolean | undefined>;
|
||||
isConnecting: Ref<boolean | undefined>;
|
||||
isReadOnly: Ref<boolean | undefined>;
|
||||
maxConnections: Ref<number | undefined>;
|
||||
runData: Ref<ExecutionOutputMapData | undefined>;
|
||||
}
|
||||
|
||||
|
|
|
@ -807,7 +807,12 @@ describe('mapLegacyEndpointsToCanvasConnectionPort', () => {
|
|||
|
||||
expect(result).toEqual([
|
||||
{ type: NodeConnectionType.Main, index: 0, label: undefined },
|
||||
{ type: NodeConnectionType.AiTool, index: 0, label: undefined },
|
||||
{
|
||||
type: NodeConnectionType.AiTool,
|
||||
index: 0,
|
||||
label: undefined,
|
||||
maxConnections: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -820,7 +825,13 @@ describe('mapLegacyEndpointsToCanvasConnectionPort', () => {
|
|||
|
||||
expect(result).toEqual([
|
||||
{ type: NodeConnectionType.Main, index: 0, label: 'Main Input' },
|
||||
{ type: NodeConnectionType.AiTool, index: 0, label: 'AI Tool', required: true },
|
||||
{
|
||||
type: NodeConnectionType.AiTool,
|
||||
index: 0,
|
||||
label: 'AI Tool',
|
||||
required: true,
|
||||
maxConnections: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -834,7 +845,12 @@ describe('mapLegacyEndpointsToCanvasConnectionPort', () => {
|
|||
|
||||
expect(result).toEqual([
|
||||
{ type: NodeConnectionType.Main, index: 0, label: undefined },
|
||||
{ type: NodeConnectionType.AiTool, index: 0, label: 'AI Tool' },
|
||||
{
|
||||
type: NodeConnectionType.AiTool,
|
||||
index: 0,
|
||||
label: 'AI Tool',
|
||||
maxConnections: undefined,
|
||||
},
|
||||
{ type: NodeConnectionType.Main, index: 1, label: undefined },
|
||||
]);
|
||||
});
|
||||
|
@ -874,7 +890,12 @@ describe('mapLegacyEndpointsToCanvasConnectionPort', () => {
|
|||
|
||||
expect(result).toEqual([
|
||||
{ type: NodeConnectionType.Main, index: 0, label: 'Main Input', required: true },
|
||||
{ type: NodeConnectionType.AiTool, index: 0, label: 'Optional Tool' },
|
||||
{
|
||||
type: NodeConnectionType.AiTool,
|
||||
index: 0,
|
||||
label: 'Optional Tool',
|
||||
maxConnections: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in a new issue