@terraware/web-components 5.0.27-rc.0 → 5.0.27

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 (295) hide show
  1. package/components/AntSwitch/index.js +22 -19
  2. package/components/Autocomplete/Autocomplete.js +130 -106
  3. package/components/Badge/index.js +29 -28
  4. package/components/BusySpinner/index.js +45 -40
  5. package/components/Button/Button.js +47 -41
  6. package/components/Checkbox.js +63 -64
  7. package/components/Confirm/index.js +83 -74
  8. package/components/DatePicker/DatePicker.js +141 -138
  9. package/components/DialogBox/DialogBox.js +118 -111
  10. package/components/Divisor.js +18 -18
  11. package/components/Dropdown.js +94 -66
  12. package/components/EditableTable/index.js +271 -211
  13. package/components/ErrorBox/ErrorBox.js +49 -47
  14. package/components/FileChooser/index.js +135 -117
  15. package/components/FormBottomBar/index.js +84 -75
  16. package/components/Icon/Icon.js +20 -12
  17. package/components/Icon/icons/index.js +256 -256
  18. package/components/IconTooltip/index.js +60 -49
  19. package/components/Map/MapBox.js +452 -545
  20. package/components/Map/MapContainer.js +29 -25
  21. package/components/Map/MapDateSliderControl.js +64 -64
  22. package/components/Map/MapDrawer.js +47 -41
  23. package/components/Map/MapLegend.js +172 -176
  24. package/components/Map/MapViewStyleControl.js +63 -59
  25. package/components/Map/ReactMapboxWrapper.js +2 -3
  26. package/components/Map/index.js +180 -161
  27. package/components/Map/types.js +17 -12
  28. package/components/Map/useMaintainLayerOrder.js +26 -16
  29. package/components/Markdown/index.js +26 -25
  30. package/components/Message/Message.js +76 -73
  31. package/components/MultiSelect/index.js +124 -112
  32. package/components/Navbar/NavFooter.js +13 -9
  33. package/components/Navbar/NavItem.js +76 -70
  34. package/components/Navbar/NavSection.js +19 -17
  35. package/components/Navbar/Navbar.js +46 -39
  36. package/components/Navbar/SubNavbar.js +13 -9
  37. package/components/Note.js +22 -18
  38. package/components/OverlayModal/OverlayModal.js +63 -56
  39. package/components/PageForm/index.js +41 -33
  40. package/components/PhotoChooser/index.js +173 -165
  41. package/components/PhotosCarousel/PhotoSlide.js +35 -32
  42. package/components/PhotosCarousel/index.js +85 -80
  43. package/components/PhotosCarousel/usePhotosCarousel.js +76 -69
  44. package/components/Pill/index.js +45 -41
  45. package/components/PillList/index.js +24 -17
  46. package/components/PlacementWrapper/index.js +59 -38
  47. package/components/PopoverMenu/Popover.js +85 -68
  48. package/components/PopoverMenu/index.js +56 -51
  49. package/components/PopoverMultiSelect/index.js +59 -52
  50. package/components/ProgressCircle/ProgressCircle.js +32 -28
  51. package/components/RadioButton.js +19 -17
  52. package/components/Select/Select.js +15 -13
  53. package/components/Select/SelectT.js +250 -235
  54. package/components/Separator/index.js +17 -14
  55. package/components/Size.js +1 -0
  56. package/components/Slider/index.js +56 -43
  57. package/components/SummaryBox.js +72 -70
  58. package/components/Tabs/index.js +135 -133
  59. package/components/TextTruncated/index.js +130 -117
  60. package/components/Textfield/Textfield.js +190 -158
  61. package/components/Textfield/TruncatedTextArea.js +84 -70
  62. package/components/TimelineSlider/TimelineMarker.js +30 -25
  63. package/components/TimelineSlider/TimelineRail.js +11 -8
  64. package/components/TimelineSlider/index.js +66 -64
  65. package/components/TimelineSliderV2/ClusterBadge.js +29 -25
  66. package/components/TimelineSliderV2/TimelineDot.js +35 -31
  67. package/components/TimelineSliderV2/TimelineRail.js +11 -8
  68. package/components/TimelineSliderV2/index.js +103 -121
  69. package/components/TimelineSliderV2/layout.js +147 -124
  70. package/components/TimelineSliderV2/types.js +1 -0
  71. package/components/Tooltip/Tooltip.js +56 -47
  72. package/components/ViewPhotosDialog/index.js +35 -28
  73. package/components/VirtualWalkthrough/Annotation.js +207 -163
  74. package/components/VirtualWalkthrough/AnnotationEditPane.js +184 -198
  75. package/components/VirtualWalkthrough/AnnotationPanel.js +199 -187
  76. package/components/VirtualWalkthrough/Application.js +34 -31
  77. package/components/VirtualWalkthrough/AutoRotator.js +223 -85
  78. package/components/VirtualWalkthrough/BlockingSpinner.js +28 -26
  79. package/components/VirtualWalkthrough/BoundaryRing.js +34 -30
  80. package/components/VirtualWalkthrough/BoundaryWall.js +36 -32
  81. package/components/VirtualWalkthrough/CameraInfo.js +89 -100
  82. package/components/VirtualWalkthrough/ControlsInfoPane.js +195 -254
  83. package/components/VirtualWalkthrough/GradientSky.js +73 -72
  84. package/components/VirtualWalkthrough/SplatControls.js +221 -209
  85. package/components/VirtualWalkthrough/SplatCrop.js +20 -21
  86. package/components/VirtualWalkthrough/SplatFadeCrop.js +20 -21
  87. package/components/VirtualWalkthrough/SplatModel.js +85 -65
  88. package/components/VirtualWalkthrough/SplatRevealRain.js +46 -41
  89. package/components/VirtualWalkthrough/TfAnnotationManager.js +379 -197
  90. package/components/VirtualWalkthrough/TfXrNavigation.js +162 -73
  91. package/components/VirtualWalkthrough/VirtualWalkthroughViewer.js +431 -438
  92. package/components/VirtualWalkthrough/VrAnnotationPanel.js +22 -18
  93. package/components/VirtualWalkthrough/XrAnnotationInteraction.js +17 -14
  94. package/components/VirtualWalkthrough/XrExitButton.js +17 -14
  95. package/components/VirtualWalkthrough/XrGazeDwell.js +17 -14
  96. package/components/VirtualWalkthrough/XrPointerRay.js +17 -14
  97. package/components/VirtualWalkthrough/XrStartPose.js +121 -67
  98. package/components/VirtualWalkthrough/boundary-ring.js +117 -87
  99. package/components/VirtualWalkthrough/boundary-wall.js +258 -175
  100. package/components/VirtualWalkthrough/camera-adoption.js +64 -18
  101. package/components/VirtualWalkthrough/groundPlane.js +47 -32
  102. package/components/VirtualWalkthrough/gsplat-fade-crop-effect.js +35 -35
  103. package/components/VirtualWalkthrough/splatFormat.js +47 -13
  104. package/components/VirtualWalkthrough/useAdoptedCamera.js +44 -23
  105. package/components/VirtualWalkthrough/useRestartOnVr.js +37 -26
  106. package/components/VirtualWalkthrough/useXrRenderTuning.js +54 -33
  107. package/components/VirtualWalkthrough/vr-annotation-panel-layout.js +99 -67
  108. package/components/VirtualWalkthrough/vr-annotation-panel.js +433 -403
  109. package/components/VirtualWalkthrough/vr-annotation-scroll.js +28 -10
  110. package/components/VirtualWalkthrough/walkthrough-camera.js +329 -204
  111. package/components/VirtualWalkthrough/xr-annotation-candidate-buffer.js +80 -39
  112. package/components/VirtualWalkthrough/xr-annotation-candidates.js +18 -6
  113. package/components/VirtualWalkthrough/xr-annotation-interaction.js +56 -31
  114. package/components/VirtualWalkthrough/xr-annotation-targeting.js +38 -22
  115. package/components/VirtualWalkthrough/xr-button-arming.js +17 -7
  116. package/components/VirtualWalkthrough/xr-button-hold.js +36 -22
  117. package/components/VirtualWalkthrough/xr-exit-button-mask.js +36 -22
  118. package/components/VirtualWalkthrough/xr-exit-button.js +207 -148
  119. package/components/VirtualWalkthrough/xr-face-buttons.js +29 -19
  120. package/components/VirtualWalkthrough/xr-gaze-dwell-state.js +34 -23
  121. package/components/VirtualWalkthrough/xr-gaze-dwell.js +144 -107
  122. package/components/VirtualWalkthrough/xr-interactive-ui.js +24 -12
  123. package/components/VirtualWalkthrough/xr-pointer-ray.js +20 -13
  124. package/components/VirtualWalkthrough/xr-progress-pie-material.js +88 -117
  125. package/components/VirtualWalkthrough/xr-progress-pie.js +10 -1
  126. package/components/VirtualWalkthrough/xr-ray-quad.js +30 -16
  127. package/components/VirtualWalkthrough/xr-render-tuning.js +48 -17
  128. package/components/VirtualWalkthrough/xr-scene-bounds.js +37 -21
  129. package/components/VirtualWalkthrough/xr-start-pose.js +73 -27
  130. package/components/VirtualWalkthrough/xr-teleport-gesture.js +42 -16
  131. package/components/svg/BlobbyGrayIconImage.js +39 -41
  132. package/components/svg/BlobbyGrayIconImport.js +39 -41
  133. package/components/svg/BlobbyGrayIconUploadToTheCloud.js +39 -41
  134. package/components/svg/BlobbyIconCloudChecked.js +164 -202
  135. package/components/svg/BlobbyIconExportOrUploadPhoto.js +164 -202
  136. package/components/svg/BlobbyIconFolder.js +164 -202
  137. package/components/svg/BlobbyIconGraphReport.js +164 -202
  138. package/components/svg/BlobbyIconHappy.js +164 -202
  139. package/components/svg/BlobbyIconHeartMonitor.js +164 -202
  140. package/components/svg/BlobbyIconLeaf.js +164 -202
  141. package/components/svg/BlobbyIconLibrary.js +164 -202
  142. package/components/svg/BlobbyIconNursery.js +166 -204
  143. package/components/svg/BlobbyIconOrganization.js +164 -202
  144. package/components/svg/BlobbyIconParchment.js +164 -202
  145. package/components/svg/BlobbyIconPeople.js +164 -202
  146. package/components/svg/BlobbyIconSeedBank.js +164 -202
  147. package/components/svg/BlobbyIconSeedling.js +164 -202
  148. package/components/svg/BlobbyIconSeeds.js +164 -202
  149. package/components/svg/BlobbyIconSite.js +167 -206
  150. package/components/svg/BlobbyIconSparkles.js +164 -202
  151. package/components/svg/BlobbyIconWrench.js +164 -202
  152. package/components/svg/Bug.js +21 -19
  153. package/components/svg/Calendar.js +21 -19
  154. package/components/svg/CaretDown.js +21 -19
  155. package/components/svg/CaretLeft.js +21 -19
  156. package/components/svg/CaretRight.js +21 -19
  157. package/components/svg/CaretUp.js +21 -19
  158. package/components/svg/ChevronDown.js +21 -19
  159. package/components/svg/ChevronUp.js +21 -19
  160. package/components/svg/Close.js +21 -19
  161. package/components/svg/Critical.js +21 -19
  162. package/components/svg/Edit.js +164 -202
  163. package/components/svg/Error.js +21 -19
  164. package/components/svg/Filter.js +21 -19
  165. package/components/svg/Folder.js +21 -19
  166. package/components/svg/Help.js +21 -19
  167. package/components/svg/Home.js +21 -19
  168. package/components/svg/IconAdd.js +21 -19
  169. package/components/svg/IconArrowRight.js +23 -21
  170. package/components/svg/IconBusinessNetwork.js +21 -19
  171. package/components/svg/IconCalendar.js +21 -19
  172. package/components/svg/IconCancel.js +21 -19
  173. package/components/svg/IconChargingBattery.js +21 -19
  174. package/components/svg/IconChecklist.js +23 -21
  175. package/components/svg/IconCheckmark.js +21 -19
  176. package/components/svg/IconCoinInHand.js +21 -19
  177. package/components/svg/IconColumns.js +21 -19
  178. package/components/svg/IconComment.js +22 -20
  179. package/components/svg/IconDashboard.js +21 -19
  180. package/components/svg/IconDataMigration.js +21 -19
  181. package/components/svg/IconDocument.js +23 -21
  182. package/components/svg/IconDownloadFromTheCloud.js +23 -21
  183. package/components/svg/IconEdit.js +23 -21
  184. package/components/svg/IconExpand.js +23 -21
  185. package/components/svg/IconExport.js +21 -19
  186. package/components/svg/IconExternalLink.js +21 -19
  187. package/components/svg/IconEye.js +21 -19
  188. package/components/svg/IconEyeOff.js +21 -19
  189. package/components/svg/IconFile.js +21 -19
  190. package/components/svg/IconFolder.js +21 -19
  191. package/components/svg/IconFullScreen.js +22 -20
  192. package/components/svg/IconFutures.js +21 -19
  193. package/components/svg/IconGraphReport.js +21 -19
  194. package/components/svg/IconGrid.js +21 -19
  195. package/components/svg/IconHeartMonitor.js +21 -19
  196. package/components/svg/IconHelp.js +23 -21
  197. package/components/svg/IconHistory.js +21 -19
  198. package/components/svg/IconImport.js +21 -19
  199. package/components/svg/IconIndex.js +21 -19
  200. package/components/svg/IconLayers.js +21 -19
  201. package/components/svg/IconLibrary.js +22 -20
  202. package/components/svg/IconList.js +21 -19
  203. package/components/svg/IconLivePlant.js +21 -19
  204. package/components/svg/IconLocations.js +21 -19
  205. package/components/svg/IconMail.js +21 -19
  206. package/components/svg/IconManager.js +21 -19
  207. package/components/svg/IconMap.js +21 -19
  208. package/components/svg/IconMarker.js +21 -19
  209. package/components/svg/IconMenu.js +21 -19
  210. package/components/svg/IconMenuHorizontal.js +21 -19
  211. package/components/svg/IconModule.js +21 -19
  212. package/components/svg/IconMyLocation.js +21 -19
  213. package/components/svg/IconNote.js +23 -21
  214. package/components/svg/IconNursery.js +23 -21
  215. package/components/svg/IconOrg.js +21 -19
  216. package/components/svg/IconParchment.js +21 -19
  217. package/components/svg/IconPhoto.js +21 -19
  218. package/components/svg/IconPlantsFilled.js +21 -19
  219. package/components/svg/IconRedo.js +21 -19
  220. package/components/svg/IconRestorationSite.js +21 -19
  221. package/components/svg/IconSeedBank.js +21 -19
  222. package/components/svg/IconSeedling.js +21 -19
  223. package/components/svg/IconSettings.js +21 -19
  224. package/components/svg/IconSlice.js +21 -19
  225. package/components/svg/IconSubmit.js +21 -19
  226. package/components/svg/IconSubtract.js +23 -21
  227. package/components/svg/IconSynced.js +21 -19
  228. package/components/svg/IconTrashCan.js +21 -19
  229. package/components/svg/IconTreasureMap.js +21 -19
  230. package/components/svg/IconUnavailable.js +21 -19
  231. package/components/svg/IconUndo.js +21 -19
  232. package/components/svg/IconVariable.js +21 -19
  233. package/components/svg/IconVideo.js +31 -30
  234. package/components/svg/IconWifi.js +21 -19
  235. package/components/svg/Info.js +21 -19
  236. package/components/svg/Key.js +21 -19
  237. package/components/svg/Leaf.js +21 -19
  238. package/components/svg/Lock.js +21 -19
  239. package/components/svg/Logo.js +43 -48
  240. package/components/svg/MenuVertical.js +21 -19
  241. package/components/svg/Notification.js +21 -19
  242. package/components/svg/Person.js +21 -19
  243. package/components/svg/Plus.js +21 -19
  244. package/components/svg/RestorationSite.js +21 -19
  245. package/components/svg/Search.js +21 -19
  246. package/components/svg/Seeds.js +21 -19
  247. package/components/svg/Site.js +21 -19
  248. package/components/svg/Sparkles.js +21 -19
  249. package/components/svg/Species.js +21 -19
  250. package/components/svg/Species2.js +164 -202
  251. package/components/svg/Spinner.js +48 -54
  252. package/components/svg/Star.js +21 -19
  253. package/components/svg/Success.js +21 -19
  254. package/components/svg/SuccessFilled.js +21 -19
  255. package/components/svg/TerrawareLogoDesktop.js +49 -55
  256. package/components/svg/TerrawareLogoMobile.js +26 -25
  257. package/components/svg/Touchscreen.js +21 -19
  258. package/components/svg/UploadCloud.js +164 -202
  259. package/components/svg/Warning.js +21 -19
  260. package/components/svg/WelcomeClipboard.js +133 -156
  261. package/components/svg/index.js +129 -129
  262. package/components/table/EnhancedTableToolbar.js +74 -68
  263. package/components/table/EnhancedTableToolbarV2.js +41 -31
  264. package/components/table/TableCellRenderer.js +231 -196
  265. package/components/table/TableHeader.js +73 -71
  266. package/components/table/TableHeaderItem.js +102 -97
  267. package/components/table/density.js +19 -18
  268. package/components/table/index.js +369 -350
  269. package/components/table/sort.js +52 -27
  270. package/components/table/types.js +1 -0
  271. package/components/types/index.js +1 -0
  272. package/hooks/cameraEntityContext.js +13 -2
  273. package/hooks/useBoolean.js +13 -17
  274. package/hooks/useCameraPosition.js +56 -56
  275. package/hooks/useDevicePerformance.js +17 -17
  276. package/hooks/useXr.js +87 -85
  277. package/index.js +58 -58
  278. package/package.json +1 -1
  279. package/setupTests.js +4 -0
  280. package/strings/export.js +170 -111
  281. package/strings/index.js +54 -25
  282. package/strings/locales.js +41 -21
  283. package/strings/strings-en.js +51 -49
  284. package/strings/strings-es.js +51 -49
  285. package/strings/strings-fr.js +51 -49
  286. package/strings/strings-gx.js +51 -49
  287. package/style-dictionary-dist/TerrawareTheme.js +186 -186
  288. package/theme.js +76 -74
  289. package/utils/color.js +15 -8
  290. package/utils/date.js +108 -42
  291. package/utils/index.js +4 -4
  292. package/utils/preventDefaultEvent.js +3 -3
  293. package/utils/text.js +11 -11
  294. package/utils/useDeviceInfo.js +79 -42
  295. package/virtualWalkthrough.js +23 -23
