@neurodyn/react-room-viewer 0.0.13 → 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.
package/dist/index.d.ts CHANGED
@@ -86,6 +86,11 @@ declare const resources: {
86
86
  readonly unauthorized: "Invalid API key";
87
87
  readonly 'payment-required': "Viewer temporarily unavailable";
88
88
  };
89
+ readonly loadError: {
90
+ readonly 'viewer-load-failed': "We could not load the viewer. Please try again.";
91
+ readonly 'artwork-load-failed': "The image could not be loaded — its link is missing or incorrect in the widget settings.";
92
+ readonly 'room-load-failed': "The room photo could not be loaded. Please try again.";
93
+ };
89
94
  readonly resourceType: {
90
95
  readonly rugs: {
91
96
  readonly productCategory: "Rug";
@@ -200,6 +205,11 @@ declare const resources: {
200
205
  readonly unauthorized: "Неверный API-ключ";
201
206
  readonly 'payment-required': "Просмотр временно недоступен";
202
207
  };
208
+ readonly loadError: {
209
+ readonly 'viewer-load-failed': "Не удалось загрузить просмотр. Попробуйте ещё раз.";
210
+ readonly 'artwork-load-failed': "Не удалось загрузить изображение — ссылка на него отсутствует или указана неверно в настройках виджета.";
211
+ readonly 'room-load-failed': "Не удалось загрузить фото комнаты. Попробуйте ещё раз.";
212
+ };
203
213
  readonly resourceType: {
204
214
  readonly rugs: {
205
215
  readonly productCategory: "Ковер";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Fragment, jsx as jsx_runtime_jsx, jsxs } from "react/jsx-runtime";
2
- import { Suspense, createContext, forwardRef, use as external_react_use, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo as external_react_useMemo, useReducer, useRef, useState } from "react";
2
+ import { Component, Suspense, createContext, forwardRef, use as external_react_use, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo as external_react_useMemo, useReducer, useRef, useState } from "react";
3
3
  import { createPortal } from "react-dom";
4
4
  import { I18nextProvider, initReactI18next, useTranslation } from "react-i18next";
5
5
  import react_shadow from "react-shadow";
@@ -19,7 +19,8 @@ import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
19
19
  import { Content as react_tabs_Content, List, Root as react_tabs_Root, Trigger } from "@radix-ui/react-tabs";
20
20
  import { formatHex, oklch } from "culori";
21
21
  import qrcode from "qrcode";
22
- import { GLTFExporter, USDZExporter } from "three/examples/jsm/Addons.js";
22
+ import { GLTFExporter } from "three/examples/jsm/exporters/GLTFExporter.js";
23
+ import { USDZExporter } from "three/examples/jsm/exporters/USDZExporter.js";
23
24
  import "@google/model-viewer";
24
25
  import { Clone, DragControls, Environment, useGLTF, useProgress, useTexture } from "@react-three/drei";
25
26
  import { Canvas, useLoader, useThree } from "@react-three/fiber";
@@ -1359,6 +1360,10 @@ var __webpack_modules__ = {
1359
1360
  height: auto;
1360
1361
  }
1361
1362
 
1363
+ .h-dvh {
1364
+ height: 100dvh;
1365
+ }
1366
+
1362
1367
  .h-full {
1363
1368
  height: 100%;
1364
1369
  }
@@ -1371,10 +1376,6 @@ var __webpack_modules__ = {
1371
1376
  height: 1px;
1372
1377
  }
1373
1378
 
1374
- .h-svh {
1375
- height: 100svh;
1376
- }
1377
-
1378
1379
  .max-h-\\(--radix-select-content-available-height\\) {
1379
1380
  max-height: var(--radix-select-content-available-height);
1380
1381
  }
@@ -6640,6 +6641,11 @@ const ns1 = {
6640
6641
  unauthorized: 'Invalid API key',
6641
6642
  'payment-required': 'Viewer temporarily unavailable'
6642
6643
  },
6644
+ loadError: {
6645
+ 'viewer-load-failed': 'We could not load the viewer. Please try again.',
6646
+ 'artwork-load-failed': 'The image could not be loaded — its link is missing or incorrect in the widget settings.',
6647
+ 'room-load-failed': 'The room photo could not be loaded. Please try again.'
6648
+ },
6643
6649
  resourceType: {
6644
6650
  rugs: {
6645
6651
  productCategory: 'Rug',
@@ -6753,6 +6759,11 @@ const ns1_ns1 = {
6753
6759
  unauthorized: 'Неверный API-ключ',
6754
6760
  'payment-required': 'Просмотр временно недоступен'
6755
6761
  },
6762
+ loadError: {
6763
+ 'viewer-load-failed': 'Не удалось загрузить просмотр. Попробуйте ещё раз.',
6764
+ 'artwork-load-failed': 'Не удалось загрузить изображение — ссылка на него отсутствует или указана неверно в настройках виджета.',
6765
+ 'room-load-failed': 'Не удалось загрузить фото комнаты. Попробуйте ещё раз.'
6766
+ },
6756
6767
  resourceType: {
6757
6768
  rugs: {
6758
6769
  productCategory: 'Ковер',
@@ -6833,7 +6844,7 @@ roomViewerI18n.use(i18next_browser_languagedetector).use(initReactI18next).init(
6833
6844
  }
6834
6845
  });
6835
6846
  const PROPERTY_BLOCK_RE = /@property\s+(--[^{\s]+)\s*\{([^}]*)\}/g;
6836
- const INITIAL_VALUE_RE = /initial-value:([^;\n]+);/i;
6847
+ const INITIAL_VALUE_RE = /initial-value:([^;}]+)/i;
6837
6848
  const NON_INHERITING_RE = /inherits:\s*false(?:\s*;|\s*$)/i;
6838
6849
  const ROOT_TO_HOST_SELECTOR_RE = /:root\s*,\s*:host|:root/g;
6839
6850
  function createShadowRootCssText(cssText) {
@@ -8402,7 +8413,7 @@ function progress_Progress({ className, value, max = 100, ...props }) {
8402
8413
  })
8403
8414
  });
8404
8415
  }
8405
- const fullscreenDialogContentClassName = 'inset-0 h-svh min-w-full max-w-none translate-x-0 translate-y-0 rounded-none border-none bg-transparent p-0 shadow-none sm:p-0 sm:max-w-none';
8416
+ const fullscreenDialogContentClassName = 'inset-0 h-dvh min-w-full max-w-none translate-x-0 translate-y-0 rounded-none border-none bg-transparent p-0 shadow-none sm:p-0 sm:max-w-none';
8406
8417
  const RoomViewerMetadataContext = createContext(null);
8407
8418
  const RoomViewerSurfaceContext = createContext(null);
8408
8419
  const RoomViewerRoomsContext = createContext(null);
@@ -8459,6 +8470,7 @@ const device_isMobile = 'mobile' === device.type;
8459
8470
  function getErrorMessageKey(variant) {
8460
8471
  if ('custom-interior-processing' === variant) return 'floor.error.processingBody';
8461
8472
  if ('viewer-load-failed' === variant) return 'floor.error.loadBody';
8473
+ if ('artwork-load-failed' === variant || 'room-load-failed' === variant) return `loadError.${variant}`;
8462
8474
  return `accessError.${variant}`;
8463
8475
  }
8464
8476
  function FloorErrorDialog({ variant, open, portalContainer, showDialogCloseButton = false, onOpenChange, onTryAgain, onCancel }) {
@@ -10067,6 +10079,23 @@ function CanvasCaptureBridge({ ref }) {
10067
10079
  ]);
10068
10080
  return null;
10069
10081
  }
