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
package/src/runtime/fetch.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
// HTTP via the host. `fetch` is async (host streams the body), `fetchLocal` reads a preloaded asset
|
|
2
|
-
// synchronously.
|
|
2
|
+
// synchronously. Over the `_creatorFetch` bridge (sdk/src/bridges/fetch.d.ts).
|
|
3
|
+
//
|
|
4
|
+
// Ownership (owned handles, docs/plans/hosts-unification-plan.md phase 10): a fetched body lives as long as
|
|
5
|
+
// the app holds the response — `_h` is the owned handle the runtime returned with the completion,
|
|
6
|
+
// its collection frees the host buffer. There is nothing to dispose. A LOCAL asset's buffer is the
|
|
7
|
+
// host's (cached by name, shared by every `fetchLocal` of that name), so its wrapper only carries
|
|
8
|
+
// the id. The same goes for a `File` (picker / camera / canvas export): a host buffer the wrapper
|
|
9
|
+
// names but does not own.
|
|
10
|
+
|
|
3
11
|
|
|
4
12
|
export type FetchOptions = {
|
|
5
13
|
body?: any
|
|
@@ -13,47 +21,50 @@ export interface FetchResponse {
|
|
|
13
21
|
readonly status: number
|
|
14
22
|
json<T = any>(): T
|
|
15
23
|
text(): string
|
|
24
|
+
/** @deprecated A no-op: the body is freed with the response's handle (owned handles). Apps written
|
|
25
|
+
* against the pre-owned SDK still call it after `text()` / `json()`; remove the call. */
|
|
16
26
|
dispose(): void
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
class FetchResponseImpl implements FetchResponse {
|
|
20
30
|
readonly status: number
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
/** @internal The buffer handle (`_h.id` = the host buffer id). Hosts and the runtime read a buffer
|
|
32
|
+
* off any object carrying `_h` — a File / FetchResponse alike (FormData entries, `UIImage` src,
|
|
33
|
+
* `Texture.load`, `share`, `files.write`). */
|
|
34
|
+
readonly _h: Handle
|
|
35
|
+
constructor(h: Handle, status: number) {
|
|
36
|
+
this._h = h
|
|
24
37
|
this.status = status
|
|
25
38
|
}
|
|
26
39
|
json<T = any>(): T {
|
|
27
|
-
return
|
|
40
|
+
return _creatorFetch.toJson(this._h.id)
|
|
28
41
|
}
|
|
29
42
|
text(): string {
|
|
30
|
-
return
|
|
31
|
-
}
|
|
32
|
-
dispose(): void {
|
|
33
|
-
_creatorUtils.disposeFetch(this._id)
|
|
43
|
+
return _creatorFetch.toText(this._h.id)
|
|
34
44
|
}
|
|
45
|
+
/** @deprecated see the interface */
|
|
46
|
+
dispose(): void {}
|
|
35
47
|
}
|
|
36
48
|
|
|
37
49
|
export const fetch = (url: string, options: FetchOptions = {}): Promise<FetchResponse> =>
|
|
38
50
|
new Promise<FetchResponse>((resolve, reject) => {
|
|
39
|
-
|
|
40
|
-
resolve(new FetchResponseImpl(
|
|
51
|
+
_creatorFetch.fetch(url, options, (h, statusCode) => {
|
|
52
|
+
resolve(new FetchResponseImpl(h, statusCode))
|
|
41
53
|
}, reject)
|
|
42
54
|
})
|
|
43
55
|
|
|
44
56
|
export const fetchLocal = (path: string): FetchResponse =>
|
|
45
|
-
new FetchResponseImpl(
|
|
57
|
+
new FetchResponseImpl({ id: _creatorFetch.local(path) }, 200) // the host's buffer: not owned
|
|
46
58
|
|
|
47
59
|
/** A handle to a picked / fetched file (see openFilePicker). */
|
|
48
60
|
export class File {
|
|
49
|
-
/** @internal host buffer
|
|
50
|
-
*
|
|
51
|
-
|
|
52
|
-
readonly _id: number
|
|
61
|
+
/** @internal The host buffer (not owned — see the header). Same key `FetchResponse` carries, so
|
|
62
|
+
* both are accepted anywhere a buffer is expected. */
|
|
63
|
+
readonly _h: Handle
|
|
53
64
|
readonly name: string
|
|
54
65
|
readonly size: number
|
|
55
66
|
constructor(systemId: number, name: string, size: number) {
|
|
56
|
-
this.
|
|
67
|
+
this._h = { id: systemId }
|
|
57
68
|
this.name = name
|
|
58
69
|
this.size = size
|
|
59
70
|
}
|
|
@@ -62,9 +73,8 @@ export class File {
|
|
|
62
73
|
type FormDataValue = string | number | boolean | File | FetchResponse
|
|
63
74
|
|
|
64
75
|
export class FormData {
|
|
65
|
-
// Host-facing:
|
|
66
|
-
// (the buffer id), everything else as a text field. See
|
|
67
|
-
// creator-pkg creatorUtilsHandler.cpp, iOS Fetch.swift buildMultipartBody.
|
|
76
|
+
// Host-facing: the runtime walks `_entries` and treats any object value carrying `_h` as binary
|
|
77
|
+
// (the buffer id), everything else as a text field. See runtime/native src/host/fetch.cpp.
|
|
68
78
|
private _entries: [string, FormDataValue, filename?: string][] = []
|
|
69
79
|
|
|
70
80
|
append(name: string, value: FormDataValue, filename?: string): void {
|
package/src/runtime/files.ts
CHANGED
|
@@ -12,7 +12,9 @@ export function openFilePicker(
|
|
|
12
12
|
options: OpenFilePickerOptions & { multiple?: boolean } = {},
|
|
13
13
|
): Promise<File | File[] | null> {
|
|
14
14
|
return new Promise((resolve, reject) => {
|
|
15
|
-
|
|
15
|
+
// No picker on this host (headless, a hidden render): nothing picked, like a cancelled dialog.
|
|
16
|
+
if (typeof _creatorDevice.openFilePicker !== "function") { resolve(options.multiple ? [] : null); return }
|
|
17
|
+
_creatorDevice.openFilePicker((resp) => {
|
|
16
18
|
if (!resp || resp.length === 0) {
|
|
17
19
|
resolve(options.multiple ? [] : null)
|
|
18
20
|
return
|
|
@@ -24,7 +26,7 @@ export function openFilePicker(
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// ---- Local filesystem -------------------------------------------------------------------------
|
|
27
|
-
// `files` is the read/write/delete half of `fetchLocal`, for platforms where the app owns a
|
|
29
|
+
// `files` (the `_creatorFiles` bridge, absent on sandboxed hosts) is the read/write/delete half of `fetchLocal`, for platforms where the app owns a
|
|
28
30
|
// filesystem — desktop today. A RELATIVE path is resolved against the app's OWN folder (for a
|
|
29
31
|
// packaged desktop app: the directory the exe sits in), never the process cwd, so a save written by
|
|
30
32
|
// a shortcut and one written from a terminal land in the same place; an ABSOLUTE path goes wherever
|
|
@@ -61,8 +63,7 @@ export type FileEntry = {
|
|
|
61
63
|
modified: number
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
const supported = () =>
|
|
65
|
-
typeof _creatorUtils !== "undefined" && typeof _creatorUtils.writeLocal === "function"
|
|
66
|
+
const supported = () => typeof _creatorFiles !== "undefined"
|
|
66
67
|
|
|
67
68
|
// The host is asked for the work only when it can do it; otherwise the promise rejects with the
|
|
68
69
|
// same message the native bridge uses, so a project sees one error either way.
|
|
@@ -72,10 +73,10 @@ const run = <T>(op: string, path: string, call: (resolve: (v: T) => void, reject
|
|
|
72
73
|
call(resolve, reject)
|
|
73
74
|
})
|
|
74
75
|
|
|
75
|
-
// A File / FetchResponse crosses as
|
|
76
|
-
//
|
|
76
|
+
// A File / FetchResponse crosses as its buffer handle — the same `_h` convention FormData entries
|
|
77
|
+
// and image `src` objects use, so the bytes never travel through JS.
|
|
77
78
|
const payload = (data: FileData) =>
|
|
78
|
-
(typeof data === "object" && "
|
|
79
|
+
(typeof data === "object" && "_h" in (data as any) ? { _h: (data as any)._h } : data) as any
|
|
79
80
|
|
|
80
81
|
export const files = {
|
|
81
82
|
/** Is there a filesystem here (desktop)? False on web/mobile, where every other call rejects. */
|
|
@@ -86,38 +87,38 @@ export const files = {
|
|
|
86
87
|
/** Replace the file's contents, creating the parent folders on the way. */
|
|
87
88
|
write(path: string, data: FileData): Promise<void> {
|
|
88
89
|
return run<void>("write", path, (resolve, reject) =>
|
|
89
|
-
|
|
90
|
+
_creatorFiles.write(path, payload(data), false, () => resolve(), reject))
|
|
90
91
|
},
|
|
91
92
|
|
|
92
93
|
/** Add to the end of the file, creating it (and the parent folders) when it isn't there yet. */
|
|
93
94
|
append(path: string, data: FileData): Promise<void> {
|
|
94
95
|
return run<void>("append", path, (resolve, reject) =>
|
|
95
|
-
|
|
96
|
+
_creatorFiles.write(path, payload(data), true, () => resolve(), reject))
|
|
96
97
|
},
|
|
97
98
|
|
|
98
99
|
/** The whole file as UTF-8 text — null when there is no such file. */
|
|
99
100
|
readText(path: string): Promise<string | null> {
|
|
100
101
|
return run<string | null>("readText", path, (resolve, reject) =>
|
|
101
|
-
|
|
102
|
+
_creatorFiles.readText(path, resolve, reject))
|
|
102
103
|
},
|
|
103
104
|
|
|
104
105
|
/** The whole file as bytes — null when there is no such file. */
|
|
105
106
|
readBytes(path: string): Promise<Uint8Array | null> {
|
|
106
107
|
return run<Uint8Array | null>("readBytes", path, (resolve, reject) =>
|
|
107
|
-
|
|
108
|
+
_creatorFiles.readBytes(path, resolve, reject))
|
|
108
109
|
},
|
|
109
110
|
|
|
110
111
|
/** Delete a file, or a directory (empty, or any directory with `{ recursive: true }`). Deleting
|
|
111
112
|
* something already gone is success — what's promised is that the path is free afterwards. */
|
|
112
113
|
rm(path: string, options: { recursive?: boolean } = {}): Promise<void> {
|
|
113
114
|
return run<void>("rm", path, (resolve, reject) =>
|
|
114
|
-
|
|
115
|
+
_creatorFiles.remove(path, !!options.recursive, () => resolve(), reject))
|
|
115
116
|
},
|
|
116
117
|
|
|
117
118
|
/** Create a directory and any missing parents. An existing directory is fine. */
|
|
118
119
|
mkdir(path: string): Promise<void> {
|
|
119
120
|
return run<void>("mkdir", path, (resolve, reject) =>
|
|
120
|
-
|
|
121
|
+
_creatorFiles.mkdir(path, () => resolve(), reject))
|
|
121
122
|
},
|
|
122
123
|
|
|
123
124
|
/** The entries of a directory, sorted by path — null when there is no such directory (an empty
|
|
@@ -126,13 +127,13 @@ export const files = {
|
|
|
126
127
|
* is a file, not a directory, rejects. Filter in JS — `entries.filter(e => e.name.endsWith(".json"))`. */
|
|
127
128
|
ls(path: string, options: { recursive?: boolean } = {}): Promise<FileEntry[] | null> {
|
|
128
129
|
return run<FileEntry[] | null>("ls", path, (resolve, reject) =>
|
|
129
|
-
|
|
130
|
+
_creatorFiles.list(path, !!options.recursive, resolve, reject))
|
|
130
131
|
},
|
|
131
132
|
|
|
132
133
|
/** What is at `path`: a file or a directory entry (`path` equal to `name`), or null when nothing
|
|
133
134
|
* is there — `(await files.stat(p)) !== null` is the exists check. */
|
|
134
135
|
stat(path: string): Promise<FileEntry | null> {
|
|
135
136
|
return run<FileEntry | null>("stat", path, (resolve, reject) =>
|
|
136
|
-
|
|
137
|
+
_creatorFiles.stat(path, (e) => resolve(e ? { ...e, path: e.name } : null), reject))
|
|
137
138
|
},
|
|
138
139
|
}
|
package/src/runtime/input.ts
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
// - CONTINUOUS — mouse motion/wheel, sticks, triggers: polled every frame (`Input.mouse.delta`,
|
|
8
8
|
// `Input.gamepad(0).axis('leftX')`), never events.
|
|
9
9
|
// Bindings ("jump = Space | GamepadSouth") are app code: compare `e.code`, OR a few `Input.key`s.
|
|
10
|
-
// Hosts report physical state only (`
|
|
10
|
+
// Hosts report physical state only (`_creatorInput.key / read / registerEvent`).
|
|
11
11
|
|
|
12
|
-
/** Continuous channel ids — the `
|
|
12
|
+
/** Continuous channel ids — the `_creatorInput.read` contract (sdk/src/bridges/input.d.ts). */
|
|
13
13
|
export const InputChannel = {
|
|
14
14
|
MouseX: 0, MouseY: 1, MouseDX: 2, MouseDY: 3, WheelX: 4, WheelY: 5, PointerLocked: 6,
|
|
15
15
|
GamepadCount: 7, PointerOnUI: 8,
|
|
@@ -77,10 +77,12 @@ const deliver = (kind: number, code: string, gamepad: number, repeat: number) =>
|
|
|
77
77
|
const ensureHostListener = () => {
|
|
78
78
|
if (hostListenerInstalled) return
|
|
79
79
|
hostListenerInstalled = true
|
|
80
|
-
|
|
80
|
+
input()?.registerEvent?.(deliver)
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
// The host's input bridge, or undefined on a host without one (every read then answers "nothing").
|
|
84
|
+
const input = () => (typeof _creatorInput !== "undefined" ? _creatorInput : undefined)
|
|
85
|
+
const read = (channel: number): number => input()?.read?.(channel) ?? 0
|
|
84
86
|
|
|
85
87
|
/** Mouse state — polled. Position is the cursor inside the viewport in logical px; `delta` is the
|
|
86
88
|
* motion during the previous frame (raw where the OS offers it) and keeps counting while locked —
|
|
@@ -99,8 +101,8 @@ export const mouse = {
|
|
|
99
101
|
* `if (!Input.mouse.onUI) look(Input.mouse.delta)` never turns a HUD drag into camera look.
|
|
100
102
|
* Always false while `locked`. A press that fell through to the scene reads false. */
|
|
101
103
|
get onUI(): boolean { return read(InputChannel.PointerOnUI) !== 0 },
|
|
102
|
-
lock(): boolean { return
|
|
103
|
-
unlock(): void {
|
|
104
|
+
lock(): boolean { return input()?.setPointerLock?.(true) ?? false },
|
|
105
|
+
unlock(): void { input()?.setPointerLock?.(false) },
|
|
104
106
|
}
|
|
105
107
|
|
|
106
108
|
export type GamepadAxisName = "leftX" | "leftY" | "rightX" | "rightY" | "leftTrigger" | "rightTrigger"
|
|
@@ -117,6 +119,13 @@ export interface GamepadState {
|
|
|
117
119
|
axis(name: GamepadAxisName, deadzone?: number): number
|
|
118
120
|
/** Held? Same as `Input.key(code, index)`. */
|
|
119
121
|
button(code: string): boolean
|
|
122
|
+
/** Rumble: `strong` = the heavy low-frequency motor, `weak` = the light high-frequency one, both
|
|
123
|
+
* 0..1, for `durationMs` (default 200, hosts cap at 5000). A new call replaces the running
|
|
124
|
+
* effect. The host stops the motors by itself — after the duration, on focus loss and when the
|
|
125
|
+
* project is swapped — so there is nothing to clean up. False when the pad or host has no motors. */
|
|
126
|
+
rumble(strong: number, weak?: number, durationMs?: number): boolean
|
|
127
|
+
/** Stop the running rumble now. */
|
|
128
|
+
stopRumble(): void
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
const padCache: GamepadState[] = []
|
|
@@ -131,7 +140,11 @@ const gamepadAt = (index: number): GamepadState => {
|
|
|
131
140
|
const v = read(base + PAD_AXIS[name])
|
|
132
141
|
return deadzone > 0 && Math.abs(v) < deadzone ? 0 : v
|
|
133
142
|
},
|
|
134
|
-
button(code) { return
|
|
143
|
+
button(code) { return input()?.key?.(code, index) ?? false },
|
|
144
|
+
rumble(strong, weak = strong, durationMs = 200) {
|
|
145
|
+
return input()?.rumble?.(index, strong, weak, durationMs) ?? false
|
|
146
|
+
},
|
|
147
|
+
stopRumble() { input()?.rumble?.(index, 0, 0, 0) },
|
|
135
148
|
}
|
|
136
149
|
}
|
|
137
150
|
return g
|
|
@@ -145,7 +158,7 @@ export const Input = {
|
|
|
145
158
|
* frame-independent movement. `gamepad` scopes a Gamepad* code to one pad; omitted = any pad.
|
|
146
159
|
*/
|
|
147
160
|
key(code: string, gamepad?: number): boolean {
|
|
148
|
-
return
|
|
161
|
+
return input()?.key?.(code, gamepad ?? -1) ?? false
|
|
149
162
|
},
|
|
150
163
|
|
|
151
164
|
/** Listen for a discrete input moment. `keydown`/`keyup` cover EVERY button — keyboard, mouse,
|
package/src/runtime/media.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// Audio / video playback over the host
|
|
2
|
-
//
|
|
3
|
-
// when the Texture type exists — see gl/.
|
|
1
|
+
// Audio / video playback over the host's `_creatorMedia` bridge (absent on hosts without media
|
|
2
|
+
// players — the players are inert there). The listener boilerplate is folded into the shared Emitter.
|
|
4
3
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
4
|
+
// Ownership (owned handles, docs/plans/hosts-unification-plan.md phase 10): a player is an owned resource
|
|
5
|
+
// — `_h` is the handle the runtime returned; the runtime counts a PLAYING player as natively owned.
|
|
6
|
+
// So `new AudioPlayer(src).play()` in a handler, reference dropped, plays to its end and is released
|
|
7
|
+
// then; a paused player you stop referencing is released at once. There is nothing to dispose. The
|
|
8
|
+
// end-of-playback report comes by id through ONE emitter per world; a playing player is pinned so
|
|
9
|
+
// the report finds it.
|
|
9
10
|
|
|
10
11
|
import { Emitter } from "../core/events"
|
|
12
|
+
import { Pins } from "../core/pins"
|
|
11
13
|
import { Texture } from "../gl/Texture"
|
|
12
14
|
|
|
13
15
|
type MediaEvents = {
|
|
@@ -17,83 +19,84 @@ type MediaEvents = {
|
|
|
17
19
|
completed: () => void
|
|
18
20
|
}
|
|
19
21
|
|
|
22
|
+
/** The players native owns right now (playing), by id — what an end-of-playback report resolves to. */
|
|
23
|
+
const players = new Pins<MediaPlayer>()
|
|
24
|
+
/** The host's media table, or undefined on a host without one (`@optional`: the global is absent). */
|
|
25
|
+
const media = (): typeof _creatorMedia | undefined => (typeof _creatorMedia !== "undefined" ? _creatorMedia : undefined)
|
|
26
|
+
let emitterInstalled = false
|
|
27
|
+
const ensureEmitter = (): void => {
|
|
28
|
+
const m = media()
|
|
29
|
+
if (emitterInstalled || !m || typeof m.setEmitter !== "function") return
|
|
30
|
+
emitterInstalled = true
|
|
31
|
+
m.setEmitter((id) => { players.get(id)?._finished() })
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
abstract class MediaPlayer extends Emitter<MediaEvents> {
|
|
21
|
-
|
|
35
|
+
/** @internal The player's owned handle (`_h.id` = the host player id; -1 = no host player). */
|
|
36
|
+
_h: Handle = { id: -1 }
|
|
22
37
|
protected _src = ""
|
|
23
38
|
private _playing = false
|
|
24
39
|
private _loop = false
|
|
25
40
|
private _volume = 1
|
|
26
|
-
private _disposed = false
|
|
27
|
-
|
|
28
|
-
// Every host-touching operation runs through this so a disposed player can never address a freed
|
|
29
|
-
// (and possibly reused) host slot.
|
|
30
|
-
private _assertAlive(): void {
|
|
31
|
-
if (this._disposed) throw new Error("MediaPlayer has been disposed")
|
|
32
|
-
}
|
|
33
41
|
|
|
34
42
|
play(): void { this.playing = true }
|
|
35
43
|
pause(): void { this.playing = false }
|
|
44
|
+
/** Pause and rewind to the start. */
|
|
45
|
+
stop(): void {
|
|
46
|
+
this.playing = false
|
|
47
|
+
media()?.setTime?.(this._h.id, 0)
|
|
48
|
+
}
|
|
36
49
|
|
|
37
50
|
get playing(): boolean { return this._playing }
|
|
38
51
|
set playing(value: boolean) {
|
|
39
|
-
this._assertAlive()
|
|
40
52
|
this._playing = value
|
|
41
|
-
|
|
53
|
+
media()?.setPlaying(this._h.id, value)
|
|
54
|
+
if (value) players.pin(this) // playing: native owns it, the end report must find it
|
|
55
|
+
else players.unpin(this._h.id)
|
|
42
56
|
}
|
|
43
57
|
|
|
44
58
|
get volume(): number { return this._volume }
|
|
45
59
|
set volume(value: number) {
|
|
46
|
-
this._assertAlive()
|
|
47
60
|
this._volume = value
|
|
48
|
-
|
|
61
|
+
media()?.setVolume(this._h.id, value)
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
get loop(): boolean { return this._loop }
|
|
52
65
|
set loop(value: boolean) {
|
|
53
|
-
this._assertAlive()
|
|
54
66
|
this._loop = value
|
|
55
|
-
|
|
67
|
+
media()?.setLoop(this._h.id, value)
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
/** Current playback position, in seconds. Settable to seek. */
|
|
59
|
-
get time(): number {
|
|
60
|
-
set time(value: number) {
|
|
71
|
+
get time(): number { return media()?.time?.(this._h.id) ?? 0 }
|
|
72
|
+
set time(value: number) { media()?.setTime?.(this._h.id, value) }
|
|
61
73
|
|
|
62
74
|
/** Total media length in seconds (0 until metadata has loaded). */
|
|
63
|
-
get duration(): number {
|
|
75
|
+
get duration(): number { return media()?.duration?.(this._h.id) ?? 0 }
|
|
64
76
|
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Release the underlying host media resource (decoder, network, buffers) immediately and detach
|
|
70
|
-
* all listeners. Idempotent. Any further use of the player throws.
|
|
71
|
-
*/
|
|
72
|
-
dispose(): void {
|
|
73
|
-
if (this._disposed) return
|
|
74
|
-
this._disposed = true
|
|
75
|
-
this._playing = false
|
|
76
|
-
this.clearListeners()
|
|
77
|
-
_creatorUtils.removeMediaPlayer(this._id)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** @internal — the host calls this when a play-through finishes. */
|
|
81
|
-
protected _finished(): void {
|
|
82
|
-
if (this._disposed) return
|
|
77
|
+
/** @internal — the runtime reports a play-through end. */
|
|
78
|
+
_finished(): void {
|
|
83
79
|
if (this._loop) {
|
|
84
80
|
this.dispatch("loopReached")
|
|
85
81
|
} else {
|
|
86
82
|
this._playing = false
|
|
83
|
+
players.unpin(this._h.id)
|
|
87
84
|
this.dispatch("completed")
|
|
88
85
|
}
|
|
89
86
|
}
|
|
87
|
+
|
|
88
|
+
/** @deprecated There is nothing to dispose — a player the app stops referencing is released by
|
|
89
|
+
* the runtime (a playing one at its end). Kept as `stop()` so old code still silences the player;
|
|
90
|
+
* call `stop()` / `pause()` instead. */
|
|
91
|
+
dispose(): void { this.stop() }
|
|
90
92
|
}
|
|
91
93
|
|
|
92
94
|
export class AudioPlayer extends MediaPlayer {
|
|
93
95
|
constructor(src?: string) {
|
|
94
96
|
super()
|
|
95
97
|
this._src = src ?? ""
|
|
96
|
-
|
|
98
|
+
ensureEmitter()
|
|
99
|
+
this._h = media()?.createAudio(this._src) ?? { id: -1 }
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
|
|
@@ -101,11 +104,12 @@ export class VideoPlayer extends MediaPlayer {
|
|
|
101
104
|
constructor(src?: string) {
|
|
102
105
|
super()
|
|
103
106
|
this._src = src ?? ""
|
|
104
|
-
|
|
107
|
+
ensureEmitter()
|
|
108
|
+
this._h = media()?.createVideo(this._src) ?? { id: -1 }
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
/** A 3D Texture that samples this video (e.g. for a Material.video()). */
|
|
111
|
+
/** A 3D Texture that samples this video (e.g. for a Material.video()). Owned by the player. */
|
|
108
112
|
get texture(): Texture {
|
|
109
|
-
return new Texture(0, 0, _creator.createMediaPlayerTexture(this.
|
|
113
|
+
return new Texture(0, 0, _creator.createMediaPlayerTexture(this._h.id))
|
|
110
114
|
}
|
|
111
115
|
}
|
package/src/runtime/misc.ts
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
// Small platform globals that are real (not engine-specific). Grown in Phase 1.
|
|
2
2
|
|
|
3
|
+
import type { ColorInput } from "../core/color"
|
|
4
|
+
|
|
3
5
|
/** Show a transient toast notification. */
|
|
4
|
-
export const toast = (msg: string): void =>
|
|
6
|
+
export const toast = (msg: string): void => _creatorTree.toast(msg, 2000)
|
|
5
7
|
|
|
6
8
|
/** Open `url` in the system browser / external handler (fire-and-forget, like `share`). Silent
|
|
7
9
|
* no-op on hosts without one (headless). */
|
|
8
|
-
export const openURL = (url: string): void =>
|
|
10
|
+
export const openURL = (url: string): void => _creatorApp.openUrl?.(url)
|
|
9
11
|
|
|
10
12
|
export type SvgSourceValue = {
|
|
11
13
|
readonly svg: string
|
|
12
|
-
tintColor:
|
|
14
|
+
/** The source-level tint (a mask recolor under the `tintColor` style, which wins): any color, or
|
|
15
|
+
* a theme `var(--…)`. */
|
|
16
|
+
tintColor: ColorInput | null
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
/** Wrap raw SVG XML so it can be used as an image source. */
|
package/src/runtime/net.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// WebSocket over the host bridge. Same event surface as the browser's
|
|
2
|
-
// via the shared Emitter.
|
|
1
|
+
// WebSocket over the host's `_creatorSocket` bridge. Same event surface as the browser's
|
|
2
|
+
// (open/message/close/error) via the shared Emitter.
|
|
3
3
|
|
|
4
4
|
import { Emitter } from "../core/events"
|
|
5
5
|
|
|
@@ -13,18 +13,19 @@ type SocketEvents = {
|
|
|
13
13
|
export class WebSocket extends Emitter<SocketEvents> {
|
|
14
14
|
private _id: number | null = null
|
|
15
15
|
|
|
16
|
-
constructor(url: string
|
|
16
|
+
constructor(url: string) {
|
|
17
17
|
super()
|
|
18
|
-
this._id =
|
|
18
|
+
this._id = _creatorSocket.open(url, (channel, data) => this._onMessage(channel, data))
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
/** Send a text frame. */
|
|
22
|
+
send(message: string): void {
|
|
23
|
+
if (this._id !== null) _creatorSocket.send(this._id, message)
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
close(): void {
|
|
26
27
|
if (this._id !== null) {
|
|
27
|
-
|
|
28
|
+
_creatorSocket.close(this._id)
|
|
28
29
|
this._id = null
|
|
29
30
|
}
|
|
30
31
|
}
|
package/src/runtime/rpc.ts
CHANGED
|
@@ -68,7 +68,6 @@ export const __rpc = (serverUrl: string, id: string) => {
|
|
|
68
68
|
}
|
|
69
69
|
let body: any
|
|
70
70
|
try { body = res.json() } catch { body = null }
|
|
71
|
-
res.dispose()
|
|
72
71
|
if (body && typeof body === "object" && "session" in body) setSession(serverUrl, body.session ?? null)
|
|
73
72
|
if (body && body.ok === true) return body.result
|
|
74
73
|
if (body && body.ok === false) throw new RpcError(body.status ?? res.status, body.message ?? "")
|
|
@@ -132,8 +131,7 @@ class ChannelSocket {
|
|
|
132
131
|
if (this.ws || this.timer !== null) return
|
|
133
132
|
this.closedByUs = false
|
|
134
133
|
const url = this.serverUrl.replace(/^http/, "ws") + "/ws"
|
|
135
|
-
const
|
|
136
|
-
const ws = new WebSocket(url, session ? { authorization: `Bearer ${session}` } : undefined)
|
|
134
|
+
const ws = new WebSocket(url)
|
|
137
135
|
this.ws = ws
|
|
138
136
|
ws.addEventListener("open", () => {
|
|
139
137
|
this.open = true
|
package/src/runtime/service.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// A host-registered headless service (geolocation, bluetooth, contacts, …) — the UI-less sibling
|
|
2
2
|
// of NativeView (docs/service-channel-plan.md). The host registers a *capability*
|
|
3
3
|
// (`registerService("geolocation") { params, channel -> ... }`); the SDK talks to it over the
|
|
4
|
-
// shared
|
|
4
|
+
// shared call / event protocol (runtime/channel.ts) via `_creatorService`.
|
|
5
5
|
//
|
|
6
6
|
// Not a public global itself: apps meet services through typed plugin wrappers (Geolocation —
|
|
7
7
|
// src/plugins/) or the raw `Service(name)` global (src/plugins/service.ts), both thin layers
|
|
@@ -26,18 +26,22 @@ export class ServiceClient {
|
|
|
26
26
|
|
|
27
27
|
/** Whether this host registered a `registerService` factory under `name`. */
|
|
28
28
|
static isSupported(name: string): boolean {
|
|
29
|
-
return typeof
|
|
30
|
-
|
|
29
|
+
return typeof _creatorService !== "undefined" && _creatorService.supported(name)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** The contract version of the host's service `name`: 0 when it has none, 1 for one written by hand. */
|
|
33
|
+
static version(name: string): number {
|
|
34
|
+
return typeof _creatorService !== "undefined" ? _creatorService.version(name) : 0
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/** @internal open (or reuse) the host session; throws when the host has no channel/factory. */
|
|
34
38
|
private _ensureOpen(): number {
|
|
35
39
|
if (this._serviceId !== null) return this._serviceId
|
|
36
|
-
if (typeof
|
|
37
|
-
throw new Error(`Service("${this.serviceName}") is not supported on this host (
|
|
40
|
+
if (typeof _creatorService === "undefined") {
|
|
41
|
+
throw new Error(`Service("${this.serviceName}") is not supported on this host (no service channel)`)
|
|
38
42
|
}
|
|
39
|
-
const id =
|
|
40
|
-
(event: string,
|
|
43
|
+
const id = _creatorService.open(this.serviceName, JSON.stringify(this.params ?? {}),
|
|
44
|
+
(event: string, data?: unknown) => _channelEmit(this._listeners, event, data))
|
|
41
45
|
if (id < 0) {
|
|
42
46
|
throw new Error(`Service("${this.serviceName}") is not registered on this host`)
|
|
43
47
|
}
|
|
@@ -46,8 +50,11 @@ export class ServiceClient {
|
|
|
46
50
|
}
|
|
47
51
|
|
|
48
52
|
/** Invoke a method on the service (`geo.call("getCurrent", options)`). Args and the result are
|
|
49
|
-
*
|
|
50
|
-
call(method: string, ...args: any[]): Promise<any> {
|
|
53
|
+
* plain data (and Uint8Array). Rejects if the host has no such service/method. */
|
|
54
|
+
call(method: string, ...args: any[]): Promise<any> { return this._channelSend(method, args) }
|
|
55
|
+
|
|
56
|
+
/** @internal One call over the channel (runtime/channel.ts). */
|
|
57
|
+
_channelSend(method: string, args: any[]): Promise<any> {
|
|
51
58
|
let id: number
|
|
52
59
|
try {
|
|
53
60
|
id = this._ensureOpen()
|
|
@@ -55,8 +62,8 @@ export class ServiceClient {
|
|
|
55
62
|
return Promise.reject(e)
|
|
56
63
|
}
|
|
57
64
|
return _channelCall(
|
|
58
|
-
typeof
|
|
59
|
-
`Service("${this.serviceName}") is not supported on this host (
|
|
65
|
+
typeof _creatorService !== "undefined" ? _creatorService : undefined, "call",
|
|
66
|
+
`Service("${this.serviceName}") is not supported on this host (no service channel)`,
|
|
60
67
|
id, method, args)
|
|
61
68
|
}
|
|
62
69
|
|
|
@@ -76,8 +83,6 @@ export class ServiceClient {
|
|
|
76
83
|
if (this._serviceId === null) return
|
|
77
84
|
const id = this._serviceId
|
|
78
85
|
this._serviceId = null
|
|
79
|
-
if (typeof
|
|
80
|
-
_creatorUtils.serviceClose(id)
|
|
81
|
-
}
|
|
86
|
+
if (typeof _creatorService !== "undefined") _creatorService.close(id)
|
|
82
87
|
}
|
|
83
88
|
}
|
package/src/runtime/share.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
// Share a media File (image/video) via the OS share sheet. Wraps `
|
|
1
|
+
// Share a media File (image/video) via the OS share sheet. Wraps `_creatorDevice.share`, which
|
|
2
2
|
// takes the host buffer id behind the File. On iOS the sheet also lets the user save to Files/Photos;
|
|
3
|
-
// on web it saves (downloads) the file. `text` is an optional caption/message.
|
|
3
|
+
// on web it saves (downloads) the file. `text` is an optional caption/message. A silent no-op on
|
|
4
|
+
// hosts without a share slot (headless).
|
|
4
5
|
|
|
5
6
|
import type { File, FetchResponse } from "./fetch"
|
|
6
7
|
|
|
7
8
|
export function share(media: File | FetchResponse, text?: string): void {
|
|
8
|
-
|
|
9
|
+
_creatorDevice.share?.((media as any)._h.id, text)
|
|
9
10
|
}
|
package/src/runtime/storage.ts
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
-
// Key/value persistence backed by the host. Same shape as the web localStorage
|
|
1
|
+
// Key/value persistence backed by the host (`_creatorStorage`). Same shape as the web localStorage
|
|
2
|
+
// subset projects use.
|
|
3
|
+
|
|
2
4
|
|
|
3
5
|
export const localStorage = {
|
|
4
6
|
getItem(key: string): string | null {
|
|
5
|
-
return
|
|
7
|
+
return _creatorStorage.get(key)
|
|
6
8
|
},
|
|
7
9
|
setItem(key: string, value: string): void {
|
|
8
|
-
|
|
10
|
+
_creatorStorage.set(key, value)
|
|
9
11
|
},
|
|
10
12
|
removeItem(key: string): void {
|
|
11
|
-
|
|
13
|
+
_creatorStorage.remove(key)
|
|
12
14
|
},
|
|
13
15
|
}
|