jvs-ppt-ui 0.0.8 → 0.0.10
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 +60 -2
- package/dist/lib/jvs-ppt-ui.es.js +22404 -21454
- package/dist/lib/jvs-ppt-ui.umd.js +102 -101
- package/dist/lib/style.css +1 -1
- package/package.json +1 -1
- package/src/App.vue +53 -53
- package/src/components/AIPptDialog.vue +2 -0
- package/src/components/CanvasArea.vue +164 -72
- package/src/components/Header.vue +374 -144
- package/src/components/PptEditor.vue +33 -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 +244 -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 +15 -2
- package/src/composables/sidebar/useSidebarElementTransform.ts +5 -5
- package/src/composables/sidebar/useSidebarShapeFormat.ts +2 -2
- package/src/composables/useSlides.ts +495 -132
- package/src/style.css +88 -0
- package/src/types/index.ts +8 -0
- package/src/utils/line.ts +274 -312
- package/src/utils/pptxShapes.ts +1 -1
- package/src/utils/pptxShapesLazy.ts +39 -0
- package/src/utils/svgPathParser.ts +12 -1
|
@@ -1,238 +1,286 @@
|
|
|
1
|
-
import { type Ref } from 'vue'
|
|
2
|
-
|
|
3
|
-
export interface AlignmentOptions {
|
|
4
|
-
activeSlide: Ref<any>
|
|
5
|
-
selectedElementIds: Ref<string[]>
|
|
6
|
-
logicalWidth: Ref<number>
|
|
7
|
-
logicalHeight: Ref<number>
|
|
8
|
-
getElementPixels: (el: any) => { left: number; top: number; width: number; height: number; rotate: number }
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
items.
|
|
127
|
-
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
items.
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
items[0]
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
item.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (items.length
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
1
|
+
import { type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export interface AlignmentOptions {
|
|
4
|
+
activeSlide: Ref<any>
|
|
5
|
+
selectedElementIds: Ref<string[]>
|
|
6
|
+
logicalWidth: Ref<number>
|
|
7
|
+
logicalHeight: Ref<number>
|
|
8
|
+
getElementPixels: (el: any) => { left: number; top: number; width: number; height: number; rotate: number }
|
|
9
|
+
findElementInSlide?: (id: any) => any
|
|
10
|
+
findParentGroup?: (id: any) => any
|
|
11
|
+
addHistorySnapshot: () => void
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface GeomItem {
|
|
15
|
+
el: any
|
|
16
|
+
left: number
|
|
17
|
+
top: number
|
|
18
|
+
width: number
|
|
19
|
+
height: number
|
|
20
|
+
rotate: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Box {
|
|
24
|
+
left: number
|
|
25
|
+
top: number
|
|
26
|
+
width: number
|
|
27
|
+
height: number
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useCanvasAlignment(options: AlignmentOptions) {
|
|
31
|
+
const {
|
|
32
|
+
activeSlide,
|
|
33
|
+
selectedElementIds,
|
|
34
|
+
logicalWidth,
|
|
35
|
+
logicalHeight,
|
|
36
|
+
getElementPixels,
|
|
37
|
+
findElementInSlide,
|
|
38
|
+
findParentGroup,
|
|
39
|
+
addHistorySnapshot
|
|
40
|
+
} = options
|
|
41
|
+
|
|
42
|
+
// 选中集可能是顶层元素,也可能是组合内子元素(id 不会出现在 activeSlide.elements 顶层),
|
|
43
|
+
// 需要经 findElementInSlide 递归解析;祖先组合与后代同时选中时只保留祖先,避免双重位移
|
|
44
|
+
function resolveSelectedElements(): any[] {
|
|
45
|
+
const ids = selectedElementIds.value
|
|
46
|
+
if (!activeSlide.value || ids.length === 0) return []
|
|
47
|
+
let found: any[]
|
|
48
|
+
if (findElementInSlide) {
|
|
49
|
+
found = ids.map((id: any) => findElementInSlide(id)).filter(Boolean) as any[]
|
|
50
|
+
} else {
|
|
51
|
+
found = activeSlide.value.elements.filter((el: any) => ids.includes(el.id))
|
|
52
|
+
}
|
|
53
|
+
if (found.length === 0 || !findParentGroup) return found
|
|
54
|
+
return found.filter((el: any) => {
|
|
55
|
+
let parent = findParentGroup(el.id)
|
|
56
|
+
while (parent) {
|
|
57
|
+
if (found.includes(parent)) return false
|
|
58
|
+
parent = findParentGroup(parent.id)
|
|
59
|
+
}
|
|
60
|
+
return true
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function getSelectedItemsGeom(): GeomItem[] {
|
|
65
|
+
return resolveSelectedElements().map((el: any) => ({
|
|
66
|
+
el,
|
|
67
|
+
...getElementPixels(el)
|
|
68
|
+
}))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 单元素对齐 / 两元素分布的参照容器:
|
|
72
|
+
// 组内子元素以父组合盒子为容器(对齐到组合边缘而不是画布边缘),其余以画布为容器
|
|
73
|
+
function getItemsContainer(items: GeomItem[]): Box {
|
|
74
|
+
const parents = new Set<any>()
|
|
75
|
+
items.forEach((item: GeomItem) => {
|
|
76
|
+
const parent = findParentGroup ? findParentGroup(item.el.id) : null
|
|
77
|
+
if (parent) parents.add(parent)
|
|
78
|
+
})
|
|
79
|
+
if (parents.size === 1) {
|
|
80
|
+
const parent = [...parents][0]
|
|
81
|
+
const pp = getElementPixels(parent)
|
|
82
|
+
if (pp.width > 0 && pp.height > 0) return { left: pp.left, top: pp.top, width: pp.width, height: pp.height }
|
|
83
|
+
}
|
|
84
|
+
return { left: 0, top: 0, width: logicalWidth.value, height: logicalHeight.value }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 绝对坐标写回:组内子元素的 style.left/top 是相对父组合盒子的百分比,
|
|
88
|
+
// 必须减去父组合绝对偏移再除以父组合尺寸(与 getElementPixels 的解析互逆)
|
|
89
|
+
function setItemLeft(item: GeomItem, absLeft: number) {
|
|
90
|
+
const parent = findParentGroup ? findParentGroup(item.el.id) : null
|
|
91
|
+
if (parent) {
|
|
92
|
+
const pp = getElementPixels(parent)
|
|
93
|
+
const base = pp.width || logicalWidth.value
|
|
94
|
+
item.el.style.left = `${(((absLeft - pp.left) / base) * 100).toFixed(2)}%`
|
|
95
|
+
} else {
|
|
96
|
+
item.el.style.left = `${((absLeft / logicalWidth.value) * 100).toFixed(2)}%`
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function setItemTop(item: GeomItem, absTop: number) {
|
|
101
|
+
const parent = findParentGroup ? findParentGroup(item.el.id) : null
|
|
102
|
+
if (parent) {
|
|
103
|
+
const pp = getElementPixels(parent)
|
|
104
|
+
const base = pp.height || logicalHeight.value
|
|
105
|
+
item.el.style.top = `${(((absTop - pp.top) / base) * 100).toFixed(2)}%`
|
|
106
|
+
} else {
|
|
107
|
+
item.el.style.top = `${((absTop / logicalHeight.value) * 100).toFixed(2)}%`
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function quickAlignLeft() {
|
|
112
|
+
const items = getSelectedItemsGeom()
|
|
113
|
+
if (items.length === 0) return
|
|
114
|
+
if (items.length === 1) {
|
|
115
|
+
setItemLeft(items[0], getItemsContainer(items).left)
|
|
116
|
+
addHistorySnapshot()
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
const minLeft = Math.min(...items.map((i: GeomItem) => i.left))
|
|
120
|
+
items.forEach((item: GeomItem) => setItemLeft(item, minLeft))
|
|
121
|
+
addHistorySnapshot()
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function quickAlignCenter() {
|
|
125
|
+
const items = getSelectedItemsGeom()
|
|
126
|
+
if (items.length === 0) return
|
|
127
|
+
if (items.length === 1) {
|
|
128
|
+
const box = getItemsContainer(items)
|
|
129
|
+
setItemLeft(items[0], box.left + (box.width - items[0].width) / 2)
|
|
130
|
+
addHistorySnapshot()
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
const minLeft = Math.min(...items.map((i: GeomItem) => i.left))
|
|
134
|
+
const maxRight = Math.max(...items.map((i: GeomItem) => i.left + i.width))
|
|
135
|
+
const center = (minLeft + maxRight) / 2
|
|
136
|
+
items.forEach((item: GeomItem) => {
|
|
137
|
+
setItemLeft(item, center - item.width / 2)
|
|
138
|
+
})
|
|
139
|
+
addHistorySnapshot()
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function quickAlignRight() {
|
|
143
|
+
const items = getSelectedItemsGeom()
|
|
144
|
+
if (items.length === 0) return
|
|
145
|
+
if (items.length === 1) {
|
|
146
|
+
const box = getItemsContainer(items)
|
|
147
|
+
setItemLeft(items[0], box.left + box.width - items[0].width)
|
|
148
|
+
addHistorySnapshot()
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
const maxRight = Math.max(...items.map((i: GeomItem) => i.left + i.width))
|
|
152
|
+
items.forEach((item: GeomItem) => {
|
|
153
|
+
setItemLeft(item, maxRight - item.width)
|
|
154
|
+
})
|
|
155
|
+
addHistorySnapshot()
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function quickAlignTop() {
|
|
159
|
+
const items = getSelectedItemsGeom()
|
|
160
|
+
if (items.length === 0) return
|
|
161
|
+
if (items.length === 1) {
|
|
162
|
+
setItemTop(items[0], getItemsContainer(items).top)
|
|
163
|
+
addHistorySnapshot()
|
|
164
|
+
return
|
|
165
|
+
}
|
|
166
|
+
const minTop = Math.min(...items.map((i: GeomItem) => i.top))
|
|
167
|
+
items.forEach((item: GeomItem) => setItemTop(item, minTop))
|
|
168
|
+
addHistorySnapshot()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function quickAlignMiddle() {
|
|
172
|
+
const items = getSelectedItemsGeom()
|
|
173
|
+
if (items.length === 0) return
|
|
174
|
+
if (items.length === 1) {
|
|
175
|
+
const box = getItemsContainer(items)
|
|
176
|
+
setItemTop(items[0], box.top + (box.height - items[0].height) / 2)
|
|
177
|
+
addHistorySnapshot()
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
const minTop = Math.min(...items.map((i: GeomItem) => i.top))
|
|
181
|
+
const maxBottom = Math.max(...items.map((i: GeomItem) => i.top + i.height))
|
|
182
|
+
const middle = (minTop + maxBottom) / 2
|
|
183
|
+
items.forEach((item: GeomItem) => {
|
|
184
|
+
setItemTop(item, middle - item.height / 2)
|
|
185
|
+
})
|
|
186
|
+
addHistorySnapshot()
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function quickAlignBottom() {
|
|
190
|
+
const items = getSelectedItemsGeom()
|
|
191
|
+
if (items.length === 0) return
|
|
192
|
+
if (items.length === 1) {
|
|
193
|
+
const box = getItemsContainer(items)
|
|
194
|
+
setItemTop(items[0], box.top + box.height - items[0].height)
|
|
195
|
+
addHistorySnapshot()
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
const maxBottom = Math.max(...items.map((i: GeomItem) => i.top + i.height))
|
|
199
|
+
items.forEach((item: GeomItem) => {
|
|
200
|
+
setItemTop(item, maxBottom - item.height)
|
|
201
|
+
})
|
|
202
|
+
addHistorySnapshot()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function quickDistributeHorizontally() {
|
|
206
|
+
const items = getSelectedItemsGeom().sort((a: GeomItem, b: GeomItem) => a.left - b.left)
|
|
207
|
+
if (items.length < 2) return
|
|
208
|
+
|
|
209
|
+
if (items.length === 2) {
|
|
210
|
+
const box = getItemsContainer(items)
|
|
211
|
+
const totalWidths = items[0].width + items[1].width
|
|
212
|
+
const gap = (box.width - totalWidths) / 3
|
|
213
|
+
setItemLeft(items[0], box.left + gap)
|
|
214
|
+
setItemLeft(items[1], box.left + gap * 2 + items[0].width)
|
|
215
|
+
addHistorySnapshot()
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const minLeft = items[0].left
|
|
220
|
+
const maxRight = items[items.length - 1].left + items[items.length - 1].width
|
|
221
|
+
const totalWidths = items.reduce((sum: number, item: GeomItem) => sum + item.width, 0)
|
|
222
|
+
const availableWidth = maxRight - minLeft
|
|
223
|
+
const gap = (availableWidth - totalWidths) / (items.length - 1)
|
|
224
|
+
|
|
225
|
+
let currentLeft = minLeft
|
|
226
|
+
items.forEach((item: GeomItem, index: number) => {
|
|
227
|
+
if (index === 0) {
|
|
228
|
+
currentLeft += item.width + gap
|
|
229
|
+
return
|
|
230
|
+
}
|
|
231
|
+
if (index === items.length - 1) {
|
|
232
|
+
return
|
|
233
|
+
}
|
|
234
|
+
setItemLeft(item, currentLeft)
|
|
235
|
+
currentLeft += item.width + gap
|
|
236
|
+
})
|
|
237
|
+
addHistorySnapshot()
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function quickDistributeVertically() {
|
|
241
|
+
const items = getSelectedItemsGeom().sort((a: GeomItem, b: GeomItem) => a.top - b.top)
|
|
242
|
+
if (items.length < 2) return
|
|
243
|
+
|
|
244
|
+
if (items.length === 2) {
|
|
245
|
+
const box = getItemsContainer(items)
|
|
246
|
+
const totalHeights = items[0].height + items[1].height
|
|
247
|
+
const gap = (box.height - totalHeights) / 3
|
|
248
|
+
setItemTop(items[0], box.top + gap)
|
|
249
|
+
setItemTop(items[1], box.top + gap * 2 + items[0].height)
|
|
250
|
+
addHistorySnapshot()
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const minTop = items[0].top
|
|
255
|
+
const maxBottom = items[items.length - 1].top + items[items.length - 1].height
|
|
256
|
+
const totalHeights = items.reduce((sum: number, item: GeomItem) => sum + item.height, 0)
|
|
257
|
+
const availableHeight = maxBottom - minTop
|
|
258
|
+
const gap = (availableHeight - totalHeights) / (items.length - 1)
|
|
259
|
+
|
|
260
|
+
let currentTop = minTop
|
|
261
|
+
items.forEach((item: GeomItem, index: number) => {
|
|
262
|
+
if (index === 0) {
|
|
263
|
+
currentTop += item.height + gap
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
if (index === items.length - 1) {
|
|
267
|
+
return
|
|
268
|
+
}
|
|
269
|
+
setItemTop(item, currentTop)
|
|
270
|
+
currentTop += item.height + gap
|
|
271
|
+
})
|
|
272
|
+
addHistorySnapshot()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
getSelectedItemsGeom,
|
|
277
|
+
quickAlignLeft,
|
|
278
|
+
quickAlignCenter,
|
|
279
|
+
quickAlignRight,
|
|
280
|
+
quickAlignTop,
|
|
281
|
+
quickAlignMiddle,
|
|
282
|
+
quickAlignBottom,
|
|
283
|
+
quickDistributeHorizontally,
|
|
284
|
+
quickDistributeVertically
|
|
285
|
+
}
|
|
286
|
+
}
|
|
@@ -27,6 +27,7 @@ export interface ContextMenuOptions {
|
|
|
27
27
|
selectElement: (id: string, e?: MouseEvent) => void
|
|
28
28
|
updateSelectionBorders: (tableEl: HTMLTableElement) => void
|
|
29
29
|
deleteElement: (id: string) => void
|
|
30
|
+
copySelectedElements?: () => { type: 'elements'; data: any[]; sourceWidth: number; sourceHeight: number } | null
|
|
30
31
|
handleUnifiedPaste: () => Promise<void | boolean>
|
|
31
32
|
getElementPixels: (el: any) => { left: number; top: number; width: number; height: number; rotate: number }
|
|
32
33
|
notify: (msg: string) => void
|
|
@@ -50,6 +51,7 @@ export function useCanvasContextMenu(options: ContextMenuOptions) {
|
|
|
50
51
|
selectElement,
|
|
51
52
|
updateSelectionBorders,
|
|
52
53
|
deleteElement,
|
|
54
|
+
copySelectedElements,
|
|
53
55
|
handleUnifiedPaste,
|
|
54
56
|
getElementPixels,
|
|
55
57
|
notify
|
|
@@ -326,15 +328,28 @@ export function useCanvasContextMenu(options: ContextMenuOptions) {
|
|
|
326
328
|
function copySelected() {
|
|
327
329
|
if (selectedElementIds.value.length === 0) return
|
|
328
330
|
if (!activeSlide.value) return
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
|
|
332
|
+
if (copySelectedElements) {
|
|
333
|
+
const payload = copySelectedElements()
|
|
334
|
+
if (payload) {
|
|
335
|
+
try {
|
|
336
|
+
navigator.clipboard.writeText(JSON.stringify(payload))
|
|
337
|
+
} catch (err) {
|
|
338
|
+
// Ignore
|
|
339
|
+
}
|
|
340
|
+
notify(`已复制 ${payload.data.length} 个元素`)
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
const copied = getElementsByIds(selectedElementIds.value).map((el: any) => JSON.parse(JSON.stringify(el)))
|
|
344
|
+
clipboard.value = copied
|
|
345
|
+
slideClipboard.value = null
|
|
346
|
+
try {
|
|
347
|
+
navigator.clipboard.writeText(JSON.stringify(copied))
|
|
348
|
+
} catch (err) {
|
|
349
|
+
// Ignore
|
|
350
|
+
}
|
|
351
|
+
notify(`已复制 ${copied.length} 个元素`)
|
|
336
352
|
}
|
|
337
|
-
notify(`已复制 ${copied.length} 个元素`)
|
|
338
353
|
showContextMenu.value = false
|
|
339
354
|
}
|
|
340
355
|
|
|
@@ -342,21 +357,37 @@ export function useCanvasContextMenu(options: ContextMenuOptions) {
|
|
|
342
357
|
if (selectedElementIds.value.length === 0) return
|
|
343
358
|
if (!activeSlide.value) return
|
|
344
359
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
360
|
+
if (copySelectedElements) {
|
|
361
|
+
const payload = copySelectedElements()
|
|
362
|
+
if (payload) {
|
|
363
|
+
try {
|
|
364
|
+
navigator.clipboard.writeText(JSON.stringify(payload))
|
|
365
|
+
} catch (err) {
|
|
366
|
+
// Ignore
|
|
367
|
+
}
|
|
368
|
+
selectedElementIds.value.forEach(id => {
|
|
369
|
+
deleteElement(id)
|
|
370
|
+
})
|
|
371
|
+
selectedElementIds.value = []
|
|
372
|
+
selectedElementId.value = null
|
|
373
|
+
notify(`已剪切 ${payload.data.length} 个元素`)
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
const copied = getElementsByIds(selectedElementIds.value).map((el: any) => JSON.parse(JSON.stringify(el)))
|
|
377
|
+
clipboard.value = copied
|
|
378
|
+
slideClipboard.value = null
|
|
379
|
+
try {
|
|
380
|
+
navigator.clipboard.writeText(JSON.stringify(copied))
|
|
381
|
+
} catch (err) {
|
|
382
|
+
// Ignore
|
|
383
|
+
}
|
|
384
|
+
selectedElementIds.value.forEach(id => {
|
|
385
|
+
deleteElement(id)
|
|
386
|
+
})
|
|
387
|
+
selectedElementIds.value = []
|
|
388
|
+
selectedElementId.value = null
|
|
389
|
+
notify(`已剪切 ${copied.length} 个元素`)
|
|
352
390
|
}
|
|
353
|
-
|
|
354
|
-
selectedElementIds.value.forEach(id => {
|
|
355
|
-
deleteElement(id)
|
|
356
|
-
})
|
|
357
|
-
selectedElementIds.value = []
|
|
358
|
-
selectedElementId.value = null
|
|
359
|
-
notify(`已剪切 ${copied.length} 个元素`)
|
|
360
391
|
showContextMenu.value = false
|
|
361
392
|
}
|
|
362
393
|
|