@fugood/bricks-ctor 2.25.0-beta.6 → 2.25.0-beta.8
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/compile/__tests__/index.test.js +115 -0
- package/compile/index.ts +193 -13
- package/package.json +3 -3
- package/skills/bricks-ctor/rules/animation.md +3 -2
- package/tools/preview-main.mjs +18 -5
- package/types/animation.ts +16 -5
- package/types/bricks/Image.ts +12 -0
- package/types/data.ts +1 -1
- package/types/subspace.ts +1 -1
- package/utils/__tests__/id.test.js +58 -0
- package/utils/data.ts +1 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { compile } from '../index'
|
|
2
|
+
|
|
3
|
+
const SUBSPACE_ID = 'SUBSPACE_00000000-0000-0000-0000-000000000001'
|
|
4
|
+
const CANVAS_ID = 'CANVAS_00000000-0000-0000-0000-000000000001'
|
|
5
|
+
const ANIMATION_ID = 'ANIMATION_00000000-0000-0000-0000-000000000001'
|
|
6
|
+
|
|
7
|
+
const makeApp = (animations) => {
|
|
8
|
+
const rootCanvas = {
|
|
9
|
+
__typename: 'Canvas',
|
|
10
|
+
id: CANVAS_ID,
|
|
11
|
+
items: [],
|
|
12
|
+
}
|
|
13
|
+
const rootSubspace = {
|
|
14
|
+
__typename: 'Subspace',
|
|
15
|
+
id: SUBSPACE_ID,
|
|
16
|
+
title: 'Main Subspace',
|
|
17
|
+
layout: {
|
|
18
|
+
width: 96,
|
|
19
|
+
height: 54,
|
|
20
|
+
},
|
|
21
|
+
rootCanvas,
|
|
22
|
+
canvases: [rootCanvas],
|
|
23
|
+
animations,
|
|
24
|
+
bricks: [],
|
|
25
|
+
generators: [],
|
|
26
|
+
data: [],
|
|
27
|
+
dataRouting: [],
|
|
28
|
+
dataCalculation: [],
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
name: 'Compile animation test',
|
|
33
|
+
rootSubspace,
|
|
34
|
+
subspaces: [rootSubspace],
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
describe('compile animations', () => {
|
|
39
|
+
test('normalizes mixed legacy spring config', async () => {
|
|
40
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {})
|
|
41
|
+
try {
|
|
42
|
+
const config = await compile(
|
|
43
|
+
makeApp([
|
|
44
|
+
{
|
|
45
|
+
__typename: 'Animation',
|
|
46
|
+
id: ANIMATION_ID,
|
|
47
|
+
alias: 'btnPressOut',
|
|
48
|
+
title: 'Button Press Out',
|
|
49
|
+
property: 'transform.scale',
|
|
50
|
+
config: {
|
|
51
|
+
__type: 'AnimationSpringConfig',
|
|
52
|
+
toValue: 1,
|
|
53
|
+
friction: 5,
|
|
54
|
+
tension: 200,
|
|
55
|
+
speed: 14,
|
|
56
|
+
bounciness: 8,
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
]),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
expect(config.subspace_map[SUBSPACE_ID].animation_map[ANIMATION_ID].config).toEqual({
|
|
63
|
+
toValue: 1,
|
|
64
|
+
friction: 5,
|
|
65
|
+
tension: 200,
|
|
66
|
+
})
|
|
67
|
+
const warning = warnSpy.mock.calls[0][0]
|
|
68
|
+
expect(warning).toContain('Resolved animation spring config')
|
|
69
|
+
expect(warning).toContain('Button Press Out')
|
|
70
|
+
expect(warning).toContain('btnPressOut')
|
|
71
|
+
expect(warning).toContain('transform.scale')
|
|
72
|
+
expect(warning).toContain('Main Subspace')
|
|
73
|
+
expect(warning).not.toContain('ANIMATION_')
|
|
74
|
+
expect(warning).not.toContain('SUBSPACE_')
|
|
75
|
+
} finally {
|
|
76
|
+
warnSpy.mockRestore()
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('rejects invalid animation property', async () => {
|
|
81
|
+
await expect(
|
|
82
|
+
compile(
|
|
83
|
+
makeApp([
|
|
84
|
+
{
|
|
85
|
+
__typename: 'Animation',
|
|
86
|
+
id: ANIMATION_ID,
|
|
87
|
+
property: 'transform.skewX',
|
|
88
|
+
config: {
|
|
89
|
+
__type: 'AnimationTimingConfig',
|
|
90
|
+
toValue: 1,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
]),
|
|
94
|
+
),
|
|
95
|
+
).rejects.toThrow(/Invalid animation property/)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('rejects invalid animation config type', async () => {
|
|
99
|
+
await expect(
|
|
100
|
+
compile(
|
|
101
|
+
makeApp([
|
|
102
|
+
{
|
|
103
|
+
__typename: 'Animation',
|
|
104
|
+
id: ANIMATION_ID,
|
|
105
|
+
property: 'opacity',
|
|
106
|
+
config: {
|
|
107
|
+
__type: 'AnimationUnknownConfig',
|
|
108
|
+
toValue: 1,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
]),
|
|
112
|
+
),
|
|
113
|
+
).rejects.toThrow(/Invalid animation config type/)
|
|
114
|
+
})
|
|
115
|
+
})
|
package/compile/index.ts
CHANGED
|
@@ -301,6 +301,162 @@ const animationTypeMap = {
|
|
|
301
301
|
AnimationTimingConfig: 'timing',
|
|
302
302
|
AnimationSpringConfig: 'spring',
|
|
303
303
|
AnimationDecayConfig: 'decay',
|
|
304
|
+
} as const
|
|
305
|
+
|
|
306
|
+
type CompiledAnimationType = (typeof animationTypeMap)[keyof typeof animationTypeMap]
|
|
307
|
+
type WarningMetadata = Record<string, unknown>
|
|
308
|
+
|
|
309
|
+
const animationProperties = new Set([
|
|
310
|
+
'transform.translateX',
|
|
311
|
+
'transform.translateY',
|
|
312
|
+
'transform.scale',
|
|
313
|
+
'transform.scaleX',
|
|
314
|
+
'transform.scaleY',
|
|
315
|
+
'transform.rotate',
|
|
316
|
+
'transform.rotateX',
|
|
317
|
+
'transform.rotateY',
|
|
318
|
+
'opacity',
|
|
319
|
+
])
|
|
320
|
+
|
|
321
|
+
const animationComposeTypes = new Set(['parallel', 'sequence'])
|
|
322
|
+
const springConfigFamilies = [
|
|
323
|
+
['stiffness', 'damping', 'mass'],
|
|
324
|
+
['tension', 'friction'],
|
|
325
|
+
['bounciness', 'speed'],
|
|
326
|
+
]
|
|
327
|
+
const springConfigFamilyKeys = new Set(springConfigFamilies.flat())
|
|
328
|
+
|
|
329
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
330
|
+
Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
331
|
+
|
|
332
|
+
const hasDefinedConfigValue = (config: Record<string, unknown>, key: string) =>
|
|
333
|
+
config[key] !== undefined
|
|
334
|
+
|
|
335
|
+
const assertConfigValue = (
|
|
336
|
+
config: Record<string, unknown>,
|
|
337
|
+
key: string,
|
|
338
|
+
errorReference: string,
|
|
339
|
+
) => {
|
|
340
|
+
if (!hasDefinedConfigValue(config, key)) {
|
|
341
|
+
throw new Error(`Invalid animation config ${errorReference}: missing "${key}"`)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const assertAnimationProperty = (property: unknown, errorReference: string) => {
|
|
346
|
+
if (typeof property !== 'string' || !animationProperties.has(property)) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`Invalid animation property${errorReference ? ` ${errorReference}` : ''}: ${String(
|
|
349
|
+
property,
|
|
350
|
+
)}`,
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
return property
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const getAnimationType = (config: unknown, errorReference: string): CompiledAnimationType => {
|
|
357
|
+
if (!isRecord(config)) {
|
|
358
|
+
throw new Error(`Invalid animation config ${errorReference}: config must be an object`)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const animationType = animationTypeMap[config.__type as keyof typeof animationTypeMap]
|
|
362
|
+
if (!animationType) {
|
|
363
|
+
throw new Error(`Invalid animation config type ${errorReference}: ${String(config.__type)}`)
|
|
364
|
+
}
|
|
365
|
+
return animationType
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const assertAnimationComposeType = (composeType: unknown, errorReference: string) => {
|
|
369
|
+
if (typeof composeType !== 'string' || !animationComposeTypes.has(composeType)) {
|
|
370
|
+
throw new Error(`Invalid animation compose type ${errorReference}: ${String(composeType)}`)
|
|
371
|
+
}
|
|
372
|
+
return composeType
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const pickDefinedConfigValues = (config: Record<string, unknown>, keys: string[]) =>
|
|
376
|
+
keys.reduce((acc, key) => {
|
|
377
|
+
if (hasDefinedConfigValue(config, key)) acc[key] = config[key]
|
|
378
|
+
return acc
|
|
379
|
+
}, {})
|
|
380
|
+
|
|
381
|
+
const getDefinedConfigKeys = (config: Record<string, unknown>, keys: string[]) =>
|
|
382
|
+
keys.filter((key) => hasDefinedConfigValue(config, key))
|
|
383
|
+
|
|
384
|
+
const formatWarningMetadata = (metadata: WarningMetadata = {}) =>
|
|
385
|
+
Object.entries(metadata)
|
|
386
|
+
.filter(([, value]) => value !== undefined && value !== null && value !== '')
|
|
387
|
+
.map(([key, value]) => `${key}: ${String(value)}`)
|
|
388
|
+
.join(', ')
|
|
389
|
+
|
|
390
|
+
const formatWarningReference = (metadata?: WarningMetadata) => {
|
|
391
|
+
const metadataText = formatWarningMetadata(metadata)
|
|
392
|
+
return metadataText ? ` [${metadataText}]` : ''
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const normalizeSpringConfig = (
|
|
396
|
+
config: Record<string, unknown>,
|
|
397
|
+
errorReference: string,
|
|
398
|
+
warningMetadata?: WarningMetadata,
|
|
399
|
+
): Record<string, unknown> => {
|
|
400
|
+
assertConfigValue(config, 'toValue', errorReference)
|
|
401
|
+
|
|
402
|
+
const usedFamilies = springConfigFamilies.filter((keys) =>
|
|
403
|
+
keys.some((key) => hasDefinedConfigValue(config, key)),
|
|
404
|
+
)
|
|
405
|
+
if (usedFamilies.length <= 1) return config
|
|
406
|
+
|
|
407
|
+
const configWithoutSpringFamily = Object.entries(config).reduce((acc, [key, value]) => {
|
|
408
|
+
if (!springConfigFamilyKeys.has(key)) acc[key] = value
|
|
409
|
+
return acc
|
|
410
|
+
}, {})
|
|
411
|
+
|
|
412
|
+
// Match runtime normalization: physical spring values are most explicit,
|
|
413
|
+
// otherwise preserve BRICKS' historical tension/friction controls.
|
|
414
|
+
const resolvedFamily =
|
|
415
|
+
usedFamilies.find((keys) => keys.includes('stiffness')) ||
|
|
416
|
+
usedFamilies.find((keys) => keys.includes('tension')) ||
|
|
417
|
+
usedFamilies[0]
|
|
418
|
+
const resolvedFamilyKeys = getDefinedConfigKeys(config, resolvedFamily)
|
|
419
|
+
const droppedFamilyKeys = usedFamilies
|
|
420
|
+
.filter((keys) => keys !== resolvedFamily)
|
|
421
|
+
.flatMap((keys) => getDefinedConfigKeys(config, keys))
|
|
422
|
+
|
|
423
|
+
console.warn(
|
|
424
|
+
`[Warning] Resolved animation spring config${formatWarningReference(
|
|
425
|
+
warningMetadata,
|
|
426
|
+
)}: using ${resolvedFamilyKeys.join('/')}, dropping ${droppedFamilyKeys.join('/')}`,
|
|
427
|
+
)
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
...configWithoutSpringFamily,
|
|
431
|
+
...pickDefinedConfigValues(config, resolvedFamily),
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const compileAnimationConfig = (
|
|
436
|
+
animationType: CompiledAnimationType,
|
|
437
|
+
config: unknown,
|
|
438
|
+
errorReference: string,
|
|
439
|
+
warningMetadata?: WarningMetadata,
|
|
440
|
+
) => {
|
|
441
|
+
if (!isRecord(config)) {
|
|
442
|
+
throw new Error(`Invalid animation config ${errorReference}: config must be an object`)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const compiledConfig = compileProperty(omit(config, '__type'), errorReference)
|
|
446
|
+
|
|
447
|
+
if (!isRecord(compiledConfig)) {
|
|
448
|
+
throw new Error(`Invalid animation config ${errorReference}: config must compile to an object`)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (animationType === 'timing') {
|
|
452
|
+
assertConfigValue(compiledConfig, 'toValue', errorReference)
|
|
453
|
+
} else if (animationType === 'spring') {
|
|
454
|
+
return normalizeSpringConfig(compiledConfig, errorReference, warningMetadata)
|
|
455
|
+
} else if (animationType === 'decay') {
|
|
456
|
+
assertConfigValue(compiledConfig, 'velocity', errorReference)
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return compiledConfig
|
|
304
460
|
}
|
|
305
461
|
|
|
306
462
|
const compileFrame = (frame: Canvas['items'][number]['frame']) => ({
|
|
@@ -629,39 +785,63 @@ export const compile = async (app: Application) => {
|
|
|
629
785
|
`(animation index: ${animationIndex}, subspace: ${subspaceId})`,
|
|
630
786
|
)
|
|
631
787
|
|
|
632
|
-
|
|
788
|
+
const animationTypename = animation.__typename
|
|
789
|
+
if (animationTypename === 'Animation') {
|
|
633
790
|
const animationDef = animation as AnimationDef
|
|
791
|
+
const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
|
|
792
|
+
const animationWarningMetadata = {
|
|
793
|
+
animationIndex,
|
|
794
|
+
animationTitle: animationDef.title,
|
|
795
|
+
animationAlias: animationDef.alias,
|
|
796
|
+
animationProperty: animationDef.property,
|
|
797
|
+
subspaceIndex,
|
|
798
|
+
subspaceTitle: subspace.title,
|
|
799
|
+
}
|
|
800
|
+
const animationType = getAnimationType(animationDef.config, animationErrorReference)
|
|
634
801
|
map[animationId] = {
|
|
635
802
|
alias: animationDef.alias,
|
|
636
803
|
title: animationDef.title,
|
|
637
804
|
description: animationDef.description,
|
|
638
805
|
hide_short_ref: animationDef.hideShortRef,
|
|
639
806
|
animationRunType: animationDef.runType,
|
|
640
|
-
property: animationDef.property,
|
|
641
|
-
type:
|
|
642
|
-
config:
|
|
643
|
-
|
|
644
|
-
|
|
807
|
+
property: assertAnimationProperty(animationDef.property, animationErrorReference),
|
|
808
|
+
type: animationType,
|
|
809
|
+
config: compileAnimationConfig(
|
|
810
|
+
animationType,
|
|
811
|
+
animationDef.config,
|
|
812
|
+
animationErrorReference,
|
|
813
|
+
animationWarningMetadata,
|
|
645
814
|
),
|
|
646
815
|
}
|
|
647
|
-
} else if (
|
|
816
|
+
} else if (animationTypename === 'AnimationCompose') {
|
|
648
817
|
const animationDef = animation as AnimationComposeDef
|
|
818
|
+
const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
|
|
649
819
|
map[animationId] = {
|
|
650
820
|
alias: animationDef.alias,
|
|
651
821
|
title: animationDef.title,
|
|
652
822
|
description: animationDef.description,
|
|
653
823
|
hide_short_ref: animationDef.hideShortRef,
|
|
654
824
|
animationRunType: animationDef.runType,
|
|
655
|
-
compose_type:
|
|
825
|
+
compose_type: assertAnimationComposeType(
|
|
826
|
+
animationDef.composeType,
|
|
827
|
+
animationErrorReference,
|
|
828
|
+
),
|
|
656
829
|
item_list: animationDef.items.map((item, index) => {
|
|
657
830
|
const innerAnimation = item()
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
)
|
|
662
|
-
|
|
831
|
+
const innerAnimationId = assertEntryId(
|
|
832
|
+
innerAnimation?.id,
|
|
833
|
+
'ANIMATION',
|
|
834
|
+
`(animation item index: ${index}, animation: ${animationId}, subspace ${subspaceId})`,
|
|
835
|
+
)
|
|
836
|
+
return { animation_id: innerAnimationId }
|
|
663
837
|
}),
|
|
664
838
|
}
|
|
839
|
+
} else {
|
|
840
|
+
throw new Error(
|
|
841
|
+
`Invalid animation typename (animation: ${animationId}, subspace ${subspaceId}): ${String(
|
|
842
|
+
animationTypename,
|
|
843
|
+
)}`,
|
|
844
|
+
)
|
|
665
845
|
}
|
|
666
846
|
return map
|
|
667
847
|
}, {}),
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fugood/bricks-ctor",
|
|
3
|
-
"version": "2.25.0-beta.
|
|
3
|
+
"version": "2.25.0-beta.8",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"typecheck": "tsc --noEmit",
|
|
7
7
|
"build": "bun scripts/build.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@fugood/bricks-cli": "^2.25.0-beta.
|
|
10
|
+
"@fugood/bricks-cli": "^2.25.0-beta.8",
|
|
11
11
|
"@huggingface/gguf": "^0.3.2",
|
|
12
12
|
"@iarna/toml": "^3.0.0",
|
|
13
13
|
"@modelcontextprotocol/sdk": "^1.15.0",
|
|
@@ -25,5 +25,5 @@
|
|
|
25
25
|
"peerDependencies": {
|
|
26
26
|
"oxfmt": "^0.36.0"
|
|
27
27
|
},
|
|
28
|
-
"gitHead": "
|
|
28
|
+
"gitHead": "9b2439118ca2226b7eeb88f986f9da236c340d83"
|
|
29
29
|
}
|
|
@@ -41,12 +41,13 @@ const bounce: AnimationDef = {
|
|
|
41
41
|
toValue: 1,
|
|
42
42
|
friction: 7,
|
|
43
43
|
tension: 40,
|
|
44
|
-
speed: 12,
|
|
45
|
-
bounciness: 8,
|
|
46
44
|
},
|
|
47
45
|
}
|
|
48
46
|
```
|
|
49
47
|
|
|
48
|
+
Use one spring parameter family per config: `tension/friction`, `speed/bounciness`, or
|
|
49
|
+
`stiffness/damping/mass`.
|
|
50
|
+
|
|
50
51
|
### Decay Animation
|
|
51
52
|
Velocity-based deceleration animation.
|
|
52
53
|
|
package/tools/preview-main.mjs
CHANGED
|
@@ -6,6 +6,12 @@ import { createServer } from 'http'
|
|
|
6
6
|
import { parseArgs } from 'util'
|
|
7
7
|
import * as TOON from '@toon-format/toon'
|
|
8
8
|
|
|
9
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
10
|
+
stream.on('error', (err) => {
|
|
11
|
+
if (err.code !== 'EPIPE') throw err
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
|
|
9
15
|
const { values } = parseArgs({
|
|
10
16
|
args: process.argv,
|
|
11
17
|
options: {
|
|
@@ -224,16 +230,23 @@ app.on('ready', () => {
|
|
|
224
230
|
)
|
|
225
231
|
if (takeScreenshotConfig) {
|
|
226
232
|
const { delay, width, height, path } = takeScreenshotConfig
|
|
227
|
-
setTimeout(() => {
|
|
233
|
+
setTimeout(async () => {
|
|
234
|
+
let screenshotFailed = false
|
|
228
235
|
console.log('Taking screenshot')
|
|
229
|
-
|
|
236
|
+
try {
|
|
237
|
+
const image = await mainWindow.webContents.capturePage()
|
|
230
238
|
console.log('Writing screenshot to', path)
|
|
231
|
-
writeFile(path, image.resize({ width, height }).toJPEG(75))
|
|
239
|
+
await writeFile(path, image.resize({ width, height }).toJPEG(75))
|
|
240
|
+
} catch (err) {
|
|
241
|
+
screenshotFailed = true
|
|
242
|
+
console.error('Screenshot capture/write failed', err)
|
|
243
|
+
} finally {
|
|
232
244
|
if (noKeepOpen) {
|
|
233
245
|
console.log('Closing app')
|
|
234
|
-
app.
|
|
246
|
+
if (screenshotFailed) app.exit(1)
|
|
247
|
+
else app.quit()
|
|
235
248
|
}
|
|
236
|
-
}
|
|
249
|
+
}
|
|
237
250
|
}, delay)
|
|
238
251
|
}
|
|
239
252
|
}
|
package/types/animation.ts
CHANGED
|
@@ -44,10 +44,21 @@ export interface AnimationTimingConfig {
|
|
|
44
44
|
export interface AnimationSpringConfig {
|
|
45
45
|
__type: 'AnimationSpringConfig'
|
|
46
46
|
toValue: number // BRICKS Grid unit
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
// Use one spring parameter family: tension/friction, speed/bounciness,
|
|
48
|
+
// or stiffness/damping/mass.
|
|
49
|
+
friction?: number
|
|
50
|
+
tension?: number
|
|
51
|
+
speed?: number
|
|
52
|
+
bounciness?: number
|
|
53
|
+
stiffness?: number
|
|
54
|
+
damping?: number
|
|
55
|
+
mass?: number
|
|
56
|
+
velocity?: number
|
|
57
|
+
delay?: number
|
|
58
|
+
isInteraction?: boolean
|
|
59
|
+
overshootClamping?: boolean
|
|
60
|
+
restDisplacementThreshold?: number
|
|
61
|
+
restSpeedThreshold?: number
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
export interface AnimationDecayConfig {
|
|
@@ -62,7 +73,7 @@ export interface AnimationDef {
|
|
|
62
73
|
__typename: 'Animation'
|
|
63
74
|
id: string
|
|
64
75
|
alias?: string
|
|
65
|
-
title
|
|
76
|
+
title?: string
|
|
66
77
|
description?: string
|
|
67
78
|
hideShortRef?: boolean
|
|
68
79
|
runType?: 'once' | 'loop'
|
package/types/bricks/Image.ts
CHANGED
|
@@ -25,6 +25,10 @@ Default property:
|
|
|
25
25
|
"templateType": "${}",
|
|
26
26
|
"fadeDuration": 0,
|
|
27
27
|
"blurBackgroundRadius": 8,
|
|
28
|
+
"imageFilterEnabled": false,
|
|
29
|
+
"imageFilterBlur": 0,
|
|
30
|
+
"imageFilterBlurMode": "clamp",
|
|
31
|
+
"imageFilterColorMatrix": [],
|
|
28
32
|
"loadSystemIos": "auto",
|
|
29
33
|
"loadSystemAndroid": "auto"
|
|
30
34
|
}
|
|
@@ -50,6 +54,14 @@ Default property:
|
|
|
50
54
|
enableBlurBackground?: boolean | DataLink
|
|
51
55
|
/* The blur radius of the blur filter added to the image background */
|
|
52
56
|
blurBackgroundRadius?: number | DataLink
|
|
57
|
+
/* Enable Skia image filters. When disabled, Image uses the normal platform image renderer. */
|
|
58
|
+
imageFilterEnabled?: boolean | DataLink
|
|
59
|
+
/* Blur amount for the Skia image filter. */
|
|
60
|
+
imageFilterBlur?: number | DataLink
|
|
61
|
+
/* Tile mode for the Skia blur image filter. */
|
|
62
|
+
imageFilterBlurMode?: 'clamp' | 'decal' | 'repeat' | 'mirror' | DataLink
|
|
63
|
+
/* Optional 4x5 color matrix for the Skia image filter. Provide 20 numbers. */
|
|
64
|
+
imageFilterColorMatrix?: Array<number | DataLink> | DataLink
|
|
53
65
|
/* [iOS] The use priority of image loading system (Auto: sdwebimage, fallback to default if failed) */
|
|
54
66
|
loadSystemIos?: 'auto' | 'sdwebimage' | 'default' | DataLink
|
|
55
67
|
/* [Android] The use priority of image loading system (Auto: glide, fallback to fresco if failed) */
|
package/types/data.ts
CHANGED
package/types/subspace.ts
CHANGED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { makeId } from '../id'
|
|
2
|
+
|
|
3
|
+
const UUID = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
|
|
4
|
+
const UUID_ONLY_RE = new RegExp(`^${UUID}$`)
|
|
5
|
+
|
|
6
|
+
describe('makeId', () => {
|
|
7
|
+
describe('type prefixes', () => {
|
|
8
|
+
const cases = [
|
|
9
|
+
['animation', 'ANIMATION_'],
|
|
10
|
+
['brick', 'BRICK_'],
|
|
11
|
+
['dynamic-brick', 'DYNAMIC_BRICK_'],
|
|
12
|
+
['canvas', 'CANVAS_'],
|
|
13
|
+
['generator', 'GENERATOR_'],
|
|
14
|
+
['data', 'PROPERTY_BANK_DATA_NODE_'],
|
|
15
|
+
['switch', 'BRICK_STATE_GROUP_'],
|
|
16
|
+
['property_bank_command', 'PROPERTY_BANK_COMMAND_NODE_'],
|
|
17
|
+
['property_bank_calc', 'PROPERTY_BANK_COMMAND_MAP_'],
|
|
18
|
+
['automation_map', 'AUTOMATION_MAP_'],
|
|
19
|
+
['test', 'TEST_'],
|
|
20
|
+
['test_case', 'TEST_CASE_'],
|
|
21
|
+
['test_var', 'TEST_VAR_'],
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
test.each(cases)('produces %p prefix', (type, prefix) => {
|
|
25
|
+
expect(makeId(type)).toMatch(new RegExp(`^${prefix}${UUID}$`))
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('returns unique random uuids across calls', () => {
|
|
29
|
+
expect(makeId('brick')).not.toBe(makeId('brick'))
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test('unknown type falls through to an unprefixed uuid', () => {
|
|
33
|
+
expect(makeId('not-a-real-type')).toMatch(UUID_ONLY_RE)
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('subspace', () => {
|
|
38
|
+
test('throws because subspace IDs must be fixed', () => {
|
|
39
|
+
expect(() => makeId('subspace')).toThrow(/subspace is not supported/)
|
|
40
|
+
})
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('snapshotMode', () => {
|
|
44
|
+
test('produces sequential, deterministic uuids', () => {
|
|
45
|
+
const a = makeId('brick', { snapshotMode: true })
|
|
46
|
+
const b = makeId('brick', { snapshotMode: true })
|
|
47
|
+
expect(a).toMatch(/^BRICK_00000000-0000-0000-0000-\d{12}$/)
|
|
48
|
+
expect(b).toMatch(/^BRICK_00000000-0000-0000-0000-\d{12}$/)
|
|
49
|
+
expect(Number(b.slice(-12))).toBe(Number(a.slice(-12)) + 1)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('snapshotMode false falls back to random uuid', () => {
|
|
53
|
+
const id = makeId('canvas', { snapshotMode: false })
|
|
54
|
+
expect(id).toMatch(new RegExp(`^CANVAS_${UUID}$`))
|
|
55
|
+
expect(id).not.toMatch(/CANVAS_00000000-0000-0000-0000-/)
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
})
|