@bitmagic/cli 0.1.38 → 0.1.40

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.
Files changed (52) hide show
  1. package/README.md +59 -12
  2. package/dist/auth/subscribe-flow.d.ts +59 -0
  3. package/dist/auth/subscribe-flow.js +102 -0
  4. package/dist/auth/subscribe-flow.js.map +1 -0
  5. package/dist/cli.d.ts +14 -0
  6. package/dist/cli.js +2 -0
  7. package/dist/cli.js.map +1 -1
  8. package/dist/commands/login.js +27 -7
  9. package/dist/commands/login.js.map +1 -1
  10. package/dist/commands/subscribe.d.ts +14 -0
  11. package/dist/commands/subscribe.js +60 -0
  12. package/dist/commands/subscribe.js.map +1 -0
  13. package/dist/commands/verify.js +16 -3
  14. package/dist/commands/verify.js.map +1 -1
  15. package/dist/commands/whoami.d.ts +10 -14
  16. package/dist/commands/whoami.js +17 -12
  17. package/dist/commands/whoami.js.map +1 -1
  18. package/dist/editor/asset-detail-panel.d.ts +22 -0
  19. package/dist/editor/asset-detail-panel.js +72 -0
  20. package/dist/editor/asset-detail-panel.js.map +1 -0
  21. package/dist/editor/asset-preview-page.d.ts +6 -0
  22. package/dist/editor/asset-preview-page.js +103 -0
  23. package/dist/editor/asset-preview-page.js.map +1 -0
  24. package/dist/editor/asset-viewer.d.ts +19 -0
  25. package/dist/editor/asset-viewer.js +218 -0
  26. package/dist/editor/asset-viewer.js.map +1 -0
  27. package/dist/editor/server.js +128 -0
  28. package/dist/editor/server.js.map +1 -1
  29. package/dist/editor/shell-page.js +309 -30
  30. package/dist/editor/shell-page.js.map +1 -1
  31. package/dist/http/client.d.ts +10 -0
  32. package/dist/http/client.js +38 -3
  33. package/dist/http/client.js.map +1 -1
  34. package/dist/scaffold/project-files.d.ts +6 -0
  35. package/dist/scaffold/project-files.js +9 -3
  36. package/dist/scaffold/project-files.js.map +1 -1
  37. package/dist/verify/browser.js +30 -0
  38. package/dist/verify/browser.js.map +1 -1
  39. package/dist/verify/classify.d.ts +4 -0
  40. package/dist/verify/classify.js +26 -0
  41. package/dist/verify/classify.js.map +1 -1
  42. package/dist/verify/events.d.ts +41 -0
  43. package/dist/verify/events.js +72 -0
  44. package/dist/verify/events.js.map +1 -0
  45. package/dist/verify/iterate.js +3 -1
  46. package/dist/verify/iterate.js.map +1 -1
  47. package/dist/verify/result.d.ts +5 -1
  48. package/dist/verify/result.js.map +1 -1
  49. package/dist/verify/url.d.ts +12 -0
  50. package/dist/verify/url.js +14 -0
  51. package/dist/verify/url.js.map +1 -0
  52. package/package.json +2 -1
@@ -2,19 +2,24 @@ import { defineCommand } from 'citty';
2
2
  import { resolveEnvironment, resolveAuth0ClientId } from '../config/environments.js';
3
3
  import { readDefaultEnvironment } from '../config/credentials.js';
4
4
  import { getAccessToken } from '../auth/session.js';
5
- import { apiGet } from '../http/client.js';
5
+ import { fetchWhoami, defaultSubscribeDeps, describeProStatus } from '../auth/subscribe-flow.js';
6
6
  import { createOutput } from '../output.js';
7
- const deviceFlowDeps = { fetch: globalThis.fetch, sleep: (ms) => new Promise((r) => setTimeout(r, ms)) };
7
+ /**
8
+ * `bitmagic whoami` — who the CLI is authenticated as, and whether that account
9
+ * may use it.
10
+ *
11
+ * The endpoint behind this sits outside the CLI's entitlement gate, so it
12
+ * answers for an unsubscribed creator too — that is the whole point: "why is
13
+ * this refusing me" needs an answer that is not itself refused.
14
+ */
8
15
  export async function runWhoami(explicitEnv) {
9
16
  const environment = resolveEnvironment({
10
17
  explicit: explicitEnv,
11
18
  storedDefault: readDefaultEnvironment(),
12
19
  });
13
20
  const clientId = resolveAuth0ClientId(environment);
14
- const token = await getAccessToken(environment, clientId, deviceFlowDeps);
15
- return apiGet(environment, '/api/cli/v1/whoami', token, {
16
- fetch: globalThis.fetch,
17
- });
21
+ const token = await getAccessToken(environment, clientId, defaultSubscribeDeps);
22
+ return fetchWhoami(environment, token, defaultSubscribeDeps);
18
23
  }
