jvs-ppt-ui 0.0.12 → 0.0.14
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/dist/lib/jvs-ppt-ui.es.js +16545 -16459
- package/dist/lib/jvs-ppt-ui.umd.js +85 -85
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/App.vue +3 -3
- package/src/components/CanvasArea.vue +6 -5
- package/src/components/Header.vue +5 -0
- package/src/components/PresentationMode.vue +2 -1
- package/src/components/SidebarLeft.vue +3 -2
- package/src/components/TemplateGallery.vue +217 -119
- package/src/composables/sidebar/useSidebarElementTransform.ts +17 -4
- package/src/composables/useSlides.ts +19 -9
- package/src/utils/groupFlip.ts +108 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { computed, type Ref, type ComputedRef } from 'vue'
|
|
2
2
|
import { shapeFormulas } from '../../utils/pptxShapesLazy'
|
|
3
3
|
import { useSlides } from '../useSlides'
|
|
4
|
+
import { applyGroupGeometryFlip } from '../../utils/groupFlip'
|
|
4
5
|
|
|
5
6
|
export function useSidebarElementTransform(
|
|
6
7
|
selectedElement: ComputedRef<any>,
|
|
@@ -65,10 +66,16 @@ export function useSidebarElementTransform(
|
|
|
65
66
|
if (selectedElementIds.value.length > 1 && activeSlide.value) {
|
|
66
67
|
activeSlide.value.elements.forEach((el: any) => {
|
|
67
68
|
if (selectedElementIds.value.includes(el.id)) {
|
|
68
|
-
if (
|
|
69
|
-
|
|
69
|
+
if (el.type === 'group') {
|
|
70
|
+
applyGroupGeometryFlip(el, true, false)
|
|
71
|
+
} else {
|
|
72
|
+
if (!el.style) el.style = {}
|
|
73
|
+
el.style.flipH = !el.style.flipH
|
|
74
|
+
}
|
|
70
75
|
}
|
|
71
76
|
})
|
|
77
|
+
} else if (selectedElement.value && selectedElement.value.type === 'group') {
|
|
78
|
+
applyGroupGeometryFlip(selectedElement.value, true, false)
|
|
72
79
|
} else {
|
|
73
80
|
getStyle().flipH = !getStyle().flipH
|
|
74
81
|
}
|
|
@@ -79,10 +86,16 @@ export function useSidebarElementTransform(
|
|
|
79
86
|
if (selectedElementIds.value.length > 1 && activeSlide.value) {
|
|
80
87
|
activeSlide.value.elements.forEach((el: any) => {
|
|
81
88
|
if (selectedElementIds.value.includes(el.id)) {
|
|
82
|
-
if (
|
|
83
|
-
|
|
89
|
+
if (el.type === 'group') {
|
|
90
|
+
applyGroupGeometryFlip(el, false, true)
|
|
91
|
+
} else {
|
|
92
|
+
if (!el.style) el.style = {}
|
|
93
|
+
el.style.flipV = !el.style.flipV
|
|
94
|
+
}
|
|
84
95
|
}
|
|
85
96
|
})
|
|
97
|
+
} else if (selectedElement.value && selectedElement.value.type === 'group') {
|
|
98
|
+
applyGroupGeometryFlip(selectedElement.value, false, true)
|
|
86
99
|
} else {
|
|
87
100
|
getStyle().flipV = !getStyle().flipV
|
|
88
101
|
}
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
inferElementTextType,
|
|
13
13
|
normalizeTextType
|
|
14
14
|
} from '../utils/textTypes'
|
|
15
|
+
import { applyGroupGeometryFlip } from '../utils/groupFlip'
|
|
15
16
|
import type { CustomUploadHandler, UploadResourceContext, UploadResourceType } from '../types'
|
|
16
17
|
|
|
17
18
|
export type { SlideTextType }
|
|
@@ -340,15 +341,15 @@ function setAiConfig(config?: { apiUrl?: string; headers?: Record<string, string
|
|
|
340
341
|
}
|
|
341
342
|
}
|
|
342
343
|
|
|
343
|
-
//
|
|
344
|
-
export const DEFAULT_MATERIAL_API_URL = '/jvs-dam/
|
|
345
|
-
export const DEFAULT_MATERIAL_PAGE_API_URL = '/jvs-dam/
|
|
344
|
+
// 素材库配置状态(公网免版权素材检索 stock/search、单页模板、素材保存 API)
|
|
345
|
+
export const DEFAULT_MATERIAL_API_URL = '/jvs-dam/stock/search'
|
|
346
|
+
export const DEFAULT_MATERIAL_PAGE_API_URL = '/jvs-dam/stock/search'
|
|
346
347
|
|
|
347
348
|
const defaultEnvMaterialUrl = (typeof import.meta !== 'undefined' && (import.meta as any).env)
|
|
348
|
-
? String((import.meta as any).env?.
|
|
349
|
+
? String((import.meta as any).env?.VITE_MATERIAL_STOCK_API_URL || (import.meta as any).env?.VITE_MATERIAL_API_URL || (import.meta as any).env?.VITE_MATERIAL_LIST_API_URL || (import.meta as any).env?.VITE_MATERIAL_TREE_API_URL || DEFAULT_MATERIAL_API_URL)
|
|
349
350
|
: DEFAULT_MATERIAL_API_URL
|
|
350
351
|
const defaultEnvMaterialPageUrl = (typeof import.meta !== 'undefined' && (import.meta as any).env)
|
|
351
|
-
? String((import.meta as any).env?.VITE_MATERIAL_PAGE_API_URL || DEFAULT_MATERIAL_PAGE_API_URL)
|
|
352
|
+
? String((import.meta as any).env?.VITE_MATERIAL_PAGE_API_URL || (import.meta as any).env?.VITE_MATERIAL_STOCK_API_URL || DEFAULT_MATERIAL_PAGE_API_URL)
|
|
352
353
|
: DEFAULT_MATERIAL_PAGE_API_URL
|
|
353
354
|
const defaultEnvMaterialTypeUrl = (typeof import.meta !== 'undefined' && (import.meta as any).env?.VITE_MATERIAL_TYPE_API_URL)
|
|
354
355
|
? String((import.meta as any).env.VITE_MATERIAL_TYPE_API_URL)
|
|
@@ -357,11 +358,11 @@ const defaultEnvMaterialSaveUrl = (typeof import.meta !== 'undefined' && (import
|
|
|
357
358
|
? String((import.meta as any).env.VITE_MATERIAL_SAVE_API_URL)
|
|
358
359
|
: ''
|
|
359
360
|
|
|
360
|
-
/**
|
|
361
|
+
/** 素材库检索公网素材/所有内容的 API 地址 */
|
|
361
362
|
const materialApiUrl = ref<string>(defaultEnvMaterialUrl)
|
|
362
|
-
/**
|
|
363
|
+
/** 素材库首页/分类检索 API 地址 */
|
|
363
364
|
const materialTreeApiUrl = ref<string>(defaultEnvMaterialUrl)
|
|
364
|
-
/**
|
|
365
|
+
/** 素材库分页/关键词检索 API 地址 */
|
|
365
366
|
const materialPageApiUrl = ref<string>(defaultEnvMaterialPageUrl)
|
|
366
367
|
/** 素材库按类型获取单独分类素材的 API 地址 */
|
|
367
368
|
const materialTypeApiUrl = ref<string>(defaultEnvMaterialTypeUrl)
|
|
@@ -6590,7 +6591,7 @@ function convertPPTistToLocalIfNeed(newSlides: any): Slide[] {
|
|
|
6590
6591
|
const grpFlipH = !!(el.flipH || el.isFlipH || el.style?.flipH)
|
|
6591
6592
|
const grpFlipV = !!(el.flipV || el.isFlipV || el.style?.flipV)
|
|
6592
6593
|
|
|
6593
|
-
|
|
6594
|
+
const groupObj: any = {
|
|
6594
6595
|
id: el.id || 'group_' + Date.now().toString(36) + Math.random().toString(36).substring(2, 7),
|
|
6595
6596
|
type: 'group',
|
|
6596
6597
|
name: el.name || '组合',
|
|
@@ -6609,6 +6610,12 @@ function convertPPTistToLocalIfNeed(newSlides: any): Slide[] {
|
|
|
6609
6610
|
isFlipV: grpFlipV,
|
|
6610
6611
|
elements: el.elements.map((child: any) => convertChildRecursive(child, topW, topH))
|
|
6611
6612
|
}
|
|
6613
|
+
|
|
6614
|
+
if (grpFlipH || grpFlipV) {
|
|
6615
|
+
applyGroupGeometryFlip(groupObj, grpFlipH, grpFlipV)
|
|
6616
|
+
}
|
|
6617
|
+
|
|
6618
|
+
return groupObj
|
|
6612
6619
|
}
|
|
6613
6620
|
|
|
6614
6621
|
const elemPattern = extractPatternFromFill(el)
|
|
@@ -7291,6 +7298,9 @@ function preprocessImportedSlides(slides: any[]) {
|
|
|
7291
7298
|
}
|
|
7292
7299
|
processEl(child, grpW, grpH)
|
|
7293
7300
|
})
|
|
7301
|
+
if (el.flipH || el.isFlipH || el.style?.flipH || el.flipV || el.isFlipV || el.style?.flipV) {
|
|
7302
|
+
applyGroupGeometryFlip(el)
|
|
7303
|
+
}
|
|
7294
7304
|
}
|
|
7295
7305
|
}
|
|
7296
7306
|
for (const el of slide.elements) {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 组合 (Group) 翻转与几何镜像计算工具函数
|
|
3
|
+
*
|
|
4
|
+
* 遵循微软 PowerPoint / DrawingML 规范标准:
|
|
5
|
+
* 当对组合执行水平/垂直翻转时:
|
|
6
|
+
* 1. 组合内部子元素的相对几何位置镜像对调(left / top 沿中心线镜像);
|
|
7
|
+
* 2. 子元素若是形状/图片,其自身的翻转状态联动反转;
|
|
8
|
+
* 3. 子元素若是纯文本(Text),文字字形永远保持正向可读(Text is never mirrored),禁止倒影;
|
|
9
|
+
* 4. 几何镜像处理完成后,Group 容器自身不再需要使用 CSS scale(-1) 进行整体物理镜像。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export function applyGroupGeometryFlip(group: any, forceFlipH?: boolean, forceFlipV?: boolean) {
|
|
13
|
+
if (!group || !Array.isArray(group.elements) || group.elements.length === 0) return
|
|
14
|
+
|
|
15
|
+
const flipH = forceFlipH !== undefined ? forceFlipH : Boolean(group.flipH || group.isFlipH || group.style?.flipH)
|
|
16
|
+
const flipV = forceFlipV !== undefined ? forceFlipV : Boolean(group.flipV || group.isFlipV || group.style?.flipV)
|
|
17
|
+
|
|
18
|
+
if (!flipH && !flipV) return
|
|
19
|
+
|
|
20
|
+
const grpW = typeof group.width === 'number' && group.width > 0 ? group.width : (parseFloat(group.style?.width) || 0)
|
|
21
|
+
const grpH = typeof group.height === 'number' && group.height > 0 ? group.height : (parseFloat(group.style?.height) || 0)
|
|
22
|
+
|
|
23
|
+
for (const child of group.elements) {
|
|
24
|
+
if (!child) continue
|
|
25
|
+
|
|
26
|
+
// 1. 水平翻转:镜像相对水平位置
|
|
27
|
+
if (flipH) {
|
|
28
|
+
if (typeof child.style?.left === 'string' && child.style.left.endsWith('%')) {
|
|
29
|
+
const lPct = parseFloat(child.style.left) || 0
|
|
30
|
+
const wPct = parseFloat(child.style.width) || 0
|
|
31
|
+
child.style.left = `${+(100 - lPct - wPct).toFixed(4)}%`
|
|
32
|
+
} else if (typeof child.left === 'number' && grpW > 0) {
|
|
33
|
+
const cW = typeof child.width === 'number' ? child.width : (parseFloat(child.style?.width) || 0)
|
|
34
|
+
child.left = +(grpW - child.left - cW).toFixed(4)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 2. 垂直翻转:镜像相对垂直位置
|
|
39
|
+
if (flipV) {
|
|
40
|
+
if (typeof child.style?.top === 'string' && child.style.top.endsWith('%')) {
|
|
41
|
+
const tPct = parseFloat(child.style.top) || 0
|
|
42
|
+
const hPct = parseFloat(child.style.height) || 0
|
|
43
|
+
child.style.top = `${+(100 - tPct - hPct).toFixed(4)}%`
|
|
44
|
+
} else if (typeof child.top === 'number' && grpH > 0) {
|
|
45
|
+
const cH = typeof child.height === 'number' ? child.height : (parseFloat(child.style?.height) || 0)
|
|
46
|
+
child.top = +(grpH - child.top - cH).toFixed(4)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 3. 子元素自身翻转状态更新:纯文本和连线永不翻转,形状/图片等反转
|
|
51
|
+
const is2DArrow = /^(?:rightArrow|leftArrow|upArrow|downArrow|leftRightArrow|upDownArrow|quadArrow|leftRightUpArrow|bentArrow|uturnArrow|leftUpArrow|bentUpArrow|curvedRightArrow|curvedLeftArrow|curvedUpArrow|curvedDownArrow|stripedRightArrow|notchedRightArrow|pentagon|chevron|circularArrow|swooshArrow|.*ArrowCallout)$/i.test(String(child.shapType || ''))
|
|
52
|
+
const isConnector = !is2DArrow && (
|
|
53
|
+
child.type === 'line' ||
|
|
54
|
+
Boolean(child.startConnection || child.endConnection) ||
|
|
55
|
+
Boolean(child.borderTailEnd || child.borderHeadEnd) ||
|
|
56
|
+
/^(?:line|straightConnector\d*|bentConnector\d*|curvedConnector\d*)$/i.test(String(child.shapType || '')) ||
|
|
57
|
+
(/连接符/i.test(String(child.name || '')) && !is2DArrow)
|
|
58
|
+
)
|
|
59
|
+
const isPureText = child.type === 'text' || isConnector
|
|
60
|
+
|
|
61
|
+
if (isPureText) {
|
|
62
|
+
child.flipH = false
|
|
63
|
+
child.isFlipH = false
|
|
64
|
+
if (child.style) child.style.flipH = false
|
|
65
|
+
child.flipV = false
|
|
66
|
+
child.isFlipV = false
|
|
67
|
+
if (child.style) child.style.flipV = false
|
|
68
|
+
} else if (child.type === 'group') {
|
|
69
|
+
applyGroupGeometryFlip(child, flipH, flipV)
|
|
70
|
+
} else {
|
|
71
|
+
if (flipH) {
|
|
72
|
+
const curH = Boolean(child.flipH || child.isFlipH || child.style?.flipH)
|
|
73
|
+
child.flipH = !curH
|
|
74
|
+
child.isFlipH = !curH
|
|
75
|
+
if (child.style) child.style.flipH = !curH
|
|
76
|
+
}
|
|
77
|
+
if (flipV) {
|
|
78
|
+
const curV = Boolean(child.flipV || child.isFlipV || child.style?.flipV)
|
|
79
|
+
child.flipV = !curV
|
|
80
|
+
child.isFlipV = !curV
|
|
81
|
+
if (child.style) child.style.flipV = !curV
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 4. 子元素全部完成几何镜像后,Group 容器自身无需再 scale 镜像,避免将内部文本整体反转为倒影字
|
|
87
|
+
group.flipH = false
|
|
88
|
+
group.isFlipH = false
|
|
89
|
+
if (group.style) {
|
|
90
|
+
group.style.flipH = false
|
|
91
|
+
group.style.flipV = false
|
|
92
|
+
}
|
|
93
|
+
group.flipV = false
|
|
94
|
+
group.isFlipV = false
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 判断一个元素是否为包含文本的组合元素(或纯文本元素),
|
|
99
|
+
* 渲染层用此判断避免在外层容器施加 scale(-1) 导致内部文字倒影。
|
|
100
|
+
*/
|
|
101
|
+
export function isTextContainingElement(el: any): boolean {
|
|
102
|
+
if (!el) return false
|
|
103
|
+
if (el.type === 'text' || el.type === 'line') return true
|
|
104
|
+
if (el.type === 'group' && Array.isArray(el.elements)) {
|
|
105
|
+
return el.elements.some((child: any) => isTextContainingElement(child))
|
|
106
|
+
}
|
|
107
|
+
return false
|
|
108
|
+
}
|