@fugood/bricks-project 2.21.0-beta.14.test0

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.
@@ -0,0 +1,65 @@
1
+ const stage = process.env.BRICKS_STAGE || 'production'
2
+
3
+ const apiUrlMap = {
4
+ production: 'https://display.bricks.tools/api/graphql-workspace',
5
+ beta: 'https://display-beta.bricks.tools/api/graphql-workspace',
6
+ dev: 'http://localhost:3001/api/graphql-workspace',
7
+ }
8
+
9
+ const apiURL = apiUrlMap[stage]
10
+ if (!apiURL) throw new Error(`Invalid BRICKS_STAGE: ${stage}`)
11
+
12
+ const workspaceToken = process.env.BRICKS_WORKSPACE_TOKEN
13
+
14
+ const doGQL = async (query: string, variables: Record<string, any>) => {
15
+ if (!workspaceToken) throw new Error('env BRICKS_WORKSPACE_TOKEN is not set')
16
+
17
+ const data = await fetch(apiURL, {
18
+ method: 'POST',
19
+ headers: {
20
+ 'Content-Type': 'application/json',
21
+ Authorization: `Bearer ${workspaceToken}`,
22
+ },
23
+ body: JSON.stringify({ query, variables }),
24
+ }).then((res) => res.json())
25
+ return data
26
+ }
27
+
28
+ export const deployApp = async (appId: string, config: {}, lastCommitId: string) => {
29
+ const { errors } = await doGQL(
30
+ `mutation BRICKS_PROJECT_updateApplication($id: ID!, $config: JSON) {
31
+ updateApplication(
32
+ id: $id,
33
+ config: $config
34
+ ) {
35
+ _id
36
+ name
37
+ }
38
+ }`,
39
+ {
40
+ id: appId,
41
+ config: {
42
+ ...config,
43
+ bricks_project_last_commit_id: lastCommitId,
44
+ },
45
+ },
46
+ )
47
+ if (errors) throw new Error(errors[0].message)
48
+ return true
49
+ }
50
+
51
+ export const pullApp = async (appId: string) => {
52
+ const { data, errors } = await doGQL(
53
+ `query BRICKS_PROJECT_application($id: ID!) {
54
+ application(id: $id) {
55
+ _id
56
+ name
57
+ description
58
+ config
59
+ }
60
+ }`,
61
+ { id: appId },
62
+ )
63
+ if (errors) throw new Error(errors[0].message)
64
+ return data.application
65
+ }
package/api/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './application'
@@ -0,0 +1,485 @@
1
+ import _ from 'lodash'
2
+ import { parse as parseAST, ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
3
+ import escodegen from 'escodegen'
4
+ import {
5
+ Application,
6
+ Data,
7
+ AnimationDef,
8
+ AnimationComposeDef,
9
+ EventAction,
10
+ ActionWithDataParams,
11
+ ActionWithParams,
12
+ BrickItems,
13
+ SwitchCondData,
14
+ SwitchCondInnerStateCurrentCanvas,
15
+ SwitchCondPropertyBankByItemKey,
16
+ DataCalculationMap,
17
+ DataCalculationScript,
18
+ DataCalculationData,
19
+ DataCommand,
20
+ Brick,
21
+ Generator,
22
+ } from '../types'
23
+ import { generateCalulationMap } from './util'
24
+
25
+ const compileProperty = (property, result = {}) => {
26
+ if (Array.isArray(property)) {
27
+ return property.map((p) => compileProperty(p, result))
28
+ }
29
+ if (property?.__typename === 'DataLink' && property.data?.()) return `PROPERTY_BANK#${property.data().id}`
30
+ if (typeof property === 'function') return property()?.id // defined type instance getter
31
+ if (typeof property === 'object') {
32
+ return Object.entries(property).reduce((acc, [key, value]) => {
33
+ acc[key] = compileProperty(value, {})
34
+ return acc
35
+ }, result)
36
+ }
37
+ return property
38
+ }
39
+
40
+ const convertOutletKey = (templateKey: string, key: string) => {
41
+ return `${templateKey}_${_.snakeCase(key).toUpperCase()}`
42
+ }
43
+
44
+ const compileOutlets = (templateKey: string, outlets: { [key: string]: () => Data }) => {
45
+ return Object.entries(outlets).reduce((acc, [key, data]) => {
46
+ acc[convertOutletKey(templateKey, key)] = data().id
47
+ return acc
48
+ }, {})
49
+ }
50
+
51
+ const convertEventKey = (templateKey: string, key: string) => {
52
+ return `${templateKey ? `${templateKey}_` : ''}${_.snakeCase(key).toUpperCase()}`
53
+ }
54
+
55
+ const basicAnimationEvents = ['show', 'standby', 'breatheStart']
56
+
57
+ const compileAnimations = (templateKey: string, animations: { [key: string]: () => Animation }) => {
58
+ return Object.entries(animations).reduce((acc, [key, animation]) => {
59
+ acc[convertEventKey(basicAnimationEvents.includes(key) ? 'BRICK' : templateKey, key)] =
60
+ `ANIMATION#${animation().id}`
61
+ return acc
62
+ }, {})
63
+ }
64
+
65
+ const compileEvents = (
66
+ templateKey: string,
67
+ eventMap: { [key: string]: Array<EventAction> },
68
+ options = { camelCase: false },
69
+ ) => {
70
+ const { camelCase } = options
71
+ return Object.entries(eventMap).reduce((acc, [key, events]) => {
72
+ acc[convertEventKey(templateKey, key)] = events.map((event) => {
73
+ const { handler, action } = event
74
+
75
+ const parameterList: Array<object> = []
76
+ if (Object.hasOwn(action, 'params')) {
77
+ const actionDef = action as ActionWithParams
78
+ ;(actionDef.params || []).forEach(({ input, value, mapping }) => {
79
+ parameterList.push({
80
+ [camelCase ? 'inputToReceiver' : 'input_to_receiver']: input,
81
+ [camelCase ? 'resultFromSender' : 'result_from_sender']: compileProperty(value),
82
+ [camelCase ? 'resultDataMapping' : 'result_data_mapping']: mapping,
83
+ })
84
+ })
85
+ } else if (Object.hasOwn(action, 'dataParams')) {
86
+ const actionDef = action as ActionWithDataParams
87
+ ;(actionDef.dataParams || []).forEach(({ input, value, mapping }) => {
88
+ if (!input) return
89
+ parameterList.push({
90
+ [camelCase ? 'inputToReceiver' : 'input_to_receiver']: input().id,
91
+ [camelCase ? 'resultFromSender' : 'result_from_sender']: compileProperty(value),
92
+ [camelCase ? 'resultDataMapping' : 'result_data_mapping']: mapping,
93
+ })
94
+ })
95
+ }
96
+ let handlerKey
97
+ if (handler === 'system' || typeof handler === 'string') {
98
+ if (handler.startsWith('SUBSPACE_')) handlerKey = handler
99
+ else handlerKey = handler.toUpperCase()
100
+ } else if (typeof handler === 'function') {
101
+ handlerKey = (handler() as Brick | Generator)?.id
102
+ }
103
+ if (!handlerKey) throw new Error(`Invalid handler: ${handler}`)
104
+ return {
105
+ handler: handlerKey,
106
+ action: action.__actionName,
107
+ [camelCase ? 'parameterList' : 'parameter_list']: parameterList,
108
+ [camelCase ? 'waitAsync' : 'wait_async']: event.waitAsync,
109
+ }
110
+ })
111
+ return acc
112
+ }, {})
113
+ }
114
+
115
+ const compileSwitchConds = (templateKey, conds) =>
116
+ (conds || []).map((item: any) => {
117
+ const result: any = { method: item.method }
118
+ if (item.__typename === 'SwitchCondData') {
119
+ const cond = item as SwitchCondData
120
+ result.type = 'property_bank'
121
+ result.key = cond.data().id
122
+ result.value = cond.value
123
+ } else if (item.__typename === 'SwitchCondPropertyBankByItemKey') {
124
+ const cond = item as SwitchCondPropertyBankByItemKey
125
+ result.type = 'property_bank_by_item_key'
126
+ result.key = cond.data().id
127
+ result.value = cond.value
128
+ } else if (item.__typename === 'SwitchCondInnerStateOutlet') {
129
+ result.type = 'inner_state'
130
+ result.key = convertOutletKey(templateKey, item.outlet)
131
+ result.value = item.value
132
+ } else if (item.__typename === 'SwitchCondInnerStateCurrentCanvas') {
133
+ const cond = item as SwitchCondInnerStateCurrentCanvas
134
+ result.type = 'inner_state'
135
+ result.key = 'current_canvas'
136
+ result.value = cond.value().id
137
+ }
138
+ return result
139
+ })
140
+
141
+ const compileApplicationSettings = (settings: Application['settings']) => {
142
+ return {
143
+ internet_reachability_url: settings?.internetReachabilityUrl,
144
+ enable_data_lock: settings?.enableDataLock,
145
+ show_deprecated_features: settings?.showDeprecatedFeatures,
146
+ enable_unstable_bricks: settings?.enableUnstableFeatures,
147
+ runtime_cache_options: settings?.runtimeCacheOptions
148
+ ? {
149
+ disabled: settings.runtimeCacheOptions.disabled,
150
+ behavior: settings.runtimeCacheOptions.behavior,
151
+ retry_attempt_for_failure: settings.runtimeCacheOptions.retryAttemptForFailure,
152
+ }
153
+ : undefined,
154
+ tv_options: settings?.tvOptions
155
+ ? {
156
+ disabled_selectable: settings.tvOptions.disabledSelectable,
157
+ selectable_color: settings.tvOptions.selectableColor,
158
+ selectable_border: settings.tvOptions.selectableBorder,
159
+ }
160
+ : undefined,
161
+ }
162
+ }
163
+
164
+ const animationTypeMap = {
165
+ AnimationTimingConfig: 'timing',
166
+ AnimationSpringConfig: 'spring',
167
+ AnimationDecayConfig: 'decay',
168
+ }
169
+
170
+ export const compile = (app: Application) => {
171
+ const config = {
172
+ title: app.name,
173
+ subspace_map: app.subspaces.reduce((subspaceMap, subspace) => {
174
+ subspaceMap[subspace.id] = {
175
+ title: subspace.title,
176
+ description: subspace.description,
177
+ layout: {
178
+ width: subspace.layout?.width,
179
+ height: subspace.layout?.height,
180
+ resize_mode: subspace.layout?.resizeMode,
181
+ },
182
+ animation_map: subspace.animations.reduce((map, animation) => {
183
+ if (animation.__typename === 'Animation') {
184
+ const animationDef = animation as AnimationDef
185
+ map[animationDef.id] = {
186
+ title: animationDef.title,
187
+ description: animationDef.description,
188
+ animationRunType: animationDef.runType,
189
+ property: animationDef.property,
190
+ type: animationTypeMap[animationDef.config.__type],
191
+ config: compileProperty(_.omit(animationDef.config, '__type')),
192
+ }
193
+ } else if (animation.__typename === 'AnimationCompose') {
194
+ const animationDef = animation as AnimationComposeDef
195
+ map[animationDef.id] = {
196
+ title: animationDef.title,
197
+ description: animationDef.description,
198
+ animationRunType: animationDef.runType,
199
+ compose_type: animationDef.composeType,
200
+ item_list: animationDef.items.map((item) => ({ animation_id: item().id })),
201
+ }
202
+ }
203
+ return map
204
+ }, {}),
205
+ brick_map: subspace.bricks.reduce((map, brick) => {
206
+ const property = compileProperty(brick.property || {})
207
+ if (brick.templateKey === 'BRICK_ITEMS') {
208
+ const brickItems = brick as BrickItems
209
+ const buildList = (itemBrick) => ({
210
+ title: itemBrick.title,
211
+ brickId: itemBrick.brickId,
212
+ brickIdPrefix: itemBrick.brickIdPrefix,
213
+ templateKey: itemBrick.templateKey,
214
+ property: compileProperty(itemBrick.property),
215
+ animation: compileAnimations(itemBrick.templateKey, itemBrick.animation || {}),
216
+ outlet: compileOutlets(itemBrick.templateKey, itemBrick.outlets || {}),
217
+ eventMap: compileEvents(itemBrick.templateKey, itemBrick.eventMap || {}, {
218
+ camelCase: true,
219
+ }),
220
+ stateGroup: itemBrick.stateGroup.map((stateGroup) => ({
221
+ ...stateGroup,
222
+ animation: compileAnimations(itemBrick.templateKey, stateGroup.animation || {}),
223
+ commented: stateGroup.disabled,
224
+ conds: compileSwitchConds(itemBrick.templateKey, stateGroup.conds || []),
225
+ property: compileProperty(stateGroup.property),
226
+ outlet: compileOutlets(itemBrick.templateKey, stateGroup.outlets || {}),
227
+ eventMap: compileEvents(itemBrick.templateKey, stateGroup.eventMap || {}, {
228
+ camelCase: true,
229
+ }),
230
+ })),
231
+ })
232
+ if (Array.isArray(brickItems.brickList)) {
233
+ const brickList = (brickItems.brickList || []).map(buildList)
234
+ property.brickList = brickList
235
+ } else {
236
+ // Not supported Data for brickList
237
+ throw new Error('Not supported Data for brickList directly')
238
+ }
239
+ if (Array.isArray(brickItems.brickDetails)) {
240
+ const brickDetails = (brickItems.brickDetails || []).map(buildList)
241
+ property.brickDetails = brickDetails
242
+ } else {
243
+ // Not supported Data for brickList
244
+ throw new Error('Not supported Data for brickList directly')
245
+ }
246
+ }
247
+ map[brick.id] = {
248
+ template_key: brick.templateKey,
249
+ title: brick.title,
250
+ description: brick.description,
251
+ property,
252
+ animation: compileAnimations(brick.templateKey, brick.animation || {}),
253
+ event_map: compileEvents(brick.templateKey, brick.events || {}),
254
+ outlet: compileOutlets(brick.templateKey, brick.outlets || {}),
255
+ state_group: brick.switches.reduce((acc, switchCase) => {
256
+ acc[switchCase.id] = {
257
+ title: switchCase.title,
258
+ description: switchCase.description,
259
+ break: switchCase.break,
260
+ override: switchCase.override,
261
+ commented: switchCase.disabled,
262
+ conds: compileSwitchConds(brick.templateKey, switchCase.conds || []),
263
+ property: compileProperty(switchCase.property),
264
+ outlet: compileOutlets(brick.templateKey, switchCase.outlets || {}),
265
+ event_map: compileEvents(brick.templateKey, switchCase.events || {}),
266
+ animation: compileAnimations(brick.templateKey, switchCase.animation || {}),
267
+ }
268
+ return acc
269
+ }, {}),
270
+ }
271
+ return map
272
+ }, {}),
273
+ root_canvas_id: subspace.rootCanvas.id,
274
+ canvas_map: subspace.canvases.reduce((map, canvas) => {
275
+ map[canvas.id] = {
276
+ title: canvas.title,
277
+ description: canvas.description,
278
+ property: compileProperty(canvas.property),
279
+ event_map: compileEvents('CANVAS', canvas.events || {}),
280
+ state_group: canvas.switches.reduce((acc, switchCase) => {
281
+ acc[switchCase.id] = {
282
+ title: switchCase.title,
283
+ description: switchCase.description,
284
+ break: switchCase.break,
285
+ override: switchCase.override,
286
+ commented: switchCase.disabled,
287
+ conds: compileSwitchConds('CANVAS', switchCase.conds || []),
288
+ property: compileProperty(switchCase.property),
289
+ event_map: compileEvents('CANVAS', switchCase.events || {}),
290
+ animation: compileAnimations('CANVAS', switchCase.animation || {}),
291
+ }
292
+ return acc
293
+ }, {}),
294
+ item_list: [], // TODO
295
+ }
296
+ return map
297
+ }, {}),
298
+ generator_map: subspace.generators.reduce((map, generator) => {
299
+ map[generator.id] = {
300
+ template_key: generator.templateKey,
301
+ title: generator.title,
302
+ description: generator.description,
303
+ property: compileProperty(generator.property || {}),
304
+ event_map: compileEvents(generator.templateKey, generator.events || {}),
305
+ outlet: compileOutlets(generator.templateKey, generator.outlets || {}),
306
+ state_group: generator.switches.reduce((acc, switchCase) => {
307
+ acc[switchCase.id] = {
308
+ title: switchCase.title,
309
+ description: switchCase.description,
310
+ break: switchCase.break,
311
+ override: switchCase.override,
312
+ commented: switchCase.disabled,
313
+ conds: compileSwitchConds(generator.templateKey, switchCase.conds || []),
314
+ property: compileProperty(switchCase.property),
315
+ outlet: compileOutlets(generator.templateKey, switchCase.outlets || {}),
316
+ event_map: compileEvents(generator.templateKey, switchCase.events || {}),
317
+ animation: compileAnimations(generator.templateKey, switchCase.animation || {}),
318
+ }
319
+ return acc
320
+ }, {}),
321
+ }
322
+ return map
323
+ }, {}),
324
+ property_bank_map: subspace.data.reduce((map, data) => {
325
+ map[data.id] = {
326
+ title: data.title,
327
+ description: data.description,
328
+ persist_data: data.persistData,
329
+ routing: data.routing,
330
+ schema: data.schema,
331
+ type: data.type,
332
+ value: data.value,
333
+ event_map: compileEvents('PROPERTY_BANK', data.events || {}),
334
+ }
335
+ return map
336
+ }, {}),
337
+ property_bank_calc_map: subspace.dataCalculation.reduce((map, dataCalc) => {
338
+ const calc: any = {
339
+ title: dataCalc.title,
340
+ description: dataCalc.description,
341
+ }
342
+ if (dataCalc.__typename === 'DataCalculationMap') {
343
+ calc.type = 'general'
344
+ const mapCalc = dataCalc as DataCalculationMap
345
+
346
+ const generateInputPorts = (inputs, outputs) =>
347
+ inputs.reduce((acc, port) => {
348
+ if (!port.source().id) return acc
349
+
350
+ const firstOutput = outputs[0]
351
+ let disableTriggerCommandIn = firstOutput?.target().id
352
+
353
+ if (!acc[port.key]) acc[port.key] = []
354
+ acc[port.key].push({
355
+ id: port.source().id,
356
+ port: port.sourceKey,
357
+ disable_trigger_command_in: disableTriggerCommandIn,
358
+ })
359
+ return acc
360
+ }, {})
361
+
362
+ const generateOutputPorts = (outputs) =>
363
+ outputs.reduce((acc, port) => {
364
+ if (!port.target().id) return acc
365
+
366
+ if (!acc[port.key]) acc[port.key] = []
367
+ acc[port.key].push({
368
+ id: port.target().id,
369
+ port: port.targetKey,
370
+ })
371
+ return acc
372
+ }, {})
373
+
374
+ const getNodeId = (node) => {
375
+ if (node.__typename === 'DataCalculationData') return node.data.id
376
+ if (node.__typename === 'DataCommand') return node.id
377
+ throw new Error(`Invalid node: ${JSON.stringify(node)}`)
378
+ }
379
+
380
+ calc.map = mapCalc.nodes.reduce((acc, node) => {
381
+ if (node.__typename === 'DataCalculationData') {
382
+ const dataNode = node as DataCalculationData
383
+ acc[getNodeId(dataNode)] = {
384
+ title: dataNode.title,
385
+ description: dataNode.description,
386
+ type: 'data-node',
387
+ properties: {},
388
+ in: generateInputPorts(dataNode.inputs, dataNode.outputs),
389
+ out: generateOutputPorts(dataNode.outputs),
390
+ }
391
+ } else if (node.__typename === 'DataCommand') {
392
+ const commandNode = node as DataCommand
393
+ const commandName = commandNode.__commandName
394
+ const type = commandName.split('_')[0].toLowerCase()
395
+
396
+ const args = commandNode.inputs.filter(
397
+ (input) =>
398
+ typeof input.source !== 'function' ||
399
+ input.source()?.__typename !== 'DataCalculationData' &&
400
+ input.source()?.__typename !== 'DataCommand',
401
+ )
402
+ const inputs = commandNode.inputs.filter(
403
+ (input) =>
404
+ typeof input.source === 'function' &&
405
+ (input.source()?.__typename === 'DataCalculationData' ||
406
+ input.source()?.__typename === 'DataCommand'),
407
+ )
408
+ acc[getNodeId(commandNode)] = {
409
+ title: commandNode.title,
410
+ description: commandNode.description,
411
+ type: `command-node-${type}`,
412
+ properties: {
413
+ command: commandNode.__commandName,
414
+ args: args.reduce((acc, input) => {
415
+ acc[input.key] = input.source
416
+ return acc
417
+ }, {}),
418
+ },
419
+ in: generateInputPorts(inputs, commandNode.outputs),
420
+ out: generateOutputPorts(commandNode.outputs),
421
+ }
422
+ }
423
+ return acc
424
+ }, {})
425
+ calc.editor_info = mapCalc.editorInfo.reduce((acc, editorInfo) => {
426
+ acc[getNodeId(editorInfo.node)] = {
427
+ position: editorInfo.position,
428
+ points: editorInfo.points.reduce((acc, point) => {
429
+ const sourceId = getNodeId(point.source)
430
+ const targetId = getNodeId(point.target)
431
+ const key = `${sourceId}-${point.sourceOutputKey}-${targetId}-${point.targetInputKey}`
432
+ acc[key] = point.positions
433
+ return acc
434
+ }, {}),
435
+ }
436
+ return acc
437
+ }, {})
438
+ } else if (dataCalc.__typename === 'DataCalculationScript') {
439
+ const scriptCalc = dataCalc as DataCalculationScript
440
+ calc.type = 'script'
441
+
442
+ const program = parseAST(scriptCalc.code, { sourceType: 'module', ecmaVersion: 2020 })
443
+ // export function main() { ... }
444
+ const declarationBody = (
445
+ (program.body[0] as ExportNamedDeclaration).declaration as FunctionDeclaration
446
+ )?.body
447
+
448
+ calc.script_config = {
449
+ code: escodegen.generate(declarationBody),
450
+ enable_async: scriptCalc.enableAsync,
451
+ inputs: scriptCalc.inputs.reduce((acc, input) => {
452
+ acc[input.data().id] = input.key
453
+ return acc
454
+ }, {}),
455
+ disabled_triggers: scriptCalc.inputs.reduce((acc, input) => {
456
+ acc[input.data().id] = !input.trigger
457
+ return acc
458
+ }, {}),
459
+ output: scriptCalc.output?.().id,
460
+ outputs: scriptCalc.outputs.reduce((acc, output) => {
461
+ if (!acc[output.key]) acc[output.key] = []
462
+ acc[output.key].push(output.data().id)
463
+ return acc
464
+ }, {}),
465
+ error: scriptCalc.error?.().id,
466
+ }
467
+
468
+ Object.assign(calc, generateCalulationMap(calc.script_config, {
469
+ snapshotMode: process.env.BRICKS_SNAPSHOT_MODE === '1',
470
+ }))
471
+ }
472
+ map[dataCalc.id] = calc
473
+ return map
474
+ }, {}),
475
+ action_map: subspace.actions || {},
476
+ event_map: compileEvents('', subspace.events || {}),
477
+ }
478
+ return subspaceMap
479
+ }, {}),
480
+ root_subspace_id: app.rootSubspace.id,
481
+ fonts: app.fonts,
482
+ ...compileApplicationSettings(app.settings),
483
+ }
484
+ return config
485
+ }