@fugood/bricks-project 2.21.0-beta.14.test5 → 2.21.0-beta.14.test6

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/api/instance.ts CHANGED
@@ -1,19 +1,19 @@
1
- const stage = process.env.BRICKS_STAGE || 'production'
2
-
3
1
  const apiUrlMap = {
4
2
  production: 'https://display.bricks.tools/api/graphql-workspace',
5
3
  beta: 'https://display-beta.bricks.tools/api/graphql-workspace',
6
4
  dev: 'http://localhost:3001/api/graphql-workspace',
7
5
  }
8
6
 
9
- const apiURL = apiUrlMap[stage]
10
- if (!apiURL) throw new Error(`Invalid BRICKS_STAGE: ${stage}`)
11
-
12
7
  const workspaceToken = process.env.BRICKS_WORKSPACE_TOKEN
13
8
 
14
- const doGQL = async (query: string, variables: Record<string, any>) => {
9
+ type Stage = 'production' | 'beta' | 'dev'
10
+
11
+ const doGQL = async (stage: Stage, query: string, variables: Record<string, any>) => {
15
12
  if (!workspaceToken) throw new Error('env BRICKS_WORKSPACE_TOKEN is not set')
16
13
 
14
+ const apiURL = apiUrlMap[stage]
15
+ if (!apiURL) throw new Error(`Invalid stage: ${stage}`)
16
+
17
17
  const data = await fetch(apiURL, {
18
18
  method: 'POST',
19
19
  headers: {
@@ -25,9 +25,47 @@ const doGQL = async (query: string, variables: Record<string, any>) => {
25
25
  return data
26
26
  }
27
27
 
28
- export const deployApp = async (appId: string, config: {}, lastCommitId: string) => {
28
+ export const pullApp = async (stage: Stage, appId: string) => {
29
+ const { data, errors } = await doGQL(
30
+ stage,
31
+ `query BRICKS_PROJECT_application($id: ID!) {
32
+ application(id: $id) {
33
+ _id
34
+ name
35
+ description
36
+ config
37
+ lock {
38
+ enabled
39
+ }
40
+ dev_ref {
41
+ is_dev
42
+ }
43
+ }
44
+ }`,
45
+ { id: appId },
46
+ )
47
+ if (errors) throw new Error(errors[0].message)
48
+
49
+ const app = data.application
50
+ if (!app) throw new Error('App not found')
51
+ if (app.lock.enabled) throw new Error('App is locked')
52
+ if (!app.dev_ref.is_dev)
53
+ throw new Error(
54
+ 'Currently BRICKS Project is experimental, please use the fork version of the app',
55
+ )
56
+ return app
57
+ }
58
+
59
+ type Config = { title?: string }
60
+
61
+ export const deployApp = async (stage: Stage, appId: string, config: Config, lastCommitId: string) => {
62
+ const app = await pullApp(stage, appId)
63
+ if (app.config?.bricks_project_last_commit_id === lastCommitId)
64
+ throw new Error('No changes to deploy')
65
+
29
66
  const { errors } = await doGQL(
30
- `mutation BRICKS_PROJECT_updateApplication($id: ID!, $config: JSON) {
67
+ stage,
68
+ `mutation BRICKS_PROJECT_updateApplication($id: ID!, $config: String) {
31
69
  updateApplication(
32
70
  id: $id,
33
71
  config: $config
@@ -38,19 +76,62 @@ export const deployApp = async (appId: string, config: {}, lastCommitId: string)
38
76
  }`,
39
77
  {
40
78
  id: appId,
41
- config: {
79
+ config: JSON.stringify({
42
80
  ...config,
81
+ title: `${config.title || app.name || 'Untitled'} (${Date.now()})`,
43
82
  bricks_project_last_commit_id: lastCommitId,
44
- },
83
+ }),
45
84
  },
46
85
  )
47
86
  if (errors) throw new Error(errors[0].message)
48
87
  return true
49
88
  }
50
89
 
51
- export const deployModule = async (modId: string, config: {}, lastCommitId: string) => {
90
+ export const pullApplicationProject = async (stage: Stage, appId: string) => {
91
+ const { data, errors } = await doGQL(
92
+ stage,
93
+ `query BRICKS_PROJECT_applicationProject($id: ID!) {
94
+ UNSTABLE_applicationProject(id: $id, buildApplicationOnly: true)
95
+ }`,
96
+ { id: appId },
97
+ )
98
+ if (errors) throw new Error(errors[0].message)
99
+ if (!data.UNSTABLE_applicationProject) throw new Error('Application not found')
100
+ return data.UNSTABLE_applicationProject
101
+ }
102
+
103
+ export const pullModule = async (stage: Stage, modId: string) => {
104
+ const { data, errors } = await doGQL(
105
+ stage,
106
+ `query BRICKS_PROJECT_module($id: ID!) {
107
+ module(id: $id) {
108
+ _id
109
+ name
110
+ description
111
+ is_public
112
+ config
113
+ }
114
+ }`,
115
+ { id: modId },
116
+ )
117
+ if (errors) throw new Error(errors[0].message)
118
+ const mod = data.module
119
+ if (!mod) throw new Error('Module not found')
120
+ if (mod.is_public)
121
+ throw new Error(
122
+ 'Currently BRICKS Project is experimental, public module deployment is temporary not recommended',
123
+ )
124
+ return mod
125
+ }
126
+
127
+ export const deployModule = async (stage: Stage, modId: string, config: Config, lastCommitId: string) => {
128
+ const mod = await pullModule(stage, modId)
129
+ if (mod.config?.bricks_project_last_commit_id === lastCommitId)
130
+ throw new Error('No changes to deploy')
131
+
52
132
  const { errors } = await doGQL(
53
- `mutation BRICKS_PROJECT_updateModule($id: ID!, $config: JSON) {
133
+ stage,
134
+ `mutation BRICKS_PROJECT_updateModule($id: ID!, $config: String) {
54
135
  updateModule(
55
136
  id: $id
56
137
  config: $config
@@ -62,44 +143,26 @@ export const deployModule = async (modId: string, config: {}, lastCommitId: stri
62
143
  }`,
63
144
  {
64
145
  id: modId,
65
- config: {
146
+ config: JSON.stringify({
66
147
  ...config,
148
+ title: `${(config.title || mod.name || 'Untitled')} (${Date.now()})`,
67
149
  bricks_project_last_commit_id: lastCommitId,
68
- },
150
+ }),
69
151
  },
70
152
  )
71
153
  if (errors) throw new Error(errors[0].message)
72
154
  return true
73
155
  }
74
156
 
75
- export const pullApp = async (appId: string) => {
76
- const { data, errors } = await doGQL(
77
- `query BRICKS_PROJECT_application($id: ID!) {
78
- application(id: $id) {
79
- _id
80
- name
81
- description
82
- config
83
- }
84
- }`,
85
- { id: appId },
86
- )
87
- if (errors) throw new Error(errors[0].message)
88
- return data.application
89
- }
90
-
91
- export const pullModule = async (modId: string) => {
157
+ export const pullModuleProject = async (stage: Stage, modId: string) => {
92
158
  const { data, errors } = await doGQL(
93
- `query BRICKS_PROJECT_module($id: ID!) {
94
- module(id: $id) {
95
- _id
96
- name
97
- description
98
- config
99
- }
159
+ stage,
160
+ `query BRICKS_PROJECT_moduleProject($id: ID!) {
161
+ UNSTABLE_moduleProject(id: $id, buildApplicationOnly: true)
100
162
  }`,
101
163
  { id: modId },
102
164
  )
103
165
  if (errors) throw new Error(errors[0].message)
104
- return data.module
166
+ if (!data.UNSTABLE_moduleProject) throw new Error('Module not found')
167
+ return data.UNSTABLE_moduleProject
105
168
  }
package/compile/index.ts CHANGED
@@ -58,10 +58,10 @@ const convertEventKey = (templateKey: string, key: string) => {
58
58
 
59
59
  const basicAnimationEvents = ['show', 'standby', 'breatheStart']
60
60
 
61
- const compileAnimations = (templateKey: string, animations: { [key: string]: () => Animation }) => {
61
+ const compileAnimations = (templateKey: string, animations: { [key: string]: Animation }) => {
62
62
  return Object.entries(animations).reduce((acc, [key, animation]) => {
63
63
  acc[convertEventKey(basicAnimationEvents.includes(key) ? 'BRICK' : templateKey, key)] =
64
- `ANIMATION#${animation().id}`
64
+ `ANIMATION#${animation.id}`
65
65
  return acc
66
66
  }, {})
67
67
  }
@@ -318,7 +318,7 @@ export const compile = (app: Application) => {
318
318
  animation: compileAnimations(brick.templateKey, brick.animation || {}),
319
319
  event_map: compileEvents(brick.templateKey, brick.events || {}),
320
320
  outlet: compileOutlets(brick.templateKey, brick.outlets || {}),
321
- state_group: brick.switches.reduce((acc, switchCase) => {
321
+ state_group: brick.switches?.reduce((acc, switchCase) => {
322
322
  acc[switchCase.id] = {
323
323
  title: switchCase.title,
324
324
  description: switchCase.description,
@@ -343,7 +343,7 @@ export const compile = (app: Application) => {
343
343
  description: canvas.description,
344
344
  property: compileProperty(canvas.property),
345
345
  event_map: compileEvents('CANVAS', canvas.events || {}),
346
- state_group: canvas.switches.reduce((acc, switchCase) => {
346
+ state_group: canvas.switches?.reduce((acc, switchCase) => {
347
347
  acc[switchCase.id] = {
348
348
  title: switchCase.title,
349
349
  description: switchCase.description,
@@ -380,7 +380,7 @@ export const compile = (app: Application) => {
380
380
  property: compileProperty(generator.property || {}),
381
381
  event_map: compileEvents(generator.templateKey, generator.events || {}),
382
382
  outlet: compileOutlets(generator.templateKey, generator.outlets || {}),
383
- state_group: generator.switches.reduce((acc, switchCase) => {
383
+ state_group: generator.switches?.reduce((acc, switchCase) => {
384
384
  acc[switchCase.id] = {
385
385
  title: switchCase.title,
386
386
  description: switchCase.description,
@@ -429,7 +429,7 @@ export const compile = (app: Application) => {
429
429
  calc.type = 'general'
430
430
  const mapCalc = dataCalc as DataCalculationMap
431
431
 
432
- const generateInputPorts = (inputs, outputs) =>
432
+ const generateInputPorts = (inputs) =>
433
433
  inputs.reduce((acc, port) => {
434
434
  if (!acc[port.key]) acc[port.key] = null
435
435
 
@@ -442,18 +442,10 @@ export const compile = (app: Application) => {
442
442
  if (!sourceId) return acc
443
443
  if (!acc[port.key]) acc[port.key] = []
444
444
 
445
- const firstOutput = outputs[0]
446
- let disableTriggerCommandIn
447
- let firstOutputTargetNode = firstOutput?.target()
448
- if (!port.trigger && firstOutputTargetNode?.__typename === 'DataCalculationData')
449
- disableTriggerCommandIn = (firstOutputTargetNode as DataCalculationData).data().id
450
- if (!port.trigger && firstOutputTargetNode?.__typename === 'DataCommand')
451
- disableTriggerCommandIn = firstOutputTargetNode.id
452
-
453
445
  acc[port.key].push({
454
446
  id: sourceId,
455
447
  port: port.sourceKey,
456
- disable_trigger_command_in: disableTriggerCommandIn,
448
+ disable_trigger_command: !port.trigger ? true : undefined,
457
449
  })
458
450
  return acc
459
451
  }, {})
@@ -492,7 +484,7 @@ export const compile = (app: Application) => {
492
484
  description: dataNode.description,
493
485
  type: 'data-node',
494
486
  properties: {},
495
- in: generateInputPorts(dataNode.inputs, dataNode.outputs),
487
+ in: generateInputPorts(dataNode.inputs),
496
488
  out: generateOutputPorts(dataNode.outputs),
497
489
  }
498
490
  } else if (node.__typename === 'DataCommand') {
@@ -523,7 +515,7 @@ export const compile = (app: Application) => {
523
515
  return acc
524
516
  }, {}),
525
517
  },
526
- in: generateInputPorts(inputs, commandNode.outputs),
518
+ in: generateInputPorts(inputs),
527
519
  out: generateOutputPorts(commandNode.outputs),
528
520
  }
529
521
  }
package/compile/util.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { makeId } from '../uuid'
1
+ import { makeId } from '../utils/id'
2
2
 
3
3
  type ScriptConfig = {
4
4
  inputs: Record<string, string>
@@ -87,7 +87,7 @@ export const generateCalulationMap = (config: ScriptConfig, opts?: { snapshotMod
87
87
  {
88
88
  id: key,
89
89
  port: 'value',
90
- disable_trigger_command_in: config.disabled_triggers?.[key] ? sandboxId : undefined,
90
+ disable_trigger_command: config.disabled_triggers?.[key] ? true : undefined,
91
91
  },
92
92
  ],
93
93
  },
package/index.ts CHANGED
@@ -1,10 +1,5 @@
1
1
  export type * from './types'
2
2
 
3
- import type { DataLink, Data } from './types'
4
-
5
- export { makeId } from './uuid'
6
-
7
- export const linkData: ((dataGetter: () => Data) => DataLink) = (dataGetter) => ({
8
- __typename: 'DataLink',
9
- data: dataGetter,
10
- })
3
+ export { makeId } from './utils/id'
4
+ export { generateDataCalculationMapEditorInfo } from './utils/calc'
5
+ export { linkData, useSystemData } from './utils/data'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fugood/bricks-project",
3
- "version": "2.21.0-beta.14.test5",
3
+ "version": "2.21.0-beta.14.test6",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "build": "node scripts/build.js"
@@ -0,0 +1,24 @@
1
+ import { $ } from 'bun'
2
+ import { deployApp, deployModule } from '../api'
3
+
4
+ const cwd = process.cwd()
5
+
6
+ const unstagedChanges = await $`cd ${cwd} && git diff --name-only --diff-filter=ACMR`.text()
7
+ if (unstagedChanges) throw new Error('Unstaged changes found, please commit or stash your changes before deploying')
8
+
9
+ const commitId = await $`cd ${cwd} && git rev-parse HEAD`.text()
10
+ const app = await Bun.file(`${cwd}/application.json`).json()
11
+ const stage = app.stage || 'production'
12
+ const config = await Bun.file(`${cwd}/.bricks/build/application-config.json`).json()
13
+
14
+ // ask for confirmation
15
+ const confirm = prompt('Are you sure you want to deploy? (y/n)')
16
+ if (confirm !== 'y') throw new Error('Deployment cancelled')
17
+
18
+ if (!app.type || app.type === 'application') {
19
+ await deployApp(stage, app._id, config, commitId)
20
+ console.log('App deployed')
21
+ } else if (app.type === 'module') {
22
+ await deployModule(stage, app._id, config, commitId)
23
+ console.log('Module deployed')
24
+ }
@@ -0,0 +1,12 @@
1
+ import { $ } from 'bun'
2
+
3
+ const cwd = process.cwd()
4
+
5
+ const libFiles = ['types', 'utils', 'index.ts']
6
+
7
+ await $`mkdir -p ${cwd}/project`
8
+ for (const file of libFiles) {
9
+ await $`cp -r ${__dirname}/../${file} ${cwd}/project`
10
+ }
11
+
12
+ console.log('Copied files to project/')
@@ -1,15 +1,18 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
1
2
  import { app, BrowserWindow } from 'electron'
2
3
  import { readFile } from 'fs/promises'
3
4
  import { watchFile } from 'fs'
4
5
 
5
- let config = JSON.parse(await readFile(process.cwd() + '/.bricks/build/application-config.json'))
6
+ const cwd = process.cwd()
7
+
8
+ let config = JSON.parse(await readFile(`${cwd}/.bricks/build/application-config.json`))
6
9
 
7
10
  const stage = process.env.BRICKS_STAGE || 'production'
8
11
 
9
12
  const previewUrlMap = {
10
13
  production: 'https://control.bricks.tools/applicationPreview.html',
11
14
  beta: 'https://control-beta.bricks.tools/applicationPreview.html',
12
- dev: 'http://localhost:3006/dev-applicationPreview.html',
15
+ development: 'http://localhost:3006/dev-applicationPreview.html',
13
16
  }
14
17
 
15
18
  const previewUrl = previewUrlMap[stage]
@@ -33,7 +36,7 @@ app.on('ready', () => {
33
36
  mainWindow.webContents.once('dom-ready', sendConfig)
34
37
 
35
38
  watchFile(
36
- process.cwd() + '/.bricks/build/application-config.json',
39
+ `${cwd}/.bricks/build/application-config.json`,
37
40
  {
38
41
  bigint: false,
39
42
  persistent: true,
@@ -41,7 +44,7 @@ app.on('ready', () => {
41
44
  },
42
45
  async () => {
43
46
  console.log('Detected config changed')
44
- config = JSON.parse(await readFile(process.cwd() + '/.bricks/build/application-config.json'))
47
+ config = JSON.parse(await readFile(`${cwd}/.bricks/build/application-config.json`))
45
48
  sendConfig()
46
49
  },
47
50
  )
package/tools/preview.ts CHANGED
@@ -1,20 +1,37 @@
1
- import { $, main } from 'bun'
2
- import { watch, readFile } from 'fs'
1
+ import { $ } from 'bun'
2
+ import { watch } from 'fs'
3
+ import { parseArgs } from 'util'
3
4
  import { debounce } from 'lodash'
4
5
 
5
- const app = await Bun.file(process.cwd() + '/application.json').json()
6
+ const { values } = parseArgs({
7
+ args: Bun.argv,
8
+ options: {
9
+ 'skip-typecheck': {
10
+ type: 'boolean',
11
+ },
12
+ },
13
+ strict: true,
14
+ allowPositionals: true,
15
+ })
16
+
17
+ const useTypecheck = !values['skip-typecheck']
6
18
 
7
- const compile = debounce(async () => {
19
+ const compile = async () => {
20
+ if (useTypecheck) await $`bun typecheck`
8
21
  await $`bun compile`
9
- }, 500)
10
-
11
- watch(
12
- process.cwd() + '/subspaces',
13
- { recursive: true },
14
- async (event, filename) => {
15
- console.log(`Detected ${event} in ${filename}`)
16
- compile()
17
- },
18
- )
22
+ }
23
+
24
+ await compile()
25
+
26
+ const compileDebounced = debounce(compile, 500)
27
+
28
+ const cwd = process.cwd()
29
+
30
+ watch(`${cwd}/subspaces`, { recursive: true }, async (event, filename) => {
31
+ console.log(`Detected ${event} in ${filename}`)
32
+ compileDebounced()
33
+ })
34
+
35
+ const app = await Bun.file(`${cwd}/application.json`).json()
19
36
 
20
37
  await $`BRICKS_STAGE=${app.stage || 'production'} bunx electron ${__dirname}/preview-main.mjs`
package/tools/pull.ts ADDED
@@ -0,0 +1,37 @@
1
+ import { $ } from 'bun'
2
+ import { pullApplicationProject, pullModuleProject } from '../api'
3
+
4
+ const cwd = process.cwd()
5
+
6
+ const unstagedChanges = await $`cd ${cwd} && git diff --name-only --diff-filter=ACMR`.text()
7
+ if (unstagedChanges)
8
+ throw new Error('Unstaged changes found, please commit or stash your changes before deploying')
9
+
10
+ const app = await Bun.file(`${cwd}/application.json`).json()
11
+ const stage = app.stage || 'production'
12
+ const { files, lastCommitId } =
13
+ app.type === 'module'
14
+ ? await pullModuleProject(stage, app._id)
15
+ : await pullApplicationProject(stage, app._id)
16
+
17
+ const branchName = 'BRICKS_PROJECT_try-pull-application'
18
+
19
+ await $`cd ${cwd} && git branch -d ${branchName}`.nothrow()
20
+
21
+ const { exitCode } = await $`cd ${cwd} && git checkout -b ${branchName} ${lastCommitId}`.nothrow()
22
+
23
+ let useMain = false
24
+ if (exitCode !== 0) {
25
+ console.log(`Commit ${lastCommitId} not found, using main`)
26
+ // If last commit hash not found in local, use main
27
+ await $`cd ${cwd} && git checkout -b ${branchName}`
28
+ useMain = true
29
+ }
30
+
31
+ await Promise.all(files.map((file) => Bun.write(`${cwd}/${file.name}`, file.input)))
32
+
33
+ await $`cd ${cwd} && git add .`
34
+ await $`cd ${cwd} && git commit -m 'Apply ${app.name} file changes'`
35
+ if (!useMain) {
36
+ await $`cd ${cwd} && git merge main`
37
+ }
package/types/common.ts CHANGED
@@ -12,7 +12,7 @@ export interface Brick {
12
12
  events: {}
13
13
  outlets?: {}
14
14
  animation?: {}
15
- switches: Array<SwitchDef>
15
+ switches?: Array<SwitchDef>
16
16
  }
17
17
 
18
18
  export enum LocalSyncStrategy {
@@ -30,7 +30,7 @@ export interface Generator {
30
30
  property?: {}
31
31
  events: {}
32
32
  outlets?: {}
33
- switches: Array<SwitchDef>
33
+ switches?: Array<SwitchDef>
34
34
  }
35
35
 
36
36
  export type SubspaceID = string
@@ -103,7 +103,7 @@ export type ApplicationSettings = {
103
103
  borderBottomLeftRadius?: number
104
104
  borderBottomRightRadius?: number
105
105
  }
106
- },
106
+ }
107
107
  }
108
108
 
109
109
  export type Application = {
package/types/switch.ts CHANGED
@@ -36,10 +36,13 @@ export interface SwitchDef {
36
36
  animation?: {}
37
37
  conds?: Array<{
38
38
  method: '==' | '!=' | '>' | '<' | '>=' | '<='
39
- cond: SwitchCondInnerStateCurrentCanvas | SwitchCondData | {
40
- __typename: 'SwitchCondInnerStateOutlet'
41
- outlet: string
42
- value: any
43
- }
39
+ cond:
40
+ | SwitchCondInnerStateCurrentCanvas
41
+ | SwitchCondData
42
+ | {
43
+ __typename: 'SwitchCondInnerStateOutlet'
44
+ outlet: string
45
+ value: any
46
+ }
44
47
  }>
45
48
  }
package/utils/calc.ts ADDED
@@ -0,0 +1,118 @@
1
+ import type { DataCalculationMap, DataCalculationData, DataCommand } from '../types/data-calc'
2
+
3
+ const GRID = {
4
+ WIDTH: 300, // Distance between columns
5
+ HEIGHT: 150, // Distance between rows
6
+ PADDING: 15, // Edge padding
7
+ COLUMNS: 4, // Max columns in grid
8
+ ROWS: 3, // Max rows in grid
9
+ }
10
+
11
+ // TODO: Improve the algorithm to minimize crossing lines
12
+
13
+ // If we are too lazy to describe nodes in the editing interface,
14
+ // we can let it generate it automatically
15
+ export const generateDataCalculationMapEditorInfo = (
16
+ nodes: Array<DataCalculationData | DataCommand>,
17
+ ) => {
18
+ const editorInfo: DataCalculationMap['editorInfo'] = []
19
+
20
+ // Track node relationships
21
+ const inputCounts = new Map<DataCalculationData | DataCommand, number>()
22
+ const outputCounts = new Map<DataCalculationData | DataCommand, number>()
23
+ const connectedTo = new Map<
24
+ DataCalculationData | DataCommand,
25
+ Set<DataCalculationData | DataCommand>
26
+ >()
27
+
28
+ // Analyze node connections
29
+ nodes.forEach((node) => {
30
+ // Count and track inputs
31
+ if ('inputs' in node) {
32
+ const inputs = node.inputs
33
+ .filter((input) => input !== null)
34
+ .map((input) => (Array.isArray(input) ? input.length : 1))
35
+ .reduce((sum, count) => sum + count, 0)
36
+ inputCounts.set(node, inputs)
37
+
38
+ // Track connections
39
+ node.inputs.forEach((input) => {
40
+ if (Array.isArray(input)) {
41
+ input.forEach((conn) => {
42
+ if (!connectedTo.has(node)) {
43
+ connectedTo.set(node, new Set())
44
+ }
45
+ const sourceNode = nodes.find((n) => 'id' in n && n.id === conn.id)
46
+ if (sourceNode) {
47
+ connectedTo.get(node)!.add(sourceNode)
48
+ }
49
+ })
50
+ }
51
+ })
52
+ } else {
53
+ inputCounts.set(node, 0)
54
+ }
55
+
56
+ // Count outputs
57
+ if ('outputs' in node) {
58
+ const outputs = node.outputs
59
+ .filter((output) => output !== null)
60
+ .map((output) => (Array.isArray(output) ? output.length : 1))
61
+ .reduce((sum, count) => sum + count, 0)
62
+ outputCounts.set(node, outputs)
63
+ } else {
64
+ outputCounts.set(node, 0)
65
+ }
66
+ })
67
+
68
+ // Calculate layers
69
+ const layers: Array<Array<DataCalculationData | DataCommand>> = [[], [], [], []]
70
+
71
+ nodes.forEach((node) => {
72
+ const inputs = inputCounts.get(node) || 0
73
+ const outputs = outputCounts.get(node) || 0
74
+ const connections = connectedTo.get(node)?.size || 0
75
+
76
+ if (inputs === 0) {
77
+ // Input nodes (leftmost)
78
+ layers[0].push(node)
79
+ } else if (outputs === 0) {
80
+ // Output nodes (rightmost)
81
+ layers[3].push(node)
82
+ } else if (connections > 1) {
83
+ // Nodes with multiple connections (middle-right)
84
+ layers[2].push(node)
85
+ } else {
86
+ // Processing nodes (middle-left)
87
+ layers[1].push(node)
88
+ }
89
+ })
90
+
91
+ // Position nodes in each layer
92
+ layers.forEach((layerNodes, layerIndex) => {
93
+ // Sort nodes by their connections to try to minimize crossing lines
94
+ layerNodes.sort((a, b) => {
95
+ const aConns = connectedTo.get(a)?.size || 0
96
+ const bConns = connectedTo.get(b)?.size || 0
97
+ return bConns - aConns
98
+ })
99
+
100
+ // Position nodes in the layer
101
+ layerNodes.forEach((node, nodeIndex) => {
102
+ // Distribute nodes evenly within their layer
103
+ const row = nodeIndex % GRID.ROWS
104
+ const offset = Math.floor(nodeIndex / GRID.ROWS) * (GRID.HEIGHT / 2)
105
+
106
+ editorInfo.push({
107
+ node,
108
+ position: {
109
+ x: GRID.PADDING + layerIndex * GRID.WIDTH,
110
+ y: GRID.PADDING + row * GRID.HEIGHT + offset,
111
+ },
112
+ points: [],
113
+ })
114
+ })
115
+ })
116
+
117
+ return editorInfo
118
+ }
package/utils/data.ts ADDED
@@ -0,0 +1,397 @@
1
+ import type { DataLink, Data } from '../types'
2
+
3
+ export const linkData: (dataGetter: () => Data) => DataLink = (dataGetter) => ({
4
+ __typename: 'DataLink',
5
+ data: dataGetter,
6
+ })
7
+
8
+ type SystemDataName =
9
+ | 'isLocalSyncMainDevice'
10
+ | 'currentCanvasId'
11
+ | 'previousCanvasId'
12
+ | 'canvasIdHistory'
13
+ | 'rootSubspaceInfo'
14
+ | 'subspaceInfo'
15
+ | 'uniqueID'
16
+ | 'appVersion'
17
+ | 'lastUpdateTime'
18
+ | 'deviceName'
19
+ | 'deviceType'
20
+ | 'operationMode'
21
+ | 'operationVersion'
22
+ | 'screenWidth'
23
+ | 'screenHeight'
24
+ | 'screenScale'
25
+ | 'screenFontScale'
26
+ | 'orientation'
27
+ | 'touchAvailable'
28
+ | 'soundAvailable'
29
+ | 'cameraAvailable'
30
+ | 'audioInputAvailable'
31
+ | 'networkType'
32
+ | 'networkConnected'
33
+ | 'networkIpAddress'
34
+ | 'networkDetails'
35
+ | 'isInternetReachable'
36
+ | 'isWifiEnabled'
37
+ | 'deviceDisplayName'
38
+ | 'workspaceName'
39
+ | 'applicationName'
40
+ | 'macAddress'
41
+ | 'bindDeviceCode'
42
+ | 'bindDeviceCodeExpire'
43
+ | 'bindDeviceUrl'
44
+ | 'bindDeviceUseApplication'
45
+ | 'isApplicationPreview'
46
+ | 'isSupportAuthInDevice'
47
+ | 'env'
48
+ | 'isViewDebugModeEnabled'
49
+
50
+ type SystemDataInfo = {
51
+ name: SystemDataName
52
+ id: string
53
+ type: 'string' | 'number' | 'bool' | 'array' | 'object' | 'any'
54
+ title: string
55
+ description?: string
56
+ schema?: object
57
+ value?: any
58
+ }
59
+
60
+ export const systemDataList: Array<SystemDataInfo> = [
61
+ {
62
+ name: 'isLocalSyncMainDevice',
63
+ id: 'PROPERTY_BANK_DATA_NODE_325068e3-89b6-4c6a-942b-126d38c84a62',
64
+ title: 'SYSTEM: Is Local Sync Main Device',
65
+ description: 'The current device is main for local sync',
66
+ type: 'bool',
67
+ value: true,
68
+ },
69
+ {
70
+ name: 'currentCanvasId',
71
+ id: 'PROPERTY_BANK_DATA_NODE_2dfeb635-deb4-481a-9d17-ea7b1fe19f74',
72
+ title: 'SYSTEM: Current Canvas ID',
73
+ description: 'ID for known current canvas',
74
+ type: 'string',
75
+ value: '',
76
+ },
77
+ {
78
+ name: 'previousCanvasId',
79
+ id: 'PROPERTY_BANK_DATA_NODE_2971e463-0b0e-45b7-bbfb-a01d8fd49db3',
80
+ title: 'SYSTEM: Previous Canvas ID',
81
+ description: 'ID for known previous canvas',
82
+ type: 'string',
83
+ value: '',
84
+ },
85
+ {
86
+ name: 'canvasIdHistory',
87
+ id: 'PROPERTY_BANK_DATA_NODE_1a086f39-817d-40ed-92f0-5a999535d3dd',
88
+ title: 'SYSTEM: Change Canvas History',
89
+ description: 'Change Canvas ID History (Limit: 10)',
90
+ schema: { type: 'string', allowNewItem: false, allowCustomProperty: false },
91
+ type: 'array',
92
+ value: [],
93
+ },
94
+ {
95
+ name: 'rootSubspaceInfo',
96
+ id: 'PROPERTY_BANK_DATA_NODE_c0817f4a-1ff3-40ed-9cbf-71d8cefd2cd9',
97
+ title: 'SYSTEM: Root Subspace Info',
98
+ description: 'Root Subspace Information',
99
+ type: 'object',
100
+ value: {},
101
+ },
102
+ {
103
+ name: 'subspaceInfo',
104
+ id: 'PROPERTY_BANK_DATA_NODE_2657ec09-7323-46c3-a96e-3d8b5a0f79b3',
105
+ title: 'SYSTEM: Current Subspace Info',
106
+ description: 'Current Subspace Information',
107
+ type: 'object',
108
+ value: {},
109
+ },
110
+ {
111
+ name: 'uniqueID',
112
+ id: 'PROPERTY_BANK_DATA_NODE_6f4a543f-a88a-4a83-bf53-223259263be3',
113
+ title: 'SYSTEM: Unique ID',
114
+ description: 'Unique ID of current device',
115
+ type: 'string',
116
+ value: 'unknown',
117
+ },
118
+ {
119
+ name: 'appVersion',
120
+ id: 'PROPERTY_BANK_DATA_NODE_d614a258-2a45-4f91-9f6b-3936038cb2b1',
121
+ title: 'SYSTEM: App Version',
122
+ description: 'App Version of current installed BRICKS Foundation app',
123
+ type: 'string',
124
+ value: 'unknown',
125
+ },
126
+ {
127
+ name: 'lastUpdateTime',
128
+ id: 'PROPERTY_BANK_DATA_NODE_956f3b3e-cb0b-47ca-8119-65663910d110',
129
+ title: 'SYSTEM: Last Update Time',
130
+ description: 'Last update time of current installed BRICKS Foundation app',
131
+ type: 'string',
132
+ value: '2019-07-09T03:32:27.058Z',
133
+ },
134
+ {
135
+ name: 'deviceName',
136
+ id: 'PROPERTY_BANK_DATA_NODE_7b9d9ad4-e9a2-47cd-91d3-b64187895c16',
137
+ title: 'SYSTEM: Device Name',
138
+ description: 'Name of current device',
139
+ type: 'string',
140
+ value: 'Default Device Name',
141
+ },
142
+ {
143
+ name: 'deviceType',
144
+ id: 'PROPERTY_BANK_DATA_NODE_b0beae12-e45e-4870-ae15-858eeac566fa',
145
+ title: 'SYSTEM: Device Type',
146
+ description: 'Type of current device (Handset, Tv, Tablet)',
147
+ type: 'string',
148
+ value: 'Tv',
149
+ },
150
+ {
151
+ name: 'operationMode',
152
+ id: 'PROPERTY_BANK_DATA_NODE_2e715d3b-92bf-4761-b5c0-ca21f7c1ada8',
153
+ title: 'SYSTEM: Operation Mode',
154
+ description: 'Operation Mode of current device (android, ios, ...)',
155
+ type: 'string',
156
+ value: 'android',
157
+ },
158
+ {
159
+ name: 'operationVersion',
160
+ id: 'PROPERTY_BANK_DATA_NODE_fa886963-0a16-4b5f-b4ea-6c25b049db03',
161
+ title: 'SYSTEM: Operation Version',
162
+ description: 'Operation Version of current device',
163
+ type: 'string',
164
+ value: '',
165
+ },
166
+ {
167
+ name: 'screenWidth',
168
+ id: 'PROPERTY_BANK_DATA_NODE_2942470a-c3bc-413e-8f2c-30a89aded3f8',
169
+ title: 'SYSTEM: Screen Width',
170
+ description: 'Width of current device main screen',
171
+ type: 'number',
172
+ value: 1280,
173
+ },
174
+ {
175
+ name: 'screenHeight',
176
+ id: 'PROPERTY_BANK_DATA_NODE_4f031e27-f67b-40e0-a317-9f6c5215d97d',
177
+ title: 'SYSTEM: Screen Height',
178
+ description: 'Height of current device main screen',
179
+ type: 'number',
180
+ value: 768,
181
+ },
182
+ {
183
+ name: 'screenScale',
184
+ id: 'PROPERTY_BANK_DATA_NODE_c3c9b7aa-1dc1-4be1-8a4a-d9e299b9eeaa',
185
+ title: 'SYSTEM: Screen Scale',
186
+ description: 'Scale of current device main screen',
187
+ type: 'number',
188
+ value: 2,
189
+ },
190
+ {
191
+ name: 'screenFontScale',
192
+ id: 'PROPERTY_BANK_DATA_NODE_0312f380-d525-4938-82d2-5b35ef8ef1e8',
193
+ title: 'SYSTEM: Screen Font Scale',
194
+ description: 'Font scale of current device main screen',
195
+ type: 'number',
196
+ value: 1,
197
+ },
198
+ {
199
+ name: 'orientation',
200
+ id: 'PROPERTY_BANK_DATA_NODE_4f584f51-a250-4f97-a2f8-d5e6a3affb4f',
201
+ title: 'SYSTEM: Orientation',
202
+ description: 'Orientation of current device main screen',
203
+ type: 'string',
204
+ value: 'landspace',
205
+ },
206
+ {
207
+ name: 'touchAvailable',
208
+ id: 'PROPERTY_BANK_DATA_NODE_1fe7555f-df41-43ce-a268-3ea1b5efda6d',
209
+ title: 'SYSTEM: Touch Available',
210
+ description: 'Is touch available of current device main screen?',
211
+ type: 'bool',
212
+ value: true,
213
+ },
214
+ {
215
+ name: 'soundAvailable',
216
+ id: 'PROPERTY_BANK_DATA_NODE_bf839498-f7d6-4212-ac3a-b409e3ad77a5',
217
+ title: 'SYSTEM: Sound Available',
218
+ description: 'Is sound available of current device?',
219
+ type: 'bool',
220
+ value: true,
221
+ },
222
+ {
223
+ name: 'cameraAvailable',
224
+ id: 'PROPERTY_BANK_DATA_NODE_4878f3c2-8bb8-4c94-855c-06bda5a287d5',
225
+ title: 'SYSTEM: Camera Available',
226
+ description: 'Is camera available of current device?',
227
+ type: 'bool',
228
+ value: true,
229
+ },
230
+ {
231
+ name: 'audioInputAvailable',
232
+ id: 'PROPERTY_BANK_DATA_NODE_4854b259-96c1-40d7-b17b-47156b4698dc',
233
+ title: 'SYSTEM: Audio Input Available',
234
+ description: 'Is audio input available of current device?',
235
+ type: 'bool',
236
+ value: false,
237
+ },
238
+ {
239
+ name: 'networkType',
240
+ id: 'PROPERTY_BANK_DATA_NODE_1cbb1756-e53c-45a9-be23-5d84a214e144',
241
+ title: 'SYSTEM: Network Type',
242
+ description: 'Network Type of current device',
243
+ type: 'string',
244
+ value: 'wifi',
245
+ },
246
+ {
247
+ name: 'networkConnected',
248
+ id: 'PROPERTY_BANK_DATA_NODE_687b62fe-ead5-417a-b415-9bd5233784a4',
249
+ title: 'SYSTEM: Network Connected',
250
+ description: 'Is Network connected of current device?',
251
+ type: 'bool',
252
+ value: true,
253
+ },
254
+ {
255
+ name: 'networkIpAddress',
256
+ id: 'PROPERTY_BANK_DATA_NODE_41971ba7-8294-4aa6-a425-ecd39853c3e6',
257
+ title: 'SYSTEM: Network IP Address',
258
+ description: 'Current network IP address',
259
+ type: 'string',
260
+ value: '',
261
+ },
262
+ {
263
+ name: 'networkDetails',
264
+ id: 'PROPERTY_BANK_DATA_NODE_9351fbfc-4ace-477a-b05d-41ac9b05087d',
265
+ title: 'SYSTEM: Network Details',
266
+ description: 'Network details, provide different data depends on Network Type',
267
+ type: 'object',
268
+ value: null,
269
+ },
270
+ {
271
+ name: 'isInternetReachable',
272
+ id: 'PROPERTY_BANK_DATA_NODE_e367ae7e-46ec-46d0-b87d-51e544591ff9',
273
+ title: 'SYSTEM: Internet Reachable',
274
+ description: 'Is Internet reachable?',
275
+ type: 'bool',
276
+ value: true,
277
+ },
278
+ {
279
+ name: 'isWifiEnabled',
280
+ id: 'PROPERTY_BANK_DATA_NODE_be395305-fb85-4d05-8506-22a4ae8c7b9e',
281
+ title: 'SYSTEM: Wifi Enabled',
282
+ description: 'Is WiFi enabled?',
283
+ type: 'bool',
284
+ value: true,
285
+ },
286
+ {
287
+ name: 'deviceDisplayName',
288
+ id: 'PROPERTY_BANK_DATA_NODE_76356a9a-fc46-11ec-9905-db53d6811cb7',
289
+ title: 'SYSTEM: Device Display Name',
290
+ description: 'Current device display name',
291
+ type: 'string',
292
+ value: '',
293
+ },
294
+ {
295
+ name: 'workspaceName',
296
+ id: 'PROPERTY_BANK_DATA_NODE_d857125e-0401-40f1-b768-56190aa8e147',
297
+ title: 'SYSTEM: Workspace Name',
298
+ description: 'Current workspace name',
299
+ type: 'string',
300
+ value: '',
301
+ },
302
+ {
303
+ name: 'applicationName',
304
+ id: 'PROPERTY_BANK_DATA_NODE_df395fd6-a754-4b60-8bac-1360933e6707',
305
+ title: 'SYSTEM: Application Name',
306
+ description: 'Current application name',
307
+ type: 'string',
308
+ value: '',
309
+ },
310
+ {
311
+ name: 'macAddress',
312
+ id: 'PROPERTY_BANK_DATA_NODE_f01fcc78-0723-11ed-ac00-877339de1030',
313
+ title: 'SYSTEM: MAC Address',
314
+ description: 'Network adapter MAC address of current device',
315
+ type: 'string',
316
+ value: '',
317
+ },
318
+ {
319
+ name: 'bindDeviceCode',
320
+ id: 'PROPERTY_BANK_DATA_NODE_1ca90262-3584-11ed-8c9a-174949844872',
321
+ title: 'SYSTEM: Bind Device Code',
322
+ description: 'The passcode to bind the device',
323
+ type: 'string',
324
+ value: '',
325
+ },
326
+ {
327
+ name: 'bindDeviceCodeExpire',
328
+ id: 'PROPERTY_BANK_DATA_NODE_5b52d204-3584-11ed-bb5e-abb4d270daf1',
329
+ title: 'SYSTEM: Bind Device Code Expire Time',
330
+ description: 'The expire time of bind passcode',
331
+ type: 'string',
332
+ value: '',
333
+ },
334
+ {
335
+ name: 'bindDeviceUrl',
336
+ id: 'PROPERTY_BANK_DATA_NODE_833a7db2-3584-11ed-88dd-3b3d3ff58855',
337
+ title: 'SYSTEM: Bind Device URL',
338
+ description: 'The URL to bind the device',
339
+ type: 'string',
340
+ value: '',
341
+ },
342
+ {
343
+ name: 'bindDeviceUseApplication',
344
+ id: 'PROPERTY_BANK_DATA_NODE_631bbce8-9c90-4590-b8ec-8b27f4a0e50b',
345
+ title: 'SYSTEM: Selected Application on Bind Device',
346
+ description: 'Current selected use application info on Bind Device',
347
+ type: 'object',
348
+ value: null,
349
+ },
350
+ {
351
+ name: 'isApplicationPreview',
352
+ id: 'PROPERTY_BANK_DATA_NODE_c673967a-3996-11ed-96d9-efbc3f7eac53',
353
+ title: 'SYSTEM: Is Preview Mode',
354
+ description: 'Is current in preview mode?',
355
+ type: 'bool',
356
+ value: '',
357
+ },
358
+ {
359
+ name: 'isSupportAuthInDevice',
360
+ id: 'PROPERTY_BANK_DATA_NODE_beb2a666-3e18-11ed-bdab-6f0cb082fa67',
361
+ title: 'SYSTEM: Is Support Auth In Device',
362
+ description: 'Is current device support auth in device?',
363
+ type: 'bool',
364
+ value: '',
365
+ },
366
+ {
367
+ name: 'env',
368
+ id: 'PROPERTY_BANK_DATA_NODE_74e7a574-1099-4fe5-b193-8a9ea7d20ed0',
369
+ title: 'SYSTEM: Environment Variables',
370
+ description: 'Environment Variables of the device',
371
+ type: 'object',
372
+ value: {},
373
+ },
374
+ {
375
+ name: 'isViewDebugModeEnabled',
376
+ id: 'PROPERTY_BANK_DATA_NODE_3f61c4f6-1e02-492e-8e4c-a8dc4a5bc101',
377
+ title: 'SYSTEM: Is View Debug mode Enabled',
378
+ description: 'Is View Debug mode enabled?',
379
+ type: 'bool',
380
+ value: false,
381
+ },
382
+ ]
383
+
384
+ export const useSystemData = (name: SystemDataName): Data => {
385
+ const info = systemDataList.find((data) => data.name === name)
386
+ if (!info) throw new Error(`System data '${name}' not found`)
387
+ return {
388
+ __typename: 'Data',
389
+ id: info.id,
390
+ type: info.type,
391
+ title: info.title,
392
+ description: info.description,
393
+ routing: 'read-only',
394
+ schema: info.schema,
395
+ value: info.value,
396
+ }
397
+ }
@@ -1,4 +1,3 @@
1
-
2
1
  import { v4 as uuid } from 'uuid'
3
2
 
4
3
  let count = 0
@@ -8,9 +7,29 @@ const countUUID = () => {
8
7
  return `00000000-0000-0000-0000-${(count++).toString().padStart(12, '0')}`
9
8
  }
10
9
 
11
- export const makeId = (type, opts?: {
12
- snapshotMode?: boolean,
13
- }) => {
10
+ // Make a random id if not want to use fixed id
11
+ export const makeId = (
12
+ type:
13
+ | 'animation'
14
+ | 'brick'
15
+ | 'canvas'
16
+ | 'generator'
17
+ | 'data'
18
+ | 'switch'
19
+ | 'property_bank_command'
20
+ | 'property_bank_calc'
21
+ | 'dynamic-brick'
22
+ | 'test'
23
+ | 'test_case'
24
+ | 'test_var'
25
+ | 'subspace',
26
+ opts?: {
27
+ snapshotMode?: boolean
28
+ },
29
+ ) => {
30
+ if (type === 'subspace') {
31
+ throw new Error('Currently subspace is not supported for ID generation, please use a fixed ID')
32
+ }
14
33
  let prefix = ''
15
34
  switch (type) {
16
35
  case 'animation':
@@ -22,9 +41,6 @@ export const makeId = (type, opts?: {
22
41
  case 'dynamic-brick':
23
42
  prefix = 'DYNAMIC_BRICK_'
24
43
  break
25
- case 'subspace':
26
- prefix = 'SUBSPACE_'
27
- break
28
44
  case 'canvas':
29
45
  prefix = 'CANVAS_'
30
46
  break
@@ -34,6 +50,9 @@ export const makeId = (type, opts?: {
34
50
  case 'data':
35
51
  prefix = 'PROPERTY_BANK_DATA_NODE_'
36
52
  break
53
+ case 'switch':
54
+ prefix = 'BRICK_STATE_GROUP_'
55
+ break
37
56
  case 'property_bank_command':
38
57
  prefix = 'PROPERTY_BANK_COMMAND_NODE_'
39
58
  break
@@ -49,9 +68,6 @@ export const makeId = (type, opts?: {
49
68
  case 'test_var':
50
69
  prefix = 'TEST_VAR_'
51
70
  break
52
- case 'state_group':
53
- prefix = 'BRICK_STATE_GROUP_'
54
- break
55
71
  default:
56
72
  }
57
73
  return `${prefix}${opts?.snapshotMode ? countUUID() : uuid()}`