lecodes-sdk 1.1.0 → 2.0.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 +5 -2
- package/dist/editor.d.ts +12 -0
- package/dist/global.d.ts +4 -8
- package/dist/host.d.ts +2 -3
- package/dist/types/animate/tween/Animation.d.ts +0 -3
- package/dist/types/animate/tween/animateValue.d.ts +4 -2
- package/dist/types/animate/tween/easing.d.ts +8 -0
- package/dist/types/animate/tween/spec.d.ts +15 -7
- package/dist/types/audio/audio.d.ts +2 -1
- package/dist/types/canvas/Canvas.d.ts +40 -108
- package/dist/types/canvas/gen/cssColor.d.ts +17 -0
- package/dist/types/canvas/gen/recorder.d.ts +118 -0
- package/dist/types/canvas/gen/spec.d.ts +144 -0
- package/dist/types/core/color.d.ts +3 -1
- package/dist/types/core/pins.d.ts +18 -0
- package/dist/types/g2/Node2D.d.ts +5 -8
- package/dist/types/g2/Scene2D.d.ts +6 -2
- package/dist/types/gl/Foliage.d.ts +30 -7
- package/dist/types/gl/Light.d.ts +11 -0
- package/dist/types/gl/Lightmap.d.ts +13 -2
- package/dist/types/gl/Material.d.ts +17 -3
- package/dist/types/gl/Model.d.ts +6 -2
- package/dist/types/gl/Node.d.ts +3 -6
- package/dist/types/gl/Particles.d.ts +15 -0
- package/dist/types/gl/Scene.d.ts +41 -13
- package/dist/types/gl/Texture.d.ts +1 -1
- package/dist/types/gl/animation/Locomotion.d.ts +59 -3
- package/dist/types/gl/terrain/Terrain.d.ts +8 -0
- package/dist/types/inject.d.ts +11 -11
- package/dist/types/inject.editor.d.ts +1 -0
- package/dist/types/net/core.d.ts +7 -0
- package/dist/types/plugin.d.ts +76 -0
- package/dist/types/plugins/gen/camera/sdk/camera.d.ts +24 -0
- package/dist/types/plugins/gen/camera/sdk/camera.gen.d.ts +25 -0
- package/dist/types/plugins/{geolocation.d.ts → gen/geolocation/sdk/geolocation.d.ts} +2 -20
- package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +31 -0
- package/dist/types/plugins/{map.d.ts → gen/map/sdk/map.d.ts} +9 -50
- package/dist/types/plugins/gen/map/sdk/map.gen.d.ts +53 -0
- package/dist/types/plugins/gen/push/sdk/push.d.ts +23 -0
- package/dist/types/plugins/gen/push/sdk/push.gen.d.ts +35 -0
- package/dist/types/plugins/{qr.d.ts → gen/qr-scanner/sdk/qr-scanner.d.ts} +2 -3
- package/dist/types/plugins/gen/qr-scanner/sdk/qr-scanner.gen.d.ts +15 -0
- package/dist/types/runtime/app.d.ts +9 -2
- package/dist/types/runtime/fetch.d.ts +2 -0
- package/dist/types/runtime/input.d.ts +8 -1
- package/dist/types/runtime/media.d.ts +6 -10
- package/dist/types/runtime/misc.d.ts +4 -1
- package/dist/types/runtime/net.d.ts +3 -2
- package/dist/types/runtime/touch.d.ts +32 -0
- package/dist/types/scene/defineScene.d.ts +43 -2
- package/dist/types/scene/editor.d.ts +52 -0
- package/dist/types/scene/gizmos.d.ts +7 -4
- package/dist/types/ui/NativeView.d.ts +6 -4
- package/dist/types/ui/UI.d.ts +1 -1
- package/dist/types/ui/UIBottomSheet.d.ts +6 -12
- package/dist/types/ui/UIButton.d.ts +12 -14
- package/dist/types/ui/UIContainer.d.ts +0 -6
- package/dist/types/ui/UIImage.d.ts +1 -4
- package/dist/types/ui/UIInput.d.ts +8 -24
- package/dist/types/ui/UIModal.d.ts +0 -2
- package/dist/types/ui/UINode.d.ts +70 -56
- package/dist/types/ui/UIPager.d.ts +28 -27
- package/dist/types/ui/UIPopover.d.ts +0 -2
- package/dist/types/ui/UIScreen.d.ts +12 -17
- package/dist/types/ui/UIScrollable.d.ts +1 -4
- package/dist/types/ui/UIText.d.ts +0 -2
- package/dist/types/ui/UIVideo.d.ts +3 -5
- package/dist/types/ui/UIVirtualizedList.d.ts +14 -16
- package/dist/types/ui/UIWidget.d.ts +10 -9
- package/dist/types/ui/colorKeys.gen.d.ts +9 -0
- package/dist/types/ui/presentable.d.ts +46 -32
- package/dist/types/ui/router.d.ts +18 -7
- package/dist/types/ui/styleColor.d.ts +1 -0
- package/dist/types/ui/transitions.d.ts +18 -0
- package/dist/types/ui/tree.d.ts +75 -0
- package/dist/types/version.d.ts +10 -0
- package/dist/types.json +1 -1
- package/package.json +12 -3
- package/prompts/2d.md +2 -6
- package/prompts/3d.md +1 -5
- package/prompts/README.md +142 -142
- package/prompts/canvas.md +9 -8
- package/prompts/compose.ts +1 -1
- package/prompts/core.md +3 -3
- package/prompts/design.md +1 -1
- package/prompts/dist/2d-game.md +77 -54
- package/prompts/dist/3d-app.md +75 -52
- package/prompts/dist/ar-app.md +74 -51
- package/prompts/dist/design.md +30 -8
- package/prompts/dist/ui-app.md +73 -46
- package/prompts/ui-design.md +2 -3
- package/prompts/ui.md +45 -37
- package/src/animate/tween/Animation.ts +262 -378
- package/src/animate/tween/animateValue.ts +103 -100
- package/src/animate/tween/easing.ts +10 -3
- package/src/animate/tween/spec.ts +505 -479
- package/src/audio/Sound.ts +3 -3
- package/src/audio/audio.ts +162 -161
- package/src/bridges/2d.d.ts +317 -0
- package/src/bridges/app.d.ts +91 -0
- package/src/bridges/audio.d.ts +97 -0
- package/src/bridges/canvas.d.ts +79 -0
- package/src/bridges/device.d.ts +72 -0
- package/src/bridges/fetch.d.ts +80 -0
- package/src/bridges/files.d.ts +70 -0
- package/src/bridges/gl.d.ts +1133 -0
- package/src/bridges/input.d.ts +72 -0
- package/src/bridges/media.d.ts +55 -0
- package/src/bridges/nav.d.ts +71 -0
- package/src/bridges/net.d.ts +52 -0
- package/src/bridges/service.d.ts +52 -0
- package/src/bridges/socket.d.ts +31 -0
- package/src/bridges/storage.d.ts +33 -0
- package/src/bridges/tree.d.ts +301 -0
- package/src/bridges/types.d.ts +49 -0
- package/src/canvas/Canvas.ts +114 -159
- package/src/canvas/gen/cssColor.ts +224 -0
- package/src/canvas/gen/recorder.ts +212 -0
- package/src/canvas/gen/spec.ts +201 -0
- package/src/chisel.ts +193 -0
- package/src/compile/assetMacro.ts +1 -1
- package/src/compile/bundler.ts +11 -2
- package/src/compile/compileProject.ts +43 -4
- package/src/compile/fontMacro.ts +3 -4
- package/src/compile/header.ts +26 -5
- package/src/compile/index.ts +3 -1
- package/src/compile/liteMaterial.ts +1 -1
- package/src/compile/sceneEditor.ts +11 -26
- package/src/core/color.ts +73 -30
- package/src/core/pins.ts +51 -0
- package/src/core/signals.ts +8 -1
- package/src/g2/CharacterController2D.ts +3 -3
- package/src/g2/Node2D.ts +57 -39
- package/src/g2/Physics2D.ts +2 -2
- package/src/g2/Scene2D.ts +35 -23
- package/src/g2/Texture2D.ts +1 -1
- package/src/g2/loop.ts +4 -4
- package/src/gl/Foliage.ts +72 -17
- package/src/gl/Geometry.ts +1 -2
- package/src/gl/Light.ts +13 -0
- package/src/gl/Lightmap.ts +45 -29
- package/src/gl/Material.ts +95 -51
- package/src/gl/Model.ts +172 -167
- package/src/gl/Node.ts +91 -24
- package/src/gl/Particles.ts +20 -1
- package/src/gl/Scene.ts +100 -44
- package/src/gl/Texture.ts +8 -7
- package/src/gl/animation/AnimationClip.ts +1 -1
- package/src/gl/animation/Locomotion.ts +77 -8
- package/src/gl/nav/NavMesh.ts +3 -4
- package/src/gl/physics/Physics.ts +2 -2
- package/src/gl/physics/physicsEvents.ts +3 -3
- package/src/gl/terrain/Terrain.ts +33 -5
- package/src/gl/touch.ts +14 -15
- package/src/host.d.ts +2 -3
- package/src/inject.editor.ts +7 -0
- package/src/inject.ts +233 -236
- package/src/net/core.ts +6 -5
- package/src/net/index.ts +1 -1
- package/src/net/replication.ts +1 -1
- package/src/plugin.ts +191 -0
- package/src/plugins/gen/camera/contract.d.ts +27 -0
- package/src/plugins/gen/camera/sdk/camera.gen.ts +46 -0
- package/src/plugins/gen/camera/sdk/camera.ts +57 -0
- package/src/plugins/gen/geolocation/contract.d.ts +50 -0
- package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +54 -0
- package/src/plugins/{geolocation.ts → gen/geolocation/sdk/geolocation.ts} +22 -43
- package/src/plugins/gen/map/contract.d.ts +144 -0
- package/src/plugins/gen/map/sdk/map.gen.ts +88 -0
- package/src/plugins/{map.ts → gen/map/sdk/map.ts} +68 -102
- package/src/plugins/gen/push/contract.d.ts +61 -0
- package/src/plugins/gen/push/sdk/push.gen.ts +60 -0
- package/src/plugins/gen/push/sdk/push.ts +105 -0
- package/src/plugins/gen/qr-scanner/contract.d.ts +16 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.gen.ts +29 -0
- package/src/plugins/gen/qr-scanner/sdk/qr-scanner.ts +52 -0
- package/src/plugins/permission.ts +5 -4
- package/src/runtime/app.ts +20 -9
- package/src/runtime/appEvents.ts +5 -4
- package/src/runtime/channel.ts +18 -15
- package/src/runtime/clipboard.ts +4 -3
- package/src/runtime/datetime.ts +2 -1
- package/src/runtime/device.ts +17 -15
- package/src/runtime/fetch.ts +30 -20
- package/src/runtime/files.ts +16 -15
- package/src/runtime/input.ts +21 -8
- package/src/runtime/media.ts +50 -46
- package/src/runtime/misc.ts +7 -3
- package/src/runtime/net.ts +8 -7
- package/src/runtime/rpc.ts +1 -3
- package/src/runtime/service.ts +19 -14
- package/src/runtime/share.ts +4 -3
- package/src/runtime/storage.ts +6 -4
- package/src/runtime/touch.ts +32 -0
- package/src/scene/defineScene.ts +61 -365
- package/src/scene/editor.ts +408 -0
- package/src/scene/editorPlugins.ts +3 -3
- package/src/scene/gizmos.ts +154 -148
- package/src/server/db/marci/query.ts +1 -1
- package/src/server/host.ts +1 -1
- package/src/server/runtime.ts +1 -1
- package/src/ui/NativeView.ts +72 -25
- package/src/ui/UI.ts +3 -3
- package/src/ui/UIBottomSheet.ts +16 -17
- package/src/ui/UIButton.ts +54 -16
- package/src/ui/UIContainer.ts +0 -6
- package/src/ui/UIImage.ts +34 -30
- package/src/ui/UIInput.ts +29 -37
- package/src/ui/UIModal.ts +1 -3
- package/src/ui/UINode.ts +347 -297
- package/src/ui/UIPager.ts +93 -78
- package/src/ui/UIPopover.ts +0 -2
- package/src/ui/UIScreen.ts +41 -36
- package/src/ui/UIScrollable.ts +19 -13
- package/src/ui/UISpacer.ts +1 -1
- package/src/ui/UITabs.ts +8 -6
- package/src/ui/UIText.ts +7 -17
- package/src/ui/UIVideo.ts +30 -27
- package/src/ui/UIVirtualizedList.ts +58 -59
- package/src/ui/UIWidget.ts +38 -18
- package/src/ui/colorKeys.gen.ts +37 -0
- package/src/ui/fonts.ts +2 -2
- package/src/ui/presentable.ts +59 -42
- package/src/ui/router.ts +52 -35
- package/src/ui/styleColor.ts +56 -0
- package/src/ui/theme.ts +7 -6
- package/src/ui/transitions.ts +249 -0
- package/src/ui/tree.ts +346 -0
- package/src/version.ts +24 -0
- package/tests/helpers/engineWorld.ts +11 -0
- package/tests/helpers/fakeTree.ts +353 -0
- package/tests/helpers/hostStubs.ts +31 -0
- package/tests/helpers/index.ts +14 -0
- package/tests/helpers/memoryMarci.ts +124 -0
- package/tests/helpers/phases.ts +23 -0
- package/tests/helpers/preload.ts +18 -0
- package/tests/helpers/stubApp.ts +2 -0
- package/tests/helpers/stubDevice.ts +2 -0
- package/tests/helpers/stubFetch.ts +2 -0
- package/tests/helpers/stubInput.ts +2 -0
- package/dist/inject.js +0 -4730
- package/dist/types/core/registry.d.ts +0 -7
- package/dist/types/plugins/camera.d.ts +0 -25
- package/dist/types/plugins/push.d.ts +0 -46
- package/src/bridges.d.ts +0 -1760
- package/src/compile/__tests__/assetIconMacro.test.ts +0 -219
- package/src/compile/__tests__/assetMacro.test.ts +0 -100
- package/src/compile/__tests__/assetName.test.ts +0 -55
- package/src/compile/__tests__/compile.test.ts +0 -310
- package/src/compile/__tests__/detectEntry.test.ts +0 -151
- package/src/compile/__tests__/fontMacro.test.ts +0 -199
- package/src/compile/__tests__/serverSplit.test.ts +0 -27
- package/src/core/__tests__/stateMachine.test.ts +0 -132
- package/src/core/registry.ts +0 -23
- package/src/plugins/camera.ts +0 -81
- package/src/plugins/push.ts +0 -132
- package/src/plugins/qr.ts +0 -73
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/push/sdk/push.ts — do not edit here.
|
|
2
|
+
// Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
|
|
3
|
+
// Push — remote notifications. The host owns the OS token, the permission prompt and the
|
|
4
|
+
// registration with the le.codes relay; the app only ever sees the opaque project-scoped ADDRESS
|
|
5
|
+
// (never the raw APNs / FCM token) plus payloads.
|
|
6
|
+
//
|
|
7
|
+
// Hand-written over the generated wire because the public API is a module singleton with
|
|
8
|
+
// `addEventListener`, and a listener must OPEN the session: events arrive on a live one only.
|
|
9
|
+
//
|
|
10
|
+
// The app is not running when a push arrives — display is host / OS work. The JS world sees
|
|
11
|
+
// exactly three delivery paths:
|
|
12
|
+
// - event "message": arrived while this app was foregrounded (the host suppresses the banner)
|
|
13
|
+
// - event "tap": tapped while the world was alive (warm)
|
|
14
|
+
// - getLaunch(): the notification that cold-started this world (stable pull, or null)
|
|
15
|
+
// A payload's `url` additionally rides the app-lifecycle deep-link path (`app.launchUrl` /
|
|
16
|
+
// the "url" event), so apps that already route links get notification routing for free.
|
|
17
|
+
//
|
|
18
|
+
// The OS permission prompt happens inside `register()`; `getStatus()` and `getLaunch()` never
|
|
19
|
+
// prompt. On a host without the service every method rejects at once, BEFORE any permission path.
|
|
20
|
+
|
|
21
|
+
import { NAME, PushBase } from "./push.gen"
|
|
22
|
+
import type { PushEvents, PushPayload, PushRegisterOptions, PushRegistration, PushStatus } from "../contract"
|
|
23
|
+
|
|
24
|
+
export type { PushPayload, PushRegisterOptions, PushStatus }
|
|
25
|
+
|
|
26
|
+
export type PushEvent = keyof PushEvents
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Remote push notifications. `Push.isSupported` reports whether this host registered the
|
|
30
|
+
* service — check it before offering notification features.
|
|
31
|
+
*/
|
|
32
|
+
// PURE IIFE so an app that never uses push tree-shakes the whole plugin away.
|
|
33
|
+
export const Push: {
|
|
34
|
+
/** Whether this host registered a "push" service. */
|
|
35
|
+
readonly isSupported: boolean
|
|
36
|
+
/** Permission + registration state. Never prompts. */
|
|
37
|
+
getStatus(): Promise<PushStatus>
|
|
38
|
+
/** Ask for permission (first time) and register this device → the opaque address your server
|
|
39
|
+
* targets. Idempotent — safe to call every launch; rejects "denied" / "unavailable". */
|
|
40
|
+
register(options?: PushRegisterOptions): Promise<PushRegistration>
|
|
41
|
+
/** Remove this device's registration (the address stops receiving). */
|
|
42
|
+
unregister(): Promise<void>
|
|
43
|
+
/** The notification that cold-started this app run, or null. Stable across calls. */
|
|
44
|
+
getLaunch(): Promise<PushPayload | null>
|
|
45
|
+
/** "message" = received while the app is open; "tap" = the user tapped one while it ran. */
|
|
46
|
+
addEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void
|
|
47
|
+
removeEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void
|
|
48
|
+
} = /*#__PURE__*/ (() => {
|
|
49
|
+
let client: PushBase | null = null
|
|
50
|
+
const service = (): PushBase => client ??= new PushBase()
|
|
51
|
+
|
|
52
|
+
const assertSupported = (): void => {
|
|
53
|
+
// Fail before the host is touched — a permission dialog for a feature that can't exist
|
|
54
|
+
// would be worse than the error.
|
|
55
|
+
if (!PushBase.supports(NAME)) {
|
|
56
|
+
throw new Error('Push isn\'t available on this host — it needs a registered "push" service.')
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// `on` only records listeners; events need a live session to arrive on. Poke the host with the
|
|
61
|
+
// prompt-free getStatus once so a listener-only app (already registered on a previous run)
|
|
62
|
+
// still hears warm taps.
|
|
63
|
+
let pipeOpened = false
|
|
64
|
+
const ensurePipe = (): void => {
|
|
65
|
+
if (pipeOpened || !PushBase.supports(NAME)) return
|
|
66
|
+
pipeOpened = true
|
|
67
|
+
service().getStatus().catch(() => { pipeOpened = false })
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const push = {
|
|
71
|
+
async getStatus(): Promise<PushStatus> {
|
|
72
|
+
assertSupported()
|
|
73
|
+
return await service().getStatus()
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
async register(options?: PushRegisterOptions): Promise<PushRegistration> {
|
|
77
|
+
assertSupported()
|
|
78
|
+
return await service().register(options ?? {})
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async unregister(): Promise<void> {
|
|
82
|
+
assertSupported()
|
|
83
|
+
await service().unregister()
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async getLaunch(): Promise<PushPayload | null> {
|
|
87
|
+
assertSupported()
|
|
88
|
+
return (await service().getLaunch()) ?? null
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
addEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void {
|
|
92
|
+
// Recording on an unsupported host is a silent no-op (the events just never fire),
|
|
93
|
+
// like app / device listeners — only the promise-returning methods reject loudly.
|
|
94
|
+
service().on(event, callback)
|
|
95
|
+
ensurePipe()
|
|
96
|
+
},
|
|
97
|
+
removeEventListener(event: PushEvent, callback: (payload: PushPayload) => void): void {
|
|
98
|
+
service().off(event, callback)
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
Object.defineProperty(push, "isSupported", {
|
|
102
|
+
get: (): boolean => PushBase.supports(NAME),
|
|
103
|
+
})
|
|
104
|
+
return push as any
|
|
105
|
+
})()
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/qr-scanner/contract.d.ts — do not edit here.
|
|
2
|
+
// Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
|
|
3
|
+
// qr-scanner — a camera view that decodes QR codes.
|
|
4
|
+
// A view with events only: the app opens / embeds it and listens.
|
|
5
|
+
|
|
6
|
+
import type { View } from "../../../plugin"
|
|
7
|
+
|
|
8
|
+
export interface QRScannerParams {}
|
|
9
|
+
|
|
10
|
+
export interface QRScannerEvents {
|
|
11
|
+
/** One per decoded camera frame: the payload, or null for a frame with no readable code (it
|
|
12
|
+
* repeats while the user aims). The same code may be reported more than once. */
|
|
13
|
+
scan: { data: string | null }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface QRScanner extends View<"qrScanner", QRScannerParams, QRScannerEvents> {}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/qr-scanner/sdk/qr-scanner.gen.ts — do not edit here.
|
|
2
|
+
// Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
|
|
3
|
+
// GENERATED by `lecodes plugin gen` from contract.d.ts — do not edit.
|
|
4
|
+
// qr-scanner: the view "qrScanner", contract version 1.
|
|
5
|
+
|
|
6
|
+
import { ViewChannel } from "../../../../plugin"
|
|
7
|
+
import type { QRScannerEvents, QRScannerParams } from "../contract"
|
|
8
|
+
|
|
9
|
+
/** What the halves register under. */
|
|
10
|
+
export const NAME = "qrScanner"
|
|
11
|
+
/** The contract's version: the highest `@since` of its members. */
|
|
12
|
+
export const VERSION = 1
|
|
13
|
+
|
|
14
|
+
// ---- what the host's half has --------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/** A method of the contract. */
|
|
17
|
+
export type Method = never
|
|
18
|
+
|
|
19
|
+
/** Whether this host has the view "qrScanner" — and, given a method, a half new enough for it
|
|
20
|
+
* (a method added after the first version of the contract is tagged `@since`). */
|
|
21
|
+
export const supports = (method?: Method): boolean =>
|
|
22
|
+
ViewChannel.supports(NAME) // every method is of version 1
|
|
23
|
+
|
|
24
|
+
// ---- the element ----------------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/** The view "qrScanner" with the wire's methods as its own. */
|
|
27
|
+
export class QRScannerBase extends ViewChannel<QRScannerEvents> {
|
|
28
|
+
constructor(params: QRScannerParams = {}) { super("QRScanner", NAME, params) }
|
|
29
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/qr-scanner/sdk/qr-scanner.ts — do not edit here.
|
|
2
|
+
// Change it in the plugin repo and run `bun run vendor:plugins`; `bun run check:plugins` fails when this copy is stale.
|
|
3
|
+
// QRScanner — the host's camera QR scanner as a view the app opens fullscreen
|
|
4
|
+
// (`await scanner.open()`), pushes (`Router.push(scanner)`) or embeds as a live preview.
|
|
5
|
+
//
|
|
6
|
+
// Hand-written over the generated wire for two things: `onScan` hands over the decoded string, not
|
|
7
|
+
// the event's payload, and the permission is asked BEFORE the view is presented.
|
|
8
|
+
|
|
9
|
+
import { prepareCamera, type NativeView, type PresentOptions } from "../../../../plugin"
|
|
10
|
+
import { NAME, QRScannerBase } from "./qr-scanner.gen"
|
|
11
|
+
|
|
12
|
+
export interface QRScanner extends NativeView {
|
|
13
|
+
/** Fires per decoded camera frame: the decoded string, or `null` for a frame with no readable
|
|
14
|
+
* code (expect those repeatedly while the user aims). The same code can be reported more than
|
|
15
|
+
* once — `close()` or debounce once you have what you need. */
|
|
16
|
+
onScan(callback: (data: string | null) => void): this
|
|
17
|
+
/** Request the camera permission, then present the scanner. The current destination (and its
|
|
18
|
+
* loading state) stays visible until the scanner is ready; rejects if the camera is denied,
|
|
19
|
+
* the host can't scan, or another navigation superseded this one. */
|
|
20
|
+
open(options?: PresentOptions): Promise<void>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class QRScannerElement extends QRScannerBase {
|
|
24
|
+
onScan(callback: (data: string | null) => void): this {
|
|
25
|
+
return this.on("scan", payload => callback(payload?.data ?? null))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** @internal prepare-then-present (see Presentable._prepare). */
|
|
29
|
+
_prepare(): Promise<void> { return prepareCamera(this) }
|
|
30
|
+
|
|
31
|
+
override async open(options?: PresentOptions): Promise<void> {
|
|
32
|
+
await this._prepare()
|
|
33
|
+
super.open(options)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create a QR scanner view. `QRScanner.isSupported` reports whether this host can scan at all —
|
|
39
|
+
* check it before offering the feature.
|
|
40
|
+
*/
|
|
41
|
+
// PURE IIFE so an app that never scans tree-shakes the whole plugin away.
|
|
42
|
+
export const QRScanner: {
|
|
43
|
+
(): QRScanner
|
|
44
|
+
/** Whether this host registered a "qrScanner" view. */
|
|
45
|
+
readonly isSupported: boolean
|
|
46
|
+
} = /*#__PURE__*/ (() => {
|
|
47
|
+
const factory = (): QRScanner => new QRScannerElement() as unknown as QRScanner
|
|
48
|
+
Object.defineProperty(factory, "isSupported", {
|
|
49
|
+
get: (): boolean => QRScannerBase.supports(NAME),
|
|
50
|
+
})
|
|
51
|
+
return factory as any
|
|
52
|
+
})()
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// Camera permission, host-gated: hosts expose it as `
|
|
2
|
-
// that
|
|
1
|
+
// Camera permission, host-gated: hosts expose it as `_creatorDevice.requestCamera`. Hosts without
|
|
2
|
+
// that slot resolve immediately — their view factory prompts on its own (e.g. getUserMedia on
|
|
3
3
|
// the web). Shared by ARScene.prepare, QRScanner and CameraView.
|
|
4
|
+
|
|
4
5
|
export const _requestCameraPermission = (): Promise<void> =>
|
|
5
|
-
typeof
|
|
6
|
-
? new Promise<void>((res, rej) =>
|
|
6
|
+
typeof _creatorDevice.requestCamera === "function"
|
|
7
|
+
? new Promise<void>((res, rej) => _creatorDevice.requestCamera!(res, rej))
|
|
7
8
|
: Promise.resolve()
|
package/src/runtime/app.ts
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
// "pause"/"resume", never Activity/Scene vocabulary — and the current values are synchronous pulls
|
|
3
3
|
// so render-loop code can read them without subscribing.
|
|
4
4
|
|
|
5
|
+
import { setAppBackHandler } from "../ui/tree"
|
|
5
6
|
import { appEventsOn, appEventsOff, keyboardEventsOn, keyboardEventsOff } from "./appEvents"
|
|
7
|
+
import { SDK_VERSION } from "../version"
|
|
6
8
|
|
|
7
9
|
export type AppState = "active" | "background"
|
|
8
10
|
|
|
@@ -26,15 +28,19 @@ type AppEventMap = {
|
|
|
26
28
|
}
|
|
27
29
|
|
|
28
30
|
export const app = {
|
|
31
|
+
/** The SDK this app was compiled with (semver, e.g. `"2.0.0"`). Its major is the bundle ↔ runtime
|
|
32
|
+
* contract: a host runs only bundles of its own major, and the launchers send it as `?sdk=` when
|
|
33
|
+
* they fetch a published bundle — the platform keeps one bundle per major. */
|
|
34
|
+
sdkVersion: SDK_VERSION,
|
|
29
35
|
/** Current lifecycle state. `"background"` while the app is not the foreground app / the tab is
|
|
30
36
|
* hidden. `"active"` on hosts that don't track it. */
|
|
31
37
|
get state(): AppState {
|
|
32
|
-
return
|
|
38
|
+
return _creatorApp.state?.() === "background" ? "background" : "active"
|
|
33
39
|
},
|
|
34
40
|
/** The URL the app was (most recently) opened with — the deep link — or `null` for a plain
|
|
35
41
|
* launch. Warm links update it and fire the `"url"` event. */
|
|
36
42
|
get launchUrl(): string | null {
|
|
37
|
-
return
|
|
43
|
+
return _creatorApp.launchUrl?.() ?? null
|
|
38
44
|
},
|
|
39
45
|
/** On-screen keyboard height in logical px currently overlapping the app viewport, `0` when
|
|
40
46
|
* hidden. This is the RAW overlap, independent of the focused input's `keyboardShrink` policy —
|
|
@@ -42,7 +48,7 @@ export const app = {
|
|
|
42
48
|
* mode (`keyboardShrink: false`), e.g. a chat composer's `pb`. `0` on hosts without an
|
|
43
49
|
* on-screen keyboard. */
|
|
44
50
|
get keyboardHeight(): number {
|
|
45
|
-
return
|
|
51
|
+
return (typeof _creatorInput !== "undefined" ? _creatorInput.keyboardHeight?.() : undefined) ?? 0
|
|
46
52
|
},
|
|
47
53
|
/** Lock the screen orientation — `"landscape"` for a horizontal game (the device rotates to
|
|
48
54
|
* landscape right away and stays there whichever way it is held), `"portrait"` for upright
|
|
@@ -51,29 +57,29 @@ export const app = {
|
|
|
51
57
|
* ordinary resize — layouts reflow, a scene's viewport follows. Silent no-op on hosts
|
|
52
58
|
* without a rotatable screen (desktop, headless, web). */
|
|
53
59
|
setOrientation(mode: OrientationLock): void {
|
|
54
|
-
|
|
60
|
+
_creatorApp.setOrientation?.(mode)
|
|
55
61
|
},
|
|
56
62
|
/** Re-run this app's bundle in a fresh world — the platform's `location.reload()`. Pass `url`
|
|
57
63
|
* to reboot with different launch arguments (the new run reads them via `app.launchUrl`),
|
|
58
64
|
* `null` to reboot with a cleared one, or omit it to replay the current one. No-op on hosts
|
|
59
65
|
* without world control (headless). */
|
|
60
66
|
restart(url?: string | null): void {
|
|
61
|
-
|
|
67
|
+
_creatorApp.restart?.(url)
|
|
62
68
|
},
|
|
63
69
|
/** Leave this app. Inside the LeCodes viewer that returns to the launcher (the LeCodes home
|
|
64
70
|
* screen); a standalone-built app leaves to the platform instead — Android backgrounds it,
|
|
65
71
|
* iOS ignores the call (the platform forbids programmatic exit). */
|
|
66
72
|
quit(): void {
|
|
67
|
-
|
|
73
|
+
_creatorApp.quit?.()
|
|
68
74
|
},
|
|
69
75
|
/** Global back-press fallback (Android back button/gesture — iOS has no system back). Fires
|
|
70
76
|
* only when nothing else consumed the press: no open widget/modal claimed it, the current
|
|
71
|
-
* screen has no `
|
|
77
|
+
* screen has no `onBack`, every pager is at its tab root and the router is at its
|
|
72
78
|
* stack root. The place for app-wide policy — e.g. double-press-to-quit:
|
|
73
79
|
*
|
|
74
80
|
* ```ts
|
|
75
81
|
* let armedAt = 0
|
|
76
|
-
* app.
|
|
82
|
+
* app.onBack(() => {
|
|
77
83
|
* if (Date.now() - armedAt < 2000) { app.quit(); return }
|
|
78
84
|
* armedAt = Date.now()
|
|
79
85
|
* toast("Press back again to exit")
|
|
@@ -81,8 +87,13 @@ export const app = {
|
|
|
81
87
|
* ```
|
|
82
88
|
*
|
|
83
89
|
* Replaces the previous handler; pass `null` to remove. */
|
|
90
|
+
onBack(callback: (() => void) | null): void {
|
|
91
|
+
setAppBackHandler(callback ?? null)
|
|
92
|
+
},
|
|
93
|
+
/** @deprecated Renamed `onBack` (2026-09-26). An alias for the projects compiled before the
|
|
94
|
+
* rename; removed with the release that recompiles them. */
|
|
84
95
|
onBackPressed(callback: (() => void) | null): void {
|
|
85
|
-
|
|
96
|
+
setAppBackHandler(callback ?? null)
|
|
86
97
|
},
|
|
87
98
|
addEventListener<E extends keyof AppEventMap>(event: E, callback: AppEventMap[E]): void {
|
|
88
99
|
if (event === "keyboard") {
|
package/src/runtime/appEvents.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
// The shared app-event dispatcher. The host exposes ONE
|
|
1
|
+
// The shared app-event dispatcher. The host exposes ONE `_creatorApp.registerEvent` slot per JS world
|
|
2
2
|
// (docs/app-lifecycle-plan.md), so every SDK consumer (the `app` global's pause/resume/url, the
|
|
3
3
|
// `device` global's online/offline) subscribes here and this module registers with the host once,
|
|
4
4
|
// lazily on the first subscription — no top-level side effect, tree-shaking drops it when unused.
|
|
5
5
|
|
|
6
|
+
|
|
6
7
|
type AppEventCallback = (data?: string) => void
|
|
7
8
|
|
|
8
9
|
const listeners: Record<string, AppEventCallback[]> = {}
|
|
@@ -17,7 +18,7 @@ export const appEventsOn = (event: string, cb: AppEventCallback): void => {
|
|
|
17
18
|
;(listeners[event] ??= []).push(cb)
|
|
18
19
|
if (!registered) {
|
|
19
20
|
registered = true
|
|
20
|
-
|
|
21
|
+
_creatorApp.registerEvent?.(dispatch)
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
|
|
@@ -28,7 +29,7 @@ export const appEventsOff = (event: string, cb: AppEventCallback): void => {
|
|
|
28
29
|
if (i >= 0) arr.splice(i, 1)
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
// The keyboard channel is a separate host slot (registerKeyboardEvent) — the
|
|
32
|
+
// The keyboard channel is a separate host slot (`_creatorInput.registerKeyboardEvent`) — the app-event
|
|
32
33
|
// string channel can't carry the (height, duration) number pair. Same lazy single-slot pattern.
|
|
33
34
|
|
|
34
35
|
type KeyboardCallback = (height: number, duration: number) => void
|
|
@@ -44,7 +45,7 @@ export const keyboardEventsOn = (cb: KeyboardCallback): void => {
|
|
|
44
45
|
keyboardListeners.push(cb)
|
|
45
46
|
if (!keyboardRegistered) {
|
|
46
47
|
keyboardRegistered = true
|
|
47
|
-
|
|
48
|
+
if (typeof _creatorInput !== "undefined") _creatorInput.registerKeyboardEvent?.(dispatchKeyboard)
|
|
48
49
|
}
|
|
49
50
|
}
|
|
50
51
|
|
package/src/runtime/channel.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
// The
|
|
2
|
-
// (`
|
|
3
|
-
// (`
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
1
|
+
// The wire protocol shared by the two host plugin channels — NativeView
|
|
2
|
+
// (`_creatorTree.viewCall`, see ui/NativeView.ts) and headless services
|
|
3
|
+
// (`_creatorService.call`, see runtime/service.ts). One protocol, two transports: `args`, results
|
|
4
|
+
// and event payloads travel as VALUES (`value` of bridges/types.d.ts — plain data and Uint8Array;
|
|
5
|
+
// the runtime reads them once, the host gets its platform's own), and what a host leaves out
|
|
6
|
+
// arrives as `undefined`. Only a view's / a service's PARAMS are JSON text: they are part of the
|
|
7
|
+
// tree (a node's property, a destination). Typed plugins (QRScanner, CameraView, Geolocation)
|
|
8
|
+
// layer methods over this — see src/plugins/.
|
|
7
9
|
|
|
8
10
|
/** @internal event-name → listeners. NativeView exposes it as `nvl` (host contract). */
|
|
9
11
|
export type ChannelListeners = Record<string, ((data?: any) => void)[]>
|
|
@@ -21,20 +23,21 @@ export const _channelOff = (listeners: ChannelListeners, event: string, callback
|
|
|
21
23
|
if (i >= 0) list.splice(i, 1)
|
|
22
24
|
}
|
|
23
25
|
|
|
24
|
-
/** @internal deliver a host event:
|
|
25
|
-
export const _channelEmit = (listeners: ChannelListeners, event: string,
|
|
26
|
+
/** @internal deliver a host event: fan out (copy-before-iterate). */
|
|
27
|
+
export const _channelEmit = (listeners: ChannelListeners, event: string, data?: unknown): void => {
|
|
26
28
|
const list = listeners[event]
|
|
27
29
|
if (!list || list.length === 0) return
|
|
28
|
-
const data = dataJson === undefined || dataJson === "" ? undefined : JSON.parse(dataJson)
|
|
29
30
|
for (const cb of [...list]) cb(data)
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
type ChannelCallBridge = (
|
|
33
|
-
id: number, method: string, args:
|
|
34
|
-
onComplete: (result?:
|
|
34
|
+
id: number, method: string, args: unknown[],
|
|
35
|
+
onComplete: (result?: unknown) => void, onError: (err: string) => void,
|
|
35
36
|
) => void
|
|
36
37
|
|
|
37
|
-
/** @internal one
|
|
38
|
+
/** @internal one call over a channel bridge. An argument that cannot cross (a cycle, a typed array
|
|
39
|
+
* that is not bytes) makes the bridge THROW: the promise rejects with it, the host never sees
|
|
40
|
+
* the call.
|
|
38
41
|
*
|
|
39
42
|
* Takes the HANDLER OBJECT plus the member name — never a plucked-off function reference. On
|
|
40
43
|
* Apple hosts the bridge members are JSExport-ed Objective-C/Swift methods, and JavaScriptCore
|
|
@@ -55,7 +58,7 @@ export const _channelCall = (
|
|
|
55
58
|
rej(new Error(unsupportedError))
|
|
56
59
|
return
|
|
57
60
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
+
// A host rejects with its message — as a string, or as the Error the runtime made of it (the
|
|
62
|
+
// Android host): the message is the same either way, never "Error: <message>".
|
|
63
|
+
bridge.call(owner, id, method, args, res, (err: unknown) => rej(typeof err === "string" ? new Error(err) : err))
|
|
61
64
|
})
|
package/src/runtime/clipboard.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
// System clipboard (docs/app-lifecycle-plan.md). Write is fire-and-forget; read is async because
|
|
2
2
|
// web gates it behind a permission prompt (native hosts complete synchronously).
|
|
3
3
|
|
|
4
|
+
|
|
4
5
|
export const clipboard = {
|
|
5
6
|
/** Put `text` on the system clipboard. Silent no-op on hosts without clipboard access. */
|
|
6
7
|
write(text: string): void {
|
|
7
|
-
|
|
8
|
+
_creatorDevice.clipboardWrite?.(text)
|
|
8
9
|
},
|
|
9
10
|
/** Read text from the system clipboard. Rejects when the platform has no clipboard access, the
|
|
10
11
|
* user denied it, or there is nothing readable. */
|
|
11
12
|
read(): Promise<string> {
|
|
12
13
|
return new Promise((resolve, reject) => {
|
|
13
|
-
if (typeof
|
|
14
|
+
if (typeof _creatorDevice.clipboardRead !== "function") {
|
|
14
15
|
reject(new Error("Clipboard is not supported on this platform"))
|
|
15
16
|
return
|
|
16
17
|
}
|
|
17
|
-
|
|
18
|
+
_creatorDevice.clipboardRead(resolve, (err: string) => reject(new Error(err)))
|
|
18
19
|
})
|
|
19
20
|
},
|
|
20
21
|
}
|
package/src/runtime/datetime.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// Even before that pass lands, each method is a class method, so chisel's method-granular DCE drops
|
|
16
16
|
// whichever ones a project doesn't use.
|
|
17
17
|
|
|
18
|
+
|
|
18
19
|
export type Locale = "en" | "ru"
|
|
19
20
|
export type DateInput = Date | number | string | DateValue
|
|
20
21
|
export type Unit = "ms" | "second" | "minute" | "hour" | "day" | "week" | "month" | "year"
|
|
@@ -58,7 +59,7 @@ const DATA: Record<Locale, LocaleData> = { en: EN, ru: RU }
|
|
|
58
59
|
|
|
59
60
|
const resolveLocale = (locale?: Locale): Locale => {
|
|
60
61
|
if (locale) return locale === "ru" ? "ru" : "en"
|
|
61
|
-
const lang = typeof
|
|
62
|
+
const lang = typeof _creatorDevice !== "undefined" ? _creatorDevice.language : ""
|
|
62
63
|
return lang && lang.toLowerCase().indexOf("ru") === 0 ? "ru" : "en"
|
|
63
64
|
}
|
|
64
65
|
|
package/src/runtime/device.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Platform info + the host resize event. The resize listener registers with the host lazily on the
|
|
2
2
|
// first subscription (no top-level side effect, so tree-shaking can drop this entirely if unused).
|
|
3
3
|
|
|
4
|
+
import { ensureEmitter, onTreeResize } from "../ui/tree"
|
|
4
5
|
import { Quat } from "../math/quat"
|
|
5
6
|
import { Vec3 } from "../math/vec"
|
|
6
7
|
import { appEventsOn, appEventsOff } from "./appEvents"
|
|
@@ -94,10 +95,10 @@ export const device = {
|
|
|
94
95
|
* mouse-look instead of on-screen sticks. It is not the OS name: a Mac reports `"desktop"`,
|
|
95
96
|
* like Windows and Linux do (the OS/version detail is host-side, not here). */
|
|
96
97
|
get platform(): "web" | "android" | "ios" | "desktop" | string {
|
|
97
|
-
return
|
|
98
|
+
return _creatorDevice.platform
|
|
98
99
|
},
|
|
99
100
|
get language(): string {
|
|
100
|
-
return
|
|
101
|
+
return _creatorDevice.language
|
|
101
102
|
},
|
|
102
103
|
/** Display device-pixel ratio (physical px per logical/CSS px): 1 on standard displays, 2–3 on
|
|
103
104
|
* retina / iOS. Bake a Canvas at this ratio for crisp output without hardcoding, e.g.
|
|
@@ -105,17 +106,17 @@ export const device = {
|
|
|
105
106
|
* renders at logical resolution, so on web this only helps UI canvases / is forward-looking for
|
|
106
107
|
* 2D; on iOS (physical surface) it makes 2D canvases crisp. Falls back to 1 if the host omits it. */
|
|
107
108
|
get pixelRatio(): number {
|
|
108
|
-
return
|
|
109
|
+
return _creatorDevice.pixelRatio || 1
|
|
109
110
|
},
|
|
110
111
|
/** Current display width in logical px — the same value the "resize" event delivers, but readable at
|
|
111
112
|
* any time (not only inside the listener). 0 until the host has reported a size. */
|
|
112
113
|
get width(): number {
|
|
113
|
-
return
|
|
114
|
+
return _creatorTree.getDisplaySize()[0] ?? 0
|
|
114
115
|
},
|
|
115
116
|
/** Current display height in logical px — the same value the "resize" event delivers, readable at any
|
|
116
117
|
* time. 0 until the host has reported a size. */
|
|
117
118
|
get height(): number {
|
|
118
|
-
return
|
|
119
|
+
return _creatorTree.getDisplaySize()[1] ?? 0
|
|
119
120
|
},
|
|
120
121
|
/** Opt into the precise-touch system where the platform supports it. OFF by default. When on, fast
|
|
121
122
|
* strokes are sampled at the touch digitizer's full rate (iOS coalesced touches, ≈120–240 Hz) instead
|
|
@@ -123,14 +124,14 @@ export const device = {
|
|
|
123
124
|
* points and smoother lines. Leave it off for tap/button UIs. No-op on hosts without a coalesced-input
|
|
124
125
|
* concept (web already coalesces pointermove; headless). */
|
|
125
126
|
setPreciseTouch(enabled: boolean): void {
|
|
126
|
-
|
|
127
|
+
_creatorDevice.setPreciseTouch?.(enabled)
|
|
127
128
|
},
|
|
128
129
|
/** Fire a one-shot haptic of the given semantic `style` (default `"medium"`) — a physical tap on
|
|
129
130
|
* supported hardware (iOS Taptic Engine, Android vibrator). Chosen by meaning, not duration, so it
|
|
130
131
|
* feels native on each platform; see {@link HapticStyle}. Host-gated: a silent no-op where there's
|
|
131
132
|
* no haptic hardware (iPad, older iPhones, web, headless). */
|
|
132
133
|
vibrate(style: HapticStyle = "medium"): void {
|
|
133
|
-
|
|
134
|
+
_creatorDevice.vibrate?.(style)
|
|
134
135
|
},
|
|
135
136
|
/** Device-orientation sensor (gyro + accelerometer, fused) for tilt/steering and magic-window /
|
|
136
137
|
* 360° panoramas. Poll `attitude` / `gravity` inside setLoop; they return the freshest fused
|
|
@@ -138,7 +139,7 @@ export const device = {
|
|
|
138
139
|
motion: {
|
|
139
140
|
/** Whether this device has the motion sensors at all (no gyro → false; iPad/older, web, headless). */
|
|
140
141
|
get available(): boolean {
|
|
141
|
-
return
|
|
142
|
+
return _creatorDevice.motionAvailable?.() ?? false
|
|
142
143
|
},
|
|
143
144
|
/** Whether updates are currently running (start succeeded and stop hasn't been called). */
|
|
144
145
|
get enabled(): boolean {
|
|
@@ -148,18 +149,18 @@ export const device = {
|
|
|
148
149
|
* Async so a web host can await its permission prompt; native resolves immediately. */
|
|
149
150
|
async start(options: MotionOptions = {}): Promise<boolean> {
|
|
150
151
|
motionFrame = options.frame ?? "world"
|
|
151
|
-
motionEnabled = !!(await (
|
|
152
|
+
motionEnabled = !!(await (_creatorDevice.motionStart?.(options.interval ?? 1 / 60) ?? false))
|
|
152
153
|
return motionEnabled
|
|
153
154
|
},
|
|
154
155
|
/** Stop sensor updates and release the sensor (battery). */
|
|
155
156
|
stop(): void {
|
|
156
|
-
|
|
157
|
+
_creatorDevice.motionStop?.()
|
|
157
158
|
motionEnabled = false
|
|
158
159
|
},
|
|
159
160
|
/** Capture the current heading as "forward" — a yaw-only recenter (pitch/roll stay gravity-
|
|
160
161
|
* referenced, so the horizon stays level). No-op in the "device" frame. */
|
|
161
162
|
recenter(): void {
|
|
162
|
-
const s =
|
|
163
|
+
const s = _creatorDevice.motionSample?.()
|
|
163
164
|
if (!s || motionFrame === "device") { motionYawOffset = 0; return }
|
|
164
165
|
const f = orientedAttitude(s).rotateVec3([0, 0, -1]) // world-space camera forward
|
|
165
166
|
motionYawOffset = Math.atan2(-f.x, -f.z) // heading about world up
|
|
@@ -168,7 +169,7 @@ export const device = {
|
|
|
168
169
|
* and screen-oriented, so `camera.quaternion = device.motion.attitude` is a complete magic-window
|
|
169
170
|
* / panorama camera. `Quat.identity` until the first sample arrives / when not running. */
|
|
170
171
|
get attitude(): Quat {
|
|
171
|
-
const s =
|
|
172
|
+
const s = _creatorDevice.motionSample?.()
|
|
172
173
|
if (!s) return Quat.identity
|
|
173
174
|
if (motionFrame === "device") return new Quat(s[0], s[1], s[2], s[3])
|
|
174
175
|
const q = orientedAttitude(s)
|
|
@@ -178,7 +179,7 @@ export const device = {
|
|
|
178
179
|
* down, matching clientX/clientY), orientation-aware — a 2D game reads `gravity.x / gravity.y`.
|
|
179
180
|
* `(0,0,0)` when not running. */
|
|
180
181
|
get gravity(): Vec3 {
|
|
181
|
-
const s =
|
|
182
|
+
const s = _creatorDevice.motionSample?.()
|
|
182
183
|
if (!s) return new Vec3(0, 0, 0)
|
|
183
184
|
if (motionFrame === "device") return new Vec3(s[4], s[5], s[6])
|
|
184
185
|
// Re-express the raw device-frame gravity in screen space: undo the interface roll (about the
|
|
@@ -250,7 +251,7 @@ export const device = {
|
|
|
250
251
|
* is sure there's no network. `true` on hosts that don't track it. Change events: `"online"` /
|
|
251
252
|
* `"offline"`. */
|
|
252
253
|
get online(): boolean {
|
|
253
|
-
return
|
|
254
|
+
return _creatorApp.isOnline?.() ?? true
|
|
254
255
|
},
|
|
255
256
|
addEventListener<E extends keyof DeviceEventMap>(channel: E, callback: DeviceEventMap[E]): void {
|
|
256
257
|
if (channel === "online" || channel === "offline") {
|
|
@@ -269,7 +270,8 @@ export const device = {
|
|
|
269
270
|
resizeListeners.push(callback as ResizeCallback)
|
|
270
271
|
if (!resizeRegistered) {
|
|
271
272
|
resizeRegistered = true
|
|
272
|
-
|
|
273
|
+
ensureEmitter()
|
|
274
|
+
onTreeResize(onResize)
|
|
273
275
|
}
|
|
274
276
|
},
|
|
275
277
|
removeEventListener<E extends keyof DeviceEventMap>(channel: E, callback: DeviceEventMap[E]): void {
|