@collabdt/core 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/components/authentication/ForgotPassword.d.ts.map +1 -1
- package/dist/core/components/authentication/ForgotPassword.js +0 -1
- package/dist/core/components/authentication/ForgotPassword.js.map +1 -1
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.d.ts +12 -2
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.js +137 -139
- package/dist/core/components/ui/Comments/CollapsibleCommentItem.js.map +1 -1
- package/dist/core/components/ui/Comments/Comment.d.ts +18 -1
- package/dist/core/components/ui/Comments/Comment.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/Comment.js +94 -23
- package/dist/core/components/ui/Comments/Comment.js.map +1 -1
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts +34 -0
- package/dist/core/components/ui/Comments/CommentActionButtons.d.ts.map +1 -0
- package/dist/core/components/ui/Comments/CommentActionButtons.js +106 -0
- package/dist/core/components/ui/Comments/CommentActionButtons.js.map +1 -0
- package/dist/core/components/ui/Comments/CommentInput.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CommentInput.js +5 -2
- package/dist/core/components/ui/Comments/CommentInput.js.map +1 -1
- package/dist/core/components/ui/Comments/CommentsSection.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/CommentsSection.js +86 -42
- package/dist/core/components/ui/Comments/CommentsSection.js.map +1 -1
- package/dist/core/components/ui/Comments/commentUtils.d.ts.map +1 -1
- package/dist/core/components/ui/Comments/commentUtils.js +2 -1
- package/dist/core/components/ui/Comments/commentUtils.js.map +1 -1
- package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.d.ts.map +1 -1
- package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.js +0 -1
- package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.js.map +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.d.ts.map +1 -1
- package/dist/core/components/ui/InfoSidebar/Container.js +16 -12
- package/dist/core/components/ui/InfoSidebar/Container.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorInput.d.ts.map +1 -1
- package/dist/core/components/ui/Sensors/SensorInput.js +0 -1
- package/dist/core/components/ui/Sensors/SensorInput.js.map +1 -1
- package/dist/core/components/ui/Sensors/SensorsSection.js +0 -2
- package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
- package/dist/core/components/ui/Sidebar.d.ts.map +1 -1
- package/dist/core/components/ui/Sidebar.js +27 -4
- package/dist/core/components/ui/Sidebar.js.map +1 -1
- package/dist/core/components/ui/ToolbarButton.d.ts.map +1 -1
- package/dist/core/components/ui/ToolbarButton.js +0 -1
- package/dist/core/components/ui/ToolbarButton.js.map +1 -1
- package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.js +0 -2
- package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.js.map +1 -1
- package/dist/core/components/viewers/Data/details/EnergySourcesGrid.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/details/EnergySourcesGrid.js +0 -10
- package/dist/core/components/viewers/Data/details/EnergySourcesGrid.js.map +1 -1
- package/dist/core/components/viewers/Data/files/DatatableFileAdder.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/files/DatatableFileAdder.js +0 -1
- package/dist/core/components/viewers/Data/files/DatatableFileAdder.js.map +1 -1
- package/dist/core/components/viewers/Data/files/utils/fuzzySearch.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/files/utils/fuzzySearch.js +0 -1
- package/dist/core/components/viewers/Data/files/utils/fuzzySearch.js.map +1 -1
- package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.js +0 -1
- package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.js.map +1 -1
- package/dist/core/components/viewers/Data/utils/filterHelper.d.ts.map +1 -1
- package/dist/core/components/viewers/Data/utils/filterHelper.js +0 -2
- package/dist/core/components/viewers/Data/utils/filterHelper.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js +0 -3
- package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js +1 -7
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.js +1 -9
- package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.js.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.js +4 -3
- package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.js.map +1 -1
- package/dist/core/components/viewers/bim/src/ClippingPlane.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/ClippingPlane.js +0 -1
- package/dist/core/components/viewers/bim/src/ClippingPlane.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.d.ts +16 -10
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js +26 -6
- 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 +23 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js +108 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.d.ts +44 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js +77 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts +24 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js +27 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js.map +1 -0
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.js +2 -2
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.js.map +1 -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 +242 -61
- package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.js +1 -5
- package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.js +0 -1
- package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.js.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/measureBimTool.d.ts.map +1 -1
- package/dist/core/components/viewers/bim/src/tools/measureBimTool.js +1 -2
- package/dist/core/components/viewers/bim/src/tools/measureBimTool.js.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.d.ts.map +1 -1
- package/dist/core/components/viewers/map/MapViewer.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.js +11 -14
- package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/ckanDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/ckanDatasets.js +2 -5
- package/dist/core/components/viewers/map/datasets/src/ckanDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/localDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/localDatasets.js +0 -3
- package/dist/core/components/viewers/map/datasets/src/localDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.d.ts.map +1 -1
- package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.js +0 -11
- package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.js +0 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.js +0 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.js +0 -1
- package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.js +174 -53
- package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.js +2 -1
- package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.js.map +1 -1
- 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 +0 -3
- package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.js +4 -3
- package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.js +0 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.js.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js +1 -5
- package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js.map +1 -1
- package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.d.ts.map +1 -1
- package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js +1 -3
- package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js.map +1 -1
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts.map +1 -1
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js +0 -1
- package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js.map +1 -1
- package/dist/core/components/viewers/map/utils/flyToEach.d.ts.map +1 -1
- package/dist/core/components/viewers/map/utils/flyToEach.js +0 -1
- package/dist/core/components/viewers/map/utils/flyToEach.js.map +1 -1
- package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.d.ts.map +1 -1
- package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.js +0 -2
- package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.js.map +1 -1
- package/dist/core/hooks/comments/comments.d.ts +11 -0
- package/dist/core/hooks/comments/comments.d.ts.map +1 -1
- package/dist/core/hooks/comments/comments.js +3 -1
- package/dist/core/hooks/comments/comments.js.map +1 -1
- package/dist/core/hooks/comments/createCommentHooks.d.ts +11 -0
- package/dist/core/hooks/comments/createCommentHooks.d.ts.map +1 -1
- package/dist/core/hooks/comments/createCommentHooks.js +30 -1
- package/dist/core/hooks/comments/createCommentHooks.js.map +1 -1
- package/dist/core/hooks/files/files.d.ts.map +1 -1
- package/dist/core/hooks/files/files.js +0 -1
- package/dist/core/hooks/files/files.js.map +1 -1
- package/dist/core/hooks/ui/useResizableSidebarWidth.d.ts +30 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.d.ts.map +1 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.js +75 -0
- package/dist/core/hooks/ui/useResizableSidebarWidth.js.map +1 -0
- package/dist/core/i18n/messages/en.json +5 -0
- package/dist/core/i18n/messages/es.json +6 -1
- package/dist/core/i18n/messages/fr.json +6 -1
- package/dist/core/store/Datasets/reducer.d.ts.map +1 -1
- package/dist/core/store/Datasets/reducer.js +0 -2
- package/dist/core/store/Datasets/reducer.js.map +1 -1
- package/dist/core/store/Files/reducer.js +0 -1
- package/dist/core/store/Files/reducer.js.map +1 -1
- package/dist/core/store/Menus/context.d.ts.map +1 -1
- package/dist/core/store/Menus/context.js +4 -1
- package/dist/core/store/Menus/context.js.map +1 -1
- package/dist/core/store/Menus/reducer.d.ts +20 -0
- 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/markerUtils.d.ts +10 -0
- package/dist/core/utils/markerUtils.d.ts.map +1 -1
- package/dist/core/utils/markerUtils.js +9 -1
- package/dist/core/utils/markerUtils.js.map +1 -1
- package/dist/core/utils/memcache.d.ts.map +1 -1
- package/dist/core/utils/memcache.js +0 -7
- package/dist/core/utils/memcache.js.map +1 -1
- package/package.json +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.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 * as React from 'react'\r\nimport { mutate } from 'swr'\r\n\r\nimport { Button } from '../../../../../../../../components/ui/Button'\r\nimport { Card, CardContent, CardHeader, CardTitle } from '../../../../../../../../components/ui/Card'\r\nimport { FilesContext } from '../../../../../../../../store'\r\n\r\n\r\nimport { FileManagerRow } from './FileManagerRow'\r\n\r\nimport type { DbFile } from '../../../../../../../../types/dbTypes'\r\n\r\nconst is3DModelFile = (extension?: string | null): boolean => {\r\n if (!extension) return false\r\n return ['glb', 'gltf', 'fbx', 'obj', 'collada'].includes(extension.toLowerCase())\r\n}\r\n\r\nconst isHiddenFileType = (extension?: string | null): boolean => {\r\n if (!extension) return false\r\n const ext = extension.toLowerCase()\r\n return ext === 'frag' || ext === 'ifc'\r\n}\r\n\r\nconst shouldExcludeByTag = (tag?: string | null): boolean => {\r\n if (!tag) return false\r\n return tag === 'user' || tag === 'bim-file' || tag === 'fragment-file' || tag === 'bimModel'\r\n}\r\n\r\ntype FileManagerProp = {\r\n closeFileManager?: () => void\r\n}\r\n\r\nexport default function MapFileManager({ closeFileManager }: FileManagerProp) {\r\n const { state: fileState, dispatch: fileDispatch } = React.useContext(FilesContext)\r\n const { files, mapFileIds } = fileState.files\r\n\r\n const mapFiles: DbFile[] = files.filter(file =>\r\n !is3DModelFile(file.extension) && !isHiddenFileType(file.extension) && !shouldExcludeByTag(file.tag)\r\n )\r\n\r\n const allOnMap = mapFiles.length > 0 && mapFiles.every(file => mapFileIds.includes(file.id))\r\n\r\n const toggleAll = () => {\r\n if (allOnMap) {\r\n mapFiles.forEach(file => fileDispatch({ type: 'REMOVE_FROM_MAP', payload: { id: file.id } }))\r\n }\r\n else {\r\n fileDispatch({ type: 'ADD_ALL_TO_MAP', payload: { ids: mapFiles.map(f => f.id) } })\r\n }\r\n }\r\n\r\n const handleDeleteFile = async (file: DbFile) => {\r\n try {\r\n const response = await fetch(`/api/files/${file.id}`, { method: 'DELETE' })\r\n if (response.ok) {\r\n fileDispatch({ type: 'REMOVE_FILE', payload: { id: file.id } })\r\n }\r\n mutate(['files'])\r\n }\r\n catch (error) {\r\n console.error('Error when deleting a file', error)\r\n }\r\n }\r\n\r\n return (\r\n <Card className=\"fixed bottom-12 right-[50%] translate-x-[50%] bg-background round-sm shadow-md z-10 pointer-events-auto max-w-[410px] max-h-[60vh]\">\r\n <CardHeader className=\"flex flex-row justify-between items-center pointer-events-auto py-0\">\r\n <CardTitle className=\"text-sm\">\r\n Added Files (\r\n {mapFiles.length}\r\n )\r\n </CardTitle>\r\n <div>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n onClick={toggleAll}\r\n className=\"opacity-70 hover:opacity-100 transition-opacity duration-200 hover:bg-transparent\"\r\n >\r\n {allOnMap ? <LR.Eye size={18} /> : <LR.EyeOff size={18} />}\r\n </Button>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n onClick={() => closeFileManager()}\r\n className=\"opacity-70 hover:opacity-100 transition-opacity duration-200 hover:bg-transparent\"\r\n >\r\n <span className=\"sr-only\">Clear</span>\r\n <LR.X size={18} />\r\n </Button>\r\n </div>\r\n </CardHeader>\r\n <CardContent>\r\n {mapFiles.length === 0\r\n ? (\r\n <div className=\"py-4 text-center text-sm text-muted-foreground\">\r\n No files added to map\r\n </div>\r\n )\r\n : (\r\n <div className=\"space-y-2 max-h-40 overflow-y-auto\">\r\n {mapFiles.map(file => (\r\n <FileManagerRow\r\n key={file.id}\r\n file={file}\r\n isOnMap={mapFileIds.includes(file.id)}\r\n onVisibilityToggle={() => {\r\n const isOnMap = mapFileIds.includes(file.id)\r\n fileDispatch({\r\n type: isOnMap ? 'REMOVE_FROM_MAP' : 'ADD_TO_MAP',\r\n payload: { id: file.id },\r\n })\r\n }}\r\n onRemoveFile={() => handleDeleteFile(file)}\r\n onEditFile={() =>
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.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 * as React from 'react'\r\nimport { mutate } from 'swr'\r\n\r\nimport { Button } from '../../../../../../../../components/ui/Button'\r\nimport { Card, CardContent, CardHeader, CardTitle } from '../../../../../../../../components/ui/Card'\r\nimport { FilesContext } from '../../../../../../../../store'\r\n\r\n\r\nimport { FileManagerRow } from './FileManagerRow'\r\n\r\nimport type { DbFile } from '../../../../../../../../types/dbTypes'\r\n\r\nconst is3DModelFile = (extension?: string | null): boolean => {\r\n if (!extension) return false\r\n return ['glb', 'gltf', 'fbx', 'obj', 'collada'].includes(extension.toLowerCase())\r\n}\r\n\r\nconst isHiddenFileType = (extension?: string | null): boolean => {\r\n if (!extension) return false\r\n const ext = extension.toLowerCase()\r\n return ext === 'frag' || ext === 'ifc'\r\n}\r\n\r\nconst shouldExcludeByTag = (tag?: string | null): boolean => {\r\n if (!tag) return false\r\n return tag === 'user' || tag === 'bim-file' || tag === 'fragment-file' || tag === 'bimModel'\r\n}\r\n\r\ntype FileManagerProp = {\r\n closeFileManager?: () => void\r\n}\r\n\r\nexport default function MapFileManager({ closeFileManager }: FileManagerProp) {\r\n const { state: fileState, dispatch: fileDispatch } = React.useContext(FilesContext)\r\n const { files, mapFileIds } = fileState.files\r\n\r\n const mapFiles: DbFile[] = files.filter(file =>\r\n !is3DModelFile(file.extension) && !isHiddenFileType(file.extension) && !shouldExcludeByTag(file.tag)\r\n )\r\n\r\n const allOnMap = mapFiles.length > 0 && mapFiles.every(file => mapFileIds.includes(file.id))\r\n\r\n const toggleAll = () => {\r\n if (allOnMap) {\r\n mapFiles.forEach(file => fileDispatch({ type: 'REMOVE_FROM_MAP', payload: { id: file.id } }))\r\n }\r\n else {\r\n fileDispatch({ type: 'ADD_ALL_TO_MAP', payload: { ids: mapFiles.map(f => f.id) } })\r\n }\r\n }\r\n\r\n const handleDeleteFile = async (file: DbFile) => {\r\n try {\r\n const response = await fetch(`/api/files/${file.id}`, { method: 'DELETE' })\r\n if (response.ok) {\r\n fileDispatch({ type: 'REMOVE_FILE', payload: { id: file.id } })\r\n }\r\n mutate(['files'])\r\n }\r\n catch (error) {\r\n console.error('Error when deleting a file', error)\r\n }\r\n }\r\n\r\n return (\r\n <Card className=\"fixed bottom-12 right-[50%] translate-x-[50%] bg-background round-sm shadow-md z-10 pointer-events-auto max-w-[410px] max-h-[60vh]\">\r\n <CardHeader className=\"flex flex-row justify-between items-center pointer-events-auto py-0\">\r\n <CardTitle className=\"text-sm\">\r\n Added Files (\r\n {mapFiles.length}\r\n )\r\n </CardTitle>\r\n <div>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n onClick={toggleAll}\r\n className=\"opacity-70 hover:opacity-100 transition-opacity duration-200 hover:bg-transparent\"\r\n >\r\n {allOnMap ? <LR.Eye size={18} /> : <LR.EyeOff size={18} />}\r\n </Button>\r\n <Button\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n onClick={() => closeFileManager()}\r\n className=\"opacity-70 hover:opacity-100 transition-opacity duration-200 hover:bg-transparent\"\r\n >\r\n <span className=\"sr-only\">Clear</span>\r\n <LR.X size={18} />\r\n </Button>\r\n </div>\r\n </CardHeader>\r\n <CardContent>\r\n {mapFiles.length === 0\r\n ? (\r\n <div className=\"py-4 text-center text-sm text-muted-foreground\">\r\n No files added to map\r\n </div>\r\n )\r\n : (\r\n <div className=\"space-y-2 max-h-40 overflow-y-auto\">\r\n {mapFiles.map(file => (\r\n <FileManagerRow\r\n key={file.id}\r\n file={file}\r\n isOnMap={mapFileIds.includes(file.id)}\r\n onVisibilityToggle={() => {\r\n const isOnMap = mapFileIds.includes(file.id)\r\n fileDispatch({\r\n type: isOnMap ? 'REMOVE_FROM_MAP' : 'ADD_TO_MAP',\r\n payload: { id: file.id },\r\n })\r\n }}\r\n onRemoveFile={() => handleDeleteFile(file)}\r\n onEditFile={() => {\r\n // ⚠️ Edit-file action not implemented\r\n }}\r\n />\r\n ))}\r\n </div>\r\n )}\r\n </CardContent>\r\n </Card>\r\n )\r\n}\r\n"],"mappings":";AAyEQ,SAYgB,KAZhB;AApER,YAAY,QAAQ;AACpB,YAAY,WAAW;AACvB,SAAS,cAAc;AAEvB,SAAS,cAAc;AACvB,SAAS,MAAM,aAAa,YAAY,iBAAiB;AACzD,SAAS,oBAAoB;AAG7B,SAAS,sBAAsB;AAI/B,MAAM,gBAAgB,CAAC,cAAuC;AAC5D,MAAI,CAAC,UAAW,QAAO;AACvB,SAAO,CAAC,OAAO,QAAQ,OAAO,OAAO,SAAS,EAAE,SAAS,UAAU,YAAY,CAAC;AAClF;AAEA,MAAM,mBAAmB,CAAC,cAAuC;AAC/D,MAAI,CAAC,UAAW,QAAO;AACvB,QAAM,MAAM,UAAU,YAAY;AAClC,SAAO,QAAQ,UAAU,QAAQ;AACnC;AAEA,MAAM,qBAAqB,CAAC,QAAiC;AAC3D,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,QAAQ,UAAU,QAAQ,cAAc,QAAQ,mBAAmB,QAAQ;AACpF;AAMe,SAAR,eAAgC,EAAE,iBAAiB,GAAoB;AAC5E,QAAM,EAAE,OAAO,WAAW,UAAU,aAAa,IAAI,MAAM,WAAW,YAAY;AAClF,QAAM,EAAE,OAAO,WAAW,IAAI,UAAU;AAExC,QAAM,WAAqB,MAAM;AAAA,IAAO,UACtC,CAAC,cAAc,KAAK,SAAS,KAAK,CAAC,iBAAiB,KAAK,SAAS,KAAK,CAAC,mBAAmB,KAAK,GAAG;AAAA,EACrG;AAEA,QAAM,WAAW,SAAS,SAAS,KAAK,SAAS,MAAM,UAAQ,WAAW,SAAS,KAAK,EAAE,CAAC;AAE3F,QAAM,YAAY,MAAM;AACtB,QAAI,UAAU;AACZ,eAAS,QAAQ,UAAQ,aAAa,EAAE,MAAM,mBAAmB,SAAS,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC,CAAC;AAAA,IAC9F,OACK;AACH,mBAAa,EAAE,MAAM,kBAAkB,SAAS,EAAE,KAAK,SAAS,IAAI,OAAK,EAAE,EAAE,EAAE,EAAE,CAAC;AAAA,IACpF;AAAA,EACF;AAEA,QAAM,mBAAmB,OAAO,SAAiB;AAC/C,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,cAAc,KAAK,EAAE,IAAI,EAAE,QAAQ,SAAS,CAAC;AAC1E,UAAI,SAAS,IAAI;AACf,qBAAa,EAAE,MAAM,eAAe,SAAS,EAAE,IAAI,KAAK,GAAG,EAAE,CAAC;AAAA,MAChE;AACA,aAAO,CAAC,OAAO,CAAC;AAAA,IAClB,SACO,OAAO;AACZ,cAAQ,MAAM,8BAA8B,KAAK;AAAA,IACnD;AAAA,EACF;AAEA,SACE,qBAAC,QAAK,WAAU,sIACd;AAAA,yBAAC,cAAW,WAAU,uEACpB;AAAA,2BAAC,aAAU,WAAU,WAAU;AAAA;AAAA,QAE5B,SAAS;AAAA,QAAO;AAAA,SAEnB;AAAA,MACA,qBAAC,SACC;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS;AAAA,YACT,WAAU;AAAA,YAET,qBAAW,oBAAC,GAAG,KAAH,EAAO,MAAM,IAAI,IAAK,oBAAC,GAAG,QAAH,EAAU,MAAM,IAAI;AAAA;AAAA,QAC1D;AAAA,QACA;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS,MAAM,iBAAiB;AAAA,YAChC,WAAU;AAAA,YAEV;AAAA,kCAAC,UAAK,WAAU,WAAU,mBAAK;AAAA,cAC/B,oBAAC,GAAG,GAAH,EAAK,MAAM,IAAI;AAAA;AAAA;AAAA,QAClB;AAAA,SACF;AAAA,OACF;AAAA,IACA,oBAAC,eACE,mBAAS,WAAW,IAEf,oBAAC,SAAI,WAAU,kDAAiD,mCAEhE,IAGA,oBAAC,SAAI,WAAU,sCACZ,mBAAS,IAAI,UACZ;AAAA,MAAC;AAAA;AAAA,QAEC;AAAA,QACA,SAAS,WAAW,SAAS,KAAK,EAAE;AAAA,QACpC,oBAAoB,MAAM;AACxB,gBAAM,UAAU,WAAW,SAAS,KAAK,EAAE;AAC3C,uBAAa;AAAA,YACX,MAAM,UAAU,oBAAoB;AAAA,YACpC,SAAS,EAAE,IAAI,KAAK,GAAG;AAAA,UACzB,CAAC;AAAA,QACH;AAAA,QACA,cAAc,MAAM,iBAAiB,IAAI;AAAA,QACzC,YAAY,MAAM;AAAA,QAElB;AAAA;AAAA,MAbK,KAAK;AAAA,IAcZ,CACD,GACH,GAER;AAAA,KACF;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;AAqD9B,eAAO,MAAM,YAAY,GAAI,kBAAkB;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,
|
|
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;AAqD9B,eAAO,MAAM,YAAY,GAAI,kBAAkB;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,sBAyTvE,CAAA"}
|
|
@@ -201,11 +201,8 @@ const SensorLayers = ({ minioBaseUrl }) => {
|
|
|
201
201
|
if (!("coordinates" in feature.geometry)) return;
|
|
202
202
|
const [lng, lat] = feature.geometry.coordinates;
|
|
203
203
|
const source = map.getSource("sensors");
|
|
204
|
-
console.log(source);
|
|
205
|
-
console.log(clusterId);
|
|
206
204
|
source.getClusterExpansionZoom(clusterId).then(
|
|
207
205
|
(zoom) => {
|
|
208
|
-
console.log("easing to");
|
|
209
206
|
map.easeTo({ center: [lng, lat], zoom });
|
|
210
207
|
}
|
|
211
208
|
).catch((error) => {
|
|
@@ -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 console.log(source)\r\n console.log(clusterId)\r\n source.getClusterExpansionZoom(clusterId).then(\r\n (zoom: number) => {\r\n console.log('easing to')\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 { 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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TabSelector.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/map/src/MapSidebar/TabSelector.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;AAExE,UAAU,gBAAgB;IACxB,SAAS,EAAE,cAAc,CAAA;IACzB,iBAAiB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CACjD;AAED,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,
|
|
1
|
+
{"version":3,"file":"TabSelector.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/map/src/MapSidebar/TabSelector.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAI9B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAA;AAExE,UAAU,gBAAgB;IACxB,SAAS,EAAE,cAAc,CAAA;IACzB,iBAAiB,EAAE,CAAC,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;CACjD;AAED,wBAAgB,WAAW,CAAC,EAAE,SAAS,EAAE,iBAAiB,EAAE,EAAE,gBAAgB,qBAsC7E"}
|
|
@@ -13,15 +13,16 @@ function TabSelector({ activeTab, onTabChangeAction }) {
|
|
|
13
13
|
];
|
|
14
14
|
return /* @__PURE__ */ jsxs("div", { children: [
|
|
15
15
|
/* @__PURE__ */ jsx(Separator, {}),
|
|
16
|
-
/* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsx("div", { className: "flex items-center
|
|
16
|
+
/* @__PURE__ */ jsx("div", { className: "px-4 py-3", children: /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 w-full", children: tabs.map((tab) => /* @__PURE__ */ jsx(
|
|
17
17
|
"div",
|
|
18
18
|
{
|
|
19
|
-
|
|
19
|
+
title: tab.label,
|
|
20
|
+
className: `flex-1 min-w-0 truncate text-center px-2 py-2 rounded-md cursor-pointer transition-colors text-xs font-medium ${activeTab === tab.id ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:text-foreground hover:bg-accent/50"}`,
|
|
20
21
|
onClick: () => onTabChangeAction(tab.id),
|
|
21
22
|
children: tab.label
|
|
22
23
|
},
|
|
23
24
|
tab.id
|
|
24
|
-
)) }) })
|
|
25
|
+
)) }) }),
|
|
25
26
|
/* @__PURE__ */ jsx(Separator, {})
|
|
26
27
|
] });
|
|
27
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../src/core/components/viewers/map/src/MapSidebar/TabSelector.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 { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\n\r\nimport { Separator } from '../../../../ui/Separator'\r\n\r\nimport type { SidebarTabType } from '../../../../../store/Menus/reducer'\r\n\r\ninterface TabSelectorProps {\r\n activeTab: SidebarTabType\r\n onTabChangeAction: (tab: SidebarTabType) => void\r\n}\r\n\r\nexport function TabSelector({ activeTab, onTabChangeAction }: TabSelectorProps) {\r\n // Translation\r\n const t = useTranslations('TabSelector')\r\n\r\n const tabs = [\r\n { id: 'file', label: t('fileLabel') },\r\n { id: 'layers', label: t('layersTitle') },\r\n { id: 'communication', label: t('communicationTitle') },\r\n { id: 'sensors', label: t('sensorsTitle') },\r\n { id: 'settings', label: t('settingsTitle') },\r\n ]\r\n\r\n return (\r\n <div>\r\n <Separator />\r\n\r\n <div className=\"px-4 py-3\">\r\n <div className=\"flex items-center
|
|
1
|
+
{"version":3,"sources":["../../../../../../../src/core/components/viewers/map/src/MapSidebar/TabSelector.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 { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\n\r\nimport { Separator } from '../../../../ui/Separator'\r\n\r\nimport type { SidebarTabType } from '../../../../../store/Menus/reducer'\r\n\r\ninterface TabSelectorProps {\r\n activeTab: SidebarTabType\r\n onTabChangeAction: (tab: SidebarTabType) => void\r\n}\r\n\r\nexport function TabSelector({ activeTab, onTabChangeAction }: TabSelectorProps) {\r\n // Translation\r\n const t = useTranslations('TabSelector')\r\n\r\n const tabs = [\r\n { id: 'file', label: t('fileLabel') },\r\n { id: 'layers', label: t('layersTitle') },\r\n { id: 'communication', label: t('communicationTitle') },\r\n { id: 'sensors', label: t('sensorsTitle') },\r\n { id: 'settings', label: t('settingsTitle') },\r\n ]\r\n\r\n return (\r\n <div>\r\n <Separator />\r\n\r\n <div className=\"px-4 py-3\">\r\n <div className=\"flex items-center gap-1 w-full\">\r\n {tabs.map(tab => (\r\n <div\r\n key={tab.id}\r\n title={tab.label}\r\n className={`flex-1 min-w-0 truncate text-center px-2 py-2 rounded-md cursor-pointer transition-colors text-xs font-medium ${\r\n activeTab === tab.id\r\n ? 'bg-accent text-accent-foreground'\r\n : 'text-muted-foreground hover:text-foreground hover:bg-accent/50'\r\n }`}\r\n onClick={() => onTabChangeAction(tab.id as SidebarTabType)}\r\n >\r\n {tab.label}\r\n </div>\r\n ))}\r\n </div>\r\n </div>\r\n\r\n <Separator />\r\n </div>\r\n )\r\n}\r\n"],"mappings":";AA8BI,SACE,KADF;AAzBJ,SAAS,uBAAuB;AAGhC,SAAS,iBAAiB;AASnB,SAAS,YAAY,EAAE,WAAW,kBAAkB,GAAqB;AAE9E,QAAM,IAAI,gBAAgB,aAAa;AAEvC,QAAM,OAAO;AAAA,IACX,EAAE,IAAI,QAAQ,OAAO,EAAE,WAAW,EAAE;AAAA,IACpC,EAAE,IAAI,UAAU,OAAO,EAAE,aAAa,EAAE;AAAA,IACxC,EAAE,IAAI,iBAAiB,OAAO,EAAE,oBAAoB,EAAE;AAAA,IACtD,EAAE,IAAI,WAAW,OAAO,EAAE,cAAc,EAAE;AAAA,IAC1C,EAAE,IAAI,YAAY,OAAO,EAAE,eAAe,EAAE;AAAA,EAC9C;AAEA,SACE,qBAAC,SACC;AAAA,wBAAC,aAAU;AAAA,IAEX,oBAAC,SAAI,WAAU,aACb,8BAAC,SAAI,WAAU,kCACZ,eAAK,IAAI,SACR;AAAA,MAAC;AAAA;AAAA,QAEC,OAAO,IAAI;AAAA,QACX,WAAW,iHACT,cAAc,IAAI,KACd,qCACA,gEACN;AAAA,QACA,SAAS,MAAM,kBAAkB,IAAI,EAAoB;AAAA,QAExD,cAAI;AAAA;AAAA,MATA,IAAI;AAAA,IAUX,CACD,GACH,GACF;AAAA,IAEA,oBAAC,aAAU;AAAA,KACb;AAEJ;","names":[]}
|
package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilesSection.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B,OAAO,KAAK,EAAE,MAAM,IAAI,KAAK,EAAE,MAAM,uCAAuC,CAAA;AAmB5E,UAAU,iBAAiB;IACzB,KAAK,EAAE,KAAK,EAAE,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,KAAU,EAAE,EAAE,iBAAiB,
|
|
1
|
+
{"version":3,"file":"FilesSection.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAU9B,OAAO,KAAK,EAAE,MAAM,IAAI,KAAK,EAAE,MAAM,uCAAuC,CAAA;AAmB5E,UAAU,iBAAiB;IACzB,KAAK,EAAE,KAAK,EAAE,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,YAAY,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,KAAU,EAAE,EAAE,iBAAiB,qBA4IhF"}
|
|
@@ -59,7 +59,6 @@ function FilesSection({ files: _filesProp, query = "" }) {
|
|
|
59
59
|
const { handleAddFile, uploadState } = useFileUploadWithProgress({
|
|
60
60
|
acceptedFileTypes: "*",
|
|
61
61
|
onUploadSuccess: () => {
|
|
62
|
-
console.log("File uploaded successfully");
|
|
63
62
|
mutate(`/api/files`);
|
|
64
63
|
},
|
|
65
64
|
onUploadError: (error) => console.error("Error uploading file:", error)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.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 { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { mutate } from 'swr'\r\n\r\nimport { useDeleteFile } from '../../../../../../../../hooks/files/files'\r\nimport { BuildingsContext, FilesContext, MenusContext } from '../../../../../../../../store'\r\nimport ConfirmDialog from '../../../../../../../ConfirmDialog'\r\nimport { CollapsibleSection } from '../../../../../../../ui/CollapsibleSection'\r\nimport { useFileDeleteHandler, FileItemComponent, useFileActions, useFileUploadWithProgress } from '../../../../../../../ui/FilesManager'\r\nimport { LoadingSpinner } from '../../../../../../../ui/LoadingSpinner'\r\n\r\nimport type { DbFile as IFile } from '../../../../../../../../types/dbTypes'\r\n\r\n\r\n\r\n// Hoisted out of the JSX so the array reference is stable across renders.\r\n// Inline `options={['view','move','info','delete']}` gets a new array identity\r\n// on every render, defeating React.memo on FileItemComponent.\r\nconst FILE_OPTIONS: import('../../../../../../../../types/global').FileAction[] = ['view', 'move', 'info', 'delete']\r\n\r\nconst shouldExcludeByTag = (tag?: string | null): boolean => {\r\n if (!tag) return false\r\n return tag === 'user' || tag === 'bim-file' || tag === 'fragment-file' || tag === 'bimModel'\r\n}\r\n\r\nconst shouldExcludeByType = (type?: string | null): boolean => {\r\n if (!type) return false\r\n return type === 'bim-file'\r\n}\r\n\r\ninterface FilesSectionProps {\r\n files: IFile[]\r\n query?: string\r\n}\r\n\r\nexport function FilesSection({ files: _filesProp, query = '' }: FilesSectionProps) {\r\n const t = useTranslations('FileSelection')\r\n\r\n const { state: buildingsState } = React.useContext(BuildingsContext)\r\n const { state: fileState, dispatch: fileDispatch } = React.useContext(FilesContext)\r\n const files = fileState.files.files\r\n const { mapFileIds } = fileState.files\r\n const { building } = buildingsState.buildings\r\n const buildingId = building?.id || -1\r\n\r\n const { deleteFile: deleteFileApi } = useDeleteFile(buildingId)\r\n\r\n // Wrap so the store is updated immediately alongside the API call\r\n const deleteFile = React.useCallback(async (fileId: number) => {\r\n fileDispatch({ type: 'REMOVE_FILE', payload: { id: fileId } })\r\n await deleteFileApi(fileId)\r\n }, [deleteFileApi, fileDispatch])\r\n\r\n const { handleDeleteFile } = useFileDeleteHandler({\r\n deleteFile,\r\n onDeleteSuccess: () => {},\r\n })\r\n\r\n const { handleAddFile, uploadState } = useFileUploadWithProgress({\r\n acceptedFileTypes: '*',\r\n onUploadSuccess: () => {\r\n console.log('File uploaded successfully')\r\n mutate(`/api/files`)\r\n },\r\n onUploadError: (error) => console.error('Error uploading file:', error),\r\n })\r\n\r\n const nonBimFiles = React.useMemo(() => {\r\n return files\r\n .filter(file => file.extension !== 'ifc' && file.extension !== 'frag')\r\n .filter(file => !shouldExcludeByTag(file.tag))\r\n .filter(file => !shouldExcludeByType((file as any).type))\r\n .map(file => ({ ...file, isVisible: mapFileIds.includes(file.id) }))\r\n .sort((a, b) => {\r\n const aOnMap = mapFileIds.includes(a.id)\r\n const bOnMap = mapFileIds.includes(b.id)\r\n if (aOnMap !== bOnMap) return aOnMap ? -1 : 1\r\n return a.name.localeCompare(b.name)\r\n })\r\n }, [files, mapFileIds])\r\n\r\n const [localFiles, setLocalFiles] = React.useState(nonBimFiles)\r\n\r\n React.useEffect(() => {\r\n setLocalFiles(nonBimFiles)\r\n }, [nonBimFiles])\r\n\r\n const handleMoveFile = React.useCallback((file: IFile) => {\r\n fileDispatch({ type: 'EDIT_FILE', payload: { file } })\r\n }, [fileDispatch])\r\n\r\n // useCallback so the onView prop identity stays stable across renders.\r\n // Otherwise useFileActions' internal useCallback dep on onView re-fires every\r\n // render, producing a new handleAction identity, which defeats React.memo on\r\n // FileItemComponent.\r\n const handleViewFile = React.useCallback((file: IFile, newVisibility: boolean) => {\r\n fileDispatch({\r\n type: newVisibility ? 'ADD_TO_MAP' : 'REMOVE_FROM_MAP',\r\n payload: { id: file.id },\r\n })\r\n }, [fileDispatch])\r\n\r\n const { handleAction, deleteDialog } = useFileActions({\r\n files: localFiles,\r\n setFiles: setLocalFiles,\r\n buildingId,\r\n handleDeleteFile,\r\n onView: handleViewFile,\r\n onMove: handleMoveFile,\r\n })\r\n\r\n const areAllMapFilesHidden = React.useMemo(\r\n () => nonBimFiles.every(file => !mapFileIds.includes(file.id)),\r\n [nonBimFiles, mapFileIds]\r\n )\r\n\r\n const filteredFiles = React.useMemo(() => {\r\n if (!query.trim()) return nonBimFiles\r\n return nonBimFiles.filter(file =>\r\n file.name.toLowerCase().includes(query.toLowerCase())\r\n )\r\n }, [nonBimFiles, query])\r\n\r\n const handleSwitchVariant = () => ({\r\n checked: !areAllMapFilesHidden,\r\n onCheckedChange: (checked: boolean) => {\r\n if (checked) {\r\n fileDispatch({ type: 'ADD_ALL_TO_MAP', payload: { ids: nonBimFiles.map(f => f.id) } })\r\n }\r\n else {\r\n fileDispatch({ type: 'REMOVE_ALL_FROM_MAP' } as never)\r\n }\r\n },\r\n })\r\n\r\n return (\r\n <div className=\"h-full min-h-0\">\r\n <CollapsibleSection\r\n title={t('filesTitle')}\r\n icon={LR.FileText}\r\n className=\"h-full min-h-0 flex flex-col\"\r\n style={{ height: '100%', minHeight: 0 }}\r\n itemCount={filteredFiles.length}\r\n onAddItem={uploadState.uploading ? undefined : handleAddFile}\r\n addItemTitle={uploadState.uploading ? `${t('uploadingFile')} ${uploadState.progress}%` : t('addFileTitle')}\r\n switchVariant={handleSwitchVariant()}\r\n >\r\n <div className=\"flex-1 min-h-0 overflow-y-auto space-y-1\">\r\n {uploadState.uploading && (\r\n <div className=\"flex items-center gap-2 px-2 py-2 text-sm text-muted-foreground\">\r\n <LoadingSpinner className=\"h-4 w-4\" />\r\n <span>{t('uploadingFile')} {uploadState.progress}%</span>\r\n </div>\r\n )}\r\n {filteredFiles.map((item) => (\r\n <FileItemComponent\r\n key={item.id}\r\n file={item}\r\n onAction={handleAction}\r\n options={FILE_OPTIONS}\r\n translationKey=\"FileSelection\"\r\n confirmDelete={false}\r\n />\r\n ))}\r\n </div>\r\n </CollapsibleSection>\r\n\r\n <ConfirmDialog\r\n isOpen={deleteDialog.isOpen}\r\n isDeleting={deleteDialog.isDeleting}\r\n onOpenChange={deleteDialog.onOpenChange}\r\n handleConfirm={deleteDialog.onConfirm}\r\n itemName={deleteDialog.itemName}\r\n />\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.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 { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { mutate } from 'swr'\r\n\r\nimport { useDeleteFile } from '../../../../../../../../hooks/files/files'\r\nimport { BuildingsContext, FilesContext, MenusContext } from '../../../../../../../../store'\r\nimport ConfirmDialog from '../../../../../../../ConfirmDialog'\r\nimport { CollapsibleSection } from '../../../../../../../ui/CollapsibleSection'\r\nimport { useFileDeleteHandler, FileItemComponent, useFileActions, useFileUploadWithProgress } from '../../../../../../../ui/FilesManager'\r\nimport { LoadingSpinner } from '../../../../../../../ui/LoadingSpinner'\r\n\r\nimport type { DbFile as IFile } from '../../../../../../../../types/dbTypes'\r\n\r\n\r\n\r\n// Hoisted out of the JSX so the array reference is stable across renders.\r\n// Inline `options={['view','move','info','delete']}` gets a new array identity\r\n// on every render, defeating React.memo on FileItemComponent.\r\nconst FILE_OPTIONS: import('../../../../../../../../types/global').FileAction[] = ['view', 'move', 'info', 'delete']\r\n\r\nconst shouldExcludeByTag = (tag?: string | null): boolean => {\r\n if (!tag) return false\r\n return tag === 'user' || tag === 'bim-file' || tag === 'fragment-file' || tag === 'bimModel'\r\n}\r\n\r\nconst shouldExcludeByType = (type?: string | null): boolean => {\r\n if (!type) return false\r\n return type === 'bim-file'\r\n}\r\n\r\ninterface FilesSectionProps {\r\n files: IFile[]\r\n query?: string\r\n}\r\n\r\nexport function FilesSection({ files: _filesProp, query = '' }: FilesSectionProps) {\r\n const t = useTranslations('FileSelection')\r\n\r\n const { state: buildingsState } = React.useContext(BuildingsContext)\r\n const { state: fileState, dispatch: fileDispatch } = React.useContext(FilesContext)\r\n const files = fileState.files.files\r\n const { mapFileIds } = fileState.files\r\n const { building } = buildingsState.buildings\r\n const buildingId = building?.id || -1\r\n\r\n const { deleteFile: deleteFileApi } = useDeleteFile(buildingId)\r\n\r\n // Wrap so the store is updated immediately alongside the API call\r\n const deleteFile = React.useCallback(async (fileId: number) => {\r\n fileDispatch({ type: 'REMOVE_FILE', payload: { id: fileId } })\r\n await deleteFileApi(fileId)\r\n }, [deleteFileApi, fileDispatch])\r\n\r\n const { handleDeleteFile } = useFileDeleteHandler({\r\n deleteFile,\r\n onDeleteSuccess: () => {},\r\n })\r\n\r\n const { handleAddFile, uploadState } = useFileUploadWithProgress({\r\n acceptedFileTypes: '*',\r\n onUploadSuccess: () => {\r\n mutate(`/api/files`)\r\n },\r\n onUploadError: (error) => console.error('Error uploading file:', error),\r\n })\r\n\r\n const nonBimFiles = React.useMemo(() => {\r\n return files\r\n .filter(file => file.extension !== 'ifc' && file.extension !== 'frag')\r\n .filter(file => !shouldExcludeByTag(file.tag))\r\n .filter(file => !shouldExcludeByType((file as any).type))\r\n .map(file => ({ ...file, isVisible: mapFileIds.includes(file.id) }))\r\n .sort((a, b) => {\r\n const aOnMap = mapFileIds.includes(a.id)\r\n const bOnMap = mapFileIds.includes(b.id)\r\n if (aOnMap !== bOnMap) return aOnMap ? -1 : 1\r\n return a.name.localeCompare(b.name)\r\n })\r\n }, [files, mapFileIds])\r\n\r\n const [localFiles, setLocalFiles] = React.useState(nonBimFiles)\r\n\r\n React.useEffect(() => {\r\n setLocalFiles(nonBimFiles)\r\n }, [nonBimFiles])\r\n\r\n const handleMoveFile = React.useCallback((file: IFile) => {\r\n fileDispatch({ type: 'EDIT_FILE', payload: { file } })\r\n }, [fileDispatch])\r\n\r\n // useCallback so the onView prop identity stays stable across renders.\r\n // Otherwise useFileActions' internal useCallback dep on onView re-fires every\r\n // render, producing a new handleAction identity, which defeats React.memo on\r\n // FileItemComponent.\r\n const handleViewFile = React.useCallback((file: IFile, newVisibility: boolean) => {\r\n fileDispatch({\r\n type: newVisibility ? 'ADD_TO_MAP' : 'REMOVE_FROM_MAP',\r\n payload: { id: file.id },\r\n })\r\n }, [fileDispatch])\r\n\r\n const { handleAction, deleteDialog } = useFileActions({\r\n files: localFiles,\r\n setFiles: setLocalFiles,\r\n buildingId,\r\n handleDeleteFile,\r\n onView: handleViewFile,\r\n onMove: handleMoveFile,\r\n })\r\n\r\n const areAllMapFilesHidden = React.useMemo(\r\n () => nonBimFiles.every(file => !mapFileIds.includes(file.id)),\r\n [nonBimFiles, mapFileIds]\r\n )\r\n\r\n const filteredFiles = React.useMemo(() => {\r\n if (!query.trim()) return nonBimFiles\r\n return nonBimFiles.filter(file =>\r\n file.name.toLowerCase().includes(query.toLowerCase())\r\n )\r\n }, [nonBimFiles, query])\r\n\r\n const handleSwitchVariant = () => ({\r\n checked: !areAllMapFilesHidden,\r\n onCheckedChange: (checked: boolean) => {\r\n if (checked) {\r\n fileDispatch({ type: 'ADD_ALL_TO_MAP', payload: { ids: nonBimFiles.map(f => f.id) } })\r\n }\r\n else {\r\n fileDispatch({ type: 'REMOVE_ALL_FROM_MAP' } as never)\r\n }\r\n },\r\n })\r\n\r\n return (\r\n <div className=\"h-full min-h-0\">\r\n <CollapsibleSection\r\n title={t('filesTitle')}\r\n icon={LR.FileText}\r\n className=\"h-full min-h-0 flex flex-col\"\r\n style={{ height: '100%', minHeight: 0 }}\r\n itemCount={filteredFiles.length}\r\n onAddItem={uploadState.uploading ? undefined : handleAddFile}\r\n addItemTitle={uploadState.uploading ? `${t('uploadingFile')} ${uploadState.progress}%` : t('addFileTitle')}\r\n switchVariant={handleSwitchVariant()}\r\n >\r\n <div className=\"flex-1 min-h-0 overflow-y-auto space-y-1\">\r\n {uploadState.uploading && (\r\n <div className=\"flex items-center gap-2 px-2 py-2 text-sm text-muted-foreground\">\r\n <LoadingSpinner className=\"h-4 w-4\" />\r\n <span>{t('uploadingFile')} {uploadState.progress}%</span>\r\n </div>\r\n )}\r\n {filteredFiles.map((item) => (\r\n <FileItemComponent\r\n key={item.id}\r\n file={item}\r\n onAction={handleAction}\r\n options={FILE_OPTIONS}\r\n translationKey=\"FileSelection\"\r\n confirmDelete={false}\r\n />\r\n ))}\r\n </div>\r\n </CollapsibleSection>\r\n\r\n <ConfirmDialog\r\n isOpen={deleteDialog.isOpen}\r\n isDeleting={deleteDialog.isDeleting}\r\n onOpenChange={deleteDialog.onOpenChange}\r\n handleConfirm={deleteDialog.onConfirm}\r\n itemName={deleteDialog.itemName}\r\n />\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
|
package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModelsSection.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAW9B,OAAO,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,uCAAuC,CAAA;AAO7E,UAAU,kBAAkB;IAC1B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,KAAU,EAAE,EAAE,kBAAkB,
|
|
1
|
+
{"version":3,"file":"ModelsSection.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAW9B,OAAO,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,uCAAuC,CAAA;AAO7E,UAAU,kBAAkB;IAC1B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,KAAU,EAAE,EAAE,kBAAkB,qBAgLtE"}
|
|
@@ -40,15 +40,11 @@ function ModelsSection({ files, query = "" }) {
|
|
|
40
40
|
const { buildings, building: currentBuilding } = buildingsState.buildings;
|
|
41
41
|
const { deleteFile } = useDeleteFile();
|
|
42
42
|
const { handleDeleteFile } = useFileDeleteHandler({
|
|
43
|
-
deleteFile
|
|
44
|
-
onDeleteSuccess: () => {
|
|
45
|
-
console.log("Model file deleted successfully");
|
|
46
|
-
}
|
|
43
|
+
deleteFile
|
|
47
44
|
});
|
|
48
45
|
const { handleAddFile, uploadState } = useFileUploadWithProgress({
|
|
49
46
|
acceptedFileTypes: ".ifc,.frag",
|
|
50
47
|
onUploadSuccess: () => {
|
|
51
|
-
console.log("Model uploaded successfully");
|
|
52
48
|
mutate(`/api/files`);
|
|
53
49
|
},
|
|
54
50
|
onUploadError: (error) => {
|
package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.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 { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { mutate } from 'swr'\r\n\r\nimport { useDeleteFile } from '../../../../../../../../hooks/files/files'\r\nimport { BimContext, BuildingsContext } from '../../../../../../../../store'\r\nimport ConfirmDialog from '../../../../../../../ConfirmDialog'\r\nimport { CollapsibleSection } from '../../../../../../../ui/CollapsibleSection'\r\nimport { useFileDeleteHandler, FileItemComponent, useFileActions, useFileUploadWithProgress } from '../../../../../../../ui/FilesManager'\r\nimport { LoadingSpinner } from '../../../../../../../ui/LoadingSpinner'\r\nimport { toggleBimToMap as dispatchToggleBimToMap } from '../../../../../utils/toggleBimToMap'\r\n\r\nimport type { DbFile as DbFile } from '../../../../../../../../types/dbTypes'\r\n\r\n\r\ntype FileAction = import('../../../../../../../../types/global').FileAction\r\nconst OPTIONS_ON_MAP: FileAction[] = ['download', 'view', 'move', 'info', 'delete']\r\nconst OPTIONS_OFF_MAP: FileAction[] = ['download', 'view', 'info', 'delete']\r\n\r\ninterface ModelsSectionProps {\r\n files: DbFile[]\r\n query?: string\r\n}\r\n\r\nexport function ModelsSection({ files, query = '' }: ModelsSectionProps) {\r\n // Translation\r\n const t = useTranslations('FileItemComponent')\r\n\r\n // Get Map context for models added to map\r\n const { state: bimState, dispatch: bimDispatch } = React.useContext(BimContext)\r\n const { bimModelsAddedToMap } = bimState.bim\r\n\r\n const { state: buildingsState } = React.useContext(BuildingsContext)\r\n const { buildings, building: currentBuilding } = buildingsState.buildings\r\n\r\n const { deleteFile } = useDeleteFile()\r\n\r\n // Use the reusable delete handler\r\n const { handleDeleteFile } = useFileDeleteHandler({\r\n deleteFile,\r\n onDeleteSuccess: () => {\r\n console.log('Model file deleted successfully')\r\n },\r\n })\r\n\r\n // Use the new upload hook with progress\r\n const { handleAddFile, uploadState } = useFileUploadWithProgress({\r\n acceptedFileTypes: '.ifc,.frag',\r\n onUploadSuccess: () => {\r\n console.log('Model uploaded successfully')\r\n mutate(`/api/files`)\r\n },\r\n onUploadError: (error) => {\r\n console.error('Error uploading model:', error)\r\n }\r\n })\r\n\r\n // Filter and sort models: BIM files only, with models added to map at the top\r\n const sortedModels = React.useMemo(() => {\r\n const bimFiles = files.filter(file => file.extension === 'frag')\r\n\r\n return bimFiles\r\n .map(file => {\r\n const isAddedToMap = bimModelsAddedToMap.some(model => model.bimFile.id === file.id)\r\n return { ...file, isAddedToMap, isVisible: isAddedToMap }\r\n })\r\n .sort((a, b) => {\r\n // Models added to map come first\r\n if (a.isAddedToMap && !b.isAddedToMap) return -1\r\n if (!a.isAddedToMap && b.isAddedToMap) return 1\r\n return 0\r\n })\r\n }, [files, bimModelsAddedToMap])\r\n\r\n // Local state for file management\r\n const [loadedModels, setLoadedModels] = React.useState(sortedModels)\r\n\r\n // Keep local list in sync with sorted models\r\n React.useEffect(() => {\r\n setLoadedModels(sortedModels)\r\n }, [sortedModels])\r\n\r\n const handleToggleModelOnMap = React.useCallback((file: DbFile, isVisible: boolean) => {\r\n if (isVisible) {\r\n // Add model to map — reuse the same payload shape as the building popover\r\n const building = buildings.find(b => b.id === file.attachedFilesBuildingId) ?? currentBuilding ?? null\r\n dispatchToggleBimToMap(bimDispatch, file, building)\r\n } else {\r\n // Remove model from map\r\n bimDispatch({\r\n type: 'REMOVE_BIM_FROM_MAP',\r\n payload: { bimModelName: file.name },\r\n })\r\n }\r\n }, [bimDispatch, buildings])\r\n\r\n const handleMoveModel = React.useCallback((file: DbFile) => {\r\n const isOnMap = bimModelsAddedToMap.some(m => m.bimFile.id === file.id)\r\n if (!isOnMap) return\r\n bimDispatch({\r\n type: 'EDIT_BIM_MODEL_BY_NAME',\r\n payload: { editingBimModel: file.name },\r\n })\r\n }, [bimDispatch, bimModelsAddedToMap])\r\n\r\n const resolveBuildingForFile = React.useCallback(async (file: DbFile) => {\r\n const attachedBuildingId = file.attachedFilesBuildingId\r\n\r\n if (attachedBuildingId == null) {\r\n return currentBuilding ?? null\r\n }\r\n\r\n const cachedBuilding = buildings.find(building => building.id === attachedBuildingId) ?? null\r\n if (cachedBuilding?.buildingOsmId != null) {\r\n return cachedBuilding\r\n }\r\n\r\n try {\r\n const response = await fetch(`/api/buildings/${attachedBuildingId}`)\r\n if (!response.ok) {\r\n return cachedBuilding ?? currentBuilding ?? null\r\n }\r\n\r\n const data = await response.json() as { building?: typeof cachedBuilding }\r\n return data.building ?? cachedBuilding ?? currentBuilding ?? null\r\n }\r\n catch {\r\n return cachedBuilding ?? currentBuilding ?? null\r\n }\r\n }, [buildings, currentBuilding])\r\n\r\n const handleToggleModelOnMapAsync = React.useCallback(async (file: DbFile, isVisible: boolean) => {\r\n if (!isVisible) {\r\n bimDispatch({\r\n type: 'REMOVE_BIM_FROM_MAP',\r\n payload: { bimModelName: file.name },\r\n })\r\n return\r\n }\r\n\r\n const building = await resolveBuildingForFile(file)\r\n dispatchToggleBimToMap(bimDispatch, file, building)\r\n }, [bimDispatch, resolveBuildingForFile])\r\n\r\n // Use the common file actions hook\r\n const { handleAction, deleteDialog } = useFileActions({\r\n files: loadedModels,\r\n setFiles: setLoadedModels,\r\n buildingId: null,\r\n handleDeleteFile,\r\n onView: handleToggleModelOnMapAsync,\r\n onMove: handleMoveModel,\r\n })\r\n\r\n // Filter models based on search query — use sortedModels directly so the\r\n // sidebar always reflects the current BimContext state without waiting for\r\n // the loadedModels sync effect (which lags one render behind).\r\n const filteredModels = React.useMemo(() => {\r\n if (!query.trim()) return sortedModels\r\n return sortedModels.filter(file =>\r\n file.name.toLowerCase().includes(query.toLowerCase())\r\n )\r\n }, [sortedModels, query])\r\n\r\n return (\r\n <div className=\"h-full min-h-0\">\r\n <CollapsibleSection\r\n title={t('modelsTitles')}\r\n icon={LR.Box}\r\n className=\"h-full min-h-0 flex flex-col\"\r\n style={{ height: '100%', minHeight: 0 }}\r\n itemCount={filteredModels.length}\r\n onAddItem={uploadState.uploading ? undefined : handleAddFile}\r\n addItemTitle={uploadState.uploading ? `${t('uploadingFile')} ${uploadState.progress}%` : t('addBimTitle')}\r\n >\r\n <div className=\"flex-1 min-h-0 overflow-y-scroll space-y-1\">\r\n {uploadState.uploading && (\r\n <div className=\"flex items-center gap-2 px-2 py-2 text-sm text-muted-foreground\">\r\n <LoadingSpinner className=\"h-4 w-4\" />\r\n <span>{t('uploadingFile')} {uploadState.progress}%</span>\r\n </div>\r\n )}\r\n {filteredModels.map((file) => (\r\n <div key={file.id}>\r\n <FileItemComponent\r\n file={file}\r\n onAction={handleAction}\r\n options={file.isAddedToMap ? OPTIONS_ON_MAP : OPTIONS_OFF_MAP}\r\n confirmDelete={false}\r\n />\r\n </div>\r\n ))}\r\n </div>\r\n </CollapsibleSection>\r\n\r\n <ConfirmDialog\r\n isOpen={deleteDialog.isOpen}\r\n isDeleting={deleteDialog.isDeleting}\r\n onOpenChange={deleteDialog.onOpenChange}\r\n handleConfirm={deleteDialog.onConfirm}\r\n itemName={deleteDialog.itemName}\r\n />\r\n </div>\r\n )\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.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 { useTranslations } from 'next-intl'\r\nimport * as React from 'react'\r\nimport { mutate } from 'swr'\r\n\r\nimport { useDeleteFile } from '../../../../../../../../hooks/files/files'\r\nimport { BimContext, BuildingsContext } from '../../../../../../../../store'\r\nimport ConfirmDialog from '../../../../../../../ConfirmDialog'\r\nimport { CollapsibleSection } from '../../../../../../../ui/CollapsibleSection'\r\nimport { useFileDeleteHandler, FileItemComponent, useFileActions, useFileUploadWithProgress } from '../../../../../../../ui/FilesManager'\r\nimport { LoadingSpinner } from '../../../../../../../ui/LoadingSpinner'\r\nimport { toggleBimToMap as dispatchToggleBimToMap } from '../../../../../utils/toggleBimToMap'\r\n\r\nimport type { DbFile as DbFile } from '../../../../../../../../types/dbTypes'\r\n\r\n\r\ntype FileAction = import('../../../../../../../../types/global').FileAction\r\nconst OPTIONS_ON_MAP: FileAction[] = ['download', 'view', 'move', 'info', 'delete']\r\nconst OPTIONS_OFF_MAP: FileAction[] = ['download', 'view', 'info', 'delete']\r\n\r\ninterface ModelsSectionProps {\r\n files: DbFile[]\r\n query?: string\r\n}\r\n\r\nexport function ModelsSection({ files, query = '' }: ModelsSectionProps) {\r\n // Translation\r\n const t = useTranslations('FileItemComponent')\r\n\r\n // Get Map context for models added to map\r\n const { state: bimState, dispatch: bimDispatch } = React.useContext(BimContext)\r\n const { bimModelsAddedToMap } = bimState.bim\r\n\r\n const { state: buildingsState } = React.useContext(BuildingsContext)\r\n const { buildings, building: currentBuilding } = buildingsState.buildings\r\n\r\n const { deleteFile } = useDeleteFile()\r\n\r\n // Use the reusable delete handler\r\n const { handleDeleteFile } = useFileDeleteHandler({\r\n deleteFile,\r\n })\r\n\r\n // Use the new upload hook with progress\r\n const { handleAddFile, uploadState } = useFileUploadWithProgress({\r\n acceptedFileTypes: '.ifc,.frag',\r\n onUploadSuccess: () => {\r\n mutate(`/api/files`)\r\n },\r\n onUploadError: (error) => {\r\n console.error('Error uploading model:', error)\r\n }\r\n })\r\n\r\n // Filter and sort models: BIM files only, with models added to map at the top\r\n const sortedModels = React.useMemo(() => {\r\n const bimFiles = files.filter(file => file.extension === 'frag')\r\n\r\n return bimFiles\r\n .map(file => {\r\n const isAddedToMap = bimModelsAddedToMap.some(model => model.bimFile.id === file.id)\r\n return { ...file, isAddedToMap, isVisible: isAddedToMap }\r\n })\r\n .sort((a, b) => {\r\n // Models added to map come first\r\n if (a.isAddedToMap && !b.isAddedToMap) return -1\r\n if (!a.isAddedToMap && b.isAddedToMap) return 1\r\n return 0\r\n })\r\n }, [files, bimModelsAddedToMap])\r\n\r\n // Local state for file management\r\n const [loadedModels, setLoadedModels] = React.useState(sortedModels)\r\n\r\n // Keep local list in sync with sorted models\r\n React.useEffect(() => {\r\n setLoadedModels(sortedModels)\r\n }, [sortedModels])\r\n\r\n const handleToggleModelOnMap = React.useCallback((file: DbFile, isVisible: boolean) => {\r\n if (isVisible) {\r\n // Add model to map — reuse the same payload shape as the building popover\r\n const building = buildings.find(b => b.id === file.attachedFilesBuildingId) ?? currentBuilding ?? null\r\n dispatchToggleBimToMap(bimDispatch, file, building)\r\n } else {\r\n // Remove model from map\r\n bimDispatch({\r\n type: 'REMOVE_BIM_FROM_MAP',\r\n payload: { bimModelName: file.name },\r\n })\r\n }\r\n }, [bimDispatch, buildings])\r\n\r\n const handleMoveModel = React.useCallback((file: DbFile) => {\r\n const isOnMap = bimModelsAddedToMap.some(m => m.bimFile.id === file.id)\r\n if (!isOnMap) return\r\n bimDispatch({\r\n type: 'EDIT_BIM_MODEL_BY_NAME',\r\n payload: { editingBimModel: file.name },\r\n })\r\n }, [bimDispatch, bimModelsAddedToMap])\r\n\r\n const resolveBuildingForFile = React.useCallback(async (file: DbFile) => {\r\n const attachedBuildingId = file.attachedFilesBuildingId\r\n\r\n if (attachedBuildingId == null) {\r\n return currentBuilding ?? null\r\n }\r\n\r\n const cachedBuilding = buildings.find(building => building.id === attachedBuildingId) ?? null\r\n if (cachedBuilding?.buildingOsmId != null) {\r\n return cachedBuilding\r\n }\r\n\r\n try {\r\n const response = await fetch(`/api/buildings/${attachedBuildingId}`)\r\n if (!response.ok) {\r\n return cachedBuilding ?? currentBuilding ?? null\r\n }\r\n\r\n const data = await response.json() as { building?: typeof cachedBuilding }\r\n return data.building ?? cachedBuilding ?? currentBuilding ?? null\r\n }\r\n catch {\r\n return cachedBuilding ?? currentBuilding ?? null\r\n }\r\n }, [buildings, currentBuilding])\r\n\r\n const handleToggleModelOnMapAsync = React.useCallback(async (file: DbFile, isVisible: boolean) => {\r\n if (!isVisible) {\r\n bimDispatch({\r\n type: 'REMOVE_BIM_FROM_MAP',\r\n payload: { bimModelName: file.name },\r\n })\r\n return\r\n }\r\n\r\n const building = await resolveBuildingForFile(file)\r\n dispatchToggleBimToMap(bimDispatch, file, building)\r\n }, [bimDispatch, resolveBuildingForFile])\r\n\r\n // Use the common file actions hook\r\n const { handleAction, deleteDialog } = useFileActions({\r\n files: loadedModels,\r\n setFiles: setLoadedModels,\r\n buildingId: null,\r\n handleDeleteFile,\r\n onView: handleToggleModelOnMapAsync,\r\n onMove: handleMoveModel,\r\n })\r\n\r\n // Filter models based on search query — use sortedModels directly so the\r\n // sidebar always reflects the current BimContext state without waiting for\r\n // the loadedModels sync effect (which lags one render behind).\r\n const filteredModels = React.useMemo(() => {\r\n if (!query.trim()) return sortedModels\r\n return sortedModels.filter(file =>\r\n file.name.toLowerCase().includes(query.toLowerCase())\r\n )\r\n }, [sortedModels, query])\r\n\r\n return (\r\n <div className=\"h-full min-h-0\">\r\n <CollapsibleSection\r\n title={t('modelsTitles')}\r\n icon={LR.Box}\r\n className=\"h-full min-h-0 flex flex-col\"\r\n style={{ height: '100%', minHeight: 0 }}\r\n itemCount={filteredModels.length}\r\n onAddItem={uploadState.uploading ? undefined : handleAddFile}\r\n addItemTitle={uploadState.uploading ? `${t('uploadingFile')} ${uploadState.progress}%` : t('addBimTitle')}\r\n >\r\n <div className=\"flex-1 min-h-0 overflow-y-scroll space-y-1\">\r\n {uploadState.uploading && (\r\n <div className=\"flex items-center gap-2 px-2 py-2 text-sm text-muted-foreground\">\r\n <LoadingSpinner className=\"h-4 w-4\" />\r\n <span>{t('uploadingFile')} {uploadState.progress}%</span>\r\n </div>\r\n )}\r\n {filteredModels.map((file) => (\r\n <div key={file.id}>\r\n <FileItemComponent\r\n file={file}\r\n onAction={handleAction}\r\n options={file.isAddedToMap ? OPTIONS_ON_MAP : OPTIONS_OFF_MAP}\r\n confirmDelete={false}\r\n />\r\n </div>\r\n ))}\r\n </div>\r\n </CollapsibleSection>\r\n\r\n <ConfirmDialog\r\n isOpen={deleteDialog.isOpen}\r\n isDeleting={deleteDialog.isDeleting}\r\n onOpenChange={deleteDialog.onOpenChange}\r\n handleConfirm={deleteDialog.onConfirm}\r\n itemName={deleteDialog.itemName}\r\n />\r\n </div>\r\n )\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoLc,cACA,YADA;AA/Kd,YAAY,QAAQ;AACpB,SAAS,uBAAuB;AAChC,YAAY,WAAW;AACvB,SAAS,cAAc;AAEvB,SAAS,qBAAqB;AAC9B,SAAS,YAAY,wBAAwB;AAC7C,OAAO,mBAAmB;AAC1B,SAAS,0BAA0B;AACnC,SAAS,sBAAsB,mBAAmB,gBAAgB,iCAAiC;AACnG,SAAS,sBAAsB;AAC/B,SAAS,kBAAkB,8BAA8B;AAMzD,MAAM,iBAA+B,CAAC,YAAY,QAAQ,QAAQ,QAAQ,QAAQ;AAClF,MAAM,kBAAgC,CAAC,YAAY,QAAQ,QAAQ,QAAQ;AAOpE,SAAS,cAAc,EAAE,OAAO,QAAQ,GAAG,GAAuB;AAEvE,QAAM,IAAI,gBAAgB,mBAAmB;AAG7C,QAAM,EAAE,OAAO,UAAU,UAAU,YAAY,IAAI,MAAM,WAAW,UAAU;AAC9E,QAAM,EAAE,oBAAoB,IAAI,SAAS;AAEzC,QAAM,EAAE,OAAO,eAAe,IAAI,MAAM,WAAW,gBAAgB;AACnE,QAAM,EAAE,WAAW,UAAU,gBAAgB,IAAI,eAAe;AAEhE,QAAM,EAAE,WAAW,IAAI,cAAc;AAGrC,QAAM,EAAE,iBAAiB,IAAI,qBAAqB;AAAA,IAChD;AAAA,EACF,CAAC;AAGD,QAAM,EAAE,eAAe,YAAY,IAAI,0BAA0B;AAAA,IAC/D,mBAAmB;AAAA,IACnB,iBAAiB,MAAM;AACrB,aAAO,YAAY;AAAA,IACrB;AAAA,IACA,eAAe,CAAC,UAAU;AACxB,cAAQ,MAAM,0BAA0B,KAAK;AAAA,IAC/C;AAAA,EACF,CAAC;AAGD,QAAM,eAAe,MAAM,QAAQ,MAAM;AACvC,UAAM,WAAW,MAAM,OAAO,UAAQ,KAAK,cAAc,MAAM;AAE/D,WAAO,SACJ,IAAI,UAAQ;AACX,YAAM,eAAe,oBAAoB,KAAK,WAAS,MAAM,QAAQ,OAAO,KAAK,EAAE;AACnF,aAAO,iCAAK,OAAL,EAAW,cAAc,WAAW,aAAa;AAAA,IAC1D,CAAC,EACA,KAAK,CAAC,GAAG,MAAM;AAEd,UAAI,EAAE,gBAAgB,CAAC,EAAE,aAAc,QAAO;AAC9C,UAAI,CAAC,EAAE,gBAAgB,EAAE,aAAc,QAAO;AAC9C,aAAO;AAAA,IACT,CAAC;AAAA,EACL,GAAG,CAAC,OAAO,mBAAmB,CAAC;AAG/B,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAS,YAAY;AAGnE,QAAM,UAAU,MAAM;AACpB,oBAAgB,YAAY;AAAA,EAC9B,GAAG,CAAC,YAAY,CAAC;AAEjB,QAAM,yBAAyB,MAAM,YAAY,CAAC,MAAc,cAAuB;AApFzF;AAqFI,QAAI,WAAW;AAEb,YAAM,YAAW,qBAAU,KAAK,OAAK,EAAE,OAAO,KAAK,uBAAuB,MAAzD,YAA8D,oBAA9D,YAAiF;AAClG,6BAAuB,aAAa,MAAM,QAAQ;AAAA,IACpD,OAAO;AAEL,kBAAY;AAAA,QACV,MAAM;AAAA,QACN,SAAS,EAAE,cAAc,KAAK,KAAK;AAAA,MACrC,CAAC;AAAA,IACH;AAAA,EACF,GAAG,CAAC,aAAa,SAAS,CAAC;AAE3B,QAAM,kBAAkB,MAAM,YAAY,CAAC,SAAiB;AAC1D,UAAM,UAAU,oBAAoB,KAAK,OAAK,EAAE,QAAQ,OAAO,KAAK,EAAE;AACtE,QAAI,CAAC,QAAS;AACd,gBAAY;AAAA,MACV,MAAM;AAAA,MACN,SAAS,EAAE,iBAAiB,KAAK,KAAK;AAAA,IACxC,CAAC;AAAA,EACH,GAAG,CAAC,aAAa,mBAAmB,CAAC;AAErC,QAAM,yBAAyB,MAAM,YAAY,OAAO,SAAiB;AA3G3E;AA4GI,UAAM,qBAAqB,KAAK;AAEhC,QAAI,sBAAsB,MAAM;AAC9B,aAAO,4CAAmB;AAAA,IAC5B;AAEA,UAAM,kBAAiB,eAAU,KAAK,cAAY,SAAS,OAAO,kBAAkB,MAA7D,YAAkE;AACzF,SAAI,iDAAgB,kBAAiB,MAAM;AACzC,aAAO;AAAA,IACT;AAEA,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,kBAAkB,kBAAkB,EAAE;AACnE,UAAI,CAAC,SAAS,IAAI;AAChB,gBAAO,+CAAkB,oBAAlB,YAAqC;AAAA,MAC9C;AAEA,YAAM,OAAO,MAAM,SAAS,KAAK;AACjC,cAAO,sBAAK,aAAL,YAAiB,mBAAjB,YAAmC,oBAAnC,YAAsD;AAAA,IAC/D,SACM;AACJ,cAAO,+CAAkB,oBAAlB,YAAqC;AAAA,IAC9C;AAAA,EACF,GAAG,CAAC,WAAW,eAAe,CAAC;AAE/B,QAAM,8BAA8B,MAAM,YAAY,OAAO,MAAc,cAAuB;AAChG,QAAI,CAAC,WAAW;AACd,kBAAY;AAAA,QACV,MAAM;AAAA,QACN,SAAS,EAAE,cAAc,KAAK,KAAK;AAAA,MACrC,CAAC;AACD;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,uBAAuB,IAAI;AAClD,2BAAuB,aAAa,MAAM,QAAQ;AAAA,EACpD,GAAG,CAAC,aAAa,sBAAsB,CAAC;AAGxC,QAAM,EAAE,cAAc,aAAa,IAAI,eAAe;AAAA,IACpD,OAAO;AAAA,IACP,UAAU;AAAA,IACV,YAAY;AAAA,IACZ;AAAA,IACA,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AAKD,QAAM,iBAAiB,MAAM,QAAQ,MAAM;AACzC,QAAI,CAAC,MAAM,KAAK,EAAG,QAAO;AAC1B,WAAO,aAAa;AAAA,MAAO,UACzB,KAAK,KAAK,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC;AAAA,IACtD;AAAA,EACF,GAAG,CAAC,cAAc,KAAK,CAAC;AAExB,SACE,qBAAC,SAAI,WAAU,kBACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,cAAc;AAAA,QACvB,MAAM,GAAG;AAAA,QACT,WAAU;AAAA,QACV,OAAO,EAAE,QAAQ,QAAQ,WAAW,EAAE;AAAA,QACtC,WAAW,eAAe;AAAA,QAC1B,WAAW,YAAY,YAAY,SAAY;AAAA,QAC/C,cAAc,YAAY,YAAY,GAAG,EAAE,eAAe,CAAC,IAAI,YAAY,QAAQ,MAAM,EAAE,aAAa;AAAA,QAExG,+BAAC,SAAI,WAAU,8CACZ;AAAA,sBAAY,aACX,qBAAC,SAAI,WAAU,mEACb;AAAA,gCAAC,kBAAe,WAAU,WAAU;AAAA,YACpC,qBAAC,UAAM;AAAA,gBAAE,eAAe;AAAA,cAAE;AAAA,cAAE,YAAY;AAAA,cAAS;AAAA,eAAC;AAAA,aACpD;AAAA,UAED,eAAe,IAAI,CAAC,SACnB,oBAAC,SACC;AAAA,YAAC;AAAA;AAAA,cACC;AAAA,cACA,UAAU;AAAA,cACV,SAAS,KAAK,eAAe,iBAAiB;AAAA,cAC9C,eAAe;AAAA;AAAA,UACjB,KANQ,KAAK,EAOf,CACD;AAAA,WACH;AAAA;AAAA,IACF;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,QAAQ,aAAa;AAAA,QACrB,YAAY,aAAa;AAAA,QACzB,cAAc,aAAa;AAAA,QAC3B,eAAe,aAAa;AAAA,QAC5B,UAAU,aAAa;AAAA;AAAA,IACzB;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadToPresignedURLS.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB,GAAU,iBAAY,EAAE,SAAI,sBAmB5D,CAAA;AACD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAInD;AAED,eAAO,MAAM,sBAAsB,GAAU,iBAAY,EAAE,SAAI,EAAE,eAAU,
|
|
1
|
+
{"version":3,"file":"uploadToPresignedURLS.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,oBAAoB,GAAU,iBAAY,EAAE,SAAI,sBAmB5D,CAAA;AACD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAInD;AAED,eAAO,MAAM,sBAAsB,GAAU,iBAAY,EAAE,SAAI,EAAE,eAAU,qBA8B1E,CAAA"}
|
package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js
CHANGED
|
@@ -27,9 +27,7 @@ const uploadFileWithProgress = async (presignedUrl, file, onProgress) => {
|
|
|
27
27
|
xhr.upload.addEventListener("progress", (event) => {
|
|
28
28
|
if (event.lengthComputable) {
|
|
29
29
|
const percentComplete = Math.round(event.loaded / event.total * 100);
|
|
30
|
-
onProgress(percentComplete, event.loaded
|
|
31
|
-
console.log(`Upload progress: ${percent}%`);
|
|
32
|
-
});
|
|
30
|
+
onProgress(percentComplete, event.loaded);
|
|
33
31
|
}
|
|
34
32
|
});
|
|
35
33
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nexport const uploadToPresignedUrl = async (presignedUrl, file) => {\r\n try {\r\n const response = await fetch(presignedUrl, {\r\n method: 'PUT',\r\n body: file,\r\n headers: {\r\n 'Content-Type': file.type,\r\n },\r\n })\r\n\r\n if (!response.ok) {\r\n throw new Error(`Upload failed: ${response.status} ${response.statusText}`)\r\n }\r\n\r\n return response\r\n }\r\n catch (error) {\r\n throw new Error(`Upload failed: ${error.message}`)\r\n }\r\n}\r\nexport function getFileExtension(file: File): string {\r\n const parts = file.name.split('.')\r\n if (parts.length <= 1) return ''\r\n return parts.pop()!.toLowerCase()\r\n}\r\n// Progress version\r\nexport const uploadFileWithProgress = async (presignedUrl, file, onProgress) => {\r\n return new Promise((resolve, reject) => {\r\n const xhr = new XMLHttpRequest()\r\n\r\n if (onProgress) {\r\n xhr.upload.addEventListener('progress', (event) => {\r\n if (event.lengthComputable) {\r\n const percentComplete = Math.round((event.loaded / event.total) * 100)\r\n onProgress(percentComplete, event.loaded
|
|
1
|
+
{"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nexport const uploadToPresignedUrl = async (presignedUrl, file) => {\r\n try {\r\n const response = await fetch(presignedUrl, {\r\n method: 'PUT',\r\n body: file,\r\n headers: {\r\n 'Content-Type': file.type,\r\n },\r\n })\r\n\r\n if (!response.ok) {\r\n throw new Error(`Upload failed: ${response.status} ${response.statusText}`)\r\n }\r\n\r\n return response\r\n }\r\n catch (error) {\r\n throw new Error(`Upload failed: ${error.message}`)\r\n }\r\n}\r\nexport function getFileExtension(file: File): string {\r\n const parts = file.name.split('.')\r\n if (parts.length <= 1) return ''\r\n return parts.pop()!.toLowerCase()\r\n}\r\n// Progress version\r\nexport const uploadFileWithProgress = async (presignedUrl, file, onProgress) => {\r\n return new Promise((resolve, reject) => {\r\n const xhr = new XMLHttpRequest()\r\n\r\n if (onProgress) {\r\n xhr.upload.addEventListener('progress', (event) => {\r\n if (event.lengthComputable) {\r\n const percentComplete = Math.round((event.loaded / event.total) * 100)\r\n onProgress(percentComplete, event.loaded)\r\n }\r\n })\r\n }\r\n\r\n xhr.addEventListener('load', () => {\r\n if (xhr.status >= 200 && xhr.status < 300) {\r\n resolve(xhr)\r\n }\r\n else {\r\n reject(new Error(`Upload failed: ${xhr.status}`))\r\n }\r\n })\r\n\r\n xhr.addEventListener('error', () => {\r\n reject(new Error('Network error occurred'))\r\n })\r\n\r\n xhr.open('PUT', presignedUrl)\r\n xhr.setRequestHeader('Content-Type', file.type)\r\n xhr.send(file)\r\n })\r\n}\r\n"],"mappings":"AAGO,MAAM,uBAAuB,OAAO,cAAc,SAAS;AAChE,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,cAAc;AAAA,MACzC,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,SAAS;AAAA,QACP,gBAAgB,KAAK;AAAA,MACvB;AAAA,IACF,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,IAAI,MAAM,kBAAkB,SAAS,MAAM,IAAI,SAAS,UAAU,EAAE;AAAA,IAC5E;AAEA,WAAO;AAAA,EACT,SACO,OAAO;AACZ,UAAM,IAAI,MAAM,kBAAkB,MAAM,OAAO,EAAE;AAAA,EACnD;AACF;AACO,SAAS,iBAAiB,MAAoB;AACnD,QAAM,QAAQ,KAAK,KAAK,MAAM,GAAG;AACjC,MAAI,MAAM,UAAU,EAAG,QAAO;AAC9B,SAAO,MAAM,IAAI,EAAG,YAAY;AAClC;AAEO,MAAM,yBAAyB,OAAO,cAAc,MAAM,eAAe;AAC9E,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,MAAM,IAAI,eAAe;AAE/B,QAAI,YAAY;AACd,UAAI,OAAO,iBAAiB,YAAY,CAAC,UAAU;AACjD,YAAI,MAAM,kBAAkB;AAC1B,gBAAM,kBAAkB,KAAK,MAAO,MAAM,SAAS,MAAM,QAAS,GAAG;AACrE,qBAAW,iBAAiB,MAAM,MAAM;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,iBAAiB,QAAQ,MAAM;AACjC,UAAI,IAAI,UAAU,OAAO,IAAI,SAAS,KAAK;AACzC,gBAAQ,GAAG;AAAA,MACb,OACK;AACH,eAAO,IAAI,MAAM,kBAAkB,IAAI,MAAM,EAAE,CAAC;AAAA,MAClD;AAAA,IACF,CAAC;AAED,QAAI,iBAAiB,SAAS,MAAM;AAClC,aAAO,IAAI,MAAM,wBAAwB,CAAC;AAAA,IAC5C,CAAC;AAED,QAAI,KAAK,OAAO,YAAY;AAC5B,QAAI,iBAAiB,gBAAgB,KAAK,IAAI;AAC9C,QAAI,KAAK,IAAI;AAAA,EACf,CAAC;AACH;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MapClickManager.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/map/utils/MapEventManager/MapClickManager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,UAAU,MAAM,aAAa,CAAA;AAEzC;;;;GAIG;AACH,oBAAY,qBAAqB;IAG/B,sBAAsB,KAAK;IAC3B,2BAA2B,MAAM;IACjC,0BAA0B,MAAM;IAChC,wBAAwB,MAAM;IAC9B,0BAA0B,MAAM;IAChC,sBAAsB,MAAM;IAC5B,qBAAqB,MAAM;IAC3B,UAAU,OAAO;CAClB;AACD,MAAM,MAAM,aAAa,GAAG,CAC1B,KAAK,EAAE,UAAU,CAAC,aAAa,EAC/B,QAAQ,EAAE,UAAU,CAAC,iBAAiB,EAAE,KACrC,IAAI,CAAA;AAQT,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAgB;IAC3B,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,mBAAmB,CAAqC;gBAEpD,GAAG,EAAE,UAAU,CAAC,GAAG;IAOxB,QAAQ,CACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,qBAAqB,EAC/B,QAAQ,EAAE,aAAa;IAOlB,UAAU,CAAC,OAAO,KAAA;IAKzB,OAAO,CAAC,cAAc;
|
|
1
|
+
{"version":3,"file":"MapClickManager.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/map/utils/MapEventManager/MapClickManager.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,UAAU,MAAM,aAAa,CAAA;AAEzC;;;;GAIG;AACH,oBAAY,qBAAqB;IAG/B,sBAAsB,KAAK;IAC3B,2BAA2B,MAAM;IACjC,0BAA0B,MAAM;IAChC,wBAAwB,MAAM;IAC9B,0BAA0B,MAAM;IAChC,sBAAsB,MAAM;IAC5B,qBAAqB,MAAM;IAC3B,UAAU,OAAO;CAClB;AACD,MAAM,MAAM,aAAa,GAAG,CAC1B,KAAK,EAAE,UAAU,CAAC,aAAa,EAC/B,QAAQ,EAAE,UAAU,CAAC,iBAAiB,EAAE,KACrC,IAAI,CAAA;AAQT,qBAAa,eAAe;IAC1B,OAAO,CAAC,GAAG,CAAgB;IAC3B,OAAO,CAAC,aAAa,CAAqB;IAC1C,OAAO,CAAC,mBAAmB,CAAqC;gBAEpD,GAAG,EAAE,UAAU,CAAC,GAAG;IAOxB,QAAQ,CACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,qBAAqB,EAC/B,QAAQ,EAAE,aAAa;IAOlB,UAAU,CAAC,OAAO,KAAA;IAKzB,OAAO,CAAC,cAAc;IA2BtB,OAAO;CAIR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../src/core/components/viewers/map/utils/MapEventManager/MapClickManager.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// MapClickManager.ts\r\nimport type maplibregl from 'maplibre-gl'\r\n\r\n/**\r\n * This is very important. Declare a new priority for your layer in this enum if you need to,\r\n * otherwise, just use the one that has been declared\r\n * Higher priority is clicked first.\r\n */\r\nexport enum MapLayerClickPriority {\r\n // Lowest priority: a site polygon only handles a click when no other\r\n // interactive layer (buildings, comments, sensors, files, ...) was hit.\r\n SiteLayerClickPriority = 50,\r\n BuildingLayersClickPriority = 100,\r\n CommentLayersClickPriority = 200,\r\n MartinLayerClickPriority = 300,\r\n OpenDataLayerClickPriority = 350,\r\n FileLayerCLickPriority = 400,\r\n BimModelLayerPriority = 600,\r\n ActiveTool = 1000, // SPECIAL ONE, MAKE SURE TO DISABLE THE TOOL WHEN YOU ARE FINISH.\r\n}\r\nexport type ClickCallback = (\r\n event: maplibregl.MapMouseEvent,\r\n features: maplibregl.MapGeoJSONFeature[],\r\n) => void\r\n\r\ninterface LayerClickHandler {\r\n layerId: string\r\n priority: number\r\n callback: ClickCallback\r\n}\r\n\r\nexport class MapClickManager {\r\n private map: maplibregl.Map // the instance of the map\r\n private clickHandlers: LayerClickHandler[] // all click handlers for layers that has been registered\r\n private boundedClickHandler: (e: maplibregl.MapMouseEvent) => {} // clickHandler has \"this\" object binded to it, so inside the handleClick private function, we can still access this.map.\r\n\r\n constructor(map: maplibregl.Map) {\r\n this.map = map\r\n this.boundedClickHandler = this.handleMapClick.bind(this)\r\n this.clickHandlers = []\r\n this.map.on('click', this.boundedClickHandler)\r\n }\r\n\r\n public register(\r\n layerId: string,\r\n priority: MapLayerClickPriority,\r\n callback: ClickCallback,\r\n ) {\r\n this.clickHandlers.push({ layerId, priority, callback })\r\n this.clickHandlers.sort((a, b) => b.priority - a.priority)\r\n // console.log(this.clickHandlers)\r\n }\r\n\r\n public unregister(layerId) {\r\n this.clickHandlers = this.clickHandlers.filter(handler => handler.layerId !== layerId)\r\n // console.log(this.clickHandlers)\r\n }\r\n\r\n private handleMapClick(e: maplibregl.MapMouseEvent) {\r\n // for active tools\r\n for (const handler of this.clickHandlers) {\r\n if (handler.priority === MapLayerClickPriority.ActiveTool) {\r\n handler.callback(e, [])\r\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../../src/core/components/viewers/map/utils/MapEventManager/MapClickManager.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\n// MapClickManager.ts\r\nimport type maplibregl from 'maplibre-gl'\r\n\r\n/**\r\n * This is very important. Declare a new priority for your layer in this enum if you need to,\r\n * otherwise, just use the one that has been declared\r\n * Higher priority is clicked first.\r\n */\r\nexport enum MapLayerClickPriority {\r\n // Lowest priority: a site polygon only handles a click when no other\r\n // interactive layer (buildings, comments, sensors, files, ...) was hit.\r\n SiteLayerClickPriority = 50,\r\n BuildingLayersClickPriority = 100,\r\n CommentLayersClickPriority = 200,\r\n MartinLayerClickPriority = 300,\r\n OpenDataLayerClickPriority = 350,\r\n FileLayerCLickPriority = 400,\r\n BimModelLayerPriority = 600,\r\n ActiveTool = 1000, // SPECIAL ONE, MAKE SURE TO DISABLE THE TOOL WHEN YOU ARE FINISH.\r\n}\r\nexport type ClickCallback = (\r\n event: maplibregl.MapMouseEvent,\r\n features: maplibregl.MapGeoJSONFeature[],\r\n) => void\r\n\r\ninterface LayerClickHandler {\r\n layerId: string\r\n priority: number\r\n callback: ClickCallback\r\n}\r\n\r\nexport class MapClickManager {\r\n private map: maplibregl.Map // the instance of the map\r\n private clickHandlers: LayerClickHandler[] // all click handlers for layers that has been registered\r\n private boundedClickHandler: (e: maplibregl.MapMouseEvent) => {} // clickHandler has \"this\" object binded to it, so inside the handleClick private function, we can still access this.map.\r\n\r\n constructor(map: maplibregl.Map) {\r\n this.map = map\r\n this.boundedClickHandler = this.handleMapClick.bind(this)\r\n this.clickHandlers = []\r\n this.map.on('click', this.boundedClickHandler)\r\n }\r\n\r\n public register(\r\n layerId: string,\r\n priority: MapLayerClickPriority,\r\n callback: ClickCallback,\r\n ) {\r\n this.clickHandlers.push({ layerId, priority, callback })\r\n this.clickHandlers.sort((a, b) => b.priority - a.priority)\r\n // console.log(this.clickHandlers)\r\n }\r\n\r\n public unregister(layerId) {\r\n this.clickHandlers = this.clickHandlers.filter(handler => handler.layerId !== layerId)\r\n // console.log(this.clickHandlers)\r\n }\r\n\r\n private handleMapClick(e: maplibregl.MapMouseEvent) {\r\n // for active tools\r\n for (const handler of this.clickHandlers) {\r\n if (handler.priority === MapLayerClickPriority.ActiveTool) {\r\n handler.callback(e, [])\r\n return\r\n }\r\n }\r\n\r\n // console.log(e.point)\r\n const features = this.map.queryRenderedFeatures(e.point)\r\n // console.log(features)\r\n\r\n if (!features) return\r\n\r\n for (let i = 0; i < this.clickHandlers.length; i++) {\r\n const currentHandler = this.clickHandlers[i]\r\n const hits = features.filter(f => f.layer?.id == currentHandler.layerId)\r\n\r\n if (hits.length > 0) {\r\n // console.log(currentHandler)\r\n currentHandler.callback(e, hits)\r\n break\r\n }\r\n }\r\n }\r\n\r\n destroy() {\r\n this.map.off('click', this.boundedClickHandler)\r\n this.clickHandlers = []\r\n }\r\n}\r\n"],"mappings":"AAWO,IAAK,wBAAL,kBAAKA,2BAAL;AAGL,EAAAA,8CAAA,4BAAyB,MAAzB;AACA,EAAAA,8CAAA,iCAA8B,OAA9B;AACA,EAAAA,8CAAA,gCAA6B,OAA7B;AACA,EAAAA,8CAAA,8BAA2B,OAA3B;AACA,EAAAA,8CAAA,gCAA6B,OAA7B;AACA,EAAAA,8CAAA,4BAAyB,OAAzB;AACA,EAAAA,8CAAA,2BAAwB,OAAxB;AACA,EAAAA,8CAAA,gBAAa,OAAb;AAVU,SAAAA;AAAA,GAAA;AAuBL,MAAM,gBAAgB;AAAA;AAAA,EAK3B,YAAY,KAAqB;AAC/B,SAAK,MAAM;AACX,SAAK,sBAAsB,KAAK,eAAe,KAAK,IAAI;AACxD,SAAK,gBAAgB,CAAC;AACtB,SAAK,IAAI,GAAG,SAAS,KAAK,mBAAmB;AAAA,EAC/C;AAAA,EAEO,SACL,SACA,UACA,UACA;AACA,SAAK,cAAc,KAAK,EAAE,SAAS,UAAU,SAAS,CAAC;AACvD,SAAK,cAAc,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AAAA,EAE3D;AAAA,EAEO,WAAW,SAAS;AACzB,SAAK,gBAAgB,KAAK,cAAc,OAAO,aAAW,QAAQ,YAAY,OAAO;AAAA,EAEvF;AAAA,EAEQ,eAAe,GAA6B;AAElD,eAAW,WAAW,KAAK,eAAe;AACxC,UAAI,QAAQ,aAAa,sBAAkC;AACzD,gBAAQ,SAAS,GAAG,CAAC,CAAC;AACtB;AAAA,MACF;AAAA,IACF;AAGA,UAAM,WAAW,KAAK,IAAI,sBAAsB,EAAE,KAAK;AAGvD,QAAI,CAAC,SAAU;AAEf,aAAS,IAAI,GAAG,IAAI,KAAK,cAAc,QAAQ,KAAK;AAClD,YAAM,iBAAiB,KAAK,cAAc,CAAC;AAC3C,YAAM,OAAO,SAAS,OAAO,OAAE;AA9ErC;AA8EwC,wBAAE,UAAF,mBAAS,OAAM,eAAe;AAAA,OAAO;AAEvE,UAAI,KAAK,SAAS,GAAG;AAEnB,uBAAe,SAAS,GAAG,IAAI;AAC/B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,UAAU;AACR,SAAK,IAAI,IAAI,SAAS,KAAK,mBAAmB;AAC9C,SAAK,gBAAgB,CAAC;AAAA,EACxB;AACF;","names":["MapLayerClickPriority"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flyToEach.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/map/utils/flyToEach.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAEtC,eAAO,MAAM,SAAS,GAAU,KAAK,GAAG,EAAE,WAAW,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"flyToEach.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/map/utils/flyToEach.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,aAAa,CAAA;AAEtC,eAAO,MAAM,SAAS,GAAU,KAAK,GAAG,EAAE,WAAW,QAAQ,EAAE,kBAsB9D,CAAA"}
|
|
@@ -8,7 +8,6 @@ const flyToEach = async (map, buildings) => {
|
|
|
8
8
|
if (osmId) {
|
|
9
9
|
building.buildingOsmId = String(osmId);
|
|
10
10
|
}
|
|
11
|
-
console.log("OSM ID for", building.buildingName, ":", osmId);
|
|
12
11
|
if (typeof osmId === "number") {
|
|
13
12
|
await updateBuildingOsmId(building.id, osmId);
|
|
14
13
|
} else {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/map/utils/flyToEach.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { getOsmIdFromLatLng } from './getOsmIdFromMartin'\r\nimport { updateBuildingOsmId } from './updateBuildingOsmId'\r\n\r\nimport type { Building } from '../../../../types/dbTypes'\r\nimport type { Map } from 'maplibre-gl'\r\n\r\nexport const flyToEach = async (map: Map, buildings: Building[]) => {\r\n // Sequentially process each building and wait for each to finish\r\n for (const building of buildings) {\r\n if (building.buildingOsmId) continue\r\n try {\r\n const osmId = await getOsmIdFromLatLng(map, building.buildingLatitude, building.buildingLongitude)\r\n if (osmId) {\r\n building.buildingOsmId = String(osmId)\r\n }\r\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/map/utils/flyToEach.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { getOsmIdFromLatLng } from './getOsmIdFromMartin'\r\nimport { updateBuildingOsmId } from './updateBuildingOsmId'\r\n\r\nimport type { Building } from '../../../../types/dbTypes'\r\nimport type { Map } from 'maplibre-gl'\r\n\r\nexport const flyToEach = async (map: Map, buildings: Building[]) => {\r\n // Sequentially process each building and wait for each to finish\r\n for (const building of buildings) {\r\n if (building.buildingOsmId) continue\r\n try {\r\n const osmId = await getOsmIdFromLatLng(map, building.buildingLatitude, building.buildingLongitude)\r\n if (osmId) {\r\n building.buildingOsmId = String(osmId)\r\n }\r\n if (typeof osmId === 'number') {\r\n await updateBuildingOsmId(building.id, osmId)\r\n }\r\n else {\r\n throw new TypeError(`Failed to fetch OSM ID for ${building.buildingName}`)\r\n }\r\n }\r\n catch (error) {\r\n console.error(error)\r\n // Optionally continue on error, or break if you want to stop\r\n // continue;\r\n }\r\n }\r\n}\r\n"],"mappings":"AAGA,SAAS,0BAA0B;AACnC,SAAS,2BAA2B;AAK7B,MAAM,YAAY,OAAO,KAAU,cAA0B;AAElE,aAAW,YAAY,WAAW;AAChC,QAAI,SAAS,cAAe;AAC5B,QAAI;AACF,YAAM,QAAQ,MAAM,mBAAmB,KAAK,SAAS,kBAAkB,SAAS,iBAAiB;AACjG,UAAI,OAAO;AACT,iBAAS,gBAAgB,OAAO,KAAK;AAAA,MACvC;AACA,UAAI,OAAO,UAAU,UAAU;AAC7B,cAAM,oBAAoB,SAAS,IAAI,KAAK;AAAA,MAC9C,OACK;AACH,cAAM,IAAI,UAAU,8BAA8B,SAAS,YAAY,EAAE;AAAA,MAC3E;AAAA,IACF,SACO,OAAO;AACZ,cAAQ,MAAM,KAAK;AAAA,IAGrB;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getOsmIdbyCoordinates.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/map/utils/getOsmIdbyCoordinates.ts"],"names":[],"mappings":"AAGA,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"getOsmIdbyCoordinates.d.ts","sourceRoot":"","sources":["../../../../../../src/core/components/viewers/map/utils/getOsmIdbyCoordinates.ts"],"names":[],"mappings":"AAGA,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CA6CxB"}
|
|
@@ -7,7 +7,6 @@ async function getOsmId(latitude, longitude) {
|
|
|
7
7
|
const headers = {
|
|
8
8
|
"User-Agent": "CDT-NA/1.0 (narellano@cims.carleton.ca)"
|
|
9
9
|
};
|
|
10
|
-
console.log(url.toString());
|
|
11
10
|
await new Promise((resolve) => setTimeout(resolve, 2e3));
|
|
12
11
|
try {
|
|
13
12
|
const response = await fetch(url.toString(), { headers });
|
|
@@ -16,7 +15,6 @@ async function getOsmId(latitude, longitude) {
|
|
|
16
15
|
return null;
|
|
17
16
|
}
|
|
18
17
|
const data = await response.json();
|
|
19
|
-
console.log("Nominatim response:", data);
|
|
20
18
|
if (data && (typeof data.osm_id === "string" || typeof data.osm_id === "number")) {
|
|
21
19
|
const osmIdNum = Number(data.osm_id);
|
|
22
20
|
if (isNaN(osmIdNum)) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../src/core/components/viewers/map/utils/getOsmIdbyCoordinates.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nexport async function getOsmId(\r\n latitude: number,\r\n longitude: number,\r\n): Promise<number | null> {\r\n const nominatimApiUrl = 'https://nominatim.openstreetmap.org/reverse'\r\n\r\n const url = new URL(nominatimApiUrl)\r\n url.searchParams.append('format', 'json')\r\n url.searchParams.append('lat', latitude.toString())\r\n url.searchParams.append('lon', longitude.toString())\r\n\r\n // Set User-Agent and Referer headers\r\n const headers = {\r\n 'User-Agent': 'CDT-NA/1.0 (narellano@cims.carleton.ca)',\r\n }\r\n\r\n
|
|
1
|
+
{"version":3,"sources":["../../../../../../src/core/components/viewers/map/utils/getOsmIdbyCoordinates.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nexport async function getOsmId(\r\n latitude: number,\r\n longitude: number,\r\n): Promise<number | null> {\r\n const nominatimApiUrl = 'https://nominatim.openstreetmap.org/reverse'\r\n\r\n const url = new URL(nominatimApiUrl)\r\n url.searchParams.append('format', 'json')\r\n url.searchParams.append('lat', latitude.toString())\r\n url.searchParams.append('lon', longitude.toString())\r\n\r\n // Set User-Agent and Referer headers\r\n const headers = {\r\n 'User-Agent': 'CDT-NA/1.0 (narellano@cims.carleton.ca)',\r\n }\r\n\r\n // Wait at least 1000ms between requests\r\n await new Promise(resolve => setTimeout(resolve, 2000))\r\n\r\n try {\r\n const response = await fetch(url.toString(), { headers })\r\n\r\n if (!response.ok) {\r\n console.error(`HTTP error! Status: ${response.status}`)\r\n return null\r\n }\r\n\r\n const data = await response.json()\r\n\r\n if (data && (typeof data.osm_id === 'string' || typeof data.osm_id === 'number')) {\r\n const osmIdNum = Number(data.osm_id)\r\n if (isNaN(osmIdNum)) {\r\n console.warn('OSM ID is not a valid number:', data.osm_id)\r\n return null\r\n }\r\n else {\r\n return osmIdNum\r\n }\r\n }\r\n else {\r\n console.warn('No OSM ID found for the given coordinates in the response:', data)\r\n return null\r\n }\r\n }\r\n catch (error) {\r\n console.error('Error fetching OSM ID:', error)\r\n return null\r\n }\r\n}\r\n\r\n// Attribution: Data © OpenStreetMap contributors, ODbL license.\r\n"],"mappings":"AAGA,eAAsB,SACpB,UACA,WACwB;AACxB,QAAM,kBAAkB;AAExB,QAAM,MAAM,IAAI,IAAI,eAAe;AACnC,MAAI,aAAa,OAAO,UAAU,MAAM;AACxC,MAAI,aAAa,OAAO,OAAO,SAAS,SAAS,CAAC;AAClD,MAAI,aAAa,OAAO,OAAO,UAAU,SAAS,CAAC;AAGnD,QAAM,UAAU;AAAA,IACd,cAAc;AAAA,EAChB;AAGA,QAAM,IAAI,QAAQ,aAAW,WAAW,SAAS,GAAI,CAAC;AAEtD,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,IAAI,SAAS,GAAG,EAAE,QAAQ,CAAC;AAExD,QAAI,CAAC,SAAS,IAAI;AAChB,cAAQ,MAAM,uBAAuB,SAAS,MAAM,EAAE;AACtD,aAAO;AAAA,IACT;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,QAAI,SAAS,OAAO,KAAK,WAAW,YAAY,OAAO,KAAK,WAAW,WAAW;AAChF,YAAM,WAAW,OAAO,KAAK,MAAM;AACnC,UAAI,MAAM,QAAQ,GAAG;AACnB,gBAAQ,KAAK,iCAAiC,KAAK,MAAM;AACzD,eAAO;AAAA,MACT,OACK;AACH,eAAO;AAAA,MACT;AAAA,IACF,OACK;AACH,cAAQ,KAAK,8DAA8D,IAAI;AAC/E,aAAO;AAAA,IACT;AAAA,EACF,SACO,OAAO;AACZ,YAAQ,MAAM,0BAA0B,KAAK;AAC7C,WAAO;AAAA,EACT;AACF;","names":[]}
|
|
@@ -37,4 +37,15 @@ export declare const useCreateComment: () => {
|
|
|
37
37
|
createError: any;
|
|
38
38
|
createdData: import("../..").Comment;
|
|
39
39
|
};
|
|
40
|
+
export declare const useDeleteComments: () => {
|
|
41
|
+
deleteComments: import("swr/mutation").TriggerWithOptionsArgs<{
|
|
42
|
+
ids: number[];
|
|
43
|
+
buildingIds: number[];
|
|
44
|
+
authorIds: number[];
|
|
45
|
+
}, any, [string], {
|
|
46
|
+
ids: number[];
|
|
47
|
+
}>;
|
|
48
|
+
isDeleting: boolean;
|
|
49
|
+
deleteError: any;
|
|
50
|
+
};
|
|
40
51
|
//# sourceMappingURL=comments.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/comments/comments.ts"],"names":[],"mappings":"AAOA;;;GAGG;AAEH,eAAO,MAAM,WAAW;;;;CACc,CAAC;AAEvC,eAAO,MAAM,UAAU,GAAI,IAAI,MAAM,GAAG,IAAI;;;;;;;;;;;CACL,CAAC;AAExC,eAAO,MAAM,mBAAmB,GAAI,UAAU,MAAM,GAAG,IAAI;;;;CACL,CAAC;AAEvD,eAAO,MAAM,qBAAqB,GAAI,YAAY,MAAM,GAAG,IAAI;;;;CACL,CAAC;AAE3D,eAAO,MAAM,gBAAgB;;;;;;;CACc,CAAC"}
|
|
1
|
+
{"version":3,"file":"comments.d.ts","sourceRoot":"","sources":["../../../../src/core/hooks/comments/comments.ts"],"names":[],"mappings":"AAOA;;;GAGG;AAEH,eAAO,MAAM,WAAW;;;;CACc,CAAC;AAEvC,eAAO,MAAM,UAAU,GAAI,IAAI,MAAM,GAAG,IAAI;;;;;;;;;;;CACL,CAAC;AAExC,eAAO,MAAM,mBAAmB,GAAI,UAAU,MAAM,GAAG,IAAI;;;;CACL,CAAC;AAEvD,eAAO,MAAM,qBAAqB,GAAI,YAAY,MAAM,GAAG,IAAI;;;;CACL,CAAC;AAE3D,eAAO,MAAM,gBAAgB;;;;;;;CACc,CAAC;AAE5C,eAAO,MAAM,iBAAiB;;;;;;;;;;CACc,CAAC"}
|
|
@@ -5,11 +5,13 @@ const useComment = (id) => useCoreHooks().comment.useComment(id);
|
|
|
5
5
|
const useCommentsByAuthor = (authorId) => useCoreHooks().comment.useCommentsByAuthor(authorId);
|
|
6
6
|
const useCommentsByBuilding = (buildingId) => useCoreHooks().comment.useCommentsByBuilding(buildingId);
|
|
7
7
|
const useCreateComment = () => useCoreHooks().comment.useCreateComment();
|
|
8
|
+
const useDeleteComments = () => useCoreHooks().comment.useDeleteComments();
|
|
8
9
|
export {
|
|
9
10
|
useComment,
|
|
10
11
|
useComments,
|
|
11
12
|
useCommentsByAuthor,
|
|
12
13
|
useCommentsByBuilding,
|
|
13
|
-
useCreateComment
|
|
14
|
+
useCreateComment,
|
|
15
|
+
useDeleteComments
|
|
14
16
|
};
|
|
15
17
|
//# sourceMappingURL=comments.js.map
|