@opentiny/tiny-engine-canvas 1.0.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 (52) hide show
  1. package/.eslintrc.js +42 -0
  2. package/README.md +7 -0
  3. package/canvas.html +212 -0
  4. package/dist/index.js +48919 -0
  5. package/index.html +13 -0
  6. package/package.json +30 -0
  7. package/public/favicon.ico +0 -0
  8. package/src/Design.vue +53 -0
  9. package/src/assets/logo.png +0 -0
  10. package/src/canvas.js +34 -0
  11. package/src/components/builtin/CanvasBox.vue +22 -0
  12. package/src/components/builtin/CanvasCol.vue +89 -0
  13. package/src/components/builtin/CanvasCollection.js +278 -0
  14. package/src/components/builtin/CanvasCollection.vue +106 -0
  15. package/src/components/builtin/CanvasIcon.vue +30 -0
  16. package/src/components/builtin/CanvasImg.vue +18 -0
  17. package/src/components/builtin/CanvasPlaceholder.vue +26 -0
  18. package/src/components/builtin/CanvasRow.vue +67 -0
  19. package/src/components/builtin/CanvasRowColContainer.vue +42 -0
  20. package/src/components/builtin/CanvasSlot.vue +22 -0
  21. package/src/components/builtin/CanvasText.vue +18 -0
  22. package/src/components/builtin/builtin.json +955 -0
  23. package/src/components/builtin/helper.js +46 -0
  24. package/src/components/builtin/index.js +33 -0
  25. package/src/components/common/index.js +158 -0
  26. package/src/components/container/CanvasAction.vue +554 -0
  27. package/src/components/container/CanvasContainer.vue +244 -0
  28. package/src/components/container/CanvasDivider.vue +246 -0
  29. package/src/components/container/CanvasDragItem.vue +38 -0
  30. package/src/components/container/CanvasFooter.vue +86 -0
  31. package/src/components/container/CanvasMenu.vue +214 -0
  32. package/src/components/container/CanvasResize.vue +195 -0
  33. package/src/components/container/CanvasResizeBorder.vue +219 -0
  34. package/src/components/container/container.js +791 -0
  35. package/src/components/container/keyboard.js +147 -0
  36. package/src/components/container/shortCutPopover.vue +181 -0
  37. package/src/components/render/CanvasEmpty.vue +14 -0
  38. package/src/components/render/RenderMain.js +408 -0
  39. package/src/components/render/context.js +53 -0
  40. package/src/components/render/render.js +689 -0
  41. package/src/components/render/runner.js +140 -0
  42. package/src/i18n/en.json +5 -0
  43. package/src/i18n/zh.json +5 -0
  44. package/src/i18n.js +21 -0
  45. package/src/index.js +96 -0
  46. package/src/locale.js +19 -0
  47. package/src/lowcode.js +104 -0
  48. package/src/main.js +17 -0
  49. package/test/form.json +690 -0
  50. package/test/group.json +99 -0
  51. package/test/jsslot.json +427 -0
  52. package/vite.config.js +73 -0
