jvs-ppt-ui 0.0.8 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -2
- package/dist/lib/jvs-ppt-ui.es.js +17099 -16996
- package/dist/lib/jvs-ppt-ui.umd.js +77 -77
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CanvasArea.vue +40 -10
- package/src/components/Header.vue +12 -4
- package/src/components/PptEditor.vue +8 -1
- package/src/components/Toolbar.vue +2 -1
- package/src/composables/canvas/useCanvasTransform.ts +13 -0
- package/src/composables/useSlides.ts +13 -5
- package/src/types/index.ts +4 -0
- package/src/utils/line.ts +274 -312
package/src/utils/line.ts
CHANGED
|
@@ -150,143 +150,246 @@ export function getLinePointByOffset(
|
|
|
150
150
|
] as [number, number]
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
+
function getElementBox(el: any, fallbackPt: [number, number], logWidth = 1000, logHeight = 562.5) {
|
|
154
|
+
if (!el) {
|
|
155
|
+
return { left: fallbackPt[0] - 10, right: fallbackPt[0] + 10, top: fallbackPt[1] - 10, bottom: fallbackPt[1] + 10 }
|
|
156
|
+
}
|
|
157
|
+
const style = el.style || {}
|
|
158
|
+
const left = getPxVal(style.left || el.left, logWidth)
|
|
159
|
+
const top = getPxVal(style.top || el.top, logHeight)
|
|
160
|
+
const width = getPxVal(style.width || el.width, logWidth) || 20
|
|
161
|
+
const height = getPxVal(style.height || el.height, logHeight) || 20
|
|
162
|
+
return { left, top, right: left + width, bottom: top + height }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function getAnchorNormal(idx: number | undefined, isEllipse = false): [number, number] | null {
|
|
166
|
+
if (idx === undefined) return null
|
|
167
|
+
if (isEllipse) {
|
|
168
|
+
if (idx === 0) return [0, -1]
|
|
169
|
+
if (idx === 1) return [-1, -1]
|
|
170
|
+
if (idx === 2) return [-1, 0]
|
|
171
|
+
if (idx === 3) return [-1, 1]
|
|
172
|
+
if (idx === 4) return [0, 1]
|
|
173
|
+
if (idx === 5) return [1, 1]
|
|
174
|
+
if (idx === 6) return [1, 0]
|
|
175
|
+
if (idx === 7) return [1, -1]
|
|
176
|
+
} else {
|
|
177
|
+
const k = idx % 4
|
|
178
|
+
if (k === 0) return [0, -1]
|
|
179
|
+
if (k === 1) return [-1, 0]
|
|
180
|
+
if (k === 2) return [0, 1]
|
|
181
|
+
if (k === 3) return [1, 0]
|
|
182
|
+
}
|
|
183
|
+
return null
|
|
184
|
+
}
|
|
185
|
+
|
|
153
186
|
function getDefaultOrthogonalTurnPoints(
|
|
154
187
|
x1: number, y1: number,
|
|
155
188
|
x2: number, y2: number,
|
|
156
189
|
element: any,
|
|
157
190
|
startEl?: any,
|
|
158
|
-
endEl?: any
|
|
191
|
+
endEl?: any,
|
|
192
|
+
obstacles?: any[]
|
|
159
193
|
): [number, number][] {
|
|
160
|
-
|
|
194
|
+
const pad = 20
|
|
195
|
+
const isStartEllipse = isEllipseShape(startEl)
|
|
196
|
+
const isEndEllipse = isEllipseShape(endEl)
|
|
161
197
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
const sBox = getElementBox(startEl, [x1, y1])
|
|
199
|
+
const eBox = getElementBox(endEl, [x2, y2])
|
|
200
|
+
|
|
201
|
+
const idx1 = element.startConnection?.idx
|
|
202
|
+
const idx2 = element.endConnection?.idx
|
|
203
|
+
|
|
204
|
+
let sNorm = getAnchorNormal(idx1, isStartEllipse)
|
|
205
|
+
let eNorm = getAnchorNormal(idx2, isEndEllipse)
|
|
206
|
+
|
|
207
|
+
let eEntryNorm: [number, number] | null = eNorm ? [-eNorm[0], -eNorm[1]] : null
|
|
208
|
+
|
|
209
|
+
if (!sNorm && !eEntryNorm) {
|
|
210
|
+
if (Math.abs(x2 - x1) >= Math.abs(y2 - y1)) {
|
|
211
|
+
return [[x2, y1]]
|
|
212
|
+
} else {
|
|
213
|
+
return [[x1, y2]]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!sNorm && eEntryNorm) {
|
|
218
|
+
sNorm = eEntryNorm[0] !== 0 ? [0, y2 >= y1 ? 1 : -1] : [x2 >= x1 ? 1 : -1, 0]
|
|
219
|
+
} else if (sNorm && !eEntryNorm) {
|
|
220
|
+
eEntryNorm = sNorm[0] !== 0 ? [0, y2 >= y1 ? 1 : -1] : [x2 >= x1 ? 1 : -1, 0]
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!sNorm || !eEntryNorm) return [[(x1 + x2) / 2, y1], [(x1 + x2) / 2, y2]]
|
|
224
|
+
|
|
225
|
+
const sDx = sNorm[0]
|
|
226
|
+
const sDy = sNorm[1]
|
|
227
|
+
const eDx = eEntryNorm[0]
|
|
228
|
+
const eDy = eEntryNorm[1]
|
|
229
|
+
|
|
230
|
+
// 1. Can we do a clean 1-turn (L-shape)?
|
|
231
|
+
if (sDx !== 0 && sDy === 0 && eDx === 0 && eDy !== 0) {
|
|
232
|
+
if ((x2 - x1) * sDx > 0 && (y2 - y1) * eDy > 0) {
|
|
233
|
+
return [[x2, y1]]
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (sDy !== 0 && sDx === 0 && eDy === 0 && eDx !== 0) {
|
|
237
|
+
if ((y2 - y1) * sDy > 0 && (x2 - x1) * eDx > 0) {
|
|
238
|
+
return [[x1, y2]]
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// 2. Parallel cases
|
|
243
|
+
if (sDx !== 0 && sDy === 0 && eDx !== 0 && eDy === 0) {
|
|
244
|
+
if (sDx === eDx) {
|
|
245
|
+
const mx = sDx > 0 ? Math.max(x1, x2, sBox.right, eBox.right) + pad : Math.min(x1, x2, sBox.left, eBox.left) - pad
|
|
246
|
+
return [[mx, y1], [mx, y2]]
|
|
247
|
+
} else if ((x2 - x1) * sDx > 0) {
|
|
248
|
+
const mx = (x1 + x2) / 2
|
|
249
|
+
return [[mx, y1], [mx, y2]]
|
|
250
|
+
}
|
|
251
|
+
}
|
|
179
252
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
253
|
+
if (sDy !== 0 && sDx === 0 && eDy !== 0 && eDx === 0) {
|
|
254
|
+
if (sDy === eDy) {
|
|
255
|
+
const my = sDy > 0 ? Math.max(y1, y2, sBox.bottom, eBox.bottom) + pad : Math.min(y1, y2, sBox.top, eBox.top) - pad
|
|
256
|
+
return [[x1, my], [x2, my]]
|
|
257
|
+
} else if ((y2 - y1) * sDy > 0) {
|
|
258
|
+
const my = (y1 + y2) / 2
|
|
259
|
+
return [[x1, my], [x2, my]]
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// 3. Perpendicular wrap-around (Start H, End V)
|
|
264
|
+
if (sDx !== 0 && eDy !== 0) {
|
|
265
|
+
const xExit = sDx > 0 ? Math.max(x1 + pad, sBox.right + pad) : Math.min(x1 - pad, sBox.left - pad)
|
|
266
|
+
let yMid: number
|
|
267
|
+
if (eDy > 0) {
|
|
268
|
+
if (y2 > sBox.bottom + pad) {
|
|
269
|
+
yMid = (sBox.bottom + y2) / 2
|
|
270
|
+
if (obstacles && obstacles.length > 0 && isSegmentObstructed(xExit, yMid, x2, yMid, obstacles)) {
|
|
271
|
+
const inBetween = obstacles.filter(obs => obs.bottom > sBox.bottom && obs.top < y2)
|
|
272
|
+
for (const obs of inBetween) {
|
|
273
|
+
const candidateAbove = (sBox.bottom + obs.top) / 2
|
|
274
|
+
if (!isSegmentObstructed(xExit, candidateAbove, x2, candidateAbove, obstacles)) {
|
|
275
|
+
yMid = candidateAbove
|
|
276
|
+
break
|
|
277
|
+
}
|
|
278
|
+
const candidateBelow = (obs.bottom + y2) / 2
|
|
279
|
+
if (!isSegmentObstructed(xExit, candidateBelow, x2, candidateBelow, obstacles)) {
|
|
280
|
+
yMid = candidateBelow
|
|
281
|
+
break
|
|
282
|
+
}
|
|
283
|
+
}
|
|
191
284
|
}
|
|
192
|
-
absTurnPoints = [[mx, y1], [mx, y2]]
|
|
193
285
|
} else {
|
|
194
|
-
|
|
195
|
-
if (idx1 === idx2) {
|
|
196
|
-
const isBottom = (idx1 === 2 && !isStartEllipse) || (idx1 === 4 && isStartEllipse)
|
|
197
|
-
const isTop = (idx1 === 0 && !isStartEllipse) || (idx1 === 0 && isStartEllipse)
|
|
198
|
-
if (isBottom) my = Math.max(y1, y2) + 30
|
|
199
|
-
else if (isTop) my = Math.min(y1, y2) - 30
|
|
200
|
-
}
|
|
201
|
-
absTurnPoints = [[x1, my], [x2, my]]
|
|
286
|
+
yMid = Math.min(sBox.top - pad, eBox.top - pad)
|
|
202
287
|
}
|
|
203
288
|
} else {
|
|
204
|
-
if (
|
|
205
|
-
|
|
289
|
+
if (y2 < sBox.top - pad) {
|
|
290
|
+
yMid = (sBox.top + y2) / 2
|
|
291
|
+
if (obstacles && obstacles.length > 0 && isSegmentObstructed(xExit, yMid, x2, yMid, obstacles)) {
|
|
292
|
+
const inBetween = obstacles.filter(obs => obs.top < sBox.top && obs.bottom > y2)
|
|
293
|
+
for (const obs of inBetween) {
|
|
294
|
+
const candidateBelow = (sBox.top + obs.bottom) / 2
|
|
295
|
+
if (!isSegmentObstructed(xExit, candidateBelow, x2, candidateBelow, obstacles)) {
|
|
296
|
+
yMid = candidateBelow
|
|
297
|
+
break
|
|
298
|
+
}
|
|
299
|
+
const candidateAbove = (obs.top + y2) / 2
|
|
300
|
+
if (!isSegmentObstructed(xExit, candidateAbove, x2, candidateAbove, obstacles)) {
|
|
301
|
+
yMid = candidateAbove
|
|
302
|
+
break
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
206
306
|
} else {
|
|
207
|
-
|
|
307
|
+
yMid = Math.max(sBox.bottom + pad, eBox.bottom + pad)
|
|
208
308
|
}
|
|
209
309
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
isStartHoriz = (idx1 === 2 || idx1 === 6)
|
|
217
|
-
}
|
|
218
|
-
if (isStartHoriz) {
|
|
219
|
-
const mx = (x1 + x2) / 2
|
|
220
|
-
absTurnPoints = [[mx, y1], [mx, y2]]
|
|
221
|
-
} else {
|
|
222
|
-
const my = (y1 + y2) / 2
|
|
223
|
-
absTurnPoints = [[x1, my], [x2, my]]
|
|
224
|
-
}
|
|
225
|
-
} else if (!element.startConnection && element.endConnection) {
|
|
226
|
-
const isEndEllipse = isEllipseShape(endEl)
|
|
227
|
-
const idx2 = element.endConnection.idx
|
|
228
|
-
let isEndHoriz = (idx2 === 1 || idx2 === 3)
|
|
229
|
-
if (isEndEllipse) {
|
|
230
|
-
isEndHoriz = (idx2 === 2 || idx2 === 6)
|
|
231
|
-
}
|
|
232
|
-
if (isEndHoriz) {
|
|
233
|
-
const mx = (x1 + x2) / 2
|
|
234
|
-
absTurnPoints = [[mx, y1], [mx, y2]]
|
|
235
|
-
} else {
|
|
236
|
-
const my = (y1 + y2) / 2
|
|
237
|
-
absTurnPoints = [[x1, my], [x2, my]]
|
|
238
|
-
}
|
|
239
|
-
} else if (element.startConnection && element.endConnection) {
|
|
240
|
-
const isStartEllipse = isEllipseShape(startEl)
|
|
241
|
-
const isEndEllipse = isEllipseShape(endEl)
|
|
242
|
-
const idx1 = element.startConnection.idx
|
|
243
|
-
const idx2 = element.endConnection.idx
|
|
244
|
-
|
|
245
|
-
let isStartHoriz = (idx1 === 1 || idx1 === 3)
|
|
246
|
-
if (isStartEllipse) {
|
|
247
|
-
isStartHoriz = (idx1 === 2 || idx1 === 6)
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
let isEndHoriz = (idx2 === 1 || idx2 === 3)
|
|
251
|
-
if (isEndEllipse) {
|
|
252
|
-
isEndHoriz = (idx2 === 2 || idx2 === 6)
|
|
253
|
-
}
|
|
310
|
+
return [
|
|
311
|
+
[xExit, y1],
|
|
312
|
+
[xExit, yMid],
|
|
313
|
+
[x2, yMid]
|
|
314
|
+
]
|
|
315
|
+
}
|
|
254
316
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
317
|
+
// 4. Perpendicular wrap-around (Start V, End H)
|
|
318
|
+
if (sDy !== 0 && eDx !== 0) {
|
|
319
|
+
const yExit = sDy > 0 ? Math.max(y1 + pad, sBox.bottom + pad) : Math.min(y1 - pad, sBox.top - pad)
|
|
320
|
+
let xMid: number
|
|
321
|
+
if (eDx > 0) {
|
|
322
|
+
if (x2 > sBox.right + pad) {
|
|
323
|
+
xMid = (sBox.right + x2) / 2
|
|
324
|
+
if (obstacles && obstacles.length > 0 && isSegmentObstructed(xMid, yExit, xMid, y2, obstacles)) {
|
|
325
|
+
const inBetween = obstacles.filter(obs => obs.right > sBox.right && obs.left < x2)
|
|
326
|
+
for (const obs of inBetween) {
|
|
327
|
+
const candidateLeft = (sBox.right + obs.left) / 2
|
|
328
|
+
if (!isSegmentObstructed(candidateLeft, yExit, candidateLeft, y2, obstacles)) {
|
|
329
|
+
xMid = candidateLeft
|
|
330
|
+
break
|
|
331
|
+
}
|
|
332
|
+
const candidateRight = (obs.right + x2) / 2
|
|
333
|
+
if (!isSegmentObstructed(candidateRight, yExit, candidateRight, y2, obstacles)) {
|
|
334
|
+
xMid = candidateRight
|
|
335
|
+
break
|
|
336
|
+
}
|
|
337
|
+
}
|
|
271
338
|
}
|
|
272
|
-
absTurnPoints = [[x1, my], [x2, my]]
|
|
273
|
-
} else if (isStartHoriz && !isEndHoriz) {
|
|
274
|
-
absTurnPoints = [[x2, y1]]
|
|
275
339
|
} else {
|
|
276
|
-
|
|
340
|
+
xMid = Math.min(sBox.left - pad, eBox.left - pad)
|
|
277
341
|
}
|
|
278
342
|
} else {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
343
|
+
if (x2 < sBox.left - pad) {
|
|
344
|
+
xMid = (sBox.left + x2) / 2
|
|
345
|
+
if (obstacles && obstacles.length > 0 && isSegmentObstructed(xMid, yExit, xMid, y2, obstacles)) {
|
|
346
|
+
const inBetween = obstacles.filter(obs => obs.left < sBox.left && obs.right > x2)
|
|
347
|
+
for (const obs of inBetween) {
|
|
348
|
+
const candidateRight = (sBox.left + obs.right) / 2
|
|
349
|
+
if (!isSegmentObstructed(candidateRight, yExit, candidateRight, y2, obstacles)) {
|
|
350
|
+
xMid = candidateRight
|
|
351
|
+
break
|
|
352
|
+
}
|
|
353
|
+
const candidateLeft = (obs.left + x2) / 2
|
|
354
|
+
if (!isSegmentObstructed(candidateLeft, yExit, candidateLeft, y2, obstacles)) {
|
|
355
|
+
xMid = candidateLeft
|
|
356
|
+
break
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
283
360
|
} else {
|
|
284
|
-
|
|
285
|
-
absTurnPoints = [[x1, my], [x2, my]]
|
|
361
|
+
xMid = Math.max(sBox.right + pad, eBox.right + pad)
|
|
286
362
|
}
|
|
287
363
|
}
|
|
364
|
+
return [
|
|
365
|
+
[x1, yExit],
|
|
366
|
+
[xMid, yExit],
|
|
367
|
+
[xMid, y2]
|
|
368
|
+
]
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// 5. Parallel facing opposite directions and e is behind s
|
|
372
|
+
if (sDx !== 0) {
|
|
373
|
+
const xExit = sDx > 0 ? Math.max(x1 + pad, sBox.right + pad) : Math.min(x1 - pad, sBox.left - pad)
|
|
374
|
+
const yAround = y1 < y2 ? Math.min(sBox.top - pad, eBox.top - pad) : Math.max(sBox.bottom + pad, eBox.bottom + pad)
|
|
375
|
+
const xEntry = eDx > 0 ? Math.min(x2 - pad, eBox.left - pad) : Math.max(x2 + pad, eBox.right + pad)
|
|
376
|
+
return [
|
|
377
|
+
[xExit, y1],
|
|
378
|
+
[xExit, yAround],
|
|
379
|
+
[xEntry, yAround],
|
|
380
|
+
[xEntry, y2]
|
|
381
|
+
]
|
|
382
|
+
} else {
|
|
383
|
+
const yExit = sDy > 0 ? Math.max(y1 + pad, sBox.bottom + pad) : Math.min(y1 - pad, sBox.top - pad)
|
|
384
|
+
const xAround = x1 < x2 ? Math.min(sBox.left - pad, eBox.left - pad) : Math.max(sBox.right + pad, eBox.right + pad)
|
|
385
|
+
const yEntry = eDy > 0 ? Math.min(y2 - pad, eBox.top - pad) : Math.max(y2 + pad, eBox.bottom + pad)
|
|
386
|
+
return [
|
|
387
|
+
[x1, yExit],
|
|
388
|
+
[xAround, yExit],
|
|
389
|
+
[xAround, yEntry],
|
|
390
|
+
[x2, yEntry]
|
|
391
|
+
]
|
|
288
392
|
}
|
|
289
|
-
return absTurnPoints
|
|
290
393
|
}
|
|
291
394
|
|
|
292
395
|
export function getLinePathTurningPoints(
|
|
@@ -297,25 +400,34 @@ export function getLinePathTurningPoints(
|
|
|
297
400
|
): [number, number][] {
|
|
298
401
|
const isCustom = element.customControlPoints
|
|
299
402
|
|
|
300
|
-
const leftPx = getPxVal(element.style.left, 1000)
|
|
301
|
-
const topPx = getPxVal(element.style.top, 562.5)
|
|
403
|
+
const leftPx = getPxVal(element.style?.left || element.left, 1000)
|
|
404
|
+
const topPx = getPxVal(element.style?.top || element.top, 562.5)
|
|
302
405
|
const rawStart = Array.isArray(element.start) ? element.start : [0, 0]
|
|
303
406
|
const rawEnd = Array.isArray(element.end) ? element.end : [getPxVal(element.style?.width || element.width, 1000) || 0, getPxVal(element.style?.height || element.height, 562.5) || 0]
|
|
304
407
|
const s: [number, number] = absStart || [leftPx + rawStart[0], topPx + rawStart[1]]
|
|
305
408
|
const e: [number, number] = absEnd || [leftPx + rawEnd[0], topPx + rawEnd[1]]
|
|
306
409
|
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
410
|
+
const findElementRecursive = (list: any[], id: string): any => {
|
|
411
|
+
for (const it of list) {
|
|
412
|
+
if (!it) continue
|
|
413
|
+
if (String(it.id) === String(id)) return it
|
|
414
|
+
if (it.type === 'group' && Array.isArray(it.elements)) {
|
|
415
|
+
const found = findElementRecursive(it.elements, id)
|
|
416
|
+
if (found) return found
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return null
|
|
420
|
+
}
|
|
421
|
+
const startEl = (element.startConnection && elements) ? findElementRecursive(elements, element.startConnection.id) : null
|
|
422
|
+
const endEl = (element.endConnection && elements) ? findElementRecursive(elements, element.endConnection.id) : null
|
|
311
423
|
|
|
312
|
-
|
|
313
|
-
const obstacles = getSlideObstacles(element, elements)
|
|
314
|
-
if (obstacles.length > 0) {
|
|
315
|
-
// 1. Calculate the default orthogonal path turning points first
|
|
316
|
-
const defaultTurnPoints = getDefaultOrthogonalTurnPoints(s[0], s[1], e[0], e[1], element, startEl, endEl)
|
|
424
|
+
let absTurnPoints: [number, number][] = []
|
|
317
425
|
|
|
318
|
-
|
|
426
|
+
if (!isCustom && (element.broken !== undefined || element.broken2 !== undefined)) {
|
|
427
|
+
const obstacles = (elements && elements.length > 0) ? getSlideObstacles(element, elements) : []
|
|
428
|
+
const defaultTurnPoints = getDefaultOrthogonalTurnPoints(s[0], s[1], e[0], e[1], element, startEl, endEl, obstacles)
|
|
429
|
+
|
|
430
|
+
if (obstacles.length > 0) {
|
|
319
431
|
const defaultPathPoints = [s, ...defaultTurnPoints, e]
|
|
320
432
|
let defaultObstructed = false
|
|
321
433
|
for (let i = 0; i < defaultPathPoints.length - 1; i++) {
|
|
@@ -325,176 +437,36 @@ export function getLinePathTurningPoints(
|
|
|
325
437
|
}
|
|
326
438
|
}
|
|
327
439
|
|
|
328
|
-
// 3. If default path is NOT obstructed, use it! It's perfectly stable and uses midpoint.
|
|
329
440
|
if (!defaultObstructed) {
|
|
330
|
-
|
|
331
|
-
return defaultTurnPoints
|
|
332
|
-
} else {
|
|
333
|
-
return defaultTurnPoints.map(pt => [pt[0] - leftPx, pt[1] - topPx])
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
// 4. Otherwise, run A* to avoid the obstacle
|
|
338
|
-
const idx1 = element.startConnection?.idx
|
|
339
|
-
const idx2 = element.endConnection?.idx
|
|
340
|
-
const astarPath = routeAvoidObstacles(s[0], s[1], e[0], e[1], idx1, idx2, obstacles, startEl, endEl)
|
|
341
|
-
if (astarPath && astarPath.length >= 2) {
|
|
342
|
-
const absTurnPoints = astarPath.slice(1, -1)
|
|
343
|
-
if (absStart && absEnd) {
|
|
344
|
-
return absTurnPoints
|
|
345
|
-
} else {
|
|
346
|
-
return absTurnPoints.map(pt => [pt[0] - leftPx, pt[1] - topPx])
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const x1 = s[0]
|
|
353
|
-
const y1 = s[1]
|
|
354
|
-
const x2 = e[0]
|
|
355
|
-
const y2 = e[1]
|
|
356
|
-
|
|
357
|
-
let absTurnPoints: [number, number][] = []
|
|
358
|
-
|
|
359
|
-
if (element.broken !== undefined) {
|
|
360
|
-
if (isCustom && element.broken) {
|
|
361
|
-
const absB: [number, number] = [leftPx + element.broken[0], topPx + element.broken[1]]
|
|
362
|
-
absTurnPoints = [absB]
|
|
363
|
-
} else {
|
|
364
|
-
if (element.startConnection && element.endConnection) {
|
|
365
|
-
const idx1 = element.startConnection.idx
|
|
366
|
-
const idx2 = element.endConnection.idx
|
|
367
|
-
|
|
368
|
-
let isStartHoriz = (idx1 === 1 || idx1 === 3)
|
|
369
|
-
if (isStartEllipse) {
|
|
370
|
-
isStartHoriz = (idx1 === 2 || idx1 === 6)
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
let isEndHoriz = (idx2 === 1 || idx2 === 3)
|
|
374
|
-
if (isEndEllipse) {
|
|
375
|
-
isEndHoriz = (idx2 === 2 || idx2 === 6)
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (isStartHoriz && !isEndHoriz) {
|
|
379
|
-
absTurnPoints = [[x2, y1]]
|
|
380
|
-
} else if (!isStartHoriz && isEndHoriz) {
|
|
381
|
-
absTurnPoints = [[x1, y2]]
|
|
382
|
-
} else if (isStartHoriz && isEndHoriz) {
|
|
383
|
-
let mx = (x1 + x2) / 2
|
|
384
|
-
if (idx1 === idx2) {
|
|
385
|
-
const isRight = (idx1 === 3 && !isStartEllipse) || (idx1 === 6 && isStartEllipse)
|
|
386
|
-
const isLeft = (idx1 === 1 && !isStartEllipse) || (idx1 === 2 && isStartEllipse)
|
|
387
|
-
if (isRight) mx = Math.max(x1, x2) + 30
|
|
388
|
-
else if (isLeft) mx = Math.min(x1, x2) - 30
|
|
389
|
-
}
|
|
390
|
-
absTurnPoints = [[mx, y1], [mx, y2]]
|
|
391
|
-
} else {
|
|
392
|
-
let my = (y1 + y2) / 2
|
|
393
|
-
if (idx1 === idx2) {
|
|
394
|
-
const isBottom = (idx1 === 2 && !isStartEllipse) || (idx1 === 4 && isStartEllipse)
|
|
395
|
-
const isTop = (idx1 === 0 && !isStartEllipse) || (idx1 === 0 && isStartEllipse)
|
|
396
|
-
if (isBottom) my = Math.max(y1, y2) + 30
|
|
397
|
-
else if (isTop) my = Math.min(y1, y2) - 30
|
|
398
|
-
}
|
|
399
|
-
absTurnPoints = [[x1, my], [x2, my]]
|
|
400
|
-
}
|
|
441
|
+
absTurnPoints = defaultTurnPoints
|
|
401
442
|
} else {
|
|
402
|
-
|
|
403
|
-
|
|
443
|
+
const idx1 = element.startConnection?.idx
|
|
444
|
+
const idx2 = element.endConnection?.idx
|
|
445
|
+
const astarPath = routeAvoidObstacles(s[0], s[1], e[0], e[1], idx1, idx2, obstacles, startEl, endEl)
|
|
446
|
+
if (astarPath && astarPath.length >= 2) {
|
|
447
|
+
absTurnPoints = astarPath.slice(1, -1)
|
|
404
448
|
} else {
|
|
405
|
-
absTurnPoints =
|
|
449
|
+
absTurnPoints = defaultTurnPoints
|
|
406
450
|
}
|
|
407
451
|
}
|
|
452
|
+
} else {
|
|
453
|
+
absTurnPoints = defaultTurnPoints
|
|
408
454
|
}
|
|
409
|
-
} else if (element.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
]
|
|
418
|
-
|
|
419
|
-
absTurnPoints = [
|
|
420
|
-
[x1, absB2[1]],
|
|
421
|
-
[x2, absB2[1]]
|
|
422
|
-
]
|
|
423
|
-
}
|
|
455
|
+
} else if (isCustom && element.broken !== undefined && element.broken) {
|
|
456
|
+
absTurnPoints = [[leftPx + element.broken[0], topPx + element.broken[1]]]
|
|
457
|
+
} else if (isCustom && element.broken2 !== undefined && element.broken2) {
|
|
458
|
+
const dir = getBroken2Direction(element)
|
|
459
|
+
const absB2: [number, number] = [leftPx + element.broken2[0], topPx + element.broken2[1]]
|
|
460
|
+
if (dir === 'horizontal') {
|
|
461
|
+
absTurnPoints = [
|
|
462
|
+
[absB2[0], s[1]],
|
|
463
|
+
[absB2[0], e[1]]
|
|
464
|
+
]
|
|
424
465
|
} else {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
isStartHoriz = (idx1 === 2 || idx1 === 6)
|
|
430
|
-
}
|
|
431
|
-
if (isStartHoriz) {
|
|
432
|
-
const mx = (x1 + x2) / 2
|
|
433
|
-
absTurnPoints = [[mx, y1], [mx, y2]]
|
|
434
|
-
} else {
|
|
435
|
-
const my = (y1 + y2) / 2
|
|
436
|
-
absTurnPoints = [[x1, my], [x2, my]]
|
|
437
|
-
}
|
|
438
|
-
} else if (!element.startConnection && element.endConnection) {
|
|
439
|
-
const idx2 = element.endConnection.idx
|
|
440
|
-
let isEndHoriz = (idx2 === 1 || idx2 === 3)
|
|
441
|
-
if (isEndEllipse) {
|
|
442
|
-
isEndHoriz = (idx2 === 2 || idx2 === 6)
|
|
443
|
-
}
|
|
444
|
-
if (isEndHoriz) {
|
|
445
|
-
const mx = (x1 + x2) / 2
|
|
446
|
-
absTurnPoints = [[mx, y1], [mx, y2]]
|
|
447
|
-
} else {
|
|
448
|
-
const my = (y1 + y2) / 2
|
|
449
|
-
absTurnPoints = [[x1, my], [x2, my]]
|
|
450
|
-
}
|
|
451
|
-
} else if (element.startConnection && element.endConnection) {
|
|
452
|
-
const idx1 = element.startConnection.idx
|
|
453
|
-
const idx2 = element.endConnection.idx
|
|
454
|
-
|
|
455
|
-
let isStartHoriz = (idx1 === 1 || idx1 === 3)
|
|
456
|
-
if (isStartEllipse) {
|
|
457
|
-
isStartHoriz = (idx1 === 2 || idx1 === 6)
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
let isEndHoriz = (idx2 === 1 || idx2 === 3)
|
|
461
|
-
if (isEndEllipse) {
|
|
462
|
-
isEndHoriz = (idx2 === 2 || idx2 === 6)
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
if (isStartHoriz && isEndHoriz) {
|
|
466
|
-
let mx = (x1 + x2) / 2
|
|
467
|
-
if (idx1 === idx2) {
|
|
468
|
-
const isRight = (idx1 === 3 && !isStartEllipse) || (idx1 === 6 && isStartEllipse)
|
|
469
|
-
const isLeft = (idx1 === 1 && !isStartEllipse) || (idx1 === 2 && isStartEllipse)
|
|
470
|
-
if (isRight) mx = Math.max(x1, x2) + 30
|
|
471
|
-
else if (isLeft) mx = Math.min(x1, x2) - 30
|
|
472
|
-
}
|
|
473
|
-
absTurnPoints = [[mx, y1], [mx, y2]]
|
|
474
|
-
} else if (!isStartHoriz && !isEndHoriz) {
|
|
475
|
-
let my = (y1 + y2) / 2
|
|
476
|
-
if (idx1 === idx2) {
|
|
477
|
-
const isBottom = (idx1 === 2 && !isStartEllipse) || (idx1 === 4 && isStartEllipse)
|
|
478
|
-
const isTop = (idx1 === 0 && !isStartEllipse) || (idx1 === 0 && isStartEllipse)
|
|
479
|
-
if (isBottom) my = Math.max(y1, y2) + 30
|
|
480
|
-
else if (isTop) my = Math.min(y1, y2) - 30
|
|
481
|
-
}
|
|
482
|
-
absTurnPoints = [[x1, my], [x2, my]]
|
|
483
|
-
} else if (isStartHoriz && !isEndHoriz) {
|
|
484
|
-
absTurnPoints = [[x2, y1]]
|
|
485
|
-
} else {
|
|
486
|
-
absTurnPoints = [[x1, y2]]
|
|
487
|
-
}
|
|
488
|
-
} else {
|
|
489
|
-
const dir = getBroken2Direction(element)
|
|
490
|
-
if (dir === 'horizontal') {
|
|
491
|
-
const mx = (x1 + x2) / 2
|
|
492
|
-
absTurnPoints = [[mx, y1], [mx, y2]]
|
|
493
|
-
} else {
|
|
494
|
-
const my = (y1 + y2) / 2
|
|
495
|
-
absTurnPoints = [[x1, my], [x2, my]]
|
|
496
|
-
}
|
|
497
|
-
}
|
|
466
|
+
absTurnPoints = [
|
|
467
|
+
[s[0], absB2[1]],
|
|
468
|
+
[e[0], absB2[1]]
|
|
469
|
+
]
|
|
498
470
|
}
|
|
499
471
|
} else if (element.curve) {
|
|
500
472
|
absTurnPoints = [[leftPx + element.curve[0], topPx + element.curve[1]]]
|
|
@@ -643,6 +615,8 @@ export function getSvgWidth(el: any) {
|
|
|
643
615
|
}
|
|
644
616
|
}
|
|
645
617
|
}
|
|
618
|
+
const styleW = getPxVal(el.style?.width || el.width, 1000)
|
|
619
|
+
if (styleW && styleW > 0) return Math.max(24, styleW)
|
|
646
620
|
const width = Math.abs((el.start ? el.start[0] : 0) - (el.end ? el.end[0] : 0))
|
|
647
621
|
return width < 24 ? 24 : width
|
|
648
622
|
}
|
|
@@ -664,6 +638,8 @@ export function getSvgHeight(el: any) {
|
|
|
664
638
|
}
|
|
665
639
|
}
|
|
666
640
|
}
|
|
641
|
+
const styleH = getPxVal(el.style?.height || el.height, 562.5)
|
|
642
|
+
if (styleH && styleH > 0) return Math.max(24, styleH)
|
|
667
643
|
const height = Math.abs((el.start ? el.start[1] : 0) - (el.end ? el.end[1] : 0))
|
|
668
644
|
return height < 24 ? 24 : height
|
|
669
645
|
}
|
|
@@ -702,9 +678,6 @@ export function isStandardShapeType(target: any): boolean {
|
|
|
702
678
|
if (target.type && target.type !== 'shape' && target.type !== 'text') return false
|
|
703
679
|
|
|
704
680
|
const shapType = String(target.shapType || target.pathFormula || '').toLowerCase()
|
|
705
|
-
if (!shapType) {
|
|
706
|
-
return !!target.pathFormula || target.viewBox?.[0] === 200
|
|
707
|
-
}
|
|
708
681
|
|
|
709
682
|
// 明确排除非标准自定义/自由多边形
|
|
710
683
|
if (shapType === 'custom' || shapType.startsWith('custom:') || /freeform/i.test(shapType) || shapType === 'unknown') {
|
|
@@ -848,15 +821,12 @@ function getSlideObstacles(element: any, elements: any[]) {
|
|
|
848
821
|
const logWidth = 1000
|
|
849
822
|
const logHeight = 562.5
|
|
850
823
|
|
|
851
|
-
const
|
|
852
|
-
const endId = element.endConnection?.id
|
|
824
|
+
const lineId = String(element.id)
|
|
853
825
|
|
|
854
826
|
for (const el of elements) {
|
|
855
|
-
if (el.id ===
|
|
856
|
-
if (el.id !== startId && el.id !== endId) continue
|
|
827
|
+
if (!el || String(el.id) === lineId) continue
|
|
857
828
|
if (el.type !== 'shape' && el.type !== 'text' && el.type !== 'image') continue
|
|
858
829
|
|
|
859
|
-
if (!el) continue
|
|
860
830
|
const style = el.style || {}
|
|
861
831
|
const left = getPxVal(style.left || el.left, logWidth)
|
|
862
832
|
const top = getPxVal(style.top || el.top, logHeight)
|
|
@@ -866,8 +836,8 @@ function getSlideObstacles(element: any, elements: any[]) {
|
|
|
866
836
|
if (typeof document !== 'undefined' && (!width || !height || el.type === 'text')) {
|
|
867
837
|
const domEl = document.getElementById(`canvas-el-${el.id}`) || document.getElementById(`screen-element-${el.id}`)
|
|
868
838
|
if (domEl) {
|
|
869
|
-
if (domEl.offsetWidth) width = domEl.offsetWidth
|
|
870
|
-
if (domEl.offsetHeight) height = domEl.offsetHeight
|
|
839
|
+
if (domEl.offsetWidth && !width) width = domEl.offsetWidth
|
|
840
|
+
if (domEl.offsetHeight && !height) height = domEl.offsetHeight
|
|
871
841
|
}
|
|
872
842
|
}
|
|
873
843
|
|
|
@@ -889,7 +859,7 @@ function isSegmentObstructed(x3: number, y3: number, x4: number, y4: number, obs
|
|
|
889
859
|
const minY = Math.min(y3, y4)
|
|
890
860
|
const maxY = Math.max(y3, y4)
|
|
891
861
|
|
|
892
|
-
const eps =
|
|
862
|
+
const eps = 0.5
|
|
893
863
|
|
|
894
864
|
for (const obs of obstacles) {
|
|
895
865
|
const obsLeft = obs.left + eps
|
|
@@ -899,13 +869,13 @@ function isSegmentObstructed(x3: number, y3: number, x4: number, y4: number, obs
|
|
|
899
869
|
|
|
900
870
|
if (obsRight <= obsLeft || obsBottom <= obsTop) continue
|
|
901
871
|
|
|
902
|
-
if (x3
|
|
872
|
+
if (Math.abs(x3 - x4) < 0.5) {
|
|
903
873
|
if (x3 > obsLeft && x3 < obsRight) {
|
|
904
874
|
if (minY < obsBottom && maxY > obsTop) {
|
|
905
875
|
return true
|
|
906
876
|
}
|
|
907
877
|
}
|
|
908
|
-
} else if (y3
|
|
878
|
+
} else if (Math.abs(y3 - y4) < 0.5) {
|
|
909
879
|
if (y3 > obsTop && y3 < obsBottom) {
|
|
910
880
|
if (minX < obsRight && maxX > obsLeft) {
|
|
911
881
|
return true
|
|
@@ -1249,7 +1219,7 @@ export function updateLineGeometry(
|
|
|
1249
1219
|
absControls.broken = [(absStart[0] + absEnd[0]) / 2, (absStart[1] + absEnd[1]) / 2]
|
|
1250
1220
|
}
|
|
1251
1221
|
|
|
1252
|
-
if (el.path !== undefined
|
|
1222
|
+
if (el.path !== undefined) {
|
|
1253
1223
|
delete el.path
|
|
1254
1224
|
}
|
|
1255
1225
|
|
|
@@ -1424,17 +1394,13 @@ export function syncSlideConnections(slide: any, logWidth: number, logHeight: nu
|
|
|
1424
1394
|
}
|
|
1425
1395
|
const pt = getConnectionPoint(target, idx, logWidth, logHeight, targetDim)
|
|
1426
1396
|
const isLineSelected = Boolean(selectedElementIds && selectedElementIds.includes(el.id))
|
|
1427
|
-
const
|
|
1428
|
-
const shouldSnapStart = (!selectedElementIds || selectedElementIds.length === 0) || (isTargetSelected && !isLineSelected)
|
|
1397
|
+
const shouldSnapStart = !isLineSelected
|
|
1429
1398
|
if (shouldSnapStart) {
|
|
1430
1399
|
if (Math.abs(pt[0] - currentStartAbs[0]) > 0.5 || Math.abs(pt[1] - currentStartAbs[1]) > 0.5) {
|
|
1431
1400
|
newStartAbs = pt
|
|
1432
1401
|
updated = true
|
|
1433
1402
|
}
|
|
1434
1403
|
}
|
|
1435
|
-
} else {
|
|
1436
|
-
// 目标形状不是系统已知标准形状(如自由曲线/自定义形状等),不给绑定锚点,原样呈现 PPT 原生排版位置
|
|
1437
|
-
delete el.startConnection
|
|
1438
1404
|
}
|
|
1439
1405
|
}
|
|
1440
1406
|
|
|
@@ -1474,17 +1440,13 @@ export function syncSlideConnections(slide: any, logWidth: number, logHeight: nu
|
|
|
1474
1440
|
}
|
|
1475
1441
|
const pt = getConnectionPoint(target, idx, logWidth, logHeight, targetDim)
|
|
1476
1442
|
const isLineSelected = Boolean(selectedElementIds && selectedElementIds.includes(el.id))
|
|
1477
|
-
const
|
|
1478
|
-
const shouldSnapEnd = (!selectedElementIds || selectedElementIds.length === 0) || (isTargetSelected && !isLineSelected)
|
|
1443
|
+
const shouldSnapEnd = !isLineSelected
|
|
1479
1444
|
if (shouldSnapEnd) {
|
|
1480
1445
|
if (Math.abs(pt[0] - currentEndAbs[0]) > 0.5 || Math.abs(pt[1] - currentEndAbs[1]) > 0.5) {
|
|
1481
1446
|
newEndAbs = pt
|
|
1482
1447
|
updated = true
|
|
1483
1448
|
}
|
|
1484
1449
|
}
|
|
1485
|
-
} else {
|
|
1486
|
-
// 目标形状不是系统已知标准形状,不给绑定锚点,原样呈现 PPT 原生排版位置
|
|
1487
|
-
delete el.endConnection
|
|
1488
1450
|
}
|
|
1489
1451
|
}
|
|
1490
1452
|
|