2021-11-19 01:17:13 -08:00
import { getStyleTokenValue } from "@/components/helpers" ;
import { START_NODE_TYPE } from "@/constants" ;
import { IBounds , INodeUi , IZoomConfig , XYPosition } from "@/Interface" ;
import { Connection , Endpoint , Overlay , OverlaySpec , PaintStyle } from "jsplumb" ;
import {
IConnection ,
ITaskData ,
INodeExecutionData ,
NodeInputConnections ,
INodeTypeDescription ,
} from 'n8n-workflow' ;
export const OVERLAY_DROP_NODE_ID = 'drop-add-node' ;
export const OVERLAY_MIDPOINT_ARROW_ID = 'midpoint-arrow' ;
export const OVERLAY_ENDPOINT_ARROW_ID = 'endpoint-arrow' ;
export const OVERLAY_RUN_ITEMS_ID = 'run-items-label' ;
export const OVERLAY_CONNECTION_ACTIONS_ID = 'connection-actions' ;
export const JSPLUMB_FLOWCHART_STUB = 26 ;
export const OVERLAY_INPUT_NAME_LABEL = 'input-name-label' ;
export const OVERLAY_INPUT_NAME_LABEL_POSITION = [ - 3 , . 5 ] ;
export const OVERLAY_INPUT_NAME_LABEL_POSITION_MOVED = [ - 4.5 , . 5 ] ;
export const OVERLAY_OUTPUT_NAME_LABEL = 'output-name-label' ;
export const GRID_SIZE = 20 ;
const MIN_X_TO_SHOW_OUTPUT_LABEL = 90 ;
const MIN_Y_TO_SHOW_OUTPUT_LABEL = 100 ;
export const NODE_SIZE = 100 ;
export const DEFAULT_START_POSITION_X = 240 ;
export const DEFAULT_START_POSITION_Y = 300 ;
export const HEADER_HEIGHT = 65 ;
export const SIDEBAR_WIDTH = 65 ;
export const MAX_X_TO_PUSH_DOWNSTREAM_NODES = 300 ;
export const PUSH_NODES_OFFSET = NODE_SIZE * 2 + GRID_SIZE ;
const LOOPBACK_MINIMUM = 140 ;
export const INPUT_UUID_KEY = '-input' ;
export const OUTPUT_UUID_KEY = '-output' ;
export const DEFAULT_START_NODE = {
name : 'Start' ,
type : START_NODE_TYPE ,
typeVersion : 1 ,
position : [
DEFAULT_START_POSITION_X ,
DEFAULT_START_POSITION_Y ,
] as XYPosition ,
parameters : { } ,
} ;
export const CONNECTOR_FLOWCHART_TYPE = [ 'N8nCustom' , {
cornerRadius : 12 ,
stub : JSPLUMB_FLOWCHART_STUB + 10 ,
targetGap : 4 ,
alwaysRespectStubs : false ,
2021-11-25 09:38:00 -08:00
loopbackVerticalLength : NODE_SIZE + GRID_SIZE , // height of vertical segment when looping
2021-11-19 01:17:13 -08:00
loopbackMinimum : LOOPBACK_MINIMUM , // minimum length before flowchart loops around
getEndpointOffset ( endpoint : Endpoint ) {
const indexOffset = 10 ; // stub offset between different endpoints of same node
const index = endpoint && endpoint . __meta ? endpoint.__meta.index : 0 ;
const outputOverlay = getOverlay ( endpoint , OVERLAY_OUTPUT_NAME_LABEL ) ;
const labelOffset = outputOverlay && outputOverlay . label && outputOverlay . label . length > 1 ? 10 : 0 ;
return index * indexOffset + labelOffset ;
} ,
} ] ;
export const CONNECTOR_PAINT_STYLE_DEFAULT : PaintStyle = {
stroke : getStyleTokenValue ( '--color-foreground-dark' ) ,
strokeWidth : 2 ,
outlineWidth : 12 ,
outlineStroke : 'transparent' ,
} ;
export const CONNECTOR_PAINT_STYLE_PULL : PaintStyle = {
. . . CONNECTOR_PAINT_STYLE_DEFAULT ,
stroke : getStyleTokenValue ( '--color-foreground-xdark' ) ,
} ;
export const CONNECTOR_PAINT_STYLE_PRIMARY = {
. . . CONNECTOR_PAINT_STYLE_DEFAULT ,
stroke : getStyleTokenValue ( '--color-primary' ) ,
} ;
export const CONNECTOR_PAINT_STYLE_SUCCESS = {
. . . CONNECTOR_PAINT_STYLE_DEFAULT ,
stroke : getStyleTokenValue ( '--color-success-light' ) ,
} ;
export const CONNECTOR_ARROW_OVERLAYS : OverlaySpec [ ] = [
[
'Arrow' ,
{
id : OVERLAY_ENDPOINT_ARROW_ID ,
location : 1 ,
width : 12 ,
foldback : 1 ,
length : 10 ,
visible : true ,
} ,
] ,
[
'Arrow' ,
{
id : OVERLAY_MIDPOINT_ARROW_ID ,
location : 0.5 ,
width : 12 ,
foldback : 1 ,
length : 10 ,
visible : false ,
} ,
] ,
] ;
export const CONNECTOR_DROP_NODE_OVERLAY : OverlaySpec [ ] = [
[
'Label' ,
{
id : OVERLAY_DROP_NODE_ID ,
label : 'Drop connection<br />to add node' ,
cssClass : 'drop-add-node-label' ,
location : 0.5 ,
visible : true ,
} ,
] ,
] ;
export const ANCHOR_POSITIONS : {
[ key : string ] : {
[ key : number ] : string [ ] | number [ ] [ ] ;
}
} = {
input : {
1 : [
[ 0.01 , 0.5 , - 1 , 0 ] ,
] ,
2 : [
[ 0.01 , 0.3 , - 1 , 0 ] ,
[ 0.01 , 0.7 , - 1 , 0 ] ,
] ,
3 : [
[ 0.01 , 0.25 , - 1 , 0 ] ,
[ 0.01 , 0.5 , - 1 , 0 ] ,
[ 0.01 , 0.75 , - 1 , 0 ] ,
] ,
4 : [
[ 0.01 , 0.2 , - 1 , 0 ] ,
[ 0.01 , 0.4 , - 1 , 0 ] ,
[ 0.01 , 0.6 , - 1 , 0 ] ,
[ 0.01 , 0.8 , - 1 , 0 ] ,
] ,
} ,
output : {
1 : [
[ . 99 , 0.5 , 1 , 0 ] ,
] ,
2 : [
[ . 99 , 0.3 , 1 , 0 ] ,
[ . 99 , 0.7 , 1 , 0 ] ,
] ,
3 : [
[ . 99 , 0.25 , 1 , 0 ] ,
[ . 99 , 0.5 , 1 , 0 ] ,
[ . 99 , 0.75 , 1 , 0 ] ,
] ,
4 : [
[ . 99 , 0.2 , 1 , 0 ] ,
[ . 99 , 0.4 , 1 , 0 ] ,
[ . 99 , 0.6 , 1 , 0 ] ,
[ . 99 , 0.8 , 1 , 0 ] ,
] ,
} ,
} ;
export const getInputEndpointStyle = ( nodeTypeData : INodeTypeDescription , color : string ) = > ( {
width : 8 ,
height : nodeTypeData && nodeTypeData . outputs . length > 2 ? 18 : 20 ,
fill : getStyleTokenValue ( color ) ,
stroke : getStyleTokenValue ( color ) ,
lineWidth : 0 ,
} ) ;
export const getInputNameOverlay = ( label : string ) = > ( [
'Label' ,
{
id : OVERLAY_INPUT_NAME_LABEL ,
location : OVERLAY_INPUT_NAME_LABEL_POSITION ,
label ,
cssClass : 'node-input-endpoint-label' ,
visible : true ,
} ,
] ) ;
export const getOutputEndpointStyle = ( nodeTypeData : INodeTypeDescription , color : string ) = > ( {
radius : nodeTypeData && nodeTypeData . outputs . length > 2 ? 7 : 9 ,
fill : getStyleTokenValue ( color ) ,
outlineStroke : 'none' ,
} ) ;
export const getOutputNameOverlay = ( label : string ) = > ( [
'Label' ,
{
id : OVERLAY_OUTPUT_NAME_LABEL ,
location : [ 1.9 , 0.5 ] ,
label ,
cssClass : 'node-output-endpoint-label' ,
visible : true ,
} ,
] ) ;
export const addOverlays = ( connection : Connection , overlays : OverlaySpec [ ] ) = > {
overlays . forEach ( ( overlay : OverlaySpec ) = > {
connection . addOverlay ( overlay ) ;
} ) ;
} ;
export const getLeftmostTopNode = ( nodes : INodeUi [ ] ) : INodeUi = > {
return nodes . reduce ( ( leftmostTop , node ) = > {
if ( node . position [ 0 ] > leftmostTop . position [ 0 ] || node . position [ 1 ] > leftmostTop . position [ 1 ] ) {
return leftmostTop ;
}
return node ;
} ) ;
} ;
export const getWorkflowCorners = ( nodes : INodeUi [ ] ) : IBounds = > {
return nodes . reduce ( ( accu : IBounds , node : INodeUi ) = > {
if ( node . position [ 0 ] < accu . minX ) {
accu . minX = node . position [ 0 ] ;
}
if ( node . position [ 1 ] < accu . minY ) {
accu . minY = node . position [ 1 ] ;
}
if ( node . position [ 0 ] > accu . maxX ) {
accu . maxX = node . position [ 0 ] ;
}
if ( node . position [ 1 ] > accu . maxY ) {
accu . maxY = node . position [ 1 ] ;
}
return accu ;
} , {
minX : nodes [ 0 ] . position [ 0 ] ,
minY : nodes [ 0 ] . position [ 1 ] ,
maxX : nodes [ 0 ] . position [ 0 ] ,
maxY : nodes [ 0 ] . position [ 1 ] ,
} ) ;
} ;
export const scaleSmaller = ( { scale , offset : [ xOffset , yOffset ] } : IZoomConfig ) : IZoomConfig = > {
scale /= 1.25 ;
xOffset /= 1.25 ;
yOffset /= 1.25 ;
xOffset += window . innerWidth / 10 ;
yOffset += window . innerHeight / 10 ;
return {
scale ,
offset : [ xOffset , yOffset ] ,
} ;
} ;
export const scaleBigger = ( { scale , offset : [ xOffset , yOffset ] } : IZoomConfig ) : IZoomConfig = > {
scale *= 1.25 ;
xOffset -= window . innerWidth / 10 ;
yOffset -= window . innerHeight / 10 ;
xOffset *= 1.25 ;
yOffset *= 1.25 ;
return {
scale ,
offset : [ xOffset , yOffset ] ,
} ;
} ;
export const scaleReset = ( config : IZoomConfig ) : IZoomConfig = > {
if ( config . scale > 1 ) { // zoomed in
while ( config . scale > 1 ) {
config = scaleSmaller ( config ) ;
}
}
else {
while ( config . scale < 1 ) {
config = scaleBigger ( config ) ;
}
}
config . scale = 1 ;
return config ;
} ;
export const getOverlay = ( item : Connection | Endpoint , overlayId : string ) = > {
try {
return item . getOverlay ( overlayId ) ; // handle when _jsPlumb element is deleted
} catch ( e ) {
return null ;
}
} ;
export const showOverlay = ( item : Connection | Endpoint , overlayId : string ) = > {
const overlay = getOverlay ( item , overlayId ) ;
if ( overlay ) {
overlay . setVisible ( true ) ;
}
} ;
export const hideOverlay = ( item : Connection | Endpoint , overlayId : string ) = > {
const overlay = getOverlay ( item , overlayId ) ;
if ( overlay ) {
overlay . setVisible ( false ) ;
}
} ;
export const showOrHideMidpointArrow = ( connection : Connection ) = > {
if ( ! connection || ! connection . endpoints || connection . endpoints . length !== 2 ) {
return ;
}
const hasItemsLabel = ! ! getOverlay ( connection , OVERLAY_RUN_ITEMS_ID ) ;
const sourceEndpoint = connection . endpoints [ 0 ] ;
const targetEndpoint = connection . endpoints [ 1 ] ;
const sourcePosition = sourceEndpoint . anchor . lastReturnValue [ 0 ] ;
const targetPosition = targetEndpoint . anchor . lastReturnValue ? targetEndpoint . anchor . lastReturnValue [ 0 ] : sourcePosition + 1 ; // lastReturnValue is null when moving connections from node to another
const minimum = hasItemsLabel ? 150 : 0 ;
const isBackwards = sourcePosition >= targetPosition ;
const isTooLong = Math . abs ( sourcePosition - targetPosition ) >= minimum ;
const arrow = getOverlay ( connection , OVERLAY_MIDPOINT_ARROW_ID ) ;
if ( arrow ) {
arrow . setVisible ( isBackwards && isTooLong ) ;
arrow . setLocation ( hasItemsLabel ? . 6 : .5 ) ;
}
} ;
export const getConnectorLengths = ( connection : Connection ) : [ number , number ] = > {
if ( ! connection . connector ) {
return [ 0 , 0 ] ;
}
const bounds = connection . connector . bounds ;
const diffX = Math . abs ( bounds . maxX - bounds . minX ) ;
const diffY = Math . abs ( bounds . maxY - bounds . minY ) ;
return [ diffX , diffY ] ;
} ;
const isLoopingBackwards = ( connection : Connection ) = > {
const sourceEndpoint = connection . endpoints [ 0 ] ;
const targetEndpoint = connection . endpoints [ 1 ] ;
const sourcePosition = sourceEndpoint . anchor . lastReturnValue [ 0 ] ;
const targetPosition = targetEndpoint . anchor . lastReturnValue [ 0 ] ;
return targetPosition - sourcePosition < ( - 1 * LOOPBACK_MINIMUM ) ;
} ;
export const showOrHideItemsLabel = ( connection : Connection ) = > {
if ( ! connection || ! connection . connector ) {
return ;
}
const overlay = getOverlay ( connection , OVERLAY_RUN_ITEMS_ID ) ;
if ( ! overlay ) {
return ;
}
const actionsOverlay = getOverlay ( connection , OVERLAY_CONNECTION_ACTIONS_ID ) ;
if ( actionsOverlay && actionsOverlay . visible ) {
overlay . setVisible ( false ) ;
return ;
}
const [ diffX , diffY ] = getConnectorLengths ( connection ) ;
if ( diffX < MIN_X_TO_SHOW_OUTPUT_LABEL && diffY < MIN_Y_TO_SHOW_OUTPUT_LABEL ) {
overlay . setVisible ( false ) ;
}
else {
overlay . setVisible ( true ) ;
}
const innerElement = overlay . canvas && overlay . canvas . querySelector ( 'span' ) ;
if ( innerElement ) {
if ( diffY === 0 || isLoopingBackwards ( connection ) ) {
innerElement . classList . add ( 'floating' ) ;
}
else {
innerElement . classList . remove ( 'floating' ) ;
}
}
} ;
export const getIcon = ( name : string ) : string = > {
if ( name === 'trash' ) {
return ` <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="trash" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-trash fa-w-14 Icon__medium_ctPPJ"><path data-v-66d5c7e2="" fill="currentColor" d="M432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16zM53.2 467a48 48 0 0 0 47.9 45h245.8a48 48 0 0 0 47.9-45L416 128H32z" class=""></path></svg> ` ;
}
if ( name === 'plus' ) {
return ` <svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="plus" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" class="svg-inline--fa fa-plus fa-w-14 Icon__medium_ctPPJ"><path data-v-301ed208="" fill="currentColor" d="M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z" class=""></path></svg> ` ;
}
return '' ;
} ;
const canUsePosition = ( position1 : XYPosition , position2 : XYPosition ) = > {
if ( Math . abs ( position1 [ 0 ] - position2 [ 0 ] ) <= 100 ) {
if ( Math . abs ( position1 [ 1 ] - position2 [ 1 ] ) <= 50 ) {
return false ;
}
}
return true ;
} ;
export const getNewNodePosition = ( nodes : INodeUi [ ] , newPosition : XYPosition , movePosition? : XYPosition ) : XYPosition = > {
const targetPosition : XYPosition = [ . . . newPosition ] ;
targetPosition [ 0 ] = targetPosition [ 0 ] - ( targetPosition [ 0 ] % GRID_SIZE ) ;
targetPosition [ 1 ] = targetPosition [ 1 ] - ( targetPosition [ 1 ] % GRID_SIZE ) ;
if ( ! movePosition ) {
movePosition = [ 40 , 40 ] ;
}
let conflictFound = false ;
let i , node ;
do {
conflictFound = false ;
for ( i = 0 ; i < nodes . length ; i ++ ) {
node = nodes [ i ] ;
if ( ! canUsePosition ( node . position , targetPosition ) ) {
conflictFound = true ;
break ;
}
}
if ( conflictFound === true ) {
targetPosition [ 0 ] += movePosition [ 0 ] ;
targetPosition [ 1 ] += movePosition [ 1 ] ;
}
} while ( conflictFound === true ) ;
return targetPosition ;
} ;
export const getMousePosition = ( e : MouseEvent | TouchEvent ) : XYPosition = > {
// @ts-ignore
const x = e . pageX !== undefined ? e . pageX : ( e . touches && e . touches [ 0 ] && e . touches [ 0 ] . pageX ? e . touches [ 0 ] . pageX : 0 ) ;
// @ts-ignore
const y = e . pageY !== undefined ? e . pageY : ( e . touches && e . touches [ 0 ] && e . touches [ 0 ] . pageY ? e . touches [ 0 ] . pageY : 0 ) ;
return [ x , y ] ;
} ;
export const getRelativePosition = ( x : number , y : number , scale : number , offset : XYPosition ) : XYPosition = > {
return [
( x - offset [ 0 ] ) / scale ,
( y - offset [ 1 ] ) / scale ,
] ;
} ;
export const getBackgroundStyles = ( scale : number , offsetPosition : XYPosition ) = > {
const squareSize = GRID_SIZE * scale ;
const dotSize = 1 * scale ;
const dotPosition = ( GRID_SIZE / 2 ) * scale ;
const styles : object = {
'background-size' : ` ${ squareSize } px ${ squareSize } px ` ,
'background-position' : ` left ${ offsetPosition [ 0 ] } px top ${ offsetPosition [ 1 ] } px ` ,
} ;
if ( squareSize > 10.5 ) {
const dotColor = getStyleTokenValue ( '--color-canvas-dot' ) ;
return {
. . . styles ,
'background-image' : ` radial-gradient(circle at ${ dotPosition } px ${ dotPosition } px, ${ dotColor } ${ dotSize } px, transparent 0) ` ,
} ;
}
return styles ;
} ;
export const hideConnectionActions = ( connection : Connection | null ) = > {
if ( connection && connection . connector ) {
hideOverlay ( connection , OVERLAY_CONNECTION_ACTIONS_ID ) ;
showOrHideItemsLabel ( connection ) ;
showOrHideMidpointArrow ( connection ) ;
}
} ;
export const showConectionActions = ( connection : Connection | null ) = > {
if ( connection && connection . connector ) {
showOverlay ( connection , OVERLAY_CONNECTION_ACTIONS_ID ) ;
hideOverlay ( connection , OVERLAY_RUN_ITEMS_ID ) ;
if ( ! getOverlay ( connection , OVERLAY_RUN_ITEMS_ID ) ) {
hideOverlay ( connection , OVERLAY_MIDPOINT_ARROW_ID ) ;
}
}
} ;
export const getOutputSummary = ( data : ITaskData [ ] , nodeConnections : NodeInputConnections ) = > {
const outputMap : { [ sourceOutputIndex : string ] : { [ targetNodeName : string ] : { [ targetInputIndex : string ] : { total : number , iterations : number } } } } = { } ;
data . forEach ( ( run : ITaskData ) = > {
if ( ! run . data || ! run . data . main ) {
return ;
}
run . data . main . forEach ( ( output : INodeExecutionData [ ] | null , i : number ) = > {
if ( ! nodeConnections [ i ] ) {
return ;
}
nodeConnections [ i ]
. map ( ( connection : IConnection ) = > {
const sourceOutputIndex = i ;
const targetNodeName = connection . node ;
const targetInputIndex = connection . index ;
if ( ! outputMap [ sourceOutputIndex ] ) {
outputMap [ sourceOutputIndex ] = { } ;
}
if ( ! outputMap [ sourceOutputIndex ] [ targetNodeName ] ) {
outputMap [ sourceOutputIndex ] [ targetNodeName ] = { } ;
}
if ( ! outputMap [ sourceOutputIndex ] [ targetNodeName ] [ targetInputIndex ] ) {
outputMap [ sourceOutputIndex ] [ targetNodeName ] [ targetInputIndex ] = {
total : 0 ,
iterations : 0 ,
} ;
}
outputMap [ sourceOutputIndex ] [ targetNodeName ] [ targetInputIndex ] . total += output ? output.length : 0 ;
outputMap [ sourceOutputIndex ] [ targetNodeName ] [ targetInputIndex ] . iterations += output ? 1 : 0 ;
} ) ;
} ) ;
} ) ;
return outputMap ;
} ;
export const resetConnection = ( connection : Connection ) = > {
connection . removeOverlay ( OVERLAY_RUN_ITEMS_ID ) ;
connection . setPaintStyle ( CONNECTOR_PAINT_STYLE_DEFAULT ) ;
showOrHideMidpointArrow ( connection ) ;
if ( connection . canvas ) {
connection . canvas . classList . remove ( 'success' ) ;
}
} ;
export const addConnectionOutputSuccess = ( connection : Connection , output : { total : number , iterations : number } ) = > {
connection . setPaintStyle ( CONNECTOR_PAINT_STYLE_SUCCESS ) ;
if ( connection . canvas ) {
connection . canvas . classList . add ( 'success' ) ;
}
if ( getOverlay ( connection , OVERLAY_RUN_ITEMS_ID ) ) {
connection . removeOverlay ( OVERLAY_RUN_ITEMS_ID ) ;
}
let label = ` ${ output . total } ` ;
label = output . total > 1 ? ` ${ label } items ` : ` ${ label } item ` ;
label = output . iterations > 1 ? ` ${ label } total ` : label ;
connection . addOverlay ( [
'Label' ,
{
id : OVERLAY_RUN_ITEMS_ID ,
label : ` <span> ${ label } </span> ` ,
cssClass : 'connection-run-items-label' ,
location : .5 ,
} ,
] ) ;
showOrHideItemsLabel ( connection ) ;
showOrHideMidpointArrow ( connection ) ;
} ;
export const getZoomToFit = ( nodes : INodeUi [ ] ) : { offset : XYPosition , zoomLevel : number } = > {
const { minX , minY , maxX , maxY } = getWorkflowCorners ( nodes ) ;
const PADDING = NODE_SIZE * 4 ;
const editorWidth = window . innerWidth ;
const diffX = maxX - minX + SIDEBAR_WIDTH + PADDING ;
const scaleX = editorWidth / diffX ;
const editorHeight = window . innerHeight ;
const diffY = maxY - minY + HEADER_HEIGHT + PADDING ;
const scaleY = editorHeight / diffY ;
const zoomLevel = Math . min ( scaleX , scaleY , 1 ) ;
let xOffset = ( minX * - 1 ) * zoomLevel + SIDEBAR_WIDTH ; // find top right corner
xOffset += ( editorWidth - SIDEBAR_WIDTH - ( maxX - minX + NODE_SIZE ) * zoomLevel ) / 2 ; // add padding to center workflow
let yOffset = ( minY * - 1 ) * zoomLevel + HEADER_HEIGHT ; // find top right corner
yOffset += ( editorHeight - HEADER_HEIGHT - ( maxY - minY + NODE_SIZE * 2 ) * zoomLevel ) / 2 ; // add padding to center workflow
return {
zoomLevel ,
offset : [ xOffset , yOffset ] ,
} ;
} ;
export const getUniqueNodeName = ( nodes : INodeUi [ ] , originalName : string , additinalUsedNames? : string [ ] ) = > {
// Check if node-name is unique else find one that is
additinalUsedNames = additinalUsedNames || [ ] ;
// Get all the names of the current nodes
const nodeNames = nodes . map ( ( node : INodeUi ) = > {
return node . name ;
} ) ;
// Check first if the current name is already unique
if ( ! nodeNames . includes ( originalName ) && ! additinalUsedNames . includes ( originalName ) ) {
return originalName ;
}
const nameMatch = originalName . match ( /(.*\D+)(\d*)/ ) ;
let ignore , baseName , nameIndex , uniqueName ;
let index = 1 ;
if ( nameMatch === null ) {
// Name is only a number
index = parseInt ( originalName , 10 ) ;
baseName = '' ;
uniqueName = baseName + index ;
} else {
// Name is string or string/number combination
[ ignore , baseName , nameIndex ] = nameMatch ;
if ( nameIndex !== '' ) {
index = parseInt ( nameIndex , 10 ) ;
}
uniqueName = baseName ;
}
while (
nodeNames . includes ( uniqueName ) ||
additinalUsedNames . includes ( uniqueName )
) {
uniqueName = baseName + ( index ++ ) ;
}
return uniqueName ;
} ;
export const showDropConnectionState = ( connection : Connection , targetEndpoint? : Endpoint ) = > {
if ( connection && connection . connector ) {
if ( targetEndpoint ) {
connection . connector . setTargetEndpoint ( targetEndpoint ) ;
}
connection . setPaintStyle ( CONNECTOR_PAINT_STYLE_PRIMARY ) ;
hideOverlay ( connection , OVERLAY_DROP_NODE_ID ) ;
}
} ;
export const showPullConnectionState = ( connection : Connection ) = > {
if ( connection && connection . connector ) {
connection . connector . resetTargetEndpoint ( ) ;
connection . setPaintStyle ( CONNECTOR_PAINT_STYLE_PULL ) ;
showOverlay ( connection , OVERLAY_DROP_NODE_ID ) ;
}
} ;
export const resetConnectionAfterPull = ( connection : Connection ) = > {
if ( connection && connection . connector ) {
connection . connector . resetTargetEndpoint ( ) ;
connection . setPaintStyle ( CONNECTOR_PAINT_STYLE_DEFAULT ) ;
}
} ;
export const resetInputLabelPosition = ( targetEndpoint : Endpoint ) = > {
const inputNameOverlay = getOverlay ( targetEndpoint , OVERLAY_INPUT_NAME_LABEL ) ;
if ( inputNameOverlay ) {
inputNameOverlay . setLocation ( OVERLAY_INPUT_NAME_LABEL_POSITION ) ;
}
} ;
export const moveBackInputLabelPosition = ( targetEndpoint : Endpoint ) = > {
const inputNameOverlay = getOverlay ( targetEndpoint , OVERLAY_INPUT_NAME_LABEL ) ;
if ( inputNameOverlay ) {
inputNameOverlay . setLocation ( OVERLAY_INPUT_NAME_LABEL_POSITION_MOVED ) ;
}
} ;
export const addConnectionActionsOverlay = ( connection : Connection , onDelete : Function , onAdd : Function ) = > {
if ( getOverlay ( connection , OVERLAY_CONNECTION_ACTIONS_ID ) ) {
return ; // avoid free floating actions when moving connection from one node to another
}
connection . addOverlay ( [
'Label' ,
{
id : OVERLAY_CONNECTION_ACTIONS_ID ,
label : ` <div class="add"> ${ getIcon ( 'plus' ) } </div> <div class="delete"> ${ getIcon ( 'trash' ) } </div> ` ,
cssClass : OVERLAY_CONNECTION_ACTIONS_ID ,
visible : false ,
events : {
mousedown : ( overlay : Overlay , event : MouseEvent ) = > {
const element = event . target as HTMLElement ;
if ( element . classList . contains ( 'delete' ) || ( element . parentElement && element . parentElement . classList . contains ( 'delete' ) ) ) {
onDelete ( ) ;
}
else if ( element . classList . contains ( 'add' ) || ( element . parentElement && element . parentElement . classList . contains ( 'add' ) ) ) {
onAdd ( ) ;
}
} ,
} ,
} ,
] ) ;
} ;
export const getOutputEndpointUUID = ( nodeIndex : string , outputIndex : number ) = > {
return ` ${ nodeIndex } ${ OUTPUT_UUID_KEY } ${ outputIndex } ` ;
} ;
export const getInputEndpointUUID = ( nodeIndex : string , inputIndex : number ) = > {
return ` ${ nodeIndex } ${ INPUT_UUID_KEY } ${ inputIndex } ` ;
} ;