@collabdt/core 0.3.1 → 0.3.2

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 (196) hide show
  1. package/dist/core/components/authentication/ForgotPassword.d.ts.map +1 -1
  2. package/dist/core/components/authentication/ForgotPassword.js +0 -1
  3. package/dist/core/components/authentication/ForgotPassword.js.map +1 -1
  4. package/dist/core/components/ui/Comments/CollapsibleCommentItem.d.ts +12 -2
  5. package/dist/core/components/ui/Comments/CollapsibleCommentItem.d.ts.map +1 -1
  6. package/dist/core/components/ui/Comments/CollapsibleCommentItem.js +137 -139
  7. package/dist/core/components/ui/Comments/CollapsibleCommentItem.js.map +1 -1
  8. package/dist/core/components/ui/Comments/Comment.d.ts +18 -1
  9. package/dist/core/components/ui/Comments/Comment.d.ts.map +1 -1
  10. package/dist/core/components/ui/Comments/Comment.js +94 -23
  11. package/dist/core/components/ui/Comments/Comment.js.map +1 -1
  12. package/dist/core/components/ui/Comments/CommentActionButtons.d.ts +34 -0
  13. package/dist/core/components/ui/Comments/CommentActionButtons.d.ts.map +1 -0
  14. package/dist/core/components/ui/Comments/CommentActionButtons.js +106 -0
  15. package/dist/core/components/ui/Comments/CommentActionButtons.js.map +1 -0
  16. package/dist/core/components/ui/Comments/CommentInput.d.ts.map +1 -1
  17. package/dist/core/components/ui/Comments/CommentInput.js +5 -2
  18. package/dist/core/components/ui/Comments/CommentInput.js.map +1 -1
  19. package/dist/core/components/ui/Comments/CommentsSection.d.ts.map +1 -1
  20. package/dist/core/components/ui/Comments/CommentsSection.js +86 -42
  21. package/dist/core/components/ui/Comments/CommentsSection.js.map +1 -1
  22. package/dist/core/components/ui/Comments/commentUtils.d.ts.map +1 -1
  23. package/dist/core/components/ui/Comments/commentUtils.js +2 -1
  24. package/dist/core/components/ui/Comments/commentUtils.js.map +1 -1
  25. package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.d.ts.map +1 -1
  26. package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.js +0 -1
  27. package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.js.map +1 -1
  28. package/dist/core/components/ui/InfoSidebar/Container.d.ts.map +1 -1
  29. package/dist/core/components/ui/InfoSidebar/Container.js +16 -12
  30. package/dist/core/components/ui/InfoSidebar/Container.js.map +1 -1
  31. package/dist/core/components/ui/Sensors/SensorInput.d.ts.map +1 -1
  32. package/dist/core/components/ui/Sensors/SensorInput.js +0 -1
  33. package/dist/core/components/ui/Sensors/SensorInput.js.map +1 -1
  34. package/dist/core/components/ui/Sensors/SensorsSection.js +0 -2
  35. package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
  36. package/dist/core/components/ui/Sidebar.d.ts.map +1 -1
  37. package/dist/core/components/ui/Sidebar.js +27 -4
  38. package/dist/core/components/ui/Sidebar.js.map +1 -1
  39. package/dist/core/components/ui/ToolbarButton.d.ts.map +1 -1
  40. package/dist/core/components/ui/ToolbarButton.js +0 -1
  41. package/dist/core/components/ui/ToolbarButton.js.map +1 -1
  42. package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.d.ts.map +1 -1
  43. package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.js +0 -2
  44. package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.js.map +1 -1
  45. package/dist/core/components/viewers/Data/details/EnergySourcesGrid.d.ts.map +1 -1
  46. package/dist/core/components/viewers/Data/details/EnergySourcesGrid.js +0 -10
  47. package/dist/core/components/viewers/Data/details/EnergySourcesGrid.js.map +1 -1
  48. package/dist/core/components/viewers/Data/files/DatatableFileAdder.d.ts.map +1 -1
  49. package/dist/core/components/viewers/Data/files/DatatableFileAdder.js +0 -1
  50. package/dist/core/components/viewers/Data/files/DatatableFileAdder.js.map +1 -1
  51. package/dist/core/components/viewers/Data/files/utils/fuzzySearch.d.ts.map +1 -1
  52. package/dist/core/components/viewers/Data/files/utils/fuzzySearch.js +0 -1
  53. package/dist/core/components/viewers/Data/files/utils/fuzzySearch.js.map +1 -1
  54. package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.d.ts.map +1 -1
  55. package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.js +0 -1
  56. package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.js.map +1 -1
  57. package/dist/core/components/viewers/Data/utils/filterHelper.d.ts.map +1 -1
  58. package/dist/core/components/viewers/Data/utils/filterHelper.js +0 -2
  59. package/dist/core/components/viewers/Data/utils/filterHelper.js.map +1 -1
  60. package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.d.ts.map +1 -1
  61. package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js +0 -3
  62. package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js.map +1 -1
  63. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
  64. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js +1 -7
  65. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js.map +1 -1
  66. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.d.ts.map +1 -1
  67. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.js +1 -9
  68. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.js.map +1 -1
  69. package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.d.ts.map +1 -1
  70. package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.js +4 -3
  71. package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.js.map +1 -1
  72. package/dist/core/components/viewers/bim/src/ClippingPlane.d.ts.map +1 -1
  73. package/dist/core/components/viewers/bim/src/ClippingPlane.js +0 -1
  74. package/dist/core/components/viewers/bim/src/ClippingPlane.js.map +1 -1
  75. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.d.ts +16 -10
  76. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.d.ts.map +1 -1
  77. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js +26 -6
  78. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js.map +1 -1
  79. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts +23 -0
  80. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts.map +1 -0
  81. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js +108 -0
  82. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js.map +1 -0
  83. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.d.ts +44 -0
  84. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.d.ts.map +1 -0
  85. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js +77 -0
  86. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js.map +1 -0
  87. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts +24 -0
  88. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts.map +1 -0
  89. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js +27 -0
  90. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js.map +1 -0
  91. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.d.ts.map +1 -1
  92. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.js +2 -2
  93. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.js.map +1 -1
  94. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts.map +1 -1
  95. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js +242 -61
  96. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js.map +1 -1
  97. package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.d.ts.map +1 -1
  98. package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.js +1 -5
  99. package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.js.map +1 -1
  100. package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.d.ts.map +1 -1
  101. package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.js +0 -1
  102. package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.js.map +1 -1
  103. package/dist/core/components/viewers/bim/src/tools/measureBimTool.d.ts.map +1 -1
  104. package/dist/core/components/viewers/bim/src/tools/measureBimTool.js +1 -2
  105. package/dist/core/components/viewers/bim/src/tools/measureBimTool.js.map +1 -1
  106. package/dist/core/components/viewers/map/MapViewer.d.ts.map +1 -1
  107. package/dist/core/components/viewers/map/MapViewer.js.map +1 -1
  108. package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.d.ts.map +1 -1
  109. package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.js +11 -14
  110. package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.js.map +1 -1
  111. package/dist/core/components/viewers/map/datasets/src/ckanDatasets.d.ts.map +1 -1
  112. package/dist/core/components/viewers/map/datasets/src/ckanDatasets.js +2 -5
  113. package/dist/core/components/viewers/map/datasets/src/ckanDatasets.js.map +1 -1
  114. package/dist/core/components/viewers/map/datasets/src/localDatasets.d.ts.map +1 -1
  115. package/dist/core/components/viewers/map/datasets/src/localDatasets.js +0 -3
  116. package/dist/core/components/viewers/map/datasets/src/localDatasets.js.map +1 -1
  117. package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.d.ts.map +1 -1
  118. package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.js +0 -11
  119. package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.js.map +1 -1
  120. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.d.ts.map +1 -1
  121. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.js +0 -1
  122. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.js.map +1 -1
  123. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.d.ts.map +1 -1
  124. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.js +0 -1
  125. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.js.map +1 -1
  126. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.d.ts.map +1 -1
  127. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.js +0 -1
  128. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.js.map +1 -1
  129. package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.d.ts.map +1 -1
  130. package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.js +174 -53
  131. package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.js.map +1 -1
  132. package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.d.ts.map +1 -1
  133. package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.js +2 -1
  134. package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.js.map +1 -1
  135. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts.map +1 -1
  136. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js +0 -3
  137. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
  138. package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.d.ts.map +1 -1
  139. package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.js +4 -3
  140. package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.js.map +1 -1
  141. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
  142. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.js +0 -1
  143. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.js.map +1 -1
  144. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.d.ts.map +1 -1
  145. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js +1 -5
  146. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js.map +1 -1
  147. package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.d.ts.map +1 -1
  148. package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js +1 -3
  149. package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js.map +1 -1
  150. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts.map +1 -1
  151. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js +0 -1
  152. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js.map +1 -1
  153. package/dist/core/components/viewers/map/utils/flyToEach.d.ts.map +1 -1
  154. package/dist/core/components/viewers/map/utils/flyToEach.js +0 -1
  155. package/dist/core/components/viewers/map/utils/flyToEach.js.map +1 -1
  156. package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.d.ts.map +1 -1
  157. package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.js +0 -2
  158. package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.js.map +1 -1
  159. package/dist/core/hooks/comments/comments.d.ts +11 -0
  160. package/dist/core/hooks/comments/comments.d.ts.map +1 -1
  161. package/dist/core/hooks/comments/comments.js +3 -1
  162. package/dist/core/hooks/comments/comments.js.map +1 -1
  163. package/dist/core/hooks/comments/createCommentHooks.d.ts +11 -0
  164. package/dist/core/hooks/comments/createCommentHooks.d.ts.map +1 -1
  165. package/dist/core/hooks/comments/createCommentHooks.js +30 -1
  166. package/dist/core/hooks/comments/createCommentHooks.js.map +1 -1
  167. package/dist/core/hooks/files/files.d.ts.map +1 -1
  168. package/dist/core/hooks/files/files.js +0 -1
  169. package/dist/core/hooks/files/files.js.map +1 -1
  170. package/dist/core/hooks/ui/useResizableSidebarWidth.d.ts +30 -0
  171. package/dist/core/hooks/ui/useResizableSidebarWidth.d.ts.map +1 -0
  172. package/dist/core/hooks/ui/useResizableSidebarWidth.js +75 -0
  173. package/dist/core/hooks/ui/useResizableSidebarWidth.js.map +1 -0
  174. package/dist/core/i18n/messages/en.json +5 -0
  175. package/dist/core/i18n/messages/es.json +6 -1
  176. package/dist/core/i18n/messages/fr.json +6 -1
  177. package/dist/core/store/Datasets/reducer.d.ts.map +1 -1
  178. package/dist/core/store/Datasets/reducer.js +0 -2
  179. package/dist/core/store/Datasets/reducer.js.map +1 -1
  180. package/dist/core/store/Files/reducer.js +0 -1
  181. package/dist/core/store/Files/reducer.js.map +1 -1
  182. package/dist/core/store/Menus/context.d.ts.map +1 -1
  183. package/dist/core/store/Menus/context.js +4 -1
  184. package/dist/core/store/Menus/context.js.map +1 -1
  185. package/dist/core/store/Menus/reducer.d.ts +20 -0
  186. package/dist/core/store/Menus/reducer.d.ts.map +1 -1
  187. package/dist/core/store/Menus/reducer.js +21 -3
  188. package/dist/core/store/Menus/reducer.js.map +1 -1
  189. package/dist/core/utils/markerUtils.d.ts +10 -0
  190. package/dist/core/utils/markerUtils.d.ts.map +1 -1
  191. package/dist/core/utils/markerUtils.js +9 -1
  192. package/dist/core/utils/markerUtils.js.map +1 -1
  193. package/dist/core/utils/memcache.d.ts.map +1 -1
  194. package/dist/core/utils/memcache.js +0 -7
  195. package/dist/core/utils/memcache.js.map +1 -1
  196. package/package.json +4 -1