@@ -0,0 +1,554 @@
1
+ <template>
2
+ <div
3
+ v-show="selectState.height && selectState.width"
4
+ class="canvas-rect select"
5
+ :style="{
6
+ top: selectState.top + 'px',
7
+ left: selectState.left + 'px',
8
+ height: selectState.height + 'px',
9
+ width: selectState.width + 'px'
10
+ }"
11
+ >
12
+ <div v-if="!resize" ref="labelRef" class="corner-mark-left" :style="labelStyle">
13
+ <span>{{ selectState.componentName }}</span>
14
+ <TinyPopover
15
+ v-model="showPopover"
16
+ placement="top-start"
17
+ title="快捷设置"
18
+ width="310"
19
+ popper-class="short-cut-set"
20
+ trigger="manual"
21
+ >
22
+ <shortCutPopover v-if="showPopover" @active="activeSetting('props')"></shortCutPopover>
23
+ <template #reference>
24
+ <icon-setting class="icon-setting" @click.stop="showPopover = !showPopover"></icon-setting>
25
+ </template>
26
+ </TinyPopover>
27
+ </div>
28
+ <!-- 绝对定位画布时调节元素大小 -->
29
+ <template v-else>
30
+ <div class="drag-resize resize-top" @mousedown.stop="onMousedown($event, 'center', 'start')"></div>
31
+ <div class="drag-resize resize-bottom" @mousedown.stop="onMousedown($event, 'center', 'end')"></div>
32
+ <div class="drag-resize resize-left" @mousedown.stop="onMousedown($event, 'start', 'center')"></div>
33
+ <div class="drag-resize resize-right" @mousedown.stop="onMousedown($event, 'end', 'center')"></div>
34
+ <div class="drag-resize resize-top-left" @mousedown.stop="onMousedown($event, 'start', 'start')"></div>
35
+ <div class="drag-resize resize-top-right" @mousedown.stop="onMousedown($event, 'end', 'start')"></div>
36
+ <div class="drag-resize resize-bottom-left" @mousedown.stop="onMousedown($event, 'start', 'end')"></div>
37
+ <div class="drag-resize resize-bottom-right" @mousedown.stop="onMousedown($event, 'end', 'end')"></div>
38
+ </template>
39
+ <div v-if="showAction" class="corner-mark-right" :style="fixStyle">
40
+ <template v-if="!isModal">
41
+ <div v-if="showToParent" title="选择父级">
42
+ <icon-chevron-left @click.stop="selectParent"></icon-chevron-left>
43
+ </div>
44
+ <div title="向上移动">
45
+ <icon-arrow-up @click.stop="moveUp"></icon-arrow-up>
46
+ </div>
47
+ <div title="向下移动">
48
+ <icon-arrow-down @click.stop="moveDown"></icon-arrow-down>
49
+ </div>
50
+ <div title="复制">
51
+ <icon-copy @click.stop="copy"></icon-copy>
52
+ </div>
53
+ </template>
54
+ <template v-else>
55
+ <div title="隐藏">
56
+ <icon-eyeclose @click.stop="hide"></icon-eyeclose>
57
+ </div>
58
+ </template>
59
+ <div title="删除">
60
+ <icon-del @click.stop="remove"></icon-del>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ <div v-show="hoverState.height && hoverState.width" class="canvas-rect hover">
65
+ <div class="corner-mark-left">
66
+ {{ hoverState.componentName }}
67
+ </div>
68
+ <div v-show="hoverState.configure?.isContainer" class="corner-mark-bottom-right">拖放元素到容器内</div>
69
+ </div>
70
+ <div v-show="lineState.height && lineState.width" class="canvas-rect line">
71
+ <div :class="['hover-line', lineState.position]">
72
+ <div v-if="lineState.position === 'in' && hoverState.configure" class="choose-slots"></div>
73
+ </div>
74
+ </div>
75
+ </template>
76
+ <script>
77
+ import { watchPostEffect, ref, watch, computed } from 'vue'
78
+ import {
79
+ IconDel,
80
+ IconSetting,
81
+ IconChevronLeft,
82
+ IconArrowDown,
83
+ IconArrowUp,
84
+ IconCopy,
85
+ IconEyeclose
86
+ } from '@opentiny/vue-icon'
87
+ import {
88
+ getCurrent,
89
+ removeNodeById,
90
+ selectNode,
91
+ updateRect,
92
+ copyNode,
93
+ getRenderer,
94
+ getSchema,
95
+ dragStart,
96
+ getCurrentElement
97
+ } from './container'
98
+ import { useResource } from '@opentiny/tiny-engine-controller'
99
+ import { Popover } from '@opentiny/vue'
100
+ import shortCutPopover from './shortCutPopover.vue'
101
+
102
+ // 工具操作条高度
103
+ const OPTION_BAR_HEIGHT = 24
104
+ // 标签高度
105
+ const LABEL_HEIGHT = 24
106
+ // 操作条最大宽度
107
+ const MAX_OPTION_WIDTH = 110
108
+
109
+ // 画布右边滚动条宽度
110
+ const SCROLL_BAR_WIDTH = 8
111
+
112
+ // 当工具操作条和标签高度并排显示时,需要的间距 6px
113
+ const OPTION_SPACE = 6
114
+
115
+ export default {
116
+ components: {
117
+ IconDel: IconDel(),
118
+ IconSetting: IconSetting(),
119
+ IconChevronLeft: IconChevronLeft(),
120
+ IconArrowDown: IconArrowDown(),
121
+ IconArrowUp: IconArrowUp(),
122
+ IconCopy: IconCopy(),
123
+ IconEyeclose: IconEyeclose(),
124
+ shortCutPopover,
125
+ TinyPopover: Popover
126
+ },
127
+ props: {
128
+ hoverState: {
129
+ type: Object,
130
+ default: () => ({})
131
+ },
132
+ selectState: {
133
+ type: Object,
134
+ default: () => ({})
135
+ },
136
+ lineState: {
137
+ type: Object,
138
+ default: () => ({})
139
+ },
140
+ resize: {
141
+ type: Boolean,
142
+ default: false
143
+ },
144
+ windowGetClickEventTarget: Object
145
+ },
146
+ emits: ['remove', 'selectSlot', 'setting'],
147
+ setup(props) {
148
+ const remove = () => {
149
+ removeNodeById(getCurrent().schema?.id)
150
+ }
151
+
152
+ const moveChild = (list, selected, addend) => {
153
+ if (!list || list.length < 2) {
154
+ return
155
+ }
156
+
157
+ const index = list.indexOf(selected)
158
+
159
+ if (index > -1) {
160
+ const toIndex = index + addend
161
+
162
+ if (toIndex > -1 && toIndex < list.length) {
163
+ // eslint-disable-next-line no-extra-semi
164
+ ;[list[index], list[toIndex]] = [list[toIndex], list[index]]
165
+
166
+ updateRect()
167
+ }
168
+ }
169
+ }
170
+
171
+ const moveUp = () => {
172
+ const { parent, schema } = getCurrent()
173
+ moveChild(parent?.children, schema, -1)
174
+ }
175
+
176
+ const moveDown = () => {
177
+ const { parent, schema } = getCurrent()
178
+ moveChild(parent?.children, schema, 1)
179
+ }
180
+
181
+ const selectParent = () => {
182
+ const parentId = getCurrent().parent?.id
183
+ parentId && selectNode(parentId)
184
+ }
185
+
186
+ const copy = () => {
187
+ copyNode(getCurrent().schema.id)
188
+ }
189
+
190
+ const hide = () => {
191
+ getRenderer().setCondition(getCurrent().schema?.id, false)
192
+ updateRect()
193
+ }
194
+
195
+ const showAction = computed(() => {
196
+ const { parent } = getCurrent()
197
+ return !props.resize && parent && parent?.type !== 'JSSlot'
198
+ })
199
+
200
+ const showToParent = computed(() => getCurrent().parent !== getSchema())
201
+
202
+ const isModal = computed(() => {
203
+ const config = useResource().getMaterial(props.selectState.componentName)
204
+ return config?.configure?.isModal
205
+ })
206
+
207
+ const fixStyle = ref('')
208
+
209
+ let showPopover = ref(false)
210
+
211
+ const activeSetting = () => {
212
+ showPopover.value = false
213
+ }
214
+
215
+ const findParentHasClass = (target) => {
216
+ let parent = target.parentNode
217
+ let flag = false
218
+
219
+ if (parent.className === undefined) {
220
+ return false
221
+ }
222
+
223
+ let name = JSON.stringify(parent.className)
224
+
225
+ if (name && name.indexOf('short-cut-set') === -1 && name.indexOf('tiny-dialog-box') === -1) {
226
+ flag = findParentHasClass(parent)
227
+ } else {
228
+ flag = true
229
+ }
230
+
231
+ return flag
232
+ }
233
+
234
+ const onMousedown = (event, horizontal, vertical) => {
235
+ const element = getCurrentElement()
236
+ if (!element) {
237
+ return
238
+ }
239
+ const { x, y, height, width } = element.getBoundingClientRect()
240
+
241
+ dragStart(getCurrent().schema, element, {
242
+ offsetX: event.clientX,
243
+ offsetY: event.clientY,
244
+ x,
245
+ y,
246
+ height,
247
+ width,
248
+ horizontal,
249
+ vertical
250
+ })
251
+ }
252
+
253
+ watch(
254
+ () => props.windowGetClickEventTarget,
255
+ (newProps) => {
256
+ if (newProps) {
257
+ let flag = findParentHasClass(newProps)
258
+ if (!flag) {
259
+ showPopover.value = false
260
+ }
261
+ }
262
+ }
263
+ )
264
+
265
+ const labelRef = ref(null)
266
+ const labelStyle = ref('')
267
+
268
+ const bottomPanelHeight = ref(0)
269
+ const topToolbarHeight = ref(0)
270
+
271
+ watchPostEffect(() => {
272
+ if (!bottomPanelHeight.value) {
273
+ bottomPanelHeight.value = document.querySelector('#tiny-bottom-panel')?.offsetHeight
274
+ }
275
+ if (!topToolbarHeight.value) {
276
+ topToolbarHeight.value = document.querySelector('.tiny-engine-toolbar')?.offsetHeight
277
+ }
278
+
279
+ const { left, top, width, height } = props.selectState
280
+
281
+ // 是否 将label 标签放置到底部,判断 top 距离
282
+ const isLabelAtBottom = top <= topToolbarHeight.value + LABEL_HEIGHT
283
+ // 是否将操作栏放置到底部,判断当前选中组件底部与页面底部的距离。
284
+ const isOptionAtBottom = window.innerHeight - top - height - bottomPanelHeight.value > OPTION_BAR_HEIGHT
285
+
286
+ // 选中组件需要最小的宽度,如果小于这个最小宽度,label 和 option 组件可能会重叠遮挡,labelRef.value.clientWidth:label 的宽度
287
+ const minWidth = MAX_OPTION_WIDTH + labelRef.value?.clientWidth || 0
288
+ let translateXDis = 0
289
+
290
+ const siteCanvas = document.querySelector('.site-canvas')
291
+ const right = siteCanvas.getBoundingClientRect().right
292
+ // 判断是否偏右,偏右且重叠的话,需要移动 label 的位移,不能移动 option 的位移,否则有可能被遮挡
293
+ const isOverRight = right <= left + width + SCROLL_BAR_WIDTH
294
+
295
+ // 如果选中组件宽度小于最小宽度要求,则需要位移
296
+ if (width < minWidth) {
297
+ translateXDis = MAX_OPTION_WIDTH - width + (labelRef.value?.clientWidth || 0) + OPTION_SPACE
298
+ }
299
+
300
+ labelStyle.value = `top: unset; ${isLabelAtBottom ? 'bottom' : 'top'}: -${LABEL_HEIGHT}px; ${
301
+ isOverRight && isLabelAtBottom === isOptionAtBottom && `left: -${translateXDis}px;`
302
+ }`
303
+
304
+ fixStyle.value = `
305
+ ${translateXDis && !isOverRight ? `transform: translateX(${translateXDis}px);` : ''}
306
+ ${isOptionAtBottom ? 'bottom' : 'top'}: -${OPTION_BAR_HEIGHT}px;`
307
+ })
308
+
309
+ return {
310
+ remove,
311
+ moveUp,
312
+ moveDown,
313
+ copy,
314
+ hide,
315
+ selectParent,
316
+ fixStyle,
317
+ showAction,
318
+ showPopover,
319
+ showToParent,
320
+ activeSetting,
321
+ isModal,
322
+ onMousedown,
323
+ labelStyle,
324
+ labelRef
325
+ }
326
+ }
327
+ }
328
+ </script>
329
+
330
+ <style lang="less">
331
+ .canvas-rect {
332
+ position: fixed;
333
+ box-sizing: border-box;
334
+ pointer-events: none;
335
+ border: 1px solid var(--ti-lowcode-canvas-rect-border-color);
336
+ z-index: 2;
337
+ &.absolute {
338
+ pointer-events: all;
339
+ }
340
+ &.hover {
341
+ border-style: dashed;
342
+ top: v-bind("hoverState.top + 'px'");
343
+ left: v-bind("hoverState.left + 'px'");
344
+ height: v-bind("hoverState.height + 'px'");
345
+ width: v-bind("hoverState.width + 'px'");
346
+
347
+ .corner-mark-left {
348
+ height: 14px;
349
+ top: -14px;
350
+ padding-left: 0;
351
+ font-size: 12px;
352
+ }
353
+ }
354
+ &.line {
355
+ border-color: transparent;
356
+ top: v-bind("lineState.top + 'px'");
357
+ left: v-bind("lineState.left + 'px'");
358
+ height: v-bind("lineState.height + 'px'");
359
+ width: v-bind("lineState.width + 'px'");
360
+ }
361
+ .hover-line {
362
+ &.top {
363
+ width: 100%;
364
+ height: 5px;
365
+ background: var(--ti-lowcode-icon-bind-color);
366
+ position: absolute;
367
+ top: -3px;
368
+ }
369
+ &.left {
370
+ width: 5px;
371
+ height: 100%;
372
+ background: var(--ti-lowcode-icon-bind-color);
373
+ position: absolute;
374
+ left: -3px;
375
+ }
376
+ &.bottom {
377
+ width: 100%;
378
+ height: 5px;
379
+ background: var(--ti-lowcode-icon-bind-color);
380
+ position: absolute;
381
+ bottom: -3px;
382
+ }
383
+ &.right {
384
+ width: 5px;
385
+ height: 100%;
386
+ background: var(--ti-lowcode-icon-bind-color);
387
+ position: absolute;
388
+ right: -3px;
389
+ }
390
+ &.in {
391
+ width: 100%;
392
+ height: 100%;
393
+ background: var(--ti-lowcode-canvas-hover-line-in-bg-color);
394
+ }
395
+ &.forbid {
396
+ width: 100%;
397
+ height: 100%;
398
+ background: var(--ti-lowcode-canvas-hover-line-forbid-bg-color);
399
+ }
400
+ }
401
+
402
+ .choose-slots {
403
+ display: flex;
404
+ justify-content: left;
405
+ align-items: left;
406
+ height: 100%;
407
+ & > div {
408
+ pointer-events: all;
409
+ width: 40px;
410
+ border: 1px solid var(--ti-lowcode-canvas-choose-slot-border-color);
411
+ color: var(--ti-lowcode-canvas-choose-slot-color);
412
+ overflow: hidden;
413
+ font-size: 10px;
414
+ margin: 2px;
415
+ text-align: center;
416
+ &:hover {
417
+ background: #40a9ff;
418
+ color: #fff;
419
+ }
420
+ }
421
+ }
422
+
423
+ .corner-mark-left {
424
+ display: flex;
425
+ align-items: center;
426
+ font-size: 14px;
427
+ position: absolute;
428
+ top: -24px;
429
+ height: 24px;
430
+ color: var(--ti-lowcode-canvas-corner-mark-left-color);
431
+ padding: 0 8px;
432
+
433
+ .icon-setting {
434
+ margin-left: 4px;
435
+ margin-bottom: 2px;
436
+ }
437
+ }
438
+
439
+ .corner-mark-bottom-right {
440
+ position: absolute;
441
+ font-size: 12px;
442
+ right: -1px;
443
+ color: var(--ti-lowcode-canvas-corner-mark-bottom-right-color);
444
+ bottom: -20px;
445
+ background: var(--ti-lowcode-canvas-corner-mark-bottom-right-bg-color);
446
+ border: 1px solid var(--ti-lowcode-canvas-corner-mark-bottom-right-border-color);
447
+ padding: 0 2px;
448
+ overflow: hidden;
449
+ white-space: nowrap;
450
+ text-overflow: ellipsis;
451
+ }
452
+
453
+ .corner-mark-right {
454
+ display: flex;
455
+ align-items: center;
456
+ position: absolute;
457
+ right: -1px;
458
+ height: 24px;
459
+ padding: 0 4px;
460
+ color: var(--ti-lowcode-canvas-corner-mark-right-color);
461
+ background: var(--ti-lowcode-canvas-corner-mark-right-bg-color);
462
+ pointer-events: all;
463
+ cursor: pointer;
464
+
465
+ div {
466
+ font-size: 0;
467
+
468
+ svg {
469
+ margin: 0 4px;
470
+ font-size: 16px;
471
+ }
472
+ }
473
+ }
474
+
475
+ &.select {
476
+ z-index: 3;
477
+ border-width: 2px;
478
+
479
+ .corner-mark-left {
480
+ white-space: nowrap;
481
+ pointer-events: all;
482
+ left: -2px;
483
+ color: var(--ti-lowcode-canvas-select-corner-mark-left-color);
484
+ background: var(--ti-lowcode-canvas-select-corner-mark-left-bg-color);
485
+ svg {
486
+ cursor: pointer;
487
+ }
488
+ }
489
+ }
490
+ }
491
+ .short-cut-set.tiny-popper.tiny-popover {
492
+ background: var(--ti-lowcode-toolbar-bg);
493
+ padding: 10px;
494
+ .body label,
495
+ .header {
496
+ color: var(--ti-lowcode-dialog-font-color);
497
+ font-size: 12px;
498
+ }
499
+ .tiny-popover__title {
500
+ color: var(--ti-lowcode-dialog-font-color);
501
+ }
502
+ }
503
+
504
+ .short-cut-set.tiny-popper.tiny-popover[x-placement^='bottom'] .popper__arrow::after {
505
+ border-bottom-color: var(--ti-lowcode-toolbar-bg);
506
+ }
507
+
508
+ .drag-resize {
509
+ position: absolute;
510
+ top: -6px;
511
+ bottom: -6px;
512
+ left: -6px;
513
+ right: -6px;
514
+ height: 6px;
515
+ width: 6px;
516
+ background-color: #409eff;
517
+ cursor: pointer;
518
+ pointer-events: auto !important;
519
+ &.resize-top {
520
+ left: calc(50% - 3px);
521
+ cursor: n-resize;
522
+ }
523
+ &.resize-bottom {
524
+ left: calc(50% - 3px);
525
+ top: auto;
526
+ cursor: s-resize;
527
+ }
528
+ &.resize-left {
529
+ top: calc(50% - 3px);
530
+ cursor: e-resize;
531
+ }
532
+ &.resize-right {
533
+ top: calc(50% - 3px);
534
+ left: auto;
535
+ cursor: e-resize;
536
+ }
537
+ &.resize-top-left {
538
+ cursor: nw-resize;
539
+ }
540
+ &.resize-top-right {
541
+ left: auto;
542
+ cursor: ne-resize;
543
+ }
544
+ &.resize-bottom-left {
545
+ top: auto;
546
+ cursor: sw-resize;
547
+ }
548
+ &.resize-bottom-right {
549
+ left: auto;
550
+ top: auto;
551
+ cursor: se-resize;
552
+ }
553
+ }
554
+ </style>