@vizbl/react-room-viewer 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/components/Footer.d.ts +1 -0
  2. package/dist/components/FrameWidthTabsField.d.ts +2 -0
  3. package/dist/components/Header.d.ts +1 -0
  4. package/dist/components/LogoSign.d.ts +1 -0
  5. package/dist/components/MaterialSelectField.d.ts +2 -0
  6. package/dist/components/MaterialSwiper.d.ts +1 -0
  7. package/dist/components/MattingField.d.ts +2 -0
  8. package/dist/components/RoomSwiper.d.ts +1 -0
  9. package/dist/components/SceneControlls.d.ts +0 -0
  10. package/dist/components/Swiper.d.ts +5 -0
  11. package/dist/components/scene/components/AR.d.ts +2 -0
  12. package/dist/components/scene/components/CanvasCaptureBridge.d.ts +8 -0
  13. package/dist/components/scene/components/CanvasWrapper.d.ts +7 -0
  14. package/dist/components/scene/components/Carpet.d.ts +9 -0
  15. package/dist/components/scene/components/Frame.d.ts +6 -0
  16. package/dist/components/scene/components/FramePart.d.ts +8 -0
  17. package/dist/components/scene/components/Matting.d.ts +8 -0
  18. package/dist/components/scene/components/Plane.d.ts +7 -0
  19. package/dist/components/scene/components/Poster.d.ts +7 -0
  20. package/dist/components/scene/components/Room.d.ts +1 -0
  21. package/dist/components/scene/components/RoomBackground.d.ts +1 -0
  22. package/dist/components/scene/hooks/useCarpetCanvases.d.ts +7 -0
  23. package/dist/components/scene/hooks/useCarpetInference.d.ts +9 -0
  24. package/dist/components/scene/hooks/useSceneHelpers.d.ts +4 -0
  25. package/dist/components/scene/hooks/useWallPose.d.ts +7 -0
  26. package/dist/components/scene/index.d.ts +1 -0
  27. package/dist/components/scene/lib/canvas.d.ts +1 -0
  28. package/dist/components/scene/lib/carpet-inference-export.d.ts +8 -0
  29. package/dist/components/scene/lib/carpet-inference.d.ts +12 -0
  30. package/dist/components/scene/lib/carpet-lighting.d.ts +9 -0
  31. package/dist/components/scene/lib/carpet-mask.d.ts +8 -0
  32. package/dist/components/scene/lib/carpet-pose.d.ts +3 -0
  33. package/dist/components/scene/lib/carpet-processing.d.ts +3 -0
  34. package/dist/components/scene/lib/carpet-rendering.d.ts +5 -0
  35. package/dist/components/scene/lib/room-image.d.ts +1 -0
  36. package/dist/config/index.d.ts +14 -0
  37. package/dist/container.d.ts +1 -0
  38. package/dist/context.d.ts +46 -0
  39. package/dist/dialog.d.ts +5 -0
  40. package/dist/hooks/useFrameWidthOptions.d.ts +5 -0
  41. package/dist/hooks/useMattingOptions.d.ts +5 -0
  42. package/dist/i18n.d.ts +90 -0
  43. package/dist/index.css +5085 -0
  44. package/dist/index.d.ts +8 -0
  45. package/dist/index.js +4029 -0
  46. package/dist/locales/en/ns1.d.ts +42 -0
  47. package/dist/locales/ru/ns1.d.ts +42 -0
  48. package/dist/root.d.ts +12 -0
  49. package/dist/types/index.d.ts +45 -0
  50. package/dist/utils/core.d.ts +7 -0
  51. package/dist/utils/device.d.ts +3 -0
  52. package/dist/utils/id.d.ts +1 -0
  53. package/dist/utils/index.d.ts +2 -0
  54. package/dist/utils/room-helpers.d.ts +5 -0
  55. package/dist/utils/scene-load.d.ts +10 -0
  56. package/dist/utils/segmentation.d.ts +8 -0
  57. package/package.json +81 -0
