bitsnap-react 1.15.0 → 1.17.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.
Files changed (161) hide show
  1. package/AGENTS.md +8 -0
  2. package/README.md +132 -2
  3. package/dist/CartProvider-DGRZDTYC.mjs +2 -0
  4. package/dist/CartProvider-YVOHSMPE.js +2 -0
  5. package/dist/CartProvider-YVOHSMPE.js.map +1 -0
  6. package/dist/PanelModelFiles-NYCOFPLY.js +2 -0
  7. package/dist/PanelModelFiles-NYCOFPLY.js.map +1 -0
  8. package/dist/PanelModelFiles-RV7OHUUZ.mjs +2 -0
  9. package/dist/PanelModelFiles-RV7OHUUZ.mjs.map +1 -0
  10. package/dist/Viewer3D-2IDEH6W3.mjs +2 -0
  11. package/dist/Viewer3D-2IDEH6W3.mjs.map +1 -0
  12. package/dist/analytics.d.mts +2 -2
  13. package/dist/analytics.d.ts +2 -2
  14. package/dist/analytics.js +1 -1
  15. package/dist/analytics.js.map +1 -1
  16. package/dist/analytics.mjs +1 -1
  17. package/dist/catalog.d.mts +6 -2
  18. package/dist/catalog.d.ts +6 -2
  19. package/dist/catalog.js +1 -1
  20. package/dist/catalog.js.map +1 -1
  21. package/dist/catalog.mjs +1 -1
  22. package/dist/chunk-2NL5FCXC.mjs +4 -0
  23. package/dist/chunk-2NL5FCXC.mjs.map +1 -0
  24. package/dist/{chunk-PEVB3VL4.mjs → chunk-2UDAB2C5.mjs} +2 -2
  25. package/dist/{chunk-PEVB3VL4.mjs.map → chunk-2UDAB2C5.mjs.map} +1 -1
  26. package/dist/{chunk-7DG4UPWE.js → chunk-4BL7TLK3.js} +2 -2
  27. package/dist/chunk-4BL7TLK3.js.map +1 -0
  28. package/dist/chunk-4ETIDGYZ.mjs +2 -0
  29. package/dist/chunk-4ETIDGYZ.mjs.map +1 -0
  30. package/dist/chunk-555KFWGY.js +4 -0
  31. package/dist/chunk-555KFWGY.js.map +1 -0
  32. package/dist/chunk-5ED7FWNY.mjs +2 -0
  33. package/dist/chunk-5ED7FWNY.mjs.map +1 -0
  34. package/dist/chunk-5FZAROP6.js +2 -0
  35. package/dist/chunk-5FZAROP6.js.map +1 -0
  36. package/dist/chunk-5R6AGO3Z.mjs +2 -0
  37. package/dist/chunk-5R6AGO3Z.mjs.map +1 -0
  38. package/dist/chunk-6GCCNBEU.js +2 -0
  39. package/dist/chunk-6GCCNBEU.js.map +1 -0
  40. package/dist/chunk-6Q3VGPSF.js +2 -0
  41. package/dist/chunk-6Q3VGPSF.js.map +1 -0
  42. package/dist/chunk-6YIRRDCL.mjs +2 -0
  43. package/dist/chunk-6YIRRDCL.mjs.map +1 -0
  44. package/dist/chunk-7RXW6XFY.js +2 -0
  45. package/dist/chunk-7RXW6XFY.js.map +1 -0
  46. package/dist/chunk-A6VCP3A4.mjs +2 -0
  47. package/dist/chunk-A6VCP3A4.mjs.map +1 -0
  48. package/dist/chunk-BZXV77PT.mjs +2 -0
  49. package/dist/chunk-BZXV77PT.mjs.map +1 -0
  50. package/dist/{chunk-MG6VV5J2.js → chunk-C3BBJOVV.js} +2 -2
  51. package/dist/chunk-C3BBJOVV.js.map +1 -0
  52. package/dist/chunk-CUPG4B5W.mjs +2 -0
  53. package/dist/chunk-CUPG4B5W.mjs.map +1 -0
  54. package/dist/chunk-ESF5ZVVF.js +2 -0
  55. package/dist/chunk-ESF5ZVVF.js.map +1 -0
  56. package/dist/chunk-HKNWW37T.mjs +2 -0
  57. package/dist/chunk-HKNWW37T.mjs.map +1 -0
  58. package/dist/chunk-JSDGZ4NY.js.map +1 -1
  59. package/dist/chunk-KOYBZORL.js.map +1 -1
  60. package/dist/chunk-L7Q7OKM5.mjs +2 -0
  61. package/dist/chunk-L7Q7OKM5.mjs.map +1 -0
  62. package/dist/chunk-M2G74N43.mjs +2 -0
  63. package/dist/chunk-M2G74N43.mjs.map +1 -0
  64. package/dist/chunk-MIMQTH2G.js +2 -0
  65. package/dist/chunk-MIMQTH2G.js.map +1 -0
  66. package/dist/chunk-SYN5KU3O.js +2 -0
  67. package/dist/chunk-SYN5KU3O.js.map +1 -0
  68. package/dist/chunk-VNYHJT6V.mjs +2 -0
  69. package/dist/chunk-VNYHJT6V.mjs.map +1 -0
  70. package/dist/chunk-W3RGQYPD.js.map +1 -1
  71. package/dist/chunk-X3LVYIJR.mjs +2 -0
  72. package/dist/chunk-X3LVYIJR.mjs.map +1 -0
  73. package/dist/consent.js.map +1 -1
  74. package/dist/course.d.mts +3 -3
  75. package/dist/course.d.ts +3 -3
  76. package/dist/course.js +1 -1
  77. package/dist/course.js.map +1 -1
  78. package/dist/course.mjs +1 -1
  79. package/dist/course.mjs.map +1 -1
  80. package/dist/embed/analytics.js +1 -1
  81. package/dist/errors.js.map +1 -1
  82. package/dist/index.css +1 -1
  83. package/dist/index.css.map +1 -1
  84. package/dist/index.d.mts +41 -12
  85. package/dist/index.d.ts +41 -12
  86. package/dist/index.js +1 -1
  87. package/dist/index.js.map +1 -1
  88. package/dist/index.mjs +1 -1
  89. package/dist/index.mjs.map +1 -1
  90. package/dist/{instrument-BE8ktMUv.d.mts → instrument-eXaC8hc2.d.mts} +1 -1
  91. package/dist/{instrument-BE8ktMUv.d.ts → instrument-eXaC8hc2.d.ts} +1 -1
  92. package/dist/internal.js.map +1 -1
  93. package/dist/methods-3W34GRYL.js +2 -0
  94. package/dist/methods-3W34GRYL.js.map +1 -0
  95. package/dist/methods-LUJSKSQ4.mjs +2 -0
  96. package/dist/model-files-client-HHDXJDTS.mjs +2 -0
  97. package/dist/model-files-client-HHDXJDTS.mjs.map +1 -0
  98. package/dist/model-files-client-P4PORZJ6.js +2 -0
  99. package/dist/model-files-client-P4PORZJ6.js.map +1 -0
  100. package/dist/models.d.mts +2 -0
  101. package/dist/models.d.ts +2 -0
  102. package/dist/models.js +1 -1
  103. package/dist/models.js.map +1 -1
  104. package/dist/models.mjs +1 -1
  105. package/dist/models.mjs.map +1 -1
  106. package/dist/returns.d.mts +19 -10
  107. package/dist/returns.d.ts +19 -10
  108. package/dist/returns.js +1 -1
  109. package/dist/returns.js.map +1 -1
  110. package/dist/returns.mjs +1 -1
  111. package/dist/{texts-tQtAGJCT.d.mts → texts-LGhuQfA9.d.mts} +28 -3
  112. package/dist/{texts-CIEFbJTN.d.ts → texts-m6XQGZ6w.d.ts} +28 -3
  113. package/dist/types-ClFCVzk_.d.ts +121 -0
  114. package/dist/types-D6LsoycW.d.mts +121 -0
  115. package/dist/viewer3d.css +2 -0
  116. package/dist/viewer3d.css.map +1 -0
  117. package/dist/viewer3d.d.mts +318 -0
  118. package/dist/viewer3d.d.ts +318 -0
  119. package/dist/viewer3d.js +2 -0
  120. package/dist/viewer3d.js.map +1 -0
  121. package/dist/viewer3d.mjs +63 -0
  122. package/dist/viewer3d.mjs.map +1 -0
  123. package/package.json +43 -16
  124. package/dist/CartProvider-64QEJ7A7.js +0 -2
  125. package/dist/CartProvider-64QEJ7A7.js.map +0 -1
  126. package/dist/CartProvider-XKK7A3C2.mjs +0 -2
  127. package/dist/chunk-2TLWMYXW.mjs +0 -2
  128. package/dist/chunk-2TLWMYXW.mjs.map +0 -1
  129. package/dist/chunk-7DG4UPWE.js.map +0 -1
  130. package/dist/chunk-7TLJKYZD.mjs +0 -2
  131. package/dist/chunk-7TLJKYZD.mjs.map +0 -1
  132. package/dist/chunk-AZQOW73L.mjs +0 -4
  133. package/dist/chunk-AZQOW73L.mjs.map +0 -1
  134. package/dist/chunk-DY2S42Q6.js +0 -2
  135. package/dist/chunk-DY2S42Q6.js.map +0 -1
  136. package/dist/chunk-FGB5FJCI.js +0 -2
  137. package/dist/chunk-FGB5FJCI.js.map +0 -1
  138. package/dist/chunk-GLAJPUMA.js +0 -2
  139. package/dist/chunk-GLAJPUMA.js.map +0 -1
  140. package/dist/chunk-H4WAQQVO.js +0 -2
  141. package/dist/chunk-H4WAQQVO.js.map +0 -1
  142. package/dist/chunk-MG6VV5J2.js.map +0 -1
  143. package/dist/chunk-Q3DSK66F.mjs +0 -2
  144. package/dist/chunk-Q3DSK66F.mjs.map +0 -1
  145. package/dist/chunk-RWEP6IUC.mjs +0 -2
  146. package/dist/chunk-RWEP6IUC.mjs.map +0 -1
  147. package/dist/chunk-S4Z4O46O.mjs +0 -2
  148. package/dist/chunk-S4Z4O46O.mjs.map +0 -1
  149. package/dist/chunk-VY6CZVVA.js +0 -4
  150. package/dist/chunk-VY6CZVVA.js.map +0 -1
  151. package/dist/chunk-XZYCQ655.js +0 -2
  152. package/dist/chunk-XZYCQ655.js.map +0 -1
  153. package/dist/chunk-ZIWQ57GY.mjs +0 -2
  154. package/dist/chunk-ZIWQ57GY.mjs.map +0 -1
  155. package/dist/methods-NUMVR53L.js +0 -2
  156. package/dist/methods-NUMVR53L.js.map +0 -1
  157. package/dist/methods-YKZZ6OVT.mjs +0 -2
  158. package/dist/types-DP1vxo5E.d.mts +0 -53
  159. package/dist/types-u8VwmC3j.d.ts +0 -53
  160. /package/dist/{CartProvider-XKK7A3C2.mjs.map → CartProvider-DGRZDTYC.mjs.map} +0 -0
  161. /package/dist/{methods-YKZZ6OVT.mjs.map → methods-LUJSKSQ4.mjs.map} +0 -0
package/AGENTS.md CHANGED
@@ -70,9 +70,17 @@ For Astro, omit `"use client"`, and mount with `client:load` or `client:visible`
70
70
  client before mounting. Use an effect and a ready flag for standalone buttons.
71
71
  Configure Apple Pay and test on an eligible device over HTTPS.
72
72
  - `BitsnapReturns` takes `projectID` and optional `locale`, `appearance`, `texts`.
73
+ - `Product3DViewer` (`bitsnap-react/viewer3d`, ESM only) takes `projectID`,
74
+ `productId`, optional `variantId`; it needs `three`, `@react-three/fiber` and
75
+ `@react-three/drei` installed. Never import it from the main entry.
73
76
  - For the panel, configure `setProjectID` and `setLoginURL("/panel/login")` on
74
77
  the client before mounting `<PanelProvider><Panel /></PanelProvider>`.
75
78
  Mount `<PanelProvider><PanelLogin /></PanelProvider>` at that login URL.
79
+ `PanelLogin` takes optional `locale` (`"en"`; Polish by default) and `texts`.
80
+ - The e-mail-code logins (`PanelLogin`, `BitsnapReturns`, notifications) show
81
+ the shop logo from the shop settings above the form (Bitsnap's logo when the
82
+ shop has none). `showLogo={false}` hides it; `logoUrl` / `logoDarkUrl`
83
+ replace it (notifications: `showLoginLogo`, `projectLogo`).
76
84
 
77
85
  ## Avoid these mistakes
78
86
 
package/README.md CHANGED
@@ -22,6 +22,49 @@ It uses modern React patterns and libraries like:
22
22
  - Protocol Buffers for data serialization
23
23
  - Tailwind CSS for styling
24
24
 
