diff --git a/packages/visual-flow/src/components/socket/MultiInSocket.ts b/packages/visual-flow/src/components/socket/MultiInSocket.ts index 8664bda..89655e4 100644 --- a/packages/visual-flow/src/components/socket/MultiInSocket.ts +++ b/packages/visual-flow/src/components/socket/MultiInSocket.ts @@ -36,7 +36,7 @@ export class MultiInSocket extends Socket { ); } - onMouseDown(): void { + getHoveredLine() { if (this.connectedLines.length > 0) { const { x: mouseDx, y: mouseDy } = Point.minus( this.graph.mouseBoardPos, @@ -73,10 +73,18 @@ export class MultiInSocket extends Socket { nearestLine = line; } } + return nearestLine; + } else { + return this.connectedLines[0] ?? null; + } + } - this.disconnectTo(nearestLine); - nearestLine.neverLeaves = this; - this.graph.startDraggingLine(nearestLine); + onMouseDown(): void { + if (this.connectedLines.length > 0) { + const hoveredLine = this.getHoveredLine(); + this.disconnectTo(hoveredLine); + hoveredLine.neverLeaves = this; + this.graph.startDraggingLine(hoveredLine); } else { // do nothing } diff --git a/packages/visual-flow/src/components/socket/MultiOutSocket.ts b/packages/visual-flow/src/components/socket/MultiOutSocket.ts index 1e21771..11ec579 100644 --- a/packages/visual-flow/src/components/socket/MultiOutSocket.ts +++ b/packages/visual-flow/src/components/socket/MultiOutSocket.ts @@ -30,6 +30,10 @@ export class MultiOutSocket extends Socket { return this.connectedLines; } + getHoveredLine(): Line | null { + return null; + } + onMouseDown(): void { const line = this.connectToNewLine(new BasicLine()); this.graph.startDraggingLine(line); diff --git a/packages/visual-flow/src/components/socket/SingleInSocket.ts b/packages/visual-flow/src/components/socket/SingleInSocket.ts index 4f2f2b8..f0a3c42 100644 --- a/packages/visual-flow/src/components/socket/SingleInSocket.ts +++ b/packages/visual-flow/src/components/socket/SingleInSocket.ts @@ -46,6 +46,10 @@ export class SingleInSocket extends Socket { } } + getHoveredLine(): Line | null { + return this.connectedLine; + } + protected exportData(): any { return {}; } diff --git a/packages/visual-flow/src/components/socket/SingleOutSocket.ts b/packages/visual-flow/src/components/socket/SingleOutSocket.ts index 030b91c..0378b0a 100644 --- a/packages/visual-flow/src/components/socket/SingleOutSocket.ts +++ b/packages/visual-flow/src/components/socket/SingleOutSocket.ts @@ -31,6 +31,10 @@ export class SingleOutSocket extends Socket { return false; } + getHoveredLine(): Line | null { + return this.connectedLine; + } + onMouseDown(): void { if (this.connectedLine) { // do nothing diff --git a/packages/visual-flow/src/model/graph.ts b/packages/visual-flow/src/model/graph.ts index 0d258ea..0d0d4a5 100644 --- a/packages/visual-flow/src/model/graph.ts +++ b/packages/visual-flow/src/model/graph.ts @@ -251,7 +251,7 @@ export class Graph { state: State = idelState; protected mouseDown: boolean = false; protected scaleEndTimeout: number = NaN; - protected hoveredItem: Block | Socket | null = null; + protected hoveredItem: Block | [Socket, Line | null] | null = null; mousePagePos: Point; mouseGraphPos: Point; mouseBoardPos: Point; @@ -271,10 +271,21 @@ export class Graph { block.selected = true; } - setHoveredItem(hoveredItem: Block | Socket | null) { + setHoveredItem(hoveredItem: Block | [Socket, Line | null] | null) { if (this.hoveredItem !== hoveredItem) { - this.hoveredItem?.onUnhover(); - hoveredItem?.onHover(); + if (Array.isArray(this.hoveredItem)) { + this.hoveredItem[0].onUnhover(); + this.hoveredItem[1]?.onUnhover(); + } else { + this.hoveredItem?.onUnhover(); + } + + if (Array.isArray(hoveredItem)) { + hoveredItem[0].onHover(); + hoveredItem[1]?.onHover(); + } else { + hoveredItem?.onHover(); + } this.hoveredItem = hoveredItem; } } @@ -626,7 +637,14 @@ export class Graph { if (this.state.type === GraphStateType.IDLE) { const draggingSource = this.getDraggingSource(shiftKey); if (draggingSource) { - this.setHoveredItem(draggingSource[1] ?? draggingSource[0]); + if (draggingSource[1]) { + this.setHoveredItem([ + draggingSource[1], + draggingSource[1].getHoveredLine(), + ]); + } else { + this.setHoveredItem(draggingSource[0]); + } } else { this.setHoveredItem(null); } @@ -639,7 +657,7 @@ export class Graph { const { line, predictor } = this.state; const targetSocket = this.getDraggingTarget(line); if (targetSocket) { - this.setHoveredItem(targetSocket); + this.setHoveredItem([targetSocket, targetSocket.getHoveredLine()]); line.setBoardPosB(this.mouseBoardPos, targetSocket.direction); predictor.setBoardPosB( line.neverLeaves ? this.mouseBoardPos : targetSocket.boardPos, diff --git a/packages/visual-flow/src/model/line.ts b/packages/visual-flow/src/model/line.ts index 333f00c..8be378a 100644 --- a/packages/visual-flow/src/model/line.ts +++ b/packages/visual-flow/src/model/line.ts @@ -107,12 +107,12 @@ export abstract class Line extends ModelBase { this.arrowEl?.setAttribute("d", this.arrowPath); } - hover() { + onHover() { this.lineEl?.classList.add("hovered"); this.arrowEl?.classList.add("hovered"); } - unhover() { + onUnhover() { this.lineEl?.classList.remove("hovered"); this.arrowEl?.classList.remove("hovered"); } diff --git a/packages/visual-flow/src/model/socket.ts b/packages/visual-flow/src/model/socket.ts index 4dc5960..a7ccb99 100644 --- a/packages/visual-flow/src/model/socket.ts +++ b/packages/visual-flow/src/model/socket.ts @@ -81,6 +81,8 @@ export abstract class Socket extends ModelBase { return line; } + abstract getHoveredLine(): Line | null; + protected abstract exportData(): any; exportRecord(): SocketRecord { return { diff --git a/packages/visual-flow/src/view/line.styles.ts b/packages/visual-flow/src/view/line.styles.ts index 2b5de54..1ec9144 100644 --- a/packages/visual-flow/src/view/line.styles.ts +++ b/packages/visual-flow/src/view/line.styles.ts @@ -1,32 +1,48 @@ import { tokens } from "@fluentui/tokens"; import { makeResetStyles, makeStyles, mergeClasses } from "@refina/griffel"; +const colors = { + default: tokens.colorBrandForegroundInverted, + dragging: tokens.colorBrandForegroundOnLightPressed, + hovered: tokens.colorBrandForegroundOnLight, +}; + const curveClassName = makeResetStyles({ - stroke: tokens.colorCompoundBrandStroke, + stroke: colors.default, fill: "none", }); const curveStyles = makeStyles({ dragging: { - stroke: tokens.colorCompoundBrandStrokePressed, + stroke: colors.dragging, strokeWidth: tokens.strokeWidthThickest, }, predicting: { opacity: 0.4, }, + hoverable: { + "&.hovered": { + stroke: colors.hovered, + }, + }, }); const arrowClassName = makeResetStyles({ - fill: tokens.colorCompoundBrandStroke, + fill: colors.default, }); const arrowStyles = makeStyles({ dragging: { - fill: tokens.colorCompoundBrandStrokePressed, + fill: colors.dragging, }, predicting: { opacity: 0.4, }, + hoverable: { + "&.hovered": { + fill: colors.hovered, + }, + }, }); export default { @@ -35,11 +51,13 @@ export default { curveClassName, dragging && curveStyles.dragging, predicting && curveStyles.predicting, + !(dragging || predicting) && curveStyles.hoverable, ), arrow: (dragging: boolean, predicting: boolean) => mergeClasses( arrowClassName, dragging && arrowStyles.dragging, predicting && arrowStyles.predicting, + !(dragging || predicting) && arrowStyles.hoverable, ), };