lecodes-web-host 2.0.1 → 2.0.3

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/api.d.ts CHANGED
@@ -46,12 +46,45 @@ export type PreviewError = {
46
46
  detail?: string;
47
47
  nativeFrames?: string[];
48
48
  };
49
+ /** A 3D engine in place of the runtime's: the `_creator` contract implemented in TypeScript
50
+ * (lecodes-gl-lite). The host opens and closes its scene, tells it the size, and disposes of it;
51
+ * the engine shows its own canvas. */
52
+ export type Engine3d = Record<string, any> & {
53
+ openScene(sceneId: number): void;
54
+ closeScene(): void;
55
+ _onResize?(width: number, height: number, factor: number): void;
56
+ _dispose?(): void;
57
+ };
58
+ /** The host's fetched buffers and media players: the ids a bundle hands an engine are these. */
59
+ export type HostStores = {
60
+ fetch(url: string, options: any, onComplete: (systemId: number, statusCode: number) => void, onReject: (err: Error) => void): unknown;
61
+ getBuffer(systemId: number): ArrayBuffer;
62
+ getBlob(systemId: number): Blob;
63
+ putBuffer(buffer: ArrayBuffer): number;
64
+ putLocalBuffer(name: string, buffer: ArrayBuffer): void;
65
+ hasLocalBuffer(name: string): boolean;
66
+ getLocalName(systemId: number): string | undefined;
67
+ getPlayer(id: number): HTMLVideoElement | HTMLAudioElement | null | undefined;
68
+ removePlayers(): void;
69
+ };
70
+ /** What the host lends an engine of the embedder's. */
71
+ export type EnginePage = {
72
+ /** A canvas over the whole container, its backing store in device px, hidden until shown. */
73
+ newCanvas(): HTMLCanvasElement;
74
+ preloads: Set<string>;
75
+ /** The painter behind `_creatorCanvas` (a texture made of a canvas of the app's is read here). */
76
+ canvasHost: any;
77
+ stores: HostStores;
78
+ };
49
79
  export type HostOptions = {
50
80
  /** The header of the bundle the host is for (`parseBundleHeader`): its engines decide the profile,
51
81
  * its materials the archives, its fonts the boot faces. */
52
82
  header?: BundleHeader;
53
83
  /** The runtime profile, in place of the header's (a host with no bundle yet: an editor). */
54
84
  profile?: HostProfile;
85
+ /** A 3D engine of the embedder's as `_creator`, on a profile that carries none (`2d`, `ui`):
86
+ * an editor's viewport on the lite engine. Ignored on the `full` profile. */
87
+ engine3d?: (page: EnginePage) => Promise<Engine3d>;
55
88
  /** false = an engine-only host: no UI renderer, no fonts. Default true. */
56
89
  ui?: boolean;
57
90
  /** The project, as the key its storage is kept under. Default: the header's name. */
@@ -147,6 +180,10 @@ export type Layer = {
147
180
  export type HostUI = {
148
181
  /** The layers the host paints now. */
149
182
  frame(): Layer[];
183
+ /** The node the runtime made last. An inspector that is told of an element as the SDK makes it
184
+ * (`__lecodesCaptureSites` as a function) ties the element to this node: the SDK calls straight
185
+ * after the create that made it. */
186
+ readonly lastCreated: number;
150
187
  /** The elements named `name`. */
151
188
  findAll(name: string): RenderNode[];
152
189
  /** The render tree as JSON / as text (what `lecodes render` writes). */
@@ -167,6 +204,10 @@ export type Host = {
167
204
  readonly world: Record<string, unknown>;
168
205
  /** The UI as the host paints it; null on an engine-only host. */
169
206
  readonly ui: HostUI | null;
207
+ /** The embedder's 3D engine (`engine3d`), as it made it; null where the runtime's own runs. */
208
+ engine3d(): Engine3d | null;
209
+ /** Bytes of the host's store, by their id: what a bundle fetched, what an engine captured. */
210
+ getBuffer(systemId: number): ArrayBuffer | undefined;
170
211
  /** Evaluate a bundle. Throws when the bundle is of another SDK major than this host. */
171
212
  run(code: string, options?: RunOptions): Run;
172
213
  /** A plugin's web half, by the id an app lists it under; a bundle gets the ones its header names. */
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ const assetsURL = function(dep, importerUrl) {
6
6
  return new URL(dep, importerUrl).href;
7
7
  };
8
8
  const seen = {};
9
- const __vitePreload = function preload(baseModule, deps, importerUrl) {
9
+ const __lecodesHostPreload = function preload(baseModule, deps, importerUrl) {
10
10
  let promise = Promise.resolve();
11
11
  if (deps && deps.length > 0) {
12
12
  let allSettled2 = function(promises) {
@@ -84,9 +84,9 @@ const __vitePreload = function preload(baseModule, deps, importerUrl) {
84
84
  });
85
85
  };
86
86
  const factories = {
87
- full: () => __vitePreload(() => import("./chunks/creator-full-BYRA3WbP.js"), true ? [] : void 0, import.meta.url),
88
- "2d": () => __vitePreload(() => import("./chunks/creator-2d-D13oxDsX.js"), true ? [] : void 0, import.meta.url),
89
- ui: () => __vitePreload(() => import("./chunks/creator-ui-Cvoc0NZK.js"), true ? [] : void 0, import.meta.url)
87
+ full: () => __lecodesHostPreload(() => import("./chunks/creator-full-BYRA3WbP.js"), true ? [] : void 0, import.meta.url),
88
+ "2d": () => __lecodesHostPreload(() => import("./chunks/creator-2d-D13oxDsX.js"), true ? [] : void 0, import.meta.url),
89
+ ui: () => __lecodesHostPreload(() => import("./chunks/creator-ui-Cvoc0NZK.js"), true ? [] : void 0, import.meta.url)
90
90
  };
91
91
  const wasmUrl = {
92
92
  full: () => new URL("" + new URL("assets/creator-full-D2dSDdDj.wasm", import.meta.url).href, import.meta.url).href,
@@ -767,6 +767,67 @@ const installWasmCrashSymbolizer = () => {
767
767
  };
768
768
  const arrayBuffers = [null];
769
769
  new TextDecoder("utf-8");
770
+ const _fetch = async (url, fetchOptions, onComplete, onReject) => {
771
+ let body = void 0;
772
+ if (fetchOptions.body) {
773
+ if (typeof fetchOptions.body === "object" && Array.isArray(fetchOptions.body._entries)) {
774
+ const formData = new FormData();
775
+ for (let [key, value, filename] of fetchOptions.body._entries) {
776
+ if (typeof value === "object") {
777
+ formData.append(key, new Blob([getBuffer(value._id)]), filename);
778
+ } else {
779
+ formData.append(key, value);
780
+ }
781
+ }
782
+ body = formData;
783
+ } else if (typeof fetchOptions.body === "string") {
784
+ body = fetchOptions.body;
785
+ } else {
786
+ onReject(new Error("Invalid body"));
787
+ }
788
+ }
789
+ const resp = await fetch(url, {
790
+ method: fetchOptions.method,
791
+ body,
792
+ headers: fetchOptions.headers,
793
+ mode: "cors",
794
+ credentials: "omit"
795
+ });
796
+ const onProgress = typeof fetchOptions.onProgress === "function" ? fetchOptions.onProgress : null;
797
+ if (!onProgress) {
798
+ const buffer = await resp.arrayBuffer();
799
+ arrayBuffers.push(buffer);
800
+ onComplete(arrayBuffers.length - 1, resp.status);
801
+ return;
802
+ }
803
+ const reader = resp.body?.getReader();
804
+ if (!reader) {
805
+ const buffer = await resp.arrayBuffer();
806
+ arrayBuffers.push(buffer);
807
+ onComplete(arrayBuffers.length - 1, resp.status);
808
+ return;
809
+ }
810
+ const contentLength = Number(resp.headers.get("content-length")) || void 0;
811
+ let loaded = 0;
812
+ const chunks = [];
813
+ while (true) {
814
+ const { done, value } = await reader.read();
815
+ if (done) break;
816
+ if (value) {
817
+ chunks.push(value);
818
+ loaded += value.length;
819
+ onProgress({ loaded, total: contentLength });
820
+ }
821
+ }
822
+ const result = new Uint8Array(loaded);
823
+ let offset = 0;
824
+ for (const chunk of chunks) {
825
+ result.set(chunk, offset);
826
+ offset += chunk.length;
827
+ }
828
+ arrayBuffers.push(result.buffer);
829
+ onComplete(arrayBuffers.length - 1, resp.status);
830
+ };
770
831
  const getUrl = (systemId) => {
771
832
  const blob = new Blob([arrayBuffers[systemId]]);
772
833
  return URL.createObjectURL(blob);
@@ -785,7 +846,17 @@ const putBuffer = (buffer) => {
785
846
  return arrayBuffers.length - 1;
786
847
  };
787
848
  const map = /* @__PURE__ */ new Map();
849
+ const putLocalBuffer = (str, buf) => {
850
+ map.set(str, buf);
851
+ const systemId = localResources.get(str);
852
+ if (systemId !== void 0) arrayBuffers[systemId] = buf;
853
+ };
854
+ const hasLocalBuffer = (str) => map.has(str);
788
855
  const localResources = /* @__PURE__ */ new Map();
856
+ const getLocalName = (systemId) => {
857
+ for (const [name, id] of localResources) if (id === systemId) return name;
858
+ return void 0;
859
+ };
789
860
  const _fetchLocal = (path) => {
790
861
  const existsSystemId = localResources.get(path);
791
862
  if (existsSystemId !== void 0) {
@@ -1280,11 +1351,24 @@ class Canvas2DPainter {
1280
1351
  }
1281
1352
  drawImage(image, srcRect, dst, opts) {
1282
1353
  if (!image.handle) return;
1283
- const { src, dst: d } = srcRect ? { src: srcRect, dst } : computeObjectFit(image.naturalWidth, image.naturalHeight, dst, opts.fit);
1354
+ const fit = srcRect ? { src: srcRect, dst } : computeObjectFit(image.naturalWidth, image.naturalHeight, dst, opts.fit);
1355
+ const d = fit.dst;
1356
+ let src = fit.src;
1284
1357
  const dw = Math.max(1, Math.ceil(d.width * this.dpr));
1285
1358
  const dh = Math.max(1, Math.ceil(d.height * this.dpr));
1286
- const handle = image.rasterize ? image.rasterize(dw, dh) : image.handle;
1287
1359
  const whole = !srcRect && opts.fit !== "cover";
1360
+ let handle = image.handle;
1361
+ if (image.rasterize) {
1362
+ if (whole) handle = image.rasterize(dw, dh);
1363
+ else {
1364
+ const k = Math.max(dw / src.width, dh / src.height);
1365
+ const rw = Math.max(1, Math.ceil(image.naturalWidth * k));
1366
+ const rh = Math.max(1, Math.ceil(image.naturalHeight * k));
1367
+ handle = image.rasterize(rw, rh);
1368
+ const kx = rw / image.naturalWidth, ky = rh / image.naturalHeight;
1369
+ src = { x: src.x * kx, y: src.y * ky, width: src.width * kx, height: src.height * ky };
1370
+ }
1371
+ }
1288
1372
  if (opts.tint === void 0) {
1289
1373
  if (whole) this.ctx.drawImage(handle, d.x, d.y, d.width, d.height);
1290
1374
  else this.ctx.drawImage(handle, src.x, src.y, src.width, src.height, d.x, d.y, d.width, d.height);
@@ -3871,7 +3955,7 @@ const loadBootFonts = async (fonts, doc, budgetMs = 2e3) => {
3871
3955
  }
3872
3956
  await Promise.race([Promise.all(loads), new Promise((res) => setTimeout(res, budgetMs))]);
3873
3957
  };
3874
- const SDK_VERSION = "2.0.1";
3958
+ const SDK_VERSION = "2.0.3";
3875
3959
  const sdkMajor = (version) => {
3876
3960
  const m = version?.match(/^(\d+)\.\d+\.\d+/);
3877
3961
  return m ? Number(m[1]) : null;
@@ -4963,6 +5047,7 @@ const makeWorld = () => {
4963
5047
  };
4964
5048
  return { global, evaluate };
4965
5049
  };
5050
+ const stores = { fetch: _fetch, getBuffer, getBlob, putBuffer, putLocalBuffer, hasLocalBuffer, getLocalName, getPlayer, removePlayers };
4966
5051
  const nullHostUI = (onDestination, onDropped) => ({
4967
5052
  nodeFactory() {
4968
5053
  },
@@ -5044,15 +5129,27 @@ const createHost$1 = async (root, options = {}) => {
5044
5129
  M._free(p);
5045
5130
  }
5046
5131
  };
5132
+ let engine3d = null;
5133
+ let engine3dScene = -1;
5047
5134
  let destination = -1;
5048
- const onDestination = (kind) => {
5135
+ const onDestination = (kind, id) => {
5049
5136
  destination = kind;
5050
5137
  if (kind === TREE_DEST_SCENE3D) gl?.show();
5051
5138
  if (kind === TREE_DEST_SCENE2D) scene2d?.show();
5139
+ if (engine3d && kind === TREE_DEST_SCENE3D && id !== engine3dScene) {
5140
+ engine3dScene = id;
5141
+ engine3d.openScene(id);
5142
+ }
5052
5143
  };
5053
5144
  const onDropped = (kind) => {
5054
5145
  if (kind === destination) return;
5055
- if (kind === TREE_DEST_SCENE3D) gl?.hide();
5146
+ if (kind === TREE_DEST_SCENE3D) {
5147
+ gl?.hide();
5148
+ if (engine3d && engine3dScene >= 0) {
5149
+ engine3dScene = -1;
5150
+ engine3d.closeScene();
5151
+ }
5152
+ }
5056
5153
  if (kind === TREE_DEST_SCENE2D) scene2d?.hide();
5057
5154
  };
5058
5155
  const canvasHost = createCanvasHost();
@@ -5110,12 +5207,17 @@ const createHost$1 = async (root, options = {}) => {
5110
5207
  M._creatorWebInit();
5111
5208
  if (options.launchUrl) withStr(options.launchUrl, (p) => M._creatorWebSetLaunchUrl(p));
5112
5209
  renderer?.ui.install();
5210
+ if (!has3d && options.engine3d) {
5211
+ engine3d = await options.engine3d({ newCanvas: page.newCanvas, preloads, canvasHost, stores });
5212
+ Object.defineProperty(globals, "_creator", { value: engine3d, configurable: true, writable: true, enumerable: false });
5213
+ }
5113
5214
  if (has3d && header?.gl !== "defer") globals._creator?.createEngine();
5114
5215
  const safeArea = createSafeArea(root);
5115
5216
  if (options.safeArea) safeArea.set(options.safeArea);
5116
5217
  const resize = (size) => {
5117
5218
  gl?.resize();
5118
5219
  scene2d?.resize();
5220
+ engine3d?._onResize?.(size.width, size.height, size.factor);
5119
5221
  if (renderer) {
5120
5222
  renderer.resize(size.width, size.height, size.factor);
5121
5223
  renderer.setSafeArea(safeArea.current());
@@ -5146,6 +5248,10 @@ const createHost$1 = async (root, options = {}) => {
5146
5248
  world: globals,
5147
5249
  /** The UI as the renderer holds it (the render tree, the pointer routing); null on an engine-only host. */
5148
5250
  ui: renderer?.ui ?? null,
5251
+ /** The embedder's 3D engine (`engine3d`), as it made it; null where the runtime's own runs. */
5252
+ engine3d: () => engine3d,
5253
+ /** Bytes of the host's store, by their id: what a bundle fetched, what an engine captured. */
5254
+ getBuffer: (systemId) => getBuffer(systemId),
5149
5255
  /** Evaluate a bundle. Throws when the bundle is of another SDK major than this host. */
5150
5256
  run(code, runOptions = {}) {
5151
5257
  const bundle = runOptions.header ?? parseBundleHeader$1(code);
@@ -5184,6 +5290,7 @@ const createHost$1 = async (root, options = {}) => {
5184
5290
  app.dispose();
5185
5291
  removePlayers();
5186
5292
  safeArea.destroy();
5293
+ engine3d?._dispose?.();
5187
5294
  gl?.destroy();
5188
5295
  scene2d?.destroy();
5189
5296
  page.destroy();