1
- import React , { useCallback , useEffect , useMemo , useState } from 'react' ;
1
+ import React , { useCallback , useEffect , useMemo , useRef , useState } from 'react' ;
2
2
import { useSetObserver } from 'react-observing' ;
3
3
import { useFrame } from 'react-frame-component' ;
4
4
5
5
import { useBoardScrollContext , useBoardZoomContext , useDragLineContext , useToggleSelectedItem } from '../../context' ;
6
- import { getEdgeParams , getStraightPath } from '../../services' ;
7
- import { useLinePath } from './UseLinePath' ;
6
+ import { getCtrlKeyBySystem , getCurvedPath , getEdgeParams , getStraightPath } from '../../services' ;
8
7
import { TId } from '../../types' ;
9
8
10
9
@@ -19,20 +18,23 @@ interface IDraggableLineProps {
19
18
height1 : number ;
20
19
height2 : number ;
21
20
lineWidth : number ;
21
+ isCurved ?: boolean ;
22
22
newConnection ?: boolean ;
23
23
lineId : TId | undefined ;
24
24
onDragLineEnd ?: ( ) => void ;
25
25
onDragLineStart ?: ( ) => void ;
26
26
position1FromCenter ?: boolean ;
27
27
disableStartDraggable ?: boolean ;
28
28
}
29
- export const DraggableLine : React . FC < IDraggableLineProps > = ( { lineId, newConnection = false , position1FromCenter = false , disableStartDraggable = false , nodeId, lineWidth, onDragLineEnd, onDragLineStart, ...rest } ) => {
29
+ export const DraggableLine : React . FC < IDraggableLineProps > = ( { lineId, isCurved , newConnection = false , position1FromCenter = false , disableStartDraggable = false , nodeId, lineWidth, onDragLineEnd, onDragLineStart, ...rest } ) => {
30
30
const setDragLine = useSetObserver ( useDragLineContext ( ) ) ;
31
31
const addSelectedItem = useToggleSelectedItem ( ) ;
32
32
const scrollObject = useBoardScrollContext ( ) ;
33
33
const zoomObject = useBoardZoomContext ( ) ;
34
34
const { window } = useFrame ( ) ;
35
35
36
+ const isDragging = useRef ( false ) ;
37
+
36
38
37
39
const [ rawTop1 , setRawTop1 ] = useState ( rest . top1 ) ;
38
40
const [ rawTop2 , setRawTop2 ] = useState ( rest . top2 ) ;
@@ -49,52 +51,66 @@ export const DraggableLine: React.FC<IDraggableLineProps> = ({ lineId, newConnec
49
51
} , [ rest . top1 , rest . top2 , rest . left1 , rest . left2 ] ) ;
50
52
51
53
52
- const linePath = useLinePath ( {
53
- top1 : rawTop1 ,
54
- top2 : rawTop2 ,
55
- left1 : rawLeft1 ,
56
- left2 : rawLeft2 ,
57
- width1 : rest . width1 ,
58
- width2 : rest . width2 ,
59
- height1 : rest . height1 ,
60
- height2 : rest . height2 ,
61
- } ) ;
62
-
54
+ const intersectionPoints = useMemo ( ( ) => {
55
+ const dragAroundSpace = 20 ;
63
56
64
- const linePath2 = useMemo ( ( ) => {
65
- const { sx, sy, tx, ty } = getEdgeParams (
57
+ return getEdgeParams (
66
58
{
67
- y : rawTop1 ,
68
- x : rawLeft1 ,
69
- width : rest . width1 + 10 ,
70
- height : rest . height1 + 10 ,
59
+ y : rawTop1 - ( showDragLine === 'start' ? dragAroundSpace / 2 : 5 ) ,
60
+ x : rawLeft1 - ( showDragLine === 'start' ? dragAroundSpace / 2 : 5 ) ,
61
+ width : showDragLine === 'start' ? dragAroundSpace : rest . width1 + 10 ,
62
+ height : showDragLine === 'start' ? dragAroundSpace : rest . height1 + 10 ,
71
63
} ,
72
64
{
73
- y : rawTop2 - 5 ,
74
- x : rawLeft2 - 5 ,
75
- width : 10 ,
76
- height : 10 ,
65
+ y : rawTop2 - ( showDragLine === 'end' ? dragAroundSpace / 2 : 5 ) ,
66
+ x : rawLeft2 - ( showDragLine === 'end' ? dragAroundSpace / 2 : 5 ) ,
67
+ width : showDragLine === 'end' ? dragAroundSpace : rest . width2 + 10 ,
68
+ height : showDragLine === 'end' ? dragAroundSpace : rest . height2 + 10 ,
77
69
}
78
70
) ;
71
+ } , [ showDragLine , rawTop1 , rawTop2 , rawLeft1 , rawLeft2 , rest . width1 , rest . height1 , rest . width2 , rest . height2 ] ) ;
72
+
79
73
80
- const [ edgePath ] = getStraightPath ( {
81
- sourceX : sx ,
82
- sourceY : sy ,
83
- targetX : tx ,
84
- targetY : ty ,
74
+ const [ linePath ] = useMemo ( ( ) => {
75
+ const path = getStraightPath ( {
76
+ sourceX : intersectionPoints . sourceX ,
77
+ sourceY : intersectionPoints . sourceY ,
78
+ targetX : intersectionPoints . targetX ,
79
+ targetY : intersectionPoints . targetY ,
85
80
} ) ;
86
81
87
- return edgePath ;
88
- } , [ rawTop1 , rawTop2 , rawLeft1 , rawLeft2 , rest . width1 , rest . width2 , rest . height1 , rest . height2 ] ) ;
82
+ return path ;
83
+ } , [ intersectionPoints ] ) ;
84
+
85
+ const [ curvedLinePath ] = useMemo ( ( ) => {
86
+ const [ path ] = getCurvedPath (
87
+ {
88
+ sourceX : intersectionPoints . sourceX ,
89
+ sourceY : intersectionPoints . sourceY ,
90
+ targetX : intersectionPoints . targetX ,
91
+ targetY : intersectionPoints . targetY ,
92
+ } ,
93
+ { offset : 35 }
94
+ ) ;
95
+
96
+
97
+ return [ path ] ;
98
+ } , [ intersectionPoints ] ) ;
89
99
90
100
91
101
const handleStartMouseDown = useCallback ( ( e : React . MouseEvent ) => {
92
- if ( lineId ) addSelectedItem ( [ lineId ] , false ) ;
93
- setShowDragLine ( 'start' ) ;
94
- onDragLineStart ?.( ) ;
102
+ if ( lineId ) addSelectedItem ( [ lineId ] , getCtrlKeyBySystem ( e . nativeEvent ) ) ;
95
103
if ( ! window ) return ;
96
104
97
105
const handleMouseMove = ( e : MouseEvent ) => {
106
+ if ( ! isDragging . current ) {
107
+ setShowDragLine ( 'start' ) ;
108
+ onDragLineStart ?.( ) ;
109
+ setDragLine ( { type : 'start' , nodeId, lineId } ) ;
110
+
111
+ isDragging . current = true ;
112
+ }
113
+
98
114
const newLeft = ( e . pageX - scrollObject . left . value ) / zoomObject . value ;
99
115
const newTop = ( e . pageY - scrollObject . top . value ) / zoomObject . value ;
100
116
@@ -103,6 +119,8 @@ export const DraggableLine: React.FC<IDraggableLineProps> = ({ lineId, newConnec
103
119
}
104
120
105
121
const handleMouseUp = ( ) => {
122
+ isDragging . current = false ;
123
+
106
124
setShowDragLine ( undefined ) ;
107
125
setRawLeft1 ( rest . left1 ) ;
108
126
setDragLine ( undefined ) ;
@@ -112,20 +130,24 @@ export const DraggableLine: React.FC<IDraggableLineProps> = ({ lineId, newConnec
112
130
window . removeEventListener ( 'mouseup' , handleMouseUp )
113
131
}
114
132
115
- handleMouseMove ( e . nativeEvent ) ;
116
-
117
- setDragLine ( { type : 'start' , nodeId, lineId } ) ;
118
133
window . addEventListener ( 'mousemove' , handleMouseMove ) ;
119
134
window . addEventListener ( 'mouseup' , handleMouseUp ) ;
120
- } , [ setDragLine , onDragLineStart , onDragLineEnd , window , scrollObject , zoomObject , rawTop1 , rawLeft1 , linePath . y1 , linePath . x1 , rest . left1 , rest . top1 , nodeId , lineId ] ) ;
135
+ } , [ setDragLine , onDragLineStart , onDragLineEnd , window , scrollObject , zoomObject , rawTop1 , rawLeft1 , rest . left1 , rest . top1 , nodeId , lineId ] ) ;
121
136
122
137
const handleEndMouseDown = useCallback ( ( e : React . MouseEvent ) => {
123
- if ( lineId ) addSelectedItem ( [ lineId ] , false ) ;
124
- setShowDragLine ( 'end' ) ;
125
- onDragLineStart ?.( ) ;
138
+ if ( lineId ) addSelectedItem ( [ lineId ] , getCtrlKeyBySystem ( e . nativeEvent ) ) ;
126
139
if ( ! window ) return ;
127
140
141
+
128
142
const handleMouseMove = ( e : MouseEvent ) => {
143
+ if ( ! isDragging . current ) {
144
+ setShowDragLine ( 'end' ) ;
145
+ onDragLineStart ?.( ) ;
146
+ setDragLine ( { type : 'end' , nodeId, lineId } ) ;
147
+
148
+ isDragging . current = true ;
149
+ }
150
+
129
151
const newLeft = ( e . pageX - scrollObject . left . value ) / zoomObject . value ;
130
152
const newTop = ( e . pageY - scrollObject . top . value ) / zoomObject . value ;
131
153
@@ -134,6 +156,8 @@ export const DraggableLine: React.FC<IDraggableLineProps> = ({ lineId, newConnec
134
156
}
135
157
136
158
const handleMouseUp = ( ) => {
159
+ isDragging . current = false ;
160
+
137
161
setShowDragLine ( undefined ) ;
138
162
setRawLeft2 ( rest . left2 ) ;
139
163
setDragLine ( undefined ) ;
@@ -143,20 +167,51 @@ export const DraggableLine: React.FC<IDraggableLineProps> = ({ lineId, newConnec
143
167
window . removeEventListener ( 'mouseup' , handleMouseUp )
144
168
}
145
169
146
- handleMouseMove ( e . nativeEvent ) ;
147
-
148
- setDragLine ( { type : 'end' , nodeId, lineId } ) ;
149
170
window . addEventListener ( 'mousemove' , handleMouseMove )
150
171
window . addEventListener ( 'mouseup' , handleMouseUp )
151
- } , [ setDragLine , onDragLineStart , onDragLineEnd , window , scrollObject , zoomObject , linePath . y2 , linePath . x2 , rest . left2 , rest . top2 , nodeId , lineId ] ) ;
172
+ } , [ setDragLine , onDragLineStart , onDragLineEnd , window , scrollObject , zoomObject , rest . left2 , rest . top2 , nodeId , lineId ] ) ;
173
+
174
+ const handleMoveDown = useCallback ( ( event : React . MouseEvent < SVGPathElement > ) => {
175
+ const pathLength = event . currentTarget . getTotalLength ( ) ;
176
+
177
+ const clickX = event . clientX ;
178
+ const clickY = event . clientY ;
179
+
180
+ let closestPointLength = 0 ;
181
+ let closestDistance = Infinity ;
182
+
183
+ const segmentCount = 100 ;
184
+ for ( let i = 0 ; i <= segmentCount ; i ++ ) {
185
+ const pointLength = ( i / segmentCount ) * pathLength ;
186
+ const point = event . currentTarget . getPointAtLength ( pointLength ) ;
187
+
188
+ const distance = Math . sqrt (
189
+ Math . pow ( clickX - point . x , 2 ) + Math . pow ( clickY - point . y , 2 )
190
+ ) ;
191
+
192
+ if ( distance < closestDistance ) {
193
+ closestDistance = distance ;
194
+ closestPointLength = pointLength ;
195
+ }
196
+ }
197
+
198
+ const halfPathLength = pathLength / 2 ;
199
+ const isCloserToStart = closestPointLength <= halfPathLength ;
200
+
201
+ if ( isCloserToStart ) {
202
+ handleStartMouseDown ( event ) ;
203
+ } else {
204
+ handleEndMouseDown ( event ) ;
205
+ }
206
+ } , [ handleStartMouseDown , handleEndMouseDown ] ) ;
152
207
153
208
154
209
return (
155
210
< >
156
211
{ showDragLine && (
157
212
< path
158
213
fill = "none"
159
- d = { linePath2 }
214
+ d = { linePath }
160
215
stroke = "#0f77bf"
161
216
strokeLinecap = "round"
162
217
strokeWidth = { lineWidth }
@@ -166,40 +221,25 @@ export const DraggableLine: React.FC<IDraggableLineProps> = ({ lineId, newConnec
166
221
167
222
{ newConnection && (
168
223
< rect
169
- x = { rawLeft1 - 3 }
224
+ x = { rawLeft1 - 6.5 }
170
225
fill = 'transparent'
171
- width = { linePath . width1 }
172
- height = { ( linePath . height1 / 2 ) }
226
+ width = { rest . width1 + 15 }
227
+ height = { ( rest . height1 / 2 ) + 15 }
173
228
onMouseDown = { handleEndMouseDown }
174
- y = { rawTop1 + ( linePath . height1 / 2 ) + 2 }
229
+ y = { rawTop1 + ( rest . height1 / 2 ) }
175
230
style = { { cursor : 'crosshair' , pointerEvents : showDragLine ? 'none' : 'auto' } }
176
231
/>
177
232
) }
178
233
179
234
{ ( ! showDragLine && ! newConnection ) && (
180
- < >
181
- { ! disableStartDraggable && (
182
- < rect
183
- width = { 20 }
184
- height = { 20 }
185
- fill = 'transparent'
186
- y = { linePath . y1 - 10 }
187
- x = { linePath . x1 - 10 }
188
- onMouseDown = { handleStartMouseDown }
189
- style = { { cursor : 'crosshair' , pointerEvents : 'auto' } }
190
- />
191
- ) }
192
-
193
- < rect
194
- width = { 20 }
195
- height = { 20 }
196
- fill = 'transparent'
197
- y = { linePath . y2 - 10 }
198
- x = { linePath . x2 - 10 }
199
- onMouseDown = { handleEndMouseDown }
200
- style = { { cursor : 'crosshair' , pointerEvents : 'auto' } }
201
- />
202
- </ >
235
+ < path
236
+ fill = "none"
237
+ strokeWidth = { 14 }
238
+ stroke = "transparent"
239
+ onMouseDown = { handleMoveDown }
240
+ d = { isCurved ? curvedLinePath : linePath }
241
+ style = { { cursor : 'crosshair' , pointerEvents : 'auto' } }
242
+ />
203
243
) }
204
244
</ >
205
245
) ;
0 commit comments