1- import { generateUUID , getArrowPoints , setAttributes } from './utils/index'
1+ import { generateUUID , getArrowPoints , getOffsetLT , setAttributes } from './utils/index'
22import LinkDragMoveHelper from './utils/LinkDragMoveHelper'
33import { findEle } from './utils/dom'
4- import { createSvgGroup } from './utils/svg'
4+ import { createSvgGroup , editSvgText } from './utils/svg'
55import type { CustomSvg , Topic } from './types/dom'
66import type { MindElixirInstance , Uid } from './index'
77
8+ // p1: starting point
9+ // p2: control point of starting point
10+ // p3: control point of ending point
11+ // p4: ending point
12+
813export type LinkItem = {
914 id : string
1015 label : string
@@ -24,22 +29,42 @@ export type DivData = {
2429 cy : number // center y
2530 w : number // div width
2631 h : number // div height
32+ ctrlX : number // control point x
33+ ctrlY : number // control point y
34+ }
35+
36+ function calcCtrlP ( mei : MindElixirInstance , tpc : Topic , delta : { x : number ; y : number } ) {
37+ const { offsetLeft : x , offsetTop : y } = getOffsetLT ( mei . nodes , tpc )
38+ const w = tpc . offsetWidth
39+ const h = tpc . offsetHeight
40+ const cx = x + w / 2
41+ const cy = y + h / 2
42+ const ctrlX = cx + delta . x
43+ const ctrlY = cy + delta . y
44+ return {
45+ w,
46+ h,
47+ cx,
48+ cy,
49+ ctrlX,
50+ ctrlY,
51+ }
2752}
2853
2954// calc starting and ending point using control point and div status
30- function calcP ( data : DivData , ctrlX : number , ctrlY : number ) {
55+ function calcP ( data : DivData ) {
3156 let x , y
32- const k = ( data . cy - ctrlY ) / ( ctrlX - data . cx )
57+ const k = ( data . cy - data . ctrlY ) / ( data . ctrlX - data . cx )
3358 if ( k > data . h / data . w || k < - data . h / data . w ) {
34- if ( data . cy - ctrlY < 0 ) {
59+ if ( data . cy - data . ctrlY < 0 ) {
3560 x = data . cx - data . h / 2 / k
3661 y = data . cy + data . h / 2
3762 } else {
3863 x = data . cx + data . h / 2 / k
3964 y = data . cy - data . h / 2
4065 }
4166 } else {
42- if ( data . cx - ctrlX < 0 ) {
67+ if ( data . cx - data . ctrlX < 0 ) {
4368 x = data . cx + data . w / 2
4469 y = data . cy - ( data . w * k ) / 2
4570 } else {
@@ -66,103 +91,60 @@ const createText = function (string: string, x: number, y: number, color?: strin
6691 return text
6792}
6893
69- export const createLink = function ( this : MindElixirInstance , from : Topic , to : Topic , isInitPaint ?: boolean , obj ?: LinkItem ) {
94+ export const drawCustomLink = function ( this : MindElixirInstance , from : Topic , to : Topic , obj : LinkItem , isInitPaint ?: boolean ) {
7095 if ( ! from || ! to ) {
7196 return // not expand
7297 }
98+ const start = performance . now ( )
7399 this . hideLinkController ( )
74- const map = this . map . getBoundingClientRect ( )
75- const pfrom = from . getBoundingClientRect ( )
76- const pto = to . getBoundingClientRect ( )
77- const fromCenterX = ( pfrom . x + pfrom . width / 2 - map . x ) / this . scaleVal
78- const fromCenterY = ( pfrom . y + pfrom . height / 2 - map . y ) / this . scaleVal
79- const toCenterX = ( pto . x + pto . width / 2 - map . x ) / this . scaleVal
80- const toCenterY = ( pto . y + pto . height / 2 - map . y ) / this . scaleVal
100+ const fromData = calcCtrlP ( this , from , obj . delta1 )
101+ const toData = calcCtrlP ( this , to , obj . delta2 )
81102
82- // p1: starting point
83- // p2: control point of starting point
84- // p3: control point of ending point
85- // p4: ending point
86-
87- let p2x , p2y , p3x , p3y
88- if ( isInitPaint && obj ) {
89- p2x = fromCenterX + obj . delta1 . x
90- p2y = fromCenterY + obj . delta1 . y
91- p3x = toCenterX + obj . delta2 . x
92- p3y = toCenterY + obj . delta2 . y
93- } else {
94- if ( ( fromCenterY + toCenterY ) / 2 - fromCenterY <= pfrom . height / 2 ) {
95- // the situation that two div is too close
96- p2x = ( pfrom . x + pfrom . width - map . x ) / this . scaleVal + 100
97- p2y = fromCenterY
98- p3x = ( pto . x + pto . width - map . x ) / this . scaleVal + 100
99- p3y = toCenterY
100- } else {
101- p2x = ( fromCenterX + toCenterX ) / 2
102- p2y = ( fromCenterY + toCenterY ) / 2
103- p3x = ( fromCenterX + toCenterX ) / 2
104- p3y = ( fromCenterY + toCenterY ) / 2
105- }
106- }
107-
108- const fromData = {
109- cx : fromCenterX ,
110- cy : fromCenterY ,
111- w : pfrom . width ,
112- h : pfrom . height ,
113- }
114- const toData = {
115- cx : toCenterX ,
116- cy : toCenterY ,
117- w : pto . width ,
118- h : pto . height ,
119- }
120-
121- const { x : p1x , y : p1y } = calcP ( fromData , p2x , p2y )
122- const { x : p4x , y : p4y } = calcP ( toData , p3x , p3y )
103+ const { x : p1x , y : p1y } = calcP ( fromData )
104+ const { ctrlX : p2x , ctrlY : p2y } = fromData
105+ const { ctrlX : p3x , ctrlY : p3y } = toData
106+ const { x : p4x , y : p4y } = calcP ( toData )
123107
124108 const arrowPoint = getArrowPoints ( p3x , p3y , p4x , p4y )
125109
126- const newLinkObj = {
127- id : '' ,
128- label : obj ?. label || 'custom link' ,
129- from : from . nodeObj . id ,
130- to : to . nodeObj . id ,
131- delta1 : {
132- x : p2x - fromCenterX ,
133- y : p2y - fromCenterY ,
134- } ,
135- delta2 : {
136- x : p3x - toCenterX ,
137- y : p3y - toCenterY ,
138- } ,
139- }
140110 const newSvgGroup = createSvgGroup (
141111 `M ${ p1x } ${ p1y } C ${ p2x } ${ p2y } ${ p3x } ${ p3y } ${ p4x } ${ p4y } ` ,
142112 `M ${ arrowPoint . x1 } ${ arrowPoint . y1 } L ${ p4x } ${ p4y } L ${ arrowPoint . x2 } ${ arrowPoint . y2 } `
143113 )
144114
145115 const halfx = p1x / 8 + ( p2x * 3 ) / 8 + ( p3x * 3 ) / 8 + p4x / 8
146116 const halfy = p1y / 8 + ( p2y * 3 ) / 8 + ( p3y * 3 ) / 8 + p4y / 8
147- const label = createText ( newLinkObj . label , halfx , halfy , this . theme . cssVar [ '--color' ] )
117+ const label = createText ( obj . label , halfx , halfy , this . theme . cssVar [ '--color' ] )
148118 newSvgGroup . appendChild ( label )
149119
150- if ( isInitPaint && obj ) {
151- newLinkObj . id = obj . id
152- // overwrite
153- this . linkData [ obj . id ] = newLinkObj
154- } else {
155- // new
156- newLinkObj . id = generateUUID ( )
157- this . linkData [ newLinkObj . id ] = newLinkObj
158- this . currentLink = newSvgGroup
159- }
160- newSvgGroup . linkObj = newLinkObj
161- newSvgGroup . dataset . linkid = newLinkObj . id
120+ newSvgGroup . linkObj = obj
121+ newSvgGroup . dataset . linkid = obj . id
162122 this . linkSvgGroup . appendChild ( newSvgGroup )
163123 if ( ! isInitPaint ) {
164- this . showLinkController ( p2x , p2y , p3x , p3y , newLinkObj , fromData , toData )
124+ this . linkData [ obj . id ] = obj
125+ this . currentLink = newSvgGroup
126+ this . showLinkController ( obj , fromData , toData )
165127 }
128+ const end = performance . now ( )
129+ console . log ( `DrawCustomLink Execution time: ${ end - start } ms` )
130+ }
131+
132+ export const createLink = function ( this : MindElixirInstance , from : Topic , to : Topic ) {
133+ const newLinkObj = {
134+ id : generateUUID ( ) ,
135+ label : 'Custom Link' ,
136+ from : from . nodeObj . id ,
137+ to : to . nodeObj . id ,
138+ delta1 : {
139+ x : 0 ,
140+ y : - 200 ,
141+ } ,
142+ delta2 : {
143+ x : 0 ,
144+ y : - 200 ,
145+ } ,
146+ }
147+ this . drawCustomLink ( from , to , newLinkObj )
166148}
167149
168150export const removeLink = function ( this : MindElixirInstance , linkSvg ?: CustomSvg ) {
@@ -175,68 +157,43 @@ export const removeLink = function (this: MindElixirInstance, linkSvg?: CustomSv
175157 if ( ! link ) return
176158 this . hideLinkController ( )
177159 const id = link . linkObj ! . id
178- console . log ( id )
179160 delete this . linkData [ id ]
180161 link . remove ( )
181- link = null
162+ link = null // useless
182163}
183164
184165export const selectLink = function ( this : MindElixirInstance , link : CustomSvg ) {
185166 this . currentLink = link
186167 const obj = link . linkObj
187- if ( ! obj ) return
188- const from = obj . from
189- const to = obj . to
190168
191- const map = this . map . getBoundingClientRect ( )
192- const pfrom = findEle ( from ) . getBoundingClientRect ( )
193- const pto = findEle ( to ) . getBoundingClientRect ( )
194- const fromCenterX = ( pfrom . x + pfrom . width / 2 - map . x ) / this . scaleVal
195- const fromCenterY = ( pfrom . y + pfrom . height / 2 - map . y ) / this . scaleVal
196- const toCenterX = ( pto . x + pto . width / 2 - map . x ) / this . scaleVal
197- const toCenterY = ( pto . y + pto . height / 2 - map . y ) / this . scaleVal
169+ const from = findEle ( obj . from )
170+ const to = findEle ( obj . to )
198171
199- const fromData = {
200- cx : fromCenterX ,
201- cy : fromCenterY ,
202- w : pfrom . width ,
203- h : pfrom . height ,
204- }
205- const toData = {
206- cx : toCenterX ,
207- cy : toCenterY ,
208- w : pto . width ,
209- h : pto . height ,
210- }
172+ const fromData = calcCtrlP ( this , from , obj . delta1 )
173+ const toData = calcCtrlP ( this , to , obj . delta2 )
211174
212- const p2x = fromCenterX + obj . delta1 . x
213- const p2y = fromCenterY + obj . delta1 . y
214- const p3x = toCenterX + obj . delta2 . x
215- const p3y = toCenterY + obj . delta2 . y
216-
217- this . showLinkController ( p2x , p2y , p3x , p3y , obj , fromData , toData )
175+ this . showLinkController ( obj , fromData , toData )
218176}
177+
219178export const hideLinkController = function ( this : MindElixirInstance ) {
220179 this . linkController . style . display = 'none'
221180 this . P2 . style . display = 'none'
222181 this . P3 . style . display = 'none'
223182}
224- export const showLinkController = function (
225- this : MindElixirInstance ,
226- p2x : number ,
227- p2y : number ,
228- p3x : number ,
229- p3y : number ,
230- linkItem : LinkItem ,
231- fromData : DivData ,
232- toData : DivData
233- ) {
183+
184+ export const showLinkController = function ( this : MindElixirInstance , linkItem : LinkItem , fromData : DivData , toData : DivData ) {
234185 this . linkController . style . display = 'initial'
235186 this . P2 . style . display = 'initial'
236187 this . P3 . style . display = 'initial'
188+ this . nodes . appendChild ( this . linkController )
189+ this . nodes . appendChild ( this . P2 )
190+ this . nodes . appendChild ( this . P3 )
237191
238- let { x : p1x , y : p1y } = calcP ( fromData , p2x , p2y )
239- let { x : p4x , y : p4y } = calcP ( toData , p3x , p3y )
192+ // init points
193+ let { x : p1x , y : p1y } = calcP ( fromData )
194+ let { ctrlX : p2x , ctrlY : p2y } = fromData
195+ let { ctrlX : p3x , ctrlY : p3y } = toData
196+ let { x : p4x , y : p4y } = calcP ( toData )
240197
241198 this . P2 . style . cssText = `top:${ p2y } px;left:${ p2x } px;`
242199 this . P3 . style . cssText = `top:${ p3y } px;left:${ p3x } px;`
@@ -261,24 +218,20 @@ export const showLinkController = function (
261218 this . helper1 = LinkDragMoveHelper . create ( this . P2 )
262219 this . helper2 = LinkDragMoveHelper . create ( this . P3 )
263220
221+ // TODO: generate cb function
264222 this . helper1 . init ( this . map , ( deltaX , deltaY ) => {
265- /**
266- * user will control bezier with p2 & p3
267- * p1 & p4 is depend on p2 & p3
268- */
223+ // recalc key points
269224 p2x = p2x - deltaX / this . scaleVal
270225 p2y = p2y - deltaY / this . scaleVal
271-
272- const p1 = calcP ( fromData , p2x , p2y )
226+ const p1 = calcP ( { ...fromData , ctrlX : p2x , ctrlY : p2y } )
273227 p1x = p1 . x
274228 p1y = p1 . y
275-
229+ const halfx = p1x / 8 + ( p2x * 3 ) / 8 + ( p3x * 3 ) / 8 + p4x / 8
230+ const halfy = p1y / 8 + ( p2y * 3 ) / 8 + ( p3y * 3 ) / 8 + p4y / 8
231+ // update dom position
276232 this . P2 . style . top = p2y + 'px'
277233 this . P2 . style . left = p2x + 'px'
278234 this . currentLink ?. children [ 0 ] . setAttribute ( 'd' , `M ${ p1x } ${ p1y } C ${ p2x } ${ p2y } ${ p3x } ${ p3y } ${ p4x } ${ p4y } ` )
279-
280- const halfx = p1x / 8 + ( p2x * 3 ) / 8 + ( p3x * 3 ) / 8 + p4x / 8
281- const halfy = p1y / 8 + ( p2y * 3 ) / 8 + ( p3y * 3 ) / 8 + p4y / 8
282235 setAttributes ( this . currentLink ! . children [ 2 ] , {
283236 x : halfx + '' ,
284237 y : halfy + '' ,
@@ -289,30 +242,30 @@ export const showLinkController = function (
289242 x2 : p2x + '' ,
290243 y2 : p2y + '' ,
291244 } )
245+ // update linkItem
292246 linkItem . delta1 . x = p2x - fromData . cx
293247 linkItem . delta1 . y = p2y - fromData . cy
294248 } )
295249
296250 this . helper2 . init ( this . map , ( deltaX , deltaY ) => {
297251 p3x = p3x - deltaX / this . scaleVal
298252 p3y = p3y - deltaY / this . scaleVal
299-
300- const p4 = calcP ( toData , p3x , p3y )
253+ const p4 = calcP ( { ...toData , ctrlX : p3x , ctrlY : p3y } )
301254 p4x = p4 . x
302255 p4y = p4 . y
256+ const halfx = p1x / 8 + ( p2x * 3 ) / 8 + ( p3x * 3 ) / 8 + p4x / 8
257+ const halfy = p1y / 8 + ( p2y * 3 ) / 8 + ( p3y * 3 ) / 8 + p4y / 8
303258 const arrowPoint = getArrowPoints ( p3x , p3y , p4x , p4y )
304259
305260 this . P3 . style . top = p3y + 'px'
306261 this . P3 . style . left = p3x + 'px'
307262 this . currentLink ?. children [ 0 ] . setAttribute ( 'd' , `M ${ p1x } ${ p1y } C ${ p2x } ${ p2y } ${ p3x } ${ p3y } ${ p4x } ${ p4y } ` )
308263 this . currentLink ?. children [ 1 ] . setAttribute ( 'd' , `M ${ arrowPoint . x1 } ${ arrowPoint . y1 } L ${ p4x } ${ p4y } L ${ arrowPoint . x2 } ${ arrowPoint . y2 } ` )
309-
310- const halfx = p1x / 8 + ( p2x * 3 ) / 8 + ( p3x * 3 ) / 8 + p4x / 8
311- const halfy = p1y / 8 + ( p2y * 3 ) / 8 + ( p3y * 3 ) / 8 + p4y / 8
312264 setAttributes ( this . currentLink ! . children [ 2 ] , {
313265 x : halfx + '' ,
314266 y : halfy + '' ,
315267 } )
268+ // TODO: absctract this
316269 setAttributes ( this . line2 , {
317270 x1 : p3x + '' ,
318271 y1 : p3y + '' ,
@@ -328,6 +281,30 @@ export function renderCustomLink(this: MindElixirInstance) {
328281 this . linkSvgGroup . innerHTML = ''
329282 for ( const prop in this . linkData ) {
330283 const link = this . linkData [ prop ]
331- this . createLink ( findEle ( link . from ) , findEle ( link . to ) , true , link )
284+ this . drawCustomLink ( findEle ( link . from ) , findEle ( link . to ) , link , true )
332285 }
286+ this . nodes . appendChild ( this . linkSvgGroup )
287+ }
288+
289+ export function editCutsomLinkLabel ( this : MindElixirInstance , el : CustomSvg ) {
290+ console . time ( 'editSummary' )
291+ if ( ! el ) return
292+ const textEl = el . children [ 2 ]
293+ console . log ( textEl , el )
294+ editSvgText ( this , textEl , div => {
295+ const node = el . linkObj
296+ const text = div . textContent ?. trim ( ) || ''
297+ if ( text === '' ) node . label = origin
298+ else node . label = text
299+ div . remove ( )
300+ if ( text === origin ) return
301+ textEl . innerHTML = node . label
302+ this . linkDiv ( )
303+ // this.bus.fire('operation', {
304+ // name: 'finishEditSummary',
305+ // obj: node,
306+ // origin,
307+ // })
308+ } )
309+ console . timeEnd ( 'editSummary' )
333310}
0 commit comments