From c63c351234ae265f173967aa8a2bc1544c4464e4 Mon Sep 17 00:00:00 2001 From: Julian Wielga Date: Thu, 18 Apr 2024 19:38:11 +0200 Subject: [PATCH] pan on edge --- .../src/components/graph/EspNode/link.ts | 3 +- .../client/src/components/graph/Graph.tsx | 67 +++++++++++++++---- .../src/components/graph/PanZoomPlugin.ts | 39 +---------- .../src/components/sidePanels/SidePanel.tsx | 2 +- 4 files changed, 61 insertions(+), 50 deletions(-) diff --git a/designer/client/src/components/graph/EspNode/link.ts b/designer/client/src/components/graph/EspNode/link.ts index 417c01ef834..45af521e6e3 100644 --- a/designer/client/src/components/graph/EspNode/link.ts +++ b/designer/client/src/components/graph/EspNode/link.ts @@ -1,6 +1,6 @@ /* eslint-disable i18next/no-literal-string */ import { dia, routers } from "jointjs"; -import { Edge, EdgeKind, ScenarioGraph, ProcessDefinitionData } from "../../../types"; +import { Edge, EdgeKind, ProcessDefinitionData, ScenarioGraph } from "../../../types"; import NodeUtils from "../NodeUtils"; const LINK_TEXT_COLOR = "#686868"; @@ -76,6 +76,7 @@ export const defaultRouter: routers.RouterJSON = { endDirections, step: 15, padding: 20, + maximumLoops: 200, }, }; diff --git a/designer/client/src/components/graph/Graph.tsx b/designer/client/src/components/graph/Graph.tsx index 8e32516a539..789104b9a97 100644 --- a/designer/client/src/components/graph/Graph.tsx +++ b/designer/client/src/components/graph/Graph.tsx @@ -38,6 +38,10 @@ import "./jqueryPassiveEvents"; // TODO: this is needed here due to our webpack config - needs fixing (NU-1559). styles; +function clamp(number: number, max: number) { + return Math.round(Math.min(max, Math.max(-max, number))); +} + type Props = GraphProps & { processCategory: string; processDefinitionData: ProcessDefinitionData; @@ -132,21 +136,63 @@ export class Graph extends React.Component { return paper; }; + private bindMoveWithEdge(cellView: dia.CellView) { + const { paper, model } = cellView; + const cell = this.graph.getCell(model.id); + const border = 80; + + const mousePosition = new g.Point(this.viewport.center()); + + const updateMousePosition = (cellView: dia.CellView, event: dia.Event) => { + mousePosition.update(event.clientX, event.clientY); + }; + + let frame: number; + const panWithEdge = () => { + const rect = this.viewport.clone().inflate(-border, -border); + if (!rect.containsPoint(mousePosition)) { + const distance = rect.pointNearestToPoint(mousePosition).difference(mousePosition); + const x = clamp(distance.x / 2, border / 4); + const y = clamp(distance.y / 2, border / 4); + this.panAndZoom.panBy({ + x, + y, + }); + if (isModelElement(cell)) { + const p = cell.position(); + cell.position(p.x - x, p.y - y); + } + } + frame = requestAnimationFrame(panWithEdge); + }; + frame = requestAnimationFrame(panWithEdge); + + paper.on(Events.CELL_POINTERMOVE, updateMousePosition); + return () => { + paper.off(Events.CELL_POINTERMOVE, updateMousePosition); + cancelAnimationFrame(frame); + }; + } + private bindPaperEvents() { this.processGraphPaper //trigger new custom event on finished cell move .on(Events.CELL_POINTERDOWN, (cellView: dia.CellView) => { - const model = cellView.model; + const { model, paper } = cellView; + const moveCallback = () => { cellView.once(Events.CELL_POINTERUP, () => { cellView.trigger(Events.CELL_MOVED, cellView); - this.processGraphPaper.trigger(Events.CELL_MOVED, cellView); + paper.trigger(Events.CELL_MOVED, cellView); }); }; model.once(Events.CHANGE_POSITION, moveCallback); + const cleanup = this.bindMoveWithEdge(cellView); + cellView.once(Events.CELL_POINTERUP, () => { model.off(Events.CHANGE_POSITION, moveCallback); + cleanup(); }); }) //we want to inject node during 'Drag and Drop' from graph paper @@ -621,18 +667,18 @@ export class Graph extends React.Component { return model; } - moveSelectedNodesRelatively(movedNodeId: string, position: Position): dia.Cell[] { + private moveSelectedNodesRelatively(movedNodeId: string, position: Position): dia.Cell[] { this.redrawing = true; const nodeIdsToBeMoved = without(this.props.selectionState, movedNodeId); - const cellsToBeMoved = nodeIdsToBeMoved.map((nodeId) => this.graph.getCell(nodeId)); + const cellsToBeMoved = nodeIdsToBeMoved.map((nodeId) => this.graph.getCell(nodeId)).filter(isModelElement); const { position: originalPosition } = this.findNodeInLayout(movedNodeId); const offset = { x: position.x - originalPosition.x, y: position.y - originalPosition.y, }; - cellsToBeMoved.filter(isModelElement).forEach((cell) => { + cellsToBeMoved.forEach((cell) => { const { position: originalPosition } = this.findNodeInLayout(cell.id.toString()); - cell.position(originalPosition.x + offset.x, originalPosition.y + offset.y); + cell.position(originalPosition.x + offset.x, originalPosition.y + offset.y, { group: true }); }); this.redrawing = false; return cellsToBeMoved; @@ -663,10 +709,9 @@ export class Graph extends React.Component { private bindNodesMoving(): void { this.graph.on( Events.CHANGE_POSITION, - rafThrottle((element: dia.Cell, position: Position) => { - if (this.redrawing || !isModelElement(element)) { - return; - } + rafThrottle((element: dia.Cell, position: dia.Point, options) => { + if (this.redrawing || !isModelElement(element)) return; + if (options.group) return; const movingCells: dia.Cell[] = [element]; const nodeId = element.id.toString(); @@ -675,8 +720,6 @@ export class Graph extends React.Component { const movedNodes = this.moveSelectedNodesRelatively(nodeId, position); movingCells.push(...movedNodes); } - - this.panAndZoom.panToOverflow(movingCells, this.viewport); }), ); } diff --git a/designer/client/src/components/graph/PanZoomPlugin.ts b/designer/client/src/components/graph/PanZoomPlugin.ts index 398428b0aaa..53c6966caff 100644 --- a/designer/client/src/components/graph/PanZoomPlugin.ts +++ b/designer/client/src/components/graph/PanZoomPlugin.ts @@ -11,14 +11,6 @@ function isModified(event: MouseEvent | TouchEvent) { return event.shiftKey || event.ctrlKey || event.altKey || event.metaKey; } -function clamp(number: number, max: number) { - return Math.round(Math.min(max, Math.max(-max, number))); -} - -function adjust(number: number, multi = 1, pow = 1) { - return Math.round((number >= 0 ? Math.pow(number, pow) : -Math.pow(-number, pow)) * multi); -} - function transformToCSSMatrix({ x, y, k }: ZoomTransform): string { const matrix = [ [k.toFixed(4), 0, x.toFixed(4)], @@ -33,9 +25,9 @@ export type Viewport = g.Rect; export class PanZoomPlugin { private zoomBehavior: ZoomBehavior; private paperSelection: Selection; - private panBy = rafThrottle(({ x, y }: { x: number; y: number }) => { - this.paperSelection.interrupt("pan").transition("pan").duration(25).call(this.zoomBehavior.translateBy, x, y); - }); + panBy = ({ x, y }: { x: number; y: number }) => { + this.paperSelection.call(this.zoomBehavior.translateBy, x, y); + }; private globalCursor: GlobalCursor; private zoomTo = throttle((scale: number): void => { this.paperSelection.transition().duration(750).call(this.zoomBehavior.scaleTo, scale); @@ -115,31 +107,6 @@ export class PanZoomPlugin { this.zoomTo(this.zoom * 0.5); } - panToOverflow(cells: dia.Cell[], updatedViewport?: Viewport) { - this.viewport = updatedViewport; - - const border = Math.min(100, adjust(this.viewport.width, 0.05), adjust(this.viewport.height, 0.05)); - const adjustedViewport = this.viewport.inflate(-border, -border).round(); - - const cellsBBox = this.paper.localToClientRect(this.paper.model.getCellsBBox(cells)).round(); - - if (adjustedViewport.containsRect(cellsBBox)) { - return; - } - - const top = Math.min(0, adjustedViewport.topLine().pointOffset(cellsBBox.topMiddle())); - const bottom = Math.max(0, adjustedViewport.bottomLine().pointOffset(cellsBBox.bottomMiddle())); - const left = Math.min(0, -adjustedViewport.leftLine().pointOffset(cellsBBox.leftMiddle())); - const right = Math.max(0, -adjustedViewport.rightLine().pointOffset(cellsBBox.rightMiddle())); - - const x = -Math.round(left + right); - const y = -Math.round(top + bottom); - this.panBy({ - x: clamp(adjust(x, 0.2, 1.5), 60), - y: clamp(adjust(y, 0.2, 1.5), 60), - }); - } - private getTranslatedCenter = (center: g.Point, viewport: Viewport, scale: number) => { const viewportRelativeCenter = viewport.translate(0, -viewport.y).center(); return viewportRelativeCenter.translate(center.scale(-scale, -scale)); diff --git a/designer/client/src/components/sidePanels/SidePanel.tsx b/designer/client/src/components/sidePanels/SidePanel.tsx index 677a051d560..d0cec6f07ac 100644 --- a/designer/client/src/components/sidePanels/SidePanel.tsx +++ b/designer/client/src/components/sidePanels/SidePanel.tsx @@ -45,7 +45,7 @@ function useGraphViewportAdjustment(side: keyof Graph["viewportAdjustment"], inV useEffect(() => { getGraph?.()?.adjustViewport({ - [side]: ref.current?.getBoundingClientRect().width * isOccupied, + [side]: inView ? ref.current?.getBoundingClientRect().width * isOccupied : 0, }); }, [getGraph, inView, isOccupied, side]);