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.
- package/AGENTS.md +8 -0
- package/README.md +132 -2
- package/dist/CartProvider-DGRZDTYC.mjs +2 -0
- package/dist/CartProvider-YVOHSMPE.js +2 -0
- package/dist/CartProvider-YVOHSMPE.js.map +1 -0
- package/dist/PanelModelFiles-NYCOFPLY.js +2 -0
- package/dist/PanelModelFiles-NYCOFPLY.js.map +1 -0
- package/dist/PanelModelFiles-RV7OHUUZ.mjs +2 -0
- package/dist/PanelModelFiles-RV7OHUUZ.mjs.map +1 -0
- package/dist/Viewer3D-2IDEH6W3.mjs +2 -0
- package/dist/Viewer3D-2IDEH6W3.mjs.map +1 -0
- package/dist/analytics.d.mts +2 -2
- package/dist/analytics.d.ts +2 -2
- package/dist/analytics.js +1 -1
- package/dist/analytics.js.map +1 -1
- package/dist/analytics.mjs +1 -1
- package/dist/catalog.d.mts +6 -2
- package/dist/catalog.d.ts +6 -2
- package/dist/catalog.js +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/catalog.mjs +1 -1
- package/dist/chunk-2NL5FCXC.mjs +4 -0
- package/dist/chunk-2NL5FCXC.mjs.map +1 -0
- package/dist/{chunk-PEVB3VL4.mjs → chunk-2UDAB2C5.mjs} +2 -2
- package/dist/{chunk-PEVB3VL4.mjs.map → chunk-2UDAB2C5.mjs.map} +1 -1
- package/dist/{chunk-7DG4UPWE.js → chunk-4BL7TLK3.js} +2 -2
- package/dist/chunk-4BL7TLK3.js.map +1 -0
- package/dist/chunk-4ETIDGYZ.mjs +2 -0
- package/dist/chunk-4ETIDGYZ.mjs.map +1 -0
- package/dist/chunk-555KFWGY.js +4 -0
- package/dist/chunk-555KFWGY.js.map +1 -0
- package/dist/chunk-5ED7FWNY.mjs +2 -0
- package/dist/chunk-5ED7FWNY.mjs.map +1 -0
- package/dist/chunk-5FZAROP6.js +2 -0
- package/dist/chunk-5FZAROP6.js.map +1 -0
- package/dist/chunk-5R6AGO3Z.mjs +2 -0
- package/dist/chunk-5R6AGO3Z.mjs.map +1 -0
- package/dist/chunk-6GCCNBEU.js +2 -0
- package/dist/chunk-6GCCNBEU.js.map +1 -0
- package/dist/chunk-6Q3VGPSF.js +2 -0
- package/dist/chunk-6Q3VGPSF.js.map +1 -0
- package/dist/chunk-6YIRRDCL.mjs +2 -0
- package/dist/chunk-6YIRRDCL.mjs.map +1 -0
- package/dist/chunk-7RXW6XFY.js +2 -0
- package/dist/chunk-7RXW6XFY.js.map +1 -0
- package/dist/chunk-A6VCP3A4.mjs +2 -0
- package/dist/chunk-A6VCP3A4.mjs.map +1 -0
- package/dist/chunk-BZXV77PT.mjs +2 -0
- package/dist/chunk-BZXV77PT.mjs.map +1 -0
- package/dist/{chunk-MG6VV5J2.js → chunk-C3BBJOVV.js} +2 -2
- package/dist/chunk-C3BBJOVV.js.map +1 -0
- package/dist/chunk-CUPG4B5W.mjs +2 -0
- package/dist/chunk-CUPG4B5W.mjs.map +1 -0
- package/dist/chunk-ESF5ZVVF.js +2 -0
- package/dist/chunk-ESF5ZVVF.js.map +1 -0
- package/dist/chunk-HKNWW37T.mjs +2 -0
- package/dist/chunk-HKNWW37T.mjs.map +1 -0
- package/dist/chunk-JSDGZ4NY.js.map +1 -1
- package/dist/chunk-KOYBZORL.js.map +1 -1
- package/dist/chunk-L7Q7OKM5.mjs +2 -0
- package/dist/chunk-L7Q7OKM5.mjs.map +1 -0
- package/dist/chunk-M2G74N43.mjs +2 -0
- package/dist/chunk-M2G74N43.mjs.map +1 -0
- package/dist/chunk-MIMQTH2G.js +2 -0
- package/dist/chunk-MIMQTH2G.js.map +1 -0
- package/dist/chunk-SYN5KU3O.js +2 -0
- package/dist/chunk-SYN5KU3O.js.map +1 -0
- package/dist/chunk-VNYHJT6V.mjs +2 -0
- package/dist/chunk-VNYHJT6V.mjs.map +1 -0
- package/dist/chunk-W3RGQYPD.js.map +1 -1
- package/dist/chunk-X3LVYIJR.mjs +2 -0
- package/dist/chunk-X3LVYIJR.mjs.map +1 -0
- package/dist/consent.js.map +1 -1
- package/dist/course.d.mts +3 -3
- package/dist/course.d.ts +3 -3
- package/dist/course.js +1 -1
- package/dist/course.js.map +1 -1
- package/dist/course.mjs +1 -1
- package/dist/course.mjs.map +1 -1
- package/dist/embed/analytics.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.mts +41 -12
- package/dist/index.d.ts +41 -12
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{instrument-BE8ktMUv.d.mts → instrument-eXaC8hc2.d.mts} +1 -1
- package/dist/{instrument-BE8ktMUv.d.ts → instrument-eXaC8hc2.d.ts} +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/methods-3W34GRYL.js +2 -0
- package/dist/methods-3W34GRYL.js.map +1 -0
- package/dist/methods-LUJSKSQ4.mjs +2 -0
- package/dist/model-files-client-HHDXJDTS.mjs +2 -0
- package/dist/model-files-client-HHDXJDTS.mjs.map +1 -0
- package/dist/model-files-client-P4PORZJ6.js +2 -0
- package/dist/model-files-client-P4PORZJ6.js.map +1 -0
- package/dist/models.d.mts +2 -0
- package/dist/models.d.ts +2 -0
- package/dist/models.js +1 -1
- package/dist/models.js.map +1 -1
- package/dist/models.mjs +1 -1
- package/dist/models.mjs.map +1 -1
- package/dist/returns.d.mts +19 -10
- package/dist/returns.d.ts +19 -10
- package/dist/returns.js +1 -1
- package/dist/returns.js.map +1 -1
- package/dist/returns.mjs +1 -1
- package/dist/{texts-tQtAGJCT.d.mts → texts-LGhuQfA9.d.mts} +28 -3
- package/dist/{texts-CIEFbJTN.d.ts → texts-m6XQGZ6w.d.ts} +28 -3
- package/dist/types-ClFCVzk_.d.ts +121 -0
- package/dist/types-D6LsoycW.d.mts +121 -0
- package/dist/viewer3d.css +2 -0
- package/dist/viewer3d.css.map +1 -0
- package/dist/viewer3d.d.mts +318 -0
- package/dist/viewer3d.d.ts +318 -0
- package/dist/viewer3d.js +2 -0
- package/dist/viewer3d.js.map +1 -0
- package/dist/viewer3d.mjs +63 -0
- package/dist/viewer3d.mjs.map +1 -0
- package/package.json +43 -16
- package/dist/CartProvider-64QEJ7A7.js +0 -2
- package/dist/CartProvider-64QEJ7A7.js.map +0 -1
- package/dist/CartProvider-XKK7A3C2.mjs +0 -2
- package/dist/chunk-2TLWMYXW.mjs +0 -2
- package/dist/chunk-2TLWMYXW.mjs.map +0 -1
- package/dist/chunk-7DG4UPWE.js.map +0 -1
- package/dist/chunk-7TLJKYZD.mjs +0 -2
- package/dist/chunk-7TLJKYZD.mjs.map +0 -1
- package/dist/chunk-AZQOW73L.mjs +0 -4
- package/dist/chunk-AZQOW73L.mjs.map +0 -1
- package/dist/chunk-DY2S42Q6.js +0 -2
- package/dist/chunk-DY2S42Q6.js.map +0 -1
- package/dist/chunk-FGB5FJCI.js +0 -2
- package/dist/chunk-FGB5FJCI.js.map +0 -1
- package/dist/chunk-GLAJPUMA.js +0 -2
- package/dist/chunk-GLAJPUMA.js.map +0 -1
- package/dist/chunk-H4WAQQVO.js +0 -2
- package/dist/chunk-H4WAQQVO.js.map +0 -1
- package/dist/chunk-MG6VV5J2.js.map +0 -1
- package/dist/chunk-Q3DSK66F.mjs +0 -2
- package/dist/chunk-Q3DSK66F.mjs.map +0 -1
- package/dist/chunk-RWEP6IUC.mjs +0 -2
- package/dist/chunk-RWEP6IUC.mjs.map +0 -1
- package/dist/chunk-S4Z4O46O.mjs +0 -2
- package/dist/chunk-S4Z4O46O.mjs.map +0 -1
- package/dist/chunk-VY6CZVVA.js +0 -4
- package/dist/chunk-VY6CZVVA.js.map +0 -1
- package/dist/chunk-XZYCQ655.js +0 -2
- package/dist/chunk-XZYCQ655.js.map +0 -1
- package/dist/chunk-ZIWQ57GY.mjs +0 -2
- package/dist/chunk-ZIWQ57GY.mjs.map +0 -1
- package/dist/methods-NUMVR53L.js +0 -2
- package/dist/methods-NUMVR53L.js.map +0 -1
- package/dist/methods-YKZZ6OVT.mjs +0 -2
- package/dist/types-DP1vxo5E.d.mts +0 -53
- package/dist/types-u8VwmC3j.d.ts +0 -53
- /package/dist/{CartProvider-XKK7A3C2.mjs.map → CartProvider-DGRZDTYC.mjs.map} +0 -0
- /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"]}
|
package/dist/analytics.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as Tracker, a as TrackerOptions, C as ClientEvent } from './instrument-
|
|
2
|
-
export {
|
|
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
|
|
package/dist/analytics.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { T as Tracker, a as TrackerOptions, C as ClientEvent } from './instrument-
|
|
2
|
-
export {
|
|
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
|
|
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
|
package/dist/analytics.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/
|
|
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"}
|
package/dist/analytics.mjs
CHANGED
|
@@ -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-
|
|
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
|
package/dist/catalog.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import {
|
|
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 {
|
|
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
|
|
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
|
package/dist/catalog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/
|
|
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
|
|
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
|