@tmagic/editor 1.4.8 → 1.4.9

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 (143) hide show
  1. package/package.json +11 -11
  2. package/src/Editor.vue +211 -0
  3. package/src/components/CodeBlockEditor.vue +268 -0
  4. package/src/components/CodeParams.vue +60 -0
  5. package/src/components/ContentMenu.vue +193 -0
  6. package/src/components/FloatingBox.vue +178 -0
  7. package/src/components/Icon.vue +27 -0
  8. package/src/components/Resizer.vue +23 -0
  9. package/src/components/ScrollBar.vue +151 -0
  10. package/src/components/ScrollViewer.vue +128 -0
  11. package/src/components/SearchInput.vue +37 -0
  12. package/src/components/SplitView.vue +186 -0
  13. package/src/components/ToolButton.vue +138 -0
  14. package/src/components/Tree.vue +71 -0
  15. package/src/components/TreeNode.vue +150 -0
  16. package/src/editorProps.ts +117 -0
  17. package/src/fields/Code.vue +50 -0
  18. package/src/fields/CodeLink.vue +80 -0
  19. package/src/fields/CodeSelect.vue +132 -0
  20. package/src/fields/CodeSelectCol.vue +139 -0
  21. package/src/fields/DataSourceFieldSelect.vue +149 -0
  22. package/src/fields/DataSourceFields.vue +323 -0
  23. package/src/fields/DataSourceInput.vue +339 -0
  24. package/src/fields/DataSourceMethodSelect.vue +153 -0
  25. package/src/fields/DataSourceMethods.vue +104 -0
  26. package/src/fields/DataSourceMocks.vue +255 -0
  27. package/src/fields/DataSourceSelect.vue +98 -0
  28. package/src/fields/EventSelect.vue +375 -0
  29. package/src/fields/KeyValue.vue +137 -0
  30. package/src/fields/PageFragmentSelect.vue +63 -0
  31. package/src/fields/UISelect.vue +135 -0
  32. package/src/hooks/index.ts +24 -0
  33. package/src/hooks/use-code-block-edit.ts +83 -0
  34. package/src/hooks/use-data-source-edit.ts +46 -0
  35. package/src/hooks/use-data-source-method.ts +100 -0
  36. package/src/hooks/use-editor-content-height.ts +26 -0
  37. package/src/hooks/use-filter.ts +55 -0
  38. package/src/hooks/use-float-box.ts +76 -0
  39. package/src/hooks/use-getso.ts +35 -0
  40. package/src/hooks/use-next-float-box-position.ts +29 -0
  41. package/src/hooks/use-node-status.ts +48 -0
  42. package/src/hooks/use-stage.ts +129 -0
  43. package/src/hooks/use-window-rect.ts +20 -0
  44. package/src/icons/AppManageIcon.vue +15 -0
  45. package/src/icons/CenterIcon.vue +13 -0
  46. package/src/icons/CodeIcon.vue +28 -0
  47. package/src/icons/FolderMinusIcon.vue +22 -0
  48. package/src/icons/PinIcon.vue +25 -0
  49. package/src/icons/PinnedIcon.vue +25 -0
  50. package/src/index.ts +118 -0
  51. package/src/initService.ts +448 -0
  52. package/src/layouts/AddPageBox.vue +55 -0
  53. package/src/layouts/CodeEditor.vue +226 -0
  54. package/src/layouts/Framework.vue +163 -0
  55. package/src/layouts/NavMenu.vue +200 -0
  56. package/src/layouts/PropsPanel.vue +131 -0
  57. package/src/layouts/page-bar/AddButton.vue +48 -0
  58. package/src/layouts/page-bar/PageBar.vue +159 -0
  59. package/src/layouts/page-bar/PageBarScrollContainer.vue +156 -0
  60. package/src/layouts/page-bar/SwitchTypeButton.vue +45 -0
  61. package/src/layouts/sidebar/ComponentListPanel.vue +134 -0
  62. package/src/layouts/sidebar/Sidebar.vue +278 -0
  63. package/src/layouts/sidebar/code-block/CodeBlockList.vue +160 -0
  64. package/src/layouts/sidebar/code-block/CodeBlockListPanel.vue +70 -0
  65. package/src/layouts/sidebar/data-source/DataSourceConfigPanel.vue +84 -0
  66. package/src/layouts/sidebar/data-source/DataSourceList.vue +172 -0
  67. package/src/layouts/sidebar/data-source/DataSourceListPanel.vue +106 -0
  68. package/src/layouts/sidebar/layer/LayerMenu.vue +128 -0
  69. package/src/layouts/sidebar/layer/LayerNodeTool.vue +32 -0
  70. package/src/layouts/sidebar/layer/LayerPanel.vue +119 -0
  71. package/src/layouts/sidebar/layer/use-click.ts +113 -0
  72. package/src/layouts/sidebar/layer/use-drag.ts +167 -0
  73. package/src/layouts/sidebar/layer/use-keybinding.ts +54 -0
  74. package/src/layouts/sidebar/layer/use-node-status.ts +114 -0
  75. package/src/layouts/workspace/Breadcrumb.vue +35 -0
  76. package/src/layouts/workspace/Workspace.vue +46 -0
  77. package/src/layouts/workspace/viewer/NodeListMenu.vue +115 -0
  78. package/src/layouts/workspace/viewer/Stage.vue +246 -0
  79. package/src/layouts/workspace/viewer/StageOverlay.vue +68 -0
  80. package/src/layouts/workspace/viewer/ViewerMenu.vue +145 -0
  81. package/src/services/BaseService.ts +230 -0
  82. package/src/services/codeBlock.ts +327 -0
  83. package/src/services/componentList.ts +58 -0
  84. package/src/services/dataSource.ts +216 -0
  85. package/src/services/dep.ts +152 -0
  86. package/src/services/editor.ts +1135 -0
  87. package/src/services/events.ts +93 -0
  88. package/src/services/history.ts +126 -0
  89. package/src/services/keybinding.ts +220 -0
  90. package/src/services/props.ts +286 -0
  91. package/src/services/stageOverlay.ts +212 -0
  92. package/src/services/storage.ts +155 -0
  93. package/src/services/ui.ts +156 -0
  94. package/src/shims-vue.d.ts +6 -0
  95. package/src/theme/breadcrumb.scss +6 -0
  96. package/src/theme/code-block.scss +6 -0
  97. package/src/theme/code-editor.scss +38 -0
  98. package/src/theme/common/var.scss +14 -0
  99. package/src/theme/component-list-panel.scss +95 -0
  100. package/src/theme/content-menu.scss +76 -0
  101. package/src/theme/data-source-field.scss +12 -0
  102. package/src/theme/data-source-fields.scss +13 -0
  103. package/src/theme/data-source-input.scss +18 -0
  104. package/src/theme/data-source-methods.scss +13 -0
  105. package/src/theme/data-source.scss +28 -0
  106. package/src/theme/event.scss +37 -0
  107. package/src/theme/floating-box.scss +32 -0
  108. package/src/theme/framework.scss +69 -0
  109. package/src/theme/icon.scss +12 -0
  110. package/src/theme/index.scss +10 -0
  111. package/src/theme/key-value.scss +20 -0
  112. package/src/theme/layer-panel.scss +26 -0
  113. package/src/theme/layout.scss +9 -0
  114. package/src/theme/nav-menu.scss +78 -0
  115. package/src/theme/page-bar.scss +89 -0
  116. package/src/theme/page-fragment-select.scss +14 -0
  117. package/src/theme/props-panel.scss +55 -0
  118. package/src/theme/resizer.scss +66 -0
  119. package/src/theme/ruler.scss +38 -0
  120. package/src/theme/search-input.scss +14 -0
  121. package/src/theme/sidebar.scss +79 -0
  122. package/src/theme/stage.scss +76 -0
  123. package/src/theme/theme.scss +27 -0
  124. package/src/theme/tree.scss +89 -0
  125. package/src/theme/workspace.scss +9 -0
  126. package/src/type.ts +749 -0
  127. package/src/utils/compose.ts +52 -0
  128. package/src/utils/config.ts +29 -0
  129. package/src/utils/content-menu.ts +95 -0
  130. package/src/utils/data-source/formConfigs/base.ts +30 -0
  131. package/src/utils/data-source/formConfigs/http.ts +60 -0
  132. package/src/utils/data-source/index.ts +243 -0
  133. package/src/utils/editor.ts +313 -0
  134. package/src/utils/idle-task.ts +72 -0
  135. package/src/utils/index.ts +24 -0
  136. package/src/utils/keybinding-config.ts +125 -0
  137. package/src/utils/logger.ts +47 -0
  138. package/src/utils/monaco-editor.ts +8 -0
  139. package/src/utils/operator.ts +106 -0
  140. package/src/utils/props.ts +504 -0
  141. package/src/utils/scroll-viewer.ts +165 -0
  142. package/src/utils/tree.ts +15 -0
  143. package/src/utils/undo-redo.ts +76 -0
