@collabdt/core 0.3.2 → 0.3.3

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 (145) hide show
  1. package/dist/core/components/ui/Comments/CollapsibleCommentItem.js.map +1 -1
  2. package/dist/core/components/ui/Comments/Comment.js.map +1 -1
  3. package/dist/core/components/ui/Comments/CommentActionButtons.d.ts +5 -1
  4. package/dist/core/components/ui/Comments/CommentActionButtons.d.ts.map +1 -1
  5. package/dist/core/components/ui/Comments/CommentActionButtons.js +37 -9
  6. package/dist/core/components/ui/Comments/CommentActionButtons.js.map +1 -1
  7. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts +1 -2
  8. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts.map +1 -1
  9. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js +66 -94
  10. package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js.map +1 -1
  11. package/dist/core/components/ui/Sensors/Sensor.d.ts +16 -3
  12. package/dist/core/components/ui/Sensors/Sensor.d.ts.map +1 -1
  13. package/dist/core/components/ui/Sensors/Sensor.js +76 -37
  14. package/dist/core/components/ui/Sensors/Sensor.js.map +1 -1
  15. package/dist/core/components/ui/Sensors/SensorChart.d.ts +12 -2
  16. package/dist/core/components/ui/Sensors/SensorChart.d.ts.map +1 -1
  17. package/dist/core/components/ui/Sensors/SensorChart.js +49 -12
  18. package/dist/core/components/ui/Sensors/SensorChart.js.map +1 -1
  19. package/dist/core/components/ui/Sensors/SensorDetailDialog.d.ts +9 -0
  20. package/dist/core/components/ui/Sensors/SensorDetailDialog.d.ts.map +1 -0
  21. package/dist/core/components/ui/Sensors/SensorDetailDialog.js +128 -0
  22. package/dist/core/components/ui/Sensors/SensorDetailDialog.js.map +1 -0
  23. package/dist/core/components/ui/Sensors/SensorInput.d.ts +6 -1
  24. package/dist/core/components/ui/Sensors/SensorInput.d.ts.map +1 -1
  25. package/dist/core/components/ui/Sensors/SensorInput.js +94 -8
  26. package/dist/core/components/ui/Sensors/SensorInput.js.map +1 -1
  27. package/dist/core/components/ui/Sensors/SensorTagsSection.d.ts +3 -1
  28. package/dist/core/components/ui/Sensors/SensorTagsSection.d.ts.map +1 -1
  29. package/dist/core/components/ui/Sensors/SensorTagsSection.js +4 -1
  30. package/dist/core/components/ui/Sensors/SensorTagsSection.js.map +1 -1
  31. package/dist/core/components/ui/Sensors/SensorsSection.d.ts +1 -3
  32. package/dist/core/components/ui/Sensors/SensorsSection.d.ts.map +1 -1
  33. package/dist/core/components/ui/Sensors/SensorsSection.js +33 -8
  34. package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
  35. package/dist/core/components/ui/Sensors/TimeZoneSelect.d.ts +7 -0
  36. package/dist/core/components/ui/Sensors/TimeZoneSelect.d.ts.map +1 -0
  37. package/dist/core/components/ui/Sensors/TimeZoneSelect.js +56 -0
  38. package/dist/core/components/ui/Sensors/TimeZoneSelect.js.map +1 -0
  39. package/dist/core/components/ui/Sensors/sensorData.d.ts +42 -0
  40. package/dist/core/components/ui/Sensors/sensorData.d.ts.map +1 -0
  41. package/dist/core/components/ui/Sensors/sensorData.js +118 -0
  42. package/dist/core/components/ui/Sensors/sensorData.js.map +1 -0
  43. package/dist/core/components/ui/Sensors/sensorRange.d.ts +24 -0
  44. package/dist/core/components/ui/Sensors/sensorRange.d.ts.map +1 -0
  45. package/dist/core/components/ui/Sensors/sensorRange.js +34 -0
  46. package/dist/core/components/ui/Sensors/sensorRange.js.map +1 -0
  47. package/dist/core/components/ui/Sensors/useSensorSeries.d.ts +15 -0
  48. package/dist/core/components/ui/Sensors/useSensorSeries.d.ts.map +1 -0
  49. package/dist/core/components/ui/Sensors/useSensorSeries.js +63 -0
  50. package/dist/core/components/ui/Sensors/useSensorSeries.js.map +1 -0
  51. package/dist/core/components/viewers/Viewer.js +1 -1
  52. package/dist/core/components/viewers/Viewer.js.map +1 -1
  53. package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.d.ts +1 -3
  54. package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.d.ts.map +1 -1
  55. package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.js +2 -2
  56. package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.js.map +1 -1
  57. package/dist/core/components/viewers/bim/src/BimSidebar/src/index.js +1 -1
  58. package/dist/core/components/viewers/bim/src/BimSidebar/src/index.js.map +1 -1
  59. package/dist/core/components/viewers/bim/src/tools/AddToBim/index.d.ts +1 -2
  60. package/dist/core/components/viewers/bim/src/tools/AddToBim/index.d.ts.map +1 -1
  61. package/dist/core/components/viewers/bim/src/tools/AddToBim/index.js +25 -2
  62. package/dist/core/components/viewers/bim/src/tools/AddToBim/index.js.map +1 -1
  63. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js.map +1 -1
  64. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts +1 -22
  65. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts.map +1 -1
  66. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js +2 -105
  67. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js.map +1 -1
  68. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.d.ts +8 -1
  69. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.d.ts.map +1 -1
  70. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.js +15 -3
  71. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.js.map +1 -1
  72. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js.map +1 -1
  73. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts +2 -23
  74. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts.map +1 -1
  75. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js +2 -24
  76. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js.map +1 -1
  77. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.d.ts +33 -0
  78. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.d.ts.map +1 -0
  79. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.js +90 -0
  80. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.js.map +1 -0
  81. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts +7 -1
  82. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts.map +1 -1
  83. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js +26 -230
  84. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js.map +1 -1
  85. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts +7 -1
  86. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts.map +1 -1
  87. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js +39 -11
  88. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js.map +1 -1
  89. package/dist/core/components/viewers/map/MapViewer.d.ts +1 -2
  90. package/dist/core/components/viewers/map/MapViewer.d.ts.map +1 -1
  91. package/dist/core/components/viewers/map/MapViewer.js +2 -2
  92. package/dist/core/components/viewers/map/MapViewer.js.map +1 -1
  93. package/dist/core/components/viewers/map/src/MapLayers/index.d.ts +1 -2
  94. package/dist/core/components/viewers/map/src/MapLayers/index.d.ts.map +1 -1
  95. package/dist/core/components/viewers/map/src/MapLayers/index.js +2 -2
  96. package/dist/core/components/viewers/map/src/MapLayers/index.js.map +1 -1
  97. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts +1 -3
  98. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts.map +1 -1
  99. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js +74 -39
  100. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
  101. package/dist/core/components/viewers/map/src/MapSidebar/index.js +1 -1
  102. package/dist/core/components/viewers/map/src/MapSidebar/index.js.map +1 -1
  103. package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.d.ts +1 -3
  104. package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.d.ts.map +1 -1
  105. package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.js +2 -2
  106. package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.js.map +1 -1
  107. package/dist/core/components/viewers/shared/markers/BimMarkerCluster.d.ts +19 -0
  108. package/dist/core/components/viewers/shared/markers/BimMarkerCluster.d.ts.map +1 -0
  109. package/dist/core/components/viewers/shared/markers/BimMarkerCluster.js +108 -0
  110. package/dist/core/components/viewers/shared/markers/BimMarkerCluster.js.map +1 -0
  111. package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.d.ts +24 -0
  112. package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.d.ts.map +1 -0
  113. package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.js +27 -0
  114. package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.js.map +1 -0
  115. package/dist/core/components/viewers/shared/markers/types.d.ts +66 -0
  116. package/dist/core/components/viewers/shared/markers/types.d.ts.map +1 -0
  117. package/dist/core/components/viewers/shared/markers/types.js +1 -0
  118. package/dist/core/components/viewers/shared/markers/types.js.map +1 -0
  119. package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.d.ts +22 -0
  120. package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.d.ts.map +1 -0
  121. package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.js +233 -0
  122. package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.js.map +1 -0
  123. package/dist/core/hooks/ui/useResizableSidebarWidth.js.map +1 -1
  124. package/dist/core/i18n/messages/en.json +5 -1
  125. package/dist/core/i18n/messages/es.json +5 -1
  126. package/dist/core/i18n/messages/fr.json +5 -1
  127. package/dist/core/store/AppConfig/context.d.ts.map +1 -1
  128. package/dist/core/store/AppConfig/context.js +4 -1
  129. package/dist/core/store/AppConfig/context.js.map +1 -1
  130. package/dist/core/store/AppConfig/reducer.d.ts +10 -0
  131. package/dist/core/store/AppConfig/reducer.d.ts.map +1 -1
  132. package/dist/core/store/AppConfig/reducer.js +10 -0
  133. package/dist/core/store/AppConfig/reducer.js.map +1 -1
  134. package/dist/core/store/Menus/context.d.ts.map +1 -1
  135. package/dist/core/store/Menus/context.js +5 -2
  136. package/dist/core/store/Menus/context.js.map +1 -1
  137. package/dist/core/store/Menus/reducer.d.ts +45 -1
  138. package/dist/core/store/Menus/reducer.d.ts.map +1 -1
  139. package/dist/core/store/Menus/reducer.js +21 -3
  140. package/dist/core/store/Menus/reducer.js.map +1 -1
  141. package/dist/core/utils/timeUtils.d.ts +24 -1
  142. package/dist/core/utils/timeUtils.d.ts.map +1 -1
  143. package/dist/core/utils/timeUtils.js +52 -3
  144. package/dist/core/utils/timeUtils.js.map +1 -1
  145. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.tsx"],"sourcesContent":["'use client'\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as LR from 'lucide-react'\r\nimport { useSession } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { Source, Layer, Popup, Marker } from 'react-map-gl/maplibre'\r\nimport { toast } from 'sonner'\r\n\r\nimport { useSensor, useSensors } from '../../../../../../../hooks/sensors/sensors'\r\nimport { useSensorTypes } from '../../../../../../../hooks/sensorTypes/sensorTypes'\r\nimport { useUsers } from '../../../../../../../hooks/users/users'\r\nimport { MapContext, MenusContext } from '../../../../../../../store'\r\nimport { ViewerNames, type Sensor as ISensor} from '../../../../../../../types/dbTypes'\r\nimport Sensor from '../../../../../../ui/Sensors/Sensor'\r\nimport { UNTAGGED_TAG } from '../../../../../../ui/Sensors/SensorsSection'\r\nimport { extractCoordinatesFromFeature } from '../../../../utils/extractCoordinates'\r\nimport { MapLayerClickPriority } from '../../../../utils/MapEventManager/MapClickManager'\r\nimport { createClusterLayer, createClusterCountLayer, createUnclusteredPointLayer } from '../mapLayersUtils'\r\n\r\nimport type { SensorType} from '../../../../../../../types/dbTypes';\r\nimport type { ClickCallback } from '../../../../utils/MapEventManager/MapClickManager';\r\nimport type { MapGeoJSONFeature, MapLayerMouseEvent } from 'maplibre-gl'\r\n\r\nconst SensorIconMarker = ({ feature, isHighlighted, onMouseEnter, onMouseLeave, sensorTypes }: { feature: MapGeoJSONFeature; isHighlighted?: boolean; onMouseEnter?: () => void; onMouseLeave?: () => void; sensorTypes: SensorType[] }) => {\r\n\r\n const coords = extractCoordinatesFromFeature(feature)\r\n if (!coords) return null\r\n\r\n const sensorType = sensorTypes.find(t => t.id === feature.properties?.typeId)\r\n const icon = sensorType?.icon || 'Radio'\r\n const SensorIcon = LR[icon] || LR.Radio\r\n\r\n return (\r\n <Marker key={String(feature.properties?.id ?? `${coords.lng},${coords.lat}`)} longitude={coords.lng} latitude={coords.lat} anchor=\"center\">\r\n <div\r\n style={{\r\n width: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\r\n border: isHighlighted ? '3px solid #73cee2' : '2px solid white',\r\n backgroundColor: 'black',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n boxShadow: isHighlighted ? '0 0 12px rgba(115, 206, 226, 0.5)' : 'none',\r\n transition: 'all 0.2s ease-in-out',\r\n transform: isHighlighted ? 'scale(1.2)' : 'scale(1)',\r\n }}\r\n onMouseEnter={onMouseEnter}\r\n onMouseLeave={onMouseLeave}\r\n >\r\n <SensorIcon style={{ width: '20px', height: '20px', color: 'hsl(var(--primary-foreground))' }} />\r\n </div>\r\n </Marker>\r\n )\r\n}\r\n\r\nexport const SensorLayers = ({ minioBaseUrl }: { minioBaseUrl?: string }) => {\r\n const [hoveredSensorId, setHoveredSensorId] = React.useState<number | null>(null)\r\n const clusterLayer = createClusterLayer('sensors')\r\n const clusterCountLayer = createClusterCountLayer('sensors')\r\n const unclusteredPointLayer = createUnclusteredPointLayer('sensors')\r\n\r\n // global map state\r\n\r\n const t = useTranslations('SensorLayers')\r\n const tSensors = useTranslations('SensorsSection')\r\n\r\n const { state: mapState } = React.useContext(MapContext)\r\n const { map, mapClickManager } = mapState.map\r\n const { sensors } = useSensors()\r\n const { users } = useUsers()\r\n const user = useSession().data?.user\r\n\r\n const [popupInfo, setPopUpInfo] = React.useState<Partial<ISensor & { sensorType: SensorType }> | null>(null)\r\n const { deleteSensor, updateSensor } = useSensor(popupInfo?.id ?? null)\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { visibleSensorTypes, visibleSensorTags, currentSensorId } = menusState.menus\r\n const typesVisible = visibleSensorTypes?.[ViewerNames.map] || []\r\n const tagsVisible = visibleSensorTags?.[ViewerNames.map] || []\r\n\r\n const {sensorTypes} = useSensorTypes()\r\n\r\n const eligibleSensors: Array<ISensor & { authorName: string } & { sensorType: SensorType }> = sensors\r\n .filter((sensor) => sensor.viewer === ViewerNames.map)\r\n .filter((sensor) => {\r\n const matchesType = typesVisible.includes(sensor.typeId)\r\n const hasNoTags = !sensor.tags?.length\r\n const matchesTag = hasNoTags\r\n ? tagsVisible.includes(UNTAGGED_TAG)\r\n : sensor.tags?.some(tag => tagsVisible.includes(tag)) ?? false\r\n return matchesType || matchesTag\r\n })\r\n .map((sensor) => {\r\n const user = users.find(u => u.id === sensor.authorId)\r\n const sensorType = sensorTypes.find(t => t.id === sensor.typeId)\r\n return {\r\n ...sensor,\r\n authorName: user?.name ?? 'Unknown User',\r\n sensorType,\r\n }\r\n })\r\n\r\n // Close popup if the sensor was deleted\r\n React.useEffect(() => {\r\n if (popupInfo && !sensors.find((s) => s.id === popupInfo.id)) {\r\n setPopUpInfo(null)\r\n }\r\n }, [sensors, popupInfo])\r\n\r\n const geojsonSensorData = React.useMemo(() => {\r\n const convertDataToGeojson = (sensorData: typeof eligibleSensors): GeoJSON.FeatureCollection<GeoJSON.Point, { [key: string]: any }> => {\r\n const sensorFeatures: GeoJSON.Feature<GeoJSON.Point, { [key: string]: any }>[] = sensorData\r\n .map((sensor) => {\r\n const { longitude, latitude, id, name, typeId, sensorType, data, dataFormat, updateFrequency, createdAt, authorId, organizationId, visible, url, authorName } = sensor\r\n\r\n return {\r\n type: 'Feature',\r\n geometry: {\r\n type: 'Point',\r\n coordinates: [longitude, latitude],\r\n },\r\n properties: {\r\n id: Number(id),\r\n organizationId,\r\n visible,\r\n longitude,\r\n latitude,\r\n authorId: Number(authorId),\r\n authorName,\r\n name,\r\n typeId: Number(typeId),\r\n typeIcon: sensorType?.icon,\r\n data,\r\n dataFormat,\r\n updateFrequency,\r\n url,\r\n createdAt,\r\n viewer: ViewerNames.map,\r\n },\r\n }\r\n })\r\n\r\n\r\n\r\n const sensorFC: GeoJSON.FeatureCollection<GeoJSON.Point, { [key: string]: any }> = {\r\n type: 'FeatureCollection',\r\n features: sensorFeatures,\r\n }\r\n return sensorFC\r\n }\r\n return convertDataToGeojson(eligibleSensors)\r\n }, [eligibleSensors])\r\n\r\n // event listeners for sensor unclustered points\r\n React.useEffect(() => {\r\n if (!map) return\r\n const showSensorPopUp: ClickCallback = (e: MapLayerMouseEvent, features: MapGeoJSONFeature[]) => {\r\n const feature = features?.[0]\r\n\r\n if (!feature || feature.properties.point_count) return\r\n\r\n if (feature.geometry.type != 'Point') return\r\n const [longitude, latitude] = feature.geometry.coordinates\r\n const { id, authorId, name, typeId, data, dataFormat, updateFrequency, createdAt, url } = feature.properties\r\n setPopUpInfo({\r\n id: Number(id),\r\n authorId: Number(authorId),\r\n organizationId: feature.properties?.organizationId,\r\n visible: feature.properties?.visible,\r\n longitude,\r\n latitude,\r\n name,\r\n typeId: Number(typeId),\r\n data,\r\n dataFormat,\r\n updateFrequency,\r\n url,\r\n createdAt,\r\n viewer: ViewerNames.map,\r\n })\r\n }\r\n\r\n const mouseEnterChangeCursor = () => {\r\n map.getCanvas().style.cursor = 'pointer'\r\n }\r\n const mouseLeaveChangeCursor = () => {\r\n map.getCanvas().style.cursor = ''\r\n }\r\n\r\n // event listener for clicking on single point to show sensor, hover to change cursor\r\n mapClickManager.register('sensors-unclustered-points', MapLayerClickPriority.CommentLayersClickPriority, showSensorPopUp)\r\n\r\n map.on('mouseenter', 'sensors-unclustered-points', mouseEnterChangeCursor)\r\n map.on('mouseleave', 'sensors-unclustered-points', mouseLeaveChangeCursor)\r\n return () => {\r\n mapClickManager.unregister('sensors-unclustered-points')\r\n map.off('mouseenter', 'sensors-unclustered-points', mouseEnterChangeCursor)\r\n map.off('mouseleave', 'sensors-unclustered-points', mouseLeaveChangeCursor)\r\n }\r\n }, [map, mapClickManager])\r\n\r\n const handleRemoveSensor = () => {\r\n toast.success(t('sensorDeleted'))\r\n deleteSensor()\r\n setPopUpInfo(null)\r\n }\r\n\r\n // event listeners for clustered points\r\n React.useEffect(() => {\r\n if (!map) return\r\n\r\n const zoomInToDecluster = (e: MapLayerMouseEvent) => {\r\n const feature = e.features?.[0] // only proceed if this is actually a cluster\r\n if (!feature || !feature.properties.cluster_id) return\r\n\r\n const clusterId = feature.properties.cluster_id as number\r\n\r\n // Check if geometry has coordinates (not a GeometryCollection)\r\n if (!('coordinates' in feature.geometry)) return\r\n const [lng, lat] = feature.geometry.coordinates as [number, number]\r\n\r\n const source = map.getSource('sensors') as any\r\n source.getClusterExpansionZoom(clusterId).then(\r\n (zoom: number) => {\r\n map.easeTo({ center: [lng, lat], zoom })\r\n },\r\n )\r\n .catch((error) => {\r\n console.error(error)\r\n })\r\n }\r\n\r\n const mouseEnterChangeCursor = () => { map.getCanvas().style.cursor = 'pointer' }\r\n const mouseLeaveChangeCursor = () => { map.getCanvas().style.cursor = '' }\r\n\r\n // event listener for clicking on single point to zoom in and decluster, hover to change cursor\r\n map.on('click', 'sensors-clusters', zoomInToDecluster)\r\n map.on('mouseenter', 'sensors-clusters', mouseEnterChangeCursor)\r\n map.on('mouseleave', 'sensors-clusters', mouseLeaveChangeCursor)\r\n return () => {\r\n map.off('click', 'sensors-clusters', zoomInToDecluster)\r\n map.off('mouseenter', 'sensors-clusters', mouseEnterChangeCursor)\r\n map.off('mouseleave', 'sensors-clusters', mouseLeaveChangeCursor)\r\n }\r\n }, [map])\r\n\r\n const renderPopup = () => {\r\n if (!popupInfo) return null\r\n const sensorType = sensorTypes.find(t => t.id === popupInfo.typeId)\r\n const liveSensor = sensors.find(s => s.id === popupInfo.id)\r\n const dataUrl = popupInfo.url\r\n ? `${minioBaseUrl ?? ''}/sensors/${popupInfo.url}`\r\n : popupInfo.data || ''\r\n\r\n return (\r\n <Popup\r\n className=\"noBorderPopup\"\r\n longitude={popupInfo.longitude}\r\n latitude={popupInfo.latitude}\r\n closeOnClick={false}\r\n onClose={() => setPopUpInfo(null)}\r\n anchor=\"bottom\"\r\n style={{ height: '50px', border: 'none', boxShadow: 'none' }}\r\n offset={[0, 10]}\r\n >\r\n <Sensor\r\n sensorName={popupInfo.name || ''}\r\n sensorType={sensorType}\r\n sensorId={popupInfo.id}\r\n tags={liveSensor?.tags ?? []}\r\n onAddTag={async (tag) => { await updateSensor({ tags: [...(liveSensor?.tags ?? []), tag] }) }}\r\n onDeleteTag={async (tag) => { await updateSensor({ tags: (liveSensor?.tags ?? []).filter(t => t !== tag) }) }}\r\n tagsTranslations={{\r\n addTag: tSensors('addTag'),\r\n removeTag: tSensors('removeTag'),\r\n cancel: tSensors('cancel'),\r\n newTagPlaceholder: tSensors('newTagPlaceholder'),\r\n }}\r\n dataUrl={dataUrl}\r\n dataFormat={popupInfo.dataFormat}\r\n updateFrequency={popupInfo.updateFrequency}\r\n buildingId={popupInfo.buildingId}\r\n createdAt={popupInfo.createdAt}\r\n onRemove={user.id === String(popupInfo.authorId) ? handleRemoveSensor : null}\r\n onClose={() => setPopUpInfo(null)}\r\n size=\"sm\"\r\n />\r\n {/* inline styles to override MapLibre's Pop up CSS */}\r\n <style>\r\n {`\r\n .noBorderPopup .maplibregl-popup-content {\r\n border: none !important;\r\n box-shadow: none !important;\r\n background: transparent !important;\r\n }\r\n .noBorderPopup .maplibregl-popup-tip {\r\n display: none !important;\r\n }\r\n `}\r\n </style>\r\n </Popup>\r\n )\r\n }\r\n\r\n // Track unclustered sensors to display icons\r\n const [unclusteredFeatures, setUnclusteredFeatures] = React.useState<MapGeoJSONFeature[]>([])\r\n\r\n React.useEffect(() => {\r\n if (!map) return\r\n\r\n const updateUnclusteredFeatures = () => {\r\n const allFeatures = map.querySourceFeatures('sensors')\r\n const unclusteredOnly = allFeatures.filter(\r\n (f) => !f.properties?.cluster && !f.properties?.point_count\r\n )\r\n // Deduplicate by id\r\n const uniqueFeatures = new Map()\r\n unclusteredOnly.forEach((f) => uniqueFeatures.set(f.properties.id, f))\r\n setUnclusteredFeatures(Array.from(uniqueFeatures.values()))\r\n }\r\n\r\n const onSourceData = (e: any) => {\r\n if (e?.sourceId === 'sensors') updateUnclusteredFeatures()\r\n }\r\n\r\n map.on('sourcedata', onSourceData)\r\n map.on('moveend', updateUnclusteredFeatures)\r\n map.on('zoomend', updateUnclusteredFeatures)\r\n\r\n updateUnclusteredFeatures()\r\n\r\n return () => {\r\n map.off('sourcedata', onSourceData)\r\n map.off('moveend', updateUnclusteredFeatures)\r\n map.off('zoomend', updateUnclusteredFeatures)\r\n }\r\n }, [map])\r\n\r\n return (\r\n <>\r\n {(typesVisible.length > 0 || tagsVisible.length > 0) &&\r\n <Source\r\n id=\"sensors\"\r\n type=\"geojson\"\r\n data={geojsonSensorData}\r\n cluster={true}\r\n clusterMaxZoom={14}\r\n clusterRadius={40}\r\n >\r\n <Layer {...clusterLayer} />\r\n <Layer {...clusterCountLayer} />\r\n <Layer {...unclusteredPointLayer} />\r\n\r\n {renderPopup()}\r\n {unclusteredFeatures\r\n .filter((feature) => feature.properties?.id !== popupInfo?.id)\r\n .map((feature) => (\r\n <SensorIconMarker\r\n key={String(feature.properties?.id)}\r\n feature={feature}\r\n isHighlighted={currentSensorId === feature.properties?.id || hoveredSensorId === feature.properties?.id}\r\n onMouseEnter={() => setHoveredSensorId(feature.properties?.id)}\r\n onMouseLeave={() => setHoveredSensorId(null)}\r\n sensorTypes={sensorTypes}\r\n />\r\n ))}\r\n </Source>}\r\n </>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a","_b","user","t"]}
