refactor(editor): Upgrade to jsPlumb 5 (#4989)

* WIP: Nodeview

* Replace types

* Finish N8nPlus endpoint type

* Working on connector

* Apply prettier

* Fixed prettier issues

* Debugging rendering

* Fixed connectorrs position recalc

* Fix snapping and output labels, WIP dragging

* Fix N8nPlus endpoint rendering issues

* Cleanup

* Fix undo/redo and canvas add button position, cleanup

* Cleanup

* Revert accidental CLI changes

* Fix pnpm-lock

* Address bugs that came up during review

* Reset CLI package from master

* Various fixes

* Fix run items label toggling

* Linter fixes

* Fix stalk size for larger run items label

* Remove comment

* Correctly reset workspace after renaming the node

* Fix canvas e2e tests

* Fix undo/redo tests

* Fix stalk positioning and triggering of endpoint overlays

* Repaint connections on pin removal

* Limit repaintings

* Unbind jsPlumb events on deactivation

* Fix jsPlumb managment of Sticky and minor memort managment improvments

* Address rest of PR points

* Lint fix

* Copy patches folder to docker

* Fix e2e tests

* set allowNonAppliedPatches to allow build

* fix(editor): Handling router errors when navigation is canceled by user (#5271)

* 🔨 Handling router errors in main sidebar, removing unused code
* 🔨 Handling router errors in modals

* ci(core): Fix docker nightly/custom image build (no-changelog) (#5284)

* ci(core): Copy patches dir to Docker (no-changelog)

* Update patch

* Update package-lock

* reapply the patch

* skip patchedDependencies after the frontend is built

---------

Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>

* Fix connector hover state on success

* Remove allowNonAppliedPatches from package.json

---------

Co-authored-by: Milorad FIlipović <milorad@n8n.io>
Co-authored-by: कारतोफ्फेलस्क्रिप्ट™ <aditya@netroy.in>
This commit is contained in:
OlegIvaniv
2023-01-30 18:20:50 +01:00
committed by GitHub
parent 5cb7e5007d
commit 766501723b
30 changed files with 1756 additions and 2268 deletions

View File

@@ -1,7 +1,7 @@
<template>
<div :class="$style['content']">
<div
class="node-view-root"
class="node-view-root do-not-select"
id="node-view-root"
data-test-id="node-view-root"
@dragover="onDragOver"
@@ -37,10 +37,11 @@
v-show="showCanvasAddButton"
:showTooltip="!containsTrigger && showTriggerMissingTooltip"
:position="canvasStore.canvasAddButtonPosition"
ref="canvasAddButton"
@hook:mounted="canvasStore.setRecenteredCanvasAddButtonPosition"
data-test-id="canvas-add-button"
/>
<div v-for="nodeData in nodes" :key="nodeData.id">
<template v-for="nodeData in nodes">
<node
v-if="nodeData.type !== STICKY_NODE_TYPE"
@duplicateNode="duplicateNode"
@@ -51,6 +52,7 @@
@runWorkflow="onRunNode"
@moved="onNodeMoved"
@run="onNodeRun"
:ref="`node-${nodeData.id}`"
:key="`${nodeData.id}_node`"
:name="nodeData.name"
:isReadOnly="isReadOnly"
@@ -74,6 +76,7 @@
@nodeSelected="nodeSelectedByName"
@removeNode="(name) => removeNode(name, true)"
:key="`${nodeData.id}_sticky`"
:ref="`node-${nodeData.id}`"
:name="nodeData.name"
:isReadOnly="isReadOnly"
:instance="instance"
@@ -82,7 +85,7 @@
:gridSize="GRID_SIZE"
:hideActions="pullConnActive"
/>
</div>
</template>
</div>
</div>
<node-details-view
@@ -157,15 +160,21 @@
</template>
<script lang="ts">
import Vue from 'vue';
import Vue, { ComponentInstance } from 'vue';
import { mapStores } from 'pinia';
import type {
OnConnectionBindInfo,
Connection,
import {
Endpoint,
N8nPlusEndpoint,
jsPlumbInstance,
} from 'jsplumb';
Connection,
EVENT_CONNECTION,
ConnectionEstablishedParams,
EVENT_CONNECTION_DETACHED,
EVENT_CONNECTION_MOVED,
INTERCEPT_BEFORE_DROP,
BeforeDropParams,
ConnectionDetachedParams,
ConnectionMovedParams,
} from '@jsplumb/core';
import type { MessageBoxInputData } from 'element-ui/types/message-box';
import {
@@ -206,7 +215,6 @@ import Node from '@/components/Node.vue';
import NodeSettings from '@/components/NodeSettings.vue';
import Sticky from '@/components/Sticky.vue';
import CanvasAddButton from './CanvasAddButton.vue';
import mixins from 'vue-typed-mixins';
import { v4 as uuid } from 'uuid';
import {
@@ -275,7 +283,20 @@ import {
RemoveConnectionCommand,
RemoveNodeCommand,
RenameNodeCommand,
historyBus,
} from '@/models/history';
import {
EVENT_ENDPOINT_MOUSEOVER,
EVENT_ENDPOINT_MOUSEOUT,
EVENT_DRAG_MOVE,
EVENT_CONNECTION_DRAG,
EVENT_CONNECTION_ABORT,
EVENT_CONNECTION_MOUSEOUT,
EVENT_CONNECTION_MOUSEOVER,
BrowserJsPlumbInstance,
ready,
} from '@jsplumb/browser-ui';
import { N8nPlusEndpoint } from '@/plugins/endpoints/N8nPlusEndpointType';
interface AddNodeOptions {
position?: XYPosition;
@@ -313,7 +334,6 @@ export default mixins(
},
setup() {
const { registerCustomAction, unregisterCustomAction } = useGlobalLinkActions();
return {
registerCustomAction,
unregisterCustomAction,
@@ -563,7 +583,7 @@ export default mixins(
nodeViewScale(): number {
return this.canvasStore.nodeViewScale;
},
instance(): jsPlumbInstance {
instance(): BrowserJsPlumbInstance {
return this.canvasStore.jsPlumbInstance;
},
},
@@ -587,7 +607,10 @@ export default mixins(
isExecutionPreview: false,
showTriggerMissingTooltip: false,
workflowData: null as INewWorkflowData | null,
activeConnection: null as null | Connection,
isProductionExecutionPreview: false,
enterTimer: undefined as undefined | ReturnType<typeof setTimeout>,
exitTimer: undefined as undefined | ReturnType<typeof setTimeout>,
// jsplumb automatically deletes all loose connections which is in turn recorded
// in undo history as a user action.
// This should prevent automatically removed connections from populating undo stack
@@ -1488,7 +1511,7 @@ export default mixins(
}
}
return this.importWorkflowData(workflowData!, false, 'paste');
return this.importWorkflowData(workflowData!, 'paste', false);
}
},
@@ -1516,9 +1539,8 @@ export default mixins(
// Imports the given workflow data into the current workflow
async importWorkflowData(
workflowData: IWorkflowToShare,
// eslint-disable-next-line @typescript-eslint/default-param-last
importTags = true,
source: string,
importTags = true,
): Promise<void> {
// eslint-disable-line @typescript-eslint/default-param-last
// If it is JSON check if it looks on the first look like data we can use
@@ -2032,14 +2054,14 @@ export default mixins(
this.historyStore.stopRecordingUndo();
},
initNodeView() {
this.instance.importDefaults({
Connector: NodeViewUtils.CONNECTOR_FLOWCHART_TYPE,
Endpoint: ['Dot', { radius: 5 }],
DragOptions: { cursor: 'pointer', zIndex: 5000 },
PaintStyle: NodeViewUtils.CONNECTOR_PAINT_STYLE_DEFAULT,
HoverPaintStyle: NodeViewUtils.CONNECTOR_PAINT_STYLE_PRIMARY,
ConnectionOverlays: NodeViewUtils.CONNECTOR_ARROW_OVERLAYS,
Container: '#node-view',
this.instance?.importDefaults({
endpoint: {
type: 'Dot',
options: { radius: 5 },
},
paintStyle: NodeViewUtils.CONNECTOR_PAINT_STYLE_DEFAULT,
hoverPaintStyle: NodeViewUtils.CONNECTOR_PAINT_STYLE_PRIMARY,
connectionOverlays: NodeViewUtils.CONNECTOR_ARROW_OVERLAYS,
});
const insertNodeAfterSelected = (info: {
@@ -2067,7 +2089,7 @@ export default mixins(
this.onToggleNodeCreator({ source: info.eventSource, createNodeActive: true });
};
this.instance.bind('connectionAborted', (connection) => {
this.instance.bind(EVENT_CONNECTION_ABORT, (connection: Connection) => {
try {
if (this.dropPrevented) {
this.dropPrevented = false;
@@ -2075,10 +2097,10 @@ export default mixins(
}
if (this.pullConnActiveNodeName) {
const sourceNode = this.workflowsStore.getNodeById(connection.sourceId);
const sourceNode = this.workflowsStore.getNodeById(connection.parameters.nodeId);
if (sourceNode) {
const sourceNodeName = sourceNode.name;
const outputIndex = connection.getParameters().index;
const outputIndex = connection.parameters.index;
this.connectTwoNodes(
sourceNodeName,
@@ -2093,8 +2115,8 @@ export default mixins(
}
insertNodeAfterSelected({
sourceId: connection.sourceId,
index: connection.getParameters().index,
sourceId: connection.parameters.nodeId,
index: connection.parameters.index,
eventSource: 'node_connection_drop',
});
} catch (e) {
@@ -2102,11 +2124,10 @@ export default mixins(
}
});
this.instance.bind('beforeDrop', (info) => {
this.instance.bind(INTERCEPT_BEFORE_DROP, (info: BeforeDropParams) => {
try {
const sourceInfo = info.connection.endpoints[0].getParameters();
// @ts-ignore
const targetInfo = info.dropEndpoint.getParameters();
const sourceInfo = info.connection.endpoints[0].parameters;
const targetInfo = info.dropEndpoint.parameters;
const sourceNodeName = this.workflowsStore.getNodeById(sourceInfo.nodeId)?.name || '';
const targetNodeName = this.workflowsStore.getNodeById(targetInfo.nodeId)?.name || '';
@@ -2127,13 +2148,10 @@ export default mixins(
}
});
// only one set of visible actions should be visible at the same time
let activeConnection: null | Connection = null;
this.instance.bind('connection', (info: OnConnectionBindInfo) => {
this.instance.bind(EVENT_CONNECTION, (info: ConnectionEstablishedParams) => {
try {
const sourceInfo = info.sourceEndpoint.getParameters();
const targetInfo = info.targetEndpoint.getParameters();
const sourceInfo = info.sourceEndpoint.parameters;
const targetInfo = info.targetEndpoint.parameters;
const sourceNodeName = this.workflowsStore.getNodeById(sourceInfo.nodeId)?.name;
const targetNodeName = this.workflowsStore.getNodeById(targetInfo.nodeId)?.name;
@@ -2148,86 +2166,6 @@ export default mixins(
}
NodeViewUtils.resetConnection(info.connection);
if (!this.isReadOnly) {
let exitTimer: NodeJS.Timeout | undefined;
let enterTimer: NodeJS.Timeout | undefined;
info.connection.bind('mouseover', (connection: Connection) => {
try {
if (exitTimer !== undefined) {
clearTimeout(exitTimer);
exitTimer = undefined;
}
if (enterTimer) {
return;
}
if (!info.connection || info.connection === activeConnection) {
return;
}
NodeViewUtils.hideConnectionActions(activeConnection);
enterTimer = setTimeout(() => {
enterTimer = undefined;
if (info.connection) {
activeConnection = info.connection;
NodeViewUtils.showConnectionActions(info.connection);
}
}, 150);
} catch (e) {
console.error(e); // eslint-disable-line no-console
}
});
info.connection.bind('mouseout', (connection: Connection) => {
try {
if (exitTimer) {
return;
}
if (enterTimer) {
clearTimeout(enterTimer);
enterTimer = undefined;
}
if (!info.connection || activeConnection !== info.connection) {
return;
}
exitTimer = setTimeout(() => {
exitTimer = undefined;
if (info.connection && activeConnection === info.connection) {
NodeViewUtils.hideConnectionActions(activeConnection);
activeConnection = null;
}
}, 500);
} catch (e) {
console.error(e); // eslint-disable-line no-console
}
});
NodeViewUtils.addConnectionActionsOverlay(
info.connection,
() => {
activeConnection = null;
this.__deleteJSPlumbConnection(info.connection);
},
() => {
setTimeout(() => {
insertNodeAfterSelected({
sourceId: info.sourceId,
index: sourceInfo.index,
connection: info.connection,
eventSource: 'node_connection_action',
});
}, 150);
},
);
}
NodeViewUtils.moveBackInputLabelPosition(info.targetEndpoint);
const connectionData: [IConnection, IConnection] = [
@@ -2247,26 +2185,105 @@ export default mixins(
connection: connectionData,
setStateDirty: true,
});
this.dropPrevented = true;
if (!this.suspendRecordingDetachedConnections) {
this.historyStore.pushCommandToUndo(new AddConnectionCommand(connectionData, this));
this.historyStore.pushCommandToUndo(new AddConnectionCommand(connectionData));
}
if (!this.isReadOnly) {
NodeViewUtils.addConnectionActionsOverlay(
info.connection,
() => {
this.activeConnection = null;
this.__deleteJSPlumbConnection(info.connection);
},
() => {
insertNodeAfterSelected({
sourceId: info.sourceEndpoint.parameters.nodeId,
index: sourceInfo.index,
connection: info.connection,
eventSource: 'node_connection_action',
});
},
);
}
} catch (e) {
console.error(e); // eslint-disable-line no-console
}
});
this.instance.bind('connectionMoved', (info) => {
this.instance.bind(EVENT_DRAG_MOVE, () => {
this.instance?.connections.forEach((connection) => {
NodeViewUtils.showOrHideItemsLabel(connection);
NodeViewUtils.showOrHideMidpointArrow(connection);
Object.values(connection.overlays).forEach((overlay) => {
if (!overlay.canvas) return;
this.instance?.repaint(overlay.canvas);
});
});
});
this.instance.bind(EVENT_CONNECTION_MOUSEOVER, (connection: Connection) => {
try {
if (this.exitTimer !== undefined) {
clearTimeout(this.exitTimer);
this.exitTimer = undefined;
}
if (
this.isReadOnly ||
this.enterTimer ||
!connection ||
connection === this.activeConnection
)
return;
if (this.activeConnection) NodeViewUtils.hideConnectionActions(this.activeConnection);
this.enterTimer = setTimeout(() => {
this.enterTimer = undefined;
if (connection) {
NodeViewUtils.showConnectionActions(connection);
this.activeConnection = connection;
}
}, 150);
} catch (e) {
console.error(e); // eslint-disable-line no-console
}
});
this.instance.bind(EVENT_CONNECTION_MOUSEOUT, (connection: Connection) => {
try {
if (this.exitTimer) return;
if (this.enterTimer) {
clearTimeout(this.enterTimer);
this.enterTimer = undefined;
}
if (this.isReadOnly || !connection || this.activeConnection?.id !== connection.id) return;
this.exitTimer = setTimeout(() => {
this.exitTimer = undefined;
if (connection && this.activeConnection === connection) {
NodeViewUtils.hideConnectionActions(this.activeConnection);
this.activeConnection = null;
}
}, 500);
} catch (e) {
console.error(e); // eslint-disable-line no-console
}
});
this.instance.bind(EVENT_CONNECTION_MOVED, (info: ConnectionMovedParams) => {
try {
// When a connection gets moved from one node to another it for some reason
// calls the "connection" event but not the "connectionDetached" one. So we listen
// additionally to the "connectionMoved" event and then only delete the existing connection.
NodeViewUtils.resetInputLabelPosition(info.originalTargetEndpoint);
NodeViewUtils.resetInputLabelPosition(info.connection);
// @ts-ignore
const sourceInfo = info.originalSourceEndpoint.getParameters();
// @ts-ignore
const targetInfo = info.originalTargetEndpoint.getParameters();
const sourceInfo = info.connection.parameters;
const targetInfo = info.originalEndpoint.parameters;
const connectionInfo = [
{
@@ -2286,8 +2303,18 @@ export default mixins(
console.error(e); // eslint-disable-line no-console
}
});
this.instance.bind('connectionDetached', async (info) => {
this.instance.bind(EVENT_ENDPOINT_MOUSEOVER, (endpoint: Endpoint, mouse) => {
// This event seems bugged. It gets called constantly even when the mouse is not over the endpoint
// if the endpoint has a connection attached to it. So we need to check if the mouse is actually over
// the endpoint.
if (!endpoint.isTarget || mouse.target !== endpoint.endpoint.canvas) return;
this.instance.setHover(endpoint, true);
});
this.instance.bind(EVENT_ENDPOINT_MOUSEOUT, (endpoint: Endpoint) => {
if (!endpoint.isTarget) return;
this.instance.setHover(endpoint, false);
});
this.instance.bind(EVENT_CONNECTION_DETACHED, async (info: ConnectionDetachedParams) => {
try {
const connectionInfo: [IConnection, IConnection] | null = getConnectionInfo(info);
NodeViewUtils.resetInputLabelPosition(info.targetEndpoint);
@@ -2297,14 +2324,12 @@ export default mixins(
if (this.pullConnActiveNodeName) {
// establish new connection when dragging connection from one node to another
this.historyStore.startRecordingUndo();
const sourceNode = this.workflowsStore.getNodeById(info.connection.sourceId);
const sourceNode = this.workflowsStore.getNodeById(info.connection.parameters.nodeId);
const sourceNodeName = sourceNode.name;
const outputIndex = info.connection.getParameters().index;
const outputIndex = info.connection.parameters.index;
if (connectionInfo) {
this.historyStore.pushCommandToUndo(
new RemoveConnectionCommand(connectionInfo, this),
);
this.historyStore.pushCommandToUndo(new RemoveConnectionCommand(connectionInfo));
}
this.connectTwoNodes(sourceNodeName, outputIndex, this.pullConnActiveNodeName, 0, true);
this.pullConnActiveNodeName = null;
@@ -2324,26 +2349,27 @@ export default mixins(
console.error(e); // eslint-disable-line no-console
}
});
// @ts-ignore
this.instance.bind('connectionDrag', (connection: Connection) => {
this.instance.bind(EVENT_CONNECTION_DRAG, (connection: Connection) => {
// The overlays are visible by default so we need to hide the midpoint arrow
// manually
connection.overlays['midpoint-arrow']?.setVisible(false);
try {
this.pullConnActiveNodeName = null;
this.pullConnActive = true;
this.newNodeInsertPosition = null;
NodeViewUtils.resetConnection(connection);
const nodes = [...document.querySelectorAll('.node-default')];
const nodes = [...document.querySelectorAll('.node-wrapper')];
const onMouseMove = (e: MouseEvent | TouchEvent) => {
if (!connection) {
return;
}
const element = document.querySelector('.jtk-endpoint.dropHover');
const element = document.querySelector('.jtk-endpoint.jtk-drag-hover');
if (element) {
// @ts-ignore
NodeViewUtils.showDropConnectionState(connection, element._jsPlumb);
const endpoint = element.jtk.endpoint;
NodeViewUtils.showDropConnectionState(connection, endpoint);
return;
}
@@ -2360,7 +2386,7 @@ export default mixins(
this.pullConnActiveNodeName = node.name;
const endpointUUID = this.getInputEndpointUUID(nodeName, 0);
if (endpointUUID) {
const endpoint = this.instance.getEndpoint(endpointUUID);
const endpoint = this.instance?.getEndpoint(endpointUUID);
NodeViewUtils.showDropConnectionState(connection, endpoint);
@@ -2395,8 +2421,17 @@ export default mixins(
console.error(e); // eslint-disable-line no-console
}
});
// @ts-ignore
this.instance.bind(
[EVENT_CONNECTION_DRAG, EVENT_CONNECTION_ABORT, EVENT_CONNECTION_DETACHED],
(connection: Connection) => {
Object.values(this.instance?.endpointsByElement)
.flatMap((endpoints) => Object.values(endpoints))
.filter((endpoint) => endpoint.endpoint.type === 'N8nPlus')
.forEach((endpoint) =>
setTimeout(() => endpoint.instance.revalidate(endpoint.element), 0),
);
},
);
this.instance.bind('plusEndpointClick', (endpoint: Endpoint) => {
if (endpoint && endpoint.__meta) {
insertNodeAfterSelected({
@@ -2555,10 +2590,8 @@ export default mixins(
}
const uuid: [string, string] = [outputUuid, inputUuid];
// Create connections in DOM
// @ts-ignore
this.instance.connect({
this.instance?.connect({
uuids: uuid,
detachable: !this.isReadOnly,
});
@@ -2581,15 +2614,12 @@ export default mixins(
if (!sourceNode || !targetNode) {
return;
}
// @ts-ignore
const connections = this.instance.getConnections({
const connections = this.instance?.getConnections({
source: sourceNode.id,
target: targetNode.id,
});
// @ts-ignore
connections.forEach((connectionInstance) => {
connections.forEach((connectionInstance: Connection) => {
if (connectionInstance.__meta) {
// Only delete connections from specific indexes (if it can be determined by meta)
if (
@@ -2611,13 +2641,8 @@ 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
}
this.instance?.deleteConnection(connection); // on delete, triggers connectionDetached event which applies mutation to store
if (trackHistory && connection.__meta) {
const connectionData: [IConnection, IConnection] = [
{
@@ -2635,18 +2660,14 @@ export default mixins(
this.historyStore.pushCommandToUndo(removeCommand);
}
},
__removeConnectionByConnectionInfo(
info: OnConnectionBindInfo,
removeVisualConnection = false,
trackHistory = false,
) {
__removeConnectionByConnectionInfo(info, removeVisualConnection = false, trackHistory = false) {
const connectionInfo: [IConnection, IConnection] | null = getConnectionInfo(info);
if (connectionInfo) {
if (removeVisualConnection) {
this.__deleteJSPlumbConnection(info.connection, trackHistory);
} else if (trackHistory) {
this.historyStore.pushCommandToUndo(new RemoveConnectionCommand(connectionInfo, this));
this.historyStore.pushCommandToUndo(new RemoveConnectionCommand(connectionInfo));
}
this.workflowsStore.removeConnection({ connection: connectionInfo });
}
@@ -2751,7 +2772,7 @@ export default mixins(
const targetEndpoint = NodeViewUtils.getInputEndpointUUID(targetId, targetInputIndex);
// @ts-ignore
const connections = this.instance.getConnections({
const connections = this.instance?.getConnections({
source: sourceId,
target: targetId,
}) as Connection[];
@@ -2763,14 +2784,19 @@ export default mixins(
},
getJSPlumbEndpoints(nodeName: string): Endpoint[] {
const node = this.workflowsStore.getNodeByName(nodeName);
return this.instance.getEndpoints(node !== null ? node.id : '');
const nodeEls: Element = (this.$refs[`node-${node?.id}`] as ComponentInstance[])[0]
.$el as Element;
const endpoints = this.instance?.getEndpoints(nodeEls);
return endpoints as Endpoint[];
},
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,
// @ts-ignore
endpoint.endpoint.type === 'N8nPlus' && endpoint?.__meta?.index === outputIndex,
);
},
getIncomingOutgoingConnections(nodeName: string): {
@@ -2781,12 +2807,12 @@ export default mixins(
if (node) {
// @ts-ignore
const outgoing = this.instance.getConnections({
const outgoing = this.instance?.getConnections({
source: node.id,
});
// @ts-ignore
const incoming = this.instance.getConnections({
const incoming = this.instance?.getConnections({
target: node.id,
}) as Connection[];
@@ -2823,8 +2849,7 @@ export default mixins(
const sourceId = sourceNode !== null ? sourceNode.id : '';
if (data === null || data.length === 0 || waiting) {
// @ts-ignore
const outgoing = this.instance.getConnections({
const outgoing = this.instance?.getConnections({
source: sourceId,
}) as Connection[];
@@ -2833,8 +2858,7 @@ export default mixins(
});
const endpoints = this.getJSPlumbEndpoints(sourceNodeName);
endpoints.forEach((endpoint: Endpoint) => {
// @ts-ignore
if (endpoint.type === 'N8nPlus') {
if (endpoint.endpoint.type === 'N8nPlus') {
(endpoint.endpoint as N8nPlusEndpoint).clearSuccessOutput();
}
});
@@ -2875,6 +2899,7 @@ export default mixins(
);
if (endpoint && endpoint.endpoint) {
const output = outputMap[sourceOutputIndex][NODE_OUTPUT_DEFAULT_KEY][0];
if (output && output.total > 0) {
(endpoint.endpoint as N8nPlusEndpoint).setSuccessOutput(
NodeViewUtils.getRunItemsLabel(output),
@@ -2963,7 +2988,7 @@ export default mixins(
);
if (waitForNewConnection) {
this.instance.setSuspendDrawing(false, true);
this.instance?.setSuspendDrawing(false, true);
waitForNewConnection = false;
}
}, 100); // just to make it clear to users that this is a new connection
@@ -2973,14 +2998,10 @@ export default mixins(
setTimeout(() => {
// Suspend drawing
this.instance.setSuspendDrawing(true);
// Remove all endpoints and the connections in jsplumb
this.instance.removeAllEndpoints(node.id);
// Remove the draggable
// @ts-ignore
this.instance.destroyDraggable(node.id);
this.instance?.setSuspendDrawing(true);
(this.instance?.endpointsByElement[node.id] || [])
.flat()
.forEach((endpoint) => this.instance?.deleteEndpoint(endpoint));
// Remove the connections in data
this.workflowsStore.removeAllNodeConnection(node);
@@ -2989,13 +3010,13 @@ export default mixins(
if (!waitForNewConnection) {
// Now it can draw again
this.instance.setSuspendDrawing(false, true);
this.instance?.setSuspendDrawing(false, true);
}
// Remove node from selected index if found in it
this.uiStore.removeNodeFromSelection(node);
if (trackHistory) {
this.historyStore.pushCommandToUndo(new RemoveNodeCommand(node, this));
this.historyStore.pushCommandToUndo(new RemoveNodeCommand(node));
}
}, 0); // allow other events to finish like drag stop
if (trackHistory && trackBulk) {
@@ -3069,7 +3090,7 @@ export default mixins(
workflow.renameNode(currentName, newName);
if (trackHistory) {
this.historyStore.pushCommandToUndo(new RenameNodeCommand(currentName, newName, this));
this.historyStore.pushCommandToUndo(new RenameNodeCommand(currentName, newName));
}
// Update also last selected node and execution data
@@ -3104,18 +3125,12 @@ export default mixins(
deleteEveryEndpoint() {
// Check as it does not exist on first load
if (this.instance) {
const nodes = this.workflowsStore.allNodes;
nodes.forEach((node: INodeUi) => {
try {
// important to prevent memory leak
// @ts-ignore
this.instance.destroyDraggable(node.id);
} catch (e) {
console.error(e);
}
});
this.instance?.reset();
Object.values(this.instance?.endpointsByElement)
.flatMap((endpoint) => endpoint)
.forEach((endpoint) => endpoint.destroy());
this.instance.deleteEveryEndpoint();
this.instance.deleteEveryConnection({ fireEvent: true });
}
},
matchCredentials(node: INodeUi) {
@@ -3241,7 +3256,7 @@ export default mixins(
this.workflowsStore.addNode(node);
if (trackHistory) {
this.historyStore.pushCommandToUndo(new AddNodeCommand(node, this));
this.historyStore.pushCommandToUndo(new AddNodeCommand(node));
}
});
@@ -3249,7 +3264,7 @@ export default mixins(
await Vue.nextTick();
// Suspend drawing
this.instance.setSuspendDrawing(true);
this.instance?.setSuspendDrawing(true);
// Load the connections
if (connections !== undefined) {
@@ -3285,9 +3300,8 @@ export default mixins(
}
}
}
// Now it can draw again
this.instance.setSuspendDrawing(false, true);
this.instance?.setSuspendDrawing(false, true);
},
async addNodesToWorkflow(data: IWorkflowDataUpdate): Promise<IWorkflowDataUpdate> {
// Because nodes with the same name maybe already exist, it could
@@ -3412,6 +3426,7 @@ export default mixins(
tempWorkflow.connectionsBySourceNode,
true,
);
this.historyStore.stopRecordingUndo();
this.uiStore.stateIsDirty = true;
@@ -3458,7 +3473,6 @@ export default mixins(
}
// Keep only the connection to node which get also exported
// @ts-ignore
typeConnections = {};
for (type of Object.keys(connections)) {
for (sourceIndex = 0; sourceIndex < connections[type].length; sourceIndex++) {
@@ -3508,14 +3522,12 @@ export default mixins(
// Ignore all errors
});
}
this.workflowsStore.removeAllConnections({ setStateDirty: false });
this.workflowsStore.removeAllNodes({ setStateDirty: false, removePinData: true });
// Reset workflow execution data
this.workflowsStore.setWorkflowExecutionData(null);
this.workflowsStore.resetAllNodesIssues();
// vm.$forceUpdate();
this.workflowsStore.setActive(false);
this.workflowsStore.setWorkflowId(PLACEHOLDER_EMPTY_WORKFLOW_ID);
@@ -3622,12 +3634,12 @@ export default mixins(
} catch (e) {}
},
async onImportWorkflowDataEvent(data: IDataObject) {
await this.importWorkflowData(data.data as IWorkflowDataUpdate, undefined, 'file');
await this.importWorkflowData(data.data as IWorkflowDataUpdate, 'file');
},
async onImportWorkflowUrlEvent(data: IDataObject) {
const workflowData = await this.getWorkflowDataFromUrl(data.url as string);
if (workflowData !== undefined) {
await this.importWorkflowData(workflowData, undefined, 'url');
await this.importWorkflowData(workflowData, 'url');
}
},
addPinDataConnections(pinData: IPinData) {
@@ -3638,7 +3650,7 @@ export default mixins(
}
// @ts-ignore
const connections = this.instance.getConnections({
const connections = this.instance?.getConnections({
source: node.id,
}) as Connection[];
@@ -3658,11 +3670,13 @@ export default mixins(
}
// @ts-ignore
const connections = this.instance.getConnections({
const connections = this.instance?.getConnections({
source: node.id,
}) as Connection[];
this.instance.setSuspendDrawing(true);
connections.forEach(NodeViewUtils.resetConnection);
this.instance.setSuspendDrawing(false, true);
});
},
onToggleNodeCreator({
@@ -3738,10 +3752,10 @@ export default mixins(
},
onMoveNode({ nodeName, position }: { nodeName: string; position: XYPosition }): void {
this.workflowsStore.updateNodeProperties({ name: nodeName, properties: { position } });
const node = this.workflowsStore.getNodeByName(nodeName);
setTimeout(() => {
const node = this.workflowsStore.getNodeByName(nodeName);
if (node) {
this.instance.repaintEverything();
this.instance?.repaintEverything();
this.onNodeMoved(node);
}
}, 0);
@@ -3780,12 +3794,12 @@ export default mixins(
},
},
async mounted() {
this.resetWorkspace();
this.canvasStore.initInstance(this.$refs.nodeView as HTMLElement);
this.$titleReset();
window.addEventListener('message', this.onPostMessageReceived);
this.startLoading();
this.resetWorkspace();
const loadPromises = [
this.loadActiveWorkflows(),
this.loadCredentials(),
@@ -3806,8 +3820,7 @@ export default mixins(
);
return;
}
this.instance.ready(async () => {
ready(async () => {
try {
try {
this.initNodeView();
@@ -3879,6 +3892,7 @@ export default mixins(
}
this.uiStore.addFirstStepOnLoad = false;
this.initNodeView();
document.addEventListener('keydown', this.keyDown);
document.addEventListener('keyup', this.keyUp);
window.addEventListener('message', this.onPostMessageReceived);
@@ -3886,13 +3900,13 @@ export default mixins(
this.$root.$on('newWorkflow', this.newWorkflow);
this.$root.$on('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$on('importWorkflowUrl', this.onImportWorkflowUrlEvent);
this.$root.$on('nodeMove', this.onMoveNode);
this.$root.$on('revertAddNode', this.onRevertAddNode);
this.$root.$on('revertRemoveNode', this.onRevertRemoveNode);
this.$root.$on('revertAddConnection', this.onRevertAddConnection);
this.$root.$on('revertRemoveConnection', this.onRevertRemoveConnection);
this.$root.$on('revertRenameNode', this.onRevertNameChange);
this.$root.$on('enableNodeToggle', this.onRevertEnableToggle);
historyBus.$on('nodeMove', this.onMoveNode);
historyBus.$on('revertAddNode', this.onRevertAddNode);
historyBus.$on('revertRemoveNode', this.onRevertRemoveNode);
historyBus.$on('revertAddConnection', this.onRevertAddConnection);
historyBus.$on('revertRemoveConnection', this.onRevertRemoveConnection);
historyBus.$on('revertRenameNode', this.onRevertNameChange);
historyBus.$on('enableNodeToggle', this.onRevertEnableToggle);
dataPinningEventBus.$on('pin-data', this.addPinDataConnections);
dataPinningEventBus.$on('unpin-data', this.removePinDataConnections);
@@ -3908,20 +3922,23 @@ export default mixins(
this.$root.$off('newWorkflow', this.newWorkflow);
this.$root.$off('importWorkflowData', this.onImportWorkflowDataEvent);
this.$root.$off('importWorkflowUrl', this.onImportWorkflowUrlEvent);
this.$root.$off('nodeMove', this.onMoveNode);
this.$root.$off('revertAddNode', this.onRevertAddNode);
this.$root.$off('revertRemoveNode', this.onRevertRemoveNode);
this.$root.$off('revertAddConnection', this.onRevertAddConnection);
this.$root.$off('revertRemoveConnection', this.onRevertRemoveConnection);
this.$root.$off('revertRenameNode', this.onRevertNameChange);
this.$root.$off('enableNodeToggle', this.onRevertEnableToggle);
historyBus.$off('nodeMove', this.onMoveNode);
historyBus.$off('revertAddNode', this.onRevertAddNode);
historyBus.$off('revertRemoveNode', this.onRevertRemoveNode);
historyBus.$off('revertAddConnection', this.onRevertAddConnection);
historyBus.$off('revertRemoveConnection', this.onRevertRemoveConnection);
historyBus.$off('revertRenameNode', this.onRevertNameChange);
historyBus.$off('enableNodeToggle', this.onRevertEnableToggle);
dataPinningEventBus.$off('pin-data', this.addPinDataConnections);
dataPinningEventBus.$off('unpin-data', this.removePinDataConnections);
nodeViewEventBus.$off('saveWorkflow', this.saveCurrentWorkflowExternal);
this.instance.unbind();
},
destroyed() {
this.resetWorkspace();
this.instance.unbind();
this.instance.destroy();
this.uiStore.stateIsDirty = false;
window.removeEventListener('message', this.onPostMessageReceived);
this.$root.$off('newWorkflow', this.newWorkflow);
@@ -4016,40 +4033,6 @@ export default mixins(
</style>
<style lang="scss">
.connection-run-items-label {
span {
border-radius: 7px;
background-color: hsla(
var(--color-canvas-background-h),
var(--color-canvas-background-s),
var(--color-canvas-background-l),
0.85
);
line-height: 1.3em;
padding: 0px 3px;
white-space: nowrap;
font-size: var(--font-size-s);
font-weight: var(--font-weight-regular);
color: var(--color-success);
}
.floating {
position: absolute;
top: -22px;
transform: translateX(-50%);
}
}
.connection-input-name-label {
position: relative;
span {
position: absolute;
top: -10px;
left: -60px;
}
}
.drop-add-node-label {
color: var(--color-text-dark);
font-weight: 600;
@@ -4058,30 +4041,12 @@ export default mixins(
background-color: #ffffff55;
}
.node-input-endpoint-label,
.node-output-endpoint-label {
background-color: hsla(
var(--color-canvas-background-h),
var(--color-canvas-background-s),
var(--color-canvas-background-l),
0.85
);
border-radius: 7px;
font-size: 0.7em;
padding: 2px;
white-space: nowrap;
}
.node-input-endpoint-label {
text-align: right;
}
.connection-actions {
&:hover {
display: block !important;
}
> div {
> button {
color: var(--color-foreground-xdark);
border: 2px solid var(--color-foreground-xdark);
background-color: var(--color-background-xlight);