package/dist/index.js ADDED
@@ -0,0 +1,4029 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { Suspense, createContext, use as external_react_use, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo as external_react_useMemo, useRef, useState } from "react";
3
+ import { clsx } from "clsx";
4
+ import { twMerge } from "tailwind-merge";
5
+ import { QueryClient, QueryClientProvider, useInfiniteQuery, useQueries, useQuery as react_query_useQuery } from "@tanstack/react-query";
6
+ import { initReactI18next, useTranslation } from "react-i18next";
7
+ import { Label, Popover, Select, Separator as external_radix_ui_Separator, Slider } from "radix-ui";
8
+ import axios, { AxiosHeaders as external_axios_AxiosHeaders } from "axios";
9
+ import { ACESFilmicToneMapping, CanvasTexture, LinearFilter, MathUtils, NearestFilter, Plane, Quaternion, Raycaster, SRGBColorSpace, TextureLoader, Vector2, Vector3 } from "three";
10
+ import { cva } from "class-variance-authority";
11
+ import { ArrowLeft, ArrowRight, Check, ChevronDownIcon, ChevronUpIcon, CircleIcon, XIcon } from "lucide-react";
12
+ import { Indicator, Item, Root } from "@radix-ui/react-radio-group";
13
+ import { Swiper, SwiperSlide } from "swiper/react";
14
+ import { Slot } from "@radix-ui/react-slot";
15
+ import "swiper/css";
16
+ import { Content, List, Root as react_tabs_Root, Trigger } from "@radix-ui/react-tabs";
17
+ import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
18
+ import { Close, Content as react_dialog_Content, Description, Overlay, Portal, Root as react_dialog_Root, Title } from "@radix-ui/react-dialog";
19
+ import { formatHex, oklch } from "culori";
20
+ import qrcode from "qrcode";
21
+ import { GLTFExporter, USDZExporter } from "three/examples/jsm/Addons.js";
22
+ import { UAParser } from "ua-parser-js";
23
+ import "@google/model-viewer";
24
+ import { Clone, DragControls, Environment, useGLTF, useProgress } from "@react-three/drei";
25
+ import { Canvas, useLoader, useThree } from "@react-three/fiber";
26
+ import i18next from "i18next";
27
+ import i18next_browser_languagedetector from "i18next-browser-languagedetector";
28
+ import { createPortal } from "react-dom";
29
+ const initialState = {
30
+ theme: 'system',
31
+ setTheme: ()=>null
32
+ };
33
+ const ThemeProviderContext = /*#__PURE__*/ createContext(initialState);
34
+ function ThemeProvider({ children, defaultTheme = 'system', storageKey = 'vizbl-theme', root = window.document.documentElement, enableStorage = false, ...props }) {
35
+ const [theme, setTheme] = useState(()=>{
36
+ if (enableStorage) return localStorage.getItem(storageKey) || defaultTheme;
37
+ return defaultTheme;
38
+ });
39
+ useEffect(()=>{
40
+ root.classList.remove('light', 'dark');
41
+ if ('system' === theme) {
42
+ const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
43
+ root.classList.add(systemTheme);
44
+ return;
45
+ }
46
+ root.classList.add(theme);
47
+ }, [
48
+ theme,
49
+ root
50
+ ]);
51
+ const value = {
52
+ theme,
53
+ setTheme: (theme)=>{
54
+ if (enableStorage) localStorage.setItem(storageKey, theme);
55
+ setTheme(theme);
56
+ }
57
+ };
58
+ return /*#__PURE__*/ jsx(ThemeProviderContext, {
59
+ ...props,
60
+ value: value,
61
+ children: children
62
+ });
63
+ }
64
+ function utils_cn(...inputs) {
65
+ return twMerge(clsx(inputs));
66
+ }
67
+ function CheckDone(props) {
68
+ return /*#__PURE__*/ jsx("svg", {
69
+ xmlns: "http://www.w3.org/2000/svg",
70
+ width: "20",
71
+ height: "20",
72
+ viewBox: "0 0 20 20",
73
+ fill: "none",
74
+ ...props,
75
+ children: /*#__PURE__*/ jsx("path", {
76
+ d: "M8.21723 14.9975C8.10298 14.997 7.99004 14.9731 7.8854 14.9272C7.78077 14.8814 7.68665 14.8145 7.60889 14.7308L3.55889 10.4225C3.4075 10.2611 3.3264 10.0463 3.33343 9.82513C3.34046 9.604 3.43505 9.39471 3.59639 9.24332C3.75773 9.09192 3.97261 9.01082 4.19374 9.01785C4.41488 9.02489 4.62417 9.11948 4.77556 9.28082L8.20889 12.9392L15.2172 5.27248C15.2883 5.18392 15.3768 5.11083 15.4772 5.0577C15.5776 5.00458 15.6878 4.97254 15.8011 4.96355C15.9143 4.95456 16.0282 4.96882 16.1357 5.00544C16.2432 5.04207 16.3421 5.10029 16.4263 5.17652C16.5105 5.25276 16.5782 5.3454 16.6253 5.44877C16.6724 5.55213 16.6979 5.66403 16.7001 5.7776C16.7024 5.89116 16.6815 6.00399 16.6385 6.10916C16.5956 6.21433 16.5316 6.30961 16.4506 6.38915L8.83389 14.7225C8.75686 14.8077 8.66308 14.8761 8.55841 14.9234C8.45373 14.9707 8.34042 14.996 8.22556 14.9975H8.21723Z",
77
+ fill: "currentColor"
78
+ })
79
+ });
80
+ }
81
+ function DropDownArrow(props) {
82
+ return /*#__PURE__*/ jsx("svg", {
83
+ xmlns: "http://www.w3.org/2000/svg",
84
+ width: "24",
85
+ height: "24",
86
+ viewBox: "0 0 24 24",
87
+ fill: "none",
88
+ ...props,
89
+ children: /*#__PURE__*/ jsx("path", {
90
+ d: "M12.0002 15.496C11.8686 15.4968 11.7381 15.4716 11.6163 15.4218C11.4944 15.372 11.3836 15.2987 11.2902 15.206L7.29019 11.206C7.10188 11.0177 6.99609 10.7623 6.99609 10.496C6.99609 10.2297 7.10188 9.97434 7.29019 9.78604C7.47849 9.59773 7.73388 9.49194 8.00019 9.49194C8.26649 9.49194 8.52188 9.59773 8.71019 9.78604L12.0002 13.096L15.3002 9.91604C15.3922 9.81375 15.5043 9.73154 15.6295 9.67455C15.7547 9.61756 15.8903 9.58703 16.0279 9.58485C16.1654 9.58267 16.302 9.60889 16.4289 9.66189C16.5559 9.71488 16.6705 9.79349 16.7657 9.89282C16.8609 9.99214 16.9346 10.11 16.9821 10.2391C17.0297 10.3682 17.0501 10.5057 17.042 10.6431C17.034 10.7804 16.9977 10.9146 16.9355 11.0373C16.8732 11.16 16.7863 11.2685 16.6802 11.356L12.6802 15.216C12.4973 15.3924 12.2542 15.4925 12.0002 15.496Z",
91
+ fill: "currentColor"
92
+ })
93
+ });
94
+ }
95
+ function Loader(props) {
96
+ return /*#__PURE__*/ jsxs("svg", {
97
+ xmlns: "http://www.w3.org/2000/svg",
98
+ width: "24",
99
+ height: "24",
100
+ viewBox: "0 0 24 24",
101
+ fill: "none",
102
+ ...props,
103
+ children: [
104
+ /*#__PURE__*/ jsx("path", {
105
+ d: "M12 2C11.7348 2 11.4804 2.10536 11.2929 2.29289C11.1054 2.48043 11 2.73478 11 3V5C11 5.26522 11.1054 5.51957 11.2929 5.70711C11.4804 5.89464 11.7348 6 12 6C12.2652 6 12.5196 5.89464 12.7071 5.70711C12.8946 5.51957 13 5.26522 13 5V3C13 2.73478 12.8946 2.48043 12.7071 2.29289C12.5196 2.10536 12.2652 2 12 2Z",
106
+ fill: "currentColor"
107
+ }),
108
+ /*#__PURE__*/ jsx("path", {
109
+ d: "M21.0002 11H19.0002C18.735 11 18.4806 11.1054 18.2931 11.2929C18.1055 11.4804 18.0002 11.7348 18.0002 12C18.0002 12.2652 18.1055 12.5196 18.2931 12.7071C18.4806 12.8946 18.735 13 19.0002 13H21.0002C21.2654 13 21.5197 12.8946 21.7073 12.7071C21.8948 12.5196 22.0002 12.2652 22.0002 12C22.0002 11.7348 21.8948 11.4804 21.7073 11.2929C21.5197 11.1054 21.2654 11 21.0002 11Z",
110
+ fill: "currentColor"
111
+ }),
112
+ /*#__PURE__*/ jsx("path", {
113
+ d: "M6 12C6 11.7348 5.89464 11.4804 5.70711 11.2929C5.51957 11.1054 5.26522 11 5 11H3C2.73478 11 2.48043 11.1054 2.29289 11.2929C2.10536 11.4804 2 11.7348 2 12C2 12.2652 2.10536 12.5196 2.29289 12.7071C2.48043 12.8946 2.73478 13 3 13H5C5.26522 13 5.51957 12.8946 5.70711 12.7071C5.89464 12.5196 6 12.2652 6 12Z",
114
+ fill: "currentColor"
115
+ }),
116
+ /*#__PURE__*/ jsx("path", {
117
+ d: "M6.22009 5.00298C6.02357 4.82786 5.76656 4.7362 5.50358 4.74744C5.2406 4.75869 4.99234 4.87196 4.8115 5.06322C4.63065 5.25447 4.53143 5.50867 4.5349 5.77187C4.53838 6.03507 4.64426 6.28656 4.83009 6.47298L6.27009 7.86298C6.36673 7.95629 6.48117 8.02917 6.60659 8.07728C6.73201 8.12538 6.86584 8.14773 7.00009 8.14298C7.1348 8.14246 7.26801 8.11474 7.39173 8.06147C7.51546 8.0082 7.62714 7.93048 7.72009 7.83298C7.90634 7.64561 8.01088 7.39216 8.01088 7.12798C8.01088 6.86379 7.90634 6.61034 7.72009 6.42298L6.22009 5.00298Z",
118
+ fill: "currentColor"
119
+ }),
120
+ /*#__PURE__*/ jsx("path", {
121
+ d: "M17 8.14285C17.2575 8.14182 17.5047 8.04153 17.69 7.86285L19.13 6.47285C19.3057 6.2893 19.4049 6.04577 19.4075 5.79172C19.4102 5.53767 19.316 5.29214 19.1441 5.10502C18.9723 4.91789 18.7356 4.80319 18.4823 4.78421C18.2289 4.76524 17.9779 4.84341 17.78 5.00285L16.34 6.42285C16.1538 6.61021 16.0493 6.86366 16.0493 7.12785C16.0493 7.39203 16.1538 7.64548 16.34 7.83285C16.5132 8.01557 16.7489 8.12627 17 8.14285Z",
122
+ fill: "currentColor"
123
+ }),
124
+ /*#__PURE__*/ jsx("path", {
125
+ d: "M12 17.9999C11.7348 17.9999 11.4804 18.1053 11.2929 18.2928C11.1054 18.4803 11 18.7347 11 18.9999V20.9999C11 21.2651 11.1054 21.5195 11.2929 21.707C11.4804 21.8946 11.7348 21.9999 12 21.9999C12.2652 21.9999 12.5196 21.8946 12.7071 21.707C12.8946 21.5195 13 21.2651 13 20.9999V18.9999C13 18.7347 12.8946 18.4803 12.7071 18.2928C12.5196 18.1053 12.2652 17.9999 12 17.9999Z",
126
+ fill: "currentColor"
127
+ }),
128
+ /*#__PURE__*/ jsx("path", {
129
+ d: "M17.7302 16.1399C17.5392 15.9556 17.2829 15.8547 17.0175 15.8594C16.7521 15.864 16.4995 15.974 16.3152 16.1649C16.1309 16.3559 16.0299 16.6122 16.0346 16.8776C16.0393 17.143 16.1492 17.3956 16.3402 17.5799L17.7802 18.9999C17.9702 19.1835 18.2251 19.2845 18.4893 19.2808C18.7535 19.277 19.0055 19.1688 19.1902 18.9799C19.2837 18.8868 19.3579 18.7762 19.4085 18.6543C19.4591 18.5325 19.4851 18.4019 19.4851 18.2699C19.4851 18.138 19.4591 18.0073 19.4085 17.8855C19.3579 17.7637 19.2837 17.653 19.1902 17.5599L17.7302 16.1399Z",
130
+ fill: "currentColor"
131
+ }),
132
+ /*#__PURE__*/ jsx("path", {
133
+ d: "M6.27011 16.141L4.83011 17.531C4.73638 17.624 4.66198 17.7346 4.61121 17.8564C4.56045 17.9783 4.53431 18.109 4.53431 18.241C4.53431 18.373 4.56045 18.5037 4.61121 18.6256C4.66198 18.7474 4.73638 18.858 4.83011 18.951C4.9239 19.0467 5.03596 19.1226 5.15965 19.1741C5.28333 19.2256 5.41612 19.2518 5.55011 19.251C5.79662 19.2531 6.03522 19.1641 6.22011 19.001L7.66011 17.611C7.75466 17.5197 7.83031 17.4107 7.88273 17.2902C7.93516 17.1697 7.96334 17.0401 7.96566 16.9087C7.96798 16.7773 7.9444 16.6467 7.89626 16.5244C7.84813 16.4022 7.77637 16.2905 7.68511 16.196C7.59384 16.1014 7.48484 16.0258 7.36433 15.9734C7.24383 15.9209 7.11418 15.8928 6.98278 15.8904C6.85139 15.8881 6.72082 15.9117 6.59854 15.9598C6.47626 16.008 6.36466 16.0797 6.27011 16.171V16.141Z",
134
+ fill: "currentColor"
135
+ })
136
+ ]
137
+ });
138
+ }
139
+ function Move(props) {
140
+ return /*#__PURE__*/ jsxs("svg", {
141
+ width: "24",
142
+ height: "24",
143
+ viewBox: "0 0 24 24",
144
+ fill: "none",
145
+ xmlns: "http://www.w3.org/2000/svg",
146
+ ...props,
147
+ children: [
148
+ /*#__PURE__*/ jsx("g", {
149
+ clipPath: "url(#clip0_3784_10281)",
150
+ children: /*#__PURE__*/ jsx("path", {
151
+ d: "M20.739 11.3791L18.039 8.67909C17.955 8.59517 17.8554 8.52861 17.7458 8.48319C17.6361 8.43778 17.5186 8.4144 17.4 8.4144C17.2813 8.4144 17.1638 8.43778 17.0541 8.48319C16.9445 8.52861 16.8449 8.59517 16.761 8.67909C16.6771 8.763 16.6105 8.86262 16.5651 8.97226C16.5197 9.0819 16.4963 9.19941 16.4963 9.31808C16.4963 9.43676 16.5197 9.55427 16.5651 9.66391C16.6105 9.77355 16.6771 9.87317 16.761 9.95708L17.922 11.1001H12.9V6.0691L14.061 7.23909C14.1451 7.32246 14.2448 7.38843 14.3545 7.43322C14.4641 7.478 14.5815 7.50073 14.7 7.50009C14.8787 7.50113 15.0538 7.44891 15.2027 7.35009C15.3517 7.25127 15.4679 7.11032 15.5364 6.94523C15.605 6.78013 15.6228 6.59835 15.5876 6.42308C15.5525 6.24781 15.4659 6.08698 15.339 5.9611L12.639 3.26111C12.4705 3.09326 12.2423 2.99902 12.0045 2.99902C11.7666 2.99902 11.5385 3.09326 11.37 3.26111L8.66999 5.9611C8.58608 6.04442 8.5194 6.14346 8.47376 6.25255C8.42812 6.36164 8.40441 6.47866 8.404 6.59691C8.40358 6.71517 8.42646 6.83235 8.47132 6.94176C8.51619 7.05117 8.58217 7.15068 8.66549 7.23459C8.74882 7.31851 8.84785 7.38519 8.95695 7.43083C9.06604 7.47647 9.18306 7.50017 9.30131 7.50059C9.54014 7.50144 9.76952 7.40737 9.93899 7.23909L11.1 6.0781V11.1001H6.069L7.239 9.93908C7.40847 9.76961 7.50368 9.53975 7.50368 9.30008C7.50368 9.06041 7.40847 8.83056 7.239 8.66109C7.06953 8.49161 6.83967 8.39641 6.6 8.39641C6.36033 8.39641 6.13048 8.49161 5.961 8.66109L3.26101 11.3611C3.17764 11.4452 3.11167 11.5449 3.06689 11.6546C3.0221 11.7642 2.99938 11.8816 3.00002 12.0001C2.99933 12.1185 3.02203 12.2359 3.06682 12.3456C3.11161 12.4552 3.1776 12.555 3.26101 12.6391L5.961 15.3391C6.0451 15.4225 6.14483 15.4885 6.25449 15.5333C6.36414 15.578 6.48156 15.6007 6.6 15.6001C6.77896 15.6015 6.95429 15.5496 7.10354 15.4508C7.25279 15.3521 7.36918 15.211 7.43781 15.0458C7.50643 14.8805 7.52417 14.6985 7.48876 14.5231C7.45334 14.3476 7.36638 14.1868 7.239 14.0611L6.078 12.9001H11.1V17.9311L9.93899 16.7611C9.76952 16.5916 9.53966 16.4964 9.29999 16.4964C9.06032 16.4964 8.83047 16.5916 8.661 16.7611C8.49152 16.9305 8.39631 17.1604 8.39631 17.4001C8.39631 17.6397 8.49152 17.8696 8.661 18.0391L11.361 20.739C11.5297 20.9065 11.7578 21.0004 11.9955 21.0004C12.2332 21.0004 12.4612 20.9065 12.63 20.739L15.33 18.0391C15.4139 17.9551 15.4805 17.8555 15.5259 17.7459C15.5713 17.6362 15.5947 17.5187 15.5947 17.4001C15.5947 17.2814 15.5713 17.1639 15.5259 17.0542C15.4805 16.9446 15.4139 16.845 15.33 16.7611C15.2461 16.6771 15.1464 16.6106 15.0368 16.5652C14.9272 16.5198 14.8096 16.4964 14.691 16.4964C14.5723 16.4964 14.4548 16.5198 14.3452 16.5652C14.2355 16.6106 14.1359 16.6771 14.052 16.7611L12.9 17.9221V12.9001H17.931L16.761 14.0611C16.6558 14.166 16.5781 14.2952 16.5349 14.4374C16.4917 14.5795 16.4843 14.7301 16.5134 14.8758C16.5424 15.0215 16.607 15.1578 16.7013 15.2725C16.7957 15.3873 16.917 15.4769 17.0544 15.5335C17.164 15.5784 17.2815 15.6011 17.4 15.6001C17.5184 15.6008 17.6359 15.5782 17.7455 15.5334C17.8552 15.4886 17.9549 15.4226 18.039 15.3391L20.739 12.6391C20.8224 12.555 20.8884 12.4552 20.9331 12.3456C20.9779 12.2359 21.0006 12.1185 21 12.0001C20.9966 11.7671 20.9031 11.5445 20.739 11.3791Z",
152
+ fill: "currentColor"
153
+ })
154
+ }),
155
+ /*#__PURE__*/ jsx("defs", {
156
+ children: /*#__PURE__*/ jsx("clipPath", {
157
+ id: "clip0_3784_10281",
158
+ children: /*#__PURE__*/ jsx("rect", {
159
+ width: "24",
160
+ height: "24",
161
+ fill: "currentColor"
162
+ })
163
+ })
164
+ })
165
+ ]
166
+ });
167
+ }
168
+ function Paint(props) {
169
+ return /*#__PURE__*/ jsxs("svg", {
170
+ xmlns: "http://www.w3.org/2000/svg",
171
+ width: "24",
172
+ height: "24",
173
+ viewBox: "0 0 24 24",
174
+ fill: "none",
175
+ ...props,
176
+ children: [
177
+ /*#__PURE__*/ jsx("path", {
178
+ d: "M19.5398 5.08113C18.5372 4.08551 17.3458 3.30019 16.0356 2.77129C14.7253 2.24238 13.3226 1.98054 11.9098 2.00113C9.25763 1.9945 6.71146 3.04171 4.83141 4.91238C2.95135 6.78306 1.89143 9.32396 1.8848 11.9761C1.87817 14.6283 2.92538 17.1745 4.79605 19.0545C6.66673 20.9346 9.20763 21.9945 11.8598 22.0011C12.4313 22.0108 12.9898 21.8305 13.4477 21.4884C13.9057 21.1464 14.237 20.6619 14.3898 20.1111C14.4872 19.7134 14.4862 19.2979 14.3869 18.9006C14.2875 18.5033 14.0929 18.1362 13.8198 17.8311C13.7562 17.7594 13.7146 17.6709 13.7 17.5761C13.6854 17.4814 13.6984 17.3845 13.7374 17.2969C13.7765 17.2094 13.8399 17.1349 13.9201 17.0825C14.0003 17.03 14.0939 17.0018 14.1898 17.0011H15.8398C17.3927 17.0084 18.8909 16.4279 20.0336 15.3761C21.1762 14.3244 21.8786 12.8794 21.9998 11.3311C22.037 10.1767 21.838 9.02692 21.415 7.95218C20.992 6.87745 20.3539 5.90046 19.5398 5.08113ZM15.8798 15.0011H14.2298C13.7479 14.9984 13.2756 15.1355 12.8701 15.3959C12.4647 15.6563 12.1435 16.0288 11.9455 16.4681C11.7476 16.9075 11.6813 17.3948 11.7549 17.8711C11.8284 18.3473 12.0385 18.792 12.3598 19.1511C12.4224 19.2142 12.4666 19.2932 12.4877 19.3795C12.5088 19.4658 12.5061 19.5562 12.4798 19.6411C12.4298 19.8511 12.1998 19.9811 11.8898 20.0011C10.754 19.9869 9.63429 19.7309 8.60516 19.2502C7.57603 18.7695 6.66109 18.0751 5.92126 17.2132C5.18142 16.3513 4.63365 15.3417 4.31441 14.2516C3.99517 13.1615 3.91178 12.016 4.0698 10.8911C4.35496 9.00107 5.29935 7.27276 6.73593 6.01186C8.17251 4.75096 10.0087 4.03873 11.9198 4.00113H11.9998C13.1306 3.98632 14.253 4.19813 15.3007 4.62407C16.3483 5.05001 17.3001 5.68144 18.0998 6.48113C18.7238 7.10552 19.2141 7.85048 19.5407 8.67057C19.8674 9.49066 20.0236 10.3687 19.9998 11.2511C19.8963 12.2742 19.4184 13.2229 18.6579 13.9151C17.8974 14.6072 16.9081 14.994 15.8798 15.0011Z",
179
+ fill: "currentColor"
180
+ }),
181
+ /*#__PURE__*/ jsx("path", {
182
+ d: "M11.9996 8.00025C12.3974 8.00025 12.7789 7.84222 13.0602 7.56091C13.3415 7.27961 13.4996 6.89808 13.4996 6.50025C13.4996 6.10243 13.3415 5.7209 13.0602 5.43959C12.7789 5.15829 12.3974 5.00025 11.9996 5.00025C11.6018 5.00025 11.2202 5.15829 10.9389 5.43959C10.6576 5.7209 10.4996 6.10243 10.4996 6.50025C10.4996 6.89808 10.6576 7.27961 10.9389 7.56091C11.2202 7.84222 11.6018 8.00025 11.9996 8.00025Z",
183
+ fill: "currentColor"
184
+ }),
185
+ /*#__PURE__*/ jsx("path", {
186
+ d: "M15.2496 7.20116C15.079 7.29965 14.9294 7.43079 14.8094 7.58709C14.6895 7.74339 14.6014 7.92179 14.5504 8.1121C14.4994 8.30242 14.4864 8.50092 14.512 8.69627C14.5377 8.89162 14.6016 9.08 14.7001 9.25066C14.7986 9.42131 14.9298 9.57089 15.0861 9.69086C15.2424 9.81084 15.4208 9.89885 15.6111 9.94987C15.8014 10.0009 15.9999 10.0139 16.1952 9.98825C16.3906 9.96256 16.579 9.89865 16.7496 9.80016C17.0944 9.60125 17.3461 9.27352 17.4492 8.88907C17.5524 8.50462 17.4985 8.09494 17.2996 7.75016C17.1007 7.40538 16.773 7.15373 16.3885 7.05059C16.0041 6.94744 15.5944 7.00125 15.2496 7.20016V7.20116Z",
187
+ fill: "currentColor"
188
+ }),
189
+ /*#__PURE__*/ jsx("path", {
190
+ d: "M8.74976 7.20016C8.57904 7.1018 8.39062 7.03803 8.19526 7.01249C7.99989 6.98695 7.80141 7.00014 7.61114 7.0513C7.42088 7.10247 7.24255 7.19061 7.08634 7.31069C6.93014 7.43077 6.79912 7.58045 6.70076 7.75116C6.6024 7.92188 6.53863 8.1103 6.51308 8.30567C6.48754 8.50103 6.50073 8.69951 6.5519 8.88978C6.60306 9.08005 6.6912 9.25837 6.81128 9.41458C6.93137 9.57078 7.08104 9.7018 7.25176 9.80016C7.59654 9.99881 8.00611 10.0524 8.39037 9.94903C8.77464 9.84569 9.10211 9.59394 9.30076 9.24916C9.4994 8.90438 9.55295 8.49481 9.44962 8.11055C9.34629 7.72629 9.09454 7.39881 8.74976 7.20016Z",
191
+ fill: "currentColor"
192
+ }),
193
+ /*#__PURE__*/ jsx("path", {
194
+ d: "M6.15933 11.2603C5.9133 11.4264 5.7221 11.6618 5.60993 11.9366C5.49776 12.2115 5.46967 12.5135 5.52921 12.8043C5.58876 13.0951 5.73326 13.3617 5.94443 13.5704C6.1556 13.779 6.42393 13.9204 6.71546 13.9764C7.00698 14.0325 7.30858 14.0008 7.58208 13.8853C7.85557 13.7699 8.08866 13.5759 8.25183 13.3279C8.415 13.0799 8.50091 12.789 8.49868 12.4922C8.49646 12.1953 8.4062 11.9058 8.23933 11.6603C8.12971 11.4966 7.98873 11.3563 7.82454 11.2475C7.66035 11.1387 7.47622 11.0635 7.28279 11.0263C7.08936 10.9891 6.89047 10.9906 6.69763 11.0308C6.5048 11.0709 6.32183 11.1489 6.15933 11.2603Z",
195
+ fill: "currentColor"
196
+ })
197
+ ]
198
+ });
199
+ }
200
+ function Share(props) {
201
+ return /*#__PURE__*/ jsxs("svg", {
202
+ width: "24",
203
+ height: "24",
204
+ viewBox: "0 0 24 24",
205
+ fill: "none",
206
+ xmlns: "http://www.w3.org/2000/svg",
207
+ ...props,
208
+ children: [
209
+ /*#__PURE__*/ jsx("path", {
210
+ d: "M20 15.9998C20.5522 15.9998 20.9999 16.4476 21 16.9998V18.9998C20.9999 19.7952 20.6835 20.5584 20.1211 21.1209C19.5585 21.6833 18.7955 21.9998 18 21.9998H6C5.20446 21.9998 4.44149 21.6833 3.87891 21.1209C3.31646 20.5584 3.00012 19.7952 3 18.9998V16.9998C3.00009 16.4476 3.44777 15.9998 4 15.9998C4.55223 15.9998 4.99991 16.4476 5 16.9998V18.9998C5.00012 19.2648 5.1056 19.5194 5.29297 19.7068C5.48048 19.8942 5.7349 19.9998 6 19.9998H18C18.2651 19.9998 18.5195 19.8942 18.707 19.7068C18.8944 19.5194 18.9999 19.2648 19 18.9998V16.9998C19.0001 16.4476 19.4478 15.9998 20 15.9998Z",
211
+ fill: "currentColor"
212
+ }),
213
+ /*#__PURE__*/ jsx("path", {
214
+ d: "M11.3691 3.22437C11.7619 2.90402 12.3409 2.92662 12.707 3.29273L17.707 8.29273C18.0973 8.68328 18.0975 9.31635 17.707 9.7068C17.3166 10.097 16.6834 10.097 16.293 9.7068L13 6.41383V15.9998C12.9997 16.5518 12.552 16.9997 12 16.9998C11.4479 16.9998 11.0003 16.5518 11 15.9998V6.41383L7.70703 9.7068C7.31657 10.097 6.68343 10.097 6.29297 9.7068C5.90252 9.31635 5.90268 8.68328 6.29297 8.29273L11.293 3.29273L11.3691 3.22437Z",
215
+ fill: "currentColor"
216
+ })
217
+ ]
218
+ });
219
+ }
220
+ function Vizbl({ className, ...props }) {
221
+ return /*#__PURE__*/ jsxs("svg", {
222
+ xmlns: "http://www.w3.org/2000/svg",
223
+ width: "24",
224
+ height: "24",
225
+ viewBox: "0 0 24 24",
226
+ fill: "none",
227
+ className: utils_cn('size-6', className),
228
+ ...props,
229
+ children: [
230
+ /*#__PURE__*/ jsx("path", {
231
+ fillRule: "evenodd",
232
+ clipRule: "evenodd",
233
+ d: "M12.0625 1.70412C12.1393 1.70359 12.2136 1.7151 12.2842 1.73439C12.6576 1.76978 13.0261 1.88527 13.3604 2.08303L19.7012 5.84084L19.7031 5.84181C20.1036 6.08032 20.4348 6.42322 20.665 6.83498C20.8952 7.24671 21.0168 7.71415 21.0166 8.19142V8.19338L20.998 15.7403C20.9979 16.1352 20.9124 16.526 20.751 16.8848C20.7319 16.9557 20.7049 17.026 20.667 17.0928C20.6355 17.1482 20.5983 17.1987 20.5576 17.2442C20.3296 17.6 20.023 17.8979 19.6631 18.1133L13.2998 21.9072C12.4738 22.3996 11.4611 22.4004 10.6436 21.917L10.6416 21.916L4.30078 18.1582L4.29883 18.1572C3.91023 17.9258 3.58764 17.5957 3.3584 17.2002C3.35105 17.1885 3.34181 17.1773 3.33496 17.1651C3.32803 17.1526 3.32268 17.1387 3.31641 17.126C3.09976 16.7236 2.98517 16.2703 2.98535 15.8076V15.8057L3.00391 8.25588C3.00603 7.28279 3.51293 6.37777 4.33984 5.88478L10.7021 2.09181C11.0682 1.87369 11.4708 1.75231 11.877 1.72658C11.9365 1.71245 11.999 1.70462 12.0625 1.70412ZM5.16895 7.36818C4.87108 7.54583 4.6844 7.87497 4.68359 8.23635L4.66602 15.3838L9.45801 12.5254C9.51085 12.7718 9.60256 13.0142 9.73535 13.2442C9.90127 13.5315 10.1161 13.7727 10.3633 13.9639L5.49512 16.8662L11.499 20.4248C11.8036 20.6044 12.1738 20.6008 12.4707 20.4238L18.5107 16.8233L13.4971 13.877C13.9543 13.482 14.2391 12.9423 14.3242 12.3672L19.3193 15.3037L19.3379 8.21291L19.3369 8.21388C19.3369 8.03517 19.2923 7.85955 19.2061 7.7051C19.1196 7.55056 18.9947 7.42151 18.8447 7.33205L12.8887 3.80275L12.8535 9.73439C12.3304 9.51037 11.7366 9.46806 11.1738 9.63576L11.21 3.76662L5.16895 7.36818Z",
234
+ fill: "currentColor"
235
+ }),
236
+ /*#__PURE__*/ jsx("path", {
237
+ fillRule: "evenodd",
238
+ clipRule: "evenodd",
239
+ d: "M9.91641 8.60885C11.7928 7.52595 14.1912 8.16845 15.2748 10.0444C16.3582 11.9209 15.7156 14.3211 13.8393 15.4047C11.9627 16.4879 9.56339 15.8446 8.47989 13.9682C7.39671 12.0916 8.03987 9.69227 9.91641 8.60885ZM13.8031 10.894C13.189 9.83118 11.8293 9.46704 10.766 10.0805C9.7026 10.6945 9.33693 12.0551 9.95059 13.1186C10.5646 14.1821 11.9261 14.5469 12.9897 13.9331C14.0529 13.3189 14.4171 11.9575 13.8031 10.894Z",
240
+ fill: "currentColor"
241
+ })
242
+ ]
243
+ });
244
+ }
245
+ const SPINNER_LOGO_PATH = 'M15.7495 62.7798L46.4936 45.0297M46.4936 45.0297L77.2661 62.5451M46.4936 45.0297L46.7111 9.62225M49.9213 79.4237L74.5719 65.1917C75.6436 64.5705 76.5344 63.6811 77.1541 62.6137C77.7738 61.5462 78.1003 60.3385 78.1007 59.1129L78.1729 30.7811C78.1734 29.5494 77.851 28.3427 77.2373 27.2798C76.6236 26.2169 75.7398 25.3343 74.6729 24.7189L50.1007 10.6155C47.926 9.3694 45.2455 9.37719 43.0689 10.6339L18.4183 24.8659C16.2385 26.1244 14.8946 28.4401 14.8895 30.9447L14.8173 59.2766C14.8168 60.5082 15.1392 61.7149 15.7529 62.7778C16.3666 63.8408 17.2504 64.7233 18.3173 65.3387L42.8895 79.4421C45.0642 80.6882 47.7447 80.6805 49.9213 79.4237Z';
246
+ const SPINNER_SUCCESS_PATH = 'M13.6758 28.1023C13.3559 28.101 13.0397 28.0341 12.7467 27.9057C12.4537 27.7772 12.1902 27.59 11.9725 27.3557L0.632479 15.2923C0.208575 14.8406 -0.0185097 14.2389 0.00118164 13.6198C0.020873 13.0006 0.285727 12.4146 0.737479 11.9907C1.18923 11.5668 1.79088 11.3397 2.41006 11.3594C3.02924 11.3791 3.61524 11.6439 4.03915 12.0957L13.6525 22.339L33.2758 0.87233C33.475 0.624349 33.7227 0.419696 34.0038 0.270944C34.2849 0.122191 34.5935 0.0324784 34.9105 0.00731496C35.2276 -0.0178485 35.5464 0.0220682 35.8475 0.124615C36.1485 0.227162 36.4254 0.390174 36.6612 0.603637C36.897 0.8171 37.0866 1.07651 37.2185 1.36593C37.3503 1.65535 37.4216 1.96867 37.428 2.28665C37.4344 2.60463 37.3756 2.92056 37.2555 3.21502C37.1353 3.50949 36.9562 3.77627 36.7291 3.999L15.4025 27.3323C15.1868 27.5709 14.9242 27.7625 14.6311 27.895C14.338 28.0275 14.0208 28.0981 13.6991 28.1023H13.6758Z';
247
+ const SPINNER_ERROR_PATH = 'M17.3045 14.0095L27.3378 3.99955C27.5554 3.78199 27.728 3.52371 27.8457 3.23946C27.9635 2.95521 28.0241 2.65055 28.0241 2.34288C28.0241 2.03521 27.9635 1.73055 27.8457 1.4463C27.728 1.16205 27.5554 0.903769 27.3378 0.686213C27.1203 0.468656 26.862 0.296082 26.5778 0.178341C26.2935 0.0606 25.9889 -4.58466e-09 25.6812 0C25.3735 4.58466e-09 25.0689 0.0606 24.7846 0.178341C24.5003 0.296082 24.2421 0.468656 24.0245 0.686213L14.0145 10.7195L4.00451 0.686213C3.56514 0.246838 2.96921 -4.62957e-09 2.34784 0C1.72647 4.62957e-09 1.13055 0.246838 0.691177 0.686213C0.251802 1.12559 0.0049634 1.72151 0.0049634 2.34288C0.00496339 2.96425 0.251802 3.56017 0.691177 3.99955L10.7245 14.0095L0.691177 24.0195C0.472196 24.2363 0.29836 24.4943 0.179723 24.7787C0.0610849 25.063 0 25.3681 0 25.6762C0 25.9843 0.0610849 26.2894 0.179723 26.5737C0.29836 26.8581 0.472196 27.1161 0.691177 27.3329C0.908091 27.5516 1.16616 27.7252 1.4505 27.8436C1.73484 27.9621 2.03982 28.0231 2.34784 28.0231C2.65587 28.0231 2.96085 27.9621 3.24519 27.8436C3.52953 27.7252 3.7876 27.5516 4.00451 27.3329L14.0145 17.2995L24.0245 27.3329C24.296 27.6078 24.6314 27.8111 25.0007 27.9245C25.3701 28.0379 25.7618 28.0579 26.1407 27.9828C26.5197 27.9077 26.8741 27.7397 27.1722 27.4939C27.4703 27.2481 27.7028 26.9322 27.8488 26.5745C28.0272 26.1459 28.0736 25.6738 27.9821 25.2187C27.8907 24.7636 27.6655 24.346 27.3355 24.0195L17.3045 14.0095Z';
248
+ const SPINNER_LOGO_COLOR = 'var(--vizbl-borders-medium)';
249
+ function Spinner({ className, variant: type = 'default', size = 'default', state = 'loading', role, 'aria-live': ariaLive, 'aria-label': ariaLabel, ...props }) {
250
+ const resolvedAriaLabel = ariaLabel ?? ('success' === state ? 'Success' : 'error' === state ? 'Error' : 'Loading');
251
+ switch(type){
252
+ case 'logo':
253
+ return /*#__PURE__*/ jsxs("svg", {
254
+ ...props,
255
+ role: role ?? 'status',
256
+ "aria-live": ariaLive ?? 'polite',
257
+ "aria-label": resolvedAriaLabel,
258
+ width: "93",
259
+ height: "91",
260
+ viewBox: "0 0 93 91",
261
+ fill: "none",
262
+ xmlns: "http://www.w3.org/2000/svg",
263
+ "data-slot": "spinner",
264
+ "data-size": size,
265
+ "data-state": state,
266
+ className: utils_cn('spinner-logo shrink-0', 'sm' === size ? 'size-9' : 'size-22', className),
267
+ children: [
268
+ /*#__PURE__*/ jsx("g", {
269
+ className: "spinner-logo__intro-shell",
270
+ children: /*#__PURE__*/ jsx("path", {
271
+ d: SPINNER_LOGO_PATH,
272
+ className: "spinner-logo__outline spinner-logo__outline--intro",
273
+ vectorEffect: 'sm' === size ? void 0 : 'non-scaling-stroke'
274
+ })
275
+ }),
276
+ /*#__PURE__*/ jsx("circle", {
277
+ className: "spinner-logo__intro-orb",
278
+ cx: "46.27",
279
+ cy: "45.8",
280
+ r: "11.18",
281
+ fill: "white",
282
+ stroke: SPINNER_LOGO_COLOR,
283
+ strokeWidth: "5",
284
+ strokeLinecap: "round",
285
+ strokeLinejoin: "round"
286
+ }),
287
+ /*#__PURE__*/ jsx("circle", {
288
+ className: "spinner-logo__result-ring",
289
+ cx: "46.5",
290
+ cy: "45.5",
291
+ r: "33.46",
292
+ fill: "white",
293
+ stroke: SPINNER_LOGO_COLOR,
294
+ strokeWidth: "5",
295
+ strokeLinecap: "round",
296
+ strokeLinejoin: "round"
297
+ }),
298
+ /*#__PURE__*/ jsx("g", {
299
+ transform: "translate(27.786 31.4489)",
300
+ children: /*#__PURE__*/ jsx("g", {
301
+ className: "spinner-logo__result-glyph spinner-logo__result-glyph--success",
302
+ children: /*#__PURE__*/ jsx("path", {
303
+ d: SPINNER_SUCCESS_PATH,
304
+ fill: SPINNER_LOGO_COLOR
305
+ })
306
+ })
307
+ }),
308
+ /*#__PURE__*/ jsx("g", {
309
+ transform: "translate(32.4879 31.4885)",
310
+ children: /*#__PURE__*/ jsx("g", {
311
+ className: "spinner-logo__result-glyph spinner-logo__result-glyph--error",
312
+ children: /*#__PURE__*/ jsx("path", {
313
+ d: SPINNER_ERROR_PATH,
314
+ fill: SPINNER_LOGO_COLOR
315
+ })
316
+ })
317
+ })
318
+ ]
319
+ });
320
+ case 'default':
321
+ return /*#__PURE__*/ jsx(Loader, {
322
+ ...props,
323
+ role: role ?? 'status',
324
+ "aria-live": ariaLive ?? 'polite',
325
+ "aria-label": ariaLabel ?? 'Loading',
326
+ "data-slot": "spinner",
327
+ className: utils_cn('size-4 animate-spin', className)
328
+ });
329
+ default:
330
+ {
331
+ const _exhaustiveCheck = type;
332
+ return _exhaustiveCheck;
333
+ }
334
+ }
335
+ }
336
+ function separator_Separator({ className, orientation = 'horizontal', decorative = true, ...props }) {
337
+ return /*#__PURE__*/ jsx(external_radix_ui_Separator.Root, {
338
+ "data-slot": "separator",
339
+ decorative: decorative,
340
+ orientation: orientation,
341
+ className: utils_cn('bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px', className),
342
+ ...props
343
+ });
344
+ }
345
+ const IS_SERVER = 'undefined' == typeof window;
346
+ function useMediaQuery(query, { defaultValue = false, initializeWithValue = true } = {}) {
347
+ const getMatches = (query)=>{
348
+ if (IS_SERVER) return defaultValue;
349
+ return window.matchMedia(query).matches;
350
+ };
351
+ const [matches, setMatches] = useState(()=>{
352
+ if (initializeWithValue) return getMatches(query);
353
+ return defaultValue;
354
+ });
355
+ useEffect(()=>{
356
+ const matchMedia = window.matchMedia(query);
357
+ const handleChange = ()=>{
358
+ setMatches(matchMedia.matches);
359
+ };
360
+ handleChange();
361
+ matchMedia.addEventListener('change', handleChange);
362
+ return ()=>{
363
+ matchMedia.removeEventListener('change', handleChange);
364
+ };
365
+ }, [
366
+ query
367
+ ]);
368
+ return matches;
369
+ }
370
+ const DEFAULT_API_BASE_URL = 'https://api.vizbl.us';
371
+ const DEFAULT_API_TIMEOUT = 30000;
372
+ function createDefaultClient() {
373
+ return axios.create({
374
+ baseURL: DEFAULT_API_BASE_URL,
375
+ timeout: DEFAULT_API_TIMEOUT
376
+ });
377
+ }
378
+ let apiClient = createDefaultClient();
379
+ let recoveryClient = createDefaultClient();
380
+ let accessTokenResolver = null;
381
+ let headersResolver = null;
382
+ new Set([
383
+ 'common',
384
+ 'delete',
385
+ 'get',
386
+ 'head',
387
+ 'post',
388
+ 'put',
389
+ 'patch',
390
+ 'options',
391
+ 'purge',
392
+ 'link',
393
+ 'unlink'
394
+ ]);
395
+ function hasDefaultCommonHeader(client, headerName) {
396
+ const commonHeaders = client.defaults.headers.common;
397
+ if (void 0 === commonHeaders) return false;
398
+ return external_axios_AxiosHeaders.from(commonHeaders).has(headerName);
399
+ }
400
+ function withResolvedHeaders(config) {
401
+ const headers = external_axios_AxiosHeaders.from(config.headers);
402
+ if (null !== accessTokenResolver) {
403
+ const token = accessTokenResolver()?.trim();
404
+ if (token && !headers.has('Authorization')) headers.set('Authorization', `Bearer ${token}`);
405
+ }
406
+ if (null !== headersResolver) {
407
+ const customHeaders = headersResolver();
408
+ for (const [key, value] of Object.entries(customHeaders))if (value && !headers.has(key)) headers.set(key, value);
409
+ }
410
+ config.headers = headers;
411
+ return config;
412
+ }
413
+ function isFormDataPayload(data) {
414
+ if ('undefined' != typeof FormData && data instanceof FormData) return true;
415
+ if (null === data || 'object' != typeof data) return false;
416
+ const maybeFormData = data;
417
+ return maybeFormData.constructor?.name === 'FormData' || 'function' == typeof maybeFormData.getBoundary;
418
+ }
419
+ function withNormalizedContentType(client, config) {
420
+ if (void 0 === config.data) return config;
421
+ if (isFormDataPayload(config.data)) return config;
422
+ const headers = external_axios_AxiosHeaders.from(config.headers);
423
+ if (!headers.has('Content-Type') && !hasDefaultCommonHeader(client, 'Content-Type')) headers.set('Content-Type', 'application/json');
424
+ config.headers = headers;
425
+ return config;
426
+ }
427
+ function toRuntimeAxiosRequestConfig(config) {
428
+ const { skipUnauthorizedRecovery: _skipUnauthorizedRecovery, ...requestConfig } = config;
429
+ return requestConfig;
430
+ }
431
+ async function performRequest(client, config) {
432
+ const requestConfig = withNormalizedContentType(client, withResolvedHeaders({
433
+ ...config
434
+ }));
435
+ try {
436
+ return await client.request(requestConfig);
437
+ } catch (error) {
438
+ throw error;
439
+ }
440
+ }
441
+ async function requestRuntime(config) {
442
+ const client = config.skipUnauthorizedRecovery ? recoveryClient : apiClient;
443
+ return performRequest(client, toRuntimeAxiosRequestConfig(config));
444
+ }
445
+ async function client_client(config) {
446
+ return requestRuntime(config);
447
+ }
448
+ const objFetchQueryKey = (data = {})=>[
449
+ {
450
+ url: '/obj/Fetch'
451
+ },
452
+ ...data ? [
453
+ data
454
+ ] : []
455
+ ];
456
+ async function objFetch(data, config = {}) {
457
+ const { client: request = client_client, ...requestConfig } = config;
458
+ const requestData = data;
459
+ const res = await request({
460
+ method: "POST",
461
+ url: "/obj/Fetch",
462
+ data: requestData,
463
+ ...requestConfig
464
+ });
465
+ return res.data;
466
+ }
467
+ async function objGetPublic(data, config = {}) {
468
+ const { client: request = client_client, ...requestConfig } = config;
469
+ const requestData = data;
470
+ const res = await request({
471
+ method: "POST",
472
+ url: "/obj/GetPublic",
473
+ data: requestData,
474
+ ...requestConfig
475
+ });
476
+ return res.data;
477
+ }
478
+ var room_example_1_namespaceObject = JSON.parse('{"camera":{"fov":45},"floor":{"z":185.0,"pitchDeg":-87.0,"rollDeg":0},"walls":[{"id":"backWall","z":580.0,"pitchDeg":0,"rollDeg":0},{"id":"rightWall","z":315.0,"pitchDeg":0,"rollDeg":-90.0},{"id":"leftWall","z":560.0,"pitchDeg":0,"rollDeg":55.0},{"id":"ceiling","z":1000.0,"pitchDeg":90.0,"rollDeg":0}]}');
479
+ var room_example_2_namespaceObject = JSON.parse('{"camera":{"fov":45},"floor":{"z":205.0,"pitchDeg":-87.0,"rollDeg":0},"walls":[{"id":"backWall","z":570.0,"pitchDeg":0,"rollDeg":0},{"id":"rightWall","z":315.0,"pitchDeg":0,"rollDeg":-90.0},{"id":"leftWall","z":1000.0,"pitchDeg":0,"rollDeg":90.0},{"id":"ceiling","z":1000.0,"pitchDeg":90.0,"rollDeg":0}]}');
480
+ var room_example_3_namespaceObject = JSON.parse('{"camera":{"fov":45},"floor":{"z":185.0,"pitchDeg":-90.0,"rollDeg":0},"walls":[{"id":"backWall","z":550.0,"pitchDeg":0,"rollDeg":0},{"id":"rightWall","z":220.0,"pitchDeg":0,"rollDeg":-90.0},{"id":"leftWall","z":218.0,"pitchDeg":0,"rollDeg":90.0},{"id":"ceiling","z":185.0,"pitchDeg":90.0,"rollDeg":0}]}');
481
+ var room_example_4_namespaceObject = JSON.parse('{"camera":{"fov":45},"maskUrl":"https://vizbl-2.s3.us-west-1.amazonaws.com/public/room-example/room-example-4-mask.png","floor":{"z":76.41371488571167,"pitchDeg":-88.08860421180725,"rollDeg":-1.1962583474814892},"walls":[]}');
482
+ var room_example_5_namespaceObject = JSON.parse('{"camera":{"fov":45},"maskUrl":"https://vizbl-2.s3.us-west-1.amazonaws.com/public/room-example/room-example-5-mask.png","floor":{"z":86.81210279464722,"pitchDeg":-84.67234969139099,"rollDeg":-4.7433755826205015e-2},"walls":[]}');
483
+ var room_example_6_namespaceObject = JSON.parse('{"camera":{"fov":45},"maskUrl":"https://vizbl-2.s3.us-west-1.amazonaws.com/public/room-example/room-example-6-mask.png","floor":{"z":124.4072675704956,"pitchDeg":-70.20531892776489,"rollDeg":-0.7209355384111404},"walls":[]}');
484
+ const axisX = new Vector3(1, 0, 0);
485
+ const axisY = new Vector3(0, 1, 0);
486
+ const axisZ = new Vector3(0, 0, 1);
487
+ function calculatePlanePose(z, pitchDeg, rollDeg, camera) {
488
+ const direction = axisZ.clone().applyAxisAngle(axisX, MathUtils.degToRad(pitchDeg)).applyAxisAngle(axisY, MathUtils.degToRad(rollDeg)).applyQuaternion(camera.quaternion).normalize();
489
+ const quaternion = new Quaternion().setFromUnitVectors(axisZ, direction);
490
+ const position = camera.position.clone().addScaledVector(direction, -(z * ZSCALE));
491
+ return {
492
+ position: position.toArray(),
493
+ quaternion: quaternion.toArray()
494
+ };
495
+ }
496
+ function normalizeRoomConfig(config) {
497
+ return {
498
+ camera: config.camera,
499
+ maskUrl: config.maskUrl,
500
+ surfaces: [
501
+ {
502
+ ...config.floor,
503
+ id: FLOOR_SURFACE_ID
504
+ },
505
+ ...config.walls
506
+ ]
507
+ };
508
+ }
509
+ let globalIncrementalId = 0;
510
+ function getNextGlobalId(prefix) {
511
+ globalIncrementalId += 1;
512
+ return `${prefix}-${globalIncrementalId}`;
513
+ }
514
+ const ROOM_EXAMPLE_ASSET_PREFIX = 'https://vizbl-2.s3.us-west-1.amazonaws.com/public/room-example/';
515
+ const HDR_URL = 'https://vizbl.com/hdr/neutral.hdr';
516
+ const CARPET_INFERENCE_EXPORT_ENABLED = false;
517
+ const CARPET_INFERENCE_RAW_URL = 'https://api.vizbl.com/inference/carpet/run-raw';
518
+ const POSTER_VIEWER_URL = "https://viewer.vizbl.com/poster/app/index.html";
519
+ const ROOM_VIEWER_URL = "https://integrations.vizbl.com/room-viewer";
520
+ const APP_CLIP_BUNDLE_ID = "com.us.vizbl.Clip";
521
+ const ZSCALE = 0.01;
522
+ const FLOOR_SURFACE_ID = 'floor';
523
+ const SURFACE_OFFSET = 0.05;
524
+ const POSTER_ROOM_EXAMPLES = [
525
+ {
526
+ id: 'room-example-1',
527
+ name: 'Living room',
528
+ image: `${ROOM_EXAMPLE_ASSET_PREFIX}room-example-1.jpg`,
529
+ config: normalizeRoomConfig(room_example_1_namespaceObject)
530
+ },
531
+ {
532
+ id: 'room-example-2',
533
+ name: 'Bedroom',
534
+ image: `${ROOM_EXAMPLE_ASSET_PREFIX}room-example-2.jpg`,
535
+ config: normalizeRoomConfig(room_example_2_namespaceObject)
536
+ },
537
+ {
538
+ id: 'room-example-3',
539
+ name: 'Kitchen',
540
+ image: `${ROOM_EXAMPLE_ASSET_PREFIX}room-example-3.jpg`,
541
+ config: normalizeRoomConfig(room_example_3_namespaceObject)
542
+ }
543
+ ];
544
+ const CARPET_ROOM_EXAMPLES = [
545
+ {
546
+ id: 'room-example-4',
547
+ name: 'Hallway',
548
+ image: `${ROOM_EXAMPLE_ASSET_PREFIX}room-example-4.png`,
549
+ config: normalizeRoomConfig(room_example_4_namespaceObject)
550
+ },
551
+ {
552
+ id: 'room-example-5',
553
+ name: 'Dining room',
554
+ image: `${ROOM_EXAMPLE_ASSET_PREFIX}room-example-5.png`,
555
+ config: normalizeRoomConfig(room_example_5_namespaceObject)
556
+ },
557
+ {
558
+ id: 'room-example-6',
559
+ name: 'Office',
560
+ image: `${ROOM_EXAMPLE_ASSET_PREFIX}room-example-6.png`,
561
+ config: normalizeRoomConfig(room_example_6_namespaceObject)
562
+ }
563
+ ];
564
+ const RAM_TINUUIDS = [
565
+ 'RcSd3KphTyq_JQEjNCqKQA',
566
+ 'gu0109ftQmS-UQkwcW010Q',
567
+ 'nOfGLIZcRVeU93wOkHWSbg',
568
+ 'ozzH5I2kQMW20ao-33C86A',
569
+ 'DBA4wR-zT92vjtuOk9varg'
570
+ ];
571
+ const CARPET_MATERIAL_OWNER_ID = '97f6e38a-d092-41b4-ba62-1a870b54e964';
572
+ function useFrameWidthOptions() {
573
+ const { t } = useTranslation();
574
+ return external_react_useMemo(()=>[
575
+ {
576
+ value: 0.025,
577
+ label: `2.5 ${t('units.cm')}`
578
+ },
579
+ {
580
+ value: 0.05,
581
+ label: `5 ${t('units.cm')}`
582
+ }
583
+ ], [
584
+ t
585
+ ]);
586
+ }
587
+ function useMattingOptions() {
588
+ const { t } = useTranslation();
589
+ return external_react_useMemo(()=>[
590
+ {
591
+ value: 0,
592
+ label: t('mattingOptions.none')
593
+ },
594
+ {
595
+ value: 0.05,
596
+ label: `5 ${t('units.cm')}`
597
+ }
598
+ ], [
599
+ t
600
+ ]);
601
+ }
602
+ function getInitialSceneLoadState(type) {
603
+ return {
604
+ layoutReady: false,
605
+ placementReady: false,
606
+ materialReady: false,
607
+ artifactsReady: 'poster' === type,
608
+ assetsReady: false,
609
+ prepareReady: 'poster' === type
610
+ };
611
+ }
612
+ const RoomViewerContext = /*#__PURE__*/ createContext(null);
613
+ function RoomViewerProvider({ children, src, tinuuid: initialTinuuid, type, width, height, name }) {
614
+ const frameWidthOptions = useFrameWidthOptions();
615
+ const mattingOptions = useMattingOptions();
616
+ const initialRooms = 'carpet' === type ? CARPET_ROOM_EXAMPLES : POSTER_ROOM_EXAMPLES;
617
+ const [rooms, setRooms] = useState(initialRooms);
618
+ const [roomId, setRoomId] = useState(initialRooms[0].id);
619
+ const room = external_react_useMemo(()=>rooms.find((r)=>r.id === roomId) ?? rooms[0], [
620
+ rooms,
621
+ roomId
622
+ ]);
623
+ const [tinuuid, setTinuuid] = useState(initialTinuuid);
624
+ const [material, setMaterial] = useState(null);
625
+ const [frameWidth, setFrameWidth] = useState(frameWidthOptions[0].value);
626
+ const [matting, setMatting] = useState(mattingOptions[0].value);
627
+ const [angle, setAngle] = useState(0);
628
+ const [rotation, setRotation] = useState(0);
629
+ const [isDragging, setIsDragging] = useState(false);
630
+ const [containerRef, setContainerRef] = useState(null);
631
+ const [sceneLoad, setSceneLoad] = useState(()=>getInitialSceneLoadState(type));
632
+ const resourceRef = useRef(null);
633
+ const usesTinuuid = 'poster' !== type;
634
+ const objectQuery = react_query_useQuery({
635
+ queryKey: objFetchQueryKey({
636
+ tinuuid: tinuuid ?? void 0
637
+ }),
638
+ enabled: usesTinuuid && null === material && Boolean(tinuuid),
639
+ retry: false,
640
+ queryFn: async ()=>{
641
+ if (!tinuuid) throw new Error('Tinuuid is required.');
642
+ return objFetch({
643
+ tinuuid
644
+ });
645
+ }
646
+ });
647
+ const objectData = objectQuery.data ?? null;
648
+ const glbUrl = material?.native?.glbUrl ?? null;
649
+ const patchSceneLoad = useCallback((patch)=>{
650
+ setSceneLoad((current)=>Object.assign({}, current, patch));
651
+ }, []);
652
+ useEffect(()=>{
653
+ if (!usesTinuuid) return;
654
+ setTinuuid(initialTinuuid);
655
+ setMaterial(null);
656
+ }, [
657
+ initialTinuuid,
658
+ usesTinuuid
659
+ ]);
660
+ useEffect(()=>{
661
+ if (!usesTinuuid || !objectData) return;
662
+ if (null !== material) return;
663
+ setMaterial(objectData.materials?.[0] ?? null);
664
+ }, [
665
+ material,
666
+ objectData,
667
+ usesTinuuid
668
+ ]);
669
+ const contextValue = external_react_useMemo(()=>({
670
+ src,
671
+ glbUrl,
672
+ tinuuid,
673
+ type,
674
+ name,
675
+ rooms,
676
+ setRooms,
677
+ room,
678
+ setRoomId,
679
+ material,
680
+ setTinuuid,
681
+ width,
682
+ height,
683
+ frameWidth,
684
+ matting,
685
+ angle,
686
+ rotation,
687
+ isDragging,
688
+ containerRef,
689
+ setMaterial,
690
+ setFrameWidth,
691
+ setMatting,
692
+ setAngle,
693
+ setRotation,
694
+ setIsDragging,
695
+ setContainerRef,
696
+ resourceRef,
697
+ sceneLoad,
698
+ patchSceneLoad
699
+ }), [
700
+ src,
701
+ glbUrl,
702
+ tinuuid,
703
+ type,
704
+ name,
705
+ rooms,
706
+ room,
707
+ material,
708
+ width,
709
+ height,
710
+ frameWidth,
711
+ matting,
712
+ angle,
713
+ rotation,
714
+ containerRef,
715
+ isDragging,
716
+ sceneLoad,
717
+ patchSceneLoad
718
+ ]);
719
+ return /*#__PURE__*/ jsx(RoomViewerContext, {
720
+ value: contextValue,
721
+ children: children
722
+ });
723
+ }
724
+ function useRoomViewer() {
725
+ const context = external_react_use(RoomViewerContext);
726
+ if (!context) throw new Error('useRoomViewer must be used within a RoomViewerProvider');
727
+ return context;
728
+ }
729
+ function label_Label({ className, ...props }) {
730
+ return /*#__PURE__*/ jsx(Label.Root, {
731
+ "data-slot": "label",
732
+ className: utils_cn('flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50', className),
733
+ ...props
734
+ });
735
+ }
736
+ function FieldGroup({ className, ...props }) {
737
+ return /*#__PURE__*/ jsx("div", {
738
+ "data-slot": "field-group",
739
+ className: utils_cn('group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4', className),
740
+ ...props
741
+ });
742
+ }
743
+ const fieldVariants = cva('group/field flex w-full gap-3 data-[invalid=true]:text-destructive', {
744
+ variants: {
745
+ orientation: {
746
+ vertical: [
747
+ 'flex-col [&>*]:w-full [&>.sr-only]:w-auto'
748
+ ],
749
+ horizontal: [
750
+ 'flex-row items-center',
751
+ '[&>[data-slot=field-label]]:flex-auto',
752
+ 'has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
753
+ ],
754
+ responsive: [
755
+ 'flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto',
756
+ '@md/field-group:[&>[data-slot=field-label]]:flex-auto',
757
+ '@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
758
+ ]
759
+ }
760
+ },
761
+ defaultVariants: {
762
+ orientation: 'vertical'
763
+ }
764
+ });
765
+ function Field({ className, orientation = 'vertical', ...props }) {
766
+ return /*#__PURE__*/ jsx("div", {
767
+ role: "group",
768
+ "data-slot": "field",
769
+ "data-orientation": orientation,
770
+ className: utils_cn(fieldVariants({
771
+ orientation
772
+ }), className),
773
+ ...props
774
+ });
775
+ }
776
+ function FieldLabel({ className, ...props }) {
777
+ return /*#__PURE__*/ jsx(label_Label, {
778
+ "data-slot": "field-label",
779
+ className: utils_cn('group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50', 'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4', 'has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10', className),
780
+ ...props
781
+ });
782
+ }
783
+ function FieldTitle({ className, ...props }) {
784
+ return /*#__PURE__*/ jsx("div", {
785
+ "data-slot": "field-label",
786
+ className: utils_cn('flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50', className),
787
+ ...props
788
+ });
789
+ }
790
+ function select_Select({ ...props }) {
791
+ return /*#__PURE__*/ jsx(Select.Root, {
792
+ "data-slot": "select",
793
+ ...props
794
+ });
795
+ }
796
+ function SelectValue({ ...props }) {
797
+ return /*#__PURE__*/ jsx(Select.Value, {
798
+ "data-slot": "select-value",
799
+ ...props
800
+ });
801
+ }
802
+ function SelectTrigger({ className, size = 'default', children, ...props }) {
803
+ return /*#__PURE__*/ jsxs(Select.Trigger, {
804
+ "data-slot": "select-trigger",
805
+ "data-size": size,
806
+ className: utils_cn('flex w-fit items-center justify-between gap-2 whitespace-nowrap rounded-md border bg-background-white shadow-sm outline-none transition-colors', 'border-border-medium', 'text-text-dark', 'hover:border-border-dark', 'focus-visible:border-border-dark', 'aria-invalid:border-border-red', 'disabled:cursor-not-allowed disabled:border-border-medium disabled:bg-background-base disabled:opacity-40', 'data-[size=default]:h-11 data-[size=default]:px-2 data-[size=default]:py-2.5 data-[size=default]:typo-base-16', 'data-[size=md]:h-9 data-[size=md]:px-2 data-[size=md]:py-2 data-[size=md]:typo-base-14', 'data-[size=sm]:h-[1.875rem] data-[size=sm]:px-2 data-[size=sm]:typo-base-12', '*:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2', '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-6 [&_svg:not([class*=\'text-\'])]:text-text-dark', className),
807
+ ...props,
808
+ children: [
809
+ children,
810
+ /*#__PURE__*/ jsx(Select.Icon, {
811
+ asChild: true,
812
+ children: /*#__PURE__*/ jsx(DropDownArrow, {
813
+ className: "size-6"
814
+ })
815
+ })
816
+ ]
817
+ });
818
+ }
819
+ function SelectContent({ className, children, position = 'item-aligned', align = 'center', portalContainer, ...props }) {
820
+ return /*#__PURE__*/ jsx(Select.Portal, {
821
+ container: portalContainer,
822
+ children: /*#__PURE__*/ jsxs(Select.Content, {
823
+ "data-slot": "select-content",
824
+ className: utils_cn('relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-xl border bg-background-white text-text-dark shadow-md', 'border-border-medium', 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', 'popper' === position && 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1', className),
825
+ position: position,
826
+ align: align,
827
+ ...props,
828
+ children: [
829
+ /*#__PURE__*/ jsx(SelectScrollUpButton, {}),
830
+ /*#__PURE__*/ jsx(Select.Viewport, {
831
+ className: utils_cn('p-1', 'popper' === position && 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1'),
832
+ children: children
833
+ }),
834
+ /*#__PURE__*/ jsx(SelectScrollDownButton, {})
835
+ ]
836
+ })
837
+ });
838
+ }
839
+ function SelectItem({ className, children, ...props }) {
840
+ return /*#__PURE__*/ jsxs(Select.Item, {
841
+ "data-slot": "select-item",
842
+ className: utils_cn('relative flex w-full cursor-default select-none items-center gap-2 rounded-md py-2.5 pr-8 pl-3 outline-hidden', 'typo-base-16 text-text-sub', 'focus:bg-background-base focus:text-text-dark', 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50', '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\'size-\'])]:size-6 [&_svg:not([class*=\'text-\'])]:text-text-dark', '*:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2', className),
843
+ ...props,
844
+ children: [
845
+ /*#__PURE__*/ jsx("span", {
846
+ "data-slot": "select-item-indicator",
847
+ className: "absolute right-2 flex size-6 items-center justify-center",
848
+ children: /*#__PURE__*/ jsx(Select.ItemIndicator, {
849
+ children: /*#__PURE__*/ jsx(CheckDone, {
850
+ className: "size-5"
851
+ })
852
+ })
853
+ }),
854
+ /*#__PURE__*/ jsx(Select.ItemText, {
855
+ children: children
856
+ })
857
+ ]
858
+ });
859
+ }
860
+ function SelectScrollUpButton({ className, ...props }) {
861
+ return /*#__PURE__*/ jsx(Select.ScrollUpButton, {
862
+ "data-slot": "select-scroll-up-button",
863
+ className: utils_cn('flex cursor-default items-center justify-center py-1', className),
864
+ ...props,
865
+ children: /*#__PURE__*/ jsx(ChevronUpIcon, {
866
+ className: "size-4"
867
+ })
868
+ });
869
+ }
870
+ function SelectScrollDownButton({ className, ...props }) {
871
+ return /*#__PURE__*/ jsx(Select.ScrollDownButton, {
872
+ "data-slot": "select-scroll-down-button",
873
+ className: utils_cn('flex cursor-default items-center justify-center py-1', className),
874
+ ...props,
875
+ children: /*#__PURE__*/ jsx(ChevronDownIcon, {
876
+ className: "size-4"
877
+ })
878
+ });
879
+ }
880
+ function MaterialSelectField({ className, ...props }) {
881
+ const { containerRef, material, setMaterial, setTinuuid } = useRoomViewer();
882
+ const { t } = useTranslation();
883
+ const objs = useQueries({
884
+ queries: RAM_TINUUIDS.map((tinuuid)=>({
885
+ queryKey: objFetchQueryKey({
886
+ tinuuid
887
+ }),
888
+ queryFn: ()=>objFetch({
889
+ tinuuid
890
+ })
891
+ }))
892
+ });
893
+ useEffect(()=>{
894
+ const firstObject = objs[0]?.data;
895
+ const initialMaterial = firstObject?.materials?.[0];
896
+ const initialTinuuid = firstObject?.tinuuid;
897
+ if (null === material && initialMaterial && initialTinuuid) {
898
+ setMaterial(initialMaterial);
899
+ setTinuuid(initialTinuuid);
900
+ }
901
+ }, [
902
+ material,
903
+ objs,
904
+ setMaterial,
905
+ setTinuuid
906
+ ]);
907
+ return /*#__PURE__*/ jsxs(Field, {
908
+ className: utils_cn('w-fit', className),
909
+ ...props,
910
+ children: [
911
+ /*#__PURE__*/ jsx(FieldLabel, {
912
+ className: "text-xs text-text-sub font-medium",
913
+ children: t('material')
914
+ }),
915
+ /*#__PURE__*/ jsxs(select_Select, {
916
+ value: material?.hid ?? void 0,
917
+ onValueChange: (hid)=>{
918
+ const selected = objs.find((item)=>item.data?.materials?.[0]?.hid === hid)?.data;
919
+ if (!selected?.materials?.[0] || !selected.tinuuid) return;
920
+ const selectedMaterial = selected.materials[0];
921
+ const selectedTinuuid = selected.tinuuid;
922
+ setMaterial(selectedMaterial);
923
+ setTinuuid(selectedTinuuid);
924
+ },
925
+ children: [
926
+ /*#__PURE__*/ jsx(SelectTrigger, {
927
+ className: "w-[145px]",
928
+ size: "md",
929
+ children: /*#__PURE__*/ jsxs(SelectValue, {
930
+ placeholder: t('chooseMaterial'),
931
+ children: [
932
+ /*#__PURE__*/ jsx("div", {
933
+ className: "size-5 rounded-full border border-border-light",
934
+ style: {
935
+ backgroundColor: `#${material?.miniature?.color?.hex}`
936
+ }
937
+ }),
938
+ material?.miniature?.color?.name
939
+ ]
940
+ })
941
+ }),
942
+ /*#__PURE__*/ jsx(SelectContent, {
943
+ portalContainer: containerRef,
944
+ children: objs.map((item)=>{
945
+ const object = item.data;
946
+ if (!object?.materials?.[0] || !object.tinuuid) return null;
947
+ const material = object.materials[0];
948
+ return /*#__PURE__*/ jsxs(SelectItem, {
949
+ value: material.hid,
950
+ children: [
951
+ /*#__PURE__*/ jsx("div", {
952
+ className: "size-5 rounded-full border border-border-light",
953
+ style: {
954
+ backgroundColor: `#${material?.miniature?.color?.hex}`
955
+ }
956
+ }),
957
+ material.miniature?.color?.name
958
+ ]
959
+ }, `${object.tinuuid}-${material.hid}`);
960
+ })
961
+ })
962
+ ]
963
+ })
964
+ ]
965
+ });
966
+ }
967
+ function RadioGroup({ className, ...props }) {
968
+ return /*#__PURE__*/ jsx(Root, {
969
+ "data-slot": "radio-group",
970
+ className: utils_cn('grid gap-3', className),
971
+ ...props
972
+ });
973
+ }
974
+ function RadioCheckIndicator({ checked, className, ...props }) {
975
+ return /*#__PURE__*/ jsx("div", {
976
+ className: utils_cn('absolute top-1/2 left-1/2 size-6 -translate-x-1/2 -translate-y-1/2', 'flex items-center justify-center rounded-full', checked ? 'bg-lime-400' : 'bg-background-base-2', className),
977
+ ...props,
978
+ children: /*#__PURE__*/ jsx(Check, {
979
+ className: "text-constant-white size-3.5",
980
+ strokeWidth: 4
981
+ })
982
+ });
983
+ }
984
+ function RadioGroupItem({ className, variant = 'default', ...props }) {
985
+ return /*#__PURE__*/ jsxs(Item, {
986
+ "data-slot": "radio-group-item",
987
+ className: utils_cn('border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50', 'check' === variant && 'size-6', 'inverted' === variant && 'size-5 border-button-outline data-[state=checked]:border-border-red data-[state=checked]:border-4 data-[state=checked]:bg-constant-white cursor-pointer transition-all', className),
988
+ ...props,
989
+ children: [
990
+ 'check' === variant && /*#__PURE__*/ jsx("div", {
991
+ className: "relative",
992
+ children: /*#__PURE__*/ jsx(RadioCheckIndicator, {})
993
+ }),
994
+ 'inverted' !== variant && /*#__PURE__*/ jsx(Indicator, {
995
+ "data-slot": "radio-group-indicator",
996
+ className: "relative flex items-center justify-center",
997
+ children: 'check' === variant ? /*#__PURE__*/ jsx(RadioCheckIndicator, {
998
+ checked: true
999
+ }) : /*#__PURE__*/ jsx(CircleIcon, {
1000
+ className: "fill-primary absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2"
1001
+ })
1002
+ })
1003
+ ]
1004
+ });
1005
+ }
1006
+ const buttonVariants = cva('cursor-pointer inline-flex items-center justify-center gap-1.5 whitespace-nowrap rounded-xl transition-all disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive', {
1007
+ variants: {
1008
+ variant: {
1009
+ default: 'bg-primary text-primary-foreground hover:bg-primary--hover active:bg-primary--pressed disabled:bg-primary--disabled disabled:opacity-20',
1010
+ secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary--hover active:bg-secondary--pressed disabled:bg-secondary--disabled disabled:opacity-20',
1011
+ outline: 'border border-button-outline bg-transparent text-foreground hover:border-button-outline-hover active:bg-button-outline-pressed disabled:border-border-dark disabled:opacity-20',
1012
+ ghost: 'hover:bg-accent hover:text-accent-foreground active:bg-accent--pressed',
1013
+ contrast: 'bg-contrast text-contrast-foreground hover:bg-contrast--hover active:bg-contrast--pressed disabled:bg-contrast--disabled disabled:opacity-30'
1014
+ },
1015
+ size: {
1016
+ default: 'h-9 px-3.5 typo-button-normal [&_svg]:size-5',
1017
+ sm: 'h-8 px-3 typo-button-small [&_svg]:size-4',
1018
+ lg: 'h-11 px-4 typo-button-big [&_svg]:size-6',
1019
+ icon: 'size-9 rounded-full [&_svg]:size-5',
1020
+ 'icon-sm': 'size-8 rounded-full [&_svg]:size-4',
1021
+ 'icon-lg': 'size-11 rounded-full [&_svg]:size-6'
1022
+ }
1023
+ },
1024
+ defaultVariants: {
1025
+ variant: 'default',
1026
+ size: 'default'
1027
+ }
1028
+ });
1029
+ function button_Button({ className, variant, size, asChild = false, ...props }) {
1030
+ const Comp = asChild ? Slot : 'button';
1031
+ return /*#__PURE__*/ jsx(Comp, {
1032
+ "data-slot": "button",
1033
+ className: utils_cn(buttonVariants({
1034
+ variant,
1035
+ size,
1036
+ className
1037
+ })),
1038
+ ...props
1039
+ });
1040
+ }
1041
+ function Swiper_Swiper({ className, children, ...props }) {
1042
+ const { className: swiperClassName, onReachEnd, ...swiperOptions } = props.swiperOptions ?? {};
1043
+ const { t } = useTranslation();
1044
+ const [swiper, setSwiper] = useState(null);
1045
+ const [isBeginning, setIsBeginning] = useState(true);
1046
+ const [isEnd, setIsEnd] = useState(false);
1047
+ const [isLocked, setIsLocked] = useState(false);
1048
+ const updateNavState = useCallback((value)=>{
1049
+ setIsBeginning(value.isBeginning);
1050
+ setIsEnd(value.isEnd);
1051
+ setIsLocked(value.isLocked);
1052
+ }, []);
1053
+ const handleSwiper = useCallback((value)=>{
1054
+ setSwiper(value);
1055
+ updateNavState(value);
1056
+ }, [
1057
+ updateNavState
1058
+ ]);
1059
+ const handleReachEnd = useCallback((value)=>{
1060
+ setIsEnd(true);
1061
+ onReachEnd?.(value);
1062
+ }, [
1063
+ onReachEnd
1064
+ ]);
1065
+ return /*#__PURE__*/ jsxs("div", {
1066
+ className: utils_cn('relative w-full overflow-hidden', className),
1067
+ children: [
1068
+ /*#__PURE__*/ jsx(Swiper, {
1069
+ className: utils_cn('w-full py-5', swiperClassName),
1070
+ slidesPerView: "auto",
1071
+ slidesPerGroup: 2,
1072
+ onSwiper: handleSwiper,
1073
+ onReachBeginning: ()=>setIsBeginning(true),
1074
+ onReachEnd: handleReachEnd,
1075
+ onFromEdge: updateNavState,
1076
+ onLock: ()=>setIsLocked(true),
1077
+ onUnlock: ()=>setIsLocked(false),
1078
+ onResize: updateNavState,
1079
+ slidesOffsetBefore: 24,
1080
+ slidesOffsetAfter: 24,
1081
+ spaceBetween: 12,
1082
+ ...swiperOptions,
1083
+ children: children
1084
+ }),
1085
+ /*#__PURE__*/ jsxs(button_Button, {
1086
+ className: utils_cn('absolute top-1/2 left-8 -translate-y-1/2 z-1', {
1087
+ hidden: isBeginning || isLocked
1088
+ }),
1089
+ variant: "contrast",
1090
+ size: "icon",
1091
+ onClick: ()=>swiper?.slidePrev(),
1092
+ children: [
1093
+ /*#__PURE__*/ jsx(ArrowLeft, {}),
1094
+ /*#__PURE__*/ jsx("span", {
1095
+ className: "sr-only",
1096
+ children: t('previousSlide')
1097
+ })
1098
+ ]
1099
+ }),
1100
+ /*#__PURE__*/ jsxs(button_Button, {
1101
+ className: utils_cn('absolute top-1/2 right-8 -translate-y-1/2 z-1', {
1102
+ hidden: isEnd || isLocked
1103
+ }),
1104
+ variant: "contrast",
1105
+ size: "icon",
1106
+ onClick: ()=>swiper?.slideNext(),
1107
+ children: [
1108
+ /*#__PURE__*/ jsx(ArrowRight, {}),
1109
+ /*#__PURE__*/ jsx("span", {
1110
+ className: "sr-only",
1111
+ children: t('nextSlide')
1112
+ })
1113
+ ]
1114
+ })
1115
+ ]
1116
+ });
1117
+ }
1118
+ function getMaterialId(material) {
1119
+ return material.hid ?? '';
1120
+ }
1121
+ function getObjectMaterialById(object, id) {
1122
+ return object?.materials?.find((material)=>getMaterialId(material) === id);
1123
+ }
1124
+ function MaterialSwiper({ className, ...props }) {
1125
+ const { material, tinuuid, setMaterial, setTinuuid } = useRoomViewer();
1126
+ const selectedMaterialId = material ? getMaterialId(material) : '';
1127
+ const isMobile = useMediaQuery('(max-width: 640px)');
1128
+ const { data, isFetchingNextPage, fetchNextPage, hasNextPage } = useInfiniteQuery({
1129
+ queryKey: [
1130
+ 'room-viewer',
1131
+ 'carpet-materials',
1132
+ CARPET_MATERIAL_OWNER_ID
1133
+ ],
1134
+ queryFn: ({ pageParam })=>objGetPublic({
1135
+ page: pageParam,
1136
+ owners: [
1137
+ CARPET_MATERIAL_OWNER_ID
1138
+ ]
1139
+ }),
1140
+ initialPageParam: 1,
1141
+ getNextPageParam: (lastPage, _, lastPageParam)=>{
1142
+ if (!lastPage.objs?.length) return;
1143
+ return lastPageParam + 1;
1144
+ },
1145
+ getPreviousPageParam: (_, __, firstPageParam)=>{
1146
+ if (firstPageParam <= 1) return;
1147
+ return firstPageParam - 1;
1148
+ }
1149
+ });
1150
+ const objects = external_react_useMemo(()=>data?.pages.flatMap((page)=>page.objs ?? []) ?? [], [
1151
+ data
1152
+ ]);
1153
+ const initialObject = objects.find((obj)=>obj.tinuuid === tinuuid);
1154
+ const fallbackObject = objects.find((obj)=>obj.tinuuid && obj.materials?.[0]);
1155
+ useEffect(()=>{
1156
+ if (null !== material) return;
1157
+ const selectedObject = initialObject ?? fallbackObject;
1158
+ const initialMaterial = selectedObject?.materials?.[0];
1159
+ if (selectedObject && initialMaterial && selectedObject.tinuuid) {
1160
+ setMaterial(initialMaterial);
1161
+ setTinuuid(selectedObject.tinuuid);
1162
+ }
1163
+ }, [
1164
+ fallbackObject,
1165
+ initialObject,
1166
+ material,
1167
+ setMaterial,
1168
+ setTinuuid
1169
+ ]);
1170
+ const handleReachEnd = useCallback(()=>{
1171
+ if (hasNextPage && !isFetchingNextPage) fetchNextPage();
1172
+ }, [
1173
+ fetchNextPage,
1174
+ hasNextPage,
1175
+ isFetchingNextPage
1176
+ ]);
1177
+ return /*#__PURE__*/ jsx("div", {
1178
+ className: utils_cn(className),
1179
+ ...props,
1180
+ children: /*#__PURE__*/ jsx(RadioGroup, {
1181
+ value: selectedMaterialId || void 0,
1182
+ onValueChange: (id)=>{
1183
+ const selected = objects.find((obj)=>obj.materials?.some((material)=>getMaterialId(material) === id));
1184
+ const selectedMaterial = getObjectMaterialById(selected, id);
1185
+ if (!selected || !selectedMaterial || !selected.tinuuid) return;
1186
+ setMaterial(selectedMaterial);
1187
+ setTinuuid(selected.tinuuid);
1188
+ },
1189
+ children: /*#__PURE__*/ jsx(Swiper_Swiper, {
1190
+ swiperOptions: {
1191
+ onReachEnd: handleReachEnd,
1192
+ className: 'py-2 sm:py-5'
1193
+ },
1194
+ children: objects.flatMap((obj)=>obj.materials?.map((material)=>{
1195
+ const id = getMaterialId(material);
1196
+ if (!obj.tinuuid) return null;
1197
+ return /*#__PURE__*/ jsx(SwiperSlide, {
1198
+ className: utils_cn('sm:w-25! w-18! transition-transform duration-200', id !== selectedMaterialId && 'hover:scale-110'),
1199
+ children: /*#__PURE__*/ jsxs(FieldLabel, {
1200
+ htmlFor: `material-${id}`,
1201
+ className: "size-full flex flex-col gap-1 has-data-[state=checked]:bg-transparent",
1202
+ children: [
1203
+ /*#__PURE__*/ jsxs("div", {
1204
+ className: "size-full relative sm:h-19 h-13 rounded-[0.75rem] overflow-hidden grid place-items-center sm:block",
1205
+ children: [
1206
+ /*#__PURE__*/ jsx("img", {
1207
+ src: material.previews?.[0]?.subRes?.small ?? material.previews?.[0]?.url,
1208
+ alt: material.name ?? '',
1209
+ className: "size-full object-cover"
1210
+ }),
1211
+ /*#__PURE__*/ jsx(RadioGroupItem, {
1212
+ value: id,
1213
+ id: `material-${id}`,
1214
+ variant: "check",
1215
+ className: utils_cn('absolute sm:top-1.75 sm:right-1.75', {
1216
+ 'sr-only': id !== selectedMaterialId
1217
+ })
1218
+ })
1219
+ ]
1220
+ }),
1221
+ !isMobile && /*#__PURE__*/ jsx("span", {
1222
+ className: utils_cn('block w-full text-xs text-left text-text-light truncate', {
1223
+ 'text-text-dark': id === selectedMaterialId
1224
+ }),
1225
+ children: material.name
1226
+ })
1227
+ ]
1228
+ })
1229
+ }, `${obj.tinuuid}-${id}`);
1230
+ }) ?? [])
1231
+ })
1232
+ })
1233
+ });
1234
+ }
1235
+ const TabsValueContext = /*#__PURE__*/ createContext(null);
1236
+ const TabsListContext = /*#__PURE__*/ createContext(null);
1237
+ function Tabs({ value: valueProp, defaultValue, onValueChange, ...props }) {
1238
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
1239
+ const value = valueProp ?? uncontrolledValue;
1240
+ const contextValue = external_react_useMemo(()=>({
1241
+ value
1242
+ }), [
1243
+ value
1244
+ ]);
1245
+ const handleValueChange = useCallback((nextValue)=>{
1246
+ onValueChange?.(nextValue);
1247
+ if (void 0 === valueProp) setUncontrolledValue(nextValue);
1248
+ }, [
1249
+ onValueChange,
1250
+ valueProp
1251
+ ]);
1252
+ return /*#__PURE__*/ jsx(TabsValueContext, {
1253
+ value: contextValue,
1254
+ children: /*#__PURE__*/ jsx(react_tabs_Root, {
1255
+ "data-slot": "tabs",
1256
+ value: value,
1257
+ onValueChange: handleValueChange,
1258
+ ...props
1259
+ })
1260
+ });
1261
+ }
1262
+ const tabsTriggerVariants = cva("cursor-pointer group inline-flex items-center justify-center shrink-0 whitespace-nowrap outline-none transition-colors relative z-10 text-text-base [&_svg]:text-icons-normal [&_svg]:transition-colors hover:text-text-dark hover:[&_svg]:text-icons-dark active:bg-background-base-3 active:text-text-dark active:[&_svg]:text-icons-dark data-[state=active]:bg-background-white data-[state=active]:text-text-dark data-[state=active]:[&_svg]:text-icons-dark disabled:pointer-events-none disabled:opacity-50 focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background", {
1263
+ variants: {
1264
+ size: {
1265
+ default: 'h-7 rounded-lg px-2 typo-button-normal active:rounded-lg data-[state=active]:rounded-lg [&_svg]:size-4',
1266
+ sm: 'h-6 rounded-[4px] px-2 typo-button-small active:rounded-lg data-[state=active]:rounded-lg [&_svg]:size-4',
1267
+ lg: 'h-9 rounded-lg px-3 typo-button-big active:rounded-lg data-[state=active]:rounded-lg [&_svg]:size-6'
1268
+ }
1269
+ },
1270
+ defaultVariants: {
1271
+ size: 'default'
1272
+ }
1273
+ });
1274
+ const tabsListVariants = cva('relative inline-flex items-center rounded-xl bg-background-base-2', {
1275
+ variants: {
1276
+ size: {
1277
+ default: 'p-1',
1278
+ sm: 'p-[3px]',
1279
+ lg: 'p-1'
1280
+ }
1281
+ },
1282
+ defaultVariants: {
1283
+ size: 'default'
1284
+ }
1285
+ });
1286
+ function TabsList({ className, children, size = 'default', ...props }) {
1287
+ const tabsValue = external_react_use(TabsValueContext);
1288
+ const listRef = useRef(null);
1289
+ const triggerRefs = useRef(new Map());
1290
+ const indicatorRef = useRef(null);
1291
+ const activeValueRef = useRef(tabsValue?.value);
1292
+ const animationFrameRef = useRef(null);
1293
+ const updateIndicator = useCallback(()=>{
1294
+ animationFrameRef.current = null;
1295
+ const indicatorElement = indicatorRef.current;
1296
+ if (!indicatorElement) return;
1297
+ const activeValue = activeValueRef.current;
1298
+ if (!activeValue) {
1299
+ indicatorElement.style.opacity = '0';
1300
+ return;
1301
+ }
1302
+ const listElement = listRef.current;
1303
+ const triggerElement = triggerRefs.current.get(activeValue);
1304
+ if (!listElement || !triggerElement) {
1305
+ indicatorElement.style.opacity = '0';
1306
+ return;
1307
+ }
1308
+ const left = Math.round(triggerElement.offsetLeft);
1309
+ const top = Math.round(triggerElement.offsetTop);
1310
+ const width = Math.round(triggerElement.offsetWidth);
1311
+ const height = Math.round(triggerElement.offsetHeight);
1312
+ indicatorElement.style.transform = `translate3d(${left}px, ${top}px, 0)`;
1313
+ indicatorElement.style.width = `${width}px`;
1314
+ indicatorElement.style.height = `${height}px`;
1315
+ indicatorElement.style.opacity = '1';
1316
+ }, []);
1317
+ const scheduleIndicatorUpdate = useCallback(()=>{
1318
+ if (null !== animationFrameRef.current) return;
1319
+ animationFrameRef.current = requestAnimationFrame(()=>{
1320
+ updateIndicator();
1321
+ });
1322
+ }, [
1323
+ updateIndicator
1324
+ ]);
1325
+ const registerTrigger = useCallback((value, node)=>{
1326
+ if (node) triggerRefs.current.set(value, node);
1327
+ else triggerRefs.current.delete(value);
1328
+ scheduleIndicatorUpdate();
1329
+ }, [
1330
+ scheduleIndicatorUpdate
1331
+ ]);
1332
+ useLayoutEffect(()=>{
1333
+ activeValueRef.current = tabsValue?.value;
1334
+ if (null !== animationFrameRef.current) {
1335
+ cancelAnimationFrame(animationFrameRef.current);
1336
+ animationFrameRef.current = null;
1337
+ }
1338
+ updateIndicator();
1339
+ }, [
1340
+ tabsValue?.value,
1341
+ updateIndicator
1342
+ ]);
1343
+ useEffect(()=>{
1344
+ const listElement = listRef.current;
1345
+ if (!listElement || 'undefined' == typeof ResizeObserver) return;
1346
+ const observer = new ResizeObserver(()=>scheduleIndicatorUpdate());
1347
+ observer.observe(listElement);
1348
+ const activeValue = tabsValue?.value;
1349
+ if (activeValue) {
1350
+ const triggerElement = triggerRefs.current.get(activeValue);
1351
+ if (triggerElement) observer.observe(triggerElement);
1352
+ }
1353
+ return ()=>{
1354
+ observer.disconnect();
1355
+ };
1356
+ }, [
1357
+ scheduleIndicatorUpdate,
1358
+ tabsValue?.value
1359
+ ]);
1360
+ useEffect(()=>()=>{
1361
+ if (null !== animationFrameRef.current) cancelAnimationFrame(animationFrameRef.current);
1362
+ }, []);
1363
+ const listContextValue = external_react_useMemo(()=>({
1364
+ registerTrigger,
1365
+ size
1366
+ }), [
1367
+ registerTrigger,
1368
+ size
1369
+ ]);
1370
+ return /*#__PURE__*/ jsx(List, {
1371
+ ref: listRef,
1372
+ "data-slot": "tabs-list",
1373
+ className: utils_cn(tabsListVariants({
1374
+ size
1375
+ }), className),
1376
+ ...props,
1377
+ children: /*#__PURE__*/ jsxs(TabsListContext, {
1378
+ value: listContextValue,
1379
+ children: [
1380
+ /*#__PURE__*/ jsx("div", {
1381
+ ref: indicatorRef,
1382
+ "aria-hidden": true,
1383
+ "data-slot": "tabs-indicator",
1384
+ className: "pointer-events-none absolute top-0 left-0 z-0 rounded-lg bg-background-white opacity-0 transition-[transform,width,height,opacity] duration-[250ms] ease-out motion-reduce:transition-none"
1385
+ }),
1386
+ children
1387
+ ]
1388
+ })
1389
+ });
1390
+ }
1391
+ TabsList.displayName = List.displayName;
1392
+ function TabsTrigger({ className, size, children, value, ...props }) {
1393
+ const listContext = external_react_use(TabsListContext);
1394
+ const triggerRef = useRef(null);
1395
+ const resolvedSize = size ?? listContext?.size ?? 'default';
1396
+ const contentClassName = utils_cn('relative z-10 inline-flex items-center justify-center', 'lg' === resolvedSize ? 'gap-1.5' : 'gap-1');
1397
+ useLayoutEffect(()=>{
1398
+ if (!listContext || !value) return;
1399
+ listContext.registerTrigger(value, triggerRef.current);
1400
+ return ()=>{
1401
+ listContext.registerTrigger(value, null);
1402
+ };
1403
+ }, [
1404
+ listContext,
1405
+ value
1406
+ ]);
1407
+ return /*#__PURE__*/ jsx(Trigger, {
1408
+ ref: triggerRef,
1409
+ "data-slot": "tabs-trigger",
1410
+ className: utils_cn(tabsTriggerVariants({
1411
+ size: resolvedSize
1412
+ }), 'data-[state=active]:bg-transparent', className),
1413
+ value: value,
1414
+ ...props,
1415
+ children: /*#__PURE__*/ jsx("span", {
1416
+ "data-slot": "tabs-trigger-content",
1417
+ className: contentClassName,
1418
+ children: children
1419
+ })
1420
+ });
1421
+ }
1422
+ TabsTrigger.displayName = Trigger.displayName;
1423
+ function TabsContent({ className, ...props }) {
1424
+ return /*#__PURE__*/ jsx(Content, {
1425
+ "data-slot": "tabs-content",
1426
+ className: utils_cn('outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background', className),
1427
+ ...props
1428
+ });
1429
+ }
1430
+ TabsContent.displayName = Content.displayName;
1431
+ function MattingField({ className, ...props }) {
1432
+ const { matting, setMatting } = useRoomViewer();
1433
+ const { t } = useTranslation();
1434
+ const mattingOptions = useMattingOptions();
1435
+ return /*#__PURE__*/ jsxs(Field, {
1436
+ className: utils_cn('w-fit', className),
1437
+ ...props,
1438
+ children: [
1439
+ /*#__PURE__*/ jsx(FieldLabel, {
1440
+ className: "text-xs text-text-sub font-medium",
1441
+ children: t('matting')
1442
+ }),
1443
+ /*#__PURE__*/ jsx(Tabs, {
1444
+ value: matting.toString(),
1445
+ onValueChange: (value)=>setMatting(Number(value)),
1446
+ children: /*#__PURE__*/ jsx(TabsList, {
1447
+ children: mattingOptions.map((option)=>/*#__PURE__*/ jsx(TabsTrigger, {
1448
+ value: option.value.toString(),
1449
+ children: option.label
1450
+ }, option.value))
1451
+ })
1452
+ })
1453
+ ]
1454
+ });
1455
+ }
1456
+ function RoomSwiper({ className, ...props }) {
1457
+ const { t } = useTranslation();
1458
+ const { rooms, room, setRoomId, setRooms, type } = useRoomViewer();
1459
+ const isMobile = useMediaQuery('(max-width: 640px)');
1460
+ const inputId = useId();
1461
+ const getRoomName = useCallback((roomName, roomId)=>t(`rooms.${roomId}`, {
1462
+ defaultValue: roomName
1463
+ }), [
1464
+ t
1465
+ ]);
1466
+ const handleFileChange = useCallback((event)=>{
1467
+ const file = event.target.files?.[0];
1468
+ event.target.value = '';
1469
+ if (!file) return;
1470
+ const imageUrl = URL.createObjectURL(file);
1471
+ const customRoom = {
1472
+ id: getNextGlobalId('room-custom'),
1473
+ name: file.name,
1474
+ image: imageUrl,
1475
+ config: {
1476
+ camera: {
1477
+ fov: 45
1478
+ },
1479
+ surfaces: [
1480
+ {
1481
+ id: FLOOR_SURFACE_ID,
1482
+ z: 0,
1483
+ pitchDeg: -90,
1484
+ rollDeg: 0
1485
+ }
1486
+ ]
1487
+ }
1488
+ };
1489
+ setRooms((currentRooms)=>[
1490
+ customRoom,
1491
+ ...currentRooms
1492
+ ]);
1493
+ setRoomId(customRoom.id);
1494
+ }, [
1495
+ setRoomId,
1496
+ setRooms
1497
+ ]);
1498
+ return /*#__PURE__*/ jsx("div", {
1499
+ className: utils_cn(className),
1500
+ ...props,
1501
+ children: /*#__PURE__*/ jsx(RadioGroup, {
1502
+ value: room.id,
1503
+ onValueChange: setRoomId,
1504
+ children: /*#__PURE__*/ jsxs(Swiper_Swiper, {
1505
+ swiperOptions: {
1506
+ className: 'py-2 sm:py-5 pb-3 sm:pb-5'
1507
+ },
1508
+ children: [
1509
+ 'carpet' === type && /*#__PURE__*/ jsx(SwiperSlide, {
1510
+ className: "sm:w-25! w-21! h-auto",
1511
+ children: /*#__PURE__*/ jsxs(FieldLabel, {
1512
+ htmlFor: inputId,
1513
+ className: utils_cn('flex size-full cursor-pointer items-center justify-center rounded-[0.75rem] border border-dashed border-muted-foreground/40 bg-transparent px-2 text-center text-xs leading-tight text-muted-foreground transition-colors hover:border-foreground/40 hover:text-foreground'),
1514
+ children: [
1515
+ t('uploadOwnPhoto'),
1516
+ /*#__PURE__*/ jsx("input", {
1517
+ id: inputId,
1518
+ type: "file",
1519
+ accept: "image/*",
1520
+ className: "sr-only",
1521
+ onChange: handleFileChange
1522
+ })
1523
+ ]
1524
+ })
1525
+ }),
1526
+ rooms.map((r)=>/*#__PURE__*/ jsx(SwiperSlide, {
1527
+ className: utils_cn('sm:w-25! w-21! transition-transform duration-200', r.id !== room.id && 'hover:scale-110'),
1528
+ children: /*#__PURE__*/ jsxs(FieldLabel, {
1529
+ htmlFor: `room-${r.id}`,
1530
+ className: "size-full flex flex-col gap-1 has-data-[state=checked]:bg-transparent",
1531
+ children: [
1532
+ /*#__PURE__*/ jsxs("div", {
1533
+ className: "size-full relative h-17 sm:h-19 rounded-[0.75rem] overflow-hidden",
1534
+ children: [
1535
+ /*#__PURE__*/ jsx("img", {
1536
+ src: r.image,
1537
+ alt: getRoomName(r.name, r.id),
1538
+ className: "size-full object-cover",
1539
+ crossOrigin: "anonymous"
1540
+ }),
1541
+ /*#__PURE__*/ jsx(RadioGroupItem, {
1542
+ value: r.id,
1543
+ id: `room-${r.id}`,
1544
+ variant: "check",
1545
+ className: utils_cn('absolute top-1.75 right-1.75', {
1546
+ 'sr-only': r.id !== room.id
1547
+ })
1548
+ })
1549
+ ]
1550
+ }),
1551
+ !isMobile && /*#__PURE__*/ jsx("span", {
1552
+ className: utils_cn('block w-full text-xs text-left text-text-light truncate', {
1553
+ 'text-text-dark': r.id === room.id
1554
+ }),
1555
+ children: getRoomName(r.name, r.id)
1556
+ })
1557
+ ]
1558
+ })
1559
+ }, r.id))
1560
+ ]
1561
+ })
1562
+ })
1563
+ });
1564
+ }
1565
+ function Footer({ className, ...props }) {
1566
+ const { type } = useRoomViewer();
1567
+ const isMobile = useMediaQuery('(max-width: 640px)');
1568
+ return /*#__PURE__*/ jsxs("div", {
1569
+ className: utils_cn('flex sm:items-end pl-0 overflow-hidden sm:flex-row flex-col py-3 sm:py-0', className),
1570
+ ...props,
1571
+ children: [
1572
+ /*#__PURE__*/ jsx(RoomSwiper, {
1573
+ className: utils_cn('shrink-0', {
1574
+ 'sm:min-w-120.5 min-w-full': 'carpet' === type,
1575
+ grow: 'poster' === type
1576
+ })
1577
+ }),
1578
+ 'poster' === type && /*#__PURE__*/ jsxs("div", {
1579
+ className: "hidden gap-5 pr-6 pb-5 md:flex",
1580
+ children: [
1581
+ /*#__PURE__*/ jsx(MaterialSelectField, {}),
1582
+ /*#__PURE__*/ jsx(MattingField, {})
1583
+ ]
1584
+ }),
1585
+ 'carpet' === type && /*#__PURE__*/ jsxs(Fragment, {
1586
+ children: [
1587
+ !isMobile && /*#__PURE__*/ jsx(separator_Separator, {
1588
+ orientation: "vertical"
1589
+ }),
1590
+ /*#__PURE__*/ jsx(MaterialSwiper, {})
1591
+ ]
1592
+ })
1593
+ ]
1594
+ });
1595
+ }
1596
+ function LogoSign({ className, ...props }) {
1597
+ const { t } = useTranslation();
1598
+ return /*#__PURE__*/ jsxs("div", {
1599
+ className: utils_cn('group inline-flex gap-2 text-sm text-muted-foreground', 'hover:text-text-dark', 'transition-colors duration-200 ease-in-out', className),
1600
+ ...props,
1601
+ children: [
1602
+ /*#__PURE__*/ jsx("div", {
1603
+ className: "size-5 flex items-center justify-center rounded-full bg-icons-light group-hover:bg-icons-red transition-colors duration-200 ease-in-out",
1604
+ children: /*#__PURE__*/ jsx(Vizbl, {
1605
+ className: "size-4.25 text-icons-white"
1606
+ })
1607
+ }),
1608
+ t('poweredBy')
1609
+ ]
1610
+ });
1611
+ }
1612
+ function Header({ className, ...props }) {
1613
+ const { name } = useRoomViewer();
1614
+ return /*#__PURE__*/ jsxs("div", {
1615
+ className: utils_cn('h-16 grid gap-5 items-center px-6 sm:grid-cols-3', className),
1616
+ ...props,
1617
+ children: [
1618
+ /*#__PURE__*/ jsx("div", {
1619
+ className: "truncate",
1620
+ children: name
1621
+ }),
1622
+ /*#__PURE__*/ jsx("div", {
1623
+ className: "hidden justify-center sm:flex",
1624
+ children: /*#__PURE__*/ jsx(LogoSign, {})
1625
+ }),
1626
+ /*#__PURE__*/ jsx("div", {
1627
+ className: "hidden sm:block"
1628
+ })
1629
+ ]
1630
+ });
1631
+ }
1632
+ function popover_Popover({ ...props }) {
1633
+ return /*#__PURE__*/ jsx(Popover.Root, {
1634
+ "data-slot": "popover",
1635
+ ...props
1636
+ });
1637
+ }
1638
+ function PopoverTrigger({ ...props }) {
1639
+ return /*#__PURE__*/ jsx(Popover.Trigger, {
1640
+ "data-slot": "popover-trigger",
1641
+ ...props
1642
+ });
1643
+ }
1644
+ function PopoverContent({ className, align = 'center', sideOffset = 4, portalContainer, ...props }) {
1645
+ return /*#__PURE__*/ jsx(Popover.Portal, {
1646
+ container: portalContainer,
1647
+ children: /*#__PURE__*/ jsx(Popover.Content, {
1648
+ "data-slot": "popover-content",
1649
+ align: align,
1650
+ sideOffset: sideOffset,
1651
+ className: utils_cn('bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden', className),
1652
+ ...props
1653
+ })
1654
+ });
1655
+ }
1656
+ function slider_Slider({ className, defaultValue, value, min = 0, max = 100, variant = 'default', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, ...props }) {
1657
+ const _values = external_react_useMemo(()=>Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : [
1658
+ min
1659
+ ], [
1660
+ value,
1661
+ defaultValue,
1662
+ min
1663
+ ]);
1664
+ return /*#__PURE__*/ jsxs(Slider.Root, {
1665
+ "data-slot": "slider",
1666
+ defaultValue: defaultValue,
1667
+ value: value,
1668
+ min: min,
1669
+ max: max,
1670
+ "data-variant": variant,
1671
+ "aria-label": ariaLabel,
1672
+ "aria-labelledby": ariaLabelledBy,
1673
+ className: utils_cn('relative flex w-full touch-none cursor-pointer items-center select-none data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 data-[orientation=vertical]:h-full data-[orientation=vertical]:min-h-44 data-[orientation=vertical]:w-auto data-[orientation=vertical]:flex-col', className),
1674
+ ...props,
1675
+ children: [
1676
+ /*#__PURE__*/ jsx(Slider.Track, {
1677
+ "data-slot": "slider-track",
1678
+ className: utils_cn('relative grow overflow-hidden rounded-full data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-1.5', 'editor' === variant ? 'bg-border-light data-[orientation=horizontal]:h-0.5' : 'bg-muted data-[orientation=horizontal]:h-1.5'),
1679
+ children: /*#__PURE__*/ jsx(Slider.Range, {
1680
+ "data-slot": "slider-range",
1681
+ className: utils_cn('absolute data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full', 'editor' === variant ? 'bg-background-blue' : 'bg-primary')
1682
+ })
1683
+ }),
1684
+ Array.from({
1685
+ length: _values.length
1686
+ }, (_, index)=>/*#__PURE__*/ jsx(Slider.Thumb, {
1687
+ "data-slot": "slider-thumb",
1688
+ "aria-label": ariaLabel,
1689
+ "aria-labelledby": ariaLabelledBy,
1690
+ className: utils_cn('ring-ring/50 block shrink-0 rounded-full border bg-white transition-[color,box-shadow] focus-visible:outline-hidden disabled:pointer-events-none disabled:opacity-50', 'editor' === variant ? 'size-3 border-background-blue shadow-none hover:ring-2 focus-visible:ring-2' : 'size-4 border-primary shadow-sm hover:ring-4 focus-visible:ring-4')
1691
+ }, index))
1692
+ ]
1693
+ });
1694
+ }
1695
+ function Dialog({ ...props }) {
1696
+ return /*#__PURE__*/ jsx(react_dialog_Root, {
1697
+ "data-slot": "dialog",
1698
+ ...props
1699
+ });
1700
+ }
1701
+ function DialogPortal({ ...props }) {
1702
+ return /*#__PURE__*/ jsx(Portal, {
1703
+ "data-slot": "dialog-portal",
1704
+ ...props
1705
+ });
1706
+ }
1707
+ function DialogClose({ ...props }) {
1708
+ return /*#__PURE__*/ jsx(Close, {
1709
+ "data-slot": "dialog-close",
1710
+ ...props
1711
+ });
1712
+ }
1713
+ function DialogOverlay({ className, ...props }) {
1714
+ return /*#__PURE__*/ jsx(Overlay, {
1715
+ "data-slot": "dialog-overlay",
1716
+ className: utils_cn('data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50', className),
1717
+ ...props
1718
+ });
1719
+ }
1720
+ function DialogContent({ className, children, showCloseButton = true, portalContainer, ...props }) {
1721
+ return /*#__PURE__*/ jsxs(DialogPortal, {
1722
+ "data-slot": "dialog-portal",
1723
+ container: portalContainer,
1724
+ children: [
1725
+ /*#__PURE__*/ jsx(DialogOverlay, {}),
1726
+ /*#__PURE__*/ jsxs(react_dialog_Content, {
1727
+ "data-slot": "dialog-content",
1728
+ className: utils_cn('bg-background-containers data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-6 rounded-3xl border-0 p-6 shadow-[var(--shadow-login)] duration-200 outline-none sm:max-w-120 sm:p-10', className),
1729
+ ...props,
1730
+ children: [
1731
+ children,
1732
+ showCloseButton && /*#__PURE__*/ jsxs(Close, {
1733
+ "data-slot": "dialog-close",
1734
+ className: "focus-visible:ring-ring/30 absolute top-4 right-4 inline-flex size-6 items-center justify-center rounded-full text-icons-light transition-colors hover:text-icons-normal focus-visible:text-icons-dark focus-visible:ring-2 focus-visible:outline-hidden disabled:pointer-events-none [&_svg]:pointer-events-none [&_svg]:shrink-0",
1735
+ children: [
1736
+ /*#__PURE__*/ jsx(XIcon, {
1737
+ className: "size-6"
1738
+ }),
1739
+ /*#__PURE__*/ jsx("span", {
1740
+ className: "sr-only",
1741
+ children: "Close"
1742
+ })
1743
+ ]
1744
+ })
1745
+ ]
1746
+ })
1747
+ ]
1748
+ });
1749
+ }
1750
+ function DialogHeader({ className, ...props }) {
1751
+ return /*#__PURE__*/ jsx("div", {
1752
+ "data-slot": "dialog-header",
1753
+ className: utils_cn('flex flex-col gap-2 text-left', className),
1754
+ ...props
1755
+ });
1756
+ }
1757
+ function DialogTitle({ className, ...props }) {
1758
+ return /*#__PURE__*/ jsx(Title, {
1759
+ "data-slot": "dialog-title",
1760
+ className: utils_cn('typo-base-18 text-text-dark', className),
1761
+ ...props
1762
+ });
1763
+ }
1764
+ function DialogDescription({ className, ...props }) {
1765
+ return /*#__PURE__*/ jsx(Description, {
1766
+ "data-slot": "dialog-description",
1767
+ className: utils_cn('typo-base-16 text-text-light', className),
1768
+ ...props
1769
+ });
1770
+ }
1771
+ const oklchRegex = /oklch\(([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\)/;
1772
+ function getOklch(color, fallback) {
1773
+ const oklchMatch = color.match(oklchRegex);
1774
+ if (!oklchMatch) return {
1775
+ l: fallback[0],
1776
+ c: fallback[1],
1777
+ h: fallback[2]
1778
+ };
1779
+ return {
1780
+ l: Number.parseFloat(oklchMatch[1]),
1781
+ c: Number.parseFloat(oklchMatch[2]),
1782
+ h: Number.parseFloat(oklchMatch[3])
1783
+ };
1784
+ }
1785
+ function QRCode({ data, foreground, background, robustness = 'M', className, ...props }) {
1786
+ const [svg, setSVG] = useState(null);
1787
+ useEffect(()=>{
1788
+ const generateQR = async ()=>{
1789
+ try {
1790
+ const styles = getComputedStyle(document.documentElement);
1791
+ const foregroundColor = foreground ?? styles.getPropertyValue('--foreground');
1792
+ const backgroundColor = background ?? styles.getPropertyValue('--background');
1793
+ const foregroundOklch = getOklch(foregroundColor, [
1794
+ 0.21,
1795
+ 0.006,
1796
+ 285.885
1797
+ ]);
1798
+ const backgroundOklch = getOklch(backgroundColor, [
1799
+ 0.985,
1800
+ 0,
1801
+ 0
1802
+ ]);
1803
+ const newSvg = await qrcode.toString(data, {
1804
+ type: 'svg',
1805
+ color: {
1806
+ dark: formatHex(oklch({
1807
+ mode: 'oklch',
1808
+ ...foregroundOklch
1809
+ })),
1810
+ light: formatHex(oklch({
1811
+ mode: 'oklch',
1812
+ ...backgroundOklch
1813
+ }))
1814
+ },
1815
+ width: 200,
1816
+ errorCorrectionLevel: robustness,
1817
+ margin: 0
1818
+ });
1819
+ setSVG(newSvg);
1820
+ } catch (err) {
1821
+ console.error(err);
1822
+ }
1823
+ };
1824
+ generateQR();
1825
+ }, [
1826
+ data,
1827
+ foreground,
1828
+ background,
1829
+ robustness
1830
+ ]);
1831
+ if (!svg) return null;
1832
+ return /*#__PURE__*/ jsx("div", {
1833
+ className: utils_cn('size-full', '[&_svg]:size-full', className),
1834
+ dangerouslySetInnerHTML: {
1835
+ __html: svg
1836
+ },
1837
+ ...props
1838
+ });
1839
+ }
1840
+ const parser = new UAParser();
1841
+ const device = parser.getDevice();
1842
+ const os = parser.getOS();
1843
+ const isIOS = 'iOS' === os.name;
1844
+ const isAndroid = 'Android' === os.name;
1845
+ const device_isMobile = 'mobile' === device.type;
1846
+ function AR({ className, ...props }) {
1847
+ const { src, tinuuid, type, width, height, name, containerRef, resourceRef } = useRoomViewer();
1848
+ const { t, i18n } = useTranslation();
1849
+ const [usdzUrl, setUsdzUrl] = useState('');
1850
+ const [glbUrl, setGlbUrl] = useState('');
1851
+ const [open, setOpen] = useState(false);
1852
+ const [isSceneExporting, setIsSceneExporting] = useState(false);
1853
+ const modelViewerRef = useRef(null);
1854
+ const isLoading = isSceneExporting;
1855
+ const language = i18n.resolvedLanguage ?? i18n.language;
1856
+ const url = external_react_useMemo(()=>{
1857
+ if ('carpet' === type && tinuuid) {
1858
+ const url = new URL(ROOM_VIEWER_URL);
1859
+ url.searchParams.set('type', type);
1860
+ url.searchParams.set('tinuuid', tinuuid);
1861
+ url.searchParams.set('name', name);
1862
+ url.searchParams.set('language', language);
1863
+ return url.toString();
1864
+ }
1865
+ const url = new URL(POSTER_VIEWER_URL);
1866
+ url.searchParams.set('texture', src);
1867
+ url.searchParams.set('texture_width', (100 * width).toString());
1868
+ url.searchParams.set('texture_height', (100 * height).toString());
1869
+ return decodeURIComponent(url.toString());
1870
+ }, [
1871
+ src,
1872
+ tinuuid,
1873
+ type,
1874
+ width,
1875
+ height,
1876
+ name,
1877
+ language
1878
+ ]);
1879
+ const appClipUrl = external_react_useMemo(()=>{
1880
+ if (!tinuuid) return null;
1881
+ const url = new URL('https://appclip.apple.com/id');
1882
+ url.searchParams.set('p', APP_CLIP_BUNDLE_ID);
1883
+ url.searchParams.set('tinuuid', tinuuid);
1884
+ return url.toString();
1885
+ }, [
1886
+ tinuuid
1887
+ ]);
1888
+ const handleClick = async ()=>{
1889
+ if ('carpet' === type) {
1890
+ if (!tinuuid) return;
1891
+ if (isIOS && appClipUrl) return void window.location.assign(appClipUrl);
1892
+ setOpen(true);
1893
+ return;
1894
+ }
1895
+ const modelViewer = modelViewerRef.current;
1896
+ const sceneResource = resourceRef.current;
1897
+ if (!modelViewer || !sceneResource) return;
1898
+ if (modelViewer.canActivateAR) try {
1899
+ setIsSceneExporting(true);
1900
+ const clone = sceneResource.clone(true);
1901
+ clone.position.set(0, 0, 0);
1902
+ if (isIOS) {
1903
+ const usdzExporter = new USDZExporter();
1904
+ const usdzBuffer = await usdzExporter.parseAsync(clone);
1905
+ const usdzFile = new File([
1906
+ usdzBuffer
1907
+ ], 'scene.usdz', {
1908
+ type: 'model/vnd.usdz+zip'
1909
+ });
1910
+ setUsdzUrl(URL.createObjectURL(usdzFile));
1911
+ } else {
1912
+ const glbExporter = new GLTFExporter();
1913
+ const glbBuffer = await glbExporter.parseAsync(clone, {
1914
+ binary: true
1915
+ });
1916
+ const glbFile = new File([
1917
+ glbBuffer
1918
+ ], 'scene.glb', {
1919
+ type: 'model/gltf-binary'
1920
+ });
1921
+ setGlbUrl(URL.createObjectURL(glbFile));
1922
+ }
1923
+ } finally{
1924
+ setIsSceneExporting(false);
1925
+ }
1926
+ else setOpen(true);
1927
+ };
1928
+ useEffect(()=>{
1929
+ if ('' !== usdzUrl || '' !== glbUrl) modelViewerRef.current?.activateAR();
1930
+ return ()=>{
1931
+ if ('' !== usdzUrl) URL.revokeObjectURL(usdzUrl);
1932
+ if ('' !== glbUrl) URL.revokeObjectURL(glbUrl);
1933
+ };
1934
+ }, [
1935
+ usdzUrl,
1936
+ glbUrl
1937
+ ]);
1938
+ if (isAndroid && device_isMobile || 'carpet' === type && !tinuuid) return null;
1939
+ return /*#__PURE__*/ jsxs("div", {
1940
+ className: utils_cn(className),
1941
+ ...props,
1942
+ children: [
1943
+ /*#__PURE__*/ jsx(button_Button, {
1944
+ variant: "contrast",
1945
+ disabled: isLoading,
1946
+ onClick: handleClick,
1947
+ children: t('ar.viewInPlace')
1948
+ }),
1949
+ /*#__PURE__*/ jsx(Dialog, {
1950
+ open: open,
1951
+ onOpenChange: setOpen,
1952
+ children: /*#__PURE__*/ jsxs(DialogContent, {
1953
+ className: "gap-6 sm:max-w-120",
1954
+ showCloseButton: false,
1955
+ portalContainer: containerRef,
1956
+ children: [
1957
+ /*#__PURE__*/ jsx("div", {
1958
+ className: "flex items-center justify-center p-10 bg-constant-white rounded-2xl",
1959
+ children: /*#__PURE__*/ jsx(QRCode, {
1960
+ className: "size-48",
1961
+ robustness: "H",
1962
+ data: url
1963
+ })
1964
+ }),
1965
+ /*#__PURE__*/ jsx("p", {
1966
+ className: "text-lg text-center text-text-sub font-medium",
1967
+ children: t('ar.scanQrCode')
1968
+ }),
1969
+ /*#__PURE__*/ jsx(DialogClose, {
1970
+ asChild: true,
1971
+ children: /*#__PURE__*/ jsx(button_Button, {
1972
+ variant: "contrast",
1973
+ size: "lg",
1974
+ children: t('close')
1975
+ })
1976
+ })
1977
+ ]
1978
+ })
1979
+ }),
1980
+ /*#__PURE__*/ jsx("model-viewer", {
1981
+ ref: modelViewerRef,
1982
+ className: "fixed w-px h-px opacity-0 pointer-events-none",
1983
+ src: glbUrl,
1984
+ iosSrc: usdzUrl,
1985
+ reveal: "manual",
1986
+ ar: true,
1987
+ cameraControls: true,
1988
+ arPlacement: "wall",
1989
+ arModes: "webxr",
1990
+ arScale: "fixed",
1991
+ shadowIntensity: 1,
1992
+ interactionPromptThreshold: 100
1993
+ })
1994
+ ]
1995
+ });
1996
+ }
1997
+ const initialSize = {
1998
+ width: void 0,
1999
+ height: void 0
2000
+ };
2001
+ function useIsMounted() {
2002
+ const isMounted = useRef(false);
2003
+ useEffect(()=>{
2004
+ isMounted.current = true;
2005
+ return ()=>{
2006
+ isMounted.current = false;
2007
+ };
2008
+ }, []);
2009
+ return useCallback(()=>isMounted.current, []);
2010
+ }
2011
+ function useResizeObserver(options) {
2012
+ const { ref, box = 'content-box' } = options;
2013
+ const [{ width, height }, setSize] = useState(initialSize);
2014
+ const isMounted = useIsMounted();
2015
+ const previousSize = useRef({
2016
+ ...initialSize
2017
+ });
2018
+ const onResize = useRef(void 0);
2019
+ onResize.current = options.onResize;
2020
+ useEffect(()=>{
2021
+ if (!ref.current) return;
2022
+ if ('undefined' == typeof window || !('ResizeObserver' in window)) return;
2023
+ const observer = new ResizeObserver(([entry])=>{
2024
+ const boxProp = 'border-box' === box ? 'borderBoxSize' : 'device-pixel-content-box' === box ? 'devicePixelContentBoxSize' : 'contentBoxSize';
2025
+ const newWidth = extractSize(entry, boxProp, 'inlineSize');
2026
+ const newHeight = extractSize(entry, boxProp, 'blockSize');
2027
+ const hasChanged = previousSize.current.width !== newWidth || previousSize.current.height !== newHeight;
2028
+ if (hasChanged) {
2029
+ const newSize = {
2030
+ width: newWidth,
2031
+ height: newHeight
2032
+ };
2033
+ previousSize.current.width = newWidth;
2034
+ previousSize.current.height = newHeight;
2035
+ if (onResize.current) onResize.current(newSize);
2036
+ else if (isMounted()) setSize(newSize);
2037
+ }
2038
+ });
2039
+ observer.observe(ref.current, {
2040
+ box
2041
+ });
2042
+ return ()=>{
2043
+ observer.disconnect();
2044
+ };
2045
+ }, [
2046
+ box,
2047
+ ref,
2048
+ isMounted
2049
+ ]);
2050
+ return {
2051
+ width,
2052
+ height
2053
+ };
2054
+ }
2055
+ function extractSize(entry, box, sizeType) {
2056
+ if (!entry[box]) {
2057
+ if ('contentBoxSize' === box) return entry.contentRect['inlineSize' === sizeType ? 'width' : 'height'];
2058
+ return;
2059
+ }
2060
+ return Array.isArray(entry[box]) ? entry[box][0][sizeType] : entry[box][sizeType];
2061
+ }
2062
+ function useRoomImageAspectRatio(src) {
2063
+ const [aspectRatio, setAspectRatio] = useState(null);
2064
+ useEffect(()=>{
2065
+ let isCancelled = false;
2066
+ const image = new Image();
2067
+ setAspectRatio(null);
2068
+ image.crossOrigin = 'anonymous';
2069
+ image.decoding = 'async';
2070
+ image.onload = ()=>{
2071
+ if (!isCancelled && image.height > 0) setAspectRatio(image.width / image.height);
2072
+ };
2073
+ image.onerror = ()=>{
2074
+ if (!isCancelled) setAspectRatio(null);
2075
+ };
2076
+ image.src = src;
2077
+ return ()=>{
2078
+ isCancelled = true;
2079
+ };
2080
+ }, [
2081
+ src
2082
+ ]);
2083
+ return aspectRatio;
2084
+ }
2085
+ async function canvasToPngBlob(canvas, errorMessage) {
2086
+ return new Promise((resolve, reject)=>{
2087
+ canvas.toBlob((blob)=>{
2088
+ if (!blob) return void reject(new Error(errorMessage));
2089
+ resolve(blob);
2090
+ }, 'image/png');
2091
+ });
2092
+ }
2093
+ function CanvasCaptureBridge({ ref }) {
2094
+ const { camera, gl, invalidate, scene } = useThree();
2095
+ useImperativeHandle(ref, ()=>({
2096
+ capture: async ()=>{
2097
+ invalidate();
2098
+ await new Promise((resolve)=>requestAnimationFrame(()=>resolve()));
2099
+ gl.render(scene, camera);
2100
+ return canvasToPngBlob(gl.domElement, 'Failed to create scene share image');
2101
+ }
2102
+ }), [
2103
+ camera,
2104
+ gl,
2105
+ invalidate,
2106
+ scene
2107
+ ]);
2108
+ return null;
2109
+ }
2110
+ function buildResourcePoseFromHit(hit) {
2111
+ const position = hit.point.clone();
2112
+ const normal = new Vector3(0, 0, 1).applyQuaternion(hit.object.quaternion);
2113
+ position.addScaledVector(normal, SURFACE_OFFSET);
2114
+ return {
2115
+ position: position.toArray(),
2116
+ quaternion: hit.object.quaternion.toArray()
2117
+ };
2118
+ }
2119
+ const FLOOR_SEGMENTATION_CLASS = 2;
2120
+ const CARPET_SEGMENTATION_CLASS = 3;
2121
+ function isFloorOrCarpetSegmentationClass(classId) {
2122
+ return classId === FLOOR_SEGMENTATION_CLASS || classId === CARPET_SEGMENTATION_CLASS;
2123
+ }
2124
+ function getFloorOrCarpetMaskCentroid(mask) {
2125
+ let sumX = 0;
2126
+ let sumY = 0;
2127
+ let count = 0;
2128
+ for(let y = 0; y < mask.height; y++)for(let x = 0; x < mask.width; x++){
2129
+ const classId = mask.classes[y * mask.width + x];
2130
+ const isFloorArea = isFloorOrCarpetSegmentationClass(classId);
2131
+ if (isFloorArea) {
2132
+ sumX += x + 0.5;
2133
+ sumY += y + 0.5;
2134
+ count += 1;
2135
+ }
2136
+ }
2137
+ if (0 === count) return null;
2138
+ return {
2139
+ x: sumX / count,
2140
+ y: sumY / count
2141
+ };
2142
+ }
2143
+ function getInitialCarpetPose(surface, mask, camera) {
2144
+ const planePose = calculatePlanePose(surface.z, surface.pitchDeg, surface.rollDeg, camera);
2145
+ const maskCentroid = getFloorOrCarpetMaskCentroid(mask);
2146
+ if (!maskCentroid) return null;
2147
+ const quaternion = new Quaternion(...planePose.quaternion);
2148
+ const normal = new Vector3(0, 0, 1).applyQuaternion(quaternion).normalize();
2149
+ const planePosition = new Vector3(...planePose.position);
2150
+ const ndcX = maskCentroid.x / mask.width * 2 - 1;
2151
+ const ndcY = 1 - maskCentroid.y / mask.height * 2;
2152
+ const raycaster = new Raycaster();
2153
+ raycaster.setFromCamera(new Vector2(ndcX, ndcY), camera);
2154
+ const plane = new Plane().setFromNormalAndCoplanarPoint(normal, planePosition);
2155
+ const hitPoint = raycaster.ray.intersectPlane(plane, new Vector3());
2156
+ if (!hitPoint) return null;
2157
+ hitPoint.addScaledVector(normal, SURFACE_OFFSET);
2158
+ return {
2159
+ position: hitPoint.toArray(),
2160
+ quaternion: planePose.quaternion
2161
+ };
2162
+ }
2163
+ function loadImage(url) {
2164
+ return new Promise((resolve, reject)=>{
2165
+ const image = new Image();
2166
+ image.crossOrigin = 'anonymous';
2167
+ image.onload = ()=>resolve(image);
2168
+ image.onerror = ()=>reject(new Error(`Failed to load image from ${url}`));
2169
+ image.src = url;
2170
+ });
2171
+ }
2172
+ function buildSegmentationMaskCanvas(classes, width, height) {
2173
+ const canvas = document.createElement('canvas');
2174
+ canvas.width = width;
2175
+ canvas.height = height;
2176
+ const context = canvas.getContext('2d');
2177
+ if (!context) throw new Error('Failed to get 2D context for segmentation mask.');
2178
+ const imageData = context.createImageData(width, height);
2179
+ const pixelCount = width * height;
2180
+ for(let index = 0; index < pixelCount; index++){
2181
+ const classId = classes[index];
2182
+ const visible = isFloorOrCarpetSegmentationClass(classId);
2183
+ imageData.data[4 * index] = 255;
2184
+ imageData.data[4 * index + 1] = 255;
2185
+ imageData.data[4 * index + 2] = 255;
2186
+ imageData.data[4 * index + 3] = visible ? 255 : 0;
2187
+ }
2188
+ context.putImageData(imageData, 0, 0);
2189
+ return canvas;
2190
+ }
2191
+ function createRenderMaskCanvas(segmentationClasses, scaleFactor) {
2192
+ const sourceCanvas = buildSegmentationMaskCanvas(segmentationClasses.classes, segmentationClasses.width, segmentationClasses.height);
2193
+ const width = Math.max(1, segmentationClasses.width * scaleFactor);
2194
+ const height = Math.max(1, segmentationClasses.height * scaleFactor);
2195
+ const canvas = document.createElement('canvas');
2196
+ canvas.width = width;
2197
+ canvas.height = height;
2198
+ const context = canvas.getContext('2d');
2199
+ if (!context) throw new Error('Failed to get 2D context for upscaled segmentation mask.');
2200
+ context.imageSmoothingEnabled = true;
2201
+ context.imageSmoothingQuality = 'high';
2202
+ context.drawImage(sourceCanvas, 0, 0, width, height);
2203
+ return canvas;
2204
+ }
2205
+ async function loadCarpetSegmentationFromMask(maskUrl) {
2206
+ const maskImage = await loadImage(maskUrl);
2207
+ const canvas = document.createElement('canvas');
2208
+ canvas.width = maskImage.naturalWidth || maskImage.width;
2209
+ canvas.height = maskImage.naturalHeight || maskImage.height;
2210
+ const context = canvas.getContext('2d');
2211
+ if (!context) throw new Error('Failed to get 2D context for segmentation mask.');
2212
+ context.drawImage(maskImage, 0, 0);
2213
+ const { width, height } = canvas;
2214
+ const imageData = context.getImageData(0, 0, width, height);
2215
+ const classes = new Uint8Array(width * height);
2216
+ for(let index = 0; index < classes.length; index++){
2217
+ const alpha = imageData.data[4 * index + 3];
2218
+ classes[index] = alpha > 127 ? 2 : 0;
2219
+ }
2220
+ return {
2221
+ segmentationClasses: {
2222
+ classes,
2223
+ width,
2224
+ height
2225
+ }
2226
+ };
2227
+ }
2228
+ async function createMaskUrl(segmentationClasses) {
2229
+ const maskCanvas = buildSegmentationMaskCanvas(segmentationClasses.classes, segmentationClasses.width, segmentationClasses.height);
2230
+ const maskBlob = await canvasToPngBlob(maskCanvas, 'Failed to serialize segmentation mask PNG.');
2231
+ return URL.createObjectURL(maskBlob);
2232
+ }
2233
+ function sanitizeExportFileBase(imageUrl) {
2234
+ return imageUrl.replace(/[^a-z0-9]+/gi, '-').replace(/^-+|-+$/g, '').toLowerCase();
2235
+ }
2236
+ function downloadBlob(blob, filename) {
2237
+ const objectUrl = URL.createObjectURL(blob);
2238
+ const anchor = document.createElement('a');
2239
+ anchor.href = objectUrl;
2240
+ anchor.download = filename;
2241
+ anchor.click();
2242
+ URL.revokeObjectURL(objectUrl);
2243
+ }
2244
+ async function exportCarpetInferenceSnapshot(imageUrl, floor, segmentationClasses) {
2245
+ const fileBase = sanitizeExportFileBase(imageUrl);
2246
+ const maskFile = `${fileBase}-mask.png`;
2247
+ const metaFile = `${fileBase}.json`;
2248
+ const maskCanvas = buildSegmentationMaskCanvas(segmentationClasses.classes, segmentationClasses.width, segmentationClasses.height);
2249
+ const maskBlob = await canvasToPngBlob(maskCanvas, 'Failed to export segmentation mask PNG.');
2250
+ const snapshot = {
2251
+ imageUrl,
2252
+ floor,
2253
+ segmentation: {
2254
+ width: segmentationClasses.width,
2255
+ height: segmentationClasses.height,
2256
+ maskFile
2257
+ }
2258
+ };
2259
+ const snapshotBlob = new Blob([
2260
+ JSON.stringify(snapshot, null, 2)
2261
+ ], {
2262
+ type: 'application/json'
2263
+ });
2264
+ downloadBlob(maskBlob, maskFile);
2265
+ downloadBlob(snapshotBlob, metaFile);
2266
+ }
2267
+ const MODEL_INPUT_SIZE = 512;
2268
+ const MODEL_INPUT_PIXELS = MODEL_INPUT_SIZE * MODEL_INPUT_SIZE;
2269
+ const Z_SCALE_CM = 400;
2270
+ const ROLL_SCALE_DEG = 45;
2271
+ const PITCH_SCALE_DEG = 90;
2272
+ const MODEL_INPUT_CHANNEL_ORDER = 'GBR';
2273
+ function getModelChannelOffsets(channelOrder) {
2274
+ const offsets = {
2275
+ R: 0,
2276
+ G: 1,
2277
+ B: 2
2278
+ };
2279
+ const channels = channelOrder.toUpperCase().split('');
2280
+ if (3 !== channels.length || channels.some((channel)=>!(channel in offsets))) throw new Error(`Unsupported carpet inference input channel order: ${channelOrder}`);
2281
+ return channels.map((channel)=>offsets[channel]);
2282
+ }
2283
+ function bytesToBase64(bytes) {
2284
+ return new Promise((resolve, reject)=>{
2285
+ const reader = new FileReader();
2286
+ const arrayBuffer = new ArrayBuffer(bytes.byteLength);
2287
+ new Uint8Array(arrayBuffer).set(bytes);
2288
+ reader.onerror = ()=>{
2289
+ reject(new Error('Failed to encode carpet inference payload.'));
2290
+ };
2291
+ reader.onload = ()=>{
2292
+ if ('string' != typeof reader.result) return void reject(new Error('Failed to encode carpet inference payload.'));
2293
+ const separatorIndex = reader.result.indexOf(',');
2294
+ resolve(separatorIndex >= 0 ? reader.result.slice(separatorIndex + 1) : reader.result);
2295
+ };
2296
+ reader.readAsDataURL(new Blob([
2297
+ arrayBuffer
2298
+ ]));
2299
+ });
2300
+ }
2301
+ function base64ToBytes(base64) {
2302
+ const binary = atob(base64);
2303
+ const bytes = new Uint8Array(binary.length);
2304
+ for(let index = 0; index < binary.length; index++)bytes[index] = binary.charCodeAt(index);
2305
+ return bytes;
2306
+ }
2307
+ function encodeUint8ArrayToBase64(data) {
2308
+ return bytesToBase64(data);
2309
+ }
2310
+ function decodeSegmentationClasses(base64) {
2311
+ const classes = base64ToBytes(base64);
2312
+ if (classes.length !== MODEL_INPUT_PIXELS) throw new Error(`Unsupported segmentation classes length: ${classes.length}`);
2313
+ return {
2314
+ classes,
2315
+ width: MODEL_INPUT_SIZE,
2316
+ height: MODEL_INPUT_SIZE
2317
+ };
2318
+ }
2319
+ function createInputData(image) {
2320
+ const canvas = document.createElement('canvas');
2321
+ canvas.width = MODEL_INPUT_SIZE;
2322
+ canvas.height = MODEL_INPUT_SIZE;
2323
+ const context = canvas.getContext('2d');
2324
+ if (!context) throw new Error('Failed to get 2D context for carpet inference input.');
2325
+ context.drawImage(image, 0, 0, MODEL_INPUT_SIZE, MODEL_INPUT_SIZE);
2326
+ const imageData = context.getImageData(0, 0, MODEL_INPUT_SIZE, MODEL_INPUT_SIZE);
2327
+ const channelOffsets = getModelChannelOffsets(MODEL_INPUT_CHANNEL_ORDER);
2328
+ const uint8Data = new Uint8Array(3 * MODEL_INPUT_PIXELS);
2329
+ for(let source = 0, target = 0; source < 4 * MODEL_INPUT_PIXELS; source += 4, target += 3){
2330
+ uint8Data[target] = imageData.data[source + channelOffsets[0]];
2331
+ uint8Data[target + 1] = imageData.data[source + channelOffsets[1]];
2332
+ uint8Data[target + 2] = imageData.data[source + channelOffsets[2]];
2333
+ }
2334
+ return uint8Data;
2335
+ }
2336
+ function toFloorPose(result) {
2337
+ return {
2338
+ z: Number(result.deltaZ ?? 0) * Z_SCALE_CM,
2339
+ pitchDeg: -(Number(result.pitch ?? 0) * PITCH_SCALE_DEG),
2340
+ rollDeg: Number(result.roll ?? 0) * ROLL_SCALE_DEG
2341
+ };
2342
+ }
2343
+ async function runCarpetInferenceRaw(data) {
2344
+ const encodedData = await encodeUint8ArrayToBase64(data);
2345
+ const response = await fetch(CARPET_INFERENCE_RAW_URL, {
2346
+ method: 'POST',
2347
+ headers: {
2348
+ 'Content-Type': 'application/json'
2349
+ },
2350
+ body: JSON.stringify({
2351
+ data: encodedData
2352
+ })
2353
+ });
2354
+ if (!response.ok) {
2355
+ const errorText = await response.text();
2356
+ throw new Error(`Carpet inference request failed: ${response.status} ${response.statusText}${errorText ? ` - ${errorText}` : ''}`);
2357
+ }
2358
+ return response.json();
2359
+ }
2360
+ async function inferCarpetFromRoomImage(imageUrl) {
2361
+ const image = await loadImage(imageUrl);
2362
+ const inputData = createInputData(image);
2363
+ const result = await runCarpetInferenceRaw(inputData);
2364
+ const segmentationClasses = decodeSegmentationClasses(result.segmentationClasses);
2365
+ const floor = toFloorPose(result);
2366
+ if (CARPET_INFERENCE_EXPORT_ENABLED) await exportCarpetInferenceSnapshot(imageUrl, floor, {
2367
+ classes: segmentationClasses.classes,
2368
+ width: segmentationClasses.width,
2369
+ height: segmentationClasses.height
2370
+ });
2371
+ return {
2372
+ segmentationClasses,
2373
+ floor
2374
+ };
2375
+ }
2376
+ const DEFAULT_SCALE_DIV = 8;
2377
+ const DEFAULT_FILTER_DIAMETER = 4;
2378
+ const DEFAULT_SIGMA_COLOR = 70;
2379
+ const DEFAULT_SIGMA_SPACE = 70;
2380
+ const NEUTRAL_LIGHTING_VALUE = 128;
2381
+ const HIGHLIGHT_LIGHTING_CONTRAST = 0.8;
2382
+ const SHADOW_LIGHTING_CONTRAST = 1.15;
2383
+ function getCanvasContext(canvas) {
2384
+ const context = canvas.getContext('2d');
2385
+ if (!context) throw new Error('Failed to get 2D context for carpet lighting map.');
2386
+ return context;
2387
+ }
2388
+ function createCanvas(width, height) {
2389
+ const canvas = document.createElement('canvas');
2390
+ canvas.width = width;
2391
+ canvas.height = height;
2392
+ return canvas;
2393
+ }
2394
+ function createNeutralLightingCanvas(width, height) {
2395
+ const canvas = createCanvas(width, height);
2396
+ const context = getCanvasContext(canvas);
2397
+ const imageData = context.createImageData(width, height);
2398
+ for(let index = 0; index < width * height; index++){
2399
+ const offset = 4 * index;
2400
+ imageData.data[offset] = NEUTRAL_LIGHTING_VALUE;
2401
+ imageData.data[offset + 1] = NEUTRAL_LIGHTING_VALUE;
2402
+ imageData.data[offset + 2] = NEUTRAL_LIGHTING_VALUE;
2403
+ imageData.data[offset + 3] = 255;
2404
+ }
2405
+ context.putImageData(imageData, 0, 0);
2406
+ return canvas;
2407
+ }
2408
+ function extractGrayscale(imageData) {
2409
+ const grayscale = new Uint8ClampedArray(imageData.width * imageData.height);
2410
+ for(let index = 0; index < grayscale.length; index++){
2411
+ const offset = 4 * index;
2412
+ const r = imageData.data[offset];
2413
+ const g = imageData.data[offset + 1];
2414
+ const b = imageData.data[offset + 2];
2415
+ grayscale[index] = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
2416
+ }
2417
+ return grayscale;
2418
+ }
2419
+ function grayscaleToCanvas(grayscale, width, height) {
2420
+ const canvas = createCanvas(width, height);
2421
+ const context = getCanvasContext(canvas);
2422
+ const imageData = context.createImageData(width, height);
2423
+ for(let index = 0; index < width * height; index++){
2424
+ const value = Math.max(0, Math.min(255, Math.round(Number(grayscale[index] ?? 0))));
2425
+ const offset = 4 * index;
2426
+ imageData.data[offset] = value;
2427
+ imageData.data[offset + 1] = value;
2428
+ imageData.data[offset + 2] = value;
2429
+ imageData.data[offset + 3] = 255;
2430
+ }
2431
+ context.putImageData(imageData, 0, 0);
2432
+ return canvas;
2433
+ }
2434
+ function resizeGrayscale(grayscale, sourceWidth, sourceHeight, targetWidth, targetHeight) {
2435
+ const sourceCanvas = grayscaleToCanvas(grayscale, sourceWidth, sourceHeight);
2436
+ const targetCanvas = createCanvas(targetWidth, targetHeight);
2437
+ const context = getCanvasContext(targetCanvas);
2438
+ context.imageSmoothingEnabled = true;
2439
+ context.drawImage(sourceCanvas, 0, 0, targetWidth, targetHeight);
2440
+ return extractGrayscale(context.getImageData(0, 0, targetWidth, targetHeight));
2441
+ }
2442
+ function multiplyGrayscaleByWeights(grayscale, weights) {
2443
+ const result = new Float32Array(grayscale.length);
2444
+ for(let index = 0; index < grayscale.length; index++)result[index] = Number(grayscale[index] ?? 0) * (Number(weights[index] ?? 0) / 255);
2445
+ return result;
2446
+ }
2447
+ function restoreGrayscaleFromWeights(grayscale, weights) {
2448
+ const result = new Uint8ClampedArray(grayscale.length);
2449
+ for(let index = 0; index < grayscale.length; index++){
2450
+ const coverage = Number(weights[index] ?? 0) / 255;
2451
+ if (coverage <= 0.0001) {
2452
+ result[index] = 0;
2453
+ continue;
2454
+ }
2455
+ const value = Number(grayscale[index] ?? 0) / coverage;
2456
+ result[index] = Math.max(0, Math.min(255, Math.round(value)));
2457
+ }
2458
+ return result;
2459
+ }
2460
+ function bilateralFilter(grayscale, width, height, filterDiameter, sigmaColor, sigmaSpace, sampleWeights) {
2461
+ const result = new Uint8ClampedArray(grayscale.length);
2462
+ const radius = Math.max(1, Math.floor(filterDiameter / 2));
2463
+ const safeSigmaColor = Math.max(1, sigmaColor);
2464
+ const safeSigmaSpace = Math.max(1, sigmaSpace);
2465
+ const colorDenominator = 2 * safeSigmaColor * safeSigmaColor;
2466
+ const spatialDenominator = 2 * safeSigmaSpace * safeSigmaSpace;
2467
+ const spatialWeights = new Float32Array((2 * radius + 1) * (2 * radius + 1));
2468
+ let spatialWeightIndex = 0;
2469
+ for(let offsetY = -radius; offsetY <= radius; offsetY++)for(let offsetX = -radius; offsetX <= radius; offsetX++){
2470
+ const distanceSquared = offsetX * offsetX + offsetY * offsetY;
2471
+ spatialWeights[spatialWeightIndex] = Math.exp(-distanceSquared / spatialDenominator);
2472
+ spatialWeightIndex += 1;
2473
+ }
2474
+ for(let y = 0; y < height; y++)for(let x = 0; x < width; x++){
2475
+ const centerIndex = y * width + x;
2476
+ const centerSampleWeight = Number(sampleWeights?.[centerIndex] ?? 255) / 255;
2477
+ if (centerSampleWeight <= 0.0001) {
2478
+ result[centerIndex] = 0;
2479
+ continue;
2480
+ }
2481
+ const centerValue = grayscale[centerIndex];
2482
+ let weightSum = 0;
2483
+ let valueSum = 0;
2484
+ let kernelIndex = 0;
2485
+ for(let offsetY = -radius; offsetY <= radius; offsetY++){
2486
+ const sampleY = Math.max(0, Math.min(height - 1, y + offsetY));
2487
+ for(let offsetX = -radius; offsetX <= radius; offsetX++){
2488
+ const sampleX = Math.max(0, Math.min(width - 1, x + offsetX));
2489
+ const sampleIndex = sampleY * width + sampleX;
2490
+ const sampleWeight = Number(sampleWeights?.[sampleIndex] ?? 255) / 255;
2491
+ if (sampleWeight <= 0.0001) {
2492
+ kernelIndex += 1;
2493
+ continue;
2494
+ }
2495
+ const sampleValue = grayscale[sampleIndex];
2496
+ const delta = sampleValue - centerValue;
2497
+ const rangeWeight = Math.exp(-(delta * delta) / colorDenominator);
2498
+ const weight = spatialWeights[kernelIndex] * rangeWeight * sampleWeight;
2499
+ weightSum += weight;
2500
+ valueSum += sampleValue * weight;
2501
+ kernelIndex += 1;
2502
+ }
2503
+ }
2504
+ result[centerIndex] = weightSum > 0 ? Math.round(valueSum / weightSum) : centerValue;
2505
+ }
2506
+ return result;
2507
+ }
2508
+ function isLightingClass(classId) {
2509
+ return 2 === classId || 3 === classId;
2510
+ }
2511
+ function createLightingWeights(classes) {
2512
+ const weights = new Uint8ClampedArray(classes.length);
2513
+ for(let index = 0; index < classes.length; index++)weights[index] = isLightingClass(classes[index]) ? 255 : 0;
2514
+ return weights;
2515
+ }
2516
+ function getLightingExtremes(grayscale, classes, mean) {
2517
+ let min = 1 / 0;
2518
+ let max = -1 / 0;
2519
+ for(let index = 0; index < classes.length; index++){
2520
+ if (!isLightingClass(classes[index])) continue;
2521
+ const value = grayscale[index];
2522
+ min = Math.min(min, value);
2523
+ max = Math.max(max, value);
2524
+ }
2525
+ if (!Number.isFinite(min) || !Number.isFinite(max)) return {
2526
+ maxBrightnessDelta: 0,
2527
+ maxShadowDelta: 0
2528
+ };
2529
+ return {
2530
+ maxBrightnessDelta: Math.max(0, max - mean),
2531
+ maxShadowDelta: Math.max(0, mean - min)
2532
+ };
2533
+ }
2534
+ function encodeLightingValue(delta, extremes) {
2535
+ if (0 === delta) return NEUTRAL_LIGHTING_VALUE;
2536
+ const isShadow = delta < 0;
2537
+ const maxDelta = isShadow ? extremes.maxShadowDelta : extremes.maxBrightnessDelta;
2538
+ if (maxDelta <= 0) return NEUTRAL_LIGHTING_VALUE;
2539
+ const contrast = isShadow ? SHADOW_LIGHTING_CONTRAST : HIGHLIGHT_LIGHTING_CONTRAST;
2540
+ const normalized = 0.5 + delta / maxDelta * 0.5 * contrast;
2541
+ return Math.max(0, Math.min(255, Math.round(255 * normalized)));
2542
+ }
2543
+ async function createLightingCanvas(imageUrl, segmentationClasses, options = {}) {
2544
+ const { width, height, classes } = segmentationClasses;
2545
+ if (width <= 0 || height <= 0 || 0 === classes.length) return createNeutralLightingCanvas(width, height);
2546
+ const image = await loadImage(imageUrl);
2547
+ const sourceCanvas = createCanvas(width, height);
2548
+ const sourceContext = getCanvasContext(sourceCanvas);
2549
+ sourceContext.drawImage(image, 0, 0, width, height);
2550
+ const grayscale = extractGrayscale(sourceContext.getImageData(0, 0, width, height));
2551
+ const lightingWeights = createLightingWeights(classes);
2552
+ const scaleDiv = Math.max(1, Math.round(options.scaleDiv ?? DEFAULT_SCALE_DIV));
2553
+ const filterDiameter = Math.max(1, Math.round(options.filterDiameter ?? DEFAULT_FILTER_DIAMETER));
2554
+ const sigmaColor = options.sigmaColor ?? DEFAULT_SIGMA_COLOR;
2555
+ const sigmaSpace = options.sigmaSpace ?? DEFAULT_SIGMA_SPACE;
2556
+ const smallWidth = Math.max(1, Math.round(width / scaleDiv));
2557
+ const smallHeight = Math.max(1, Math.round(height / scaleDiv));
2558
+ const maskedGrayscale = multiplyGrayscaleByWeights(grayscale, lightingWeights);
2559
+ const downscaledMasked = resizeGrayscale(maskedGrayscale, width, height, smallWidth, smallHeight);
2560
+ const downscaledWeights = resizeGrayscale(lightingWeights, width, height, smallWidth, smallHeight);
2561
+ const downscaled = restoreGrayscaleFromWeights(downscaledMasked, downscaledWeights);
2562
+ const blurred = bilateralFilter(downscaled, smallWidth, smallHeight, filterDiameter, sigmaColor, sigmaSpace, downscaledWeights);
2563
+ const blurredMasked = multiplyGrayscaleByWeights(blurred, downscaledWeights);
2564
+ const smoothedMasked = resizeGrayscale(blurredMasked, smallWidth, smallHeight, width, height);
2565
+ const smoothedWeights = resizeGrayscale(downscaledWeights, smallWidth, smallHeight, width, height);
2566
+ const smoothed = restoreGrayscaleFromWeights(smoothedMasked, smoothedWeights);
2567
+ let maskCount = 0;
2568
+ let meanSum = 0;
2569
+ for(let index = 0; index < classes.length; index++)if (isLightingClass(classes[index])) {
2570
+ meanSum += smoothed[index];
2571
+ maskCount += 1;
2572
+ }
2573
+ if (0 === maskCount) return createNeutralLightingCanvas(width, height);
2574
+ const mean = meanSum / maskCount;
2575
+ const extremes = getLightingExtremes(smoothed, classes, mean);
2576
+ if (extremes.maxBrightnessDelta <= 0 && extremes.maxShadowDelta <= 0) return createNeutralLightingCanvas(width, height);
2577
+ const canvas = createCanvas(width, height);
2578
+ const context = getCanvasContext(canvas);
2579
+ const imageData = context.createImageData(width, height);
2580
+ for(let index = 0; index < classes.length; index++){
2581
+ const offset = 4 * index;
2582
+ let value = NEUTRAL_LIGHTING_VALUE;
2583
+ if (isLightingClass(classes[index])) {
2584
+ const delta = smoothed[index] - mean;
2585
+ value = encodeLightingValue(delta, extremes);
2586
+ }
2587
+ imageData.data[offset] = value;
2588
+ imageData.data[offset + 1] = value;
2589
+ imageData.data[offset + 2] = value;
2590
+ imageData.data[offset + 3] = 255;
2591
+ }
2592
+ context.putImageData(imageData, 0, 0);
2593
+ return canvas;
2594
+ }
2595
+ function getCarpetSurface(surfaces) {
2596
+ return surfaces.find((surface)=>surface.id === FLOOR_SURFACE_ID) ?? null;
2597
+ }
2598
+ function useCarpetInference(enabled) {
2599
+ const { room, setRooms } = useRoomViewer();
2600
+ const get = useThree((state)=>state.get);
2601
+ const floor = getCarpetSurface(room.config.surfaces);
2602
+ const query = react_query_useQuery({
2603
+ queryKey: [
2604
+ 'room-viewer',
2605
+ 'carpet-inference',
2606
+ room.id,
2607
+ room.image
2608
+ ],
2609
+ enabled,
2610
+ retry: 1,
2611
+ staleTime: 0,
2612
+ gcTime: 0,
2613
+ refetchOnWindowFocus: false,
2614
+ refetchOnReconnect: false,
2615
+ queryFn: async ()=>{
2616
+ if (!floor) throw new Error('Floor surface is missing for carpet inference.');
2617
+ const { camera } = get();
2618
+ if (room.config.maskUrl) {
2619
+ const data = await loadCarpetSegmentationFromMask(room.config.maskUrl);
2620
+ return {
2621
+ ...data,
2622
+ initialPose: getInitialCarpetPose(floor, data.segmentationClasses, camera)
2623
+ };
2624
+ }
2625
+ const inference = await inferCarpetFromRoomImage(room.image);
2626
+ const segmentationClasses = inference.segmentationClasses;
2627
+ const inferredFloor = {
2628
+ id: FLOOR_SURFACE_ID,
2629
+ ...inference.floor
2630
+ };
2631
+ const maskUrl = await createMaskUrl(segmentationClasses);
2632
+ return {
2633
+ segmentationClasses,
2634
+ floor: inferredFloor,
2635
+ initialPose: getInitialCarpetPose(inferredFloor, segmentationClasses, camera),
2636
+ maskUrl
2637
+ };
2638
+ }
2639
+ });
2640
+ useEffect(()=>{
2641
+ if (!query.data?.maskUrl || !query.data.floor || room.config.maskUrl === query.data.maskUrl) return;
2642
+ const { floor, maskUrl } = query.data;
2643
+ setRooms((currentRooms)=>currentRooms.map((currentRoom)=>{
2644
+ if (currentRoom.id !== room.id) return currentRoom;
2645
+ return {
2646
+ ...currentRoom,
2647
+ config: {
2648
+ ...currentRoom.config,
2649
+ maskUrl,
2650
+ surfaces: currentRoom.config.surfaces.map((surface)=>surface.id === FLOOR_SURFACE_ID ? floor : surface)
2651
+ }
2652
+ };
2653
+ }));
2654
+ }, [
2655
+ query.data,
2656
+ room.config.maskUrl,
2657
+ room.id,
2658
+ setRooms
2659
+ ]);
2660
+ return query;
2661
+ }
2662
+ function useSceneHelpers() {
2663
+ const { type } = useRoomViewer();
2664
+ const get = useThree((state)=>state.get);
2665
+ const findPlacementHit = useCallback((pointer, surfaceMeshes)=>{
2666
+ const { camera, raycaster } = get();
2667
+ raycaster.setFromCamera(pointer, camera);
2668
+ return raycaster.intersectObjects(surfaceMeshes, false).find((intersection)=>{
2669
+ const surfaceId = intersection.object.userData.surfaceId;
2670
+ if (!surfaceId) return false;
2671
+ if ('carpet' === type) return surfaceId === FLOOR_SURFACE_ID;
2672
+ return surfaceId !== FLOOR_SURFACE_ID;
2673
+ });
2674
+ }, [
2675
+ get,
2676
+ type
2677
+ ]);
2678
+ return {
2679
+ findPlacementHit
2680
+ };
2681
+ }
2682
+ const MASK_MAP_UPSCALE_FACTOR = 4;
2683
+ function useCarpetCanvases(segmentationClasses) {
2684
+ const { room, patchSceneLoad } = useRoomViewer();
2685
+ const enabled = null != segmentationClasses;
2686
+ const query = react_query_useQuery({
2687
+ queryKey: [
2688
+ 'room-viewer',
2689
+ 'carpet-canvases',
2690
+ room.id,
2691
+ room.image
2692
+ ],
2693
+ enabled,
2694
+ retry: 1,
2695
+ staleTime: 0,
2696
+ gcTime: 0,
2697
+ refetchOnWindowFocus: false,
2698
+ refetchOnReconnect: false,
2699
+ queryFn: async ()=>{
2700
+ if (!segmentationClasses) throw new Error('Segmentation classes are required to create carpet canvases.');
2701
+ const maskCanvas = createRenderMaskCanvas(segmentationClasses, MASK_MAP_UPSCALE_FACTOR);
2702
+ const lightingCanvas = await createLightingCanvas(room.image, segmentationClasses);
2703
+ return {
2704
+ lightingCanvas,
2705
+ maskCanvas
2706
+ };
2707
+ }
2708
+ });
2709
+ useEffect(()=>{
2710
+ patchSceneLoad({
2711
+ artifactsReady: Boolean(enabled && query.data)
2712
+ });
2713
+ }, [
2714
+ enabled,
2715
+ patchSceneLoad,
2716
+ query.data,
2717
+ room.id,
2718
+ room.image
2719
+ ]);
2720
+ return query;
2721
+ }
2722
+ const MASK_UNIFORM = 'uRoomMask';
2723
+ const LIGHTING_MAP_UNIFORM = 'uRoomLightingMap';
2724
+ const LIGHTING_STRENGTH_UNIFORM = 'uRoomLightingStrength';
2725
+ const RESOLUTION_UNIFORM = 'uRoomMaskResolution';
2726
+ const MASK_TEXTURE_SIZE_UNIFORM = 'uRoomMaskTextureSize';
2727
+ const MASK_THRESHOLD = 0.42;
2728
+ const LIGHTING_STRENGTH = 0.18;
2729
+ const MIN_LIGHTING_FACTOR = 0.76;
2730
+ const MAX_LIGHTING_FACTOR = 1.1;
2731
+ const MASK_BILATERAL_SAMPLE_STEP = 5.0;
2732
+ const MASK_BILATERAL_SIGMA_SPATIAL = 4.0;
2733
+ const MASK_BILATERAL_SIGMA_RANGE = 0.7;
2734
+ const MASK_BILATERAL_KERNEL_RADIUS = 2;
2735
+ function collectCarpetMaterials(sourceScene) {
2736
+ const materials = new Set();
2737
+ sourceScene.traverse((object)=>{
2738
+ if (!object.isMesh) return;
2739
+ const mesh = object;
2740
+ if (!mesh.material) return;
2741
+ if (Array.isArray(mesh.material)) {
2742
+ const clonedMaterials = mesh.material.map((material)=>{
2743
+ const clonedMaterial = material.clone();
2744
+ materials.add(clonedMaterial);
2745
+ return clonedMaterial;
2746
+ });
2747
+ mesh.material = clonedMaterials;
2748
+ return;
2749
+ }
2750
+ const clonedMaterial = mesh.material.clone();
2751
+ materials.add(clonedMaterial);
2752
+ mesh.material = clonedMaterial;
2753
+ });
2754
+ return Array.from(materials);
2755
+ }
2756
+ function createCanvasTexture(canvas, minFilter = LinearFilter, magFilter = LinearFilter) {
2757
+ const texture = new CanvasTexture(canvas);
2758
+ texture.needsUpdate = true;
2759
+ texture.minFilter = minFilter;
2760
+ texture.magFilter = magFilter;
2761
+ texture.generateMipmaps = false;
2762
+ return texture;
2763
+ }
2764
+ function patchMaterialWithMask(material, maskTexture, lightingTexture, resolution, maskTextureSize) {
2765
+ const previousOnBeforeCompile = material.onBeforeCompile;
2766
+ const previousCustomProgramCacheKey = material.customProgramCacheKey.bind(material);
2767
+ const previousTransparent = material.transparent;
2768
+ material.onBeforeCompile = (shader, renderer)=>{
2769
+ previousOnBeforeCompile?.call(material, shader, renderer);
2770
+ shader.uniforms[MASK_UNIFORM] = {
2771
+ value: maskTexture
2772
+ };
2773
+ shader.uniforms[LIGHTING_MAP_UNIFORM] = {
2774
+ value: lightingTexture
2775
+ };
2776
+ shader.uniforms[LIGHTING_STRENGTH_UNIFORM] = {
2777
+ value: LIGHTING_STRENGTH
2778
+ };
2779
+ shader.uniforms[RESOLUTION_UNIFORM] = {
2780
+ value: resolution
2781
+ };
2782
+ shader.uniforms[MASK_TEXTURE_SIZE_UNIFORM] = {
2783
+ value: maskTextureSize
2784
+ };
2785
+ shader.fragmentShader = shader.fragmentShader.replace('#include <common>', `
2786
+ #include <common>
2787
+ uniform sampler2D ${MASK_UNIFORM};
2788
+ uniform sampler2D ${LIGHTING_MAP_UNIFORM};
2789
+ uniform float ${LIGHTING_STRENGTH_UNIFORM};
2790
+ uniform vec2 ${RESOLUTION_UNIFORM};
2791
+ uniform vec2 ${MASK_TEXTURE_SIZE_UNIFORM};
2792
+
2793
+ float sampleRoomMaskAlpha(vec2 uv) {
2794
+ vec2 texel = vec2(${MASK_BILATERAL_SAMPLE_STEP.toFixed(1)}) / ${MASK_TEXTURE_SIZE_UNIFORM};
2795
+ float centerAlpha = texture2D(${MASK_UNIFORM}, uv).a;
2796
+ float spatialDenominator = 2.0 * ${MASK_BILATERAL_SIGMA_SPATIAL.toFixed(1)} * ${MASK_BILATERAL_SIGMA_SPATIAL.toFixed(1)};
2797
+ float rangeDenominator = 2.0 * ${MASK_BILATERAL_SIGMA_RANGE.toFixed(1)} * ${MASK_BILATERAL_SIGMA_RANGE.toFixed(1)};
2798
+ float weightSum = 0.0;
2799
+ float alphaSum = 0.0;
2800
+
2801
+ for (int offsetY = -${MASK_BILATERAL_KERNEL_RADIUS}; offsetY <= ${MASK_BILATERAL_KERNEL_RADIUS}; offsetY++) {
2802
+ for (int offsetX = -${MASK_BILATERAL_KERNEL_RADIUS}; offsetX <= ${MASK_BILATERAL_KERNEL_RADIUS}; offsetX++) {
2803
+ vec2 offset = vec2(float(offsetX), float(offsetY));
2804
+ float sampleAlpha = texture2D(${MASK_UNIFORM}, uv + offset * texel).a;
2805
+ float spatialWeight = exp(-dot(offset, offset) / spatialDenominator);
2806
+ float rangeDelta = sampleAlpha - centerAlpha;
2807
+ float rangeWeight = exp(-(rangeDelta * rangeDelta) / rangeDenominator);
2808
+ float weight = spatialWeight * rangeWeight;
2809
+
2810
+ weightSum += weight;
2811
+ alphaSum += sampleAlpha * weight;
2812
+ }
2813
+ }
2814
+
2815
+ return weightSum > 0.0 ? alphaSum / weightSum : centerAlpha;
2816
+ }
2817
+ `);
2818
+ shader.fragmentShader = shader.fragmentShader.replace('#include <dithering_fragment>', `
2819
+ vec2 roomMaskUv = gl_FragCoord.xy / ${RESOLUTION_UNIFORM};
2820
+ float roomMaskAlpha = sampleRoomMaskAlpha(roomMaskUv);
2821
+ if (roomMaskAlpha < 0.01) discard;
2822
+ float roomLightingEncoded = texture2D(${LIGHTING_MAP_UNIFORM}, roomMaskUv).r;
2823
+ float roomLightingSigned = roomLightingEncoded * 2.0 - 1.0;
2824
+ float roomLightingFactor = clamp(
2825
+ 1.0 + roomLightingSigned * ${LIGHTING_STRENGTH_UNIFORM},
2826
+ ${MIN_LIGHTING_FACTOR.toFixed(2)},
2827
+ ${MAX_LIGHTING_FACTOR.toFixed(2)}
2828
+ );
2829
+ gl_FragColor.rgb *= roomLightingFactor;
2830
+ gl_FragColor.a *= smoothstep(0.0, ${MASK_THRESHOLD}, roomMaskAlpha);
2831
+ #include <dithering_fragment>
2832
+ `);
2833
+ };
2834
+ material.customProgramCacheKey = ()=>`${previousCustomProgramCacheKey()}|vizbl-carpet-mask-v2`;
2835
+ material.transparent = true;
2836
+ material.needsUpdate = true;
2837
+ return ()=>{
2838
+ material.onBeforeCompile = previousOnBeforeCompile;
2839
+ material.customProgramCacheKey = previousCustomProgramCacheKey;
2840
+ material.transparent = previousTransparent;
2841
+ material.needsUpdate = true;
2842
+ };
2843
+ }
2844
+ async function warmUpCarpetScene(renderer, object, camera, scene) {
2845
+ if ('function' == typeof renderer.compileAsync) return void await renderer.compileAsync(object, camera, scene);
2846
+ renderer.compile(object, camera, scene);
2847
+ }
2848
+ const CARPET_ROTATION_X = Math.PI / 2;
2849
+ function CarpetMesh({ glbUrl, position, quaternion, maskCanvas, lightingCanvas }) {
2850
+ const { resourceRef, rotation, setIsDragging, patchSceneLoad } = useRoomViewer();
2851
+ const { gl, size, camera, scene: rootScene, invalidate } = useThree();
2852
+ const gltf = useGLTF(glbUrl);
2853
+ const resolutionRef = useRef(null);
2854
+ const maskTextureSizeRef = useRef(null);
2855
+ const maskTextureRef = useRef(null);
2856
+ const lightingTextureRef = useRef(null);
2857
+ const materialsRef = useRef(null);
2858
+ if (null === resolutionRef.current) resolutionRef.current = new Vector2(1, 1);
2859
+ if (null === maskTextureSizeRef.current) maskTextureSizeRef.current = new Vector2(1, 1);
2860
+ const resolution = resolutionRef.current;
2861
+ const maskTextureSize = maskTextureSizeRef.current;
2862
+ const sceneInstance = external_react_useMemo(()=>gltf.scene.clone(true), [
2863
+ gltf
2864
+ ]);
2865
+ const rotationRadians = external_react_useMemo(()=>MathUtils.degToRad(rotation), [
2866
+ rotation
2867
+ ]);
2868
+ const handlePointerDown = useCallback(()=>{
2869
+ setIsDragging(true);
2870
+ }, [
2871
+ setIsDragging
2872
+ ]);
2873
+ const handlePointerUp = useCallback(()=>{
2874
+ setIsDragging(false);
2875
+ }, [
2876
+ setIsDragging
2877
+ ]);
2878
+ useLayoutEffect(()=>{
2879
+ const maskTexture = createCanvasTexture(maskCanvas, NearestFilter, NearestFilter);
2880
+ maskTextureRef.current = maskTexture;
2881
+ maskTextureSize.set(Math.max(1, maskCanvas.width), Math.max(1, maskCanvas.height));
2882
+ return ()=>{
2883
+ if (maskTextureRef.current === maskTexture) maskTextureRef.current = null;
2884
+ maskTexture.dispose();
2885
+ };
2886
+ }, [
2887
+ maskCanvas,
2888
+ maskTextureSize
2889
+ ]);
2890
+ useLayoutEffect(()=>{
2891
+ const lightingTexture = createCanvasTexture(lightingCanvas);
2892
+ lightingTextureRef.current = lightingTexture;
2893
+ return ()=>{
2894
+ if (lightingTextureRef.current === lightingTexture) lightingTextureRef.current = null;
2895
+ lightingTexture.dispose();
2896
+ };
2897
+ }, [
2898
+ lightingCanvas
2899
+ ]);
2900
+ useLayoutEffect(()=>{
2901
+ const materials = collectCarpetMaterials(sceneInstance);
2902
+ materialsRef.current = materials;
2903
+ return ()=>{
2904
+ if (materialsRef.current === materials) materialsRef.current = null;
2905
+ materials.forEach((material)=>material.dispose());
2906
+ };
2907
+ }, [
2908
+ sceneInstance
2909
+ ]);
2910
+ useLayoutEffect(()=>{
2911
+ gl.getDrawingBufferSize(resolution);
2912
+ resolution.set(Math.max(1, resolution.x), Math.max(1, resolution.y));
2913
+ }, [
2914
+ gl,
2915
+ resolution,
2916
+ size.width,
2917
+ size.height
2918
+ ]);
2919
+ useLayoutEffect(()=>{
2920
+ const maskTexture = maskTextureRef.current;
2921
+ const lightingTexture = lightingTextureRef.current;
2922
+ const materials = materialsRef.current;
2923
+ if (!maskTexture || !lightingTexture || !materials) return;
2924
+ const cleanupCallbacks = materials.map((material)=>patchMaterialWithMask(material, maskTexture, lightingTexture, resolution, maskTextureSize));
2925
+ invalidate();
2926
+ return ()=>{
2927
+ cleanupCallbacks.forEach((cleanup)=>cleanup());
2928
+ };
2929
+ }, [
2930
+ invalidate,
2931
+ lightingCanvas,
2932
+ maskCanvas,
2933
+ maskTextureSize,
2934
+ resolution,
2935
+ sceneInstance
2936
+ ]);
2937
+ useLayoutEffect(()=>{
2938
+ let isCancelled = false;
2939
+ const warmUp = async ()=>{
2940
+ try {
2941
+ await warmUpCarpetScene(gl, sceneInstance, camera, rootScene);
2942
+ } catch {} finally{
2943
+ if (!isCancelled) {
2944
+ patchSceneLoad({
2945
+ prepareReady: true
2946
+ });
2947
+ invalidate();
2948
+ }
2949
+ }
2950
+ };
2951
+ warmUp();
2952
+ return ()=>{
2953
+ isCancelled = true;
2954
+ };
2955
+ }, [
2956
+ camera,
2957
+ gl,
2958
+ invalidate,
2959
+ patchSceneLoad,
2960
+ rootScene,
2961
+ sceneInstance
2962
+ ]);
2963
+ return /*#__PURE__*/ jsx("group", {
2964
+ ref: resourceRef,
2965
+ position: position,
2966
+ quaternion: quaternion,
2967
+ onPointerDown: handlePointerDown,
2968
+ onPointerUp: handlePointerUp,
2969
+ onPointerLeave: handlePointerUp,
2970
+ children: /*#__PURE__*/ jsx("group", {
2971
+ "rotation-z": rotationRadians,
2972
+ children: /*#__PURE__*/ jsx("group", {
2973
+ "rotation-x": CARPET_ROTATION_X,
2974
+ children: /*#__PURE__*/ jsx("primitive", {
2975
+ object: sceneInstance
2976
+ })
2977
+ })
2978
+ })
2979
+ });
2980
+ }
2981
+ function Carpet({ glbUrl, position, quaternion, segmentationClasses }) {
2982
+ const { data } = useCarpetCanvases(segmentationClasses);
2983
+ if (!data) return null;
2984
+ return /*#__PURE__*/ jsx(CarpetMesh, {
2985
+ glbUrl: glbUrl,
2986
+ position: position,
2987
+ quaternion: quaternion,
2988
+ maskCanvas: data.maskCanvas,
2989
+ lightingCanvas: data.lightingCanvas
2990
+ });
2991
+ }
2992
+ function usePlanePose(options) {
2993
+ const { z, pitchDeg, rollDeg } = options;
2994
+ const camera = useThree((state)=>state.camera);
2995
+ return external_react_useMemo(()=>calculatePlanePose(z, pitchDeg, rollDeg, camera), [
2996
+ camera,
2997
+ z,
2998
+ pitchDeg,
2999
+ rollDeg
3000
+ ]);
3001
+ }
3002
+ function Plane_Plane({ ref, z, pitchDeg, rollDeg, userData }) {
3003
+ const { position, quaternion } = usePlanePose({
3004
+ z,
3005
+ pitchDeg,
3006
+ rollDeg
3007
+ });
3008
+ return /*#__PURE__*/ jsxs("mesh", {
3009
+ ref: ref,
3010
+ position: position,
3011
+ quaternion: quaternion,
3012
+ userData: userData,
3013
+ children: [
3014
+ /*#__PURE__*/ jsx("planeGeometry", {
3015
+ args: [
3016
+ 64,
3017
+ 64
3018
+ ]
3019
+ }),
3020
+ /*#__PURE__*/ jsx("meshStandardMaterial", {
3021
+ transparent: true,
3022
+ opacity: 0
3023
+ })
3024
+ ]
3025
+ });
3026
+ }
3027
+ function FramePart({ node, position, scale = [
3028
+ 1,
3029
+ 1,
3030
+ 1
3031
+ ], uvScale }) {
3032
+ const meshRef = useRef(null);
3033
+ useEffect(()=>{
3034
+ if (!meshRef.current || void 0 === uvScale) return;
3035
+ const attr = meshRef.current.geometry.attributes.uv;
3036
+ const baseUVs = node.geometry.attributes.uv.array;
3037
+ for(let i = 0; i < attr.count; i++){
3038
+ const u = baseUVs[2 * i];
3039
+ const v = baseUVs[2 * i + 1];
3040
+ attr.setXY(i, u * uvScale, v);
3041
+ }
3042
+ attr.needsUpdate = true;
3043
+ }, [
3044
+ uvScale,
3045
+ node.geometry
3046
+ ]);
3047
+ return /*#__PURE__*/ jsx(Clone, {
3048
+ ref: meshRef,
3049
+ object: node,
3050
+ position: position,
3051
+ scale: scale
3052
+ });
3053
+ }
3054
+ const FRAME_OFFSET = 0.001;
3055
+ function Frame({ width, height }) {
3056
+ const { material } = useRoomViewer();
3057
+ const frameUrl = material?.native?.glbUrl;
3058
+ const { nodes } = useGLTF(frameUrl);
3059
+ const geometry = external_react_useMemo(()=>{
3060
+ const meshKeys = [
3061
+ 'vizbl0',
3062
+ 'vizbl1',
3063
+ 'vizbl2',
3064
+ 'vizbl3',
3065
+ 'vizbl4',
3066
+ 'vizbl5',
3067
+ 'vizbl6',
3068
+ 'vizbl7'
3069
+ ];
3070
+ const center = new Vector3();
3071
+ const normalizedNodes = {};
3072
+ meshKeys.forEach((key)=>{
3073
+ const originalMesh = nodes[key];
3074
+ if (!originalMesh?.geometry) return;
3075
+ const mesh = originalMesh.clone();
3076
+ mesh.updateMatrixWorld(true);
3077
+ mesh.geometry.applyMatrix4(mesh.matrixWorld);
3078
+ mesh.position.set(0, 0, 0);
3079
+ mesh.rotation.set(0, 0, 0);
3080
+ mesh.scale.set(1, 1, 1);
3081
+ mesh.updateMatrixWorld(true);
3082
+ mesh.geometry.computeBoundingBox();
3083
+ if (mesh.geometry.boundingBox) {
3084
+ mesh.geometry.boundingBox.getCenter(center);
3085
+ mesh.geometry.translate(-center.x, -center.y, -center.z);
3086
+ }
3087
+ mesh.geometry.computeBoundingBox();
3088
+ normalizedNodes[key] = mesh;
3089
+ });
3090
+ const cornerMesh = normalizedNodes.vizbl0;
3091
+ if (!cornerMesh?.geometry) throw new Error('Corner mesh not found');
3092
+ const cornerBox = cornerMesh.geometry.boundingBox;
3093
+ const cornerWidth = cornerBox.max.x - cornerBox.min.x;
3094
+ const cornerHeight = cornerBox.max.y - cornerBox.min.y;
3095
+ const barLengths = {
3096
+ top: 0,
3097
+ bottom: 0,
3098
+ left: 0,
3099
+ right: 0
3100
+ };
3101
+ const barThickness = {
3102
+ top: 0,
3103
+ bottom: 0,
3104
+ left: 0,
3105
+ right: 0
3106
+ };
3107
+ const vizbl1 = normalizedNodes.vizbl1;
3108
+ if (vizbl1?.geometry) {
3109
+ const box = vizbl1.geometry.boundingBox;
3110
+ barLengths.top = box.max.x - box.min.x;
3111
+ barThickness.top = box.max.y - box.min.y;
3112
+ }
3113
+ const vizbl3 = normalizedNodes.vizbl3;
3114
+ if (vizbl3?.geometry) {
3115
+ const box = vizbl3.geometry.boundingBox;
3116
+ barLengths.right = box.max.y - box.min.y;
3117
+ barThickness.right = box.max.x - box.min.x;
3118
+ }
3119
+ const vizbl5 = normalizedNodes.vizbl5;
3120
+ if (vizbl5?.geometry) {
3121
+ const box = vizbl5.geometry.boundingBox;
3122
+ barLengths.bottom = box.max.x - box.min.x;
3123
+ barThickness.bottom = box.max.y - box.min.y;
3124
+ }
3125
+ const vizbl7 = normalizedNodes.vizbl7;
3126
+ if (vizbl7?.geometry) {
3127
+ const box = vizbl7.geometry.boundingBox;
3128
+ barLengths.left = box.max.y - box.min.y;
3129
+ barThickness.left = box.max.x - box.min.x;
3130
+ }
3131
+ return {
3132
+ cornerSize: {
3133
+ width: cornerWidth,
3134
+ height: cornerHeight
3135
+ },
3136
+ barLengths,
3137
+ barThickness,
3138
+ normalizedNodes
3139
+ };
3140
+ }, [
3141
+ nodes
3142
+ ]);
3143
+ const innerW = width - 2 * geometry.cornerSize.width + 2 * FRAME_OFFSET;
3144
+ const innerH = height - 2 * geometry.cornerSize.height + 2 * FRAME_OFFSET;
3145
+ const topScale = innerW / geometry.barLengths.top;
3146
+ const bottomScale = innerW / geometry.barLengths.bottom;
3147
+ const leftScale = innerH / geometry.barLengths.left;
3148
+ const rightScale = innerH / geometry.barLengths.right;
3149
+ return /*#__PURE__*/ jsxs("group", {
3150
+ children: [
3151
+ /*#__PURE__*/ jsx(FramePart, {
3152
+ node: geometry.normalizedNodes.vizbl0,
3153
+ position: [
3154
+ -width / 2 + geometry.cornerSize.width / 2 - FRAME_OFFSET,
3155
+ height / 2 - geometry.cornerSize.height / 2 + FRAME_OFFSET,
3156
+ 0
3157
+ ]
3158
+ }),
3159
+ /*#__PURE__*/ jsx(FramePart, {
3160
+ node: geometry.normalizedNodes.vizbl1,
3161
+ position: [
3162
+ 0,
3163
+ height / 2 - geometry.barThickness.top / 2 + FRAME_OFFSET,
3164
+ 0
3165
+ ],
3166
+ scale: [
3167
+ topScale,
3168
+ 1,
3169
+ 1
3170
+ ],
3171
+ uvScale: topScale
3172
+ }),
3173
+ /*#__PURE__*/ jsx(FramePart, {
3174
+ node: geometry.normalizedNodes.vizbl2,
3175
+ position: [
3176
+ width / 2 - geometry.cornerSize.width / 2 + FRAME_OFFSET,
3177
+ height / 2 - geometry.cornerSize.height / 2 + FRAME_OFFSET,
3178
+ 0
3179
+ ]
3180
+ }),
3181
+ /*#__PURE__*/ jsx(FramePart, {
3182
+ node: geometry.normalizedNodes.vizbl3,
3183
+ position: [
3184
+ width / 2 - geometry.barThickness.right / 2 + FRAME_OFFSET,
3185
+ 0,
3186
+ 0
3187
+ ],
3188
+ scale: [
3189
+ 1,
3190
+ rightScale,
3191
+ 1
3192
+ ],
3193
+ uvScale: rightScale
3194
+ }),
3195
+ /*#__PURE__*/ jsx(FramePart, {
3196
+ node: geometry.normalizedNodes.vizbl4,
3197
+ position: [
3198
+ width / 2 - geometry.cornerSize.width / 2 + FRAME_OFFSET,
3199
+ -height / 2 + geometry.cornerSize.height / 2 - FRAME_OFFSET,
3200
+ 0
3201
+ ]
3202
+ }),
3203
+ /*#__PURE__*/ jsx(FramePart, {
3204
+ node: geometry.normalizedNodes.vizbl5,
3205
+ position: [
3206
+ 0,
3207
+ -height / 2 + geometry.barThickness.bottom / 2 - FRAME_OFFSET,
3208
+ 0
3209
+ ],
3210
+ scale: [
3211
+ bottomScale,
3212
+ 1,
3213
+ 1
3214
+ ],
3215
+ uvScale: bottomScale
3216
+ }),
3217
+ /*#__PURE__*/ jsx(FramePart, {
3218
+ node: geometry.normalizedNodes.vizbl6,
3219
+ position: [
3220
+ -width / 2 + geometry.cornerSize.width / 2 - FRAME_OFFSET,
3221
+ -height / 2 + geometry.cornerSize.height / 2 - FRAME_OFFSET,
3222
+ 0
3223
+ ]
3224
+ }),
3225
+ /*#__PURE__*/ jsx(FramePart, {
3226
+ node: geometry.normalizedNodes.vizbl7,
3227
+ position: [
3228
+ -width / 2 + geometry.barThickness.left / 2 - FRAME_OFFSET,
3229
+ 0,
3230
+ 0
3231
+ ],
3232
+ scale: [
3233
+ 1,
3234
+ leftScale,
3235
+ 1
3236
+ ],
3237
+ uvScale: leftScale
3238
+ })
3239
+ ]
3240
+ });
3241
+ }
3242
+ function Matting({ width, height, position, quaternion }) {
3243
+ return /*#__PURE__*/ jsxs("mesh", {
3244
+ position: position,
3245
+ quaternion: quaternion,
3246
+ children: [
3247
+ /*#__PURE__*/ jsx("planeGeometry", {
3248
+ args: [
3249
+ width,
3250
+ height
3251
+ ]
3252
+ }),
3253
+ /*#__PURE__*/ jsx("meshStandardMaterial", {
3254
+ color: "white"
3255
+ })
3256
+ ]
3257
+ });
3258
+ }
3259
+ function Poster({ src, position, quaternion }) {
3260
+ const { width, height, angle, material, frameWidth, rotation, resourceRef, matting, setIsDragging } = useRoomViewer();
3261
+ const texture = useLoader(TextureLoader, src);
3262
+ const angleRadians = external_react_useMemo(()=>MathUtils.degToRad(angle), [
3263
+ angle
3264
+ ]);
3265
+ const rotationRadians = external_react_useMemo(()=>MathUtils.degToRad(rotation), [
3266
+ rotation
3267
+ ]);
3268
+ const mattingWidth = width + 2 * matting;
3269
+ const mattingHeight = height + 2 * matting;
3270
+ const mattingFrameWidth = matting > 0 ? mattingWidth : width;
3271
+ const mattingFrameHeight = matting > 0 ? mattingHeight : height;
3272
+ return /*#__PURE__*/ jsx("group", {
3273
+ ref: resourceRef,
3274
+ position: position,
3275
+ quaternion: quaternion,
3276
+ onPointerDown: ()=>setIsDragging(true),
3277
+ onPointerUp: ()=>setIsDragging(false),
3278
+ onPointerLeave: ()=>setIsDragging(false),
3279
+ children: /*#__PURE__*/ jsxs("group", {
3280
+ rotation: [
3281
+ 0,
3282
+ rotationRadians,
3283
+ angleRadians
3284
+ ],
3285
+ children: [
3286
+ material?.native?.glbUrl && frameWidth > 0 && /*#__PURE__*/ jsx(Frame, {
3287
+ width: mattingFrameWidth,
3288
+ height: mattingFrameHeight
3289
+ }),
3290
+ matting > 0 && /*#__PURE__*/ jsx(Matting, {
3291
+ width: mattingWidth,
3292
+ height: mattingHeight,
3293
+ position: [
3294
+ 0,
3295
+ 0,
3296
+ 0.01
3297
+ ]
3298
+ }),
3299
+ /*#__PURE__*/ jsxs("mesh", {
3300
+ position: [
3301
+ 0,
3302
+ 0,
3303
+ 0.02
3304
+ ],
3305
+ children: [
3306
+ /*#__PURE__*/ jsx("planeGeometry", {
3307
+ args: [
3308
+ width,
3309
+ height
3310
+ ]
3311
+ }),
3312
+ /*#__PURE__*/ jsx("meshStandardMaterial", {
3313
+ map: texture
3314
+ })
3315
+ ]
3316
+ })
3317
+ ]
3318
+ })
3319
+ });
3320
+ }
3321
+ function RoomBackground() {
3322
+ const { room } = useRoomViewer();
3323
+ const scene = useThree((state)=>state.scene);
3324
+ const texture = useLoader(TextureLoader, room.image);
3325
+ useLayoutEffect(()=>{
3326
+ texture.colorSpace = SRGBColorSpace;
3327
+ scene.background = texture;
3328
+ return ()=>{
3329
+ if (scene.background === texture) scene.background = null;
3330
+ };
3331
+ }, [
3332
+ scene,
3333
+ texture
3334
+ ]);
3335
+ return null;
3336
+ }
3337
+ const CENTER_POINTER = new Vector2(0, 0);
3338
+ function Room() {
3339
+ const { src, glbUrl, type, room, patchSceneLoad } = useRoomViewer();
3340
+ const isCarpet = 'carpet' === type;
3341
+ const get = useThree((state)=>state.get);
3342
+ const [resourcePose, setResourcePose] = useState(null);
3343
+ const surfaceMapRef = useRef(new Map());
3344
+ const surfaceMeshesRef = useRef([]);
3345
+ const { data } = useCarpetInference(isCarpet);
3346
+ const { findPlacementHit } = useSceneHelpers();
3347
+ const surfaces = external_react_useMemo(()=>isCarpet ? room.config.surfaces.filter((surface)=>surface.id === FLOOR_SURFACE_ID) : room.config.surfaces, [
3348
+ isCarpet,
3349
+ room.config.surfaces
3350
+ ]);
3351
+ const updatePoseFromHit = useCallback((hit, clearOnMiss)=>{
3352
+ if (!hit) {
3353
+ if (clearOnMiss) setResourcePose(null);
3354
+ return;
3355
+ }
3356
+ setResourcePose(buildResourcePoseFromHit(hit));
3357
+ }, []);
3358
+ const setSurfaceRef = useCallback((surfaceId, node)=>{
3359
+ const surfaceMap = surfaceMapRef.current;
3360
+ if (!node) {
3361
+ surfaceMap.delete(surfaceId);
3362
+ surfaceMeshesRef.current = Array.from(surfaceMap.values());
3363
+ return;
3364
+ }
3365
+ surfaceMap.set(surfaceId, node);
3366
+ surfaceMeshesRef.current = Array.from(surfaceMap.values());
3367
+ return ()=>{
3368
+ surfaceMap.delete(surfaceId);
3369
+ surfaceMeshesRef.current = Array.from(surfaceMap.values());
3370
+ };
3371
+ }, []);
3372
+ useEffect(()=>{
3373
+ setResourcePose(null);
3374
+ patchSceneLoad({
3375
+ placementReady: false
3376
+ });
3377
+ }, [
3378
+ patchSceneLoad,
3379
+ room.id,
3380
+ type
3381
+ ]);
3382
+ useEffect(()=>{
3383
+ if (isCarpet) return void setResourcePose(data?.initialPose ?? null);
3384
+ updatePoseFromHit(findPlacementHit(CENTER_POINTER, surfaceMeshesRef.current), true);
3385
+ }, [
3386
+ isCarpet,
3387
+ room.id,
3388
+ surfaces,
3389
+ data?.initialPose,
3390
+ findPlacementHit,
3391
+ updatePoseFromHit
3392
+ ]);
3393
+ const isPlacementReady = null !== resourcePose;
3394
+ useEffect(()=>{
3395
+ patchSceneLoad({
3396
+ placementReady: isPlacementReady
3397
+ });
3398
+ }, [
3399
+ isPlacementReady,
3400
+ patchSceneLoad
3401
+ ]);
3402
+ const handleDrag = useCallback(()=>{
3403
+ const { pointer } = get();
3404
+ updatePoseFromHit(findPlacementHit(pointer, surfaceMeshesRef.current), false);
3405
+ }, [
3406
+ get,
3407
+ findPlacementHit,
3408
+ updatePoseFromHit
3409
+ ]);
3410
+ return /*#__PURE__*/ jsxs(Fragment, {
3411
+ children: [
3412
+ isCarpet ? resourcePose && glbUrl && data?.segmentationClasses && /*#__PURE__*/ jsx(DragControls, {
3413
+ autoTransform: false,
3414
+ onDrag: handleDrag,
3415
+ children: /*#__PURE__*/ jsx(Carpet, {
3416
+ glbUrl: glbUrl,
3417
+ position: resourcePose.position,
3418
+ quaternion: resourcePose.quaternion,
3419
+ segmentationClasses: data.segmentationClasses
3420
+ })
3421
+ }) : resourcePose && src && /*#__PURE__*/ jsx(DragControls, {
3422
+ autoTransform: false,
3423
+ onDrag: handleDrag,
3424
+ children: /*#__PURE__*/ jsx(Poster, {
3425
+ src: src,
3426
+ position: resourcePose.position,
3427
+ quaternion: resourcePose.quaternion
3428
+ })
3429
+ }),
3430
+ surfaces.map((surface)=>/*#__PURE__*/ jsx(Plane_Plane, {
3431
+ ref: (node)=>setSurfaceRef(surface.id, node),
3432
+ userData: {
3433
+ surfaceId: surface.id
3434
+ },
3435
+ z: surface.z,
3436
+ pitchDeg: surface.pitchDeg,
3437
+ rollDeg: surface.rollDeg
3438
+ }, surface.id)),
3439
+ /*#__PURE__*/ jsx(RoomBackground, {})
3440
+ ]
3441
+ });
3442
+ }
3443
+ function CanvasWrapper({ className, ref, captureRef, onSizeChange, ...props }) {
3444
+ const { room, src, glbUrl, type, patchSceneLoad } = useRoomViewer();
3445
+ const { active: isProgressLoading } = useProgress();
3446
+ const sceneResource = 'carpet' === type ? glbUrl : src;
3447
+ const aspectRatio = useRoomImageAspectRatio(room.image);
3448
+ const canvasContainerRef = useRef(null);
3449
+ const hasDispatchedResizeRef = useRef(false);
3450
+ const handleContainerRef = useCallback((node)=>{
3451
+ canvasContainerRef.current = node;
3452
+ if ('function' == typeof ref) return void ref(node);
3453
+ if (ref) ref.current = node;
3454
+ }, [
3455
+ ref
3456
+ ]);
3457
+ const isMobile = useMediaQuery('(max-width: 639px)');
3458
+ const { width: canvasContainerWidth, height: canvasContainerHeight } = useResizeObserver({
3459
+ ref: canvasContainerRef
3460
+ });
3461
+ const size = external_react_useMemo(()=>{
3462
+ if (!canvasContainerWidth || !canvasContainerHeight || !aspectRatio) return null;
3463
+ let width = canvasContainerWidth;
3464
+ let height = canvasContainerHeight;
3465
+ if (isMobile) {
3466
+ height = canvasContainerHeight;
3467
+ width = height * aspectRatio;
3468
+ } else if (canvasContainerWidth / canvasContainerHeight > aspectRatio) width = canvasContainerHeight * aspectRatio;
3469
+ else height = canvasContainerWidth / aspectRatio;
3470
+ return {
3471
+ width,
3472
+ height
3473
+ };
3474
+ }, [
3475
+ canvasContainerWidth,
3476
+ canvasContainerHeight,
3477
+ aspectRatio,
3478
+ isMobile
3479
+ ]);
3480
+ useLayoutEffect(()=>{
3481
+ if (!size) return;
3482
+ onSizeChange?.(size.width, size.height);
3483
+ }, [
3484
+ size,
3485
+ onSizeChange
3486
+ ]);
3487
+ useEffect(()=>{
3488
+ if (!size || hasDispatchedResizeRef.current) return;
3489
+ hasDispatchedResizeRef.current = true;
3490
+ const timeout = window.setTimeout(()=>{
3491
+ window.dispatchEvent(new Event('resize'));
3492
+ }, 250);
3493
+ return ()=>{
3494
+ window.clearTimeout(timeout);
3495
+ };
3496
+ }, [
3497
+ size
3498
+ ]);
3499
+ useEffect(()=>{
3500
+ patchSceneLoad({
3501
+ placementReady: false,
3502
+ prepareReady: 'poster' === type
3503
+ });
3504
+ }, [
3505
+ patchSceneLoad,
3506
+ room.id,
3507
+ type
3508
+ ]);
3509
+ useEffect(()=>{
3510
+ if ('carpet' !== type) return;
3511
+ patchSceneLoad({
3512
+ prepareReady: false
3513
+ });
3514
+ }, [
3515
+ patchSceneLoad,
3516
+ sceneResource,
3517
+ type
3518
+ ]);
3519
+ useEffect(()=>{
3520
+ patchSceneLoad({
3521
+ layoutReady: null !== size
3522
+ });
3523
+ }, [
3524
+ patchSceneLoad,
3525
+ size
3526
+ ]);
3527
+ useEffect(()=>{
3528
+ patchSceneLoad({
3529
+ materialReady: 'poster' === type ? Boolean(src) : Boolean(glbUrl)
3530
+ });
3531
+ }, [
3532
+ glbUrl,
3533
+ patchSceneLoad,
3534
+ src,
3535
+ type
3536
+ ]);
3537
+ useEffect(()=>{
3538
+ patchSceneLoad({
3539
+ assetsReady: !isProgressLoading
3540
+ });
3541
+ }, [
3542
+ isProgressLoading,
3543
+ patchSceneLoad
3544
+ ]);
3545
+ return /*#__PURE__*/ jsx("div", {
3546
+ ref: handleContainerRef,
3547
+ className: utils_cn('size-full flex items-center justify-center sm:pt-21 sm:pb-12', className),
3548
+ ...props,
3549
+ children: size && /*#__PURE__*/ jsx(Canvas, {
3550
+ className: "bg-transparent",
3551
+ frameloop: "demand",
3552
+ camera: {
3553
+ fov: room.config.camera.fov
3554
+ },
3555
+ gl: {
3556
+ toneMapping: ACESFilmicToneMapping,
3557
+ toneMappingExposure: 1.2,
3558
+ alpha: true,
3559
+ preserveDrawingBuffer: true
3560
+ },
3561
+ style: {
3562
+ width: `${size.width}px`,
3563
+ height: `${size.height}px`
3564
+ },
3565
+ children: /*#__PURE__*/ jsxs(Suspense, {
3566
+ fallback: null,
3567
+ children: [
3568
+ /*#__PURE__*/ jsx(CanvasCaptureBridge, {
3569
+ ref: captureRef
3570
+ }),
3571
+ /*#__PURE__*/ jsx(Environment, {
3572
+ files: 'poster' === type ? [
3573
+ HDR_URL
3574
+ ] : void 0,
3575
+ preset: 'carpet' === type ? 'warehouse' : void 0
3576
+ }),
3577
+ /*#__PURE__*/ jsx(Room, {})
3578
+ ]
3579
+ })
3580
+ })
3581
+ });
3582
+ }
3583
+ function Scene({ className, ...props }) {
3584
+ const { containerRef, isDragging, angle, rotation, setAngle, setRotation, type } = useRoomViewer();
3585
+ const { t } = useTranslation();
3586
+ const transformComponentRef = useRef(null);
3587
+ const canvasCaptureRef = useRef(null);
3588
+ const [isSharing, setIsSharing] = useState(false);
3589
+ const isMobile = useMediaQuery('(max-width: 639px)');
3590
+ const isTabletUp = useMediaQuery('(min-width: 768px)');
3591
+ const showPositionPopover = 'poster' === type || 'carpet' === type && !isTabletUp;
3592
+ const canUseNativeShare = external_react_useMemo(()=>'undefined' != typeof navigator && 'function' == typeof navigator.share, []);
3593
+ const handleCanvasSizeChange = useCallback(()=>{
3594
+ if (isMobile) transformComponentRef.current?.centerView();
3595
+ }, [
3596
+ isMobile
3597
+ ]);
3598
+ const handleShare = useCallback(async ()=>{
3599
+ if (!canUseNativeShare) return;
3600
+ const url = window.location.href;
3601
+ const shareMessage = t('poster' === type ? 'shareMessage.poster' : 'shareMessage.carpet');
3602
+ const text = `${shareMessage}\n${url}`;
3603
+ setIsSharing(true);
3604
+ try {
3605
+ const capture = canvasCaptureRef.current;
3606
+ if (capture) try {
3607
+ const blob = await capture.capture();
3608
+ const file = new File([
3609
+ blob
3610
+ ], 'room-scene.png', {
3611
+ type: blob.type || 'image/png'
3612
+ });
3613
+ if (navigator.canShare?.({
3614
+ files: [
3615
+ file
3616
+ ]
3617
+ })) return void await navigator.share({
3618
+ text,
3619
+ files: [
3620
+ file
3621
+ ]
3622
+ });
3623
+ } catch (error) {
3624
+ console.error('Failed to generate scene screenshot for sharing', error);
3625
+ }
3626
+ await navigator.share({
3627
+ text,
3628
+ url
3629
+ });
3630
+ } catch (error) {
3631
+ if (!(error instanceof DOMException && 'AbortError' === error.name)) console.error('Failed to share scene', error);
3632
+ } finally{
3633
+ setIsSharing(false);
3634
+ }
3635
+ }, [
3636
+ canUseNativeShare,
3637
+ t,
3638
+ type
3639
+ ]);
3640
+ return /*#__PURE__*/ jsxs("div", {
3641
+ className: utils_cn('relative flex items-center justify-center overflow-hidden', className),
3642
+ ...props,
3643
+ children: [
3644
+ /*#__PURE__*/ jsx(TransformWrapper, {
3645
+ ref: transformComponentRef,
3646
+ disabled: isDragging,
3647
+ children: /*#__PURE__*/ jsx(TransformComponent, {
3648
+ wrapperClass: "size-full!",
3649
+ contentClass: "h-full! min-w-full sm:max-w-full",
3650
+ children: /*#__PURE__*/ jsx(CanvasWrapper, {
3651
+ captureRef: canvasCaptureRef,
3652
+ onSizeChange: handleCanvasSizeChange
3653
+ })
3654
+ })
3655
+ }),
3656
+ /*#__PURE__*/ jsx(AR, {
3657
+ className: "absolute bottom-4 left-4 sm:bottom-6 sm:left-6"
3658
+ }),
3659
+ 'carpet' === type && /*#__PURE__*/ jsxs("div", {
3660
+ className: "bg-button-dark typo-button-normal absolute bottom-6 left-1/2 hidden h-9 -translate-x-1/2 items-center gap-1.5 rounded-lg px-3.75 text-text-white md:inline-flex",
3661
+ children: [
3662
+ /*#__PURE__*/ jsx("span", {
3663
+ className: "shrink-0",
3664
+ children: t('scene.position')
3665
+ }),
3666
+ /*#__PURE__*/ jsx(slider_Slider, {
3667
+ value: [
3668
+ rotation
3669
+ ],
3670
+ onValueChange: ([value])=>setRotation(value ?? 0),
3671
+ min: -180,
3672
+ max: 180,
3673
+ step: 1,
3674
+ className: "w-29.5 **:data-[slot=slider-range]:bg-transparent **:data-[slot=slider-thumb]:border-background-base-3 **:data-[slot=slider-thumb]]:bg-background-base-3 **:data-[slot=slider-track]:bg-icons-normal",
3675
+ "aria-label": t('scene.position')
3676
+ })
3677
+ ]
3678
+ }),
3679
+ /*#__PURE__*/ jsxs("div", {
3680
+ className: "absolute right-4 bottom-4 inline-flex items-center gap-2 sm:right-6 sm:bottom-6",
3681
+ children: [
3682
+ showPositionPopover && /*#__PURE__*/ jsxs(popover_Popover, {
3683
+ children: [
3684
+ /*#__PURE__*/ jsx(PopoverTrigger, {
3685
+ asChild: true,
3686
+ children: /*#__PURE__*/ jsxs(button_Button, {
3687
+ variant: "contrast",
3688
+ size: isMobile ? 'icon' : 'default',
3689
+ children: [
3690
+ /*#__PURE__*/ jsx(Move, {
3691
+ className: "size-5"
3692
+ }),
3693
+ /*#__PURE__*/ jsx("span", {
3694
+ className: "sr-only sm:not-sr-only",
3695
+ children: t('scene.tunePosition')
3696
+ })
3697
+ ]
3698
+ })
3699
+ }),
3700
+ /*#__PURE__*/ jsx(PopoverContent, {
3701
+ className: "px-3 py-3.5 bg-button-dark border-button-dark text-text-white",
3702
+ side: "top",
3703
+ align: "end",
3704
+ portalContainer: containerRef,
3705
+ children: /*#__PURE__*/ jsxs(FieldGroup, {
3706
+ children: [
3707
+ /*#__PURE__*/ jsxs(Field, {
3708
+ orientation: "horizontal",
3709
+ children: [
3710
+ /*#__PURE__*/ jsx(FieldTitle, {
3711
+ children: t('scene.rotation')
3712
+ }),
3713
+ /*#__PURE__*/ jsx(slider_Slider, {
3714
+ value: [
3715
+ rotation
3716
+ ],
3717
+ onValueChange: ([value])=>setRotation(value ?? 0),
3718
+ min: -180,
3719
+ max: 180,
3720
+ step: 1,
3721
+ className: "w-40.5 **:data-[slot=slider-range]:bg-transparent **:data-[slot=slider-thumb]:border-background-base-3 **:data-[slot=slider-thumb]]:bg-background-base-3 **:data-[slot=slider-track]:bg-icons-normal",
3722
+ "aria-label": t('scene.rotation')
3723
+ })
3724
+ ]
3725
+ }),
3726
+ 'poster' === type && /*#__PURE__*/ jsxs(Field, {
3727
+ orientation: "horizontal",
3728
+ children: [
3729
+ /*#__PURE__*/ jsx(FieldTitle, {
3730
+ children: t('scene.angle')
3731
+ }),
3732
+ /*#__PURE__*/ jsx(slider_Slider, {
3733
+ value: [
3734
+ angle
3735
+ ],
3736
+ onValueChange: ([value])=>setAngle(value ?? 0),
3737
+ max: 75,
3738
+ min: -75,
3739
+ step: 1,
3740
+ className: "w-40.5 **:data-[slot=slider-range]:bg-transparent **:data-[slot=slider-thumb]:border-background-base-3 **:data-[slot=slider-thumb]]:bg-background-base-3 **:data-[slot=slider-track]:bg-icons-normal",
3741
+ "aria-label": t('scene.angle')
3742
+ })
3743
+ ]
3744
+ })
3745
+ ]
3746
+ })
3747
+ })
3748
+ ]
3749
+ }),
3750
+ 'poster' === type && /*#__PURE__*/ jsxs(button_Button, {
3751
+ variant: "contrast",
3752
+ size: isMobile ? 'icon' : 'default',
3753
+ disabled: !canUseNativeShare || isSharing,
3754
+ onClick: handleShare,
3755
+ children: [
3756
+ /*#__PURE__*/ jsx(Share, {
3757
+ className: "size-5"
3758
+ }),
3759
+ /*#__PURE__*/ jsx("span", {
3760
+ className: "sr-only sm:not-sr-only",
3761
+ children: t('scene.share')
3762
+ })
3763
+ ]
3764
+ }),
3765
+ isMobile && 'poster' === type && /*#__PURE__*/ jsxs(popover_Popover, {
3766
+ children: [
3767
+ /*#__PURE__*/ jsx(PopoverTrigger, {
3768
+ asChild: true,
3769
+ children: /*#__PURE__*/ jsx(button_Button, {
3770
+ variant: "contrast",
3771
+ size: "icon",
3772
+ children: /*#__PURE__*/ jsx(Paint, {})
3773
+ })
3774
+ }),
3775
+ /*#__PURE__*/ jsxs(PopoverContent, {
3776
+ className: "grid gap-5",
3777
+ portalContainer: containerRef,
3778
+ children: [
3779
+ /*#__PURE__*/ jsx(MaterialSelectField, {}),
3780
+ /*#__PURE__*/ jsx(MattingField, {})
3781
+ ]
3782
+ })
3783
+ ]
3784
+ })
3785
+ ]
3786
+ })
3787
+ ]
3788
+ });
3789
+ }
3790
+ function RoomViewerContainer() {
3791
+ const { setContainerRef, sceneLoad } = useRoomViewer();
3792
+ const isSceneLoading = !Object.values(sceneLoad).every(Boolean);
3793
+ return /*#__PURE__*/ jsxs("div", {
3794
+ ref: setContainerRef,
3795
+ className: "relative size-full bg-radial from-background-containers to-background-base-2 text-foreground overflow-hidden",
3796
+ children: [
3797
+ /*#__PURE__*/ jsx(Header, {
3798
+ className: "absolute top-0 right-0 left-0 z-15"
3799
+ }),
3800
+ /*#__PURE__*/ jsxs("div", {
3801
+ className: "size-full max-w-full grid grid-rows-[1fr_auto]",
3802
+ children: [
3803
+ /*#__PURE__*/ jsx(Scene, {
3804
+ className: "size-full min-h-0"
3805
+ }),
3806
+ /*#__PURE__*/ jsx(Footer, {
3807
+ className: "bg-background"
3808
+ })
3809
+ ]
3810
+ }),
3811
+ /*#__PURE__*/ jsx("div", {
3812
+ "aria-hidden": !isSceneLoading,
3813
+ className: utils_cn('absolute inset-0 z-10 flex items-center justify-center bg-radial from-background-containers to-background-base-2 transition-opacity', isSceneLoading ? 'opacity-100 pointer-events-auto duration-0' : 'opacity-0 pointer-events-none duration-250ms'),
3814
+ children: /*#__PURE__*/ jsx(Spinner, {
3815
+ variant: "logo"
3816
+ })
3817
+ })
3818
+ ]
3819
+ });
3820
+ }
3821
+ const root_queryClient = new QueryClient();
3822
+ function RoomViewerRoot({ type, src, tinuuid, name, height, width, language }) {
3823
+ const { i18n } = useTranslation();
3824
+ useEffect(()=>{
3825
+ if ('string' == typeof language) i18n.changeLanguage(language);
3826
+ }, [
3827
+ i18n,
3828
+ language
3829
+ ]);
3830
+ return /*#__PURE__*/ jsx(QueryClientProvider, {
3831
+ client: root_queryClient,
3832
+ children: /*#__PURE__*/ jsx(RoomViewerProvider, {
3833
+ src: src ?? null,
3834
+ tinuuid: tinuuid ?? null,
3835
+ type: type,
3836
+ name: name,
3837
+ width: 'poster' === type ? width ?? 0 : 0,
3838
+ height: 'poster' === type ? height ?? 0 : 0,
3839
+ children: /*#__PURE__*/ jsx(RoomViewerContainer, {})
3840
+ })
3841
+ });
3842
+ }
3843
+ const ns1 = {
3844
+ matting: 'Matting',
3845
+ material: 'Material',
3846
+ chooseMaterial: 'Choose material',
3847
+ frameWidth: 'Frame width',
3848
+ close: 'Close',
3849
+ uploadOwnPhoto: 'Upload your photo',
3850
+ previousSlide: 'Previous slide',
3851
+ nextSlide: 'Next slide',
3852
+ mattingOptions: {
3853
+ none: 'None'
3854
+ },
3855
+ units: {
3856
+ cm: 'cm'
3857
+ },
3858
+ poweredBy: 'Powered by Vizbl',
3859
+ ar: {
3860
+ viewInPlace: 'View in place',
3861
+ scanQrCode: 'Scan this QR code with your phone\'s camera to view the object in Augmented Reality'
3862
+ },
3863
+ shareMessage: {
3864
+ poster: 'Preview poster',
3865
+ carpet: 'Preview carpet'
3866
+ },
3867
+ rooms: {
3868
+ 'room-example-1': 'Living room',
3869
+ 'room-example-2': 'Bedroom',
3870
+ 'room-example-3': 'Kitchen',
3871
+ 'room-example-4': 'Hallway',
3872
+ 'room-example-5': 'Dining room',
3873
+ 'room-example-6': 'Office'
3874
+ },
3875
+ scene: {
3876
+ tunePosition: 'Tune position',
3877
+ position: 'Position',
3878
+ share: 'Share',
3879
+ angle: 'Angle',
3880
+ rotation: 'Rotation',
3881
+ reset: 'Reset'
3882
+ }
3883
+ };
3884
+ const en_ns1 = ns1;
3885
+ const ns1_ns1 = {
3886
+ matting: 'Паспарту',
3887
+ material: 'Материал',
3888
+ chooseMaterial: 'Выберите материал',
3889
+ frameWidth: 'Ширина рамки',
3890
+ close: 'Закрыть',
3891
+ uploadOwnPhoto: 'Загрузить свое фото',
3892
+ previousSlide: 'Предыдущий слайд',
3893
+ nextSlide: 'Следующий слайд',
3894
+ mattingOptions: {
3895
+ none: 'Нет'
3896
+ },
3897
+ units: {
3898
+ cm: 'см'
3899
+ },
3900
+ poweredBy: 'Технологии Аризо',
3901
+ ar: {
3902
+ viewInPlace: 'Просмотреть на месте',
3903
+ scanQrCode: 'Отсканируйте этот QR-код камерой телефона, чтобы просмотреть объект в дополненной реальности'
3904
+ },
3905
+ shareMessage: {
3906
+ poster: 'Примерить постер',
3907
+ carpet: 'Примерить ковер'
3908
+ },
3909
+ rooms: {
3910
+ 'room-example-1': 'Гостиная',
3911
+ 'room-example-2': 'Спальня',
3912
+ 'room-example-3': 'Кухня',
3913
+ 'room-example-4': 'Прихожая',
3914
+ 'room-example-5': 'Столовая',
3915
+ 'room-example-6': 'Кабинет'
3916
+ },
3917
+ scene: {
3918
+ tunePosition: 'Настроить положение',
3919
+ position: 'Положение',
3920
+ share: 'Поделиться',
3921
+ angle: 'Угол',
3922
+ rotation: 'Поворот',
3923
+ reset: 'Сбросить'
3924
+ }
3925
+ };
3926
+ const ru_ns1 = ns1_ns1;
3927
+ const resources = {
3928
+ en: {
3929
+ translation: en_ns1
3930
+ },
3931
+ ru: {
3932
+ translation: ru_ns1
3933
+ }
3934
+ };
3935
+ i18next.use(i18next_browser_languagedetector).use(initReactI18next).init({
3936
+ debug: false,
3937
+ detection: {
3938
+ caches: []
3939
+ },
3940
+ fallbackLng: 'en',
3941
+ resources,
3942
+ interpolation: {
3943
+ escapeValue: false
3944
+ }
3945
+ });
3946
+ function RoomViewerDialog({ type, src, tinuuid, width, height, name, language, ...props }) {
3947
+ const [container, setContainer] = useState(null);
3948
+ const { t } = useTranslation();
3949
+ return /*#__PURE__*/ jsxs(Fragment, {
3950
+ children: [
3951
+ container && /*#__PURE__*/ jsx(ThemeProvider, {
3952
+ root: container,
3953
+ children: /*#__PURE__*/ jsx(Dialog, {
3954
+ ...props,
3955
+ children: /*#__PURE__*/ jsxs(DialogContent, {
3956
+ className: "min-w-full h-svh p-0 rounded-none sm:p-0",
3957
+ portalContainer: container,
3958
+ showCloseButton: false,
3959
+ onInteractOutside: (e)=>{
3960
+ e.preventDefault();
3961
+ },
3962
+ children: [
3963
+ /*#__PURE__*/ jsxs(DialogHeader, {
3964
+ className: "sr-only",
3965
+ children: [
3966
+ /*#__PURE__*/ jsx(DialogTitle, {
3967
+ children: name
3968
+ }),
3969
+ /*#__PURE__*/ jsx(DialogDescription, {})
3970
+ ]
3971
+ }),
3972
+ /*#__PURE__*/ jsx(DialogClose, {
3973
+ asChild: true,
3974
+ children: /*#__PURE__*/ jsxs(button_Button, {
3975
+ className: "absolute top-3 right-6 z-20",
3976
+ variant: "secondary",
3977
+ children: [
3978
+ t('close'),
3979
+ /*#__PURE__*/ jsx(XIcon, {})
3980
+ ]
3981
+ })
3982
+ }),
3983
+ /*#__PURE__*/ jsx(RoomViewerRoot, {
3984
+ type: type,
3985
+ src: src,
3986
+ tinuuid: tinuuid,
3987
+ name: name,
3988
+ height: height,
3989
+ width: width,
3990
+ language: language
3991
+ })
3992
+ ]
3993
+ })
3994
+ })
3995
+ }),
3996
+ /*#__PURE__*/ createPortal(/*#__PURE__*/ jsx("div", {
3997
+ className: "vizbl-room-viewer",
3998
+ children: /*#__PURE__*/ jsx("div", {
3999
+ ref: setContainer
4000
+ })
4001
+ }), document.body)
4002
+ ]
4003
+ });
4004
+ }
4005
+ function RoomViewer({ type, src, tinuuid, name, language, width, height, className, ...props }) {
4006
+ const [container, setContainer] = useState(null);
4007
+ return /*#__PURE__*/ jsx("div", {
4008
+ ref: setContainer,
4009
+ className: utils_cn('vizbl-room-viewer', className),
4010
+ style: {
4011
+ height: '100%',
4012
+ width: '100%'
4013
+ },
4014
+ ...props,
4015
+ children: container && /*#__PURE__*/ jsx(ThemeProvider, {
4016
+ root: container,
4017
+ children: /*#__PURE__*/ jsx(RoomViewerRoot, {
4018
+ type: type,
4019
+ src: src,
4020
+ tinuuid: tinuuid,
4021
+ name: name,
4022
+ language: language,
4023
+ width: width,
4024
+ height: height
4025
+ })
4026
+ })
4027
+ });
4028
+ }
4029
+ export { RoomViewer, RoomViewerDialog };