@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.
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.js.map +1 -1
- package/dist/core/components/ui/Comments/Comment.js.map +1 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts +5 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.js +37 -9
- package/dist/core/components/ui/Comments/CommentActionButtons.js.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts +1 -2
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js +66 -94
- package/dist/core/components/ui/Sensors/CollapsibleSensorItem.js.map +1 -1
- package/dist/core/components/ui/Sensors/Sensor.d.ts +16 -3
- package/dist/core/components/ui/Sensors/Sensor.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/Sensor.js +76 -37
- package/dist/core/components/ui/Sensors/Sensor.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorChart.d.ts +12 -2
- package/dist/core/components/ui/Sensors/SensorChart.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorChart.js +49 -12
- package/dist/core/components/ui/Sensors/SensorChart.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorDetailDialog.d.ts +9 -0
- package/dist/core/components/ui/Sensors/SensorDetailDialog.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/SensorDetailDialog.js +128 -0
- package/dist/core/components/ui/Sensors/SensorDetailDialog.js.map +1 -0
- package/dist/core/components/ui/Sensors/SensorInput.d.ts +6 -1
- package/dist/core/components/ui/Sensors/SensorInput.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorInput.js +94 -8
- package/dist/core/components/ui/Sensors/SensorInput.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.d.ts +3 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.js +4 -1
- package/dist/core/components/ui/Sensors/SensorTagsSection.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.d.ts +1 -3
- package/dist/core/components/ui/Sensors/SensorsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js +33 -8
- package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
- package/dist/core/components/ui/Sensors/TimeZoneSelect.d.ts +7 -0
- package/dist/core/components/ui/Sensors/TimeZoneSelect.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/TimeZoneSelect.js +56 -0
- package/dist/core/components/ui/Sensors/TimeZoneSelect.js.map +1 -0
- package/dist/core/components/ui/Sensors/sensorData.d.ts +42 -0
- package/dist/core/components/ui/Sensors/sensorData.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/sensorData.js +118 -0
- package/dist/core/components/ui/Sensors/sensorData.js.map +1 -0
- package/dist/core/components/ui/Sensors/sensorRange.d.ts +24 -0
- package/dist/core/components/ui/Sensors/sensorRange.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/sensorRange.js +34 -0
- package/dist/core/components/ui/Sensors/sensorRange.js.map +1 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.d.ts +15 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.d.ts.map +1 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.js +63 -0
- package/dist/core/components/ui/Sensors/useSensorSeries.js.map +1 -0
- package/dist/core/components/viewers/Viewer.js +1 -1
- package/dist/core/components/viewers/Viewer.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.d.ts +1 -3
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.js +2 -2
- package/dist/core/components/viewers/bim/src/BimSidebar/src/SensorsTab/index.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/index.js +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/index.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.d.ts +1 -2
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.js +25 -2
- package/dist/core/components/viewers/bim/src/tools/AddToBim/index.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts +1 -22
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js +2 -105
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.d.ts +8 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.js +15 -3
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimSensor.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts +2 -23
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js +2 -24
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.d.ts +33 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.js +90 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentMarkerConfig.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts +7 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js +26 -230
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts +7 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js +39 -11
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.js.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.d.ts +1 -2
- package/dist/core/components/viewers/map/MapViewer.d.ts.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.js +2 -2
- package/dist/core/components/viewers/map/MapViewer.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/index.d.ts +1 -2
- package/dist/core/components/viewers/map/src/MapLayers/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/index.js +2 -2
- package/dist/core/components/viewers/map/src/MapLayers/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts +1 -3
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js +74 -39
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/index.js +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.d.ts +1 -3
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.js +2 -2
- package/dist/core/components/viewers/map/src/MapSidebar/src/SensorsTab/index.js.map +1 -1
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.d.ts +19 -0
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.js +108 -0
- package/dist/core/components/viewers/shared/markers/BimMarkerCluster.js.map +1 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.d.ts +24 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.js +27 -0
- package/dist/core/components/viewers/shared/markers/computeMarkerLookAt.js.map +1 -0
- package/dist/core/components/viewers/shared/markers/types.d.ts +66 -0
- package/dist/core/components/viewers/shared/markers/types.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/types.js +1 -0
- package/dist/core/components/viewers/shared/markers/types.js.map +1 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.d.ts +22 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.d.ts.map +1 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.js +233 -0
- package/dist/core/components/viewers/shared/markers/useMarkerLayerBim.js.map +1 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.js.map +1 -1
- package/dist/core/i18n/messages/en.json +5 -1
- package/dist/core/i18n/messages/es.json +5 -1
- package/dist/core/i18n/messages/fr.json +5 -1
- package/dist/core/store/AppConfig/context.d.ts.map +1 -1
- package/dist/core/store/AppConfig/context.js +4 -1
- package/dist/core/store/AppConfig/context.js.map +1 -1
- package/dist/core/store/AppConfig/reducer.d.ts +10 -0
- package/dist/core/store/AppConfig/reducer.d.ts.map +1 -1
- package/dist/core/store/AppConfig/reducer.js +10 -0
- package/dist/core/store/AppConfig/reducer.js.map +1 -1
- package/dist/core/store/Menus/context.d.ts.map +1 -1
- package/dist/core/store/Menus/context.js +5 -2
- package/dist/core/store/Menus/context.js.map +1 -1
- package/dist/core/store/Menus/reducer.d.ts +45 -1
- package/dist/core/store/Menus/reducer.d.ts.map +1 -1
- package/dist/core/store/Menus/reducer.js +21 -3
- package/dist/core/store/Menus/reducer.js.map +1 -1
- package/dist/core/utils/timeUtils.d.ts +24 -1
- package/dist/core/utils/timeUtils.d.ts.map +1 -1
- package/dist/core/utils/timeUtils.js +52 -3
- package/dist/core/utils/timeUtils.js.map +1 -1
- 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, {
|
|
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
|
|
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(
|
|
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,
|
|
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(
|
|
5
|
-
return /* @__PURE__ */ jsx("div", { className: "flex-1 flex flex-col space-y-6 py-4 overflow-hidden", children: /* @__PURE__ */ jsx(SensorsSection, {
|
|
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(
|
|
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"}
|