10082
+ class SceneErrorBoundary extends Component {
10083
+ state = {
10084
+ hasFailed: false
10085
+ };
10086
+ static getDerivedStateFromError() {
10087
+ return {
10088
+ hasFailed: true
10089
+ };
10090
+ }
10091
+ componentDidCatch(error, errorInfo) {
10092
+ console.error('Room viewer scene failed to render', error, errorInfo.componentStack);
10093
+ this.props.onError(error);
10094
+ }
10095
+ render() {
10096
+ return this.state.hasFailed ? null : this.props.children;
10097
+ }
10098
+ }
10070
10099
  const RESOURCE_BASE_NORMAL = new Vector3(0, 0, 1);
10071
10100
  const WORLD_UP = new Vector3(0, 0, 1);
10072
10101
  const MIN_PROJECTED_UP_LENGTH = 1e-6;
@@ -11007,7 +11036,7 @@ function WallResource({ src, position, quaternion, materialPatch }) {
11007
11036
  }
11008
11037
  function Room() {
11009
11038
  const { placement } = useRoomViewerMetadata();
11010
- const { src, glbUrl } = useRoomViewerResource();
11039
+ const { src, glbUrl, setLoadError } = useRoomViewerResource();
11011
11040
  const { room } = useRoomViewerRooms();
11012
11041
  const { patchSceneLoad } = useRoomViewerSceneState();
11013
11042
  const { gl, size, camera, invalidate, raycaster, pointer } = useThree();
@@ -11109,16 +11138,19 @@ function Room() {
11109
11138
  quaternion: scenePlacement.resourcePose.quaternion,
11110
11139
  materialPatch: materialPatch
11111
11140
  })