25
+ ## 1.17.0
26
+
27
+ Changes after 1.16.0, in the e-mail + 6-digit-code logins of `PanelLogin`,
28
+ `BitsnapReturns` and the notifications login dialog.
29
+
30
+ - **Shop logo on every login step.** The logo from the shop settings (light
31
+ and dark variant) shows above the form, as does Bitsnap's logo when the shop
32
+ has none. The height is reserved while it loads, so the form does not jump.
33
+ A variant that does not load is replaced by the other one; Bitsnap's logo
34
+ shows only when none loads. The image's alt text is the shop name, else
35
+ "Logo sklepu" / "Shop logo" by locale.
36
+ - **`PanelLogin`: the logo moved** from the page header (alt "Company Logo",
37
+ `theme.logoURL` / `theme.logoDarkURL`) into the login card, above the title.
38
+ The theme logos are still used, per variant: `logoUrl` replaces only the
39
+ light theme logo and `logoDarkUrl` only the dark one.
40
+ The card is top-aligned instead of vertically centred.
41
+ - **New props.** `PanelLogin` and `BitsnapReturns`: `showLogo` (default
42
+ `true`), `logoUrl` (light background), `logoDarkUrl` (dark background).
43
+ Notifications: `showLoginLogo` (default `true`; `projectLogo`,
44
+ `projectLogoDark` and `projectName` are used first). The notifications
45
+ component reads the shop logo when it mounts, so the dialog has it on open.
46
+ - **`BitsnapReturns`:** the logo sits above the step indicator.
47
+ - **New login flow states.** E-mail step and code step, with a code restored
48
+ after a reload (`sessionStorage`, 10 minutes); "I already have a code" asks
49
+ for the e-mail first; a throttled resend is reported as "already sent" (not
50
+ as a new code); countdown from the server's `retryIn`; rate limit, expired
51
+ code, too many attempts, e-mail not found, delivery failed or unavailable and
52
+ network errors each have their own message; one live region announces
53
+ "step 1 of 2" / "step 2 of 2".
54
+ - **Returns texts: new optional keys** `resendSent`, `codeSpamHint`,
55
+ `codeValidity`, `codeIncomplete` and `errorNetwork`. They are optional in
56
+ `ReturnsTexts`, so code that builds a complete texts object still compiles.
57
+ - **Notifications login: new text keys** `loginVerifyButton`,
58
+ `loginResendInText`, `loginResentMessage`, `loginExpiredError`,
59
+ `loginTooManyAttemptsError`, `loginRateLimitedError`, `loginNetworkError`,
60
+ `loginSpamHint`, `loginValidityHint`, `loginVerifyingButton`,
61
+ `loginStepEmailHint`, `loginStepCodeHint`, `loginAlreadySentMessage`,
62
+ `loginEmailNotFoundError`, `loginDeliveryUnavailableError`,
63
+ `loginResendNewCodeButton`, `loginNoCodeButton` and
64
+ `loginCodeIncompleteError`; all optional.
65
+ - The server must provide `GetLoginBranding` (public API) for the shop logo;
66
+ without it the logos given as props, or Bitsnap's, are used.
67
+
25
68
  ## Capabilities
26
69
 
27
70
  ### Cart Management:
@@ -645,7 +688,9 @@ import { ProductList, useProducts } from "bitsnap-react/catalog";
645
688
  Prices are in minor units (grosze, cents), as everywhere else in the library.
646
689
  Cards show "od {price}" when the variants differ in price, a struck-through
647
690
  regular price for discounts and an "unavailable" state for products out of
648
- stock.
691
+ stock. Products with a ready 3D model (`has3d`, sent by Bitsnap servers with
692
+ 3D support) get a small "3D" badge in the top right corner (slot
693
+ `model3dBadge`, texts `model3d` / `model3dLabel`).
649
694
 
650
695
  ### Add to cart
651
696
 
@@ -698,7 +743,7 @@ Tokens: `fontFamily`, `fontSize`, `background`, `surface`, `foreground`,
698
743
  `badgeForeground`, `radius`. Slots (`PRODUCT_LIST_SLOT_NAMES`, also exposed as
699
744
  `data-bitsnap-products-slot`): `root`, `grid`, `card`, `cardLink`, `image`,
700
745
  `imagePlaceholder`, `body`, `name`, `description`, `price`, `comparePrice`,
701
- `badge`, `actions`, `addToCartButton`, `selectButton`, `message`, `pagination`,
746
+ `badge`, `model3dBadge`, `actions`, `addToCartButton`, `selectButton`, `message`, `pagination`,
702
747
  `paginationButton`, `pageIndicator`, `emptyState`, `error`, `retryButton`,
703
748
  `skeleton`. All rules are scoped to `[data-bitsnap-products]`.
704
749
 
@@ -726,6 +771,73 @@ add it to the project's site, success or cancel URLs, or to the allowed domains
726
771
  in the Bitsnap dashboard. Otherwise the browser blocks the response. On a Bitsnap
727
772
  custom domain nothing has to be configured.
728
773
 
774
+ ## 3D product viewer (`bitsnap-react/viewer3d`)
775
+
776
+ An interactive 3D model of a product: slow auto-rotation (about one turn per
777
+ 34 s, paused while the buyer drags and when off screen or in a background tab,
778
+ off with `prefers-reduced-motion`), models over 10 MB load only after a click
779
+ ("Pokaż model 3D (24 MB)"), full screen with a buy bar, a gesture hint shown once
780
+ per browser, phones keep scrolling the page until the buyer taps the model.
781
+
782
+ The subpath is **ESM only** and needs three optional peer dependencies:
783
+
784
+ ```bash
785
+ npm install three @react-three/fiber @react-three/drei
786
+ ```
787
+
788
+ ```tsx
789
+ import { Product3DViewer } from "bitsnap-react/viewer3d";
790
+
791
+ <Product3DViewer
792
+ projectID="your-project-id"
793
+ productId="prodv2_..."
794
+ variantId={selectedVariantId} // optional; switching swaps the model in place
795
+ />;
796
+ ```
797
+
798
+ The viewer reads `GET {host}/api/integrations/{projectID}/public-commerce/products?ids={productId}&include=model3d`
799
+ (the endpoint the cart already uses) and renders `fallback` (nothing by
800
+ default) while loading, when the product or the chosen variant has no ready
801
+ model, and on servers without 3D support. three and React Three Fiber load
802
+ with `import()` only once the model may be shown, never during the server
803
+ render; the main `bitsnap-react` and `bitsnap-react/catalog` entries never
804
+ reach them, so pages without the viewer do not grow.
805
+
806
+ | Prop | Description |
807
+ | --------------- | ------------------------------------------------------------------------------------------------------------ |
808
+ | `productId` | Bitsnap product ID (or a variant ID). |
809
+ | `variantId` | The variant the page shows. A variant without its own model shows the product's. |
810
+ | `projectID` | Defaults to `setProjectID`. |
811
+ | `host` | Backend origin. Defaults to `https://bitsnap.pl` / `setCustomHost`. On a custom domain pass the domain. |
812
+ | `locale` | `pl-PL` (default) or `en*`. |
813
+ | `decoderPath` | Draco decoder folder. Default: three's decoder on jsDelivr for the installed `three` revision (see below). |
814
+ | `renderBuyBar` | `({ product, variant, close }) => ReactNode`: the bar of the full screen view. Default: name, price, "Dodaj do koszyka" (Bitsnap cart, loaded on click). |
815
+ | `productUrl` | Product page stored with the cart item by the default bar. Defaults to the current page. |
816
+ | `fallback` | Rendered when there is nothing to show. |
817
+ | `reportHref` | "Zgłoś ten model" link (DSA notice form); the viewer adds `url` with the page address. |
818
+ | `startSignal` | Bump the number to start loading now, a big model included (for your own "View in 3D" button). |
819
+ | `hideNotes` | Hide the disclaimer, report link and model facts under the viewer. |
820
+ | `texts` | Overrides of any string (`Viewer3DCopy`). |
821
+ | `appearance` | `{ background, foreground, muted, border, accent, radius }`, or set `--bitsnap-viewer3d-*` in your CSS. |
822
+
823
+ `useProduct3DModel(productId, variantId?, { projectID, host, locale })` returns
824
+ `{ status: "loading" | "ready" | "none" | "error", model, product, variant, retry }`
825
+ for your own layout, and `Model3DViewer` renders a model you already have
826
+ (`model`, `productName`, plus the props above).
827
+
828
+ **Draco decoder.** Compressed models need the Draco decoder. By default the
829
+ viewer loads three's own decoder files from
830
+ `https://cdn.jsdelivr.net/npm/three@0.<REVISION>.0/examples/jsm/libs/draco/gltf/`,
831
+ matching the `three` you installed (jsDelivr allows any origin). To self-host,
832
+ copy `node_modules/three/examples/jsm/libs/draco/gltf/` to your site and pass
833
+ `decoderPath="/draco/"`. A Content-Security-Policy needs `connect-src` for the
834
+ file host (`files.bitsnap.pl`) and the decoder host, `worker-src blob:` and
835
+ `'wasm-unsafe-eval'` (Meshopt).
836
+
837
+ **CORS.** Model files are served from `files.bitsnap.pl` with
838
+ `Access-Control-Allow-Origin: *` for model paths; the product request follows
839
+ the same rules as the product list (see below).
840
+
729
841
  # Examples
730
842
 
731
843
  1. `BitsnapCheckout` Component:
@@ -1120,6 +1232,19 @@ function LoginPage() {
1120
1232
  }
