@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.
- package/.eslintrc.js +42 -0
- package/README.md +7 -0
- package/canvas.html +212 -0
- package/dist/index.js +48919 -0
- package/index.html +13 -0
- package/package.json +30 -0
- package/public/favicon.ico +0 -0
- package/src/Design.vue +53 -0
- package/src/assets/logo.png +0 -0
- package/src/canvas.js +34 -0
- package/src/components/builtin/CanvasBox.vue +22 -0
- package/src/components/builtin/CanvasCol.vue +89 -0
- package/src/components/builtin/CanvasCollection.js +278 -0
- package/src/components/builtin/CanvasCollection.vue +106 -0
- package/src/components/builtin/CanvasIcon.vue +30 -0
- package/src/components/builtin/CanvasImg.vue +18 -0
- package/src/components/builtin/CanvasPlaceholder.vue +26 -0
- package/src/components/builtin/CanvasRow.vue +67 -0
- package/src/components/builtin/CanvasRowColContainer.vue +42 -0
- package/src/components/builtin/CanvasSlot.vue +22 -0
- package/src/components/builtin/CanvasText.vue +18 -0
- package/src/components/builtin/builtin.json +955 -0
- package/src/components/builtin/helper.js +46 -0
- package/src/components/builtin/index.js +33 -0
- package/src/components/common/index.js +158 -0
- package/src/components/container/CanvasAction.vue +554 -0
- package/src/components/container/CanvasContainer.vue +244 -0
- package/src/components/container/CanvasDivider.vue +246 -0
- package/src/components/container/CanvasDragItem.vue +38 -0
- package/src/components/container/CanvasFooter.vue +86 -0
- package/src/components/container/CanvasMenu.vue +214 -0
- package/src/components/container/CanvasResize.vue +195 -0
- package/src/components/container/CanvasResizeBorder.vue +219 -0
- package/src/components/container/container.js +791 -0
- package/src/components/container/keyboard.js +147 -0
- package/src/components/container/shortCutPopover.vue +181 -0
- package/src/components/render/CanvasEmpty.vue +14 -0
- package/src/components/render/RenderMain.js +408 -0
- package/src/components/render/context.js +53 -0
- package/src/components/render/render.js +689 -0
- package/src/components/render/runner.js +140 -0
- package/src/i18n/en.json +5 -0
- package/src/i18n/zh.json +5 -0
- package/src/i18n.js +21 -0
- package/src/index.js +96 -0
- package/src/locale.js +19 -0
- package/src/lowcode.js +104 -0
- package/src/main.js +17 -0
- package/test/form.json +690 -0
- package/test/group.json +99 -0
- package/test/jsslot.json +427 -0
- 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
|
+
}
|