From f13f769530a4a53206d1f5810d0943d154b44a1c Mon Sep 17 00:00:00 2001 From: tfukaza Date: Sat, 13 Jul 2024 16:13:04 -0700 Subject: [PATCH 1/2] Refactor input control to use interact.js --- package-lock.json | 14 ++ package.json | 5 +- src/camera.ts | 15 +- src/components/base.ts | 3 + src/components/node.ts | 2 + src/input.ts | 287 ++++++++++++---------------------- src/main.ts | 30 ++-- src/theme/standard_light.scss | 4 + 8 files changed, 158 insertions(+), 202 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8cd4314..f4ac2e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "ISC", "dependencies": { + "interactjs": "^1.10.27", "react": "^18.2.0", "react-dom": "^18.2.0" }, @@ -904,6 +905,11 @@ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==", "dev": true }, + "node_modules/@interactjs/types": { + "version": "1.10.27", + "resolved": "https://registry.npmjs.org/@interactjs/types/-/types-1.10.27.tgz", + "integrity": "sha512-BUdv0cvs4H5ODuwft2Xp4eL8Vmi3LcihK42z0Ft/FbVJZoRioBsxH+LlsBdK4tAie7PqlKGy+1oyOncu1nQ6eA==" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -2898,6 +2904,14 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, + "node_modules/interactjs": { + "version": "1.10.27", + "resolved": "https://registry.npmjs.org/interactjs/-/interactjs-1.10.27.tgz", + "integrity": "sha512-y/8RcCftGAF24gSp76X2JS3XpHiUvDQyhF8i7ujemBz77hwiHDuJzftHx7thY8cxGogwGiPJ+o97kWB6eAXnsA==", + "dependencies": { + "@interactjs/types": "1.10.27" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", diff --git a/package.json b/package.json index 920986e..9039b35 100755 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ } }, "scripts": { - "dev-vanilla": "FRAMEWORK=vanilla npx vite build --mode development --watch & FRAMEWORK=vanilla npx vite serve demo/vanilla --config demo/vanilla/vite.config.js", - "dev-react": "FRAMEWORK=react npx vite build --mode development --watch & FRAMEWORK=react npx vite serve demo/react --config demo/react/vite.config.js ", + "dev-vanilla": "FRAMEWORK=vanilla npx vite build --mode development --watch & FRAMEWORK=vanilla npx vite serve demo/vanilla --config demo/vanilla/vite.config.js --host", + "dev-react": "FRAMEWORK=react npx vite build --mode development --watch & FRAMEWORK=react npx vite serve demo/react --config demo/react/vite.config.js --host", "format": "prettier --write src", "lint": "eslint src --ext .ts", "build": "vite build --mode production" @@ -45,6 +45,7 @@ "vite": "^5.3.2" }, "dependencies": { + "interactjs": "^1.10.27", "react": "^18.2.0", "react-dom": "^18.2.0" } diff --git a/src/camera.ts b/src/camera.ts index 283229d..bf4b926 100644 --- a/src/camera.ts +++ b/src/camera.ts @@ -68,16 +68,15 @@ class Camera { /** * Handle the scroll event to zoom in and out of the camera view - * @param deltaScroll Amount of scroll + * @param deltaZoom Amount of scroll * @param mouseX Position of the mouse on the device screen * @param mouseY */ - handleScroll(deltaScroll: number, mouseX: number, mouseY: number) { + handleScroll(deltaZoom: number, mouseX: number, mouseY: number) { // Mouse position should be relative to the container mouseX -= this.containerDom.offsetLeft; mouseY -= this.containerDom.offsetTop; - let deltaZoom = 1 * this.zoom * (-deltaScroll / 1000); // Control scroll speed // Limit zoom if (this.zoom + deltaZoom < 0.2) { deltaZoom = 0.2 - this.zoom; @@ -85,6 +84,10 @@ class Camera { deltaZoom = 1 - this.zoom; } + // console.debug( + // `MouseX: ${mouseX}, MouseY: ${mouseY}, cameraWidth: ${this.cameraWidth}, cameraHeight: ${this.cameraHeight}`, + // ); + const zoomRatio = this.zoom / (this.zoom + deltaZoom); // Ratio of current zoom to new zoom // Move camera to zoom in on the mouse position this.cameraX -= @@ -98,6 +101,12 @@ class Camera { this.zoom += deltaZoom; + // console.log( + // (this.cameraWidth / this.zoom) * + // (zoomRatio - 1) * + // (1 - (this.cameraWidth * 1.5 - mouseX) / this.cameraWidth), + // ); + this.updateCamera(); } diff --git a/src/components/base.ts b/src/components/base.ts index 2649afb..76ac8b2 100755 --- a/src/components/base.ts +++ b/src/components/base.ts @@ -28,9 +28,11 @@ export abstract class Base { bindFunction(dom: HTMLElement) { dom.onmousedown = this.domMouseDown.bind(this); dom.ontouchstart = this.domTouchStart.bind(this); + dom.onpointerdown = this.domMouseDown.bind(this); } domMouseDown(e: MouseEvent): void { + console.debug(`Mouse down event triggered on ${this.gid}`); this.domCursorDown({ button: e.button, clientX: e.clientX, @@ -40,6 +42,7 @@ export abstract class Base { } domTouchStart(e: TouchEvent): void { + console.debug(`Touch start event triggered on ${this.gid}`); this.domCursorDown({ button: 0, clientX: e.touches[0].clientX, diff --git a/src/components/node.ts b/src/components/node.ts index 30dbdf4..b2cf66e 100755 --- a/src/components/node.ts +++ b/src/components/node.ts @@ -374,11 +374,13 @@ class NodeComponent extends Base { } onFocus() { + console.debug("On focus"); this.setNodeStyle({ _focus: true }); this.renderNode(this.nodeStyle); } offFocus() { + console.debug("Off focus"); this.setNodeStyle({ _focus: false }); this.renderNode(this.nodeStyle); } diff --git a/src/input.ts b/src/input.ts index 9d0dd7b..755ed49 100644 --- a/src/input.ts +++ b/src/input.ts @@ -1,3 +1,5 @@ +import interact from "interactjs"; + export enum cursorState { none = 0, mouseLeft = 1, @@ -32,29 +34,25 @@ class InputControl { * Functions as a middleware that converts mouse and touch events into a unified event format. */ _dom: HTMLElement; + _touchControl: any; _onCursorDown: null | callbackFunction; _onCursorMove: null | callbackFunction; _onCursorUp: null | callbackFunction; _onScroll: null | scrollCallbackFunction; - // _onRotate: null | scrollCallbackFunction; + _onRotate: null | scrollCallbackFunction; _onKeyDown: null | keyCallbackFunction; - _prevTouchList: TouchEvent["touches"] | null; - _prevDoubleTouchDistance: number; + _pointerMode: "pointer" | "gesture" | "none"; + _currentCursorState: cursorState; constructor(dom: HTMLElement) { - this._dom = dom; - - dom.addEventListener("mouseup", this.onMouseUp.bind(this)); - dom.addEventListener("mousemove", this.onMouseMove.bind(this)); - dom.addEventListener("mousedown", this.onMouseDown.bind(this)); + // dom.addEventListener("mouseup", this.onMouseUp.bind(this)); + // dom.addEventListener("mousemove", this.onMouseMove.bind(this)); + // dom.addEventListener("mousedown", this.onMouseDown.bind(this)); dom.addEventListener("wheel", this.onWheel.bind(this)); dom.addEventListener("keydown", this.onKeyDown.bind(this)); - dom.addEventListener("touchstart", this.onTouchStart.bind(this)); - dom.addEventListener("touchmove", this.onTouchMove.bind(this)); - dom.addEventListener("touchend", this.onTouchEnd.bind(this)); document.addEventListener("mousemove", this.onMouseMove.bind(this)); document.addEventListener("mouseup", this.onMouseUp.bind(this)); @@ -63,11 +61,89 @@ class InputControl { this._onCursorMove = null; this._onCursorUp = null; this._onScroll = null; + this._onRotate = null; this._onKeyDown = null; this._currentCursorState = cursorState.none; - this._prevTouchList = null; - this._prevDoubleTouchDistance = -1; + + this._pointerMode = "none"; + + this._dom = dom; + this._touchControl = interact(dom); + this._touchControl + .on("down", (e: PointerEvent) => { + console.log("Pointer down"); + this._callFuncWithCallbackParam(this._onCursorDown, e); + this._pointerMode = "pointer"; + }) + .on("move", (e: PointerEvent) => { + if (this._pointerMode === "gesture") { + return; + } + console.log("Pointer move"); + this._callFuncWithCallbackParam(this._onCursorMove, e); + }) + .on("up", (e: PointerEvent) => { + console.log("Pointer up"); + this._callFuncWithCallbackParam(this._onCursorUp, e); + this._pointerMode = "none"; + }); + + this._touchControl.gesturable({ + onstart: (e: any) => { + console.log("Gesture start"); + if (this._pointerMode === "pointer") { + this._callFuncWithCallbackParam(this._onCursorUp, e); + } + e.button = 1; // Middle mouse button, indicating a camera pan + this._callFuncWithCallbackParam(this._onCursorDown, e); + this._pointerMode = "gesture"; + }, + onmove: (e: any) => { + if (this._pointerMode !== "gesture") { + this._pointerMode = "gesture"; + } + + console.log("Gesture move"); + e.button = 1; // Middle mouse button, indicating a camera pan + this._callFuncWithCallbackParam(this._onCursorMove, e); + this._callFuncWithScrollCallbackParam(this._onScroll, e, e.ds); + }, + onend: (e: any) => { + console.log("end"); + e.button = 1; // Middle mouse button, indicating a camera pan + this._callFuncWithCallbackParam(this._onCursorUp, e); + this._pointerMode = "none"; + }, + }); + } + + private _callFuncWithCallbackParam( + func: callbackFunction | null, + e: PointerEvent, + ) { + func?.( + e, + e.target instanceof Element ? e.target : null, + this.convertMouseToCursorState(e.button), + e.clientX, + e.clientY, + ); + } + + private _callFuncWithScrollCallbackParam( + func: scrollCallbackFunction | null, + e: PointerEvent, + delta: number, + ) { + func?.( + e, + e.target instanceof Element ? e.target : null, + this.convertMouseToCursorState(e.button), + e.clientX, + e.clientY, + delta, + ); } setCursorDownCallback(callback: callbackFunction) { @@ -86,6 +162,14 @@ class InputControl { this._onScroll = callback; } + setRotateCallback(callback: scrollCallbackFunction) { + this._onRotate = callback; + } + + setKeyDownCallback(callback: keyCallbackFunction) { + this._onKeyDown = callback; + } + convertMouseToCursorState(button: number): cursorState { switch (button) { case 0: @@ -95,66 +179,11 @@ class InputControl { case 2: return cursorState.mouseRight; default: - return cursorState.invalid; + console.warn("Invalid mouse button"); + return cursorState.none; } } - /** - * Called when a new touch point is detected on the screen - * @param e - * @returns - */ - onTouchStart(e: TouchEvent) { - if (e.touches.length > 1) { - // If there was only one touch previously, it means up until now it has been handled as a mouse press or drag. - // Call the cursor up handler to reset the state - if (this._prevTouchList && this._prevTouchList.length == 1) { - this._onCursorUp?.( - e, - e.target instanceof Element ? e.target : null, - cursorState.touchSingle, - e.touches[0].clientX, - e.touches[0].clientY, - ); - } - - this._currentCursorState = cursorState.touchDouble; - - // If there are 3 or more touches, use the 2 most recent touches - const touch1 = e.touches[e.touches.length - 2]; - const touch2 = e.touches[e.touches.length - 1]; - // Use the middle of the two touches as the mouse position. - const middleX = (touch1.clientX + touch2.clientX) / 2; - const middleY = (touch1.clientY + touch2.clientY) / 2; - - const element = document.elementFromPoint(middleX, middleY); - - this._onCursorDown?.( - e, - element, - cursorState.touchDouble, - middleX, - middleY, - ); - - this._prevTouchList = e.touches; - this._prevDoubleTouchDistance = Math.sqrt( - Math.pow(touch1.clientX - touch2.clientX, 2) + - Math.pow(touch1.clientY - touch2.clientY, 2), - ); - return; - } - - // If there is only one touch, treat it as a left mouse button press. - this._onCursorDown?.( - e, - e.target instanceof Element ? e.target : null, - cursorState.touchSingle, - e.touches[0].clientX, - e.touches[0].clientY, - ); - } - /** * Called when the user pressed the mouse button * @param e @@ -170,85 +199,13 @@ class InputControl { ); } - /** - * Called when the user drags the touch point along the screen - * @param e - */ - onTouchMove(e: TouchEvent) { - // Single touch move is same as mouse drag - if (e.touches.length == 1) { - const element = document.elementFromPoint( - e.touches[0].clientX, - e.touches[0].clientY, - ); - - this._onCursorMove?.( - e, - element, - cursorState.touchSingle, - e.touches[0].clientX, - e.touches[0].clientY, - ); - this._prevTouchList = e.touches; - return; - } - - if (this._prevTouchList == null) { - this._prevTouchList = e.touches; - return; - } - - const curTouch1 = e.touches[e.touches.length - 2]; - const curTouch2 = e.touches[e.touches.length - 1]; - - let prevTouch1 = null; - let prevTouch2 = null; - - // Find the previous touch positions for each finger - for (let i = 0; i < e.touches.length; i++) { - if (curTouch1.identifier == this._prevTouchList[i].identifier) { - prevTouch1 = this._prevTouchList[i]; - } else if (curTouch2.identifier == this._prevTouchList[i].identifier) { - prevTouch2 = this._prevTouchList[i]; - } - } - - if (prevTouch1 == null || prevTouch2 == null) { - return; - } - - const curDistance = Math.sqrt( - Math.pow(curTouch1.clientX - curTouch2.clientX, 2) + - Math.pow(curTouch1.clientY - curTouch2.clientY, 2), - ); - - const deltaZoom = curDistance - this._prevDoubleTouchDistance; - - const middleX = (curTouch1.clientX + curTouch2.clientX) / 2; - const middleY = (curTouch1.clientY + curTouch2.clientY) / 2; - - const element = document.elementFromPoint(middleX, middleY); - - this._onCursorMove?.(e, element, cursorState.touchDouble, middleX, middleY); - this._onScroll?.( - e, - element, - cursorState.touchDouble, - middleX, - middleY, - deltaZoom, - ); - this._prevTouchList = e.touches; - - return; - } - /** * Called when the user moves the mouse * @param e */ onMouseMove(e: MouseEvent) { const element = document.elementFromPoint(e.clientX, e.clientY); + this._onCursorMove?.( e, element, @@ -258,48 +215,6 @@ class InputControl { ); } - /** - * Called when the user releases a touch point from the screen - * @param e - */ - onTouchEnd(e: TouchEvent) { - // If we previously has 2 or more touches... - if (this._prevTouchList && this._prevTouchList.length > 1) { - // TODO: Use middle of the two touches as the mouse position - this._onCursorUp?.( - e, - e.target instanceof Element ? e.target : null, - cursorState.touchDouble, - e.changedTouches[0].clientX, - e.changedTouches[0].clientY, - ); - if (e.touches.length == 1) { - // if we now have only one touch, end the double touch and resume as a single touch - this._prevTouchList = e.touches; - this._onCursorDown?.( - e, - e.target instanceof Element ? e.target : null, - cursorState.touchSingle, - e.touches[0].clientX, - e.touches[0].clientY, - ); - return; - } else if (e.touches.length == 0) { - // If we now have no touches, end the double touch - this._prevTouchList = null; - return; - } - } else { - this._onCursorUp?.( - e, - e.target instanceof Element ? e.target : null, - cursorState.touchSingle, - e.changedTouches[0].clientX, - e.changedTouches[0].clientY, - ); - } - } - /** * Called when the user releases the mouse button * @param e @@ -325,7 +240,7 @@ class InputControl { cursorState.mouseMiddle, e.clientX, e.clientY, - e.deltaY, + e.deltaY / 1000, ); } diff --git a/src/main.ts b/src/main.ts index cadb818..a38a8e9 100755 --- a/src/main.ts +++ b/src/main.ts @@ -186,11 +186,19 @@ export default class SnapLine { */ onCursorDown( _: Event, - __: Element | null, + element: Element | null, button: cursorState, clientX: number, clientY: number, ) { + if ( + element !== this.g.canvasContainer && + element !== this.g.canvasBackground + ) { + console.debug("Ignoring cursor down event"); + return; + } + this.g._currentMouseDown = button; // If the user is dragging a line when another cursor down event is detected, then the line should be deleted. @@ -249,7 +257,7 @@ export default class SnapLine { onCursorMove( _: Event, element: Element | null, - button: cursorState, + __: cursorState, clientX: number, clientY: number, ) { @@ -269,9 +277,9 @@ export default class SnapLine { g.dy = clientY - g.mousedown_y + g.dy_offset; // Do nothing if mouse is not pressed or touch is not active - if (button == cursorState.none) { - return; - } + // if (button == cursorState.none) { + // return; + // } if (g.dx !== 0 || g.dy !== 0) { g.mouseHasMoved = true; @@ -410,6 +418,7 @@ export default class SnapLine { }); g.targetObject = null; + console.debug("targetObject set to null"); g.dx = 0; g.dy = 0; g.dx_offset = 0; @@ -425,20 +434,19 @@ export default class SnapLine { onZoom( _: Event, __: Element | null, - button: cursorState, + ______: cursorState, ____: number, _____: number, deltaY: number, ) { - if (button === cursorState.mouseMiddle) { - deltaY = deltaY; - } else if (button === cursorState.touchDouble) { - deltaY = deltaY; - } this.g.camera.handleScroll(deltaY, this.g.mouse_x, this.g.mouse_y); this._setCanvasStyle({ transform: this.g.camera.canvasStyle, }); + console.debug( + "Zooming", + `Camera position: ${this.g.camera.cameraX}, ${this.g.camera.cameraY}`, + ); } /** diff --git a/src/theme/standard_light.scss b/src/theme/standard_light.scss index 6f8ac64..7ca809f 100755 --- a/src/theme/standard_light.scss +++ b/src/theme/standard_light.scss @@ -23,6 +23,10 @@ input:focus { background-size: 32px 32px; } +#sl-canvas { + // transition: transform 0.1s; +} + .sl-node { font-family: "Inter", sans-serif; min-width: 150px; From 6931ba3b0c71389589b8063d9351fee71dba5a79 Mon Sep 17 00:00:00 2001 From: tfukaza Date: Sat, 13 Jul 2024 20:46:40 -0700 Subject: [PATCH 2/2] Fix: Mouse down event was triggered twice for every click. --- src/components/base.ts | 2 +- src/components/connector.ts | 16 +++++++++---- src/components/node.ts | 47 +++++++++++++++++++++++++++++-------- src/input.ts | 1 - src/main.ts | 4 ++-- 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/components/base.ts b/src/components/base.ts index 76ac8b2..86a60e5 100755 --- a/src/components/base.ts +++ b/src/components/base.ts @@ -26,7 +26,7 @@ export abstract class Base { * @param dom The DOM element to bind the function to */ bindFunction(dom: HTMLElement) { - dom.onmousedown = this.domMouseDown.bind(this); + //dom.onmousedown = this.domMouseDown.bind(this); dom.ontouchstart = this.domTouchStart.bind(this); dom.onpointerdown = this.domMouseDown.bind(this); } diff --git a/src/components/connector.ts b/src/components/connector.ts index 8c823b5..f8b1c46 100644 --- a/src/components/connector.ts +++ b/src/components/connector.ts @@ -35,6 +35,8 @@ class ConnectorComponent extends ComponentBase { config: ConnectorConfig, parent: NodeComponent, globals: GlobalStats, + outgoingLines: lineObject[], + incomingLines: lineObject[], ) { super(config, parent, globals); @@ -45,8 +47,8 @@ class ConnectorComponent extends ComponentBase { this.dom = dom; this.parent = parent; this.prop = parent.prop; - this.outgoingLines = []; - this.incomingLines = []; + this.outgoingLines = outgoingLines; + this.incomingLines = incomingLines; this.config = config; globals.gid++; this.name = config.name || globals.gid.toString(); @@ -54,6 +56,8 @@ class ConnectorComponent extends ComponentBase { this.dom.setAttribute("sl-gid", this.gid.toString()); this.bindFunction(this.dom); + + console.log(config); } /** @@ -78,7 +82,7 @@ class ConnectorComponent extends ComponentBase { requestDelete: false, completedDelete: false, }); - this.parent.outgoingLines.push(this.outgoingLines[0]); + //this.parent.allOutgoingLines.push(this.outgoingLines[0]); this.setAllLinePositions(); } @@ -107,6 +111,7 @@ class ConnectorComponent extends ComponentBase { const targetConnector: ConnectorComponent = this.g.globalNodeTable[ gid ] as ConnectorComponent; + console.debug("Hovering over input connector", targetConnector); targetConnector.updateConnectorPosition(); connectorX = targetConnector.connectorX; connectorY = targetConnector.connectorY; @@ -204,7 +209,7 @@ class ConnectorComponent extends ComponentBase { if (connector.config.maxConnectors === currentIncomingLines.length) { console.warn( - `Connector ${connector} already has max number of connectors`, + `Connector ${connector.name} already has max number of connectors (${connector.config.maxConnectors}) connected`, ); return false; } @@ -213,7 +218,6 @@ class ConnectorComponent extends ComponentBase { this.outgoingLines[0].target = connector; connector.incomingLines.push(this.outgoingLines[0]); - connector.parent.incomingLines.push(this.outgoingLines[0]); return true; } @@ -311,6 +315,8 @@ class ConnectorComponent extends ComponentBase { line.classList.add("sl-connector-line"); line.setAttribute("stroke-width", "4"); + console.debug(`Created line from connector ${this.gid}`); + this.g.canvas!.appendChild(svg); return svg; diff --git a/src/components/node.ts b/src/components/node.ts index b2cf66e..08b7f2b 100755 --- a/src/components/node.ts +++ b/src/components/node.ts @@ -16,8 +16,8 @@ class NodeComponent extends Base { dom: HTMLElement | null; connectors: { [key: string]: ConnectorComponent }; // Dictionary of all connectors in the node, using the name as the key components: { [key: string]: ComponentBase }; // Dictionary of all components in the node except connectors - outgoingLines: lineObject[]; // Array of all lines going out of the node - incomingLines: lineObject[]; // Array of all lines coming into the node + allOutgoingLines: { [key: string]: lineObject[] }; // Dictionary of all lines going out of the node + allIncomingLines: { [key: string]: lineObject[] }; // Dictionary of all lines coming into the node nodeWidth = 0; nodeHeight = 0; @@ -39,8 +39,8 @@ class NodeComponent extends Base { this.dom = dom; this.connectors = {}; this.components = {}; - this.outgoingLines = []; - this.incomingLines = []; + this.allOutgoingLines = {}; + this.allIncomingLines = {}; this.dragStartX = this.positionX; this.dragStartY = this.positionY; @@ -126,7 +126,7 @@ class NodeComponent extends Base { filterDeletedLines(svgLines: lineObject[]) { for (let i = 0; i < svgLines.length; i++) { - if (svgLines[i].requestDelete) { + if (svgLines[i].requestDelete && svgLines[i].completedDelete) { svgLines.splice(i, 1); i--; } @@ -139,6 +139,11 @@ class NodeComponent extends Base { * @param outgoingLines Array of all lines outgoing from the node or connector */ _renderOutgoingLines(outgoingLines: lineObject[]) { + // console.debug( + // "Rendering outgoing lines", + // outgoingLines, + // outgoingLines.length, + // ); for (const line of outgoingLines) { const connector = line.start; if (!line.svg) { @@ -161,6 +166,7 @@ class NodeComponent extends Base { line.svg.style.transform = `translate3d(${connector.connectorX}px, ${connector.connectorY}px, 0)`; connector.renderLinePosition(line); } + this.filterDeletedLines(outgoingLines); } @@ -171,14 +177,31 @@ class NodeComponent extends Base { }; } + _iterateDict( + dict: { [key: string]: any }, + callback: (lines: lineObject[]) => void, + bind: any = this, + ) { + for (const key in dict) { + callback.bind(bind)(dict[key]); + } + } + _renderNodeLines() { - this._renderOutgoingLines(this.outgoingLines); + // Flatten the allOutgoingLines object into an array and call the renderLines function + this._iterateDict(this.allOutgoingLines, this._renderOutgoingLines); // For incoming lines, the renderLines function of the peer node is called. // This is to prevent duplicate rendering of lines on some declarative frontend frameworks. - for (const line of this.incomingLines) { - const peerNode = line.start.parent; - peerNode._renderOutgoingLines(peerNode.outgoingLines); - } + this._iterateDict(this.allIncomingLines, (lines: lineObject[]) => { + for (const line of lines) { + const peerNode = line.start.parent; + this._iterateDict( + peerNode.allOutgoingLines, + peerNode._renderOutgoingLines, + peerNode, + ); + } + }); } renderNode(style: any) { @@ -225,11 +248,15 @@ class NodeComponent extends Base { maxConnectors = 1, allowDragOut = true, ) { + this.allOutgoingLines[name] = []; + this.allIncomingLines[name] = []; const connector = new ConnectorComponent( dom, { name: name, maxConnectors: maxConnectors, allowDragOut: allowDragOut }, this, this.g, + this.allOutgoingLines[name], + this.allIncomingLines[name], ); this.connectors[name] = connector; this.prop[name] = null; diff --git a/src/input.ts b/src/input.ts index 755ed49..948c9e5 100644 --- a/src/input.ts +++ b/src/input.ts @@ -179,7 +179,6 @@ class InputControl { case 2: return cursorState.mouseRight; default: - console.warn("Invalid mouse button"); return cursorState.none; } } diff --git a/src/main.ts b/src/main.ts index a38a8e9..f689c20 100755 --- a/src/main.ts +++ b/src/main.ts @@ -256,14 +256,14 @@ export default class SnapLine { */ onCursorMove( _: Event, - element: Element | null, + ___: Element | null, __: cursorState, clientX: number, clientY: number, ) { const g = this.g; - g.hoverDOM = element; + g.hoverDOM = document.elementFromPoint(clientX, clientY); g.mouse_x = clientX - g.canvasContainer.offsetLeft; g.mouse_y = clientY - g.canvasContainer.offsetTop;