@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/bim/src/tools/AddToBim/src/useCommentMarkers.ts"],"sourcesContent":["\"use client\"\n\n// SPDX-License-Identifier: AGPL-3.0-or-later\n// Copyright (C) 2025 Collab Digital Twins\n\nimport { useSession } from \"next-auth/react\"\nimport * as React from \"react\"\nimport { createRoot } from \"react-dom/client\"\nimport * as THREE from \"three\"\nimport { CSS2DObject } from \"three/addons/renderers/CSS2DRenderer.js\"\n\nimport { useComments, useDeleteComments } from \"../../../../../../../hooks/comments/comments\"\nimport { HooksCtx, useCoreHooks, type HooksBag } from \"../../../../../../../hooks/provider\"\nimport { useUsers } from \"../../../../../../../hooks/users/users\"\nimport { MenusContext } from \"../../../../../../../store\"\nimport { ViewerNames } from \"../../../../../../../types/dbTypes\"\n\nimport BimComment from \"./BimComment\"\nimport BimCommentCluster from \"./BimCommentCluster\"\nimport { clusterMarkersByScreenSpace } from \"./clusterMarkersByScreenSpace\"\nimport { computeCommentLookAt } from \"./commentCameraUtils\"\nimport { renderCSS2DMarkers, type MarkerRef } from \"./renderCSS2DMarkers\"\n\ntype ClusterMarkerRef = { root: ReturnType<typeof createRoot>; css2dObject: CSS2DObject }\n\ninterface RenderState {\n world: any\n /** Non-pending BIM comments eligible for clustering. */\n items: any[]\n /** Pending (being-created) markers — always rendered as singles. */\n pending: any[]\n isVisible: boolean\n currentCommentId: number | null\n focusedCommentId: number | null\n currentUserId: string | null\n coreHooks: HooksBag | null\n onRemove: (id: number) => void\n onSelect: (id: number) => void\n onFocus: (id: number) => void\n onEdit: (id: number) => void\n onReply: (id: number) => void\n}\n\n/** Builds the per-comment props for the shared `Comment` card, gating edit/delete by author. */\nfunction makeCommentPropsMapper(state: RenderState) {\n const { currentCommentId, focusedCommentId, currentUserId, onSelect, onFocus, onEdit, onReply, onRemove } = state\n return (comment: any) => {\n const base = {\n userName: comment.authorName,\n userImageFileId: comment.imageFileId ?? null,\n userImage: comment.imageFileId ?? null,\n buildingId: comment.buildingId,\n timestamp: new Date(comment.createdAt),\n text: comment.text,\n isPending: comment.isPending || false,\n highlight: currentCommentId === comment.id,\n focused: focusedCommentId === comment.id,\n }\n if (comment.isPending) return base\n\n const isAuthor = currentUserId != null && String(comment.authorId) === currentUserId\n return {\n ...base,\n showActions: true,\n canEdit: isAuthor,\n canDelete: isAuthor,\n onSelect: () => onSelect(comment.id),\n onFocus: () => onFocus(comment.id),\n onReply: () => onReply(comment.id),\n onEdit: isAuthor ? () => onEdit(comment.id) : undefined,\n onRemove: isAuthor ? () => onRemove(comment.id) : undefined,\n }\n }\n}\n\nfunction clearSingles(world: any, registry: React.MutableRefObject<Map<string, MarkerRef>>) {\n renderCSS2DMarkers(world, {\n items: [],\n markerIdKey: \"commentId\",\n registry,\n component: BimComment,\n propsMapper: () => ({}),\n sphereColor: \"white\",\n isVisible: false,\n onRemove: () => {},\n })\n}\n\nfunction clearClusters(world: any, registry: React.MutableRefObject<Map<string, ClusterMarkerRef>>) {\n registry.current.forEach(({ root, css2dObject }) => {\n world.scene.three.remove(css2dObject)\n const el = css2dObject.element as HTMLElement | undefined\n el?.remove()\n root.unmount()\n })\n registry.current.clear()\n}\n\n/**\n * Imperative render driven by a rAF loop. Recomputes screen-space clustering\n * from the current camera and only rebuilds DOM when the layout/data signature\n * changes (individual CSS2DObjects already track the camera every frame).\n */\nfunction renderMarkers(\n state: RenderState,\n singleRegistry: React.MutableRefObject<Map<string, MarkerRef>>,\n clusterRegistry: React.MutableRefObject<Map<string, ClusterMarkerRef>>,\n lastSignatureRef: React.MutableRefObject<string>,\n) {\n const { world, items, pending, isVisible, currentCommentId, focusedCommentId, coreHooks, onRemove, onSelect, onFocus } = state\n if (!world) return\n\n if (!isVisible) {\n if (lastSignatureRef.current === \"hidden\") return\n clearSingles(world, singleRegistry)\n clearClusters(world, clusterRegistry)\n lastSignatureRef.current = \"hidden\"\n return\n }\n\n const camera = world.camera?.three\n const dom = world.renderer?.three?.domElement\n if (!camera || !dom) return\n\n const width = dom.clientWidth || dom.width || 0\n const height = dom.clientHeight || dom.height || 0\n if (!width || !height) return\n\n const { clusters, singles } = clusterMarkersByScreenSpace(items, camera, width, height, 44)\n const singleItems = [...singles, ...pending]\n\n const clusterKeys = clusters.map((c) => c.key)\n const dataHash = [...items, ...pending]\n .map((c) => `${c.id}:${c.text ?? \"\"}:${c.isPending ? 1 : 0}`)\n .join(\";\")\n const signature =\n `v2|${currentCommentId}|${focusedCommentId}|C:${clusterKeys.join(\",\")}` +\n `|S:${singleItems.map((s) => s.id).join(\",\")}|D:${dataHash}`\n if (signature === lastSignatureRef.current) return\n lastSignatureRef.current = signature\n\n // Singles (including pending) via the shared per-marker renderer\n renderCSS2DMarkers(world, {\n items: singleItems,\n markerIdKey: \"commentId\",\n registry: singleRegistry,\n component: BimComment,\n hooksContextValue: coreHooks,\n propsMapper: makeCommentPropsMapper(state),\n sphereColor: \"white\",\n isVisible: true,\n onRemove,\n })\n\n // Float the highlighted/focused marker above overlapping ones. The CSS2D renderer sorts by\n // renderOrder first (then camera distance), so this makes a selected comment that sits behind\n // nearer markers come to the front — visible and clickable instead of buried.\n singleRegistry.current.forEach((ref, id) => {\n const nid = Number(id)\n ref.css2dObject.renderOrder = nid === currentCommentId || nid === focusedCommentId ? 1 : 0\n })\n\n // Cluster bubbles, managed imperatively\n const reg = clusterRegistry.current\n const wanted = new Set(clusterKeys)\n for (const [key, ref] of reg) {\n if (!wanted.has(key)) {\n world.scene.three.remove(ref.css2dObject)\n const el = ref.css2dObject.element as HTMLElement | undefined\n el?.remove()\n ref.root.unmount()\n reg.delete(key)\n }\n }\n\n for (const cluster of clusters) {\n const members = cluster.members.map((m) => ({\n id: m.id,\n userName: m.authorName,\n imageFileId: m.imageFileId ?? null,\n }))\n const highlight = currentCommentId != null && members.some((m) => m.id === currentCommentId)\n const element = React.createElement(BimCommentCluster, { members, highlight, onSelect, onFocus })\n const wrapped = coreHooks\n ? React.createElement(HooksCtx.Provider, { value: coreHooks }, element)\n : element\n\n const existing = reg.get(cluster.key)\n if (existing) {\n existing.root.render(wrapped)\n existing.css2dObject.position.set(cluster.worldCenter.x, cluster.worldCenter.y + 0.2, cluster.worldCenter.z)\n existing.css2dObject.renderOrder = highlight ? 1 : 0\n continue\n }\n\n const labelDiv = document.createElement(\"div\")\n labelDiv.style.pointerEvents = \"auto\"\n const root = createRoot(labelDiv)\n root.render(wrapped)\n\n const css2dObject = new CSS2DObject(labelDiv)\n css2dObject.position.set(cluster.worldCenter.x, cluster.worldCenter.y + 0.2, cluster.worldCenter.z)\n css2dObject.element.style.pointerEvents = \"auto\"\n css2dObject.renderOrder = highlight ? 1 : 0\n world.scene.three.add(css2dObject)\n\n reg.set(cluster.key, { root, css2dObject })\n }\n}\n\nexport function useCommentMarkers(world: any, buildingId: number) {\n const { state: menusState, dispatch: menusDispatch, setIsSidebarOpen } = React.useContext(MenusContext)\n const { commentsVisibleInViewer, currentCommentId, focusedCommentId, focusRequestId } = menusState.menus\n\n const singleRegistry = React.useRef<Map<string, MarkerRef>>(new Map())\n const clusterRegistry = React.useRef<Map<string, ClusterMarkerRef>>(new Map())\n const lastSignatureRef = React.useRef<string>(\"\")\n const stateRef = React.useRef<RenderState | null>(null)\n const commentsRef = React.useRef<any[]>([])\n\n const { comments } = useComments()\n const { users } = useUsers()\n const coreHooks = useCoreHooks()\n const currentUserId = useSession().data?.user?.id ?? null\n const { deleteComments } = useDeleteComments()\n\n const eligibleComments = comments\n .filter((comment) => {\n const { x, y, z, viewer } = comment\n if (x == null || y == null || z == null) return false\n if (viewer !== ViewerNames.bim) return false\n if (buildingId !== -1 && comment.buildingId !== buildingId) return false\n return true\n })\n .map((comment) => {\n const user = users.find((u) => u.id === comment.authorId)\n return {\n ...comment,\n authorName: user?.name ?? \"Unknown User\",\n imageFileId: user?.imageFileId ?? null,\n image: user?.imageFileId ?? null,\n }\n })\n\n commentsRef.current = eligibleComments\n\n const handleRemoveComment = React.useCallback((commentId: number) => {\n // Cascade: remove the comment and any replies (the DB self-relation does not cascade).\n const replyIds = comments.filter((c) => c.replyToId === commentId).map((c) => c.id)\n void deleteComments({ ids: [commentId, ...replyIds] })\n }, [comments, deleteComments])\n\n const handleSelectComment = React.useCallback((commentId: number) => {\n menusDispatch({ type: \"SET_CURRENT_COMMENT_ID\", payload: { commentId } })\n }, [menusDispatch])\n\n const handleFocusComment = React.useCallback((commentId: number) => {\n menusDispatch({ type: \"SET_CURRENT_COMMENT_ID\", payload: { commentId } })\n menusDispatch({ type: \"SET_FOCUSED_COMMENT_ID\", payload: { commentId } })\n }, [menusDispatch])\n\n // Edit/reply on the 3D card open the comment's editor/reply box in the sidebar.\n const requestSidebarAction = React.useCallback((commentId: number, action: 'edit' | 'reply') => {\n setIsSidebarOpen(true)\n menusDispatch({ type: \"SET_SIDEBAR_SELECTED_TAB\", payload: { selectedTab: 'communication' } })\n menusDispatch({ type: \"SET_CURRENT_COMMENT_ID\", payload: { commentId } })\n menusDispatch({ type: \"REQUEST_COMMENT_ACTION\", payload: { commentId, action } })\n }, [menusDispatch, setIsSidebarOpen])\n\n const handleEditComment = React.useCallback((commentId: number) => {\n requestSidebarAction(commentId, 'edit')\n }, [requestSidebarAction])\n\n const handleReplyComment = React.useCallback((commentId: number) => {\n requestSidebarAction(commentId, 'reply')\n }, [requestSidebarAction])\n\n // Pending markers for loading indicators while comment is being created\n const [pendingComments, setPendingComments] = React.useState<Array<{ id: number; x: number; y: number; z: number }>>([])\n\n const addPendingComment = React.useCallback((position: { x: number; y: number; z: number }) => {\n const id = -Date.now()\n setPendingComments((prev) => [...prev, { id, ...position }])\n return id\n }, [])\n\n const removePendingComment = React.useCallback((id: number) => {\n setPendingComments((prev) => prev.filter((p) => p.id !== id))\n }, [])\n\n const isVisible = commentsVisibleInViewer?.includes(ViewerNames.bim) ?? false\n\n // Keep the latest inputs available to the rAF render loop (avoids stale closures)\n stateRef.current = {\n world,\n items: eligibleComments.map((c) => ({ ...c, isPending: false })),\n pending: pendingComments.map((p) => ({\n id: p.id,\n x: p.x,\n y: p.y,\n z: p.z,\n authorName: \"Loading...\",\n imageFileId: null,\n image: \"\",\n text: \"Creating comment...\",\n createdAt: new Date().toISOString(),\n buildingId,\n isPending: true,\n })),\n isVisible,\n currentCommentId,\n focusedCommentId,\n currentUserId,\n coreHooks,\n onRemove: handleRemoveComment,\n onSelect: handleSelectComment,\n onFocus: handleFocusComment,\n onEdit: handleEditComment,\n onReply: handleReplyComment,\n }\n\n React.useEffect(() => {\n if (!world) return\n let raf = 0\n let frame = 0\n const loop = () => {\n // Recompute at most every other frame; renderMarkers no-ops unless the\n // cluster layout or data actually changed.\n if ((frame++ & 1) === 0 && stateRef.current) {\n renderMarkers(stateRef.current, singleRegistry, clusterRegistry, lastSignatureRef)\n }\n raf = requestAnimationFrame(loop)\n }\n raf = requestAnimationFrame(loop)\n\n return () => {\n cancelAnimationFrame(raf)\n clearClusters(world, clusterRegistry)\n clearSingles(world, singleRegistry)\n lastSignatureRef.current = \"\"\n }\n }, [world])\n\n // Zoom to a comment when it is focused (double-clicked here or in the sidebar).\n React.useEffect(() => {\n if (focusedCommentId == null || !world) return\n const target = commentsRef.current.find((c) => c.id === focusedCommentId)\n if (!target || target.x == null || target.y == null || target.z == null) return\n const controls = world.camera?.controls\n if (!controls) return\n\n const camPos = controls.getPosition(new THREE.Vector3())\n const camTarget = controls.getTarget(new THREE.Vector3())\n const lookAt = computeCommentLookAt(\n { x: camPos.x, y: camPos.y, z: camPos.z },\n { x: camTarget.x, y: camTarget.y, z: camTarget.z },\n { x: target.x, y: target.y, z: target.z },\n )\n void controls.setLookAt(lookAt.camX, lookAt.camY, lookAt.camZ, lookAt.tarX, lookAt.tarY, lookAt.tarZ, true)\n }, [focusRequestId, world])\n\n const commentCount = comments.filter(\n (c) => c.viewer === ViewerNames.bim && (!buildingId || buildingId === -1 || c.buildingId === buildingId),\n ).length\n\n return { addPendingComment, removePendingComment, commentCount }\n}\n"],"mappings":"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","names":["_a","_b","_c"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.ts"],"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 { useSession } from \"next-auth/react\"\r\nimport * as React from \"react\"\r\n\r\nimport { useComments, useDeleteComments } from \"../../../../../../../hooks/comments/comments\"\r\nimport { useCoreHooks } from \"../../../../../../../hooks/provider\"\r\nimport { useUsers } from \"../../../../../../../hooks/users/users\"\r\nimport { MenusContext } from \"../../../../../../../store\"\r\nimport { ViewerNames } from \"../../../../../../../types/dbTypes\"\r\nimport { useMarkerLayerBim } from \"../../../../../shared/markers/useMarkerLayerBim\"\r\n\r\nimport { makeCommentMarkerConfig, type CommentItem } from \"./commentMarkerConfig\"\r\n\r\nimport type { MarkerStoreAdapter } from \"../../../../../shared/markers/types\"\r\n\r\n/**\r\n * Thin wrapper over the generic `useMarkerLayerBim`: owns comment data fetching, the\r\n * author-cascade delete, and the sidebar reply/edit handoff, then hands rendering off to the\r\n * shared marker layer via `commentMarkerConfig`. Signature and return shape are unchanged —\r\n * `AddToBim/index.tsx` consumes `{ addPendingComment, removePendingComment, commentCount }`.\r\n */\r\nexport function useCommentMarkers(world: any, buildingId: number) {\r\n const { state: menusState, dispatch: menusDispatch, setIsSidebarOpen } = React.useContext(MenusContext)\r\n const { commentsVisibleInViewer, currentCommentId, focusedCommentId, focusRequestId } = menusState.menus\r\n\r\n const { comments } = useComments()\r\n const { users } = useUsers()\r\n const coreHooks = useCoreHooks()\r\n const currentUserId = useSession().data?.user?.id ?? null\r\n const { deleteComments } = useDeleteComments()\r\n\r\n const eligibleComments = comments\r\n .filter((comment) => {\r\n const { x, y, z, viewer } = comment\r\n if (x == null || y == null || z == null) return false\r\n if (viewer !== ViewerNames.bim) return false\r\n if (buildingId !== -1 && comment.buildingId !== buildingId) return false\r\n return true\r\n })\r\n .map((comment) => {\r\n const user = users.find((u) => u.id === comment.authorId)\r\n return {\r\n ...comment,\r\n authorName: user?.name ?? \"Unknown User\",\r\n imageFileId: user?.imageFileId ?? null,\r\n image: user?.imageFileId ?? null,\r\n }\r\n })\r\n\r\n const handleRemoveComment = React.useCallback((commentId: number) => {\r\n // Cascade: remove the comment and any replies (the DB self-relation does not cascade).\r\n const replyIds = comments.filter((c) => c.replyToId === commentId).map((c) => c.id)\r\n void deleteComments({ ids: [commentId, ...replyIds] })\r\n }, [comments, deleteComments])\r\n\r\n // Edit/reply on the 3D card open the comment's editor/reply box in the sidebar.\r\n const requestSidebarAction = React.useCallback((commentId: number, action: 'edit' | 'reply') => {\r\n setIsSidebarOpen(true)\r\n menusDispatch({ type: \"SET_SIDEBAR_SELECTED_TAB\", payload: { selectedTab: 'communication' } })\r\n menusDispatch({ type: \"SET_CURRENT_COMMENT_ID\", payload: { commentId } })\r\n menusDispatch({ type: \"REQUEST_COMMENT_ACTION\", payload: { commentId, action } })\r\n }, [menusDispatch, setIsSidebarOpen])\r\n\r\n const handleReplyComment = React.useCallback((commentId: number) => {\r\n requestSidebarAction(commentId, 'reply')\r\n }, [requestSidebarAction])\r\n\r\n const store: MarkerStoreAdapter = {\r\n currentId: currentCommentId,\r\n focusedId: focusedCommentId,\r\n focusRequestId,\r\n setCurrentId: (id) => menusDispatch({ type: \"SET_CURRENT_COMMENT_ID\", payload: { commentId: id } }),\r\n setFocusedId: (id) => {\r\n menusDispatch({ type: \"SET_CURRENT_COMMENT_ID\", payload: { commentId: id } })\r\n menusDispatch({ type: \"SET_FOCUSED_COMMENT_ID\", payload: { commentId: id } })\r\n },\r\n requestEdit: (id) => requestSidebarAction(id, 'edit'),\r\n }\r\n\r\n const config = makeCommentMarkerConfig({\r\n currentUserId,\r\n onRemove: handleRemoveComment,\r\n onReply: handleReplyComment,\r\n buildingId,\r\n })\r\n\r\n // Non-pending items eligible for clustering; pending (being-created) items are synthesized by\r\n // `config.makePendingItem` inside `useMarkerLayerBim` itself.\r\n // Cast: `eligibleComments` is filtered above to guarantee non-null x/y/z, which `CommentItem`\r\n // requires as plain numbers — not provable to tsc through the filter + map chain.\r\n const itemsForLayer = eligibleComments.map((c) => ({ ...c, isPending: false })) as CommentItem[]\r\n\r\n const { addPending, removePending } = useMarkerLayerBim(world, itemsForLayer, {\r\n isVisible: commentsVisibleInViewer?.includes(ViewerNames.bim) ?? false,\r\n store,\r\n coreHooks,\r\n config,\r\n })\r\n\r\n const commentCount = comments.filter(\r\n (c) => c.viewer === ViewerNames.bim && (!buildingId || buildingId === -1 || c.buildingId === buildingId),\r\n ).length\r\n\r\n return { addPendingComment: addPending, removePendingComment: removePending, commentCount }\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import { type Sensor } from "../../../../../../../types/dbTypes";
|
|
2
|
+
export declare function useSensorMarkers(world: any, buildingId: number): {
|
|
2
3
|
addPendingSensor: (position: {
|
|
3
4
|
x: number;
|
|
4
5
|
y: number;
|
|
@@ -6,5 +7,10 @@ export declare function useSensorMarkers(world: any, buildingId: number, minioBa
|
|
|
6
7
|
}) => number;
|
|
7
8
|
removePendingSensor: (id: number) => void;
|
|
8
9
|
sensorCount: number;
|
|
10
|
+
detailSensor: Sensor;
|
|
11
|
+
detailSensorType: import("../../../../../../..").SensorType;
|
|
12
|
+
closeSensorDetail: () => void;
|
|
13
|
+
editSensor: Sensor;
|
|
14
|
+
closeSensorEdit: () => void;
|
|
9
15
|
};
|
|
10
16
|
//# sourceMappingURL=useSensorMarkers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSensorMarkers.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useSensorMarkers.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.ts"],"names":[],"mappings":"AAcA,OAAO,EAAe,KAAK,MAAM,EAAE,MAAM,oCAAoC,CAAA;AAM7E,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,MAAM;iCA0DP;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAA;KAAE;8BAMtC,MAAM;;;;;;;EAqF1D"}
|
|
@@ -18,19 +18,29 @@ var __spreadValues = (a, b) => {
|
|
|
18
18
|
return a;
|
|
19
19
|
};
|
|
20
20
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
21
|
+
import { useSession } from "next-auth/react";
|
|
22
|
+
import { useTranslations } from "next-intl";
|
|
21
23
|
import * as React from "react";
|
|
22
24
|
import { useCoreHooks } from "../../../../../../../hooks/provider";
|
|
23
25
|
import { useSensors, useSensor } from "../../../../../../../hooks/sensors/sensors";
|
|
24
26
|
import { useSensorTypes } from "../../../../../../../hooks/sensorTypes/sensorTypes";
|
|
25
27
|
import { useUsers } from "../../../../../../../hooks/users/users";
|
|
26
|
-
import { MenusContext } from "../../../../../../../store";
|
|
28
|
+
import { AppConfigContext, MenusContext } from "../../../../../../../store";
|
|
27
29
|
import { ViewerNames } from "../../../../../../../types/dbTypes";
|
|
28
30
|
import { UNTAGGED_TAG } from "../../../../../../ui/Sensors/SensorsSection";
|
|
29
31
|
import BimSensor from "./BimSensor";
|
|
30
32
|
import { renderCSS2DMarkers } from "./renderCSS2DMarkers";
|
|
31
|
-
function useSensorMarkers(world, buildingId
|
|
33
|
+
function useSensorMarkers(world, buildingId) {
|
|
34
|
+
var _a;
|
|
32
35
|
const { state: menusState } = React.useContext(MenusContext);
|
|
33
36
|
const { currentSensorId, visibleSensorTypes, visibleSensorTags } = menusState.menus;
|
|
37
|
+
const { state: appConfigState } = React.useContext(AppConfigContext);
|
|
38
|
+
const timeZone = appConfigState.appConfig.displayTimeZone;
|
|
39
|
+
const sessionUser = (_a = useSession().data) == null ? void 0 : _a.user;
|
|
40
|
+
const tr = useTranslations("SensorsSection");
|
|
41
|
+
const actionLabels = { expand: tr("expandSensor"), edit: tr("editSensor"), delete: tr("deleteSensor") };
|
|
42
|
+
const [detailSensor, setDetailSensor] = React.useState(null);
|
|
43
|
+
const [editSensor, setEditSensor] = React.useState(null);
|
|
34
44
|
const registry = React.useRef(/* @__PURE__ */ new Map());
|
|
35
45
|
const { sensors } = useSensors();
|
|
36
46
|
const { users } = useUsers();
|
|
@@ -45,11 +55,11 @@ function useSensorMarkers(world, buildingId, minioBaseUrl) {
|
|
|
45
55
|
if (buildingId !== -1 && sensor.buildingId !== buildingId) return false;
|
|
46
56
|
return true;
|
|
47
57
|
}).map((sensor) => {
|
|
48
|
-
var
|
|
58
|
+
var _a2, _b;
|
|
49
59
|
const user = users.find((u) => u.id === sensor.authorId);
|
|
50
60
|
const sensorType = sensorTypes.find((t) => t.id === sensor.typeId);
|
|
51
61
|
return __spreadProps(__spreadValues({}, sensor), {
|
|
52
|
-
authorName: (
|
|
62
|
+
authorName: (_a2 = user == null ? void 0 : user.name) != null ? _a2 : "Unknown User",
|
|
53
63
|
imageFileId: (_b = user == null ? void 0 : user.imageFileId) != null ? _b : "images/default-avatar.svg",
|
|
54
64
|
sensorType
|
|
55
65
|
});
|
|
@@ -78,9 +88,9 @@ function useSensorMarkers(world, buildingId, minioBaseUrl) {
|
|
|
78
88
|
const typesVisible = (visibleSensorTypes == null ? void 0 : visibleSensorTypes[ViewerNames.bim]) || [];
|
|
79
89
|
const tagsVisible = (visibleSensorTags == null ? void 0 : visibleSensorTags[ViewerNames.bim]) || [];
|
|
80
90
|
const visibleSensors = eligibleSensorsRef.current.filter((sensor) => {
|
|
81
|
-
var
|
|
91
|
+
var _a2, _b, _c;
|
|
82
92
|
const matchesType = typesVisible.includes(sensor.typeId);
|
|
83
|
-
const hasNoTags = !((
|
|
93
|
+
const hasNoTags = !((_a2 = sensor.tags) == null ? void 0 : _a2.length);
|
|
84
94
|
const matchesTag = hasNoTags ? tagsVisible.includes(UNTAGGED_TAG) : (_c = (_b = sensor.tags) == null ? void 0 : _b.some((tag) => tagsVisible.includes(tag))) != null ? _c : false;
|
|
85
95
|
return matchesType || matchesTag;
|
|
86
96
|
});
|
|
@@ -100,6 +110,7 @@ function useSensorMarkers(world, buildingId, minioBaseUrl) {
|
|
|
100
110
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
101
111
|
buildingId,
|
|
102
112
|
tags: [],
|
|
113
|
+
authorId: -1,
|
|
103
114
|
isPending: true
|
|
104
115
|
}))
|
|
105
116
|
];
|
|
@@ -110,28 +121,45 @@ function useSensorMarkers(world, buildingId, minioBaseUrl) {
|
|
|
110
121
|
component: BimSensor,
|
|
111
122
|
hooksContextValue: coreHooks,
|
|
112
123
|
propsMapper: (sensor) => {
|
|
113
|
-
var
|
|
124
|
+
var _a2;
|
|
114
125
|
return {
|
|
115
126
|
sensorName: sensor.name,
|
|
116
127
|
sensorType: sensorTypes.find((t) => t.id === sensor.typeId),
|
|
117
128
|
tags: sensor.tags,
|
|
118
|
-
dataUrl: (
|
|
129
|
+
dataUrl: (_a2 = sensor.url) != null ? _a2 : "",
|
|
119
130
|
dataFormat: sensor.dataFormat,
|
|
120
131
|
updateFrequency: sensor.updateFrequency,
|
|
121
132
|
buildingId: sensor.buildingId,
|
|
122
133
|
timestamp: new Date(sensor.createdAt),
|
|
123
|
-
highlight: currentSensorId === sensor.id
|
|
134
|
+
highlight: currentSensorId === sensor.id,
|
|
135
|
+
timeZone,
|
|
136
|
+
showActions: true,
|
|
137
|
+
onExpand: () => setDetailSensor(sensor),
|
|
138
|
+
onEdit: () => setEditSensor(sensor),
|
|
139
|
+
actionLabels,
|
|
140
|
+
canEdit: (sessionUser == null ? void 0 : sessionUser.id) === String(sensor.authorId),
|
|
141
|
+
canDelete: (sessionUser == null ? void 0 : sessionUser.id) === String(sensor.authorId)
|
|
124
142
|
};
|
|
125
143
|
},
|
|
126
144
|
sphereColor: "#10b981",
|
|
127
145
|
isVisible: true,
|
|
128
146
|
onRemove: handleRemoveSensor
|
|
129
147
|
});
|
|
130
|
-
}, [world, sensors, pendingSensors, handleRemoveSensor, visibleSensorTypes, visibleSensorTags, buildingId, currentSensorId, sensorTypes, coreHooks]);
|
|
148
|
+
}, [world, sensors, pendingSensors, handleRemoveSensor, visibleSensorTypes, visibleSensorTags, buildingId, currentSensorId, sensorTypes, coreHooks, timeZone, sessionUser == null ? void 0 : sessionUser.id]);
|
|
131
149
|
const sensorCount = sensors.filter(
|
|
132
150
|
(s) => s.viewer === ViewerNames.bim && (!buildingId || buildingId === -1 || s.buildingId === buildingId)
|
|
133
151
|
).length;
|
|
134
|
-
|
|
152
|
+
const detailSensorType = detailSensor ? sensorTypes.find((t) => t.id === detailSensor.typeId) : void 0;
|
|
153
|
+
return {
|
|
154
|
+
addPendingSensor,
|
|
155
|
+
removePendingSensor,
|
|
156
|
+
sensorCount,
|
|
157
|
+
detailSensor,
|
|
158
|
+
detailSensorType,
|
|
159
|
+
closeSensorDetail: () => setDetailSensor(null),
|
|
160
|
+
editSensor,
|
|
161
|
+
closeSensorEdit: () => setEditSensor(null)
|
|
162
|
+
};
|
|
135
163
|
}
|
|
136
164
|
export {
|
|
137
165
|
useSensorMarkers
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.ts"],"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 { useCoreHooks } from \"../../../../../../../hooks/provider\"\r\nimport { useSensors, useSensor } from \"../../../../../../../hooks/sensors/sensors\"\r\nimport { useSensorTypes } from \"../../../../../../../hooks/sensorTypes/sensorTypes\"\r\nimport { useUsers } from \"../../../../../../../hooks/users/users\"\r\nimport { MenusContext } from \"../../../../../../../store\"\r\nimport { ViewerNames } from \"../../../../../../../types/dbTypes\"\r\nimport { UNTAGGED_TAG } from \"../../../../../../ui/Sensors/SensorsSection\"\r\n\r\nimport BimSensor from \"./BimSensor\"\r\nimport { renderCSS2DMarkers, type MarkerRef } from \"./renderCSS2DMarkers\"\r\n//import { useAppConfigContext } from \"../../../../../../../store/AppConfig/context\"\r\n\r\nexport function useSensorMarkers(world: any, buildingId: number, minioBaseUrl?: string) {\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { currentSensorId, visibleSensorTypes, visibleSensorTags } = menusState.menus\r\n //const { state: appConfigState } = useAppConfigContext()\r\n\r\n const registry = React.useRef<Map<string, MarkerRef>>(new Map())\r\n\r\n const { sensors } = useSensors()\r\n const { users } = useUsers()\r\n const { sensorTypes } = useSensorTypes()\r\n const coreHooks = useCoreHooks()\r\n\r\n const [sensorToDelete, setSensorToDelete] = React.useState<number | null>(null)\r\n const { deleteSensor } = useSensor(sensorToDelete)\r\n\r\n const eligibleSensors = sensors\r\n .filter((sensor) => {\r\n const { x, y, z, viewer } = sensor\r\n if (x == null || y == null || z == null) return false\r\n if (viewer !== ViewerNames.bim) return false\r\n if (buildingId !== -1 && sensor.buildingId !== buildingId) return false\r\n return true\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 imageFileId: user?.imageFileId ?? \"images/default-avatar.svg\",\r\n sensorType,\r\n }\r\n })\r\n\r\n // Stable ref so the render effect reads current sensors without re-running on every change\r\n const eligibleSensorsRef = React.useRef(eligibleSensors)\r\n eligibleSensorsRef.current = eligibleSensors\r\n\r\n React.useEffect(() => {\r\n if (sensorToDelete !== null) {\r\n deleteSensor()\r\n setSensorToDelete(null)\r\n }\r\n }, [sensorToDelete, deleteSensor])\r\n\r\n const handleRemoveSensor = React.useCallback((sensorId: number) => {\r\n setSensorToDelete(sensorId)\r\n }, [])\r\n\r\n // Pending markers for loading indicators while sensor is being created\r\n const [pendingSensors, setPendingSensors] = React.useState<Array<{ id: number; x: number; y: number; z: number }>>([])\r\n\r\n const addPendingSensor = React.useCallback((position: { x: number; y: number; z: number }) => {\r\n const id = -Date.now()\r\n setPendingSensors(prev => [...prev, { id, ...position }])\r\n return id\r\n }, [])\r\n\r\n const removePendingSensor = React.useCallback((id: number) => {\r\n setPendingSensors(prev => prev.filter(p => p.id !== id))\r\n }, [])\r\n\r\n // Render sensor markers with visibility filtering\r\n React.useEffect(() => {\r\n const typesVisible = visibleSensorTypes?.[ViewerNames.bim] || []\r\n const tagsVisible = visibleSensorTags?.[ViewerNames.bim] || []\r\n\r\n const visibleSensors = eligibleSensorsRef.current.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: string) => tagsVisible.includes(tag)) ?? false\r\n return matchesType || matchesTag\r\n })\r\n\r\n const allSensors = [\r\n ...visibleSensors.map(s => ({ ...s, isPending: false })),\r\n ...pendingSensors.map(p => ({\r\n id: p.id,\r\n name: \"Creating...\",\r\n sensorType: undefined,\r\n typeId: undefined,\r\n x: p.x,\r\n y: p.y,\r\n z: p.z,\r\n url: \"\",\r\n dataFormat: \"Json\",\r\n updateFrequency: 0,\r\n createdAt: new Date().toISOString(),\r\n buildingId,\r\n tags: [],\r\n isPending: true,\r\n })),\r\n ]\r\n\r\n renderCSS2DMarkers(world, {\r\n items: allSensors,\r\n markerIdKey: \"sensorId\",\r\n registry,\r\n component: BimSensor,\r\n hooksContextValue: coreHooks,\r\n propsMapper: (sensor) => ({\r\n sensorName: sensor.name,\r\n sensorType: sensorTypes.find(t => t.id === sensor.typeId),\r\n tags: sensor.tags,\r\n dataUrl: sensor.url?.startsWith(\"http\")\r\n ? sensor.url\r\n : minioBaseUrl\r\n ? `${minioBaseUrl}/sensors/${sensor.url}`\r\n : \"\",\r\n dataFormat: sensor.dataFormat,\r\n updateFrequency: sensor.updateFrequency,\r\n buildingId: sensor.buildingId,\r\n timestamp: new Date(sensor.createdAt),\r\n highlight: currentSensorId === sensor.id,\r\n }),\r\n sphereColor: \"#10b981\",\r\n isVisible: true,\r\n onRemove: handleRemoveSensor,\r\n })\r\n }, [world, sensors, pendingSensors, handleRemoveSensor, visibleSensorTypes, visibleSensorTags, buildingId, currentSensorId, sensorTypes, coreHooks])\r\n\r\n const sensorCount = sensors.filter(\r\n s => s.viewer === ViewerNames.bim && (!buildingId || buildingId === -1 || s.buildingId === buildingId)\r\n ).length\r\n\r\n return { addPendingSensor, removePendingSensor, sensorCount }\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../src/core/components/viewers/bim/src/tools/AddToBim/src/useSensorMarkers.ts"],"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 { useSession } from \"next-auth/react\"\r\nimport { useTranslations } from \"next-intl\"\r\nimport * as React from \"react\"\r\n\r\nimport { useCoreHooks } from \"../../../../../../../hooks/provider\"\r\nimport { useSensors, useSensor } from \"../../../../../../../hooks/sensors/sensors\"\r\nimport { useSensorTypes } from \"../../../../../../../hooks/sensorTypes/sensorTypes\"\r\nimport { useUsers } from \"../../../../../../../hooks/users/users\"\r\nimport { AppConfigContext, MenusContext } from \"../../../../../../../store\"\r\nimport { ViewerNames, type Sensor } from \"../../../../../../../types/dbTypes\"\r\nimport { UNTAGGED_TAG } from \"../../../../../../ui/Sensors/SensorsSection\"\r\n\r\nimport BimSensor from \"./BimSensor\"\r\nimport { renderCSS2DMarkers, type MarkerRef } from \"./renderCSS2DMarkers\"\r\n\r\nexport function useSensorMarkers(world: any, buildingId: number) {\r\n const { state: menusState } = React.useContext(MenusContext)\r\n const { currentSensorId, visibleSensorTypes, visibleSensorTags } = menusState.menus\r\n const { state: appConfigState } = React.useContext(AppConfigContext)\r\n const timeZone = appConfigState.appConfig.displayTimeZone\r\n const sessionUser = useSession().data?.user\r\n const tr = useTranslations('SensorsSection')\r\n const actionLabels = { expand: tr('expandSensor'), edit: tr('editSensor'), delete: tr('deleteSensor') }\r\n const [detailSensor, setDetailSensor] = React.useState<Sensor | null>(null)\r\n const [editSensor, setEditSensor] = React.useState<Sensor | null>(null)\r\n\r\n const registry = React.useRef<Map<string, MarkerRef>>(new Map())\r\n\r\n const { sensors } = useSensors()\r\n const { users } = useUsers()\r\n const { sensorTypes } = useSensorTypes()\r\n const coreHooks = useCoreHooks()\r\n\r\n const [sensorToDelete, setSensorToDelete] = React.useState<number | null>(null)\r\n const { deleteSensor } = useSensor(sensorToDelete)\r\n\r\n const eligibleSensors = sensors\r\n .filter((sensor) => {\r\n const { x, y, z, viewer } = sensor\r\n if (x == null || y == null || z == null) return false\r\n if (viewer !== ViewerNames.bim) return false\r\n if (buildingId !== -1 && sensor.buildingId !== buildingId) return false\r\n return true\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 imageFileId: user?.imageFileId ?? \"images/default-avatar.svg\",\r\n sensorType,\r\n }\r\n })\r\n\r\n // Stable ref so the render effect reads current sensors without re-running on every change\r\n const eligibleSensorsRef = React.useRef(eligibleSensors)\r\n eligibleSensorsRef.current = eligibleSensors\r\n\r\n React.useEffect(() => {\r\n if (sensorToDelete !== null) {\r\n deleteSensor()\r\n setSensorToDelete(null)\r\n }\r\n }, [sensorToDelete, deleteSensor])\r\n\r\n const handleRemoveSensor = React.useCallback((sensorId: number) => {\r\n setSensorToDelete(sensorId)\r\n }, [])\r\n\r\n // Pending markers for loading indicators while sensor is being created\r\n const [pendingSensors, setPendingSensors] = React.useState<Array<{ id: number; x: number; y: number; z: number }>>([])\r\n\r\n const addPendingSensor = React.useCallback((position: { x: number; y: number; z: number }) => {\r\n const id = -Date.now()\r\n setPendingSensors(prev => [...prev, { id, ...position }])\r\n return id\r\n }, [])\r\n\r\n const removePendingSensor = React.useCallback((id: number) => {\r\n setPendingSensors(prev => prev.filter(p => p.id !== id))\r\n }, [])\r\n\r\n // Render sensor markers with visibility filtering\r\n React.useEffect(() => {\r\n const typesVisible = visibleSensorTypes?.[ViewerNames.bim] || []\r\n const tagsVisible = visibleSensorTags?.[ViewerNames.bim] || []\r\n\r\n const visibleSensors = eligibleSensorsRef.current.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: string) => tagsVisible.includes(tag)) ?? false\r\n return matchesType || matchesTag\r\n })\r\n\r\n const allSensors = [\r\n ...visibleSensors.map(s => ({ ...s, isPending: false })),\r\n ...pendingSensors.map(p => ({\r\n id: p.id,\r\n name: \"Creating...\",\r\n sensorType: undefined,\r\n typeId: undefined,\r\n x: p.x,\r\n y: p.y,\r\n z: p.z,\r\n url: \"\",\r\n dataFormat: \"Json\",\r\n updateFrequency: 0,\r\n createdAt: new Date().toISOString(),\r\n buildingId,\r\n tags: [],\r\n authorId: -1,\r\n isPending: true,\r\n })),\r\n ]\r\n\r\n renderCSS2DMarkers(world, {\r\n items: allSensors,\r\n markerIdKey: \"sensorId\",\r\n registry,\r\n component: BimSensor,\r\n hooksContextValue: coreHooks,\r\n propsMapper: (sensor) => ({\r\n sensorName: sensor.name,\r\n sensorType: sensorTypes.find(t => t.id === sensor.typeId),\r\n tags: sensor.tags,\r\n dataUrl: sensor.url ?? \"\",\r\n dataFormat: sensor.dataFormat,\r\n updateFrequency: sensor.updateFrequency,\r\n buildingId: sensor.buildingId,\r\n timestamp: new Date(sensor.createdAt),\r\n highlight: currentSensorId === sensor.id,\r\n timeZone,\r\n showActions: true,\r\n onExpand: () => setDetailSensor(sensor as unknown as Sensor),\r\n onEdit: () => setEditSensor(sensor as unknown as Sensor),\r\n actionLabels,\r\n canEdit: sessionUser?.id === String(sensor.authorId),\r\n canDelete: sessionUser?.id === String(sensor.authorId),\r\n }),\r\n sphereColor: \"#10b981\",\r\n isVisible: true,\r\n onRemove: handleRemoveSensor,\r\n })\r\n }, [world, sensors, pendingSensors, handleRemoveSensor, visibleSensorTypes, visibleSensorTags, buildingId, currentSensorId, sensorTypes, coreHooks, timeZone, sessionUser?.id])\r\n\r\n const sensorCount = sensors.filter(\r\n s => s.viewer === ViewerNames.bim && (!buildingId || buildingId === -1 || s.buildingId === buildingId)\r\n ).length\r\n\r\n const detailSensorType = detailSensor ? sensorTypes.find(t => t.id === detailSensor.typeId) : undefined\r\n\r\n return {\r\n addPendingSensor,\r\n removePendingSensor,\r\n sensorCount,\r\n detailSensor,\r\n detailSensorType,\r\n closeSensorDetail: () => setDetailSensor(null),\r\n editSensor,\r\n closeSensorEdit: () => setEditSensor(null),\r\n }\r\n}\r\n"],"mappings":"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","names":["_a"]}
|
|
@@ -4,9 +4,8 @@ interface Props {
|
|
|
4
4
|
width?: string;
|
|
5
5
|
height?: string;
|
|
6
6
|
organization: Organization;
|
|
7
|
-
minioBaseUrl?: string;
|
|
8
7
|
maptilerKey?: string;
|
|
9
8
|
}
|
|
10
|
-
export declare function MapViewer({ width, height, organization,
|
|
9
|
+
export declare function MapViewer({ width, height, organization, maptilerKey }: Props): React.JSX.Element;
|
|
11
10
|
export {};
|
|
12
11
|
//# sourceMappingURL=MapViewer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapViewer.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/viewers/map/MapViewer.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAA;AAkBzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAa1D,UAAU,KAAK;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,YAAY,CAAA;IAC1B,
|
|
1
|
+
{"version":3,"file":"MapViewer.d.ts","sourceRoot":"","sources":["../../../../../src/core/components/viewers/map/MapViewer.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAA;AAkBzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAa1D,UAAU,KAAK;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,YAAY,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,SAAS,CAAC,EAAE,KAAc,EAAE,MAAe,EAAE,YAAY,EAAE,WAAW,EAAE,EAAE,KAAK,qBAuL9F"}
|
|
@@ -20,7 +20,7 @@ const CANADA_DEFAULTS = {
|
|
|
20
20
|
};
|
|
21
21
|
const DEFAULT_MAP_STYLE = { name: "Satellite", url: "mapStyles/satellite.json" };
|
|
22
22
|
const MAX_GLOBE_ZOOM = 5;
|
|
23
|
-
function MapViewer({ width = "100%", height = "100%", organization,
|
|
23
|
+
function MapViewer({ width = "100%", height = "100%", organization, maptilerKey }) {
|
|
24
24
|
var _a, _b, _c;
|
|
25
25
|
const searchParams = useSearchParams();
|
|
26
26
|
const [isMapLoaded, setIsMapLoaded] = React.useState(false);
|
|
@@ -144,7 +144,7 @@ function MapViewer({ width = "100%", height = "100%", organization, minioBaseUrl
|
|
|
144
144
|
/* @__PURE__ */ jsx(NavigationControl, { visualizePitch: true }),
|
|
145
145
|
/* @__PURE__ */ jsx(SettingsButton, {}),
|
|
146
146
|
isMapLoaded && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
147
|
-
/* @__PURE__ */ jsx(MapLayers, {
|
|
147
|
+
/* @__PURE__ */ jsx(MapLayers, { organization, maptilerKey }),
|
|
148
148
|
/* @__PURE__ */ jsxs("div", { className: "absolute bottom-[10px] left-3 z-10 flex flex-col gap-2 pointer-events-none", children: [
|
|
149
149
|
/* @__PURE__ */ jsx("div", { id: "wms-time-slot", style: { display: "contents" } }),
|
|
150
150
|
/* @__PURE__ */ jsx(MapLegendHost, {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../src/core/components/viewers/map/MapViewer.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 maplibregl from 'maplibre-gl'\r\nimport { useSearchParams } from 'next/navigation'\r\nimport React from 'react'\r\nimport Map, { NavigationControl } from 'react-map-gl/maplibre'\r\n\r\n\r\nimport { MapContext } from '../../../store'\r\nimport SettingsButton from '../../ui/SettingsButton'\r\nimport { StatsOverlay } from '../../ui/stats'\r\n\r\nimport DatasetManagerMenu from './datasets/DatasetManager'\r\nimport { MapLegendHost } from './legends/MapLegendHost'\r\nimport { MapLayers } from './src/MapLayers'\r\nimport { MapClickManager } from './utils/MapEventManager/MapClickManager'\r\nimport { MapHoverManager } from './utils/MapEventManager/MapHoverManager'\r\nimport { resolveBounds } from './utils/validateBounds'\r\n\r\n\r\n\r\n\r\nimport type { Organization } from '../../../types/dbTypes'\r\nimport type { CurrentLocation } from '../../../types/map'\r\nimport type { MapRef} from 'react-map-gl/maplibre';\r\n\r\nconst CANADA_DEFAULTS = {\r\n zoom: 3,\r\n lat: 56.415,\r\n long: -98.74,\r\n} as const\r\n\r\nconst DEFAULT_MAP_STYLE = { name: 'Satellite', url: 'mapStyles/satellite.json' } as const\r\nconst MAX_GLOBE_ZOOM = 5\r\n\r\ninterface Props {\r\n width?: string\r\n height?: string\r\n organization: Organization\r\n minioBaseUrl?: string\r\n maptilerKey?: string\r\n}\r\n\r\nexport function MapViewer({ width = '100%', height = '100%', organization, minioBaseUrl, maptilerKey }: Props) {\r\n\r\n const searchParams = useSearchParams()\r\n\r\n // Add state to track if map is loaded\r\n const [isMapLoaded, setIsMapLoaded] = React.useState(false)\r\n\r\n const viewState = React.useMemo(() => {\r\n return searchParams.size === 0\r\n ? {\r\n zoom: organization.zoom ?? CANADA_DEFAULTS.zoom,\r\n bearing: organization.bearing ?? 0,\r\n pitch: organization.pitch ?? 0,\r\n longitude: organization.long ?? CANADA_DEFAULTS.long,\r\n latitude: organization.lat ?? CANADA_DEFAULTS.lat,\r\n }\r\n : {\r\n latitude: searchParams.has('lat') ? Number.parseFloat(searchParams.get('lat')) : organization.lat ?? CANADA_DEFAULTS.lat,\r\n longitude: searchParams.has('lng') ? Number.parseFloat(searchParams.get('lng')) : organization.long ?? CANADA_DEFAULTS.long,\r\n bearing: searchParams.has('bearing') ? Number.parseFloat(searchParams.get('bearing')) : organization.bearing ?? 0,\r\n pitch: searchParams.has('pitch') ? Number.parseFloat(searchParams.get('pitch')) : organization.pitch ?? 0,\r\n zoom: searchParams.has('zoom') ? Number.parseFloat(searchParams.get('zoom')) : organization.zoom ?? CANADA_DEFAULTS.zoom,\r\n }\r\n }, [searchParams, organization])\r\n\r\n const initialProjection = viewState.zoom > MAX_GLOBE_ZOOM ? 'mercator' : 'globe'\r\n\r\n // Memoize the inline style object that gets passed to <Map> for a stable prop identity.\r\n const mapContainerStyle = React.useMemo(\r\n () => ({ width, height, backgroundColor: 'black' }),\r\n [width, height],\r\n )\r\n\r\n // Adaptive pixel ratio. A hardcoded 2 doubled fragment-shader cost on 1× DPR\r\n // desktops for zero visual gain; capping at 2 keeps phones (DPR 3-4) effective.\r\n const pixelRatio = React.useMemo(\r\n () => Math.min(typeof window !== 'undefined' ? window.devicePixelRatio : 1, 2),\r\n [],\r\n )\r\n\r\n const mapRef = React.useRef<MapRef>(null)\r\n const { dispatch: mapDispatch, state: mapState } = React.useContext(MapContext)\r\n\r\n // Keep the projection in sync with zoom at all times — not only while the\r\n // map-settings sidebar is mounted. The globe projection degrades past\r\n // MAX_GLOBE_ZOOM, so we force mercator regardless of which panels are open.\r\n const activeMap = mapState?.map?.map\r\n\r\n React.useEffect(() => {\r\n if (!activeMap) return\r\n\r\n const enforceProjectionForZoom = () => {\r\n if (activeMap.getZoom() <= MAX_GLOBE_ZOOM) return\r\n\r\n const projection = activeMap.getProjection()\r\n const currentType =\r\n typeof projection === 'string'\r\n ? projection\r\n : (projection as { type?: string; name?: string })?.type ?? (projection as { type?: string; name?: string })?.name\r\n\r\n if (currentType !== 'mercator') {\r\n activeMap.setProjection({ type: 'mercator' })\r\n }\r\n }\r\n\r\n enforceProjectionForZoom()\r\n activeMap.on('zoom', enforceProjectionForZoom)\r\n\r\n return () => {\r\n activeMap.off('zoom', enforceProjectionForZoom)\r\n }\r\n }, [activeMap])\r\n\r\n const handleMapLoad = () => {\r\n const map = mapRef.current.getMap()\r\n\r\n if (map.getZoom() > MAX_GLOBE_ZOOM) {\r\n map.setProjection({ type: 'mercator' })\r\n }\r\n\r\n // Only dispatch SET_MAP after map is fully loaded\r\n if (mapRef.current) {\r\n mapDispatch({\r\n type: 'SET_MAP',\r\n payload: { map },\r\n })\r\n }\r\n // initilize a new click manager\r\n const mapClickManager = new MapClickManager(map)\r\n mapDispatch({\r\n type: 'ADD_MAP_CLICK_MANAGER',\r\n payload: { mapClickManager },\r\n })\r\n\r\n const mapHoverManager = new MapHoverManager(map)\r\n mapDispatch({\r\n type: 'ADD_MAP_HOVER_MANAGER',\r\n payload: { mapHoverManager },\r\n })\r\n\r\n // update current Location with URL params or fallback to theme location\r\n const updatedLocation: CurrentLocation = {\r\n // Then override with URL params (this ensures URL params take precedence)\r\n lat: searchParams.has('lat') ? Number.parseFloat(searchParams.get('lat')) : organization.lat ?? CANADA_DEFAULTS.lat,\r\n lng: searchParams.has('lng') ? Number.parseFloat(searchParams.get('lng')) : organization.long ?? CANADA_DEFAULTS.long,\r\n bearing: searchParams.has('bearing') ? Number.parseFloat(searchParams.get('bearing')) : organization.bearing ?? 0,\r\n pitch: searchParams.has('pitch') ? Number.parseFloat(searchParams.get('pitch')) : organization.pitch ?? 0,\r\n zoom: searchParams.has('zoom') ? Number.parseFloat(searchParams.get('zoom')) : organization.zoom ?? CANADA_DEFAULTS.zoom,\r\n // Add location details from URL params if available\r\n countrySubdivision: searchParams.has('countrySubdivision') ? searchParams.get('countrySubdivision') : (organization.countrySubdivision ?? ''),\r\n municipality: searchParams.has('municipality') ? searchParams.get('municipality') : (organization.municipality ?? ''),\r\n address: searchParams.has('address') ? searchParams.get('address') : '', // organization.address,\r\n site: searchParams.has('site') ? searchParams.get('site') : String(organization.locationSiteId ?? ''),\r\n id: String(organization.id),\r\n }\r\n\r\n mapDispatch({\r\n type: 'UPDATE_LOCATION',\r\n payload: { currentLocation: updatedLocation },\r\n })\r\n\r\n // Set map as loaded after all dispatches are complete\r\n setIsMapLoaded(true)\r\n }\r\n\r\n // Enable the perf HUD by appending ?debug=1 to the URL. Never reaches\r\n // production users unless they explicitly opt in.\r\n const isDebug = searchParams.get('debug') === '1'\r\n\r\n // useCallback so the onDblClick prop identity is stable across renders of\r\n // MapViewer; a new function each render caused react-map-gl to detach/re-attach\r\n // the listener. The console.log is intentional dev-only coordinate debugging.\r\n const onDblClick = React.useCallback((e) => {\r\n if (process.env.NODE_ENV === 'production') return\r\n const { lng, lat } = e.lngLat\r\n const elevation = mapRef.current?.queryTerrainElevation([lng, lat]) || 0\r\n const coordinates = { lng, lat, elevation, zoom: mapRef.current?.getZoom() }\r\n // eslint-disable-next-line no-console -- intentional dev-mode coordinate logging\r\n console.log('📍 Map coordinates:', coordinates)\r\n }, [])\r\n\r\n const mapStyle = mapState?.map.mapStyle ?? DEFAULT_MAP_STYLE;\r\n\r\n return (\r\n <>\r\n <Map\r\n id=\"map-component\"\r\n mapStyle={mapStyle.url}\r\n mapLib={maplibregl}\r\n onLoad={handleMapLoad}\r\n ref={mapRef}\r\n style={mapContainerStyle}\r\n initialViewState={viewState}\r\n maxPitch={60}\r\n minZoom={organization?.minZoom ?? CANADA_DEFAULTS.zoom}\r\n maxBounds={resolveBounds(organization?.maxBounds)}\r\n projection={initialProjection}\r\n doubleClickZoom={false}\r\n onDblClick={onDblClick}\r\n pixelRatio={pixelRatio}\r\n >\r\n <NavigationControl visualizePitch />\r\n <SettingsButton />\r\n {isMapLoaded\r\n && (\r\n <>\r\n <MapLayers minioBaseUrl={minioBaseUrl} organization={organization} maptilerKey={maptilerKey} />\r\n {/* Bottom-left stack: legend above the layers/styling card, gap auto-managed by flex. */}\r\n <div className=\"absolute bottom-[10px] left-3 z-10 flex flex-col gap-2 pointer-events-none\">\r\n {/* Portal slot for on-map WMS time controls; display:contents so an\r\n empty slot adds no flex item / gap, but a mounted control stacks\r\n above the legend + dataset-manager cards. */}\r\n <div id=\"wms-time-slot\" style={{ display: 'contents' }} />\r\n <MapLegendHost />\r\n <DatasetManagerMenu />\r\n </div>\r\n </>\r\n )}\r\n </Map>\r\n <StatsOverlay mapRef={mapRef} enabled={isDebug} />\r\n </>\r\n\r\n )\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c"]}
|
|
1
|
+
{"version":3,"sources":["../../../../../src/core/components/viewers/map/MapViewer.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 maplibregl from 'maplibre-gl'\r\nimport { useSearchParams } from 'next/navigation'\r\nimport React from 'react'\r\nimport Map, { NavigationControl } from 'react-map-gl/maplibre'\r\n\r\n\r\nimport { MapContext } from '../../../store'\r\nimport SettingsButton from '../../ui/SettingsButton'\r\nimport { StatsOverlay } from '../../ui/stats'\r\n\r\nimport DatasetManagerMenu from './datasets/DatasetManager'\r\nimport { MapLegendHost } from './legends/MapLegendHost'\r\nimport { MapLayers } from './src/MapLayers'\r\nimport { MapClickManager } from './utils/MapEventManager/MapClickManager'\r\nimport { MapHoverManager } from './utils/MapEventManager/MapHoverManager'\r\nimport { resolveBounds } from './utils/validateBounds'\r\n\r\n\r\n\r\n\r\nimport type { Organization } from '../../../types/dbTypes'\r\nimport type { CurrentLocation } from '../../../types/map'\r\nimport type { MapRef} from 'react-map-gl/maplibre';\r\n\r\nconst CANADA_DEFAULTS = {\r\n zoom: 3,\r\n lat: 56.415,\r\n long: -98.74,\r\n} as const\r\n\r\nconst DEFAULT_MAP_STYLE = { name: 'Satellite', url: 'mapStyles/satellite.json' } as const\r\nconst MAX_GLOBE_ZOOM = 5\r\n\r\ninterface Props {\r\n width?: string\r\n height?: string\r\n organization: Organization\r\n maptilerKey?: string\r\n}\r\n\r\nexport function MapViewer({ width = '100%', height = '100%', organization, maptilerKey }: Props) {\r\n\r\n const searchParams = useSearchParams()\r\n\r\n // Add state to track if map is loaded\r\n const [isMapLoaded, setIsMapLoaded] = React.useState(false)\r\n\r\n const viewState = React.useMemo(() => {\r\n return searchParams.size === 0\r\n ? {\r\n zoom: organization.zoom ?? CANADA_DEFAULTS.zoom,\r\n bearing: organization.bearing ?? 0,\r\n pitch: organization.pitch ?? 0,\r\n longitude: organization.long ?? CANADA_DEFAULTS.long,\r\n latitude: organization.lat ?? CANADA_DEFAULTS.lat,\r\n }\r\n : {\r\n latitude: searchParams.has('lat') ? Number.parseFloat(searchParams.get('lat')) : organization.lat ?? CANADA_DEFAULTS.lat,\r\n longitude: searchParams.has('lng') ? Number.parseFloat(searchParams.get('lng')) : organization.long ?? CANADA_DEFAULTS.long,\r\n bearing: searchParams.has('bearing') ? Number.parseFloat(searchParams.get('bearing')) : organization.bearing ?? 0,\r\n pitch: searchParams.has('pitch') ? Number.parseFloat(searchParams.get('pitch')) : organization.pitch ?? 0,\r\n zoom: searchParams.has('zoom') ? Number.parseFloat(searchParams.get('zoom')) : organization.zoom ?? CANADA_DEFAULTS.zoom,\r\n }\r\n }, [searchParams, organization])\r\n\r\n const initialProjection = viewState.zoom > MAX_GLOBE_ZOOM ? 'mercator' : 'globe'\r\n\r\n // Memoize the inline style object that gets passed to <Map> for a stable prop identity.\r\n const mapContainerStyle = React.useMemo(\r\n () => ({ width, height, backgroundColor: 'black' }),\r\n [width, height],\r\n )\r\n\r\n // Adaptive pixel ratio. A hardcoded 2 doubled fragment-shader cost on 1× DPR\r\n // desktops for zero visual gain; capping at 2 keeps phones (DPR 3-4) effective.\r\n const pixelRatio = React.useMemo(\r\n () => Math.min(typeof window !== 'undefined' ? window.devicePixelRatio : 1, 2),\r\n [],\r\n )\r\n\r\n const mapRef = React.useRef<MapRef>(null)\r\n const { dispatch: mapDispatch, state: mapState } = React.useContext(MapContext)\r\n\r\n // Keep the projection in sync with zoom at all times — not only while the\r\n // map-settings sidebar is mounted. The globe projection degrades past\r\n // MAX_GLOBE_ZOOM, so we force mercator regardless of which panels are open.\r\n const activeMap = mapState?.map?.map\r\n\r\n React.useEffect(() => {\r\n if (!activeMap) return\r\n\r\n const enforceProjectionForZoom = () => {\r\n if (activeMap.getZoom() <= MAX_GLOBE_ZOOM) return\r\n\r\n const projection = activeMap.getProjection()\r\n const currentType =\r\n typeof projection === 'string'\r\n ? projection\r\n : (projection as { type?: string; name?: string })?.type ?? (projection as { type?: string; name?: string })?.name\r\n\r\n if (currentType !== 'mercator') {\r\n activeMap.setProjection({ type: 'mercator' })\r\n }\r\n }\r\n\r\n enforceProjectionForZoom()\r\n activeMap.on('zoom', enforceProjectionForZoom)\r\n\r\n return () => {\r\n activeMap.off('zoom', enforceProjectionForZoom)\r\n }\r\n }, [activeMap])\r\n\r\n const handleMapLoad = () => {\r\n const map = mapRef.current.getMap()\r\n\r\n if (map.getZoom() > MAX_GLOBE_ZOOM) {\r\n map.setProjection({ type: 'mercator' })\r\n }\r\n\r\n // Only dispatch SET_MAP after map is fully loaded\r\n if (mapRef.current) {\r\n mapDispatch({\r\n type: 'SET_MAP',\r\n payload: { map },\r\n })\r\n }\r\n // initilize a new click manager\r\n const mapClickManager = new MapClickManager(map)\r\n mapDispatch({\r\n type: 'ADD_MAP_CLICK_MANAGER',\r\n payload: { mapClickManager },\r\n })\r\n\r\n const mapHoverManager = new MapHoverManager(map)\r\n mapDispatch({\r\n type: 'ADD_MAP_HOVER_MANAGER',\r\n payload: { mapHoverManager },\r\n })\r\n\r\n // update current Location with URL params or fallback to theme location\r\n const updatedLocation: CurrentLocation = {\r\n // Then override with URL params (this ensures URL params take precedence)\r\n lat: searchParams.has('lat') ? Number.parseFloat(searchParams.get('lat')) : organization.lat ?? CANADA_DEFAULTS.lat,\r\n lng: searchParams.has('lng') ? Number.parseFloat(searchParams.get('lng')) : organization.long ?? CANADA_DEFAULTS.long,\r\n bearing: searchParams.has('bearing') ? Number.parseFloat(searchParams.get('bearing')) : organization.bearing ?? 0,\r\n pitch: searchParams.has('pitch') ? Number.parseFloat(searchParams.get('pitch')) : organization.pitch ?? 0,\r\n zoom: searchParams.has('zoom') ? Number.parseFloat(searchParams.get('zoom')) : organization.zoom ?? CANADA_DEFAULTS.zoom,\r\n // Add location details from URL params if available\r\n countrySubdivision: searchParams.has('countrySubdivision') ? searchParams.get('countrySubdivision') : (organization.countrySubdivision ?? ''),\r\n municipality: searchParams.has('municipality') ? searchParams.get('municipality') : (organization.municipality ?? ''),\r\n address: searchParams.has('address') ? searchParams.get('address') : '', // organization.address,\r\n site: searchParams.has('site') ? searchParams.get('site') : String(organization.locationSiteId ?? ''),\r\n id: String(organization.id),\r\n }\r\n\r\n mapDispatch({\r\n type: 'UPDATE_LOCATION',\r\n payload: { currentLocation: updatedLocation },\r\n })\r\n\r\n // Set map as loaded after all dispatches are complete\r\n setIsMapLoaded(true)\r\n }\r\n\r\n // Enable the perf HUD by appending ?debug=1 to the URL. Never reaches\r\n // production users unless they explicitly opt in.\r\n const isDebug = searchParams.get('debug') === '1'\r\n\r\n // useCallback so the onDblClick prop identity is stable across renders of\r\n // MapViewer; a new function each render caused react-map-gl to detach/re-attach\r\n // the listener. The console.log is intentional dev-only coordinate debugging.\r\n const onDblClick = React.useCallback((e) => {\r\n if (process.env.NODE_ENV === 'production') return\r\n const { lng, lat } = e.lngLat\r\n const elevation = mapRef.current?.queryTerrainElevation([lng, lat]) || 0\r\n const coordinates = { lng, lat, elevation, zoom: mapRef.current?.getZoom() }\r\n // eslint-disable-next-line no-console -- intentional dev-mode coordinate logging\r\n console.log('📍 Map coordinates:', coordinates)\r\n }, [])\r\n\r\n const mapStyle = mapState?.map.mapStyle ?? DEFAULT_MAP_STYLE;\r\n\r\n return (\r\n <>\r\n <Map\r\n id=\"map-component\"\r\n mapStyle={mapStyle.url}\r\n mapLib={maplibregl}\r\n onLoad={handleMapLoad}\r\n ref={mapRef}\r\n style={mapContainerStyle}\r\n initialViewState={viewState}\r\n maxPitch={60}\r\n minZoom={organization?.minZoom ?? CANADA_DEFAULTS.zoom}\r\n maxBounds={resolveBounds(organization?.maxBounds)}\r\n projection={initialProjection}\r\n doubleClickZoom={false}\r\n onDblClick={onDblClick}\r\n pixelRatio={pixelRatio}\r\n >\r\n <NavigationControl visualizePitch />\r\n <SettingsButton />\r\n {isMapLoaded\r\n && (\r\n <>\r\n <MapLayers organization={organization} maptilerKey={maptilerKey} />\r\n {/* Bottom-left stack: legend above the layers/styling card, gap auto-managed by flex. */}\r\n <div className=\"absolute bottom-[10px] left-3 z-10 flex flex-col gap-2 pointer-events-none\">\r\n {/* Portal slot for on-map WMS time controls; display:contents so an\r\n empty slot adds no flex item / gap, but a mounted control stacks\r\n above the legend + dataset-manager cards. */}\r\n <div id=\"wms-time-slot\" style={{ display: 'contents' }} />\r\n <MapLegendHost />\r\n <DatasetManagerMenu />\r\n </div>\r\n </>\r\n )}\r\n </Map>\r\n <StatsOverlay mapRef={mapRef} enabled={isDebug} />\r\n </>\r\n\r\n )\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c"]}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import type { Organization } from '../../../../../types/dbTypes';
|
|
3
|
-
export declare const MapLayers: ({
|
|
4
|
-
minioBaseUrl?: string;
|
|
3
|
+
export declare const MapLayers: ({ organization, maptilerKey }: {
|
|
5
4
|
organization?: Organization;
|
|
6
5
|
maptilerKey?: string;
|
|
7
6
|
}) => React.JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/map/src/MapLayers/index.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAa9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAahE,eAAO,MAAM,SAAS,GAAI
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/map/src/MapLayers/index.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAa9B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAA;AAahE,eAAO,MAAM,SAAS,GAAI,+BAA+B;IAAE,YAAY,CAAC,EAAE,YAAY,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,sBAa7G,CAAA"}
|
|
@@ -18,14 +18,14 @@ function BimLayerGate() {
|
|
|
18
18
|
if (bimState.bim.bimModelsAddedToMap.length === 0) return null;
|
|
19
19
|
return /* @__PURE__ */ jsx(LazyBimLayer, {});
|
|
20
20
|
}
|
|
21
|
-
const MapLayers = ({
|
|
21
|
+
const MapLayers = ({ organization, maptilerKey }) => {
|
|
22
22
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
23
23
|
/* @__PURE__ */ jsx(SiteLayer, {}),
|
|
24
24
|
/* @__PURE__ */ jsx(CountryLayer, { organization, maptilerKey }),
|
|
25
25
|
/* @__PURE__ */ jsx(OpenDataLayers, {}),
|
|
26
26
|
/* @__PURE__ */ jsx(BuildingLayer, {}),
|
|
27
27
|
/* @__PURE__ */ jsx(CommentLayer, {}),
|
|
28
|
-
/* @__PURE__ */ jsx(SensorLayers, {
|
|
28
|
+
/* @__PURE__ */ jsx(SensorLayers, {}),
|
|
29
29
|
/* @__PURE__ */ jsx(FileLayers, {}),
|
|
30
30
|
/* @__PURE__ */ jsx(BimLayerGate, {})
|
|
31
31
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../src/core/components/viewers/map/src/MapLayers/index.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n'use client'\r\n\r\nimport dynamic from 'next/dynamic'\r\nimport * as React from 'react'\r\n\r\nimport { useBimContext } from '../../../../../store'\r\n\r\nimport { BuildingLayer } from './src/BuildingLayers'\r\nimport { CommentLayer } from './src/CommentLayer'\r\nimport { CountryLayer } from './src/CountryLayer'\r\nimport { FileLayers } from './src/FileLayer'\r\nimport { OpenDataLayers } from './src/OpenDataLayer/src'\r\nimport { SensorLayers } from './src/SensorsLayer'\r\nimport { SiteLayer } from './src/SiteLayer/SiteLayer'\r\n\r\n\r\nimport type { Organization } from '../../../../../types/dbTypes'\r\n\r\nconst LazyBimLayer = dynamic(\r\n () => import('./src/BimLayer').then(m => ({ default: m.BimLayer })),\r\n { ssr: false },\r\n)\r\n\r\nfunction BimLayerGate() {\r\n const { state: bimState } = useBimContext()\r\n if (bimState.bim.bimModelsAddedToMap.length === 0) return null\r\n return <LazyBimLayer />\r\n}\r\n\r\nexport const MapLayers = ({
|
|
1
|
+
{"version":3,"sources":["../../../../../../../src/core/components/viewers/map/src/MapLayers/index.tsx"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n'use client'\r\n\r\nimport dynamic from 'next/dynamic'\r\nimport * as React from 'react'\r\n\r\nimport { useBimContext } from '../../../../../store'\r\n\r\nimport { BuildingLayer } from './src/BuildingLayers'\r\nimport { CommentLayer } from './src/CommentLayer'\r\nimport { CountryLayer } from './src/CountryLayer'\r\nimport { FileLayers } from './src/FileLayer'\r\nimport { OpenDataLayers } from './src/OpenDataLayer/src'\r\nimport { SensorLayers } from './src/SensorsLayer'\r\nimport { SiteLayer } from './src/SiteLayer/SiteLayer'\r\n\r\n\r\nimport type { Organization } from '../../../../../types/dbTypes'\r\n\r\nconst LazyBimLayer = dynamic(\r\n () => import('./src/BimLayer').then(m => ({ default: m.BimLayer })),\r\n { ssr: false },\r\n)\r\n\r\nfunction BimLayerGate() {\r\n const { state: bimState } = useBimContext()\r\n if (bimState.bim.bimModelsAddedToMap.length === 0) return null\r\n return <LazyBimLayer />\r\n}\r\n\r\nexport const MapLayers = ({ organization, maptilerKey }: { organization?: Organization; maptilerKey?: string }) => {\r\n return (\r\n <>\r\n <SiteLayer />\r\n <CountryLayer organization={organization} maptilerKey={maptilerKey} />\r\n <OpenDataLayers />\r\n <BuildingLayer />\r\n <CommentLayer />\r\n <SensorLayers />\r\n <FileLayers />\r\n <BimLayerGate />\r\n </>\r\n )\r\n}\r\n"],"mappings":";AA6BS,SAKL,UALK,KAKL,YALK;AAxBT,OAAO,aAAa;AAGpB,SAAS,qBAAqB;AAE9B,SAAS,qBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,oBAAoB;AAC7B,SAAS,kBAAkB;AAC3B,SAAS,sBAAsB;AAC/B,SAAS,oBAAoB;AAC7B,SAAS,iBAAiB;AAK1B,MAAM,eAAe;AAAA,EACnB,MAAM,OAAO,gBAAgB,EAAE,KAAK,QAAM,EAAE,SAAS,EAAE,SAAS,EAAE;AAAA,EAClE,EAAE,KAAK,MAAM;AACf;AAEA,SAAS,eAAe;AACtB,QAAM,EAAE,OAAO,SAAS,IAAI,cAAc;AAC1C,MAAI,SAAS,IAAI,oBAAoB,WAAW,EAAG,QAAO;AAC1D,SAAO,oBAAC,gBAAa;AACvB;AAEO,MAAM,YAAY,CAAC,EAAE,cAAc,YAAY,MAA6D;AACjH,SACE,iCACE;AAAA,wBAAC,aAAU;AAAA,IACX,oBAAC,gBAAa,cAA4B,aAA0B;AAAA,IACpE,oBAAC,kBAAe;AAAA,IAChB,oBAAC,iBAAc;AAAA,IACf,oBAAC,gBAAa;AAAA,IACd,oBAAC,gBAAa;AAAA,IACd,oBAAC,cAAW;AAAA,IACZ,oBAAC,gBAAa;AAAA,KAChB;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAuD9B,eAAO,MAAM,YAAY,yBAmVxB,CAAA"}
|
|
@@ -28,9 +28,11 @@ import { toast } from "sonner";
|
|
|
28
28
|
import { useSensor, useSensors } from "../../../../../../../hooks/sensors/sensors";
|
|
29
29
|
import { useSensorTypes } from "../../../../../../../hooks/sensorTypes/sensorTypes";
|
|
30
30
|
import { useUsers } from "../../../../../../../hooks/users/users";
|
|
31
|
-
import { MapContext, MenusContext } from "../../../../../../../store";
|
|
31
|
+
import { AppConfigContext, MapContext, MenusContext } from "../../../../../../../store";
|
|
32
32
|
import { ViewerNames } from "../../../../../../../types/dbTypes";
|
|
33
33
|
import Sensor from "../../../../../../ui/Sensors/Sensor";
|
|
34
|
+
import { SensorDetailDialog } from "../../../../../../ui/Sensors/SensorDetailDialog";
|
|
35
|
+
import { SensorInput } from "../../../../../../ui/Sensors/SensorInput";
|
|
34
36
|
import { UNTAGGED_TAG } from "../../../../../../ui/Sensors/SensorsSection";
|
|
35
37
|
import { extractCoordinatesFromFeature } from "../../../../utils/extractCoordinates";
|
|
36
38
|
import { MapLayerClickPriority } from "../../../../utils/MapEventManager/MapClickManager";
|
|
@@ -67,7 +69,7 @@ const SensorIconMarker = ({ feature, isHighlighted, onMouseEnter, onMouseLeave,
|
|
|
67
69
|
}
|
|
68
70
|
) }, String((_b = (_a = feature.properties) == null ? void 0 : _a.id) != null ? _b : `${coords.lng},${coords.lat}`));
|
|
69
71
|
};
|
|
70
|
-
const SensorLayers = (
|
|
72
|
+
const SensorLayers = () => {
|
|
71
73
|
var _a, _b;
|
|
72
74
|
const [hoveredSensorId, setHoveredSensorId] = React.useState(null);
|
|
73
75
|
const clusterLayer = createClusterLayer("sensors");
|
|
@@ -87,6 +89,10 @@ const SensorLayers = ({ minioBaseUrl }) => {
|
|
|
87
89
|
const typesVisible = (visibleSensorTypes == null ? void 0 : visibleSensorTypes[ViewerNames.map]) || [];
|
|
88
90
|
const tagsVisible = (visibleSensorTags == null ? void 0 : visibleSensorTags[ViewerNames.map]) || [];
|
|
89
91
|
const { sensorTypes } = useSensorTypes();
|
|
92
|
+
const { state: appConfigState } = React.useContext(AppConfigContext);
|
|
93
|
+
const timeZone = appConfigState.appConfig.displayTimeZone;
|
|
94
|
+
const [detailSensor, setDetailSensor] = React.useState(null);
|
|
95
|
+
const [editSensor, setEditSensor] = React.useState(null);
|
|
90
96
|
const eligibleSensors = sensors.filter((sensor) => sensor.viewer === ViewerNames.map).filter((sensor) => {
|
|
91
97
|
var _a2, _b2, _c;
|
|
92
98
|
const matchesType = typesVisible.includes(sensor.typeId);
|
|
@@ -229,7 +235,7 @@ const SensorLayers = ({ minioBaseUrl }) => {
|
|
|
229
235
|
if (!popupInfo) return null;
|
|
230
236
|
const sensorType = sensorTypes.find((t2) => t2.id === popupInfo.typeId);
|
|
231
237
|
const liveSensor = sensors.find((s) => s.id === popupInfo.id);
|
|
232
|
-
const dataUrl = popupInfo.url
|
|
238
|
+
const dataUrl = popupInfo.url || popupInfo.data || "";
|
|
233
239
|
return /* @__PURE__ */ jsxs(
|
|
234
240
|
Popup,
|
|
235
241
|
{
|
|
@@ -237,6 +243,7 @@ const SensorLayers = ({ minioBaseUrl }) => {
|
|
|
237
243
|
longitude: popupInfo.longitude,
|
|
238
244
|
latitude: popupInfo.latitude,
|
|
239
245
|
closeOnClick: false,
|
|
246
|
+
closeButton: false,
|
|
240
247
|
onClose: () => setPopUpInfo(null),
|
|
241
248
|
anchor: "bottom",
|
|
242
249
|
style: { height: "50px", border: "none", boxShadow: "none" },
|
|
@@ -270,6 +277,12 @@ const SensorLayers = ({ minioBaseUrl }) => {
|
|
|
270
277
|
createdAt: popupInfo.createdAt,
|
|
271
278
|
onRemove: user.id === String(popupInfo.authorId) ? handleRemoveSensor : null,
|
|
272
279
|
onClose: () => setPopUpInfo(null),
|
|
280
|
+
onExpand: () => {
|
|
281
|
+
if (liveSensor) setDetailSensor(liveSensor);
|
|
282
|
+
},
|
|
283
|
+
onEdit: user.id === String(popupInfo.authorId) && liveSensor ? () => setEditSensor(liveSensor) : void 0,
|
|
284
|
+
showActions: true,
|
|
285
|
+
timeZone,
|
|
273
286
|
size: "sm"
|
|
274
287
|
}
|
|
275
288
|
),
|
|
@@ -315,43 +328,65 @@ const SensorLayers = ({ minioBaseUrl }) => {
|
|
|
315
328
|
map.off("zoomend", updateUnclusteredFeatures);
|
|
316
329
|
};
|
|
317
330
|
}, [map]);
|
|
318
|
-
return /* @__PURE__ */
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
331
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
332
|
+
detailSensor && /* @__PURE__ */ jsx(
|
|
333
|
+
SensorDetailDialog,
|
|
334
|
+
{
|
|
335
|
+
open: !!detailSensor,
|
|
336
|
+
onOpenChange: (o) => !o && setDetailSensor(null),
|
|
337
|
+
sensor: detailSensor,
|
|
338
|
+
sensorType: sensorTypes.find((t2) => t2.id === detailSensor.typeId)
|
|
339
|
+
}
|
|
340
|
+
),
|
|
341
|
+
editSensor && /* @__PURE__ */ jsx(
|
|
342
|
+
SensorInput,
|
|
343
|
+
{
|
|
344
|
+
viewer: ViewerNames.map,
|
|
345
|
+
layout: "dialog",
|
|
346
|
+
isOpen: !!editSensor,
|
|
347
|
+
editSensor: editSensor != null ? editSensor : void 0,
|
|
348
|
+
onCancel: () => setEditSensor(null),
|
|
349
|
+
onSaved: () => setEditSensor(null)
|
|
350
|
+
}
|
|
351
|
+
),
|
|
352
|
+
(typesVisible.length > 0 || tagsVisible.length > 0) && /* @__PURE__ */ jsxs(
|
|
353
|
+
Source,
|
|
354
|
+
{
|
|
355
|
+
id: "sensors",
|
|
356
|
+
type: "geojson",
|
|
357
|
+
data: geojsonSensorData,
|
|
358
|
+
cluster: true,
|
|
359
|
+
clusterMaxZoom: 14,
|
|
360
|
+
clusterRadius: 40,
|
|
361
|
+
children: [
|
|
362
|
+
/* @__PURE__ */ jsx(Layer, __spreadValues({}, clusterLayer)),
|
|
363
|
+
/* @__PURE__ */ jsx(Layer, __spreadValues({}, clusterCountLayer)),
|
|
364
|
+
/* @__PURE__ */ jsx(Layer, __spreadValues({}, unclusteredPointLayer)),
|
|
365
|
+
renderPopup(),
|
|
366
|
+
unclusteredFeatures.filter((feature) => {
|
|
367
|
+
var _a2;
|
|
368
|
+
return ((_a2 = feature.properties) == null ? void 0 : _a2.id) !== (popupInfo == null ? void 0 : popupInfo.id);
|
|
369
|
+
}).map((feature) => {
|
|
370
|
+
var _a2, _b2, _c;
|
|
371
|
+
return /* @__PURE__ */ jsx(
|
|
372
|
+
SensorIconMarker,
|
|
373
|
+
{
|
|
374
|
+
feature,
|
|
375
|
+
isHighlighted: currentSensorId === ((_b2 = feature.properties) == null ? void 0 : _b2.id) || hoveredSensorId === ((_c = feature.properties) == null ? void 0 : _c.id),
|
|
376
|
+
onMouseEnter: () => {
|
|
377
|
+
var _a3;
|
|
378
|
+
return setHoveredSensorId((_a3 = feature.properties) == null ? void 0 : _a3.id);
|
|
379
|
+
},
|
|
380
|
+
onMouseLeave: () => setHoveredSensorId(null),
|
|
381
|
+
sensorTypes
|
|
345
382
|
},
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
) });
|
|
383
|
+
String((_a2 = feature.properties) == null ? void 0 : _a2.id)
|
|
384
|
+
);
|
|
385
|
+
})
|
|
386
|
+
]
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
] });
|
|
355
390
|
};
|
|
356
391
|
export {
|
|
357
392
|
SensorLayers
|