@@ -0,0 +1,108 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Avatar } from "../../../../../../ui/Avatar";
5
+ import { UserAvatar } from "../../../../../../ui/UserAvatar";
6
+ function clusterColor(count) {
7
+ if (count >= 750) return "#f28cb1";
8
+ if (count >= 100) return "#f1f075";
9
+ return "#51bbd6";
10
+ }
11
+ function BimCommentCluster({ members, highlight = false, onSelect, onFocus }) {
12
+ const [expanded, setExpanded] = React.useState(false);
13
+ const [fanned, setFanned] = React.useState(false);
14
+ const closeTimer = React.useRef(null);
15
+ const count = members.length;
16
+ const radius = Math.min(64, 26 + count * 4);
17
+ React.useEffect(() => {
18
+ if (!expanded) {
19
+ setFanned(false);
20
+ return;
21
+ }
22
+ const raf = requestAnimationFrame(() => setFanned(true));
23
+ return () => cancelAnimationFrame(raf);
24
+ }, [expanded]);
25
+ React.useEffect(() => () => {
26
+ if (closeTimer.current) clearTimeout(closeTimer.current);
27
+ }, []);
28
+ const open = () => {
29
+ if (closeTimer.current) {
30
+ clearTimeout(closeTimer.current);
31
+ closeTimer.current = null;
32
+ }
33
+ setExpanded(true);
34
+ };
35
+ const scheduleClose = () => {
36
+ if (closeTimer.current) clearTimeout(closeTimer.current);
37
+ closeTimer.current = setTimeout(() => setExpanded(false), 150);
38
+ };
39
+ return /* @__PURE__ */ jsxs(
40
+ "div",
41
+ {
42
+ className: "relative -translate-x-1/2 -translate-y-1/2 pointer-events-auto",
43
+ onMouseEnter: open,
44
+ onMouseLeave: scheduleClose,
45
+ children: [
46
+ /* @__PURE__ */ jsx(
47
+ "div",
48
+ {
49
+ className: "relative flex items-center justify-center rounded-full text-sm font-medium select-none cursor-pointer",
50
+ style: {
51
+ width: 36,
52
+ height: 36,
53
+ backgroundColor: clusterColor(count),
54
+ color: "#000",
55
+ textShadow: "0 0 2px #fff, 0 0 2px #fff",
56
+ boxShadow: highlight ? "0 0 0 2px #73cee2, 0 0 8px rgba(115, 206, 226, 0.5)" : "0 0 0 1px white"
57
+ },
58
+ children: count
59
+ }
60
+ ),
61
+ expanded && /* @__PURE__ */ jsx("div", { className: "absolute left-1/2 top-1/2", children: members.map((member, i) => {
62
+ var _a;
63
+ const angle = 2 * Math.PI * i / count - Math.PI / 2;
64
+ const x = fanned ? Math.cos(angle) * radius : 0;
65
+ const y = fanned ? Math.sin(angle) * radius : 0;
66
+ return /* @__PURE__ */ jsx(
67
+ "div",
68
+ {
69
+ className: "absolute",
70
+ style: {
71
+ transform: `translate(-50%, -50%) translate(${x}px, ${y}px)`,
72
+ opacity: fanned ? 1 : 0,
73
+ transition: "transform 200ms ease, opacity 200ms ease",
74
+ transitionDelay: `${i * 20}ms`
75
+ },
76
+ onMouseEnter: open,
77
+ onPointerDown: (e) => {
78
+ e.stopPropagation();
79
+ onSelect == null ? void 0 : onSelect(member.id);
80
+ onFocus == null ? void 0 : onFocus(member.id);
81
+ },
82
+ children: /* @__PURE__ */ jsx(
83
+ "div",
84
+ {
85
+ className: "h-9 w-9 rounded-full overflow-hidden cursor-pointer bg-card",
86
+ style: { boxShadow: "0 0 0 1px white" },
87
+ children: /* @__PURE__ */ jsx(Avatar, { className: "h-full w-full", children: /* @__PURE__ */ jsx(
88
+ UserAvatar,
89
+ {
90
+ imageFileId: (_a = member.imageFileId) != null ? _a : null,
91
+ name: member.userName,
92
+ className: "h-full w-full rounded-full object-cover"
93
+ }
94
+ ) })
95
+ }
96
+ )
97
+ },
98
+ member.id
99
+ );
100
+ }) })
101
+ ]
102
+ }
103
+ );
104
+ }
105
+ export {
106
+ BimCommentCluster as default
107
+ };
108
+ //# sourceMappingURL=BimCommentCluster.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.tsx"],"sourcesContent":["'use client'\n\n// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (C) 2025 Collab Digital Twins\n\nimport * as React from 'react'\n\nimport { Avatar } from '../../../../../../ui/Avatar'\nimport { UserAvatar } from '../../../../../../ui/UserAvatar'\n\nexport interface ClusterMember {\n id: number\n userName?: string\n imageFileId?: number | null\n}\n\ninterface BimCommentClusterProps {\n members: ClusterMember[]\n highlight?: boolean\n onSelect?: (id: number) => void\n onFocus?: (id: number) => void\n}\n\n/** Cluster bubble colour, matching the map comment cluster palette (mapLayersUtils). */\nfunction clusterColor(count: number): string {\n if (count >= 750) return '#f28cb1'\n if (count >= 100) return '#f1f075'\n return '#51bbd6'\n}\n\n/**\n * Rendered inside a CSS2DObject at a cluster's world center. Shows a numbered circle for\n * overlapping BIM comment markers; hovering fans the members out (animated) so each can be\n * seen and clicked, and collapses back on mouse-out. A short close delay keeps the fan open\n * while the pointer crosses the gap between the bubble and a member (same trick the map\n * cluster expansion uses), so members stay clickable. Members mount only while expanded, so a\n * collapsed cluster is just the bubble — no leftover container behind it.\n */\nexport default function BimCommentCluster({ members, highlight = false, onSelect, onFocus }: BimCommentClusterProps): React.ReactElement {\n const [expanded, setExpanded] = React.useState(false)\n const [fanned, setFanned] = React.useState(false)\n const closeTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const count = members.length\n const radius = Math.min(64, 26 + count * 4)\n\n // Animate the fan: members mount at the centre, then move out to their radius on the next frame.\n React.useEffect(() => {\n if (!expanded) {\n setFanned(false)\n return\n }\n const raf = requestAnimationFrame(() => setFanned(true))\n return () => cancelAnimationFrame(raf)\n }, [expanded])\n\n React.useEffect(() => () => {\n if (closeTimer.current) clearTimeout(closeTimer.current)\n }, [])\n\n const open = () => {\n if (closeTimer.current) {\n clearTimeout(closeTimer.current)\n closeTimer.current = null\n }\n setExpanded(true)\n }\n\n const scheduleClose = () => {\n if (closeTimer.current) clearTimeout(closeTimer.current)\n closeTimer.current = setTimeout(() => setExpanded(false), 150)\n }\n\n return (\n <div\n className=\"relative -translate-x-1/2 -translate-y-1/2 pointer-events-auto\"\n onMouseEnter={open}\n onMouseLeave={scheduleClose}\n >\n <div\n className=\"relative flex items-center justify-center rounded-full text-sm font-medium select-none cursor-pointer\"\n style={{\n width: 36,\n height: 36,\n backgroundColor: clusterColor(count),\n color: '#000',\n textShadow: '0 0 2px #fff, 0 0 2px #fff',\n boxShadow: highlight\n ? '0 0 0 2px #73cee2, 0 0 8px rgba(115, 206, 226, 0.5)'\n : '0 0 0 1px white',\n }}\n >\n {count}\n </div>\n\n {expanded && (\n <div className=\"absolute left-1/2 top-1/2\">\n {members.map((member, i) => {\n const angle = (2 * Math.PI * i) / count - Math.PI / 2\n const x = fanned ? Math.cos(angle) * radius : 0\n const y = fanned ? Math.sin(angle) * radius : 0\n return (\n <div\n key={member.id}\n className=\"absolute\"\n style={{\n transform: `translate(-50%, -50%) translate(${x}px, ${y}px)`,\n opacity: fanned ? 1 : 0,\n transition: 'transform 200ms ease, opacity 200ms ease',\n transitionDelay: `${i * 20}ms`,\n }}\n onMouseEnter={open}\n // pointerdown (not click): the CSS2D renderer re-transforms this element every\n // frame and it is still animating out, so a browser click frequently never fires.\n onPointerDown={(e) => {\n e.stopPropagation()\n onSelect?.(member.id)\n onFocus?.(member.id)\n }}\n >\n <div\n className=\"h-9 w-9 rounded-full overflow-hidden cursor-pointer bg-card\"\n style={{ boxShadow: '0 0 0 1px white' }}\n >\n <Avatar className=\"h-full w-full\">\n <UserAvatar\n imageFileId={member.imageFileId ?? null}\n name={member.userName}\n className=\"h-full w-full rounded-full object-cover\"\n />\n </Avatar>\n </div>\n </div>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":";AAyEI,SAKE,KALF;AApEJ,YAAY,WAAW;AAEvB,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAgB3B,SAAS,aAAa,OAAuB;AAC3C,MAAI,SAAS,IAAK,QAAO;AACzB,MAAI,SAAS,IAAK,QAAO;AACzB,SAAO;AACT;AAUe,SAAR,kBAAmC,EAAE,SAAS,YAAY,OAAO,UAAU,QAAQ,GAA+C;AACvI,QAAM,CAAC,UAAU,WAAW,IAAI,MAAM,SAAS,KAAK;AACpD,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,KAAK;AAChD,QAAM,aAAa,MAAM,OAA6C,IAAI;AAC1E,QAAM,QAAQ,QAAQ;AACtB,QAAM,SAAS,KAAK,IAAI,IAAI,KAAK,QAAQ,CAAC;AAG1C,QAAM,UAAU,MAAM;AACpB,QAAI,CAAC,UAAU;AACb,gBAAU,KAAK;AACf;AAAA,IACF;AACA,UAAM,MAAM,sBAAsB,MAAM,UAAU,IAAI,CAAC;AACvD,WAAO,MAAM,qBAAqB,GAAG;AAAA,EACvC,GAAG,CAAC,QAAQ,CAAC;AAEb,QAAM,UAAU,MAAM,MAAM;AAC1B,QAAI,WAAW,QAAS,cAAa,WAAW,OAAO;AAAA,EACzD,GAAG,CAAC,CAAC;AAEL,QAAM,OAAO,MAAM;AACjB,QAAI,WAAW,SAAS;AACtB,mBAAa,WAAW,OAAO;AAC/B,iBAAW,UAAU;AAAA,IACvB;AACA,gBAAY,IAAI;AAAA,EAClB;AAEA,QAAM,gBAAgB,MAAM;AAC1B,QAAI,WAAW,QAAS,cAAa,WAAW,OAAO;AACvD,eAAW,UAAU,WAAW,MAAM,YAAY,KAAK,GAAG,GAAG;AAAA,EAC/D;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,cAAc;AAAA,MACd,cAAc;AAAA,MAEd;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO;AAAA,cACL,OAAO;AAAA,cACP,QAAQ;AAAA,cACR,iBAAiB,aAAa,KAAK;AAAA,cACnC,OAAO;AAAA,cACP,YAAY;AAAA,cACZ,WAAW,YACP,wDACA;AAAA,YACN;AAAA,YAEC;AAAA;AAAA,QACH;AAAA,QAEC,YACC,oBAAC,SAAI,WAAU,6BACZ,kBAAQ,IAAI,CAAC,QAAQ,MAAM;AAhGtC;AAiGY,gBAAM,QAAS,IAAI,KAAK,KAAK,IAAK,QAAQ,KAAK,KAAK;AACpD,gBAAM,IAAI,SAAS,KAAK,IAAI,KAAK,IAAI,SAAS;AAC9C,gBAAM,IAAI,SAAS,KAAK,IAAI,KAAK,IAAI,SAAS;AAC9C,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,WAAU;AAAA,cACV,OAAO;AAAA,gBACL,WAAW,mCAAmC,CAAC,OAAO,CAAC;AAAA,gBACvD,SAAS,SAAS,IAAI;AAAA,gBACtB,YAAY;AAAA,gBACZ,iBAAiB,GAAG,IAAI,EAAE;AAAA,cAC5B;AAAA,cACA,cAAc;AAAA,cAGd,eAAe,CAAC,MAAM;AACpB,kBAAE,gBAAgB;AAClB,qDAAW,OAAO;AAClB,mDAAU,OAAO;AAAA,cACnB;AAAA,cAEA;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,OAAO,EAAE,WAAW,kBAAkB;AAAA,kBAEtC,8BAAC,UAAO,WAAU,iBAChB;AAAA,oBAAC;AAAA;AAAA,sBACC,cAAa,YAAO,gBAAP,YAAsB;AAAA,sBACnC,MAAM,OAAO;AAAA,sBACb,WAAU;AAAA;AAAA,kBACZ,GACF;AAAA;AAAA,cACF;AAAA;AAAA,YA5BK,OAAO;AAAA,UA6Bd;AAAA,QAEJ,CAAC,GACH;AAAA;AAAA;AAAA,EAEJ;AAEJ;","names":[]}
@@ -0,0 +1,44 @@
1
+ import * as THREE from 'three';
2
+ /**
3
+ * Screen-space clustering for 3D (BIM) comment markers.
4
+ *
5
+ * The BIM viewer renders each comment as an independent CSS2DObject at a world
6
+ * position, so markers that are close together (or aligned along the camera
7
+ * axis) overlap on screen. This groups markers whose projected screen positions
8
+ * fall within `threshold` pixels of each other so the caller can draw a single
9
+ * numbered cluster bubble instead of a pile of overlapping avatars.
10
+ *
11
+ * Pure and side-effect free (no scene mutation) so it can be unit tested.
12
+ */
13
+ export interface HasWorldPosition {
14
+ id: number;
15
+ x?: number | null;
16
+ y?: number | null;
17
+ z?: number | null;
18
+ }
19
+ export interface ScreenCluster<T> {
20
+ /** Stable key derived from member ids (sorted) — used for registry diffing. */
21
+ key: string;
22
+ screenX: number;
23
+ screenY: number;
24
+ worldCenter: {
25
+ x: number;
26
+ y: number;
27
+ z: number;
28
+ };
29
+ members: T[];
30
+ }
31
+ export interface ClusterResult<T> {
32
+ clusters: ScreenCluster<T>[];
33
+ singles: T[];
34
+ }
35
+ export interface ProjectedPoint {
36
+ screenX: number;
37
+ screenY: number;
38
+ /** false when the point is behind the camera. */
39
+ onScreen: boolean;
40
+ }
41
+ /** Project a world point to pixel coordinates for the given camera/viewport. */
42
+ export declare function projectPoint(x: number, y: number, z: number, camera: THREE.Camera, width: number, height: number): ProjectedPoint;
43
+ export declare function clusterMarkersByScreenSpace<T extends HasWorldPosition>(items: T[], camera: THREE.Camera, width: number, height: number, threshold?: number): ClusterResult<T>;
44
+ //# sourceMappingURL=clusterMarkersByScreenSpace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clusterMarkersByScreenSpace.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAA;IACV,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjB,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,+EAA+E;IAC/E,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAChD,OAAO,EAAE,CAAC,EAAE,CAAA;CACb;AAED,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,EAAE,CAAA;IAC5B,OAAO,EAAE,CAAC,EAAE,CAAA;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;IACf,iDAAiD;IACjD,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED,gFAAgF;AAChF,wBAAgB,YAAY,CAC1B,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,cAAc,CAYhB;AAOD,wBAAgB,2BAA2B,CAAC,CAAC,SAAS,gBAAgB,EACpE,KAAK,EAAE,CAAC,EAAE,EACV,MAAM,EAAE,KAAK,CAAC,MAAM,EACpB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,SAAS,SAAK,GACb,aAAa,CAAC,CAAC,CAAC,CAiDlB"}
@@ -0,0 +1,77 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
4
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
+ var __spreadValues = (a, b) => {
7
+ for (var prop in b || (b = {}))
8
+ if (__hasOwnProp.call(b, prop))
9
+ __defNormalProp(a, prop, b[prop]);
10
+ if (__getOwnPropSymbols)
11
+ for (var prop of __getOwnPropSymbols(b)) {
12
+ if (__propIsEnum.call(b, prop))
13
+ __defNormalProp(a, prop, b[prop]);
14
+ }
15
+ return a;
16
+ };
17
+ import * as THREE from "three";
18
+ function projectPoint(x, y, z, camera, width, height) {
19
+ const pos = new THREE.Vector3(x, y, z);
20
+ const cameraSpace = pos.clone().applyMatrix4(camera.matrixWorldInverse);
21
+ const behind = cameraSpace.z >= 0;
22
+ const ndc = pos.project(camera);
23
+ return {
24
+ screenX: (ndc.x * 0.5 + 0.5) * width,
25
+ screenY: (-ndc.y * 0.5 + 0.5) * height,
26
+ onScreen: !behind
27
+ };
28
+ }
29
+ function average(values) {
30
+ if (values.length === 0) return 0;
31
+ return values.reduce((sum, v) => sum + v, 0) / values.length;
32
+ }
33
+ function clusterMarkersByScreenSpace(items, camera, width, height, threshold = 44) {
34
+ const projected = items.filter(
35
+ (it) => it.x != null && it.y != null && it.z != null
36
+ ).map((item) => __spreadValues({ item }, projectPoint(item.x, item.y, item.z, camera, width, height)));
37
+ const onScreen = projected.filter((p) => p.onScreen);
38
+ const clusters = [];
39
+ const singles = projected.filter((p) => !p.onScreen).map((p) => p.item);
40
+ const used = new Array(onScreen.length).fill(false);
41
+ for (let i = 0; i < onScreen.length; i++) {
42
+ if (used[i]) continue;
43
+ used[i] = true;
44
+ const group = [onScreen[i]];
45
+ for (let j = i + 1; j < onScreen.length; j++) {
46
+ if (used[j]) continue;
47
+ const dx = onScreen[i].screenX - onScreen[j].screenX;
48
+ const dy = onScreen[i].screenY - onScreen[j].screenY;
49
+ if (Math.hypot(dx, dy) <= threshold) {
50
+ used[j] = true;
51
+ group.push(onScreen[j]);
52
+ }
53
+ }
54
+ if (group.length === 1) {
55
+ singles.push(group[0].item);
56
+ continue;
57
+ }
58
+ const members = group.map((g) => g.item);
59
+ clusters.push({
60
+ key: members.map((m) => m.id).sort((a, b) => a - b).join("-"),
61
+ screenX: average(group.map((g) => g.screenX)),
62
+ screenY: average(group.map((g) => g.screenY)),
63
+ worldCenter: {
64
+ x: average(members.map((m) => m.x)),
65
+ y: average(members.map((m) => m.y)),
66
+ z: average(members.map((m) => m.z))
67
+ },
68
+ members
69
+ });
70
+ }
71
+ return { clusters, singles };
72
+ }
73
+ export {
74
+ clusterMarkersByScreenSpace,
75
+ projectPoint
76
+ };
77
+ //# sourceMappingURL=clusterMarkersByScreenSpace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (C) 2025 Collab Digital Twins\n\nimport * as THREE from 'three'\n\n/**\n * Screen-space clustering for 3D (BIM) comment markers.\n *\n * The BIM viewer renders each comment as an independent CSS2DObject at a world\n * position, so markers that are close together (or aligned along the camera\n * axis) overlap on screen. This groups markers whose projected screen positions\n * fall within `threshold` pixels of each other so the caller can draw a single\n * numbered cluster bubble instead of a pile of overlapping avatars.\n *\n * Pure and side-effect free (no scene mutation) so it can be unit tested.\n */\n\nexport interface HasWorldPosition {\n id: number\n x?: number | null\n y?: number | null\n z?: number | null\n}\n\nexport interface ScreenCluster<T> {\n /** Stable key derived from member ids (sorted) — used for registry diffing. */\n key: string\n screenX: number\n screenY: number\n worldCenter: { x: number; y: number; z: number }\n members: T[]\n}\n\nexport interface ClusterResult<T> {\n clusters: ScreenCluster<T>[]\n singles: T[]\n}\n\nexport interface ProjectedPoint {\n screenX: number\n screenY: number\n /** false when the point is behind the camera. */\n onScreen: boolean\n}\n\n/** Project a world point to pixel coordinates for the given camera/viewport. */\nexport function projectPoint(\n x: number,\n y: number,\n z: number,\n camera: THREE.Camera,\n width: number,\n height: number,\n): ProjectedPoint {\n const pos = new THREE.Vector3(x, y, z)\n // Camera-space z is positive when the point is behind the camera (it looks down -z).\n const cameraSpace = pos.clone().applyMatrix4(camera.matrixWorldInverse)\n const behind = cameraSpace.z >= 0\n\n const ndc = pos.project(camera)\n return {\n screenX: (ndc.x * 0.5 + 0.5) * width,\n screenY: (-ndc.y * 0.5 + 0.5) * height,\n onScreen: !behind,\n }\n}\n\nfunction average(values: number[]): number {\n if (values.length === 0) return 0\n return values.reduce((sum, v) => sum + v, 0) / values.length\n}\n\nexport function clusterMarkersByScreenSpace<T extends HasWorldPosition>(\n items: T[],\n camera: THREE.Camera,\n width: number,\n height: number,\n threshold = 44,\n): ClusterResult<T> {\n const projected = items\n .filter((it): it is T & { x: number; y: number; z: number } =>\n it.x != null && it.y != null && it.z != null,\n )\n .map((item) => ({ item, ...projectPoint(item.x, item.y, item.z, camera, width, height) }))\n\n const onScreen = projected.filter((p) => p.onScreen)\n const clusters: ScreenCluster<T>[] = []\n // Points behind the camera are never clustered; they fall through as singles.\n const singles: T[] = projected.filter((p) => !p.onScreen).map((p) => p.item)\n\n const used = new Array(onScreen.length).fill(false)\n\n for (let i = 0; i < onScreen.length; i++) {\n if (used[i]) continue\n used[i] = true\n const group = [onScreen[i]]\n\n for (let j = i + 1; j < onScreen.length; j++) {\n if (used[j]) continue\n const dx = onScreen[i].screenX - onScreen[j].screenX\n const dy = onScreen[i].screenY - onScreen[j].screenY\n if (Math.hypot(dx, dy) <= threshold) {\n used[j] = true\n group.push(onScreen[j])\n }\n }\n\n if (group.length === 1) {\n singles.push(group[0].item)\n continue\n }\n\n const members = group.map((g) => g.item)\n clusters.push({\n key: members.map((m) => m.id).sort((a, b) => a - b).join('-'),\n screenX: average(group.map((g) => g.screenX)),\n screenY: average(group.map((g) => g.screenY)),\n worldCenter: {\n x: average(members.map((m) => m.x as number)),\n y: average(members.map((m) => m.y as number)),\n z: average(members.map((m) => m.z as number)),\n },\n members,\n })\n }\n\n return { clusters, singles }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAGA,YAAY,WAAW;AA2ChB,SAAS,aACd,GACA,GACA,GACA,QACA,OACA,QACgB;AAChB,QAAM,MAAM,IAAI,MAAM,QAAQ,GAAG,GAAG,CAAC;AAErC,QAAM,cAAc,IAAI,MAAM,EAAE,aAAa,OAAO,kBAAkB;AACtE,QAAM,SAAS,YAAY,KAAK;AAEhC,QAAM,MAAM,IAAI,QAAQ,MAAM;AAC9B,SAAO;AAAA,IACL,UAAU,IAAI,IAAI,MAAM,OAAO;AAAA,IAC/B,UAAU,CAAC,IAAI,IAAI,MAAM,OAAO;AAAA,IAChC,UAAU,CAAC;AAAA,EACb;AACF;AAEA,SAAS,QAAQ,QAA0B;AACzC,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,SAAO,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,GAAG,CAAC,IAAI,OAAO;AACxD;AAEO,SAAS,4BACd,OACA,QACA,OACA,QACA,YAAY,IACM;AAClB,QAAM,YAAY,MACf;AAAA,IAAO,CAAC,OACP,GAAG,KAAK,QAAQ,GAAG,KAAK,QAAQ,GAAG,KAAK;AAAA,EAC1C,EACC,IAAI,CAAC,SAAU,iBAAE,QAAS,aAAa,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,OAAO,MAAM,EAAI;AAE3F,QAAM,WAAW,UAAU,OAAO,CAAC,MAAM,EAAE,QAAQ;AACnD,QAAM,WAA+B,CAAC;AAEtC,QAAM,UAAe,UAAU,OAAO,CAAC,MAAM,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AAE3E,QAAM,OAAO,IAAI,MAAM,SAAS,MAAM,EAAE,KAAK,KAAK;AAElD,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,QAAI,KAAK,CAAC,EAAG;AACb,SAAK,CAAC,IAAI;AACV,UAAM,QAAQ,CAAC,SAAS,CAAC,CAAC;AAE1B,aAAS,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AAC5C,UAAI,KAAK,CAAC,EAAG;AACb,YAAM,KAAK,SAAS,CAAC,EAAE,UAAU,SAAS,CAAC,EAAE;AAC7C,YAAM,KAAK,SAAS,CAAC,EAAE,UAAU,SAAS,CAAC,EAAE;AAC7C,UAAI,KAAK,MAAM,IAAI,EAAE,KAAK,WAAW;AACnC,aAAK,CAAC,IAAI;AACV,cAAM,KAAK,SAAS,CAAC,CAAC;AAAA,MACxB;AAAA,IACF;AAEA,QAAI,MAAM,WAAW,GAAG;AACtB,cAAQ,KAAK,MAAM,CAAC,EAAE,IAAI;AAC1B;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI;AACvC,aAAS,KAAK;AAAA,MACZ,KAAK,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC,EAAE,KAAK,GAAG;AAAA,MAC5D,SAAS,QAAQ,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;AAAA,MAC5C,SAAS,QAAQ,MAAM,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;AAAA,MAC5C,aAAa;AAAA,QACX,GAAG,QAAQ,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAW,CAAC;AAAA,QAC5C,GAAG,QAAQ,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAW,CAAC;AAAA,QAC5C,GAAG,QAAQ,QAAQ,IAAI,CAAC,MAAM,EAAE,CAAW,CAAC;AAAA,MAC9C;AAAA,MACA;AAAA,IACF,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,UAAU,QAAQ;AAC7B;","names":[]}
@@ -0,0 +1,24 @@
1
+ /** Minimal 3D vector shape so this helper stays free of a three.js import (easy to unit test). */
2
+ export interface Vec3 {
3
+ x: number;
4
+ y: number;
5
+ z: number;
6
+ }
7
+ /** Camera pose in the shape `camera-controls` `setLookAt` expects. */
8
+ export interface LookAt {
9
+ camX: number;
10
+ camY: number;
11
+ camZ: number;
12
+ tarX: number;
13
+ tarY: number;
14
+ tarZ: number;
15
+ }
16
+ /**
17
+ * Frame a comment without changing the current view direction: keep looking along
18
+ * the same axis the user already has, but re-target the comment and pull the camera
19
+ * to `distance` away from it. Used for double-click "zoom to comment".
20
+ *
21
+ * Pure and three.js-free so it can be unit tested with plain objects.
22
+ */
23
+ export declare function computeCommentLookAt(camPos: Vec3, target: Vec3, commentPoint: Vec3, distance?: number): LookAt;
24
+ //# sourceMappingURL=commentCameraUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commentCameraUtils.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.ts"],"names":[],"mappings":"AAGA,kGAAkG;AAClG,MAAM,WAAW,IAAI;IACnB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,sEAAsE;AACtE,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAID;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,IAAI,EACZ,MAAM,EAAE,IAAI,EACZ,YAAY,EAAE,IAAI,EAClB,QAAQ,SAAI,GACX,MAAM,CAyBR"}
@@ -0,0 +1,27 @@
1
+ const DEFAULT_DIRECTION = { x: 1, y: 1, z: 1 };
2
+ function computeCommentLookAt(camPos, target, commentPoint, distance = 8) {
3
+ let dx = camPos.x - target.x;
4
+ let dy = camPos.y - target.y;
5
+ let dz = camPos.z - target.z;
6
+ let length = Math.hypot(dx, dy, dz);
7
+ if (length < 1e-6) {
8
+ dx = DEFAULT_DIRECTION.x;
9
+ dy = DEFAULT_DIRECTION.y;
10
+ dz = DEFAULT_DIRECTION.z;
11
+ length = Math.hypot(dx, dy, dz);
12
+ }
13
+ const safeDistance = distance > 0 ? distance : 8;
14
+ const scale = safeDistance / length;
15
+ return {
16
+ camX: commentPoint.x + dx * scale,
17
+ camY: commentPoint.y + dy * scale,
18
+ camZ: commentPoint.z + dz * scale,
19
+ tarX: commentPoint.x,
20
+ tarY: commentPoint.y,
21
+ tarZ: commentPoint.z
22
+ };
23
+ }
24
+ export {
25
+ computeCommentLookAt
26
+ };
27
+ //# sourceMappingURL=commentCameraUtils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (C) 2025 Collab Digital Twins\n\n/** Minimal 3D vector shape so this helper stays free of a three.js import (easy to unit test). */\nexport interface Vec3 {\n x: number\n y: number\n z: number\n}\n\n/** Camera pose in the shape `camera-controls` `setLookAt` expects. */\nexport interface LookAt {\n camX: number\n camY: number\n camZ: number\n tarX: number\n tarY: number\n tarZ: number\n}\n\nconst DEFAULT_DIRECTION: Vec3 = { x: 1, y: 1, z: 1 }\n\n/**\n * Frame a comment without changing the current view direction: keep looking along\n * the same axis the user already has, but re-target the comment and pull the camera\n * to `distance` away from it. Used for double-click \"zoom to comment\".\n *\n * Pure and three.js-free so it can be unit tested with plain objects.\n */\nexport function computeCommentLookAt(\n camPos: Vec3,\n target: Vec3,\n commentPoint: Vec3,\n distance = 8,\n): LookAt {\n let dx = camPos.x - target.x\n let dy = camPos.y - target.y\n let dz = camPos.z - target.z\n let length = Math.hypot(dx, dy, dz)\n\n // Degenerate pose (camera sitting on its target): fall back to a sensible angle.\n if (length < 1e-6) {\n dx = DEFAULT_DIRECTION.x\n dy = DEFAULT_DIRECTION.y\n dz = DEFAULT_DIRECTION.z\n length = Math.hypot(dx, dy, dz)\n }\n\n const safeDistance = distance > 0 ? distance : 8\n const scale = safeDistance / length\n\n return {\n camX: commentPoint.x + dx * scale,\n camY: commentPoint.y + dy * scale,\n camZ: commentPoint.z + dz * scale,\n tarX: commentPoint.x,\n tarY: commentPoint.y,\n tarZ: commentPoint.z,\n }\n}\n"],"mappings":"AAoBA,MAAM,oBAA0B,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAS5C,SAAS,qBACd,QACA,QACA,cACA,WAAW,GACH;AACR,MAAI,KAAK,OAAO,IAAI,OAAO;AAC3B,MAAI,KAAK,OAAO,IAAI,OAAO;AAC3B,MAAI,KAAK,OAAO,IAAI,OAAO;AAC3B,MAAI,SAAS,KAAK,MAAM,IAAI,IAAI,EAAE;AAGlC,MAAI,SAAS,MAAM;AACjB,SAAK,kBAAkB;AACvB,SAAK,kBAAkB;AACvB,SAAK,kBAAkB;AACvB,aAAS,KAAK,MAAM,IAAI,IAAI,EAAE;AAAA,EAChC;AAEA,QAAM,eAAe,WAAW,IAAI,WAAW;AAC/C,QAAM,QAAQ,eAAe;AAE7B,SAAO;AAAA,IACL,MAAM,aAAa,IAAI,KAAK;AAAA,IAC5B,MAAM,aAAa,IAAI,KAAK;AAAA,IAC5B,MAAM,aAAa,IAAI,KAAK;AAAA,IAC5B,MAAM,aAAa;AAAA,IACnB,MAAM,aAAa;AAAA,IACnB,MAAM,aAAa;AAAA,EACrB;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"renderCSS2DMarkers.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAA;AAErE,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,qCAAqC,CAAA;AAE7E,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;IACnC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAA;IAClB,WAAW,EAAE,WAAW,CAAA;CACzB;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,WAAW,EAAE,WAAW,GAAG,UAAU,CAAA;IACrC,+FAA+F;IAC/F,QAAQ,EAAE,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAA;IACxD,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;IACnC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC7C,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,iBAAiB,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;CACpC;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAClH,KAAK,EAAE,GAAG,EACV,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,IAAI,CA2FN"}
1
+ {"version":3,"file":"renderCSS2DMarkers.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAC9B,OAAO,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAA;AAErE,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,qCAAqC,CAAA;AAE7E,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAA;IACnC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAA;IAClB,WAAW,EAAE,WAAW,CAAA;CACzB;AAED,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,EAAE,CAAA;IACV,WAAW,EAAE,WAAW,GAAG,UAAU,CAAA;IACrC,+FAA+F;IAC/F,QAAQ,EAAE,KAAK,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,CAAA;IACxD,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAA;IACnC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC7C,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,iBAAiB,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;CACpC;AAED,wBAAgB,kBAAkB,CAAC,CAAC,SAAS;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAClH,KAAK,EAAE,GAAG,EACV,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,GACtB,IAAI,CA6FN"}
@@ -56,13 +56,13 @@ function renderCSS2DMarkers(world, config) {
56
56
  for (const item of validItems) {
57
57
  const itemId = String(item.id);
58
58
  const existing = reg.get(itemId);
59
- const resolvedProps = __spreadProps(__spreadValues({}, propsMapper(item)), {
59
+ const resolvedProps = __spreadValues({
60
60
  worldCamera: world.camera.three,
61
61
  targetPoint: new THREE.Vector3(item.x, item.y, item.z),
62
62
  onClose: () => {
63
63
  },
64
64
  onRemove: () => onRemove(item.id)
65
- });
65
+ }, propsMapper(item));
66
66
  if (existing) {
67
67
  const markerElement2 = React.createElement(component, __spreadProps(__spreadValues({}, resolvedProps), { sphere: existing.sphere }));
68
68
  existing.root.render(
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as React from 'react'\r\nimport { createRoot } from 'react-dom/client'\r\nimport * as THREE from 'three'\r\nimport { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js'\r\n\r\nimport { HooksCtx, type HooksBag } from '../../../../../../../hooks/provider'\r\n\r\nexport interface MarkerRef {\r\n root: ReturnType<typeof createRoot>\r\n sphere: THREE.Mesh\r\n css2dObject: CSS2DObject\r\n}\r\n\r\nexport interface MarkerConfig<T> {\r\n items: T[]\r\n markerIdKey: 'commentId' | 'sensorId'\r\n /** Persistent registry keyed by item id — create with React.useRef(new Map()) in the caller */\r\n registry: React.MutableRefObject<Map<string, MarkerRef>>\r\n component: React.ComponentType<any>\r\n propsMapper: (item: T) => Record<string, any>\r\n sphereColor: string\r\n isVisible: boolean\r\n onRemove: (id: number) => void\r\n hooksContextValue?: HooksBag | null\r\n}\r\n\r\nexport function renderCSS2DMarkers<T extends { id: number; x?: number | null; y?: number | null; z?: number | null }>(\r\n world: any,\r\n config: MarkerConfig<T>\r\n): void {\r\n if (!world) return\r\n\r\n const { items, markerIdKey, component, propsMapper, sphereColor, isVisible, onRemove, registry, hooksContextValue } = config\r\n const reg = registry.current\r\n\r\n // Filter items to only include those with valid 3D coordinates\r\n const validItems = items.filter((item): item is T & { x: number; y: number; z: number } =>\r\n item.x != null && item.y != null && item.z != null\r\n )\r\n\r\n // Hide all markers if not visible — remove all from scene and clear registry\r\n if (!isVisible) {\r\n reg.forEach(({ sphere, css2dObject }) => {\r\n world.scene.three.remove(sphere)\r\n world.scene.three.remove(css2dObject)\r\n sphere.geometry?.dispose()\r\n if (sphere.material instanceof THREE.Material) sphere.material.dispose()\r\n })\r\n reg.clear()\r\n return\r\n }\r\n\r\n const eligibleIds = new Set(validItems.map(item => String(item.id)))\r\n\r\n // Remove markers for deleted/invisible items\r\n const toDelete: string[] = []\r\n reg.forEach(({ sphere, css2dObject }, id) => {\r\n if (!eligibleIds.has(id)) {\r\n world.scene.three.remove(sphere)\r\n world.scene.three.remove(css2dObject)\r\n sphere.geometry?.dispose()\r\n if (sphere.material instanceof THREE.Material) sphere.material.dispose()\r\n toDelete.push(id)\r\n }\r\n })\r\n toDelete.forEach(id => reg.delete(id))\r\n\r\n // Update or create markers\r\n for (const item of validItems) {\r\n const itemId = String(item.id)\r\n const existing = reg.get(itemId)\r\n\r\n const resolvedProps = {\r\n ...propsMapper(item),\r\n worldCamera: world.camera.three,\r\n targetPoint: new THREE.Vector3(item.x, item.y, item.z),\r\n onClose: () => {},\r\n onRemove: () => onRemove(item.id),\r\n }\r\n\r\n if (existing) {\r\n // Re-render with latest props (e.g. highlight change)\r\n const markerElement = React.createElement(component, { ...resolvedProps, sphere: existing.sphere })\r\n existing.root.render(\r\n hooksContextValue\r\n ? React.createElement(HooksCtx.Provider, { value: hooksContextValue }, markerElement)\r\n : markerElement\r\n )\r\n continue\r\n }\r\n\r\n // Create new marker\r\n const hitPoint = new THREE.Vector3(item.x, item.y, item.z)\r\n\r\n const sphereGeometry = new THREE.SphereGeometry(0.04, 10, 10)\r\n const sphereMaterial = new THREE.MeshBasicMaterial({ color: sphereColor })\r\n const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)\r\n sphere.position.copy(hitPoint)\r\n sphere.userData[markerIdKey] = itemId\r\n world.scene.three.add(sphere)\r\n\r\n const labelDiv = document.createElement('div')\r\n labelDiv.style.pointerEvents = 'auto'\r\n const root = createRoot(labelDiv)\r\n const markerElement = React.createElement(component, { ...resolvedProps, sphere })\r\n root.render(\r\n hooksContextValue\r\n ? React.createElement(HooksCtx.Provider, { value: hooksContextValue }, markerElement)\r\n : markerElement\r\n )\r\n\r\n const css2dObject = new CSS2DObject(labelDiv)\r\n css2dObject.position.copy(hitPoint)\r\n css2dObject.position.y += 0.2\r\n css2dObject.userData[markerIdKey] = itemId\r\n css2dObject.element.style.pointerEvents = 'auto'\r\n world.scene.three.add(css2dObject)\r\n\r\n reg.set(itemId, { root, sphere, css2dObject })\r\n }\r\n}\r\n"],"mappings":"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","names":["markerElement"]}
1
+ {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as React from 'react'\r\nimport { createRoot } from 'react-dom/client'\r\nimport * as THREE from 'three'\r\nimport { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js'\r\n\r\nimport { HooksCtx, type HooksBag } from '../../../../../../../hooks/provider'\r\n\r\nexport interface MarkerRef {\r\n root: ReturnType<typeof createRoot>\r\n sphere: THREE.Mesh\r\n css2dObject: CSS2DObject\r\n}\r\n\r\nexport interface MarkerConfig<T> {\r\n items: T[]\r\n markerIdKey: 'commentId' | 'sensorId'\r\n /** Persistent registry keyed by item id — create with React.useRef(new Map()) in the caller */\r\n registry: React.MutableRefObject<Map<string, MarkerRef>>\r\n component: React.ComponentType<any>\r\n propsMapper: (item: T) => Record<string, any>\r\n sphereColor: string\r\n isVisible: boolean\r\n onRemove: (id: number) => void\r\n hooksContextValue?: HooksBag | null\r\n}\r\n\r\nexport function renderCSS2DMarkers<T extends { id: number; x?: number | null; y?: number | null; z?: number | null }>(\r\n world: any,\r\n config: MarkerConfig<T>\r\n): void {\r\n if (!world) return\r\n\r\n const { items, markerIdKey, component, propsMapper, sphereColor, isVisible, onRemove, registry, hooksContextValue } = config\r\n const reg = registry.current\r\n\r\n // Filter items to only include those with valid 3D coordinates\r\n const validItems = items.filter((item): item is T & { x: number; y: number; z: number } =>\r\n item.x != null && item.y != null && item.z != null\r\n )\r\n\r\n // Hide all markers if not visible — remove all from scene and clear registry\r\n if (!isVisible) {\r\n reg.forEach(({ sphere, css2dObject }) => {\r\n world.scene.three.remove(sphere)\r\n world.scene.three.remove(css2dObject)\r\n sphere.geometry?.dispose()\r\n if (sphere.material instanceof THREE.Material) sphere.material.dispose()\r\n })\r\n reg.clear()\r\n return\r\n }\r\n\r\n const eligibleIds = new Set(validItems.map(item => String(item.id)))\r\n\r\n // Remove markers for deleted/invisible items\r\n const toDelete: string[] = []\r\n reg.forEach(({ sphere, css2dObject }, id) => {\r\n if (!eligibleIds.has(id)) {\r\n world.scene.three.remove(sphere)\r\n world.scene.three.remove(css2dObject)\r\n sphere.geometry?.dispose()\r\n if (sphere.material instanceof THREE.Material) sphere.material.dispose()\r\n toDelete.push(id)\r\n }\r\n })\r\n toDelete.forEach(id => reg.delete(id))\r\n\r\n // Update or create markers\r\n for (const item of validItems) {\r\n const itemId = String(item.id)\r\n const existing = reg.get(itemId)\r\n\r\n const resolvedProps = {\r\n worldCamera: world.camera.three,\r\n targetPoint: new THREE.Vector3(item.x, item.y, item.z),\r\n onClose: () => {},\r\n onRemove: () => onRemove(item.id),\r\n // propsMapper wins: it can supply richer, per-item handlers (e.g. author-gated\r\n // delete, select/focus/edit/reply) that override these generic defaults.\r\n ...propsMapper(item),\r\n }\r\n\r\n if (existing) {\r\n // Re-render with latest props (e.g. highlight change)\r\n const markerElement = React.createElement(component, { ...resolvedProps, sphere: existing.sphere })\r\n existing.root.render(\r\n hooksContextValue\r\n ? React.createElement(HooksCtx.Provider, { value: hooksContextValue }, markerElement)\r\n : markerElement\r\n )\r\n continue\r\n }\r\n\r\n // Create new marker\r\n const hitPoint = new THREE.Vector3(item.x, item.y, item.z)\r\n\r\n const sphereGeometry = new THREE.SphereGeometry(0.04, 10, 10)\r\n const sphereMaterial = new THREE.MeshBasicMaterial({ color: sphereColor })\r\n const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial)\r\n sphere.position.copy(hitPoint)\r\n sphere.userData[markerIdKey] = itemId\r\n world.scene.three.add(sphere)\r\n\r\n const labelDiv = document.createElement('div')\r\n labelDiv.style.pointerEvents = 'auto'\r\n const root = createRoot(labelDiv)\r\n const markerElement = React.createElement(component, { ...resolvedProps, sphere })\r\n root.render(\r\n hooksContextValue\r\n ? React.createElement(HooksCtx.Provider, { value: hooksContextValue }, markerElement)\r\n : markerElement\r\n )\r\n\r\n const css2dObject = new CSS2DObject(labelDiv)\r\n css2dObject.position.copy(hitPoint)\r\n css2dObject.position.y += 0.2\r\n css2dObject.userData[markerIdKey] = itemId\r\n css2dObject.element.style.pointerEvents = 'auto'\r\n world.scene.three.add(css2dObject)\r\n\r\n reg.set(itemId, { root, sphere, css2dObject })\r\n }\r\n}\r\n"],"mappings":"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","names":["markerElement"]}
@@ -1 +1 @@
1
- {"version":3,"file":"useCommentMarkers.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.ts"],"names":[],"mappings":"AAgBA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM;kCA6CP;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;+BAMtC,MAAM;;EAoD3D"}
1
+ {"version":3,"file":"useCommentMarkers.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.ts"],"names":[],"mappings":"AAkNA,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM;kCAsEP;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;+BAMtC,MAAM;;EAgF3D"}