@@ -0,0 +1,504 @@
1
+ /*
2
+ * Tencent is pleased to support the open source community by making TMagicEditor available.
3
+ *
4
+ * Copyright (C) 2023 THL A29 Limited, a Tencent company. All rights reserved.
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+
19
+ import type { FormConfig, FormState, TabPaneConfig } from '@tmagic/form';
20
+
21
+ import dataSourceService from '@editor/services/dataSource';
22
+
23
+ const arrayOptions = [
24
+ { text: '包含', value: 'include' },
25
+ { text: '不包含', value: 'not_include' },
26
+ ];
27
+
28
+ const eqOptions = [
29
+ { text: '等于', value: '=' },
30
+ { text: '不等于', value: '!=' },
31
+ ];
32
+
33
+ const numberOptions = [
34
+ { text: '大于', value: '>' },
35
+ { text: '大于等于', value: '>=' },
36
+ { text: '小于', value: '<' },
37
+ { text: '小于等于', value: '<=' },
38
+ { text: '在范围内', value: 'between' },
39
+ { text: '不在范围内', value: 'not_between' },
40
+ ];
41
+
42
+ export const styleTabConfig: TabPaneConfig = {
43
+ title: '样式',
44
+ items: [
45
+ {
46
+ name: 'style',
47
+ items: [
48
+ {
49
+ type: 'fieldset',
50
+ legend: '位置',
51
+ items: [
52
+ {
53
+ type: 'data-source-field-select',
54
+ name: 'position',
55
+ text: '固定定位',
56
+ checkStrictly: false,
57
+ dataSourceFieldType: ['string'],
58
+ fieldConfig: {
59
+ type: 'checkbox',
60
+ activeValue: 'fixed',
61
+ inactiveValue: 'absolute',
62
+ defaultValue: 'absolute',
63
+ },
64
+ },
65
+ {
66
+ type: 'data-source-field-select',
67
+ name: 'left',
68
+ text: 'left',
69
+ checkStrictly: false,
70
+ dataSourceFieldType: ['string', 'number'],
71
+ fieldConfig: {
72
+ type: 'text',
73
+ },
74
+ },
75
+ {
76
+ type: 'data-source-field-select',
77
+ name: 'top',
78
+ text: 'top',
79
+ checkStrictly: false,
80
+ dataSourceFieldType: ['string', 'number'],
81
+ fieldConfig: {
82
+ type: 'text',
83
+ },
84
+ disabled: (vm: FormState, { model }: any) =>
85
+ model.position === 'fixed' && model._magic_position === 'fixedBottom',
86
+ },
87
+ {
88
+ type: 'data-source-field-select',
89
+ name: 'right',
90
+ text: 'right',
91
+ checkStrictly: false,
92
+ dataSourceFieldType: ['string', 'number'],
93
+ fieldConfig: {
94
+ type: 'text',
95
+ },
96
+ },
97
+ {
98
+ type: 'data-source-field-select',
99
+ name: 'bottom',
100
+ text: 'bottom',
101
+ checkStrictly: false,
102
+ dataSourceFieldType: ['string', 'number'],
103
+ fieldConfig: {
104
+ type: 'text',
105
+ },
106
+ disabled: (vm: FormState, { model }: any) =>
107
+ model.position === 'fixed' && model._magic_position === 'fixedTop',
108
+ },
109
+ ],
110
+ },
111
+ {
112
+ type: 'fieldset',
113
+ legend: '盒子',
114
+ items: [
115
+ {
116
+ type: 'data-source-field-select',
117
+ name: 'width',
118
+ text: '宽度',
119
+ checkStrictly: false,
120
+ dataSourceFieldType: ['string', 'number'],
121
+ fieldConfig: {
122
+ type: 'text',
123
+ },
124
+ },
125
+ {
126
+ type: 'data-source-field-select',
127
+ name: 'height',
128
+ text: '高度',
129
+ checkStrictly: false,
130
+ dataSourceFieldType: ['string', 'number'],
131
+ fieldConfig: {
132
+ type: 'text',
133
+ },
134
+ },
135
+ {
136
+ type: 'data-source-field-select',
137
+ text: 'overflow',
138
+ name: 'overflow',
139
+ checkStrictly: false,
140
+ dataSourceFieldType: ['string'],
141
+ fieldConfig: {
142
+ type: 'select',
143
+ options: [
144
+ { text: 'visible', value: 'visible' },
145
+ { text: 'hidden', value: 'hidden' },
146
+ { text: 'clip', value: 'clip' },
147
+ { text: 'scroll', value: 'scroll' },
148
+ { text: 'auto', value: 'auto' },
149
+ { text: 'overlay', value: 'overlay' },
150
+ ],
151
+ },
152
+ },
153
+ ],
154
+ },
155
+ {
156
+ type: 'fieldset',
157
+ legend: '边框',
158
+ items: [
159
+ {
160
+ type: 'data-source-field-select',
161
+ name: 'borderWidth',
162
+ text: '宽度',
163
+ defaultValue: '0',
164
+ checkStrictly: false,
165
+ dataSourceFieldType: ['string', 'number'],
166
+ fieldConfig: {
167
+ type: 'text',
168
+ },
169
+ },
170
+ {
171
+ type: 'data-source-field-select',
172
+ name: 'borderColor',
173
+ text: '颜色',
174
+ checkStrictly: false,
175
+ dataSourceFieldType: ['string'],
176
+ fieldConfig: {
177
+ type: 'text',
178
+ },
179
+ },
180
+ {
181
+ type: 'data-source-field-select',
182
+ name: 'borderStyle',
183
+ text: '样式',
184
+ defaultValue: 'none',
185
+ checkStrictly: false,
186
+ dataSourceFieldType: ['string'],
187
+ fieldConfig: {
188
+ type: 'select',
189
+ options: [
190
+ { text: 'none', value: 'none' },
191
+ { text: 'hidden', value: 'hidden' },
192
+ { text: 'dotted', value: 'dotted' },
193
+ { text: 'dashed', value: 'dashed' },
194
+ { text: 'solid', value: 'solid' },
195
+ { text: 'double', value: 'double' },
196
+ { text: 'groove', value: 'groove' },
197
+ { text: 'ridge', value: 'ridge' },
198
+ { text: 'inset', value: 'inset' },
199
+ { text: 'outset', value: 'outset' },
200
+ ],
201
+ },
202
+ },
203
+ ],
204
+ },
205
+ {
206
+ type: 'fieldset',
207
+ legend: '背景',
208
+ items: [
209
+ {
210
+ type: 'data-source-field-select',
211
+ name: 'backgroundImage',
212
+ text: '背景图',
213
+ checkStrictly: false,
214
+ dataSourceFieldType: ['string'],
215
+ fieldConfig: {
216
+ type: 'text',
217
+ },
218
+ },
219
+ {
220
+ type: 'data-source-field-select',
221
+ name: 'backgroundColor',
222
+ text: '背景颜色',
223
+ checkStrictly: false,
224
+ dataSourceFieldType: ['string'],
225
+ fieldConfig: {
226
+ type: 'colorPicker',
227
+ },
228
+ },
229
+ {
230
+ type: 'data-source-field-select',
231
+ name: 'backgroundRepeat',
232
+ text: '背景图重复',
233
+ defaultValue: 'no-repeat',
234
+ checkStrictly: false,
235
+ dataSourceFieldType: ['string'],
236
+ fieldConfig: {
237
+ type: 'select',
238
+ options: [
239
+ { text: 'repeat', value: 'repeat' },
240
+ { text: 'repeat-x', value: 'repeat-x' },
241
+ { text: 'repeat-y', value: 'repeat-y' },
242
+ { text: 'no-repeat', value: 'no-repeat' },
243
+ { text: 'inherit', value: 'inherit' },
244
+ ],
245
+ },
246
+ },
247
+ {
248
+ type: 'data-source-field-select',
249
+ name: 'backgroundSize',
250
+ text: '背景图大小',
251
+ defaultValue: '100% 100%',
252
+ checkStrictly: false,
253
+ dataSourceFieldType: ['string'],
254
+ fieldConfig: {
255
+ type: 'text',
256
+ },
257
+ },
258
+ ],
259
+ },
260
+ {
261
+ type: 'fieldset',
262
+ legend: '字体',
263
+ items: [
264
+ {
265
+ type: 'data-source-field-select',
266
+ name: 'color',
267
+ text: '颜色',
268
+ checkStrictly: false,
269
+ dataSourceFieldType: ['string'],
270
+ fieldConfig: {
271
+ type: 'colorPicker',
272
+ },
273
+ },
274
+ {
275
+ type: 'data-source-field-select',
276
+ name: 'fontSize',
277
+ text: '大小',
278
+ checkStrictly: false,
279
+ dataSourceFieldType: ['string', 'number'],
280
+ fieldConfig: {
281
+ type: 'text',
282
+ },
283
+ },
284
+ {
285
+ type: 'data-source-field-select',
286
+ name: 'fontWeight',
287
+ text: '粗细',
288
+ checkStrictly: false,
289
+ dataSourceFieldType: ['string', 'number'],
290
+ fieldConfig: {
291
+ type: 'text',
292
+ },
293
+ },
294
+ ],
295
+ },
296
+ {
297
+ type: 'fieldset',
298
+ legend: '变形',
299
+ name: 'transform',
300
+ items: [
301
+ {
302
+ type: 'data-source-field-select',
303
+ name: 'rotate',
304
+ text: '旋转角度',
305
+ checkStrictly: false,
306
+ dataSourceFieldType: ['string'],
307
+ fieldConfig: {
308
+ type: 'text',
309
+ },
310
+ },
311
+ {
312
+ type: 'data-source-field-select',
313
+ name: 'scale',
314
+ text: '缩放',
315
+ checkStrictly: false,
316
+ dataSourceFieldType: ['number', 'string'],
317
+ fieldConfig: {
318
+ type: 'text',
319
+ },
320
+ },
321
+ ],
322
+ },
323
+ ],
324
+ },
325
+ ],
326
+ };
327
+
328
+ export const eventTabConfig: TabPaneConfig = {
329
+ title: '事件',
330
+ items: [
331
+ {
332
+ name: 'events',
333
+ src: 'component',
334
+ labelWidth: '100px',
335
+ type: 'event-select',
336
+ },
337
+ ],
338
+ };
339
+
340
+ export const advancedTabConfig: TabPaneConfig = {
341
+ title: '高级',
342
+ lazy: true,
343
+ items: [
344
+ {
345
+ name: 'created',
346
+ text: 'created',
347
+ type: 'code-select',
348
+ },
349
+ {
350
+ name: 'mounted',
351
+ text: 'mounted',
352
+ type: 'code-select',
353
+ },
354
+ ],
355
+ };
356
+
357
+ export const displayTabConfig: TabPaneConfig = {
358
+ title: '显示条件',
359
+ display: (vm: FormState, { model }: any) => model.type !== 'page',
360
+ items: [
361
+ {
362
+ type: 'groupList',
363
+ name: 'displayConds',
364
+ titlePrefix: '条件组',
365
+ expandAll: true,
366
+ items: [
367
+ {
368
+ type: 'table',
369
+ name: 'cond',
370
+ items: [
371
+ {
372
+ type: 'data-source-field-select',
373
+ name: 'field',
374
+ value: 'key',
375
+ label: '字段',
376
+ checkStrictly: false,
377
+ dataSourceFieldType: ['string', 'number', 'boolean', 'any'],
378
+ },
379
+ {
380
+ type: 'select',
381
+ options: (mForm, { model }) => {
382
+ const [id, ...fieldNames] = model.field;
383
+
384
+ const ds = dataSourceService.getDataSourceById(id);
385
+
386
+ let fields = ds?.fields || [];
387
+ let type = '';
388
+ (fieldNames || []).forEach((fieldName: string) => {
389
+ const field = fields.find((f) => f.name === fieldName);
390
+ fields = field?.fields || [];
391
+ type = field?.type || '';
392
+ });
393
+
394
+ if (type === 'array') {
395
+ return arrayOptions;
396
+ }
397
+
398
+ if (type === 'boolean') {
399
+ return [
400
+ { text: '是', value: 'is' },
401
+ { text: '不是', value: 'not' },
402
+ ];
403
+ }
404
+
405
+ if (type === 'number') {
406
+ return [...eqOptions, ...numberOptions];
407
+ }
408
+
409
+ if (type === 'string') {
410
+ return [...arrayOptions, ...eqOptions];
411
+ }
412
+
413
+ return [...arrayOptions, ...eqOptions, ...numberOptions];
414
+ },
415
+ label: '条件',
416
+ name: 'op',
417
+ },
418
+ {
419
+ label: '值',
420
+ items: [
421
+ {
422
+ name: 'value',
423
+ type: (mForm, { model }) => {
424
+ const [id, ...fieldNames] = model.field;
425
+
426
+ const ds = dataSourceService.getDataSourceById(id);
427
+
428
+ let fields = ds?.fields || [];
429
+ let type = '';
430
+ (fieldNames || []).forEach((fieldName: string) => {
431
+ const field = fields.find((f) => f.name === fieldName);
432
+ fields = field?.fields || [];
433
+ type = field?.type || '';
434
+ });
435
+
436
+ if (type === 'number') {
437
+ return 'number';
438
+ }
439
+
440
+ if (type === 'boolean') {
441
+ return 'select';
442
+ }
443
+
444
+ return 'text';
445
+ },
446
+ options: [
447
+ { text: 'true', value: true },
448
+ { text: 'false', value: false },
449
+ ],
450
+ display: (vm, { model }) => !['between', 'not_between'].includes(model.op),
451
+ },
452
+ {
453
+ name: 'range',
454
+ type: 'number-range',
455
+ display: (vm, { model }) => ['between', 'not_between'].includes(model.op),
456
+ },
457
+ ],
458
+ },
459
+ ],
460
+ },
461
+ ],
462
+ },
463
+ ],
464
+ };
465
+
466
+ /**
467
+ * 统一为组件属性表单加上事件、高级、样式配置
468
+ * @param config 组件属性配置
469
+ * @returns Object
470
+ */
471
+ export const fillConfig = (config: FormConfig = [], labelWidth = '80px'): FormConfig => [
472
+ {
473
+ type: 'tab',
474
+ labelWidth,
475
+ items: [
476
+ {
477
+ title: '属性',
478
+ items: [
479
+ // 组件类型,必须要有
480
+ {
481
+ text: 'type',
482
+ name: 'type',
483
+ type: 'hidden',
484
+ },
485
+ // 组件id,必须要有
486
+ {
487
+ name: 'id',
488
+ type: 'display',
489
+ text: 'id',
490
+ },
491
+ {
492
+ name: 'name',
493
+ text: '组件名称',
494
+ },
495
+ ...config,
496
+ ],
497
+ },
498
+ { ...styleTabConfig },
499
+ { ...eventTabConfig },
500
+ { ...advancedTabConfig },
501
+ { ...displayTabConfig },
502
+ ],
503
+ },
504
+ ];
@@ -0,0 +1,165 @@
1
+ import { EventEmitter } from 'events';
2
+
3
+ interface ScrollViewerOptions {
4
+ container: HTMLDivElement;
5
+ target: HTMLDivElement;
6
+ zoom: number;
7
+ correctionScrollSize?: {
8
+ width: number;
9
+ height: number;
10
+ };
11
+ }
12
+
13
+ export class ScrollViewer extends EventEmitter {
14
+ private container: HTMLDivElement;
15
+ private target: HTMLDivElement;
16
+ private zoom = 1;
17
+
18
+ private scrollLeft = 0;
19
+ private scrollTop = 0;
20
+
21
+ private scrollHeight = 0;
22
+ private scrollWidth = 0;
23
+
24
+ private width = 0;
25
+ private height = 0;
26
+
27
+ private translateXCorrectionValue = 0;
28
+ private translateYCorrectionValue = 0;
29
+
30
+ private correctionScrollSize = {
31
+ width: 0,
32
+ height: 0,
33
+ };
34
+
35
+ private resizeObserver = new ResizeObserver(() => {
36
+ this.setSize();
37
+ this.setScrollSize();
38
+ });
39
+
40
+ constructor(options: ScrollViewerOptions) {
41
+ super();
42
+
43
+ this.container = options.container;
44
+ this.target = options.target;
45
+ this.zoom = options.zoom;
46
+
47
+ if (this.correctionScrollSize) {
48
+ this.correctionScrollSize = {
49
+ ...this.correctionScrollSize,
50
+ ...options.correctionScrollSize,
51
+ };
52
+ }
53
+
54
+ this.container.addEventListener('wheel', this.wheelHandler, false);
55
+
56
+ this.setSize();
57
+ this.setScrollSize();
58
+ this.resizeObserver.observe(this.container);
59
+ this.resizeObserver.observe(this.target);
60
+ }
61
+
62
+ public destroy() {
63
+ this.resizeObserver.disconnect();
64
+ this.container.removeEventListener('wheel', this.wheelHandler, false);
65
+ this.removeAllListeners();
66
+ }
67
+
68
+ public setZoom(zoom: number) {
69
+ this.zoom = zoom;
70
+ this.setScrollSize();
71
+ }
72
+
73
+ public scrollTo({ left, top }: { left?: number; top?: number }) {
74
+ if (typeof left !== 'undefined') {
75
+ this.scrollLeft = left;
76
+ }
77
+
78
+ if (typeof top !== 'undefined') {
79
+ this.scrollTop = top;
80
+ }
81
+
82
+ const translateX = -this.scrollLeft + this.translateXCorrectionValue;
83
+ const translateY = -this.scrollTop + this.translateYCorrectionValue;
84
+ this.target.style.transform = `translate(${translateX}px, ${translateY}px)`;
85
+ }
86
+
87
+ private wheelHandler = (event: WheelEvent) => {
88
+ const { deltaX, deltaY, currentTarget } = event;
89
+
90
+ if (currentTarget !== this.container) return;
91
+
92
+ let top: number | undefined;
93
+ if (this.scrollHeight > this.height) {
94
+ top = this.scrollTop + this.getPos(deltaY, this.scrollTop, this.scrollHeight, this.height);
95
+ }
96
+
97
+ let left: number | undefined;
98
+ if (this.scrollWidth > this.width) {
99
+ left = this.scrollLeft + this.getPos(deltaX, this.scrollLeft, this.scrollWidth, this.width);
100
+ }
101
+
102
+ this.scrollTo({ left, top });
103
+ this.emit('scroll', {
104
+ scrollLeft: this.scrollLeft,
105
+ scrollTop: this.scrollTop,
106
+ scrollHeight: this.scrollHeight,
107
+ scrollWidth: this.scrollWidth,
108
+ });
109
+ };
110
+
111
+ private getPos(delta: number, scrollPos: number, scrollSize: number, size: number) {
112
+ let pos = 0;
113
+ if (delta < 0) {
114
+ if (scrollPos > 0) {
115
+ pos = Math.max(delta, -scrollPos);
116
+ }
117
+ } else {
118
+ const leftPos = scrollSize - size - scrollPos;
119
+ if (leftPos > 0) {
120
+ pos = Math.min(delta, leftPos);
121
+ }
122
+ }
123
+ return pos;
124
+ }
125
+
126
+ private setScrollSize = () => {
127
+ const targetRect = this.target.getBoundingClientRect();
128
+ this.scrollWidth = targetRect.width * this.zoom + this.correctionScrollSize.width;
129
+ const targetMarginTop = Number(this.target.style.marginTop) || 0;
130
+ this.scrollHeight = (targetRect.height + targetMarginTop) * this.zoom + this.correctionScrollSize.height;
131
+
132
+ let left: number | undefined;
133
+ let top: number | undefined;
134
+ if (this.scrollWidth < this.width) {
135
+ left = 0;
136
+ this.translateXCorrectionValue = 0;
137
+ } else {
138
+ this.translateXCorrectionValue = (this.scrollWidth - this.width) / 2;
139
+ }
140
+ if (this.scrollHeight < this.height) {
141
+ top = 0;
142
+ this.translateYCorrectionValue = 0;
143
+ } else {
144
+ this.translateYCorrectionValue = (this.scrollHeight - this.height) / 2;
145
+ }
146
+
147
+ this.scrollTo({
148
+ left,
149
+ top,
150
+ });
151
+
152
+ this.emit('scroll', {
153
+ scrollLeft: this.scrollLeft,
154
+ scrollTop: this.scrollTop,
155
+ scrollHeight: this.scrollHeight,
156
+ scrollWidth: this.scrollWidth,
157
+ });
158
+ };
159
+
160
+ private setSize = () => {
161
+ const { width, height } = this.container.getBoundingClientRect();
162
+ this.width = width;
163
+ this.height = height;
164
+ };
165
+ }
@@ -0,0 +1,15 @@
1
+ import type { Id } from '@tmagic/schema';
2
+ import { getKeys } from '@tmagic/utils';
3
+
4
+ import type { LayerNodeStatus } from '@editor/type';
5
+
6
+ export const updateStatus = (nodeStatusMap: Map<Id, LayerNodeStatus>, id: Id, status: Partial<LayerNodeStatus>) => {
7
+ const nodeStatus = nodeStatusMap.get(id);
8
+
9
+ if (!nodeStatus) return;
10
+ getKeys(status).forEach((key) => {
11
+ if (nodeStatus[key] !== undefined && status[key] !== undefined) {
12
+ nodeStatus[key] = Boolean(status[key]);
13
+ }
14
+ });
15
+ };