mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
✨ Add plus endpoint (#2450)
* fix * refactor * clean up css * refactor setzoom level * refactor * refactor * refactor func * remove scope * remove localstorage caching * clean up imports * update zero case * add delete connection * update selected state * add base type, remove straight line * add stub offset back * rename param * add label offset * update font size of items * move up label * fix error state while executing * disrespect stubs * check for errors * refactor position * clean up extra space * make entire node connectable * Revert "make entire node connectable" e304f7c5b8ff1b41268450c60ca4bc3b2ada5d4a * always show border * add border to zoom buttons * update spacing * update colors * allow connecting to entire node * fix pull conn active * two line names * apply select to all lines * increase input margin * override target pos * reset conn after pull * fix types * update orientation * fix up connectors snapping * hide arrow on pull * update overrides for connectors * change text * update pull colors * set to 1 line when selected * fix executions bug * build * refactor node component * remove comment * refactor more * remove prop * fix build issue * fix input drag bug in executions * reset offset * update select background * handle issue when endpoints are not set * fix connection aborted issue * add try catch to help show errors * wrap bind with try/catch * set default styles * reset pos despite zoom * add more checks * clean up impl * update icon * handle unknown types * hide items on init * fix importing unknown types with credentials * change opacity * push up item label * update color * update label class and colors * add to drop distance * fix z-index to match node * disable eslint * fix lasso tool selection * update background color * update waiting state * update tooltip positions * update wait node border * fix selection bug mostly * if selected, move above other nodes * add line through disabled nodes * remove node color option * move label above connection * success color for line through * update options index * hide waiting icon when disabled * fix gmail icon * refactor icons * clear execution data on disable/delete * fix selected node * fix executing behavior * optional __meta * set grid size * remove default color * remove node color * add comments * comments * add comments * remove empty space * update comment * refactor uuids * fix type issue * Revert "fix type issue" 9523b34f9604f75253ae0631f29fc27267a99d78 * Revert "fix type issue" 9523b34f9604f75253ae0631f29fc27267a99d78 * Revert "refactor uuids" 07f6848065cb9a98475fddb8330846106f9e70ad * fix build issues * refactor * update uuid * child nodes * skip nodes behind when pushing in loop * shift output icon for switch node * don't show output if waiting * waiting on init * build * change to bezier * implement plus * revert connector change * add bezier type * fix snapping * clean up impl * refactor func * make const * rename type * refactor to simplify * Revert "refactor to simplify" 2db0ed504c752c33de975370d86a83a04ffcda14 * enable flowchart mode * clean up flowchart type * refactor type * merge types * configure curviness * set in localstorage * fix straight line arrow bug * show arrow when pulling * refactor / simplify * fix target gap in bezier * refactor target gap * add comments * add comment * fix dragging connections * fix bug when moving connection * update comment * rename file * update values * update minor * update straight line box * clean up conn types * clean up z-indexes * move color filters to node icon * update background color * update to use grid size value * fix endpoint offsets * set yspan range lower * remove overlays when moving conn * prevent unwanted connections * fix messed up connections * remove console log * clear execution issues on workflow run * update corner radius * fix drag/delete bug * increase offset * update disabled state * hide if endpoint has conn * node insert on click * simplify impl * fix z-indexes * add drop hover message * address comments * refactor * refactor func * remove drop conn overlay * update messagE * update colors * remove console log * rebuild impl * add stalk * fix disabled state * fix dragging bug * remove node selection * disable plus in executions * add success output to talk * add success output * set output * fix bug * fix switch size * update size for 3 output nodes * update loops * fix bug when node is renamed * set final values * update loop behavior * update comment * hide output labels on multi outputs * center output * fix flicker when deleting nodes * fix dragging space * increase stalk if if * more type checks * rename event source * rename event source * support touch when dragging * offset center * center plus * fix repaint behavior * remove extending output * add offset to switch * fix merge node bug * rename endpoint * refactor css, fix merge bug * fix unrelated issues * space out * remove flowchart type * rename constant key * clean up css * rename var * fix more type issues * update types * fix spacing * simplify vertical offset * refactor css * add license * update css * clean up scss * update to use css var * clean up code * update params * show message * fix plus position * fix merge node bugs * hide box when not dragging and hidden * fix potential issue * address comments
This commit is contained in:
@@ -66,11 +66,22 @@ declare module 'jsplumb' {
|
||||
}
|
||||
|
||||
interface Endpoint {
|
||||
endpoint: any; // tslint:disable-line:no-any
|
||||
elementId: string;
|
||||
__meta?: {
|
||||
nodeName: string,
|
||||
nodeId: string,
|
||||
index: number,
|
||||
totalEndpoints: number;
|
||||
};
|
||||
getUuid(): string;
|
||||
getOverlay(name: string): any; // tslint:disable-line:no-any
|
||||
repaint(params?: object): void;
|
||||
}
|
||||
|
||||
interface N8nPlusEndpoint extends Endpoint {
|
||||
setSuccessOutput(message: string): void;
|
||||
clearSuccessOutput(): void;
|
||||
}
|
||||
|
||||
interface Overlay {
|
||||
@@ -103,6 +114,7 @@ export interface IEndpointOptions {
|
||||
parameters?: any; // tslint:disable-line:no-any
|
||||
uuid?: string;
|
||||
enabled?: boolean;
|
||||
cssClass?: string;
|
||||
}
|
||||
|
||||
export interface IUpdateInformation {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div :class="{'node-wrapper': true, selected: isSelected}" :style="nodePosition">
|
||||
<div class="node-wrapper" :style="nodePosition">
|
||||
<div class="select-background" v-show="isSelected"></div>
|
||||
<div :class="{'node-default': true, 'touch-active': isTouchActive, 'is-touch-device': isTouchDevice}" :data-name="data.name" :ref="data.name">
|
||||
<div :class="nodeClass" :style="nodeStyle" @dblclick="setNodeActive" @click.left="mouseLeftClick" v-touch:start="touchStart" v-touch:end="touchEnd">
|
||||
@@ -519,8 +519,7 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
/** node */
|
||||
.node-wrapper.selected {
|
||||
.jtk-endpoint {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
@@ -551,32 +550,31 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
/** node endpoints */
|
||||
.jtk-endpoint {
|
||||
z-index:5;
|
||||
}
|
||||
|
||||
.jtk-connector.jtk-hover {
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.disabled-linethrough {
|
||||
.jtk-endpoint.plus-endpoint {
|
||||
z-index: 6;
|
||||
}
|
||||
|
||||
.jtk-endpoint.jtk-hover {
|
||||
.jtk-endpoint.dot-output-endpoint {
|
||||
z-index: 7;
|
||||
}
|
||||
|
||||
.jtk-overlay {
|
||||
z-index:7;
|
||||
z-index: 7;
|
||||
}
|
||||
|
||||
.disabled-linethrough {
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.jtk-connector.jtk-dragging {
|
||||
z-index: 8;
|
||||
}
|
||||
|
||||
.jtk-endpoint.jtk-drag-active {
|
||||
.jtk-drag-active.dot-output-endpoint, .jtk-drag-active.rect-input-endpoint {
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
@@ -593,3 +591,87 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
$--stalklength: 40px;
|
||||
$--box-size-medium: 24px;
|
||||
$--box-size-small: 18px;
|
||||
|
||||
.plus-endpoint {
|
||||
cursor: pointer;
|
||||
|
||||
.plus-stalk {
|
||||
border-top: 2px solid var(--color-foreground-dark);
|
||||
position: absolute;
|
||||
width: $--stalklength;
|
||||
height: 0;
|
||||
right: 100%;
|
||||
top: calc(50% - 1px);
|
||||
pointer-events: none;
|
||||
|
||||
.connection-run-items-label {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
display: none;
|
||||
left: calc(50% + 4px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.plus-container {
|
||||
color: var(--color-foreground-xdark);
|
||||
border: 2px solid var(--color-foreground-xdark);
|
||||
background-color: var(--color-background-xlight);
|
||||
border-radius: var(--border-radius-base);
|
||||
height: $--box-size-medium;
|
||||
width: $--box-size-medium;
|
||||
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: var(--font-size-2xs);
|
||||
position: absolute;
|
||||
|
||||
top: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
|
||||
&.small {
|
||||
height: $--box-size-small;
|
||||
width: $--box-size-small;
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.fa-plus {
|
||||
width: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.drop-hover-message {
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: var(--font-size-2xs);
|
||||
line-height: var(--font-line-height-regular);
|
||||
color: var(--color-text-light);
|
||||
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: calc(100% + 8px);
|
||||
width: 200px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.hidden > * {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.success .plus-stalk {
|
||||
border-color: var(--color-success-light);
|
||||
|
||||
span {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Endpoint } from 'jsplumb';
|
||||
import {
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { getStyleTokenValue } from '../helpers';
|
||||
|
||||
export const nodeBase = mixins(
|
||||
deviceSupportHelpers,
|
||||
@@ -68,13 +69,14 @@ export const nodeBase = mixins(
|
||||
endpointStyle: CanvasHelpers.getInputEndpointStyle(nodeTypeData, '--color-foreground-xdark'),
|
||||
endpointHoverStyle: CanvasHelpers.getInputEndpointStyle(nodeTypeData, '--color-primary'),
|
||||
isSource: false,
|
||||
isTarget: !this.isReadOnly,
|
||||
isTarget: !this.isReadOnly && nodeTypeData.inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView,
|
||||
parameters: {
|
||||
nodeIndex: this.nodeIndex,
|
||||
type: inputName,
|
||||
index,
|
||||
},
|
||||
enabled: !this.isReadOnly && nodeTypeData.inputs.length > 1, // only enabled for nodes with multiple inputs.. otherwise attachment handled by connectionDrag event in NodeView
|
||||
enabled: !this.isReadOnly, // enabled in default case to allow dragging
|
||||
cssClass: 'rect-input-endpoint',
|
||||
dragAllowedWhenFull: true,
|
||||
dropOptions: {
|
||||
tolerance: 'touch',
|
||||
@@ -92,7 +94,9 @@ export const nodeBase = mixins(
|
||||
const endpoint: Endpoint = this.instance.addEndpoint(this.nodeId, newEndpointData);
|
||||
endpoint.__meta = {
|
||||
nodeName: node.name,
|
||||
nodeId: this.nodeId,
|
||||
index: i,
|
||||
totalEndpoints: nodeTypeData.inputs.length,
|
||||
};
|
||||
|
||||
// TODO: Activate again if it makes sense. Currently makes problems when removing
|
||||
@@ -140,6 +144,7 @@ export const nodeBase = mixins(
|
||||
type: inputName,
|
||||
index,
|
||||
},
|
||||
cssClass: 'dot-output-endpoint',
|
||||
dragAllowedWhenFull: false,
|
||||
dragProxy: ['Rectangle', { width: 1, height: 1, strokeWidth: 0 }],
|
||||
};
|
||||
@@ -151,11 +156,53 @@ export const nodeBase = mixins(
|
||||
];
|
||||
}
|
||||
|
||||
const endpoint: Endpoint = this.instance.addEndpoint(this.nodeId, newEndpointData);
|
||||
const endpoint: Endpoint = this.instance.addEndpoint(this.nodeId, {...newEndpointData});
|
||||
endpoint.__meta = {
|
||||
nodeName: node.name,
|
||||
nodeId: this.nodeId,
|
||||
index: i,
|
||||
totalEndpoints: nodeTypeData.outputs.length,
|
||||
};
|
||||
|
||||
if (!this.isReadOnly) {
|
||||
const plusEndpointData: IEndpointOptions = {
|
||||
uuid: CanvasHelpers.getOutputEndpointUUID(this.nodeIndex, index),
|
||||
anchor: anchorPosition,
|
||||
maxConnections: -1,
|
||||
endpoint: 'N8nPlus',
|
||||
isSource: true,
|
||||
isTarget: false,
|
||||
enabled: !this.isReadOnly,
|
||||
endpointStyle: {
|
||||
fill: getStyleTokenValue('--color-xdark'),
|
||||
outlineStroke: 'none',
|
||||
hover: false,
|
||||
showOutputLabel: nodeTypeData.outputs.length === 1,
|
||||
size: nodeTypeData.outputs.length >= 3 ? 'small' : 'medium',
|
||||
},
|
||||
endpointHoverStyle: {
|
||||
fill: getStyleTokenValue('--color-primary'),
|
||||
outlineStroke: 'none',
|
||||
hover: true, // hack to distinguish hover state
|
||||
},
|
||||
parameters: {
|
||||
nodeIndex: this.nodeIndex,
|
||||
type: inputName,
|
||||
index,
|
||||
},
|
||||
cssClass: 'plus-draggable-endpoint',
|
||||
dragAllowedWhenFull: false,
|
||||
dragProxy: ['Rectangle', { width: 1, height: 1, strokeWidth: 0 }],
|
||||
};
|
||||
|
||||
const plusEndpoint: Endpoint = this.instance.addEndpoint(this.nodeId, plusEndpointData);
|
||||
plusEndpoint.__meta = {
|
||||
nodeName: node.name,
|
||||
nodeId: this.nodeId,
|
||||
index: i,
|
||||
totalEndpoints: nodeTypeData.outputs.length,
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
__makeInstanceDraggable(node: INodeUi) {
|
||||
|
||||
@@ -11,6 +11,7 @@ export const DEFAULT_NEW_WORKFLOW_NAME = 'My workflow';
|
||||
export const MIN_WORKFLOW_NAME_LENGTH = 1;
|
||||
export const MAX_WORKFLOW_NAME_LENGTH = 128;
|
||||
export const DUPLICATE_POSTFFIX = ' copy';
|
||||
export const NODE_OUTPUT_DEFAULT_KEY = '_NODE_OUTPUT_DEFAULT_KEY_';
|
||||
|
||||
// tags
|
||||
export const MAX_TAG_NAME_LENGTH = 24;
|
||||
|
||||
495
packages/editor-ui/src/plugins/PlusEndpointType.js
Normal file
495
packages/editor-ui/src/plugins/PlusEndpointType.js
Normal file
@@ -0,0 +1,495 @@
|
||||
/**
|
||||
* Custom Plus Endpoint
|
||||
* Based on jsplumb Blank Endpoint type
|
||||
*
|
||||
* Source GitHub repository:
|
||||
* https://github.com/jsplumb/jsplumb
|
||||
*
|
||||
* Source files:
|
||||
* https://github.com/jsplumb/jsplumb/blob/fb5fce52794fa52306825bdaa62bf3855cdfd7e0/src/defaults.js#L1230
|
||||
*
|
||||
* All 1.x.x and 2.x.x versions of jsPlumb Community edition, and so also the
|
||||
* content of this file, are dual-licensed under both MIT and GPLv2.
|
||||
*
|
||||
* MIT LICENSE
|
||||
*
|
||||
* Copyright (c) 2010 - 2014 jsPlumb, http://jsplumbtoolkit.com/
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining
|
||||
* a copy of this software and associated documentation files (the
|
||||
* "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish,
|
||||
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||
* permit persons to whom the Software is furnished to do so, subject to
|
||||
* the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* ===============================================================================
|
||||
* GNU GENERAL PUBLIC LICENSE
|
||||
* Version 2, June 1991
|
||||
*
|
||||
* Copyright (C) 1989, 1991 Free Software Foundation, Inc.
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
* Everyone is permitted to copy and distribute verbatim copies
|
||||
* of this license document, but changing it is not allowed.
|
||||
*
|
||||
* Preamble
|
||||
*
|
||||
* The licenses for most software are designed to take away your
|
||||
* freedom to share and change it. By contrast, the GNU General Public
|
||||
* License is intended to guarantee your freedom to share and change free
|
||||
* software--to make sure the software is free for all its users. This
|
||||
* General Public License applies to most of the Free Software
|
||||
* Foundation's software and to any other program whose authors commit to
|
||||
* using it. (Some other Free Software Foundation software is covered by
|
||||
* the GNU Lesser General Public License instead.) You can apply it to
|
||||
* your programs, too.
|
||||
*
|
||||
* When we speak of free software, we are referring to freedom, not
|
||||
* price. Our General Public Licenses are designed to make sure that you
|
||||
* have the freedom to distribute copies of free software (and charge for
|
||||
* this service if you wish), that you receive source code or can get it
|
||||
* if you want it, that you can change the software or use pieces of it
|
||||
* in new free programs; and that you know you can do these things.
|
||||
*
|
||||
* To protect your rights, we need to make restrictions that forbid
|
||||
* anyone to deny you these rights or to ask you to surrender the rights.
|
||||
* These restrictions translate to certain responsibilities for you if you
|
||||
* distribute copies of the software, or if you modify it.
|
||||
*
|
||||
* For example, if you distribute copies of such a program, whether
|
||||
* gratis or for a fee, you must give the recipients all the rights that
|
||||
* you have. You must make sure that they, too, receive or can get the
|
||||
* source code. And you must show them these terms so they know their
|
||||
* rights.
|
||||
*
|
||||
* We protect your rights with two steps: (1) copyright the software, and
|
||||
* (2) offer you this license which gives you legal permission to copy,
|
||||
* distribute and/or modify the software.
|
||||
*
|
||||
* Also, for each author's protection and ours, we want to make certain
|
||||
* that everyone understands that there is no warranty for this free
|
||||
* software. If the software is modified by someone else and passed on, we
|
||||
* want its recipients to know that what they have is not the original, so
|
||||
* that any problems introduced by others will not reflect on the original
|
||||
* authors' reputations.
|
||||
*
|
||||
* Finally, any free program is threatened constantly by software
|
||||
* patents. We wish to avoid the danger that redistributors of a free
|
||||
* program will individually obtain patent licenses, in effect making the
|
||||
* program proprietary. To prevent this, we have made it clear that any
|
||||
* patent must be licensed for everyone's free use or not licensed at all.
|
||||
*
|
||||
* The precise terms and conditions for copying, distribution and
|
||||
* modification follow.
|
||||
*
|
||||
* GNU GENERAL PUBLIC LICENSE
|
||||
* TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
*
|
||||
* 0. This License applies to any program or other work which contains
|
||||
* a notice placed by the copyright holder saying it may be distributed
|
||||
* under the terms of this General Public License. The "Program", below,
|
||||
* refers to any such program or work, and a "work based on the Program"
|
||||
* means either the Program or any derivative work under copyright law:
|
||||
* that is to say, a work containing the Program or a portion of it,
|
||||
* either verbatim or with modifications and/or translated into another
|
||||
* language. (Hereinafter, translation is included without limitation in
|
||||
* the term "modification".) Each licensee is addressed as "you".
|
||||
*
|
||||
* Activities other than copying, distribution and modification are not
|
||||
* covered by this License; they are outside its scope. The act of
|
||||
* running the Program is not restricted, and the output from the Program
|
||||
* is covered only if its contents constitute a work based on the
|
||||
* Program (independent of having been made by running the Program).
|
||||
* Whether that is true depends on what the Program does.
|
||||
*
|
||||
* 1. You may copy and distribute verbatim copies of the Program's
|
||||
* source code as you receive it, in any medium, provided that you
|
||||
* conspicuously and appropriately publish on each copy an appropriate
|
||||
* copyright notice and disclaimer of warranty; keep intact all the
|
||||
* notices that refer to this License and to the absence of any warranty;
|
||||
* and give any other recipients of the Program a copy of this License
|
||||
* along with the Program.
|
||||
*
|
||||
* You may charge a fee for the physical act of transferring a copy, and
|
||||
* you may at your option offer warranty protection in exchange for a fee.
|
||||
*
|
||||
* 2. You may modify your copy or copies of the Program or any portion
|
||||
* of it, thus forming a work based on the Program, and copy and
|
||||
* distribute such modifications or work under the terms of Section 1
|
||||
* above, provided that you also meet all of these conditions:
|
||||
*
|
||||
* a) You must cause the modified files to carry prominent notices
|
||||
* stating that you changed the files and the date of any change.
|
||||
*
|
||||
* b) You must cause any work that you distribute or publish, that in
|
||||
* whole or in part contains or is derived from the Program or any
|
||||
* part thereof, to be licensed as a whole at no charge to all third
|
||||
* parties under the terms of this License.
|
||||
*
|
||||
* c) If the modified program normally reads commands interactively
|
||||
* when run, you must cause it, when started running for such
|
||||
* interactive use in the most ordinary way, to print or display an
|
||||
* announcement including an appropriate copyright notice and a
|
||||
* notice that there is no warranty (or else, saying that you provide
|
||||
* a warranty) and that users may redistribute the program under
|
||||
* these conditions, and telling the user how to view a copy of this
|
||||
* License. (Exception: if the Program itself is interactive but
|
||||
* does not normally print such an announcement, your work based on
|
||||
* the Program is not required to print an announcement.)
|
||||
*
|
||||
* These requirements apply to the modified work as a whole. If
|
||||
* identifiable sections of that work are not derived from the Program,
|
||||
* and can be reasonably considered independent and separate works in
|
||||
* themselves, then this License, and its terms, do not apply to those
|
||||
* sections when you distribute them as separate works. But when you
|
||||
* distribute the same sections as part of a whole which is a work based
|
||||
* on the Program, the distribution of the whole must be on the terms of
|
||||
* this License, whose permissions for other licensees extend to the
|
||||
* entire whole, and thus to each and every part regardless of who wrote it.
|
||||
*
|
||||
* Thus, it is not the intent of this section to claim rights or contest
|
||||
* your rights to work written entirely by you; rather, the intent is to
|
||||
* exercise the right to control the distribution of derivative or
|
||||
* collective works based on the Program.
|
||||
*
|
||||
* In addition, mere aggregation of another work not based on the Program
|
||||
* with the Program (or with a work based on the Program) on a volume of
|
||||
* a storage or distribution medium does not bring the other work under
|
||||
* the scope of this License.
|
||||
*
|
||||
* 3. You may copy and distribute the Program (or a work based on it,
|
||||
* under Section 2) in object code or executable form under the terms of
|
||||
* Sections 1 and 2 above provided that you also do one of the following:
|
||||
*
|
||||
* a) Accompany it with the complete corresponding machine-readable
|
||||
* source code, which must be distributed under the terms of Sections
|
||||
* 1 and 2 above on a medium customarily used for software interchange; or,
|
||||
*
|
||||
* b) Accompany it with a written offer, valid for at least three
|
||||
* years, to give any third party, for a charge no more than your
|
||||
* cost of physically performing source distribution, a complete
|
||||
* machine-readable copy of the corresponding source code, to be
|
||||
* distributed under the terms of Sections 1 and 2 above on a medium
|
||||
* customarily used for software interchange; or,
|
||||
*
|
||||
* c) Accompany it with the information you received as to the offer
|
||||
* to distribute corresponding source code. (This alternative is
|
||||
* allowed only for noncommercial distribution and only if you
|
||||
* received the program in object code or executable form with such
|
||||
* an offer, in accord with Subsection b above.)
|
||||
*
|
||||
* The source code for a work means the preferred form of the work for
|
||||
* making modifications to it. For an executable work, complete source
|
||||
* code means all the source code for all modules it contains, plus any
|
||||
* associated interface definition files, plus the scripts used to
|
||||
* control compilation and installation of the executable. However, as a
|
||||
* special exception, the source code distributed need not include
|
||||
* anything that is normally distributed (in either source or binary
|
||||
* form) with the major components (compiler, kernel, and so on) of the
|
||||
* operating system on which the executable runs, unless that component
|
||||
* itself accompanies the executable.
|
||||
*
|
||||
* If distribution of executable or object code is made by offering
|
||||
* access to copy from a designated place, then offering equivalent
|
||||
* access to copy the source code from the same place counts as
|
||||
* distribution of the source code, even though third parties are not
|
||||
* compelled to copy the source along with the object code.
|
||||
*
|
||||
* 4. You may not copy, modify, sublicense, or distribute the Program
|
||||
* except as expressly provided under this License. Any attempt
|
||||
* otherwise to copy, modify, sublicense or distribute the Program is
|
||||
* void, and will automatically terminate your rights under this License.
|
||||
* However, parties who have received copies, or rights, from you under
|
||||
* this License will not have their licenses terminated so long as such
|
||||
* parties remain in full compliance.
|
||||
*
|
||||
* 5. You are not required to accept this License, since you have not
|
||||
* signed it. However, nothing else grants you permission to modify or
|
||||
* distribute the Program or its derivative works. These actions are
|
||||
* prohibited by law if you do not accept this License. Therefore, by
|
||||
* modifying or distributing the Program (or any work based on the
|
||||
* Program), you indicate your acceptance of this License to do so, and
|
||||
* all its terms and conditions for copying, distributing or modifying
|
||||
* the Program or works based on it.
|
||||
*
|
||||
* 6. Each time you redistribute the Program (or any work based on the
|
||||
* Program), the recipient automatically receives a license from the
|
||||
* original licensor to copy, distribute or modify the Program subject to
|
||||
* these terms and conditions. You may not impose any further
|
||||
* restrictions on the recipients' exercise of the rights granted herein.
|
||||
* You are not responsible for enforcing compliance by third parties to
|
||||
* this License.
|
||||
*
|
||||
* 7. If, as a consequence of a court judgment or allegation of patent
|
||||
* infringement or for any other reason (not limited to patent issues),
|
||||
* conditions are imposed on you (whether by court order, agreement or
|
||||
* otherwise) that contradict the conditions of this License, they do not
|
||||
* excuse you from the conditions of this License. If you cannot
|
||||
* distribute so as to satisfy simultaneously your obligations under this
|
||||
* License and any other pertinent obligations, then as a consequence you
|
||||
* may not distribute the Program at all. For example, if a patent
|
||||
* license would not permit royalty-free redistribution of the Program by
|
||||
* all those who receive copies directly or indirectly through you, then
|
||||
* the only way you could satisfy both it and this License would be to
|
||||
* refrain entirely from distribution of the Program.
|
||||
*
|
||||
* If any portion of this section is held invalid or unenforceable under
|
||||
* any particular circumstance, the balance of the section is intended to
|
||||
* apply and the section as a whole is intended to apply in other
|
||||
* circumstances.
|
||||
*
|
||||
* It is not the purpose of this section to induce you to infringe any
|
||||
* patents or other property right claims or to contest validity of any
|
||||
* such claims; this section has the sole purpose of protecting the
|
||||
* integrity of the free software distribution system, which is
|
||||
* implemented by public license practices. Many people have made
|
||||
* generous contributions to the wide range of software distributed
|
||||
* through that system in reliance on consistent application of that
|
||||
* system; it is up to the author/donor to decide if he or she is willing
|
||||
* to distribute software through any other system and a licensee cannot
|
||||
* impose that choice.
|
||||
*
|
||||
* This section is intended to make thoroughly clear what is believed to
|
||||
* be a consequence of the rest of this License.
|
||||
*
|
||||
* 8. If the distribution and/or use of the Program is restricted in
|
||||
* certain countries either by patents or by copyrighted interfaces, the
|
||||
* original copyright holder who places the Program under this License
|
||||
* may add an explicit geographical distribution limitation excluding
|
||||
* those countries, so that distribution is permitted only in or among
|
||||
* countries not thus excluded. In such case, this License incorporates
|
||||
* the limitation as if written in the body of this License.
|
||||
*
|
||||
* 9. The Free Software Foundation may publish revised and/or new versions
|
||||
* of the General Public License from time to time. Such new versions will
|
||||
* be similar in spirit to the present version, but may differ in detail to
|
||||
* address new problems or concerns.
|
||||
*
|
||||
* Each version is given a distinguishing version number. If the Program
|
||||
* specifies a version number of this License which applies to it and "any
|
||||
* later version", you have the option of following the terms and conditions
|
||||
* either of that version or of any later version published by the Free
|
||||
* Software Foundation. If the Program does not specify a version number of
|
||||
* this License, you may choose any version ever published by the Free Software
|
||||
* Foundation.
|
||||
*
|
||||
* 10. If you wish to incorporate parts of the Program into other free
|
||||
* programs whose distribution conditions are different, write to the author
|
||||
* to ask for permission. For software which is copyrighted by the Free
|
||||
* Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
* make exceptions for this. Our decision will be guided by the two goals
|
||||
* of preserving the free status of all derivatives of our free software and
|
||||
* of promoting the sharing and reuse of software generally.
|
||||
*
|
||||
* NO WARRANTY
|
||||
*
|
||||
* 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
* FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
* OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
* PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
* OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
* TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
* PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
* REPAIR OR CORRECTION.
|
||||
*
|
||||
* 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
* WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
* REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
* INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
* OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
* TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
* YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
* PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGES.
|
||||
*
|
||||
*/
|
||||
(function () {
|
||||
var root = this, _jp = root.jsPlumb, _ju = root.jsPlumbUtil;
|
||||
|
||||
var DOMElementEndpoint = function (params) {
|
||||
_jp.jsPlumbUIComponent.apply(this, arguments);
|
||||
this._jsPlumb.displayElements = [];
|
||||
};
|
||||
_ju.extend(DOMElementEndpoint, _jp.jsPlumbUIComponent, {
|
||||
getDisplayElements: function () {
|
||||
return this._jsPlumb.displayElements;
|
||||
},
|
||||
appendDisplayElement: function (el) {
|
||||
this._jsPlumb.displayElements.push(el);
|
||||
},
|
||||
});
|
||||
|
||||
/*
|
||||
* Class: Endpoints.N8nPlus
|
||||
*/
|
||||
_jp.Endpoints.N8nPlus = function (params) {
|
||||
const _super = _jp.Endpoints.AbstractEndpoint.apply(this, arguments);
|
||||
this.type = "N8nPlus";
|
||||
this.label = '';
|
||||
this.labelOffset = 0;
|
||||
this.size = 'medium';
|
||||
this.showOutputLabel = true;
|
||||
|
||||
const boxSize = {
|
||||
medium: 24,
|
||||
small: 18,
|
||||
};
|
||||
const stalkLength = 40;
|
||||
|
||||
DOMElementEndpoint.apply(this, arguments);
|
||||
|
||||
var clazz = params.cssClass ? " " + params.cssClass : "";
|
||||
|
||||
this.canvas = _jp.createElement("div", {
|
||||
display: "block",
|
||||
background: "transparent",
|
||||
position: "absolute",
|
||||
}, this._jsPlumb.instance.endpointClass + clazz + ' plus-endpoint');
|
||||
|
||||
this.canvas.innerHTML = `
|
||||
<div class="plus-stalk">
|
||||
<div class="connection-run-items-label">
|
||||
<span class="floating"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="plus-container">
|
||||
<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">
|
||||
<path 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>
|
||||
<div class="drop-hover-message">
|
||||
Click to add node</br>
|
||||
or drag to connect
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.canvas.addEventListener('click', (e) => {
|
||||
this._jsPlumb.instance.fire('plusEndpointClick', params.endpoint, e);
|
||||
});
|
||||
|
||||
this._jsPlumb.instance.appendElement(this.canvas);
|
||||
|
||||
const container = this.canvas.querySelector('.plus-container');
|
||||
const message = container.querySelector('.drop-hover-message');
|
||||
const plusStalk = this.canvas.querySelector('.plus-stalk');
|
||||
const successOutput = this.canvas.querySelector('.plus-stalk span');
|
||||
|
||||
this.setSuccessOutput = (label) => {
|
||||
this.canvas.classList.add('success');
|
||||
if (this.showOutputLabel) {
|
||||
successOutput.textContent = label;
|
||||
this.label = label;
|
||||
this.labelOffset = successOutput.offsetWidth;
|
||||
|
||||
plusStalk.style.width = `${stalkLength + this.labelOffset}px`;
|
||||
if (this._jsPlumb && this._jsPlumb.instance && !this._jsPlumb.instance.isSuspendDrawing()) {
|
||||
params.endpoint.repaint(); // force rerender to move plus hoverable/draggable space
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.clearSuccessOutput = () => {
|
||||
this.canvas.classList.remove('success');
|
||||
successOutput.textContent = '';
|
||||
this.label = '';
|
||||
this.labelOffset = 0;
|
||||
plusStalk.style.width = `${stalkLength}px`;
|
||||
params.endpoint.repaint();
|
||||
};
|
||||
|
||||
const isDragging = () => {
|
||||
const endpoint = params.endpoint;
|
||||
const plusConnections = endpoint.connections;
|
||||
|
||||
if (plusConnections.length) {
|
||||
return !!plusConnections.find((conn) => conn && conn.targetId && conn.targetId.startsWith('jsPlumb'));
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const hasEndpointConnections = () => {
|
||||
const endpoint = params.endpoint;
|
||||
const plusConnections = endpoint.connections;
|
||||
|
||||
if (plusConnections.length >= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const allConnections = this._jsPlumb.instance.getConnections({
|
||||
source: endpoint.elementId,
|
||||
}); // includes connections from other output endpoints like dot
|
||||
|
||||
return !!allConnections.find((connection) => {
|
||||
if (!connection || !connection.endpoints || !connection.endpoints.length || !connection.endpoints[0]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const sourceEndpoint = connection.endpoints[0];
|
||||
return sourceEndpoint === endpoint || sourceEndpoint.getUuid() === endpoint.getUuid();
|
||||
});
|
||||
};
|
||||
|
||||
this.paint = function (style, anchor) {
|
||||
if (hasEndpointConnections()) {
|
||||
this.canvas.classList.add('hidden');
|
||||
}
|
||||
else {
|
||||
this.canvas.classList.remove('hidden');
|
||||
container.style.color = style.fill;
|
||||
container.style['border-color'] = style.fill;
|
||||
message.style.display = style.hover ? 'inline' : 'none';
|
||||
}
|
||||
_ju.sizeElement(this.canvas, this.x, this.y, this.w, this.h);
|
||||
};
|
||||
|
||||
this._compute = (anchorPoint, orientation, endpointStyle, connectorPaintStyle) => {
|
||||
this.size = endpointStyle.size || this.size;
|
||||
this.showOutputLabel = !!endpointStyle.showOutputLabel;
|
||||
|
||||
if (this.size !== 'medium') {
|
||||
container.classList.add(this.size);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.label && !this.labelOffset) { // if label is hidden, offset is 0 so recalculate
|
||||
this.setSuccessOutput(this.label);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
const defaultPosition = [anchorPoint[0] + stalkLength + this.labelOffset, anchorPoint[1] - boxSize[this.size] / 2, boxSize[this.size], boxSize[this.size]];
|
||||
|
||||
if (isDragging()) {
|
||||
return defaultPosition;
|
||||
}
|
||||
|
||||
if (hasEndpointConnections()) {
|
||||
return [0, 0, 0, 0]; // remove hoverable box from view
|
||||
}
|
||||
|
||||
return defaultPosition;
|
||||
};
|
||||
};
|
||||
_ju.extend(_jp.Endpoints.N8nPlus, [_jp.Endpoints.AbstractEndpoint, DOMElementEndpoint], {
|
||||
cleanup: function () {
|
||||
if (this.canvas && this.canvas.parentNode) {
|
||||
this.canvas.parentNode.removeChild(this.canvas);
|
||||
}
|
||||
},
|
||||
});
|
||||
_jp.Endpoints.svg.N8nPlus = _jp.Endpoints.N8nPlus;
|
||||
})();
|
||||
@@ -862,7 +862,7 @@ export const store = new Vuex.Store({
|
||||
return state.workflowExecutionData;
|
||||
},
|
||||
getWorkflowRunData: (state): IRunData | null => {
|
||||
if (state.workflowExecutionData === null) {
|
||||
if (!state.workflowExecutionData || !state.workflowExecutionData.data || !state.workflowExecutionData.data.resultData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,11 +108,11 @@
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import {
|
||||
Connection, Endpoint,
|
||||
Connection, Endpoint, N8nPlusEndpoint,
|
||||
} from 'jsplumb';
|
||||
import { MessageBoxInputData } from 'element-ui/types/message-box';
|
||||
import { jsPlumb, OnConnectionBindInfo } from 'jsplumb';
|
||||
import { NODE_NAME_PREFIX, PLACEHOLDER_EMPTY_WORKFLOW_ID, START_NODE_TYPE, WEBHOOK_NODE_TYPE, WORKFLOW_OPEN_MODAL_KEY } from '@/constants';
|
||||
import { NODE_NAME_PREFIX, NODE_OUTPUT_DEFAULT_KEY, PLACEHOLDER_EMPTY_WORKFLOW_ID, START_NODE_TYPE, WEBHOOK_NODE_TYPE, WORKFLOW_OPEN_MODAL_KEY } from '@/constants';
|
||||
import { copyPaste } from '@/components/mixins/copyPaste';
|
||||
import { externalHooks } from '@/components/mixins/externalHooks';
|
||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
@@ -168,6 +168,7 @@ import {
|
||||
} from '../Interface';
|
||||
import { mapGetters } from 'vuex';
|
||||
import '../plugins/N8nCustomConnectorType';
|
||||
import '../plugins/PlusEndpointType';
|
||||
|
||||
export default mixins(
|
||||
copyPaste,
|
||||
@@ -360,7 +361,7 @@ export default mixins(
|
||||
this.$externalHooks().run('execution.open', { workflowId: data.workflowData.id, workflowName: data.workflowData.name, executionId });
|
||||
this.$telemetry.track('User opened read-only execution', { workflow_id: data.workflowData.id, execution_mode: data.mode, execution_finished: data.finished });
|
||||
|
||||
if (data.finished !== true && data.data.resultData.error) {
|
||||
if (data.finished !== true && data && data.data && data.data.resultData && data.data.resultData.error) {
|
||||
// Check if any node contains an error
|
||||
let nodeErrorFound = false;
|
||||
if (data.data.resultData.runData) {
|
||||
@@ -1519,7 +1520,6 @@ export default mixins(
|
||||
this.pullConnActive = true;
|
||||
this.newNodeInsertPosition = null;
|
||||
CanvasHelpers.resetConnection(connection);
|
||||
CanvasHelpers.addOverlays(connection, CanvasHelpers.CONNECTOR_DROP_NODE_OVERLAY);
|
||||
const nodes = [...document.querySelectorAll('.node-default')];
|
||||
|
||||
const onMouseMove = (e: MouseEvent | TouchEvent) => {
|
||||
@@ -1579,6 +1579,17 @@ export default mixins(
|
||||
console.error(e); // eslint-disable-line no-console
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
this.instance.bind(('plusEndpointClick'), (endpoint: Endpoint) => {
|
||||
if (endpoint && endpoint.__meta) {
|
||||
insertNodeAfterSelected({
|
||||
sourceId: endpoint.__meta.nodeId,
|
||||
index: endpoint.__meta.index,
|
||||
eventSource: 'plus_endpoint',
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
async newWorkflow (): Promise<void> {
|
||||
await this.resetWorkspace();
|
||||
@@ -1715,8 +1726,13 @@ export default mixins(
|
||||
// it visibly stays behind free floating without a connection.
|
||||
connection.removeOverlays();
|
||||
|
||||
const sourceEndpoint = connection.endpoints && connection.endpoints[0];
|
||||
this.pullConnActiveNodeName = null; // prevent new connections when connectionDetached is triggered
|
||||
this.instance.deleteConnection(connection); // on delete, triggers connectionDetached event which applies mutation to store
|
||||
if (sourceEndpoint) {
|
||||
const endpoints = this.instance.getEndpoints(sourceEndpoint.elementId);
|
||||
endpoints.forEach((endpoint: Endpoint) => endpoint.repaint()); // repaint both circle and plus endpoint
|
||||
}
|
||||
},
|
||||
__removeConnectionByConnectionInfo (info: OnConnectionBindInfo, removeVisualConnection = false) {
|
||||
// @ts-ignore
|
||||
@@ -1808,6 +1824,16 @@ export default mixins(
|
||||
return uuids[0] === sourceEndpoint && uuids[1] === targetEndpoint;
|
||||
});
|
||||
},
|
||||
getJSPlumbEndpoints (nodeName: string): Endpoint[] {
|
||||
const nodeIndex = this.getNodeIndex(nodeName);
|
||||
const nodeId = `${NODE_NAME_PREFIX}${nodeIndex}`;
|
||||
return this.instance.getEndpoints(nodeId);
|
||||
},
|
||||
getPlusEndpoint (nodeName: string, outputIndex: number): Endpoint | undefined {
|
||||
const endpoints = this.getJSPlumbEndpoints(nodeName);
|
||||
// @ts-ignore
|
||||
return endpoints.find((endpoint: Endpoint) => endpoint.type === 'N8nPlus' && endpoint.__meta && endpoint.__meta.index === outputIndex);
|
||||
},
|
||||
getIncomingOutgoingConnections(nodeName: string): {incoming: Connection[], outgoing: Connection[]} {
|
||||
const name = `${NODE_NAME_PREFIX}${this.$store.getters.getNodeIndex(nodeName)}`;
|
||||
// @ts-ignore
|
||||
@@ -1847,33 +1873,47 @@ export default mixins(
|
||||
outgoing.forEach((connection: Connection) => {
|
||||
CanvasHelpers.resetConnection(connection);
|
||||
});
|
||||
const endpoints = this.getJSPlumbEndpoints(sourceNodeName);
|
||||
endpoints.forEach((endpoint: Endpoint) => {
|
||||
// @ts-ignore
|
||||
if (endpoint.type === 'N8nPlus') {
|
||||
(endpoint.endpoint as N8nPlusEndpoint).clearSuccessOutput();
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeConnections = (this.$store.getters.outgoingConnectionsByNodeName(sourceNodeName) as INodeConnections).main;
|
||||
if (!nodeConnections) {
|
||||
return;
|
||||
}
|
||||
|
||||
const outputMap = CanvasHelpers.getOutputSummary(data, nodeConnections);
|
||||
const outputMap = CanvasHelpers.getOutputSummary(data, nodeConnections || []);
|
||||
|
||||
Object.keys(outputMap).forEach((sourceOutputIndex: string) => {
|
||||
Object.keys(outputMap[sourceOutputIndex]).forEach((targetNodeName: string) => {
|
||||
Object.keys(outputMap[sourceOutputIndex][targetNodeName]).forEach((targetInputIndex: string) => {
|
||||
if (targetNodeName) {
|
||||
const connection = this.getJSPlumbConnection(sourceNodeName, parseInt(sourceOutputIndex, 10), targetNodeName, parseInt(targetInputIndex, 10));
|
||||
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (connection) {
|
||||
const output = outputMap[sourceOutputIndex][targetNodeName][targetInputIndex];
|
||||
if (!output || !output.total) {
|
||||
CanvasHelpers.resetConnection(connection);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
CanvasHelpers.addConnectionOutputSuccess(connection, output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CanvasHelpers.addConnectionOutputSuccess(connection, output);
|
||||
const endpoint = this.getPlusEndpoint(sourceNodeName, parseInt(sourceOutputIndex, 10));
|
||||
if (endpoint && endpoint.endpoint) {
|
||||
const output = outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0];
|
||||
if (output && output.total > 0) {
|
||||
(endpoint.endpoint as N8nPlusEndpoint).setSuccessOutput(CanvasHelpers.getRunItemsLabel(output));
|
||||
}
|
||||
else {
|
||||
(endpoint.endpoint as N8nPlusEndpoint).clearSuccessOutput();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1911,6 +1951,7 @@ export default mixins(
|
||||
}
|
||||
}
|
||||
|
||||
let waitForNewConnection = false;
|
||||
// connect nodes before/after deleted node
|
||||
const nodeType: INodeTypeDescription | null = this.$store.getters.nodeType(node.type, node.typeVersion);
|
||||
if (nodeType && nodeType.outputs.length === 1
|
||||
@@ -1920,6 +1961,7 @@ export default mixins(
|
||||
const conn1 = incoming[0];
|
||||
const conn2 = outgoing[0];
|
||||
if (conn1.__meta && conn2.__meta) {
|
||||
waitForNewConnection = true;
|
||||
const sourceNodeName = conn1.__meta.sourceNodeName;
|
||||
const sourceNodeOutputIndex = conn1.__meta.sourceOutputIndex;
|
||||
const targetNodeName = conn2.__meta.targetNodeName;
|
||||
@@ -1927,7 +1969,12 @@ export default mixins(
|
||||
|
||||
setTimeout(() => {
|
||||
this.connectTwoNodes(sourceNodeName, sourceNodeOutputIndex, targetNodeName, targetNodeOuputIndex);
|
||||
}, 100);
|
||||
|
||||
if (waitForNewConnection) {
|
||||
this.instance.setSuspendDrawing(false, true);
|
||||
waitForNewConnection = false;
|
||||
}
|
||||
}, 100); // just to make it clear to users that this is a new connection
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1952,8 +1999,10 @@ export default mixins(
|
||||
this.$store.commit('removeNode', node);
|
||||
this.$store.commit('clearNodeExecutionData', node.name);
|
||||
|
||||
if (!waitForNewConnection) {
|
||||
// Now it can draw again
|
||||
this.instance.setSuspendDrawing(false, true);
|
||||
}
|
||||
|
||||
// Remove node from selected index if found in it
|
||||
this.$store.commit('removeNodeFromSelection', node);
|
||||
@@ -2603,7 +2652,7 @@ export default mixins(
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
> span.floating {
|
||||
.floating {
|
||||
position: absolute;
|
||||
top: -22px;
|
||||
transform: translateX(-50%);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getStyleTokenValue } from "@/components/helpers";
|
||||
import { START_NODE_TYPE } from "@/constants";
|
||||
import { NODE_OUTPUT_DEFAULT_KEY, START_NODE_TYPE } from "@/constants";
|
||||
import { IBounds, INodeUi, IZoomConfig, XYPosition } from "@/Interface";
|
||||
import { Connection, Endpoint, Overlay, OverlaySpec, PaintStyle } from "jsplumb";
|
||||
import {
|
||||
@@ -57,11 +57,13 @@ export const CONNECTOR_FLOWCHART_TYPE = ['N8nCustom', {
|
||||
getEndpointOffset(endpoint: Endpoint) {
|
||||
const indexOffset = 10; // stub offset between different endpoints of same node
|
||||
const index = endpoint && endpoint.__meta ? endpoint.__meta.index : 0;
|
||||
const totalEndpoints = endpoint && endpoint.__meta ? endpoint.__meta.totalEndpoints : 0;
|
||||
|
||||
const outputOverlay = getOverlay(endpoint, OVERLAY_OUTPUT_NAME_LABEL);
|
||||
const labelOffset = outputOverlay && outputOverlay.label && outputOverlay.label.length > 1 ? 10 : 0;
|
||||
const outputsOffset = totalEndpoints > 3 ? 24 : 0; // avoid intersecting plus
|
||||
|
||||
return index * indexOffset + labelOffset;
|
||||
return index * indexOffset + labelOffset + outputsOffset;
|
||||
},
|
||||
}];
|
||||
|
||||
@@ -112,23 +114,9 @@ export const CONNECTOR_ARROW_OVERLAYS: OverlaySpec[] = [
|
||||
],
|
||||
];
|
||||
|
||||
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[][];
|
||||
[key: number]: number[][];
|
||||
}
|
||||
} = {
|
||||
input: {
|
||||
@@ -511,20 +499,33 @@ export const getOutputSummary = (data: ITaskData[], nodeConnections: NodeInputCo
|
||||
}
|
||||
|
||||
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][NODE_OUTPUT_DEFAULT_KEY]) {
|
||||
outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY] = {};
|
||||
outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0] = {
|
||||
total: 0,
|
||||
iterations: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const defaultOutput = outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0];
|
||||
defaultOutput.total += output ? output.length : 0;
|
||||
defaultOutput.iterations += output ? 1 : 0;
|
||||
|
||||
if (!nodeConnections[sourceOutputIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodeConnections[sourceOutputIndex]
|
||||
.map((connection: IConnection) => {
|
||||
const targetNodeName = connection.node;
|
||||
const targetInputIndex = connection.index;
|
||||
|
||||
if (!outputMap[sourceOutputIndex][targetNodeName]) {
|
||||
outputMap[sourceOutputIndex][targetNodeName] = {};
|
||||
}
|
||||
@@ -554,6 +555,13 @@ export const resetConnection = (connection: Connection) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getRunItemsLabel = (output: {total: number, iterations: number}): string => {
|
||||
let label = `${output.total}`;
|
||||
label = output.total > 1 ? `${label} items` : `${label} item`;
|
||||
label = output.iterations > 1 ? `${label} total` : label;
|
||||
return label;
|
||||
};
|
||||
|
||||
export const addConnectionOutputSuccess = (connection: Connection, output: {total: number, iterations: number}) => {
|
||||
connection.setPaintStyle(CONNECTOR_PAINT_STYLE_SUCCESS);
|
||||
if (connection.canvas) {
|
||||
@@ -564,15 +572,11 @@ export const addConnectionOutputSuccess = (connection: Connection, output: {tota
|
||||
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>`,
|
||||
label: `<span>${getRunItemsLabel(output)}</span>`,
|
||||
cssClass: 'connection-run-items-label',
|
||||
location: .5,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user