@reactvision/react-viro 2.57.0 → 2.57.2
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/android/react_viro/react_viro-release.aar +0 -0
- package/android/viro_renderer/viro_renderer-release.aar +0 -0
- package/components/AR/ViroARPlane.tsx +1 -4
- package/components/AR/ViroARPlaneSelector.tsx +10 -5
- package/components/AR/ViroARScene.tsx +2 -4
- package/components/Studio/StudioARScene.tsx +243 -48
- package/components/Studio/StudioSceneNavigator.tsx +15 -0
- package/components/Studio/VRTStudioModule.ts +10 -0
- package/components/Studio/domain/StudioSounds.tsx +46 -0
- package/components/Studio/domain/apiRequestHelpers.ts +612 -0
- package/components/Studio/domain/collisionBindingsRuntime.ts +17 -2
- package/components/Studio/domain/defaultApiRequestExecutor.ts +61 -0
- package/components/Studio/domain/expressionEvaluator.ts +603 -0
- package/components/Studio/domain/sceneNavigationHandler.ts +770 -20
- package/components/Studio/domain/soundManager.ts +141 -0
- package/components/Studio/domain/utils.ts +50 -0
- package/components/Studio/domain/variableStore.ts +74 -0
- package/components/Studio/domain/viroNodeFactory.tsx +175 -44
- package/components/Studio/domain/visibilityStore.ts +66 -0
- package/components/Studio/index.ts +1 -0
- package/components/Studio/types.ts +178 -1
- package/components/Utilities/VRModuleOpenXR.ts +35 -0
- package/components/Utilities/ViroVersion.ts +1 -1
- package/components/ViroAnimatedImage.tsx +1 -0
- package/components/ViroFlexView.tsx +1 -0
- package/components/ViroImage.tsx +1 -0
- package/components/ViroObjectDetector.tsx +13 -5
- package/components/ViroParticleEmitter.tsx +2 -0
- package/components/ViroPolygon.tsx +2 -1
- package/components/ViroQuad.tsx +2 -1
- package/components/ViroSurface.tsx +1 -0
- package/components/ViroVideo.tsx +1 -0
- package/components/ViroXRSceneNavigator.tsx +8 -0
- package/dist/components/AR/ViroARPlane.d.ts +1 -1
- package/dist/components/AR/ViroARPlane.js +1 -5
- package/dist/components/AR/ViroARPlaneSelector.d.ts +1 -1
- package/dist/components/AR/ViroARPlaneSelector.js +10 -5
- package/dist/components/AR/ViroARScene.d.ts +1 -1
- package/dist/components/AR/ViroARScene.js +2 -5
- package/dist/components/Studio/StudioARScene.d.ts +3 -0
- package/dist/components/Studio/StudioARScene.js +142 -15
- package/dist/components/Studio/StudioSceneNavigator.js +14 -0
- package/dist/components/Studio/VRTStudioModule.d.ts +6 -0
- package/dist/components/Studio/VRTStudioModule.js +10 -0
- package/dist/components/Studio/domain/StudioSounds.d.ts +11 -0
- package/dist/components/Studio/domain/StudioSounds.js +69 -0
- package/dist/components/Studio/domain/apiRequestHelpers.d.ts +180 -0
- package/dist/components/Studio/domain/apiRequestHelpers.js +496 -0
- package/dist/components/Studio/domain/collisionBindingsRuntime.d.ts +3 -1
- package/dist/components/Studio/domain/collisionBindingsRuntime.js +4 -3
- package/dist/components/Studio/domain/defaultApiRequestExecutor.d.ts +14 -0
- package/dist/components/Studio/domain/defaultApiRequestExecutor.js +58 -0
- package/dist/components/Studio/domain/expressionEvaluator.d.ts +108 -0
- package/dist/components/Studio/domain/expressionEvaluator.js +520 -0
- package/dist/components/Studio/domain/sceneNavigationHandler.d.ts +35 -8
- package/dist/components/Studio/domain/sceneNavigationHandler.js +564 -22
- package/dist/components/Studio/domain/soundManager.d.ts +52 -0
- package/dist/components/Studio/domain/soundManager.js +118 -0
- package/dist/components/Studio/domain/utils.d.ts +16 -0
- package/dist/components/Studio/domain/utils.js +48 -0
- package/dist/components/Studio/domain/variableStore.d.ts +21 -0
- package/dist/components/Studio/domain/variableStore.js +65 -0
- package/dist/components/Studio/domain/viroNodeFactory.d.ts +4 -9
- package/dist/components/Studio/domain/viroNodeFactory.js +84 -25
- package/dist/components/Studio/domain/visibilityStore.d.ts +33 -0
- package/dist/components/Studio/domain/visibilityStore.js +66 -0
- package/dist/components/Studio/index.d.ts +1 -1
- package/dist/components/Studio/types.d.ts +150 -1
- package/dist/components/Utilities/VRModuleOpenXR.d.ts +19 -0
- package/dist/components/Utilities/VRModuleOpenXR.js +16 -0
- package/dist/components/Utilities/ViroVersion.d.ts +1 -1
- package/dist/components/Utilities/ViroVersion.js +1 -1
- package/dist/components/ViroAnimatedImage.js +1 -0
- package/dist/components/ViroFlexView.js +1 -0
- package/dist/components/ViroImage.js +1 -0
- package/dist/components/ViroObjectDetector.d.ts +13 -5
- package/dist/components/ViroObjectDetector.js +13 -5
- package/dist/components/ViroParticleEmitter.js +2 -0
- package/dist/components/ViroPolygon.js +1 -0
- package/dist/components/ViroQuad.js +1 -0
- package/dist/components/ViroSurface.js +1 -0
- package/dist/components/ViroVideo.js +1 -0
- package/dist/components/ViroXRSceneNavigator.d.ts +8 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -1
- package/dist/plugins/withViroAndroid.js +18 -0
- package/index.ts +4 -3
- package/ios/dist/ViroRenderer/ViroKit.framework/Info.plist +0 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Shaders.dat +1 -1
- package/ios/dist/lib/libViroReact.a +0 -0
- package/package.json +1 -1
|
@@ -51,7 +51,12 @@ const collisionBindingsRuntime_1 = require("./domain/collisionBindingsRuntime");
|
|
|
51
51
|
const collisionPairKey_1 = require("./domain/collisionPairKey");
|
|
52
52
|
const triggerImageRegistry_1 = require("./domain/triggerImageRegistry");
|
|
53
53
|
const viroNodeFactory_1 = require("./domain/viroNodeFactory");
|
|
54
|
+
const defaultApiRequestExecutor_1 = require("./domain/defaultApiRequestExecutor");
|
|
54
55
|
const sceneNavigationHandler_1 = require("./domain/sceneNavigationHandler");
|
|
56
|
+
const variableStore_1 = require("./domain/variableStore");
|
|
57
|
+
const visibilityStore_1 = require("./domain/visibilityStore");
|
|
58
|
+
const soundManager_1 = require("./domain/soundManager");
|
|
59
|
+
const StudioSounds_1 = require("./domain/StudioSounds");
|
|
55
60
|
const studioMaterials_1 = require("./domain/studioMaterials");
|
|
56
61
|
const useStudioShaderTimeUniforms_1 = require("./domain/useStudioShaderTimeUniforms");
|
|
57
62
|
const useStudioShaderViewportUniforms_1 = require("./domain/useStudioShaderViewportUniforms");
|
|
@@ -70,8 +75,82 @@ const StudioARScene = (props) => {
|
|
|
70
75
|
};
|
|
71
76
|
exports.StudioARScene = StudioARScene;
|
|
72
77
|
const StudioARSceneInner = (props) => {
|
|
73
|
-
const { sceneNavigator, sceneData, onReady, onSceneChange } = props;
|
|
78
|
+
const { sceneNavigator, sceneData, onReady, onSceneChange, variableStore } = props;
|
|
74
79
|
const { scene, assets, animations, collision_bindings, functions } = sceneData;
|
|
80
|
+
// ─── Sequence scheduler ───────────────────────────────────────────────────
|
|
81
|
+
// One per scene. Drives WAIT steps; cancelled on unmount and on navigation so
|
|
82
|
+
// a pending WAIT never fires into a torn-down or replaced scene.
|
|
83
|
+
const schedulerRef = (0, react_1.useRef)(null);
|
|
84
|
+
if (schedulerRef.current === null) {
|
|
85
|
+
schedulerRef.current = new sceneNavigationHandler_1.SequenceScheduler();
|
|
86
|
+
}
|
|
87
|
+
(0, react_1.useEffect)(() => {
|
|
88
|
+
return () => {
|
|
89
|
+
schedulerRef.current?.dispose();
|
|
90
|
+
schedulerRef.current = null;
|
|
91
|
+
// dispose() bumps the scheduler generation first; reset() then clears any
|
|
92
|
+
// pending sound backstop timers and fires their callbacks, which now
|
|
93
|
+
// no-op via the generation guard so unmount can't advance a waited step.
|
|
94
|
+
soundManagerRef.current?.reset();
|
|
95
|
+
};
|
|
96
|
+
}, []);
|
|
97
|
+
// ─── Variable store ───────────────────────────────────────────────────────
|
|
98
|
+
// Normally passed down by the navigator (session-scoped); hosts mounting this
|
|
99
|
+
// scene directly get a scene-local fallback. Seeding happens here, at instance
|
|
100
|
+
// init, so values exist before any effect dispatches on_load. seed() is
|
|
101
|
+
// initialize-if-absent, hence idempotent and strict-mode safe.
|
|
102
|
+
const variableStoreRef = (0, react_1.useRef)(null);
|
|
103
|
+
if (variableStoreRef.current === null) {
|
|
104
|
+
variableStoreRef.current = variableStore ?? new variableStore_1.StudioVariableStore();
|
|
105
|
+
variableStoreRef.current.seed(sceneData.variables ?? []);
|
|
106
|
+
}
|
|
107
|
+
// ─── Visibility store ─────────────────────────────────────────────────────
|
|
108
|
+
// Scene-scoped (asset placements are per-scene), keyed by asset id. Seeded
|
|
109
|
+
// from each asset's author-time hidden_on_load default; Set Visibility
|
|
110
|
+
// actions flip it at runtime. Re-seeded on scene change so a persisted
|
|
111
|
+
// instance doesn't carry stale visibility across a navigation.
|
|
112
|
+
const visibilityStoreRef = (0, react_1.useRef)(null);
|
|
113
|
+
if (visibilityStoreRef.current === null) {
|
|
114
|
+
visibilityStoreRef.current = new visibilityStore_1.StudioVisibilityStore();
|
|
115
|
+
visibilityStoreRef.current.seed(assets);
|
|
116
|
+
}
|
|
117
|
+
(0, react_1.useEffect)(() => {
|
|
118
|
+
visibilityStoreRef.current?.reseed(assets);
|
|
119
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
120
|
+
}, [scene.id]);
|
|
121
|
+
// ─── Sound manager ────────────────────────────────────────────────────────
|
|
122
|
+
// Per-scene. PLAY/STOP scene-function actions drive it; <StudioSounds> renders
|
|
123
|
+
// the active list. Reset on scene change so sounds don't leak across a
|
|
124
|
+
// navigation (sounds, unlike variables, are not session-scoped).
|
|
125
|
+
const soundManagerRef = (0, react_1.useRef)(null);
|
|
126
|
+
if (soundManagerRef.current === null) {
|
|
127
|
+
soundManagerRef.current = new soundManager_1.StudioSoundManager();
|
|
128
|
+
}
|
|
129
|
+
(0, react_1.useEffect)(() => {
|
|
130
|
+
soundManagerRef.current?.reset();
|
|
131
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
132
|
+
}, [scene.id]);
|
|
133
|
+
// Position for a spatial PLAY: look up the placed target asset (matches the
|
|
134
|
+
// node factory's position derivation, position_z defaulting to -2).
|
|
135
|
+
const getAssetPosition = (0, react_1.useCallback)((assetId) => {
|
|
136
|
+
const a = assets.find((x) => x.id === assetId);
|
|
137
|
+
if (!a)
|
|
138
|
+
return undefined;
|
|
139
|
+
return [a.position_x ?? 0, a.position_y ?? 0, a.position_z ?? -2];
|
|
140
|
+
}, [assets]);
|
|
141
|
+
const runtimeCtx = (0, react_1.useMemo)(() => ({
|
|
142
|
+
scheduler: schedulerRef.current,
|
|
143
|
+
variableStore: variableStoreRef.current,
|
|
144
|
+
apiRequestExecutor: defaultApiRequestExecutor_1.defaultApiRequestExecutor,
|
|
145
|
+
visibilityStore: visibilityStoreRef.current,
|
|
146
|
+
soundManager: soundManagerRef.current,
|
|
147
|
+
getAssetPosition,
|
|
148
|
+
}), [getAssetPosition]);
|
|
149
|
+
// Cancel this scene's pending WAITs before handing off to the next scene.
|
|
150
|
+
const handleSceneChange = (0, react_1.useCallback)((sceneId, sceneName) => {
|
|
151
|
+
schedulerRef.current?.cancelAll();
|
|
152
|
+
onSceneChange?.(sceneId, sceneName);
|
|
153
|
+
}, [onSceneChange]);
|
|
75
154
|
// ─── Material registration ────────────────────────────────────────────────
|
|
76
155
|
const materialsRegisteredRef = (0, react_1.useRef)(false);
|
|
77
156
|
if (!materialsRegisteredRef.current) {
|
|
@@ -103,7 +182,10 @@ const StudioARSceneInner = (props) => {
|
|
|
103
182
|
const triggerAnimation = (0, react_1.useCallback)((targetAssetId, animationKey) => {
|
|
104
183
|
// Viro's animation prop is edge-triggered on false→true. Force false first,
|
|
105
184
|
// then flip to true on the next frame so a re-trigger of the same key fires.
|
|
106
|
-
setAnimOverrides((prev) => ({
|
|
185
|
+
setAnimOverrides((prev) => ({
|
|
186
|
+
...prev,
|
|
187
|
+
[targetAssetId]: { key: animationKey, run: false },
|
|
188
|
+
}));
|
|
107
189
|
const handle = requestAnimationFrame(() => {
|
|
108
190
|
triggerHandlesRef.current.delete(handle);
|
|
109
191
|
setAnimOverrides((prev) => {
|
|
@@ -148,21 +230,29 @@ const StudioARSceneInner = (props) => {
|
|
|
148
230
|
interruptible: activeAnim.interruptible,
|
|
149
231
|
delay: activeAnim.delay_ms ?? 0,
|
|
150
232
|
onStart: activeAnim.on_start_function
|
|
151
|
-
? () => (0, sceneNavigationHandler_1.executeOnLoadFunction)(activeAnim.on_start_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key))
|
|
233
|
+
? () => (0, sceneNavigationHandler_1.executeOnLoadFunction)(activeAnim.on_start_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key), handleSceneChange, runtimeCtx)
|
|
152
234
|
: undefined,
|
|
153
235
|
onFinish: activeAnim.on_finish_function
|
|
154
|
-
? () => (0, sceneNavigationHandler_1.executeOnLoadFunction)(activeAnim.on_finish_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key))
|
|
236
|
+
? () => (0, sceneNavigationHandler_1.executeOnLoadFunction)(activeAnim.on_finish_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key), handleSceneChange, runtimeCtx)
|
|
155
237
|
: undefined,
|
|
156
238
|
};
|
|
157
239
|
}
|
|
158
240
|
return states;
|
|
159
|
-
}, [
|
|
241
|
+
}, [
|
|
242
|
+
animations,
|
|
243
|
+
animOverrides,
|
|
244
|
+
loadedAssetIds,
|
|
245
|
+
functions,
|
|
246
|
+
sceneNavigator,
|
|
247
|
+
handleSceneChange,
|
|
248
|
+
runtimeCtx,
|
|
249
|
+
]);
|
|
160
250
|
// ─── on_load_function ─────────────────────────────────────────────────────
|
|
161
251
|
const onLoadExecutedRef = (0, react_1.useRef)(false);
|
|
162
252
|
(0, react_1.useEffect)(() => {
|
|
163
253
|
if (scene.on_load_function && !onLoadExecutedRef.current) {
|
|
164
254
|
onLoadExecutedRef.current = true;
|
|
165
|
-
(0, sceneNavigationHandler_1.executeOnLoadFunction)(scene.on_load_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key),
|
|
255
|
+
(0, sceneNavigationHandler_1.executeOnLoadFunction)(scene.on_load_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key), handleSceneChange, runtimeCtx);
|
|
166
256
|
}
|
|
167
257
|
}, [scene.id]);
|
|
168
258
|
// ─── Collision bindings ───────────────────────────────────────────────────
|
|
@@ -188,8 +278,15 @@ const StudioARSceneInner = (props) => {
|
|
|
188
278
|
const getCollisionHandler = (0, react_1.useCallback)((placementId) => {
|
|
189
279
|
if (!collisionAssetIds.has(placementId))
|
|
190
280
|
return undefined;
|
|
191
|
-
return (0, collisionBindingsRuntime_1.createPlacementCollisionHandler)(placementId, bindingsByPairKey, sceneNavigator, animations, collisionCooldownRef, (id, key) => triggerAnimationRef.current(id, key),
|
|
192
|
-
}, [
|
|
281
|
+
return (0, collisionBindingsRuntime_1.createPlacementCollisionHandler)(placementId, bindingsByPairKey, sceneNavigator, animations, collisionCooldownRef, (id, key) => triggerAnimationRef.current(id, key), handleSceneChange, runtimeCtx);
|
|
282
|
+
}, [
|
|
283
|
+
bindingsByPairKey,
|
|
284
|
+
collisionAssetIds,
|
|
285
|
+
sceneNavigator,
|
|
286
|
+
animations,
|
|
287
|
+
handleSceneChange,
|
|
288
|
+
runtimeCtx,
|
|
289
|
+
]);
|
|
193
290
|
// ─── Trigger image targets ────────────────────────────────────────────────
|
|
194
291
|
const { planeAssets, imageTriggeredAssets } = (0, react_1.useMemo)(() => {
|
|
195
292
|
const plane = assets.filter((a) => !a.trigger_image_url);
|
|
@@ -221,7 +318,9 @@ const StudioARSceneInner = (props) => {
|
|
|
221
318
|
};
|
|
222
319
|
}, [imageTriggeredAssets]);
|
|
223
320
|
// ─── Ready callback ───────────────────────────────────────────────────────
|
|
224
|
-
(0, react_1.useEffect)(() => {
|
|
321
|
+
(0, react_1.useEffect)(() => {
|
|
322
|
+
onReady?.();
|
|
323
|
+
}, []);
|
|
225
324
|
// ─── Render helpers ───────────────────────────────────────────────────────
|
|
226
325
|
const maxModels = react_native_1.Platform.OS === "android" ? ANDROID_MAX_3D_MODELS : IOS_MAX_3D_MODELS;
|
|
227
326
|
const renderedPlaneAssets = (0, react_1.useMemo)(() => {
|
|
@@ -235,10 +334,20 @@ const StudioARSceneInner = (props) => {
|
|
|
235
334
|
return null;
|
|
236
335
|
}
|
|
237
336
|
}
|
|
238
|
-
return (0, viroNodeFactory_1.createNode)(asset, sceneNavigator, animations, scene, (id, key) => triggerAnimationRef.current(id, key), animationStates, handleAssetLoaded, getCollisionHandler(asset.id),
|
|
337
|
+
return (0, viroNodeFactory_1.createNode)(asset, sceneNavigator, animations, scene, (id, key) => triggerAnimationRef.current(id, key), animationStates, handleAssetLoaded, getCollisionHandler(asset.id), handleSceneChange, runtimeCtx);
|
|
239
338
|
})
|
|
240
339
|
.filter(Boolean);
|
|
241
|
-
}, [
|
|
340
|
+
}, [
|
|
341
|
+
planeAssets,
|
|
342
|
+
sceneNavigator,
|
|
343
|
+
animations,
|
|
344
|
+
animationStates,
|
|
345
|
+
handleAssetLoaded,
|
|
346
|
+
getCollisionHandler,
|
|
347
|
+
maxModels,
|
|
348
|
+
handleSceneChange,
|
|
349
|
+
runtimeCtx,
|
|
350
|
+
]);
|
|
242
351
|
const renderedImageTriggeredAssets = (0, react_1.useMemo)(() => {
|
|
243
352
|
if (ViroPlatform_1.isQuest)
|
|
244
353
|
return [];
|
|
@@ -247,7 +356,7 @@ const StudioARSceneInner = (props) => {
|
|
|
247
356
|
const targetName = urlToTargetName.get(asset.trigger_image_url);
|
|
248
357
|
if (!targetName)
|
|
249
358
|
return null;
|
|
250
|
-
const node = (0, viroNodeFactory_1.createNode)(asset, sceneNavigator, animations, scene, (id, key) => triggerAnimationRef.current(id, key), animationStates, handleAssetLoaded, getCollisionHandler(asset.id),
|
|
359
|
+
const node = (0, viroNodeFactory_1.createNode)(asset, sceneNavigator, animations, scene, (id, key) => triggerAnimationRef.current(id, key), animationStates, handleAssetLoaded, getCollisionHandler(asset.id), handleSceneChange, runtimeCtx);
|
|
251
360
|
if (!node)
|
|
252
361
|
return null;
|
|
253
362
|
return (<ViroARImageMarker_1.ViroARImageMarker key={asset.id} target={targetName}>
|
|
@@ -255,7 +364,17 @@ const StudioARSceneInner = (props) => {
|
|
|
255
364
|
</ViroARImageMarker_1.ViroARImageMarker>);
|
|
256
365
|
})
|
|
257
366
|
.filter(Boolean);
|
|
258
|
-
}, [
|
|
367
|
+
}, [
|
|
368
|
+
urlToTargetName,
|
|
369
|
+
imageTriggeredAssets,
|
|
370
|
+
sceneNavigator,
|
|
371
|
+
animations,
|
|
372
|
+
animationStates,
|
|
373
|
+
handleAssetLoaded,
|
|
374
|
+
getCollisionHandler,
|
|
375
|
+
handleSceneChange,
|
|
376
|
+
runtimeCtx,
|
|
377
|
+
]);
|
|
259
378
|
// ─── Plane detection (AR only) ────────────────────────────────────────────
|
|
260
379
|
const planeDetectionMode = (scene.plane_detection ?? "NONE").toUpperCase();
|
|
261
380
|
const planeAlignment = (scene.plane_direction ?? "Horizontal");
|
|
@@ -283,14 +402,22 @@ const StudioARSceneInner = (props) => {
|
|
|
283
402
|
const physicsWorld = physicsWorldConfig?.enabled
|
|
284
403
|
? (0, physicsConfig_1.buildViroPhysicsWorld)(physicsWorldConfig)
|
|
285
404
|
: undefined;
|
|
286
|
-
const physicsProps = physicsWorld
|
|
405
|
+
const physicsProps = physicsWorld
|
|
406
|
+
? { physicsWorld: physicsWorld }
|
|
407
|
+
: {};
|
|
287
408
|
// ─── Render ───────────────────────────────────────────────────────────────
|
|
288
409
|
const children = (<>
|
|
289
410
|
{ViroPlatform_1.isQuest && <ViroController_1.ViroController controllerVisibility reticleVisibility/>}
|
|
290
411
|
<ViroAmbientLight_1.ViroAmbientLight color="#ffffff" intensity={1000}/>
|
|
291
412
|
{renderAssets()}
|
|
292
413
|
{renderedImageTriggeredAssets}
|
|
293
|
-
|
|
414
|
+
<StudioSounds_1.StudioSounds manager={soundManagerRef.current}/>
|
|
415
|
+
{assets.length === 0 && (<ViroText_1.ViroText text="No assets to display" position={[0, 0, -2]} style={{
|
|
416
|
+
fontFamily: "Arial",
|
|
417
|
+
fontSize: 16,
|
|
418
|
+
color: "#CCCCCC",
|
|
419
|
+
textAlign: "center",
|
|
420
|
+
}}/>)}
|
|
294
421
|
</>);
|
|
295
422
|
if (ViroPlatform_1.isQuest) {
|
|
296
423
|
return <ViroScene_1.ViroScene {...physicsProps}>{children}</ViroScene_1.ViroScene>;
|
|
@@ -43,6 +43,7 @@ const ViroXRSceneNavigator_1 = require("../ViroXRSceneNavigator");
|
|
|
43
43
|
const ViroPlatform_1 = require("../Utilities/ViroPlatform");
|
|
44
44
|
const animationRegistry_1 = require("./domain/animationRegistry");
|
|
45
45
|
const studioMaterials_1 = require("./domain/studioMaterials");
|
|
46
|
+
const variableStore_1 = require("./domain/variableStore");
|
|
46
47
|
const StudioARScene_1 = require("./StudioARScene");
|
|
47
48
|
const VRTStudioModule_1 = require("./VRTStudioModule");
|
|
48
49
|
function LoadingARScene() { return <ViroARScene_1.ViroARScene />; }
|
|
@@ -72,6 +73,18 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
72
73
|
function StudioSceneNavigator({ sceneId, worldAlignment = "Gravity", autofocus = true, style, onSceneReady, onError, onSceneChange, onExitViro, }) {
|
|
73
74
|
const navigatorRef = (0, react_1.useRef)(null);
|
|
74
75
|
const loadedSceneIdRef = (0, react_1.useRef)(null);
|
|
76
|
+
// Session-scoped variable store: outlives every scene push, resets when the
|
|
77
|
+
// navigator (= the AR/VR session) unmounts.
|
|
78
|
+
const variableStoreRef = (0, react_1.useRef)(null);
|
|
79
|
+
if (variableStoreRef.current === null) {
|
|
80
|
+
variableStoreRef.current = new variableStore_1.StudioVariableStore();
|
|
81
|
+
}
|
|
82
|
+
(0, react_1.useEffect)(() => {
|
|
83
|
+
return () => {
|
|
84
|
+
variableStoreRef.current?.reset();
|
|
85
|
+
variableStoreRef.current = null;
|
|
86
|
+
};
|
|
87
|
+
}, []);
|
|
75
88
|
const onSceneReadyRef = (0, react_1.useRef)(onSceneReady);
|
|
76
89
|
const onErrorRef = (0, react_1.useRef)(onError);
|
|
77
90
|
const onSceneChangeRef = (0, react_1.useRef)(onSceneChange);
|
|
@@ -137,6 +150,7 @@ function StudioSceneNavigator({ sceneId, worldAlignment = "Gravity", autofocus =
|
|
|
137
150
|
sceneData,
|
|
138
151
|
onReady: onSceneReadyRef.current,
|
|
139
152
|
onSceneChange: onSceneChangeRef.current,
|
|
153
|
+
variableStore: variableStoreRef.current,
|
|
140
154
|
},
|
|
141
155
|
};
|
|
142
156
|
if (ViroPlatform_1.isQuest) {
|
|
@@ -12,4 +12,10 @@ export declare const VRTStudioModule: {
|
|
|
12
12
|
rvGetProject: () => Promise<StudioModuleResult>;
|
|
13
13
|
/** Returns the configured project ID, or null if not set. */
|
|
14
14
|
rvGetProjectId: () => Promise<string | null>;
|
|
15
|
+
/**
|
|
16
|
+
* POSTs a pre-serialised scene-api-request body ({function_id, variables})
|
|
17
|
+
* to the egress proxy, authenticated with the app's RVApiKey. The resolved
|
|
18
|
+
* `data` is the proxy's outcome envelope JSON.
|
|
19
|
+
*/
|
|
20
|
+
rvStudioApiRequest: (bodyJson: string) => Promise<StudioModuleResult>;
|
|
15
21
|
};
|
|
@@ -28,4 +28,14 @@ exports.VRTStudioModule = {
|
|
|
28
28
|
return Promise.resolve(null);
|
|
29
29
|
return native.rvGetProjectId();
|
|
30
30
|
},
|
|
31
|
+
/**
|
|
32
|
+
* POSTs a pre-serialised scene-api-request body ({function_id, variables})
|
|
33
|
+
* to the egress proxy, authenticated with the app's RVApiKey. The resolved
|
|
34
|
+
* `data` is the proxy's outcome envelope JSON.
|
|
35
|
+
*/
|
|
36
|
+
rvStudioApiRequest: (bodyJson) => {
|
|
37
|
+
if (!native)
|
|
38
|
+
return Promise.resolve(NOT_AVAILABLE);
|
|
39
|
+
return native.rvStudioApiRequest(bodyJson);
|
|
40
|
+
},
|
|
31
41
|
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { StudioSoundManager } from "./soundManager";
|
|
3
|
+
/**
|
|
4
|
+
* Renders every actively-playing sound from the manager, re-painting the whole
|
|
5
|
+
* list on any change (like the reactive variable-text nodes). Non-looping sounds
|
|
6
|
+
* remove themselves on finish; any sound removes itself on error so a clip that
|
|
7
|
+
* fails to load releases a waiting step instead of stalling the sequence.
|
|
8
|
+
*/
|
|
9
|
+
export declare const StudioSounds: React.FC<{
|
|
10
|
+
manager: StudioSoundManager;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.StudioSounds = void 0;
|
|
37
|
+
const React = __importStar(require("react"));
|
|
38
|
+
const ViroSound_1 = require("../../ViroSound");
|
|
39
|
+
const ViroSpatialSound_1 = require("../../ViroSpatialSound");
|
|
40
|
+
/**
|
|
41
|
+
* Renders every actively-playing sound from the manager, re-painting the whole
|
|
42
|
+
* list on any change (like the reactive variable-text nodes). Non-looping sounds
|
|
43
|
+
* remove themselves on finish; any sound removes itself on error so a clip that
|
|
44
|
+
* fails to load releases a waiting step instead of stalling the sequence.
|
|
45
|
+
*/
|
|
46
|
+
const StudioSounds = ({ manager, }) => {
|
|
47
|
+
const [, force] = React.useReducer((n) => n + 1, 0);
|
|
48
|
+
React.useEffect(() => manager.subscribe(force), [manager]);
|
|
49
|
+
return (<>
|
|
50
|
+
{manager.getActive().map((s) => {
|
|
51
|
+
const shared = {
|
|
52
|
+
source: { uri: s.url },
|
|
53
|
+
volume: s.volume,
|
|
54
|
+
loop: s.loop,
|
|
55
|
+
paused: false,
|
|
56
|
+
onFinish: () => {
|
|
57
|
+
if (!s.loop)
|
|
58
|
+
manager.remove(s.playId);
|
|
59
|
+
},
|
|
60
|
+
onError: (e) => {
|
|
61
|
+
console.warn(`[Studio] Sound failed: ${s.audioAssetId} (#${s.playId})`, e?.nativeEvent?.error);
|
|
62
|
+
manager.remove(s.playId);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
return s.position ? (<ViroSpatialSound_1.ViroSpatialSound key={s.playId} position={s.position} {...shared}/>) : (<ViroSound_1.ViroSound key={s.playId} {...shared}/>);
|
|
66
|
+
})}
|
|
67
|
+
</>);
|
|
68
|
+
};
|
|
69
|
+
exports.StudioSounds = StudioSounds;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Request helpers: template validation + interpolation (URL / headers /
|
|
3
|
+
* JSON body), allowlist host matching, dot-path response extraction, and
|
|
4
|
+
* response->variable binding application. Pure logic, no platform APIs (no
|
|
5
|
+
* URL constructor — Hermes-safe), shared by the Studio editor, the
|
|
6
|
+
* scene-api-request egress proxy, and the scene runtime.
|
|
7
|
+
*
|
|
8
|
+
* Dual-maintained: this file exists in the Viro repo
|
|
9
|
+
* (components/Studio/domain/) and the ReactVisionStudio monorepo
|
|
10
|
+
* (packages/common/scene-assets/). Keep the two copies identical.
|
|
11
|
+
*
|
|
12
|
+
* Interpolation rules (fail closed — unknown or malformed placeholders are
|
|
13
|
+
* errors, never substituted with empty strings):
|
|
14
|
+
* - Placeholders are written {{name}} where name matches the
|
|
15
|
+
* scene_variables name constraint.
|
|
16
|
+
* - URL: the origin (scheme://host[:port]) must be static; placeholders are
|
|
17
|
+
* only legal after it and substitute percent-encoded.
|
|
18
|
+
* - Headers: values substitute as strings; control characters are rejected.
|
|
19
|
+
* - Body: the template must parse as JSON. A string value that is EXACTLY
|
|
20
|
+
* one placeholder substitutes as the typed value (number stays number);
|
|
21
|
+
* embedded placeholders substitute stringified; placeholders outside
|
|
22
|
+
* string values are invalid JSON and rejected. Re-serialisation makes
|
|
23
|
+
* structural injection impossible.
|
|
24
|
+
*/
|
|
25
|
+
export type ApiVariableType = "BOOLEAN" | "NUMBER" | "STRING";
|
|
26
|
+
export type ApiPrimitiveValue = boolean | number | string;
|
|
27
|
+
export type ApiRequestBindingSource = "BODY" | "STATUS" | "OK" | "ERROR_MESSAGE";
|
|
28
|
+
export type ApiHeaderTemplate = {
|
|
29
|
+
key: string;
|
|
30
|
+
value_template: string;
|
|
31
|
+
};
|
|
32
|
+
export declare const API_REQUEST_TIMEOUT_MIN_MS = 1000;
|
|
33
|
+
export declare const API_REQUEST_TIMEOUT_MAX_MS = 30000;
|
|
34
|
+
export declare const API_REQUEST_TIMEOUT_DEFAULT_MS = 10000;
|
|
35
|
+
export declare const API_REQUEST_MAX_URL_LENGTH = 2048;
|
|
36
|
+
export declare const API_REQUEST_MAX_BODY_LENGTH = 65536;
|
|
37
|
+
export declare const API_REQUEST_MAX_SELECTOR_LENGTH = 256;
|
|
38
|
+
export declare const API_REQUEST_MAX_VARIABLES = 32;
|
|
39
|
+
export declare const API_REQUEST_MAX_VARIABLE_VALUE_LENGTH = 4096;
|
|
40
|
+
export declare const API_REQUEST_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
41
|
+
export type ApiRequestMethod = (typeof API_REQUEST_METHODS)[number];
|
|
42
|
+
export type TemplateValidation = {
|
|
43
|
+
ok: true;
|
|
44
|
+
names: string[];
|
|
45
|
+
} | {
|
|
46
|
+
ok: false;
|
|
47
|
+
error: string;
|
|
48
|
+
};
|
|
49
|
+
type Fail = {
|
|
50
|
+
ok: false;
|
|
51
|
+
error: string;
|
|
52
|
+
};
|
|
53
|
+
/** Names of every well-formed placeholder, in order of appearance. */
|
|
54
|
+
export declare const extractPlaceholders: (template: string) => string[];
|
|
55
|
+
/**
|
|
56
|
+
* A template string is valid when every "{{" / "}}" belongs to a well-formed
|
|
57
|
+
* placeholder. Malformed names ({{9x}}, {{a b}}) and stray braces are errors.
|
|
58
|
+
*/
|
|
59
|
+
export declare const validateTemplateString: (template: string) => TemplateValidation;
|
|
60
|
+
export type ValueLookup = (name: string) => ApiPrimitiveValue | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Fail-soft interpolation for display strings (alert title / message). Unlike
|
|
63
|
+
* the request templates this never errors: a well-formed {{name}} whose value
|
|
64
|
+
* is missing is left verbatim, so a stale or mistyped reference stays visible
|
|
65
|
+
* rather than blanking the field or suppressing the whole alert. Malformed
|
|
66
|
+
* braces (not a valid placeholder) are left untouched.
|
|
67
|
+
*/
|
|
68
|
+
export declare const interpolateDisplayTemplate: (template: string, get: ValueLookup) => string;
|
|
69
|
+
export type UrlTemplateValidation = {
|
|
70
|
+
ok: true;
|
|
71
|
+
host: string;
|
|
72
|
+
port: string | null;
|
|
73
|
+
names: string[];
|
|
74
|
+
} | Fail;
|
|
75
|
+
/**
|
|
76
|
+
* Validates scheme + static origin (no placeholders, no userinfo, no IPv6
|
|
77
|
+
* literal) and the placeholder syntax of the whole template. Returns the
|
|
78
|
+
* pinned host (lowercase, no port) and explicit port if present.
|
|
79
|
+
*/
|
|
80
|
+
export declare const validateUrlTemplate: (template: string) => UrlTemplateValidation;
|
|
81
|
+
export type UrlInterpolation = {
|
|
82
|
+
ok: true;
|
|
83
|
+
url: string;
|
|
84
|
+
} | Fail;
|
|
85
|
+
/**
|
|
86
|
+
* Substitutes placeholders percent-encoded into the path/query part. The
|
|
87
|
+
* origin is template-static by validation; the post-substitution recheck is
|
|
88
|
+
* belt-and-braces.
|
|
89
|
+
*/
|
|
90
|
+
export declare const interpolateUrlTemplate: (template: string, get: ValueLookup) => UrlInterpolation;
|
|
91
|
+
export declare const HEADER_NAME_PATTERN: RegExp;
|
|
92
|
+
export declare const isForbiddenHeaderName: (name: string) => boolean;
|
|
93
|
+
export type HeadersValidation = {
|
|
94
|
+
ok: true;
|
|
95
|
+
} | Fail;
|
|
96
|
+
export declare const validateHeaderTemplates: (headers: ApiHeaderTemplate[], options?: {
|
|
97
|
+
reservedNames?: string[];
|
|
98
|
+
}) => HeadersValidation;
|
|
99
|
+
export type HeadersInterpolation = {
|
|
100
|
+
ok: true;
|
|
101
|
+
headers: {
|
|
102
|
+
key: string;
|
|
103
|
+
value: string;
|
|
104
|
+
}[];
|
|
105
|
+
} | Fail;
|
|
106
|
+
export declare const interpolateHeaders: (headers: ApiHeaderTemplate[], get: ValueLookup, options?: {
|
|
107
|
+
reservedNames?: string[];
|
|
108
|
+
}) => HeadersInterpolation;
|
|
109
|
+
export type BodyValidation = {
|
|
110
|
+
ok: true;
|
|
111
|
+
names: string[];
|
|
112
|
+
} | Fail;
|
|
113
|
+
/**
|
|
114
|
+
* The body template must be JSON; placeholders may only appear inside string
|
|
115
|
+
* values (a bare {{n}} outside a string is invalid JSON by construction).
|
|
116
|
+
*/
|
|
117
|
+
export declare const validateBodyTemplate: (template: string) => BodyValidation;
|
|
118
|
+
export type BodyInterpolation = {
|
|
119
|
+
ok: true;
|
|
120
|
+
body: string;
|
|
121
|
+
} | Fail;
|
|
122
|
+
/**
|
|
123
|
+
* Parse -> walk -> substitute (typed for whole-placeholder strings) ->
|
|
124
|
+
* re-stringify. The serialiser guarantees escaping, so variable values can
|
|
125
|
+
* never change the JSON structure.
|
|
126
|
+
*/
|
|
127
|
+
export declare const interpolateJsonBody: (template: string, get: ValueLookup) => BodyInterpolation;
|
|
128
|
+
/**
|
|
129
|
+
* Pattern grammar: "host.tld" or "*.host.tld", optional ":port". A pattern
|
|
130
|
+
* without a port matches the default HTTPS port only (no explicit port in the
|
|
131
|
+
* URL); a pattern with a port matches exactly that port. The wildcard matches
|
|
132
|
+
* subdomains at a label boundary, never the apex itself.
|
|
133
|
+
*/
|
|
134
|
+
export declare const matchHostPattern: (host: string, port: string | null, pattern: string) => boolean;
|
|
135
|
+
export declare const isHostAllowlisted: (host: string, port: string | null, patterns: string[]) => boolean;
|
|
136
|
+
export declare const validateSelector: (selector: string) => boolean;
|
|
137
|
+
export type ExtractionResult = {
|
|
138
|
+
found: true;
|
|
139
|
+
value: unknown;
|
|
140
|
+
} | {
|
|
141
|
+
found: false;
|
|
142
|
+
};
|
|
143
|
+
/** Walks `data.items[0].name`-style selectors. Missing path -> not found. */
|
|
144
|
+
export declare const extractByPath: (value: unknown, selector: string) => ExtractionResult;
|
|
145
|
+
export type ApiRequestBindingInput = {
|
|
146
|
+
source: ApiRequestBindingSource;
|
|
147
|
+
selector: string | null;
|
|
148
|
+
variable_name: string;
|
|
149
|
+
variable_type: ApiVariableType;
|
|
150
|
+
};
|
|
151
|
+
export type ApiRequestOutcomeInput = {
|
|
152
|
+
ok: boolean;
|
|
153
|
+
status: number | null;
|
|
154
|
+
body?: unknown;
|
|
155
|
+
error_message?: string | null;
|
|
156
|
+
};
|
|
157
|
+
export type CoercionResult = {
|
|
158
|
+
ok: true;
|
|
159
|
+
value: ApiPrimitiveValue;
|
|
160
|
+
} | {
|
|
161
|
+
ok: false;
|
|
162
|
+
};
|
|
163
|
+
/** Primitive-only, lossless-ish coercion into the variable's declared type. */
|
|
164
|
+
export declare const coerceBindingValue: (value: unknown, type: ApiVariableType) => CoercionResult;
|
|
165
|
+
export type ApiRequestBindingWrite = {
|
|
166
|
+
name: string;
|
|
167
|
+
type: ApiVariableType;
|
|
168
|
+
value: ApiPrimitiveValue;
|
|
169
|
+
};
|
|
170
|
+
export type BindingApplication = {
|
|
171
|
+
writes: ApiRequestBindingWrite[];
|
|
172
|
+
warnings: string[];
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* Pure: resolves each applicable binding against the outcome and returns the
|
|
176
|
+
* variable writes plus warnings for skipped bindings (missing path, type
|
|
177
|
+
* mismatch). The caller applies writes to its variable store.
|
|
178
|
+
*/
|
|
179
|
+
export declare const applyBindings: (bindings: ApiRequestBindingInput[], outcome: ApiRequestOutcomeInput) => BindingApplication;
|
|
180
|
+
export {};
|