jvs-ppt-ui 0.0.9 → 0.0.11
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 +3 -0
- package/dist/lib/jvs-ppt-ui.es.js +22611 -21764
- package/dist/lib/jvs-ppt-ui.umd.js +102 -101
- package/dist/lib/style.css +1 -1
- package/package.json +7 -7
- package/scripts/check_connectors.cjs +25 -0
- package/scripts/check_slide1.cjs +11 -0
- package/scripts/check_slide123.cjs +25 -0
- package/scripts/convert-fonts-to-woff2.cjs +97 -0
- package/scripts/extract-slides.ts +149 -0
- package/scripts/find_candidate_pptx.cjs +34 -0
- package/scripts/find_orange_slide.cjs +29 -0
- package/scripts/generate-complete-ppt-preview.js +385 -0
- package/scripts/inspect_315_327.cjs +19 -0
- package/scripts/mcp-smoke-test.js +568 -0
- package/scripts/patch-pptxtojson.cjs +81 -0
- package/scripts/verify-fonts.cjs +20 -0
- package/src/App.vue +53 -53
- package/src/components/AIPptDialog.vue +2 -0
- package/src/components/CanvasArea.vue +124 -62
- package/src/components/Header.vue +364 -142
- package/src/components/PptEditor.vue +26 -15
- package/src/components/PresentationMode.vue +24 -4
- package/src/components/SidebarRight.vue +8 -1
- package/src/components/SvgChart.vue +5 -2
- package/src/components/TemplateGallery.vue +1321 -866
- package/src/components/Toolbar.vue +243 -22
- package/src/components/canvas/CanvasTransformHandles.vue +5 -1
- package/src/components/canvas/elements/CanvasShapeElement.vue +98 -19
- package/src/components/canvas/elements/CanvasTextElement.vue +39 -9
- package/src/components/canvas/textSelectionOverlay.ts +305 -0
- package/src/composables/canvas/useCanvasAlignment.ts +286 -238
- package/src/composables/canvas/useCanvasContextMenu.ts +53 -22
- package/src/composables/canvas/useCanvasFormatting.ts +35 -12
- package/src/composables/canvas/useCanvasTable.ts +52 -24
- package/src/composables/canvas/useCanvasText.ts +12 -0
- package/src/composables/canvas/useCanvasTransform.ts +2 -2
- package/src/composables/sidebar/useSidebarElementTransform.ts +5 -5
- package/src/composables/sidebar/useSidebarShapeFormat.ts +2 -2
- package/src/composables/useSlides.ts +482 -127
- package/src/style.css +88 -0
- package/src/types/index.ts +4 -0
- package/src/utils/pptxShapes.ts +1 -1
- package/src/utils/pptxShapesLazy.ts +39 -0
- package/src/utils/svgPathParser.ts +12 -1
|
@@ -2,7 +2,7 @@ import { ref, computed, watch, nextTick, type WatchStopHandle } from 'vue'
|
|
|
2
2
|
import { extractAnimationsFromPptxArrayBuffer } from '../utils/pptxAnimationExtractor'
|
|
3
3
|
import { syncSlideConnections, scaleSvgPath, extractPathStartEnd, isEllipseShape, isStandardShapeType } from '../utils/line'
|
|
4
4
|
import { getSvgPathRange } from '../utils/svgPathParser'
|
|
5
|
-
import {
|
|
5
|
+
import { shapeFormulas, shapeList, ensurePptxShapes } from '../utils/pptxShapesLazy'
|
|
6
6
|
import { compileSchemaToSlides, compileSingleSchema } from '../utils/dslCompiler'
|
|
7
7
|
import { defaultStyleRecipe } from './presetThemes'
|
|
8
8
|
import { extractTextAlignFromHtml } from '../utils/canvas/elementStyles'
|
|
@@ -71,6 +71,8 @@ export interface SlideElement {
|
|
|
71
71
|
keypoints?: number[]
|
|
72
72
|
start?: [number, number]
|
|
73
73
|
end?: [number, number]
|
|
74
|
+
startPoint?: [number, number]
|
|
75
|
+
endPoint?: [number, number]
|
|
74
76
|
lineStyle?: 'solid' | 'dashed'
|
|
75
77
|
points?: [string, string]
|
|
76
78
|
borderHeadEnd?: { type?: string; w?: string; len?: string } | null
|
|
@@ -102,10 +104,10 @@ export interface SlideElement {
|
|
|
102
104
|
gridColor?: string
|
|
103
105
|
color?: string
|
|
104
106
|
fontFamily?: string
|
|
105
|
-
top?: string
|
|
106
|
-
left?: string
|
|
107
|
-
width?: string
|
|
108
|
-
height?: string
|
|
107
|
+
top?: string | number
|
|
108
|
+
left?: string | number
|
|
109
|
+
width?: string | number
|
|
110
|
+
height?: string | number
|
|
109
111
|
rotate?: number
|
|
110
112
|
flipH?: boolean
|
|
111
113
|
isFlipH?: boolean
|
|
@@ -2275,21 +2277,27 @@ const activeSlide = computed<Slide>({
|
|
|
2275
2277
|
})
|
|
2276
2278
|
|
|
2277
2279
|
let isSyncingConnections = false
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
+
// 连接线端点吸附(线跟随形状)是确定性重推导且幂等(<0.5px 不更新)。
|
|
2281
|
+
// 快照/深 watcher 共用同一收敛入口,避免各自为政产生不同步的几何状态。
|
|
2282
|
+
function settleActiveSlideConnections() {
|
|
2283
|
+
const cur = activeSlide.value
|
|
2284
|
+
if (!cur || !Array.isArray(cur.elements) || !cur.elements.length) return
|
|
2280
2285
|
if (isSyncingConnections) return
|
|
2281
2286
|
if (isCanvasDragging.value) return
|
|
2282
2287
|
|
|
2283
2288
|
// 性能守卫:如果页面中不存在任何线条或连接线,直接跳过全量连接线更新计算
|
|
2284
|
-
|
|
2285
|
-
if (!hasLineElements) return
|
|
2289
|
+
if (!cur.elements.some(el => el?.type === 'line')) return
|
|
2286
2290
|
|
|
2287
2291
|
isSyncingConnections = true
|
|
2288
2292
|
try {
|
|
2289
|
-
syncSlideConnections(
|
|
2293
|
+
syncSlideConnections(cur, logicalWidth.value, logicalHeight.value, selectedElementIds.value)
|
|
2290
2294
|
} finally {
|
|
2291
2295
|
isSyncingConnections = false
|
|
2292
2296
|
}
|
|
2297
|
+
}
|
|
2298
|
+
watch(() => activeSlide.value?.elements, (newElements) => {
|
|
2299
|
+
if (!newElements || !newElements.length) return
|
|
2300
|
+
settleActiveSlideConnections()
|
|
2293
2301
|
}, { deep: true, immediate: true, flush: 'post' })
|
|
2294
2302
|
const activeTab = ref<'design' | 'transition' | 'animation' | 'format' | 'multi-style' | 'multi-position' | 'material' | null>(null)
|
|
2295
2303
|
|
|
@@ -2606,6 +2614,7 @@ function revokeSlideBlobUrls(slideList: any[]) {
|
|
|
2606
2614
|
function initHistory() {
|
|
2607
2615
|
ensurePageWatchers()
|
|
2608
2616
|
ensureMasterWatchers()
|
|
2617
|
+
slidesRevision.value++ // 导入/重置整册替换数组,补一次 bump 供对外同步
|
|
2609
2618
|
// 双栈即将整体重置,此时清资源池是安全的(不会有存活条目引用池内 id)
|
|
2610
2619
|
assetPool.clear()
|
|
2611
2620
|
assetIntern.clear()
|
|
@@ -2673,6 +2682,11 @@ function syncDOMWithRestoredSlides() {
|
|
|
2673
2682
|
function addHistorySnapshot() {
|
|
2674
2683
|
if (isUndoingRedoing.value || pauseHistory.value) return
|
|
2675
2684
|
syncActiveEditingTextToElement()
|
|
2685
|
+
// 连接线吸附是 flush:'post' 的深度 watcher,在手动快照之后才执行:若快照先落盘,
|
|
2686
|
+
// 吸附结果会被防抖 watcher 再推一条幽灵条目(同一操作两条历史,撤销第一步看似无效;
|
|
2687
|
+
// 还原旧条目还会让吸附反复重推导,undo 前的 flush 不断推新条目,撤销指针原地打转)。
|
|
2688
|
+
// 序列化前先就地收敛一次(拖拽 stopDrag 里显式调 syncSlideConnections 同理),幂等无副作用。
|
|
2689
|
+
settleActiveSlideConnections()
|
|
2676
2690
|
ensurePageWatchers()
|
|
2677
2691
|
|
|
2678
2692
|
const __perfT0 = performance.now()
|
|
@@ -2723,6 +2737,9 @@ function addHistorySnapshot() {
|
|
|
2723
2737
|
|
|
2724
2738
|
historyIndex.value = historyStack.value.length - 1
|
|
2725
2739
|
currentPageIds = slides.value.map(s => s && s.id)
|
|
2740
|
+
// 真实落盘的编辑统一失效缩略图/画布快照:非拖拽类编辑(换形状/样式/文字提交/对齐/
|
|
2741
|
+
// keypoint 调整等)原先没有任何 bump 点,SidebarLeft 激活页缩略图的 v-memo 会停在旧内容
|
|
2742
|
+
activeSlideVersion.value++
|
|
2726
2743
|
if (import.meta.env.DEV) {
|
|
2727
2744
|
console.debug(`[perf:addSnapshot] ${(performance.now() - __perfT0).toFixed(1)}ms pages=${slides.value.length} dirty=${candidates.size} stack=${historyStack.value.length}`)
|
|
2728
2745
|
// 漏标脏页保险丝:未纳入候选集的页应与落盘字符串完全一致,不一致说明脏标漏页(仅 DEV)
|
|
@@ -2802,6 +2819,10 @@ function scheduleHistorySnapshot() {
|
|
|
2802
2819
|
|
|
2803
2820
|
// 每页一个 deep watcher:任何页的任何深层变更只标脏自己那一页(替代原整册 deep watch)。
|
|
2804
2821
|
// 标脏不受守卫限制(守卫只作用于 flush),pauseHistory/撤销期间产生的标记靠下次 flush 的字符串比对自愈
|
|
2822
|
+
// 对外变更版本号:PptEditor 侧的对外同步 watch 用它替代整册 deep watch(后者每次拖拽/打字
|
|
2823
|
+
// 都要 O(整册) 深遍历重收集依赖,页数越多越卡)。任何变更点 bump 一次即可
|
|
2824
|
+
const slidesRevision = ref(0)
|
|
2825
|
+
|
|
2805
2826
|
function ensurePageWatchers() {
|
|
2806
2827
|
const n = slides.value.length
|
|
2807
2828
|
while (pageWatchers.length > n) pageWatchers.pop()!()
|
|
@@ -2812,6 +2833,7 @@ function ensurePageWatchers() {
|
|
|
2812
2833
|
// 此窗口内还原路径已同步 currentPageStrs,无需标脏。watcher 回调先于 nextTick 复位执行,守卫可靠
|
|
2813
2834
|
if (isUndoingRedoing.value) return
|
|
2814
2835
|
dirtyPageIndexes.add(i)
|
|
2836
|
+
slidesRevision.value++
|
|
2815
2837
|
scheduleHistorySnapshot()
|
|
2816
2838
|
}, { deep: true }))
|
|
2817
2839
|
}
|
|
@@ -2820,6 +2842,7 @@ function ensurePageWatchers() {
|
|
|
2820
2842
|
// 长度哨兵:纯尾部追加(末页后 addSlide)不触发任何 index getter,必须靠它补注册 watcher
|
|
2821
2843
|
watch(() => slides.value.length, () => {
|
|
2822
2844
|
ensurePageWatchers()
|
|
2845
|
+
slidesRevision.value++
|
|
2823
2846
|
scheduleHistorySnapshot()
|
|
2824
2847
|
})
|
|
2825
2848
|
|
|
@@ -2861,6 +2884,7 @@ function ensureMasterWatchers() {
|
|
|
2861
2884
|
masterWatchers.push(watch(() => slideMasters.value[i], () => {
|
|
2862
2885
|
if (isUndoingRedoing.value) return
|
|
2863
2886
|
dirtyMasterIndexes.add(i)
|
|
2887
|
+
slidesRevision.value++
|
|
2864
2888
|
scheduleMasterHistorySnapshot()
|
|
2865
2889
|
}, { deep: true }))
|
|
2866
2890
|
}
|
|
@@ -2868,6 +2892,7 @@ function ensureMasterWatchers() {
|
|
|
2868
2892
|
|
|
2869
2893
|
watch(() => slideMasters.value.length, () => {
|
|
2870
2894
|
ensureMasterWatchers()
|
|
2895
|
+
slidesRevision.value++
|
|
2871
2896
|
scheduleMasterHistorySnapshot()
|
|
2872
2897
|
})
|
|
2873
2898
|
|
|
@@ -2918,6 +2943,7 @@ const canRedo = computed(() => isMasterEditingView.value
|
|
|
2918
2943
|
// 按页还原:只解析与当前状态字符串不同的页,未变页对象引用复用(缩略图/组件 vnode 稳定)。
|
|
2919
2944
|
// 始终返回新数组:依赖 slides.value 数组 identity 的 watcher(缩略图测量/滚动重置等)照常触发
|
|
2920
2945
|
function applyHistoryEntry<T>(entryStrs: string[], currentStrs: string[], list: T[]): { list: T[]; changed: boolean } {
|
|
2946
|
+
slidesRevision.value++ // undo/redo 窗口期页 watcher 被守卫跳过,还原本身补一次 bump 供对外同步
|
|
2921
2947
|
if (entryStrs.length !== list.length || entryStrs.length !== currentStrs.length) {
|
|
2922
2948
|
// 结构性差异(增删页):与旧行为一致,整册逐页解析
|
|
2923
2949
|
return { list: entryStrs.map(s => parsePage<T>(s)), changed: true }
|
|
@@ -3254,47 +3280,288 @@ function pasteSlideData(targetSlideData: any, afterIndex = activeIndex.value, is
|
|
|
3254
3280
|
}
|
|
3255
3281
|
}
|
|
3256
3282
|
|
|
3257
|
-
function
|
|
3258
|
-
if (!activeSlide.value || !
|
|
3283
|
+
function copySelectedElements(): { type: 'elements'; data: SlideElement[]; sourceWidth: number; sourceHeight: number } | null {
|
|
3284
|
+
if (!activeSlide.value || !activeSlide.value.elements) return null
|
|
3285
|
+
if (selectedElementIds.value.length === 0) return null
|
|
3286
|
+
|
|
3287
|
+
const targetElements: SlideElement[] = []
|
|
3288
|
+
const selectedSet = new Set(selectedElementIds.value)
|
|
3289
|
+
|
|
3290
|
+
function isAnyAncestorSelected(id: string): boolean {
|
|
3291
|
+
let parent = findParentGroupInSlide(id, activeSlide.value!.elements)
|
|
3292
|
+
while (parent) {
|
|
3293
|
+
if (selectedSet.has(parent.id)) return true
|
|
3294
|
+
parent = findParentGroupInSlide(parent.id, activeSlide.value!.elements)
|
|
3295
|
+
}
|
|
3296
|
+
return false
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
function getAbsoluteCanvasBox(el: SlideElement): { left: number; top: number; width: number; height: number } {
|
|
3300
|
+
const parentGroups: SlideElement[] = []
|
|
3301
|
+
let curr = findParentGroupInSlide(el.id, activeSlide.value!.elements)
|
|
3302
|
+
while (curr) {
|
|
3303
|
+
parentGroups.unshift(curr)
|
|
3304
|
+
curr = findParentGroupInSlide(curr.id, activeSlide.value!.elements)
|
|
3305
|
+
}
|
|
3306
|
+
|
|
3307
|
+
let box = {
|
|
3308
|
+
left: parseValToPx(el.style?.left, logicalWidth.value, (el as any).left || 0),
|
|
3309
|
+
top: parseValToPx(el.style?.top, logicalHeight.value, (el as any).top || 0),
|
|
3310
|
+
width: parseValToPx(el.style?.width, logicalWidth.value, (el as any).width || 200),
|
|
3311
|
+
height: parseValToPx(el.style?.height, logicalHeight.value, (el as any).height || 100)
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
if (parentGroups.length === 0) return box
|
|
3315
|
+
|
|
3316
|
+
let pLeft = 0
|
|
3317
|
+
let pTop = 0
|
|
3318
|
+
let pWidth = logicalWidth.value
|
|
3319
|
+
let pHeight = logicalHeight.value
|
|
3320
|
+
|
|
3321
|
+
for (const pg of parentGroups) {
|
|
3322
|
+
const gLeft = parseValToPx(pg.style?.left, pWidth, (pg as any).left || 0)
|
|
3323
|
+
const gTop = parseValToPx(pg.style?.top, pHeight, (pg as any).top || 0)
|
|
3324
|
+
const gWidth = parseValToPx(pg.style?.width, pWidth, (pg as any).width || 200)
|
|
3325
|
+
const gHeight = parseValToPx(pg.style?.height, pHeight, (pg as any).height || 100)
|
|
3326
|
+
pLeft += gLeft
|
|
3327
|
+
pTop += gTop
|
|
3328
|
+
pWidth = gWidth
|
|
3329
|
+
pHeight = gHeight
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
const relLeftPx = parseValToPx(el.style?.left, pWidth, (el as any).left || 0)
|
|
3333
|
+
const relTopPx = parseValToPx(el.style?.top, pHeight, (el as any).top || 0)
|
|
3334
|
+
const elWidthPx = parseValToPx(el.style?.width, pWidth, (el as any).width || 200)
|
|
3335
|
+
const elHeightPx = parseValToPx(el.style?.height, pHeight, (el as any).height || 50)
|
|
3336
|
+
|
|
3337
|
+
return {
|
|
3338
|
+
left: pLeft + relLeftPx,
|
|
3339
|
+
top: pTop + relTopPx,
|
|
3340
|
+
width: elWidthPx,
|
|
3341
|
+
height: elHeightPx
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
|
|
3345
|
+
function findElementById(id: string, list: SlideElement[]): SlideElement | null {
|
|
3346
|
+
for (const item of list) {
|
|
3347
|
+
if (item.id === id) return item
|
|
3348
|
+
if (item.type === 'group' && Array.isArray(item.elements)) {
|
|
3349
|
+
const found = findElementById(id, item.elements)
|
|
3350
|
+
if (found) return found
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
return null
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3356
|
+
selectedElementIds.value.forEach(id => {
|
|
3357
|
+
if (isAnyAncestorSelected(id)) {
|
|
3358
|
+
return
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
const el = findElementById(id, activeSlide.value!.elements)
|
|
3362
|
+
if (!el) return
|
|
3363
|
+
|
|
3364
|
+
const parentGroup = findParentGroupInSlide(id, activeSlide.value!.elements)
|
|
3365
|
+
if (parentGroup) {
|
|
3366
|
+
const box = getAbsoluteCanvasBox(el)
|
|
3367
|
+
const childClone = JSON.parse(JSON.stringify(el)) as SlideElement
|
|
3368
|
+
delete (childClone as any).groupId
|
|
3369
|
+
if (!childClone.style) childClone.style = {} as any
|
|
3370
|
+
childClone.style.left = `${((box.left / logicalWidth.value) * 100).toFixed(4)}%`
|
|
3371
|
+
childClone.style.top = `${((box.top / logicalHeight.value) * 100).toFixed(4)}%`
|
|
3372
|
+
childClone.style.width = `${((box.width / logicalWidth.value) * 100).toFixed(4)}%`
|
|
3373
|
+
childClone.style.height = `${((box.height / logicalHeight.value) * 100).toFixed(4)}%`
|
|
3374
|
+
;(childClone as any).left = box.left
|
|
3375
|
+
;(childClone as any).top = box.top
|
|
3376
|
+
;(childClone as any).width = box.width
|
|
3377
|
+
;(childClone as any).height = box.height
|
|
3378
|
+
|
|
3379
|
+
if (childClone.type === 'line') {
|
|
3380
|
+
const dX = box.left
|
|
3381
|
+
const dY = box.top
|
|
3382
|
+
if (childClone.startPoint && Array.isArray(childClone.startPoint)) childClone.startPoint = [childClone.startPoint[0] + dX, childClone.startPoint[1] + dY]
|
|
3383
|
+
if (childClone.endPoint && Array.isArray(childClone.endPoint)) childClone.endPoint = [childClone.endPoint[0] + dX, childClone.endPoint[1] + dY]
|
|
3384
|
+
}
|
|
3385
|
+
targetElements.push(childClone)
|
|
3386
|
+
} else {
|
|
3387
|
+
targetElements.push(JSON.parse(JSON.stringify(el)))
|
|
3388
|
+
}
|
|
3389
|
+
})
|
|
3390
|
+
|
|
3391
|
+
if (targetElements.length === 0) return null
|
|
3392
|
+
|
|
3393
|
+
clipboard.value = targetElements
|
|
3394
|
+
slideClipboard.value = null
|
|
3395
|
+
|
|
3396
|
+
return {
|
|
3397
|
+
type: 'elements',
|
|
3398
|
+
data: targetElements,
|
|
3399
|
+
sourceWidth: logicalWidth.value || 1280,
|
|
3400
|
+
sourceHeight: logicalHeight.value || 720
|
|
3401
|
+
}
|
|
3402
|
+
}
|
|
3403
|
+
|
|
3404
|
+
function scaleElementIfNeeded(el: any, sX: number, sY: number) {
|
|
3405
|
+
if (Math.abs(sX - 1) < 0.001 && Math.abs(sY - 1) < 0.001) return
|
|
3406
|
+
|
|
3407
|
+
if (el.style) {
|
|
3408
|
+
if (typeof el.style.left === 'string' && el.style.left.endsWith('px')) {
|
|
3409
|
+
el.style.left = `${(parseFloat(el.style.left) * sX).toFixed(1)}px`
|
|
3410
|
+
}
|
|
3411
|
+
if (typeof el.style.top === 'string' && el.style.top.endsWith('px')) {
|
|
3412
|
+
el.style.top = `${(parseFloat(el.style.top) * sY).toFixed(1)}px`
|
|
3413
|
+
}
|
|
3414
|
+
if (typeof el.style.width === 'string' && el.style.width.endsWith('px')) {
|
|
3415
|
+
el.style.width = `${(parseFloat(el.style.width) * sX).toFixed(1)}px`
|
|
3416
|
+
}
|
|
3417
|
+
if (typeof el.style.height === 'string' && el.style.height.endsWith('px')) {
|
|
3418
|
+
el.style.height = `${(parseFloat(el.style.height) * sY).toFixed(1)}px`
|
|
3419
|
+
}
|
|
3420
|
+
if (typeof el.style.fontSize === 'string' && el.style.fontSize.endsWith('px')) {
|
|
3421
|
+
el.style.fontSize = `${(parseFloat(el.style.fontSize) * ((sX + sY) / 2)).toFixed(1)}px`
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
|
|
3425
|
+
if (typeof el.left === 'number') el.left = +(el.left * sX).toFixed(1)
|
|
3426
|
+
if (typeof el.top === 'number') el.top = +(el.top * sY).toFixed(1)
|
|
3427
|
+
if (typeof el.width === 'number') el.width = +(el.width * sX).toFixed(1)
|
|
3428
|
+
if (typeof el.height === 'number') el.height = +(el.height * sY).toFixed(1)
|
|
3429
|
+
|
|
3430
|
+
if (el.type === 'line') {
|
|
3431
|
+
if (el.startPoint && Array.isArray(el.startPoint)) el.startPoint = [el.startPoint[0] * sX, el.startPoint[1] * sY]
|
|
3432
|
+
if (el.endPoint && Array.isArray(el.endPoint)) el.endPoint = [el.endPoint[0] * sX, el.endPoint[1] * sY]
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
if (el.type === 'group' && Array.isArray(el.elements)) {
|
|
3436
|
+
el.elements.forEach((child: any) => {
|
|
3437
|
+
if (child.style && typeof child.style.fontSize === 'string' && child.style.fontSize.endsWith('px')) {
|
|
3438
|
+
child.style.fontSize = `${(parseFloat(child.style.fontSize) * ((sX + sY) / 2)).toFixed(1)}px`
|
|
3439
|
+
}
|
|
3440
|
+
if (child.type === 'group') {
|
|
3441
|
+
scaleElementIfNeeded(child, sX, sY)
|
|
3442
|
+
}
|
|
3443
|
+
})
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
|
|
3447
|
+
function pasteElementsData(elementsArray: any[], options?: { sourceWidth?: number; sourceHeight?: number }) {
|
|
3448
|
+
if (!activeSlide.value || !elementsArray || !Array.isArray(elementsArray) || elementsArray.length === 0) return
|
|
3259
3449
|
const pastedIds: string[] = []
|
|
3450
|
+
const oldIdToNewId = new Map<string, string>()
|
|
3260
3451
|
const oldGroupIdToNewGroupId = new Map<string, string>()
|
|
3261
3452
|
|
|
3453
|
+
const destW = logicalWidth.value || 1280
|
|
3454
|
+
const destH = logicalHeight.value || 720
|
|
3455
|
+
const srcW = (options && options.sourceWidth && options.sourceWidth > 0) ? options.sourceWidth : destW
|
|
3456
|
+
const srcH = (options && options.sourceHeight && options.sourceHeight > 0) ? options.sourceHeight : destH
|
|
3457
|
+
const scaleX = destW / srcW
|
|
3458
|
+
const scaleY = destH / srcH
|
|
3459
|
+
const needScale = Math.abs(scaleX - 1) > 0.001 || Math.abs(scaleY - 1) > 0.001
|
|
3460
|
+
|
|
3461
|
+
function reassignIdsRecursively(item: any) {
|
|
3462
|
+
const oldId = item.id
|
|
3463
|
+
const prefix = (oldId && typeof oldId === 'string' && oldId.startsWith('title')) ? 'title-' : ''
|
|
3464
|
+
const newId = prefix + Date.now().toString(36) + Math.random().toString(36).substring(2, 7)
|
|
3465
|
+
if (oldId) {
|
|
3466
|
+
oldIdToNewId.set(oldId, newId)
|
|
3467
|
+
}
|
|
3468
|
+
item.id = newId
|
|
3469
|
+
|
|
3470
|
+
if (item.groupId) {
|
|
3471
|
+
if (!oldGroupIdToNewGroupId.has(item.groupId)) {
|
|
3472
|
+
oldGroupIdToNewGroupId.set(item.groupId, 'group_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 7))
|
|
3473
|
+
}
|
|
3474
|
+
item.groupId = oldGroupIdToNewGroupId.get(item.groupId)
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
if (item.type === 'group' && Array.isArray(item.elements)) {
|
|
3478
|
+
item.elements.forEach(reassignIdsRecursively)
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3482
|
+
function remapLineConnections(item: any) {
|
|
3483
|
+
if (item.type === 'line') {
|
|
3484
|
+
if (item.startConnection && item.startConnection.id) {
|
|
3485
|
+
if (oldIdToNewId.has(item.startConnection.id)) {
|
|
3486
|
+
item.startConnection.id = oldIdToNewId.get(item.startConnection.id)!
|
|
3487
|
+
} else {
|
|
3488
|
+
delete item.startConnection
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
if (item.endConnection && item.endConnection.id) {
|
|
3492
|
+
if (oldIdToNewId.has(item.endConnection.id)) {
|
|
3493
|
+
item.endConnection.id = oldIdToNewId.get(item.endConnection.id)!
|
|
3494
|
+
} else {
|
|
3495
|
+
delete item.endConnection
|
|
3496
|
+
}
|
|
3497
|
+
}
|
|
3498
|
+
if (item.startElementId) {
|
|
3499
|
+
if (oldIdToNewId.has(item.startElementId)) {
|
|
3500
|
+
item.startElementId = oldIdToNewId.get(item.startElementId)
|
|
3501
|
+
} else {
|
|
3502
|
+
delete item.startElementId
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
if (item.endElementId) {
|
|
3506
|
+
if (oldIdToNewId.has(item.endElementId)) {
|
|
3507
|
+
item.endElementId = oldIdToNewId.get(item.endElementId)
|
|
3508
|
+
} else {
|
|
3509
|
+
delete item.endElementId
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
if (item.type === 'group' && Array.isArray(item.elements)) {
|
|
3514
|
+
item.elements.forEach(remapLineConnections)
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
const offsetPx = 20
|
|
3519
|
+
|
|
3262
3520
|
elementsArray.forEach((el: any) => {
|
|
3263
3521
|
const newEl = JSON.parse(JSON.stringify(el))
|
|
3264
|
-
newEl
|
|
3265
|
-
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
newEl.groupId = oldGroupIdToNewGroupId.get(newEl.groupId)
|
|
3522
|
+
reassignIdsRecursively(newEl)
|
|
3523
|
+
remapLineConnections(newEl)
|
|
3524
|
+
|
|
3525
|
+
if (needScale) {
|
|
3526
|
+
scaleElementIfNeeded(newEl, scaleX, scaleY)
|
|
3270
3527
|
}
|
|
3271
3528
|
|
|
3272
3529
|
if (newEl.style && newEl.style.left) {
|
|
3273
3530
|
const leftVal = parseFloat(newEl.style.left) || 0
|
|
3274
|
-
const isPx = newEl.style.left.endsWith('px')
|
|
3275
3531
|
const isPct = newEl.style.left.endsWith('%')
|
|
3276
|
-
if (
|
|
3277
|
-
newEl.style.left = `${leftVal +
|
|
3278
|
-
} else if (isPct) {
|
|
3279
|
-
newEl.style.left = `${(leftVal + (20 / (logicalWidth.value || 1280)) * 100).toFixed(2)}%`
|
|
3532
|
+
if (isPct) {
|
|
3533
|
+
newEl.style.left = `${(leftVal + (offsetPx / destW) * 100).toFixed(4)}%`
|
|
3280
3534
|
} else {
|
|
3281
|
-
newEl.style.left = `${leftVal +
|
|
3535
|
+
newEl.style.left = `${(leftVal + offsetPx).toFixed(1)}px`
|
|
3282
3536
|
}
|
|
3283
3537
|
}
|
|
3284
3538
|
if (newEl.style && newEl.style.top) {
|
|
3285
3539
|
const topVal = parseFloat(newEl.style.top) || 0
|
|
3286
|
-
const isPx = newEl.style.top.endsWith('px')
|
|
3287
3540
|
const isPct = newEl.style.top.endsWith('%')
|
|
3288
|
-
if (
|
|
3289
|
-
newEl.style.top = `${topVal +
|
|
3290
|
-
} else if (isPct) {
|
|
3291
|
-
newEl.style.top = `${(topVal + (20 / (logicalHeight.value || 720)) * 100).toFixed(2)}%`
|
|
3541
|
+
if (isPct) {
|
|
3542
|
+
newEl.style.top = `${(topVal + (offsetPx / destH) * 100).toFixed(4)}%`
|
|
3292
3543
|
} else {
|
|
3293
|
-
newEl.style.top = `${topVal +
|
|
3544
|
+
newEl.style.top = `${(topVal + offsetPx).toFixed(1)}px`
|
|
3294
3545
|
}
|
|
3295
3546
|
}
|
|
3296
3547
|
|
|
3297
|
-
|
|
3548
|
+
if (typeof newEl.left === 'number') newEl.left += offsetPx
|
|
3549
|
+
if (typeof newEl.top === 'number') newEl.top += offsetPx
|
|
3550
|
+
|
|
3551
|
+
if (newEl.type === 'line') {
|
|
3552
|
+
if (newEl.startPoint && Array.isArray(newEl.startPoint)) {
|
|
3553
|
+
newEl.startPoint = [newEl.startPoint[0] + offsetPx, newEl.startPoint[1] + offsetPx]
|
|
3554
|
+
}
|
|
3555
|
+
if (newEl.endPoint && Array.isArray(newEl.endPoint)) {
|
|
3556
|
+
newEl.endPoint = [newEl.endPoint[0] + offsetPx, newEl.endPoint[1] + offsetPx]
|
|
3557
|
+
}
|
|
3558
|
+
}
|
|
3559
|
+
|
|
3560
|
+
if (newEl.type === 'group') {
|
|
3561
|
+
updateGroupBounds(newEl)
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3564
|
+
activeSlide.value!.elements.push(newEl)
|
|
3298
3565
|
pastedIds.push(newEl.id)
|
|
3299
3566
|
})
|
|
3300
3567
|
|
|
@@ -3302,6 +3569,7 @@ function pasteElementsData(elementsArray: any[]) {
|
|
|
3302
3569
|
if (pastedIds.length > 0) {
|
|
3303
3570
|
selectedElementId.value = pastedIds[pastedIds.length - 1]
|
|
3304
3571
|
}
|
|
3572
|
+
addHistorySnapshot()
|
|
3305
3573
|
notify(`已粘贴 ${pastedIds.length} 个元素`)
|
|
3306
3574
|
}
|
|
3307
3575
|
|
|
@@ -3411,6 +3679,10 @@ function duplicateSelectedElements(targetSlide = activeSlide.value) {
|
|
|
3411
3679
|
targetSlide.elements.push(cloned)
|
|
3412
3680
|
}
|
|
3413
3681
|
|
|
3682
|
+
if (cloned.type === 'group') {
|
|
3683
|
+
updateGroupBounds(cloned)
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3414
3686
|
newElementIds.push(cloned.id)
|
|
3415
3687
|
clonedElements.push(cloned)
|
|
3416
3688
|
})
|
|
@@ -3491,6 +3763,12 @@ async function handleUnifiedPaste(afterSlideIndex?: number) {
|
|
|
3491
3763
|
pasteSlideData(data.data, afterSlideIndex !== undefined ? afterSlideIndex : activeIndex.value)
|
|
3492
3764
|
return true
|
|
3493
3765
|
}
|
|
3766
|
+
if (data && data.type === 'elements' && Array.isArray(data.data) && data.data.length > 0) {
|
|
3767
|
+
clipboard.value = data.data
|
|
3768
|
+
slideClipboard.value = null
|
|
3769
|
+
pasteElementsData(data.data, { sourceWidth: data.sourceWidth, sourceHeight: data.sourceHeight })
|
|
3770
|
+
return true
|
|
3771
|
+
}
|
|
3494
3772
|
if (Array.isArray(data) && data.length > 0 && data[0].type && data[0].style) {
|
|
3495
3773
|
clipboard.value = data
|
|
3496
3774
|
slideClipboard.value = null
|
|
@@ -4209,36 +4487,20 @@ function combineElements() {
|
|
|
4209
4487
|
const idx = activeSlide.value!.elements.indexOf(el)
|
|
4210
4488
|
if (idx > maxZIndex) maxZIndex = idx
|
|
4211
4489
|
|
|
4212
|
-
const
|
|
4213
|
-
const
|
|
4214
|
-
const
|
|
4215
|
-
|
|
4216
|
-
let height = parseValToPx(el.style?.height, logicalHeight.value, 50)
|
|
4217
|
-
|
|
4218
|
-
if (domEl) {
|
|
4219
|
-
width = domEl.offsetWidth || width
|
|
4220
|
-
height = domEl.offsetHeight || height
|
|
4221
|
-
}
|
|
4222
|
-
|
|
4223
|
-
if (width <= 0) width = 100
|
|
4224
|
-
if (height <= 0) height = 30
|
|
4490
|
+
const rawLeft = el.style?.left ?? (typeof (el as any).left === 'number' ? `${(el as any).left}px` : undefined)
|
|
4491
|
+
const rawTop = el.style?.top ?? (typeof (el as any).top === 'number' ? `${(el as any).top}px` : undefined)
|
|
4492
|
+
const rawWidth = el.style?.width ?? (typeof (el as any).width === 'number' ? `${(el as any).width}px` : undefined)
|
|
4493
|
+
const rawHeight = el.style?.height ?? (typeof (el as any).height === 'number' ? `${(el as any).height}px` : undefined)
|
|
4225
4494
|
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
})
|
|
4495
|
+
const left = parseValToPx(rawLeft, logicalWidth.value, 0)
|
|
4496
|
+
const top = parseValToPx(rawTop, logicalHeight.value, 0)
|
|
4497
|
+
let width = parseValToPx(rawWidth, logicalWidth.value, 100)
|
|
4498
|
+
let height = parseValToPx(rawHeight, logicalHeight.value, 40)
|
|
4231
4499
|
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
let width = parseValToPx(el.style?.width, logicalWidth.value, 200)
|
|
4237
|
-
let height = parseValToPx(el.style?.height, logicalHeight.value, 50)
|
|
4238
|
-
|
|
4239
|
-
if (domEl) {
|
|
4240
|
-
width = domEl.offsetWidth || width
|
|
4241
|
-
height = domEl.offsetHeight || height
|
|
4500
|
+
const domEl = typeof document !== 'undefined' ? document.getElementById(`canvas-el-${el.id}`) : null
|
|
4501
|
+
if (domEl && el.type === 'text') {
|
|
4502
|
+
if (domEl.offsetHeight > 0) height = domEl.offsetHeight
|
|
4503
|
+
if (el.style?.width === 'auto' && domEl.offsetWidth > 0) width = domEl.offsetWidth
|
|
4242
4504
|
}
|
|
4243
4505
|
|
|
4244
4506
|
if (width <= 0) width = 100
|
|
@@ -4256,15 +4518,21 @@ function combineElements() {
|
|
|
4256
4518
|
// Convert each target element's coordinates relative to the group container (%)
|
|
4257
4519
|
const childElements: SlideElement[] = targetElements.map(el => {
|
|
4258
4520
|
const childClone = JSON.parse(JSON.stringify(el)) as SlideElement
|
|
4259
|
-
const domEl = document.getElementById(`canvas-el-${el.id}`)
|
|
4260
|
-
const elLeft = parseValToPx(el.style?.left, logicalWidth.value, 0)
|
|
4261
|
-
const elTop = parseValToPx(el.style?.top, logicalHeight.value, 0)
|
|
4262
|
-
let elWidth = parseValToPx(el.style?.width, logicalWidth.value, 200)
|
|
4263
|
-
let elHeight = parseValToPx(el.style?.height, logicalHeight.value, 50)
|
|
4264
4521
|
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4522
|
+
const rawLeft = el.style?.left ?? (typeof (el as any).left === 'number' ? `${(el as any).left}px` : undefined)
|
|
4523
|
+
const rawTop = el.style?.top ?? (typeof (el as any).top === 'number' ? `${(el as any).top}px` : undefined)
|
|
4524
|
+
const rawWidth = el.style?.width ?? (typeof (el as any).width === 'number' ? `${(el as any).width}px` : undefined)
|
|
4525
|
+
const rawHeight = el.style?.height ?? (typeof (el as any).height === 'number' ? `${(el as any).height}px` : undefined)
|
|
4526
|
+
|
|
4527
|
+
const elLeft = parseValToPx(rawLeft, logicalWidth.value, 0)
|
|
4528
|
+
const elTop = parseValToPx(rawTop, logicalHeight.value, 0)
|
|
4529
|
+
let elWidth = parseValToPx(rawWidth, logicalWidth.value, 100)
|
|
4530
|
+
let elHeight = parseValToPx(rawHeight, logicalHeight.value, 40)
|
|
4531
|
+
|
|
4532
|
+
const domEl = typeof document !== 'undefined' ? document.getElementById(`canvas-el-${el.id}`) : null
|
|
4533
|
+
if (domEl && el.type === 'text') {
|
|
4534
|
+
if (domEl.offsetHeight > 0) elHeight = domEl.offsetHeight
|
|
4535
|
+
if (el.style?.width === 'auto' && domEl.offsetWidth > 0) elWidth = domEl.offsetWidth
|
|
4268
4536
|
}
|
|
4269
4537
|
|
|
4270
4538
|
const relLeft = ((elLeft - minLeft) / groupWidth) * 100
|
|
@@ -4278,6 +4546,20 @@ function combineElements() {
|
|
|
4278
4546
|
childClone.style.width = `${relWidth.toFixed(4)}%`
|
|
4279
4547
|
childClone.style.height = `${relHeight.toFixed(4)}%`
|
|
4280
4548
|
|
|
4549
|
+
;(childClone as any).left = elLeft - minLeft
|
|
4550
|
+
;(childClone as any).top = elTop - minTop
|
|
4551
|
+
;(childClone as any).width = elWidth
|
|
4552
|
+
;(childClone as any).height = elHeight
|
|
4553
|
+
|
|
4554
|
+
if (childClone.type === 'line') {
|
|
4555
|
+
if (childClone.startPoint && Array.isArray(childClone.startPoint)) {
|
|
4556
|
+
childClone.startPoint = [childClone.startPoint[0] - minLeft, childClone.startPoint[1] - minTop]
|
|
4557
|
+
}
|
|
4558
|
+
if (childClone.endPoint && Array.isArray(childClone.endPoint)) {
|
|
4559
|
+
childClone.endPoint = [childClone.endPoint[0] - minLeft, childClone.endPoint[1] - minTop]
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
|
|
4281
4563
|
return childClone
|
|
4282
4564
|
})
|
|
4283
4565
|
|
|
@@ -4295,6 +4577,10 @@ function combineElements() {
|
|
|
4295
4577
|
height: `${((groupHeight / logicalHeight.value) * 100).toFixed(4)}%`,
|
|
4296
4578
|
rotate: 0
|
|
4297
4579
|
},
|
|
4580
|
+
left: minLeft,
|
|
4581
|
+
top: minTop,
|
|
4582
|
+
width: groupWidth,
|
|
4583
|
+
height: groupHeight,
|
|
4298
4584
|
elements: childElements
|
|
4299
4585
|
}
|
|
4300
4586
|
|
|
@@ -4367,6 +4653,22 @@ function uncombineElement(groupId?: string) {
|
|
|
4367
4653
|
childClone.style.width = `${((absWidthPx / logicalWidth.value) * 100).toFixed(4)}%`
|
|
4368
4654
|
childClone.style.height = `${((absHeightPx / logicalHeight.value) * 100).toFixed(4)}%`
|
|
4369
4655
|
|
|
4656
|
+
;(childClone as any).left = absLeftPx
|
|
4657
|
+
;(childClone as any).top = absTopPx
|
|
4658
|
+
;(childClone as any).width = absWidthPx
|
|
4659
|
+
;(childClone as any).height = absHeightPx
|
|
4660
|
+
|
|
4661
|
+
if (childClone.type === 'line') {
|
|
4662
|
+
const dX = groupLeft
|
|
4663
|
+
const dY = groupTop
|
|
4664
|
+
if (childClone.startPoint && Array.isArray(childClone.startPoint)) {
|
|
4665
|
+
childClone.startPoint = [childClone.startPoint[0] + dX, childClone.startPoint[1] + dY]
|
|
4666
|
+
}
|
|
4667
|
+
if (childClone.endPoint && Array.isArray(childClone.endPoint)) {
|
|
4668
|
+
childClone.endPoint = [childClone.endPoint[0] + dX, childClone.endPoint[1] + dY]
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
|
|
4370
4672
|
return childClone
|
|
4371
4673
|
})
|
|
4372
4674
|
|
|
@@ -4488,10 +4790,10 @@ function updateGroupBounds(groupEl: SlideElement) {
|
|
|
4488
4790
|
groupEl.style.width = isWidthPx ? `${newGroupWidth.toFixed(1)}px` : `${((newGroupWidth / parentDim.width) * 100).toFixed(4)}%`
|
|
4489
4791
|
groupEl.style.height = isHeightPx ? `${newGroupHeight.toFixed(1)}px` : `${((newGroupHeight / parentDim.height) * 100).toFixed(4)}%`
|
|
4490
4792
|
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4793
|
+
;(groupEl as any).left = minAbsLeft
|
|
4794
|
+
;(groupEl as any).top = minAbsTop
|
|
4795
|
+
;(groupEl as any).width = newGroupWidth
|
|
4796
|
+
;(groupEl as any).height = newGroupHeight
|
|
4495
4797
|
|
|
4496
4798
|
absChildren.forEach(({ child, absLeft, absTop, absWidth, absHeight }) => {
|
|
4497
4799
|
const newRelLeftPct = ((absLeft - minAbsLeft) / newGroupWidth) * 100
|
|
@@ -4505,10 +4807,10 @@ function updateGroupBounds(groupEl: SlideElement) {
|
|
|
4505
4807
|
child.style.width = `${newRelWidthPct.toFixed(4)}%`
|
|
4506
4808
|
child.style.height = `${newRelHeightPct.toFixed(4)}%`
|
|
4507
4809
|
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4810
|
+
;(child as any).left = absLeft - minAbsLeft
|
|
4811
|
+
;(child as any).top = absTop - minAbsTop
|
|
4812
|
+
;(child as any).width = absWidth
|
|
4813
|
+
;(child as any).height = absHeight
|
|
4512
4814
|
})
|
|
4513
4815
|
|
|
4514
4816
|
// Recursive parent group bounds update if nested in another group
|
|
@@ -4548,6 +4850,8 @@ function deleteElement(id: string, silent = false) {
|
|
|
4548
4850
|
const currentAnimationIndex = ref(0)
|
|
4549
4851
|
const isPlayingAnimation = ref(false)
|
|
4550
4852
|
const playedTriggerAnimations = ref<string[]>([])
|
|
4853
|
+
// 动画代际:快速跳步/换页/退出放映时 +1,使挂起的 autoNext setTimeout 链失效(防止旧链在新页继续跑)
|
|
4854
|
+
const animationGeneration = ref(0)
|
|
4551
4855
|
|
|
4552
4856
|
function getFormatedAnimationsForSlide(slide: Slide) {
|
|
4553
4857
|
if (!slide.animations) return []
|
|
@@ -4643,7 +4947,9 @@ function runAnimationStep() {
|
|
|
4643
4947
|
if (finishedCount === animations.length) {
|
|
4644
4948
|
isPlayingAnimation.value = false
|
|
4645
4949
|
if (autoNext) {
|
|
4950
|
+
const gen = animationGeneration.value
|
|
4646
4951
|
setTimeout(() => {
|
|
4952
|
+
if (gen !== animationGeneration.value) return
|
|
4647
4953
|
runAnimationStep()
|
|
4648
4954
|
}, 50)
|
|
4649
4955
|
}
|
|
@@ -4669,36 +4975,6 @@ function runAnimationStep() {
|
|
|
4669
4975
|
}
|
|
4670
4976
|
}
|
|
4671
4977
|
|
|
4672
|
-
function revokeAnimationStep() {
|
|
4673
|
-
if (currentAnimationIndex.value <= 0) return
|
|
4674
|
-
|
|
4675
|
-
currentAnimationIndex.value -= 1
|
|
4676
|
-
const formatted = formatedAnimations.value
|
|
4677
|
-
const { animations } = formatted[currentAnimationIndex.value]
|
|
4678
|
-
|
|
4679
|
-
for (const animation of animations) {
|
|
4680
|
-
const el = document.getElementById(`screen-element-${animation.elId}`)
|
|
4681
|
-
if (!el) continue
|
|
4682
|
-
|
|
4683
|
-
el.style.animationDuration = ''
|
|
4684
|
-
el.style.animationDelay = ''
|
|
4685
|
-
el.style.animationIterationCount = ''
|
|
4686
|
-
el.style.removeProperty('--animate-duration')
|
|
4687
|
-
el.style.removeProperty('--animate-repeat')
|
|
4688
|
-
el.style.removeProperty('visibility')
|
|
4689
|
-
for (const cls of Array.from(el.classList)) {
|
|
4690
|
-
if (cls.startsWith('animate__')) {
|
|
4691
|
-
el.classList.remove(cls)
|
|
4692
|
-
}
|
|
4693
|
-
}
|
|
4694
|
-
el.classList.remove('animate__animated')
|
|
4695
|
-
}
|
|
4696
|
-
|
|
4697
|
-
if (animations.every(item => item.type === 'attention')) {
|
|
4698
|
-
revokeAnimationStep()
|
|
4699
|
-
}
|
|
4700
|
-
}
|
|
4701
|
-
|
|
4702
4978
|
function runTriggerAnimations(clickedElId: string) {
|
|
4703
4979
|
if (!activeSlide.value || !activeSlide.value.animations) return
|
|
4704
4980
|
const elIds = activeSlide.value.elements.map(el => el.id)
|
|
@@ -4768,11 +5044,62 @@ function restoreAnimationState(slide: Slide, targetIndex: number) {
|
|
|
4768
5044
|
}
|
|
4769
5045
|
}
|
|
4770
5046
|
|
|
5047
|
+
// 纯自动步:全部动画都是 auto / meantime 触发(不消耗点击)
|
|
5048
|
+
function isAutoAnimationStep(step: FormatedAnimation): boolean {
|
|
5049
|
+
return step.animations.every(a => a.trigger === 'auto' || a.trigger === 'meantime')
|
|
5050
|
+
}
|
|
5051
|
+
|
|
5052
|
+
function clearElementAnimationState(el: HTMLElement) {
|
|
5053
|
+
el.style.animationDuration = ''
|
|
5054
|
+
el.style.animationDelay = ''
|
|
5055
|
+
el.style.animationIterationCount = ''
|
|
5056
|
+
el.style.removeProperty('--animate-duration')
|
|
5057
|
+
el.style.removeProperty('--animate-repeat')
|
|
5058
|
+
el.style.removeProperty('visibility')
|
|
5059
|
+
for (const cls of Array.from(el.classList)) {
|
|
5060
|
+
if (cls.startsWith('animate__')) {
|
|
5061
|
+
el.classList.remove(cls)
|
|
5062
|
+
}
|
|
5063
|
+
}
|
|
5064
|
+
el.classList.remove('animate__animated')
|
|
5065
|
+
}
|
|
5066
|
+
|
|
5067
|
+
// 瞬间补齐 [currentAnimationIndex, target) 的动画步(不播动画,元素直接就位),并作废挂起的自动链
|
|
5068
|
+
function completeAnimationStepsTo(target: number) {
|
|
5069
|
+
const formatted = formatedAnimations.value
|
|
5070
|
+
for (let i = currentAnimationIndex.value; i < target && i < formatted.length; i++) {
|
|
5071
|
+
for (const animation of formatted[i].animations) {
|
|
5072
|
+
const el = document.getElementById(`screen-element-${animation.elId}`)
|
|
5073
|
+
if (!el) continue
|
|
5074
|
+
clearElementAnimationState(el)
|
|
5075
|
+
if (animation.type === 'out') el.style.visibility = 'hidden'
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
animationGeneration.value++
|
|
5079
|
+
isPlayingAnimation.value = false
|
|
5080
|
+
currentAnimationIndex.value = Math.min(target, formatted.length)
|
|
5081
|
+
}
|
|
5082
|
+
|
|
5083
|
+
// 撤销 [target, currentAnimationIndex) 的动画步:in→隐藏、out→恢复可见,并作废挂起的自动链
|
|
5084
|
+
function revokeAnimationStepsTo(target: number) {
|
|
5085
|
+
const formatted = formatedAnimations.value
|
|
5086
|
+
for (let i = target; i < currentAnimationIndex.value && i < formatted.length; i++) {
|
|
5087
|
+
for (const animation of formatted[i].animations) {
|
|
5088
|
+
const el = document.getElementById(`screen-element-${animation.elId}`)
|
|
5089
|
+
if (!el) continue
|
|
5090
|
+
clearElementAnimationState(el)
|
|
5091
|
+
if (animation.type === 'in') el.style.visibility = 'hidden'
|
|
5092
|
+
else if (animation.type === 'out') el.style.removeProperty('visibility')
|
|
5093
|
+
}
|
|
5094
|
+
}
|
|
5095
|
+
animationGeneration.value++
|
|
5096
|
+
isPlayingAnimation.value = false
|
|
5097
|
+
currentAnimationIndex.value = Math.max(0, target)
|
|
5098
|
+
}
|
|
5099
|
+
|
|
4771
5100
|
function execNext() {
|
|
4772
5101
|
const formatted = formatedAnimations.value
|
|
4773
|
-
|
|
4774
|
-
runAnimationStep()
|
|
4775
|
-
} else {
|
|
5102
|
+
const goNextSlide = () => {
|
|
4776
5103
|
if (activeIndex.value < slides.value.length - 1) {
|
|
4777
5104
|
activeIndex.value++
|
|
4778
5105
|
currentAnimationIndex.value = 0
|
|
@@ -4781,22 +5108,42 @@ function execNext() {
|
|
|
4781
5108
|
notify('演示已结束')
|
|
4782
5109
|
}
|
|
4783
5110
|
}
|
|
5111
|
+
if (formatted.length && currentAnimationIndex.value < formatted.length) {
|
|
5112
|
+
// 连续的纯自动步不消耗点击:瞬间补齐,停到下一个点击步或本页末尾(PPT 行为)
|
|
5113
|
+
let target = currentAnimationIndex.value
|
|
5114
|
+
while (target < formatted.length && isAutoAnimationStep(formatted[target])) target++
|
|
5115
|
+
if (target > currentAnimationIndex.value) {
|
|
5116
|
+
completeAnimationStepsTo(target)
|
|
5117
|
+
}
|
|
5118
|
+
if (currentAnimationIndex.value < formatted.length) {
|
|
5119
|
+
runAnimationStep()
|
|
5120
|
+
} else {
|
|
5121
|
+
goNextSlide()
|
|
5122
|
+
}
|
|
5123
|
+
} else {
|
|
5124
|
+
goNextSlide()
|
|
5125
|
+
}
|
|
4784
5126
|
}
|
|
4785
5127
|
|
|
4786
5128
|
function execPrev() {
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
5129
|
+
const formatted = formatedAnimations.value
|
|
5130
|
+
// 回退只落在"点击步"上:纯自动步不占点击;已播步里没有点击步时直接退回上一页
|
|
5131
|
+
let target = currentAnimationIndex.value
|
|
5132
|
+
while (target > 0 && isAutoAnimationStep(formatted[target - 1])) target--
|
|
5133
|
+
if (target > 0) {
|
|
5134
|
+
revokeAnimationStepsTo(target - 1)
|
|
5135
|
+
return
|
|
5136
|
+
}
|
|
5137
|
+
// target===0:已播步全是自动步(不占点击)→ 视同本页无点击交互,直接退回上一页
|
|
5138
|
+
if (activeIndex.value > 0) {
|
|
5139
|
+
activeIndex.value--
|
|
5140
|
+
const prevSlide = slides.value[activeIndex.value]
|
|
5141
|
+
if (prevSlide) {
|
|
5142
|
+
const prevFormatted = getFormatedAnimationsForSlide(prevSlide)
|
|
5143
|
+
currentAnimationIndex.value = prevFormatted.length
|
|
5144
|
+
nextTick(() => {
|
|
5145
|
+
restoreAnimationState(prevSlide, currentAnimationIndex.value)
|
|
5146
|
+
})
|
|
4800
5147
|
}
|
|
4801
5148
|
}
|
|
4802
5149
|
}
|
|
@@ -4850,6 +5197,7 @@ function needWaitAnimation(slide: Slide, elId: string, animationIndex: number) {
|
|
|
4850
5197
|
|
|
4851
5198
|
watch([activeIndex, isPresenting], () => {
|
|
4852
5199
|
playedTriggerAnimations.value = []
|
|
5200
|
+
animationGeneration.value++ // 换页/进出放映:作废挂起的 autoNext 自动链
|
|
4853
5201
|
currentAnimationIndex.value = 0
|
|
4854
5202
|
if (!isPresenting.value) return
|
|
4855
5203
|
|
|
@@ -5703,6 +6051,10 @@ function convertPPTistToLocalIfNeed(newSlides: any): Slide[] {
|
|
|
5703
6051
|
if ((el.wrap === 'none' || el.wrap === false) && (!el.style.whiteSpace || el.style.whiteSpace === 'normal')) {
|
|
5704
6052
|
el.style.whiteSpace = 'nowrap'
|
|
5705
6053
|
}
|
|
6054
|
+
const rawLh = el.style?.lineHeight || el.lineHeight || el.text?.lineHeight
|
|
6055
|
+
if (rawLh && !el.style.lineHeight) {
|
|
6056
|
+
el.style.lineHeight = rawLh
|
|
6057
|
+
}
|
|
5706
6058
|
if (el.shadow) {
|
|
5707
6059
|
if (isEffectiveLocalShadow(el.shadow)) {
|
|
5708
6060
|
if (!el.style.boxShadow || el.style.boxShadow === 'none') {
|
|
@@ -5873,7 +6225,7 @@ function convertPPTistToLocalIfNeed(newSlides: any): Slide[] {
|
|
|
5873
6225
|
|
|
5874
6226
|
const normType = String(shapType).toLowerCase()
|
|
5875
6227
|
|
|
5876
|
-
for (const group of
|
|
6228
|
+
for (const group of shapeList()) {
|
|
5877
6229
|
for (const item of group.children) {
|
|
5878
6230
|
const itemPptxType = (item.pptxShapeType || '').toLowerCase()
|
|
5879
6231
|
const itemFormula = (item.pathFormula || '').toLowerCase()
|
|
@@ -5894,8 +6246,8 @@ function convertPPTistToLocalIfNeed(newSlides: any): Slide[] {
|
|
|
5894
6246
|
const matchedShapType = item.pptxShapeType || shapType
|
|
5895
6247
|
const matchedPathFormula = item.pathFormula
|
|
5896
6248
|
|
|
5897
|
-
if (item.pathFormula &&
|
|
5898
|
-
const formula =
|
|
6249
|
+
if (item.pathFormula && shapeFormulas()[item.pathFormula]) {
|
|
6250
|
+
const formula = shapeFormulas()[item.pathFormula]
|
|
5899
6251
|
let kps = formula.defaultValue ? [...formula.defaultValue] : []
|
|
5900
6252
|
if (el.keypoints) {
|
|
5901
6253
|
if (Array.isArray(el.keypoints) && el.keypoints.length > 0) {
|
|
@@ -6648,7 +7000,7 @@ function convertPPTistToLocalIfNeed(newSlides: any): Slide[] {
|
|
|
6648
7000
|
let pFormula = el.pathFormula
|
|
6649
7001
|
if (!pFormula && el.shapType) {
|
|
6650
7002
|
const norm = String(el.shapType).toLowerCase()
|
|
6651
|
-
let shape =
|
|
7003
|
+
let shape = shapeList().flatMap((x: any) => x.children).find((item: any) => {
|
|
6652
7004
|
const t1 = (item.pptxShapeType || '').toLowerCase()
|
|
6653
7005
|
const t2 = (item.pathFormula || '').toLowerCase()
|
|
6654
7006
|
return t1 === norm || t2 === norm ||
|
|
@@ -6663,7 +7015,7 @@ function convertPPTistToLocalIfNeed(newSlides: any): Slide[] {
|
|
|
6663
7015
|
if (shape) pFormula = shape.pathFormula
|
|
6664
7016
|
}
|
|
6665
7017
|
if (pFormula) {
|
|
6666
|
-
const formulaObj =
|
|
7018
|
+
const formulaObj = shapeFormulas()[pFormula as string]
|
|
6667
7019
|
if (formulaObj && formulaObj.defaultValue) {
|
|
6668
7020
|
const kps = el.keypoints && Array.isArray(el.keypoints) && el.keypoints.length > 0 ? [...el.keypoints] : []
|
|
6669
7021
|
const defaults = formulaObj.defaultValue
|
|
@@ -6704,7 +7056,7 @@ function convertPPTistToLocalIfNeed(newSlides: any): Slide[] {
|
|
|
6704
7056
|
pathFormula: (() => {
|
|
6705
7057
|
if (type === 'shape' && el.shapType && !el.pathFormula) {
|
|
6706
7058
|
const norm = String(el.shapType).toLowerCase()
|
|
6707
|
-
let shape =
|
|
7059
|
+
let shape = shapeList().flatMap((x: any) => x.children).find((item: any) => {
|
|
6708
7060
|
const t1 = (item.pptxShapeType || '').toLowerCase()
|
|
6709
7061
|
const t2 = (item.pathFormula || '').toLowerCase()
|
|
6710
7062
|
return t1 === norm || t2 === norm ||
|
|
@@ -7853,6 +8205,7 @@ async function aiReplaceCurrentSlide(options?: { primaryCategory?: string; secon
|
|
|
7853
8205
|
notify('正在进行一键美化,替换当前幻灯片内容...')
|
|
7854
8206
|
|
|
7855
8207
|
try {
|
|
8208
|
+
await ensurePptxShapes() // 后续 pptParserFn/convertPPTistToLocalIfNeed 同步消费形状表,先确保就绪
|
|
7856
8209
|
const pureText = extractPureTextFromSlide(activeSlide.value)
|
|
7857
8210
|
if (!pureText) {
|
|
7858
8211
|
notify('当前幻灯片没有可提取的纯文字内容')
|
|
@@ -8891,6 +9244,7 @@ export function useSlides() {
|
|
|
8891
9244
|
zoomRatio,
|
|
8892
9245
|
fitTrigger,
|
|
8893
9246
|
slides,
|
|
9247
|
+
slidesRevision,
|
|
8894
9248
|
activeIndex,
|
|
8895
9249
|
activeSlide,
|
|
8896
9250
|
selectedSlideIndexes,
|
|
@@ -8971,6 +9325,7 @@ export function useSlides() {
|
|
|
8971
9325
|
slideClipboard,
|
|
8972
9326
|
pasteSlideData,
|
|
8973
9327
|
pasteElementsData,
|
|
9328
|
+
copySelectedElements,
|
|
8974
9329
|
handleUnifiedPaste,
|
|
8975
9330
|
shapeFormatPainter,
|
|
8976
9331
|
currentSlideAnimations,
|