From 3f996913d28d28f0e24d068fce3f99f722f3103f Mon Sep 17 00:00:00 2001 From: Oleg Ivaniv Date: Mon, 21 Nov 2022 09:59:06 +0100 Subject: [PATCH 01/39] WIP: Nodeview --- packages/editor-ui/package.json | 5 + packages/editor-ui/src/components/Node.vue | 2 +- .../src/components/mixins/mouseSelect.ts | 6 +- .../src/components/mixins/nodeBase.ts | 35 ++-- packages/editor-ui/src/stores/canvas.ts | 5 + packages/editor-ui/src/views/NodeView.vue | 157 +++++++++++++----- packages/editor-ui/vite.config.ts | 9 + pnpm-lock.yaml | 46 ++++- 8 files changed, 199 insertions(+), 66 deletions(-) diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 62ee2ddc34a51..1782d121bf69f 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -38,6 +38,11 @@ "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^5.15.3", "@fortawesome/vue-fontawesome": "^2.0.2", + "@jsplumb/browser-ui": "^5.12.6", + "@jsplumb/common": "^5.12.6", + "@jsplumb/connector-bezier": "^5.12.6", + "@jsplumb/core": "^5.12.6", + "@jsplumb/util": "^5.12.6", "axios": "^0.21.1", "dateformat": "^3.0.3", "esprima-next": "5.8.4", diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index 652b9124707ac..eceb9e1c61ccd 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -1,5 +1,5 @@ diff --git a/packages/editor-ui/src/mixins/nodeBase.ts b/packages/editor-ui/src/mixins/nodeBase.ts index 76116ce9778f8..0c71a938009b3 100644 --- a/packages/editor-ui/src/mixins/nodeBase.ts +++ b/packages/editor-ui/src/mixins/nodeBase.ts @@ -213,25 +213,21 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({ endpoint: { type: 'N8nPlus', options: { - radius: 12, + dimensions: 24, connectedEndpoint: endpoint, + showOutputLabel: nodeTypeData.outputs.length === 1, + size: nodeTypeData.outputs.length >= 3 ? 'small' : 'medium', + hoverMessage: this.$locale.baseText('nodeBase.clickToAddNodeOrDragToConnect'), }, }, source: true, target: false, enabled: !this.isReadOnly, paintStyle: { - // fill: getStyleTokenValue('--color-xdark'), outlineStroke: 'none', - // hover: false, - showOutputLabel: nodeTypeData.outputs.length === 1, - size: nodeTypeData.outputs.length >= 3 ? 'small' : 'medium', - // hoverMessage: this.$locale.baseText('nodeBase.clickToAddNodeOrDragToConnect'), }, hoverPaintStyle: { - // fill: getStyleTokenValue('--color-primary'), outlineStroke: 'none', - // hover: true, // hack to distinguish hover state }, parameters: { nodeId: this.nodeId, @@ -240,7 +236,6 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({ }, cssClass: 'plus-draggable-endpoint', dragAllowedWhenFull: false, - dragProxy: ['Rectangle', {width: 1, height: 1, strokeWidth: 0}], }; const plusEndpoint = this.instance.addEndpoint(this.$refs[this.data.name] as Element, plusEndpointData); @@ -350,16 +345,12 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({ }); }, __addNode(node: INodeUi) { - console.log('Add node'); const nodeTypeData = (this.nodeTypesStore.getNodeType(node.type, node.typeVersion) ?? this.nodeTypesStore.getNodeType(NO_OP_NODE_TYPE)) as INodeTypeDescription; - console.log('before __addInputEndpoints'); this.__addInputEndpoints(node, nodeTypeData); - console.log('after __addInputEndpoints'); this.__addOutputEndpoints(node, nodeTypeData); - console.log('Before making instance draggable'); - this.__makeInstanceDraggable(node); + // this.__makeInstanceDraggable(node); }, touchEnd(e: MouseEvent) { if (this.isTouchDevice) { diff --git a/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointRenderer.ts b/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointRenderer.ts index de023b2169650..c3d6e4cbe35a8 100644 --- a/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointRenderer.ts +++ b/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointRenderer.ts @@ -1,51 +1,45 @@ -import { registerEndpointRenderer, svg, createElement } from '@jsplumb/browser-ui'; +import { registerEndpointRenderer, svg } from '@jsplumb/browser-ui'; import { N8nPlusEndpoint } from './N8nPlusEndpointType'; -import { PaintStyle } from '@jsplumb/common'; -import { isEmpty } from '@jsplumb/util'; -const CIRCLE = 'circle'; -const stalkLength = 40; export const register = () => { registerEndpointRenderer(N8nPlusEndpoint.type, { - makeNode: (ep: N8nPlusEndpoint, style: PaintStyle) => { - const group = svg.node('g', { - "width": 24, - "height": 24, - }); - + makeNode: (ep: N8nPlusEndpoint) => { + const group = svg.node('g'); const containerBorder = svg.node('rect', { rx: 3, - stroke: '#000000', 'stroke-width': 2, fillOpacity: 0, - height: 22, - width: 22, + height: ep.params.dimensions - 2, + width: ep.params.dimensions - 2, y: 1, x: 1, }); const plusPath = svg.node('path', { d: "m16.40655,10.89837l-3.30491,0l0,-3.30491c0,-0.40555 -0.32889,-0.73443 -0.73443,-0.73443l-0.73443,0c-0.40554,0 -0.73442,0.32888 -0.73442,0.73443l0,3.30491l-3.30491,0c-0.40555,0 -0.73443,0.32888 -0.73443,0.73442l0,0.73443c0,0.40554 0.32888,0.73443 0.73443,0.73443l3.30491,0l0,3.30491c0,0.40554 0.32888,0.73442 0.73442,0.73442l0.73443,0c0.40554,0 0.73443,-0.32888 0.73443,-0.73442l0,-3.30491l3.30491,0c0.40554,0 0.73442,-0.32889 0.73442,-0.73443l0,-0.73443c0,-0.40554 -0.32888,-0.73442 -0.73442,-0.73442z", }); - if (ep.size !== 'medium') { - ep.addClass(ep.size); + if (ep.params.size !== 'medium') { + ep.addClass(ep.params.size); } group.appendChild(containerBorder); group.appendChild(plusPath); - ep.setStalkOverlay(); + ep.setupOverlays(); ep.setVisible(false); - console.log('__DEBUG: Make node'); return group; }, - updateNode: (ep: N8nPlusEndpoint, node) => { + updateNode: (ep: N8nPlusEndpoint) => { + console.log('__DEBUG: Update node plus', ep); const hasConnections = () => { - const connections = [...ep.endpoint.connections, ...ep.connectedEndpoint.connections]; + const connections = [...ep.endpoint.connections, ...ep.params.connectedEndpoint.connections]; return connections.length > 0; }; + ep.instance.setSuspendDrawing(true); + ep.setVisible(!ep.instance.isConnectionBeingDragged && !hasConnections()); setTimeout(() => ep.setIsVisible(!hasConnections()), 0); + ep.instance.setSuspendDrawing(false); }, }); diff --git a/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointType.ts b/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointType.ts index 21863c2a889ab..df984624d9e9e 100644 --- a/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointType.ts +++ b/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointType.ts @@ -1,111 +1,115 @@ -import { EndpointHandler, Endpoint, EndpointRepresentation, Orientation, Overlay } from '@jsplumb/core'; -import { AnchorPlacement, DotEndpointParams, EndpointRepresentationParams } from '@jsplumb/common'; -import { createElement } from '@jsplumb/browser-ui'; +import { EndpointHandler, Endpoint, EndpointRepresentation, Overlay } from '@jsplumb/core'; +import { AnchorPlacement, EndpointRepresentationParams } from '@jsplumb/common'; +import { createElement, EVENT_ENDPOINT_MOUSEOVER, EVENT_ENDPOINT_MOUSEOUT, EVENT_ENDPOINT_CLICK } from '@jsplumb/browser-ui'; export type ComputedN8nPlusEndpoint = [number, number, number, number, number]; -const boxSize = { - medium: 24, - small: 18, -}; -const stalkLength = 40; interface N8nPlusEndpointParams extends EndpointRepresentationParams { - radius?: number; + dimensions: number; connectedEndpoint: Endpoint; + hoverMessage: string; + showOutputLabel: boolean; + size: 'small' | 'medium'; } +export const PlusStalkOverlay = 'plus-stalk'; +export const HoverMessageOverlay = 'hover-message'; + export class N8nPlusEndpoint extends EndpointRepresentation { - radius: number; - defaultOffset: number; - defaultInnerRadius: number; + params: N8nPlusEndpointParams; label: string; labelOffset: number; - size: string; - hoverMessage: string; - showOutputLabel: boolean; - connectedEndpoint: Endpoint; stalkOverlay: Overlay | null; + messageOverlay: Overlay | null; constructor(endpoint: Endpoint, params: N8nPlusEndpointParams) { super(endpoint, params); - params = params || {}; - this.radius = params.radius || 9; - this.connectedEndpoint = params.connectedEndpoint; - console.log("๐Ÿš€ ~ file: N8nPlusEndpointType.ts:33 ~ N8nPlusEndpoint ~ constructor ~ params.connectedEndpoint", params.connectedEndpoint); - this.defaultOffset = 0.5 * this.radius; - this.defaultInnerRadius = this.radius / 3; + this.params = params; this.label = ''; this.labelOffset = 0; - this.size = 'medium'; - this.hoverMessage = ''; - this.showOutputLabel = true; this.stalkOverlay = null; + this.messageOverlay = null; - console.log('__DEBUG: Constructor'); + this.unbindEvents(); + this.bindEvents(); } static type = 'N8nPlus'; type = N8nPlusEndpoint.type; - - setStalkOverlay = () => { + setupOverlays = () => { this.clearOverlays(); + this.endpoint.instance.setSuspendDrawing(true); this.stalkOverlay = this.endpoint.addOverlay({ type: 'Custom', options: { - id: 'plus-stalk', - create: (c: Endpoint) => { - const stalk = createElement('div', {}, 'plus-stalk'); - console.log('__DEBUG: Create plus stalk'); + id: PlusStalkOverlay, + create: () => { + const stalk = createElement('i', {}, PlusStalkOverlay); return stalk; }, }, }); - // this.stalkOverlay.setVisible(true); - console.log("__DEBUG: Stalk overlay", this.stalkOverlay); - // setTimeout(() => { - // this.instance.revalidate(this.stalkOverlay?.canvas); - // console.log("__DEBUG: Stalk revalidate", this.stalkOverlay.canvas); - // }, 0); + this.messageOverlay = this.endpoint.addOverlay({ + type: 'Custom', + options: { + id: HoverMessageOverlay, + create: () => { + const hoverMessage = createElement('p', {}, HoverMessageOverlay); + hoverMessage.innerHTML = this.params.hoverMessage; + return hoverMessage; + }, + }, + }); + this.endpoint.instance.setSuspendDrawing(false); + }; + bindEvents = () => { + this.instance.bind(EVENT_ENDPOINT_MOUSEOVER, this.setHoverMessageVisible); + this.instance.bind(EVENT_ENDPOINT_MOUSEOUT, this.unsetHoverMessageVisible); + this.instance.bind(EVENT_ENDPOINT_CLICK, this.fireClickEvent); + }; + unbindEvents = () => { + this.instance.unbind(EVENT_ENDPOINT_MOUSEOVER, this.setHoverMessageVisible); + this.instance.unbind(EVENT_ENDPOINT_MOUSEOUT, this.unsetHoverMessageVisible); + this.instance.unbind(EVENT_ENDPOINT_CLICK, this.fireClickEvent); + }; + fireClickEvent = (endpoint: Endpoint) => { + if (endpoint === this.endpoint) { + this.instance.fire('plusEndpointClick', this.endpoint); + } + }; + setHoverMessageVisible = (endpoint: Endpoint) => { + if (endpoint === this.endpoint && this.messageOverlay) { + this.instance.addOverlayClass(this.messageOverlay, 'visible'); + } + }; + unsetHoverMessageVisible = (endpoint: Endpoint) => { + if (endpoint === this.endpoint && this.messageOverlay) { + this.instance.removeOverlayClass(this.messageOverlay, 'visible'); + } }; - clearOverlays = () => { Object.keys(this.endpoint.getOverlays()).forEach((key) => { - console.log("__DEBUG: Removing overlay: ", key); this.endpoint.removeOverlay(key); }); - if(this.stalkOverlay) { - this.stalkOverlay = null; - } - // // this.endpoint.removeOverlay(this.stalkOverlay?.id); - - - // // this.endpoint.instance._removeElement(this.stalkOverlay.component.); - - // setTimeout(() => { - // console.log("๐Ÿš€ ~ file: N8nPlusEndpointType.ts:64 ~ N8nPlusEndpoint ~ this.endpoint.overlays[this.stalkOverlay?.id].canvas", this.endpoint.overlays['plus-stalk']); - // this.endpoint.instance._removeElement(this.endpoint.overlays['plus-stalk'].canvas); - // }, 0); + this.stalkOverlay = null; + this.messageOverlay = null; }; setOverlaysVisible = (visible: boolean) => { Object.keys(this.endpoint.getOverlays()).forEach((overlay) => { this.endpoint.getOverlays()[overlay].setVisible(visible); - console.log("__DEBUG Set is visible setting overlay", overlay); }); }; setIsVisible = (visible: boolean) => { Object.keys(this.endpoint.getOverlays()).forEach((overlay) => { this.endpoint.getOverlays()[overlay].setVisible(visible); - console.log("__DEBUG Set is visible setting overlay", overlay); }); + this.setVisible(visible); - // this.stalkOverlay?.component[visible? 'addClass' : 'removeClass']('visible'); - console.log("__DEBUG Set is visible", visible); - // console.log('__DEBUG Stalk is visible: ', this.stalkOverlay?.isVisible()); }; + // setSuccessOutput = (label: string) => { - // console.log('Set success output'); // if (!this.plusElement) return; // this.plusElement.classList.add('success'); @@ -125,7 +129,6 @@ export class N8nPlusEndpoint extends EndpointRepresentation { - // ep.clearOverlays(); - // ep.setStalkOverlay(); - console.log('๐Ÿš€ ~ file: N8nPlusEndpointType.ts:93 ~ ep', ep); - console.log('Compute, N8nPlusEndpointHandler', anchorPoint); - let x = anchorPoint.curX - ep.radius, - y = anchorPoint.curY - ep.radius, - w = ep.radius * 2, - h = ep.radius * 2; - - if (endpointStyle && endpointStyle.stroke) { - const lw = endpointStyle.strokeWidth || 1; - x -= lw; - y -= lw; - w += lw * 2; - h += lw * 2; - } + const x = anchorPoint.curX - (ep.params.dimensions / 2); + const y = anchorPoint.curY - (ep.params.dimensions / 2); + const w = ep.params.dimensions; + const h = ep.params.dimensions; + ep.x = x; ep.y = y; ep.w = w; ep.h = h; - ep.size = endpointStyle.size || ep.size; - ep.showOutputLabel = !!endpointStyle.showOutputLabel; - - // if (!ep.plusElement) [x, y, w, h, ep.radius]; - - if (ep.hoverMessage !== endpointStyle.hoverMessage) { - // const container = this.plusElement.querySelector('.plus-container') as HTMLElement; - // const message = container.querySelector('.drop-hover-message'); - ep.hoverMessage = endpointStyle.hoverMessage; - - // message.innerHTML = endpointStyle.hoverMessage; - // console.log( - // "๐Ÿš€ ~ file: N8nPlusEndpointType.ts:126 ~ ep.plusElement.querySelector('.drop-hover-message'", - // ep, - // ); - // ep.plusElement.querySelector('.drop-hover-message').innerHTML = endpointStyle.hoverMessage; - } - // console.log('Compute 2'); - // if (ep.plusElement && endpointStyle.size !== 'medium') { - // ep.plusElement.classList.add(ep.size); - // } - // console.log('Compute 3'); - // setTimeout(() => { - // console.log('Recalc offset', ep); - // if (ep.label && !ep.labelOffset) { - // // if label is hidden, offset is 0 so recalculate - // ep.setSuccessOutput(ep.label); - // } - // }, 0); - - // const defaultPosition = [anchorPoint[0] + stalkLength + this.labelOffset, anchorPoint[1] - boxSize[ep.size] / 2, boxSize[ep.size], boxSize[ep.size]]; - - // if (isDragging()) { - // return defaultPosition; - // } - - // if (hasEndpointConnections()) { - // return [0, 0, 0, 0]; // remove hoverable box from view - // } - - // return defaultPosition; - // ep.setStalkOverlay(); - console.log('__DEBUG: Compute'); ep.addClass('plus-endpoint'); - return [x, y, w, h, ep.radius]; + return [x, y, w, h, ep.params.dimensions]; }, - getParams: (ep: N8nPlusEndpoint): Record => { - console.log('__DEBUG: Get Params'); - return { radius: ep.radius, connectedEndpoint: ep.connectedEndpoint }; + getParams: (ep: N8nPlusEndpoint): N8nPlusEndpointParams => { + return ep.params; }, }; diff --git a/packages/editor-ui/src/stores/canvas.ts b/packages/editor-ui/src/stores/canvas.ts index 5a74c3561b8a2..2418266551df6 100644 --- a/packages/editor-ui/src/stores/canvas.ts +++ b/packages/editor-ui/src/stores/canvas.ts @@ -138,6 +138,9 @@ export const useCanvasStore = defineStore('canvas', () => { }; function initInstance(container: Element) { + if(newInstance.value) { + console.log('__DEBUG: newInstance.value already exists', newInstance.value); + }; newInstance.value = newJsPlumbInstance({ container, connector: CONNECTOR_FLOWCHART_TYPE, diff --git a/packages/editor-ui/src/utils/nodeViewUtils.ts b/packages/editor-ui/src/utils/nodeViewUtils.ts index 17df5b0a2367a..98f1a89b2a745 100644 --- a/packages/editor-ui/src/utils/nodeViewUtils.ts +++ b/packages/editor-ui/src/utils/nodeViewUtils.ts @@ -213,9 +213,9 @@ export const getInputNameOverlay = (labelText: string): OverlaySpec => ({ visible: true, create: (component: Endpoint) => { console.log("๐Ÿš€ ~ file: nodeViewUtils.ts:215 ~ getInputNameOverlay ~ component", component); - component.bind('anchor:changed', (params: any) => { - console.log("๐Ÿš€ ~ file: nodeViewUtils.ts:217 ~ component.bind ~ params", params); - }); + // component.bind('anchor:changed', (params: any) => { + // console.log("๐Ÿš€ ~ file: nodeViewUtils.ts:217 ~ component.bind ~ params", params); + // }); const label = document.createElement('div'); label.innerHTML = labelText; label.classList.add('node-input-endpoint-label'); @@ -310,7 +310,16 @@ export const getOverlay = (item: Connection | Endpoint, overlayId: string) => { export const showOverlay = (item: Connection | Endpoint, overlayId: string) => { const overlay = getOverlay(item, overlayId); if (overlay) { + // const overlayElement = overlay.canvas; + // console.log("__DEBUG: Overlay canvas", overlayElement); + // item.instance.repaint(overlayElement); + item.instance.setSuspendDrawing(true); overlay.setVisible(true); + overlay.setVisible(false); + setTimeout(() => { + overlay.setVisible(true); + item.instance.setSuspendDrawing(false); + }, 500); } }; @@ -745,15 +754,18 @@ export const addConnectionActionsOverlay = ( 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.instance.setSuspendDrawing(true); + connection.instance.removeOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID); + + // if (getOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID)) { + // return; // avoid free floating actions when moving connection from one node to another + // } const overlay = connection.addOverlay({ type: 'Custom', options: { id: OVERLAY_CONNECTION_ACTIONS_ID, - location: [2, 19], - create: (component: any) => { + // location: [2, 19], + create: (component: Connection) => { const div = document.createElement('div'); const addButton = document.createElement('button'); const deleteButton = document.createElement('button'); @@ -775,69 +787,9 @@ export const addConnectionActionsOverlay = ( }, }); overlay.setVisible(false); + connection.instance.setSuspendDrawing(false); }; -export const addOutputEdnpointOverlay = (endpoint: Endpoint) => { - const overlay = { - type: 'Custom', - options: { - id: OVERLAY_CONNECTION_ACTIONS_ID, - create: (component: any) => { - const div = document.createElement('div'); - div.classList.add('plus-endpoint', 'dot-output-endpoint', 'jtk-endpoint'); - div.innerHTML = ` -
-
- -
-
- -
- -
- Click to add node
- or drag to connect -
-
- `; - - // div.addEventListener('mousedown', (e) => { - // e.stopPropagation(); - // const eventDelegate = document.querySelector('.katavorio-delegated-draggable'); - // const modE = { - // ...e, - // target: (endpoint as any).endpoint.canvas, - // }; - // const event = new MouseEvent('mousedown', modE); - // const ev = { - // ...event, - // target: (endpoint as any).endpoint.canvas, - // }; - // (endpoint as any).endpoint.canvas.addEventListener('mousedown', (event1) => { - // event1.stopPropagation(); - // setTimeout(() => { - - // eventDelegate.dispatchEvent(event1); - // }, 0); - - // }); - // (endpoint as any).endpoint.canvas.dispatchEvent(event); - // // console.log('Mouse down', endpoint); - // // endpoint.instance.trigger((endpoint as any).endpoint.canvas, EVENT_ENDPOINT_MOUSEDOWN, e, {}); - // // endpoint.instance.isConnectionBeingDragged = true; - // // endpoint.trigger(EVENT_CONNECTION_MOUSEDOWN, e); - // }); - return div; - }, - }, - }; - - endpoint.addOverlay(overlay); -}; - - export const getOutputEndpointUUID = (nodeId: string, outputIndex: number) => { return `${nodeId}${OUTPUT_UUID_KEY}${outputIndex}`; }; diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 370aaac482ac0..464669031d9d2 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -320,7 +320,6 @@ export default mixins( }, setup() { const { registerCustomAction, unregisterCustomAction } = useGlobalLinkActions(); - // Connectors.register(BezierConnector.type, BezierConnector); return { registerCustomAction, unregisterCustomAction, @@ -1239,7 +1238,6 @@ export default mixins( }, deleteSelectedNodes() { - console.log('Delete selected nodes'); // Copy "selectedNodes" as the nodes get deleted out of selection // when they get deleted and if we would use original it would mess // with the index and would so not delete all nodes @@ -1671,7 +1669,6 @@ export default mixins( }, nodeSelectedByName(nodeName: string, setActive = false, deselectAllOthers?: boolean) { - console.log("๐Ÿš€ ~ file: NodeView.vue:1674 ~ nodeSelectedByName ~ nodeName", nodeName); if (deselectAllOthers === true) { this.deselectAllNodes(); } @@ -2044,8 +2041,6 @@ export default mixins( paintStyle: NodeViewUtils.CONNECTOR_PAINT_STYLE_DEFAULT, hoverPaintStyle: NodeViewUtils.CONNECTOR_PAINT_STYLE_PRIMARY, connectionOverlays: NodeViewUtils.CONNECTOR_ARROW_OVERLAYS, - // container: this.$refs[`node-${node?.id}`], - // Container: '#node-view', }); const insertNodeAfterSelected = (info: { @@ -2097,7 +2092,6 @@ export default mixins( index: connection.parameters.index, eventSource: 'node_connection_drop', }); - console.log('__DEBUG: Connection abort'); } catch (e) { console.error(e); // eslint-disable-line no-console } @@ -2146,27 +2140,6 @@ export default mixins( } NodeViewUtils.resetConnection(info.connection); - - if (!this.isReadOnly) { - NodeViewUtils.addConnectionActionsOverlay(info.connection, - () => { - console.log('On delete button'); - activeConnection = null; - this.__deleteJSPlumbConnection(info.connection); - }, - () => { - setTimeout(() => { - console.log('On add button'); - insertNodeAfterSelected({ - sourceId: info.sourceEndpoint.parameters.nodeId, - index: sourceInfo.index, - connection: info.connection, - eventSource: 'node_connection_action', - }); - }, 150); - }); - } - NodeViewUtils.moveBackInputLabelPosition(info.targetEndpoint); const connectionData: [IConnection, IConnection] = [ @@ -2189,12 +2162,36 @@ export default mixins( if (!this.suspendRecordingDetachedConnections) { this.historyStore.pushCommandToUndo(new AddConnectionCommand(connectionData, this)); } + if (!this.isReadOnly) { + NodeViewUtils.addConnectionActionsOverlay(info.connection, + () => { + 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 } }); let exitTimer: NodeJS.Timeout | undefined; let enterTimer: NodeJS.Timeout | undefined; + this.newInstance.bind(jsPlumbBrowserUI.EVENT_DRAG_MOVE, (payload: jsPlumbBrowserUI.DragStopPayload) => { + this.newInstance?.connections + .flatMap((connection) => Object.values(connection.overlays)) + .forEach((overlay) => { + if(!overlay.canvas) return; + this.newInstance?.repaint(overlay.canvas); + }); + + }); this.newInstance.bind(EVENT_CONNECTION_MOUSEOVER, (connection: Connection) => { try { if (exitTimer !== undefined) { @@ -2209,8 +2206,8 @@ export default mixins( enterTimer = setTimeout(() => { enterTimer = undefined; if (connection) { - activeConnection = connection; NodeViewUtils.showConnectionActions(connection); + activeConnection = connection; } }, 150); } catch (e) { @@ -2219,7 +2216,6 @@ export default mixins( }); this.newInstance.bind(EVENT_CONNECTION_MOUSEOUT, (connection: Connection) => { - // console.log('Connection mouse out', connection); try { if (exitTimer) return; @@ -2272,11 +2268,16 @@ export default mixins( console.error(e); // eslint-disable-line no-console } }); - this.newInstance?.bind(EVENT_ENDPOINT_MOUSEOVER, async(endpoint: Endpoint) => { - endpoint.endpoint.addClass('hover'); + this.newInstance?.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.newInstance.setHover(endpoint, true); }); - this.newInstance?.bind(EVENT_ENDPOINT_MOUSEOUT, async(endpoint: Endpoint) => { - endpoint.endpoint.removeClass('hover'); + this.newInstance?.bind(EVENT_ENDPOINT_MOUSEOUT, (endpoint: Endpoint) => { + if (!endpoint.isTarget) return; + this.newInstance.setHover(endpoint, false); }); this.newInstance?.bind(EVENT_CONNECTION_DETACHED, async(info: ConnectionDetachedParams) => { try { @@ -2308,8 +2309,10 @@ export default mixins( console.error(e); // eslint-disable-line no-console } }); - this.newInstance?.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; @@ -2323,9 +2326,10 @@ export default mixins( return; } - const element = document.querySelector('.jtk-endpoint.hover'); + const element = document.querySelector('.jtk-endpoint.jtk-drag-hover'); if (element) { - NodeViewUtils.showDropConnectionState(connection, element.jtk.endpoint); + const endpoint = element.jtk.endpoint; + NodeViewUtils.showDropConnectionState(connection, endpoint); return; } @@ -2378,35 +2382,22 @@ export default mixins( } }); - // this.newInstance?.bind(EVENT_CONNECTION_ABORT, (params: any) => { - // console.log('__DEBUG: Drag Abort', params); - // this.newInstance?.repaint(plusEndpoint.element); - // }); - this.newInstance?.bind([EVENT_CONNECTION_DRAG, EVENT_CONNECTION_DRAG], (params: any) => { - const allEndpoints = Object.values(this.newInstance?.endpointsByElement) - .flat() + this.newInstance?.bind([EVENT_CONNECTION_DRAG, EVENT_CONNECTION_ABORT, EVENT_CONNECTION_DETACHED], (connection: Connection) => { + Object.values(this.newInstance?.endpointsByElement) + .flatMap((endpoints) => Object.values(endpoints)) .filter((endpoint) => endpoint.endpoint.type === 'N8nPlus') - .map((endpoint) => endpoint.endpoint.canvas) - .forEach((endpoint) => { - console.log("__DEBUG: Repainting endpoint", endpoint); - setTimeout(() => { - this.newInstance?.repaint(endpoint); - }, 0); + .forEach((endpoint) => setTimeout(() => endpoint.instance.revalidate(endpoint.element), 0)); + }); + + this.newInstance?.bind(('plusEndpointClick'), (endpoint: Endpoint) => { + if (endpoint && endpoint.__meta) { + insertNodeAfterSelected({ + sourceId: endpoint.__meta.nodeId, + index: endpoint.__meta.index, + eventSource: 'plus_endpoint', }); - // console.log("๐Ÿš€ ~ file: NodeView.vue:2385 ~ this.newInstance?.bind ~ allEndpoints", allEndpoints) - console.log('__DEBUG: Connection Drag Move', allEndpoints); - // this.newInstance?.repaint(plusEndpoint.element); + } }); - // @ts-ignore - // this.newInstance?.bind(('plusEndpointClick'), (endpoint: Endpoint) => { - // if (endpoint && endpoint.__meta) { - // insertNodeAfterSelected({ - // sourceId: endpoint.__meta.nodeId, - // index: endpoint.__meta.index, - // eventSource: 'plus_endpoint', - // }); - // } - // }); }, async newWorkflow(): Promise { this.startLoading(); @@ -2594,15 +2585,12 @@ export default mixins( if (!sourceNode || !targetNode) { return; } - - // @ts-ignore const connections = this.newInstance?.getConnections({ - source: sourceId, - target: targetId, + 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 ( @@ -2986,13 +2974,9 @@ export default mixins( setTimeout(() => { // Suspend drawing this.newInstance?.setSuspendDrawing(true); - - // Remove all connections - // this.newInstance?.unmanage((this.$refs[`node-${node?.id}`] as ComponentInstance[])[0].$el as Element, true); - - // Remove the draggable - // Todo is this still neede? - // this.newInstance?.destroyDraggable(node.id); + this.newInstance?.endpointsByElement[node.id] + .flat() + .forEach((endpoint) => this.newInstance?.deleteEndpoint(endpoint)); // Remove the connections in data this.workflowsStore.removeAllNodeConnection(node); @@ -3116,18 +3100,11 @@ export default mixins( deleteEveryEndpoint() { // Check as it does not exist on first load if (this.newInstance) { - const nodes = this.workflowsStore.allNodes; - nodes.forEach((node: INodeUi) => { - try { - // important to prevent memory leak - // @ts-ignore - this.newInstance?.destroyDraggable(node.id); - } catch (e) { - console.error(e); - } - }); + Object.values(this.newInstance?.endpointsByElement) + .flatMap((endpoint) => endpoint) + .forEach((endpoint) => endpoint.destroy()); - // this.newInstance?.deleteEveryEndpoint(); + this.newInstance.deleteEveryConnection({ fireEvent: true}); } }, matchCredentials(node: INodeUi) { @@ -3787,13 +3764,12 @@ export default mixins( }, }, async mounted() { - // Connectors.register(N8nConnector.type, N8nConnector); + 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(), @@ -3816,7 +3792,6 @@ export default mixins( return; } jsPlumbBrowserUI.ready(async () => { - console.log('Ready'); try { try { this.initNodeView(); From d3261b991821e5d3abdb091ccd5e18a8eade8a37 Mon Sep 17 00:00:00 2001 From: Oleg Ivaniv Date: Tue, 20 Dec 2022 18:37:21 +0100 Subject: [PATCH 12/39] Fix undo/redo and canvas add button position, cleanup --- packages/editor-ui/package.json | 1 - packages/editor-ui/src/Interface.ts | 118 --- packages/editor-ui/src/components/Node.vue | 1 - .../editor-ui/src/components/NodeSettings.vue | 2 +- packages/editor-ui/src/components/Sticky.vue | 4 - packages/editor-ui/src/mixins/nodeBase.ts | 111 +-- packages/editor-ui/src/mixins/nodeHelpers.ts | 2 +- packages/editor-ui/src/models/history.ts | 61 +- .../src/plugins/N8nCustomConnectorType.js | 896 ------------------ .../editor-ui/src/plugins/PlusEndpointType.js | 518 ---------- .../endpoints/N8nPlusEndpointRenderer.ts | 1 - .../plugins/endpoints/N8nPlusEndpointType.ts | 57 +- packages/editor-ui/src/stores/canvas.ts | 28 +- packages/editor-ui/src/stores/ui.ts | 1 - packages/editor-ui/src/utils/nodeViewUtils.ts | 99 +- .../editor-ui/src/views/CanvasAddButton.vue | 2 +- packages/editor-ui/src/views/NodeView.vue | 112 +-- packages/editor-ui/vite.config.ts | 2 +- pnpm-lock.yaml | 6 - 19 files changed, 165 insertions(+), 1857 deletions(-) delete mode 100644 packages/editor-ui/src/plugins/N8nCustomConnectorType.js delete mode 100644 packages/editor-ui/src/plugins/PlusEndpointType.js diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index 34e1e1e944bc9..1586b762da22c 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -53,7 +53,6 @@ "flatted": "^3.2.4", "jquery": "^3.4.1", "jsonpath": "^1.1.1", - "jsplumb": "2.15.4", "lodash-es": "^4.17.21", "lodash.camelcase": "^4.3.0", "lodash.debounce": "^4.0.8", diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index f2b67663cc885..313c44d67db84 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -1,16 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { IMenuItem } from 'n8n-design-system'; -import { - jsPlumbInstance, - DragOptions, - DropOptions, - ElementGroupRef, - Endpoint, - EndpointOptions, - EndpointRectangle, - EndpointRectangleOptions, - EndpointSpec, -} from 'jsplumb'; import { GenericValue, IConnections, @@ -42,99 +31,6 @@ import { FAKE_DOOR_FEATURES } from './constants'; import { BulkCommand, Undoable } from '@/models/history'; export * from 'n8n-design-system/src/types'; - -// declare module 'jsplumb' { -// interface PaintStyle { -// stroke?: string; -// fill?: string; -// strokeWidth?: number; -// outlineStroke?: string; -// outlineWidth?: number; -// } - -// // Extend jsPlumb Anchor interface -// interface Anchor { -// lastReturnValue: number[]; -// } - -// interface Connection { -// __meta?: { -// sourceNodeName: string, -// sourceOutputIndex: number, -// targetNodeName: string, -// targetOutputIndex: number, -// }; -// canvas?: HTMLElement; -// connector?: { -// setTargetEndpoint: (endpoint: Endpoint) => void; -// resetTargetEndpoint: () => void; -// bounds: { -// minX: number; -// maxX: number; -// minY: number; -// maxY: number; -// } -// }; - -// // bind(event: string, (connection: Connection): void;): void; // tslint:disable-line:no-any -// bind(event: string, callback: Function): void; -// removeOverlay(name: string): void; -// removeOverlays(): void; -// setParameter(name: string, value: any): void; // tslint:disable-line:no-any -// setPaintStyle(arg0: PaintStyle): void; -// addOverlay(arg0: any[]): void; // tslint:disable-line:no-any -// setConnector(arg0: any[]): void; // tslint:disable-line:no-any -// getUuids(): [string, string]; -// } - -// 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 { -// setVisible(visible: boolean): void; -// setLocation(location: number): void; -// canvas?: HTMLElement; -// } - -// interface OnConnectionBindInfo { -// originalSourceEndpoint: Endpoint; -// originalTargetEndpoint: Endpoint; -// getParameters(): { index: number }; -// } -// } - -// EndpointOptions from jsplumb seems incomplete and wrong so we define an own one -export type IEndpointOptions = Omit & { - endpointStyle: EndpointStyle; - endpointHoverStyle: EndpointStyle; - endpoint?: EndpointSpec | string; - dragAllowedWhenFull?: boolean; - dropOptions?: DropOptions & { - tolerance: string; - }; - dragProxy?: - | string - | string[] - | EndpointSpec - | [EndpointRectangle, EndpointRectangleOptions & { strokeWidth: number }]; -}; - export type EndpointStyle = { width?: number; height?: number; @@ -148,20 +44,6 @@ export type EndpointStyle = { hoverMessage?: string; }; -export type IDragOptions = DragOptions & { - grid: [number, number]; - filter: string; -}; - -export type IJsPlumbInstance = Omit & { - clearDragSelection: () => void; - addEndpoint( - el: ElementGroupRef, - params?: IEndpointOptions, - referenceParams?: IEndpointOptions, - ): Endpoint | Endpoint[]; - draggable(el: {}, options?: IDragOptions): IJsPlumbInstance; -}; export interface IUpdateInformation { name: string; diff --git a/packages/editor-ui/src/components/Node.vue b/packages/editor-ui/src/components/Node.vue index fa29a52c8a724..1bec60519e055 100644 --- a/packages/editor-ui/src/components/Node.vue +++ b/packages/editor-ui/src/components/Node.vue @@ -512,7 +512,6 @@ export default mixins( this.data.name, !this.data.disabled, this.data.disabled === true, - this, ), ); this.$telemetry.track('User clicked node hover button', { diff --git a/packages/editor-ui/src/components/NodeSettings.vue b/packages/editor-ui/src/components/NodeSettings.vue index b6a27890290d1..dea3d0959242e 100644 --- a/packages/editor-ui/src/components/NodeSettings.vue +++ b/packages/editor-ui/src/components/NodeSettings.vue @@ -522,7 +522,7 @@ export default mixins(externalHooks, nodeHelpers).extend({ }, nameChanged(name: string) { if (this.node) { - this.historyStore.pushCommandToUndo(new RenameNodeCommand(this.node.name, name, this)); + this.historyStore.pushCommandToUndo(new RenameNodeCommand(this.node.name, name)); } // @ts-ignore this.valueChanged({ diff --git a/packages/editor-ui/src/components/Sticky.vue b/packages/editor-ui/src/components/Sticky.vue index cddb9799d384d..f711c81f3022f 100644 --- a/packages/editor-ui/src/components/Sticky.vue +++ b/packages/editor-ui/src/components/Sticky.vue @@ -186,9 +186,6 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext if (!this.isSelected && this.node) { this.$emit('nodeSelected', this.node.name, false, true); } - if (this.node) { - this.instance.destroyDraggable(this.node.id); // todo avoid destroying if possible - } }, onResize({ height, width, dX, dY }: { width: number; height: number; dX: number; dY: number }) { if (!this.node) { @@ -202,7 +199,6 @@ export default mixins(externalHooks, nodeBase, nodeHelpers, workflowHelpers).ext }, onResizeEnd() { this.isResizing = false; - this.__makeInstanceDraggable(this.data); }, setParameters(params: { content?: string; height?: number; width?: number }) { if (this.node) { diff --git a/packages/editor-ui/src/mixins/nodeBase.ts b/packages/editor-ui/src/mixins/nodeBase.ts index 0c71a938009b3..f3bbec010d59e 100644 --- a/packages/editor-ui/src/mixins/nodeBase.ts +++ b/packages/editor-ui/src/mixins/nodeBase.ts @@ -1,24 +1,22 @@ import { PropType } from 'vue'; import mixins from 'vue-typed-mixins'; -import { INodeUi, XYPosition } from '@/Interface'; -// import { IJsPlumbInstance, IEndpointOptions, INodeUi, XYPosition } from '@/Interface'; +import { INodeUi } from '@/Interface'; import { deviceSupportHelpers } from '@/mixins/deviceSupportHelpers'; -import { NO_OP_NODE_TYPE, STICKY_NODE_TYPE } from '@/constants'; +import { NO_OP_NODE_TYPE } from '@/constants'; import { INodeTypeDescription } from 'n8n-workflow'; import { mapStores } from 'pinia'; import { useUIStore } from '@/stores/ui'; import { useWorkflowsStore } from '@/stores/workflows'; import { useNodeTypesStore } from '@/stores/nodeTypes'; -import { BrowserJsPlumbInstance, DragStartEventParams, EVENT_DRAG_START, EVENT_CONNECTION_ABORT, EVENT_CONNECTION_DRAG } from '@jsplumb/browser-ui'; -// import { SingleAnchorSpec, EndpointOptions } from '@jsplumb/common'; +import { BrowserJsPlumbInstance } from '@jsplumb/browser-ui'; import { EndpointOptions } from '@jsplumb/core'; import * as NodeViewUtils from '@/utils/nodeViewUtils'; -// import { getStyleTokenValue } from "@/utils"; import { useHistoryStore } from '@/stores/history'; import { MoveNodeCommand } from '@/models/history'; import { useCanvasStore } from '@/stores/canvas'; import { getStyleTokenValue } from '@/utils'; + export const nodeBase = mixins(deviceSupportHelpers).extend({ mounted() { // Initialize the node @@ -102,12 +100,7 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({ cssClass: 'rect-input-endpoint', dragAllowedWhenFull: true, hoverClass: 'dropHover', - // dropOptions: { - // tolerance: 'touch', - // hoverClass: 'dropHover', - // }, }; - console.log("๐Ÿš€ ~ file: nodeBase.ts:110 ~ nodeTypeData.inputs.forEach ~ !this.isReadOnly && nodeTypeData.inputs.length > 1", !this.isReadOnly && nodeTypeData.inputs.length > 1); const endpoint = this.instance?.addEndpoint( this.$refs[this.data.name] as Element, @@ -185,7 +178,6 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({ connectionsDirected: true, cssClass: 'dot-output-endpoint', dragAllowedWhenFull: false, - // dragProxy: ['Rectangle', {width: 1, height: 1, strokeWidth: 0}], }; @@ -249,100 +241,6 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({ } } }); - console.log('Added output endpoints'); - }, - __makeInstanceDraggable(node: INodeUi) { - console.log('Make instance draggable'); - // TODO: This caused problems with displaying old information - // https://github.com/jsplumb/katavorio/wiki - // https://jsplumb.github.io/jsplumb/home.html - // Make nodes draggable - // this.instance.importDefaults({ - // dragOptions: {} - // }) - this.instance.draggable(this.nodeId, { - grid: [NodeViewUtils.GRID_SIZE, NodeViewUtils.GRID_SIZE], - start: (params: { e: MouseEvent }) => { - if (this.isReadOnly === true) { - // Do not allow to move nodes in readOnly mode - return false; - } - // @ts-ignore - this.dragging = true; - - const isSelected = this.uiStore.isNodeSelected(this.data.name); - const nodeName = this.data.name; - if (this.data.type === STICKY_NODE_TYPE && !isSelected) { - setTimeout(() => { - this.$emit('nodeSelected', nodeName, false, true); - }, 0); - } - - if (params.e && !isSelected) { - // Only the node which gets dragged directly gets an event, for all others it is - // undefined. So check if the currently dragged node is selected and if not clear - // the drag-selection. - this.instance.clearDragSelection(); - this.uiStore.resetSelectedNodes(); - } - - this.uiStore.addActiveAction('dragActive'); - return true; - }, - stop: (params: { e: MouseEvent }) => { - // @ts-ignore - this.dragging = false; - if (this.uiStore.isActionActive('dragActive')) { - const moveNodes = this.uiStore.getSelectedNodes.slice(); - const selectedNodeNames = moveNodes.map((node: INodeUi) => node.name); - if (!selectedNodeNames.includes(this.data.name)) { - // If the current node is not in selected add it to the nodes which - // got moved manually - moveNodes.push(this.data); - } - - if (moveNodes.length > 1) { - this.historyStore.startRecordingUndo(); - } - // This does for some reason just get called once for the node that got clicked - // even though "start" and "drag" gets called for all. So lets do for now - // some dirty DOM query to get the new positions till I have more time to - // create a proper solution - let newNodePosition: XYPosition; - moveNodes.forEach((node: INodeUi) => { - const element = document.getElementById(node.id); - if (element === null) { - return; - } - - newNodePosition = [ - parseInt(element.style.left!.slice(0, -2), 10), - parseInt(element.style.top!.slice(0, -2), 10), - ]; - - const updateInformation = { - name: node.name, - properties: { - // @ts-ignore, draggable does not have definitions - position: newNodePosition, - }, - }; - const oldPosition = node.position; - if (oldPosition[0] !== newNodePosition[0] || oldPosition[1] !== newNodePosition[1]) { - this.historyStore.pushCommandToUndo( - new MoveNodeCommand(node.name, oldPosition, newNodePosition, this), - ); - this.workflowsStore.updateNodeProperties(updateInformation); - this.$emit('moved', node); - } - }); - if (moveNodes.length > 1) { - this.historyStore.stopRecordingUndo(); - } - } - }, - filter: '.node-description, .node-description .node-name, .node-description .node-subtitle', - }); }, __addNode(node: INodeUi) { const nodeTypeData = (this.nodeTypesStore.getNodeType(node.type, node.typeVersion) ?? @@ -350,7 +248,6 @@ export const nodeBase = mixins(deviceSupportHelpers).extend({ this.__addInputEndpoints(node, nodeTypeData); this.__addOutputEndpoints(node, nodeTypeData); - // this.__makeInstanceDraggable(node); }, touchEnd(e: MouseEvent) { if (this.isTouchDevice) { diff --git a/packages/editor-ui/src/mixins/nodeHelpers.ts b/packages/editor-ui/src/mixins/nodeHelpers.ts index ada50027be8a6..d59fba881f368 100644 --- a/packages/editor-ui/src/mixins/nodeHelpers.ts +++ b/packages/editor-ui/src/mixins/nodeHelpers.ts @@ -505,7 +505,7 @@ export const nodeHelpers = mixins(restApi).extend({ this.updateNodeCredentialIssues(node); if (trackHistory) { this.historyStore.pushCommandToUndo( - new EnableNodeToggleCommand(node.name, oldState === true, node.disabled === true, this), + new EnableNodeToggleCommand(node.name, oldState === true, node.disabled === true), ); } } diff --git a/packages/editor-ui/src/models/history.ts b/packages/editor-ui/src/models/history.ts index 2afaf117f9430..761391a312351 100644 --- a/packages/editor-ui/src/models/history.ts +++ b/packages/editor-ui/src/models/history.ts @@ -21,17 +21,16 @@ export enum COMMANDS { // this timeout in between canvas actions // (0 is usually enough but leaving this just in case) const CANVAS_ACTION_TIMEOUT = 10; +export const historyBus = new Vue(); export abstract class Undoable {} export abstract class Command extends Undoable { readonly name: string; - eventBus: Vue; - constructor(name: string, eventBus: Vue) { + constructor(name: string) { super(); this.name = name; - this.eventBus = eventBus; } abstract getReverseCommand(): Command; abstract isEqualTo(anotherCommand: Command): boolean; @@ -52,15 +51,15 @@ export class MoveNodeCommand extends Command { oldPosition: XYPosition; newPosition: XYPosition; - constructor(nodeName: string, oldPosition: XYPosition, newPosition: XYPosition, eventBus: Vue) { - super(COMMANDS.MOVE_NODE, eventBus); + constructor(nodeName: string, oldPosition: XYPosition, newPosition: XYPosition) { + super(COMMANDS.MOVE_NODE); this.nodeName = nodeName; this.newPosition = newPosition; this.oldPosition = oldPosition; } getReverseCommand(): Command { - return new MoveNodeCommand(this.nodeName, this.newPosition, this.oldPosition, this.eventBus); + return new MoveNodeCommand(this.nodeName, this.newPosition, this.oldPosition); } isEqualTo(anotherCommand: Command): boolean { @@ -76,7 +75,7 @@ export class MoveNodeCommand extends Command { async revert(): Promise { return new Promise((resolve) => { - this.eventBus.$root.$emit('nodeMove', { + historyBus.$emit('nodeMove', { nodeName: this.nodeName, position: this.oldPosition, }); @@ -88,13 +87,13 @@ export class MoveNodeCommand extends Command { export class AddNodeCommand extends Command { node: INodeUi; - constructor(node: INodeUi, eventBus: Vue) { - super(COMMANDS.ADD_NODE, eventBus); + constructor(node: INodeUi) { + super(COMMANDS.ADD_NODE); this.node = node; } getReverseCommand(): Command { - return new RemoveNodeCommand(this.node, this.eventBus); + return new RemoveNodeCommand(this.node); } isEqualTo(anotherCommand: Command): boolean { @@ -103,7 +102,7 @@ export class AddNodeCommand extends Command { async revert(): Promise { return new Promise((resolve) => { - this.eventBus.$root.$emit('revertAddNode', { node: this.node }); + historyBus.$emit('revertAddNode', { node: this.node }); resolve(); }); } @@ -112,13 +111,13 @@ export class AddNodeCommand extends Command { export class RemoveNodeCommand extends Command { node: INodeUi; - constructor(node: INodeUi, eventBus: Vue) { - super(COMMANDS.REMOVE_NODE, eventBus); + constructor(node: INodeUi) { + super(COMMANDS.REMOVE_NODE); this.node = node; } getReverseCommand(): Command { - return new AddNodeCommand(this.node, this.eventBus); + return new AddNodeCommand(this.node); } isEqualTo(anotherCommand: Command): boolean { @@ -127,7 +126,7 @@ export class RemoveNodeCommand extends Command { async revert(): Promise { return new Promise((resolve) => { - this.eventBus.$root.$emit('revertRemoveNode', { node: this.node }); + historyBus.$emit('revertRemoveNode', { node: this.node }); resolve(); }); } @@ -136,13 +135,13 @@ export class RemoveNodeCommand extends Command { export class AddConnectionCommand extends Command { connectionData: [IConnection, IConnection]; - constructor(connectionData: [IConnection, IConnection], eventBus: Vue) { - super(COMMANDS.ADD_CONNECTION, eventBus); + constructor(connectionData: [IConnection, IConnection]) { + super(COMMANDS.ADD_CONNECTION); this.connectionData = connectionData; } getReverseCommand(): Command { - return new RemoveConnectionCommand(this.connectionData, this.eventBus); + return new RemoveConnectionCommand(this.connectionData); } isEqualTo(anotherCommand: Command): boolean { @@ -157,7 +156,7 @@ export class AddConnectionCommand extends Command { async revert(): Promise { return new Promise((resolve) => { - this.eventBus.$root.$emit('revertAddConnection', { connection: this.connectionData }); + historyBus.$emit('revertAddConnection', { connection: this.connectionData }); resolve(); }); } @@ -166,13 +165,13 @@ export class AddConnectionCommand extends Command { export class RemoveConnectionCommand extends Command { connectionData: [IConnection, IConnection]; - constructor(connectionData: [IConnection, IConnection], eventBus: Vue) { - super(COMMANDS.REMOVE_CONNECTION, eventBus); + constructor(connectionData: [IConnection, IConnection]) { + super(COMMANDS.REMOVE_CONNECTION); this.connectionData = connectionData; } getReverseCommand(): Command { - return new AddConnectionCommand(this.connectionData, this.eventBus); + return new AddConnectionCommand(this.connectionData); } isEqualTo(anotherCommand: Command): boolean { @@ -188,7 +187,7 @@ export class RemoveConnectionCommand extends Command { async revert(): Promise { return new Promise((resolve) => { setTimeout(() => { - this.eventBus.$root.$emit('revertRemoveConnection', { connection: this.connectionData }); + historyBus.$emit('revertRemoveConnection', { connection: this.connectionData }); resolve(); }, CANVAS_ACTION_TIMEOUT); }); @@ -200,15 +199,15 @@ export class EnableNodeToggleCommand extends Command { oldState: boolean; newState: boolean; - constructor(nodeName: string, oldState: boolean, newState: boolean, eventBus: Vue) { - super(COMMANDS.ENABLE_NODE_TOGGLE, eventBus); + constructor(nodeName: string, oldState: boolean, newState: boolean) { + super(COMMANDS.ENABLE_NODE_TOGGLE); this.nodeName = nodeName; this.newState = newState; this.oldState = oldState; } getReverseCommand(): Command { - return new EnableNodeToggleCommand(this.nodeName, this.newState, this.oldState, this.eventBus); + return new EnableNodeToggleCommand(this.nodeName, this.newState, this.oldState); } isEqualTo(anotherCommand: Command): boolean { @@ -219,7 +218,7 @@ export class EnableNodeToggleCommand extends Command { async revert(): Promise { return new Promise((resolve) => { - this.eventBus.$root.$emit('enableNodeToggle', { + historyBus.$emit('enableNodeToggle', { nodeName: this.nodeName, isDisabled: this.oldState, }); @@ -232,14 +231,14 @@ export class RenameNodeCommand extends Command { currentName: string; newName: string; - constructor(currentName: string, newName: string, eventBus: Vue) { - super(COMMANDS.RENAME_NODE, eventBus); + constructor(currentName: string, newName: string) { + super(COMMANDS.RENAME_NODE); this.currentName = currentName; this.newName = newName; } getReverseCommand(): Command { - return new RenameNodeCommand(this.newName, this.currentName, this.eventBus); + return new RenameNodeCommand(this.newName, this.currentName); } isEqualTo(anotherCommand: Command): boolean { @@ -252,7 +251,7 @@ export class RenameNodeCommand extends Command { async revert(): Promise { return new Promise((resolve) => { - this.eventBus.$root.$emit('revertRenameNode', { + historyBus.$emit('revertRenameNode', { currentName: this.currentName, newName: this.newName, }); diff --git a/packages/editor-ui/src/plugins/N8nCustomConnectorType.js b/packages/editor-ui/src/plugins/N8nCustomConnectorType.js deleted file mode 100644 index 7736f42622256..0000000000000 --- a/packages/editor-ui/src/plugins/N8nCustomConnectorType.js +++ /dev/null @@ -1,896 +0,0 @@ -/** - * Custom connector type - * Based on jsplumb Flowchart and Bezier types - * - * Source GitHub repository: - * https://github.com/jsplumb/jsplumb - * - * Source files: - * https://github.com/jsplumb/jsplumb/blob/fb5fce52794fa52306825bdaa62bf3855cdfd7e0/src/connectors-flowchart.js - * https://github.com/jsplumb/jsplumb/blob/fb5fce52794fa52306825bdaa62bf3855cdfd7e0/src/connectors-bezier.js - * - * - * 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 () { - 'use strict'; - var root = window, - _jp = root.jsPlumb, - _ju = root.jsPlumbUtil, - _jg = root.Biltong; - var STRAIGHT = 'Straight'; - var ARC = 'Arc'; - - /** - * Custom connector type - * - * @param stub {number} length of stub segments in flowchart - * @param getEndpointOffset {Function} callback to offset stub length based on endpoint in flowchart - * @param midpoint {number} float percent of halfway point of segments in flowchart - * @param loopbackVerticalLength {number} height of vertical segment when looping in flowchart - * @param cornerRadius {number} radius of flowchart connectors - * @param loopbackMinimum {number} minimum threshold before looping behavior takes effect in flowchart - * @param targetGap {number} gap between connector and target endpoint in both flowchart and bezier - */ - const N8nCustom = function (params) { - params = params || {}; - this.type = 'N8nCustom'; - - params.stub = params.stub == null ? 30 : params.stub; - - var _super = _jp.Connectors.AbstractConnector.apply(this, arguments), - minorAnchor = 0, // seems to be angle at which connector leaves endpoint - majorAnchor = 0, // translates to curviness of bezier curve - segments, - midpoint = params.midpoint == null ? 0.5 : params.midpoint, - alwaysRespectStubs = params.alwaysRespectStubs === true, - loopbackVerticalLength = params.loopbackVerticalLength || 0, - lastx = null, - lasty = null, - cornerRadius = params.cornerRadius != null ? params.cornerRadius : 0, - loopbackMinimum = params.loopbackMinimum || 100, - curvinessCoeffient = 0.4, - zBezierOffset = 40, - targetGap = params.targetGap || 0, - stub = params.stub || 0; - - /** - * Set target endpoint - * (to override default behavior tracking mouse when dragging mouse) - * @param {Endpoint} endpoint - */ - this.setTargetEndpoint = function (endpoint) { - this.overrideTargetEndpoint = endpoint; - }; - - /** - * reset target endpoint overriding default behavior - */ - this.resetTargetEndpoint = function () { - this.overrideTargetEndpoint = null; - }; - - this._compute = function (originalPaintInfo, connParams) { - const paintInfo = _getPaintInfo(connParams, { - targetGap, - stub, - overrideTargetEndpoint: this.overrideTargetEndpoint, - getEndpointOffset: params.getEndpointOffset, - }); - Object.keys(paintInfo).forEach((key) => { - // override so that bounding box is calculated correctly wheen target override is set - originalPaintInfo[key] = paintInfo[key]; - }); - - if (paintInfo.tx < 0) { - this._computeFlowchart(paintInfo); - } else { - this._computeBezier(paintInfo); - } - }; - - this._computeBezier = function (paintInfo) { - var sp = paintInfo.sourcePos, - tp = paintInfo.targetPos, - _w = Math.abs(sp[0] - tp[0]) - paintInfo.targetGap, - _h = Math.abs(sp[1] - tp[1]); - - var _CP, - _CP2, - _sx = sp[0] < tp[0] ? _w : 0, - _sy = sp[1] < tp[1] ? _h : 0, - _tx = sp[0] < tp[0] ? 0 : _w, - _ty = sp[1] < tp[1] ? 0 : _h; - - if (paintInfo.ySpan <= 20 || (paintInfo.ySpan <= 100 && paintInfo.xSpan <= 100)) { - majorAnchor = 0.1; - } else { - majorAnchor = paintInfo.xSpan * curvinessCoeffient + zBezierOffset; - } - - _CP = _findControlPoint( - [_sx, _sy], - sp, - tp, - paintInfo.sourceEndpoint, - paintInfo.targetEndpoint, - paintInfo.so, - paintInfo.to, - majorAnchor, - minorAnchor, - ); - _CP2 = _findControlPoint( - [_tx, _ty], - tp, - sp, - paintInfo.targetEndpoint, - paintInfo.sourceEndpoint, - paintInfo.to, - paintInfo.so, - majorAnchor, - minorAnchor, - ); - - _super.addSegment(this, 'Bezier', { - x1: _sx, - y1: _sy, - x2: _tx, - y2: _ty, - cp1x: _CP[0], - cp1y: _CP[1], - cp2x: _CP2[0], - cp2y: _CP2[1], - }); - }; - - /** - * helper method to add a segment. - */ - const addFlowchartSegment = function (segments, x, y, paintInfo) { - if (lastx === x && lasty === y) { - return; - } - var lx = lastx == null ? paintInfo.sx : lastx, - ly = lasty == null ? paintInfo.sy : lasty, - o = lx === x ? 'v' : 'h'; - - lastx = x; - lasty = y; - segments.push([lx, ly, x, y, o]); - }; - - this._computeFlowchart = function (paintInfo) { - segments = []; - lastx = null; - lasty = null; - - // calculate Stubs. - var stubs = calcualteStubSegment(paintInfo, { alwaysRespectStubs }); - - // add the start stub segment. use stubs for loopback as it will look better, with the loop spaced - // away from the element. - addFlowchartSegment(segments, stubs[0], stubs[1], paintInfo); - - // compute the rest of the line - var p = calculateLineSegment(paintInfo, stubs, { - midpoint, - loopbackMinimum, - loopbackVerticalLength, - }); - if (p) { - for (var i = 0; i < p.length; i++) { - addFlowchartSegment(segments, p[i][0], p[i][1], paintInfo); - } - } - - // line to end stub - addFlowchartSegment(segments, stubs[2], stubs[3], paintInfo); - - // end stub to end (common) - addFlowchartSegment(segments, paintInfo.tx, paintInfo.ty, paintInfo); - - // write out the segments. - writeFlowchartSegments(_super, this, segments, paintInfo, cornerRadius); - }; - }; - - _jp.Connectors.N8nCustom = N8nCustom; - _ju.extend(_jp.Connectors.N8nCustom, _jp.Connectors.AbstractConnector); - - function _findControlPoint( - point, - sourceAnchorPosition, - targetAnchorPosition, - sourceEndpoint, - targetEndpoint, - soo, - too, - majorAnchor, - minorAnchor, - ) { - // determine if the two anchors are perpendicular to each other in their orientation. we swap the control - // points around if so (code could be tightened up) - var perpendicular = soo[0] !== too[0] || soo[1] === too[1], - p = []; - - if (!perpendicular) { - if (soo[0] === 0) { - p.push( - sourceAnchorPosition[0] < targetAnchorPosition[0] - ? point[0] + minorAnchor - : point[0] - minorAnchor, - ); - } else { - p.push(point[0] - majorAnchor * soo[0]); - } - - if (soo[1] === 0) { - p.push( - sourceAnchorPosition[1] < targetAnchorPosition[1] - ? point[1] + minorAnchor - : point[1] - minorAnchor, - ); - } else { - p.push(point[1] + majorAnchor * too[1]); - } - } else { - if (too[0] === 0) { - p.push( - targetAnchorPosition[0] < sourceAnchorPosition[0] - ? point[0] + minorAnchor - : point[0] - minorAnchor, - ); - } else { - p.push(point[0] + majorAnchor * too[0]); - } - - if (too[1] === 0) { - p.push( - targetAnchorPosition[1] < sourceAnchorPosition[1] - ? point[1] + minorAnchor - : point[1] - minorAnchor, - ); - } else { - p.push(point[1] + majorAnchor * soo[1]); - } - } - - return p; - } - - function sgn(n) { - return n < 0 ? -1 : n === 0 ? 0 : 1; - } - - function getFlowchartSegmentDirections(segment) { - return [sgn(segment[2] - segment[0]), sgn(segment[3] - segment[1])]; - } - - function getSegmentLength(s) { - return Math.sqrt(Math.pow(s[0] - s[2], 2) + Math.pow(s[1] - s[3], 2)); - } - - function _cloneArray(a) { - var _a = []; - _a.push.apply(_a, a); - return _a; - } - - function writeFlowchartSegments(_super, conn, segments, paintInfo, cornerRadius) { - var current = null, - next, - currentDirection, - nextDirection; - for (var i = 0; i < segments.length - 1; i++) { - current = current || _cloneArray(segments[i]); - next = _cloneArray(segments[i + 1]); - - currentDirection = getFlowchartSegmentDirections(current); - nextDirection = getFlowchartSegmentDirections(next); - - if (cornerRadius > 0 && current[4] !== next[4]) { - var minSegLength = Math.min(getSegmentLength(current), getSegmentLength(next)); - var radiusToUse = Math.min(cornerRadius, minSegLength / 2); - - current[2] -= currentDirection[0] * radiusToUse; - current[3] -= currentDirection[1] * radiusToUse; - next[0] += nextDirection[0] * radiusToUse; - next[1] += nextDirection[1] * radiusToUse; - - var ac = - (currentDirection[1] === nextDirection[0] && nextDirection[0] === 1) || - (currentDirection[1] === nextDirection[0] && - nextDirection[0] === 0 && - currentDirection[0] !== nextDirection[1]) || - (currentDirection[1] === nextDirection[0] && nextDirection[0] === -1), - sgny = next[1] > current[3] ? 1 : -1, - sgnx = next[0] > current[2] ? 1 : -1, - sgnEqual = sgny === sgnx, - cx = (sgnEqual && ac) || (!sgnEqual && !ac) ? next[0] : current[2], - cy = (sgnEqual && ac) || (!sgnEqual && !ac) ? current[3] : next[1]; - - _super.addSegment(conn, STRAIGHT, { - x1: current[0], - y1: current[1], - x2: current[2], - y2: current[3], - }); - - _super.addSegment(conn, ARC, { - r: radiusToUse, - x1: current[2], - y1: current[3], - x2: next[0], - y2: next[1], - cx: cx, - cy: cy, - ac: ac, - }); - } else { - // dx + dy are used to adjust for line width. - var dx = - current[2] === current[0] - ? 0 - : current[2] > current[0] - ? paintInfo.lw / 2 - : -(paintInfo.lw / 2), - dy = - current[3] === current[1] - ? 0 - : current[3] > current[1] - ? paintInfo.lw / 2 - : -(paintInfo.lw / 2); - - _super.addSegment(conn, STRAIGHT, { - x1: current[0] - dx, - y1: current[1] - dy, - x2: current[2] + dx, - y2: current[3] + dy, - }); - } - current = next; - } - if (next != null) { - // last segment - _super.addSegment(conn, STRAIGHT, { - x1: next[0], - y1: next[1], - x2: next[2], - y2: next[3], - }); - } - } - - const lineCalculators = { - opposite: function (paintInfo, { axis, startStub, endStub, idx, midx, midy }) { - var pi = paintInfo, - comparator = pi['is' + axis.toUpperCase() + 'GreaterThanStubTimes2']; - - if ( - !comparator || - (pi.so[idx] === 1 && startStub > endStub) || - (pi.so[idx] === -1 && startStub < endStub) - ) { - return { - x: [ - [startStub, midy], - [endStub, midy], - ], - y: [ - [midx, startStub], - [midx, endStub], - ], - }[axis]; - } else if ( - (pi.so[idx] === 1 && startStub < endStub) || - (pi.so[idx] === -1 && startStub > endStub) - ) { - return { - x: [ - [midx, pi.sy], - [midx, pi.ty], - ], - y: [ - [pi.sx, midy], - [pi.tx, midy], - ], - }[axis]; - } - }, - }; - - const stubCalculators = { - opposite: function (paintInfo, { axis, alwaysRespectStubs }) { - var pi = paintInfo, - idx = axis === 'x' ? 0 : 1, - areInProximity = { - x: function () { - return ( - (pi.so[idx] === 1 && - ((pi.startStubX > pi.endStubX && pi.tx > pi.startStubX) || - (pi.sx > pi.endStubX && pi.tx > pi.sx))) || - (pi.so[idx] === -1 && - ((pi.startStubX < pi.endStubX && pi.tx < pi.startStubX) || - (pi.sx < pi.endStubX && pi.tx < pi.sx))) - ); - }, - y: function () { - return ( - (pi.so[idx] === 1 && - ((pi.startStubY > pi.endStubY && pi.ty > pi.startStubY) || - (pi.sy > pi.endStubY && pi.ty > pi.sy))) || - (pi.so[idx] === -1 && - ((pi.startStubY < pi.endStubY && pi.ty < pi.startStubY) || - (pi.sy < pi.endStubY && pi.ty < pi.sy))) - ); - }, - }; - - if (!alwaysRespectStubs && areInProximity[axis]()) { - return { - x: [ - (paintInfo.sx + paintInfo.tx) / 2, - paintInfo.startStubY, - (paintInfo.sx + paintInfo.tx) / 2, - paintInfo.endStubY, - ], - y: [ - paintInfo.startStubX, - (paintInfo.sy + paintInfo.ty) / 2, - paintInfo.endStubX, - (paintInfo.sy + paintInfo.ty) / 2, - ], - }[axis]; - } else { - return [paintInfo.startStubX, paintInfo.startStubY, paintInfo.endStubX, paintInfo.endStubY]; - } - }, - }; - - function calcualteStubSegment(paintInfo, { alwaysRespectStubs }) { - return stubCalculators['opposite'](paintInfo, { - axis: paintInfo.sourceAxis, - alwaysRespectStubs, - }); - } - - function calculateLineSegment( - paintInfo, - stubs, - { midpoint, loopbackVerticalLength, loopbackMinimum }, - ) { - const axis = paintInfo.sourceAxis, - idx = paintInfo.sourceAxis === 'x' ? 0 : 1, - oidx = paintInfo.sourceAxis === 'x' ? 1 : 0, - startStub = stubs[idx], - otherStartStub = stubs[oidx], - endStub = stubs[idx + 2], - otherEndStub = stubs[oidx + 2]; - - const diffX = paintInfo.endStubX - paintInfo.startStubX; - const diffY = paintInfo.endStubY - paintInfo.startStubY; - const direction = -1; // vertical direction of loop, always below source - - var midx = paintInfo.startStubX + (paintInfo.endStubX - paintInfo.startStubX) * midpoint, - midy; - - if (diffY >= 0 || diffX < -1 * loopbackMinimum) { - // loop backward behavior - midy = paintInfo.startStubY - (diffX < 0 ? direction * loopbackVerticalLength : 0); - } else { - // original flowchart behavior - midy = paintInfo.startStubY + (paintInfo.endStubY - paintInfo.startStubY) * midpoint; - } - - return lineCalculators['opposite'](paintInfo, { - axis, - startStub, - otherStartStub, - endStub, - otherEndStub, - idx, - oidx, - midx, - midy, - }); - } - - function _getPaintInfo(params, { targetGap, stub, overrideTargetEndpoint, getEndpointOffset }) { - let { targetPos, targetEndpoint } = params; - - if (overrideTargetEndpoint) { - targetPos = overrideTargetEndpoint.anchor.getCurrentLocation(); - targetEndpoint = overrideTargetEndpoint; - } - - const sourceGap = 0; - - stub = stub || 0; - const sourceStub = _ju.isArray(stub) ? stub[0] : stub; - const targetStub = _ju.isArray(stub) ? stub[1] : stub; - - var segment = _jg.quadrant(params.sourcePos, targetPos), - swapX = targetPos[0] < params.sourcePos[0], - swapY = targetPos[1] < params.sourcePos[1], - lw = params.strokeWidth || 1, - so = params.sourceEndpoint.anchor.getOrientation(params.sourceEndpoint), // source orientation - to = targetEndpoint.anchor.getOrientation(targetEndpoint), // target orientation - x = swapX ? targetPos[0] : params.sourcePos[0], - y = swapY ? targetPos[1] : params.sourcePos[1], - w = Math.abs(targetPos[0] - params.sourcePos[0]), - h = Math.abs(targetPos[1] - params.sourcePos[1]); - - // if either anchor does not have an orientation set, we derive one from their relative - // positions. we fix the axis to be the one in which the two elements are further apart, and - // point each anchor at the other element. this is also used when dragging a new connection. - if ((so[0] === 0 && so[1] === 0) || (to[0] === 0 && to[1] === 0)) { - var index = w > h ? 0 : 1, - oIndex = [1, 0][index]; - so = []; - to = []; - so[index] = params.sourcePos[index] > targetPos[index] ? -1 : 1; - to[index] = params.sourcePos[index] > targetPos[index] ? 1 : -1; - so[oIndex] = 0; - to[oIndex] = 0; - } - - const sx = swapX ? w + sourceGap * so[0] : sourceGap * so[0], - sy = swapY ? h + sourceGap * so[1] : sourceGap * so[1], - tx = swapX ? targetGap * to[0] : w + targetGap * to[0], - ty = swapY ? targetGap * to[1] : h + targetGap * to[1], - oProduct = so[0] * to[0] + so[1] * to[1]; - - const sourceStubWithOffset = - sourceStub + - (getEndpointOffset && params.sourceEndpoint ? getEndpointOffset(params.sourceEndpoint) : 0); - const targetStubWithOffset = - targetStub + (getEndpointOffset && targetEndpoint ? getEndpointOffset(targetEndpoint) : 0); - - // same as paintinfo generated by jsplumb AbstractConnector type - var result = { - sx: sx, - sy: sy, - tx: tx, - ty: ty, - lw: lw, - xSpan: Math.abs(tx - sx), - ySpan: Math.abs(ty - sy), - mx: (sx + tx) / 2, - my: (sy + ty) / 2, - so: so, - to: to, - x: x, - y: y, - w: w, - h: h, - segment: segment, - startStubX: sx + so[0] * sourceStubWithOffset, - startStubY: sy + so[1] * sourceStubWithOffset, - endStubX: tx + to[0] * targetStubWithOffset, - endStubY: ty + to[1] * targetStubWithOffset, - isXGreaterThanStubTimes2: Math.abs(sx - tx) > sourceStubWithOffset + targetStubWithOffset, - isYGreaterThanStubTimes2: Math.abs(sy - ty) > sourceStubWithOffset + targetStubWithOffset, - opposite: oProduct === -1, - perpendicular: oProduct === 0, - orthogonal: oProduct === 1, - sourceAxis: so[0] === 0 ? 'y' : 'x', - points: [x, y, w, h, sx, sy, tx, ty], - stubs: [sourceStubWithOffset, targetStubWithOffset], - anchorOrientation: 'opposite', // always opposite since our endpoints are always opposite (source orientation is left (1) and target orientaiton is right (-1)) - - /** custom keys added */ - sourceEndpoint: params.sourceEndpoint, - targetEndpoint: targetEndpoint, - sourcePos: params.sourcePos, - targetPos: targetEndpoint.anchor.getCurrentLocation(), - targetGap, - }; - - return result; - } -}.call(typeof window !== 'undefined' ? window : this)); diff --git a/packages/editor-ui/src/plugins/PlusEndpointType.js b/packages/editor-ui/src/plugins/PlusEndpointType.js deleted file mode 100644 index 6e97c54193a22..0000000000000 --- a/packages/editor-ui/src/plugins/PlusEndpointType.js +++ /dev/null @@ -1,518 +0,0 @@ -/** - * 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 () { - 'use strict'; - var root = window, - _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 = ` -
-
- -
-
- -
- -
- Click to add node
- or drag to connect -
-
- `; - - 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.hoverMessage !== endpointStyle.hoverMessage) { - this.hoverMessage = endpointStyle.hoverMessage; - message.innerHTML = endpointStyle.hoverMessage; - } - - 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; -})(); diff --git a/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointRenderer.ts b/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointRenderer.ts index c3d6e4cbe35a8..7c6d43f3154e7 100644 --- a/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointRenderer.ts +++ b/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointRenderer.ts @@ -29,7 +29,6 @@ export const register = () => { }, updateNode: (ep: N8nPlusEndpoint) => { - console.log('__DEBUG: Update node plus', ep); const hasConnections = () => { const connections = [...ep.endpoint.connections, ...ep.params.connectedEndpoint.connections]; return connections.length > 0; diff --git a/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointType.ts b/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointType.ts index df984624d9e9e..63fba0176a3b7 100644 --- a/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointType.ts +++ b/packages/editor-ui/src/plugins/endpoints/N8nPlusEndpointType.ts @@ -109,37 +109,32 @@ export class N8nPlusEndpoint extends EndpointRepresentation { - // if (!this.plusElement) return; - - // this.plusElement.classList.add('success'); - // if (this.showOutputLabel) { - // const plusStalk = this.plusElement.querySelector('.plus-stalk') as HTMLElement; - // const successOutput = this.plusElement.querySelector('.plus-stalk span') as HTMLElement; - - // 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 - // // } - // } - // }; - - // clearSuccessOutput(endpoint: N8nPlusEndpoint) { - // // const container = this.plusElement.querySelector('.plus-container'); - // const plusStalk = this.plusElement.querySelector('.plus-stalk') as HTMLElement; - // const successOutput = this.plusElement.querySelector('.plus-stalk span') as HTMLElement; - - // this.plusElement.classList.remove('success'); - // successOutput.textContent = ''; - // this.label = ''; - // this.labelOffset = 0; - // plusStalk.style.width = `${stalkLength}px`; - // endpoint.instance.repaint(endpoint); - // } + setSuccessOutput(label: string) { + + this.endpoint.addClass('success'); + // if (this.params.showOutputLabel) { + // const plusStalk = this.plusElement.querySelector('.plus-stalk') as HTMLElement; + // const successOutput = this.plusElement.querySelector('.plus-stalk span') as HTMLElement; + + // 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 + // } + // } + }; + + clearSuccessOutput() { + this.endpoint.removeClass('success'); + // successOutput.textContent = ''; + // this.label = ''; + // this.labelOffset = 0; + // plusStalk.style.width = `${stalkLength}px`; + // endpoint.instance.repaint(endpoint); + } // ep.clearOverlays(); // ep.setStalkOverlay(); diff --git a/packages/editor-ui/src/stores/canvas.ts b/packages/editor-ui/src/stores/canvas.ts index 2418266551df6..3478931123d22 100644 --- a/packages/editor-ui/src/stores/canvas.ts +++ b/packages/editor-ui/src/stores/canvas.ts @@ -32,7 +32,6 @@ export const useCanvasStore = defineStore('canvas', () => { const uiStore = useUIStore(); const historyStore = useHistoryStore(); - console.log('Before'); const newInstance = ref(); const isDragging = ref(false); @@ -138,36 +137,22 @@ export const useCanvasStore = defineStore('canvas', () => { }; function initInstance(container: Element) { - if(newInstance.value) { - console.log('__DEBUG: newInstance.value already exists', newInstance.value); - }; newInstance.value = newJsPlumbInstance({ container, connector: CONNECTOR_FLOWCHART_TYPE, resizeObserver: false, dragOptions: { cursor: 'pointer', - // resizeObserver: false, grid: { w: GRID_SIZE, h: GRID_SIZE }, start: (params: BeforeStartEventParams) => { const draggedNode = params.drag.getDragElement(); const nodeName = draggedNode.getAttribute('data-name'); if(!nodeName) return; - const nodeData = workflowStore.getNodeByName(nodeName); - console.log('Started dragging', params); - // @ts-ignore isDragging.value = true; const isSelected = uiStore.isNodeSelected(nodeName); - if (nodeData?.type === STICKY_NODE_TYPE && !isSelected) { - setTimeout(() => { - console.log('Node selected????'); - // this.$emit('nodeSelected', nodeName, false, true); - }, 0); - } if (params.e && !isSelected) { - console.log("๐Ÿš€ ~ file: canvas.ts:167 ~ initInstance ~ isSelected", isSelected); // Only the node which gets dragged directly gets an event, for all others it is // undefined. So check if the currently dragged node is selected and if not clear // the drag-selection. @@ -203,7 +188,6 @@ export const useCanvasStore = defineStore('canvas', () => { let newNodePosition: XYPosition; moveNodes.forEach((node: INodeUi) => { const element = document.getElementById(node.id); - console.log("๐Ÿš€ ~ file: canvas.ts:203 ~ moveNodes.forEach ~ element", element); if (element === null) { return; } @@ -216,17 +200,15 @@ export const useCanvasStore = defineStore('canvas', () => { const updateInformation = { name: node.name, properties: { - // @ts-ignore, draggable does not have definitions position: newNodePosition, }, }; const oldPosition = node.position; if (oldPosition[0] !== newNodePosition[0] || oldPosition[1] !== newNodePosition[1]) { - // historyStore.pushCommandToUndo( - // new MoveNodeCommand(node.name, oldPosition, newNodePosition, this), - // ); + historyStore.pushCommandToUndo( + new MoveNodeCommand(node.name, oldPosition, newNodePosition), + ); workflowStore.updateNodeProperties(updateInformation); - // this.$emit('moved', node); } }); if (moveNodes.length > 1) { @@ -239,18 +221,14 @@ export const useCanvasStore = defineStore('canvas', () => { }); newInstance.value?.setDragConstrainFunction((pos: XYPosition) => { const isReadOnly = uiStore.isReadOnly; - console.log("๐Ÿš€ ~ file: canvas.ts:147 ~ initInstance ~ isReadOnly", isReadOnly); if (isReadOnly) { // Do not allow to move nodes in readOnly mode return null; } return pos; }); - window.__plumbInstance = () => newInstance.value; } return { - // jsPlumbInstance, - // jsPlumbInstanceNew, isDemo, nodeViewScale, canvasAddButtonPosition, diff --git a/packages/editor-ui/src/stores/ui.ts b/packages/editor-ui/src/stores/ui.ts index e1af285d3bbfc..3091e7aef7edb 100644 --- a/packages/editor-ui/src/stores/ui.ts +++ b/packages/editor-ui/src/stores/ui.ts @@ -239,7 +239,6 @@ export const useUIStore = defineStore(STORES.UI, { this.fakeDoorFeatures.find((fakeDoor) => fakeDoor.id.toString() === id); }, isReadOnly(): boolean { - console.log("๐Ÿš€ ~ file: ui.ts:244 ~ isReadOnly ~ ![VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW].includes(this.currentView as VIEWS);", ![VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW].includes(this.currentView as VIEWS)); return ![VIEWS.WORKFLOW, VIEWS.NEW_WORKFLOW].includes(this.currentView as VIEWS); }, isNodeView(): boolean { diff --git a/packages/editor-ui/src/utils/nodeViewUtils.ts b/packages/editor-ui/src/utils/nodeViewUtils.ts index 98f1a89b2a745..33089d7945e90 100644 --- a/packages/editor-ui/src/utils/nodeViewUtils.ts +++ b/packages/editor-ui/src/utils/nodeViewUtils.ts @@ -7,12 +7,10 @@ import { QUICKSTART_NOTE_NAME, } from '@/constants'; import { EndpointStyle, IBounds, INodeUi, IZoomConfig, XYPosition } from '@/Interface'; -// import { AnchorArraySpec, Connection, Overlay, PaintStyle } from "jsplumb"; import { ArrayAnchorSpec, ConnectorSpec, OverlaySpec, PaintStyle } from '@jsplumb/common'; -import { Endpoint, Overlay, Connection } from '@jsplumb/core'; +import { Endpoint, Connection } from '@jsplumb/core'; import { N8nConnector } from '@/plugins/connectors/N8nCustomConnector'; import { closestNumberDivisibleBy } from '@/utils'; -import { AnchorArraySpec } from 'jsplumb'; import { IConnection, INode, @@ -209,13 +207,8 @@ export const getInputNameOverlay = (labelText: string): OverlaySpec => ({ type: 'Custom', options: { id: OVERLAY_OUTPUT_NAME_LABEL, - // location: 0.5, visible: true, create: (component: Endpoint) => { - console.log("๐Ÿš€ ~ file: nodeViewUtils.ts:215 ~ getInputNameOverlay ~ component", component); - // component.bind('anchor:changed', (params: any) => { - // console.log("๐Ÿš€ ~ file: nodeViewUtils.ts:217 ~ component.bind ~ params", params); - // }); const label = document.createElement('div'); label.innerHTML = labelText; label.classList.add('node-input-endpoint-label'); @@ -234,7 +227,6 @@ export const getOutputNameOverlay = (labelText: string): OverlaySpec => ({ type: 'Custom', options: { id: OVERLAY_OUTPUT_NAME_LABEL, - // location: 0.5, visible: true, create: (component: Endpoint) => { const label = document.createElement('div'); @@ -310,16 +302,7 @@ export const getOverlay = (item: Connection | Endpoint, overlayId: string) => { export const showOverlay = (item: Connection | Endpoint, overlayId: string) => { const overlay = getOverlay(item, overlayId); if (overlay) { - // const overlayElement = overlay.canvas; - // console.log("__DEBUG: Overlay canvas", overlayElement); - // item.instance.repaint(overlayElement); - item.instance.setSuspendDrawing(true); overlay.setVisible(true); - overlay.setVisible(false); - setTimeout(() => { - overlay.setVisible(true); - item.instance.setSuspendDrawing(false); - }, 500); } }; @@ -341,8 +324,6 @@ export const showOrHideMidpointArrow = (connection: Connection) => { const targetEndpoint = connection.endpoints[1]; const sourcePosition = sourceEndpoint._anchor.computedPosition?.curX ?? 0; const targetPosition = targetEndpoint._anchor.computedPosition?.curX ?? sourcePosition + 1; - // ? targetEndpoint._anchor.computedPosition?.x - // : sourcePosition + 1; // lastReturnValue is null when moving connections from node to another const minimum = hasItemsLabel ? 150 : 0; const isBackwards = sourcePosition >= targetPosition; @@ -377,14 +358,11 @@ const isLoopingBackwards = (connection: Connection) => { }; export const showOrHideItemsLabel = (connection: Connection) => { - if (!connection || !connection.connector) { - return; - } + if (!connection?.connector) return; const overlay = getOverlay(connection, OVERLAY_RUN_ITEMS_ID); - if (!overlay) { - return; - } + if (!overlay) return; + const actionsOverlay = getOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID); if (actionsOverlay && actionsOverlay.visible) { @@ -533,22 +511,26 @@ export const getBackgroundStyles = ( return styles; }; -export const hideConnectionActions = (connection: Connection | null) => { - if (connection?.connector) { - hideOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID); - showOrHideItemsLabel(connection); - showOrHideMidpointArrow(connection); - } +export const hideConnectionActions = (connection: Connection) => { + if(!connection) return; + + hideOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID); + showOrHideItemsLabel(connection); + showOrHideMidpointArrow(connection); + + connection.instance.repaintEverything(); }; -export const showConnectionActions = (connection: Connection | null) => { - if (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 showConnectionActions = (connection: Connection) => { + if(!connection) return; + + 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); } + + connection.instance.repaintEverything(); }; export const getOutputSummary = (data: ITaskData[], nodeConnections: NodeInputConnections) => { @@ -618,9 +600,8 @@ export const resetConnection = (connection: Connection) => { connection.removeOverlay(OVERLAY_RUN_ITEMS_ID); connection.setPaintStyle(CONNECTOR_PAINT_STYLE_DEFAULT); showOrHideMidpointArrow(connection); - if (connection.canvas) { - connection.removeClass('success'); - } + connection.removeClass('success'); + }; export const getRunItemsLabel = (output: { total: number; iterations: number }): string => { @@ -635,26 +616,30 @@ export const addConnectionOutputSuccess = ( output: { total: number; iterations: number }, ) => { connection.setPaintStyle(CONNECTOR_PAINT_STYLE_SUCCESS); - if (connection.canvas) { - connection.addClass('success'); - } + connection.addClass('success'); if (getOverlay(connection, OVERLAY_RUN_ITEMS_ID)) { connection.removeOverlay(OVERLAY_RUN_ITEMS_ID); } - connection.addOverlay({ - type: 'Label', + const overlay = connection.addOverlay({ + type: 'Custom', options: { id: OVERLAY_RUN_ITEMS_ID, - label: `${getRunItemsLabel(output)}`, - cssClass: 'connection-run-items-label', + create() { + const label = document.createElement('div'); + label.classList.add('connection-run-items-label'); + label.innerHTML = getRunItemsLabel(output); + return label; + }, location: 0.5, }, }); + overlay.setVisible(true); showOrHideItemsLabel(connection); showOrHideMidpointArrow(connection); + overlay.instance.repaintEverything(); }; const getContentDimensions = (): { editorWidth: number; editorHeight: number } => { @@ -710,7 +695,6 @@ export const showDropConnectionState = (connection: Connection, targetEndpoint?: if (connection?.connector) { const connector = connection.connector as N8nConnector; if (targetEndpoint) { - console.log("๐Ÿš€ ~ file: nodeViewUtils.ts:704 ~ showDropConnectionState ~ targetEndpoint", targetEndpoint); connector.setTargetEndpoint(targetEndpoint); } connection.setPaintStyle(CONNECTOR_PAINT_STYLE_PRIMARY); @@ -754,17 +738,11 @@ export const addConnectionActionsOverlay = ( onDelete: Function, onAdd: Function, ) => { - connection.instance.setSuspendDrawing(true); - connection.instance.removeOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID); - // if (getOverlay(connection, OVERLAY_CONNECTION_ACTIONS_ID)) { - // return; // avoid free floating actions when moving connection from one node to another - // } const overlay = connection.addOverlay({ type: 'Custom', options: { id: OVERLAY_CONNECTION_ACTIONS_ID, - // location: [2, 19], create: (component: Connection) => { const div = document.createElement('div'); const addButton = document.createElement('button'); @@ -786,8 +764,13 @@ export const addConnectionActionsOverlay = ( }, }, }); - overlay.setVisible(false); - connection.instance.setSuspendDrawing(false); + + // Overlays are created in visible state and if + // immediately hidden they are not rendered correctly and won't show up on hover + overlay.setVisible(true); + setTimeout(() => { + overlay.setVisible(false); + }, 200); }; export const getOutputEndpointUUID = (nodeId: string, outputIndex: number) => { diff --git a/packages/editor-ui/src/views/CanvasAddButton.vue b/packages/editor-ui/src/views/CanvasAddButton.vue index 085fc45ed7ff2..4db9df4c5ea63 100644 --- a/packages/editor-ui/src/views/CanvasAddButton.vue +++ b/packages/editor-ui/src/views/CanvasAddButton.vue @@ -1,6 +1,6 @@