@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,408 @@
1
+ /**
2
+ * Copyright (c) 2023 - present TinyEngine Authors.
3
+ * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license.
6
+ *
7
+ * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
8
+ * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
9
+ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
10
+ *
11
+ */
12
+
13
+ import { h, provide, inject, nextTick, shallowReactive, reactive, ref, watch, watchEffect } from 'vue'
14
+ import { I18nInjectionKey } from 'vue-i18n'
15
+ import { useBroadcastChannel } from '@vueuse/core'
16
+ import { constants } from '@opentiny/tiny-engine-utils'
17
+ import { generateFunction } from '@opentiny/tiny-engine-controller/utils'
18
+ import renderer, { parseData, setConfigure, setController, globalNotify, isStateAccessor } from './render'
19
+ import { getNode as getNodeById, clearNodes, getRoot, setContext, getContext, setCondition, context } from './context'
20
+ import CanvasEmpty from './CanvasEmpty.vue'
21
+
22
+ const { BROADCAST_CHANNEL } = constants
23
+
24
+ const reset = (obj) => {
25
+ Object.keys(obj).forEach((key) => delete obj[key])
26
+ }
27
+
28
+ const refreshKey = ref(0)
29
+ const methods = {}
30
+ const schema = reactive({})
31
+ const state = shallowReactive({})
32
+ const bridge = {}
33
+ const utils = {}
34
+ const props = {}
35
+
36
+ const globalState = ref([])
37
+ const stores = shallowReactive({})
38
+ const dataSourceMap = shallowReactive({})
39
+
40
+ const Func = Function
41
+
42
+ watchEffect(() => {
43
+ reset(stores)
44
+ globalState.value.forEach(({ id, state = {}, getters = {} }) => {
45
+ const computedGetters = Object.keys(getters).reduce(
46
+ (acc, key) => ({
47
+ ...acc,
48
+ [key]: new Func('return ' + getters[key].value)().call(acc, state)
49
+ }),
50
+ {}
51
+ )
52
+
53
+ stores[id] = { ...state, ...computedGetters }
54
+ })
55
+ })
56
+
57
+ const getUtils = () => utils
58
+
59
+ const setUtils = (data, clear, isForceRefresh) => {
60
+ if (clear) {
61
+ reset(utils)
62
+ }
63
+ const utilsCollection = {}
64
+ // 目前画布还不具备远程加载utils工具类的功能,目前只能加载TinyVue组件库中的组件工具
65
+ data?.forEach((item) => {
66
+ const util = window.TinyVue[item.name]
67
+ if (util) {
68
+ utilsCollection[item.name] = util
69
+ }
70
+
71
+ // 此处需要把工具类中的icon图标也加入utils上下文环境
72
+ const utilIcon = window.TinyVueIcon[item.name]
73
+ if (utilIcon) {
74
+ utilsCollection[item.name] = utilIcon
75
+ }
76
+
77
+ // 解析函数式的工具类
78
+ if (item.type === 'function') {
79
+ const defaultFn = () => {}
80
+ utilsCollection[item.name] = generateFunction(item.content.value, context) || defaultFn
81
+ }
82
+ })
83
+ Object.assign(utils, utilsCollection)
84
+
85
+ // 因为工具类并不具有响应式行为,所以需要通过修改key来强制刷新画布
86
+ if (isForceRefresh) {
87
+ refreshKey.value++
88
+ }
89
+ }
90
+
91
+ const setBridge = (data, clear) => {
92
+ clear && reset(bridge)
93
+ Object.assign(bridge, data)
94
+ }
95
+
96
+ const getBridge = () => bridge
97
+
98
+ const getMethods = () => methods
99
+
100
+ const setMethods = (data = {}, clear) => {
101
+ clear && reset(methods)
102
+ // 这里有些方法在画布还是有执行的必要的,比如说表格的renderer和formatText方法,包括一些自定义渲染函数
103
+ Object.assign(
104
+ methods,
105
+ Object.fromEntries(
106
+ Object.keys(data).map((key) => {
107
+ return [key, parseData(data[key], {}, getContext())]
108
+ })
109
+ )
110
+ )
111
+ setContext(methods)
112
+ }
113
+
114
+ const getState = () => state
115
+
116
+ const deleteState = (variable) => {
117
+ delete state[variable]
118
+ }
119
+
120
+ const generateAccessor = (type, accessor, property) => {
121
+ const accessorFn = generateFunction(accessor[type].value, context)
122
+
123
+ return { property, accessorFn, type }
124
+ }
125
+
126
+ // 这里缓存状态变量对应的访问器,用于watchEffect更新和取消监听
127
+ const stateAccessorMap = new Map()
128
+
129
+ // 缓存区块属性的访问器
130
+ const propsAccessorMap = new Map()
131
+
132
+ const generateStateAccessors = (type, accessor, key) => {
133
+ const stateWatchEffectKey = `${key}${type}`
134
+ const { property, accessorFn } = generateAccessor(type, accessor, key)
135
+
136
+ // 将之前已有的watchEffect取消监听,这里操作很有必要,不然会造成数据混乱
137
+ stateAccessorMap.get(stateWatchEffectKey)?.()
138
+
139
+ // 更新watchEffect监听
140
+ stateAccessorMap.set(
141
+ stateWatchEffectKey,
142
+ watchEffect(() => {
143
+ try {
144
+ accessorFn()
145
+ } catch (error) {
146
+ globalNotify({
147
+ type: 'warning',
148
+ title: `状态变量${property}的访问器函数:${accessorFn.name}执行报错`,
149
+ message: error?.message || `状态变量${property}的访问器函数:${accessorFn.name}执行报错,请检查语法`
150
+ })
151
+ }
152
+ })
153
+ )
154
+ }
155
+
156
+ const setState = (data, clear) => {
157
+ clear && reset(state)
158
+ if (!schema.state) {
159
+ schema.state = data
160
+ }
161
+
162
+ Object.assign(state, parseData(data, {}, getContext()) || {})
163
+
164
+ // 在状态变量合并之后,执行访问器中watchEffect,为了可以在访问器函数中可以访问其他state变量
165
+ Object.entries(data)?.forEach(([key, stateData]) => {
166
+ if (isStateAccessor(stateData)) {
167
+ const accessor = stateData.accessor
168
+ if (accessor?.getter?.value) {
169
+ generateStateAccessors('getter', accessor, key)
170
+ }
171
+
172
+ if (accessor?.setter?.value) {
173
+ generateStateAccessors('setter', accessor, key)
174
+ }
175
+ }
176
+ })
177
+ }
178
+
179
+ const getDataSourceMap = () => {
180
+ return dataSourceMap.value
181
+ }
182
+
183
+ const setDataSourceMap = (list) => {
184
+ dataSourceMap.value = list.reduce((dMap, config) => {
185
+ const dataSource = { config: config.data }
186
+
187
+ const result = {
188
+ code: '',
189
+ msg: 'success',
190
+ data: {}
191
+ }
192
+ result.data =
193
+ dataSource.config.type === 'array'
194
+ ? { items: dataSource?.config?.data, total: dataSource?.config?.data?.length }
195
+ : dataSource?.config?.data
196
+
197
+ dataSource.load = () => Promise.resolve(result)
198
+ dMap[config.name] = dataSource
199
+
200
+ return dMap
201
+ }, {})
202
+ }
203
+
204
+ const getGlobalState = () => {
205
+ return globalState.value
206
+ }
207
+
208
+ const setGlobalState = (data = []) => {
209
+ globalState.value = data
210
+ }
211
+
212
+ const setProps = (data, clear) => {
213
+ clear && reset(props)
214
+ Object.assign(props, data)
215
+ }
216
+
217
+ const getProps = () => props
218
+
219
+ const initProps = (properties = []) => {
220
+ const props = {}
221
+ const accessorFunctions = []
222
+
223
+ properties.forEach(({ content = [] }) => {
224
+ content.forEach(({ defaultValue, property, accessor }) => {
225
+ // 如果没有设置defaultValue就是undefined这和vue处理方式一样
226
+ props[property] = defaultValue
227
+
228
+ // 如果区块属性有访问器accessor,则先解析getter和setter函数
229
+ if (accessor?.getter?.value) {
230
+ // 此处不能直接执行watchEffect,需要在上下文环境设置好之后去执行,此处只是收集函数
231
+ accessorFunctions.push(generateAccessor('getter', accessor, property))
232
+ }
233
+
234
+ if (accessor?.setter?.value) {
235
+ accessorFunctions.push(generateAccessor('setter', accessor, property))
236
+ }
237
+ })
238
+ })
239
+
240
+ setProps(props, true)
241
+
242
+ return accessorFunctions
243
+ }
244
+
245
+ const getSchema = () => schema
246
+
247
+ const setPagecss = (css = '') => {
248
+ const id = 'page-css'
249
+ let element = document.getElementById(id)
250
+ const head = document.querySelector('head')
251
+
252
+ document.body.setAttribute('style', '')
253
+
254
+ if (!element) {
255
+ element = document.createElement('style')
256
+ element.setAttribute('type', 'text/css')
257
+ element.setAttribute('id', id)
258
+
259
+ element.innerHTML = css
260
+ head.appendChild(element)
261
+ } else {
262
+ element.innerHTML = css
263
+ }
264
+ }
265
+
266
+ const setSchema = async (data) => {
267
+ const newSchema = JSON.parse(JSON.stringify(data || schema))
268
+ reset(schema)
269
+ // 页面初始化的时候取消所有状态变量的watchEffect监听
270
+ stateAccessorMap.forEach((stateAccessorFn) => {
271
+ stateAccessorFn()
272
+ })
273
+
274
+ // 区块初始化的时候取消所有的区块属性watchEffect监听
275
+ propsAccessorMap.forEach((propsAccessorFn) => {
276
+ propsAccessorFn()
277
+ })
278
+
279
+ // 清空存状态变量和区块props访问器的缓存
280
+ stateAccessorMap.clear()
281
+ propsAccessorMap.clear()
282
+
283
+ const context = {
284
+ utils,
285
+ bridge,
286
+ stores,
287
+ state,
288
+ props,
289
+ dataSourceMap: {},
290
+ emit: () => {} // 兼容访问器中getter和setter中this.emit写法
291
+ }
292
+ Object.defineProperty(context, 'dataSourceMap', {
293
+ get: getDataSourceMap
294
+ })
295
+ // 此处提升很重要,因为setState、initProps也会触发画布重新渲染,所以需要提升上下文环境的设置时间
296
+ setContext(context, true)
297
+
298
+ // 设置方法调用上下文
299
+ setMethods(newSchema.methods, true)
300
+
301
+ // 如果是区块则需要设置对外暴露的props
302
+ const accessorFunctions = initProps(newSchema.schema?.properties)
303
+
304
+ // 这里setState(会触发画布渲染),是因为状态管理里面的变量会用到props、utils、bridge、stores、methods
305
+ setState(newSchema.state, true)
306
+ clearNodes()
307
+ await nextTick()
308
+ setPagecss(data.css)
309
+ Object.assign(schema, newSchema)
310
+
311
+ // 当上下文环境设置完成之后再去处理区块属性访问器的watchEffect
312
+ accessorFunctions.forEach(({ property, accessorFn, type }) => {
313
+ const propsWatchEffectKey = `${property}${type}`
314
+ propsAccessorMap.set(
315
+ propsWatchEffectKey,
316
+ watchEffect(() => {
317
+ try {
318
+ accessorFn()
319
+ } catch (error) {
320
+ globalNotify({
321
+ type: 'warning',
322
+ title: `区块属性${property}的访问器函数:${accessorFn.name}执行报错`,
323
+ message: error?.message || `区块属性${property}的访问器函数:${accessorFn.name}执行报错,请检查语法`
324
+ })
325
+ }
326
+ })
327
+ )
328
+ })
329
+
330
+ return schema
331
+ }
332
+
333
+ const getNode = (id, parent) => (id ? getNodeById(id, parent) : schema)
334
+
335
+ export default {
336
+ setup() {
337
+ provide('rootSchema', schema)
338
+
339
+ const { locale } = inject(I18nInjectionKey).global
340
+ const { data } = useBroadcastChannel({ name: BROADCAST_CHANNEL.CanvasLang })
341
+ const { post } = useBroadcastChannel({ name: BROADCAST_CHANNEL.SchemaLength })
342
+
343
+ watch(data, () => {
344
+ locale.value = data.value
345
+ })
346
+
347
+ watch(
348
+ () => schema?.children?.length,
349
+ (length) => {
350
+ post(length)
351
+ }
352
+ )
353
+
354
+ // 这里监听schema.methods,为了保证methods上下文环境始终为最新
355
+ watch(
356
+ () => schema.methods,
357
+ (value) => {
358
+ setMethods(value, true)
359
+ },
360
+ {
361
+ deep: true
362
+ }
363
+ )
364
+ },
365
+ render() {
366
+ return h(
367
+ 'tiny-i18n-host',
368
+ {
369
+ locale: 'zh_CN',
370
+ key: refreshKey.value,
371
+ ref: 'page',
372
+ className: 'design-page'
373
+ },
374
+ schema.children?.length
375
+ ? schema.children.map((child) => h(renderer, { schema: child, parent: schema }))
376
+ : [h(CanvasEmpty)]
377
+ )
378
+ }
379
+ }
380
+
381
+ export const api = {
382
+ getUtils,
383
+ setUtils,
384
+ getBridge,
385
+ setBridge,
386
+ getMethods,
387
+ setMethods,
388
+ setController,
389
+ setConfigure,
390
+ getSchema,
391
+ setSchema,
392
+ getState,
393
+ deleteState,
394
+ setState,
395
+ getProps,
396
+ setProps,
397
+ getContext,
398
+ getNode,
399
+ getRoot,
400
+ setPagecss,
401
+ setCondition,
402
+ getGlobalState,
403
+ getDataSourceMap,
404
+ setDataSourceMap,
405
+ setGlobalState
406
+ }
407
+
408
+ window.api = api
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Copyright (c) 2023 - present TinyEngine Authors.
3
+ * Copyright (c) 2023 - present Huawei Cloud Computing Technologies Co., Ltd.
4
+ *
5
+ * Use of this source code is governed by an MIT-style license.
6
+ *
7
+ * THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
8
+ * BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
9
+ * A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
10
+ *
11
+ */
12
+
13
+ import { shallowReactive } from 'vue'
14
+ import { utils } from '@opentiny/tiny-engine-utils'
15
+
16
+ export const context = shallowReactive({})
17
+
18
+ // 从大纲树控制隐藏
19
+ export const conditions = shallowReactive({})
20
+
21
+ const nodes = {}
22
+
23
+ export const setNode = (schema, parent) => {
24
+ schema.id = schema.id || utils.guid()
25
+ nodes[schema.id] = { node: schema, parent }
26
+ }
27
+
28
+ export const getNode = (id, parent) => {
29
+ return parent ? nodes[id] : nodes[id].node
30
+ }
31
+
32
+ export const delNode = (id) => delete nodes[id]
33
+
34
+ export const clearNodes = () => {
35
+ Object.keys(nodes).forEach(delNode)
36
+ }
37
+
38
+ export const getRoot = (id) => {
39
+ const { parent } = getNode(id, true)
40
+
41
+ return parent?.id ? getRoot(parent.id) : parent
42
+ }
43
+
44
+ export const setContext = (ctx, clear) => {
45
+ clear && Object.keys(context).forEach((key) => delete context[key])
46
+ Object.assign(context, ctx)
47
+ }
48
+
49
+ export const getContext = () => context
50
+
51
+ export const setCondition = (id, visible = false) => {
52
+ conditions[id] = visible
53
+ }