@arronqzy/vue-view 0.1.0

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.
Files changed (75) hide show
  1. package/README.md +50 -0
  2. package/package.json +49 -0
  3. package/src/env.d.ts +62 -0
  4. package/src/index.ts +4 -0
  5. package/src/panel/VueViewOnlinePreview.vue +276 -0
  6. package/src/panel/VueViewPanel.vue +871 -0
  7. package/src/panel/components/ConfigHintIcon.vue +34 -0
  8. package/src/panel/components/ElementsLayer.vue +165 -0
  9. package/src/panel/components/MaterialPreview.vue +135 -0
  10. package/src/panel/components/MaterialSidebar.vue +526 -0
  11. package/src/panel/components/MaterialSidebarTreeNode.vue +305 -0
  12. package/src/panel/components/MoveableLayer.vue +859 -0
  13. package/src/panel/components/PanelCanvas.vue +630 -0
  14. package/src/panel/components/PanelConfigSidebar.vue +397 -0
  15. package/src/panel/components/PanelRulers.vue +177 -0
  16. package/src/panel/components/SelectLayer.vue +115 -0
  17. package/src/panel/components/ViewElementScopePanel.vue +76 -0
  18. package/src/panel/components/WorkspaceConfigSidebar.vue +147 -0
  19. package/src/panel/components/WorkspaceProjectNav.vue +192 -0
  20. package/src/panel/components/WorkspaceStageSplit.vue +258 -0
  21. package/src/panel/components/config/ConfigColorField.vue +52 -0
  22. package/src/panel/components/config/ConfigFieldGroup.vue +20 -0
  23. package/src/panel/components/config/ConfigSection.vue +50 -0
  24. package/src/panel/components/config/PanelConfigAudioSection.vue +256 -0
  25. package/src/panel/components/config/PanelConfigChartSection.vue +650 -0
  26. package/src/panel/components/config/PanelConfigGeometrySection.vue +209 -0
  27. package/src/panel/components/config/PanelConfigGridChildSpan.vue +68 -0
  28. package/src/panel/components/config/PanelConfigGridSection.vue +103 -0
  29. package/src/panel/components/config/PanelConfigImageSection.vue +136 -0
  30. package/src/panel/components/config/PanelConfigMultiSelect.vue +434 -0
  31. package/src/panel/components/config/PanelConfigNodeInfo.vue +165 -0
  32. package/src/panel/components/config/PanelConfigReferenceSection.vue +77 -0
  33. package/src/panel/components/config/PanelConfigStyleSections.vue +208 -0
  34. package/src/panel/components/config/PanelConfigTextSection.vue +195 -0
  35. package/src/panel/components/config/PanelConfigVideoSection.vue +107 -0
  36. package/src/panel/components/config/shared.ts +74 -0
  37. package/src/panel/components/elementsLayerNodes.ts +830 -0
  38. package/src/panel/components/materialSidebarData.ts +85 -0
  39. package/src/panel/components/scope-config/ScopeConfigProvider.vue +153 -0
  40. package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.vue +234 -0
  41. package/src/panel/components/scope-config/ScopeTemplatePreviewHost.vue +192 -0
  42. package/src/panel/components/scope-config/ScopeTemplatePreviewPanel.vue +42 -0
  43. package/src/panel/components/scope-config/ScopeTemplateUsageHint.vue +20 -0
  44. package/src/panel/components/scope-config/ScopeTemplateWarningsPanel.vue +63 -0
  45. package/src/panel/components/scope-config/scopeConfigContext.ts +17 -0
  46. package/src/panel/components/scope-config/useScopeConfig.ts +11 -0
  47. package/src/panel/constants/messages.ts +34 -0
  48. package/src/panel/constants/zIndex.ts +6 -0
  49. package/src/panel/hooks/usePanelElements.ts +1075 -0
  50. package/src/panel/hooks/useRafThrottledScroll.ts +25 -0
  51. package/src/panel/hooks/useWorkspaceProjects.ts +240 -0
  52. package/src/panel/lib/panel-ruler-canvas.ts +139 -0
  53. package/src/panel/library/workspace-project-cache.ts +23 -0
  54. package/src/panel/library/workspace-project-db.ts +111 -0
  55. package/src/panel/library/workspace-project-sync.ts +41 -0
  56. package/src/panel/library/workspace-snapshot.ts +30 -0
  57. package/src/panel/parseOnlinePreviewSearchParams.ts +13 -0
  58. package/src/panel/scope/view-scope-store.ts +82 -0
  59. package/src/panel/types.ts +127 -0
  60. package/src/panel/utils/chartOptionBuilder.ts +327 -0
  61. package/src/panel/utils/gridPlacement.ts +189 -0
  62. package/src/panel/utils/mappingLayerOps.ts +142 -0
  63. package/src/panel/utils/panelElementDefaults.ts +161 -0
  64. package/src/panel/utils/panelElementNodes.ts +35 -0
  65. package/src/panel/utils/panelStateIO.ts +124 -0
  66. package/src/panel/utils/scope-autocomplete.ts +114 -0
  67. package/src/panel/utils/scope-field-labels.ts +46 -0
  68. package/src/panel/utils/scope-template-chart.ts +92 -0
  69. package/src/panel/utils/scope-template-preview.ts +124 -0
  70. package/src/panel/utils/scope-template-spread.ts +229 -0
  71. package/src/panel/utils/scope-template-warnings.ts +243 -0
  72. package/src/panel/utils/scope-template.ts +97 -0
  73. package/src/panel/utils/updateElementDraft.ts +221 -0
  74. package/src/panel/viewportZoom.ts +26 -0
  75. package/src/tailwind.css +43 -0
