jvs-ppt-ui 0.0.16 → 0.0.17
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 +21389 -21034
- package/dist/lib/jvs-ppt-ui.umd.js +83 -83
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/components/CanvasArea.vue +9707 -9566
- package/src/components/SidebarRight.vue +10 -6
- package/src/components/TemplateGallery.vue +36 -3
- package/src/components/canvas/elements/CanvasGroupElement.vue +214 -213
- package/src/components/canvas/elements/CanvasShapeElement.vue +894 -882
- package/src/components/sidebar/panels/SidebarTextFormatPanel.vue +1 -1
- package/src/composables/canvas/useCanvasText.ts +44 -18
- package/src/composables/sidebar/useSidebarTextFormat.ts +72 -1
- package/src/composables/useSlides.ts +9804 -9627
- package/src/utils/canvas/elementStyles.ts +256 -0
|
@@ -465,12 +465,15 @@ function applyTextStyleToHtml(html: string, styles: any) {
|
|
|
465
465
|
paragraphs.forEach(applyTextStyles)
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
// 综合处理字体与样式覆盖:如果指定了 fontFamily
|
|
469
|
-
if (styles.fontFamily) {
|
|
468
|
+
// 综合处理字体与样式覆盖:如果指定了 fontFamily 或 fontSize,清理/更新所有块级和内联节点的原有硬编码值
|
|
469
|
+
if (styles.fontFamily || styles.fontSize) {
|
|
470
470
|
div.querySelectorAll('p, div, li, h1, h2, h3, h4, h5, h6, span, b, strong, i, em, a').forEach((elem: any) => {
|
|
471
|
-
if (elem.style && elem.style.fontFamily) {
|
|
471
|
+
if (styles.fontFamily && elem.style && elem.style.fontFamily) {
|
|
472
472
|
elem.style.fontFamily = styles.fontFamily
|
|
473
473
|
}
|
|
474
|
+
if (styles.fontSize && elem.style && elem.style.fontSize) {
|
|
475
|
+
elem.style.fontSize = styles.fontSize
|
|
476
|
+
}
|
|
474
477
|
})
|
|
475
478
|
}
|
|
476
479
|
|
|
@@ -571,11 +574,12 @@ function setStyleVal(prop: string, val: any) {
|
|
|
571
574
|
}
|
|
572
575
|
}
|
|
573
576
|
|
|
574
|
-
if (prop === 'fontFamily') {
|
|
577
|
+
if (prop === 'fontFamily' || prop === 'fontSize') {
|
|
575
578
|
const container = document.getElementById(`editable-${el.id}`)
|
|
576
579
|
if (container) {
|
|
577
|
-
container.style.fontFamily = val
|
|
578
|
-
container.
|
|
580
|
+
if (prop === 'fontFamily') container.style.fontFamily = val
|
|
581
|
+
else container.style.fontSize = val
|
|
582
|
+
container.innerHTML = applyTextStyleToHtml(container.innerHTML, { [prop]: val })
|
|
579
583
|
if (el.type === 'shape') {
|
|
580
584
|
el.text = container.innerHTML
|
|
581
585
|
} else {
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
VideoPlay
|
|
12
12
|
} from '@element-plus/icons-vue'
|
|
13
13
|
import { useSlides, DEFAULT_MATERIAL_PAGE_API_URL } from '../composables/useSlides'
|
|
14
|
+
import { isImagePlaceholder } from '../utils/canvas/elementStyles'
|
|
14
15
|
|
|
15
16
|
const {
|
|
16
17
|
addElement,
|
|
@@ -20,7 +21,12 @@ const {
|
|
|
20
21
|
materialPageApiUrl,
|
|
21
22
|
materialHeaders,
|
|
22
23
|
activeTab,
|
|
23
|
-
materialPopoverVisible
|
|
24
|
+
materialPopoverVisible,
|
|
25
|
+
activeSlide,
|
|
26
|
+
selectedElementId,
|
|
27
|
+
placeholderMaterialTargetId,
|
|
28
|
+
resolveActivePlaceholderTargetId,
|
|
29
|
+
replacePlaceholderElement
|
|
24
30
|
} = useSlides()
|
|
25
31
|
|
|
26
32
|
export interface MaterialItem {
|
|
@@ -682,7 +688,7 @@ function formatMaterialTitle(title?: string, fallback = '素材', maxLen = 12):
|
|
|
682
688
|
return text.length > maxLen ? `${text.slice(0, maxLen)}...` : text
|
|
683
689
|
}
|
|
684
690
|
|
|
685
|
-
//
|
|
691
|
+
// 点击素材卡片插入画布(区分视频与图片/矢量/插画,若当前处于图片占位符替换状态则优先替换占位符)
|
|
686
692
|
function handleSelectMaterial(meta: MaterialItem) {
|
|
687
693
|
const isVid = isVideoMaterial(meta)
|
|
688
694
|
const targetUrl = meta.sourceUrl || meta.url || meta.downloadUrl || meta.previewUrl
|
|
@@ -692,9 +698,36 @@ function handleSelectMaterial(meta: MaterialItem) {
|
|
|
692
698
|
return
|
|
693
699
|
}
|
|
694
700
|
|
|
695
|
-
let initialDimensions: { width?: number; height?: number } | undefined = undefined
|
|
696
701
|
const rawW = meta.originalWidth || meta.width
|
|
697
702
|
const rawH = meta.originalHeight || meta.height
|
|
703
|
+
|
|
704
|
+
const currentSelectedEl = selectedElementId?.value
|
|
705
|
+
? activeSlide?.value?.elements?.find((el: any) => el.id === selectedElementId.value)
|
|
706
|
+
: null
|
|
707
|
+
|
|
708
|
+
const explicitTargetId =
|
|
709
|
+
placeholderMaterialTargetId?.value ||
|
|
710
|
+
(typeof window !== 'undefined' ? (window as any).__jvsPlaceholderTargetId : null)
|
|
711
|
+
|
|
712
|
+
const targetPlaceholderId =
|
|
713
|
+
(resolveActivePlaceholderTargetId ? resolveActivePlaceholderTargetId() : null) ||
|
|
714
|
+
(explicitTargetId && (!currentSelectedEl || currentSelectedEl.id === explicitTargetId)
|
|
715
|
+
? explicitTargetId
|
|
716
|
+
: (currentSelectedEl && isImagePlaceholder(currentSelectedEl) ? currentSelectedEl.id : null))
|
|
717
|
+
|
|
718
|
+
if (targetPlaceholderId && replacePlaceholderElement) {
|
|
719
|
+
const replaced = replacePlaceholderElement(targetPlaceholderId, targetUrl, {
|
|
720
|
+
isVideo: isVid,
|
|
721
|
+
width: Number(rawW) || undefined,
|
|
722
|
+
height: Number(rawH) || undefined,
|
|
723
|
+
notifyMessage: isVid
|
|
724
|
+
? `已使用视频 "${formatMaterialTitle(meta.title, '高清视频', 12)}" 替换占位符`
|
|
725
|
+
: `已使用素材 "${formatMaterialTitle(meta.title, '图片', 12)}" 替换占位符图片`
|
|
726
|
+
})
|
|
727
|
+
if (replaced) return
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
let initialDimensions: { width?: number; height?: number } | undefined = undefined
|
|
698
731
|
if (rawW && rawH && rawW > 0 && rawH > 0) {
|
|
699
732
|
const maxWidth = 480
|
|
700
733
|
const maxHeight = 360
|
|
@@ -1,213 +1,214 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
import CanvasTextElement from './CanvasTextElement.vue'
|
|
4
|
-
import CanvasShapeElement from './CanvasShapeElement.vue'
|
|
5
|
-
import CanvasLineElement from './CanvasLineElement.vue'
|
|
6
|
-
import CanvasImageElement from './CanvasImageElement.vue'
|
|
7
|
-
import SvgChart from '../../SvgChart.vue'
|
|
8
|
-
import { getElementImageSrc, hasElementFill, hasElementBorder, getElementDropShadowFilter } from '../../../utils/canvas/elementStyles'
|
|
9
|
-
|
|
10
|
-
const props = defineProps<{
|
|
11
|
-
element: any
|
|
12
|
-
activeSlide?: any
|
|
13
|
-
croppingElementId?: string | null
|
|
14
|
-
selectedElementIds?: string[]
|
|
15
|
-
selectedElementId?: string | null
|
|
16
|
-
isEditingText?: boolean
|
|
17
|
-
themeRecipe?: any
|
|
18
|
-
getTextStrokeStyle?: (el: any) => string
|
|
19
|
-
getElementTextShadow?: (el: any) => string
|
|
20
|
-
getRenderedContent?: (el: any) => string
|
|
21
|
-
// 直接父盒子的像素尺寸(顶层时为幻灯片逻辑尺寸),用于解析本组 % 样式,
|
|
22
|
-
// 再下传给子元素解析它们的 % 宽高。与 PresentationGroupChild 的
|
|
23
|
-
// parentW/parentH 锚同一思路:渲染期没有可查的 DOM 地图时用数据链解析。
|
|
24
|
-
parentWidthPx?: number
|
|
25
|
-
parentHeightPx?: number
|
|
26
|
-
}>()
|
|
27
|
-
|
|
28
|
-
function resolvePx(val: any, parentPx: number | undefined): number {
|
|
29
|
-
if (val === undefined || val === null || val === '' || val === 'auto') return NaN
|
|
30
|
-
if (typeof val === 'number') return val
|
|
31
|
-
const s = String(val).trim()
|
|
32
|
-
if (s.endsWith('%')) {
|
|
33
|
-
const pct = parseFloat(s)
|
|
34
|
-
const p = Number(parentPx)
|
|
35
|
-
if (!isNaN(pct) && isFinite(p) && p > 0) return (pct / 100) * p
|
|
36
|
-
return NaN
|
|
37
|
-
}
|
|
38
|
-
const n = parseFloat(s)
|
|
39
|
-
return isNaN(n) ? NaN : n
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// 本组盒子自身的像素尺寸:px 样式直接用,% 样式相对父盒解析。
|
|
43
|
-
const selfWidthPx = computed(() => {
|
|
44
|
-
const resolved = resolvePx((props.element as any)?.style?.width, props.parentWidthPx)
|
|
45
|
-
if (isFinite(resolved) && resolved > 0) return resolved
|
|
46
|
-
const p = Number(props.parentWidthPx)
|
|
47
|
-
return isFinite(p) && p > 0 ? p : NaN
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
const selfHeightPx = computed(() => {
|
|
51
|
-
const resolved = resolvePx((props.element as any)?.style?.height, props.parentHeightPx)
|
|
52
|
-
if (isFinite(resolved) && resolved > 0) return resolved
|
|
53
|
-
const p = Number(props.parentHeightPx)
|
|
54
|
-
return isFinite(p) && p > 0 ? p : NaN
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const emit = defineEmits<{
|
|
58
|
-
(e: 'childMouseDown', ev: MouseEvent, child: any, parent: any): void
|
|
59
|
-
(e: 'childClick', ev: MouseEvent, child: any, parent: any): void
|
|
60
|
-
(e: 'childDblClick', ev: MouseEvent, child: any, parent: any): void
|
|
61
|
-
(e: 'childContextMenu', ev: MouseEvent, child: any, parent: any): void
|
|
62
|
-
(e: 'textFocus', child: any): void
|
|
63
|
-
(e: 'textBlur', ev: FocusEvent, child: any): void
|
|
64
|
-
(e: 'textInput', ev: Event, child: any): void
|
|
65
|
-
(e: 'textKeyDown', ev: KeyboardEvent, child: any): void
|
|
66
|
-
(e: 'textMouseMove', ev: MouseEvent, child: any): void
|
|
67
|
-
(e: 'textMouseLeave', ev: MouseEvent, child: any): void
|
|
68
|
-
(e: 'startEditing', id: string, ev?: MouseEvent): void
|
|
69
|
-
(e: 'formulaDblClick', child: any): void
|
|
70
|
-
(e: 'tableDblClick', ev: MouseEvent, child: any): void
|
|
71
|
-
}>()
|
|
72
|
-
|
|
73
|
-
function sortGroupElements(elements: any[]) {
|
|
74
|
-
if (!elements || !Array.isArray(elements)) return []
|
|
75
|
-
return [...elements].sort((a: any, b: any) => {
|
|
76
|
-
const orderA = a.order !== undefined ? a.order : (parseFloat(a.style?.zIndex) || 0)
|
|
77
|
-
const orderB = b.order !== undefined ? b.order : (parseFloat(b.style?.zIndex) || 0)
|
|
78
|
-
return orderA - orderB
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
</script>
|
|
82
|
-
|
|
83
|
-
<template>
|
|
84
|
-
<div class="group-container-wrapper" style="width: 100%; height: 100%; position: relative; overflow: visible; pointer-events: none;">
|
|
85
|
-
<div
|
|
86
|
-
v-for="child in sortGroupElements(element.elements || [])"
|
|
87
|
-
:key="child.id"
|
|
88
|
-
:id="`canvas-el-${child.id}`"
|
|
89
|
-
class="group-child-item"
|
|
90
|
-
:style="{
|
|
91
|
-
position: 'absolute',
|
|
92
|
-
left: child.style?.left,
|
|
93
|
-
top: child.style?.top,
|
|
94
|
-
width: child.style?.width,
|
|
95
|
-
height: (child.type === 'text' && (child.autoFit?.type === 'shape' || child.style?.height === 'auto') && !child.content?.includes('<table') && !child.latex && child.style?.writingMode !== 'vertical-rl' && child.style?.writingMode !== 'vertical-lr') ? 'auto' : (child.style?.height || (child.height ? `${child.height}px` : 'auto')),
|
|
96
|
-
minHeight: (child.type === 'text' && !child.content?.includes('<table') && !child.latex) ? child.style?.height : undefined,
|
|
97
|
-
transform: `rotate(${child.style?.rotate || 0}deg) scale(${child.type !== 'text' && child.type !== 'line' && ((child.style as any)?.flipH || child.flipH || child.isFlipH) ? -1 : 1}, ${child.type !== 'text' && child.type !== 'line' && ((child.style as any)?.flipV || child.flipV || child.isFlipV) ? -1 : 1})`,
|
|
98
|
-
opacity: child.style?.opacity !== undefined ? child.style.opacity : 1,
|
|
99
|
-
filter: getElementDropShadowFilter(child),
|
|
100
|
-
pointerEvents: child.type === 'group' ? 'none' : (((child.type === 'shape' || !child.type) && !hasElementFill(child) && !hasElementBorder(child)) ? 'none' : 'auto')
|
|
101
|
-
}"
|
|
102
|
-
@mousedown.stop="emit('childMouseDown', $event, child, element)"
|
|
103
|
-
@click.stop="emit('childClick', $event, child, element)"
|
|
104
|
-
@dblclick.stop="emit('childDblClick', $event, child, element)"
|
|
105
|
-
@contextmenu.stop.prevent="emit('childContextMenu', $event, child, element)"
|
|
106
|
-
>
|
|
107
|
-
<!-- Child Text -->
|
|
108
|
-
<template v-if="child.type === 'text'">
|
|
109
|
-
<CanvasTextElement
|
|
110
|
-
:element="child"
|
|
111
|
-
:active-slide="activeSlide"
|
|
112
|
-
:is-selected="selectedElementId === child.id"
|
|
113
|
-
:is-editing-text="isEditingText"
|
|
114
|
-
:selected-element-id="selectedElementId"
|
|
115
|
-
:theme-recipe="themeRecipe"
|
|
116
|
-
:rendered-content="getRenderedContent ? getRenderedContent(child) : child.content"
|
|
117
|
-
:get-text-stroke-style="getTextStrokeStyle"
|
|
118
|
-
:get-element-text-shadow="getElementTextShadow"
|
|
119
|
-
@focusin="emit('textFocus', child)"
|
|
120
|
-
@focusout="emit('textBlur', $event, child)"
|
|
121
|
-
@input="emit('textInput', $event, child)"
|
|
122
|
-
@keydown="emit('textKeyDown', $event, child)"
|
|
123
|
-
@mousemove="emit('textMouseMove', $event, child)"
|
|
124
|
-
@mouseleave="emit('textMouseLeave', $event, child)"
|
|
125
|
-
@dblclick="emit('startEditing', child.id)"
|
|
126
|
-
@formula-dbl-click="emit('formulaDblClick', child)"
|
|
127
|
-
@table-dbl-click="emit('tableDblClick', $event, child)"
|
|
128
|
-
/>
|
|
129
|
-
</template>
|
|
130
|
-
|
|
131
|
-
<!-- Child Shape -->
|
|
132
|
-
<template v-else-if="child.type === 'shape'">
|
|
133
|
-
<CanvasShapeElement
|
|
134
|
-
:element="child"
|
|
135
|
-
:active-slide="activeSlide"
|
|
136
|
-
:is-selected="selectedElementId === child.id"
|
|
137
|
-
:is-editing-text="isEditingText"
|
|
138
|
-
:selected-element-id="selectedElementId"
|
|
139
|
-
:theme-recipe="themeRecipe"
|
|
140
|
-
:parent-width-px="selfWidthPx"
|
|
141
|
-
:parent-height-px="selfHeightPx"
|
|
142
|
-
:get-text-stroke-style="getTextStrokeStyle"
|
|
143
|
-
:get-element-text-shadow="getElementTextShadow"
|
|
144
|
-
|
|
145
|
-
@text-
|
|
146
|
-
@text-
|
|
147
|
-
@text-
|
|
148
|
-
@
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
:
|
|
182
|
-
:
|
|
183
|
-
:
|
|
184
|
-
:parent-
|
|
185
|
-
:
|
|
186
|
-
:selected-element-
|
|
187
|
-
:
|
|
188
|
-
:
|
|
189
|
-
:
|
|
190
|
-
:get-
|
|
191
|
-
:get-
|
|
192
|
-
|
|
193
|
-
@child-
|
|
194
|
-
@child-
|
|
195
|
-
@child-
|
|
196
|
-
@
|
|
197
|
-
@text-
|
|
198
|
-
@text-
|
|
199
|
-
@text-
|
|
200
|
-
@text-
|
|
201
|
-
@text-mouse-
|
|
202
|
-
@
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
</
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import CanvasTextElement from './CanvasTextElement.vue'
|
|
4
|
+
import CanvasShapeElement from './CanvasShapeElement.vue'
|
|
5
|
+
import CanvasLineElement from './CanvasLineElement.vue'
|
|
6
|
+
import CanvasImageElement from './CanvasImageElement.vue'
|
|
7
|
+
import SvgChart from '../../SvgChart.vue'
|
|
8
|
+
import { getElementImageSrc, hasElementFill, hasElementBorder, getElementDropShadowFilter, isImagePlaceholder } from '../../../utils/canvas/elementStyles'
|
|
9
|
+
|
|
10
|
+
const props = defineProps<{
|
|
11
|
+
element: any
|
|
12
|
+
activeSlide?: any
|
|
13
|
+
croppingElementId?: string | null
|
|
14
|
+
selectedElementIds?: string[]
|
|
15
|
+
selectedElementId?: string | null
|
|
16
|
+
isEditingText?: boolean
|
|
17
|
+
themeRecipe?: any
|
|
18
|
+
getTextStrokeStyle?: (el: any) => string
|
|
19
|
+
getElementTextShadow?: (el: any) => string
|
|
20
|
+
getRenderedContent?: (el: any) => string
|
|
21
|
+
// 直接父盒子的像素尺寸(顶层时为幻灯片逻辑尺寸),用于解析本组 % 样式,
|
|
22
|
+
// 再下传给子元素解析它们的 % 宽高。与 PresentationGroupChild 的
|
|
23
|
+
// parentW/parentH 锚同一思路:渲染期没有可查的 DOM 地图时用数据链解析。
|
|
24
|
+
parentWidthPx?: number
|
|
25
|
+
parentHeightPx?: number
|
|
26
|
+
}>()
|
|
27
|
+
|
|
28
|
+
function resolvePx(val: any, parentPx: number | undefined): number {
|
|
29
|
+
if (val === undefined || val === null || val === '' || val === 'auto') return NaN
|
|
30
|
+
if (typeof val === 'number') return val
|
|
31
|
+
const s = String(val).trim()
|
|
32
|
+
if (s.endsWith('%')) {
|
|
33
|
+
const pct = parseFloat(s)
|
|
34
|
+
const p = Number(parentPx)
|
|
35
|
+
if (!isNaN(pct) && isFinite(p) && p > 0) return (pct / 100) * p
|
|
36
|
+
return NaN
|
|
37
|
+
}
|
|
38
|
+
const n = parseFloat(s)
|
|
39
|
+
return isNaN(n) ? NaN : n
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 本组盒子自身的像素尺寸:px 样式直接用,% 样式相对父盒解析。
|
|
43
|
+
const selfWidthPx = computed(() => {
|
|
44
|
+
const resolved = resolvePx((props.element as any)?.style?.width, props.parentWidthPx)
|
|
45
|
+
if (isFinite(resolved) && resolved > 0) return resolved
|
|
46
|
+
const p = Number(props.parentWidthPx)
|
|
47
|
+
return isFinite(p) && p > 0 ? p : NaN
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const selfHeightPx = computed(() => {
|
|
51
|
+
const resolved = resolvePx((props.element as any)?.style?.height, props.parentHeightPx)
|
|
52
|
+
if (isFinite(resolved) && resolved > 0) return resolved
|
|
53
|
+
const p = Number(props.parentHeightPx)
|
|
54
|
+
return isFinite(p) && p > 0 ? p : NaN
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
const emit = defineEmits<{
|
|
58
|
+
(e: 'childMouseDown', ev: MouseEvent, child: any, parent: any): void
|
|
59
|
+
(e: 'childClick', ev: MouseEvent, child: any, parent: any): void
|
|
60
|
+
(e: 'childDblClick', ev: MouseEvent, child: any, parent: any): void
|
|
61
|
+
(e: 'childContextMenu', ev: MouseEvent, child: any, parent: any): void
|
|
62
|
+
(e: 'textFocus', child: any): void
|
|
63
|
+
(e: 'textBlur', ev: FocusEvent, child: any): void
|
|
64
|
+
(e: 'textInput', ev: Event, child: any): void
|
|
65
|
+
(e: 'textKeyDown', ev: KeyboardEvent, child: any): void
|
|
66
|
+
(e: 'textMouseMove', ev: MouseEvent, child: any): void
|
|
67
|
+
(e: 'textMouseLeave', ev: MouseEvent, child: any): void
|
|
68
|
+
(e: 'startEditing', id: string, ev?: MouseEvent): void
|
|
69
|
+
(e: 'formulaDblClick', child: any): void
|
|
70
|
+
(e: 'tableDblClick', ev: MouseEvent, child: any): void
|
|
71
|
+
}>()
|
|
72
|
+
|
|
73
|
+
function sortGroupElements(elements: any[]) {
|
|
74
|
+
if (!elements || !Array.isArray(elements)) return []
|
|
75
|
+
return [...elements].sort((a: any, b: any) => {
|
|
76
|
+
const orderA = a.order !== undefined ? a.order : (parseFloat(a.style?.zIndex) || 0)
|
|
77
|
+
const orderB = b.order !== undefined ? b.order : (parseFloat(b.style?.zIndex) || 0)
|
|
78
|
+
return orderA - orderB
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<template>
|
|
84
|
+
<div class="group-container-wrapper" style="width: 100%; height: 100%; position: relative; overflow: visible; pointer-events: none;">
|
|
85
|
+
<div
|
|
86
|
+
v-for="child in sortGroupElements(element.elements || [])"
|
|
87
|
+
:key="child.id"
|
|
88
|
+
:id="`canvas-el-${child.id}`"
|
|
89
|
+
class="group-child-item"
|
|
90
|
+
:style="{
|
|
91
|
+
position: 'absolute',
|
|
92
|
+
left: child.style?.left,
|
|
93
|
+
top: child.style?.top,
|
|
94
|
+
width: child.style?.width,
|
|
95
|
+
height: (child.type === 'text' && (child.autoFit?.type === 'shape' || child.style?.height === 'auto') && !child.content?.includes('<table') && !child.latex && child.style?.writingMode !== 'vertical-rl' && child.style?.writingMode !== 'vertical-lr') ? 'auto' : (child.style?.height || (child.height ? `${child.height}px` : 'auto')),
|
|
96
|
+
minHeight: (child.type === 'text' && !child.content?.includes('<table') && !child.latex) ? child.style?.height : undefined,
|
|
97
|
+
transform: `rotate(${child.style?.rotate || 0}deg) scale(${child.type !== 'text' && child.type !== 'line' && ((child.style as any)?.flipH || child.flipH || child.isFlipH) ? -1 : 1}, ${child.type !== 'text' && child.type !== 'line' && ((child.style as any)?.flipV || child.flipV || child.isFlipV) ? -1 : 1})`,
|
|
98
|
+
opacity: child.style?.opacity !== undefined ? child.style.opacity : 1,
|
|
99
|
+
filter: getElementDropShadowFilter(child),
|
|
100
|
+
pointerEvents: child.type === 'group' ? 'none' : (((child.type === 'shape' || !child.type) && !hasElementFill(child) && !hasElementBorder(child)) ? 'none' : 'auto')
|
|
101
|
+
}"
|
|
102
|
+
@mousedown.stop="emit('childMouseDown', $event, child, element)"
|
|
103
|
+
@click.stop="emit('childClick', $event, child, element)"
|
|
104
|
+
@dblclick.stop="emit('childDblClick', $event, child, element)"
|
|
105
|
+
@contextmenu.stop.prevent="emit('childContextMenu', $event, child, element)"
|
|
106
|
+
>
|
|
107
|
+
<!-- Child Text -->
|
|
108
|
+
<template v-if="child.type === 'text'">
|
|
109
|
+
<CanvasTextElement
|
|
110
|
+
:element="child"
|
|
111
|
+
:active-slide="activeSlide"
|
|
112
|
+
:is-selected="selectedElementId === child.id"
|
|
113
|
+
:is-editing-text="isEditingText"
|
|
114
|
+
:selected-element-id="selectedElementId"
|
|
115
|
+
:theme-recipe="themeRecipe"
|
|
116
|
+
:rendered-content="getRenderedContent ? getRenderedContent(child) : child.content"
|
|
117
|
+
:get-text-stroke-style="getTextStrokeStyle"
|
|
118
|
+
:get-element-text-shadow="getElementTextShadow"
|
|
119
|
+
@focusin="emit('textFocus', child)"
|
|
120
|
+
@focusout="emit('textBlur', $event, child)"
|
|
121
|
+
@input="emit('textInput', $event, child)"
|
|
122
|
+
@keydown="emit('textKeyDown', $event, child)"
|
|
123
|
+
@mousemove="emit('textMouseMove', $event, child)"
|
|
124
|
+
@mouseleave="emit('textMouseLeave', $event, child)"
|
|
125
|
+
@dblclick="emit('startEditing', child.id)"
|
|
126
|
+
@formula-dbl-click="emit('formulaDblClick', child)"
|
|
127
|
+
@table-dbl-click="emit('tableDblClick', $event, child)"
|
|
128
|
+
/>
|
|
129
|
+
</template>
|
|
130
|
+
|
|
131
|
+
<!-- Child Shape -->
|
|
132
|
+
<template v-else-if="child.type === 'shape'">
|
|
133
|
+
<CanvasShapeElement
|
|
134
|
+
:element="child"
|
|
135
|
+
:active-slide="activeSlide"
|
|
136
|
+
:is-selected="selectedElementId === child.id"
|
|
137
|
+
:is-editing-text="isEditingText"
|
|
138
|
+
:selected-element-id="selectedElementId"
|
|
139
|
+
:theme-recipe="themeRecipe"
|
|
140
|
+
:parent-width-px="selfWidthPx"
|
|
141
|
+
:parent-height-px="selfHeightPx"
|
|
142
|
+
:get-text-stroke-style="getTextStrokeStyle"
|
|
143
|
+
:get-element-text-shadow="getElementTextShadow"
|
|
144
|
+
:is-image-placeholder="isImagePlaceholder"
|
|
145
|
+
@text-focus="emit('textFocus', child)"
|
|
146
|
+
@text-blur="emit('textBlur', $event, child)"
|
|
147
|
+
@text-input="emit('textInput', $event, child)"
|
|
148
|
+
@text-key-down="emit('textKeyDown', $event, child)"
|
|
149
|
+
@start-editing="(ev) => emit('startEditing', child.id, ev)"
|
|
150
|
+
/>
|
|
151
|
+
</template>
|
|
152
|
+
|
|
153
|
+
<!-- Child Line -->
|
|
154
|
+
<template v-else-if="child.type === 'line'">
|
|
155
|
+
<CanvasLineElement :element="child" :all-elements="element.elements" />
|
|
156
|
+
</template>
|
|
157
|
+
|
|
158
|
+
<!-- Child Image -->
|
|
159
|
+
<template
|
|
160
|
+
v-else-if="child.type === 'image' || child.type === 'pic' || child.type === 'picture' || (child.type !== 'text' && child.type !== 'line' && child.type !== 'shape' && child.type !== 'group' && child.type !== 'chart' && Boolean(getElementImageSrc(child)))">
|
|
161
|
+
<CanvasImageElement :element="child" :cropping-element-id="croppingElementId" />
|
|
162
|
+
</template>
|
|
163
|
+
|
|
164
|
+
<!-- Child Chart -->
|
|
165
|
+
<template v-else-if="child.type === 'chart'">
|
|
166
|
+
<div style="width: 100%; height: 100%; overflow: visible; box-sizing: border-box;" :style="{
|
|
167
|
+
backgroundColor: child.style?.fill || 'transparent',
|
|
168
|
+
borderWidth: child.style?.borderWidth || '0px',
|
|
169
|
+
borderStyle: child.style?.borderStyle || 'solid',
|
|
170
|
+
borderColor: child.style?.borderColor || 'transparent',
|
|
171
|
+
borderRadius: child.style?.borderRadius || '0px',
|
|
172
|
+
boxShadow: child.style?.boxShadow || 'none'
|
|
173
|
+
}">
|
|
174
|
+
<SvgChart :element="child" />
|
|
175
|
+
</div>
|
|
176
|
+
</template>
|
|
177
|
+
|
|
178
|
+
<!-- Nested Group -->
|
|
179
|
+
<template v-else-if="child.type === 'group'">
|
|
180
|
+
<CanvasGroupElement
|
|
181
|
+
:element="child"
|
|
182
|
+
:active-slide="activeSlide"
|
|
183
|
+
:cropping-element-id="croppingElementId"
|
|
184
|
+
:parent-width-px="selfWidthPx"
|
|
185
|
+
:parent-height-px="selfHeightPx"
|
|
186
|
+
:selected-element-ids="selectedElementIds"
|
|
187
|
+
:selected-element-id="selectedElementId"
|
|
188
|
+
:is-editing-text="isEditingText"
|
|
189
|
+
:theme-recipe="themeRecipe"
|
|
190
|
+
:get-text-stroke-style="getTextStrokeStyle"
|
|
191
|
+
:get-element-text-shadow="getElementTextShadow"
|
|
192
|
+
:get-rendered-content="getRenderedContent"
|
|
193
|
+
@child-mouse-down="(ev, c, p) => emit('childMouseDown', ev, c, p)"
|
|
194
|
+
@child-click="(ev, c, p) => emit('childClick', ev, c, p)"
|
|
195
|
+
@child-dbl-click="(ev, c, p) => emit('childDblClick', ev, c, p)"
|
|
196
|
+
@child-context-menu="(ev, c, p) => emit('childContextMenu', ev, c, p)"
|
|
197
|
+
@text-focus="(c) => emit('textFocus', c)"
|
|
198
|
+
@text-blur="(ev, c) => emit('textBlur', ev, c)"
|
|
199
|
+
@text-input="(ev, c) => emit('textInput', ev, c)"
|
|
200
|
+
@text-key-down="(ev, c) => emit('textKeyDown', ev, c)"
|
|
201
|
+
@text-mouse-move="(ev, c) => emit('textMouseMove', ev, c)"
|
|
202
|
+
@text-mouse-leave="(ev, c) => emit('textMouseLeave', ev, c)"
|
|
203
|
+
@start-editing="(id, ev) => emit('startEditing', id, ev)"
|
|
204
|
+
/>
|
|
205
|
+
</template>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</template>
|
|
209
|
+
|
|
210
|
+
<style scoped>
|
|
211
|
+
.group-child-item {
|
|
212
|
+
box-sizing: border-box;
|
|
213
|
+
}
|
|
214
|
+
</style>
|