Skip to content

Commit 4ed9421

Browse files
committed
refactor: fully refactor customlink module
1 parent 016cd1d commit 4ed9421

File tree

10 files changed

+202
-201
lines changed

10 files changed

+202
-201
lines changed

src/customLink.ts

Lines changed: 118 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
import { generateUUID, getArrowPoints, setAttributes } from './utils/index'
1+
import { generateUUID, getArrowPoints, getOffsetLT, setAttributes } from './utils/index'
22
import LinkDragMoveHelper from './utils/LinkDragMoveHelper'
33
import { findEle } from './utils/dom'
4-
import { createSvgGroup } from './utils/svg'
4+
import { createSvgGroup, editSvgText } from './utils/svg'
55
import type { CustomSvg, Topic } from './types/dom'
66
import 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+
813
export 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

168150
export 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

184165
export 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+
219178
export 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

Comments
 (0)