lecodes-sdk 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +114 -0
- package/dist/global.d.ts +503 -0
- package/dist/host.d.ts +61 -0
- package/dist/inject.js +4730 -0
- package/dist/types/animate/animate.d.ts +20 -0
- package/dist/types/animate/bezier.d.ts +2 -0
- package/dist/types/animate/easings.d.ts +21 -0
- package/dist/types/canvas/Canvas.d.ts +168 -0
- package/dist/types/core/Aspect.d.ts +171 -0
- package/dist/types/core/InspectorUI.d.ts +88 -0
- package/dist/types/core/StateMachine.d.ts +81 -0
- package/dist/types/core/color.d.ts +13 -0
- package/dist/types/core/compWrite.d.ts +11 -0
- package/dist/types/core/events.d.ts +11 -0
- package/dist/types/core/fields.d.ts +58 -0
- package/dist/types/core/registry.d.ts +7 -0
- package/dist/types/core/signals.d.ts +29 -0
- package/dist/types/core/time.d.ts +31 -0
- package/dist/types/g2/Camera2D.d.ts +19 -0
- package/dist/types/g2/CharacterController2D.d.ts +134 -0
- package/dist/types/g2/Node2D.d.ts +91 -0
- package/dist/types/g2/OneWay2D.d.ts +24 -0
- package/dist/types/g2/Physics2D.d.ts +147 -0
- package/dist/types/g2/Scene2D.d.ts +72 -0
- package/dist/types/g2/Shape2D.d.ts +58 -0
- package/dist/types/g2/Sprite.d.ts +51 -0
- package/dist/types/g2/SpriteAnimation.d.ts +50 -0
- package/dist/types/g2/SpriteSheet.d.ts +69 -0
- package/dist/types/g2/Texture2D.d.ts +16 -0
- package/dist/types/g2/Tilemap.d.ts +20 -0
- package/dist/types/g2/Tileset.d.ts +32 -0
- package/dist/types/g2/Trigger2D.d.ts +20 -0
- package/dist/types/g2/autotile.d.ts +133 -0
- package/dist/types/g2/cells.d.ts +14 -0
- package/dist/types/g2/defineScene2d.d.ts +141 -0
- package/dist/types/g2/groups2d.d.ts +16 -0
- package/dist/types/g2/loop.d.ts +4 -0
- package/dist/types/g2/scenarios2d.d.ts +30 -0
- package/dist/types/g2/touch.d.ts +2 -0
- package/dist/types/gl/Camera.d.ts +79 -0
- package/dist/types/gl/CameraPlace.d.ts +26 -0
- package/dist/types/gl/CharacterController.d.ts +98 -0
- package/dist/types/gl/Gearbox.d.ts +86 -0
- package/dist/types/gl/Geometry.d.ts +39 -0
- package/dist/types/gl/IK.d.ts +53 -0
- package/dist/types/gl/InstancedMesh.d.ts +46 -0
- package/dist/types/gl/Light.d.ts +73 -0
- package/dist/types/gl/Lightmap.d.ts +85 -0
- package/dist/types/gl/Locomotion.d.ts +279 -0
- package/dist/types/gl/Material.d.ts +100 -0
- package/dist/types/gl/Mesh.d.ts +33 -0
- package/dist/types/gl/Model.d.ts +53 -0
- package/dist/types/gl/NavAgent.d.ts +99 -0
- package/dist/types/gl/NavMesh.d.ts +169 -0
- package/dist/types/gl/Node.d.ts +99 -0
- package/dist/types/gl/Noise.d.ts +20 -0
- package/dist/types/gl/Particles.d.ts +277 -0
- package/dist/types/gl/Physics.d.ts +91 -0
- package/dist/types/gl/Plane.d.ts +19 -0
- package/dist/types/gl/Ragdoll.d.ts +86 -0
- package/dist/types/gl/Ray.d.ts +8 -0
- package/dist/types/gl/Scene.d.ts +221 -0
- package/dist/types/gl/Shape.d.ts +122 -0
- package/dist/types/gl/Terrain.d.ts +280 -0
- package/dist/types/gl/Texture.d.ts +27 -0
- package/dist/types/gl/Trigger.d.ts +10 -0
- package/dist/types/gl/Vehicle.d.ts +191 -0
- package/dist/types/gl/Wheel.d.ts +95 -0
- package/dist/types/gl/animation/AnimationClip.d.ts +60 -0
- package/dist/types/gl/animation/Animator.d.ts +219 -0
- package/dist/types/gl/animation/Layer.d.ts +31 -0
- package/dist/types/gl/animation/Loop.d.ts +17 -0
- package/dist/types/gl/animation/Playback.d.ts +36 -0
- package/dist/types/gl/animation/core.d.ts +367 -0
- package/dist/types/gl/controls.d.ts +19 -0
- package/dist/types/gl/physicsEvents.d.ts +1 -0
- package/dist/types/gl/scenarios.d.ts +98 -0
- package/dist/types/gl/state.d.ts +5 -0
- package/dist/types/gl/terrainMesh.d.ts +46 -0
- package/dist/types/gl/touch.d.ts +2 -0
- package/dist/types/inject.d.ts +123 -0
- package/dist/types/math/Mathf.d.ts +39 -0
- package/dist/types/math/mat4.d.ts +66 -0
- package/dist/types/math/quat.d.ts +53 -0
- package/dist/types/math/vec.d.ts +129 -0
- package/dist/types/net/codec.d.ts +25 -0
- package/dist/types/net/core.d.ts +106 -0
- package/dist/types/net/index.d.ts +80 -0
- package/dist/types/net/replication.d.ts +101 -0
- package/dist/types/plugins/camera.d.ts +25 -0
- package/dist/types/plugins/geolocation.d.ts +39 -0
- package/dist/types/plugins/oauth.d.ts +25 -0
- package/dist/types/plugins/permission.d.ts +1 -0
- package/dist/types/plugins/push.d.ts +46 -0
- package/dist/types/plugins/qr.d.ts +21 -0
- package/dist/types/plugins/service.d.ts +33 -0
- package/dist/types/runtime/app.d.ts +67 -0
- package/dist/types/runtime/appEvents.d.ts +7 -0
- package/dist/types/runtime/channel.d.ts +1 -0
- package/dist/types/runtime/clipboard.d.ts +7 -0
- package/dist/types/runtime/datetime.d.ts +80 -0
- package/dist/types/runtime/device.d.ts +126 -0
- package/dist/types/runtime/fetch.d.ts +31 -0
- package/dist/types/runtime/files.d.ts +33 -0
- package/dist/types/runtime/input.d.ts +121 -0
- package/dist/types/runtime/media.d.ts +46 -0
- package/dist/types/runtime/misc.d.ts +11 -0
- package/dist/types/runtime/net.d.ts +15 -0
- package/dist/types/runtime/rpc.d.ts +44 -0
- package/dist/types/runtime/service.d.ts +1 -0
- package/dist/types/runtime/share.d.ts +2 -0
- package/dist/types/runtime/storage.d.ts +5 -0
- package/dist/types/runtime/touch.d.ts +56 -0
- package/dist/types/scene/defineScene.d.ts +296 -0
- package/dist/types/scene/editorPlugins.d.ts +82 -0
- package/dist/types/scene/gizmos.d.ts +56 -0
- package/dist/types/scene/grammar.d.ts +56 -0
- package/dist/types/scene/level.d.ts +98 -0
- package/dist/types/scene/material.d.ts +76 -0
- package/dist/types/server/auth/appConfig.d.ts +16 -0
- package/dist/types/server/auth/global.d.ts +56 -0
- package/dist/types/server/auth/models.d.ts +164 -0
- package/dist/types/server/auth/types.d.ts +56 -0
- package/dist/types/server/channel.d.ts +28 -0
- package/dist/types/server/context.d.ts +27 -0
- package/dist/types/server/db/defineDb.d.ts +36 -0
- package/dist/types/server/db/fields.d.ts +146 -0
- package/dist/types/server/db/index.d.ts +6 -0
- package/dist/types/server/db/marci/query.d.ts +326 -0
- package/dist/types/server/db/types.d.ts +163 -0
- package/dist/types/server/errors.d.ts +8 -0
- package/dist/types/server/inject.d.ts +5 -0
- package/dist/types/ui/NativeView.d.ts +38 -0
- package/dist/types/ui/UI.d.ts +27 -0
- package/dist/types/ui/UIBottomSheet.d.ts +71 -0
- package/dist/types/ui/UIButton.d.ts +30 -0
- package/dist/types/ui/UIContainer.d.ts +33 -0
- package/dist/types/ui/UIImage.d.ts +35 -0
- package/dist/types/ui/UIInput.d.ts +110 -0
- package/dist/types/ui/UIModal.d.ts +70 -0
- package/dist/types/ui/UINode.d.ts +363 -0
- package/dist/types/ui/UIPager.d.ts +123 -0
- package/dist/types/ui/UIPopover.d.ts +39 -0
- package/dist/types/ui/UIScreen.d.ts +57 -0
- package/dist/types/ui/UIScrollable.d.ts +40 -0
- package/dist/types/ui/UISpacer.d.ts +9 -0
- package/dist/types/ui/UITabs.d.ts +96 -0
- package/dist/types/ui/UIText.d.ts +19 -0
- package/dist/types/ui/UIVideo.d.ts +28 -0
- package/dist/types/ui/UIVirtualizedList.d.ts +97 -0
- package/dist/types/ui/UIWidget.d.ts +65 -0
- package/dist/types/ui/fonts.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +68 -0
- package/dist/types/ui/router.d.ts +40 -0
- package/dist/types/ui/theme.d.ts +26 -0
- package/dist/types.json +1 -0
- package/package.json +46 -0
- package/prompts/2d.md +395 -0
- package/prompts/3d-scene-files.md +113 -0
- package/prompts/3d-scene.md +119 -0
- package/prompts/3d.md +303 -0
- package/prompts/README.md +142 -0
- package/prompts/ar.md +109 -0
- package/prompts/canvas.md +27 -0
- package/prompts/compose.ts +46 -0
- package/prompts/concept.md +49 -0
- package/prompts/core-design.md +75 -0
- package/prompts/core.md +264 -0
- package/prompts/design.md +218 -0
- package/prompts/dist/2d-game.md +1530 -0
- package/prompts/dist/3d-app.md +1672 -0
- package/prompts/dist/ar-app.md +1548 -0
- package/prompts/dist/concept.md +49 -0
- package/prompts/dist/design.md +498 -0
- package/prompts/dist/ui-app.md +1134 -0
- package/prompts/index.md +37 -0
- package/prompts/intake-prompt.md +33 -0
- package/prompts/namer-prompt.md +12 -0
- package/prompts/router-prompt.md +50 -0
- package/prompts/select.ts +111 -0
- package/prompts/ui-design.md +203 -0
- package/prompts/ui.md +803 -0
- package/src/animate/animate.ts +238 -0
- package/src/animate/bezier.ts +138 -0
- package/src/animate/easings.ts +126 -0
- package/src/bridges.d.ts +1345 -0
- package/src/canvas/Canvas.ts +305 -0
- package/src/compile/__tests__/assetIconMacro.test.ts +219 -0
- package/src/compile/__tests__/assetMacro.test.ts +74 -0
- package/src/compile/__tests__/assetName.test.ts +55 -0
- package/src/compile/__tests__/compile.test.ts +287 -0
- package/src/compile/__tests__/detectEntry.test.ts +132 -0
- package/src/compile/__tests__/fontMacro.test.ts +199 -0
- package/src/compile/aspectMacro.ts +86 -0
- package/src/compile/assetIconMacro.ts +384 -0
- package/src/compile/assetMacro.ts +146 -0
- package/src/compile/assetName.ts +50 -0
- package/src/compile/bundler.ts +287 -0
- package/src/compile/compileProject.ts +127 -0
- package/src/compile/detectEntry.ts +128 -0
- package/src/compile/fontMacro.ts +459 -0
- package/src/compile/fontRegistry.ts +78 -0
- package/src/compile/header.ts +67 -0
- package/src/compile/index.ts +100 -0
- package/src/compile/libraryImports.ts +52 -0
- package/src/compile/liteMaterial.ts +247 -0
- package/src/compile/sceneEditor.ts +88 -0
- package/src/compile/serverSplit.ts +233 -0
- package/src/compile/serverTypes.ts +227 -0
- package/src/compile/sfnt.ts +98 -0
- package/src/compile/shaderSchema.ts +202 -0
- package/src/compile/shaderTargets.ts +77 -0
- package/src/compile/sourcemap.ts +25 -0
- package/src/core/Aspect.ts +568 -0
- package/src/core/InspectorUI.ts +212 -0
- package/src/core/StateMachine.ts +308 -0
- package/src/core/__tests__/stateMachine.test.ts +132 -0
- package/src/core/color.ts +66 -0
- package/src/core/compWrite.ts +42 -0
- package/src/core/events.ts +38 -0
- package/src/core/fields.ts +120 -0
- package/src/core/registry.ts +23 -0
- package/src/core/signals.ts +277 -0
- package/src/core/time.ts +84 -0
- package/src/g2/Camera2D.ts +40 -0
- package/src/g2/CharacterController2D.ts +276 -0
- package/src/g2/Node2D.ts +267 -0
- package/src/g2/OneWay2D.ts +66 -0
- package/src/g2/Physics2D.ts +346 -0
- package/src/g2/Scene2D.ts +209 -0
- package/src/g2/Shape2D.ts +259 -0
- package/src/g2/Sprite.ts +89 -0
- package/src/g2/SpriteAnimation.ts +171 -0
- package/src/g2/SpriteSheet.ts +166 -0
- package/src/g2/Texture2D.ts +47 -0
- package/src/g2/Tilemap.ts +41 -0
- package/src/g2/Tileset.ts +71 -0
- package/src/g2/Trigger2D.ts +77 -0
- package/src/g2/autotile.ts +433 -0
- package/src/g2/cells.ts +91 -0
- package/src/g2/defineScene2d.ts +381 -0
- package/src/g2/groups2d.ts +106 -0
- package/src/g2/loop.ts +50 -0
- package/src/g2/scenarios2d.ts +69 -0
- package/src/g2/touch.ts +83 -0
- package/src/gl/Camera.ts +160 -0
- package/src/gl/CameraPlace.ts +52 -0
- package/src/gl/CharacterController.ts +238 -0
- package/src/gl/Gearbox.ts +212 -0
- package/src/gl/Geometry.ts +279 -0
- package/src/gl/IK.ts +193 -0
- package/src/gl/InstancedMesh.ts +132 -0
- package/src/gl/Light.ts +135 -0
- package/src/gl/Lightmap.ts +402 -0
- package/src/gl/Locomotion.ts +498 -0
- package/src/gl/Material.ts +279 -0
- package/src/gl/Mesh.ts +83 -0
- package/src/gl/Model.ts +124 -0
- package/src/gl/NavAgent.ts +337 -0
- package/src/gl/NavMesh.ts +397 -0
- package/src/gl/Node.ts +350 -0
- package/src/gl/Noise.ts +30 -0
- package/src/gl/Particles.ts +676 -0
- package/src/gl/Physics.ts +222 -0
- package/src/gl/Plane.ts +53 -0
- package/src/gl/Ragdoll.ts +270 -0
- package/src/gl/Ray.ts +16 -0
- package/src/gl/Scene.ts +505 -0
- package/src/gl/Shape.ts +409 -0
- package/src/gl/Terrain.ts +1092 -0
- package/src/gl/Texture.ts +63 -0
- package/src/gl/Trigger.ts +45 -0
- package/src/gl/Vehicle.ts +473 -0
- package/src/gl/Wheel.ts +240 -0
- package/src/gl/animation/AnimationClip.ts +204 -0
- package/src/gl/animation/Animator.ts +329 -0
- package/src/gl/animation/Layer.ts +39 -0
- package/src/gl/animation/Loop.ts +31 -0
- package/src/gl/animation/Playback.ts +52 -0
- package/src/gl/animation/core.ts +670 -0
- package/src/gl/controls.ts +95 -0
- package/src/gl/physicsEvents.ts +20 -0
- package/src/gl/scenarios.ts +291 -0
- package/src/gl/state.ts +6 -0
- package/src/gl/terrainMesh.ts +219 -0
- package/src/gl/touch.ts +68 -0
- package/src/host.d.ts +61 -0
- package/src/inject.ts +203 -0
- package/src/math/Mathf.ts +118 -0
- package/src/math/mat4.ts +278 -0
- package/src/math/quat.ts +232 -0
- package/src/math/vec.ts +255 -0
- package/src/net/codec.ts +119 -0
- package/src/net/core.ts +384 -0
- package/src/net/index.ts +181 -0
- package/src/net/replication.ts +622 -0
- package/src/plugins/camera.ts +81 -0
- package/src/plugins/geolocation.ts +123 -0
- package/src/plugins/oauth.ts +61 -0
- package/src/plugins/permission.ts +7 -0
- package/src/plugins/push.ts +132 -0
- package/src/plugins/qr.ts +73 -0
- package/src/plugins/service.ts +47 -0
- package/src/runtime/app.ts +101 -0
- package/src/runtime/appEvents.ts +54 -0
- package/src/runtime/channel.ts +61 -0
- package/src/runtime/clipboard.ts +20 -0
- package/src/runtime/datetime.ts +329 -0
- package/src/runtime/device.ts +293 -0
- package/src/runtime/fetch.ts +77 -0
- package/src/runtime/files.ts +108 -0
- package/src/runtime/input.ts +175 -0
- package/src/runtime/media.ts +111 -0
- package/src/runtime/misc.ts +16 -0
- package/src/runtime/net.ts +36 -0
- package/src/runtime/rpc.ts +218 -0
- package/src/runtime/service.ts +83 -0
- package/src/runtime/share.ts +9 -0
- package/src/runtime/storage.ts +13 -0
- package/src/runtime/touch.ts +76 -0
- package/src/scene/defineScene.ts +1396 -0
- package/src/scene/editorPlugins.ts +110 -0
- package/src/scene/gizmos.ts +148 -0
- package/src/scene/grammar.ts +120 -0
- package/src/scene/level.ts +296 -0
- package/src/scene/material.ts +188 -0
- package/src/server/auth/appConfig.ts +12 -0
- package/src/server/auth/global.ts +80 -0
- package/src/server/auth/host.ts +318 -0
- package/src/server/auth/models.ts +83 -0
- package/src/server/auth/types.ts +50 -0
- package/src/server/channel.ts +56 -0
- package/src/server/context.ts +36 -0
- package/src/server/db/defineDb.ts +237 -0
- package/src/server/db/fields.ts +132 -0
- package/src/server/db/httpTransport.ts +93 -0
- package/src/server/db/index.ts +7 -0
- package/src/server/db/marci/query.ts +412 -0
- package/src/server/db/types.ts +202 -0
- package/src/server/errors.ts +12 -0
- package/src/server/host.ts +74 -0
- package/src/server/inject.ts +13 -0
- package/src/server/runtime.ts +133 -0
- package/src/server/validate.ts +87 -0
- package/src/ui/NativeView.ts +142 -0
- package/src/ui/UI.ts +39 -0
- package/src/ui/UIBottomSheet.ts +139 -0
- package/src/ui/UIButton.ts +101 -0
- package/src/ui/UIContainer.ts +60 -0
- package/src/ui/UIImage.ts +83 -0
- package/src/ui/UIInput.ts +185 -0
- package/src/ui/UIModal.ts +139 -0
- package/src/ui/UINode.ts +830 -0
- package/src/ui/UIPager.ts +362 -0
- package/src/ui/UIPopover.ts +100 -0
- package/src/ui/UIScreen.ts +123 -0
- package/src/ui/UIScrollable.ts +87 -0
- package/src/ui/UISpacer.ts +14 -0
- package/src/ui/UITabs.ts +236 -0
- package/src/ui/UIText.ts +51 -0
- package/src/ui/UIVideo.ts +88 -0
- package/src/ui/UIVirtualizedList.ts +241 -0
- package/src/ui/UIWidget.ts +127 -0
- package/src/ui/fonts.ts +13 -0
- package/src/ui/presentable.ts +117 -0
- package/src/ui/router.ts +132 -0
- package/src/ui/theme.ts +84 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Audio / video playback over the host. Ported from worker/src/MediaPlayer.ts, with the listener
|
|
2
|
+
// boilerplate folded into the shared Emitter. VideoPlayer.texture (a 3D Texture) is wired in Phase 4
|
|
3
|
+
// when the Texture type exists — see gl/.
|
|
4
|
+
//
|
|
5
|
+
// The host no longer routes end-of-playback events back by id through a Registry: each player hands
|
|
6
|
+
// the host a `_finished` callback at creation time. The host holds the JS closure directly — valid
|
|
7
|
+
// on every backend (web JS, Android quickjs, iOS JavaScriptCore) — and invokes it when a play-through
|
|
8
|
+
// completes, so there is no id table to keep in sync.
|
|
9
|
+
|
|
10
|
+
import { Emitter } from "../core/events"
|
|
11
|
+
import { Texture } from "../gl/Texture"
|
|
12
|
+
|
|
13
|
+
type MediaEvents = {
|
|
14
|
+
/** Fired at each loop boundary while `loop` is true. */
|
|
15
|
+
loopReached: () => void
|
|
16
|
+
/** Fired once when playback reaches the end while `loop` is false. */
|
|
17
|
+
completed: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
abstract class MediaPlayer extends Emitter<MediaEvents> {
|
|
21
|
+
protected _id = -1
|
|
22
|
+
protected _src = ""
|
|
23
|
+
private _playing = false
|
|
24
|
+
private _loop = false
|
|
25
|
+
private _volume = 1
|
|
26
|
+
private _disposed = false
|
|
27
|
+
|
|
28
|
+
// Every host-touching operation runs through this so a disposed player can never address a freed
|
|
29
|
+
// (and possibly reused) host slot.
|
|
30
|
+
private _assertAlive(): void {
|
|
31
|
+
if (this._disposed) throw new Error("MediaPlayer has been disposed")
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
play(): void { this.playing = true }
|
|
35
|
+
pause(): void { this.playing = false }
|
|
36
|
+
|
|
37
|
+
get playing(): boolean { return this._playing }
|
|
38
|
+
set playing(value: boolean) {
|
|
39
|
+
this._assertAlive()
|
|
40
|
+
this._playing = value
|
|
41
|
+
_creatorUtils.updateMediaPlayerPlaying(this._id, value)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get volume(): number { return this._volume }
|
|
45
|
+
set volume(value: number) {
|
|
46
|
+
this._assertAlive()
|
|
47
|
+
this._volume = value
|
|
48
|
+
_creatorUtils.updateMediaPlayerVolume(this._id, value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get loop(): boolean { return this._loop }
|
|
52
|
+
set loop(value: boolean) {
|
|
53
|
+
this._assertAlive()
|
|
54
|
+
this._loop = value
|
|
55
|
+
_creatorUtils.updateMediaPlayerLoop(this._id, value)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Current playback position, in seconds. Settable to seek. */
|
|
59
|
+
get time(): number { this._assertAlive(); return _creatorUtils.getMediaPlayerTime(this._id) }
|
|
60
|
+
set time(value: number) { this._assertAlive(); _creatorUtils.setMediaPlayerTime(this._id, value) }
|
|
61
|
+
|
|
62
|
+
/** Total media length in seconds (0 until metadata has loaded). */
|
|
63
|
+
get duration(): number { this._assertAlive(); return _creatorUtils.getMediaPlayerDuration(this._id) }
|
|
64
|
+
|
|
65
|
+
/** True once {@link dispose} has been called; the player is unusable afterwards. */
|
|
66
|
+
get disposed(): boolean { return this._disposed }
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Release the underlying host media resource (decoder, network, buffers) immediately and detach
|
|
70
|
+
* all listeners. Idempotent. Any further use of the player throws.
|
|
71
|
+
*/
|
|
72
|
+
dispose(): void {
|
|
73
|
+
if (this._disposed) return
|
|
74
|
+
this._disposed = true
|
|
75
|
+
this._playing = false
|
|
76
|
+
this.clearListeners()
|
|
77
|
+
_creatorUtils.removeMediaPlayer(this._id)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @internal — the host calls this when a play-through finishes. */
|
|
81
|
+
protected _finished(): void {
|
|
82
|
+
if (this._disposed) return
|
|
83
|
+
if (this._loop) {
|
|
84
|
+
this.dispatch("loopReached")
|
|
85
|
+
} else {
|
|
86
|
+
this._playing = false
|
|
87
|
+
this.dispatch("completed")
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export class AudioPlayer extends MediaPlayer {
|
|
93
|
+
constructor(src?: string) {
|
|
94
|
+
super()
|
|
95
|
+
this._src = src ?? ""
|
|
96
|
+
this._id = _creatorUtils.createAudioPlayer(this._src, () => this._finished())
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class VideoPlayer extends MediaPlayer {
|
|
101
|
+
constructor(src?: string) {
|
|
102
|
+
super()
|
|
103
|
+
this._src = src ?? ""
|
|
104
|
+
this._id = _creatorUtils.createVideoPlayer(this._src, () => this._finished())
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** A 3D Texture that samples this video (e.g. for a Material.video()). */
|
|
108
|
+
get texture(): Texture {
|
|
109
|
+
return new Texture(0, 0, _creator.createMediaPlayerTexture(this._id))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Small platform globals that are real (not engine-specific). Grown in Phase 1.
|
|
2
|
+
|
|
3
|
+
/** Show a transient toast notification. */
|
|
4
|
+
export const toast = (msg: string): void => _creatorUI.toast(msg)
|
|
5
|
+
|
|
6
|
+
/** Open `url` in the system browser / external handler (fire-and-forget, like `share`). Silent
|
|
7
|
+
* no-op on hosts without one (headless). */
|
|
8
|
+
export const openURL = (url: string): void => _creatorUtils.openUrl?.(url)
|
|
9
|
+
|
|
10
|
+
export type SvgSourceValue = {
|
|
11
|
+
readonly svg: string
|
|
12
|
+
tintColor: string | null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Wrap raw SVG XML so it can be used as an image source. */
|
|
16
|
+
export const SvgSource = (svg: string): SvgSourceValue => ({ svg, tintColor: null })
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// WebSocket over the host bridge. Same event surface as the browser's (open/message/close/error)
|
|
2
|
+
// via the shared Emitter. Ported from worker/src/WebSocket.ts.
|
|
3
|
+
|
|
4
|
+
import { Emitter } from "../core/events"
|
|
5
|
+
|
|
6
|
+
type SocketEvents = {
|
|
7
|
+
open: () => void
|
|
8
|
+
message: (data: any) => void
|
|
9
|
+
close: (code?: number) => void
|
|
10
|
+
error: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class WebSocket extends Emitter<SocketEvents> {
|
|
14
|
+
private _id: number | null = null
|
|
15
|
+
|
|
16
|
+
constructor(url: string, headers?: Record<string, string>) {
|
|
17
|
+
super()
|
|
18
|
+
this._id = _creatorUtils.websocketOpen(url, (channel, data) => this._onMessage(channel, data), headers)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
send(message: string | ArrayBuffer): void {
|
|
22
|
+
if (this._id !== null) _creatorUtils.websocketSend(this._id, message)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
close(): void {
|
|
26
|
+
if (this._id !== null) {
|
|
27
|
+
_creatorUtils.websocketClose(this._id)
|
|
28
|
+
this._id = null
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private _onMessage(channel: string, data: any): void {
|
|
33
|
+
if (data !== undefined && data !== null) this.dispatch(channel as keyof SocketEvents, data)
|
|
34
|
+
else this.dispatch(channel as keyof SocketEvents)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client transport for the app backend (docs/backend-plan.md §3.2 / §3.3). The compiler replaces
|
|
3
|
+
* every `*.server.ts` module in the client bundle with a stub whose exports are built by the three
|
|
4
|
+
* globals below — user code never calls them; it imports the server function and calls it.
|
|
5
|
+
*
|
|
6
|
+
* export const listNotes = __rpc("<serverUrl>", "notes.server.ts#listNotes")
|
|
7
|
+
* export const chat = __channel("<serverUrl>", "chat.server.ts#chat")
|
|
8
|
+
* export const db = __serverOnly("schema.server.ts#db")
|
|
9
|
+
*
|
|
10
|
+
* Wire (implemented by the runner):
|
|
11
|
+
* POST <serverUrl>/api/<id> body {"args":[…]}, `authorization: Bearer <session>` when known
|
|
12
|
+
* → 200 {"ok":true,"result":…,"session"?:"…"} | {"ok":false,"status":n,"message":"…","session"?:"…"}
|
|
13
|
+
* WS <serverUrl>/ws frames: → {t:"hello",session} {t:"sub",id,ch,topic} {t:"unsub",id}
|
|
14
|
+
* ← {t:"session",session} {t:"ok",id} {t:"err",id,status,message} {t:"ev",ch,topic,event,data}
|
|
15
|
+
* (`ev` is addressed by channel + topic, not by subscription id, so the server can fan one publish out
|
|
16
|
+
* to every socket of a topic natively; the client delivers it to each matching subscription.)
|
|
17
|
+
*
|
|
18
|
+
* The session token is transport-owned (cookie-like): stored under `lecodes.session:<serverUrl>` in
|
|
19
|
+
* localStorage, sent on every call and on the socket, replaced whenever a response carries `session`.
|
|
20
|
+
* User code never sees it — "am I logged in" is an endpoint (`me()`), see the plan.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { fetch } from "./fetch"
|
|
24
|
+
import { localStorage } from "./storage"
|
|
25
|
+
import { WebSocket } from "./net"
|
|
26
|
+
|
|
27
|
+
/** Rejection value of a failed endpoint call: `status` mirrors the server's `ApiError`
|
|
28
|
+
* (0 = the request itself failed: no network / no server). */
|
|
29
|
+
export class RpcError extends Error {
|
|
30
|
+
readonly status: number
|
|
31
|
+
constructor(status: number, message: string) {
|
|
32
|
+
super(message)
|
|
33
|
+
this.name = "RpcError"
|
|
34
|
+
this.status = status
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ───────────────────────────── session token ─────────────────────────────
|
|
39
|
+
|
|
40
|
+
const sessionKey = (serverUrl: string) => `lecodes.session:${serverUrl}`
|
|
41
|
+
const getSession = (serverUrl: string): string | null => {
|
|
42
|
+
try { return localStorage.getItem(sessionKey(serverUrl)) } catch { return null }
|
|
43
|
+
}
|
|
44
|
+
const setSession = (serverUrl: string, token: string | null) => {
|
|
45
|
+
try {
|
|
46
|
+
if (token) localStorage.setItem(sessionKey(serverUrl), token)
|
|
47
|
+
else localStorage.removeItem(sessionKey(serverUrl))
|
|
48
|
+
} catch { /* storage-less host: the session lives for this run only */ }
|
|
49
|
+
sockets.get(serverUrl)?.onSessionChanged(token)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ───────────────────────────── endpoints ─────────────────────────────
|
|
53
|
+
|
|
54
|
+
const noServer = (id: string) => new RpcError(0, `Endpoint ${id}: the project has no deployed server (compile with serverUrl)`)
|
|
55
|
+
|
|
56
|
+
/** Build the client proxy of endpoint `id`. Positional args travel as a JSON array. */
|
|
57
|
+
export const __rpc = (serverUrl: string, id: string) => {
|
|
58
|
+
const call = async (...args: unknown[]): Promise<any> => {
|
|
59
|
+
if (!serverUrl) throw noServer(id)
|
|
60
|
+
const headers: Record<string, string> = { "content-type": "application/json" }
|
|
61
|
+
const session = getSession(serverUrl)
|
|
62
|
+
if (session) headers.authorization = `Bearer ${session}`
|
|
63
|
+
let res
|
|
64
|
+
try {
|
|
65
|
+
res = await fetch(`${serverUrl}/api/${encodeURIComponent(id)}`, { method: "POST", headers, body: JSON.stringify({ args }) })
|
|
66
|
+
} catch (e) {
|
|
67
|
+
throw new RpcError(0, `Endpoint ${id}: request failed (${(e as Error)?.message ?? e})`)
|
|
68
|
+
}
|
|
69
|
+
let body: any
|
|
70
|
+
try { body = res.json() } catch { body = null }
|
|
71
|
+
res.dispose()
|
|
72
|
+
if (body && typeof body === "object" && "session" in body) setSession(serverUrl, body.session ?? null)
|
|
73
|
+
if (body && body.ok === true) return body.result
|
|
74
|
+
if (body && body.ok === false) throw new RpcError(body.status ?? res.status, body.message ?? "")
|
|
75
|
+
throw new RpcError(res.status, `Endpoint ${id}: unexpected response (HTTP ${res.status})`)
|
|
76
|
+
}
|
|
77
|
+
return call
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** A server-only export (db, models, helpers) referenced from client code: any use throws. */
|
|
81
|
+
export const __serverOnly = (id: string): any =>
|
|
82
|
+
new Proxy(function () {} as any, {
|
|
83
|
+
get: (_t, prop) => { if (prop === "then") return undefined; throw new RpcError(0, `${id} is server-only — it can't be used in client code`) },
|
|
84
|
+
apply: () => { throw new RpcError(0, `${id} is server-only — it can't be used in client code`) },
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// ───────────────────────────── channels ─────────────────────────────
|
|
88
|
+
|
|
89
|
+
type Handlers = Record<string, ((payload: any) => void) | undefined> & {
|
|
90
|
+
/** The socket dropped and came back: subscriptions were re-sent, but events in between are lost — refetch. */
|
|
91
|
+
reconnect?: () => void
|
|
92
|
+
/** The subscription was refused (`onJoin` threw) or the socket can't connect. */
|
|
93
|
+
error?: (e: RpcError) => void
|
|
94
|
+
}
|
|
95
|
+
type Sub = { id: number, ch: string, topic: string, handlers: Handlers, ready: boolean }
|
|
96
|
+
|
|
97
|
+
const BACKOFF_MS = [1000, 2000, 5000, 10000, 30000]
|
|
98
|
+
|
|
99
|
+
/** One multiplexed socket per server URL, shared by every channel of the app. */
|
|
100
|
+
class ChannelSocket {
|
|
101
|
+
private ws: WebSocket | null = null
|
|
102
|
+
private open = false
|
|
103
|
+
private subs = new Map<number, Sub>()
|
|
104
|
+
private nextId = 1
|
|
105
|
+
private attempts = 0
|
|
106
|
+
private timer: ReturnType<typeof setTimeout> | null = null
|
|
107
|
+
private closedByUs = false
|
|
108
|
+
private everOpened = false
|
|
109
|
+
private serverUrl: string
|
|
110
|
+
|
|
111
|
+
constructor(serverUrl: string) { this.serverUrl = serverUrl }
|
|
112
|
+
|
|
113
|
+
subscribe(ch: string, topic: string, handlers: Handlers): { close(): void } {
|
|
114
|
+
const sub: Sub = { id: this.nextId++, ch, topic, handlers, ready: false }
|
|
115
|
+
this.subs.set(sub.id, sub)
|
|
116
|
+
if (this.open) this.send({ t: "sub", id: sub.id, ch, topic })
|
|
117
|
+
else this.connect()
|
|
118
|
+
return {
|
|
119
|
+
close: () => {
|
|
120
|
+
if (!this.subs.delete(sub.id)) return
|
|
121
|
+
if (this.open) this.send({ t: "unsub", id: sub.id })
|
|
122
|
+
if (this.subs.size === 0) this.shutdown()
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
onSessionChanged(token: string | null) {
|
|
128
|
+
if (this.open) this.send({ t: "hello", session: token })
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private connect() {
|
|
132
|
+
if (this.ws || this.timer !== null) return
|
|
133
|
+
this.closedByUs = false
|
|
134
|
+
const url = this.serverUrl.replace(/^http/, "ws") + "/ws"
|
|
135
|
+
const session = getSession(this.serverUrl)
|
|
136
|
+
const ws = new WebSocket(url, session ? { authorization: `Bearer ${session}` } : undefined)
|
|
137
|
+
this.ws = ws
|
|
138
|
+
ws.addEventListener("open", () => {
|
|
139
|
+
this.open = true
|
|
140
|
+
this.attempts = 0
|
|
141
|
+
this.send({ t: "hello", session: getSession(this.serverUrl) })
|
|
142
|
+
const reconnected = this.everOpened
|
|
143
|
+
this.everOpened = true
|
|
144
|
+
for (const sub of this.subs.values()) {
|
|
145
|
+
sub.ready = false
|
|
146
|
+
this.send({ t: "sub", id: sub.id, ch: sub.ch, topic: sub.topic })
|
|
147
|
+
if (reconnected) sub.handlers.reconnect?.()
|
|
148
|
+
}
|
|
149
|
+
})
|
|
150
|
+
ws.addEventListener("message", (data) => {
|
|
151
|
+
if (typeof data !== "string") return
|
|
152
|
+
let msg: any
|
|
153
|
+
try { msg = JSON.parse(data) } catch { return }
|
|
154
|
+
this.onFrame(msg)
|
|
155
|
+
})
|
|
156
|
+
const onDown = () => {
|
|
157
|
+
if (this.ws !== ws) return
|
|
158
|
+
this.ws = null
|
|
159
|
+
this.open = false
|
|
160
|
+
if (this.closedByUs || this.subs.size === 0) return
|
|
161
|
+
const delay = BACKOFF_MS[Math.min(this.attempts++, BACKOFF_MS.length - 1)]
|
|
162
|
+
this.timer = setTimeout(() => { this.timer = null; this.connect() }, delay)
|
|
163
|
+
}
|
|
164
|
+
ws.addEventListener("close", onDown)
|
|
165
|
+
ws.addEventListener("error", onDown)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private onFrame(msg: any) {
|
|
169
|
+
switch (msg?.t) {
|
|
170
|
+
case "session": setSession(this.serverUrl, msg.session ?? null); break
|
|
171
|
+
case "ok": { const s = this.subs.get(msg.id); if (s) s.ready = true; break }
|
|
172
|
+
case "err": {
|
|
173
|
+
const s = this.subs.get(msg.id)
|
|
174
|
+
if (!s) break
|
|
175
|
+
this.subs.delete(msg.id)
|
|
176
|
+
s.handlers.error?.(new RpcError(msg.status ?? 0, msg.message ?? "subscription refused"))
|
|
177
|
+
break
|
|
178
|
+
}
|
|
179
|
+
case "ev": {
|
|
180
|
+
for (const s of this.subs.values()) {
|
|
181
|
+
if (s.ch === msg.ch && s.topic === msg.topic) s.handlers[msg.event]?.(msg.data)
|
|
182
|
+
}
|
|
183
|
+
break
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private send(frame: unknown) { this.ws?.send(JSON.stringify(frame)) }
|
|
189
|
+
|
|
190
|
+
private shutdown() {
|
|
191
|
+
this.closedByUs = true
|
|
192
|
+
if (this.timer !== null) { clearTimeout(this.timer); this.timer = null }
|
|
193
|
+
this.ws?.close()
|
|
194
|
+
this.ws = null
|
|
195
|
+
this.open = false
|
|
196
|
+
sockets.delete(this.serverUrl)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const sockets = new Map<string, ChannelSocket>()
|
|
201
|
+
const socketFor = (serverUrl: string) => {
|
|
202
|
+
let s = sockets.get(serverUrl)
|
|
203
|
+
if (!s) { s = new ChannelSocket(serverUrl); sockets.set(serverUrl, s) }
|
|
204
|
+
return s
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export interface ClientChannel<E extends Record<string, unknown> = Record<string, unknown>> {
|
|
208
|
+
/** Subscribe to `topic`; handlers are keyed by event name (+ `reconnect`/`error`). Returns `{ close }`. */
|
|
209
|
+
subscribe(topic: string, handlers: { [K in keyof E]?: (payload: E[K]) => void } & { reconnect?: () => void, error?: (e: RpcError) => void }): { close(): void }
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** Build the client proxy of channel `id`. */
|
|
213
|
+
export const __channel = (serverUrl: string, id: string): ClientChannel<any> => ({
|
|
214
|
+
subscribe(topic, handlers) {
|
|
215
|
+
if (!serverUrl) throw noServer(id)
|
|
216
|
+
return socketFor(serverUrl).subscribe(id, topic, handlers as Handlers)
|
|
217
|
+
},
|
|
218
|
+
})
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// A host-registered headless service (geolocation, bluetooth, contacts, …) — the UI-less sibling
|
|
2
|
+
// of NativeView (docs/service-channel-plan.md). The host registers a *capability*
|
|
3
|
+
// (`registerService("geolocation") { params, channel -> ... }`); the SDK talks to it over the
|
|
4
|
+
// shared JSON call/event protocol (runtime/channel.ts) via `_creatorUtils.service*`.
|
|
5
|
+
//
|
|
6
|
+
// Not a public global itself: apps meet services through typed plugin wrappers (Geolocation —
|
|
7
|
+
// src/plugins/) or the raw `Service(name)` global (src/plugins/service.ts), both thin layers
|
|
8
|
+
// over this exactly like QRScanner wraps NativeViewElement.
|
|
9
|
+
|
|
10
|
+
import { _channelCall, _channelEmit, _channelOff, _channelOn, type ChannelListeners } from "./channel"
|
|
11
|
+
|
|
12
|
+
/** @internal Base for typed service plugins. The host session opens lazily on the first `call()`
|
|
13
|
+
* (`on()` only records listeners), and `close()` releases it — a later `call()` reopens a fresh
|
|
14
|
+
* session. Session ids are host-allocated (websocket model): the `onEvent` callback is handed
|
|
15
|
+
* over at open, so no SDK-side ref registry is needed. */
|
|
16
|
+
export class ServiceClient {
|
|
17
|
+
readonly serviceName: string
|
|
18
|
+
readonly params: any
|
|
19
|
+
private _serviceId: number | null = null
|
|
20
|
+
private readonly _listeners: ChannelListeners = {}
|
|
21
|
+
|
|
22
|
+
constructor(serviceName: string, params: any = {}) {
|
|
23
|
+
this.serviceName = serviceName
|
|
24
|
+
this.params = params
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Whether this host registered a `registerService` factory under `name`. */
|
|
28
|
+
static isSupported(name: string): boolean {
|
|
29
|
+
return typeof _creatorUtils !== "undefined" && typeof _creatorUtils.isServiceSupported === "function"
|
|
30
|
+
&& _creatorUtils.isServiceSupported(name)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** @internal open (or reuse) the host session; throws when the host has no channel/factory. */
|
|
34
|
+
private _ensureOpen(): number {
|
|
35
|
+
if (this._serviceId !== null) return this._serviceId
|
|
36
|
+
if (typeof _creatorUtils === "undefined" || typeof _creatorUtils.serviceOpen !== "function") {
|
|
37
|
+
throw new Error(`Service("${this.serviceName}") is not supported on this host (requires _creatorUtils.serviceOpen)`)
|
|
38
|
+
}
|
|
39
|
+
const id = _creatorUtils.serviceOpen(this.serviceName, JSON.stringify(this.params ?? {}),
|
|
40
|
+
(event: string, dataJson?: string) => _channelEmit(this._listeners, event, dataJson))
|
|
41
|
+
if (id < 0) {
|
|
42
|
+
throw new Error(`Service("${this.serviceName}") is not registered on this host`)
|
|
43
|
+
}
|
|
44
|
+
this._serviceId = id
|
|
45
|
+
return id
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Invoke a method on the service (`geo.call("getCurrent", options)`). Args and the result are
|
|
49
|
+
* JSON-serialized. Rejects if the host has no such service/method. */
|
|
50
|
+
call(method: string, ...args: any[]): Promise<any> {
|
|
51
|
+
let id: number
|
|
52
|
+
try {
|
|
53
|
+
id = this._ensureOpen()
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return Promise.reject(e)
|
|
56
|
+
}
|
|
57
|
+
return _channelCall(
|
|
58
|
+
typeof _creatorUtils !== "undefined" ? _creatorUtils : undefined, "serviceCall",
|
|
59
|
+
`Service("${this.serviceName}") is not supported on this host (requires _creatorUtils.serviceCall)`,
|
|
60
|
+
id, method, args)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Subscribe to an event the service emits (`geo.on("position", cb)`). */
|
|
64
|
+
on(event: string, callback: (data?: any) => void): this {
|
|
65
|
+
_channelOn(this._listeners, event, callback)
|
|
66
|
+
return this
|
|
67
|
+
}
|
|
68
|
+
off(event: string, callback: (data?: any) => void): this {
|
|
69
|
+
_channelOff(this._listeners, event, callback)
|
|
70
|
+
return this
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Close the host session (stops whatever the service was doing). Listeners stay registered —
|
|
74
|
+
* a later `call()` opens a fresh session that delivers to them again. */
|
|
75
|
+
close(): void {
|
|
76
|
+
if (this._serviceId === null) return
|
|
77
|
+
const id = this._serviceId
|
|
78
|
+
this._serviceId = null
|
|
79
|
+
if (typeof _creatorUtils !== "undefined" && typeof _creatorUtils.serviceClose === "function") {
|
|
80
|
+
_creatorUtils.serviceClose(id)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Share a media File (image/video) via the OS share sheet. Wraps `_creatorUtils.shareMedia`, which
|
|
2
|
+
// takes the host buffer id behind the File. On iOS the sheet also lets the user save to Files/Photos;
|
|
3
|
+
// on web it saves (downloads) the file. `text` is an optional caption/message.
|
|
4
|
+
|
|
5
|
+
import type { File, FetchResponse } from "./fetch"
|
|
6
|
+
|
|
7
|
+
export function share(media: File | FetchResponse, text?: string): void {
|
|
8
|
+
_creatorUtils.shareMedia((media as any)._id, text)
|
|
9
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Key/value persistence backed by the host. Same shape as the web localStorage subset projects use.
|
|
2
|
+
|
|
3
|
+
export const localStorage = {
|
|
4
|
+
getItem(key: string): string | null {
|
|
5
|
+
return _creatorUtils.localStorageGetValue(key)
|
|
6
|
+
},
|
|
7
|
+
setItem(key: string, value: string): void {
|
|
8
|
+
_creatorUtils.localStorageSetValue(key, value)
|
|
9
|
+
},
|
|
10
|
+
removeItem(key: string): void {
|
|
11
|
+
_creatorUtils.localStorageRemoveValue(key)
|
|
12
|
+
},
|
|
13
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// Pointer event objects delivered to 3D scene/node touch handlers. Ported from worker/src/TouchEvent.ts.
|
|
2
|
+
// The dispatch wiring (reading the active scene, hit entity) is set up in Phase 4 with the 3D Scene;
|
|
3
|
+
// these classes are the injected globals user code receives. `target` is the hit Node (typed loosely
|
|
4
|
+
// here to avoid a forward dependency on gl/).
|
|
5
|
+
|
|
6
|
+
export class ClickEvent<T = any> {
|
|
7
|
+
readonly type = "click"
|
|
8
|
+
readonly pointerId: number
|
|
9
|
+
readonly clientX: number
|
|
10
|
+
readonly clientY: number
|
|
11
|
+
/** World-space hit position (2D scenes only; undefined in 3D). */
|
|
12
|
+
worldX?: number
|
|
13
|
+
worldY?: number
|
|
14
|
+
target!: T
|
|
15
|
+
constructor(clientX: number, clientY: number, pointerId: number) {
|
|
16
|
+
this.clientX = clientX
|
|
17
|
+
this.clientY = clientY
|
|
18
|
+
this.pointerId = pointerId
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface TrackHandler {
|
|
23
|
+
onMove?(pos: { clientX: number, clientY: number, deltaX: number, deltaY: number }): void
|
|
24
|
+
onEnd?(pos: { clientX: number, clientY: number, deltaX: number, deltaY: number }): void
|
|
25
|
+
onCancel?(): void
|
|
26
|
+
claim?: boolean | "pan-x" | "pan-y" | "pan-down" | "pan-up" | "pan-left" | "pan-right"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class TouchStartEvent<T = any> {
|
|
30
|
+
readonly type = "touchstart"
|
|
31
|
+
readonly pointerId: number
|
|
32
|
+
readonly clientX: number
|
|
33
|
+
readonly clientY: number
|
|
34
|
+
/** World-space hit position (2D scenes only; undefined in 3D). */
|
|
35
|
+
worldX?: number
|
|
36
|
+
worldY?: number
|
|
37
|
+
target!: T
|
|
38
|
+
/** @internal — read by the dispatch system after the handler runs. */
|
|
39
|
+
_trackHandler?: TrackHandler
|
|
40
|
+
constructor(clientX: number, clientY: number, pointerId: number) {
|
|
41
|
+
this.clientX = clientX
|
|
42
|
+
this.clientY = clientY
|
|
43
|
+
this.pointerId = pointerId
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Capture subsequent moves/end for this pointer (drag/pan gestures). */
|
|
47
|
+
track(handler: TrackHandler): void {
|
|
48
|
+
this._trackHandler = handler
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Delivered to a `UIButton` `onLongPress` handler when a finger is held on the button past the
|
|
53
|
+
* host's long-press threshold. Like `TouchStartEvent` it can `track(...)` the rest of the gesture —
|
|
54
|
+
* so the common "press-and-hold, then drag to move" flow is one handler: start the drag from here. */
|
|
55
|
+
export class LongPressEvent<T = any> {
|
|
56
|
+
readonly type = "longpress"
|
|
57
|
+
readonly pointerId: number
|
|
58
|
+
readonly clientX: number
|
|
59
|
+
readonly clientY: number
|
|
60
|
+
/** World-space hit position (2D scenes only; undefined in 3D). */
|
|
61
|
+
worldX?: number
|
|
62
|
+
worldY?: number
|
|
63
|
+
target!: T
|
|
64
|
+
/** @internal — read by the dispatch system after the handler runs. */
|
|
65
|
+
_trackHandler?: TrackHandler
|
|
66
|
+
constructor(clientX: number, clientY: number, pointerId: number) {
|
|
67
|
+
this.clientX = clientX
|
|
68
|
+
this.clientY = clientY
|
|
69
|
+
this.pointerId = pointerId
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Capture subsequent moves/end for this pointer — drag the element after the hold. */
|
|
73
|
+
track(handler: TrackHandler): void {
|
|
74
|
+
this._trackHandler = handler
|
|
75
|
+
}
|
|
76
|
+
}
|