@@ -1,445 +1,438 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
- import { useTheme } from "@mui/material";
4
- import { Entity } from "@playcanvas/react";
5
- import { Camera, Script } from "@playcanvas/react/components";
6
- import { useApp } from "@playcanvas/react/hooks";
7
- import { Color, Vec3 } from "playcanvas";
8
- import { XrControllers } from "playcanvas/scripts/esm/xr/xr-controllers.mjs";
9
- import { CameraEntityContext } from "../../hooks/cameraEntityContext.js";
10
- import { useCameraPosition } from "../../hooks/useCameraPosition.js";
11
- import { useDevicePerformance } from "../../hooks/useDevicePerformance.js";
12
- import { useXr } from "../../hooks/useXr.js";
13
- import Annotation from "./Annotation.js";
14
- import AnnotationPanel from "./AnnotationPanel.js";
15
- import { AutoRotator } from "./AutoRotator.js";
16
- import BoundaryRing from "./BoundaryRing.js";
17
- import BoundaryWall from "./BoundaryWall.js";
18
- import GradientSky from "./GradientSky.js";
19
- import SplatControls from "./SplatControls.js";
20
- import SplatModel from "./SplatModel.js";
21
- import { TfAnnotationManager } from "./TfAnnotationManager.js";
22
- import { TfXrNavigation } from "./TfXrNavigation.js";
23
- import VrAnnotationPanel from "./VrAnnotationPanel.js";
24
- import XrAnnotationInteraction from "./XrAnnotationInteraction.js";
25
- import XrExitButton from "./XrExitButton.js";
26
- import XrGazeDwell from "./XrGazeDwell.js";
27
- import XrPointerRay from "./XrPointerRay.js";
28
- import { XrStartPose } from "./XrStartPose.js";
29
- import { useAdoptedCamera } from "./useAdoptedCamera.js";
30
- import { useXrRenderTuning } from "./useXrRenderTuning.js";
31
- import { WalkthroughCamera } from "./walkthrough-camera.js";
32
- import { rayHitsInteractiveUi } from "./xr-interactive-ui.js";
33
- const DEFAULT_FOCUS_POINT = [
34
- 0,
35
- 0.1,
36
- 0
37
- ];
38
- const DEFAULT_POSITION = [
39
- 1,
40
- 0.1,
41
- 0
42
- ];
1
+ import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
2
+ import { useTheme } from '@mui/material';
3
+ import { Entity } from '@playcanvas/react';
4
+ import { Script, Camera } from '@playcanvas/react/components';
5
+ import { useApp } from '@playcanvas/react/hooks';
6
+ import { Vec3, Color } from 'playcanvas';
7
+ import { XrControllers } from 'playcanvas/scripts/esm/xr/xr-controllers.mjs';
8
+ import { CameraEntityContext } from '../../hooks/cameraEntityContext.js';
9
+ import { useCameraPosition } from '../../hooks/useCameraPosition.js';
10
+ import { useDevicePerformance } from '../../hooks/useDevicePerformance.js';
11
+ import useXr from '../../hooks/useXr.js';
12
+ import Annotation from './Annotation.js';
13
+ import AnnotationPanel from './AnnotationPanel.js';
14
+ import { AutoRotator } from './AutoRotator.js';
15
+ import BoundaryRing from './BoundaryRing.js';
16
+ import BoundaryWall from './BoundaryWall.js';
17
+ import GradientSky from './GradientSky.js';
18
+ import SplatControls from './SplatControls.js';
19
+ import SplatModel from './SplatModel.js';
20
+ import { TfAnnotationManager } from './TfAnnotationManager.js';
21
+ import { TfXrNavigation } from './TfXrNavigation.js';
22
+ import VrAnnotationPanel from './VrAnnotationPanel.js';
23
+ import XrAnnotationInteraction from './XrAnnotationInteraction.js';
24
+ import XrExitButton from './XrExitButton.js';
25
+ import XrGazeDwell from './XrGazeDwell.js';
26
+ import XrPointerRay from './XrPointerRay.js';
27
+ import { XrStartPose } from './XrStartPose.js';
28
+ import useAdoptedCamera from './useAdoptedCamera.js';
29
+ import useXrRenderTuning from './useXrRenderTuning.js';
30
+ import { WalkthroughCamera } from './walkthrough-camera.js';
31
+ import { rayHitsInteractiveUi } from './xr-interactive-ui.js';
32
+ import { jsx, jsxs } from 'react/jsx-runtime';
33
+
34
+ const DEFAULT_FOCUS_POINT = [0, 0.1, 0];
35
+ const DEFAULT_POSITION = [1, 0.1, 0];
36
+
37
+ // How far outside the clamp radius the boundary wall stands. Without it the wall would be coplanar
38
+ // with the surface the head is clamped to, so a pinned user would have it in their face and through
39
+ // their near clip plane.
43
40
  const WALL_INSET = 0.25;