11112
- }) : scenePlacement.resourcePose && src && /*#__PURE__*/ jsx_runtime_jsx(DragControls, {
11113
- autoTransform: false,
11114
- onDrag: scenePlacement.handleDrag,
11115
- onDragEnd: scenePlacement.handleDragEnd,
11116
- onDragStart: scenePlacement.handleDragStart,
11117
- children: /*#__PURE__*/ jsx_runtime_jsx(WallResource, {
11118
- src: src,
11119
- position: scenePlacement.resourcePose.position,
11120
- quaternion: scenePlacement.resourcePose.quaternion,
11121
- materialPatch: materialPatch
11141
+ }) : scenePlacement.resourcePose && src && /*#__PURE__*/ jsx_runtime_jsx(SceneErrorBoundary, {
11142
+ onError: ()=>setLoadError('artwork-load-failed'),
11143
+ children: /*#__PURE__*/ jsx_runtime_jsx(DragControls, {
11144
+ autoTransform: false,
11145
+ onDrag: scenePlacement.handleDrag,
11146
+ onDragEnd: scenePlacement.handleDragEnd,
11147
+ onDragStart: scenePlacement.handleDragStart,
11148
+ children: /*#__PURE__*/ jsx_runtime_jsx(WallResource, {
11149
+ src: src,
11150
+ position: scenePlacement.resourcePose.position,
11151
+ quaternion: scenePlacement.resourcePose.quaternion,
11152
+ materialPatch: materialPatch
11153
+ })
11122
11154
  })
11123
11155
  }),
11124
11156
  scene.planes.map((plane)=>/*#__PURE__*/ jsx_runtime_jsx(RoomPlane, {
@@ -11126,7 +11158,53 @@ function Room() {
11126
11158
  geometry: scenePlacement.planeGeometries.get(plane.id),
11127
11159
  setPlaneRef: scenePlacement.setPlaneRef
11128
11160
  }, plane.id)),
11129
- /*#__PURE__*/ jsx_runtime_jsx(RoomBackground, {})
11161
+ /*#__PURE__*/ jsx_runtime_jsx(SceneErrorBoundary, {
11162
+ onError: ()=>setLoadError('room-load-failed'),
11163
+ children: /*#__PURE__*/ jsx_runtime_jsx(RoomBackground, {})
11164
+ })
11165
+ ]
11166
+ });
11167
+ }
11168
+ async function layoutGetConfig(data, config = {}) {
11169
+ const { client: request = client_client, ...requestConfig } = config;
11170
+ const requestData = data;
11171
+ const res = await request({
11172
+ method: "POST",
11173
+ url: "/layout/GetConfig",
11174
+ data: requestData,
11175
+ ...requestConfig
11176
+ });
11177
+ return res.data;
11178
+ }
11179
+ const HDR_NEUTRAL_LINK_TYPE = 'hdr_neutral';
11180
+ const ROOM_VIEWER_CONFIG_QUERY_KEY = [
11181
+ 'room-viewer',
11182
+ 'layout-config'
11183
+ ];
11184
+ function getNeutralHdrUrl(config) {
11185
+ const url = config?.internal?.links?.find((link)=>link.type === HDR_NEUTRAL_LINK_TYPE)?.url?.trim();
11186
+ return url || void 0;
11187
+ }
11188
+ function FloorRoomEnvironment() {
11189
+ const configQuery = react_query_useQuery({
11190
+ queryKey: ROOM_VIEWER_CONFIG_QUERY_KEY,
11191
+ queryFn: ()=>layoutGetConfig(),
11192
+ staleTime: 1 / 0
11193
+ });
11194
+ if (configQuery.isPending) return null;
11195
+ const hdrUrl = getNeutralHdrUrl(configQuery.data);
11196
+ return /*#__PURE__*/ jsx_runtime_jsx(Environment, {
11197
+ files: hdrUrl ? [
11198
+ hdrUrl
11199
+ ] : void 0,
11200
+ preset: hdrUrl ? void 0 : 'warehouse'
11201
+ });
11202
+ }
11203
+ function RoomEnvironment({ placement }) {
11204
+ if ('floor' === placement) return /*#__PURE__*/ jsx_runtime_jsx(FloorRoomEnvironment, {});
11205
+ return /*#__PURE__*/ jsx_runtime_jsx(Environment, {
11206
+ files: [
11207
+ HDR_URL
11130
11208
  ]
11131
11209
  });
11132
11210
  }
