@xsolla/xui-b2c-glass-cube 0.225.0 → 0.227.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/{chunk-U3XM7FPH.js → chunk-WWTMZRLH.js} +8 -4
- package/chunk-WWTMZRLH.js.map +1 -0
- package/{chunk-H563G5UY.mjs → chunk-X3CIIR4N.mjs} +8 -4
- package/{chunk-H563G5UY.mjs.map → chunk-X3CIIR4N.mjs.map} +1 -1
- package/index.js +2 -2
- package/index.mjs +1 -1
- package/package.json +3 -3
- package/react/index.d.mts +2 -0
- package/react/index.d.ts +2 -0
- package/react/index.js +5 -5
- package/react/index.js.map +1 -1
- package/react/index.mjs +1 -1
- package/react/index.mjs.map +1 -1
- package/chunk-U3XM7FPH.js.map +0 -1
|
@@ -192,13 +192,17 @@ function mountGlassCube(host, options) {
|
|
|
192
192
|
onComplete: complete,
|
|
193
193
|
onContextLost: fallBack
|
|
194
194
|
});
|
|
195
|
-
if (item !== options.item) {
|
|
196
|
-
await created.setItem(item);
|
|
197
|
-
}
|
|
198
195
|
if (isDisposed || isFallback) {
|
|
199
196
|
created.dispose();
|
|
200
197
|
return;
|
|
201
198
|
}
|
|
199
|
+
if (item !== options.item) {
|
|
200
|
+
await created.setItem(item);
|
|
201
|
+
if (isDisposed || isFallback) {
|
|
202
|
+
created.dispose();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
202
206
|
scene = created;
|
|
203
207
|
if (sealed) {
|
|
204
208
|
complete();
|
|
@@ -264,4 +268,4 @@ function mountGlassCube(host, options) {
|
|
|
264
268
|
|
|
265
269
|
|
|
266
270
|
exports.DEFAULT_GLASS_CUBE_ASSETS = DEFAULT_GLASS_CUBE_ASSETS; exports.canPlayGlassCube = canPlayGlassCube; exports.normalizeGlassCubeLabel = normalizeGlassCubeLabel; exports.glassCubeLabelKey = glassCubeLabelKey; exports.mountGlassCube = mountGlassCube;
|
|
267
|
-
//# sourceMappingURL=chunk-
|
|
271
|
+
//# sourceMappingURL=chunk-WWTMZRLH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/builds/frontend-platform/xsolla-ui-toolkit-v2/packages/b2c/glass-cube/dist/chunk-WWTMZRLH.js","../src/assets.ts","../src/capabilities.ts","../src/label.ts","../src/layout.ts","../src/mount.ts"],"names":[],"mappings":"AAAA;ACUA,IAAM,SAAA,EAAW,6DAAA;AAMV,IAAM,0BAAA,EAA6C;AAAA,EACxD,YAAA,EAAc,CAAA,EAAA;AACd,EAAA;AACA,EAAA;AACF;AAEgB;AAGF,EAAA;AACd;ADhBiB;AACA;AEPX;AAEG;AACO,EAAA;AAChB;AAEgB;AACJ,EAAA;AACD,IAAA;AACT,EAAA;AACe,EAAA;AACA,EAAA;AACN,IAAA;AACT,EAAA;AACI,EAAA;AACA,EAAA;AACS,IAAA;AACP,oBAAA;AACQ,IAAA;AACN,EAAA;AACM,IAAA;AACd,EAAA;AACc,EAAA;AACP,EAAA;AACT;AAEgB;AAIP,EAAA;AAET;AAMgB;AAGN,EAAA;AACV;AFLiB;AACA;AGlCD;AAGA,EAAA;AACL,IAAA;AACT,EAAA;AACW,EAAA;AACF,IAAA;AACT,EAAA;AACM,EAAA;AACF,EAAA;AACK,IAAA;AACT,EAAA;AACa,EAAA;AACE,EAAA;AACJ,IAAA;AACX,EAAA;AACS,EAAA;AACX;AAGgB;AACC,EAAA;AACjB;AHgCiB;AACA;AIlDJ;AAEM;AACb;AACA;AACA;AAGA;AACA;AAaJ;AAGc,EAAA;AAChB;AAEgB;AAIR,EAAA;AAEO,EAAA;AACR,EAAA;AACA,EAAA;AACQ,EAAA;AACD,IAAA;AACD,IAAA;AACT,IAAA;AAAgB;AAEN,IAAA;AACX,EAAA;AAEa,EAAA;AACR,EAAA;AACQ,EAAA;AACF,IAAA;AACE,IAAA;AACF,IAAA;AACX,EAAA;AAEc,EAAA;AACA,EAAA;AACT,EAAA;AAEA,EAAA;AACG,EAAA;AACF,EAAA;AACK,IAAA;AACL,IAAA;AACC,IAAA;AACC,IAAA;AACI,IAAA;AACX,IAAA;AACW,IAAA;AACZ,EAAA;AAEW,EAAA;AACP,EAAA;AAEmC,EAAA;AAER,EAAA;AAC9B,IAAA;AACA,IAAA;AACA,IAAA;AACY,IAAA;AAGC,MAAA;AACb,IAAA;AACa,IAAA;AACF,MAAA;AACJ,MAAA;AACH,QAAA;AACS,QAAA;AACA,QAAA;AACF,QAAA;AACL,UAAA;AACK,UAAA;AACC,UAAA;AACC,UAAA;AACP,UAAA;AACA,UAAA;AACD,QAAA;AACK,QAAA;AACR,MAAA;AACS,MAAA;AACX,IAAA;AACA,IAAA;AACM,MAAA;AACO,QAAA;AACX,MAAA;AACF,IAAA;AACU,IAAA;AACH,MAAA;AACP,IAAA;AACF,EAAA;AAEe,EAAA;AAER,EAAA;AACT;AJoBiB;AACA;AKzID;AAIC,EAAA;AACT,EAAA;AACS,EAAA;AAEJ,EAAA;AACwB,EAAA;AAC/B,EAAA;AACA,EAAA;AAEA,EAAA;AAEA,EAAA;AAEE,EAAA;AACA,IAAA;AACF,MAAA;AACF,IAAA;AACA,IAAA;AACQ,oBAAA;AACV,EAAA;AAEM,EAAA;AACA,IAAA;AACF,MAAA;AACF,IAAA;AACa,IAAA;AACN,oBAAA;AACC,IAAA;AACD,IAAA;AACF,IAAA;AACM,MAAA;AACX,IAAA;AACF,EAAA;AAEc,EAAA;AACR,IAAA;AACM,MAAA;AACF,MAAA;AACJ,QAAA;AACO,QAAA;AACD,QAAA;AACN,QAAA;AACA,QAAA;AACA,QAAA;AACO,QAAA;AACC,QAAA;AACR,QAAA;AACA,QAAA;AACD,MAAA;AAGG,MAAA;AACM,QAAA;AACR,QAAA;AACF,MAAA;AAEI,MAAA;AACI,QAAA;AACF,QAAA;AACF,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AAEQ,MAAA;AACJ,MAAA;AACO,QAAA;AACT,QAAA;AACF,MAAA;AACI,MAAA;AACF,QAAA;AACQ,QAAA;AACV,MAAA;AACM,IAAA;AACG,MAAA;AACX,IAAA;AACF,EAAA;AAEI,EAAA;AACA,EAAA;AACM,IAAA;AACH,EAAA;AACG,IAAA;AACC,IAAA;AACX,EAAA;AAEO,EAAA;AACE,IAAA;AACD,MAAA;AACF,QAAA;AACF,MAAA;AACW,MAAA;AACH,QAAA;AACD,MAAA;AACL,QAAA;AACF,MAAA;AACF,IAAA;AACM,IAAA;AACG,MAAA;AACH,MAAA;AACK,QAAA;AACP,QAAA;AACF,MAAA;AACM,MAAA;AACF,MAAA;AACF,QAAA;AACF,MAAA;AAGM,MAAA;AACR,IAAA;AACY,IAAA;AACH,MAAA;AACT,IAAA;AACU,IAAA;AACJ,MAAA;AACF,QAAA;AACF,MAAA;AACA,MAAA;AACO,sBAAA;AACC,MAAA;AACD,MAAA;AACT,IAAA;AACF,EAAA;AACF;AL0HiB;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/builds/frontend-platform/xsolla-ui-toolkit-v2/packages/b2c/glass-cube/dist/chunk-WWTMZRLH.js","sourcesContent":[null,"/** URLs of the binary assets the scene loads at runtime. */\nexport interface GlassCubeAssets {\n /** Glass cube model (`glass-cube.glb`). */\n readonly cubeModelUrl: string;\n /** Sol, the printer, as a rigged model (`sol.glb`). */\n readonly printerModelUrl: string;\n /** Studio light map Sol was rendered in (`studio.hdr`). */\n readonly environmentUrl: string;\n}\n\nconst CDN_BASE = \"https://cdn.xsolla.net/xsolla-ui-toolkit-prod/glass-cube/v1\";\n\n/**\n * Served from the toolkit CDN bucket (`cdn-assets/xsolla-ui-toolkit-prod/glass-cube/v1`).\n * Bump `?v=` whenever a file changes in place — the CDN caches aggressively.\n */\nexport const DEFAULT_GLASS_CUBE_ASSETS: GlassCubeAssets = {\n cubeModelUrl: `${CDN_BASE}/glass-cube.glb?v=1`,\n printerModelUrl: `${CDN_BASE}/sol.glb?v=1`,\n environmentUrl: `${CDN_BASE}/studio.hdr?v=1`,\n};\n\nexport function resolveGlassCubeAssets(\n overrides?: Partial<GlassCubeAssets>\n): GlassCubeAssets {\n return { ...DEFAULT_GLASS_CUBE_ASSETS, ...overrides };\n}\n","/**\n * Deliberately free of `three` imports: these probes run before deciding whether\n * to download the WebGL chunk at all.\n */\nconst webgl2Support = new WeakMap<Window, boolean>();\n\nfunction defaultWindow(): Window | undefined {\n return typeof window === \"undefined\" ? undefined : window;\n}\n\nexport function hasWebgl2(win: Window | undefined = defaultWindow()): boolean {\n if (!win) {\n return false;\n }\n const cached = webgl2Support.get(win);\n if (cached !== undefined) {\n return cached;\n }\n let supported = false;\n try {\n const gl = win.document.createElement(\"canvas\").getContext(\"webgl2\");\n gl?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n supported = gl !== null && gl !== undefined;\n } catch {\n supported = false;\n }\n webgl2Support.set(win, supported);\n return supported;\n}\n\nexport function prefersReducedMotion(\n win: Window | undefined = defaultWindow()\n): boolean {\n return (\n win?.matchMedia?.(\"(prefers-reduced-motion: reduce)\")?.matches ?? false\n );\n}\n\n/**\n * `false` means the print cannot play: show the item sealed right away instead\n * of never showing it. Checked once, before the scene chunk is requested.\n */\nexport function canPlayGlassCube(\n win: Window | undefined = defaultWindow()\n): boolean {\n return !prefersReducedMotion(win) && hasWebgl2(win);\n}\n","import type { GlassCubeLabel } from \"./types\";\n\n/**\n * Accepts the tag the way consumers usually hold it — one string such as\n * `\"XLA-000-006\"` — and splits it at the first dash into the series and the\n * number the cube face prints on separate lines. An object passes through;\n * an empty string means no tag.\n */\nexport function normalizeGlassCubeLabel(\n label: string | GlassCubeLabel | undefined\n): GlassCubeLabel | undefined {\n if (label === undefined) {\n return undefined;\n }\n if (typeof label !== \"string\") {\n return label;\n }\n const trimmed = label.trim();\n if (trimmed === \"\") {\n return undefined;\n }\n const dash = trimmed.indexOf(\"-\");\n if (dash <= 0) {\n return { series: trimmed, number: \"\" };\n }\n return { series: trimmed.slice(0, dash), number: trimmed.slice(dash) };\n}\n\n/** Stable key for comparing labels by value (React deps, memoisation). */\nexport function glassCubeLabelKey(label: GlassCubeLabel | undefined): string {\n return label ? `${label.series}\\u0000${label.number}` : \"\";\n}\n","/**\n * DOM the cube lives in. Mirrors the pay-station `x-status-cube` template:\n *\n * root (flex, centred) as wide as the print: `size × 2.5`, capped by the host\n * ├─ stage square the printed cube is fitted to; its width is the cube size\n * │ └─ img static fallback, shown instead of the canvas\n * └─ overscan 2.5× the stage, centred on it; Sol flies in from outside the cube\n * └─ canvas (appended by the scene)\n *\n * Everything is inline-styled so consumers need no stylesheet; the class names\n * are hooks for anyone who wants to override.\n */\n\n/** Sol flies in from outside the cube frame — the canvas is this much bigger than the cube. */\nexport const STAGE_OVERSCAN = 2.5;\n\nconst CLASS_ROOT = \"xui-glass-cube\";\nconst CLASS_STAGE = `${CLASS_ROOT}__stage`;\nconst CLASS_OVERSCAN = `${CLASS_ROOT}__overscan`;\nconst CLASS_FALLBACK = `${CLASS_ROOT}__fallback`;\n\n/** The fallback picture is inset the way the item sits inside the printed glass. */\nconst FALLBACK_INSET = \"15%\";\nconst FALLBACK_SIZE = \"70%\";\n\nexport interface GlassCubeLayout {\n readonly root: HTMLElement;\n readonly stage: HTMLElement;\n readonly overscan: HTMLElement;\n setSize(px: number | undefined): void;\n showFallback(src: string): void;\n setFallbackSrc(src: string): void;\n dispose(): void;\n}\n\nfunction assign(\n element: HTMLElement,\n style: Partial<CSSStyleDeclaration>\n): void {\n Object.assign(element.style, style);\n}\n\nexport function createGlassCubeLayout(\n host: HTMLElement,\n size: number | undefined\n): GlassCubeLayout {\n const document = host.ownerDocument;\n\n const root = document.createElement(\"div\");\n root.className = CLASS_ROOT;\n root.setAttribute(\"aria-hidden\", \"true\");\n assign(root, {\n position: \"relative\",\n display: \"flex\",\n justifyContent: \"center\",\n // Never wider than the host, so a narrow column shrinks the whole print.\n maxWidth: \"100%\",\n });\n\n const stage = document.createElement(\"div\");\n stage.className = CLASS_STAGE;\n assign(stage, {\n position: \"relative\",\n flexShrink: \"0\",\n width: `${100 / STAGE_OVERSCAN}%`,\n });\n // Keeps the stage square without `aspect-ratio`.\n const square = document.createElement(\"div\");\n assign(square, { paddingTop: \"100%\" });\n stage.appendChild(square);\n\n const overscan = document.createElement(\"div\");\n overscan.className = CLASS_OVERSCAN;\n assign(overscan, {\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n width: \"100%\",\n height: `${STAGE_OVERSCAN * 100}%`,\n pointerEvents: \"none\",\n transform: \"translate(-50%, -50%)\",\n });\n\n root.append(stage, overscan);\n host.appendChild(root);\n\n let fallback: HTMLImageElement | null = null;\n\n const layout: GlassCubeLayout = {\n root,\n stage,\n overscan,\n setSize(px) {\n // The stage is a fixed fraction of the root, so sizing the root to the\n // whole print makes the stage exactly `px` wide — no wrapper needed.\n root.style.width = px === undefined ? \"100%\" : `${px * STAGE_OVERSCAN}px`;\n },\n showFallback(src) {\n overscan.remove();\n if (!fallback) {\n fallback = document.createElement(\"img\");\n fallback.className = CLASS_FALLBACK;\n fallback.alt = \"\";\n assign(fallback, {\n position: \"absolute\",\n top: FALLBACK_INSET,\n left: FALLBACK_INSET,\n width: FALLBACK_SIZE,\n height: FALLBACK_SIZE,\n objectFit: \"contain\",\n });\n stage.appendChild(fallback);\n }\n fallback.src = src;\n },\n setFallbackSrc(src) {\n if (fallback) {\n fallback.src = src;\n }\n },\n dispose() {\n root.remove();\n },\n };\n\n layout.setSize(size);\n\n return layout;\n}\n","import { resolveGlassCubeAssets } from \"./assets\";\nimport { canPlayGlassCube } from \"./capabilities\";\nimport { normalizeGlassCubeLabel } from \"./label\";\nimport { createGlassCubeLayout } from \"./layout\";\nimport type { GlassCubeScene } from \"./scene/scene/glass-cube-scene\";\nimport type { GlassCubeHandle, GlassCubeOptions } from \"./types\";\n\n/**\n * Builds the cube inside `host`. The WebGL scene (and `three` with it) is\n * requested only when the print can actually play; otherwise, and whenever the\n * scene fails to load or loses its context, the item is shown as a plain picture.\n */\nexport function mountGlassCube(\n host: HTMLElement,\n options: GlassCubeOptions\n): GlassCubeHandle {\n const sealed = options.sealed ?? false;\n const printer = options.printer ?? !sealed;\n const layout = createGlassCubeLayout(host, options.size);\n\n let item = options.item;\n let scene: GlassCubeScene | null = null;\n let isDisposed = false;\n let isFallback = false;\n /** The cube has been sealed at least once — the fallback must not announce it again. */\n let hasCompleted = false;\n /** A print is wanted as soon as the scene is ready. */\n let playPending = !sealed && (options.autoPlay ?? true);\n\n const complete = (): void => {\n if (isDisposed) {\n return;\n }\n hasCompleted = true;\n options.onComplete?.();\n };\n\n const fallBack = (): void => {\n if (isDisposed || isFallback) {\n return;\n }\n isFallback = true;\n scene?.dispose();\n scene = null;\n layout.showFallback(item);\n if (!hasCompleted) {\n complete();\n }\n };\n\n const start = async (): Promise<void> => {\n try {\n const { GlassCubeScene } = await import(\"./scene/scene/glass-cube-scene\");\n const created = await GlassCubeScene.create({\n container: layout.overscan,\n stage: layout.stage,\n item: options.item,\n fallbackItem: options.fallbackItem,\n sealed,\n printer,\n label: normalizeGlassCubeLabel(options.label),\n assets: resolveGlassCubeAssets(options.assets),\n onComplete: complete,\n onContextLost: fallBack,\n });\n // Torn down while the assets were loading: release the scene without\n // uploading a late texture to it.\n if (isDisposed || isFallback) {\n created.dispose();\n return;\n }\n // The picture may have been swapped while the assets were loading.\n if (item !== options.item) {\n await created.setItem(item);\n if (isDisposed || isFallback) {\n created.dispose();\n return;\n }\n }\n // Published only now: `play` / `setItem` queue up behind `ready` until then.\n scene = created;\n if (sealed) {\n complete();\n return;\n }\n if (playPending) {\n playPending = false;\n created.play();\n }\n } catch {\n fallBack();\n }\n };\n\n let ready: Promise<void>;\n if (canPlayGlassCube(host.ownerDocument.defaultView ?? undefined)) {\n ready = start();\n } else {\n ready = Promise.resolve();\n fallBack();\n }\n\n return {\n play() {\n if (sealed || isFallback || isDisposed) {\n return;\n }\n if (scene) {\n scene.play();\n } else {\n playPending = true;\n }\n },\n async setItem(src) {\n item = src;\n if (isFallback) {\n layout.setFallbackSrc(src);\n return;\n }\n await ready;\n if (isDisposed || isFallback) {\n return;\n }\n // Already printed by `start` when the swap happened during loading; the\n // scene skips a repeat of its current URL.\n await scene?.setItem(src);\n },\n setSize(px) {\n layout.setSize(px);\n },\n dispose() {\n if (isDisposed) {\n return;\n }\n isDisposed = true;\n scene?.dispose();\n scene = null;\n layout.dispose();\n },\n };\n}\n"]}
|
|
@@ -192,13 +192,17 @@ function mountGlassCube(host, options) {
|
|
|
192
192
|
onComplete: complete,
|
|
193
193
|
onContextLost: fallBack
|
|
194
194
|
});
|
|
195
|
-
if (item !== options.item) {
|
|
196
|
-
await created.setItem(item);
|
|
197
|
-
}
|
|
198
195
|
if (isDisposed || isFallback) {
|
|
199
196
|
created.dispose();
|
|
200
197
|
return;
|
|
201
198
|
}
|
|
199
|
+
if (item !== options.item) {
|
|
200
|
+
await created.setItem(item);
|
|
201
|
+
if (isDisposed || isFallback) {
|
|
202
|
+
created.dispose();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
202
206
|
scene = created;
|
|
203
207
|
if (sealed) {
|
|
204
208
|
complete();
|
|
@@ -264,4 +268,4 @@ export {
|
|
|
264
268
|
glassCubeLabelKey,
|
|
265
269
|
mountGlassCube
|
|
266
270
|
};
|
|
267
|
-
//# sourceMappingURL=chunk-
|
|
271
|
+
//# sourceMappingURL=chunk-X3CIIR4N.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/assets.ts","../src/capabilities.ts","../src/label.ts","../src/layout.ts","../src/mount.ts"],"sourcesContent":["/** URLs of the binary assets the scene loads at runtime. */\nexport interface GlassCubeAssets {\n /** Glass cube model (`glass-cube.glb`). */\n readonly cubeModelUrl: string;\n /** Sol, the printer, as a rigged model (`sol.glb`). */\n readonly printerModelUrl: string;\n /** Studio light map Sol was rendered in (`studio.hdr`). */\n readonly environmentUrl: string;\n}\n\nconst CDN_BASE = \"https://cdn.xsolla.net/xsolla-ui-toolkit-prod/glass-cube/v1\";\n\n/**\n * Served from the toolkit CDN bucket (`cdn-assets/xsolla-ui-toolkit-prod/glass-cube/v1`).\n * Bump `?v=` whenever a file changes in place — the CDN caches aggressively.\n */\nexport const DEFAULT_GLASS_CUBE_ASSETS: GlassCubeAssets = {\n cubeModelUrl: `${CDN_BASE}/glass-cube.glb?v=1`,\n printerModelUrl: `${CDN_BASE}/sol.glb?v=1`,\n environmentUrl: `${CDN_BASE}/studio.hdr?v=1`,\n};\n\nexport function resolveGlassCubeAssets(\n overrides?: Partial<GlassCubeAssets>\n): GlassCubeAssets {\n return { ...DEFAULT_GLASS_CUBE_ASSETS, ...overrides };\n}\n","/**\n * Deliberately free of `three` imports: these probes run before deciding whether\n * to download the WebGL chunk at all.\n */\nconst webgl2Support = new WeakMap<Window, boolean>();\n\nfunction defaultWindow(): Window | undefined {\n return typeof window === \"undefined\" ? undefined : window;\n}\n\nexport function hasWebgl2(win: Window | undefined = defaultWindow()): boolean {\n if (!win) {\n return false;\n }\n const cached = webgl2Support.get(win);\n if (cached !== undefined) {\n return cached;\n }\n let supported = false;\n try {\n const gl = win.document.createElement(\"canvas\").getContext(\"webgl2\");\n gl?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n supported = gl !== null && gl !== undefined;\n } catch {\n supported = false;\n }\n webgl2Support.set(win, supported);\n return supported;\n}\n\nexport function prefersReducedMotion(\n win: Window | undefined = defaultWindow()\n): boolean {\n return (\n win?.matchMedia?.(\"(prefers-reduced-motion: reduce)\")?.matches ?? false\n );\n}\n\n/**\n * `false` means the print cannot play: show the item sealed right away instead\n * of never showing it. Checked once, before the scene chunk is requested.\n */\nexport function canPlayGlassCube(\n win: Window | undefined = defaultWindow()\n): boolean {\n return !prefersReducedMotion(win) && hasWebgl2(win);\n}\n","import type { GlassCubeLabel } from \"./types\";\n\n/**\n * Accepts the tag the way consumers usually hold it — one string such as\n * `\"XLA-000-006\"` — and splits it at the first dash into the series and the\n * number the cube face prints on separate lines. An object passes through;\n * an empty string means no tag.\n */\nexport function normalizeGlassCubeLabel(\n label: string | GlassCubeLabel | undefined\n): GlassCubeLabel | undefined {\n if (label === undefined) {\n return undefined;\n }\n if (typeof label !== \"string\") {\n return label;\n }\n const trimmed = label.trim();\n if (trimmed === \"\") {\n return undefined;\n }\n const dash = trimmed.indexOf(\"-\");\n if (dash <= 0) {\n return { series: trimmed, number: \"\" };\n }\n return { series: trimmed.slice(0, dash), number: trimmed.slice(dash) };\n}\n\n/** Stable key for comparing labels by value (React deps, memoisation). */\nexport function glassCubeLabelKey(label: GlassCubeLabel | undefined): string {\n return label ? `${label.series}\\u0000${label.number}` : \"\";\n}\n","/**\n * DOM the cube lives in. Mirrors the pay-station `x-status-cube` template:\n *\n * root (flex, centred) as wide as the print: `size × 2.5`, capped by the host\n * ├─ stage square the printed cube is fitted to; its width is the cube size\n * │ └─ img static fallback, shown instead of the canvas\n * └─ overscan 2.5× the stage, centred on it; Sol flies in from outside the cube\n * └─ canvas (appended by the scene)\n *\n * Everything is inline-styled so consumers need no stylesheet; the class names\n * are hooks for anyone who wants to override.\n */\n\n/** Sol flies in from outside the cube frame — the canvas is this much bigger than the cube. */\nexport const STAGE_OVERSCAN = 2.5;\n\nconst CLASS_ROOT = \"xui-glass-cube\";\nconst CLASS_STAGE = `${CLASS_ROOT}__stage`;\nconst CLASS_OVERSCAN = `${CLASS_ROOT}__overscan`;\nconst CLASS_FALLBACK = `${CLASS_ROOT}__fallback`;\n\n/** The fallback picture is inset the way the item sits inside the printed glass. */\nconst FALLBACK_INSET = \"15%\";\nconst FALLBACK_SIZE = \"70%\";\n\nexport interface GlassCubeLayout {\n readonly root: HTMLElement;\n readonly stage: HTMLElement;\n readonly overscan: HTMLElement;\n setSize(px: number | undefined): void;\n showFallback(src: string): void;\n setFallbackSrc(src: string): void;\n dispose(): void;\n}\n\nfunction assign(\n element: HTMLElement,\n style: Partial<CSSStyleDeclaration>\n): void {\n Object.assign(element.style, style);\n}\n\nexport function createGlassCubeLayout(\n host: HTMLElement,\n size: number | undefined\n): GlassCubeLayout {\n const document = host.ownerDocument;\n\n const root = document.createElement(\"div\");\n root.className = CLASS_ROOT;\n root.setAttribute(\"aria-hidden\", \"true\");\n assign(root, {\n position: \"relative\",\n display: \"flex\",\n justifyContent: \"center\",\n // Never wider than the host, so a narrow column shrinks the whole print.\n maxWidth: \"100%\",\n });\n\n const stage = document.createElement(\"div\");\n stage.className = CLASS_STAGE;\n assign(stage, {\n position: \"relative\",\n flexShrink: \"0\",\n width: `${100 / STAGE_OVERSCAN}%`,\n });\n // Keeps the stage square without `aspect-ratio`.\n const square = document.createElement(\"div\");\n assign(square, { paddingTop: \"100%\" });\n stage.appendChild(square);\n\n const overscan = document.createElement(\"div\");\n overscan.className = CLASS_OVERSCAN;\n assign(overscan, {\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n width: \"100%\",\n height: `${STAGE_OVERSCAN * 100}%`,\n pointerEvents: \"none\",\n transform: \"translate(-50%, -50%)\",\n });\n\n root.append(stage, overscan);\n host.appendChild(root);\n\n let fallback: HTMLImageElement | null = null;\n\n const layout: GlassCubeLayout = {\n root,\n stage,\n overscan,\n setSize(px) {\n // The stage is a fixed fraction of the root, so sizing the root to the\n // whole print makes the stage exactly `px` wide — no wrapper needed.\n root.style.width = px === undefined ? \"100%\" : `${px * STAGE_OVERSCAN}px`;\n },\n showFallback(src) {\n overscan.remove();\n if (!fallback) {\n fallback = document.createElement(\"img\");\n fallback.className = CLASS_FALLBACK;\n fallback.alt = \"\";\n assign(fallback, {\n position: \"absolute\",\n top: FALLBACK_INSET,\n left: FALLBACK_INSET,\n width: FALLBACK_SIZE,\n height: FALLBACK_SIZE,\n objectFit: \"contain\",\n });\n stage.appendChild(fallback);\n }\n fallback.src = src;\n },\n setFallbackSrc(src) {\n if (fallback) {\n fallback.src = src;\n }\n },\n dispose() {\n root.remove();\n },\n };\n\n layout.setSize(size);\n\n return layout;\n}\n","import { resolveGlassCubeAssets } from \"./assets\";\nimport { canPlayGlassCube } from \"./capabilities\";\nimport { normalizeGlassCubeLabel } from \"./label\";\nimport { createGlassCubeLayout } from \"./layout\";\nimport type { GlassCubeScene } from \"./scene/scene/glass-cube-scene\";\nimport type { GlassCubeHandle, GlassCubeOptions } from \"./types\";\n\n/**\n * Builds the cube inside `host`. The WebGL scene (and `three` with it) is\n * requested only when the print can actually play; otherwise, and whenever the\n * scene fails to load or loses its context, the item is shown as a plain picture.\n */\nexport function mountGlassCube(\n host: HTMLElement,\n options: GlassCubeOptions\n): GlassCubeHandle {\n const sealed = options.sealed ?? false;\n const printer = options.printer ?? !sealed;\n const layout = createGlassCubeLayout(host, options.size);\n\n let item = options.item;\n let scene: GlassCubeScene | null = null;\n let isDisposed = false;\n let isFallback = false;\n /** The cube has been sealed at least once — the fallback must not announce it again. */\n let hasCompleted = false;\n /** A print is wanted as soon as the scene is ready. */\n let playPending = !sealed && (options.autoPlay ?? true);\n\n const complete = (): void => {\n if (isDisposed) {\n return;\n }\n hasCompleted = true;\n options.onComplete?.();\n };\n\n const fallBack = (): void => {\n if (isDisposed || isFallback) {\n return;\n }\n isFallback = true;\n scene?.dispose();\n scene = null;\n layout.showFallback(item);\n if (!hasCompleted) {\n complete();\n }\n };\n\n const start = async (): Promise<void> => {\n try {\n const { GlassCubeScene } = await import(\"./scene/scene/glass-cube-scene\");\n const created = await GlassCubeScene.create({\n container: layout.overscan,\n stage: layout.stage,\n item: options.item,\n fallbackItem: options.fallbackItem,\n sealed,\n printer,\n label: normalizeGlassCubeLabel(options.label),\n assets: resolveGlassCubeAssets(options.assets),\n onComplete: complete,\n onContextLost: fallBack,\n });\n // The picture may have been swapped while the assets were loading.\n if (item !== options.item) {\n await created.setItem(item);\n }\n if (isDisposed || isFallback) {\n created.dispose();\n return;\n }\n // Published only now: `play` / `setItem` queue up behind `ready` until then.\n scene = created;\n if (sealed) {\n complete();\n return;\n }\n if (playPending) {\n playPending = false;\n created.play();\n }\n } catch {\n fallBack();\n }\n };\n\n let ready: Promise<void>;\n if (canPlayGlassCube(host.ownerDocument.defaultView ?? undefined)) {\n ready = start();\n } else {\n ready = Promise.resolve();\n fallBack();\n }\n\n return {\n play() {\n if (sealed || isFallback || isDisposed) {\n return;\n }\n if (scene) {\n scene.play();\n } else {\n playPending = true;\n }\n },\n async setItem(src) {\n item = src;\n if (isFallback) {\n layout.setFallbackSrc(src);\n return;\n }\n await ready;\n if (isDisposed || isFallback) {\n return;\n }\n // Already printed by `start` when the swap happened during loading; the\n // scene skips a repeat of its current URL.\n await scene?.setItem(src);\n },\n setSize(px) {\n layout.setSize(px);\n },\n dispose() {\n if (isDisposed) {\n return;\n }\n isDisposed = true;\n scene?.dispose();\n scene = null;\n layout.dispose();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/assets.ts","../src/capabilities.ts","../src/label.ts","../src/layout.ts","../src/mount.ts"],"sourcesContent":["/** URLs of the binary assets the scene loads at runtime. */\nexport interface GlassCubeAssets {\n /** Glass cube model (`glass-cube.glb`). */\n readonly cubeModelUrl: string;\n /** Sol, the printer, as a rigged model (`sol.glb`). */\n readonly printerModelUrl: string;\n /** Studio light map Sol was rendered in (`studio.hdr`). */\n readonly environmentUrl: string;\n}\n\nconst CDN_BASE = \"https://cdn.xsolla.net/xsolla-ui-toolkit-prod/glass-cube/v1\";\n\n/**\n * Served from the toolkit CDN bucket (`cdn-assets/xsolla-ui-toolkit-prod/glass-cube/v1`).\n * Bump `?v=` whenever a file changes in place — the CDN caches aggressively.\n */\nexport const DEFAULT_GLASS_CUBE_ASSETS: GlassCubeAssets = {\n cubeModelUrl: `${CDN_BASE}/glass-cube.glb?v=1`,\n printerModelUrl: `${CDN_BASE}/sol.glb?v=1`,\n environmentUrl: `${CDN_BASE}/studio.hdr?v=1`,\n};\n\nexport function resolveGlassCubeAssets(\n overrides?: Partial<GlassCubeAssets>\n): GlassCubeAssets {\n return { ...DEFAULT_GLASS_CUBE_ASSETS, ...overrides };\n}\n","/**\n * Deliberately free of `three` imports: these probes run before deciding whether\n * to download the WebGL chunk at all.\n */\nconst webgl2Support = new WeakMap<Window, boolean>();\n\nfunction defaultWindow(): Window | undefined {\n return typeof window === \"undefined\" ? undefined : window;\n}\n\nexport function hasWebgl2(win: Window | undefined = defaultWindow()): boolean {\n if (!win) {\n return false;\n }\n const cached = webgl2Support.get(win);\n if (cached !== undefined) {\n return cached;\n }\n let supported = false;\n try {\n const gl = win.document.createElement(\"canvas\").getContext(\"webgl2\");\n gl?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n supported = gl !== null && gl !== undefined;\n } catch {\n supported = false;\n }\n webgl2Support.set(win, supported);\n return supported;\n}\n\nexport function prefersReducedMotion(\n win: Window | undefined = defaultWindow()\n): boolean {\n return (\n win?.matchMedia?.(\"(prefers-reduced-motion: reduce)\")?.matches ?? false\n );\n}\n\n/**\n * `false` means the print cannot play: show the item sealed right away instead\n * of never showing it. Checked once, before the scene chunk is requested.\n */\nexport function canPlayGlassCube(\n win: Window | undefined = defaultWindow()\n): boolean {\n return !prefersReducedMotion(win) && hasWebgl2(win);\n}\n","import type { GlassCubeLabel } from \"./types\";\n\n/**\n * Accepts the tag the way consumers usually hold it — one string such as\n * `\"XLA-000-006\"` — and splits it at the first dash into the series and the\n * number the cube face prints on separate lines. An object passes through;\n * an empty string means no tag.\n */\nexport function normalizeGlassCubeLabel(\n label: string | GlassCubeLabel | undefined\n): GlassCubeLabel | undefined {\n if (label === undefined) {\n return undefined;\n }\n if (typeof label !== \"string\") {\n return label;\n }\n const trimmed = label.trim();\n if (trimmed === \"\") {\n return undefined;\n }\n const dash = trimmed.indexOf(\"-\");\n if (dash <= 0) {\n return { series: trimmed, number: \"\" };\n }\n return { series: trimmed.slice(0, dash), number: trimmed.slice(dash) };\n}\n\n/** Stable key for comparing labels by value (React deps, memoisation). */\nexport function glassCubeLabelKey(label: GlassCubeLabel | undefined): string {\n return label ? `${label.series}\\u0000${label.number}` : \"\";\n}\n","/**\n * DOM the cube lives in. Mirrors the pay-station `x-status-cube` template:\n *\n * root (flex, centred) as wide as the print: `size × 2.5`, capped by the host\n * ├─ stage square the printed cube is fitted to; its width is the cube size\n * │ └─ img static fallback, shown instead of the canvas\n * └─ overscan 2.5× the stage, centred on it; Sol flies in from outside the cube\n * └─ canvas (appended by the scene)\n *\n * Everything is inline-styled so consumers need no stylesheet; the class names\n * are hooks for anyone who wants to override.\n */\n\n/** Sol flies in from outside the cube frame — the canvas is this much bigger than the cube. */\nexport const STAGE_OVERSCAN = 2.5;\n\nconst CLASS_ROOT = \"xui-glass-cube\";\nconst CLASS_STAGE = `${CLASS_ROOT}__stage`;\nconst CLASS_OVERSCAN = `${CLASS_ROOT}__overscan`;\nconst CLASS_FALLBACK = `${CLASS_ROOT}__fallback`;\n\n/** The fallback picture is inset the way the item sits inside the printed glass. */\nconst FALLBACK_INSET = \"15%\";\nconst FALLBACK_SIZE = \"70%\";\n\nexport interface GlassCubeLayout {\n readonly root: HTMLElement;\n readonly stage: HTMLElement;\n readonly overscan: HTMLElement;\n setSize(px: number | undefined): void;\n showFallback(src: string): void;\n setFallbackSrc(src: string): void;\n dispose(): void;\n}\n\nfunction assign(\n element: HTMLElement,\n style: Partial<CSSStyleDeclaration>\n): void {\n Object.assign(element.style, style);\n}\n\nexport function createGlassCubeLayout(\n host: HTMLElement,\n size: number | undefined\n): GlassCubeLayout {\n const document = host.ownerDocument;\n\n const root = document.createElement(\"div\");\n root.className = CLASS_ROOT;\n root.setAttribute(\"aria-hidden\", \"true\");\n assign(root, {\n position: \"relative\",\n display: \"flex\",\n justifyContent: \"center\",\n // Never wider than the host, so a narrow column shrinks the whole print.\n maxWidth: \"100%\",\n });\n\n const stage = document.createElement(\"div\");\n stage.className = CLASS_STAGE;\n assign(stage, {\n position: \"relative\",\n flexShrink: \"0\",\n width: `${100 / STAGE_OVERSCAN}%`,\n });\n // Keeps the stage square without `aspect-ratio`.\n const square = document.createElement(\"div\");\n assign(square, { paddingTop: \"100%\" });\n stage.appendChild(square);\n\n const overscan = document.createElement(\"div\");\n overscan.className = CLASS_OVERSCAN;\n assign(overscan, {\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n width: \"100%\",\n height: `${STAGE_OVERSCAN * 100}%`,\n pointerEvents: \"none\",\n transform: \"translate(-50%, -50%)\",\n });\n\n root.append(stage, overscan);\n host.appendChild(root);\n\n let fallback: HTMLImageElement | null = null;\n\n const layout: GlassCubeLayout = {\n root,\n stage,\n overscan,\n setSize(px) {\n // The stage is a fixed fraction of the root, so sizing the root to the\n // whole print makes the stage exactly `px` wide — no wrapper needed.\n root.style.width = px === undefined ? \"100%\" : `${px * STAGE_OVERSCAN}px`;\n },\n showFallback(src) {\n overscan.remove();\n if (!fallback) {\n fallback = document.createElement(\"img\");\n fallback.className = CLASS_FALLBACK;\n fallback.alt = \"\";\n assign(fallback, {\n position: \"absolute\",\n top: FALLBACK_INSET,\n left: FALLBACK_INSET,\n width: FALLBACK_SIZE,\n height: FALLBACK_SIZE,\n objectFit: \"contain\",\n });\n stage.appendChild(fallback);\n }\n fallback.src = src;\n },\n setFallbackSrc(src) {\n if (fallback) {\n fallback.src = src;\n }\n },\n dispose() {\n root.remove();\n },\n };\n\n layout.setSize(size);\n\n return layout;\n}\n","import { resolveGlassCubeAssets } from \"./assets\";\nimport { canPlayGlassCube } from \"./capabilities\";\nimport { normalizeGlassCubeLabel } from \"./label\";\nimport { createGlassCubeLayout } from \"./layout\";\nimport type { GlassCubeScene } from \"./scene/scene/glass-cube-scene\";\nimport type { GlassCubeHandle, GlassCubeOptions } from \"./types\";\n\n/**\n * Builds the cube inside `host`. The WebGL scene (and `three` with it) is\n * requested only when the print can actually play; otherwise, and whenever the\n * scene fails to load or loses its context, the item is shown as a plain picture.\n */\nexport function mountGlassCube(\n host: HTMLElement,\n options: GlassCubeOptions\n): GlassCubeHandle {\n const sealed = options.sealed ?? false;\n const printer = options.printer ?? !sealed;\n const layout = createGlassCubeLayout(host, options.size);\n\n let item = options.item;\n let scene: GlassCubeScene | null = null;\n let isDisposed = false;\n let isFallback = false;\n /** The cube has been sealed at least once — the fallback must not announce it again. */\n let hasCompleted = false;\n /** A print is wanted as soon as the scene is ready. */\n let playPending = !sealed && (options.autoPlay ?? true);\n\n const complete = (): void => {\n if (isDisposed) {\n return;\n }\n hasCompleted = true;\n options.onComplete?.();\n };\n\n const fallBack = (): void => {\n if (isDisposed || isFallback) {\n return;\n }\n isFallback = true;\n scene?.dispose();\n scene = null;\n layout.showFallback(item);\n if (!hasCompleted) {\n complete();\n }\n };\n\n const start = async (): Promise<void> => {\n try {\n const { GlassCubeScene } = await import(\"./scene/scene/glass-cube-scene\");\n const created = await GlassCubeScene.create({\n container: layout.overscan,\n stage: layout.stage,\n item: options.item,\n fallbackItem: options.fallbackItem,\n sealed,\n printer,\n label: normalizeGlassCubeLabel(options.label),\n assets: resolveGlassCubeAssets(options.assets),\n onComplete: complete,\n onContextLost: fallBack,\n });\n // Torn down while the assets were loading: release the scene without\n // uploading a late texture to it.\n if (isDisposed || isFallback) {\n created.dispose();\n return;\n }\n // The picture may have been swapped while the assets were loading.\n if (item !== options.item) {\n await created.setItem(item);\n if (isDisposed || isFallback) {\n created.dispose();\n return;\n }\n }\n // Published only now: `play` / `setItem` queue up behind `ready` until then.\n scene = created;\n if (sealed) {\n complete();\n return;\n }\n if (playPending) {\n playPending = false;\n created.play();\n }\n } catch {\n fallBack();\n }\n };\n\n let ready: Promise<void>;\n if (canPlayGlassCube(host.ownerDocument.defaultView ?? undefined)) {\n ready = start();\n } else {\n ready = Promise.resolve();\n fallBack();\n }\n\n return {\n play() {\n if (sealed || isFallback || isDisposed) {\n return;\n }\n if (scene) {\n scene.play();\n } else {\n playPending = true;\n }\n },\n async setItem(src) {\n item = src;\n if (isFallback) {\n layout.setFallbackSrc(src);\n return;\n }\n await ready;\n if (isDisposed || isFallback) {\n return;\n }\n // Already printed by `start` when the swap happened during loading; the\n // scene skips a repeat of its current URL.\n await scene?.setItem(src);\n },\n setSize(px) {\n layout.setSize(px);\n },\n dispose() {\n if (isDisposed) {\n return;\n }\n isDisposed = true;\n scene?.dispose();\n scene = null;\n layout.dispose();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
package/index.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var _chunkWWTMZRLHjs = require('./chunk-WWTMZRLH.js');
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
exports.DEFAULT_GLASS_CUBE_ASSETS =
|
|
10
|
+
exports.DEFAULT_GLASS_CUBE_ASSETS = _chunkWWTMZRLHjs.DEFAULT_GLASS_CUBE_ASSETS; exports.canPlayGlassCube = _chunkWWTMZRLHjs.canPlayGlassCube; exports.mountGlassCube = _chunkWWTMZRLHjs.mountGlassCube;
|
|
11
11
|
//# sourceMappingURL=index.js.map
|
package/index.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xsolla/xui-b2c-glass-cube",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.227.0",
|
|
4
4
|
"description": "WebGL glass cube: Sol seals an item into a glass cube. Framework-agnostic core (three.js) with a thin React wrapper.",
|
|
5
5
|
"main": "./index.js",
|
|
6
6
|
"module": "./index.mjs",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"test:coverage": "vitest run --coverage"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
|
-
"react": ">=16.
|
|
28
|
-
"three": "
|
|
27
|
+
"react": ">=16.14.0",
|
|
28
|
+
"three": "~0.183.0"
|
|
29
29
|
},
|
|
30
30
|
"peerDependenciesMeta": {
|
|
31
31
|
"react": {
|
package/react/index.d.mts
CHANGED
|
@@ -13,6 +13,8 @@ interface GlassCubeProps extends Omit<GlassCubeOptions, "onComplete"> {
|
|
|
13
13
|
* `sealed`, `printer`, `label`, `fallbackItem` and `assets` change what the
|
|
14
14
|
* scene is made of, so they rebuild it. `autoPlay` is read on mount only; the
|
|
15
15
|
* `ref` exposes the `GlassCubeHandle` for `play()`.
|
|
16
|
+
*
|
|
17
|
+
* Requires React 16.14+ (the build uses the automatic JSX runtime).
|
|
16
18
|
*/
|
|
17
19
|
declare const GlassCube: React.ForwardRefExoticComponent<GlassCubeProps & React.RefAttributes<GlassCubeHandle>>;
|
|
18
20
|
|
package/react/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ interface GlassCubeProps extends Omit<GlassCubeOptions, "onComplete"> {
|
|
|
13
13
|
* `sealed`, `printer`, `label`, `fallbackItem` and `assets` change what the
|
|
14
14
|
* scene is made of, so they rebuild it. `autoPlay` is read on mount only; the
|
|
15
15
|
* `ref` exposes the `GlassCubeHandle` for `play()`.
|
|
16
|
+
*
|
|
17
|
+
* Requires React 16.14+ (the build uses the automatic JSX runtime).
|
|
16
18
|
*/
|
|
17
19
|
declare const GlassCube: React.ForwardRefExoticComponent<GlassCubeProps & React.RefAttributes<GlassCubeHandle>>;
|
|
18
20
|
|
package/react/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunkWWTMZRLHjs = require('../chunk-WWTMZRLH.js');
|
|
8
8
|
|
|
9
9
|
// src/react/GlassCube.tsx
|
|
10
10
|
|
|
@@ -39,8 +39,8 @@ var GlassCube = _react.forwardRef.call(void 0,
|
|
|
39
39
|
sizeRef.current = size;
|
|
40
40
|
autoPlayRef.current = autoPlay;
|
|
41
41
|
onCompleteRef.current = onComplete;
|
|
42
|
-
const normalizedLabel =
|
|
43
|
-
const labelKey =
|
|
42
|
+
const normalizedLabel = _chunkWWTMZRLHjs.normalizeGlassCubeLabel.call(void 0, label);
|
|
43
|
+
const labelKey = _chunkWWTMZRLHjs.glassCubeLabelKey.call(void 0, normalizedLabel);
|
|
44
44
|
const assetsKey = assets ? `${assets.cubeModelUrl}\0${assets.printerModelUrl}\0${assets.environmentUrl}` : "";
|
|
45
45
|
_react.useImperativeHandle.call(void 0,
|
|
46
46
|
ref,
|
|
@@ -57,7 +57,7 @@ var GlassCube = _react.forwardRef.call(void 0,
|
|
|
57
57
|
if (!host) {
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
-
const cube =
|
|
60
|
+
const cube = _chunkWWTMZRLHjs.mountGlassCube.call(void 0, host, {
|
|
61
61
|
item: itemRef.current,
|
|
62
62
|
fallbackItem,
|
|
63
63
|
size: sizeRef.current,
|
|
@@ -95,5 +95,5 @@ var GlassCube = _react.forwardRef.call(void 0,
|
|
|
95
95
|
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
exports.DEFAULT_GLASS_CUBE_ASSETS =
|
|
98
|
+
exports.DEFAULT_GLASS_CUBE_ASSETS = _chunkWWTMZRLHjs.DEFAULT_GLASS_CUBE_ASSETS; exports.GlassCube = GlassCube; exports.canPlayGlassCube = _chunkWWTMZRLHjs.canPlayGlassCube;
|
|
99
99
|
//# sourceMappingURL=index.js.map
|
package/react/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/builds/frontend-platform/xsolla-ui-toolkit-v2/packages/b2c/glass-cube/dist/react/index.js","../../src/react/GlassCube.tsx"],"names":["GlassCube"],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,uDAA6B;AAC7B;AACA;ACRA;
|
|
1
|
+
{"version":3,"sources":["/builds/frontend-platform/xsolla-ui-toolkit-v2/packages/b2c/glass-cube/dist/react/index.js","../../src/react/GlassCube.tsx"],"names":["GlassCube"],"mappings":"AAAA;AACE;AACA;AACA;AACA;AACA;AACF,uDAA6B;AAC7B;AACA;ACRA;AAGE;AACA;AACA;AACA;AAAA,8BACK;AAqGD,+CAAA;AAjFC,IAAM,UAAA,EAAY,+BAAA;AAAA,EACvB,SAASA,UAAAA,CACP;AAAA,IACE,IAAA;AAAA,IACA,YAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA;AAAA,IACA,OAAA;AAAA,IACA,QAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA,IACA,UAAA;AAAA,IACA,SAAA;AAAA,IACA,KAAA;AAAA,IACA;AAAA,EACF,CAAA,EACA,GAAA,EACc;AACd,IAAA,MAAM,QAAA,EAAU,2BAAA,IAA2B,CAAA;AAC3C,IAAA,MAAM,QAAA,EAAU,2BAAA,IAAmC,CAAA;AAEnD,IAAA,MAAM,QAAA,EAAU,2BAAA,IAAW,CAAA;AAC3B,IAAA,MAAM,QAAA,EAAU,2BAAA,IAAW,CAAA;AAC3B,IAAA,MAAM,YAAA,EAAc,2BAAA,QAAe,CAAA;AACnC,IAAA,MAAM,cAAA,EAAgB,2BAAA,UAAiB,CAAA;AACvC,IAAA,OAAA,CAAQ,QAAA,EAAU,IAAA;AAClB,IAAA,OAAA,CAAQ,QAAA,EAAU,IAAA;AAClB,IAAA,WAAA,CAAY,QAAA,EAAU,QAAA;AACtB,IAAA,aAAA,CAAc,QAAA,EAAU,UAAA;AAExB,IAAA,MAAM,gBAAA,EAAkB,sDAAA,KAA6B,CAAA;AACrD,IAAA,MAAM,SAAA,EAAW,gDAAA,eAAiC,CAAA;AAClD,IAAA,MAAM,UAAA,EAAY,OAAA,EACd,CAAA,EAAA;AAGJ,IAAA;AACE,MAAA;AACO,MAAA;AACe,QAAA;AACF,QAAA;AACD,QAAA;AACM,QAAA;AACzB,MAAA;AACC,MAAA;AACH,IAAA;AAEgB,IAAA;AACO,MAAA;AACV,MAAA;AACT,QAAA;AACF,MAAA;AACa,MAAA;AACG,QAAA;AACd,QAAA;AACc,QAAA;AACd,QAAA;AACA,QAAA;AACsB,QAAA;AACf,QAAA;AACP,QAAA;AACkB,QAAA;AACnB,MAAA;AACiB,MAAA;AACL,MAAA;AACO,QAAA;AACL,QAAA;AACf,MAAA;AAGmB,IAAA;AAEL,IAAA;AACQ,MAAA;AACf,IAAA;AAEO,IAAA;AACW,sBAAA;AAClB,IAAA;AAGP,IAAA;AAAC,MAAA;AAAA,MAAA;AACM,QAAA;AACL,QAAA;AACA,QAAA;AACa,QAAA;AAAA,MAAA;AACf,IAAA;AAEJ,EAAA;AACF;ADvBgC;AACA;AACA;AACA;AACA","file":"/builds/frontend-platform/xsolla-ui-toolkit-v2/packages/b2c/glass-cube/dist/react/index.js","sourcesContent":[null,"import React, {\n type CSSProperties,\n type ReactElement,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n} from \"react\";\nimport { glassCubeLabelKey, normalizeGlassCubeLabel } from \"../label\";\nimport { mountGlassCube } from \"../mount\";\nimport type { GlassCubeHandle, GlassCubeOptions } from \"../types\";\n\nexport interface GlassCubeProps extends Omit<GlassCubeOptions, \"onComplete\"> {\n onComplete?: () => void;\n className?: string;\n style?: CSSProperties;\n testID?: string;\n}\n\n/**\n * Thin wrapper over `mountGlassCube`. `item` and `size` update in place;\n * `sealed`, `printer`, `label`, `fallbackItem` and `assets` change what the\n * scene is made of, so they rebuild it. `autoPlay` is read on mount only; the\n * `ref` exposes the `GlassCubeHandle` for `play()`.\n *\n * Requires React 16.14+ (the build uses the automatic JSX runtime).\n */\nexport const GlassCube = forwardRef<GlassCubeHandle, GlassCubeProps>(\n function GlassCube(\n {\n item,\n fallbackItem,\n size,\n sealed,\n printer,\n autoPlay,\n label,\n assets,\n onComplete,\n className,\n style,\n testID,\n },\n ref\n ): ReactElement {\n const hostRef = useRef<HTMLDivElement>(null);\n const cubeRef = useRef<GlassCubeHandle | null>(null);\n // Latest props the mount reads lazily, without them being remount triggers.\n const itemRef = useRef(item);\n const sizeRef = useRef(size);\n const autoPlayRef = useRef(autoPlay);\n const onCompleteRef = useRef(onComplete);\n itemRef.current = item;\n sizeRef.current = size;\n autoPlayRef.current = autoPlay;\n onCompleteRef.current = onComplete;\n\n const normalizedLabel = normalizeGlassCubeLabel(label);\n const labelKey = glassCubeLabelKey(normalizedLabel);\n const assetsKey = assets\n ? `${assets.cubeModelUrl}\\u0000${assets.printerModelUrl}\\u0000${assets.environmentUrl}`\n : \"\";\n\n useImperativeHandle(\n ref,\n () => ({\n play: () => cubeRef.current?.play(),\n setItem: (src) => cubeRef.current?.setItem(src) ?? Promise.resolve(),\n setSize: (px) => cubeRef.current?.setSize(px),\n dispose: () => cubeRef.current?.dispose(),\n }),\n []\n );\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) {\n return;\n }\n const cube = mountGlassCube(host, {\n item: itemRef.current,\n fallbackItem,\n size: sizeRef.current,\n sealed,\n printer,\n autoPlay: autoPlayRef.current,\n label: normalizedLabel,\n assets,\n onComplete: () => onCompleteRef.current?.(),\n });\n cubeRef.current = cube;\n return () => {\n cubeRef.current = null;\n cube.dispose();\n };\n // `label` / `assets` are compared by value through their keys.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sealed, printer, fallbackItem, labelKey, assetsKey]);\n\n useEffect(() => {\n void cubeRef.current?.setItem(item);\n }, [item]);\n\n useEffect(() => {\n cubeRef.current?.setSize(size);\n }, [size]);\n\n return (\n <div\n ref={hostRef}\n className={className}\n style={style}\n data-testid={testID}\n />\n );\n }\n);\n"]}
|
package/react/index.mjs
CHANGED
package/react/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/react/GlassCube.tsx"],"sourcesContent":["import React, {\n type CSSProperties,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n} from \"react\";\nimport { glassCubeLabelKey, normalizeGlassCubeLabel } from \"../label\";\nimport { mountGlassCube } from \"../mount\";\nimport type { GlassCubeHandle, GlassCubeOptions } from \"../types\";\n\nexport interface GlassCubeProps extends Omit<GlassCubeOptions, \"onComplete\"> {\n onComplete?: () => void;\n className?: string;\n style?: CSSProperties;\n testID?: string;\n}\n\n/**\n * Thin wrapper over `mountGlassCube`. `item` and `size` update in place;\n * `sealed`, `printer`, `label`, `fallbackItem` and `assets` change what the\n * scene is made of, so they rebuild it. `autoPlay` is read on mount only; the\n * `ref` exposes the `GlassCubeHandle` for `play()`.\n */\nexport const GlassCube = forwardRef<GlassCubeHandle, GlassCubeProps>(\n function GlassCube(\n {\n item,\n fallbackItem,\n size,\n sealed,\n printer,\n autoPlay,\n label,\n assets,\n onComplete,\n className,\n style,\n testID,\n },\n ref\n ):
|
|
1
|
+
{"version":3,"sources":["../../src/react/GlassCube.tsx"],"sourcesContent":["import React, {\n type CSSProperties,\n type ReactElement,\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n} from \"react\";\nimport { glassCubeLabelKey, normalizeGlassCubeLabel } from \"../label\";\nimport { mountGlassCube } from \"../mount\";\nimport type { GlassCubeHandle, GlassCubeOptions } from \"../types\";\n\nexport interface GlassCubeProps extends Omit<GlassCubeOptions, \"onComplete\"> {\n onComplete?: () => void;\n className?: string;\n style?: CSSProperties;\n testID?: string;\n}\n\n/**\n * Thin wrapper over `mountGlassCube`. `item` and `size` update in place;\n * `sealed`, `printer`, `label`, `fallbackItem` and `assets` change what the\n * scene is made of, so they rebuild it. `autoPlay` is read on mount only; the\n * `ref` exposes the `GlassCubeHandle` for `play()`.\n *\n * Requires React 16.14+ (the build uses the automatic JSX runtime).\n */\nexport const GlassCube = forwardRef<GlassCubeHandle, GlassCubeProps>(\n function GlassCube(\n {\n item,\n fallbackItem,\n size,\n sealed,\n printer,\n autoPlay,\n label,\n assets,\n onComplete,\n className,\n style,\n testID,\n },\n ref\n ): ReactElement {\n const hostRef = useRef<HTMLDivElement>(null);\n const cubeRef = useRef<GlassCubeHandle | null>(null);\n // Latest props the mount reads lazily, without them being remount triggers.\n const itemRef = useRef(item);\n const sizeRef = useRef(size);\n const autoPlayRef = useRef(autoPlay);\n const onCompleteRef = useRef(onComplete);\n itemRef.current = item;\n sizeRef.current = size;\n autoPlayRef.current = autoPlay;\n onCompleteRef.current = onComplete;\n\n const normalizedLabel = normalizeGlassCubeLabel(label);\n const labelKey = glassCubeLabelKey(normalizedLabel);\n const assetsKey = assets\n ? `${assets.cubeModelUrl}\\u0000${assets.printerModelUrl}\\u0000${assets.environmentUrl}`\n : \"\";\n\n useImperativeHandle(\n ref,\n () => ({\n play: () => cubeRef.current?.play(),\n setItem: (src) => cubeRef.current?.setItem(src) ?? Promise.resolve(),\n setSize: (px) => cubeRef.current?.setSize(px),\n dispose: () => cubeRef.current?.dispose(),\n }),\n []\n );\n\n useEffect(() => {\n const host = hostRef.current;\n if (!host) {\n return;\n }\n const cube = mountGlassCube(host, {\n item: itemRef.current,\n fallbackItem,\n size: sizeRef.current,\n sealed,\n printer,\n autoPlay: autoPlayRef.current,\n label: normalizedLabel,\n assets,\n onComplete: () => onCompleteRef.current?.(),\n });\n cubeRef.current = cube;\n return () => {\n cubeRef.current = null;\n cube.dispose();\n };\n // `label` / `assets` are compared by value through their keys.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sealed, printer, fallbackItem, labelKey, assetsKey]);\n\n useEffect(() => {\n void cubeRef.current?.setItem(item);\n }, [item]);\n\n useEffect(() => {\n cubeRef.current?.setSize(size);\n }, [size]);\n\n return (\n <div\n ref={hostRef}\n className={className}\n style={style}\n data-testid={testID}\n />\n );\n }\n);\n"],"mappings":";;;;;;;;;AAAA;AAAA,EAGE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAqGD;AAjFC,IAAM,YAAY;AAAA,EACvB,SAASA,WACP;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,GACA,KACc;AACd,UAAM,UAAU,OAAuB,IAAI;AAC3C,UAAM,UAAU,OAA+B,IAAI;AAEnD,UAAM,UAAU,OAAO,IAAI;AAC3B,UAAM,UAAU,OAAO,IAAI;AAC3B,UAAM,cAAc,OAAO,QAAQ;AACnC,UAAM,gBAAgB,OAAO,UAAU;AACvC,YAAQ,UAAU;AAClB,YAAQ,UAAU;AAClB,gBAAY,UAAU;AACtB,kBAAc,UAAU;AAExB,UAAM,kBAAkB,wBAAwB,KAAK;AACrD,UAAM,WAAW,kBAAkB,eAAe;AAClD,UAAM,YAAY,SACd,GAAG,OAAO,YAAY,KAAS,OAAO,eAAe,KAAS,OAAO,cAAc,KACnF;AAEJ;AAAA,MACE;AAAA,MACA,OAAO;AAAA,QACL,MAAM,MAAM,QAAQ,SAAS,KAAK;AAAA,QAClC,SAAS,CAAC,QAAQ,QAAQ,SAAS,QAAQ,GAAG,KAAK,QAAQ,QAAQ;AAAA,QACnE,SAAS,CAAC,OAAO,QAAQ,SAAS,QAAQ,EAAE;AAAA,QAC5C,SAAS,MAAM,QAAQ,SAAS,QAAQ;AAAA,MAC1C;AAAA,MACA,CAAC;AAAA,IACH;AAEA,cAAU,MAAM;AACd,YAAM,OAAO,QAAQ;AACrB,UAAI,CAAC,MAAM;AACT;AAAA,MACF;AACA,YAAM,OAAO,eAAe,MAAM;AAAA,QAChC,MAAM,QAAQ;AAAA,QACd;AAAA,QACA,MAAM,QAAQ;AAAA,QACd;AAAA,QACA;AAAA,QACA,UAAU,YAAY;AAAA,QACtB,OAAO;AAAA,QACP;AAAA,QACA,YAAY,MAAM,cAAc,UAAU;AAAA,MAC5C,CAAC;AACD,cAAQ,UAAU;AAClB,aAAO,MAAM;AACX,gBAAQ,UAAU;AAClB,aAAK,QAAQ;AAAA,MACf;AAAA,IAGF,GAAG,CAAC,QAAQ,SAAS,cAAc,UAAU,SAAS,CAAC;AAEvD,cAAU,MAAM;AACd,WAAK,QAAQ,SAAS,QAAQ,IAAI;AAAA,IACpC,GAAG,CAAC,IAAI,CAAC;AAET,cAAU,MAAM;AACd,cAAQ,SAAS,QAAQ,IAAI;AAAA,IAC/B,GAAG,CAAC,IAAI,CAAC;AAET,WACE;AAAA,MAAC;AAAA;AAAA,QACC,KAAK;AAAA,QACL;AAAA,QACA;AAAA,QACA,eAAa;AAAA;AAAA,IACf;AAAA,EAEJ;AACF;","names":["GlassCube"]}
|
package/chunk-U3XM7FPH.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["/builds/frontend-platform/xsolla-ui-toolkit-v2/packages/b2c/glass-cube/dist/chunk-U3XM7FPH.js","../src/assets.ts","../src/capabilities.ts","../src/label.ts","../src/layout.ts","../src/mount.ts"],"names":[],"mappings":"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","file":"/builds/frontend-platform/xsolla-ui-toolkit-v2/packages/b2c/glass-cube/dist/chunk-U3XM7FPH.js","sourcesContent":[null,"/** URLs of the binary assets the scene loads at runtime. */\nexport interface GlassCubeAssets {\n /** Glass cube model (`glass-cube.glb`). */\n readonly cubeModelUrl: string;\n /** Sol, the printer, as a rigged model (`sol.glb`). */\n readonly printerModelUrl: string;\n /** Studio light map Sol was rendered in (`studio.hdr`). */\n readonly environmentUrl: string;\n}\n\nconst CDN_BASE = \"https://cdn.xsolla.net/xsolla-ui-toolkit-prod/glass-cube/v1\";\n\n/**\n * Served from the toolkit CDN bucket (`cdn-assets/xsolla-ui-toolkit-prod/glass-cube/v1`).\n * Bump `?v=` whenever a file changes in place — the CDN caches aggressively.\n */\nexport const DEFAULT_GLASS_CUBE_ASSETS: GlassCubeAssets = {\n cubeModelUrl: `${CDN_BASE}/glass-cube.glb?v=1`,\n printerModelUrl: `${CDN_BASE}/sol.glb?v=1`,\n environmentUrl: `${CDN_BASE}/studio.hdr?v=1`,\n};\n\nexport function resolveGlassCubeAssets(\n overrides?: Partial<GlassCubeAssets>\n): GlassCubeAssets {\n return { ...DEFAULT_GLASS_CUBE_ASSETS, ...overrides };\n}\n","/**\n * Deliberately free of `three` imports: these probes run before deciding whether\n * to download the WebGL chunk at all.\n */\nconst webgl2Support = new WeakMap<Window, boolean>();\n\nfunction defaultWindow(): Window | undefined {\n return typeof window === \"undefined\" ? undefined : window;\n}\n\nexport function hasWebgl2(win: Window | undefined = defaultWindow()): boolean {\n if (!win) {\n return false;\n }\n const cached = webgl2Support.get(win);\n if (cached !== undefined) {\n return cached;\n }\n let supported = false;\n try {\n const gl = win.document.createElement(\"canvas\").getContext(\"webgl2\");\n gl?.getExtension(\"WEBGL_lose_context\")?.loseContext();\n supported = gl !== null && gl !== undefined;\n } catch {\n supported = false;\n }\n webgl2Support.set(win, supported);\n return supported;\n}\n\nexport function prefersReducedMotion(\n win: Window | undefined = defaultWindow()\n): boolean {\n return (\n win?.matchMedia?.(\"(prefers-reduced-motion: reduce)\")?.matches ?? false\n );\n}\n\n/**\n * `false` means the print cannot play: show the item sealed right away instead\n * of never showing it. Checked once, before the scene chunk is requested.\n */\nexport function canPlayGlassCube(\n win: Window | undefined = defaultWindow()\n): boolean {\n return !prefersReducedMotion(win) && hasWebgl2(win);\n}\n","import type { GlassCubeLabel } from \"./types\";\n\n/**\n * Accepts the tag the way consumers usually hold it — one string such as\n * `\"XLA-000-006\"` — and splits it at the first dash into the series and the\n * number the cube face prints on separate lines. An object passes through;\n * an empty string means no tag.\n */\nexport function normalizeGlassCubeLabel(\n label: string | GlassCubeLabel | undefined\n): GlassCubeLabel | undefined {\n if (label === undefined) {\n return undefined;\n }\n if (typeof label !== \"string\") {\n return label;\n }\n const trimmed = label.trim();\n if (trimmed === \"\") {\n return undefined;\n }\n const dash = trimmed.indexOf(\"-\");\n if (dash <= 0) {\n return { series: trimmed, number: \"\" };\n }\n return { series: trimmed.slice(0, dash), number: trimmed.slice(dash) };\n}\n\n/** Stable key for comparing labels by value (React deps, memoisation). */\nexport function glassCubeLabelKey(label: GlassCubeLabel | undefined): string {\n return label ? `${label.series}\\u0000${label.number}` : \"\";\n}\n","/**\n * DOM the cube lives in. Mirrors the pay-station `x-status-cube` template:\n *\n * root (flex, centred) as wide as the print: `size × 2.5`, capped by the host\n * ├─ stage square the printed cube is fitted to; its width is the cube size\n * │ └─ img static fallback, shown instead of the canvas\n * └─ overscan 2.5× the stage, centred on it; Sol flies in from outside the cube\n * └─ canvas (appended by the scene)\n *\n * Everything is inline-styled so consumers need no stylesheet; the class names\n * are hooks for anyone who wants to override.\n */\n\n/** Sol flies in from outside the cube frame — the canvas is this much bigger than the cube. */\nexport const STAGE_OVERSCAN = 2.5;\n\nconst CLASS_ROOT = \"xui-glass-cube\";\nconst CLASS_STAGE = `${CLASS_ROOT}__stage`;\nconst CLASS_OVERSCAN = `${CLASS_ROOT}__overscan`;\nconst CLASS_FALLBACK = `${CLASS_ROOT}__fallback`;\n\n/** The fallback picture is inset the way the item sits inside the printed glass. */\nconst FALLBACK_INSET = \"15%\";\nconst FALLBACK_SIZE = \"70%\";\n\nexport interface GlassCubeLayout {\n readonly root: HTMLElement;\n readonly stage: HTMLElement;\n readonly overscan: HTMLElement;\n setSize(px: number | undefined): void;\n showFallback(src: string): void;\n setFallbackSrc(src: string): void;\n dispose(): void;\n}\n\nfunction assign(\n element: HTMLElement,\n style: Partial<CSSStyleDeclaration>\n): void {\n Object.assign(element.style, style);\n}\n\nexport function createGlassCubeLayout(\n host: HTMLElement,\n size: number | undefined\n): GlassCubeLayout {\n const document = host.ownerDocument;\n\n const root = document.createElement(\"div\");\n root.className = CLASS_ROOT;\n root.setAttribute(\"aria-hidden\", \"true\");\n assign(root, {\n position: \"relative\",\n display: \"flex\",\n justifyContent: \"center\",\n // Never wider than the host, so a narrow column shrinks the whole print.\n maxWidth: \"100%\",\n });\n\n const stage = document.createElement(\"div\");\n stage.className = CLASS_STAGE;\n assign(stage, {\n position: \"relative\",\n flexShrink: \"0\",\n width: `${100 / STAGE_OVERSCAN}%`,\n });\n // Keeps the stage square without `aspect-ratio`.\n const square = document.createElement(\"div\");\n assign(square, { paddingTop: \"100%\" });\n stage.appendChild(square);\n\n const overscan = document.createElement(\"div\");\n overscan.className = CLASS_OVERSCAN;\n assign(overscan, {\n position: \"absolute\",\n top: \"50%\",\n left: \"50%\",\n width: \"100%\",\n height: `${STAGE_OVERSCAN * 100}%`,\n pointerEvents: \"none\",\n transform: \"translate(-50%, -50%)\",\n });\n\n root.append(stage, overscan);\n host.appendChild(root);\n\n let fallback: HTMLImageElement | null = null;\n\n const layout: GlassCubeLayout = {\n root,\n stage,\n overscan,\n setSize(px) {\n // The stage is a fixed fraction of the root, so sizing the root to the\n // whole print makes the stage exactly `px` wide — no wrapper needed.\n root.style.width = px === undefined ? \"100%\" : `${px * STAGE_OVERSCAN}px`;\n },\n showFallback(src) {\n overscan.remove();\n if (!fallback) {\n fallback = document.createElement(\"img\");\n fallback.className = CLASS_FALLBACK;\n fallback.alt = \"\";\n assign(fallback, {\n position: \"absolute\",\n top: FALLBACK_INSET,\n left: FALLBACK_INSET,\n width: FALLBACK_SIZE,\n height: FALLBACK_SIZE,\n objectFit: \"contain\",\n });\n stage.appendChild(fallback);\n }\n fallback.src = src;\n },\n setFallbackSrc(src) {\n if (fallback) {\n fallback.src = src;\n }\n },\n dispose() {\n root.remove();\n },\n };\n\n layout.setSize(size);\n\n return layout;\n}\n","import { resolveGlassCubeAssets } from \"./assets\";\nimport { canPlayGlassCube } from \"./capabilities\";\nimport { normalizeGlassCubeLabel } from \"./label\";\nimport { createGlassCubeLayout } from \"./layout\";\nimport type { GlassCubeScene } from \"./scene/scene/glass-cube-scene\";\nimport type { GlassCubeHandle, GlassCubeOptions } from \"./types\";\n\n/**\n * Builds the cube inside `host`. The WebGL scene (and `three` with it) is\n * requested only when the print can actually play; otherwise, and whenever the\n * scene fails to load or loses its context, the item is shown as a plain picture.\n */\nexport function mountGlassCube(\n host: HTMLElement,\n options: GlassCubeOptions\n): GlassCubeHandle {\n const sealed = options.sealed ?? false;\n const printer = options.printer ?? !sealed;\n const layout = createGlassCubeLayout(host, options.size);\n\n let item = options.item;\n let scene: GlassCubeScene | null = null;\n let isDisposed = false;\n let isFallback = false;\n /** The cube has been sealed at least once — the fallback must not announce it again. */\n let hasCompleted = false;\n /** A print is wanted as soon as the scene is ready. */\n let playPending = !sealed && (options.autoPlay ?? true);\n\n const complete = (): void => {\n if (isDisposed) {\n return;\n }\n hasCompleted = true;\n options.onComplete?.();\n };\n\n const fallBack = (): void => {\n if (isDisposed || isFallback) {\n return;\n }\n isFallback = true;\n scene?.dispose();\n scene = null;\n layout.showFallback(item);\n if (!hasCompleted) {\n complete();\n }\n };\n\n const start = async (): Promise<void> => {\n try {\n const { GlassCubeScene } = await import(\"./scene/scene/glass-cube-scene\");\n const created = await GlassCubeScene.create({\n container: layout.overscan,\n stage: layout.stage,\n item: options.item,\n fallbackItem: options.fallbackItem,\n sealed,\n printer,\n label: normalizeGlassCubeLabel(options.label),\n assets: resolveGlassCubeAssets(options.assets),\n onComplete: complete,\n onContextLost: fallBack,\n });\n // The picture may have been swapped while the assets were loading.\n if (item !== options.item) {\n await created.setItem(item);\n }\n if (isDisposed || isFallback) {\n created.dispose();\n return;\n }\n // Published only now: `play` / `setItem` queue up behind `ready` until then.\n scene = created;\n if (sealed) {\n complete();\n return;\n }\n if (playPending) {\n playPending = false;\n created.play();\n }\n } catch {\n fallBack();\n }\n };\n\n let ready: Promise<void>;\n if (canPlayGlassCube(host.ownerDocument.defaultView ?? undefined)) {\n ready = start();\n } else {\n ready = Promise.resolve();\n fallBack();\n }\n\n return {\n play() {\n if (sealed || isFallback || isDisposed) {\n return;\n }\n if (scene) {\n scene.play();\n } else {\n playPending = true;\n }\n },\n async setItem(src) {\n item = src;\n if (isFallback) {\n layout.setFallbackSrc(src);\n return;\n }\n await ready;\n if (isDisposed || isFallback) {\n return;\n }\n // Already printed by `start` when the swap happened during loading; the\n // scene skips a repeat of its current URL.\n await scene?.setItem(src);\n },\n setSize(px) {\n layout.setSize(px);\n },\n dispose() {\n if (isDisposed) {\n return;\n }\n isDisposed = true;\n scene?.dispose();\n scene = null;\n layout.dispose();\n },\n };\n}\n"]}
|