41
+
42
+ /**
43
+ * Largest world size (metres) a hotspot is allowed to reach as the camera approaches it, so it
44
+ * stops growing rather than filling the view from close up.
45
+ */
44
46
  const MAX_HOTSPOT_WORLD_SIZE = 0.75;
45
- const VirtualWalkthroughViewer = ({ splatSrc, splatFormat, camera = /*#__PURE__*/ jsx(Camera, {
47
+
48
+ /**
49
+ * Every coordinate, distance and size in this component is world-space, in the metres an XR
50
+ * headset imposes on the scene: `scaleFactor` converts the splat's own arbitrary units to them,
51
+ * and is applied to nothing but the splat itself.
52
+ */
53
+
54
+ const VirtualWalkthroughViewer = ({
55
+ splatSrc,
56
+ splatFormat,
57
+ camera = /*#__PURE__*/jsx(Camera, {
46
58
  clearColor: "#EAF8FF",
47
59
  fov: 60
48
- }), origin = DEFAULT_FOCUS_POINT, cameraPosition = DEFAULT_POSITION, sceneBounds, groundPlane: groundPlaneProp, skyColor, groundColor, averageCameraHeight = 0, scaleFactor = 1, splatModelProps, autoStartVr = false, onXrExit, onXrClose, annotations, onSaveAnnotations, editable = false, showFreeFly = false, isFullScreen = false, onToggleFullScreen, maxImagesPerAnnotation })=>{
49
- const theme = useTheme();
50
- const [cameraRoot, setCameraRoot] = useState(null);
51
- const [cameraEntity, setCameraEntity] = useState(null);
52
- const adoptedCamera = useAdoptedCamera(cameraRoot, cameraEntity);
53
- const activeCamera = adoptedCamera ?? cameraEntity;
54
- const { setCamera } = useCameraPosition(activeCamera);
55
- const { isHighPerformance } = useDevicePerformance();
56
- const app = useApp();
57
- const [showAnnotations, setShowAnnotations] = useState(true);
58
- const [autoRotate, setAutoRotate] = useState(true);
59
- const [isEdit, setIsEdit] = useState(false);
60
- const [isFreeFly, setIsFreeFly] = useState(false);
61
- const [selectedAnnotationIndex, setSelectedAnnotationIndex] = useState(-1);
62
- const [localAnnotations, setLocalAnnotations] = useState(annotations);
63
- const [isTextFieldFocused, setIsTextFieldFocused] = useState(false);
64
- const [viewingAnnotation, setViewingAnnotation] = useState(null);
65
- const [viewingAnnotationIndex, setViewingAnnotationIndex] = useState(-1);
66
- const [viewedScreenPos, setViewedScreenPos] = useState(null);
67
- const reportXrError = useCallback((error)=>{
68
- console.warn(error.message, error);
69
- onXrExit?.(error);
70
- }, [
71
- onXrExit
72
- ]);
73
- const { isCurrentlyInXr, isCurrentlyInVr, isCurrentlyInAr, isXrAvailable, startXr } = useXr({
74
- onError: reportXrError
60
+ }),
61
+ origin = DEFAULT_FOCUS_POINT,
62
+ cameraPosition = DEFAULT_POSITION,
63
+ sceneBounds,
64
+ groundPlane: groundPlaneProp,
65
+ skyColor,
66
+ groundColor,
67
+ averageCameraHeight = 0,
68
+ scaleFactor = 1,
69
+ splatModelProps,
70
+ autoStartVr = false,
71
+ onXrExit,
72
+ onXrClose,
73
+ annotations,
74
+ onSaveAnnotations,
75
+ editable = false,
76
+ showFreeFly = false,
77
+ isFullScreen = false,
78
+ onToggleFullScreen,
79
+ maxImagesPerAnnotation
80
+ }) => {
81
+ const theme = useTheme();
82
+ // Held as state so the effects and hooks that address these entities re-run once they
83
+ // exist. Addressed directly: a viewer mounted into a host's scene shares the graph
84
+ // with the host's entities, so the camera can be found by component type.
85
+ const [cameraRoot, setCameraRoot] = useState(null);
86
+ const [cameraEntity, setCameraEntity] = useState(null);
87
+ const adoptedCamera = useAdoptedCamera(cameraRoot, cameraEntity);
88
+ // The entity carrying the live camera component: the viewer's own, or the host's while adopted.
89
+ // Everything that poses the camera or projects through it addresses this rather than the entity
90
+ // the viewer mounts a camera of its own on, which holds nothing while a camera is adopted.
91
+ const activeCamera = adoptedCamera ?? cameraEntity;
92
+ const {
93
+ setCamera
94
+ } = useCameraPosition(activeCamera);
95
+ const {
96
+ isHighPerformance
97
+ } = useDevicePerformance();
98
+ const app = useApp();
99
+ const [showAnnotations, setShowAnnotations] = useState(true);
100
+ const [autoRotate, setAutoRotate] = useState(true);
101
+ const [isEdit, setIsEdit] = useState(false);
102
+ const [isFreeFly, setIsFreeFly] = useState(false);
103
+ const [selectedAnnotationIndex, setSelectedAnnotationIndex] = useState(-1);
104
+ const [localAnnotations, setLocalAnnotations] = useState(annotations);
105
+ const [isTextFieldFocused, setIsTextFieldFocused] = useState(false);
106
+ const [viewingAnnotation, setViewingAnnotation] = useState(null);
107
+ const [viewingAnnotationIndex, setViewingAnnotationIndex] = useState(-1);
108
+ const [viewedScreenPos, setViewedScreenPos] = useState(null);
109
+ const reportXrError = useCallback(error => {
110
+ console.warn(error.message, error);
111
+ onXrExit?.(error);
112
+ }, [onXrExit]);
113
+ const {
114
+ isCurrentlyInXr,
115
+ isCurrentlyInVr,
116
+ isCurrentlyInAr,
117
+ isXrAvailable,
118
+ startXr
119
+ } = useXr({
120
+ onError: reportXrError
121
+ });
122
+ useXrRenderTuning();
123
+ const autoStartRequested = useRef(false);
124
+
125
+ // Edge-triggered so that ending a session doesn't pull the user straight back into one. The ref
126
+ // is written before the early returns, so lowering the prop re-arms the next rising edge.
127
+ useEffect(() => {
128
+ const isRisingEdge = autoStartVr && !autoStartRequested.current;
129
+ autoStartRequested.current = autoStartVr;
130
+ if (!isRisingEdge || isCurrentlyInXr) {
131
+ return;
132
+ }
133
+ if (!isXrAvailable('VR')) {
134
+ reportXrError(new Error('A VR session was requested, but this device reports no immersive VR support'));
135
+ return;
136
+ }
137
+ startXr('VR');
138
+ }, [autoStartVr, isCurrentlyInXr, isXrAvailable, startXr, reportXrError]);
139
+ const wasInXr = useRef(false);
140
+
141
+ // Edge-detected rather than reported whenever the viewer is out of XR, which would fire on mount
142
+ // and tell a caller a session had ended before one ever began.
143
+ useEffect(() => {
144
+ if (wasInXr.current && !isCurrentlyInXr) {
145
+ onXrExit?.();
146
+ }
147
+ wasInXr.current = isCurrentlyInXr;
148
+ }, [isCurrentlyInXr, onXrExit]);
149
+ const sceneBoundsRadius = useMemo(() => {
150
+ if (sceneBounds?.m !== undefined) {
151
+ return sceneBounds.m;
152
+ }
153
+ const dx = cameraPosition[0] - origin[0];
154
+ const dy = cameraPosition[1] - origin[1];
155
+ const dz = cameraPosition[2] - origin[2];
156
+ return Math.sqrt(dx * dx + dy * dy + dz * dz);
157
+ }, [cameraPosition, sceneBounds, origin]);
158
+ const sceneBoundsCenter = useMemo(() => sceneBounds ? new Vec3(sceneBounds.x, sceneBounds.y, sceneBounds.z) : new Vec3(origin[0], cameraPosition[1], origin[2]), [sceneBounds, origin, cameraPosition]);
159
+ const groundPlane = useMemo(() => groundPlaneProp?.length === 3 ? groundPlaneProp.map(p => new Vec3(p[0], p[1], p[2])) : [], [groundPlaneProp]);
160
+
161
+ // Also runs when a session ends. WalkthroughCamera is unmounted for the duration of a session, so
162
+ // the camera entity is left holding the last head pose — an eye height above wherever in the rig
163
+ // the user was standing — which is neither where nor how high the walkthrough left off.
164
+ useEffect(() => {
165
+ if (isCurrentlyInXr) {
166
+ return;
167
+ }
168
+ setCamera(origin, cameraPosition);
169
+ }, [origin, cameraPosition, setCamera, isCurrentlyInXr]);
170
+
171
+ // Re-applied when a session ends for the same reason: the script that comes back is a new
172
+ // instance, and without the ground plane it walks the camera at the bounds centre's height
173
+ // instead of an eye height above the ground.
174
+ useEffect(() => {
175
+ if (!groundPlane.length || isCurrentlyInXr) {
176
+ return;
177
+ }
178
+ // @ts-expect-error - scripts are added dynamically to the entity
179
+ const walkthroughCam = cameraRoot?.script?.walkthroughCamera;
180
+ if (walkthroughCam) {
181
+ // Should be changed to a react prop if shallowEquals in playcanvas/react is fixed (see https://github.com/playcanvas/react/pull/298)
182
+ walkthroughCam.groundPlane = groundPlane;
183
+ }
184
+ }, [groundPlane, cameraRoot, isCurrentlyInXr]);
185
+ useEffect(() => {
186
+ // Set imperatively for the same reason as BoundaryRing: boundsCenter is a Vec3, and the
187
+ // @playcanvas/react memo() comparator stops at the first prop with an .equals() method.
188
+ // @ts-expect-error - scripts are added dynamically to the entity
189
+ const navigation = cameraRoot?.script?.tfXrNavigation;
190
+ if (navigation) {
191
+ navigation.boundsCenter = sceneBoundsCenter;
192
+ navigation.boundsRadius = sceneBoundsRadius;
193
+ }
194
+ }, [cameraRoot, sceneBoundsCenter, sceneBoundsRadius]);
195
+ const handleToggleFreeFly = useCallback(() => {
196
+ const newFreeFly = !isFreeFly;
197
+ // @ts-expect-error - scripts are added dynamically to the entity
198
+ const walkthroughCam = cameraRoot?.script?.walkthroughCamera;
199
+ if (walkthroughCam) {
200
+ walkthroughCam.freeFly = newFreeFly;
201
+ }
202
+ if (!newFreeFly) {
203
+ setCamera(origin, cameraPosition);
204
+ }
205
+ setIsFreeFly(newFreeFly);
206
+ }, [isFreeFly, cameraRoot, setCamera, origin, cameraPosition]);
207
+ useEffect(() => {
208
+ setLocalAnnotations(annotations);
209
+ }, [annotations]);
210
+ useEffect(() => {
211
+ if (!isEdit) {
212
+ setSelectedAnnotationIndex(-1);
213
+ }
214
+ }, [isEdit]);
215
+ const handleAnnotationPositionChange = useCallback(position => {
216
+ setLocalAnnotations(prev => {
217
+ if (selectedAnnotationIndex === -1) {
218
+ return prev;
219
+ }
220
+ const updated = [...prev];
221
+ updated[selectedAnnotationIndex] = {
222
+ ...updated[selectedAnnotationIndex],
223
+ position
224
+ };
225
+ return updated;
75
226
  });
76
- useXrRenderTuning();
77
- const autoStartRequested = useRef(false);
78
- useEffect(()=>{
79
- const isRisingEdge = autoStartVr && !autoStartRequested.current;
80
- autoStartRequested.current = autoStartVr;
81
- if (!isRisingEdge || isCurrentlyInXr) return;
82
- if (!isXrAvailable('VR')) return void reportXrError(new Error('A VR session was requested, but this device reports no immersive VR support'));
83
- startXr('VR');
84
- }, [
85
- autoStartVr,
86
- isCurrentlyInXr,
87
- isXrAvailable,
88
- startXr,
89
- reportXrError
90
- ]);
91
- const wasInXr = useRef(false);
92
- useEffect(()=>{
93
- if (wasInXr.current && !isCurrentlyInXr) onXrExit?.();
94
- wasInXr.current = isCurrentlyInXr;
95
- }, [
96
- isCurrentlyInXr,
97
- onXrExit
98
- ]);
99
- const sceneBoundsRadius = useMemo(()=>{
100
- if (sceneBounds?.m !== void 0) return sceneBounds.m;
101
- const dx = cameraPosition[0] - origin[0];
102
- const dy = cameraPosition[1] - origin[1];
103
- const dz = cameraPosition[2] - origin[2];
104
- return Math.sqrt(dx * dx + dy * dy + dz * dz);
105
- }, [
106
- cameraPosition,
107
- sceneBounds,
108
- origin
109
- ]);
110
- const sceneBoundsCenter = useMemo(()=>sceneBounds ? new Vec3(sceneBounds.x, sceneBounds.y, sceneBounds.z) : new Vec3(origin[0], cameraPosition[1], origin[2]), [
111
- sceneBounds,
112
- origin,
113
- cameraPosition
114
- ]);
115
- const groundPlane = useMemo(()=>groundPlaneProp?.length === 3 ? groundPlaneProp.map((p)=>new Vec3(p[0], p[1], p[2])) : [], [
116
- groundPlaneProp
117
- ]);
118
- useEffect(()=>{
119
- if (isCurrentlyInXr) return;
120
- setCamera(origin, cameraPosition);
121
- }, [
122
- origin,
123
- cameraPosition,
124
- setCamera,
125
- isCurrentlyInXr
126
- ]);
127
- useEffect(()=>{
128
- if (!groundPlane.length || isCurrentlyInXr) return;
129
- const walkthroughCam = cameraRoot?.script?.walkthroughCamera;
130
- if (walkthroughCam) walkthroughCam.groundPlane = groundPlane;
131
- }, [
132
- groundPlane,
133
- cameraRoot,
134
- isCurrentlyInXr
135
- ]);
136
- useEffect(()=>{
137
- const navigation = cameraRoot?.script?.tfXrNavigation;
138
- if (navigation) {
139
- navigation.boundsCenter = sceneBoundsCenter;
140
- navigation.boundsRadius = sceneBoundsRadius;
141
- }
142
- }, [
143
- cameraRoot,
144
- sceneBoundsCenter,
145
- sceneBoundsRadius
146
- ]);
147
- const handleToggleFreeFly = useCallback(()=>{
148
- const newFreeFly = !isFreeFly;
149
- const walkthroughCam = cameraRoot?.script?.walkthroughCamera;
150
- if (walkthroughCam) walkthroughCam.freeFly = newFreeFly;
151
- if (!newFreeFly) setCamera(origin, cameraPosition);
152
- setIsFreeFly(newFreeFly);
153
- }, [
154
- isFreeFly,
155
- cameraRoot,
156
- setCamera,
157
- origin,
158
- cameraPosition
159
- ]);
160
- useEffect(()=>{
161
- setLocalAnnotations(annotations);
162
- }, [
163
- annotations
164
- ]);
165
- useEffect(()=>{
166
- if (!isEdit) setSelectedAnnotationIndex(-1);
167
- }, [
168
- isEdit
169
- ]);
170
- const handleAnnotationPositionChange = useCallback((position)=>{
171
- setLocalAnnotations((prev)=>{
172
- if (-1 === selectedAnnotationIndex) return prev;
173
- const updated = [
174
- ...prev
175
- ];
176
- updated[selectedAnnotationIndex] = {
177
- ...updated[selectedAnnotationIndex],
178
- position
179
- };
180
- return updated;
181
- });
182
- }, [
183
- selectedAnnotationIndex
184
- ]);
185
- const handleSave = useCallback(()=>{
186
- const saveAndClose = async ()=>{
187
- await onSaveAnnotations(localAnnotations);
188
- setIsEdit(false);
189
- setSelectedAnnotationIndex(-1);
190
- };
191
- saveAndClose();
192
- }, [
193
- onSaveAnnotations,
194
- localAnnotations
195
- ]);
196
- const handleCancel = useCallback(()=>{
197
- setLocalAnnotations(annotations);
198
- setIsEdit(false);
199
- setSelectedAnnotationIndex(-1);
200
- }, [
201
- annotations
202
- ]);
203
- const handleAddAnnotation = useCallback(()=>{
204
- const newAnnotation = {
205
- position: origin,
206
- title: ''
207
- };
208
- setLocalAnnotations((prev)=>[
209
- ...prev,
210
- newAnnotation
211
- ]);
212
- setSelectedAnnotationIndex(localAnnotations.length);
213
- }, [
214
- origin,
215
- localAnnotations
216
- ]);
217
- const handleDeleteAnnotation = useCallback(()=>{
218
- if (-1 === selectedAnnotationIndex) return;
219
- setLocalAnnotations((prev)=>prev.filter((_, index)=>index !== selectedAnnotationIndex));
220
- setSelectedAnnotationIndex(-1);
221
- }, [
222
- selectedAnnotationIndex
223
- ]);
224
- const handleDeselectAnnotation = useCallback(()=>{
225
- setSelectedAnnotationIndex(-1);
226
- }, []);
227
- const handleAnnotationView = useCallback((annotation, annotationIndex, screenX, screenY)=>{
228
- setViewingAnnotation(annotation);
229
- setViewingAnnotationIndex(annotationIndex);
230
- setViewedScreenPos(null);
231
- }, []);
232
- const handleAnnotationScreenPositionUpdate = useCallback((_index, screenX, screenY, size)=>{
233
- if (isCurrentlyInXr) return;
234
- setViewedScreenPos((prev)=>prev && prev.x === screenX && prev.y === screenY && prev.size === size ? prev : {
235
- x: screenX,
236
- y: screenY,
237
- size
238
- });
239
- }, [
240
- isCurrentlyInXr
241
- ]);
242
- const handleCloseAnnotation = useCallback(()=>{
243
- setViewingAnnotation(null);
244
- setViewingAnnotationIndex(-1);
245
- setViewedScreenPos(null);
246
- }, []);
247
- const isTeleportBlocked = useCallback((inputSource)=>rayHitsInteractiveUi(app, inputSource.getOrigin(), inputSource.getDirection()), [
248
- app
249
- ]);
250
- useEffect(()=>{
251
- if (!isCurrentlyInXr) handleCloseAnnotation();
252
- }, [
253
- isCurrentlyInXr,
254
- handleCloseAnnotation
255
- ]);
256
- const handleAnnotationUpdate = useCallback((updates)=>{
257
- if (-1 === selectedAnnotationIndex) return;
258
- setLocalAnnotations((prev)=>{
259
- const updated = [
260
- ...prev
261
- ];
262
- updated[selectedAnnotationIndex] = {
263
- ...updated[selectedAnnotationIndex],
264
- ...updates
265
- };
266
- return updated;
267
- });
268
- }, [
269
- selectedAnnotationIndex
270
- ]);
271
- const handleImagesChange = useCallback((files)=>handleAnnotationUpdate({
272
- images: files
273
- }), [
274
- handleAnnotationUpdate
275
- ]);
276
- const canSave = useMemo(()=>localAnnotations.every((annotation)=>annotation.title && '' !== annotation.title.trim()), [
277
- localAnnotations
278
- ]);
279
- const splatModel = useMemo(()=>/*#__PURE__*/ jsx(SplatModel, {
280
- ...splatModelProps,
281
- splatSrc: splatSrc,
282
- splatFormat: splatFormat,
283
- rotation: [
284
- -180,
285
- 0,
286
- 0
287
- ],
288
- scaleFactor: scaleFactor,
289
- revealRain: isHighPerformance
290
- }, "splat"), [
291
- isHighPerformance,
292
- splatSrc,
293
- splatFormat,
294
- scaleFactor,
295
- splatModelProps
296
- ]);
297
- return /*#__PURE__*/ jsxs(CameraEntityContext.Provider, {
298
- value: activeCamera,
299
- children: [
300
- /*#__PURE__*/ jsx(GradientSky, {
301
- topColor: skyColor || '#FFFFFF',
302
- horizonColor: skyColor || '#EAF8FF',
303
- groundColor: groundColor || '#C3BDB7'
304
- }),
305
- /*#__PURE__*/ jsxs(Entity, {
306
- name: "camera-root",
307
- ref: setCameraRoot,
308
- children: [
309
- /*#__PURE__*/ jsx(Entity, {
310
- name: "camera",
311
- ref: setCameraEntity,
312
- children: camera
313
- }),
314
- !isCurrentlyInXr && /*#__PURE__*/ jsx(Script, {
315
- script: WalkthroughCamera,
316
- boundsCenter: sceneBoundsCenter,
317
- boundsRadius: sceneBoundsRadius,
318
- enableFly: !isTextFieldFocused,
319
- averageCameraHeight: averageCameraHeight
320
- }),
321
- /*#__PURE__*/ jsx(XrExitButton, {
322
- onClose: onXrClose
323
- }),
324
- /*#__PURE__*/ jsx(Script, {
325
- script: XrControllers,
326
- enabled: !isEdit
327
- }),
328
- /*#__PURE__*/ jsx(XrAnnotationInteraction, {
329
- onDismiss: handleCloseAnnotation
330
- }),
331
- /*#__PURE__*/ jsx(XrPointerRay, {}),
332
- isCurrentlyInVr && viewingAnnotation && /*#__PURE__*/ jsx(VrAnnotationPanel, {
333
- annotation: viewingAnnotation,
334
- annotationIndex: viewingAnnotationIndex
335
- }, viewingAnnotationIndex),
336
- /*#__PURE__*/ jsx(XrGazeDwell, {
337
- activeIndex: viewingAnnotationIndex
338
- }),
339
- /*#__PURE__*/ jsx(Script, {
340
- script: XrStartPose,
341
- targetX: cameraPosition[0],
342
- targetZ: cameraPosition[2],
343
- focusX: origin[0],
344
- focusZ: origin[2]
345
- }),
346
- /*#__PURE__*/ jsx(Script, {
347
- script: TfXrNavigation,
348
- enabled: !isEdit,
349
- enableTeleport: !isCurrentlyInAr && !viewingAnnotation,
350
- enableSnapVertical: false,
351
- isTeleportBlocked: isTeleportBlocked
352
- }),
353
- !isCurrentlyInXr && /*#__PURE__*/ jsx(Script, {
354
- script: AutoRotator,
355
- enabled: !isEdit && autoRotate && !viewingAnnotation,
356
- startDelay: 0.5,
357
- restartDelay: 3,
358
- startFadeInTime: 0.5
359
- })
360
- ]
361
- }),
362
- /*#__PURE__*/ jsxs(Entity, {
363
- name: "content-root",
364
- children: [
365
- splatModel,
366
- sceneBounds?.m !== void 0 && 3 === groundPlane.length && /*#__PURE__*/ jsx(BoundaryRing, {
367
- center: sceneBoundsCenter,
368
- radius: sceneBoundsRadius,
369
- groundPlane: groundPlane
370
- }),
371
- localAnnotations.length > 0 && /*#__PURE__*/ jsxs(Entity, {
372
- name: "annotations-root",
373
- children: [
374
- /*#__PURE__*/ jsx(Script, {
375
- script: TfAnnotationManager,
376
- enabled: true,
377
- hotspotSize: 30,
378
- maxWorldSize: MAX_HOTSPOT_WORLD_SIZE,
379
- opacity: 1,
380
- hotspotColor: new Color().fromString(theme.palette.TwClrIcnBrand),
381
- hoverColor: new Color().fromString('#ffffff'),
382
- hotspotBackgroundColor: theme.palette.TwClrBaseWhite
383
- }),
384
- localAnnotations.map((annotation, index)=>/*#__PURE__*/ jsx(Annotation, {
385
- ...annotation,
386
- index: index,
387
- visible: showAnnotations,
388
- isEdit: isEdit,
389
- isSelected: selectedAnnotationIndex === index,
390
- isViewed: viewingAnnotationIndex === index,
391
- onSelect: ()=>setSelectedAnnotationIndex(index),
392
- onPositionChange: handleAnnotationPositionChange,
393
- onView: (anno, screenX, screenY)=>handleAnnotationView(anno, index, screenX, screenY),
394
- onScreenPositionUpdate: handleAnnotationScreenPositionUpdate
395
- }, `annotation-${index}`))
396
- ]
397
- })
398
- ]
399
- }),
400
- isCurrentlyInXr && sceneBoundsRadius > 0 && /*#__PURE__*/ jsx(BoundaryWall, {
401
- center: sceneBoundsCenter,
402
- radius: sceneBoundsRadius + WALL_INSET,
403
- groundPlane: groundPlane,
404
- baseY: 0
405
- }),
406
- /*#__PURE__*/ jsx(SplatControls, {
407
- defaultCameraFocus: origin,
408
- defaultCameraPosition: cameraPosition,
409
- showAnnotations: showAnnotations,
410
- onToggleAnnotations: setShowAnnotations,
411
- autoRotate: autoRotate,
412
- onToggleAutoRotate: setAutoRotate,
413
- isEdit: isEdit,
414
- onToggleEdit: setIsEdit,
415
- onSave: handleSave,
416
- onCancel: handleCancel,
417
- onAddAnnotation: handleAddAnnotation,
418
- onDeleteAnnotation: handleDeleteAnnotation,
419
- onDeselectAnnotation: handleDeselectAnnotation,
420
- hasSelectedAnnotation: selectedAnnotationIndex >= 0,
421
- selectedAnnotation: selectedAnnotationIndex >= 0 ? localAnnotations[selectedAnnotationIndex] : null,
422
- selectedAnnotationIndex: selectedAnnotationIndex,
423
- onAnnotationUpdate: handleAnnotationUpdate,
424
- onImagesChange: handleImagesChange,
425
- maxImagesPerAnnotation: maxImagesPerAnnotation,
426
- onTextFieldFocus: setIsTextFieldFocused,
427
- canSave: canSave,
428
- editable: editable,
429
- isFullScreen: isFullScreen,
430
- onToggleFullScreen: onToggleFullScreen,
431
- isFreeFly: isFreeFly,
432
- onToggleFreeFly: handleToggleFreeFly,
433
- showFreeFly: showFreeFly,
434
- onError: reportXrError
435
- }),
436
- /*#__PURE__*/ jsx(AnnotationPanel, {
437
- annotation: viewingAnnotation,
438
- hotspotPosition: viewedScreenPos,
439
- onClose: handleCloseAnnotation
440
- })
441
- ]
227
+ }, [selectedAnnotationIndex]);
228
+ const handleSave = useCallback(() => {
229
+ const saveAndClose = async () => {
230
+ await onSaveAnnotations(localAnnotations);
231
+ setIsEdit(false);
232
+ setSelectedAnnotationIndex(-1);
233
+ };
234
+ void saveAndClose();
235
+ }, [onSaveAnnotations, localAnnotations]);
236
+ const handleCancel = useCallback(() => {
237
+ setLocalAnnotations(annotations);
238
+ setIsEdit(false);
239
+ setSelectedAnnotationIndex(-1);
240
+ }, [annotations]);
241
+ const handleAddAnnotation = useCallback(() => {
242
+ const newAnnotation = {
243
+ position: origin,
244
+ title: ''
245
+ };
246
+ setLocalAnnotations(prev => [...prev, newAnnotation]);
247
+ setSelectedAnnotationIndex(localAnnotations.length);
248
+ }, [origin, localAnnotations]);
249
+ const handleDeleteAnnotation = useCallback(() => {
250
+ if (selectedAnnotationIndex === -1) {
251
+ return;
252
+ }
253
+ setLocalAnnotations(prev => prev.filter((_, index) => index !== selectedAnnotationIndex));
254
+ setSelectedAnnotationIndex(-1);
255
+ }, [selectedAnnotationIndex]);
256
+ const handleDeselectAnnotation = useCallback(() => {
257
+ setSelectedAnnotationIndex(-1);
258
+ }, []);
259
+ const handleAnnotationView = useCallback(
260
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
261
+ (annotation, annotationIndex, screenX, screenY) => {
262
+ setViewingAnnotation(annotation);
263
+ setViewingAnnotationIndex(annotationIndex);
264
+ // Cleared so the connector line waits for the hotspot's post-aim position
265
+ // rather than briefly pointing at its old spot.
266
+ setViewedScreenPos(null);
267
+ }, []);
268
+ const handleAnnotationScreenPositionUpdate = useCallback((_index, screenX, screenY, size) => {
269
+ if (isCurrentlyInXr) {
270
+ return;
271
+ }
272
+ // The scene is frozen while the panel is open, so guard against redundant
273
+ // updates from the per-frame callback to avoid needless re-renders.
274
+ setViewedScreenPos(prev => prev && prev.x === screenX && prev.y === screenY && prev.size === size ? prev : {
275
+ x: screenX,
276
+ y: screenY,
277
+ size
442
278
  });
279
+ }, [isCurrentlyInXr]);
280
+ const handleCloseAnnotation = useCallback(() => {
281
+ setViewingAnnotation(null);
282
+ setViewingAnnotationIndex(-1);
283
+ setViewedScreenPos(null);
284
+ }, []);
285
+ const isTeleportBlocked = useCallback(inputSource => rayHitsInteractiveUi(app, inputSource.getOrigin(), inputSource.getDirection()), [app]);
286
+ useEffect(() => {
287
+ if (!isCurrentlyInXr) {
288
+ handleCloseAnnotation();
289
+ }
290
+ }, [isCurrentlyInXr, handleCloseAnnotation]);
291
+ const handleAnnotationUpdate = useCallback(updates => {
292
+ if (selectedAnnotationIndex === -1) {
293
+ return;
294
+ }
295
+ setLocalAnnotations(prev => {
296
+ const updated = [...prev];
297
+ updated[selectedAnnotationIndex] = {
298
+ ...updated[selectedAnnotationIndex],
299
+ ...updates
300
+ };
301
+ return updated;
302
+ });
303
+ }, [selectedAnnotationIndex]);
304
+ const handleImagesChange = useCallback(files => handleAnnotationUpdate({
305
+ images: files
306
+ }), [handleAnnotationUpdate]);
307
+ const canSave = useMemo(() => localAnnotations.every(annotation => annotation.title && annotation.title.trim() !== ''), [localAnnotations]);
308
+ const splatModel = useMemo(() => /*#__PURE__*/jsx(SplatModel, {
309
+ ...splatModelProps,
310
+ splatSrc: splatSrc,
311
+ splatFormat: splatFormat,
312
+ rotation: [-180, 0, 0],
313
+ scaleFactor: scaleFactor,
314
+ revealRain: isHighPerformance
315
+ }, 'splat'), [isHighPerformance, splatSrc, splatFormat, scaleFactor, splatModelProps]);
316
+ return /*#__PURE__*/jsxs(CameraEntityContext.Provider, {
317
+ value: activeCamera,
318
+ children: [/*#__PURE__*/jsx(GradientSky, {
319
+ topColor: skyColor || '#FFFFFF',
320
+ horizonColor: skyColor || '#EAF8FF',
321
+ groundColor: groundColor || '#C3BDB7'
322
+ }), /*#__PURE__*/jsxs(Entity, {
323
+ name: "camera-root",
324
+ ref: setCameraRoot,
325
+ children: [/*#__PURE__*/jsx(Entity, {
326
+ name: "camera",
327
+ ref: setCameraEntity,
328
+ children: camera
329
+ }), !isCurrentlyInXr && /*#__PURE__*/jsx(Script, {
330
+ script: WalkthroughCamera,
331
+ boundsCenter: sceneBoundsCenter,
332
+ boundsRadius: sceneBoundsRadius,
333
+ enableFly: !isTextFieldFocused,
334
+ averageCameraHeight: averageCameraHeight
335
+ }), /*#__PURE__*/jsx(XrExitButton, {
336
+ onClose: onXrClose
337
+ }), /*#__PURE__*/jsx(Script, {
338
+ script: XrControllers,
339
+ enabled: !isEdit
340
+ }), /*#__PURE__*/jsx(XrAnnotationInteraction, {
341
+ onDismiss: handleCloseAnnotation
342
+ }), /*#__PURE__*/jsx(XrPointerRay, {}), isCurrentlyInVr && viewingAnnotation && /*#__PURE__*/jsx(VrAnnotationPanel, {
343
+ annotation: viewingAnnotation,
344
+ annotationIndex: viewingAnnotationIndex
345
+ }, viewingAnnotationIndex), /*#__PURE__*/jsx(XrGazeDwell, {
346
+ activeIndex: viewingAnnotationIndex
347
+ }), /*#__PURE__*/jsx(Script, {
348
+ script: XrStartPose,
349
+ targetX: cameraPosition[0],
350
+ targetZ: cameraPosition[2],
351
+ focusX: origin[0],
352
+ focusZ: origin[2]
353
+ }), /*#__PURE__*/jsx(Script, {
354
+ script: TfXrNavigation,
355
+ enabled: !isEdit,
356
+ enableTeleport: !isCurrentlyInAr && !viewingAnnotation,
357
+ enableSnapVertical: false,
358
+ isTeleportBlocked: isTeleportBlocked
359
+ }), !isCurrentlyInXr && /*#__PURE__*/jsx(Script, {
360
+ script: AutoRotator,
361
+ enabled: !isEdit && autoRotate && !viewingAnnotation,
362
+ startDelay: 0.5,
363
+ restartDelay: 3,
364
+ startFadeInTime: 0.5
365
+ })]
366
+ }), /*#__PURE__*/jsxs(Entity, {
367
+ name: "content-root",
368
+ children: [splatModel, sceneBounds?.m !== undefined && groundPlane.length === 3 && /*#__PURE__*/jsx(BoundaryRing, {
369
+ center: sceneBoundsCenter,
370
+ radius: sceneBoundsRadius,
371
+ groundPlane: groundPlane
372
+ }), localAnnotations.length > 0 && /*#__PURE__*/jsxs(Entity, {
373
+ name: "annotations-root",
374
+ children: [/*#__PURE__*/jsx(Script, {
375
+ script: TfAnnotationManager,
376
+ enabled: true,
377
+ hotspotSize: 30,
378
+ maxWorldSize: MAX_HOTSPOT_WORLD_SIZE,
379
+ opacity: 1,
380
+ hotspotColor: new Color().fromString(theme.palette.TwClrIcnBrand),
381
+ hoverColor: new Color().fromString('#ffffff'),
382
+ hotspotBackgroundColor: theme.palette.TwClrBaseWhite
383
+ }), localAnnotations.map((annotation, index) => /*#__PURE__*/jsx(Annotation, {
384
+ ...annotation,
385
+ index: index,
386
+ visible: showAnnotations,
387
+ isEdit: isEdit,
388
+ isSelected: selectedAnnotationIndex === index,
389
+ isViewed: viewingAnnotationIndex === index,
390
+ onSelect: () => setSelectedAnnotationIndex(index),
391
+ onPositionChange: handleAnnotationPositionChange,
392
+ onView: (anno, screenX, screenY) => handleAnnotationView(anno, index, screenX, screenY),
393
+ onScreenPositionUpdate: handleAnnotationScreenPositionUpdate
394
+ }, `annotation-${index}`))]
395
+ })]
396
+ }), isCurrentlyInXr && sceneBoundsRadius > 0 && /*#__PURE__*/jsx(BoundaryWall, {
397
+ center: sceneBoundsCenter,
398
+ radius: sceneBoundsRadius + WALL_INSET,
399
+ groundPlane: groundPlane,
400
+ baseY: 0
401
+ }), /*#__PURE__*/jsx(SplatControls, {
402
+ defaultCameraFocus: origin,
403
+ defaultCameraPosition: cameraPosition,
404
+ showAnnotations: showAnnotations,
405
+ onToggleAnnotations: setShowAnnotations,
406
+ autoRotate: autoRotate,
407
+ onToggleAutoRotate: setAutoRotate,
408
+ isEdit: isEdit,
409
+ onToggleEdit: setIsEdit,
410
+ onSave: handleSave,
411
+ onCancel: handleCancel,
412
+ onAddAnnotation: handleAddAnnotation,
413
+ onDeleteAnnotation: handleDeleteAnnotation,
414
+ onDeselectAnnotation: handleDeselectAnnotation,
415
+ hasSelectedAnnotation: selectedAnnotationIndex >= 0,
416
+ selectedAnnotation: selectedAnnotationIndex >= 0 ? localAnnotations[selectedAnnotationIndex] : null,
417
+ selectedAnnotationIndex: selectedAnnotationIndex,
418
+ onAnnotationUpdate: handleAnnotationUpdate,
419
+ onImagesChange: handleImagesChange,
420
+ maxImagesPerAnnotation: maxImagesPerAnnotation,
421
+ onTextFieldFocus: setIsTextFieldFocused,
422
+ canSave: canSave,
423
+ editable: editable,
424
+ isFullScreen: isFullScreen,
425
+ onToggleFullScreen: onToggleFullScreen,
426
+ isFreeFly: isFreeFly,
427
+ onToggleFreeFly: handleToggleFreeFly,
428
+ showFreeFly: showFreeFly,
429
+ onError: reportXrError
430
+ }), /*#__PURE__*/jsx(AnnotationPanel, {
431
+ annotation: viewingAnnotation,
432
+ hotspotPosition: viewedScreenPos,
433
+ onClose: handleCloseAnnotation
434
+ })]
435
+ });
443
436
  };
444
- const VirtualWalkthrough_VirtualWalkthroughViewer = VirtualWalkthroughViewer;
445
- export default VirtualWalkthrough_VirtualWalkthroughViewer;
437
+
438
+ export { VirtualWalkthroughViewer as default };