19
24
  export const whoamiCommand = defineCommand({
20
25
  meta: { name: 'whoami', description: 'Show the identity the CLI is authenticated as.' },
@@ -30,16 +35,16 @@ export const whoamiCommand = defineCommand({
30
35
  const me = await runWhoami(args.env);
31
36
  output.info(`user: ${me.userId}`);
32
37
  output.info(`roles: ${me.roles.length > 0 ? me.roles.join(', ') : '(none)'}`);
33
- if (me.access) {
34
- const until = me.pro === true && me.proUntil ? ` (renews ${me.proUntil.slice(0, 10)})` : '';
35
- output.info(`access: ${me.access}${until}`);
36
- }
38
+ output.info(`pro: ${describeProStatus(me)}`);
39
+ if (!me.pro)
40
+ output.info(' Run `bitmagic subscribe` to use the CLI.');
37
41
  output.result({
38
42
  ok: true,
39
43
  userId: me.userId,
40
44
  roles: me.roles,
41
- ...(me.access ? { access: me.access } : {}),
42
- ...(me.pro === undefined ? {} : { pro: me.pro, proUntil: me.proUntil ?? null }),
45
+ pro: me.pro,
46
+ proUntil: me.proUntil,
47
+ cancelAtPeriodEnd: me.cancelAtPeriodEnd ?? false,
43
48
  });
44
49
  },
45
50
  });
@@ -1 +1 @@
1
- {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"whoami.js","sourceRoot":"","sources":["../../src/commands/whoami.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,iBAAiB,EAAuB,MAAM,2BAA2B,CAAC;AACtH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAI5C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,WAAoB;IAClD,MAAM,WAAW,GAAG,kBAAkB,CAAC;QACrC,QAAQ,EAAE,WAAW;QACrB,aAAa,EAAE,sBAAsB,EAAE;KACxC,CAAC,CAAC;IACH,MAAM,QAAQ,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,WAAW,EAAE,QAAQ,EAAE,oBAAoB,CAAC,CAAC;IAChF,OAAO,WAAW,CAAC,WAAW,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAC;IACzC,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gDAAgD,EAAE;IACvF,IAAI,EAAE;QACJ,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0CAA0C,EAAE;QAChF,IAAI,EAAE;YACJ,IAAI,EAAE,SAAS;YACf,WAAW,EAAE,kEAAkE;SAChF;KACF;IACD,KAAK,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE;QAChB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;QAChD,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,MAAM,EAAE,CAAC,CAAC;QACnC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAE9E,MAAM,CAAC,IAAI,CAAC,UAAU,iBAAiB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAC/C,IAAI,CAAC,EAAE,CAAC,GAAG;YAAE,MAAM,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;QAE5E,MAAM,CAAC,MAAM,CAAC;YACZ,EAAE,EAAE,IAAI;YACR,MAAM,EAAE,EAAE,CAAC,MAAM;YACjB,KAAK,EAAE,EAAE,CAAC,KAAK;YACf,GAAG,EAAE,EAAE,CAAC,GAAG;YACX,QAAQ,EAAE,EAAE,CAAC,QAAQ;YACrB,iBAAiB,EAAE,EAAE,CAAC,iBAAiB,IAAI,KAAK;SACjD,CAAC,CAAC;IACL,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The asset overlay's CSS and markup, composed into `shell-page.ts`.
3
+ *
4
+ * Split out for one reason: that file is the busiest in the package — it grew past 1400 lines and
5
+ * two sessions have added panel features to it in a day. Its style and its markup are the parts
6
+ * that carry no closure state, so they can leave without threading anything across a boundary. The
7
+ * overlay's behaviour stays inline there, because it needs `postFlat`, `await_`, `loaded`,
8
+ * `gameState` and `GAME_URL` from the enclosing IIFE.
9
+ *
10
+ * Same template-literal rules as its host: **no backticks**, and a backslash in a regex has to be
11
+ * doubled to survive the literal.
12
+ */
13
+ /** Styles for `#asset-overlay`, appended to the shell's stylesheet. */
14
+ export declare const ASSET_DETAIL_CSS = "\n /* Modelled on #hq-overlay, deliberately: the dev view has one modal idiom and this is it. */\n #asset-overlay { display: none; position: fixed; inset: 0; background: rgba(8,8,12,.78);\n align-items: center; justify-content: center; z-index: 11; }\n #asset-overlay.show { display: flex; }\n #asset-dialog { display: flex; width: min(1100px, 94vw); height: min(760px, 88vh);\n background: #17171d; border: 1px solid #34343f; border-radius: 8px;\n overflow: hidden; }\n /* The viewer takes the room. Everything else is a caption. */\n #asset-view { flex: 1; min-width: 0; position: relative; background: #1e293b; }\n #asset-view iframe { display: block; width: 100%; height: 100%; border: 0; }\n #asset-view img { display: block; width: 100%; height: 100%; object-fit: contain;\n background: #101014; }\n #asset-view audio { position: absolute; left: 50%; top: 50%; width: min(420px, 80%);\n transform: translate(-50%, -50%); }\n #asset-note { position: absolute; inset: auto 0 0 0; padding: 10px 12px; background: #101014e6;\n color: #e0938e; font-size: 12px; }\n #asset-note.hidden, #asset-view > .hidden { display: none; }\n #asset-meta { width: 268px; flex: none; padding: 14px; overflow-y: auto;\n border-left: 1px solid #26262e; }\n #asset-meta h2 { margin: 0 0 2px; font-size: 15px; word-break: break-word; }\n #asset-meta .sub { color: #6a6a7a; font-size: 11px; margin-bottom: 12px; }\n #asset-facts { display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; font-size: 12px; }\n #asset-facts dt { color: #6a6a7a; }\n #asset-facts dd { margin: 0; color: #d9d9e2; word-break: break-word; }\n #asset-facts dd.mono { font-family: ui-monospace, monospace; font-size: 11px; }\n #asset-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }\n #asset-close { margin-left: auto; }\n";
15
+ /**
16
+ * The overlay itself.
17
+ *
18
+ * `<audio controls>` rather than a play button of our own: the web Creator withholds native
19
+ * controls because its player sits in a 46px row, and here there is a whole dialog. A creator
20
+ * checking a sound effect wants the scrubber.
21
+ */
22
+ export declare const ASSET_DETAIL_HTML = "\n <div id=\"asset-overlay\">\n <div id=\"asset-dialog\">\n <div id=\"asset-view\">\n <iframe id=\"asset-frame\" class=\"hidden\" title=\"Asset preview\"></iframe>\n <img id=\"asset-image\" class=\"hidden\" alt=\"\">\n <audio id=\"asset-audio\" class=\"hidden\" controls preload=\"none\"></audio>\n <div id=\"asset-note\" class=\"hidden\"></div>\n </div>\n <aside id=\"asset-meta\">\n <h2 id=\"asset-name\"></h2>\n <div class=\"sub\" id=\"asset-sub\"></div>\n <dl id=\"asset-facts\"></dl>\n <div id=\"asset-actions\">\n <button id=\"asset-reset\" type=\"button\" title=\"Back to the angle the thumbnail uses\">Reset view</button>\n <button id=\"asset-copy\" type=\"button\" title=\"Copy this asset's id\">Copy id</button>\n <button id=\"asset-close\" type=\"button\">Close</button>\n </div>\n </aside>\n </div>\n </div>\n";
@@ -0,0 +1,72 @@
1
+ /**
2
+ * The asset overlay's CSS and markup, composed into `shell-page.ts`.
3
+ *
4
+ * Split out for one reason: that file is the busiest in the package — it grew past 1400 lines and
5
+ * two sessions have added panel features to it in a day. Its style and its markup are the parts
6
+ * that carry no closure state, so they can leave without threading anything across a boundary. The
7
+ * overlay's behaviour stays inline there, because it needs `postFlat`, `await_`, `loaded`,
8
+ * `gameState` and `GAME_URL` from the enclosing IIFE.
9
+ *
10
+ * Same template-literal rules as its host: **no backticks**, and a backslash in a regex has to be
11
+ * doubled to survive the literal.
12
+ */
13
+ /** Styles for `#asset-overlay`, appended to the shell's stylesheet. */
14
+ export const ASSET_DETAIL_CSS = `
15
+ /* Modelled on #hq-overlay, deliberately: the dev view has one modal idiom and this is it. */
16
+ #asset-overlay { display: none; position: fixed; inset: 0; background: rgba(8,8,12,.78);
17
+ align-items: center; justify-content: center; z-index: 11; }
18
+ #asset-overlay.show { display: flex; }
19
+ #asset-dialog { display: flex; width: min(1100px, 94vw); height: min(760px, 88vh);
20
+ background: #17171d; border: 1px solid #34343f; border-radius: 8px;
21
+ overflow: hidden; }
22
+ /* The viewer takes the room. Everything else is a caption. */
23
+ #asset-view { flex: 1; min-width: 0; position: relative; background: #1e293b; }
24
+ #asset-view iframe { display: block; width: 100%; height: 100%; border: 0; }
25
+ #asset-view img { display: block; width: 100%; height: 100%; object-fit: contain;
26
+ background: #101014; }
27
+ #asset-view audio { position: absolute; left: 50%; top: 50%; width: min(420px, 80%);
28
+ transform: translate(-50%, -50%); }
29
+ #asset-note { position: absolute; inset: auto 0 0 0; padding: 10px 12px; background: #101014e6;
30
+ color: #e0938e; font-size: 12px; }
31
+ #asset-note.hidden, #asset-view > .hidden { display: none; }
32
+ #asset-meta { width: 268px; flex: none; padding: 14px; overflow-y: auto;
33
+ border-left: 1px solid #26262e; }
34
+ #asset-meta h2 { margin: 0 0 2px; font-size: 15px; word-break: break-word; }
35
+ #asset-meta .sub { color: #6a6a7a; font-size: 11px; margin-bottom: 12px; }
36
+ #asset-facts { display: grid; grid-template-columns: auto 1fr; gap: 4px 10px; font-size: 12px; }
37
+ #asset-facts dt { color: #6a6a7a; }
38
+ #asset-facts dd { margin: 0; color: #d9d9e2; word-break: break-word; }
39
+ #asset-facts dd.mono { font-family: ui-monospace, monospace; font-size: 11px; }
40
+ #asset-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 14px; }
41
+ #asset-close { margin-left: auto; }
42
+ `;
43
+ /**
44
+ * The overlay itself.
45
+ *
46
+ * `<audio controls>` rather than a play button of our own: the web Creator withholds native
47
+ * controls because its player sits in a 46px row, and here there is a whole dialog. A creator
48
+ * checking a sound effect wants the scrubber.
49
+ */
50
+ export const ASSET_DETAIL_HTML = `
51
+ <div id="asset-overlay">
52
+ <div id="asset-dialog">
53
+ <div id="asset-view">
54
+ <iframe id="asset-frame" class="hidden" title="Asset preview"></iframe>
55
+ <img id="asset-image" class="hidden" alt="">
56
+ <audio id="asset-audio" class="hidden" controls preload="none"></audio>
57
+ <div id="asset-note" class="hidden"></div>
58
+ </div>
59
+ <aside id="asset-meta">
60
+ <h2 id="asset-name"></h2>
61
+ <div class="sub" id="asset-sub"></div>
62
+ <dl id="asset-facts"></dl>
63
+ <div id="asset-actions">
64
+ <button id="asset-reset" type="button" title="Back to the angle the thumbnail uses">Reset view</button>
65
+ <button id="asset-copy" type="button" title="Copy this asset's id">Copy id</button>
66
+ <button id="asset-close" type="button">Close</button>
67
+ </div>
68
+ </aside>
69
+ </div>
70
+ </div>
71
+ `;
72
+ //# sourceMappingURL=asset-detail-panel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-detail-panel.js","sourceRoot":"","sources":["../../src/editor/asset-detail-panel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,uEAAuE;AACvE,MAAM,CAAC,MAAM,gBAAgB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4B/B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG;;;;;;;;;;;;;;;;;;;;;CAqBhC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export interface AssetPreviewPageOptions {
2
+ /** Port vite serves the project on — where the engine modules come from. Never hardcoded: a
3
+ * default port drifts inside 3000–3199 when it is taken. */
4
+ gamePort: number;
5
+ }
6
+ export declare function renderAssetPreviewPage(options: AssetPreviewPageOptions): string;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * The page that hosts `asset-viewer.ts`, served by the sidecar at `/asset-preview`.
3
+ *
4
+ * Two things here are load-bearing.
5
+ *
6
+ * **The import map is generated, not transcribed.** `three` and its addons come from the same
7
+ * `CDN_IMPORTS` a scaffolded `index.html` is built from, and the `engine/` prefixes from the same
8
+ * `importMapEntries()` — with the project's vite origin in front, because those modules live on the
9
+ * other port. The repo already maintains five copies of this map by hand and has a contract test
10
+ * guarding them precisely because a partial update once broke every page; a sixth copy is a
11
+ * liability, and this is the way to have the map without having a copy of it.
12
+ *
13
+ * **The engine prefixes point at the game's origin.** Vite's default `server.cors.origin` is
14
+ * `defaultAllowedOrigins`, which matches any `localhost`/`127.0.0.1` port, so a page served from
15
+ * the sidecar may import modules from the project's vite server with no configuration anywhere. It
16
+ * matters that they come from the *project* rather than from this package: the voxel decoder must
17
+ * be the one the creator's own engine writes files with.
18
+ */
19
+ import { CDN_IMPORTS } from '../scaffold/project-files.js';
20
+ import { importMapEntries } from '../scaffold/aliases.js';
21
+ /** Attribute- and script-safe: these land inside a `<script type="importmap">` block. */
22
+ function importMapFor(gamePort) {
23
+ const origin = `http://localhost:${gamePort}`;
24
+ const engine = Object.fromEntries(Object.entries(importMapEntries()).map(([name, target]) => [name, `${origin}${target}`]));
25
+ return { ...CDN_IMPORTS, ...engine };
26
+ }
27
+ export function renderAssetPreviewPage(options) {
28
+ const imports = JSON.stringify({ imports: importMapFor(options.gamePort) }, null, 4);
29
+ return `<!DOCTYPE html>
30
+ <html lang="en">
31
+ <head>
32
+ <meta charset="utf-8">
33
+ <title>Asset preview</title>
34
+ <style>
35
+ html, body { margin: 0; height: 100%; background: #1e293b; overflow: hidden;
36
+ font: 12px/1.5 system-ui, -apple-system, sans-serif; color: #c8c8d4; }
37
+ #stage { position: absolute; inset: 0; }
38
+ #stage canvas { display: block; width: 100%; height: 100%; }
39
+ /* Sits over the canvas rather than beside it: the dialog gives this iframe every pixel, and a
40
+ status line that reserved space would shrink the thing being looked at. */
41
+ #status { position: absolute; left: 0; right: 0; top: 0; padding: 10px 12px;
42
+ background: linear-gradient(#1e293bcc, #1e293b00); pointer-events: none; }
43
+ #status.failed { color: #e0938e; }
44
+ #hint { position: absolute; left: 12px; bottom: 10px; color: #7d8595; pointer-events: none; }
45
+ #hint.hidden, #status.hidden { display: none; }
46
+ </style>
47
+ </head>
48
+ <body>
49
+ <div id="stage"></div>
50
+ <div id="status">Loading…</div>
51
+ <div id="hint" class="hidden">Drag to turn · scroll to zoom</div>
52
+
53
+ <script type="importmap">
54
+ ${imports}
55
+ </script>
56
+
57
+ <script type="module">
58
+ import { startAssetViewer } from '/asset-viewer.js';
59
+
60
+ var params = new URLSearchParams(window.location.search);
61
+ var status = document.getElementById('status');
62
+ var hint = document.getElementById('hint');
63
+
64
+ /**
65
+ * The parent is the dev view's shell on the sidecar's own origin, so this is same-origin and the
66
+ * message is only a convenience. It is still the shell's signal that the viewer works: no READY
67
+ * within a few seconds and it swaps this iframe for the cached thumbnail, which is what a project
68
+ * whose engine cannot serve the voxel modules falls back to.
69
+ */
70
+ function tell(message) {
71
+ try { window.parent.postMessage(message, window.location.origin); } catch (error) { /* detached */ }
72
+ }
73
+
74
+ try {
75
+ var viewer = await startAssetViewer({
76
+ container: document.getElementById('stage'),
77
+ url: params.get('url') || '',
78
+ kind: params.get('kind') === 'vxl' ? 'vxl' : 'glb',
79
+ sourceEndpoint: '/api/assets/source',
80
+ });
81
+ status.classList.add('hidden');
82
+ hint.classList.remove('hidden');
83
+ tell({ type: 'ASSET_PREVIEW_READY', triangles: viewer.triangles });
84
+ window.addEventListener('message', function (event) {
85
+ if (event.origin === window.location.origin && event.data && event.data.type === 'RESET_VIEW') {
86
+ viewer.resetView();
87
+ }
88
+ });
89
+ // Nothing else holds this: the shell drops the whole iframe on close, and pagehide is what runs
90
+ // when it does. Without it the GPU keeps the context until the collector gets round to it.
91
+ window.addEventListener('pagehide', function () { viewer.dispose(); });
92
+ } catch (error) {
93
+ var reason = error && error.message ? error.message : String(error);
94
+ status.textContent = reason;
95
+ status.classList.add('failed');
96
+ tell({ type: 'ASSET_PREVIEW_FAILED', error: reason });
97
+ }
98
+ </script>
99
+ </body>
100
+ </html>
101
+ `;
102
+ }
103
+ //# sourceMappingURL=asset-preview-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-preview-page.js","sourceRoot":"","sources":["../../src/editor/asset-preview-page.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAQ1D,yFAAyF;AACzF,SAAS,YAAY,CAAC,QAAgB;IACpC,MAAM,MAAM,GAAG,oBAAoB,QAAQ,EAAE,CAAC;IAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAC/B,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CACzF,CAAC;IACF,OAAO,EAAE,GAAG,WAAW,EAAE,GAAG,MAAM,EAAE,CAAC;AACvC,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAgC;IACrE,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACrF,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;EAyBP,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+CR,CAAC;AACF,CAAC"}
@@ -0,0 +1,19 @@
1
+ /** What the panel knows about an asset, reduced to what there is to draw. */
2
+ export type AssetViewerKind = 'glb' | 'vxl';
3
+ export interface AssetViewerOptions {
4
+ /** The element to fill. Sized by CSS; the viewer follows it. */
5
+ container: HTMLElement;
6
+ /** The GLB or VXL to show. For `vxl` this is fetched through the sidecar, not directly. */
7
+ url: string;
8
+ kind: AssetViewerKind;
9
+ /** Where to fetch bytes from, so the caller can point this at its own origin. */
10
+ sourceEndpoint: string;
11
+ }
12
+ export interface AssetViewerHandle {
13
+ /** Back to the angle the thumbnail was rendered at. */
14
+ resetView(): void;
15
+ /** Triangles in the loaded object — the one number that says "this is a heavy asset". */
16
+ readonly triangles: number;
17
+ dispose(): void;
18
+ }
19
+ export declare function startAssetViewer(options: AssetViewerOptions): Promise<AssetViewerHandle>;
@@ -0,0 +1,218 @@
1
+ /**
2
+ * The orbitable asset viewer the dev view opens when a creator clicks a row in the assets panel.
3
+ *
4
+ * ── Why this is browser code living in `cli/src` ─────────────────────────────────────────────
5
+ *
6
+ * The obvious home is `game/`, next to the engine's own preview renderers, served as a standalone
7
+ * page the way `game/animation-preview.html` already is. That route was designed and rejected on
8
+ * distribution, not on rendering:
9
+ *
10
+ * - The engine tarball ships `game/src/**`, `game/agent-docs`, `sw-cache-buster.js` and
11
+ * `templates` — and **no `game/*.html`**. A page under `game/` can never reach a scaffolded
12
+ * project; it would have to be hand-retyped as a second copy inside `scaffold/project-files.ts`,
13
+ * with nothing comparing the two.
14
+ * - A module under `game/src/debug/` reaches projects only through a nightly tarball plus
15
+ * `bitmagic upgrade` — which refuses a dirty git tree and replaces `engine/` wholesale. That
16
+ * asks a creator to commit their work and take a full engine bump in order to look at a tree.
17
+ *
18
+ * `editor/server.ts`'s own header already states the principle this follows: `bitmagic dev` ships
19
+ * its own shell and its own endpoints, so an old project gets the new view by upgrading the CLI
20
+ * alone. This file is that — served from `dist/` by the sidecar at `/asset-viewer.js`.
21
+ *
22
+ * Unlike `shell-page.ts`, which is a string and therefore invisible to tsc, this is real
23
+ * TypeScript: it is type-checked and linted with the rest of the package. `cli/tsconfig.json`
24
+ * already carries `lib: ["DOM"]` for exactly this kind of file.
25
+ *
26
+ * ── The one import map, and why `three` must be the engine's `three` ─────────────────────────
27
+ *
28
+ * Bare specifiers here (`three`, `three/examples/jsm/…`, and the two `engine/…` modules the voxel
29
+ * path pulls) are resolved by the import map on the page that loads this — generated in
30
+ * `asset-preview-page.ts` from the same `CDN_IMPORTS` a scaffolded `index.html` uses, with the
31
+ * `engine/` prefixes pointed at the project's own vite server.
32
+ *
33
+ * That sharing is not tidiness. `three` resolves to the WEBGPU build, and r185 matches lights to
34
+ * materials by class identity: a viewer that pulled the classic build while the engine modules
35
+ * pulled the webgpu one would produce two module instances and render every voxel mesh unlit. So
36
+ * this file imports `three` exactly as the engine does — including taking `WebGLRenderer` from its
37
+ * own module, because the webgpu build does not export one.
38
+ */
39
+ import * as THREE from 'three';
40
+ import { WebGLRenderer } from 'three/src/renderers/WebGLRenderer.js';
41
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
42
+ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
43
+ /**
44
+ * The framing every Bitmagic thumbnail uses: 30° above the horizon, 45° around Y, at 2.8 × the
45
+ * bounding sphere's radius.
46
+ *
47
+ * Copied deliberately from `GlbPreviewRenderer` and `VoxelPreviewRenderer`, which both use it, so
48
+ * the overlay opens on exactly the picture the creator clicked and only then lets them move. A
49
+ * viewer that opened on some other angle would read as a different asset for the first second.
50
+ */
51
+ const CAMERA_ELEVATION = Math.PI / 6;
52
+ const CAMERA_AZIMUTH = Math.PI / 4;
53
+ const CAMERA_DISTANCE_FACTOR = 2 * 1.4;
54
+ /** The engine's preview lighting, so a mesh looks the same here as it does in its tile. */
55
+ function addPreviewLights(scene) {
56
+ scene.add(new THREE.AmbientLight(0xffffff, 1.2));
57
+ const key = new THREE.DirectionalLight(0xffffff, 1.5);
58
+ key.position.set(5, 10, 7);
59
+ scene.add(key);
60
+ const fill = new THREE.DirectionalLight(0xffffff, 0.8);
61
+ fill.position.set(-3, 2, -5);
62
+ scene.add(fill);
63
+ }
64
+ function asMesh(object) {
65
+ const candidate = object;
66
+ return candidate.isMesh === true ? candidate : null;
67
+ }
68
+ function disposeMaterial(material) {
69
+ for (const value of Object.values(material)) {
70
+ const texture = value;
71
+ if (texture !== null && typeof texture === 'object' && texture.isTexture === true) {
72
+ texture.dispose?.();
73
+ }
74
+ }
75
+ material.dispose?.();
76
+ }
77
+ /** Recursively release geometries, materials and textures. A viewer opened and closed twenty times
78
+ * in a session must not leave a mesh on the GPU each time. */
79
+ function disposeObjectTree(root) {
80
+ root.traverse((object) => {
81
+ const mesh = asMesh(object);
82
+ if (mesh === null)
83
+ return;
84
+ mesh.geometry?.dispose?.();
85
+ const material = mesh.material;
86
+ if (Array.isArray(material)) {
87
+ for (const entry of material)
88
+ disposeMaterial(entry);
89
+ }
90
+ else if (material) {
91
+ disposeMaterial(material);
92
+ }
93
+ });
94
+ }
95
+ function countTriangles(root) {
96
+ let triangles = 0;
97
+ root.traverse((object) => {
98
+ const mesh = asMesh(object);
99
+ if (mesh === null || !mesh.geometry)
100
+ return;
101
+ const index = mesh.geometry.getIndex?.();
102
+ const position = mesh.geometry.getAttribute?.('position');
103
+ if (index)
104
+ triangles += index.count / 3;
105
+ else if (position)
106
+ triangles += position.count / 3;
107
+ });
108
+ return Math.round(triangles);
109
+ }
110
+ /**
111
+ * Specifier held in a variable on purpose: a literal would make tsc try to resolve a module that
112
+ * only exists on the other origin at runtime. The import map on the page resolves it.
113
+ */
114
+ const VOXEL_OBJECT_MODULE = 'engine/VoxelObject.js';
115
+ async function loadGlb(url) {
116
+ const gltf = await new GLTFLoader().loadAsync(url);
117
+ gltf.scene.updateMatrixWorld(true);
118
+ return gltf.scene;
119
+ }
120
+ /**
121
+ * Build the voxel asset at FULL detail — deliberately not what the thumbnail does.
122
+ *
123
+ * `VoxelPreviewRenderer` renders the coarsest baked LOD for v3 files, and its comment explains
124
+ * why: building LOD0 for every asset would stall the live game for seconds each while the panel
125
+ * backfills tiles. Here there is exactly one asset, the creator asked to look at it, and the whole
126
+ * point is to see it properly — which is what `loadFromFile` gives.
127
+ *
128
+ * No material conversion is needed even though the engine runs on WebGPU: `RendererType` defaults
129
+ * to `'webgl'` and only `GameEngine` ever changes it, so a document that never boots the engine —
130
+ * this one — gets classic materials from the shared voxel factory already.
131
+ */
132
+ async function loadVxl(bytes) {
133
+ const module = await import(VOXEL_OBJECT_MODULE);
134
+ const object = new module.VoxelObject({ shadows: false });
135
+ const bounds = await object.loadFromFile(bytes);
136
+ if (bounds === null)
137
+ throw new Error('That voxel file has no geometry in it.');
138
+ return object;
139
+ }
140
+ /** Fetch through the sidecar rather than the asset host, so no CORS policy is in the way. */
141
+ async function fetchBytes(sourceEndpoint, url) {
142
+ const response = await fetch(`${sourceEndpoint}?url=${encodeURIComponent(url)}`);
143
+ if (!response.ok) {
144
+ const detail = await response.json().catch(() => null);
145
+ throw new Error(detail?.error ?? `The dev server answered ${response.status}`);
146
+ }
147
+ return response.arrayBuffer();
148
+ }
149
+ export async function startAssetViewer(options) {
150
+ const { container, url, kind, sourceEndpoint } = options;
151
+ const object = kind === 'vxl'
152
+ ? await loadVxl(await fetchBytes(sourceEndpoint, url))
153
+ : await loadGlb(url);
154
+ const scene = new THREE.Scene();
155
+ // The same slate the thumbnails are rendered on, so the tile and the overlay match.
156
+ scene.background = new THREE.Color(0x1e293b);
157
+ addPreviewLights(scene);
158
+ scene.add(object);
159
+ const renderer = new WebGLRenderer({ antialias: true });
160
+ renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
161
+ renderer.setSize(Math.max(container.clientWidth, 1), Math.max(container.clientHeight, 1));
162
+ renderer.outputColorSpace = THREE.SRGBColorSpace;
163
+ container.appendChild(renderer.domElement);
164
+ const sphere = new THREE.Box3().setFromObject(object).getBoundingSphere(new THREE.Sphere());
165
+ const radius = sphere.radius > 0 ? sphere.radius : 1;
166
+ const distance = radius * CAMERA_DISTANCE_FACTOR;
167
+ const camera = new THREE.PerspectiveCamera(45, Math.max(container.clientWidth, 1) / Math.max(container.clientHeight, 1), distance * 0.01, distance * 10);
168
+ const center = sphere.center;
169
+ const framed = new THREE.Vector3(center.x + Math.cos(CAMERA_AZIMUTH) * Math.cos(CAMERA_ELEVATION) * distance, center.y + Math.sin(CAMERA_ELEVATION) * distance, center.z + Math.sin(CAMERA_AZIMUTH) * Math.cos(CAMERA_ELEVATION) * distance);
170
+ camera.position.copy(framed);
171
+ camera.lookAt(center);
172
+ const controls = new OrbitControls(camera, renderer.domElement);
173
+ controls.target.copy(center);
174
+ controls.enableDamping = true;
175
+ // Bounded by the asset's own size rather than by constants: a 0.5 m prop and a 200 m level both
176
+ // have to be reachable with the same scroll wheel.
177
+ controls.minDistance = radius * 0.4;
178
+ controls.maxDistance = radius * 20;
179
+ controls.update();
180
+ // The container, not the window: this viewer lives in a dialog that resizes on its own.
181
+ const resize = new ResizeObserver(() => {
182
+ const width = Math.max(container.clientWidth, 1);
183
+ const height = Math.max(container.clientHeight, 1);
184
+ camera.aspect = width / height;
185
+ camera.updateProjectionMatrix();
186
+ renderer.setSize(width, height);
187
+ });
188
+ resize.observe(container);
189
+ let frame = 0;
190
+ const tick = () => {
191
+ frame = requestAnimationFrame(tick);
192
+ controls.update();
193
+ renderer.render(scene, camera);
194
+ };
195
+ tick();
196
+ return {
197
+ triangles: countTriangles(object),
198
+ resetView() {
199
+ camera.position.copy(framed);
200
+ controls.target.copy(center);
201
+ controls.update();
202
+ },
203
+ dispose() {
204
+ cancelAnimationFrame(frame);
205
+ resize.disconnect();
206
+ controls.dispose();
207
+ scene.remove(object);
208
+ disposeObjectTree(object);
209
+ renderer.dispose();
210
+ // Hand the GPU back rather than waiting for the collector. The game is running in the next
211
+ // iframe over, and browsers drop the OLDEST context when the cap is reached — which would be
212
+ // the creator's game, not this decoration.
213
+ renderer.forceContextLoss();
214
+ renderer.domElement.remove();
215
+ },
216
+ };
217
+ }
218
+ //# sourceMappingURL=asset-viewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-viewer.js","sourceRoot":"","sources":["../../src/editor/asset-viewer.ts"],"names":[],"mappings":"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"}