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.
@@ -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 (!el.style) el.style = {}
69
- el.style.flipH = !el.style.flipH
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 (!el.style) el.style = {}
83
- el.style.flipV = !el.style.flipV
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
- // 素材库配置状态(支持获取分类树/首页、分页查询、获取单独类型、素材保存 API)
344
- export const DEFAULT_MATERIAL_API_URL = '/jvs-dam/api/dam/material/tree'
345
- export const DEFAULT_MATERIAL_PAGE_API_URL = '/jvs-dam/api/dam/material/page'
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?.VITE_MATERIAL_TREE_API_URL || (import.meta as any).env?.VITE_MATERIAL_API_URL || (import.meta as any).env?.VITE_MATERIAL_LIST_API_URL || DEFAULT_MATERIAL_API_URL)
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
- /** 素材库获取所有内容/首页分类树的 API 地址 */
361
+ /** 素材库检索公网素材/所有内容的 API 地址 */
361
362
  const materialApiUrl = ref<string>(defaultEnvMaterialUrl)
362
- /** 素材库首页分类树 API 地址 */
363
+ /** 素材库首页/分类检索 API 地址 */
363
364
  const materialTreeApiUrl = ref<string>(defaultEnvMaterialUrl)
364
- /** 素材库分页查询 API 地址 */
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
- return {
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
+ }