1
+ {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.tsx"],"sourcesContent":["'use client'\r\n\r\n// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport * as LR from 'lucide-react'\r\nimport { useSession } from 'next-auth/react'\r\nimport { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { Source, Layer, Popup, Marker } from 'react-map-gl/maplibre'\r\nimport { toast } from 'sonner'\r\n\r\nimport { useSensor, useSensors } from '../../../../../../../hooks/sensors/sensors'\r\nimport { useSensorTypes } from '../../../../../../../hooks/sensorTypes/sensorTypes'\r\nimport { useUsers } from '../../../../../../../hooks/users/users'\r\nimport { AppConfigContext, MapContext, MenusContext } from '../../../../../../../store'\r\nimport { ViewerNames, type Sensor as ISensor} from '../../../../../../../types/dbTypes'\r\nimport Sensor from '../../../../../../ui/Sensors/Sensor'\r\nimport { SensorDetailDialog } from '../../../../../../ui/Sensors/SensorDetailDialog'\r\nimport { SensorInput } from '../../../../../../ui/Sensors/SensorInput'\r\nimport { UNTAGGED_TAG } from '../../../../../../ui/Sensors/SensorsSection'\r\nimport { extractCoordinatesFromFeature } from '../../../../utils/extractCoordinates'\r\nimport { MapLayerClickPriority } from '../../../../utils/MapEventManager/MapClickManager'\r\nimport { createClusterLayer, createClusterCountLayer, createUnclusteredPointLayer } from '../mapLayersUtils'\r\n\r\nimport type { SensorType} from '../../../../../../../types/dbTypes';\r\nimport type { ClickCallback } from '../../../../utils/MapEventManager/MapClickManager';\r\nimport type { MapGeoJSONFeature, MapLayerMouseEvent } from 'maplibre-gl'\r\n\r\nconst SensorIconMarker = ({ feature, isHighlighted, onMouseEnter, onMouseLeave, sensorTypes }: { feature: MapGeoJSONFeature; isHighlighted?: boolean; onMouseEnter?: () => void; onMouseLeave?: () => void; sensorTypes: SensorType[] }) => {\r\n\r\n const coords = extractCoordinatesFromFeature(feature)\r\n if (!coords) return null\r\n\r\n const sensorType = sensorTypes.find(t => t.id === feature.properties?.typeId)\r\n const icon = sensorType?.icon || 'Radio'\r\n const SensorIcon = LR[icon] || LR.Radio\r\n\r\n return (\r\n <Marker key={String(feature.properties?.id ?? `${coords.lng},${coords.lat}`)} longitude={coords.lng} latitude={coords.lat} anchor=\"center\">\r\n <div\r\n style={{\r\n width: '36px',\r\n height: '36px',\r\n borderRadius: '50%',\r\n border: isHighlighted ? '3px solid #73cee2' : '2px solid white',\r\n backgroundColor: 'black',\r\n display: 'flex',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n boxShadow: isHighlighted ? '0 0 12px rgba(115, 206, 226, 0.5)' : 'none',\r\n transition: 'all 0.2s ease-in-out',\r\n transform: isHighlighted ? 'scale(1.2)' : 'scale(1)',\r\n }}\r\n onMouseEnter={onMouseEnter}\r\n onMouseLeave={onMouseLeave}\r\n >\r\n <SensorIcon style={{ width: '20px', height: '20px', color: 'hsl(var(--primary-foreground))' }} />\r\n </div>\r\n </Marker>\r\n )\r\n}\r\n\r\nexport const SensorLayers = () => {\r\n const [hoveredSensorId, setHoveredSensorId] = React.useState<number | null>(null)\r\n const clusterLayer = createClusterLayer('sensors')\r\n const clusterCountLayer = createClusterCountLayer('sensors')\r\n const unclusteredPointLayer = createUnclusteredPointLayer('sensors')\r\n\r\n // global map state\r\n\r\n const t = useTranslations('SensorLayers')\r\n const tSensors = useTranslations('SensorsSection')\r\n\r\n const { state: mapState } = React.useContext(MapContext)\r\n const { map, mapClickManager } = mapState.map\r\n const { sensors } = useSensors()\r\n const { users } = useUsers()\r\n const user = useSession().data?.user\r\n\r\n const [popupInfo, setPopUpInfo] = React.useState<Partial<ISensor & { sensorType: SensorType }> | null>(null)\r\n const { deleteSensor, updateSensor } = useSensor(popupInfo?.id ?? null)\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { visibleSensorTypes, visibleSensorTags, currentSensorId } = menusState.menus\r\n const typesVisible = visibleSensorTypes?.[ViewerNames.map] || []\r\n const tagsVisible = visibleSensorTags?.[ViewerNames.map] || []\r\n\r\n const {sensorTypes} = useSensorTypes()\r\n\r\n const { state: appConfigState } = React.useContext(AppConfigContext)\r\n const timeZone = appConfigState.appConfig.displayTimeZone\r\n const [detailSensor, setDetailSensor] = React.useState<ISensor | null>(null)\r\n const [editSensor, setEditSensor] = React.useState<ISensor | null>(null)\r\n\r\n const eligibleSensors: Array<ISensor & { authorName: string } & { sensorType: SensorType }> = sensors\r\n .filter((sensor) => sensor.viewer === ViewerNames.map)\r\n .filter((sensor) => {\r\n const matchesType = typesVisible.includes(sensor.typeId)\r\n const hasNoTags = !sensor.tags?.length\r\n const matchesTag = hasNoTags\r\n ? tagsVisible.includes(UNTAGGED_TAG)\r\n : sensor.tags?.some(tag => tagsVisible.includes(tag)) ?? false\r\n return matchesType || matchesTag\r\n })\r\n .map((sensor) => {\r\n const user = users.find(u => u.id === sensor.authorId)\r\n const sensorType = sensorTypes.find(t => t.id === sensor.typeId)\r\n return {\r\n ...sensor,\r\n authorName: user?.name ?? 'Unknown User',\r\n sensorType,\r\n }\r\n })\r\n\r\n // Close popup if the sensor was deleted\r\n React.useEffect(() => {\r\n if (popupInfo && !sensors.find((s) => s.id === popupInfo.id)) {\r\n setPopUpInfo(null)\r\n }\r\n }, [sensors, popupInfo])\r\n\r\n const geojsonSensorData = React.useMemo(() => {\r\n const convertDataToGeojson = (sensorData: typeof eligibleSensors): GeoJSON.FeatureCollection<GeoJSON.Point, { [key: string]: any }> => {\r\n const sensorFeatures: GeoJSON.Feature<GeoJSON.Point, { [key: string]: any }>[] = sensorData\r\n .map((sensor) => {\r\n const { longitude, latitude, id, name, typeId, sensorType, data, dataFormat, updateFrequency, createdAt, authorId, organizationId, visible, url, authorName } = sensor\r\n\r\n return {\r\n type: 'Feature',\r\n geometry: {\r\n type: 'Point',\r\n coordinates: [longitude, latitude],\r\n },\r\n properties: {\r\n id: Number(id),\r\n organizationId,\r\n visible,\r\n longitude,\r\n latitude,\r\n authorId: Number(authorId),\r\n authorName,\r\n name,\r\n typeId: Number(typeId),\r\n typeIcon: sensorType?.icon,\r\n data,\r\n dataFormat,\r\n updateFrequency,\r\n url,\r\n createdAt,\r\n viewer: ViewerNames.map,\r\n },\r\n }\r\n })\r\n\r\n\r\n\r\n const sensorFC: GeoJSON.FeatureCollection<GeoJSON.Point, { [key: string]: any }> = {\r\n type: 'FeatureCollection',\r\n features: sensorFeatures,\r\n }\r\n return sensorFC\r\n }\r\n return convertDataToGeojson(eligibleSensors)\r\n }, [eligibleSensors])\r\n\r\n // event listeners for sensor unclustered points\r\n React.useEffect(() => {\r\n if (!map) return\r\n const showSensorPopUp: ClickCallback = (e: MapLayerMouseEvent, features: MapGeoJSONFeature[]) => {\r\n const feature = features?.[0]\r\n\r\n if (!feature || feature.properties.point_count) return\r\n\r\n if (feature.geometry.type != 'Point') return\r\n const [longitude, latitude] = feature.geometry.coordinates\r\n const { id, authorId, name, typeId, data, dataFormat, updateFrequency, createdAt, url } = feature.properties\r\n setPopUpInfo({\r\n id: Number(id),\r\n authorId: Number(authorId),\r\n organizationId: feature.properties?.organizationId,\r\n visible: feature.properties?.visible,\r\n longitude,\r\n latitude,\r\n name,\r\n typeId: Number(typeId),\r\n data,\r\n dataFormat,\r\n updateFrequency,\r\n url,\r\n createdAt,\r\n viewer: ViewerNames.map,\r\n })\r\n }\r\n\r\n const mouseEnterChangeCursor = () => {\r\n map.getCanvas().style.cursor = 'pointer'\r\n }\r\n const mouseLeaveChangeCursor = () => {\r\n map.getCanvas().style.cursor = ''\r\n }\r\n\r\n // event listener for clicking on single point to show sensor, hover to change cursor\r\n mapClickManager.register('sensors-unclustered-points', MapLayerClickPriority.CommentLayersClickPriority, showSensorPopUp)\r\n\r\n map.on('mouseenter', 'sensors-unclustered-points', mouseEnterChangeCursor)\r\n map.on('mouseleave', 'sensors-unclustered-points', mouseLeaveChangeCursor)\r\n return () => {\r\n mapClickManager.unregister('sensors-unclustered-points')\r\n map.off('mouseenter', 'sensors-unclustered-points', mouseEnterChangeCursor)\r\n map.off('mouseleave', 'sensors-unclustered-points', mouseLeaveChangeCursor)\r\n }\r\n }, [map, mapClickManager])\r\n\r\n const handleRemoveSensor = () => {\r\n toast.success(t('sensorDeleted'))\r\n deleteSensor()\r\n setPopUpInfo(null)\r\n }\r\n\r\n // event listeners for clustered points\r\n React.useEffect(() => {\r\n if (!map) return\r\n\r\n const zoomInToDecluster = (e: MapLayerMouseEvent) => {\r\n const feature = e.features?.[0] // only proceed if this is actually a cluster\r\n if (!feature || !feature.properties.cluster_id) return\r\n\r\n const clusterId = feature.properties.cluster_id as number\r\n\r\n // Check if geometry has coordinates (not a GeometryCollection)\r\n if (!('coordinates' in feature.geometry)) return\r\n const [lng, lat] = feature.geometry.coordinates as [number, number]\r\n\r\n const source = map.getSource('sensors') as any\r\n source.getClusterExpansionZoom(clusterId).then(\r\n (zoom: number) => {\r\n map.easeTo({ center: [lng, lat], zoom })\r\n },\r\n )\r\n .catch((error) => {\r\n console.error(error)\r\n })\r\n }\r\n\r\n const mouseEnterChangeCursor = () => { map.getCanvas().style.cursor = 'pointer' }\r\n const mouseLeaveChangeCursor = () => { map.getCanvas().style.cursor = '' }\r\n\r\n // event listener for clicking on single point to zoom in and decluster, hover to change cursor\r\n map.on('click', 'sensors-clusters', zoomInToDecluster)\r\n map.on('mouseenter', 'sensors-clusters', mouseEnterChangeCursor)\r\n map.on('mouseleave', 'sensors-clusters', mouseLeaveChangeCursor)\r\n return () => {\r\n map.off('click', 'sensors-clusters', zoomInToDecluster)\r\n map.off('mouseenter', 'sensors-clusters', mouseEnterChangeCursor)\r\n map.off('mouseleave', 'sensors-clusters', mouseLeaveChangeCursor)\r\n }\r\n }, [map])\r\n\r\n const renderPopup = () => {\r\n if (!popupInfo) return null\r\n const sensorType = sensorTypes.find(t => t.id === popupInfo.typeId)\r\n const liveSensor = sensors.find(s => s.id === popupInfo.id)\r\n const dataUrl = popupInfo.url || popupInfo.data || ''\r\n\r\n return (\r\n <Popup\r\n className=\"noBorderPopup\"\r\n longitude={popupInfo.longitude}\r\n latitude={popupInfo.latitude}\r\n closeOnClick={false}\r\n closeButton={false}\r\n onClose={() => setPopUpInfo(null)}\r\n anchor=\"bottom\"\r\n style={{ height: '50px', border: 'none', boxShadow: 'none' }}\r\n offset={[0, 10]}\r\n >\r\n <Sensor\r\n sensorName={popupInfo.name || ''}\r\n sensorType={sensorType}\r\n sensorId={popupInfo.id}\r\n tags={liveSensor?.tags ?? []}\r\n onAddTag={async (tag) => { await updateSensor({ tags: [...(liveSensor?.tags ?? []), tag] }) }}\r\n onDeleteTag={async (tag) => { await updateSensor({ tags: (liveSensor?.tags ?? []).filter(t => t !== tag) }) }}\r\n tagsTranslations={{\r\n addTag: tSensors('addTag'),\r\n removeTag: tSensors('removeTag'),\r\n cancel: tSensors('cancel'),\r\n newTagPlaceholder: tSensors('newTagPlaceholder'),\r\n }}\r\n dataUrl={dataUrl}\r\n dataFormat={popupInfo.dataFormat}\r\n updateFrequency={popupInfo.updateFrequency}\r\n buildingId={popupInfo.buildingId}\r\n createdAt={popupInfo.createdAt}\r\n onRemove={user.id === String(popupInfo.authorId) ? handleRemoveSensor : null}\r\n onClose={() => setPopUpInfo(null)}\r\n onExpand={() => { if (liveSensor) setDetailSensor(liveSensor) }}\r\n onEdit={user.id === String(popupInfo.authorId) && liveSensor ? () => setEditSensor(liveSensor) : undefined}\r\n showActions\r\n timeZone={timeZone}\r\n size=\"sm\"\r\n />\r\n {/* inline styles to override MapLibre's Pop up CSS */}\r\n <style>\r\n {`\r\n .noBorderPopup .maplibregl-popup-content {\r\n border: none !important;\r\n box-shadow: none !important;\r\n background: transparent !important;\r\n }\r\n .noBorderPopup .maplibregl-popup-tip {\r\n display: none !important;\r\n }\r\n `}\r\n </style>\r\n </Popup>\r\n )\r\n }\r\n\r\n // Track unclustered sensors to display icons\r\n const [unclusteredFeatures, setUnclusteredFeatures] = React.useState<MapGeoJSONFeature[]>([])\r\n\r\n React.useEffect(() => {\r\n if (!map) return\r\n\r\n const updateUnclusteredFeatures = () => {\r\n const allFeatures = map.querySourceFeatures('sensors')\r\n const unclusteredOnly = allFeatures.filter(\r\n (f) => !f.properties?.cluster && !f.properties?.point_count\r\n )\r\n // Deduplicate by id\r\n const uniqueFeatures = new Map()\r\n unclusteredOnly.forEach((f) => uniqueFeatures.set(f.properties.id, f))\r\n setUnclusteredFeatures(Array.from(uniqueFeatures.values()))\r\n }\r\n\r\n const onSourceData = (e: any) => {\r\n if (e?.sourceId === 'sensors') updateUnclusteredFeatures()\r\n }\r\n\r\n map.on('sourcedata', onSourceData)\r\n map.on('moveend', updateUnclusteredFeatures)\r\n map.on('zoomend', updateUnclusteredFeatures)\r\n\r\n updateUnclusteredFeatures()\r\n\r\n return () => {\r\n map.off('sourcedata', onSourceData)\r\n map.off('moveend', updateUnclusteredFeatures)\r\n map.off('zoomend', updateUnclusteredFeatures)\r\n }\r\n }, [map])\r\n\r\n return (\r\n <>\r\n {detailSensor && (\r\n <SensorDetailDialog\r\n open={!!detailSensor}\r\n onOpenChange={(o) => !o && setDetailSensor(null)}\r\n sensor={detailSensor}\r\n sensorType={sensorTypes.find(t => t.id === detailSensor.typeId)}\r\n />\r\n )}\r\n {editSensor && (\r\n <SensorInput\r\n viewer={ViewerNames.map}\r\n layout=\"dialog\"\r\n isOpen={!!editSensor}\r\n editSensor={editSensor ?? undefined}\r\n onCancel={() => setEditSensor(null)}\r\n onSaved={() => setEditSensor(null)}\r\n />\r\n )}\r\n {(typesVisible.length > 0 || tagsVisible.length > 0) &&\r\n <Source\r\n id=\"sensors\"\r\n type=\"geojson\"\r\n data={geojsonSensorData}\r\n cluster={true}\r\n clusterMaxZoom={14}\r\n clusterRadius={40}\r\n >\r\n <Layer {...clusterLayer} />\r\n <Layer {...clusterCountLayer} />\r\n <Layer {...unclusteredPointLayer} />\r\n\r\n {renderPopup()}\r\n {unclusteredFeatures\r\n .filter((feature) => feature.properties?.id !== popupInfo?.id)\r\n .map((feature) => (\r\n <SensorIconMarker\r\n key={String(feature.properties?.id)}\r\n feature={feature}\r\n isHighlighted={currentSensorId === feature.properties?.id || hoveredSensorId === feature.properties?.id}\r\n onMouseEnter={() => setHoveredSensorId(feature.properties?.id)}\r\n onMouseLeave={() => setHoveredSensorId(null)}\r\n sensorTypes={sensorTypes}\r\n />\r\n ))}\r\n </Source>}\r\n </>\r\n )\r\n}\r\n"],"mappings":"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","names":["_a","_b","user","t"]}
@@ -31,7 +31,7 @@ function MapSidebar({ minioBaseUrl, martinBaseUrl, organization }) {
31
31
  selectedTab === "file" && ability.can("read", "File") && /* @__PURE__ */ jsx(FileTab, {}),
32
32
  selectedTab === "layers" && ability.can("read", "File") && /* @__PURE__ */ jsx(LayersTab, { martinBaseUrl, organization }),
33
33
  selectedTab === "communication" && ability.can("read", "Comment") && /* @__PURE__ */ jsx(CommunicationTab, {}),
34
- selectedTab === "sensors" && ability.can("read", "Sensor") && /* @__PURE__ */ jsx(SensorsTab, { minioBaseUrl }),
34
+ selectedTab === "sensors" && ability.can("read", "Sensor") && /* @__PURE__ */ jsx(SensorsTab, {}),
35
35
  selectedTab === "settings" && /* @__PURE__ */ jsx(SettingsTab, { countryCode: organization == null ? void 0 : organization.country })
36
36
  ]
37
37
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../src/core/components/viewers/map/src/MapSidebar/index.tsx"],"sourcesContent":["'use client'\r\n\r\n// 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\n\r\n\r\nimport { InfoSidebarContainer } from '../../../../../components/ui/InfoSidebar/Container'\r\nimport { MenusContext, usePermissions } from '../../../../../store'\r\n\r\nimport { CommunicationTab } from './src/CommunicationTab'\r\nimport { FileTab } from './src/FileTab'\r\nimport { LayersTab } from './src/LayersTab'\r\nimport { SensorsTab } from './src/SensorsTab'\r\nimport { SettingsTab } from './src/SettingsTab'\r\nimport { TabSelector } from './TabSelector'\r\n\r\nimport type { SidebarTabType } from '../../../../../store/Menus/reducer'\r\nimport type { Organization } from '../../../../../types/dbTypes'\r\n\r\nexport function MapSidebar({ minioBaseUrl, martinBaseUrl, organization }: { minioBaseUrl?: string; martinBaseUrl?: string; organization?: Organization }) {\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { state: menusState, dispatch: menusDispatch } = React.useContext(MenusContext)\r\n const { selectedTab } = menusState.menus\r\n\r\n const handleTabChange = (selectedTab: SidebarTabType) => {\r\n menusDispatch({ type: 'SET_SIDEBAR_SELECTED_TAB', payload: { selectedTab } })\r\n }\r\n\r\n return (\r\n <InfoSidebarContainer\r\n organization={organization}\r\n tabSelector={\r\n <TabSelector\r\n activeTab={selectedTab}\r\n onTabChangeAction={handleTabChange}\r\n />\r\n }\r\n >\r\n {selectedTab === 'file' && ability.can('read', 'File') && <FileTab />}\r\n {selectedTab === 'layers' && ability.can('read', 'File') && <LayersTab martinBaseUrl={martinBaseUrl} organization={organization} />}\r\n {selectedTab === 'communication' && ability.can('read', 'Comment') && <CommunicationTab />}\r\n {selectedTab === 'sensors' && ability.can('read', 'Sensor') && <SensorsTab minioBaseUrl={minioBaseUrl} />}\r\n {selectedTab === 'settings' && <SettingsTab countryCode={organization?.country} />}\r\n </InfoSidebarContainer>\r\n )\r\n}"],"mappings":";AAiCI,SAGI,KAHJ;AA5BJ,YAAY,WAAW;AAGvB,SAAS,4BAA4B;AACrC,SAAS,cAAc,sBAAsB;AAE7C,SAAS,wBAAwB;AACjC,SAAS,eAAe;AACxB,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAKrB,SAAS,WAAW,EAAE,cAAc,eAAe,aAAa,GAAmF;AAExJ,QAAM,EAAE,QAAQ,IAAI,eAAe;AAEnC,QAAM,EAAE,OAAO,YAAY,UAAU,cAAc,IAAI,MAAM,WAAW,YAAY;AACpF,QAAM,EAAE,YAAY,IAAI,WAAW;AAEnC,QAAM,kBAAkB,CAACA,iBAAgC;AACvD,kBAAc,EAAE,MAAM,4BAA4B,SAAS,EAAE,aAAAA,aAAY,EAAE,CAAC;AAAA,EAC9E;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,UACX,mBAAmB;AAAA;AAAA,MACrB;AAAA,MAGD;AAAA,wBAAgB,UAAU,QAAQ,IAAI,QAAQ,MAAM,KAAK,oBAAC,WAAQ;AAAA,QAClE,gBAAgB,YAAY,QAAQ,IAAI,QAAQ,MAAM,KAAK,oBAAC,aAAU,eAA8B,cAA4B;AAAA,QAChI,gBAAgB,mBAAmB,QAAQ,IAAI,QAAQ,SAAS,KAAK,oBAAC,oBAAiB;AAAA,QACvF,gBAAgB,aAAa,QAAQ,IAAI,QAAQ,QAAQ,KAAK,oBAAC,cAAW,cAA4B;AAAA,QACtG,gBAAgB,cAAc,oBAAC,eAAY,aAAa,6CAAc,SAAS;AAAA;AAAA;AAAA,EAClF;AAEJ;","names":["selectedTab"]}
1
+ {"version":3,"sources":["../../../../../../../src/core/components/viewers/map/src/MapSidebar/index.tsx"],"sourcesContent":["'use client'\r\n\r\n// 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\n\r\n\r\nimport { InfoSidebarContainer } from '../../../../../components/ui/InfoSidebar/Container'\r\nimport { MenusContext, usePermissions } from '../../../../../store'\r\n\r\nimport { CommunicationTab } from './src/CommunicationTab'\r\nimport { FileTab } from './src/FileTab'\r\nimport { LayersTab } from './src/LayersTab'\r\nimport { SensorsTab } from './src/SensorsTab'\r\nimport { SettingsTab } from './src/SettingsTab'\r\nimport { TabSelector } from './TabSelector'\r\n\r\nimport type { SidebarTabType } from '../../../../../store/Menus/reducer'\r\nimport type { Organization } from '../../../../../types/dbTypes'\r\n\r\nexport function MapSidebar({ minioBaseUrl, martinBaseUrl, organization }: { minioBaseUrl?: string; martinBaseUrl?: string; organization?: Organization }) {\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { state: menusState, dispatch: menusDispatch } = React.useContext(MenusContext)\r\n const { selectedTab } = menusState.menus\r\n\r\n const handleTabChange = (selectedTab: SidebarTabType) => {\r\n menusDispatch({ type: 'SET_SIDEBAR_SELECTED_TAB', payload: { selectedTab } })\r\n }\r\n\r\n return (\r\n <InfoSidebarContainer\r\n organization={organization}\r\n tabSelector={\r\n <TabSelector\r\n activeTab={selectedTab}\r\n onTabChangeAction={handleTabChange}\r\n />\r\n }\r\n >\r\n {selectedTab === 'file' && ability.can('read', 'File') && <FileTab />}\r\n {selectedTab === 'layers' && ability.can('read', 'File') && <LayersTab martinBaseUrl={martinBaseUrl} organization={organization} />}\r\n {selectedTab === 'communication' && ability.can('read', 'Comment') && <CommunicationTab />}\r\n {selectedTab === 'sensors' && ability.can('read', 'Sensor') && <SensorsTab />}\r\n {selectedTab === 'settings' && <SettingsTab countryCode={organization?.country} />}\r\n </InfoSidebarContainer>\r\n )\r\n}"],"mappings":";AAiCI,SAGI,KAHJ;AA5BJ,YAAY,WAAW;AAGvB,SAAS,4BAA4B;AACrC,SAAS,cAAc,sBAAsB;AAE7C,SAAS,wBAAwB;AACjC,SAAS,eAAe;AACxB,SAAS,iBAAiB;AAC1B,SAAS,kBAAkB;AAC3B,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AAKrB,SAAS,WAAW,EAAE,cAAc,eAAe,aAAa,GAAmF;AAExJ,QAAM,EAAE,QAAQ,IAAI,eAAe;AAEnC,QAAM,EAAE,OAAO,YAAY,UAAU,cAAc,IAAI,MAAM,WAAW,YAAY;AACpF,QAAM,EAAE,YAAY,IAAI,WAAW;AAEnC,QAAM,kBAAkB,CAACA,iBAAgC;AACvD,kBAAc,EAAE,MAAM,4BAA4B,SAAS,EAAE,aAAAA,aAAY,EAAE,CAAC;AAAA,EAC9E;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,aACE;AAAA,QAAC;AAAA;AAAA,UACC,WAAW;AAAA,UACX,mBAAmB;AAAA;AAAA,MACrB;AAAA,MAGD;AAAA,wBAAgB,UAAU,QAAQ,IAAI,QAAQ,MAAM,KAAK,oBAAC,WAAQ;AAAA,QAClE,gBAAgB,YAAY,QAAQ,IAAI,QAAQ,MAAM,KAAK,oBAAC,aAAU,eAA8B,cAA4B;AAAA,QAChI,gBAAgB,mBAAmB,QAAQ,IAAI,QAAQ,SAAS,KAAK,oBAAC,oBAAiB;AAAA,QACvF,gBAAgB,aAAa,QAAQ,IAAI,QAAQ,QAAQ,KAAK,oBAAC,cAAW;AAAA,QAC1E,gBAAgB,cAAc,oBAAC,eAAY,aAAa,6CAAc,SAAS;AAAA;AAAA;AAAA,EAClF;AAEJ;","names":["selectedTab"]}
@@ -5,7 +5,5 @@ export interface FileItem {
5
5
  icon: React.ElementType;
6
6
  visible: boolean;
7
7
  }
8
- export declare function SensorsTab({ minioBaseUrl }: {
9
- minioBaseUrl?: string;
10
- }): React.JSX.Element;
8
+ export declare function SensorsTab(): React.JSX.Element;
11
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAA;AAChE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,KAAK,CAAC,WAAW,CAAA;IACvB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,wBAAgB,UAAU,CAAC,EAAE,YAAY,EAAE,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,qBAMrE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAA;AAChE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,KAAK,CAAC,WAAW,CAAA;IACvB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,wBAAgB,UAAU,sBAMzB"}
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import { SensorsSection } from "../../../../../../ui/Sensors/SensorsSection";
4
- function SensorsTab({ minioBaseUrl }) {
5
- return /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col space-y-6 py-4 overflow-hidden", children: /* @__PURE__ */ jsx(SensorsSection, { minioBaseUrl }) });
4
+ function SensorsTab() {
5
+ return /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col space-y-6 py-4 overflow-hidden", children: /* @__PURE__ */ jsx(SensorsSection, {}) });
6
6
  }
7
7
  export {
8
8
  SensorsTab
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.tsx"],"sourcesContent":["'use client'\r\n\r\n// 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\n\r\nimport { SensorsSection } from '../../../../../../ui/Sensors/SensorsSection'\r\n\r\nimport type { DbFile } from '../../../../../../../types/dbTypes'\r\nexport interface FileItem {\r\n task: DbFile\r\n icon: React.ElementType\r\n visible: boolean\r\n}\r\n\r\nexport function SensorsTab({ minioBaseUrl }: { minioBaseUrl?: string }) {\r\n return (\r\n <div className=\"flex-1 flex flex-col space-y-6 py-4 overflow-hidden\">\r\n <SensorsSection minioBaseUrl={minioBaseUrl} />\r\n </div>\r\n )\r\n}\r\n"],"mappings":";AAmBM;AAZN,SAAS,sBAAsB;AASxB,SAAS,WAAW,EAAE,aAAa,GAA8B;AACtE,SACE,oBAAC,SAAI,WAAU,uDACb,8BAAC,kBAAe,cAA4B,GAC9C;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.tsx"],"sourcesContent":["'use client'\r\n\r\n// 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\n\r\nimport { SensorsSection } from '../../../../../../ui/Sensors/SensorsSection'\r\n\r\nimport type { DbFile } from '../../../../../../../types/dbTypes'\r\nexport interface FileItem {\r\n task: DbFile\r\n icon: React.ElementType\r\n visible: boolean\r\n}\r\n\r\nexport function SensorsTab() {\r\n return (\r\n <div className=\"flex-1 flex flex-col space-y-6 py-4 overflow-hidden\">\r\n <SensorsSection />\r\n </div>\r\n )\r\n}\r\n"],"mappings":";AAmBM;AAZN,SAAS,sBAAsB;AASxB,SAAS,aAAa;AAC3B,SACE,oBAAC,SAAI,WAAU,uDACb,8BAAC,kBAAe,GAClB;AAEJ;","names":[]}
@@ -0,0 +1,19 @@
1
+ import * as React from 'react';
2
+ import type { ClusterMember } from './types';
3
+ interface BimMarkerClusterProps {
4
+ members: ClusterMember[];
5
+ highlight?: boolean;
6
+ onSelect?: (id: number) => void;
7
+ onFocus?: (id: number) => void;
8
+ }
9
+ /**
10
+ * Rendered inside a CSS2DObject at a cluster's world center. Shows a numbered circle for
11
+ * overlapping BIM markers; hovering fans the members out (animated) so each can be
12
+ * seen and clicked, and collapses back on mouse-out. A short close delay keeps the fan open
13
+ * while the pointer crosses the gap between the bubble and a member (same trick the map
14
+ * cluster expansion uses), so members stay clickable. Members mount only while expanded, so a
15
+ * collapsed cluster is just the bubble — no leftover container behind it.
16
+ */
17
+ export declare function BimMarkerCluster({ members, highlight, onSelect, onFocus }: BimMarkerClusterProps): React.ReactElement;
18
+ export {};
19
+ //# sourceMappingURL=BimMarkerCluster.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BimMarkerCluster.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/shared/markers/BimMarkerCluster.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAK9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,UAAU,qBAAqB;IAC7B,OAAO,EAAE,aAAa,EAAE,CAAA;IACxB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC/B,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAC/B;AASD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,OAAO,EAAE,SAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,qBAAqB,GAAG,KAAK,CAAC,YAAY,CAoG7H"}
@@ -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 BimMarkerCluster({ 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
+ BimMarkerCluster
107
+ };
108
+ //# sourceMappingURL=BimMarkerCluster.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../src/core/components/viewers/shared/markers/BimMarkerCluster.tsx"],"sourcesContent":["'use client'\r\n\r\n// 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\n\r\nimport { Avatar } from '../../../ui/Avatar'\r\nimport { UserAvatar } from '../../../ui/UserAvatar'\r\n\r\nimport type { ClusterMember } from './types'\r\n\r\ninterface BimMarkerClusterProps {\r\n members: ClusterMember[]\r\n highlight?: boolean\r\n onSelect?: (id: number) => void\r\n onFocus?: (id: number) => void\r\n}\r\n\r\n/** Cluster bubble colour, matching the map comment cluster palette (mapLayersUtils). */\r\nfunction clusterColor(count: number): string {\r\n if (count >= 750) return '#f28cb1'\r\n if (count >= 100) return '#f1f075'\r\n return '#51bbd6'\r\n}\r\n\r\n/**\r\n * Rendered inside a CSS2DObject at a cluster's world center. Shows a numbered circle for\r\n * overlapping BIM markers; hovering fans the members out (animated) so each can be\r\n * seen and clicked, and collapses back on mouse-out. A short close delay keeps the fan open\r\n * while the pointer crosses the gap between the bubble and a member (same trick the map\r\n * cluster expansion uses), so members stay clickable. Members mount only while expanded, so a\r\n * collapsed cluster is just the bubble — no leftover container behind it.\r\n */\r\nexport function BimMarkerCluster({ members, highlight = false, onSelect, onFocus }: BimMarkerClusterProps): React.ReactElement {\r\n const [expanded, setExpanded] = React.useState(false)\r\n const [fanned, setFanned] = React.useState(false)\r\n const closeTimer = React.useRef<ReturnType<typeof setTimeout> | null>(null)\r\n const count = members.length\r\n const radius = Math.min(64, 26 + count * 4)\r\n\r\n // Animate the fan: members mount at the centre, then move out to their radius on the next frame.\r\n React.useEffect(() => {\r\n if (!expanded) {\r\n setFanned(false)\r\n return\r\n }\r\n const raf = requestAnimationFrame(() => setFanned(true))\r\n return () => cancelAnimationFrame(raf)\r\n }, [expanded])\r\n\r\n React.useEffect(() => () => {\r\n if (closeTimer.current) clearTimeout(closeTimer.current)\r\n }, [])\r\n\r\n const open = () => {\r\n if (closeTimer.current) {\r\n clearTimeout(closeTimer.current)\r\n closeTimer.current = null\r\n }\r\n setExpanded(true)\r\n }\r\n\r\n const scheduleClose = () => {\r\n if (closeTimer.current) clearTimeout(closeTimer.current)\r\n closeTimer.current = setTimeout(() => setExpanded(false), 150)\r\n }\r\n\r\n return (\r\n <div\r\n className=\"relative -translate-x-1/2 -translate-y-1/2 pointer-events-auto\"\r\n onMouseEnter={open}\r\n onMouseLeave={scheduleClose}\r\n >\r\n <div\r\n className=\"relative flex items-center justify-center rounded-full text-sm font-medium select-none cursor-pointer\"\r\n style={{\r\n width: 36,\r\n height: 36,\r\n backgroundColor: clusterColor(count),\r\n color: '#000',\r\n textShadow: '0 0 2px #fff, 0 0 2px #fff',\r\n boxShadow: highlight\r\n ? '0 0 0 2px #73cee2, 0 0 8px rgba(115, 206, 226, 0.5)'\r\n : '0 0 0 1px white',\r\n }}\r\n >\r\n {count}\r\n </div>\r\n\r\n {expanded && (\r\n <div className=\"absolute left-1/2 top-1/2\">\r\n {members.map((member, i) => {\r\n const angle = (2 * Math.PI * i) / count - Math.PI / 2\r\n const x = fanned ? Math.cos(angle) * radius : 0\r\n const y = fanned ? Math.sin(angle) * radius : 0\r\n return (\r\n <div\r\n key={member.id}\r\n className=\"absolute\"\r\n style={{\r\n transform: `translate(-50%, -50%) translate(${x}px, ${y}px)`,\r\n opacity: fanned ? 1 : 0,\r\n transition: 'transform 200ms ease, opacity 200ms ease',\r\n transitionDelay: `${i * 20}ms`,\r\n }}\r\n onMouseEnter={open}\r\n // pointerdown (not click): the CSS2D renderer re-transforms this element every\r\n // frame and it is still animating out, so a browser click frequently never fires.\r\n onPointerDown={(e) => {\r\n e.stopPropagation()\r\n onSelect?.(member.id)\r\n onFocus?.(member.id)\r\n }}\r\n >\r\n <div\r\n className=\"h-9 w-9 rounded-full overflow-hidden cursor-pointer bg-card\"\r\n style={{ boxShadow: '0 0 0 1px white' }}\r\n >\r\n <Avatar className=\"h-full w-full\">\r\n <UserAvatar\r\n imageFileId={member.imageFileId ?? null}\r\n name={member.userName}\r\n className=\"h-full w-full rounded-full object-cover\"\r\n />\r\n </Avatar>\r\n </div>\r\n </div>\r\n )\r\n })}\r\n </div>\r\n )}\r\n </div>\r\n )\r\n}\r\n"],"mappings":";AAqEI,SAKE,KALF;AAhEJ,YAAY,WAAW;AAEvB,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAY3B,SAAS,aAAa,OAAuB;AAC3C,MAAI,SAAS,IAAK,QAAO;AACzB,MAAI,SAAS,IAAK,QAAO;AACzB,SAAO;AACT;AAUO,SAAS,iBAAiB,EAAE,SAAS,YAAY,OAAO,UAAU,QAAQ,GAA8C;AAC7H,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;AA5FtC;AA6FY,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,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 marker without changing the current view direction: keep looking along
18
+ * the same axis the user already has, but re-target the marker and pull the camera
19
+ * to `distance` away from it. Used for double-click "zoom to marker".
20
+ *
21
+ * Pure and three.js-free so it can be unit tested with plain objects.
22
+ */
23
+ export declare function computeMarkerLookAt(camPos: Vec3, target: Vec3, markerPoint: Vec3, distance?: number): LookAt;
24
+ //# sourceMappingURL=computeMarkerLookAt.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computeMarkerLookAt.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/shared/markers/computeMarkerLookAt.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,mBAAmB,CACjC,MAAM,EAAE,IAAI,EACZ,MAAM,EAAE,IAAI,EACZ,WAAW,EAAE,IAAI,EACjB,QAAQ,SAAI,GACX,MAAM,CAyBR"}
@@ -0,0 +1,27 @@
1
+ const DEFAULT_DIRECTION = { x: 1, y: 1, z: 1 };
2
+ function computeMarkerLookAt(camPos, target, markerPoint, 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: markerPoint.x + dx * scale,
17
+ camY: markerPoint.y + dy * scale,
18
+ camZ: markerPoint.z + dz * scale,
19
+ tarX: markerPoint.x,
20
+ tarY: markerPoint.y,
21
+ tarZ: markerPoint.z
22
+ };
23
+ }
24
+ export {
25
+ computeMarkerLookAt
26
+ };
27
+ //# sourceMappingURL=computeMarkerLookAt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../src/core/components/viewers/shared/markers/computeMarkerLookAt.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n/** Minimal 3D vector shape so this helper stays free of a three.js import (easy to unit test). */\r\nexport interface Vec3 {\r\n x: number\r\n y: number\r\n z: number\r\n}\r\n\r\n/** Camera pose in the shape `camera-controls` `setLookAt` expects. */\r\nexport interface LookAt {\r\n camX: number\r\n camY: number\r\n camZ: number\r\n tarX: number\r\n tarY: number\r\n tarZ: number\r\n}\r\n\r\nconst DEFAULT_DIRECTION: Vec3 = { x: 1, y: 1, z: 1 }\r\n\r\n/**\r\n * Frame a marker without changing the current view direction: keep looking along\r\n * the same axis the user already has, but re-target the marker and pull the camera\r\n * to `distance` away from it. Used for double-click \"zoom to marker\".\r\n *\r\n * Pure and three.js-free so it can be unit tested with plain objects.\r\n */\r\nexport function computeMarkerLookAt(\r\n camPos: Vec3,\r\n target: Vec3,\r\n markerPoint: Vec3,\r\n distance = 8,\r\n): LookAt {\r\n let dx = camPos.x - target.x\r\n let dy = camPos.y - target.y\r\n let dz = camPos.z - target.z\r\n let length = Math.hypot(dx, dy, dz)\r\n\r\n // Degenerate pose (camera sitting on its target): fall back to a sensible angle.\r\n if (length < 1e-6) {\r\n dx = DEFAULT_DIRECTION.x\r\n dy = DEFAULT_DIRECTION.y\r\n dz = DEFAULT_DIRECTION.z\r\n length = Math.hypot(dx, dy, dz)\r\n }\r\n\r\n const safeDistance = distance > 0 ? distance : 8\r\n const scale = safeDistance / length\r\n\r\n return {\r\n camX: markerPoint.x + dx * scale,\r\n camY: markerPoint.y + dy * scale,\r\n camZ: markerPoint.z + dz * scale,\r\n tarX: markerPoint.x,\r\n tarY: markerPoint.y,\r\n tarZ: markerPoint.z,\r\n }\r\n}\r\n"],"mappings":"AAoBA,MAAM,oBAA0B,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,EAAE;AAS5C,SAAS,oBACd,QACA,QACA,aACA,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,YAAY,IAAI,KAAK;AAAA,IAC3B,MAAM,YAAY,IAAI,KAAK;AAAA,IAC3B,MAAM,YAAY,IAAI,KAAK;AAAA,IAC3B,MAAM,YAAY;AAAA,IAClB,MAAM,YAAY;AAAA,IAClB,MAAM,YAAY;AAAA,EACpB;AACF;","names":[]}
@@ -0,0 +1,66 @@
1
+ import type * as React from "react";
2
+ /** Bridges the framework to the parallel per-entity fields in the Menus store. */
3
+ export interface MarkerStoreAdapter {
4
+ currentId: number | null;
5
+ focusedId: number | null;
6
+ focusRequestId: number;
7
+ setCurrentId: (id: number | null) => void;
8
+ setFocusedId: (id: number | null) => void;
9
+ /** Opens the sidebar editor for this marker (dispatches REQUEST_*_ACTION). */
10
+ requestEdit: (id: number) => void;
11
+ }
12
+ /** One member inside a BIM cluster bubble. */
13
+ export interface ClusterMember {
14
+ id: number;
15
+ userName?: string;
16
+ imageFileId?: number | null;
17
+ }
18
+ /** An item positioned in the BIM scene. Must carry world coords + id. */
19
+ export interface BimMarkerItem {
20
+ id: number;
21
+ x: number;
22
+ y: number;
23
+ z: number;
24
+ isPending?: boolean;
25
+ }
26
+ /** Everything entity-specific the generic BIM marker hook needs. */
27
+ export interface BimMarkerConfig<T extends BimMarkerItem> {
28
+ markerIdKey: "commentId" | "sensorId";
29
+ sphereColor: string;
30
+ /** Camera pull-back distance for double-click focus. */
31
+ focusDistance?: number;
32
+ /** The CSS2D card component for a single marker. */
33
+ component: React.ComponentType<Record<string, unknown>>;
34
+ /** The cluster-bubble component. */
35
+ clusterComponent: React.ComponentType<{
36
+ members: ClusterMember[];
37
+ highlight: boolean;
38
+ onSelect: (id: number) => void;
39
+ onFocus: (id: number) => void;
40
+ }>;
41
+ /** Builds the card props for one item (highlight/focused/handlers injected by the hook). */
42
+ propsMapper: (item: T, ctx: {
43
+ highlight: boolean;
44
+ focused: boolean;
45
+ isAuthor: boolean;
46
+ onSelect: () => void;
47
+ onFocus: () => void;
48
+ onEdit?: () => void;
49
+ onRemove?: () => void;
50
+ }) => Record<string, unknown>;
51
+ /** Maps an item to its cluster-member representation. */
52
+ clusterMemberMapper: (item: T) => ClusterMember;
53
+ /** Per-item string folded into the re-render signature (e.g. text/name + pending flag). */
54
+ dataHashPart: (item: T) => string;
55
+ /** True if the current user may edit/delete this item (author check). */
56
+ isAuthor: (item: T) => boolean;
57
+ /** Delete handler (comment cascade vs sensor single-delete differ). */
58
+ onRemove: (id: number) => void;
59
+ /** Builds a placeholder item for a pending (being-created) marker. */
60
+ makePendingItem: (id: number, pos: {
61
+ x: number;
62
+ y: number;
63
+ z: number;
64
+ }) => T;
65
+ }
66
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/shared/markers/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAEnC,kFAAkF;AAClF,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,cAAc,EAAE,MAAM,CAAA;IACtB,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACzC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACzC,8EAA8E;IAC9E,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAClC;AAED,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAC5B;AAED,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;IACT,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,oEAAoE;AACpE,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,aAAa;IACtD,WAAW,EAAE,WAAW,GAAG,UAAU,CAAA;IACrC,WAAW,EAAE,MAAM,CAAA;IACnB,wDAAwD;IACxD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oDAAoD;IACpD,SAAS,EAAE,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACvD,oCAAoC;IACpC,gBAAgB,EAAE,KAAK,CAAC,aAAa,CAAC;QACpC,OAAO,EAAE,aAAa,EAAE,CAAA;QACxB,SAAS,EAAE,OAAO,CAAA;QAClB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;QAC9B,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;KAC9B,CAAC,CAAA;IACF,4FAA4F;IAC5F,WAAW,EAAE,CACX,IAAI,EAAE,CAAC,EACP,GAAG,EAAE;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAC7D,QAAQ,EAAE,MAAM,IAAI,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;KAAE,KACtF,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC5B,yDAAyD;IACzD,mBAAmB,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,aAAa,CAAA;IAC/C,2FAA2F;IAC3F,YAAY,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,CAAA;IACjC,yEAAyE;IACzE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAA;IAC9B,uEAAuE;IACvE,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;IAC9B,sEAAsE;IACtE,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,CAAC,CAAA;CAC7E"}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,22 @@
1
+ import { type HooksBag } from "../../../../hooks/provider";
2
+ import type { BimMarkerConfig, BimMarkerItem, MarkerStoreAdapter } from "./types";
3
+ /**
4
+ * Generic BIM marker layer. Renders `items` as clustered CSS2D markers over a `three` world,
5
+ * driving selection/focus/edit/remove through a `MarkerStoreAdapter` and shaping every
6
+ * entity-specific detail through `config`. Behaviour is a straight generalization of the
7
+ * comment marker layer (screen-space clustering, rAF diffing, double-click focus).
8
+ */
9
+ export declare function useMarkerLayerBim<T extends BimMarkerItem>(world: any, items: T[], opts: {
10
+ isVisible: boolean;
11
+ store: MarkerStoreAdapter;
12
+ coreHooks: HooksBag | null;
13
+ config: BimMarkerConfig<T>;
14
+ }): {
15
+ addPending: (pos: {
16
+ x: number;
17
+ y: number;
18
+ z: number;
19
+ }) => number;
20
+ removePending: (id: number) => void;
21
+ };
22
+ //# sourceMappingURL=useMarkerLayerBim.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMarkerLayerBim.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/shared/markers/useMarkerLayerBim.ts"],"names":[],"mappings":"AAUA,OAAO,EAAY,KAAK,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AAKpE,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAyLjF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,aAAa,EACvD,KAAK,EAAE,GAAG,EACV,KAAK,EAAE,CAAC,EAAE,EACV,IAAI,EAAE;IACJ,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,kBAAkB,CAAA;IACzB,SAAS,EAAE,QAAQ,GAAG,IAAI,CAAA;IAC1B,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,CAAA;CAC3B,GACA;IAAE,UAAU,EAAE,CAAC,GAAG,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,CAAC;IAAC,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAA;CAAE,CAiG3G"}