lecodes-sdk 1.2.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 +8 -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/Scene.d.ts +41 -13
- package/dist/types/gl/Texture.d.ts +1 -1
- package/dist/types/gl/animation/Locomotion.d.ts +8 -1
- 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 +1 -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 +1 -1
- 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 +473 -239
- package/prompts/dist/3d-app.md +553 -205
- package/prompts/dist/ar-app.md +435 -202
- package/prompts/dist/design.md +113 -95
- package/prompts/dist/ui-app.md +386 -170
- package/prompts/ui-design.md +2 -3
- package/prompts/ui.md +45 -37
- package/src/animate/tween/Animation.ts +34 -150
- package/src/animate/tween/Timeline.ts +175 -175
- package/src/animate/tween/animateValue.ts +6 -3
- package/src/animate/tween/easing.ts +10 -3
- package/src/animate/tween/spec.ts +41 -15
- package/src/audio/Sound.ts +3 -3
- package/src/audio/audio.ts +2 -1
- 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/CameraPlace.ts +52 -52
- package/src/gl/Foliage.ts +72 -17
- package/src/gl/Geometry.ts +1 -2
- package/src/gl/Light.ts +10 -0
- package/src/gl/Lightmap.ts +45 -29
- package/src/gl/Material.ts +95 -51
- package/src/gl/Mesh.ts +120 -120
- package/src/gl/Model.ts +21 -16
- package/src/gl/Node.ts +91 -24
- package/src/gl/Particles.ts +1 -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/DynamicBone.ts +482 -482
- package/src/gl/animation/Locomotion.ts +8 -3
- 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/scenarios.ts +291 -291
- package/src/gl/terrain/Terrain.ts +4 -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 +13 -16
- 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 +12 -10
- 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 +15 -9
- 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 -4629
- 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 -1769
- 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
|
@@ -1,22 +1,33 @@
|
|
|
1
|
-
//
|
|
2
|
-
// the `
|
|
3
|
-
//
|
|
4
|
-
// the
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/map/sdk/map.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
|
+
// MapView — a vector map (maplibre-native on iOS / Android, maplibre-gl in the browser) as a view
|
|
4
|
+
// the app pushes (`Router.push(map)`), opens fullscreen (`map.open()`) or embeds among a screen's
|
|
5
|
+
// children. The style is a name, a URL, a bundled `asset()`, a read file or the style object (see
|
|
6
|
+
// MapStyle). Data goes in as named layers (`map.markers(name)`, `map.line(name)`) whose `set()`
|
|
7
|
+
// replaces their content; the camera is driven with `flyTo` / `fitPoints`; the user puck is fed by
|
|
8
|
+
// the app (`setUserLocation`) — the plugin never touches the location hardware.
|
|
8
9
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
import
|
|
10
|
+
// Hand-written over the generated wire because the public API is higher than the wire:
|
|
11
|
+
// - a style is resolved to a url or to text HERE, once, at creation;
|
|
12
|
+
// - marker items and coordinate lists become GeoJSON here;
|
|
13
|
+
// - the native map exists only once presented and its style loaded (`ready`): calls issued before
|
|
14
|
+
// that are queued and replayed in order, so an app builds the map declaratively and pushes it
|
|
15
|
+
// later without a ready-dance of its own;
|
|
16
|
+
// - what lives in the STYLE (layers, data, the padding, the user puck) is kept and re-applied
|
|
17
|
+
// after every style load.
|
|
18
|
+
|
|
19
|
+
import { ViewChannel, bufferText, type FetchResponse, type Json, type NativeView } from "../../../../plugin"
|
|
20
|
+
import { NAME, ensureLayer, fitPoints, flyTo, getCamera, jumpTo, setData, setPadding, setUserLocation } from "./map.gen"
|
|
21
|
+
import type {
|
|
22
|
+
LngLat, MapCamera, MapCameraMove, MapEvents, MapFitOptions, MapLayerKind, MapPadding, MapParams,
|
|
23
|
+
MapTap as WireTap, MapUserLocationOptions,
|
|
24
|
+
} from "../contract"
|
|
25
|
+
|
|
26
|
+
export type { LngLat, MapCamera }
|
|
27
|
+
export type CameraMove = MapCameraMove
|
|
28
|
+
export type FitOptions = MapFitOptions
|
|
29
|
+
export type MapPaddingValues = MapPadding
|
|
30
|
+
export type UserLocationOptions = MapUserLocationOptions
|
|
20
31
|
|
|
21
32
|
/** The ready-made styles a map falls back on — [OpenFreeMap](https://openfreemap.org): OSM data,
|
|
22
33
|
* no key, no registration, no request limits, commercial use allowed, and the whole stack is
|
|
@@ -56,34 +67,12 @@ const NAMED_STYLES: Record<string, string> = {
|
|
|
56
67
|
// `string & {}` keeps the named suggestions in the editor while still accepting any URL.
|
|
57
68
|
export type MapStyle = MapStyleName | (string & {}) | FetchResponse | object
|
|
58
69
|
|
|
59
|
-
|
|
60
|
-
export type LngLat = [number, number]
|
|
61
|
-
|
|
62
|
-
export interface MapCamera {
|
|
63
|
-
center: LngLat
|
|
64
|
-
zoom: number
|
|
65
|
-
/** Degrees clockwise from north. */
|
|
66
|
-
bearing: number
|
|
67
|
-
/** Degrees from the vertical. */
|
|
68
|
-
pitch: number
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface MapOptions {
|
|
70
|
+
export interface MapOptions extends Omit<MapParams, "style" | "styleJson"> {
|
|
72
71
|
/** The MapLibre style: a ready-made name (`"liberty"` — the default, `"positron"`, …), a URL,
|
|
73
72
|
* a style bundled with the app (`asset("./style.json")`), an already-read file
|
|
74
73
|
* (`fetchLocal("style.json")`, `await fetch(url)`) or the style object itself — see
|
|
75
74
|
* {@link MapStyle}. */
|
|
76
75
|
style?: MapStyle
|
|
77
|
-
center?: LngLat
|
|
78
|
-
zoom?: number
|
|
79
|
-
minZoom?: number
|
|
80
|
-
maxZoom?: number
|
|
81
|
-
bearing?: number
|
|
82
|
-
pitch?: number
|
|
83
|
-
/** Two-finger rotate gesture (default true). */
|
|
84
|
-
rotate?: boolean
|
|
85
|
-
/** Two-finger tilt gesture (default false — most city maps stay flat). */
|
|
86
|
-
tilt?: boolean
|
|
87
76
|
}
|
|
88
77
|
|
|
89
78
|
/** A tap on the map itself — not on a feature of a managed layer. */
|
|
@@ -93,29 +82,6 @@ export interface MapTap {
|
|
|
93
82
|
point: [number, number]
|
|
94
83
|
}
|
|
95
84
|
|
|
96
|
-
export interface CameraMove {
|
|
97
|
-
zoom?: number
|
|
98
|
-
bearing?: number
|
|
99
|
-
pitch?: number
|
|
100
|
-
/** Animation length, ms (flyTo only; default 600). */
|
|
101
|
-
duration?: number
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface MapPaddingValues {
|
|
105
|
-
top?: number | string
|
|
106
|
-
left?: number | string
|
|
107
|
-
bottom?: number | string
|
|
108
|
-
right?: number | string
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export interface FitOptions {
|
|
112
|
-
/** Px around the points, or per edge. Added to the view padding set by `setPadding`. */
|
|
113
|
-
padding?: number | MapPaddingValues
|
|
114
|
-
maxZoom?: number
|
|
115
|
-
/** Default true. */
|
|
116
|
-
animate?: boolean
|
|
117
|
-
}
|
|
118
|
-
|
|
119
85
|
/** One marker. `id` comes back in `onTap`; `icon` names an image of the style's sprite; `color` /
|
|
120
86
|
* `title` feed the default layers; extra keys become feature properties. */
|
|
121
87
|
export interface MarkerItem {
|
|
@@ -151,13 +117,6 @@ export interface LineLayerOptions {
|
|
|
151
117
|
opacity?: number
|
|
152
118
|
}
|
|
153
119
|
|
|
154
|
-
export interface UserLocationOptions {
|
|
155
|
-
/** Horizontal accuracy radius, meters — drawn as the halo around the dot. */
|
|
156
|
-
accuracy?: number | null
|
|
157
|
-
/** Degrees clockwise from north — drawn as the direction wedge; null hides it. */
|
|
158
|
-
heading?: number | null
|
|
159
|
-
}
|
|
160
|
-
|
|
161
120
|
export interface MarkerLayer {
|
|
162
121
|
readonly name: string
|
|
163
122
|
/** Replace the layer's markers. */
|
|
@@ -208,15 +167,21 @@ export interface MapView extends NativeView {
|
|
|
208
167
|
setUserLocation(lngLat: LngLat | null, options?: UserLocationOptions): this
|
|
209
168
|
}
|
|
210
169
|
|
|
170
|
+
/** The wire's calls the wrapper sends without waiting for an answer, by name: what a queue entry
|
|
171
|
+
* replays. Every one takes the view first. */
|
|
172
|
+
const SEND = { ensureLayer, setData, flyTo, jumpTo, fitPoints, setPadding, setUserLocation }
|
|
173
|
+
type Send = typeof SEND
|
|
174
|
+
type SendArgs<M extends keyof Send> = Send[M] extends (view: any, ...args: infer A) => any ? A : never
|
|
175
|
+
|
|
211
176
|
type Queued = { method: string, args: any[] }
|
|
212
|
-
type LayerSpec = { kind:
|
|
177
|
+
type LayerSpec = { kind: MapLayerKind, options: object }
|
|
213
178
|
|
|
214
179
|
/** Methods whose effect lives in the STYLE, not in the view: a style (re)load wipes them, so the
|
|
215
180
|
* wrapper keeps their last value and re-applies it on every `ready` instead of replaying them
|
|
216
181
|
* from the queue. Camera calls are not here — the camera survives a style reload. */
|
|
217
182
|
const STATEFUL = ["ensureLayer", "setData", "setPadding", "setUserLocation"]
|
|
218
183
|
|
|
219
|
-
class MapViewElement extends
|
|
184
|
+
class MapViewElement extends ViewChannel<MapEvents> {
|
|
220
185
|
private _ready = false
|
|
221
186
|
private _queue: Queued[] = []
|
|
222
187
|
private readonly _layers = new Map<string, MapLayerHandle>()
|
|
@@ -224,18 +189,18 @@ class MapViewElement extends NativeViewElement {
|
|
|
224
189
|
private readonly _specs = new Map<string, LayerSpec>()
|
|
225
190
|
private readonly _data = new Map<string, object>()
|
|
226
191
|
private _padding?: MapPaddingValues
|
|
227
|
-
private _user?:
|
|
192
|
+
private _user?: SendArgs<"setUserLocation">
|
|
228
193
|
private readonly _readyCallbacks: (() => void)[] = []
|
|
229
194
|
private readonly _tapCallbacks: ((tap: MapTap) => void)[] = []
|
|
230
195
|
private readonly _moveCallbacks: ((camera: MapCamera) => void)[] = []
|
|
231
196
|
private readonly _errorCallbacks: ((error: { message: string }) => void)[] = []
|
|
232
197
|
|
|
233
198
|
constructor(options: MapOptions = {}) {
|
|
234
|
-
super("
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
199
|
+
super("MapView", NAME, _styleParams(options))
|
|
200
|
+
this.on("ready", () => this._onReady())
|
|
201
|
+
this.on("tap", tap => this._onTap(tap))
|
|
202
|
+
this.on("moveEnd", camera => { for (const cb of [...this._moveCallbacks]) cb(camera) })
|
|
203
|
+
this.on("error", error => this._onError(error))
|
|
239
204
|
}
|
|
240
205
|
|
|
241
206
|
onReady(callback: () => void): this { this._readyCallbacks.push(callback); return this }
|
|
@@ -251,7 +216,7 @@ class MapViewElement extends NativeViewElement {
|
|
|
251
216
|
}
|
|
252
217
|
setData(source: string, geojson: object): this {
|
|
253
218
|
this._data.set(source, geojson)
|
|
254
|
-
this._send("setData", [source, geojson])
|
|
219
|
+
this._send("setData", [source, geojson as Json])
|
|
255
220
|
return this
|
|
256
221
|
}
|
|
257
222
|
|
|
@@ -259,22 +224,23 @@ class MapViewElement extends NativeViewElement {
|
|
|
259
224
|
jumpTo(center: LngLat, options: CameraMove = {}): this { this._send("jumpTo", [center, options]); return this }
|
|
260
225
|
fitPoints(points: LngLat[], options: FitOptions = {}): this { this._send("fitPoints", [points, options]); return this }
|
|
261
226
|
setPadding(padding: MapPaddingValues): this { this._padding = padding; this._send("setPadding", [padding]); return this }
|
|
262
|
-
getCamera(): Promise<MapCamera> { return this
|
|
227
|
+
getCamera(): Promise<MapCamera> { return getCamera(this) }
|
|
263
228
|
|
|
264
229
|
setUserLocation(lngLat: LngLat | null, options: UserLocationOptions = {}): this {
|
|
265
230
|
this._user = [lngLat, options]
|
|
266
|
-
this._send("setUserLocation",
|
|
231
|
+
this._send("setUserLocation", this._user)
|
|
267
232
|
return this
|
|
268
233
|
}
|
|
269
234
|
|
|
270
235
|
/** @internal queue until `ready`, then straight through. Rejections are logged, never thrown:
|
|
271
|
-
* a fire-and-forget camera/data call has no caller to reject to. */
|
|
272
|
-
_send(method:
|
|
236
|
+
* a fire-and-forget camera / data call has no caller to reject to. */
|
|
237
|
+
_send<M extends keyof Send>(method: M, args: SendArgs<M>): void {
|
|
273
238
|
if (!this._ready) { this._queue.push({ method, args }); return }
|
|
274
|
-
|
|
239
|
+
const call: (view: this, ...args: any[]) => Promise<void> = SEND[method] ?? ((view, ...rest) => view._call(method, rest))
|
|
240
|
+
call(this, ...args).catch((e: Error) => console.error(`MapView.${method}: ${e.message}`))
|
|
275
241
|
}
|
|
276
242
|
|
|
277
|
-
private _layer(name: string, kind:
|
|
243
|
+
private _layer(name: string, kind: MapLayerKind, options: object): MapLayerHandle {
|
|
278
244
|
let layer = this._layers.get(name)
|
|
279
245
|
if (!layer) {
|
|
280
246
|
layer = new MapLayerHandle(this, name)
|
|
@@ -294,28 +260,28 @@ class MapViewElement extends NativeViewElement {
|
|
|
294
260
|
const queue = this._queue
|
|
295
261
|
this._queue = []
|
|
296
262
|
for (const [name, spec] of this._specs) this._send("ensureLayer", [name, spec.kind, spec.options])
|
|
297
|
-
for (const [source, geojson] of this._data) this._send("setData", [source, geojson])
|
|
263
|
+
for (const [source, geojson] of this._data) this._send("setData", [source, geojson as Json])
|
|
298
264
|
if (this._padding) this._send("setPadding", [this._padding])
|
|
299
265
|
if (this._user) this._send("setUserLocation", this._user)
|
|
300
|
-
for (const q of queue) if (STATEFUL.indexOf(q.method) < 0) this._send(q.method, q.args)
|
|
266
|
+
for (const q of queue) if (STATEFUL.indexOf(q.method) < 0) this._send(q.method as keyof Send, q.args as any)
|
|
301
267
|
for (const cb of [...this._readyCallbacks]) cb()
|
|
302
268
|
}
|
|
303
269
|
|
|
304
|
-
private _onError(
|
|
270
|
+
private _onError(error: { message: string }): void {
|
|
305
271
|
// Unhandled is not unheard: a style the host couldn't load is exactly the failure that looks
|
|
306
272
|
// like "the map is blank and nothing happened".
|
|
307
|
-
if (this._errorCallbacks.length === 0) { console.error(`MapView: ${
|
|
308
|
-
for (const cb of [...this._errorCallbacks]) cb(
|
|
273
|
+
if (this._errorCallbacks.length === 0) { console.error(`MapView: ${error?.message ?? "unknown error"}`); return }
|
|
274
|
+
for (const cb of [...this._errorCallbacks]) cb(error)
|
|
309
275
|
}
|
|
310
276
|
|
|
311
|
-
private _onTap(
|
|
312
|
-
const feature =
|
|
277
|
+
private _onTap(tap: WireTap): void {
|
|
278
|
+
const feature = tap?.feature
|
|
313
279
|
const layer = feature ? this._layers.get(feature.source) : undefined
|
|
314
|
-
if (layer) {
|
|
315
|
-
layer._tap({ id: feature.id, lngLat:
|
|
280
|
+
if (feature && layer) {
|
|
281
|
+
layer._tap({ id: feature.id as string | number, lngLat: tap.lngLat, properties: feature.properties ?? {} })
|
|
316
282
|
return
|
|
317
283
|
}
|
|
318
|
-
for (const cb of [...this._tapCallbacks]) cb({ lngLat:
|
|
284
|
+
for (const cb of [...this._tapCallbacks]) cb({ lngLat: tap.lngLat, point: tap.point })
|
|
319
285
|
}
|
|
320
286
|
}
|
|
321
287
|
|
|
@@ -348,15 +314,15 @@ class MapLayerHandle {
|
|
|
348
314
|
}
|
|
349
315
|
}
|
|
350
316
|
|
|
351
|
-
/** @internal `MapOptions` →
|
|
317
|
+
/** @internal `MapOptions` → the wire's params. A style URL stays a url the map fetches itself; every
|
|
352
318
|
* other form is read HERE and crosses as `styleJson` text — one string, once, at creation. A
|
|
353
319
|
* bundled `asset()` is `"id:N"` in shell compiles (the host already holds the bytes) and a plain
|
|
354
320
|
* url in server compiles, so the same call works on every host. */
|
|
355
|
-
export const _styleParams = (options: MapOptions = {}):
|
|
321
|
+
export const _styleParams = (options: MapOptions = {}): MapParams => {
|
|
356
322
|
const { style = "liberty", ...rest } = options
|
|
357
323
|
if (typeof style === "string") {
|
|
358
324
|
if (!style.startsWith("id:")) return { ...rest, style: NAMED_STYLES[style] ?? style }
|
|
359
|
-
const text =
|
|
325
|
+
const text = bufferText(+style.slice(3))
|
|
360
326
|
if (!text) throw new Error("MapView: the style asset is empty or missing")
|
|
361
327
|
return { ...rest, styleJson: text }
|
|
362
328
|
}
|
|
@@ -380,9 +346,9 @@ export const _toGeoJson = (items: MarkerItem[] | LngLat[]): object => {
|
|
|
380
346
|
|
|
381
347
|
/**
|
|
382
348
|
* Create a map view. `MapView.isSupported` reports whether this host registered a "map" view —
|
|
383
|
-
* check it before offering the feature
|
|
349
|
+
* check it before offering the feature.
|
|
384
350
|
*/
|
|
385
|
-
// PURE IIFE so an app that never uses the map tree-shakes the whole plugin away
|
|
351
|
+
// PURE IIFE so an app that never uses the map tree-shakes the whole plugin away.
|
|
386
352
|
export const MapView: {
|
|
387
353
|
(options?: MapOptions): MapView
|
|
388
354
|
/** Whether this host registered a "map" view. */
|
|
@@ -390,7 +356,7 @@ export const MapView: {
|
|
|
390
356
|
} = /*#__PURE__*/ (() => {
|
|
391
357
|
const factory = (options: MapOptions = {}): MapView => new MapViewElement(options) as unknown as MapView
|
|
392
358
|
Object.defineProperty(factory, "isSupported", {
|
|
393
|
-
get: (): boolean =>
|
|
359
|
+
get: (): boolean => ViewChannel.supports(NAME),
|
|
394
360
|
})
|
|
395
361
|
return factory as any
|
|
396
362
|
})()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/push/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
|
+
// push — remote notifications (APNs on iOS, FCM on Android).
|
|
4
|
+
// The host owns the OS token, the permission prompt and the registration with the le.codes relay;
|
|
5
|
+
// the app sees the opaque project-scoped ADDRESS and the payloads, never the raw token.
|
|
6
|
+
//
|
|
7
|
+
// An OPEN SESSION is the delivery pipe of the events. Opening one is free (no prompt, no I/O).
|
|
8
|
+
// Identity: every call is attributed to the world that makes it (the project's uuid, held by the
|
|
9
|
+
// host); a world without one rejects "unavailable" BEFORE any permission path.
|
|
10
|
+
|
|
11
|
+
import type { Json, Service, i32 } from "../../../plugin"
|
|
12
|
+
|
|
13
|
+
/** A delivered notification, as the app sees it. */
|
|
14
|
+
export interface PushPayload {
|
|
15
|
+
title: string
|
|
16
|
+
body?: string
|
|
17
|
+
/** A deep link — it also rides the app's own link path (`app.launchUrl`, the "url" event). */
|
|
18
|
+
url?: string
|
|
19
|
+
/** The developer's own JSON object, ≤ 2 KB serialized. */
|
|
20
|
+
data?: { [key: string]: Json }
|
|
21
|
+
badge?: i32
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface PushStatus {
|
|
25
|
+
/** The OS permission of the host app. "prompt" = not asked yet. */
|
|
26
|
+
permission: "granted" | "denied" | "prompt"
|
|
27
|
+
/** Whether this device holds a registration (an address) for this app. */
|
|
28
|
+
registered: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface PushRegisterOptions {
|
|
32
|
+
/** The app's OWN user id. Client-asserted and unverified. The registration reflects the latest
|
|
33
|
+
* call: registering without it clears it. */
|
|
34
|
+
user?: string
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface PushRegistration {
|
|
38
|
+
address: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface PushParams {}
|
|
42
|
+
|
|
43
|
+
export interface PushEvents {
|
|
44
|
+
/** Arrived while this app was in the foreground (the host shows no banner). */
|
|
45
|
+
message: PushPayload
|
|
46
|
+
/** Tapped while the world was alive. */
|
|
47
|
+
tap: PushPayload
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface Push extends Service<"push", PushParams, PushEvents> {
|
|
51
|
+
/** Permission + registration state. Never prompts. */
|
|
52
|
+
getStatus(): Promise<PushStatus>
|
|
53
|
+
/** Ask for the permission (the first time) and register this device. Idempotent.
|
|
54
|
+
* @rejects denied unavailable */
|
|
55
|
+
register(options?: PushRegisterOptions): Promise<PushRegistration>
|
|
56
|
+
/** Remove this device's registration.
|
|
57
|
+
* @rejects unavailable */
|
|
58
|
+
unregister(): Promise<void>
|
|
59
|
+
/** The notification that cold-started this world, or null. Stable across calls. */
|
|
60
|
+
getLaunch(): Promise<PushPayload | null>
|
|
61
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// GENERATED by scripts/vendor-plugins.ts from lecodes-plugins/plugins/push/sdk/push.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
|
+
// push: the service "push", contract version 1.
|
|
5
|
+
|
|
6
|
+
import { ServiceChannel } from "../../../../plugin"
|
|
7
|
+
import type { PushEvents, PushParams, PushPayload, PushRegisterOptions, PushRegistration, PushStatus } from "../contract"
|
|
8
|
+
|
|
9
|
+
/** What the halves register under. */
|
|
10
|
+
export const NAME = "push"
|
|
11
|
+
/** The contract's version: the highest `@since` of its members. */
|
|
12
|
+
export const VERSION = 1
|
|
13
|
+
|
|
14
|
+
// ---- the wire, call by call ----------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
/** Permission + registration state. Never prompts. */
|
|
17
|
+
export const getStatus = (service: ServiceChannel<PushEvents>): Promise<PushStatus> =>
|
|
18
|
+
service._call("getStatus", [])
|
|
19
|
+
|
|
20
|
+
/** Ask for the permission (the first time) and register this device. Idempotent.
|
|
21
|
+
* Rejects with "denied" / "unavailable". */
|
|
22
|
+
export const register = (service: ServiceChannel<PushEvents>, options?: PushRegisterOptions): Promise<PushRegistration> =>
|
|
23
|
+
service._call("register", [options])
|
|
24
|
+
|
|
25
|
+
/** Remove this device's registration.
|
|
26
|
+
* Rejects with "unavailable". */
|
|
27
|
+
export const unregister = (service: ServiceChannel<PushEvents>): Promise<void> =>
|
|
28
|
+
service._call("unregister", [])
|
|
29
|
+
|
|
30
|
+
/** The notification that cold-started this world, or null. Stable across calls. */
|
|
31
|
+
export const getLaunch = (service: ServiceChannel<PushEvents>): Promise<PushPayload | null> =>
|
|
32
|
+
service._call("getLaunch", []).then((w: any) => w ?? null)
|
|
33
|
+
|
|
34
|
+
// ---- what the host's half has --------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/** A method of the contract. */
|
|
37
|
+
export type Method = "getStatus" | "register" | "unregister" | "getLaunch"
|
|
38
|
+
|
|
39
|
+
/** Whether this host has the service "push" — and, given a method, a half new enough for it
|
|
40
|
+
* (a method added after the first version of the contract is tagged `@since`). */
|
|
41
|
+
export const supports = (method?: Method): boolean =>
|
|
42
|
+
ServiceChannel.supports(NAME) // every method is of version 1
|
|
43
|
+
|
|
44
|
+
// ---- the client -----------------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/** The service "push" with the wire's methods as its own. */
|
|
47
|
+
export class PushBase extends ServiceChannel<PushEvents> {
|
|
48
|
+
constructor(params: PushParams = {}) { super("Push", NAME, params) }
|
|
49
|
+
|
|
50
|
+
/** Permission + registration state. Never prompts. */
|
|
51
|
+
getStatus(): Promise<PushStatus> { return getStatus(this) }
|
|
52
|
+
/** Ask for the permission (the first time) and register this device. Idempotent.
|
|
53
|
+
* Rejects with "denied" / "unavailable". */
|
|
54
|
+
register(options?: PushRegisterOptions): Promise<PushRegistration> { return register(this, options) }
|
|
55
|
+
/** Remove this device's registration.
|
|
56
|
+
* Rejects with "unavailable". */
|
|
57
|
+
unregister(): Promise<void> { return unregister(this) }
|
|
58
|
+
/** The notification that cold-started this world, or null. Stable across calls. */
|
|
59
|
+
getLaunch(): Promise<PushPayload | null> { return getLaunch(this) }
|
|
60
|
+
}
|
|
@@ -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()
|