1121
1233
  ```
1122
1234
 
1235
+ Customers sign in with an e-mail and a 6-digit code. The code field fills in
1236
+ from the keyboard suggestion or a paste (spaces and dashes are ignored) and
1237
+ submits on the sixth digit. A pending code survives a reload or an app switch
1238
+ (`sessionStorage`, up to 10 minutes). Texts are Polish by default; pass
1239
+ `locale="en"` for English, and `texts` to override any string:
1240
+
1241
+ ```tsx
1242
+ <PanelLogin locale="en" texts={{ title: "Welcome back", wrongCode: "That code is wrong." }} />
1243
+ ```
1244
+
1245
+ The returns widget and the notifications login use the same code field and
1246
+ messages.
1247
+
1123
1248
  ## Complete Example
1124
1249
 
1125
1250
  ### App Entry Point
@@ -1208,6 +1333,11 @@ The panel provides the following screens:
1208
1333
  - Shows product cards with image, name, and description
1209
1334
  - Click to view product details
1210
1335
  - Supports audio, file, and ticket products
1336
+ - "Pliki 3D": purchased 3D model files (download on click with a short-lived
1337
+ link, licence, "the seller removed this file"); hidden on servers without 3D
1338
+ delivery
1339
+ - An empty list shows an empty state; only an expired session signs the buyer
1340
+ out
1211
1341
 
1212
1342
  ### Product Details
1213
1343
 
@@ -0,0 +1,2 @@
1
+ import{I as c,J as d,K as e,L as f,M as g,N as h,O as i,P as j,Q as k,R as l}from"./chunk-2NL5FCXC.mjs";import"./chunk-HKNWW37T.mjs";import"./chunk-KOVKUJAW.mjs";import"./chunk-A6VCP3A4.mjs";import"./chunk-CUPG4B5W.mjs";import"./chunk-L7Q7OKM5.mjs";import"./chunk-5R6AGO3Z.mjs";import"./chunk-X3LVYIJR.mjs";import"./chunk-2UDAB2C5.mjs";import{e as a,f as b}from"./chunk-VNYHJT6V.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{c as MARKETING_AGREEMENT_ID,d as bitsnapProjectID,k as default,h as getCartLines,l as getCheckoutMethods,g as getCheckoutSelection,b as getLocale,f as getProjectID,i as reconcileOrderOfLastPayment,a as setLocale,e as setProjectID,j as useCartProvider};
2
+ //# sourceMappingURL=CartProvider-DGRZDTYC.mjs.map
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunk555KFWGYjs = require('./chunk-555KFWGY.js');require('./chunk-5FZAROP6.js');require('./chunk-KOYBZORL.js');require('./chunk-4BL7TLK3.js');require('./chunk-SYN5KU3O.js');require('./chunk-ESF5ZVVF.js');require('./chunk-C3BBJOVV.js');var _chunk7RXW6XFYjs = require('./chunk-7RXW6XFY.js');require('./chunk-W3RGQYPD.js');require('./chunk-JSDGZ4NY.js');exports.MARKETING_AGREEMENT_ID = _chunk555KFWGYjs.I; exports.bitsnapProjectID = _chunk555KFWGYjs.J; exports.default = _chunk555KFWGYjs.Q; exports.getCartLines = _chunk555KFWGYjs.N; exports.getCheckoutMethods = _chunk555KFWGYjs.R; exports.getCheckoutSelection = _chunk555KFWGYjs.M; exports.getLocale = _chunk7RXW6XFYjs.f; exports.getProjectID = _chunk555KFWGYjs.L; exports.reconcileOrderOfLastPayment = _chunk555KFWGYjs.O; exports.setLocale = _chunk7RXW6XFYjs.e; exports.setProjectID = _chunk555KFWGYjs.K; exports.useCartProvider = _chunk555KFWGYjs.P;
2
+ //# sourceMappingURL=CartProvider-YVOHSMPE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/CartProvider-YVOHSMPE.js"],"names":[],"mappings":"AAAA,+HAAsG,+BAA4B,+BAA4B,+BAA4B,+BAA4B,+BAA4B,+BAA4B,sDAA+C,+BAA4B,+BAA4B,siBAAmQ","file":"/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/CartProvider-YVOHSMPE.js"}
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _chunk6Q3VGPSFjs = require('./chunk-6Q3VGPSF.js');var _chunk7RXW6XFYjs = require('./chunk-7RXW6XFY.js');require('./chunk-JSDGZ4NY.js');var _connect = require('@connectrpc/connect');var _connectquery = require('@connectrpc/connect-query');var _react = require('react');var _jsxruntime = require('react/jsx-runtime');var k={pl:{title:"Pliki 3D",subtitle:"Pobierasz zawsze aktualn\u0105 wersj\u0119 pliku.",download:"Pobierz",preparing:"Przygotowuj\u0119\u2026",licence:"Licencja",purchased:"Kupiono",accessUntil:"Dost\u0119p do",removed:"Sprzedawca usun\u0105\u0142 ten plik.",contact:"Napisz do sprzedawcy",expired:"Dost\u0119p do pliku wygas\u0142",failure:{1:"Sesja wygas\u0142a. Zaloguj si\u0119 ponownie.",2:"Nie znale\u017Ali\u015Bmy tego pliku.",3:"Zam\xF3wienie zosta\u0142o zwr\xF3cone lub anulowane.",4:"Dost\u0119p do pliku wygas\u0142.",5:"Sprzedawca usun\u0105\u0142 ten plik.",6:"Zam\xF3wienie nie jest op\u0142acone."},error:"Nie uda\u0142o si\u0119 pobra\u0107 pliku. Spr\xF3buj ponownie."},en:{title:"3D files",subtitle:"You always download the current version of the file.",download:"Download",preparing:"Preparing\u2026",licence:"Licence",purchased:"Purchased",accessUntil:"Access until",removed:"The seller removed this file.",contact:"Contact the seller",expired:"Access to the file has ended",failure:{1:"Your session has expired. Sign in again.",2:"We could not find this file.",3:"The order was refunded or canceled.",4:"Access to the file has ended.",5:"The seller removed this file.",6:"The order is not paid."},error:"The file could not be downloaded. Try again."}};function A(i,o){let c=Number(i);if(!Number.isFinite(c)||c<=0)return"";let l=c/(1024*1024),t=new Intl.NumberFormat(o?"en-GB":"pl-PL",{maximumFractionDigits:1});return l>=1?`${t.format(l)} MB`:`${t.format(c/1024)} KB`}function m(i,o){return i==null||i<=0n?"":new Intl.DateTimeFormat(o?"en-GB":"pl-PL",{dateStyle:"medium"}).format(new Date(Number(i)*1e3))}function U({className:i}){return _jsxruntime.jsxs.call(void 0, "svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:1.5,className:i,"aria-hidden":!0,children:[_jsxruntime.jsx.call(void 0, "path",{d:"M12 2.5 3.5 7v10L12 21.5 20.5 17V7L12 2.5Z",strokeLinejoin:"round"}),_jsxruntime.jsx.call(void 0, "path",{d:"M3.5 7 12 11.5 20.5 7M12 11.5v10",strokeLinejoin:"round"})]})}function M({accessToken:i,models:o,buttonStyle:c}){let l=_chunk7RXW6XFYjs.f.call(void 0, ).toLowerCase().startsWith("en"),t=l?k.en:k.pl,f=_connectquery.useTransport.call(void 0, ),g=_react.useMemo.call(void 0, ()=>_connect.createClient.call(void 0, _chunk6Q3VGPSFjs.b,f),[f]),[x,b]=_react.useState.call(void 0, null),[h,p]=_react.useState.call(void 0, {}),D=_react.useCallback.call(void 0, async(e,d)=>{let s=`${e.orderId}:${e.lineId}:${d}`;b(s),p(a=>({...a,[s]:""}));try{let a=await g.getModelFileDownloadUrl({accessToken:i,orderId:e.orderId,lineId:e.lineId,fileIndex:d});if(a.result.case==="success")window.location.assign(a.result.value.url);else{let u=a.result.case==="failure"?a.result.value:void 0;p(E=>({...E,[s]:_nullishCoalesce((u!=null?t.failure[u]:void 0), () => (t.error))}))}}catch (e2){p(a=>({...a,[s]:t.error}))}finally{b(null)}},[g,i,t]);return o.length===0?null:_jsxruntime.jsx.call(void 0, "section",{"aria-labelledby":"bitsnap-panel-model-files",className:"mt-10 w-full max-w-5xl","data-testid":"model-files",children:_jsxruntime.jsxs.call(void 0, "div",{className:"rounded-2xl border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900",children:[_jsxruntime.jsxs.call(void 0, "header",{className:"border-b border-neutral-200 px-5 py-4 dark:border-neutral-800",children:[_jsxruntime.jsx.call(void 0, "h2",{id:"bitsnap-panel-model-files",className:"text-lg font-semibold text-neutral-900 dark:text-neutral-50",children:t.title}),_jsxruntime.jsx.call(void 0, "p",{className:"mt-0.5 text-sm text-neutral-500 dark:text-neutral-400",children:t.subtitle})]}),_jsxruntime.jsx.call(void 0, "ul",{className:"divide-y divide-neutral-200 dark:divide-neutral-800",children:o.map(e=>{let d=e.variantName?`${e.productName} \u2013 ${e.variantName}`:e.productName;return _jsxruntime.jsxs.call(void 0, "li",{className:"flex flex-col gap-3 px-5 py-4 sm:flex-row sm:items-center","data-state":_chunk6Q3VGPSFjs.a[e.state],children:[_jsxruntime.jsxs.call(void 0, "div",{className:"flex min-w-0 flex-1 items-center gap-4",children:[_jsxruntime.jsx.call(void 0, "div",{className:"flex size-14 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-neutral-100 text-neutral-400 dark:bg-neutral-800",children:e.thumbnailUrl?_jsxruntime.jsx.call(void 0, "img",{src:e.thumbnailUrl,alt:"",className:"size-full object-contain",loading:"lazy"}):_jsxruntime.jsx.call(void 0, U,{className:"size-7"})}),_jsxruntime.jsxs.call(void 0, "div",{className:"min-w-0",children:[_jsxruntime.jsx.call(void 0, "p",{className:"truncate font-medium text-neutral-900 dark:text-neutral-50",children:d}),_jsxruntime.jsx.call(void 0, "p",{className:"mt-0.5 text-sm text-neutral-500 dark:text-neutral-400",children:[e.files[0]?`${e.files[0].format.toUpperCase()} \xB7 ${A(e.files[0].fileSize,l)}`:"",`${t.purchased} ${m(e.purchasedAt,l)}`,e.accessExpiresAt!=null&&e.state===1?`${t.accessUntil} ${m(e.accessExpiresAt,l)}`:""].filter(Boolean).join(" \xB7 ")}),e.licence&&_jsxruntime.jsxs.call(void 0, "p",{className:"mt-0.5 text-sm text-neutral-600 dark:text-neutral-300",children:[t.licence,":"," ",e.licence.url?_jsxruntime.jsx.call(void 0, "a",{href:e.licence.url,target:"_blank",rel:"noopener noreferrer",className:"underline underline-offset-2 hover:text-neutral-900 dark:hover:text-neutral-50",children:e.licence.name}):e.licence.name]}),e.state===2&&_jsxruntime.jsxs.call(void 0, "p",{className:"mt-1 text-sm text-neutral-700 dark:text-neutral-200","data-model-removed":"",children:[t.removed,_optionalChain([e, 'access', _ => _.seller, 'optionalAccess', _2 => _2.email])&&_jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment,{children:[" ",t.contact,e.seller.name?` (${e.seller.name})`:"",":"," ",_jsxruntime.jsx.call(void 0, "a",{className:"font-medium underline underline-offset-2",href:`mailto:${e.seller.email}`,children:e.seller.email})]})]}),e.state===3&&_jsxruntime.jsxs.call(void 0, "p",{className:"mt-1 text-sm text-neutral-700 dark:text-neutral-200",children:[t.expired,e.accessExpiresAt!=null?` ${m(e.accessExpiresAt,l)}`:"","."]})]})]}),e.state===1&&e.files.map(s=>{let a=`${e.orderId}:${e.lineId}:${s.index}`,u=x===a;return _jsxruntime.jsxs.call(void 0, "div",{className:"flex flex-col items-start gap-1 sm:items-end",children:[_jsxruntime.jsx.call(void 0, "button",{type:"button",onClick:()=>void D(e,s.index),disabled:x!=null,"aria-busy":u||void 0,"aria-label":`${t.download}: ${s.fileName}`,"data-model-download":"",style:c,className:"inline-flex h-10 items-center justify-center rounded-lg bg-neutral-900 px-4 text-sm font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-60 dark:bg-neutral-50 dark:text-neutral-900",children:u?t.preparing:t.download}),h[a]&&_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-red-700 dark:text-red-400",role:"alert",children:h[a]})]},a)})]},`${e.orderId}:${e.lineId}`)})})]})})}exports.default = M;
2
+ //# sourceMappingURL=PanelModelFiles-NYCOFPLY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/PanelModelFiles-NYCOFPLY.js","../src/panel/components/PanelModelFiles.tsx"],"names":["COPY","formatSize","bytes","en","value","mb","fmt"],"mappings":"AAAA,uuBAA8C,sDAAwC,+BAA4B,8CCArF,yDACA,8BACkB,+CAsG3C,IAhEEA,CAAAA,CAA+B,CACnC,EAAA,CAAI,CACF,KAAA,CAAO,UAAA,CACP,QAAA,CAAU,mDAAA,CACV,QAAA,CAAU,SAAA,CACV,SAAA,CAAW,yBAAA,CACX,OAAA,CAAS,UAAA,CACT,SAAA,CAAW,SAAA,CACX,WAAA,CAAa,gBAAA,CACb,OAAA,CAAS,uCAAA,CACT,OAAA,CAAS,sBAAA,CACT,OAAA,CAAS,kCAAA,CACT,OAAA,CAAS,CACN,CAAA,CAAoC,gDAAA,CACpC,CAAA,CAAiC,uCAAA,CACjC,CAAA,CAAgC,uDAAA,CAChC,CAAA,CAA+B,mCAAA,CAC/B,CAAA,CAA+B,uCAAA,CAC/B,CAAA,CAA8B,uCACjC,CAAA,CACA,KAAA,CAAO,iEACT,CAAA,CACA,EAAA,CAAI,CACF,KAAA,CAAO,UAAA,CACP,QAAA,CAAU,sDAAA,CACV,QAAA,CAAU,UAAA,CACV,SAAA,CAAW,iBAAA,CACX,OAAA,CAAS,SAAA,CACT,SAAA,CAAW,WAAA,CACX,WAAA,CAAa,cAAA,CACb,OAAA,CAAS,+BAAA,CACT,OAAA,CAAS,oBAAA,CACT,OAAA,CAAS,8BAAA,CACT,OAAA,CAAS,CACN,CAAA,CAAoC,0CAAA,CACpC,CAAA,CAAiC,8BAAA,CACjC,CAAA,CAAgC,qCAAA,CAChC,CAAA,CAA+B,+BAAA,CAC/B,CAAA,CAA+B,+BAAA,CAC/B,CAAA,CAA8B,wBACjC,CAAA,CACA,KAAA,CAAO,8CACT,CACF,CAAA,CAEA,SAASC,CAAAA,CAAWC,CAAAA,CAAeC,CAAAA,CAAqB,CACtD,IAAMC,CAAAA,CAAQ,MAAA,CAAOF,CAAK,CAAA,CAC1B,EAAA,CAAI,CAAC,MAAA,CAAO,QAAA,CAASE,CAAK,CAAA,EAAKA,CAAAA,EAAS,CAAA,CAAG,MAAO,EAAA,CAClD,IAAMC,CAAAA,CAAKD,CAAAA,CAAAA,CAAS,IAAA,CAAO,IAAA,CAAA,CACrBE,CAAAA,CAAM,IAAI,IAAA,CAAK,YAAA,CAAaH,CAAAA,CAAK,OAAA,CAAU,OAAA,CAAS,CACxD,qBAAA,CAAuB,CACzB,CAAC,CAAA,CACD,OAAOE,CAAAA,EAAM,CAAA,CAAI,CAAA,EAAA","file":"/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/PanelModelFiles-NYCOFPLY.js","sourcesContent":[null,"import { createClient } from \"@connectrpc/connect\";\nimport { useTransport } from \"@connectrpc/connect-query\";\nimport { useCallback, useMemo, useState } from \"react\";\n\nimport { getLocale } from \"../../components/checkout/locale\";\nimport {\n ModelDeliveryFailure,\n ModelDeliveryService,\n ModelFileState,\n type PurchasedModel,\n} from \"../../gen/proto/public/v1/model_delivery_pb\";\n\n/**\n * \"3D files\" of the customer panel: the 3D files of the buyer's paid orders in\n * this shop (`public.v1.ModelDeliveryService`), with a download button, the\n * licence and the \"seller removed the file\" state. The download URL is signed\n * on click and expires quickly, so it is never rendered into the page.\n *\n * Older Bitsnap servers have no such service: any transport error (404,\n * unimplemented, network) hides the section, so the panel looks as before.\n *\n * Loaded on demand (with `model-files-client.ts`) only when the buyer has 3D\n * files, so the main bundle carries just `model-files-load.ts`.\n */\n\ntype Copy = {\n title: string;\n subtitle: string;\n download: string;\n preparing: string;\n licence: string;\n purchased: string;\n accessUntil: string;\n removed: string;\n contact: string;\n expired: string;\n failure: Partial<Record<ModelDeliveryFailure, string>>;\n error: string;\n};\n\nconst COPY: { pl: Copy; en: Copy } = {\n pl: {\n title: \"Pliki 3D\",\n subtitle: \"Pobierasz zawsze aktualną wersję pliku.\",\n download: \"Pobierz\",\n preparing: \"Przygotowuję…\",\n licence: \"Licencja\",\n purchased: \"Kupiono\",\n accessUntil: \"Dostęp do\",\n removed: \"Sprzedawca usunął ten plik.\",\n contact: \"Napisz do sprzedawcy\",\n expired: \"Dostęp do pliku wygasł\",\n failure: {\n [ModelDeliveryFailure.UNAUTHORIZED]: \"Sesja wygasła. Zaloguj się ponownie.\",\n [ModelDeliveryFailure.NOT_FOUND]: \"Nie znaleźliśmy tego pliku.\",\n [ModelDeliveryFailure.REFUNDED]: \"Zamówienie zostało zwrócone lub anulowane.\",\n [ModelDeliveryFailure.EXPIRED]: \"Dostęp do pliku wygasł.\",\n [ModelDeliveryFailure.REMOVED]: \"Sprzedawca usunął ten plik.\",\n [ModelDeliveryFailure.UNPAID]: \"Zamówienie nie jest opłacone.\",\n },\n error: \"Nie udało się pobrać pliku. Spróbuj ponownie.\",\n },\n en: {\n title: \"3D files\",\n subtitle: \"You always download the current version of the file.\",\n download: \"Download\",\n preparing: \"Preparing…\",\n licence: \"Licence\",\n purchased: \"Purchased\",\n accessUntil: \"Access until\",\n removed: \"The seller removed this file.\",\n contact: \"Contact the seller\",\n expired: \"Access to the file has ended\",\n failure: {\n [ModelDeliveryFailure.UNAUTHORIZED]: \"Your session has expired. Sign in again.\",\n [ModelDeliveryFailure.NOT_FOUND]: \"We could not find this file.\",\n [ModelDeliveryFailure.REFUNDED]: \"The order was refunded or canceled.\",\n [ModelDeliveryFailure.EXPIRED]: \"Access to the file has ended.\",\n [ModelDeliveryFailure.REMOVED]: \"The seller removed this file.\",\n [ModelDeliveryFailure.UNPAID]: \"The order is not paid.\",\n },\n error: \"The file could not be downloaded. Try again.\",\n },\n};\n\nfunction formatSize(bytes: bigint, en: boolean): string {\n const value = Number(bytes);\n if (!Number.isFinite(value) || value <= 0) return \"\";\n const mb = value / (1024 * 1024);\n const fmt = new Intl.NumberFormat(en ? \"en-GB\" : \"pl-PL\", {\n maximumFractionDigits: 1,\n });\n return mb >= 1 ? `${fmt.format(mb)} MB` : `${fmt.format(value / 1024)} KB`;\n}\n\nfunction formatDate(seconds: bigint | undefined, en: boolean): string {\n if (seconds == null || seconds <= 0n) return \"\";\n return new Intl.DateTimeFormat(en ? \"en-GB\" : \"pl-PL\", {\n dateStyle: \"medium\",\n }).format(new Date(Number(seconds) * 1000));\n}\n\nfunction CubeIcon({ className }: { className?: string }) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.5}\n className={className}\n aria-hidden\n >\n <path d=\"M12 2.5 3.5 7v10L12 21.5 20.5 17V7L12 2.5Z\" strokeLinejoin=\"round\" />\n <path d=\"M3.5 7 12 11.5 20.5 7M12 11.5v10\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\nexport default function PanelModelFiles({\n accessToken,\n models,\n buttonStyle,\n}: {\n accessToken: string;\n models: PurchasedModel[];\n buttonStyle?: React.CSSProperties;\n}) {\n const en = getLocale().toLowerCase().startsWith(\"en\");\n const t = en ? COPY.en : COPY.pl;\n const transport = useTransport();\n const client = useMemo(\n () => createClient(ModelDeliveryService, transport),\n [transport],\n );\n const [pending, setPending] = useState<string | null>(null);\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n const download = useCallback(\n async (model: PurchasedModel, index: number) => {\n const key = `${model.orderId}:${model.lineId}:${index}`;\n setPending(key);\n setErrors((current) => ({ ...current, [key]: \"\" }));\n try {\n const response = await client.getModelFileDownloadUrl({\n accessToken,\n orderId: model.orderId,\n lineId: model.lineId,\n fileIndex: index,\n });\n if (response.result.case === \"success\") {\n // An attachment: the browser downloads it and stays on the panel.\n window.location.assign(response.result.value.url);\n } else {\n const failure =\n response.result.case === \"failure\" ? response.result.value : undefined;\n setErrors((current) => ({\n ...current,\n [key]: (failure != null ? t.failure[failure] : undefined) ?? t.error,\n }));\n }\n } catch {\n setErrors((current) => ({ ...current, [key]: t.error }));\n } finally {\n setPending(null);\n }\n },\n [client, accessToken, t],\n );\n\n if (models.length === 0) return null;\n\n return (\n <section\n aria-labelledby=\"bitsnap-panel-model-files\"\n className=\"mt-10 w-full max-w-5xl\"\n data-testid=\"model-files\"\n >\n <div className=\"rounded-2xl border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900\">\n <header className=\"border-b border-neutral-200 px-5 py-4 dark:border-neutral-800\">\n <h2\n id=\"bitsnap-panel-model-files\"\n className=\"text-lg font-semibold text-neutral-900 dark:text-neutral-50\"\n >\n {t.title}\n </h2>\n <p className=\"mt-0.5 text-sm text-neutral-500 dark:text-neutral-400\">\n {t.subtitle}\n </p>\n </header>\n <ul className=\"divide-y divide-neutral-200 dark:divide-neutral-800\">\n {models.map((model) => {\n const title = model.variantName\n ? `${model.productName} – ${model.variantName}`\n : model.productName;\n return (\n <li\n key={`${model.orderId}:${model.lineId}`}\n className=\"flex flex-col gap-3 px-5 py-4 sm:flex-row sm:items-center\"\n data-state={ModelFileState[model.state]}\n >\n <div className=\"flex min-w-0 flex-1 items-center gap-4\">\n <div className=\"flex size-14 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-neutral-100 text-neutral-400 dark:bg-neutral-800\">\n {model.thumbnailUrl ? (\n <img\n src={model.thumbnailUrl}\n alt=\"\"\n className=\"size-full object-contain\"\n loading=\"lazy\"\n />\n ) : (\n <CubeIcon className=\"size-7\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-neutral-900 dark:text-neutral-50\">\n {title}\n </p>\n <p className=\"mt-0.5 text-sm text-neutral-500 dark:text-neutral-400\">\n {[\n model.files[0]\n ? `${model.files[0].format.toUpperCase()} · ${formatSize(model.files[0].fileSize, en)}`\n : \"\",\n `${t.purchased} ${formatDate(model.purchasedAt, en)}`,\n model.accessExpiresAt != null &&\n model.state === ModelFileState.AVAILABLE\n ? `${t.accessUntil} ${formatDate(model.accessExpiresAt, en)}`\n : \"\",\n ]\n .filter(Boolean)\n .join(\" · \")}\n </p>\n {model.licence && (\n <p className=\"mt-0.5 text-sm text-neutral-600 dark:text-neutral-300\">\n {t.licence}:{\" \"}\n {model.licence.url ? (\n <a\n href={model.licence.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"underline underline-offset-2 hover:text-neutral-900 dark:hover:text-neutral-50\"\n >\n {model.licence.name}\n </a>\n ) : (\n model.licence.name\n )}\n </p>\n )}\n {model.state === ModelFileState.REMOVED && (\n <p\n className=\"mt-1 text-sm text-neutral-700 dark:text-neutral-200\"\n data-model-removed=\"\"\n >\n {t.removed}\n {model.seller?.email && (\n <>\n {\" \"}\n {t.contact}\n {model.seller.name ? ` (${model.seller.name})` : \"\"}:{\" \"}\n <a\n className=\"font-medium underline underline-offset-2\"\n href={`mailto:${model.seller.email}`}\n >\n {model.seller.email}\n </a>\n </>\n )}\n </p>\n )}\n {model.state === ModelFileState.EXPIRED && (\n <p className=\"mt-1 text-sm text-neutral-700 dark:text-neutral-200\">\n {t.expired}\n {model.accessExpiresAt != null\n ? ` ${formatDate(model.accessExpiresAt, en)}`\n : \"\"}\n .\n </p>\n )}\n </div>\n </div>\n {model.state === ModelFileState.AVAILABLE &&\n model.files.map((file) => {\n const key = `${model.orderId}:${model.lineId}:${file.index}`;\n const busy = pending === key;\n return (\n <div\n key={key}\n className=\"flex flex-col items-start gap-1 sm:items-end\"\n >\n <button\n type=\"button\"\n onClick={() => void download(model, file.index)}\n disabled={pending != null}\n aria-busy={busy || undefined}\n aria-label={`${t.download}: ${file.fileName}`}\n data-model-download=\"\"\n style={buttonStyle}\n className=\"inline-flex h-10 items-center justify-center rounded-lg bg-neutral-900 px-4 text-sm font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-60 dark:bg-neutral-50 dark:text-neutral-900\"\n >\n {busy ? t.preparing : t.download}\n </button>\n {errors[key] && (\n <p\n className=\"text-sm text-red-700 dark:text-red-400\"\n role=\"alert\"\n >\n {errors[key]}\n </p>\n )}\n </div>\n );\n })}\n </li>\n );\n })}\n </ul>\n </div>\n </section>\n );\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import{a as w,b as v}from"./chunk-6YIRRDCL.mjs";import{f as N}from"./chunk-VNYHJT6V.mjs";import"./chunk-U6FLIRIV.mjs";import{createClient as I}from"@connectrpc/connect";import{useTransport as $}from"@connectrpc/connect-query";import{useCallback as P,useMemo as z,useState as y}from"react";import{Fragment as L,jsx as r,jsxs as n}from"react/jsx-runtime";var k={pl:{title:"Pliki 3D",subtitle:"Pobierasz zawsze aktualn\u0105 wersj\u0119 pliku.",download:"Pobierz",preparing:"Przygotowuj\u0119\u2026",licence:"Licencja",purchased:"Kupiono",accessUntil:"Dost\u0119p do",removed:"Sprzedawca usun\u0105\u0142 ten plik.",contact:"Napisz do sprzedawcy",expired:"Dost\u0119p do pliku wygas\u0142",failure:{1:"Sesja wygas\u0142a. Zaloguj si\u0119 ponownie.",2:"Nie znale\u017Ali\u015Bmy tego pliku.",3:"Zam\xF3wienie zosta\u0142o zwr\xF3cone lub anulowane.",4:"Dost\u0119p do pliku wygas\u0142.",5:"Sprzedawca usun\u0105\u0142 ten plik.",6:"Zam\xF3wienie nie jest op\u0142acone."},error:"Nie uda\u0142o si\u0119 pobra\u0107 pliku. Spr\xF3buj ponownie."},en:{title:"3D files",subtitle:"You always download the current version of the file.",download:"Download",preparing:"Preparing\u2026",licence:"Licence",purchased:"Purchased",accessUntil:"Access until",removed:"The seller removed this file.",contact:"Contact the seller",expired:"Access to the file has ended",failure:{1:"Your session has expired. Sign in again.",2:"We could not find this file.",3:"The order was refunded or canceled.",4:"Access to the file has ended.",5:"The seller removed this file.",6:"The order is not paid."},error:"The file could not be downloaded. Try again."}};function A(i,o){let c=Number(i);if(!Number.isFinite(c)||c<=0)return"";let l=c/(1024*1024),t=new Intl.NumberFormat(o?"en-GB":"pl-PL",{maximumFractionDigits:1});return l>=1?`${t.format(l)} MB`:`${t.format(c/1024)} KB`}function m(i,o){return i==null||i<=0n?"":new Intl.DateTimeFormat(o?"en-GB":"pl-PL",{dateStyle:"medium"}).format(new Date(Number(i)*1e3))}function U({className:i}){return n("svg",{viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:1.5,className:i,"aria-hidden":!0,children:[r("path",{d:"M12 2.5 3.5 7v10L12 21.5 20.5 17V7L12 2.5Z",strokeLinejoin:"round"}),r("path",{d:"M3.5 7 12 11.5 20.5 7M12 11.5v10",strokeLinejoin:"round"})]})}function M({accessToken:i,models:o,buttonStyle:c}){let l=N().toLowerCase().startsWith("en"),t=l?k.en:k.pl,f=$(),g=z(()=>I(v,f),[f]),[x,b]=y(null),[h,p]=y({}),D=P(async(e,d)=>{let s=`${e.orderId}:${e.lineId}:${d}`;b(s),p(a=>({...a,[s]:""}));try{let a=await g.getModelFileDownloadUrl({accessToken:i,orderId:e.orderId,lineId:e.lineId,fileIndex:d});if(a.result.case==="success")window.location.assign(a.result.value.url);else{let u=a.result.case==="failure"?a.result.value:void 0;p(E=>({...E,[s]:(u!=null?t.failure[u]:void 0)??t.error}))}}catch{p(a=>({...a,[s]:t.error}))}finally{b(null)}},[g,i,t]);return o.length===0?null:r("section",{"aria-labelledby":"bitsnap-panel-model-files",className:"mt-10 w-full max-w-5xl","data-testid":"model-files",children:n("div",{className:"rounded-2xl border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900",children:[n("header",{className:"border-b border-neutral-200 px-5 py-4 dark:border-neutral-800",children:[r("h2",{id:"bitsnap-panel-model-files",className:"text-lg font-semibold text-neutral-900 dark:text-neutral-50",children:t.title}),r("p",{className:"mt-0.5 text-sm text-neutral-500 dark:text-neutral-400",children:t.subtitle})]}),r("ul",{className:"divide-y divide-neutral-200 dark:divide-neutral-800",children:o.map(e=>{let d=e.variantName?`${e.productName} \u2013 ${e.variantName}`:e.productName;return n("li",{className:"flex flex-col gap-3 px-5 py-4 sm:flex-row sm:items-center","data-state":w[e.state],children:[n("div",{className:"flex min-w-0 flex-1 items-center gap-4",children:[r("div",{className:"flex size-14 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-neutral-100 text-neutral-400 dark:bg-neutral-800",children:e.thumbnailUrl?r("img",{src:e.thumbnailUrl,alt:"",className:"size-full object-contain",loading:"lazy"}):r(U,{className:"size-7"})}),n("div",{className:"min-w-0",children:[r("p",{className:"truncate font-medium text-neutral-900 dark:text-neutral-50",children:d}),r("p",{className:"mt-0.5 text-sm text-neutral-500 dark:text-neutral-400",children:[e.files[0]?`${e.files[0].format.toUpperCase()} \xB7 ${A(e.files[0].fileSize,l)}`:"",`${t.purchased} ${m(e.purchasedAt,l)}`,e.accessExpiresAt!=null&&e.state===1?`${t.accessUntil} ${m(e.accessExpiresAt,l)}`:""].filter(Boolean).join(" \xB7 ")}),e.licence&&n("p",{className:"mt-0.5 text-sm text-neutral-600 dark:text-neutral-300",children:[t.licence,":"," ",e.licence.url?r("a",{href:e.licence.url,target:"_blank",rel:"noopener noreferrer",className:"underline underline-offset-2 hover:text-neutral-900 dark:hover:text-neutral-50",children:e.licence.name}):e.licence.name]}),e.state===2&&n("p",{className:"mt-1 text-sm text-neutral-700 dark:text-neutral-200","data-model-removed":"",children:[t.removed,e.seller?.email&&n(L,{children:[" ",t.contact,e.seller.name?` (${e.seller.name})`:"",":"," ",r("a",{className:"font-medium underline underline-offset-2",href:`mailto:${e.seller.email}`,children:e.seller.email})]})]}),e.state===3&&n("p",{className:"mt-1 text-sm text-neutral-700 dark:text-neutral-200",children:[t.expired,e.accessExpiresAt!=null?` ${m(e.accessExpiresAt,l)}`:"","."]})]})]}),e.state===1&&e.files.map(s=>{let a=`${e.orderId}:${e.lineId}:${s.index}`,u=x===a;return n("div",{className:"flex flex-col items-start gap-1 sm:items-end",children:[r("button",{type:"button",onClick:()=>void D(e,s.index),disabled:x!=null,"aria-busy":u||void 0,"aria-label":`${t.download}: ${s.fileName}`,"data-model-download":"",style:c,className:"inline-flex h-10 items-center justify-center rounded-lg bg-neutral-900 px-4 text-sm font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-60 dark:bg-neutral-50 dark:text-neutral-900",children:u?t.preparing:t.download}),h[a]&&r("p",{className:"text-sm text-red-700 dark:text-red-400",role:"alert",children:h[a]})]},a)})]},`${e.orderId}:${e.lineId}`)})})]})})}export{M as default};
2
+ //# sourceMappingURL=PanelModelFiles-RV7OHUUZ.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/panel/components/PanelModelFiles.tsx"],"sourcesContent":["import { createClient } from \"@connectrpc/connect\";\nimport { useTransport } from \"@connectrpc/connect-query\";\nimport { useCallback, useMemo, useState } from \"react\";\n\nimport { getLocale } from \"../../components/checkout/locale\";\nimport {\n ModelDeliveryFailure,\n ModelDeliveryService,\n ModelFileState,\n type PurchasedModel,\n} from \"../../gen/proto/public/v1/model_delivery_pb\";\n\n/**\n * \"3D files\" of the customer panel: the 3D files of the buyer's paid orders in\n * this shop (`public.v1.ModelDeliveryService`), with a download button, the\n * licence and the \"seller removed the file\" state. The download URL is signed\n * on click and expires quickly, so it is never rendered into the page.\n *\n * Older Bitsnap servers have no such service: any transport error (404,\n * unimplemented, network) hides the section, so the panel looks as before.\n *\n * Loaded on demand (with `model-files-client.ts`) only when the buyer has 3D\n * files, so the main bundle carries just `model-files-load.ts`.\n */\n\ntype Copy = {\n title: string;\n subtitle: string;\n download: string;\n preparing: string;\n licence: string;\n purchased: string;\n accessUntil: string;\n removed: string;\n contact: string;\n expired: string;\n failure: Partial<Record<ModelDeliveryFailure, string>>;\n error: string;\n};\n\nconst COPY: { pl: Copy; en: Copy } = {\n pl: {\n title: \"Pliki 3D\",\n subtitle: \"Pobierasz zawsze aktualną wersję pliku.\",\n download: \"Pobierz\",\n preparing: \"Przygotowuję…\",\n licence: \"Licencja\",\n purchased: \"Kupiono\",\n accessUntil: \"Dostęp do\",\n removed: \"Sprzedawca usunął ten plik.\",\n contact: \"Napisz do sprzedawcy\",\n expired: \"Dostęp do pliku wygasł\",\n failure: {\n [ModelDeliveryFailure.UNAUTHORIZED]: \"Sesja wygasła. Zaloguj się ponownie.\",\n [ModelDeliveryFailure.NOT_FOUND]: \"Nie znaleźliśmy tego pliku.\",\n [ModelDeliveryFailure.REFUNDED]: \"Zamówienie zostało zwrócone lub anulowane.\",\n [ModelDeliveryFailure.EXPIRED]: \"Dostęp do pliku wygasł.\",\n [ModelDeliveryFailure.REMOVED]: \"Sprzedawca usunął ten plik.\",\n [ModelDeliveryFailure.UNPAID]: \"Zamówienie nie jest opłacone.\",\n },\n error: \"Nie udało się pobrać pliku. Spróbuj ponownie.\",\n },\n en: {\n title: \"3D files\",\n subtitle: \"You always download the current version of the file.\",\n download: \"Download\",\n preparing: \"Preparing…\",\n licence: \"Licence\",\n purchased: \"Purchased\",\n accessUntil: \"Access until\",\n removed: \"The seller removed this file.\",\n contact: \"Contact the seller\",\n expired: \"Access to the file has ended\",\n failure: {\n [ModelDeliveryFailure.UNAUTHORIZED]: \"Your session has expired. Sign in again.\",\n [ModelDeliveryFailure.NOT_FOUND]: \"We could not find this file.\",\n [ModelDeliveryFailure.REFUNDED]: \"The order was refunded or canceled.\",\n [ModelDeliveryFailure.EXPIRED]: \"Access to the file has ended.\",\n [ModelDeliveryFailure.REMOVED]: \"The seller removed this file.\",\n [ModelDeliveryFailure.UNPAID]: \"The order is not paid.\",\n },\n error: \"The file could not be downloaded. Try again.\",\n },\n};\n\nfunction formatSize(bytes: bigint, en: boolean): string {\n const value = Number(bytes);\n if (!Number.isFinite(value) || value <= 0) return \"\";\n const mb = value / (1024 * 1024);\n const fmt = new Intl.NumberFormat(en ? \"en-GB\" : \"pl-PL\", {\n maximumFractionDigits: 1,\n });\n return mb >= 1 ? `${fmt.format(mb)} MB` : `${fmt.format(value / 1024)} KB`;\n}\n\nfunction formatDate(seconds: bigint | undefined, en: boolean): string {\n if (seconds == null || seconds <= 0n) return \"\";\n return new Intl.DateTimeFormat(en ? \"en-GB\" : \"pl-PL\", {\n dateStyle: \"medium\",\n }).format(new Date(Number(seconds) * 1000));\n}\n\nfunction CubeIcon({ className }: { className?: string }) {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.5}\n className={className}\n aria-hidden\n >\n <path d=\"M12 2.5 3.5 7v10L12 21.5 20.5 17V7L12 2.5Z\" strokeLinejoin=\"round\" />\n <path d=\"M3.5 7 12 11.5 20.5 7M12 11.5v10\" strokeLinejoin=\"round\" />\n </svg>\n );\n}\n\nexport default function PanelModelFiles({\n accessToken,\n models,\n buttonStyle,\n}: {\n accessToken: string;\n models: PurchasedModel[];\n buttonStyle?: React.CSSProperties;\n}) {\n const en = getLocale().toLowerCase().startsWith(\"en\");\n const t = en ? COPY.en : COPY.pl;\n const transport = useTransport();\n const client = useMemo(\n () => createClient(ModelDeliveryService, transport),\n [transport],\n );\n const [pending, setPending] = useState<string | null>(null);\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n const download = useCallback(\n async (model: PurchasedModel, index: number) => {\n const key = `${model.orderId}:${model.lineId}:${index}`;\n setPending(key);\n setErrors((current) => ({ ...current, [key]: \"\" }));\n try {\n const response = await client.getModelFileDownloadUrl({\n accessToken,\n orderId: model.orderId,\n lineId: model.lineId,\n fileIndex: index,\n });\n if (response.result.case === \"success\") {\n // An attachment: the browser downloads it and stays on the panel.\n window.location.assign(response.result.value.url);\n } else {\n const failure =\n response.result.case === \"failure\" ? response.result.value : undefined;\n setErrors((current) => ({\n ...current,\n [key]: (failure != null ? t.failure[failure] : undefined) ?? t.error,\n }));\n }\n } catch {\n setErrors((current) => ({ ...current, [key]: t.error }));\n } finally {\n setPending(null);\n }\n },\n [client, accessToken, t],\n );\n\n if (models.length === 0) return null;\n\n return (\n <section\n aria-labelledby=\"bitsnap-panel-model-files\"\n className=\"mt-10 w-full max-w-5xl\"\n data-testid=\"model-files\"\n >\n <div className=\"rounded-2xl border border-neutral-200 bg-white dark:border-neutral-800 dark:bg-neutral-900\">\n <header className=\"border-b border-neutral-200 px-5 py-4 dark:border-neutral-800\">\n <h2\n id=\"bitsnap-panel-model-files\"\n className=\"text-lg font-semibold text-neutral-900 dark:text-neutral-50\"\n >\n {t.title}\n </h2>\n <p className=\"mt-0.5 text-sm text-neutral-500 dark:text-neutral-400\">\n {t.subtitle}\n </p>\n </header>\n <ul className=\"divide-y divide-neutral-200 dark:divide-neutral-800\">\n {models.map((model) => {\n const title = model.variantName\n ? `${model.productName} – ${model.variantName}`\n : model.productName;\n return (\n <li\n key={`${model.orderId}:${model.lineId}`}\n className=\"flex flex-col gap-3 px-5 py-4 sm:flex-row sm:items-center\"\n data-state={ModelFileState[model.state]}\n >\n <div className=\"flex min-w-0 flex-1 items-center gap-4\">\n <div className=\"flex size-14 shrink-0 items-center justify-center overflow-hidden rounded-xl bg-neutral-100 text-neutral-400 dark:bg-neutral-800\">\n {model.thumbnailUrl ? (\n <img\n src={model.thumbnailUrl}\n alt=\"\"\n className=\"size-full object-contain\"\n loading=\"lazy\"\n />\n ) : (\n <CubeIcon className=\"size-7\" />\n )}\n </div>\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium text-neutral-900 dark:text-neutral-50\">\n {title}\n </p>\n <p className=\"mt-0.5 text-sm text-neutral-500 dark:text-neutral-400\">\n {[\n model.files[0]\n ? `${model.files[0].format.toUpperCase()} · ${formatSize(model.files[0].fileSize, en)}`\n : \"\",\n `${t.purchased} ${formatDate(model.purchasedAt, en)}`,\n model.accessExpiresAt != null &&\n model.state === ModelFileState.AVAILABLE\n ? `${t.accessUntil} ${formatDate(model.accessExpiresAt, en)}`\n : \"\",\n ]\n .filter(Boolean)\n .join(\" · \")}\n </p>\n {model.licence && (\n <p className=\"mt-0.5 text-sm text-neutral-600 dark:text-neutral-300\">\n {t.licence}:{\" \"}\n {model.licence.url ? (\n <a\n href={model.licence.url}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"underline underline-offset-2 hover:text-neutral-900 dark:hover:text-neutral-50\"\n >\n {model.licence.name}\n </a>\n ) : (\n model.licence.name\n )}\n </p>\n )}\n {model.state === ModelFileState.REMOVED && (\n <p\n className=\"mt-1 text-sm text-neutral-700 dark:text-neutral-200\"\n data-model-removed=\"\"\n >\n {t.removed}\n {model.seller?.email && (\n <>\n {\" \"}\n {t.contact}\n {model.seller.name ? ` (${model.seller.name})` : \"\"}:{\" \"}\n <a\n className=\"font-medium underline underline-offset-2\"\n href={`mailto:${model.seller.email}`}\n >\n {model.seller.email}\n </a>\n </>\n )}\n </p>\n )}\n {model.state === ModelFileState.EXPIRED && (\n <p className=\"mt-1 text-sm text-neutral-700 dark:text-neutral-200\">\n {t.expired}\n {model.accessExpiresAt != null\n ? ` ${formatDate(model.accessExpiresAt, en)}`\n : \"\"}\n .\n </p>\n )}\n </div>\n </div>\n {model.state === ModelFileState.AVAILABLE &&\n model.files.map((file) => {\n const key = `${model.orderId}:${model.lineId}:${file.index}`;\n const busy = pending === key;\n return (\n <div\n key={key}\n className=\"flex flex-col items-start gap-1 sm:items-end\"\n >\n <button\n type=\"button\"\n onClick={() => void download(model, file.index)}\n disabled={pending != null}\n aria-busy={busy || undefined}\n aria-label={`${t.download}: ${file.fileName}`}\n data-model-download=\"\"\n style={buttonStyle}\n className=\"inline-flex h-10 items-center justify-center rounded-lg bg-neutral-900 px-4 text-sm font-medium text-white transition-opacity hover:opacity-90 disabled:opacity-60 dark:bg-neutral-50 dark:text-neutral-900\"\n >\n {busy ? t.preparing : t.download}\n </button>\n {errors[key] && (\n <p\n className=\"text-sm text-red-700 dark:text-red-400\"\n role=\"alert\"\n >\n {errors[key]}\n </p>\n )}\n </div>\n );\n })}\n </li>\n );\n })}\n </ul>\n </div>\n </section>\n );\n}\n"],"mappings":"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","names":["createClient","useTransport","useCallback","useMemo","useState","Fragment","jsx","jsxs","COPY","formatSize","bytes","en","value","mb","fmt","formatDate","seconds","CubeIcon","className","PanelModelFiles","accessToken","models","buttonStyle","getLocale","transport","useTransport","client","useMemo","createClient","ModelDeliveryService","pending","setPending","useState","errors","setErrors","download","useCallback","model","index","key","current","response","failure","title","ModelFileState","file","busy"]}
@@ -0,0 +1,2 @@
1
+ import{c as V,d as G,f as R,g as _,i as P}from"./chunk-5ED7FWNY.mjs";import"./chunk-U6FLIRIV.mjs";import{ContactShadows as ie}from"@react-three/drei";import{Canvas as ce,useFrame as de,useThree as w}from"@react-three/fiber";import{Component as ue,useEffect as v,useLayoutEffect as z,useMemo as le,useRef as x,useState as H}from"react";import{Vector3 as me}from"three";import{OrbitControls as pe}from"three/examples/jsm/controls/OrbitControls.js";async function I(e,t={}){let o=await fetch(e,{signal:t.signal,credentials:"omit"});if(!o.ok)throw new Error(`model download failed: ${o.status}`);let i=Number(o.headers.get("content-length")),a=Number.isFinite(i)&&i>0?i:t.expectedSize??0;if(o.body==null){let s=await o.arrayBuffer();return t.onProgress?.(s.byteLength,s.byteLength),s}let m=o.body.getReader(),u=[],c=0;for(t.onProgress?.(0,a);;){let{done:s,value:g}=await m.read();if(s)break;u.push(g),c+=g.byteLength,t.onProgress?.(c,Math.max(a,c))}let d=new Uint8Array(c),p=0;for(let s of u)d.set(s,p),p+=s.byteLength;return d.buffer}import{Box3 as N,MathUtils as A,PMREMGenerator as Q,REVISION as X,Sphere as Y,Vector3 as M}from"three";import{RoomEnvironment as ee}from"three/examples/jsm/environments/RoomEnvironment.js";import{MeshoptDecoder as te}from"three/examples/jsm/libs/meshopt_decoder.module.js";import{DRACOLoader as re}from"three/examples/jsm/loaders/DRACOLoader.js";import{GLTFLoader as ne}from"three/examples/jsm/loaders/GLTFLoader.js";var B=new Map;function oe(e){let t=e?.trim()||_(X);return t.endsWith("/")?t:`${t}/`}function ae(e){let t=oe(e),o=B.get(t);return o==null&&(o=new re().setDecoderPath(t),B.set(t,o)),new ne().setDRACOLoader(o).setMeshoptDecoder(te)}function U(e,t){return ae(t).parseAsync(e,"")}function W(e){e.position.set(0,0,0),e.updateMatrixWorld(!0);let t=new N().setFromObject(e);if(t.isEmpty())return{object:e,radius:1,size:new M(1,1,1)};let o=t.getCenter(new M);e.position.set(-o.x,-t.min.y,-o.z),e.updateMatrixWorld(!0);let i=t.getSize(new M),a=new N().setFromObject(e).getBoundingSphere(new Y);return{object:e,radius:Math.max(a.radius,.001),size:i}}var se={azimuthDeg:35,elevationDeg:18};function j(e,t,o,i=se,a=1.18){let m=new M(0,e.size.y/2,0),u=A.degToRad(t)/2,c=Math.atan(Math.tan(u)*Math.max(o,.1)),d=Math.min(u,c),p=e.radius/Math.sin(d)*a,s=A.degToRad(i.azimuthDeg),g=A.degToRad(i.elevationDeg);return{position:new M(Math.sin(s)*Math.cos(g),Math.sin(g),Math.cos(s)*Math.cos(g)).multiplyScalar(p).add(m),target:m,distance:p}}function S(e,t){e.position.copy(t.position),e.near=Math.max(t.distance/200,1e-4),e.far=t.distance*50,e.lookAt(t.target),e.updateProjectionMatrix()}function q(e){let t=new Q(e),o=new ee,i=t.fromScene(o,.04);return o.dispose(),t.dispose(),{texture:i.texture,dispose:()=>i.dispose()}}import{Fragment as ve,jsx as b,jsxs as ye}from"react/jsx-runtime";var $=.08,L=new Map;function fe(e,t,o,i){let a=L.get(e);if(a==null){let m=new AbortController,u={controller:m,users:0,progress:new Set,promise:void 0};u.promise=I(e,{signal:m.signal,expectedSize:t,onProgress:(c,d)=>{u.last=[c,d];for(let p of u.progress)p(c,d)}}).then(c=>U(c,o)).then(c=>W(c.scene)),u.promise.catch(()=>{L.get(e)===u&&L.delete(e)}),L.set(e,u),a=u}return a.abortTimer!=null&&(clearTimeout(a.abortTimer),a.abortTimer=void 0),a.users+=1,a.progress.add(i),a.last!=null&&i(...a.last),a}function ge(e,t){e.users-=1,e.progress.delete(t),!(e.users>0)&&(e.abortTimer=setTimeout(()=>{e.abortTimer=void 0,e.users===0&&e.controller.abort()},0))}function he(e){let[t,o]=H(),i=x(e);return i.current=e,v(()=>{let a=!1;o(void 0);let m=(c,d)=>{a||i.current.onProgress(c,d)},u=fe(e.url,e.expectedSize,e.decoderPath,m);return u.promise.then(c=>{a||o(c)},c=>{a||i.current.onError(c)}),()=>{a=!0,ge(u,m)}},[e.url,e.attempt]),t==null?null:b(F,{onError:a=>i.current.onError(a),children:b(ce,{className:e.className,dpr:[1,2],frameloop:e.motionAllowed?"always":"demand",camera:{fov:R,position:[0,0,5]},gl:{alpha:!0,antialias:!0,preserveDrawingBuffer:!1},style:{background:"transparent",touchAction:e.interactive?"none":"pan-y"},"aria-hidden":"true",children:b(be,{model:t,...e})},e.url)})}function be({model:e,motionAllowed:t,interactive:o,allowZoom:i,resetSignal:a,onLoaded:m,onInteract:u,onError:c}){let d=w(r=>r.gl),p=w(r=>r.scene),s=w(r=>r.camera),g=w(r=>r.size),l=w(r=>r.invalidate),[Z,D]=H(!1),C=x(!1),y=x(void 0),E=x({onLoaded:m,onInteract:u,onError:c});E.current={onLoaded:m,onInteract:u,onError:c};let n=le(()=>new pe(s,d.domElement),[s,d]);v(()=>{let r=q(d);return p.environment=r.texture,p.environmentIntensity=P.environmentIntensity,l(),()=>{p.environment=null,r.dispose()}},[d,p,l]),v(()=>{let r=d.domElement,f=h=>{h.preventDefault(),E.current.onError(new Error("webgl context lost"))};return r.addEventListener("webglcontextlost",f),()=>r.removeEventListener("webglcontextlost",f)},[d]),z(()=>{n.connect(d.domElement)},[n,d]),v(()=>{n.enablePan=!1,n.enableDamping=!0,n.dampingFactor=$,n.rotateSpeed=.8,n.minPolarAngle=.05,n.maxPolarAngle=Math.PI*.62,n.autoRotateSpeed=V;let r,f=()=>{clearTimeout(r),D(!0),E.current.onInteract()},h=()=>{clearTimeout(r),r=setTimeout(()=>D(!1),G)},T=()=>l();return n.addEventListener("start",f),n.addEventListener("end",h),n.addEventListener("change",T),()=>{clearTimeout(r),n.removeEventListener("start",f),n.removeEventListener("end",h),n.removeEventListener("change",T),n.dispose()}},[n,l]),z(()=>{n.enabled=o,n.enableZoom=i,d.domElement.style.touchAction=o?"none":"pan-y",l()},[n,d,o,i,l]);let k=t&&!Z;v(()=>{n.autoRotate=k,l()},[n,k,l]);let O=g.width/Math.max(g.height,1);z(()=>{let r=j(e,R,O),f=y.current;if(y.current=r,n.minDistance=r.distance*.35,n.maxDistance=r.distance*2.5,f==null)S(s,r),n.target.copy(r.target);else{let h=new me().subVectors(s.position,n.target).normalize(),T=s.position.distanceTo(n.target)/Math.max(f.distance,1e-6);s.position.copy(r.target).addScaledVector(h,r.distance*T),s.near=r.distance/200,s.far=r.distance*50,s.updateProjectionMatrix()}n.update(),l()},[e,O,s,n,l]);let J=x(a);v(()=>{a===J.current||y.current==null||(S(s,y.current),n.target.copy(y.current.target),n.update(),D(!1),l())},[a,s,n,l]),de((r,f)=>{let h=Math.min(f,.1);n.dampingFactor=1-(1-$)**(h*60),n.update(h)&&l(),C.current||(C.current=!0,requestAnimationFrame(()=>E.current.onLoaded()))});let K=Math.max(e.size.x,e.size.z,e.radius);return ye(ve,{children:[b("ambientLight",{intensity:P.ambient}),P.directional.map(r=>b("directionalLight",{position:r.position,intensity:r.intensity},r.position.join(","))),b("primitive",{object:e.object}),b(ie,{position:[0,5e-4*e.radius,0],scale:K*2.4,far:Math.max(e.size.y,e.radius)*1.2,blur:2.6,opacity:.42,resolution:512,frames:1})]})}var F=class extends ue{state={failed:!1};static getDerivedStateFromError(){return{failed:!0}}componentDidCatch(t){this.props.onError(t)}render(){return this.state.failed?null:this.props.children}};export{he as default};
2
+ //# sourceMappingURL=Viewer3D-2IDEH6W3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/viewer3d/Viewer3D.tsx","../src/viewer3d/fetch-model.ts","../src/viewer3d/scene.three.ts"],"sourcesContent":["/**\n * The interactive 3D viewer. With `scene.three.ts`, the only module that\n * imports `three`, `@react-three/fiber` and `@react-three/drei` (optional peer\n * dependencies). `Model3DShell` loads it with `import()` inside an effect,\n * never during the server render; `test:package` checks that the main and\n * catalog entries never reach it.\n */\nimport { ContactShadows } from \"@react-three/drei\";\nimport { Canvas, useFrame, useThree } from \"@react-three/fiber\";\nimport {\n Component,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { Vector3 } from \"three\";\nimport { OrbitControls } from \"three/examples/jsm/controls/OrbitControls.js\";\n\nimport {\n AUTO_ROTATE_RESUME_MS,\n AUTO_ROTATE_SPEED,\n CAMERA_FOV,\n STUDIO_LIGHTS,\n} from \"./constants\";\nimport { fetchModel } from \"./fetch-model\";\nimport {\n applyFraming,\n createStudioEnvironment,\n frameModel,\n parseGLB,\n prepareModel,\n type Framing,\n type PreparedModel,\n} from \"./scene.three\";\n\n/** Glide after a drag: the share of speed lost per frame at 60 Hz. */\nconst DAMPING_PER_60HZ_FRAME = 0.08;\n\nexport type Viewer3DProps = {\n url: string;\n /** Draco decoder folder; default: three's decoder on jsDelivr for the installed revision. */\n decoderPath?: string;\n /** Bytes the server reported; the progress total when the response has none. */\n expectedSize: number;\n /** Bumped by the shell on retry; a failed download is not reused. */\n attempt: number;\n /** Rotation may run: on screen, tab visible, no reduced motion. */\n motionAllowed: boolean;\n /** The buyer can drag (on a phone only after a tap, so the page scrolls). */\n interactive: boolean;\n /** Wheel and pinch zoom: full screen and active touch only, so the page keeps its scroll. */\n allowZoom: boolean;\n /** Bumped by \"Reset widoku\". */\n resetSignal: number;\n onProgress: (loaded: number, total: number) => void;\n /** The first frame with the model is on screen. */\n onLoaded: () => void;\n onError: (error: unknown) => void;\n /** The buyer dragged or zoomed (hides the gesture hint). */\n onInteract: () => void;\n className?: string;\n};\n\n/**\n * Downloads and parsed models by URL, shared by every viewer of the page:\n * switching back to a variant does not download again. A download is aborted\n * only when its last viewer is gone, one task later, so a remount (React\n * StrictMode, a parent re-keying the viewer) picks the running download up\n * instead of failing with an AbortError.\n */\ntype ModelEntry = {\n promise: Promise<PreparedModel>;\n controller: AbortController;\n users: number;\n progress: Set<(loaded: number, total: number) => void>;\n last?: [number, number];\n abortTimer?: ReturnType<typeof setTimeout>;\n};\n\nconst models = new Map<string, ModelEntry>();\n\nfunction acquireModel(\n url: string,\n expectedSize: number,\n decoderPath: string | undefined,\n onProgress: (loaded: number, total: number) => void,\n): ModelEntry {\n let entry = models.get(url);\n if (entry == null) {\n const controller = new AbortController();\n const created: ModelEntry = {\n controller,\n users: 0,\n progress: new Set(),\n promise: undefined as unknown as Promise<PreparedModel>,\n };\n created.promise = fetchModel(url, {\n signal: controller.signal,\n expectedSize,\n onProgress: (loaded, total) => {\n created.last = [loaded, total];\n for (const listener of created.progress) listener(loaded, total);\n },\n })\n .then((buffer) => parseGLB(buffer, decoderPath))\n .then((gltf) => prepareModel(gltf.scene));\n // A failed or aborted download is forgotten, so a retry downloads again.\n created.promise.catch(() => {\n if (models.get(url) === created) models.delete(url);\n });\n models.set(url, created);\n entry = created;\n }\n if (entry.abortTimer != null) {\n clearTimeout(entry.abortTimer);\n entry.abortTimer = undefined;\n }\n entry.users += 1;\n entry.progress.add(onProgress);\n if (entry.last != null) onProgress(...entry.last);\n return entry;\n}\n\nfunction releaseModel(\n entry: ModelEntry,\n onProgress: (loaded: number, total: number) => void,\n): void {\n entry.users -= 1;\n entry.progress.delete(onProgress);\n if (entry.users > 0) return;\n entry.abortTimer = setTimeout(() => {\n entry.abortTimer = undefined;\n // Done downloads ignore it; the parsed model stays cached.\n if (entry.users === 0) entry.controller.abort();\n }, 0);\n}\n\nexport default function Viewer3D(props: Viewer3DProps) {\n const [model, setModel] = useState<PreparedModel>();\n const callbacks = useRef(props);\n callbacks.current = props;\n\n useEffect(() => {\n let cancelled = false;\n setModel(undefined);\n const onProgress = (loaded: number, total: number) => {\n if (!cancelled) callbacks.current.onProgress(loaded, total);\n };\n const entry = acquireModel(\n props.url,\n props.expectedSize,\n props.decoderPath,\n onProgress,\n );\n entry.promise.then(\n (prepared) => {\n if (!cancelled) setModel(prepared);\n },\n (error) => {\n if (!cancelled) callbacks.current.onError(error);\n },\n );\n return () => {\n cancelled = true;\n releaseModel(entry, onProgress);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [props.url, props.attempt]);\n\n if (model == null) return null;\n\n return (\n <CanvasErrorBoundary onError={(error) => callbacks.current.onError(error)}>\n <Canvas\n key={props.url}\n className={props.className}\n dpr={[1, 2]}\n frameloop={props.motionAllowed ? \"always\" : \"demand\"}\n camera={{ fov: CAMERA_FOV, position: [0, 0, 5] }}\n gl={{ alpha: true, antialias: true, preserveDrawingBuffer: false }}\n // The page background shows through: the theme's card colour, never a grey box.\n style={{\n background: \"transparent\",\n touchAction: props.interactive ? \"none\" : \"pan-y\",\n }}\n aria-hidden=\"true\"\n >\n <Scene model={model} {...props} />\n </Canvas>\n </CanvasErrorBoundary>\n );\n}\n\nfunction Scene({\n model,\n motionAllowed,\n interactive,\n allowZoom,\n resetSignal,\n onLoaded,\n onInteract,\n onError,\n}: Viewer3DProps & { model: PreparedModel }) {\n const gl = useThree((state) => state.gl);\n const scene = useThree((state) => state.scene);\n const camera = useThree(\n (state) => state.camera,\n ) as import(\"three\").PerspectiveCamera;\n const size = useThree((state) => state.size);\n const invalidate = useThree((state) => state.invalidate);\n const [paused, setPaused] = useState(false);\n const loadedRef = useRef(false);\n const startRef = useRef<Framing | undefined>(undefined);\n const callbacks = useRef({ onLoaded, onInteract, onError });\n callbacks.current = { onLoaded, onInteract, onError };\n\n const controls = useMemo(\n () => new OrbitControls(camera, gl.domElement),\n [camera, gl],\n );\n\n // Light: a studio environment generated on the device.\n useEffect(() => {\n const environment = createStudioEnvironment(gl);\n scene.environment = environment.texture;\n scene.environmentIntensity = STUDIO_LIGHTS.environmentIntensity;\n invalidate();\n return () => {\n scene.environment = null;\n environment.dispose();\n };\n }, [gl, scene, invalidate]);\n\n // A lost GPU context is an error the buyer can retry.\n useEffect(() => {\n const canvas = gl.domElement;\n const onLost = (event: Event) => {\n event.preventDefault();\n callbacks.current.onError(new Error(\"webgl context lost\"));\n };\n canvas.addEventListener(\"webglcontextlost\", onLost);\n return () => canvas.removeEventListener(\"webglcontextlost\", onLost);\n }, [gl]);\n\n // (Re)attach the controls to the canvas: a cleanup's `dispose` detaches\n // them and React StrictMode runs effects twice. A layout effect, before the\n // one below that sets `touch-action` (connect sets it to \"none\").\n useLayoutEffect(() => {\n controls.connect(gl.domElement);\n }, [controls, gl]);\n\n // Controls: rotate and zoom around the model, no panning.\n useEffect(() => {\n controls.enablePan = false;\n controls.enableDamping = true;\n controls.dampingFactor = DAMPING_PER_60HZ_FRAME;\n controls.rotateSpeed = 0.8;\n controls.minPolarAngle = 0.05;\n controls.maxPolarAngle = Math.PI * 0.62;\n controls.autoRotateSpeed = AUTO_ROTATE_SPEED;\n let resume: ReturnType<typeof setTimeout> | undefined;\n const onStart = () => {\n clearTimeout(resume);\n setPaused(true);\n callbacks.current.onInteract();\n };\n const onEnd = () => {\n clearTimeout(resume);\n resume = setTimeout(() => setPaused(false), AUTO_ROTATE_RESUME_MS);\n };\n const onChange = () => invalidate();\n controls.addEventListener(\"start\", onStart);\n controls.addEventListener(\"end\", onEnd);\n controls.addEventListener(\"change\", onChange);\n return () => {\n clearTimeout(resume);\n controls.removeEventListener(\"start\", onStart);\n controls.removeEventListener(\"end\", onEnd);\n controls.removeEventListener(\"change\", onChange);\n controls.dispose();\n };\n }, [controls, invalidate]);\n\n // OrbitControls sets `touch-action: none` on connect; the page decides.\n useLayoutEffect(() => {\n controls.enabled = interactive;\n controls.enableZoom = allowZoom;\n gl.domElement.style.touchAction = interactive ? \"none\" : \"pan-y\";\n invalidate();\n }, [controls, gl, interactive, allowZoom, invalidate]);\n\n const rotating = motionAllowed && !paused;\n useEffect(() => {\n controls.autoRotate = rotating;\n invalidate();\n }, [controls, rotating, invalidate]);\n\n // Framing: the start view, again on reset; a new aspect keeps the direction.\n const aspect = size.width / Math.max(size.height, 1);\n useLayoutEffect(() => {\n const framing = frameModel(model, CAMERA_FOV, aspect);\n const previous = startRef.current;\n startRef.current = framing;\n controls.minDistance = framing.distance * 0.35;\n controls.maxDistance = framing.distance * 2.5;\n if (previous == null) {\n applyFraming(camera, framing);\n controls.target.copy(framing.target);\n } else {\n const direction = new Vector3()\n .subVectors(camera.position, controls.target)\n .normalize();\n const scale =\n camera.position.distanceTo(controls.target) /\n Math.max(previous.distance, 1e-6);\n camera.position\n .copy(framing.target)\n .addScaledVector(direction, framing.distance * scale);\n camera.near = framing.distance / 200;\n camera.far = framing.distance * 50;\n camera.updateProjectionMatrix();\n }\n controls.update();\n invalidate();\n }, [model, aspect, camera, controls, invalidate]);\n\n const firstReset = useRef(resetSignal);\n useEffect(() => {\n if (resetSignal === firstReset.current || startRef.current == null) return;\n applyFraming(camera, startRef.current);\n controls.target.copy(startRef.current.target);\n controls.update();\n setPaused(false);\n invalidate();\n }, [resetSignal, camera, controls, invalidate]);\n\n useFrame((_, delta) => {\n // Time-based: the same rotation speed and glide at 60 Hz, 120 Hz or a\n // slow device (OrbitControls damps per call); damping keeps drawing.\n const step = Math.min(delta, 0.1);\n controls.dampingFactor = 1 - (1 - DAMPING_PER_60HZ_FRAME) ** (step * 60);\n if (controls.update(step)) invalidate();\n if (!loadedRef.current) {\n loadedRef.current = true;\n requestAnimationFrame(() => callbacks.current.onLoaded());\n }\n });\n\n const footprint = Math.max(model.size.x, model.size.z, model.radius);\n return (\n <>\n <ambientLight intensity={STUDIO_LIGHTS.ambient} />\n {STUDIO_LIGHTS.directional.map((light) => (\n <directionalLight key={light.position.join(\",\")} position={light.position} intensity={light.intensity} />\n ))}\n <primitive object={model.object} />\n <ContactShadows\n position={[0, 0.0005 * model.radius, 0]}\n scale={footprint * 2.4}\n far={Math.max(model.size.y, model.radius) * 1.2}\n blur={2.6}\n opacity={0.42}\n resolution={512}\n frames={1}\n />\n </>\n );\n}\n\nclass CanvasErrorBoundary extends Component<\n { onError: (error: unknown) => void; children: ReactNode },\n { failed: boolean }\n> {\n state = { failed: false };\n\n static getDerivedStateFromError() {\n return { failed: true };\n }\n\n componentDidCatch(error: unknown) {\n this.props.onError(error);\n }\n\n render() {\n return this.state.failed ? null : this.props.children;\n }\n}\n","import { needsClickToLoad } from \"./constants\";\n\n/**\n * Downloads a display copy with byte progress (no three here; `Viewer3D`\n * parses the buffer). The total comes from Content-Length, else from the size\n * the server reported (`previewSize`).\n */\nexport async function fetchModel(\n url: string,\n options: {\n signal?: AbortSignal;\n expectedSize?: number;\n onProgress?: (loaded: number, total: number) => void;\n } = {},\n): Promise<ArrayBuffer> {\n const response = await fetch(url, {\n signal: options.signal,\n credentials: \"omit\",\n });\n if (!response.ok)\n throw new Error(`model download failed: ${response.status}`);\n const header = Number(response.headers.get(\"content-length\"));\n const total =\n Number.isFinite(header) && header > 0\n ? header\n : (options.expectedSize ?? 0);\n if (response.body == null) {\n const buffer = await response.arrayBuffer();\n options.onProgress?.(buffer.byteLength, buffer.byteLength);\n return buffer;\n }\n\n const reader = response.body.getReader();\n const chunks: Uint8Array[] = [];\n let loaded = 0;\n options.onProgress?.(0, total);\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n chunks.push(value);\n loaded += value.byteLength;\n options.onProgress?.(loaded, Math.max(total, loaded));\n }\n const buffer = new Uint8Array(loaded);\n let offset = 0;\n for (const chunk of chunks) {\n buffer.set(chunk, offset);\n offset += chunk.byteLength;\n }\n return buffer.buffer;\n}\n\nconst prefetched = new Set<string>();\n\n/**\n * Warms the HTTP cache with another variant's model when the buyer points at\n * its chip (plan N5: on hover/focus only, never on idle). Models over the\n * click-to-load limit are never prefetched.\n */\nexport function prefetchModel(url: string | undefined, size: number): void {\n if (url == null || prefetched.has(url) || needsClickToLoad(size)) return;\n if (typeof window === \"undefined\") return;\n // Save-Data and slow connections skip it.\n const connection = (\n navigator as Navigator & {\n connection?: { saveData?: boolean; effectiveType?: string };\n }\n ).connection;\n if (connection?.saveData || /(^|-)2g$/.test(connection?.effectiveType ?? \"\"))\n return;\n prefetched.add(url);\n fetch(url, { credentials: \"omit\", priority: \"low\" } as RequestInit).catch(\n () => {\n prefetched.delete(url);\n },\n );\n}\n","/**\n * The 3D scene of a product model: loading, framing and light. Imports\n * `three`, so it is only reached through the dynamic `import()` of\n * `Viewer3D` (the `bitsnap-react` main and catalog entries never reach it;\n * `test:package` checks that).\n */\nimport {\n Box3,\n MathUtils,\n PMREMGenerator,\n REVISION,\n Sphere,\n Vector3,\n type Object3D,\n type PerspectiveCamera,\n type Texture,\n type WebGLRenderer,\n} from \"three\";\nimport { RoomEnvironment } from \"three/examples/jsm/environments/RoomEnvironment.js\";\nimport { MeshoptDecoder } from \"three/examples/jsm/libs/meshopt_decoder.module.js\";\nimport { DRACOLoader } from \"three/examples/jsm/loaders/DRACOLoader.js\";\nimport {\n GLTFLoader,\n type GLTF,\n} from \"three/examples/jsm/loaders/GLTFLoader.js\";\n\nimport { defaultDecoderPath } from \"./constants\";\n\nconst dracoLoaders = new Map<string, DRACOLoader>();\n\n/** The Draco decoder path in effect: the caller's, else three's files on jsDelivr. */\nexport function resolveDecoderPath(decoderPath?: string): string {\n const path = decoderPath?.trim() || defaultDecoderPath(REVISION);\n return path.endsWith(\"/\") ? path : `${path}/`;\n}\n\n/** A GLTF loader that decodes Draco and Meshopt. One Draco loader per decoder path. */\nexport function createGLTFLoader(decoderPath?: string): GLTFLoader {\n const path = resolveDecoderPath(decoderPath);\n let draco = dracoLoaders.get(path);\n if (draco == null) {\n draco = new DRACOLoader().setDecoderPath(path);\n dracoLoaders.set(path, draco);\n }\n return new GLTFLoader().setDRACOLoader(draco).setMeshoptDecoder(MeshoptDecoder);\n}\n\n/** Parses a downloaded GLB. */\nexport function parseGLB(buffer: ArrayBuffer, decoderPath?: string): Promise<GLTF> {\n return createGLTFLoader(decoderPath).parseAsync(buffer, \"\");\n}\n\nexport type PreparedModel = {\n object: Object3D;\n /** Radius of the bounding sphere, after centring (model units). */\n radius: number;\n /** Size of the bounding box (x, y, z). */\n size: Vector3;\n};\n\n/**\n * Centres the model on the origin in x and z and stands it on y = 0, so the\n * contact shadow sits under it and the camera orbits its middle.\n */\nexport function prepareModel(object: Object3D): PreparedModel {\n object.position.set(0, 0, 0);\n object.updateMatrixWorld(true);\n const box = new Box3().setFromObject(object);\n if (box.isEmpty()) {\n return { object, radius: 1, size: new Vector3(1, 1, 1) };\n }\n const center = box.getCenter(new Vector3());\n object.position.set(-center.x, -box.min.y, -center.z);\n object.updateMatrixWorld(true);\n const size = box.getSize(new Vector3());\n const sphere = new Box3()\n .setFromObject(object)\n .getBoundingSphere(new Sphere());\n return { object, radius: Math.max(sphere.radius, 1e-3), size };\n}\n\nexport type Framing = {\n position: Vector3;\n target: Vector3;\n distance: number;\n};\n\n/** Camera angles of the start view (and of the main \"3q\" thumbnail). */\nexport const START_VIEW = { azimuthDeg: 35, elevationDeg: 18 } as const;\n\n/**\n * Where the camera stands so the whole model fits with a margin, seen from\n * `view` (azimuth around y, elevation above the horizon).\n */\nexport function frameModel(\n model: PreparedModel,\n fovDeg: number,\n aspect: number,\n view: { azimuthDeg: number; elevationDeg: number } = START_VIEW,\n margin = 1.18,\n): Framing {\n const target = new Vector3(0, model.size.y / 2, 0);\n const vertical = MathUtils.degToRad(fovDeg) / 2;\n const horizontal = Math.atan(Math.tan(vertical) * Math.max(aspect, 0.1));\n const halfAngle = Math.min(vertical, horizontal);\n const distance = (model.radius / Math.sin(halfAngle)) * margin;\n const azimuth = MathUtils.degToRad(view.azimuthDeg);\n const elevation = MathUtils.degToRad(view.elevationDeg);\n const position = new Vector3(\n Math.sin(azimuth) * Math.cos(elevation),\n Math.sin(elevation),\n Math.cos(azimuth) * Math.cos(elevation),\n )\n .multiplyScalar(distance)\n .add(target);\n return { position, target, distance };\n}\n\n/** Points the camera along a framing and sets near/far planes for its size. */\nexport function applyFraming(\n camera: PerspectiveCamera,\n framing: Framing,\n): void {\n camera.position.copy(framing.position);\n camera.near = Math.max(framing.distance / 200, 1e-4);\n camera.far = framing.distance * 50;\n camera.lookAt(framing.target);\n camera.updateProjectionMatrix();\n}\n\n/**\n * A neutral studio light for PBR materials, generated on the device (three's\n * `RoomEnvironment`); no HDR download (plan S13).\n */\nexport function createStudioEnvironment(renderer: WebGLRenderer): {\n texture: Texture;\n dispose: () => void;\n} {\n const pmrem = new PMREMGenerator(renderer);\n const room = new RoomEnvironment();\n const target = pmrem.fromScene(room, 0.04);\n room.dispose();\n pmrem.dispose();\n return { texture: target.texture, dispose: () => target.dispose() };\n}\n"],"mappings":"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","names":["ContactShadows","Canvas","useFrame","useThree","Component","useEffect","useLayoutEffect","useMemo","useRef","useState","Vector3","OrbitControls","fetchModel","url","options","response","header","total","buffer","reader","chunks","loaded","done","value","offset","chunk","Box3","MathUtils","PMREMGenerator","REVISION","Sphere","Vector3","RoomEnvironment","MeshoptDecoder","DRACOLoader","GLTFLoader","dracoLoaders","resolveDecoderPath","decoderPath","path","defaultDecoderPath","REVISION","createGLTFLoader","draco","DRACOLoader","GLTFLoader","MeshoptDecoder","parseGLB","buffer","prepareModel","object","box","Box3","Vector3","center","size","sphere","Sphere","START_VIEW","frameModel","model","fovDeg","aspect","view","margin","target","vertical","MathUtils","horizontal","halfAngle","distance","azimuth","elevation","applyFraming","camera","framing","createStudioEnvironment","renderer","pmrem","PMREMGenerator","room","RoomEnvironment","Fragment","jsx","jsxs","DAMPING_PER_60HZ_FRAME","models","acquireModel","url","expectedSize","decoderPath","onProgress","entry","controller","created","fetchModel","loaded","total","listener","buffer","parseGLB","gltf","prepareModel","releaseModel","Viewer3D","props","model","setModel","useState","callbacks","useRef","useEffect","cancelled","prepared","error","CanvasErrorBoundary","Canvas","CAMERA_FOV","Scene","motionAllowed","interactive","allowZoom","resetSignal","onLoaded","onInteract","onError","gl","useThree","state","scene","camera","size","invalidate","paused","setPaused","loadedRef","startRef","controls","useMemo","OrbitControls","environment","createStudioEnvironment","STUDIO_LIGHTS","canvas","onLost","event","useLayoutEffect","AUTO_ROTATE_SPEED","resume","onStart","onEnd","AUTO_ROTATE_RESUME_MS","onChange","rotating","aspect","framing","frameModel","previous","applyFraming","direction","Vector3","scale","firstReset","useFrame","_","delta","step","footprint","light","ContactShadows","Component"]}
@@ -1,5 +1,5 @@
1
- import { T as Tracker, a as TrackerOptions, C as ClientEvent } from './instrument-BE8ktMUv.mjs';
2
- export { A as AnalyticsOption, d as AppEventName, B as BrowserEventName, e as Channel, D as DEFAULT_ENDPOINT, E as Env, f as EventName, h as EventProps, l as Instrument, I as InstrumentProvider, P as PageType, S as Source, j as StorefrontEventName, b as TrackExtra, c as createTracker, g as getTracker, i as initTracker, n as noopInstrument, r as resetTracker, s as scoreBucket, u as useInstrument, k as useInstrumentContext } from './instrument-BE8ktMUv.mjs';
1
+ import { T as Tracker, a as TrackerOptions, C as ClientEvent } from './instrument-eXaC8hc2.mjs';
2
+ export { k as AnalyticsOption, A as AppEventName, B as BrowserEventName, d as Channel, D as DEFAULT_ENDPOINT, E as Env, e as EventName, f as EventProps, l as Instrument, I as InstrumentProvider, P as PageType, S as Source, h as StorefrontEventName, b as TrackExtra, c as createTracker, g as getTracker, i as initTracker, n as noopInstrument, r as resetTracker, s as scoreBucket, u as useInstrument, j as useInstrumentContext } from './instrument-eXaC8hc2.mjs';
3
3
  import * as react from 'react';
4
4
  import { ReactNode } from 'react';
5
5
 
@@ -1,5 +1,5 @@
1
- import { T as Tracker, a as TrackerOptions, C as ClientEvent } from './instrument-BE8ktMUv.js';
2
- export { A as AnalyticsOption, d as AppEventName, B as BrowserEventName, e as Channel, D as DEFAULT_ENDPOINT, E as Env, f as EventName, h as EventProps, l as Instrument, I as InstrumentProvider, P as PageType, S as Source, j as StorefrontEventName, b as TrackExtra, c as createTracker, g as getTracker, i as initTracker, n as noopInstrument, r as resetTracker, s as scoreBucket, u as useInstrument, k as useInstrumentContext } from './instrument-BE8ktMUv.js';
1
+ import { T as Tracker, a as TrackerOptions, C as ClientEvent } from './instrument-eXaC8hc2.js';
2
+ export { k as AnalyticsOption, A as AppEventName, B as BrowserEventName, d as Channel, D as DEFAULT_ENDPOINT, E as Env, e as EventName, f as EventProps, l as Instrument, I as InstrumentProvider, P as PageType, S as Source, h as StorefrontEventName, b as TrackExtra, c as createTracker, g as getTracker, i as initTracker, n as noopInstrument, r as resetTracker, s as scoreBucket, u as useInstrument, j as useInstrumentContext } from './instrument-eXaC8hc2.js';
3
3
  import * as react from 'react';
4
4
  import { ReactNode } from 'react';
5
5
 
package/dist/analytics.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkMG6VV5J2js = require('./chunk-MG6VV5J2.js');require('./chunk-W3RGQYPD.js');require('./chunk-JSDGZ4NY.js');exports.BitsnapAnalyticsProvider = _chunkMG6VV5J2js.i; exports.DEFAULT_ENDPOINT = _chunkMG6VV5J2js.c; exports.InstrumentProvider = _chunkMG6VV5J2js.l; exports.createTracker = _chunkMG6VV5J2js.d; exports.getTracker = _chunkMG6VV5J2js.f; exports.initTracker = _chunkMG6VV5J2js.e; exports.noopInstrument = _chunkMG6VV5J2js.k; exports.resetTracker = _chunkMG6VV5J2js.g; exports.scoreBucket = _chunkMG6VV5J2js.r; exports.startAutoPageViews = _chunkMG6VV5J2js.b; exports.useAnalytics = _chunkMG6VV5J2js.j; exports.useInstrument = _chunkMG6VV5J2js.n; exports.useInstrumentContext = _chunkMG6VV5J2js.m;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkC3BBJOVVjs = require('./chunk-C3BBJOVV.js');require('./chunk-W3RGQYPD.js');require('./chunk-JSDGZ4NY.js');exports.BitsnapAnalyticsProvider = _chunkC3BBJOVVjs.i; exports.DEFAULT_ENDPOINT = _chunkC3BBJOVVjs.c; exports.InstrumentProvider = _chunkC3BBJOVVjs.l; exports.createTracker = _chunkC3BBJOVVjs.d; exports.getTracker = _chunkC3BBJOVVjs.f; exports.initTracker = _chunkC3BBJOVVjs.e; exports.noopInstrument = _chunkC3BBJOVVjs.k; exports.resetTracker = _chunkC3BBJOVVjs.g; exports.scoreBucket = _chunkC3BBJOVVjs.r; exports.startAutoPageViews = _chunkC3BBJOVVjs.b; exports.useAnalytics = _chunkC3BBJOVVjs.j; exports.useInstrument = _chunkC3BBJOVVjs.n; exports.useInstrumentContext = _chunkC3BBJOVVjs.m;
2
2
  //# sourceMappingURL=analytics.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/private/tmp/bsr-src/dist/analytics.js"],"names":[],"mappings":"AAAA,+HAAsH,+BAA4B,+BAA4B,klBAAsR","file":"/private/tmp/bsr-src/dist/analytics.js"}
1
+ {"version":3,"sources":["/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/analytics.js"],"names":[],"mappings":"AAAA,+HAAsH,+BAA4B,+BAA4B,klBAAsR","file":"/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/analytics.js"}
@@ -1,2 +1,2 @@
1
- import{b as e,c as t,d as r,e as n,f as o,g as a,i as s,j as p,k as c,l as i,m,n as u,r as v}from"./chunk-PEVB3VL4.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{s as BitsnapAnalyticsProvider,t as DEFAULT_ENDPOINT,i as InstrumentProvider,r as createTracker,o as getTracker,n as initTracker,c as noopInstrument,a as resetTracker,v as scoreBucket,e as startAutoPageViews,p as useAnalytics,u as useInstrument,m as useInstrumentContext};
1
+ import{b as e,c as t,d as r,e as n,f as o,g as a,i as s,j as p,k as c,l as i,m,n as u,r as v}from"./chunk-2UDAB2C5.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{s as BitsnapAnalyticsProvider,t as DEFAULT_ENDPOINT,i as InstrumentProvider,r as createTracker,o as getTracker,n as initTracker,c as noopInstrument,a as resetTracker,v as scoreBucket,e as startAutoPageViews,p as useAnalytics,u as useInstrument,m as useInstrumentContext};
2
2
  //# sourceMappingURL=analytics.mjs.map
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { A as AnalyticsOption } from './instrument-BE8ktMUv.mjs';
3
+ import { k as AnalyticsOption } from './instrument-eXaC8hc2.mjs';
4
4
  import { S as SlotAppearance, A as AppearanceElements } from './appearance-core-Dt2grZdl.mjs';
5
5
  import { BitsnapModels } from './models.mjs';
6
6
  import 'zod';
@@ -52,7 +52,7 @@ type ProductListAppearanceTokens = {
52
52
  * Stable slot names. They are exposed as `data-bitsnap-products-slot`
53
53
  * attributes so they can also be targeted from plain CSS.
54
54
  */
55
- declare const PRODUCT_LIST_SLOT_NAMES: readonly ["root", "grid", "card", "cardLink", "image", "imagePlaceholder", "body", "name", "description", "price", "comparePrice", "badge", "actions", "addToCartButton", "selectButton", "message", "pagination", "paginationButton", "pageIndicator", "emptyState", "error", "retryButton", "skeleton"];
55
+ declare const PRODUCT_LIST_SLOT_NAMES: readonly ["root", "grid", "card", "cardLink", "image", "imagePlaceholder", "body", "name", "description", "price", "comparePrice", "badge", "model3dBadge", "actions", "addToCartButton", "selectButton", "message", "pagination", "paginationButton", "pageIndicator", "emptyState", "error", "retryButton", "skeleton"];
56
56
  type ProductListSlotName = (typeof PRODUCT_LIST_SLOT_NAMES)[number];
57
57
  type ProductListSlotAppearance = SlotAppearance;
58
58
  type ProductListAppearanceElements = AppearanceElements<ProductListSlotName>;
@@ -90,6 +90,10 @@ type ProductListTexts = {
90
90
  unavailable: string;
91
91
  /** Badge of subscription products. */
92
92
  subscription: string;
93
+ /** Badge of products with a 3D model. */
94
+ model3d: string;
95
+ /** Screen reader label of the 3D badge. */
96
+ model3dLabel: string;
93
97
  /** Price of products whose variants differ in price. `{price}` is the lowest one. */
94
98
  priceFrom: string;
95
99
  /** Screen reader label of the struck-through regular price. */
package/dist/catalog.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { ReactNode } from 'react';
3
- import { A as AnalyticsOption } from './instrument-BE8ktMUv.js';
3
+ import { k as AnalyticsOption } from './instrument-eXaC8hc2.js';
4
4
  import { S as SlotAppearance, A as AppearanceElements } from './appearance-core-Dt2grZdl.js';
5
5
  import { BitsnapModels } from './models.js';
6
6
  import 'zod';
@@ -52,7 +52,7 @@ type ProductListAppearanceTokens = {
52
52
  * Stable slot names. They are exposed as `data-bitsnap-products-slot`
53
53
  * attributes so they can also be targeted from plain CSS.
54
54
  */
55
- declare const PRODUCT_LIST_SLOT_NAMES: readonly ["root", "grid", "card", "cardLink", "image", "imagePlaceholder", "body", "name", "description", "price", "comparePrice", "badge", "actions", "addToCartButton", "selectButton", "message", "pagination", "paginationButton", "pageIndicator", "emptyState", "error", "retryButton", "skeleton"];
55
+ declare const PRODUCT_LIST_SLOT_NAMES: readonly ["root", "grid", "card", "cardLink", "image", "imagePlaceholder", "body", "name", "description", "price", "comparePrice", "badge", "model3dBadge", "actions", "addToCartButton", "selectButton", "message", "pagination", "paginationButton", "pageIndicator", "emptyState", "error", "retryButton", "skeleton"];
56
56
  type ProductListSlotName = (typeof PRODUCT_LIST_SLOT_NAMES)[number];
57
57
  type ProductListSlotAppearance = SlotAppearance;
58
58
  type ProductListAppearanceElements = AppearanceElements<ProductListSlotName>;
@@ -90,6 +90,10 @@ type ProductListTexts = {
90
90
  unavailable: string;
91
91
  /** Badge of subscription products. */
92
92
  subscription: string;
93
+ /** Badge of products with a 3D model. */
94
+ model3d: string;
95
+ /** Screen reader label of the 3D badge. */
96
+ model3dLabel: string;
93
97
  /** Price of products whose variants differ in price. `{price}` is the lowest one. */
94
98
  priceFrom: string;
95
99
  /** Screen reader label of the struck-through regular price. */
package/dist/catalog.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkGLAJPUMAjs = require('./chunk-GLAJPUMA.js');require('./chunk-KOYBZORL.js');require('./chunk-DY2S42Q6.js');require('./chunk-MG6VV5J2.js');require('./chunk-W3RGQYPD.js');require('./chunk-JSDGZ4NY.js');exports.PRODUCT_LIST_SLOT_NAMES = _chunkGLAJPUMAjs.a; exports.ProductList = _chunkGLAJPUMAjs.d; exports.formatProductPrice = _chunkGLAJPUMAjs.b; exports.useProducts = _chunkGLAJPUMAjs.c;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunk6GCCNBEUjs = require('./chunk-6GCCNBEU.js');require('./chunk-KOYBZORL.js');require('./chunk-ESF5ZVVF.js');require('./chunk-C3BBJOVV.js');require('./chunk-7RXW6XFY.js');require('./chunk-W3RGQYPD.js');require('./chunk-JSDGZ4NY.js');exports.PRODUCT_LIST_SLOT_NAMES = _chunk6GCCNBEUjs.a; exports.ProductList = _chunk6GCCNBEUjs.d; exports.formatProductPrice = _chunk6GCCNBEUjs.b; exports.useProducts = _chunk6GCCNBEUjs.c;
2
2
  //# sourceMappingURL=catalog.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/private/tmp/bsr-src/dist/catalog.js"],"names":[],"mappings":"AAAA,+HAA4D,+BAA4B,+BAA4B,+BAA4B,+BAA4B,+BAA4B,0LAA+F","file":"/private/tmp/bsr-src/dist/catalog.js"}
1
+ {"version":3,"sources":["/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/catalog.js"],"names":[],"mappings":"AAAA,+HAA4D,+BAA4B,+BAA4B,+BAA4B,+BAA4B,+BAA4B,+BAA4B,0LAA+F","file":"/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/catalog.js"}
package/dist/catalog.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import{a as o,b as r,c as e,d as f}from"./chunk-ZIWQ57GY.mjs";import"./chunk-KOVKUJAW.mjs";import"./chunk-S4Z4O46O.mjs";import"./chunk-PEVB3VL4.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{o as PRODUCT_LIST_SLOT_NAMES,f as ProductList,r as formatProductPrice,e as useProducts};
1
+ import{a as o,b as e,c as f}from"./chunk-4ETIDGYZ.mjs";import{f as r}from"./chunk-M2G74N43.mjs";import"./chunk-KOVKUJAW.mjs";import"./chunk-5R6AGO3Z.mjs";import"./chunk-X3LVYIJR.mjs";import"./chunk-2UDAB2C5.mjs";import"./chunk-VNYHJT6V.mjs";import"./chunk-MDCP5JNP.mjs";import"./chunk-U6FLIRIV.mjs";export{o as PRODUCT_LIST_SLOT_NAMES,f as ProductList,r as formatProductPrice,e as useProducts};
2
2
  //# sourceMappingURL=catalog.mjs.map