@@ -0,0 +1,1075 @@
1
+ import { computed, onMounted } from "vue";
2
+ import { store, type Node, type State } from "@arronqzy/rx-store";
3
+ import { useStoreRef } from "@arronqzy/vue-rx-store";
4
+ import type {
5
+ PanelActionResult,
6
+ PanelElement,
7
+ PanelHistoryItem,
8
+ PanelLayer,
9
+ ReferenceCopyMode,
10
+ } from "../types";
11
+ import {
12
+ DEFAULT_LAYER,
13
+ DEFAULT_LAYER_ID,
14
+ getDefaultChartConfig,
15
+ getDefaultGridConfig,
16
+ getDefaultNodeName,
17
+ getDefaultSizeByMaterial,
18
+ getDefaultTextContent,
19
+ normalizePrimaryLayer,
20
+ randomId,
21
+ } from "../utils/panelElementDefaults";
22
+ import {
23
+ buildDeepReferenceSnapshot,
24
+ clonePanelElement,
25
+ isPanelElementNode,
26
+ } from "../utils/panelElementNodes";
27
+ import {
28
+ computeSnapPatchForNewElementOnLayer,
29
+ getGridChildSpanRect,
30
+ getGridSlotLayout,
31
+ inferSpanBySize,
32
+ } from "../utils/gridPlacement";
33
+ import {
34
+ canonicalMappingFamilyRootId,
35
+ expandMappingSeedsWithGridDescendants,
36
+ findCloneIdForSourceNodeOnMappingLayer,
37
+ getMaxZIndexByLayer,
38
+ removeMappingLayersBySourceIds,
39
+ } from "../utils/mappingLayerOps";
40
+ import {
41
+ applyGridLayoutPatchAcrossMappingFamily,
42
+ applyMappingFamilySyncPatch,
43
+ } from "../utils/updateElementDraft";
44
+
45
+ export type { PanelActionResult, PanelHistoryItem, PanelLayer } from "../types";
46
+
47
+ const HISTORY_LABEL_BY_TYPE: Record<string, string> = {
48
+ initial: "初始化",
49
+ "node.update": "更新节点",
50
+ "node.group-drag": "批量移动节点",
51
+ "node.group-resize": "批量缩放节点",
52
+ "node.group-rotate": "批量旋转节点",
53
+ "node.delete": "删除节点",
54
+ "node.batch-delete": "批量删除节点",
55
+ "node.z.front": "节点置顶",
56
+ "node.z.back": "节点置底",
57
+ "node.z.up": "节点上移一层",
58
+ "node.z.down": "节点下移一层",
59
+ "node.duplicate": "复制节点",
60
+ "node.ref-copy-mode": "设置引用拷贝模式",
61
+ "node.add": "添加节点",
62
+ "layer.activate": "切换图层",
63
+ "layer.add": "新增图层",
64
+ "layer.rename": "重命名图层",
65
+ "layer.toggle-lock": "切换图层锁定",
66
+ "layer.open-selected-mapping": "映射图层打开选中节点",
67
+ "layer.delete": "删除图层",
68
+ "layer.toggle-merge": "勾选合并图层",
69
+ "layer.merge": "合并图层",
70
+ "layer.set-primary": "设置主图层",
71
+ "panel.import": "导入面板",
72
+ };
73
+
74
+ export function usePanelElements() {
75
+ const stateRef = useStoreRef();
76
+
77
+ onMounted(() => {
78
+ const vars = store.getState().variables ?? {};
79
+ const hasLayers = Array.isArray(vars.layers);
80
+ const hasActive = typeof vars.activeLayerId === "string";
81
+ const hasNodes = (store.getState().root.children?.length ?? 0) > 0;
82
+ if (hasLayers && hasActive && hasNodes) return;
83
+
84
+ store.update((draft) => {
85
+ draft.variables = draft.variables ?? {};
86
+ draft.variables.layers = hasLayers
87
+ ? normalizePrimaryLayer((draft.variables.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER])
88
+ : [DEFAULT_LAYER];
89
+ draft.variables.activeLayerId = hasActive
90
+ ? draft.variables.activeLayerId
91
+ : DEFAULT_LAYER_ID;
92
+
93
+ if (!hasNodes) {
94
+ const seed: PanelElement[] = [];
95
+ draft.root.children = seed.map(
96
+ (el): Node => ({
97
+ id: el.id,
98
+ type: el.materialType ?? "unknown",
99
+ props: el,
100
+ children: [],
101
+ })
102
+ );
103
+ }
104
+ });
105
+ });
106
+
107
+ const layers = computed(() => {
108
+ const rawLayers =
109
+ (stateRef.value.variables?.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
110
+ return normalizePrimaryLayer(rawLayers);
111
+ });
112
+
113
+ const activeLayerId = computed(
114
+ () =>
115
+ (stateRef.value.variables?.activeLayerId as string | undefined) ?? DEFAULT_LAYER_ID
116
+ );
117
+
118
+ const allElements = computed(() => {
119
+ const nodes = stateRef.value.root.children ?? [];
120
+ return nodes
121
+ .filter((n) => isPanelElementNode(n) && n.props)
122
+ .map((n) => {
123
+ const props = n.props as PanelElement;
124
+ return { ...props, zIndex: typeof props.zIndex === "number" ? props.zIndex : 1 };
125
+ });
126
+ });
127
+
128
+ const elements = computed(() =>
129
+ allElements.value.filter((el) => el.layerId === activeLayerId.value)
130
+ );
131
+
132
+ const byId = computed(() => {
133
+ const map = new Map<string, PanelElement>();
134
+ for (const el of allElements.value) map.set(el.id, el);
135
+ return map;
136
+ });
137
+
138
+ const layerById = computed(() => {
139
+ const map = new Map<string, PanelLayer>();
140
+ for (const layer of layers.value) map.set(layer.id, layer);
141
+ return map;
142
+ });
143
+
144
+ const canUndo = computed(() => {
145
+ void stateRef.value;
146
+ return store.getHistoryCursorIndex() > 0;
147
+ });
148
+
149
+ const canRedo = computed(() => {
150
+ void stateRef.value;
151
+ return store.getHistoryCursorIndex() < store.getHistoryEntries().length - 1;
152
+ });
153
+
154
+ const historyCursor = computed(() => {
155
+ void stateRef.value;
156
+ return store.getHistoryCursorIndex();
157
+ });
158
+
159
+ const history = computed<PanelHistoryItem[]>(() => {
160
+ void stateRef.value;
161
+ const entries = store.getHistoryEntries();
162
+ const cursor = store.getHistoryCursorIndex();
163
+ return entries.map((entry, index) => {
164
+ const type = entry.meta?.type as string | undefined;
165
+ return {
166
+ index,
167
+ timestamp: entry.timestamp,
168
+ label: (type && HISTORY_LABEL_BY_TYPE[type]) || "编辑",
169
+ active: index === cursor,
170
+ };
171
+ });
172
+ });
173
+
174
+ function updateElement(
175
+ id: string,
176
+ patch: Partial<PanelElement>,
177
+ options?: { batchId?: string; meta?: Record<string, unknown> }
178
+ ) {
179
+ const current = store.getState();
180
+ const list = (current.variables?.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
181
+ const target = current.root.children?.find((n) => n.id === id);
182
+ const layerId = target?.props?.layerId as string | undefined;
183
+ const layer = list.find((l) => l.id === layerId);
184
+ if (layer?.locked) return;
185
+ const currentElement = (target?.props ?? {}) as PanelElement;
186
+ const elementByIdSnapshot = new Map<string, PanelElement>();
187
+ for (const n of current.root.children ?? []) {
188
+ if (!isPanelElementNode(n) || !n.props) continue;
189
+ const p = n.props as PanelElement;
190
+ elementByIdSnapshot.set(p.id, p);
191
+ }
192
+ const mappingFamilyRootId = canonicalMappingFamilyRootId(elementByIdSnapshot, id);
193
+ const parentGrid =
194
+ currentElement.parentGridId
195
+ ? (current.root.children ?? [])
196
+ .filter((n) => isPanelElementNode(n) && n.props)
197
+ .map((n) => n.props as PanelElement)
198
+ .find((el) => el.id === currentElement.parentGridId && el.materialType === "grid")
199
+ : undefined;
200
+ const detachFromGrid = "parentGridId" in patch && patch.parentGridId === undefined;
201
+ const hasResizePatch = "width" in patch || "height" in patch;
202
+ if (parentGrid && hasResizePatch && !detachFromGrid) {
203
+ const parentLayout = getGridSlotLayout(parentGrid);
204
+ const nextWidth =
205
+ typeof patch.width === "number" ? patch.width : currentElement.width;
206
+ const nextHeight =
207
+ typeof patch.height === "number" ? patch.height : currentElement.height;
208
+ const inferredColSpan = inferSpanBySize(
209
+ nextWidth,
210
+ parentLayout.cellWidth,
211
+ parentLayout.gap,
212
+ parentLayout.cols
213
+ );
214
+ const inferredRowSpan = inferSpanBySize(
215
+ nextHeight,
216
+ parentLayout.cellHeight,
217
+ parentLayout.gap,
218
+ parentLayout.rows
219
+ );
220
+ const total = parentLayout.rows * parentLayout.cols;
221
+ const baseSlot = Math.max(
222
+ 0,
223
+ Math.min(
224
+ total - 1,
225
+ Math.floor(
226
+ patch.gridSlotIndex !== undefined
227
+ ? patch.gridSlotIndex
228
+ : currentElement.gridSlotIndex ?? 0
229
+ )
230
+ )
231
+ );
232
+ const currentRow = Math.floor(baseSlot / parentLayout.cols);
233
+ const currentCol = baseSlot % parentLayout.cols;
234
+ const nextStartCol = Math.max(
235
+ 0,
236
+ Math.min(parentLayout.cols - inferredColSpan, currentCol)
237
+ );
238
+ const nextStartRow = Math.max(
239
+ 0,
240
+ Math.min(parentLayout.rows - inferredRowSpan, currentRow)
241
+ );
242
+ const nextSlotIndex = nextStartRow * parentLayout.cols + nextStartCol;
243
+ patch = {
244
+ ...patch,
245
+ gridSlotIndex: nextSlotIndex,
246
+ gridColSpan: inferredColSpan,
247
+ gridRowSpan: inferredRowSpan,
248
+ };
249
+ }
250
+ const hasGridChildSlotPatch =
251
+ "gridSlotIndex" in patch || "gridColSpan" in patch || "gridRowSpan" in patch;
252
+ if (currentElement.parentGridId && hasGridChildSlotPatch && !detachFromGrid) {
253
+ if (parentGrid) {
254
+ const nextSlotIndex =
255
+ patch.gridSlotIndex !== undefined
256
+ ? patch.gridSlotIndex
257
+ : currentElement.gridSlotIndex ?? 0;
258
+ const nextColSpan =
259
+ patch.gridColSpan !== undefined ? patch.gridColSpan : currentElement.gridColSpan ?? 1;
260
+ const nextRowSpan =
261
+ patch.gridRowSpan !== undefined ? patch.gridRowSpan : currentElement.gridRowSpan ?? 1;
262
+ const spanRect = getGridChildSpanRect(parentGrid, nextSlotIndex, nextColSpan, nextRowSpan);
263
+ patch = {
264
+ ...patch,
265
+ gridSlotIndex: spanRect.index,
266
+ gridColSpan: spanRect.colSpan,
267
+ gridRowSpan: spanRect.rowSpan,
268
+ x: spanRect.x,
269
+ y: spanRect.y,
270
+ width: spanRect.width,
271
+ height: spanRect.height,
272
+ };
273
+ }
274
+ }
275
+ const hasTransformPatch =
276
+ "x" in patch ||
277
+ "y" in patch ||
278
+ "width" in patch ||
279
+ "height" in patch ||
280
+ "rotate" in patch ||
281
+ "layerId" in patch;
282
+ if (currentElement.locked && hasTransformPatch) return;
283
+ const isGridNode = currentElement.materialType === "grid";
284
+ const hasGridLayoutPatch =
285
+ "gridRows" in patch ||
286
+ "gridCols" in patch ||
287
+ "gridGap" in patch ||
288
+ "gridPadding" in patch ||
289
+ "width" in patch ||
290
+ "height" in patch ||
291
+ "x" in patch ||
292
+ "y" in patch;
293
+ if (isGridNode && hasGridLayoutPatch) {
294
+ store.update(
295
+ (draft) => {
296
+ applyGridLayoutPatchAcrossMappingFamily(draft, mappingFamilyRootId, patch);
297
+ },
298
+ {
299
+ batchId: options?.batchId,
300
+ meta: { type: "node.update", id, ...(options?.meta ?? {}) },
301
+ }
302
+ );
303
+ return;
304
+ }
305
+
306
+ const syncPatch = { ...patch } as Partial<PanelElement>;
307
+ delete (syncPatch as Partial<PanelElement> & { id?: string }).id;
308
+ delete (syncPatch as Partial<PanelElement> & { layerId?: string }).layerId;
309
+ delete (syncPatch as Partial<PanelElement> & { mappingSourceNodeId?: string }).mappingSourceNodeId;
310
+ delete (syncPatch as Partial<PanelElement> & { mappingSourceLayerId?: string }).mappingSourceLayerId;
311
+ if (Object.keys(syncPatch).length > 0) {
312
+ store.update(
313
+ (draft) => {
314
+ applyMappingFamilySyncPatch(draft, mappingFamilyRootId, syncPatch, patch);
315
+ },
316
+ {
317
+ batchId: options?.batchId,
318
+ meta: { type: "node.update", id, ...(options?.meta ?? {}) },
319
+ }
320
+ );
321
+ return;
322
+ }
323
+
324
+ store.updateById(
325
+ id,
326
+ (node) => {
327
+ node.props = { ...(node.props ?? {}), ...patch };
328
+ },
329
+ {
330
+ batchId: options?.batchId,
331
+ meta: { type: "node.update", id, ...(options?.meta ?? {}) },
332
+ }
333
+ );
334
+ }
335
+
336
+ function deleteElement(id: string) {
337
+ const current = store.getState();
338
+ const target = current.root.children?.find((n) => isPanelElementNode(n) && n.id === id);
339
+ if (!target?.props) return;
340
+ const currentLayers =
341
+ (current.variables?.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
342
+ const targetLayer = currentLayers.find((l) => l.id === target.props?.layerId);
343
+ if ((target.props as PanelElement).locked) return;
344
+ if (targetLayer?.locked) return;
345
+ store.update(
346
+ (draft) => {
347
+ const deletedSourceIds = new Set<string>();
348
+ const deleting = (draft.root.children ?? []).find(
349
+ (n) => isPanelElementNode(n) && n.id === id
350
+ );
351
+ if (deleting && isPanelElementNode(deleting) && deleting.props?.id) {
352
+ const props = deleting.props as PanelElement;
353
+ deletedSourceIds.add(props.mappingSourceNodeId ?? props.id);
354
+ }
355
+ const familyIds = new Set<string>();
356
+ (draft.root.children ?? []).forEach((n) => {
357
+ if (!isPanelElementNode(n) || !n.props) return;
358
+ const props = n.props as PanelElement;
359
+ const sourceId = props.mappingSourceNodeId ?? props.id;
360
+ if (deletedSourceIds.has(sourceId)) familyIds.add(props.id);
361
+ });
362
+ draft.root.children = (draft.root.children ?? []).filter(
363
+ (n) => !(isPanelElementNode(n) && familyIds.has(n.id))
364
+ );
365
+ },
366
+ { meta: { type: "node.delete", id } }
367
+ );
368
+ }
369
+
370
+ function deleteElements(ids: string[]) {
371
+ const unlockedIds = ids.filter((id) => {
372
+ const el = byId.value.get(id);
373
+ if (!id || !el) return false;
374
+ if (el.locked) return false;
375
+ const layer = layerById.value.get(el.layerId);
376
+ return !layer?.locked;
377
+ });
378
+ const idSet = new Set(unlockedIds);
379
+ if (idSet.size === 0) return;
380
+ store.update(
381
+ (draft) => {
382
+ const deletedSourceIds = new Set<string>();
383
+ (draft.root.children ?? []).forEach((n) => {
384
+ if (!isPanelElementNode(n) || !n.props) return;
385
+ if (!idSet.has(n.id)) return;
386
+ const props = n.props as PanelElement;
387
+ deletedSourceIds.add(props.mappingSourceNodeId ?? props.id);
388
+ });
389
+ const familyIds = new Set<string>();
390
+ (draft.root.children ?? []).forEach((n) => {
391
+ if (!isPanelElementNode(n) || !n.props) return;
392
+ const props = n.props as PanelElement;
393
+ const sourceId = props.mappingSourceNodeId ?? props.id;
394
+ if (deletedSourceIds.has(sourceId)) familyIds.add(props.id);
395
+ });
396
+ draft.root.children = (draft.root.children ?? []).filter(
397
+ (n) => !(isPanelElementNode(n) && familyIds.has(n.id))
398
+ );
399
+ },
400
+ { meta: { type: "node.batch-delete", ids: Array.from(idSet) } }
401
+ );
402
+ }
403
+
404
+ function bringElementsToFront(ids: string[]) {
405
+ const unlocked = ids.filter((id) => {
406
+ const el = byId.value.get(id);
407
+ if (!id || !el || el.locked) return false;
408
+ const layer = layerById.value.get(el.layerId);
409
+ return !layer?.locked;
410
+ });
411
+ const idSet = new Set(unlocked);
412
+ if (idSet.size === 0) return;
413
+ store.update(
414
+ (draft) => {
415
+ const list = draft.root.children ?? [];
416
+ const selectedByLayer = new Map<string, PanelElement[]>();
417
+ list.forEach((n) => {
418
+ if (!isPanelElementNode(n) || !n.props || !idSet.has(n.id)) return;
419
+ const props = n.props as PanelElement;
420
+ const group = selectedByLayer.get(props.layerId) ?? [];
421
+ group.push({
422
+ ...props,
423
+ zIndex: typeof props.zIndex === "number" ? props.zIndex : 1,
424
+ });
425
+ selectedByLayer.set(props.layerId, group);
426
+ });
427
+ selectedByLayer.forEach((selected, layerId) => {
428
+ const maxZ = getMaxZIndexByLayer(list, layerId);
429
+ selected
430
+ .sort((a, b) => (a.zIndex ?? 1) - (b.zIndex ?? 1))
431
+ .forEach((node, offset) => {
432
+ const target = list.find((n) => n.id === node.id);
433
+ if (!target?.props) return;
434
+ target.props = { ...(target.props as PanelElement), zIndex: maxZ + offset + 1 };
435
+ });
436
+ });
437
+ },
438
+ { meta: { type: "node.z.front", ids: Array.from(idSet) } }
439
+ );
440
+ }
441
+
442
+ function sendElementsToBack(ids: string[]) {
443
+ const unlocked = ids.filter((id) => {
444
+ const el = byId.value.get(id);
445
+ if (!id || !el || el.locked) return false;
446
+ const layer = layerById.value.get(el.layerId);
447
+ return !layer?.locked;
448
+ });
449
+ const idSet = new Set(unlocked);
450
+ if (idSet.size === 0) return;
451
+ store.update(
452
+ (draft) => {
453
+ const list = draft.root.children ?? [];
454
+ const selectedByLayer = new Map<string, PanelElement[]>();
455
+ list.forEach((n) => {
456
+ if (!isPanelElementNode(n) || !n.props || !idSet.has(n.id)) return;
457
+ const props = n.props as PanelElement;
458
+ const group = selectedByLayer.get(props.layerId) ?? [];
459
+ group.push({
460
+ ...props,
461
+ zIndex: typeof props.zIndex === "number" ? props.zIndex : 1,
462
+ });
463
+ selectedByLayer.set(props.layerId, group);
464
+ });
465
+ selectedByLayer.forEach((selected, layerId) => {
466
+ let minZ = Number.POSITIVE_INFINITY;
467
+ list.forEach((n) => {
468
+ if (!isPanelElementNode(n) || !n.props) return;
469
+ const props = n.props as PanelElement;
470
+ if (props.layerId !== layerId) return;
471
+ const z = typeof props.zIndex === "number" ? props.zIndex : 1;
472
+ if (z < minZ) minZ = z;
473
+ });
474
+ if (!Number.isFinite(minZ)) minZ = 1;
475
+ const start = minZ - selected.length;
476
+ selected
477
+ .sort((a, b) => (a.zIndex ?? 1) - (b.zIndex ?? 1))
478
+ .forEach((node, offset) => {
479
+ const target = list.find((n) => n.id === node.id);
480
+ if (!target?.props) return;
481
+ target.props = { ...(target.props as PanelElement), zIndex: start + offset };
482
+ });
483
+ });
484
+ },
485
+ { meta: { type: "node.z.back", ids: Array.from(idSet) } }
486
+ );
487
+ }
488
+
489
+ function bringElementsForward(ids: string[]) {
490
+ const unlocked = ids.filter((id) => {
491
+ const el = byId.value.get(id);
492
+ if (!id || !el || el.locked) return false;
493
+ const layer = layerById.value.get(el.layerId);
494
+ return !layer?.locked;
495
+ });
496
+ const idSet = new Set(unlocked);
497
+ if (idSet.size === 0) return;
498
+ store.update(
499
+ (draft) => {
500
+ const list = draft.root.children ?? [];
501
+ list.forEach((n) => {
502
+ if (!isPanelElementNode(n) || !n.props || !idSet.has(n.id)) return;
503
+ const props = n.props as PanelElement;
504
+ const z = typeof props.zIndex === "number" ? props.zIndex : 1;
505
+ n.props = { ...props, zIndex: z + 1 };
506
+ });
507
+ },
508
+ { meta: { type: "node.z.up", ids: Array.from(idSet) } }
509
+ );
510
+ }
511
+
512
+ function sendElementsBackward(ids: string[]) {
513
+ const unlocked = ids.filter((id) => {
514
+ const el = byId.value.get(id);
515
+ if (!id || !el || el.locked) return false;
516
+ const layer = layerById.value.get(el.layerId);
517
+ return !layer?.locked;
518
+ });
519
+ const idSet = new Set(unlocked);
520
+ if (idSet.size === 0) return;
521
+ store.update(
522
+ (draft) => {
523
+ const list = draft.root.children ?? [];
524
+ list.forEach((n) => {
525
+ if (!isPanelElementNode(n) || !n.props || !idSet.has(n.id)) return;
526
+ const props = n.props as PanelElement;
527
+ const z = typeof props.zIndex === "number" ? props.zIndex : 1;
528
+ n.props = { ...props, zIndex: z - 1 };
529
+ });
530
+ },
531
+ { meta: { type: "node.z.down", ids: Array.from(idSet) } }
532
+ );
533
+ }
534
+
535
+ function duplicateElement(
536
+ id: string,
537
+ options?: { referenceCopyMode?: ReferenceCopyMode; position?: { x: number; y: number } }
538
+ ) {
539
+ const current = store.getState();
540
+ const node = current.root.children?.find((n) => isPanelElementNode(n) && n.id === id);
541
+ if (!node?.props) return;
542
+ const layerId = node.props.layerId as string | undefined;
543
+ const currentLayers =
544
+ (current.variables?.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
545
+ const layer = currentLayers.find((l) => l.id === layerId);
546
+ if (layer?.locked) return;
547
+
548
+ const nextId = randomId("el");
549
+ const nextProps: PanelElement = {
550
+ ...(node.props as PanelElement),
551
+ id: nextId,
552
+ x: Math.round(options?.position?.x ?? ((node.props.x ?? 0) + 20)),
553
+ y: Math.round(options?.position?.y ?? ((node.props.y ?? 0) + 20)),
554
+ zIndex: 1,
555
+ };
556
+ const all = (current.root.children ?? [])
557
+ .filter((n) => isPanelElementNode(n) && n.props)
558
+ .map((n) => n.props as PanelElement);
559
+ const desiredMode = options?.referenceCopyMode;
560
+ if (nextProps.materialType === "reference" && desiredMode) {
561
+ if (desiredMode === "deep" && nextProps.refLayerId) {
562
+ nextProps.refCopyMode = "deep";
563
+ nextProps.refSnapshot = buildDeepReferenceSnapshot(all, nextProps.refLayerId);
564
+ } else {
565
+ nextProps.refCopyMode = "shallow";
566
+ nextProps.refSnapshot = undefined;
567
+ }
568
+ }
569
+ store.update(
570
+ (draft) => {
571
+ draft.root.children = draft.root.children ?? [];
572
+ draft.root.children.push({
573
+ id: nextId,
574
+ type: node.type,
575
+ props: nextProps,
576
+ children: [],
577
+ });
578
+ },
579
+ { meta: { type: "node.duplicate", sourceId: id, id: nextId } }
580
+ );
581
+ }
582
+
583
+ function addElementFromMaterial(materialType: string, x: number, y: number) {
584
+ const current = store.getState();
585
+ const currentLayers =
586
+ (current.variables?.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
587
+ const currentLayerId =
588
+ (current.variables?.activeLayerId as string | undefined) ?? DEFAULT_LAYER_ID;
589
+ const layer = currentLayers.find((l) => l.id === currentLayerId);
590
+ if (!layer || layer.locked) return;
591
+
592
+ const size = getDefaultSizeByMaterial(materialType);
593
+ const id = randomId("el");
594
+ const next: PanelElement = {
595
+ id,
596
+ layerId: currentLayerId,
597
+ zIndex: 1,
598
+ materialType,
599
+ name: getDefaultNodeName(materialType),
600
+ ...getDefaultTextContent(materialType),
601
+ ...getDefaultGridConfig(materialType),
602
+ refCopyMode: materialType === "reference" ? "shallow" : undefined,
603
+ chart: getDefaultChartConfig(materialType),
604
+ geometryShape: materialType === "geometry" ? "rect" : undefined,
605
+ geometryColor: materialType === "geometry" ? "#3b82f6" : undefined,
606
+ geometryScript: undefined,
607
+ x: Math.round(x - size.width / 2),
608
+ y: Math.round(y - size.height / 2),
609
+ width: size.width,
610
+ height: size.height,
611
+ rotate: 0,
612
+ };
613
+ if (layer.isMapping && layer.mappingBaseLayerId) {
614
+ const fullById = new Map<string, PanelElement>();
615
+ for (const n of current.root.children ?? []) {
616
+ if (!isPanelElementNode(n) || !n.props) continue;
617
+ const p = n.props as PanelElement;
618
+ fullById.set(p.id, p);
619
+ }
620
+ const snapPatch = computeSnapPatchForNewElementOnLayer(
621
+ fullById,
622
+ layer.mappingBaseLayerId,
623
+ next.x,
624
+ next.y,
625
+ next.width,
626
+ next.height,
627
+ undefined
628
+ );
629
+ const sourceId = randomId("el");
630
+ const sourceNode: PanelElement = {
631
+ ...next,
632
+ ...snapPatch,
633
+ id: sourceId,
634
+ layerId: layer.mappingBaseLayerId,
635
+ };
636
+ const siblingMappings = currentLayers.filter(
637
+ (l) => l.isMapping && l.mappingBaseLayerId === layer.mappingBaseLayerId && !l.locked
638
+ );
639
+ const clones = siblingMappings.map((mappingLayer) => {
640
+ const cloneId = randomId("el");
641
+ const mappedParentGridId =
642
+ sourceNode.parentGridId !== undefined
643
+ ? findCloneIdForSourceNodeOnMappingLayer(
644
+ sourceNode.parentGridId,
645
+ mappingLayer.id,
646
+ fullById
647
+ )
648
+ : undefined;
649
+ return {
650
+ id: cloneId,
651
+ type: materialType,
652
+ props: {
653
+ ...clonePanelElement(sourceNode),
654
+ id: cloneId,
655
+ layerId: mappingLayer.id,
656
+ mappingSourceNodeId: sourceId,
657
+ mappingSourceLayerId: layer.mappingBaseLayerId,
658
+ parentGridId:
659
+ sourceNode.parentGridId === undefined
660
+ ? undefined
661
+ : mappedParentGridId ?? sourceNode.parentGridId,
662
+ } as PanelElement,
663
+ children: [] as Node[],
664
+ };
665
+ });
666
+ store.update(
667
+ (draft) => {
668
+ draft.root.children = draft.root.children ?? [];
669
+ draft.root.children.push({
670
+ id: sourceId,
671
+ type: materialType,
672
+ props: sourceNode,
673
+ children: [],
674
+ });
675
+ clones.forEach((clone) => draft.root.children!.push(clone as unknown as Node));
676
+ },
677
+ { meta: { type: "node.add", materialType, id: sourceId } }
678
+ );
679
+ return;
680
+ }
681
+ store.update(
682
+ (draft) => {
683
+ draft.root.children = draft.root.children ?? [];
684
+ draft.root.children.push({
685
+ id,
686
+ type: materialType,
687
+ props: next,
688
+ children: [],
689
+ });
690
+ },
691
+ { meta: { type: "node.add", materialType, id } }
692
+ );
693
+ }
694
+
695
+ function setReferenceCopyMode(id: string, mode: ReferenceCopyMode) {
696
+ const current = store.getState();
697
+ const all = (current.root.children ?? [])
698
+ .filter((n) => isPanelElementNode(n) && n.props)
699
+ .map((n) => n.props as PanelElement);
700
+ const target = all.find((el) => el.id === id);
701
+ if (!target || target.materialType !== "reference") return;
702
+ const currentLayers =
703
+ (current.variables?.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
704
+ const layer = currentLayers.find((l) => l.id === target.layerId);
705
+ if (layer?.locked) return;
706
+ store.updateById(
707
+ id,
708
+ (node) => {
709
+ const props = (node.props ?? {}) as PanelElement;
710
+ if (mode === "deep" && props.refLayerId) {
711
+ props.refCopyMode = "deep";
712
+ props.refSnapshot = buildDeepReferenceSnapshot(all, props.refLayerId);
713
+ } else {
714
+ props.refCopyMode = "shallow";
715
+ props.refSnapshot = undefined;
716
+ }
717
+ node.props = props;
718
+ },
719
+ { meta: { type: "node.ref-copy-mode", id, mode } }
720
+ );
721
+ }
722
+
723
+ function setActiveLayer(layerId: string) {
724
+ store.update(
725
+ (draft) => {
726
+ draft.variables = draft.variables ?? {};
727
+ draft.variables.activeLayerId = layerId;
728
+ },
729
+ { meta: { type: "layer.activate", layerId } }
730
+ );
731
+ }
732
+
733
+ function addLayer() {
734
+ store.update(
735
+ (draft) => {
736
+ draft.variables = draft.variables ?? {};
737
+ const list = (draft.variables.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
738
+ const nextId = randomId("layer");
739
+ const next: PanelLayer = {
740
+ id: nextId,
741
+ name: `图层${list.length + 1}`,
742
+ locked: false,
743
+ editable: true,
744
+ isPrimary: false,
745
+ isMapping: false,
746
+ mappingBaseLayerId: undefined,
747
+ mergeSelected: false,
748
+ };
749
+ draft.variables.layers = [...list, next];
750
+ draft.variables.activeLayerId = nextId;
751
+ },
752
+ { meta: { type: "layer.add" } }
753
+ );
754
+ }
755
+
756
+ function openElementsInNewLayer(ids: string[]): PanelActionResult {
757
+ const filtered = ids.filter(Boolean);
758
+ if (filtered.length === 0) return { ok: false, reason: "未选择节点" };
759
+ const current = store.getState();
760
+ const currentLayers =
761
+ (current.variables?.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
762
+ const nodeById = new Map(
763
+ (current.root.children ?? [])
764
+ .filter((n) => isPanelElementNode(n) && n.props)
765
+ .map((n) => [n.id, n.props as PanelElement])
766
+ );
767
+ const expandedIds = expandMappingSeedsWithGridDescendants(nodeById, filtered);
768
+ const selected = expandedIds
769
+ .map((id) => nodeById.get(id))
770
+ .filter((el): el is PanelElement => !!el);
771
+ if (selected.length === 0) return { ok: false, reason: "未找到可迁移节点" };
772
+ const anchorEl = nodeById.get(filtered[0]!) ?? selected[0];
773
+ const hasLocked = selected.some((el) => {
774
+ if (el.locked) return true;
775
+ const layer = currentLayers.find((l) => l.id === el.layerId);
776
+ return !!layer?.locked;
777
+ });
778
+ if (hasLocked) return { ok: false, reason: "选中节点包含锁定内容,无法在新图层打开" };
779
+
780
+ const selectedSet = new Set(selected.map((el) => el.id));
781
+ const idMap = new Map<string, string>();
782
+ selected.forEach((el) => idMap.set(el.id, randomId("el")));
783
+ const nextLayerId = randomId("layer");
784
+ const nextLayer: PanelLayer = {
785
+ id: nextLayerId,
786
+ name: `映射图层${currentLayers.length + 1}`,
787
+ locked: false,
788
+ editable: true,
789
+ isPrimary: false,
790
+ isMapping: true,
791
+ mappingBaseLayerId:
792
+ anchorEl?.mappingSourceLayerId ??
793
+ anchorEl?.layerId ??
794
+ (current.variables?.activeLayerId as string | undefined) ??
795
+ DEFAULT_LAYER_ID,
796
+ mergeSelected: false,
797
+ };
798
+ store.update(
799
+ (draft) => {
800
+ draft.variables = draft.variables ?? {};
801
+ const draftLayers = (draft.variables.layers as PanelLayer[] | undefined) ?? [DEFAULT_LAYER];
802
+ draft.variables.layers = normalizePrimaryLayer([...draftLayers, nextLayer]);
803
+ draft.variables.activeLayerId = nextLayerId;
804
+ const clones: Node[] = [];
805
+ const all = draft.root.children ?? [];
806
+ all.forEach((n) => {
807
+ if (!isPanelElementNode(n) || !n.props || !selectedSet.has(n.id)) return;
808
+ const props = n.props as PanelElement;
809
+ const nextId = idMap.get(props.id);
810
+ if (!nextId) return;
811
+ const remappedParentGridId = props.parentGridId
812
+ ? idMap.get(props.parentGridId) ?? props.parentGridId
813
+ : undefined;
814
+ const nextProps: PanelElement = {
815
+ ...clonePanelElement(props),
816
+ id: nextId,
817
+ layerId: nextLayerId,
818
+ parentGridId: remappedParentGridId,
819
+ mappingSourceNodeId: props.mappingSourceNodeId ?? props.id,
820
+ mappingSourceLayerId: props.mappingSourceLayerId ?? props.layerId,
821
+ };
822
+ clones.push({
823
+ id: nextId,
824
+ type: n.type,
825
+ props: nextProps,
826
+ children: [],
827
+ });
828
+ });
829
+ draft.root.children = [...all, ...clones];
830
+ },
831
+ {
832
+ meta: {
833
+ type: "layer.open-selected-mapping",
834
+ layerId: nextLayerId,
835
+ nodeCount: selectedSet.size,
836
+ },
837
+ }
838
+ );
839
+ return { ok: true };
840
+ }
841
+
842
+ function renameLayer(layerId: string, name: string) {
843
+ store.update(
844
+ (draft) => {
845
+ const list = (draft.variables?.layers as PanelLayer[] | undefined) ?? [];
846
+ const target = list.find((l) => l.id === layerId);
847
+ if (!target || !target.editable) return;
848
+ target.name = name.trim() || target.name;
849
+ },
850
+ { meta: { type: "layer.rename", layerId } }
851
+ );
852
+ }
853
+
854
+ function toggleLayerLock(layerId: string) {
855
+ store.update(
856
+ (draft) => {
857
+ const list = (draft.variables?.layers as PanelLayer[] | undefined) ?? [];
858
+ const target = list.find((l) => l.id === layerId);
859
+ if (!target || !target.editable) return;
860
+ target.locked = !target.locked;
861
+ },
862
+ { meta: { type: "layer.toggle-lock", layerId } }
863
+ );
864
+ }
865
+
866
+ function deleteLayer(
867
+ layerId: string,
868
+ options?: { mode?: "remove" | "move"; targetLayerId?: string }
869
+ ): PanelActionResult {
870
+ const mode = options?.mode ?? "remove";
871
+ const targetLayerId = options?.targetLayerId;
872
+ const current = store.getState();
873
+ const list = (current.variables?.layers as PanelLayer[] | undefined) ?? [];
874
+ const target = list.find((l) => l.id === layerId);
875
+ if (!target) return { ok: false, reason: "图层不存在" };
876
+ if (!target.editable) return { ok: false, reason: "默认图层不可删除" };
877
+ if (target.locked) return { ok: false, reason: "锁定图层不可删除" };
878
+ if (mode === "move" && !targetLayerId) {
879
+ return { ok: false, reason: "请选择目标图层" };
880
+ }
881
+ const remainingLayers = list.filter((l) => l.id !== layerId);
882
+ const moveTarget = remainingLayers.find((l) => l.id === targetLayerId);
883
+ if (mode === "move" && !moveTarget) {
884
+ return { ok: false, reason: "目标图层不存在" };
885
+ }
886
+ const allPanelElements = (current.root.children ?? [])
887
+ .filter((n) => isPanelElementNode(n) && n.props)
888
+ .map((n) => n.props as PanelElement);
889
+ const hasBlockingRef = allPanelElements.some((el) => {
890
+ if (target.isMapping) return false;
891
+ const willBeDeleted = mode === "remove" && el.layerId === layerId;
892
+ if (willBeDeleted) return false;
893
+ if (el.materialType !== "reference") return false;
894
+ if (el.refLayerId !== layerId) return false;
895
+ return (el.refCopyMode ?? "shallow") !== "deep";
896
+ });
897
+ if (hasBlockingRef) {
898
+ return { ok: false, reason: "该图层仍被浅拷贝引用,请先删除引用节点或改为深拷贝" };
899
+ }
900
+
901
+ store.update(
902
+ (draft) => {
903
+ draft.variables!.layers = normalizePrimaryLayer(remainingLayers.map((l) => ({
904
+ ...l,
905
+ mergeSelected: false,
906
+ })));
907
+
908
+ if (mode === "move" && moveTarget) {
909
+ draft.root.children = (draft.root.children ?? []).map((n) => {
910
+ if (isPanelElementNode(n) && n.props?.layerId === layerId) {
911
+ n.props = { ...(n.props ?? {}), layerId: moveTarget.id };
912
+ }
913
+ return n;
914
+ });
915
+ } else {
916
+ const deletedSourceIds = new Set<string>();
917
+ (draft.root.children ?? []).forEach((n) => {
918
+ if (!isPanelElementNode(n) || !n.props) return;
919
+ if (n.props?.layerId !== layerId) return;
920
+ deletedSourceIds.add((n.props as PanelElement).id);
921
+ });
922
+ draft.root.children = (draft.root.children ?? []).filter(
923
+ (n) => !isPanelElementNode(n) || n.props?.layerId !== layerId
924
+ );
925
+ removeMappingLayersBySourceIds(draft, deletedSourceIds);
926
+ }
927
+
928
+ if (draft.variables!.activeLayerId === layerId) {
929
+ draft.variables!.activeLayerId =
930
+ moveTarget?.id ?? remainingLayers[0]?.id ?? DEFAULT_LAYER_ID;
931
+ }
932
+ },
933
+ { meta: { type: "layer.delete", layerId, mode } }
934
+ );
935
+ return { ok: true };
936
+ }
937
+
938
+ function toggleLayerMergeSelected(layerId: string) {
939
+ store.update(
940
+ (draft) => {
941
+ const list = (draft.variables?.layers as PanelLayer[] | undefined) ?? [];
942
+ const target = list.find((l) => l.id === layerId);
943
+ if (!target) return;
944
+ if (target.isMapping) return;
945
+ target.mergeSelected = !target.mergeSelected;
946
+ },
947
+ { meta: { type: "layer.toggle-merge", layerId } }
948
+ );
949
+ }
950
+
951
+ function mergeSelectedLayers(name?: string) {
952
+ store.update(
953
+ (draft) => {
954
+ draft.variables = draft.variables ?? {};
955
+ const list = ((draft.variables.layers as PanelLayer[] | undefined) ?? []).slice();
956
+ const selected = list.filter((l) => l.mergeSelected && !l.isMapping);
957
+ if (selected.length < 2) return;
958
+
959
+ const nextId = randomId("layer");
960
+ const nextLayer: PanelLayer = {
961
+ id: nextId,
962
+ name: name?.trim() || `图层-${Math.random().toString(36).slice(2, 6)}`,
963
+ locked: false,
964
+ editable: true,
965
+ isPrimary: false,
966
+ isMapping: false,
967
+ mappingBaseLayerId: undefined,
968
+ mergeSelected: false,
969
+ };
970
+ const selectedSet = new Set(selected.map((l) => l.id));
971
+
972
+ draft.root.children = (draft.root.children ?? []).map((n) => {
973
+ if (isPanelElementNode(n) && selectedSet.has(n.props?.layerId)) {
974
+ n.props = { ...(n.props ?? {}), layerId: nextId };
975
+ }
976
+ return n;
977
+ });
978
+
979
+ draft.variables.layers = normalizePrimaryLayer([
980
+ ...list
981
+ .filter((l) => !selectedSet.has(l.id))
982
+ .map((l) => ({ ...l, mergeSelected: false })),
983
+ nextLayer,
984
+ ]);
985
+ draft.variables.activeLayerId = nextId;
986
+ },
987
+ { meta: { type: "layer.merge", name } }
988
+ );
989
+ }
990
+
991
+ function setPrimaryLayer(layerId: string) {
992
+ store.update(
993
+ (draft) => {
994
+ const list = (draft.variables?.layers as PanelLayer[] | undefined) ?? [];
995
+ if (list.length === 0) return;
996
+ draft.variables!.layers = list.map((layer) => ({
997
+ ...layer,
998
+ isPrimary: layer.id === layerId,
999
+ }));
1000
+ },
1001
+ { meta: { type: "layer.set-primary", layerId } }
1002
+ );
1003
+ }
1004
+
1005
+ function undo() {
1006
+ store.undo();
1007
+ }
1008
+
1009
+ function redo() {
1010
+ store.redo();
1011
+ }
1012
+
1013
+ function goToHistory(index: number) {
1014
+ store.goToHistory(index);
1015
+ }
1016
+
1017
+ function exportPanelData() {
1018
+ const current = store.getState();
1019
+ return JSON.parse(JSON.stringify(current)) as State;
1020
+ }
1021
+
1022
+ function importPanelData(nextState: State) {
1023
+ if (!nextState || typeof nextState !== "object") return false;
1024
+ if (!nextState.root || typeof nextState.root !== "object") return false;
1025
+ if (!nextState.root.id || !nextState.root.type) return false;
1026
+ if (!Array.isArray(nextState.root.children)) nextState.root.children = [];
1027
+ nextState.variables = nextState.variables ?? {};
1028
+ if (!Array.isArray(nextState.variables.layers)) {
1029
+ nextState.variables.layers = [DEFAULT_LAYER];
1030
+ }
1031
+ nextState.variables.layers = normalizePrimaryLayer(nextState.variables.layers as PanelLayer[]);
1032
+ if (typeof nextState.variables.activeLayerId !== "string") {
1033
+ nextState.variables.activeLayerId =
1034
+ (nextState.variables.layers[0] as PanelLayer | undefined)?.id ?? DEFAULT_LAYER_ID;
1035
+ }
1036
+ store.replaceState(nextState, { type: "panel.import" });
1037
+ return true;
1038
+ }
1039
+
1040
+ return {
1041
+ allElements,
1042
+ elements,
1043
+ byId,
1044
+ layers,
1045
+ activeLayerId,
1046
+ updateElement,
1047
+ deleteElement,
1048
+ deleteElements,
1049
+ bringElementsToFront,
1050
+ sendElementsToBack,
1051
+ bringElementsForward,
1052
+ sendElementsBackward,
1053
+ duplicateElement,
1054
+ setReferenceCopyMode,
1055
+ addElementFromMaterial,
1056
+ setActiveLayer,
1057
+ addLayer,
1058
+ openElementsInNewLayer,
1059
+ renameLayer,
1060
+ toggleLayerLock,
1061
+ deleteLayer,
1062
+ toggleLayerMergeSelected,
1063
+ mergeSelectedLayers,
1064
+ setPrimaryLayer,
1065
+ undo,
1066
+ redo,
1067
+ goToHistory,
1068
+ canUndo,
1069
+ canRedo,
1070
+ historyCursor,
1071
+ history,
1072
+ exportPanelData,
1073
+ importPanelData,
1074
+ };
1075
+ }