@@ -11259,11 +11337,8 @@ function CanvasWrapper({ className, ref, captureRef, onSizeChange, ...props }) {
11259
11337
  /*#__PURE__*/ jsx_runtime_jsx(CanvasCaptureBridge, {
11260
11338
  ref: captureRef
11261
11339
  }),
11262
- /*#__PURE__*/ jsx_runtime_jsx(Environment, {
11263
- files: 'wall' === placement ? [
11264
- HDR_URL
11265
- ] : void 0,
11266
- preset: 'floor' === placement ? 'warehouse' : void 0
11340
+ /*#__PURE__*/ jsx_runtime_jsx(RoomEnvironment, {
11341
+ placement: placement
11267
11342
  }),
11268
11343
  /*#__PURE__*/ jsx_runtime_jsx(Room, {})
11269
11344
  ]
@@ -14011,6 +14086,10 @@ function Header({ className, ...props }) {
14011
14086
  })
14012
14087
  });
14013
14088
  }
14089
+ function RoomViewerErrorState_getErrorMessageKey(variant) {
14090
+ if ('viewer-load-failed' === variant || 'artwork-load-failed' === variant || 'room-load-failed' === variant) return `loadError.${variant}`;
14091
+ return `accessError.${variant}`;
14092
+ }
14014
14093
  function RoomViewerErrorState({ variant, showDialogCloseButton = false }) {
14015
14094
  const { t } = useTranslation();
14016
14095
  return /*#__PURE__*/ jsxs("div", {
@@ -14021,7 +14100,7 @@ function RoomViewerErrorState({ variant, showDialogCloseButton = false }) {
14021
14100
  }),
14022
14101
  /*#__PURE__*/ jsx_runtime_jsx("h2", {
14023
14102
  className: "typo-base-16 font-medium text-text-dark",
14024
- children: t(`accessError.${variant}`)
14103
+ children: t(RoomViewerErrorState_getErrorMessageKey(variant))
14025
14104
  }),
14026
14105
  showDialogCloseButton && /*#__PURE__*/ jsx_runtime_jsx(DialogClose, {
14027
14106
  asChild: true,
@@ -14035,18 +14114,19 @@ function RoomViewerErrorState({ variant, showDialogCloseButton = false }) {
14035
14114
  }
14036
14115
  function RoomViewerContainer({ showDialogCloseButton = false }) {
14037
14116
  const { placement } = useRoomViewerMetadata();
14038
- const { isViewerAccessPending, accessError, hasRooms } = useRoomViewerResource();
14117
+ const { isViewerAccessPending, accessError, loadError, setLoadError, hasRooms } = useRoomViewerResource();
14039
14118
  const { setContainerRef, sceneLoad, isRoomScenePending } = useRoomViewerSceneState();
14040
14119
  const { t } = useTranslation();
14041
14120
  const isSceneLoading = isViewerAccessPending || isRoomScenePending || hasRooms && !Object.values(sceneLoad).every(Boolean);
14042
14121
  if ('floor' === placement) return /*#__PURE__*/ jsx_runtime_jsx(FloorViewerLayout, {
14043
14122
  showDialogCloseButton: showDialogCloseButton
14044
14123
  });
14045
- if (accessError) return /*#__PURE__*/ jsx_runtime_jsx("div", {
14124
+ const activeError = accessError ?? loadError;
14125
+ if (activeError) return /*#__PURE__*/ jsx_runtime_jsx("div", {
14046
14126
  ref: setContainerRef,
14047
14127
  className: "relative size-full overflow-hidden bg-radial from-background-containers to-background-base-2 text-foreground",
14048
14128
  children: /*#__PURE__*/ jsx_runtime_jsx(RoomViewerErrorState, {
14049
- variant: accessError,
14129
+ variant: activeError,
14050
14130
  showDialogCloseButton: showDialogCloseButton
14051
14131
  })
14052
14132
  });
@@ -14073,8 +14153,11 @@ function RoomViewerContainer({ showDialogCloseButton = false }) {
14073
14153
  /*#__PURE__*/ jsxs("div", {
14074
14154
  className: "size-full max-w-full grid grid-rows-[1fr_auto]",
14075
14155
  children: [
14076
- /*#__PURE__*/ jsx_runtime_jsx(Scene, {
14077
- className: "size-full min-h-0"
14156
+ /*#__PURE__*/ jsx_runtime_jsx(SceneErrorBoundary, {
14157
+ onError: ()=>setLoadError('viewer-load-failed'),
14158
+ children: /*#__PURE__*/ jsx_runtime_jsx(Scene, {
14159
+ className: "size-full min-h-0"
14160
+ })
14078
14161
  }),
14079
14162
  /*#__PURE__*/ jsx_runtime_jsx(Footer, {
14080
14163
  className: "bg-background"
@@ -14110,6 +14193,9 @@ function useFrameWidthOptions() {
14110
14193
  t
14111
14194
  ]);
14112
14195
  }
14196
+ function isBlankArtworkSource(src) {
14197
+ return 'string' != typeof src || 0 === src.trim().length;
14198
+ }
14113
14199
  const LEGACY_RESOURCE_TYPE_TO_CANONICAL = {
14114
14200
  poster: 'wallart',
14115
14201
  carpet: 'rugs'
@@ -14318,8 +14404,9 @@ function RoomViewerProvider({ children, src, tinuuid: initialTinuuid, type, view
14318
14404
  const isViewerAccessPending = shouldActivatePublicApi && activationQuery.isLoading || isRoomsAccessPending;
14319
14405
  const activationLoadError = activationQuery.error && null === activationAccessError ? 'viewer-load-failed' : null;
14320
14406
  const objectLoadError = objectQuery.error && null === objectAccessError ? 'viewer-load-failed' : null;
14407
+ const artworkLoadError = 'wall' === placement && isBlankArtworkSource(src) ? 'artwork-load-failed' : null;
14321
14408
  const viewerAccessError = accessError ?? activationAccessError ?? roomsAccessError ?? objectAccessError;
14322
- const viewerLoadError = loadError ?? activationLoadError ?? objectLoadError;
14409
+ const viewerLoadError = loadError ?? activationLoadError ?? objectLoadError ?? artworkLoadError;
14323
14410
  const patchSceneLoad = useCallback((patch)=>{
14324
14411
  setSceneLoad((current)=>Object.assign({}, current, patch));
14325
14412
  }, []);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@neurodyn/react-room-viewer",
3
3
  "type": "module",
4
- "version": "0.0.13",
4
+ "version": "0.0.14",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -46,7 +46,7 @@
46
46
  "react-zoom-pan-pinch": "3.7.0",
47
47
  "swiper": "^12.1.0",
48
48
  "tailwind-merge": "3.4.0",
49
- "three": "^0.182.0",
49
+ "three": "^0.185.1",
50
50
  "ua-parser-js": "^2.0.9"
51
51
  },
52
52
  "devDependencies": {
@@ -58,7 +58,7 @@
58
58
  "@tailwindcss/postcss": "4.1.17",
59
59
  "@types/react": "^19.2.4",
60
60
  "@types/react-dom": "19.2.3",
61
- "@types/three": "0.182.0",
61
+ "@types/three": "0.185.4",
62
62
  "eslint": "9.39.1",
63
63
  "postcss-rem-to-responsive-pixel": "^7.0.4",
64
64
  "react": "^19.2.0",