lecodes-sdk 2.2.9 → 2.2.10
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/dist/global.d.ts +42 -1
- package/dist/types/gl/Camera.d.ts +5 -2
- package/dist/types/gl/Node.d.ts +0 -7
- package/dist/types/gl/Scene.d.ts +6 -24
- package/dist/types/inject.d.ts +5 -1
- package/dist/types/plugin.d.ts +27 -0
- package/dist/types/plugins/gen/ar/sdk/ar.d.ts +228 -0
- package/dist/types/plugins/gen/ar/sdk/ar.gen.d.ts +73 -0
- package/dist/types/plugins/gen/ar/sdk/controls.d.ts +28 -0
- package/dist/types/plugins/gen/audio-recorder/sdk/audio-recorder.d.ts +43 -0
- package/dist/types/plugins/gen/audio-recorder/sdk/audio-recorder.gen.d.ts +47 -0
- package/dist/types/plugins/gen/geolocation/sdk/geolocation.gen.d.ts +6 -2
- package/dist/types/plugins/gen/speech/sdk/speech.d.ts +44 -0
- package/dist/types/plugins/gen/speech/sdk/speech.gen.d.ts +39 -0
- package/dist/types/runtime/permissions.d.ts +16 -0
- package/dist/types/version.d.ts +1 -1
- package/dist/types.json +1 -1
- package/package.json +1 -1
- package/prompts/ar.md +81 -48
- package/prompts/dist/2d-game.md +1567 -0
- package/prompts/dist/3d-app.md +1712 -0
- package/prompts/dist/ar-app.md +1585 -0
- package/prompts/dist/concept.md +49 -0
- package/prompts/dist/design.md +521 -0
- package/prompts/dist/ui-app.md +1175 -0
- package/prompts/index.md +3 -1
- package/src/animate/tween/Timeline.ts +175 -175
- package/src/bridges/device.d.ts +20 -6
- package/src/bridges/gl.d.ts +33 -19
- package/src/compile/header.ts +11 -4
- package/src/gl/Camera.ts +5 -2
- package/src/gl/CameraPlace.ts +52 -52
- package/src/gl/Light.ts +257 -257
- package/src/gl/Mesh.ts +120 -120
- package/src/gl/Node.ts +0 -8
- package/src/gl/Scene.ts +14 -81
- package/src/gl/animation/DynamicBone.ts +482 -482
- package/src/gl/scenarios.ts +291 -291
- package/src/inject.ts +13 -1
- package/src/plugin.ts +45 -2
- package/src/plugins/gen/ar/contract.d.ts +118 -0
- package/src/plugins/gen/ar/sdk/ar.gen.ts +112 -0
- package/src/plugins/gen/ar/sdk/ar.ts +699 -0
- package/src/plugins/gen/ar/sdk/controls.ts +148 -0
- package/src/plugins/gen/audio-recorder/contract.d.ts +64 -0
- package/src/plugins/gen/audio-recorder/sdk/audio-recorder.gen.ts +76 -0
- package/src/plugins/gen/audio-recorder/sdk/audio-recorder.ts +113 -0
- package/src/plugins/gen/geolocation/contract.d.ts +7 -5
- package/src/plugins/gen/geolocation/sdk/geolocation.gen.ts +6 -2
- package/src/plugins/gen/speech/contract.d.ts +67 -0
- package/src/plugins/gen/speech/sdk/speech.gen.ts +64 -0
- package/src/plugins/gen/speech/sdk/speech.ts +104 -0
- package/src/runtime/permissions.ts +40 -0
- package/src/server/auth/host.ts +1 -1
- package/src/ui/presentable.ts +5 -0
- package/src/ui/router.ts +4 -3
- package/src/version.ts +1 -1
- package/dist/types/gl/controls.d.ts +0 -19
- package/dist/types/plugins/permission.d.ts +0 -1
- package/src/gl/controls.ts +0 -95
- package/src/plugins/permission.ts +0 -8
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/speech/sdk/speech.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
|
+
// SpeechRecognizer — speech recognition: the microphone to text, live (dictation, voice commands).
|
|
4
|
+
//
|
|
5
|
+
// Hand-written over the generated wire because the app's object is not the wire's session: a
|
|
6
|
+
// `SpeechRecognizer` is reusable — every `start()` opens a fresh host session (one listening),
|
|
7
|
+
// `end` closes it — and its events are the SDK's `addEventListener` pattern.
|
|
8
|
+
//
|
|
9
|
+
// The permissions (speech recognition, the microphone) are asked by the host on `start()` (the
|
|
10
|
+
// contract's `@permission`); to ask earlier, or to act on a denial, `permissions`.
|
|
11
|
+
|
|
12
|
+
import { Emitter } from "../../../../plugin"
|
|
13
|
+
import { SpeechBase, NAME } from "./speech.gen"
|
|
14
|
+
import type { SpeechErrorCode, SpeechOptions, SpeechResult } from "../contract"
|
|
15
|
+
|
|
16
|
+
export type { SpeechErrorCode, SpeechOptions, SpeechResult }
|
|
17
|
+
|
|
18
|
+
export type SpeechRecognizerEvents = {
|
|
19
|
+
/** A result: partials while the user speaks (`interimResults`), then the final one. */
|
|
20
|
+
result: (result: SpeechResult) => void
|
|
21
|
+
/** The user started speaking. */
|
|
22
|
+
speechStart: () => void
|
|
23
|
+
/** The user stopped speaking: the final result follows. */
|
|
24
|
+
speechEnd: () => void
|
|
25
|
+
/** The listening is over — after the final result, after `stop()` / `abort()`, after an error. */
|
|
26
|
+
end: () => void
|
|
27
|
+
/** The listening failed; `end` follows. */
|
|
28
|
+
error: (code: SpeechErrorCode, message: string) => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Recognizes speech from the microphone. `SpeechRecognizer.isSupported` reports whether this host
|
|
33
|
+
* registered the service — check it before showing a dictation button.
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* const speech = new SpeechRecognizer()
|
|
37
|
+
* speech.addEventListener('result', r => input.value = r.transcript)
|
|
38
|
+
* speech.addEventListener('end', () => micButton.style({ opacity: 1 }))
|
|
39
|
+
* await speech.start({ language: 'en-US' })
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export class SpeechRecognizer extends Emitter<SpeechRecognizerEvents> {
|
|
43
|
+
/** Whether this host registered a "speech" service. */
|
|
44
|
+
static get isSupported(): boolean { return SpeechBase.supports(NAME) }
|
|
45
|
+
|
|
46
|
+
private _session: SpeechBase | null = null
|
|
47
|
+
|
|
48
|
+
/** Whether a listening is under way. */
|
|
49
|
+
get listening(): boolean { return this._session !== null }
|
|
50
|
+
|
|
51
|
+
/** Start listening. The host asks for the permissions first; rejects `"denied"` when refused,
|
|
52
|
+
* `"unavailable"` when there is no recognizer for the language or on the device, and before
|
|
53
|
+
* touching the host when the plugin is not there or a listening is under way. */
|
|
54
|
+
async start(options?: SpeechOptions): Promise<void> {
|
|
55
|
+
if (!SpeechRecognizer.isSupported) {
|
|
56
|
+
throw new Error('SpeechRecognizer isn\'t available on this host — it needs a registered "speech" service.')
|
|
57
|
+
}
|
|
58
|
+
if (this._session) throw new Error("SpeechRecognizer: a listening is under way — stop() or abort() it first")
|
|
59
|
+
const session = new SpeechBase()
|
|
60
|
+
session.on("result", result => { if (result && this._session === session) this.dispatch("result", result) })
|
|
61
|
+
session.on("speechStart", () => { if (this._session === session) this.dispatch("speechStart") })
|
|
62
|
+
session.on("speechEnd", () => { if (this._session === session) this.dispatch("speechEnd") })
|
|
63
|
+
session.on("error", payload => { if (payload && this._session === session) this.dispatch("error", payload.code, payload.message) })
|
|
64
|
+
session.on("end", () => {
|
|
65
|
+
if (this._session !== session) return
|
|
66
|
+
this._drop(session)
|
|
67
|
+
this.dispatch("end")
|
|
68
|
+
})
|
|
69
|
+
this._session = session
|
|
70
|
+
try {
|
|
71
|
+
await session.start(options ?? {})
|
|
72
|
+
} catch (e) {
|
|
73
|
+
this._drop(session)
|
|
74
|
+
throw e
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Stop listening and finish: the final result of what was heard, then `end`. */
|
|
79
|
+
stop(): void {
|
|
80
|
+
this._session?.stop().catch(() => {})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Stop and drop what was heard: `end`, no result. */
|
|
84
|
+
abort(): void {
|
|
85
|
+
this._session?.abort().catch(() => {})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The languages this host recognizes (BCP-47), sorted; empty when it cannot say (the web). */
|
|
89
|
+
static async supportedLanguages(): Promise<string[]> {
|
|
90
|
+
if (!SpeechRecognizer.isSupported) return []
|
|
91
|
+
const session = new SpeechBase()
|
|
92
|
+
try {
|
|
93
|
+
return await session.supportedLanguages()
|
|
94
|
+
} finally {
|
|
95
|
+
session.close()
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private _drop(session: SpeechBase): void {
|
|
100
|
+
if (this._session !== session) return
|
|
101
|
+
session.close()
|
|
102
|
+
this._session = null
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// The OS permissions: what the app may ask the platform for, and when. ONE door, host-gated
|
|
2
|
+
// (`_creatorDevice.queryPermission / requestPermission / openSettings`): the SDK's own camera
|
|
3
|
+
// users (ARScene.prepare, QRScanner, CameraView) go through `_ensurePermission`, apps through
|
|
4
|
+
// `permissions`, a plugin's native half through the host's same service (a method tagged
|
|
5
|
+
// `@permission` in its contract is gated by the generated glue, never by the plugin's code).
|
|
6
|
+
//
|
|
7
|
+
// Hosts without a dialog (headless, desktop, the web for `query` where the browser has no answer)
|
|
8
|
+
// leave the slots out, and both answers are "granted": nothing there stands between an app and a
|
|
9
|
+
// feature — the feature itself says whether it exists (`isSupported`, a rejection of its own).
|
|
10
|
+
|
|
11
|
+
export type PermissionKind = "camera" | "microphone" | "location" | "speech"
|
|
12
|
+
/** "prompt" = never asked: `request` shows the dialog. "denied" = refused, or restricted by the
|
|
13
|
+
* device's policy; the OS decides whether a `request` asks again (iOS never does: `openSettings`). */
|
|
14
|
+
export type PermissionState = "granted" | "denied" | "prompt"
|
|
15
|
+
|
|
16
|
+
const slot = (name: "queryPermission" | "requestPermission", kind: PermissionKind): Promise<PermissionState> => {
|
|
17
|
+
const fn = _creatorDevice[name]
|
|
18
|
+
if (typeof fn !== "function") return Promise.resolve("granted")
|
|
19
|
+
return new Promise((resolve, reject) => fn(kind, s => resolve(s as PermissionState), err => reject(new Error(err))))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const permissions = {
|
|
23
|
+
/** The state of `kind` without a prompt. On the web the browser may not know before a request
|
|
24
|
+
* (Safari before 16, Firefox for the camera): "prompt" then. */
|
|
25
|
+
query(kind: PermissionKind): Promise<PermissionState> { return slot("queryPermission", kind) },
|
|
26
|
+
/** Ask for `kind`: the dialog when it was never asked, the state after it (or the state as it
|
|
27
|
+
* stands when the OS will not ask again). Ask where the user expects it — the first tap on the
|
|
28
|
+
* record button, not the start of the call. */
|
|
29
|
+
request(kind: PermissionKind): Promise<PermissionState> { return slot("requestPermission", kind) },
|
|
30
|
+
/** Open the app's own page of the system settings: where a denied permission is turned back on
|
|
31
|
+
* (iOS asks exactly once). A no-op where there is no such page (web). */
|
|
32
|
+
openSettings(): void { _creatorDevice.openSettings?.() },
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Request `kind` and reject when it was not granted — the SDK's own gate before a camera view
|
|
36
|
+
* opens. @internal */
|
|
37
|
+
export const _ensurePermission = async (kind: PermissionKind): Promise<void> => {
|
|
38
|
+
const state = await permissions.request(kind)
|
|
39
|
+
if (state !== "granted") throw new Error(`The ${kind} permission was ${state === "denied" ? "denied" : "not granted"}`)
|
|
40
|
+
}
|
package/src/server/auth/host.ts
CHANGED
|
@@ -40,7 +40,7 @@ export type AuthHostConfig = {
|
|
|
40
40
|
appName?: string
|
|
41
41
|
/** Sends the sign-in code (the platform mailer). Absent = codes are only echoed to the console. */
|
|
42
42
|
mail?: (msg: AuthMail) => Promise<void>
|
|
43
|
-
/** Echo every code to the server console (
|
|
43
|
+
/** Echo every code to the server console (a developer's own backend — `lecodes dev`, a check: the developer is the only user). */
|
|
44
44
|
echoCodes?: boolean
|
|
45
45
|
/** Server console sink (default console). */
|
|
46
46
|
log?: (level: "warn" | "info", text: string) => void
|
package/src/ui/presentable.ts
CHANGED
|
@@ -113,6 +113,11 @@ export interface Presentable {
|
|
|
113
113
|
* await it and skip the swap entirely if it rejects (permission denied, or superseded by
|
|
114
114
|
* another navigation that happened while preparing). */
|
|
115
115
|
_prepare?(): Promise<void>
|
|
116
|
+
/** @internal What a destination that prepared waits for once it is presented — ARScene: its first
|
|
117
|
+
* frame drawn (the view mounted and sized, the session's frame shown). `open()` and
|
|
118
|
+
* `Router.push/replace` settle with it: they resolve then, or reject when another navigation came
|
|
119
|
+
* first. */
|
|
120
|
+
_presented?(): Promise<void>
|
|
116
121
|
/** @internal The destination tuple (see DestTuple). */
|
|
117
122
|
_dest(): DestTuple
|
|
118
123
|
}
|
package/src/ui/router.ts
CHANGED
|
@@ -97,9 +97,10 @@ export const Router: Router = /*#__PURE__*/ Object.freeze({
|
|
|
97
97
|
_setCurrent(page) // optimistic — the runtime's change event confirms
|
|
98
98
|
}
|
|
99
99
|
// A preparing destination (ARScene) keeps the current page visible until it's ready; a
|
|
100
|
-
// rejection (camera denied / superseded) propagates to the caller and nothing is pushed.
|
|
100
|
+
// rejection (camera denied / superseded) propagates to the caller and nothing is pushed. Pushed,
|
|
101
|
+
// it settles the push once it is on screen (`_presented`: ARScene's first frame), as its open().
|
|
101
102
|
const prep = page._prepare?.()
|
|
102
|
-
return prep ? prep.then(go) : go()
|
|
103
|
+
return prep ? prep.then(() => { go(); return page._presented?.() }) : go()
|
|
103
104
|
},
|
|
104
105
|
replace(page: Presentable, opts?: NavigateOptions): void | Promise<void> {
|
|
105
106
|
const go = () => {
|
|
@@ -110,7 +111,7 @@ export const Router: Router = /*#__PURE__*/ Object.freeze({
|
|
|
110
111
|
_setCurrent(page) // optimistic — the runtime's change event confirms
|
|
111
112
|
}
|
|
112
113
|
const prep = page._prepare?.()
|
|
113
|
-
return prep ? prep.then(go) : go()
|
|
114
|
+
return prep ? prep.then(() => { go(); return page._presented?.() }) : go()
|
|
114
115
|
},
|
|
115
116
|
pop(to?: number, opts?: PopOptions): void {
|
|
116
117
|
_bumpNavEpoch()
|
package/src/version.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// same manifest). Its MAJOR is the bundle ↔ runtime contract: a bundle's `// sdk:` header line
|
|
5
5
|
// and a host's embedded version must agree on the major for the host to run the bundle; minor
|
|
6
6
|
// and patch never gate anything (features are detected, never versioned).
|
|
7
|
-
export const SDK_VERSION = "2.2.
|
|
7
|
+
export const SDK_VERSION = "2.2.10"
|
|
8
8
|
|
|
9
9
|
/** The major of a semver string, or null when it is not one. */
|
|
10
10
|
export const sdkMajor = (version: string | null | undefined): number | null => {
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { Node } from "./Node";
|
|
2
|
-
import type { Scene } from "./Scene";
|
|
3
|
-
export type ControlsOptions = {
|
|
4
|
-
/** Smallest uniform scale the pinch gesture allows (default 0.08). */
|
|
5
|
-
minScale?: number;
|
|
6
|
-
/** Largest uniform scale the pinch gesture allows (default 2). */
|
|
7
|
-
maxScale?: number;
|
|
8
|
-
/** One-finger drag moves the target across the ground plane (default true). */
|
|
9
|
-
pan?: boolean;
|
|
10
|
-
/** Two-finger pinch scales the target (default true). */
|
|
11
|
-
pinch?: boolean;
|
|
12
|
-
/** Two-finger twist rotates the target around its Y axis (default true). */
|
|
13
|
-
twist?: boolean;
|
|
14
|
-
};
|
|
15
|
-
/** Returned by `addControls` — call `remove()` to detach the gesture listeners. */
|
|
16
|
-
export type ControlsHandle = {
|
|
17
|
-
remove(): void;
|
|
18
|
-
};
|
|
19
|
-
export declare const attachControls: (scene: Scene, target: Node, options?: ControlsOptions) => ControlsHandle;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const _requestCameraPermission: () => Promise<void>;
|
package/src/gl/controls.ts
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
// AR object-placement controls — the classic "drop a model in the room and manipulate it" gesture
|
|
2
|
-
// recipe: ONE finger drags the target across the ground plane (camera-relative), TWO fingers pinch to
|
|
3
|
-
// scale and twist to rotate (yaw). Lives on ARScene (see Scene.ts) because it orchestrates the scene's
|
|
4
|
-
// camera + touch input against a target node; it's built entirely on the public API (no native calls
|
|
5
|
-
// the user couldn't make), so it's a convenience, not a primitive.
|
|
6
|
-
|
|
7
|
-
import { Vec2, Vec3 } from "../math/vec"
|
|
8
|
-
import { Quat } from "../math/quat"
|
|
9
|
-
import type { Node } from "./Node"
|
|
10
|
-
import type { Scene } from "./Scene"
|
|
11
|
-
import type { TouchStartEvent } from "../runtime/touch"
|
|
12
|
-
|
|
13
|
-
export type ControlsOptions = {
|
|
14
|
-
/** Smallest uniform scale the pinch gesture allows (default 0.08). */
|
|
15
|
-
minScale?: number
|
|
16
|
-
/** Largest uniform scale the pinch gesture allows (default 2). */
|
|
17
|
-
maxScale?: number
|
|
18
|
-
/** One-finger drag moves the target across the ground plane (default true). */
|
|
19
|
-
pan?: boolean
|
|
20
|
-
/** Two-finger pinch scales the target (default true). */
|
|
21
|
-
pinch?: boolean
|
|
22
|
-
/** Two-finger twist rotates the target around its Y axis (default true). */
|
|
23
|
-
twist?: boolean
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Returned by `addControls` — call `remove()` to detach the gesture listeners. */
|
|
27
|
-
export type ControlsHandle = { remove(): void }
|
|
28
|
-
|
|
29
|
-
type ActiveTouch = { pointerId: number, x: number, y: number }
|
|
30
|
-
|
|
31
|
-
const clamp = (v: number, lo: number, hi: number): number => (v < lo ? lo : v > hi ? hi : v)
|
|
32
|
-
|
|
33
|
-
export const attachControls = (scene: Scene, target: Node, options: ControlsOptions = {}): ControlsHandle => {
|
|
34
|
-
const minScale = options.minScale ?? 0.08
|
|
35
|
-
const maxScale = options.maxScale ?? 2
|
|
36
|
-
const pan = options.pan ?? true
|
|
37
|
-
const pinch = options.pinch ?? true
|
|
38
|
-
const twist = options.twist ?? true
|
|
39
|
-
|
|
40
|
-
const active: ActiveTouch[] = []
|
|
41
|
-
|
|
42
|
-
const onTouchStart = (e: TouchStartEvent<Node | null>): void => {
|
|
43
|
-
const touch: ActiveTouch = { pointerId: e.pointerId, x: e.clientX, y: e.clientY }
|
|
44
|
-
active.push(touch)
|
|
45
|
-
e.track({
|
|
46
|
-
onMove(m) {
|
|
47
|
-
if (active.length === 2) {
|
|
48
|
-
// --- two-finger: pinch-scale + twist-rotate about the gesture axis ---
|
|
49
|
-
const other = active.find((t) => t !== touch)
|
|
50
|
-
if (other) {
|
|
51
|
-
const axis = new Vec2(other.x - touch.x, other.y - touch.y)
|
|
52
|
-
const span = axis.length()
|
|
53
|
-
if (span > 1e-3) {
|
|
54
|
-
const dir = axis.scale(1 / span)
|
|
55
|
-
const delta = new Vec2(m.deltaX, m.deltaY)
|
|
56
|
-
if (pinch) {
|
|
57
|
-
const along = -dir.dot(delta) // movement along the pinch axis
|
|
58
|
-
target.scale = clamp(target.scale.x * (span + along) / span, minScale * 0.4, maxScale * 1.5)
|
|
59
|
-
}
|
|
60
|
-
if (twist) {
|
|
61
|
-
const tangent = dir.perp().dot(delta) // movement across the axis
|
|
62
|
-
target.quaternion = target.quaternion.mul(Quat.fromAxisAngle(Vec3.up, Math.atan2(tangent, span)))
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
} else if (active.length === 1 && pan) {
|
|
67
|
-
// --- one-finger: drag across the ground plane, relative to where the camera looks ---
|
|
68
|
-
let dir = scene.camera.forward
|
|
69
|
-
const parent = target.parent
|
|
70
|
-
if (parent) dir = dir.rotate(parent.quaternion.invert()) // into the parent's local frame
|
|
71
|
-
dir = dir.withY(0)
|
|
72
|
-
const k = Math.hypot(dir.x, dir.z)
|
|
73
|
-
const screen = Math.max(...scene.camera.displaySize)
|
|
74
|
-
if (k > 1e-4 && screen > 0) {
|
|
75
|
-
const dist = target.worldPosition.distanceTo(scene.camera.worldPosition)
|
|
76
|
-
const right = new Vec3(-dir.z, 0, dir.x).scale(m.deltaX / k / screen * dist)
|
|
77
|
-
const fwd = dir.scale(-m.deltaY / k / screen * dist)
|
|
78
|
-
const move = right.add(fwd)
|
|
79
|
-
const p = target.position
|
|
80
|
-
target.position = new Vec3(p.x + move.x, p.y, p.z + move.z) // keep height fixed
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
touch.x = m.clientX
|
|
84
|
-
touch.y = m.clientY
|
|
85
|
-
},
|
|
86
|
-
onEnd() {
|
|
87
|
-
const i = active.indexOf(touch)
|
|
88
|
-
if (i >= 0) active.splice(i, 1)
|
|
89
|
-
},
|
|
90
|
-
})
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
scene.addEventListener("touchstart", onTouchStart)
|
|
94
|
-
return { remove() { scene.removeEventListener("touchstart", onTouchStart); active.length = 0 } }
|
|
95
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
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
|
-
// the web). Shared by ARScene.prepare, QRScanner and CameraView.
|
|
4
|
-
|
|
5
|
-
export const _requestCameraPermission = (): Promise<void> =>
|
|
6
|
-
typeof _creatorDevice.requestCamera === "function"
|
|
7
|
-
? new Promise<void>((res, rej) => _creatorDevice.requestCamera!(res, rej))
|
|
8
|
-
: Promise.resolve()
|