@collabdt/core 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/dist/core/components/authentication/ForgotPassword.d.ts.map +1 -1
  2. package/dist/core/components/authentication/ForgotPassword.js +0 -1
  3. package/dist/core/components/authentication/ForgotPassword.js.map +1 -1
  4. package/dist/core/components/ui/Comments/CollapsibleCommentItem.d.ts +12 -2
  5. package/dist/core/components/ui/Comments/CollapsibleCommentItem.d.ts.map +1 -1
  6. package/dist/core/components/ui/Comments/CollapsibleCommentItem.js +137 -139
  7. package/dist/core/components/ui/Comments/CollapsibleCommentItem.js.map +1 -1
  8. package/dist/core/components/ui/Comments/Comment.d.ts +18 -1
  9. package/dist/core/components/ui/Comments/Comment.d.ts.map +1 -1
  10. package/dist/core/components/ui/Comments/Comment.js +94 -23
  11. package/dist/core/components/ui/Comments/Comment.js.map +1 -1
  12. package/dist/core/components/ui/Comments/CommentActionButtons.d.ts +34 -0
  13. package/dist/core/components/ui/Comments/CommentActionButtons.d.ts.map +1 -0
  14. package/dist/core/components/ui/Comments/CommentActionButtons.js +106 -0
  15. package/dist/core/components/ui/Comments/CommentActionButtons.js.map +1 -0
  16. package/dist/core/components/ui/Comments/CommentInput.d.ts.map +1 -1
  17. package/dist/core/components/ui/Comments/CommentInput.js +5 -2
  18. package/dist/core/components/ui/Comments/CommentInput.js.map +1 -1
  19. package/dist/core/components/ui/Comments/CommentsSection.d.ts.map +1 -1
  20. package/dist/core/components/ui/Comments/CommentsSection.js +86 -42
  21. package/dist/core/components/ui/Comments/CommentsSection.js.map +1 -1
  22. package/dist/core/components/ui/Comments/commentUtils.d.ts.map +1 -1
  23. package/dist/core/components/ui/Comments/commentUtils.js +2 -1
  24. package/dist/core/components/ui/Comments/commentUtils.js.map +1 -1
  25. package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.d.ts.map +1 -1
  26. package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.js +0 -1
  27. package/dist/core/components/ui/FilesManager/src/useFileUploadWithProgress.js.map +1 -1
  28. package/dist/core/components/ui/InfoSidebar/Container.d.ts.map +1 -1
  29. package/dist/core/components/ui/InfoSidebar/Container.js +16 -12
  30. package/dist/core/components/ui/InfoSidebar/Container.js.map +1 -1
  31. package/dist/core/components/ui/Sensors/SensorInput.d.ts.map +1 -1
  32. package/dist/core/components/ui/Sensors/SensorInput.js +0 -1
  33. package/dist/core/components/ui/Sensors/SensorInput.js.map +1 -1
  34. package/dist/core/components/ui/Sensors/SensorsSection.js +0 -2
  35. package/dist/core/components/ui/Sensors/SensorsSection.js.map +1 -1
  36. package/dist/core/components/ui/Sidebar.d.ts.map +1 -1
  37. package/dist/core/components/ui/Sidebar.js +27 -4
  38. package/dist/core/components/ui/Sidebar.js.map +1 -1
  39. package/dist/core/components/ui/ToolbarButton.d.ts.map +1 -1
  40. package/dist/core/components/ui/ToolbarButton.js +0 -1
  41. package/dist/core/components/ui/ToolbarButton.js.map +1 -1
  42. package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.d.ts.map +1 -1
  43. package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.js +0 -2
  44. package/dist/core/components/viewers/Data/advancedFilter/FiltersDialog.js.map +1 -1
  45. package/dist/core/components/viewers/Data/details/EnergySourcesGrid.d.ts.map +1 -1
  46. package/dist/core/components/viewers/Data/details/EnergySourcesGrid.js +0 -10
  47. package/dist/core/components/viewers/Data/details/EnergySourcesGrid.js.map +1 -1
  48. package/dist/core/components/viewers/Data/files/DatatableFileAdder.d.ts.map +1 -1
  49. package/dist/core/components/viewers/Data/files/DatatableFileAdder.js +0 -1
  50. package/dist/core/components/viewers/Data/files/DatatableFileAdder.js.map +1 -1
  51. package/dist/core/components/viewers/Data/files/utils/fuzzySearch.d.ts.map +1 -1
  52. package/dist/core/components/viewers/Data/files/utils/fuzzySearch.js +0 -1
  53. package/dist/core/components/viewers/Data/files/utils/fuzzySearch.js.map +1 -1
  54. package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.d.ts.map +1 -1
  55. package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.js +0 -1
  56. package/dist/core/components/viewers/Data/userDetails/UserMoreOptions.js.map +1 -1
  57. package/dist/core/components/viewers/Data/utils/filterHelper.d.ts.map +1 -1
  58. package/dist/core/components/viewers/Data/utils/filterHelper.js +0 -2
  59. package/dist/core/components/viewers/Data/utils/filterHelper.js.map +1 -1
  60. package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.d.ts.map +1 -1
  61. package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js +0 -3
  62. package/dist/core/components/viewers/bim/src/BimSidebar/src/CommunicationTab/src/TopicsSection.js.map +1 -1
  63. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
  64. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js +1 -7
  65. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/FilesSection.js.map +1 -1
  66. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.d.ts.map +1 -1
  67. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.js +1 -9
  68. package/dist/core/components/viewers/bim/src/BimSidebar/src/FileTab/src/ModelsSection.js.map +1 -1
  69. package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.d.ts.map +1 -1
  70. package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.js +4 -3
  71. package/dist/core/components/viewers/bim/src/BimSidebar/src/TabSelector.js.map +1 -1
  72. package/dist/core/components/viewers/bim/src/ClippingPlane.d.ts.map +1 -1
  73. package/dist/core/components/viewers/bim/src/ClippingPlane.js +0 -1
  74. package/dist/core/components/viewers/bim/src/ClippingPlane.js.map +1 -1
  75. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.d.ts +16 -10
  76. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.d.ts.map +1 -1
  77. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js +26 -6
  78. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimComment.js.map +1 -1
  79. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts +23 -0
  80. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.d.ts.map +1 -0
  81. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js +108 -0
  82. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/BimCommentCluster.js.map +1 -0
  83. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.d.ts +44 -0
  84. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.d.ts.map +1 -0
  85. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js +77 -0
  86. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/clusterMarkersByScreenSpace.js.map +1 -0
  87. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts +24 -0
  88. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.d.ts.map +1 -0
  89. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js +27 -0
  90. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/commentCameraUtils.js.map +1 -0
  91. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.d.ts.map +1 -1
  92. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.js +2 -2
  93. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/renderCSS2DMarkers.js.map +1 -1
  94. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.d.ts.map +1 -1
  95. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js +242 -61
  96. package/dist/core/components/viewers/bim/src/tools/AddToBim/src/useCommentMarkers.js.map +1 -1
  97. package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.d.ts.map +1 -1
  98. package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.js +1 -5
  99. package/dist/core/components/viewers/bim/src/tools/ClippingTool/ClippingTool.js.map +1 -1
  100. package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.d.ts.map +1 -1
  101. package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.js +0 -1
  102. package/dist/core/components/viewers/bim/src/tools/ExplodeByLevelTool.js.map +1 -1
  103. package/dist/core/components/viewers/bim/src/tools/measureBimTool.d.ts.map +1 -1
  104. package/dist/core/components/viewers/bim/src/tools/measureBimTool.js +1 -2
  105. package/dist/core/components/viewers/bim/src/tools/measureBimTool.js.map +1 -1
  106. package/dist/core/components/viewers/map/MapViewer.d.ts.map +1 -1
  107. package/dist/core/components/viewers/map/MapViewer.js.map +1 -1
  108. package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.d.ts.map +1 -1
  109. package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.js +11 -14
  110. package/dist/core/components/viewers/map/datasets/src/arcGISDatasets.js.map +1 -1
  111. package/dist/core/components/viewers/map/datasets/src/ckanDatasets.d.ts.map +1 -1
  112. package/dist/core/components/viewers/map/datasets/src/ckanDatasets.js +2 -5
  113. package/dist/core/components/viewers/map/datasets/src/ckanDatasets.js.map +1 -1
  114. package/dist/core/components/viewers/map/datasets/src/localDatasets.d.ts.map +1 -1
  115. package/dist/core/components/viewers/map/datasets/src/localDatasets.js +0 -3
  116. package/dist/core/components/viewers/map/datasets/src/localDatasets.js.map +1 -1
  117. package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.d.ts.map +1 -1
  118. package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.js +0 -11
  119. package/dist/core/components/viewers/map/datasets/src/opendatasoftDatasets.js.map +1 -1
  120. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.d.ts.map +1 -1
  121. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.js +0 -1
  122. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.js.map +1 -1
  123. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.d.ts.map +1 -1
  124. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.js +0 -1
  125. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.js.map +1 -1
  126. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.d.ts.map +1 -1
  127. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.js +0 -1
  128. package/dist/core/components/viewers/map/src/MapFeaturePopoverMenu/src/OpenDataFeaturePopover.js.map +1 -1
  129. package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.d.ts.map +1 -1
  130. package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.js +174 -53
  131. package/dist/core/components/viewers/map/src/MapLayers/src/CommentLayer/index.js.map +1 -1
  132. package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.d.ts.map +1 -1
  133. package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.js +2 -1
  134. package/dist/core/components/viewers/map/src/MapLayers/src/FileLayer/components/MapFileManager.js.map +1 -1
  135. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.d.ts.map +1 -1
  136. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js +0 -3
  137. package/dist/core/components/viewers/map/src/MapLayers/src/SensorsLayer/index.js.map +1 -1
  138. package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.d.ts.map +1 -1
  139. package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.js +4 -3
  140. package/dist/core/components/viewers/map/src/MapSidebar/TabSelector.js.map +1 -1
  141. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.d.ts.map +1 -1
  142. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.js +0 -1
  143. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/FilesSection.js.map +1 -1
  144. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.d.ts.map +1 -1
  145. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js +1 -5
  146. package/dist/core/components/viewers/map/src/MapSidebar/src/FileTab/src/ModelsSection.js.map +1 -1
  147. package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.d.ts.map +1 -1
  148. package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js +1 -3
  149. package/dist/core/components/viewers/map/src/tools/AddTools/AddFile/utils/uploadToPresignedURLS.js.map +1 -1
  150. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.d.ts.map +1 -1
  151. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js +0 -1
  152. package/dist/core/components/viewers/map/utils/MapEventManager/MapClickManager.js.map +1 -1
  153. package/dist/core/components/viewers/map/utils/flyToEach.d.ts.map +1 -1
  154. package/dist/core/components/viewers/map/utils/flyToEach.js +0 -1
  155. package/dist/core/components/viewers/map/utils/flyToEach.js.map +1 -1
  156. package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.d.ts.map +1 -1
  157. package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.js +0 -2
  158. package/dist/core/components/viewers/map/utils/getOsmIdbyCoordinates.js.map +1 -1
  159. package/dist/core/hooks/comments/comments.d.ts +11 -0
  160. package/dist/core/hooks/comments/comments.d.ts.map +1 -1
  161. package/dist/core/hooks/comments/comments.js +3 -1
  162. package/dist/core/hooks/comments/comments.js.map +1 -1
  163. package/dist/core/hooks/comments/createCommentHooks.d.ts +11 -0
  164. package/dist/core/hooks/comments/createCommentHooks.d.ts.map +1 -1
  165. package/dist/core/hooks/comments/createCommentHooks.js +30 -1
  166. package/dist/core/hooks/comments/createCommentHooks.js.map +1 -1
  167. package/dist/core/hooks/files/files.d.ts.map +1 -1
  168. package/dist/core/hooks/files/files.js +0 -1
  169. package/dist/core/hooks/files/files.js.map +1 -1
  170. package/dist/core/hooks/ui/useResizableSidebarWidth.d.ts +30 -0
  171. package/dist/core/hooks/ui/useResizableSidebarWidth.d.ts.map +1 -0
  172. package/dist/core/hooks/ui/useResizableSidebarWidth.js +75 -0
  173. package/dist/core/hooks/ui/useResizableSidebarWidth.js.map +1 -0
  174. package/dist/core/i18n/messages/en.json +5 -0
  175. package/dist/core/i18n/messages/es.json +6 -1
  176. package/dist/core/i18n/messages/fr.json +6 -1
  177. package/dist/core/store/Datasets/reducer.d.ts.map +1 -1
  178. package/dist/core/store/Datasets/reducer.js +0 -2
  179. package/dist/core/store/Datasets/reducer.js.map +1 -1
  180. package/dist/core/store/Files/reducer.js +0 -1
  181. package/dist/core/store/Files/reducer.js.map +1 -1
  182. package/dist/core/store/Menus/context.d.ts.map +1 -1
  183. package/dist/core/store/Menus/context.js +4 -1
  184. package/dist/core/store/Menus/context.js.map +1 -1
  185. package/dist/core/store/Menus/reducer.d.ts +20 -0
  186. package/dist/core/store/Menus/reducer.d.ts.map +1 -1
  187. package/dist/core/store/Menus/reducer.js +21 -3
  188. package/dist/core/store/Menus/reducer.js.map +1 -1
  189. package/dist/core/utils/markerUtils.d.ts +10 -0
  190. package/dist/core/utils/markerUtils.d.ts.map +1 -1
  191. package/dist/core/utils/markerUtils.js +9 -1
  192. package/dist/core/utils/markerUtils.js.map +1 -1
  193. package/dist/core/utils/memcache.d.ts.map +1 -1
  194. package/dist/core/utils/memcache.js +0 -7
  195. package/dist/core/utils/memcache.js.map +1 -1
  196. package/package.json +4 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../src/core/components/viewers/map/datasets/src/localDatasets.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { VectorTile } from '@mapbox/vector-tile'\r\nimport Pbf from 'pbf'\r\n\r\nimport { DatasetGroup, type OpenDataPortal } from '../../../../../types/dbTypes'\r\nimport { layerColorByName } from '../../utils/stringToColour'\r\nimport { formatName } from '../utils'\r\n\r\nimport { getCache, setCache } from './cache'\r\nimport { isPublishedOrgTable, buildPublishedTileDatasets, type PublishedFileRow } from './publishedTiles'\r\n\r\nimport type { Dataset, FieldType, LayerGeometryType } from '../../../../../types/datasetTypes'\r\nimport type { AllGeoJSON } from '@turf/turf'\r\n\r\n\r\n// Define the structure of local dataset items\r\ninterface LocalDatasetItem {\r\n name: string\r\n organization: number\r\n featuresUrl: string\r\n tileName: string\r\n sourceLayer: string\r\n layer: {\r\n type: string\r\n properties: Record<string, string>\r\n }\r\n visible: boolean\r\n clickable: boolean\r\n url: string\r\n description: string\r\n publisher: string\r\n countrySubdivision?: string\r\n municipality?: string\r\n}\r\n\r\ninterface MartinCatalogTile {\r\n content_type: string\r\n description: string\r\n}\r\n\r\ninterface MartinCatalog {\r\n tiles: Record<string, MartinCatalogTile>\r\n}\r\n\r\ninterface MartinTileStatsAttribute {\r\n attribute: string\r\n values?: Array<string | number>\r\n min?: number | string\r\n max?: number | string\r\n}\r\n\r\ninterface MartinTileStatsLayer {\r\n layer: string\r\n attributes?: MartinTileStatsAttribute[]\r\n}\r\n\r\ninterface MartinTileStats {\r\n layers?: MartinTileStatsLayer[]\r\n}\r\n\r\ninterface MartinTileJson {\r\n id?: string\r\n name?: string\r\n description?: string\r\n tiles?: string[]\r\n vector_layers?: Array<{\r\n id: string\r\n description?: string\r\n minzoom?: number\r\n maxzoom?: number\r\n fields?: Record<string, string>\r\n }>\r\n tilestats?: MartinTileStats\r\n minzoom?: number\r\n maxzoom?: number\r\n}\r\n\r\ntype MartinIndexResponse =\r\n | MartinCatalog\r\n | MartinTileJson[]\r\n | { tiles: Record<string, MartinTileJson> }\r\n | { tilesets: MartinTileJson[] }\r\n\r\ninterface NormalizedMartinTile {\r\n tileName: string\r\n tileUrl: string\r\n description?: string\r\n vectorLayerId: string\r\n tilestats?: MartinTileStats\r\n minzoom?: number\r\n}\r\n\r\nconst ORGANIZATION_FIELD = 'organization'\r\n\r\nfunction normalizeMartinBaseUrl(apiUrl: string) {\r\n return apiUrl.replace(/\\/+$/, '')\r\n}\r\n\r\nfunction resolveMartinUrls(apiUrl: string) {\r\n const trimmed = normalizeMartinBaseUrl(apiUrl)\r\n\r\n if (trimmed.endsWith('/tiles/index.json')) {\r\n const tileBaseUrl = trimmed.replace(/\\/tiles\\/index\\.json$/, '')\r\n return {\r\n indexUrl: trimmed,\r\n tileBaseUrl,\r\n fallbackCatalogUrl: `${tileBaseUrl}/catalog`,\r\n }\r\n }\r\n\r\n if (trimmed.endsWith('/catalog')) {\r\n const tileBaseUrl = trimmed.replace(/\\/catalog$/, '')\r\n return {\r\n indexUrl: `${tileBaseUrl}/tiles/index.json`,\r\n tileBaseUrl,\r\n fallbackCatalogUrl: trimmed,\r\n }\r\n }\r\n\r\n return {\r\n indexUrl: `${trimmed}/tiles/index.json`,\r\n tileBaseUrl: trimmed,\r\n fallbackCatalogUrl: `${trimmed}/catalog`,\r\n }\r\n}\r\n\r\nfunction ensureAbsoluteTileUrl(tileUrl: string, tileBaseUrl: string) {\r\n if (/^https?:\\/\\//i.test(tileUrl)) {\r\n return tileUrl\r\n }\r\n\r\n const cleanedBase = tileBaseUrl.replace(/\\/+$/, '')\r\n const cleanedPath = tileUrl.replace(/^\\/+/, '')\r\n return `${cleanedBase}/${cleanedPath}`\r\n}\r\n\r\nfunction normalizeTileInfo(tileInfo: MartinTileJson, fallbackName: string | null, tileBaseUrl: string): NormalizedMartinTile | null {\r\n const tileName = tileInfo.id || tileInfo.name || fallbackName\r\n if (!tileName) return null\r\n\r\n const vectorLayerId = tileInfo.vector_layers?.[0]?.id || tileName\r\n const tileUrlFromInfo = Array.isArray(tileInfo.tiles) && tileInfo.tiles.length > 0\r\n ? tileInfo.tiles[0]\r\n : `${tileBaseUrl}/${tileName}/{z}/{x}/{y}`\r\n\r\n return {\r\n tileName,\r\n tileUrl: ensureAbsoluteTileUrl(tileUrlFromInfo, tileBaseUrl),\r\n description: tileInfo.description || tileInfo.vector_layers?.[0]?.description,\r\n vectorLayerId,\r\n tilestats: tileInfo.tilestats,\r\n minzoom: tileInfo.minzoom || tileInfo.vector_layers?.[0]?.minzoom,\r\n }\r\n}\r\n\r\nfunction normalizeMartinCatalog(response: MartinIndexResponse, tileBaseUrl: string): NormalizedMartinTile[] {\r\n if (Array.isArray(response)) {\r\n return response\r\n .map(tile => normalizeTileInfo(tile, null, tileBaseUrl))\r\n .filter((t): t is NormalizedMartinTile => Boolean(t))\r\n }\r\n\r\n if ('tilesets' in response && Array.isArray(response.tilesets)) {\r\n return response.tilesets\r\n .map(tile => normalizeTileInfo(tile, null, tileBaseUrl))\r\n .filter((t): t is NormalizedMartinTile => Boolean(t))\r\n }\r\n\r\n if ('tiles' in response && response.tiles) {\r\n return Object.entries(response.tiles)\r\n .map(([tileName, tileInfo]) => normalizeTileInfo(tileInfo as MartinTileJson, tileName, tileBaseUrl))\r\n .filter((t): t is NormalizedMartinTile => Boolean(t))\r\n }\r\n\r\n return []\r\n}\r\n\r\nfunction parseOrganizationValue(value: unknown): number | undefined {\r\n if (value == null) return undefined\r\n if (typeof value === 'number' && Number.isFinite(value)) return value\r\n\r\n const parsed = Number.parseInt(String(value), 10)\r\n return Number.isFinite(parsed) ? parsed : undefined\r\n}\r\n\r\nfunction extractOrganizationFromTilestats(tilestats?: MartinTileStats): number | undefined {\r\n if (!tilestats?.layers) return undefined\r\n\r\n for (const layer of tilestats.layers) {\r\n const orgAttribute = layer.attributes?.find(attr =>\r\n attr.attribute?.toLowerCase() === ORGANIZATION_FIELD,\r\n )\r\n\r\n if (!orgAttribute) continue\r\n\r\n if (orgAttribute.values && orgAttribute.values.length > 0) {\r\n const value = parseOrganizationValue(orgAttribute.values[0])\r\n if (value !== undefined) return value\r\n }\r\n\r\n if (orgAttribute.min !== undefined && orgAttribute.max !== undefined) {\r\n const min = parseOrganizationValue(orgAttribute.min)\r\n const max = parseOrganizationValue(orgAttribute.max)\r\n if (min !== undefined && max !== undefined && min === max) {\r\n return min\r\n }\r\n }\r\n }\r\n\r\n return undefined\r\n}\r\n\r\nasync function fetchOrganizationFromTile(tileUrlTemplate: string, vectorLayerId: string, preferredZoom?: number) {\r\n try {\r\n const z = typeof preferredZoom === 'number' ? Math.max(0, Math.round(preferredZoom)) : 0\r\n const sampleUrl = tileUrlTemplate\r\n .replace('{z}', String(z))\r\n .replace('{x}', '0')\r\n .replace('{y}', '0')\r\n .replace('{Z}', String(z))\r\n .replace('{X}', '0')\r\n .replace('{Y}', '0')\r\n\r\n const response = await fetch(sampleUrl)\r\n if (!response.ok) return undefined\r\n\r\n const buffer = await response.arrayBuffer()\r\n const vectorTile = new VectorTile(new Pbf(buffer))\r\n const layer = vectorTile.layers[vectorLayerId] || Object.values(vectorTile.layers)[0]\r\n\r\n if (!layer || layer.length === 0) return undefined\r\n\r\n const feature = layer.feature(0)\r\n const { properties } = feature.toGeoJSON(0, 0, z)\r\n\r\n // Determine geometry type\r\n let geometryType: LayerGeometryType | undefined\r\n if (feature.type === 1) { // Point\r\n geometryType = 'circle'\r\n } else if (feature.type === 2) { // LineString\r\n geometryType = 'line'\r\n } else if (feature.type === 3) { // Polygon\r\n geometryType = 'fill'\r\n }\r\n\r\n return {\r\n organization: parseOrganizationValue(properties?.[ORGANIZATION_FIELD]),\r\n geometryType\r\n }\r\n } catch (error) {\r\n console.warn('Failed to read organization from tile', error)\r\n return undefined\r\n }\r\n}\r\n\r\nasync function resolveDatasetOrganization(tile: NormalizedMartinTile) {\r\n const fromTilestats = extractOrganizationFromTilestats(tile.tilestats)\r\n\r\n // Always fetch from tile to get geometry type, even if we have organization from tilestats\r\n const fromTile = await fetchOrganizationFromTile(tile.tileUrl, tile.vectorLayerId, tile.minzoom)\r\n\r\n return {\r\n organization: fromTilestats !== undefined ? fromTilestats : fromTile?.organization,\r\n geometryType: fromTile?.geometryType\r\n }\r\n}\r\n\r\nasync function fetchPublishedTileLabels(): Promise<Map<string, string>> {\r\n try {\r\n const res = await fetch('/api/files')\r\n if (!res.ok) return new Map()\r\n const body = await res.json()\r\n const rows: Array<{ name?: string | null, description?: string | null }> =\r\n Array.isArray(body?.files) ? body.files : []\r\n const map = new Map<string, string>()\r\n for (const row of rows) {\r\n if (!row?.description || !row?.name) continue\r\n try {\r\n const desc = JSON.parse(row.description) as { tiledTable?: unknown }\r\n if (typeof desc?.tiledTable === 'string') {\r\n map.set(desc.tiledTable, row.name.replace(/\\.geojson$/i, ''))\r\n }\r\n }\r\n catch { /* skip malformed */ }\r\n }\r\n return map\r\n }\r\n catch {\r\n return new Map()\r\n }\r\n}\r\n\r\nasync function fetchMartinServerDatasets(\r\n portal: OpenDataPortal,\r\n): Promise<Dataset[]> {\r\n const { countrySubdivision, municipality } = portal\r\n const martinServerUrl = portal.apiUrl\r\n\r\n if (!martinServerUrl) {\r\n console.warn('No Martin server URL configured for portal', portal.name)\r\n return []\r\n }\r\n\r\n try {\r\n console.log('Fetching Martin server catalog from:', martinServerUrl)\r\n\r\n const { indexUrl, tileBaseUrl, fallbackCatalogUrl } = resolveMartinUrls(martinServerUrl)\r\n\r\n const publishedLabels = await fetchPublishedTileLabels()\r\n\r\n let response = await fetch(indexUrl)\r\n if (!response.ok && fallbackCatalogUrl && fallbackCatalogUrl !== indexUrl) {\r\n console.warn(`Failed to fetch Martin index (${indexUrl}), falling back to catalog...`)\r\n response = await fetch(fallbackCatalogUrl)\r\n }\r\n\r\n if (!response.ok) {\r\n throw new Error(`Failed to fetch Martin catalog: ${response.status}`)\r\n }\r\n\r\n const catalog: MartinIndexResponse = await response.json()\r\n const tiles = normalizeMartinCatalog(catalog, tileBaseUrl)\r\n // Drop our published org tables (served via the cdt_tiles function + discovered\r\n // from File metadata) and the cdt_tiles function source itself (no ?source= → empty).\r\n .filter(t => !isPublishedOrgTable(t.tileName) && t.tileName !== 'cdt_tiles')\r\n\r\n console.log('Martin catalog received:', tiles.length, 'tiles')\r\n\r\n const datasets: Dataset[] = await Promise.all(tiles.map(async (tileInfo) => {\r\n const publishedLabel = publishedLabels.get(tileInfo.tileName)\r\n const cleanName = publishedLabel ?? formatName(\r\n tileInfo.tileName\r\n .replace(/_\\d{8}$/, '')\r\n .replace(/__\\d+_\\d+$/, ''),\r\n )\r\n\r\n const { organization, geometryType } = await resolveDatasetOrganization(tileInfo)\r\n\r\n const getFields = async () => {\r\n return []\r\n }\r\n\r\n const getFeatures = async (): Promise<AllGeoJSON> => {\r\n return {\r\n type: 'FeatureCollection',\r\n features: [],\r\n } as AllGeoJSON\r\n }\r\n\r\n const getFeaturesData = async () => {\r\n return {\r\n id: tileInfo.tileName,\r\n name: cleanName,\r\n type: 'MVT',\r\n visible: true,\r\n clickable: true,\r\n fields: [],\r\n source: {\r\n url: tileInfo.tileUrl,\r\n description: tileInfo.description,\r\n publisher: 'Martin Server',\r\n },\r\n serviceInfo: {\r\n tileName: tileInfo.tileName,\r\n sourceLayer: tileInfo.vectorLayerId,\r\n type: 'Martin',\r\n tileUrl: tileInfo.tileUrl,\r\n },\r\n }\r\n }\r\n\r\n const dataset: Dataset = {\r\n id: tileInfo.tileName,\r\n name: cleanName,\r\n organization,\r\n publisher: 'Martin Server',\r\n dataManagementSystem: 'other',\r\n license: 'Unknown',\r\n contact: 'Unknown',\r\n keywords: [],\r\n description: tileInfo.description || `Vector tile layer from Martin server: ${tileInfo.tileName}`,\r\n dateReleased: '',\r\n dateUpdated: '',\r\n information: tileInfo.tileUrl,\r\n url: tileInfo.tileUrl,\r\n countrySubdivision: countrySubdivision,\r\n municipality: municipality,\r\n type: 'MVT',\r\n datasetType: 'MVT' as any,\r\n portal,\r\n getFields,\r\n getFeatures,\r\n getFeaturesData,\r\n group: DatasetGroup.Organizational,\r\n layerColor: layerColorByName(cleanName),\r\n layerType: geometryType,\r\n }\r\n\r\n return dataset\r\n }))\r\n\r\n console.log(`Transformed ${datasets.length} Martin datasets`)\r\n\r\n let publishedDatasets: Dataset[] = []\r\n try {\r\n const fileRes = await fetch('/api/files')\r\n if (fileRes.ok) {\r\n const fileBody = await fileRes.json()\r\n const fileRows: PublishedFileRow[] = Array.isArray(fileBody?.files) ? fileBody.files : []\r\n publishedDatasets = buildPublishedTileDatasets(fileRows, tileBaseUrl, portal)\r\n }\r\n }\r\n catch (err) {\r\n console.warn('Failed to build published-tile datasets from /api/files', err)\r\n }\r\n\r\n return [...datasets, ...publishedDatasets]\r\n } catch (error) {\r\n console.error('Error fetching Martin server datasets:', error)\r\n return []\r\n }\r\n}\r\n\r\n// Fetch Local datasets from localDatasetsJson array and transform to Dataset array\r\nexport async function fetchLocalDatasets(\r\n portal: OpenDataPortal,\r\n): Promise<Dataset[]> {\r\n const { countrySubdivision, municipality } = portal\r\n const apiUrl = portal.apiUrl || ''\r\n const trimmedApiUrl = apiUrl.replace(/\\/+$/, '')\r\n\r\n if (!apiUrl) {\r\n console.warn('No apiUrl provided for portal', portal.name)\r\n return []\r\n }\r\n\r\n // Skip legacy portals with non-absolute URLs (e.g. ./localDatasets/index.json)\r\n if (!apiUrl.startsWith('http://') && !apiUrl.startsWith('https://')) {\r\n return []\r\n }\r\n\r\n if (trimmedApiUrl.includes('/catalog') || trimmedApiUrl.includes('/tiles/index')) {\r\n return fetchMartinServerDatasets(portal)\r\n }\r\n\r\n try {\r\n // Fetch the localDatasets.json file\r\n const response = await fetch(apiUrl)\r\n if (!response.ok) {\r\n const shouldTryMartinFallback =\r\n !trimmedApiUrl.endsWith('.json')\r\n && !trimmedApiUrl.includes('/catalog')\r\n && !trimmedApiUrl.includes('/tiles/index')\r\n\r\n if (response.status === 404 && shouldTryMartinFallback) {\r\n console.warn(\r\n `Local dataset URL returned 404 (${apiUrl}); retrying as a Martin server base URL`,\r\n )\r\n return fetchMartinServerDatasets({\r\n ...portal,\r\n apiUrl: trimmedApiUrl,\r\n })\r\n }\r\n\r\n throw new Error(`Failed to fetch local datasets from ${apiUrl}: ${response.status}`)\r\n }\r\n\r\n const localDatasetsJson: LocalDatasetItem[] = await response.json()\r\n\r\n const transformedDatasets: Dataset[] = localDatasetsJson.map((datasetItem: LocalDatasetItem) => {\r\n // Simple getFields function that returns fields from layer properties\r\n const getFields = async () => {\r\n if (datasetItem.layer?.properties) {\r\n return Object.entries(datasetItem.layer.properties).map(([name, type]) => ({\r\n name,\r\n label: formatName(name),\r\n type: type as string,\r\n description: `${formatName(name)} field`,\r\n }))\r\n }\r\n return []\r\n }\r\n\r\n // Fetch GeoJSON data from the featuresUrl\r\n const getFeatures = async (): Promise<AllGeoJSON> => {\r\n // Create a cache key based on the dataset name and features URL\r\n const featuresKey = `features:local:${datasetItem.name}:${datasetItem.featuresUrl}`\r\n\r\n // Check cache first\r\n const cachedFeatures = getCache<AllGeoJSON>(featuresKey)\r\n if (cachedFeatures) {\r\n return cachedFeatures\r\n }\r\n\r\n try {\r\n // Use the featuresUrl from the dataset item\r\n let geojsonUrl: string\r\n if (datasetItem.featuresUrl.startsWith('http://') || datasetItem.featuresUrl.startsWith('https://')) {\r\n geojsonUrl = datasetItem.featuresUrl\r\n } else {\r\n const baseUrl = apiUrl.endsWith('.json')\r\n ? apiUrl.substring(0, apiUrl.lastIndexOf('/'))\r\n : apiUrl\r\n geojsonUrl = `${baseUrl}/${datasetItem.featuresUrl}`\r\n }\r\n\r\n const response = await fetch(geojsonUrl)\r\n\r\n if (!response.ok) {\r\n throw new Error(`Failed to fetch GeoJSON: ${response.status}`)\r\n }\r\n\r\n const geojsonData = await response.json() as AllGeoJSON\r\n\r\n // Cache for 1 hour\r\n setCache(featuresKey, geojsonData, 60 * 60 * 1000)\r\n\r\n return geojsonData\r\n } catch (error) {\r\n console.error(`Error loading GeoJSON for ${datasetItem.name}:`, error)\r\n return {\r\n type: 'FeatureCollection',\r\n features: [],\r\n } as AllGeoJSON\r\n }\r\n }\r\n\r\n const name = formatName(datasetItem.name)\r\n\r\n const getFeaturesData = async () => {\r\n try {\r\n return {\r\n // Basic layer information\r\n id: datasetItem.tileName || datasetItem.name.toLowerCase().replace(/\\s+/g, '-'),\r\n name: datasetItem.name,\r\n type: datasetItem.layer?.type || 'GeoJSON',\r\n\r\n // Layer configuration\r\n visible: datasetItem.visible,\r\n clickable: datasetItem.clickable,\r\n\r\n // Field information (similar to ArcGIS fields structure)\r\n fields: datasetItem.layer?.properties\r\n ? Object.entries(datasetItem.layer.properties).map(([name, type]) => ({\r\n name,\r\n type: type as FieldType,\r\n alias: formatName(name),\r\n description: `${formatName(name)} field`,\r\n }))\r\n : [],\r\n\r\n // Source information\r\n source: {\r\n url: datasetItem.url,\r\n description: datasetItem.description,\r\n publisher: datasetItem.publisher,\r\n featuresUrl: datasetItem.featuresUrl,\r\n },\r\n\r\n // Additional metadata that DatasetDetails.tsx might use\r\n editingInfo: {\r\n lastEditDate: new Date().toISOString(), // Current date as placeholder\r\n dataLastEditDate: new Date().toISOString(),\r\n },\r\n\r\n // Layer properties summary\r\n properties: datasetItem.layer?.properties || {},\r\n fieldCount: datasetItem.layer?.properties ? Object.keys(datasetItem.layer.properties).length : 0,\r\n\r\n // Geometry information\r\n geometryType: 'Unknown', // You could infer this from the actual GeoJSON if needed\r\n\r\n // Service information (for compatibility)\r\n serviceInfo: {\r\n tileName: datasetItem.tileName,\r\n sourceLayer: datasetItem.sourceLayer,\r\n type: 'Local',\r\n },\r\n }\r\n } catch (error) {\r\n console.error('Error getting features data for dataset:', datasetItem.name, error)\r\n return null\r\n }\r\n }\r\n\r\n return {\r\n id: datasetItem.tileName || datasetItem.name.toLowerCase().replace(/\\s+/g, '-'),\r\n name,\r\n organization: datasetItem.organization || 0,\r\n publisher: datasetItem.publisher || 'Unknown',\r\n dataManagementSystem: 'other',\r\n license: 'Unknown',\r\n contact: 'Unknown',\r\n keywords: [],\r\n description: datasetItem.description || 'No description available',\r\n dateReleased: '',\r\n dateUpdated: '',\r\n information: datasetItem.url || '',\r\n url: datasetItem.url || '#', // Ensure there's always a valid URL for the Link component\r\n countrySubdivision: datasetItem.countrySubdivision || countrySubdivision,\r\n municipality: datasetItem.municipality || municipality,\r\n type: datasetItem.layer?.type || 'GeoJSON',\r\n datasetType: 'GeoJSON' as const,\r\n portal,\r\n getFields,\r\n getFeatures,\r\n getFeaturesData,\r\n group: 'Organizational',\r\n layerColor: layerColorByName(name),\r\n }\r\n })\r\n\r\n return transformedDatasets\r\n } catch (error) {\r\n console.error('Error fetching local datasets:', error)\r\n return []\r\n }\r\n}\r\n"],"mappings":"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","names":["_a","name","response"]}
1
+ {"version":3,"sources":["../../../../../../../src/core/components/viewers/map/datasets/src/localDatasets.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { VectorTile } from '@mapbox/vector-tile'\r\nimport Pbf from 'pbf'\r\n\r\nimport { DatasetGroup, type OpenDataPortal } from '../../../../../types/dbTypes'\r\nimport { layerColorByName } from '../../utils/stringToColour'\r\nimport { formatName } from '../utils'\r\n\r\nimport { getCache, setCache } from './cache'\r\nimport { isPublishedOrgTable, buildPublishedTileDatasets, type PublishedFileRow } from './publishedTiles'\r\n\r\nimport type { Dataset, FieldType, LayerGeometryType } from '../../../../../types/datasetTypes'\r\nimport type { AllGeoJSON } from '@turf/turf'\r\n\r\n\r\n// Define the structure of local dataset items\r\ninterface LocalDatasetItem {\r\n name: string\r\n organization: number\r\n featuresUrl: string\r\n tileName: string\r\n sourceLayer: string\r\n layer: {\r\n type: string\r\n properties: Record<string, string>\r\n }\r\n visible: boolean\r\n clickable: boolean\r\n url: string\r\n description: string\r\n publisher: string\r\n countrySubdivision?: string\r\n municipality?: string\r\n}\r\n\r\ninterface MartinCatalogTile {\r\n content_type: string\r\n description: string\r\n}\r\n\r\ninterface MartinCatalog {\r\n tiles: Record<string, MartinCatalogTile>\r\n}\r\n\r\ninterface MartinTileStatsAttribute {\r\n attribute: string\r\n values?: Array<string | number>\r\n min?: number | string\r\n max?: number | string\r\n}\r\n\r\ninterface MartinTileStatsLayer {\r\n layer: string\r\n attributes?: MartinTileStatsAttribute[]\r\n}\r\n\r\ninterface MartinTileStats {\r\n layers?: MartinTileStatsLayer[]\r\n}\r\n\r\ninterface MartinTileJson {\r\n id?: string\r\n name?: string\r\n description?: string\r\n tiles?: string[]\r\n vector_layers?: Array<{\r\n id: string\r\n description?: string\r\n minzoom?: number\r\n maxzoom?: number\r\n fields?: Record<string, string>\r\n }>\r\n tilestats?: MartinTileStats\r\n minzoom?: number\r\n maxzoom?: number\r\n}\r\n\r\ntype MartinIndexResponse =\r\n | MartinCatalog\r\n | MartinTileJson[]\r\n | { tiles: Record<string, MartinTileJson> }\r\n | { tilesets: MartinTileJson[] }\r\n\r\ninterface NormalizedMartinTile {\r\n tileName: string\r\n tileUrl: string\r\n description?: string\r\n vectorLayerId: string\r\n tilestats?: MartinTileStats\r\n minzoom?: number\r\n}\r\n\r\nconst ORGANIZATION_FIELD = 'organization'\r\n\r\nfunction normalizeMartinBaseUrl(apiUrl: string) {\r\n return apiUrl.replace(/\\/+$/, '')\r\n}\r\n\r\nfunction resolveMartinUrls(apiUrl: string) {\r\n const trimmed = normalizeMartinBaseUrl(apiUrl)\r\n\r\n if (trimmed.endsWith('/tiles/index.json')) {\r\n const tileBaseUrl = trimmed.replace(/\\/tiles\\/index\\.json$/, '')\r\n return {\r\n indexUrl: trimmed,\r\n tileBaseUrl,\r\n fallbackCatalogUrl: `${tileBaseUrl}/catalog`,\r\n }\r\n }\r\n\r\n if (trimmed.endsWith('/catalog')) {\r\n const tileBaseUrl = trimmed.replace(/\\/catalog$/, '')\r\n return {\r\n indexUrl: `${tileBaseUrl}/tiles/index.json`,\r\n tileBaseUrl,\r\n fallbackCatalogUrl: trimmed,\r\n }\r\n }\r\n\r\n return {\r\n indexUrl: `${trimmed}/tiles/index.json`,\r\n tileBaseUrl: trimmed,\r\n fallbackCatalogUrl: `${trimmed}/catalog`,\r\n }\r\n}\r\n\r\nfunction ensureAbsoluteTileUrl(tileUrl: string, tileBaseUrl: string) {\r\n if (/^https?:\\/\\//i.test(tileUrl)) {\r\n return tileUrl\r\n }\r\n\r\n const cleanedBase = tileBaseUrl.replace(/\\/+$/, '')\r\n const cleanedPath = tileUrl.replace(/^\\/+/, '')\r\n return `${cleanedBase}/${cleanedPath}`\r\n}\r\n\r\nfunction normalizeTileInfo(tileInfo: MartinTileJson, fallbackName: string | null, tileBaseUrl: string): NormalizedMartinTile | null {\r\n const tileName = tileInfo.id || tileInfo.name || fallbackName\r\n if (!tileName) return null\r\n\r\n const vectorLayerId = tileInfo.vector_layers?.[0]?.id || tileName\r\n const tileUrlFromInfo = Array.isArray(tileInfo.tiles) && tileInfo.tiles.length > 0\r\n ? tileInfo.tiles[0]\r\n : `${tileBaseUrl}/${tileName}/{z}/{x}/{y}`\r\n\r\n return {\r\n tileName,\r\n tileUrl: ensureAbsoluteTileUrl(tileUrlFromInfo, tileBaseUrl),\r\n description: tileInfo.description || tileInfo.vector_layers?.[0]?.description,\r\n vectorLayerId,\r\n tilestats: tileInfo.tilestats,\r\n minzoom: tileInfo.minzoom || tileInfo.vector_layers?.[0]?.minzoom,\r\n }\r\n}\r\n\r\nfunction normalizeMartinCatalog(response: MartinIndexResponse, tileBaseUrl: string): NormalizedMartinTile[] {\r\n if (Array.isArray(response)) {\r\n return response\r\n .map(tile => normalizeTileInfo(tile, null, tileBaseUrl))\r\n .filter((t): t is NormalizedMartinTile => Boolean(t))\r\n }\r\n\r\n if ('tilesets' in response && Array.isArray(response.tilesets)) {\r\n return response.tilesets\r\n .map(tile => normalizeTileInfo(tile, null, tileBaseUrl))\r\n .filter((t): t is NormalizedMartinTile => Boolean(t))\r\n }\r\n\r\n if ('tiles' in response && response.tiles) {\r\n return Object.entries(response.tiles)\r\n .map(([tileName, tileInfo]) => normalizeTileInfo(tileInfo as MartinTileJson, tileName, tileBaseUrl))\r\n .filter((t): t is NormalizedMartinTile => Boolean(t))\r\n }\r\n\r\n return []\r\n}\r\n\r\nfunction parseOrganizationValue(value: unknown): number | undefined {\r\n if (value == null) return undefined\r\n if (typeof value === 'number' && Number.isFinite(value)) return value\r\n\r\n const parsed = Number.parseInt(String(value), 10)\r\n return Number.isFinite(parsed) ? parsed : undefined\r\n}\r\n\r\nfunction extractOrganizationFromTilestats(tilestats?: MartinTileStats): number | undefined {\r\n if (!tilestats?.layers) return undefined\r\n\r\n for (const layer of tilestats.layers) {\r\n const orgAttribute = layer.attributes?.find(attr =>\r\n attr.attribute?.toLowerCase() === ORGANIZATION_FIELD,\r\n )\r\n\r\n if (!orgAttribute) continue\r\n\r\n if (orgAttribute.values && orgAttribute.values.length > 0) {\r\n const value = parseOrganizationValue(orgAttribute.values[0])\r\n if (value !== undefined) return value\r\n }\r\n\r\n if (orgAttribute.min !== undefined && orgAttribute.max !== undefined) {\r\n const min = parseOrganizationValue(orgAttribute.min)\r\n const max = parseOrganizationValue(orgAttribute.max)\r\n if (min !== undefined && max !== undefined && min === max) {\r\n return min\r\n }\r\n }\r\n }\r\n\r\n return undefined\r\n}\r\n\r\nasync function fetchOrganizationFromTile(tileUrlTemplate: string, vectorLayerId: string, preferredZoom?: number) {\r\n try {\r\n const z = typeof preferredZoom === 'number' ? Math.max(0, Math.round(preferredZoom)) : 0\r\n const sampleUrl = tileUrlTemplate\r\n .replace('{z}', String(z))\r\n .replace('{x}', '0')\r\n .replace('{y}', '0')\r\n .replace('{Z}', String(z))\r\n .replace('{X}', '0')\r\n .replace('{Y}', '0')\r\n\r\n const response = await fetch(sampleUrl)\r\n if (!response.ok) return undefined\r\n\r\n const buffer = await response.arrayBuffer()\r\n const vectorTile = new VectorTile(new Pbf(buffer))\r\n const layer = vectorTile.layers[vectorLayerId] || Object.values(vectorTile.layers)[0]\r\n\r\n if (!layer || layer.length === 0) return undefined\r\n\r\n const feature = layer.feature(0)\r\n const { properties } = feature.toGeoJSON(0, 0, z)\r\n\r\n // Determine geometry type\r\n let geometryType: LayerGeometryType | undefined\r\n if (feature.type === 1) { // Point\r\n geometryType = 'circle'\r\n } else if (feature.type === 2) { // LineString\r\n geometryType = 'line'\r\n } else if (feature.type === 3) { // Polygon\r\n geometryType = 'fill'\r\n }\r\n\r\n return {\r\n organization: parseOrganizationValue(properties?.[ORGANIZATION_FIELD]),\r\n geometryType\r\n }\r\n } catch (error) {\r\n console.warn('Failed to read organization from tile', error)\r\n return undefined\r\n }\r\n}\r\n\r\nasync function resolveDatasetOrganization(tile: NormalizedMartinTile) {\r\n const fromTilestats = extractOrganizationFromTilestats(tile.tilestats)\r\n\r\n // Always fetch from tile to get geometry type, even if we have organization from tilestats\r\n const fromTile = await fetchOrganizationFromTile(tile.tileUrl, tile.vectorLayerId, tile.minzoom)\r\n\r\n return {\r\n organization: fromTilestats !== undefined ? fromTilestats : fromTile?.organization,\r\n geometryType: fromTile?.geometryType\r\n }\r\n}\r\n\r\nasync function fetchPublishedTileLabels(): Promise<Map<string, string>> {\r\n try {\r\n const res = await fetch('/api/files')\r\n if (!res.ok) return new Map()\r\n const body = await res.json()\r\n const rows: Array<{ name?: string | null, description?: string | null }> =\r\n Array.isArray(body?.files) ? body.files : []\r\n const map = new Map<string, string>()\r\n for (const row of rows) {\r\n if (!row?.description || !row?.name) continue\r\n try {\r\n const desc = JSON.parse(row.description) as { tiledTable?: unknown }\r\n if (typeof desc?.tiledTable === 'string') {\r\n map.set(desc.tiledTable, row.name.replace(/\\.geojson$/i, ''))\r\n }\r\n }\r\n catch { /* skip malformed */ }\r\n }\r\n return map\r\n }\r\n catch {\r\n return new Map()\r\n }\r\n}\r\n\r\nasync function fetchMartinServerDatasets(\r\n portal: OpenDataPortal,\r\n): Promise<Dataset[]> {\r\n const { countrySubdivision, municipality } = portal\r\n const martinServerUrl = portal.apiUrl\r\n\r\n if (!martinServerUrl) {\r\n console.warn('No Martin server URL configured for portal', portal.name)\r\n return []\r\n }\r\n\r\n try {\r\n const { indexUrl, tileBaseUrl, fallbackCatalogUrl } = resolveMartinUrls(martinServerUrl)\r\n\r\n const publishedLabels = await fetchPublishedTileLabels()\r\n\r\n let response = await fetch(indexUrl)\r\n if (!response.ok && fallbackCatalogUrl && fallbackCatalogUrl !== indexUrl) {\r\n console.warn(`Failed to fetch Martin index (${indexUrl}), falling back to catalog...`)\r\n response = await fetch(fallbackCatalogUrl)\r\n }\r\n\r\n if (!response.ok) {\r\n throw new Error(`Failed to fetch Martin catalog: ${response.status}`)\r\n }\r\n\r\n const catalog: MartinIndexResponse = await response.json()\r\n const tiles = normalizeMartinCatalog(catalog, tileBaseUrl)\r\n // Drop our published org tables (served via the cdt_tiles function + discovered\r\n // from File metadata) and the cdt_tiles function source itself (no ?source= → empty).\r\n .filter(t => !isPublishedOrgTable(t.tileName) && t.tileName !== 'cdt_tiles')\r\n\r\n const datasets: Dataset[] = await Promise.all(tiles.map(async (tileInfo) => {\r\n const publishedLabel = publishedLabels.get(tileInfo.tileName)\r\n const cleanName = publishedLabel ?? formatName(\r\n tileInfo.tileName\r\n .replace(/_\\d{8}$/, '')\r\n .replace(/__\\d+_\\d+$/, ''),\r\n )\r\n\r\n const { organization, geometryType } = await resolveDatasetOrganization(tileInfo)\r\n\r\n const getFields = async () => {\r\n return []\r\n }\r\n\r\n const getFeatures = async (): Promise<AllGeoJSON> => {\r\n return {\r\n type: 'FeatureCollection',\r\n features: [],\r\n } as AllGeoJSON\r\n }\r\n\r\n const getFeaturesData = async () => {\r\n return {\r\n id: tileInfo.tileName,\r\n name: cleanName,\r\n type: 'MVT',\r\n visible: true,\r\n clickable: true,\r\n fields: [],\r\n source: {\r\n url: tileInfo.tileUrl,\r\n description: tileInfo.description,\r\n publisher: 'Martin Server',\r\n },\r\n serviceInfo: {\r\n tileName: tileInfo.tileName,\r\n sourceLayer: tileInfo.vectorLayerId,\r\n type: 'Martin',\r\n tileUrl: tileInfo.tileUrl,\r\n },\r\n }\r\n }\r\n\r\n const dataset: Dataset = {\r\n id: tileInfo.tileName,\r\n name: cleanName,\r\n organization,\r\n publisher: 'Martin Server',\r\n dataManagementSystem: 'other',\r\n license: 'Unknown',\r\n contact: 'Unknown',\r\n keywords: [],\r\n description: tileInfo.description || `Vector tile layer from Martin server: ${tileInfo.tileName}`,\r\n dateReleased: '',\r\n dateUpdated: '',\r\n information: tileInfo.tileUrl,\r\n url: tileInfo.tileUrl,\r\n countrySubdivision: countrySubdivision,\r\n municipality: municipality,\r\n type: 'MVT',\r\n datasetType: 'MVT' as any,\r\n portal,\r\n getFields,\r\n getFeatures,\r\n getFeaturesData,\r\n group: DatasetGroup.Organizational,\r\n layerColor: layerColorByName(cleanName),\r\n layerType: geometryType,\r\n }\r\n\r\n return dataset\r\n }))\r\n\r\n let publishedDatasets: Dataset[] = []\r\n try {\r\n const fileRes = await fetch('/api/files')\r\n if (fileRes.ok) {\r\n const fileBody = await fileRes.json()\r\n const fileRows: PublishedFileRow[] = Array.isArray(fileBody?.files) ? fileBody.files : []\r\n publishedDatasets = buildPublishedTileDatasets(fileRows, tileBaseUrl, portal)\r\n }\r\n }\r\n catch (err) {\r\n console.warn('Failed to build published-tile datasets from /api/files', err)\r\n }\r\n\r\n return [...datasets, ...publishedDatasets]\r\n } catch (error) {\r\n console.error('Error fetching Martin server datasets:', error)\r\n return []\r\n }\r\n}\r\n\r\n// Fetch Local datasets from localDatasetsJson array and transform to Dataset array\r\nexport async function fetchLocalDatasets(\r\n portal: OpenDataPortal,\r\n): Promise<Dataset[]> {\r\n const { countrySubdivision, municipality } = portal\r\n const apiUrl = portal.apiUrl || ''\r\n const trimmedApiUrl = apiUrl.replace(/\\/+$/, '')\r\n\r\n if (!apiUrl) {\r\n console.warn('No apiUrl provided for portal', portal.name)\r\n return []\r\n }\r\n\r\n // Skip legacy portals with non-absolute URLs (e.g. ./localDatasets/index.json)\r\n if (!apiUrl.startsWith('http://') && !apiUrl.startsWith('https://')) {\r\n return []\r\n }\r\n\r\n if (trimmedApiUrl.includes('/catalog') || trimmedApiUrl.includes('/tiles/index')) {\r\n return fetchMartinServerDatasets(portal)\r\n }\r\n\r\n try {\r\n // Fetch the localDatasets.json file\r\n const response = await fetch(apiUrl)\r\n if (!response.ok) {\r\n const shouldTryMartinFallback =\r\n !trimmedApiUrl.endsWith('.json')\r\n && !trimmedApiUrl.includes('/catalog')\r\n && !trimmedApiUrl.includes('/tiles/index')\r\n\r\n if (response.status === 404 && shouldTryMartinFallback) {\r\n console.warn(\r\n `Local dataset URL returned 404 (${apiUrl}); retrying as a Martin server base URL`,\r\n )\r\n return fetchMartinServerDatasets({\r\n ...portal,\r\n apiUrl: trimmedApiUrl,\r\n })\r\n }\r\n\r\n throw new Error(`Failed to fetch local datasets from ${apiUrl}: ${response.status}`)\r\n }\r\n\r\n const localDatasetsJson: LocalDatasetItem[] = await response.json()\r\n\r\n const transformedDatasets: Dataset[] = localDatasetsJson.map((datasetItem: LocalDatasetItem) => {\r\n // Simple getFields function that returns fields from layer properties\r\n const getFields = async () => {\r\n if (datasetItem.layer?.properties) {\r\n return Object.entries(datasetItem.layer.properties).map(([name, type]) => ({\r\n name,\r\n label: formatName(name),\r\n type: type as string,\r\n description: `${formatName(name)} field`,\r\n }))\r\n }\r\n return []\r\n }\r\n\r\n // Fetch GeoJSON data from the featuresUrl\r\n const getFeatures = async (): Promise<AllGeoJSON> => {\r\n // Create a cache key based on the dataset name and features URL\r\n const featuresKey = `features:local:${datasetItem.name}:${datasetItem.featuresUrl}`\r\n\r\n // Check cache first\r\n const cachedFeatures = getCache<AllGeoJSON>(featuresKey)\r\n if (cachedFeatures) {\r\n return cachedFeatures\r\n }\r\n\r\n try {\r\n // Use the featuresUrl from the dataset item\r\n let geojsonUrl: string\r\n if (datasetItem.featuresUrl.startsWith('http://') || datasetItem.featuresUrl.startsWith('https://')) {\r\n geojsonUrl = datasetItem.featuresUrl\r\n } else {\r\n const baseUrl = apiUrl.endsWith('.json')\r\n ? apiUrl.substring(0, apiUrl.lastIndexOf('/'))\r\n : apiUrl\r\n geojsonUrl = `${baseUrl}/${datasetItem.featuresUrl}`\r\n }\r\n\r\n const response = await fetch(geojsonUrl)\r\n\r\n if (!response.ok) {\r\n throw new Error(`Failed to fetch GeoJSON: ${response.status}`)\r\n }\r\n\r\n const geojsonData = await response.json() as AllGeoJSON\r\n\r\n // Cache for 1 hour\r\n setCache(featuresKey, geojsonData, 60 * 60 * 1000)\r\n\r\n return geojsonData\r\n } catch (error) {\r\n console.error(`Error loading GeoJSON for ${datasetItem.name}:`, error)\r\n return {\r\n type: 'FeatureCollection',\r\n features: [],\r\n } as AllGeoJSON\r\n }\r\n }\r\n\r\n const name = formatName(datasetItem.name)\r\n\r\n const getFeaturesData = async () => {\r\n try {\r\n return {\r\n // Basic layer information\r\n id: datasetItem.tileName || datasetItem.name.toLowerCase().replace(/\\s+/g, '-'),\r\n name: datasetItem.name,\r\n type: datasetItem.layer?.type || 'GeoJSON',\r\n\r\n // Layer configuration\r\n visible: datasetItem.visible,\r\n clickable: datasetItem.clickable,\r\n\r\n // Field information (similar to ArcGIS fields structure)\r\n fields: datasetItem.layer?.properties\r\n ? Object.entries(datasetItem.layer.properties).map(([name, type]) => ({\r\n name,\r\n type: type as FieldType,\r\n alias: formatName(name),\r\n description: `${formatName(name)} field`,\r\n }))\r\n : [],\r\n\r\n // Source information\r\n source: {\r\n url: datasetItem.url,\r\n description: datasetItem.description,\r\n publisher: datasetItem.publisher,\r\n featuresUrl: datasetItem.featuresUrl,\r\n },\r\n\r\n // Additional metadata that DatasetDetails.tsx might use\r\n editingInfo: {\r\n lastEditDate: new Date().toISOString(), // Current date as placeholder\r\n dataLastEditDate: new Date().toISOString(),\r\n },\r\n\r\n // Layer properties summary\r\n properties: datasetItem.layer?.properties || {},\r\n fieldCount: datasetItem.layer?.properties ? Object.keys(datasetItem.layer.properties).length : 0,\r\n\r\n // Geometry information\r\n geometryType: 'Unknown', // You could infer this from the actual GeoJSON if needed\r\n\r\n // Service information (for compatibility)\r\n serviceInfo: {\r\n tileName: datasetItem.tileName,\r\n sourceLayer: datasetItem.sourceLayer,\r\n type: 'Local',\r\n },\r\n }\r\n } catch (error) {\r\n console.error('Error getting features data for dataset:', datasetItem.name, error)\r\n return null\r\n }\r\n }\r\n\r\n return {\r\n id: datasetItem.tileName || datasetItem.name.toLowerCase().replace(/\\s+/g, '-'),\r\n name,\r\n organization: datasetItem.organization || 0,\r\n publisher: datasetItem.publisher || 'Unknown',\r\n dataManagementSystem: 'other',\r\n license: 'Unknown',\r\n contact: 'Unknown',\r\n keywords: [],\r\n description: datasetItem.description || 'No description available',\r\n dateReleased: '',\r\n dateUpdated: '',\r\n information: datasetItem.url || '',\r\n url: datasetItem.url || '#', // Ensure there's always a valid URL for the Link component\r\n countrySubdivision: datasetItem.countrySubdivision || countrySubdivision,\r\n municipality: datasetItem.municipality || municipality,\r\n type: datasetItem.layer?.type || 'GeoJSON',\r\n datasetType: 'GeoJSON' as const,\r\n portal,\r\n getFields,\r\n getFeatures,\r\n getFeaturesData,\r\n group: 'Organizational',\r\n layerColor: layerColorByName(name),\r\n }\r\n })\r\n\r\n return transformedDatasets\r\n } catch (error) {\r\n console.error('Error fetching local datasets:', error)\r\n return []\r\n }\r\n}\r\n"],"mappings":"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","names":["_a","name","response"]}
@@ -1 +1 @@
1
- {"version":3,"file":"opendatasoftDatasets.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/map/datasets/src/opendatasoftDatasets.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAA;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAIlE,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,OAAO,EAAE,CAAC,CAsSpB"}
1
+ {"version":3,"file":"opendatasoftDatasets.d.ts","sourceRoot":"","sources":["../../../../../../../src/core/components/viewers/map/datasets/src/opendatasoftDatasets.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mCAAmC,CAAA;AAChE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAIlE,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,cAAc,GACrB,OAAO,CAAC,OAAO,EAAE,CAAC,CAsRpB"}
@@ -91,9 +91,6 @@ async function fetchOpenDataSoftDatasets(portal) {
91
91
  if ([401, 403, 499, 504].includes(code) || /unauthorized|forbidden|token required|timed out/i.test(msg)) return false;
92
92
  return true;
93
93
  }
94
- const recs = sample.records || sample.results || [];
95
- const hasGeom = Array.isArray(recs) ? recs.some((r) => !!(r == null ? void 0 : r.geometry) || !!(r == null ? void 0 : r.geom) || !!(r == null ? void 0 : r.geo_shape) || !!(r == null ? void 0 : r.geo_point_2d)) : false;
96
- console.log(`\u2139\uFE0F OpenDataSoft validation: Dataset "${datasetId}" has geometry: ${hasGeom}, records: ${recs.length}`);
97
94
  return true;
98
95
  } else if ([401, 403, 504].includes(sampleRes.status)) {
99
96
  console.error(`\u274C OpenDataSoft validation: Dataset "${datasetId}" auth/timeout error: ${sampleRes.status}`);
@@ -108,7 +105,6 @@ async function fetchOpenDataSoftDatasets(portal) {
108
105
  }
109
106
  });
110
107
  if (!isValid) {
111
- console.log(`\u{1F5D1}\uFE0F OpenDataSoft: Filtering out dataset "${datasetId}" - validation failed`);
112
108
  return null;
113
109
  }
114
110
  const getFields = async () => {
@@ -130,12 +126,9 @@ async function fetchOpenDataSoftDatasets(portal) {
130
126
  return fields;
131
127
  };
132
128
  const getFeatures = async () => {
133
- var _a2, _b2, _c2;
134
129
  const featuresKey = `features:opendatasoft:${datasetId}:${datasetModified != null ? datasetModified : ""}`;
135
130
  const cached = getCache(featuresKey);
136
131
  if (cached) {
137
- const cachedCount = "features" in cached ? (_b2 = (_a2 = cached.features) == null ? void 0 : _a2.length) != null ? _b2 : 0 : 0;
138
- console.log(`\u2705 OpenDataSoft getFeatures: Returning ${cachedCount} cached features for dataset "${datasetId}"`);
139
132
  return cached;
140
133
  }
141
134
  try {
@@ -143,10 +136,8 @@ async function fetchOpenDataSoftDatasets(portal) {
143
136
  let offset2 = 0;
144
137
  let totalCount2 = 0;
145
138
  const maxFeatures = 5e3;
146
- console.log(`\u{1F50D} OpenDataSoft getFeatures: Starting fetch for dataset "${datasetId}"`);
147
139
  do {
148
140
  const recordsUrl = `${recordsBaseUrl}?limit=100&offset=${offset2}`;
149
- console.log(`\u{1F4E1} OpenDataSoft: Fetching ${recordsUrl}`);
150
141
  const response = await fetch(buildUrl(recordsUrl));
151
142
  if (!response.ok) {
152
143
  console.error(`\u274C OpenDataSoft: Failed to fetch records - status ${response.status}`, {
@@ -162,7 +153,6 @@ async function fetchOpenDataSoftDatasets(portal) {
162
153
  console.error(`\u274C OpenDataSoft: Failed to parse JSON response:`, parseError);
163
154
  break;
164
155
  }
165
- console.log(`\u{1F4E6} OpenDataSoft: Received data, records count: ${((_c2 = data.records) == null ? void 0 : _c2.length) || 0}, data keys:`, Object.keys(data || {}));
166
156
  if (offset2 === 0) {
167
157
  totalCount2 = Math.min(data.total_count || data.nhits || 0, maxFeatures);
168
158
  }
@@ -198,7 +188,6 @@ async function fetchOpenDataSoftDatasets(portal) {
198
188
  const fetchedCount = recArray.length;
199
189
  if (fetchedCount === 0 || features.length >= maxFeatures) break;
200
190
  } while (offset2 < totalCount2);
201
- console.log(`\u2705 OpenDataSoft getFeatures: Returning ${features.length} features for dataset "${datasetId}"`);
202
191
  const result = {
203
192
  type: "FeatureCollection",
204
193
  features
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../src/core/components/viewers/map/datasets/src/opendatasoftDatasets.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { layerColorByName } from '../../utils/stringToColour'\r\nimport { formatDate, formatName, stripHtml } from '../utils'\r\n\r\nimport { buildValidationKey, getCache, setCache, withCache } from './cache'\r\n\r\nimport type { Dataset } from '../../../../../types/datasetTypes'\r\nimport type { OpenDataPortal } from '../../../../../types/dbTypes'\r\nimport type { AllGeoJSON } from '@turf/turf'\r\n\r\n// Fetch OpenDataSoft datasets and transform to Dataset array\r\nexport async function fetchOpenDataSoftDatasets(\r\n portal: OpenDataPortal,\r\n): Promise<Dataset[]> {\r\n const { apiUrl, countrySubdivision, municipality, group } = portal\r\n const baseUrl = apiUrl.replace(/\\/$/, '')\r\n const catalogBase = `${baseUrl}/catalog`\r\n const isServer = typeof window === 'undefined'\r\n\r\n const buildUrl = (target: string) => {\r\n if (isServer) {\r\n return target\r\n }\r\n return `/api/opendatasoftProxy?url=${encodeURIComponent(target)}`\r\n }\r\n\r\n try {\r\n let allDatasets: any[] = []\r\n let offset = 0\r\n let totalCount = 0\r\n\r\n do {\r\n const catalogUrl = `${catalogBase}/datasets?limit=100&offset=${offset}`\r\n const response = await fetch(buildUrl(catalogUrl))\r\n\r\n if (!response.ok) {\r\n console.error(`OpenDataSoft API error for ${portal.name}:`, response.status, response.statusText)\r\n throw new Error(`HTTP error! status: ${response.status}`)\r\n }\r\n\r\n const data = await response.json()\r\n\r\n // More flexible response structure handling\r\n if (!data) {\r\n throw new Error('Empty response from OpenDataSoft API')\r\n }\r\n\r\n // Handle different possible response structures\r\n let results: any[] = []\r\n let total = 0\r\n\r\n if (data.results && Array.isArray(data.results)) {\r\n // Standard OpenDataSoft v2 API response\r\n results = data.results\r\n total = data.total_count || data.nhits || 0\r\n }\r\n else if (data.datasets && Array.isArray(data.datasets)) {\r\n // Alternative response structure\r\n results = data.datasets\r\n total = data.total || data.count || 0\r\n }\r\n else if (Array.isArray(data)) {\r\n // Direct array response\r\n results = data\r\n total = data.length\r\n }\r\n else {\r\n // Log the actual structure for debugging\r\n console.error('Unexpected response structure:', Object.keys(data))\r\n throw new Error(\r\n `Invalid OpenDataSoft response structure. Expected 'results' array, got: ${Object.keys(\r\n data,\r\n ).join(', ')}`,\r\n )\r\n }\r\n allDatasets = allDatasets.concat(results)\r\n totalCount = total\r\n offset += 100\r\n\r\n // Safety check to prevent infinite loops\r\n if (results.length === 0) {\r\n break\r\n }\r\n } while (offset < totalCount)\r\n\r\n const transformedDatasets: Dataset[] = (await Promise.all(allDatasets.map(async (dataset: any) => {\r\n const metas = dataset.metas?.default || {}\r\n const custom = dataset.metas?.custom || {}\r\n const datasetId = dataset.dataset_id || dataset.id || ''\r\n const datasetModified = metas.modified || dataset.modified || undefined\r\n\r\n const datasetBaseUrl = `${catalogBase}/datasets/${datasetId}`\r\n const recordsBaseUrl = `${datasetBaseUrl}/records`\r\n\r\n const validKey = buildValidationKey('opendatasoft', datasetId, datasetModified)\r\n const isValid = await withCache<boolean>(validKey, 24 * 60 * 60 * 1000, async () => {\r\n try {\r\n const sampleUrl = `${recordsBaseUrl}?limit=1`\r\n const sampleRes = await fetch(buildUrl(sampleUrl))\r\n if (sampleRes.ok) {\r\n const sample = await sampleRes.json()\r\n if (sample?.error) {\r\n const code = sample.error.code\r\n const msg = `${sample.error.message || ''}`\r\n console.warn(`⚠️ OpenDataSoft validation: Dataset \"${datasetId}\" returned error:`, { code, msg })\r\n if ([401, 403, 499, 504].includes(code) || /unauthorized|forbidden|token required|timed out/i.test(msg)) return false\r\n // Even if there's an error, allow the dataset through - getFeatures will handle it\r\n return true\r\n }\r\n // If response is OK, check if it has geometry\r\n const recs = sample.records || sample.results || [];\r\n const hasGeom = Array.isArray(recs)\r\n ? recs.some((r: any) => !!r?.geometry || !!r?.geom || !!r?.geo_shape || !!r?.geo_point_2d)\r\n : false\r\n console.log(`ℹ️ OpenDataSoft validation: Dataset \"${datasetId}\" has geometry: ${hasGeom}, records: ${recs.length}`)\r\n // Allow through even if no geometry - user might want to see the dataset\r\n return true\r\n }\r\n else if ([401, 403, 504].includes(sampleRes.status)) {\r\n console.error(`❌ OpenDataSoft validation: Dataset \"${datasetId}\" auth/timeout error: ${sampleRes.status}`)\r\n return false\r\n }\r\n else {\r\n console.warn(`⚠️ OpenDataSoft validation: Dataset \"${datasetId}\" validation request failed: ${sampleRes.status}`)\r\n // Allow through on other errors\r\n return true\r\n }\r\n }\r\n catch (error) {\r\n console.error(`❌ OpenDataSoft validation: Dataset \"${datasetId}\" validation failed:`, error)\r\n // Allow through on network errors - getFeatures will handle it\r\n return true\r\n }\r\n })\r\n\r\n if (!isValid) {\r\n console.log(`🗑️ OpenDataSoft: Filtering out dataset \"${datasetId}\" - validation failed`)\r\n return null\r\n }\r\n\r\n // Self-contained getFields\r\n const getFields = async () => {\r\n const fieldsKey = `fields:opendatasoft:${datasetId}:${datasetModified ?? ''}`\r\n const cached = getCache<any[]>(fieldsKey)\r\n if (cached) return cached\r\n const fieldsUrl = `${datasetBaseUrl}`\r\n const response = await fetch(buildUrl(fieldsUrl))\r\n if (!response.ok) return []\r\n const data = await response.json()\r\n const fields = (data.metas?.default?.fields || data.fields || []).map((field: any) => ({\r\n name: field.name,\r\n label: field.label || field.name,\r\n type: field.type,\r\n description: field.description || '',\r\n }))\r\n setCache(fieldsKey, fields, 24 * 60 * 60 * 1000)\r\n return fields\r\n }\r\n\r\n // Self-contained getFeatures with caching\r\n const getFeatures = async (): Promise<AllGeoJSON> => {\r\n const featuresKey = `features:opendatasoft:${datasetId}:${datasetModified ?? ''}`\r\n const cached = getCache<AllGeoJSON>(featuresKey)\r\n if (cached) {\r\n const cachedCount = 'features' in cached ? cached.features?.length ?? 0 : 0\r\n console.log(`✅ OpenDataSoft getFeatures: Returning ${cachedCount} cached features for dataset \"${datasetId}\"`)\r\n return cached\r\n }\r\n\r\n try {\r\n const features: any[] = []\r\n let offset = 0\r\n let totalCount = 0\r\n const maxFeatures = 5_000\r\n\r\n console.log(`🔍 OpenDataSoft getFeatures: Starting fetch for dataset \"${datasetId}\"`)\r\n\r\n do {\r\n const recordsUrl = `${recordsBaseUrl}?limit=100&offset=${offset}`\r\n console.log(`📡 OpenDataSoft: Fetching ${recordsUrl}`)\r\n const response = await fetch(buildUrl(recordsUrl))\r\n if (!response.ok) {\r\n console.error(`❌ OpenDataSoft: Failed to fetch records - status ${response.status}`, {\r\n url: recordsUrl,\r\n statusText: response.statusText,\r\n })\r\n break\r\n }\r\n let data;\r\n try {\r\n data = await response.json()\r\n } catch (parseError) {\r\n console.error(`❌ OpenDataSoft: Failed to parse JSON response:`, parseError)\r\n break\r\n }\r\n console.log(`📦 OpenDataSoft: Received data, records count: ${data.records?.length || 0}, data keys:`, Object.keys(data || {}))\r\n\r\n if (offset === 0) {\r\n totalCount = Math.min(data.total_count || data.nhits || 0, maxFeatures)\r\n }\r\n\r\n const recArray: any[] = data.records || data.results || [];\r\n if (Array.isArray(recArray)) {\r\n recArray.forEach((record: any) => {\r\n if (features.length >= maxFeatures) return;\r\n\r\n // Extract geometry from possible fields\r\n let geom: any = null;\r\n if (record.geometry) {\r\n geom = record.geometry;\r\n } else if (record.geom) {\r\n geom = record.geom.type === 'Feature' && record.geom.geometry ? record.geom.geometry : record.geom;\r\n } else if (record.geo_shape) {\r\n geom = record.geo_shape;\r\n } else if (record.geo_point_2d && typeof record.geo_point_2d.lat === 'number' && typeof record.geo_point_2d.lon === 'number') {\r\n geom = { type: 'Point', coordinates: [record.geo_point_2d.lon, record.geo_point_2d.lat] };\r\n }\r\n\r\n if (geom) {\r\n const props: any = { ...record };\r\n delete props.geometry;\r\n delete props.geom;\r\n delete props.geo_shape;\r\n delete props.geo_point_2d;\r\n\r\n features.push({\r\n type: 'Feature',\r\n geometry: geom,\r\n properties: props,\r\n });\r\n }\r\n });\r\n }\r\n\r\n offset += 100\r\n const fetchedCount = recArray.length;\r\n if (fetchedCount === 0 || features.length >= maxFeatures) break\r\n } while (offset < totalCount)\r\n\r\n console.log(`✅ OpenDataSoft getFeatures: Returning ${features.length} features for dataset \"${datasetId}\"`)\r\n\r\n const result = {\r\n type: 'FeatureCollection',\r\n features,\r\n } as AllGeoJSON\r\n\r\n // Cache the result for 1 hour\r\n setCache(featuresKey, result, 60 * 60 * 1000)\r\n\r\n return result\r\n } catch (error) {\r\n console.error(`❌ OpenDataSoft getFeatures: Error fetching features for dataset \"${datasetId}\":`, error)\r\n // Return empty feature collection on error instead of throwing\r\n return {\r\n type: 'FeatureCollection',\r\n features: [],\r\n } as AllGeoJSON\r\n }\r\n }\r\n\r\n const name = formatName(metas.title || dataset.title || datasetId)\r\n\r\n return {\r\n id: datasetId,\r\n name,\r\n publisher:\r\n metas.publisher\r\n || custom['data-owner']?.[0]\r\n || dataset.publisher\r\n || 'Unknown',\r\n dataManagementSystem: 'Opendatasoft',\r\n license: metas.license || dataset.license || 'Unknown',\r\n contact: custom['data-team']?.[0] || dataset.contact || 'Unknown',\r\n keywords: metas.keyword || dataset.keywords || [],\r\n description: stripHtml(\r\n metas.description || dataset.description || 'No description available',\r\n ),\r\n dateReleased: metas.created\r\n ? formatDate(metas.created)\r\n : (dataset.created\r\n ? formatDate(dataset.created)\r\n : ''),\r\n dateUpdated: metas.modified\r\n ? formatDate(metas.modified)\r\n : (dataset.modified\r\n ? formatDate(dataset.modified)\r\n : ''),\r\n information: `${apiUrl}/explore/dataset/${datasetId}/`,\r\n countrySubdivision,\r\n municipality,\r\n type: portal.name,\r\n datasetType: dataset.type || 'GeoJSON',\r\n portal,\r\n getFields,\r\n getFeatures,\r\n group,\r\n layerColor: layerColorByName(name),\r\n }\r\n })))\r\n .filter(Boolean) as Dataset[]\r\n\r\n return transformedDatasets\r\n }\r\n catch (error) {\r\n console.error(`Error fetching OpenDataSoft datasets for ${portal.name}:`, error)\r\n return []\r\n }\r\n}\r\n"],"mappings":"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","names":["_a","_b","_c","offset","totalCount"]}
1
+ {"version":3,"sources":["../../../../../../../src/core/components/viewers/map/datasets/src/opendatasoftDatasets.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\r\n// Copyright (C) 2025 Collab Digital Twins\r\n\r\nimport { layerColorByName } from '../../utils/stringToColour'\r\nimport { formatDate, formatName, stripHtml } from '../utils'\r\n\r\nimport { buildValidationKey, getCache, setCache, withCache } from './cache'\r\n\r\nimport type { Dataset } from '../../../../../types/datasetTypes'\r\nimport type { OpenDataPortal } from '../../../../../types/dbTypes'\r\nimport type { AllGeoJSON } from '@turf/turf'\r\n\r\n// Fetch OpenDataSoft datasets and transform to Dataset array\r\nexport async function fetchOpenDataSoftDatasets(\r\n portal: OpenDataPortal,\r\n): Promise<Dataset[]> {\r\n const { apiUrl, countrySubdivision, municipality, group } = portal\r\n const baseUrl = apiUrl.replace(/\\/$/, '')\r\n const catalogBase = `${baseUrl}/catalog`\r\n const isServer = typeof window === 'undefined'\r\n\r\n const buildUrl = (target: string) => {\r\n if (isServer) {\r\n return target\r\n }\r\n return `/api/opendatasoftProxy?url=${encodeURIComponent(target)}`\r\n }\r\n\r\n try {\r\n let allDatasets: any[] = []\r\n let offset = 0\r\n let totalCount = 0\r\n\r\n do {\r\n const catalogUrl = `${catalogBase}/datasets?limit=100&offset=${offset}`\r\n const response = await fetch(buildUrl(catalogUrl))\r\n\r\n if (!response.ok) {\r\n console.error(`OpenDataSoft API error for ${portal.name}:`, response.status, response.statusText)\r\n throw new Error(`HTTP error! status: ${response.status}`)\r\n }\r\n\r\n const data = await response.json()\r\n\r\n // More flexible response structure handling\r\n if (!data) {\r\n throw new Error('Empty response from OpenDataSoft API')\r\n }\r\n\r\n // Handle different possible response structures\r\n let results: any[] = []\r\n let total = 0\r\n\r\n if (data.results && Array.isArray(data.results)) {\r\n // Standard OpenDataSoft v2 API response\r\n results = data.results\r\n total = data.total_count || data.nhits || 0\r\n }\r\n else if (data.datasets && Array.isArray(data.datasets)) {\r\n // Alternative response structure\r\n results = data.datasets\r\n total = data.total || data.count || 0\r\n }\r\n else if (Array.isArray(data)) {\r\n // Direct array response\r\n results = data\r\n total = data.length\r\n }\r\n else {\r\n // Log the actual structure for debugging\r\n console.error('Unexpected response structure:', Object.keys(data))\r\n throw new Error(\r\n `Invalid OpenDataSoft response structure. Expected 'results' array, got: ${Object.keys(\r\n data,\r\n ).join(', ')}`,\r\n )\r\n }\r\n allDatasets = allDatasets.concat(results)\r\n totalCount = total\r\n offset += 100\r\n\r\n // Safety check to prevent infinite loops\r\n if (results.length === 0) {\r\n break\r\n }\r\n } while (offset < totalCount)\r\n\r\n const transformedDatasets: Dataset[] = (await Promise.all(allDatasets.map(async (dataset: any) => {\r\n const metas = dataset.metas?.default || {}\r\n const custom = dataset.metas?.custom || {}\r\n const datasetId = dataset.dataset_id || dataset.id || ''\r\n const datasetModified = metas.modified || dataset.modified || undefined\r\n\r\n const datasetBaseUrl = `${catalogBase}/datasets/${datasetId}`\r\n const recordsBaseUrl = `${datasetBaseUrl}/records`\r\n\r\n const validKey = buildValidationKey('opendatasoft', datasetId, datasetModified)\r\n const isValid = await withCache<boolean>(validKey, 24 * 60 * 60 * 1000, async () => {\r\n try {\r\n const sampleUrl = `${recordsBaseUrl}?limit=1`\r\n const sampleRes = await fetch(buildUrl(sampleUrl))\r\n if (sampleRes.ok) {\r\n const sample = await sampleRes.json()\r\n if (sample?.error) {\r\n const code = sample.error.code\r\n const msg = `${sample.error.message || ''}`\r\n console.warn(`⚠️ OpenDataSoft validation: Dataset \"${datasetId}\" returned error:`, { code, msg })\r\n if ([401, 403, 499, 504].includes(code) || /unauthorized|forbidden|token required|timed out/i.test(msg)) return false\r\n // Even if there's an error, allow the dataset through - getFeatures will handle it\r\n return true\r\n }\r\n // Allow through even if no geometry - user might want to see the dataset\r\n return true\r\n }\r\n else if ([401, 403, 504].includes(sampleRes.status)) {\r\n console.error(`❌ OpenDataSoft validation: Dataset \"${datasetId}\" auth/timeout error: ${sampleRes.status}`)\r\n return false\r\n }\r\n else {\r\n console.warn(`⚠️ OpenDataSoft validation: Dataset \"${datasetId}\" validation request failed: ${sampleRes.status}`)\r\n // Allow through on other errors\r\n return true\r\n }\r\n }\r\n catch (error) {\r\n console.error(`❌ OpenDataSoft validation: Dataset \"${datasetId}\" validation failed:`, error)\r\n // Allow through on network errors - getFeatures will handle it\r\n return true\r\n }\r\n })\r\n\r\n if (!isValid) {\r\n return null\r\n }\r\n\r\n // Self-contained getFields\r\n const getFields = async () => {\r\n const fieldsKey = `fields:opendatasoft:${datasetId}:${datasetModified ?? ''}`\r\n const cached = getCache<any[]>(fieldsKey)\r\n if (cached) return cached\r\n const fieldsUrl = `${datasetBaseUrl}`\r\n const response = await fetch(buildUrl(fieldsUrl))\r\n if (!response.ok) return []\r\n const data = await response.json()\r\n const fields = (data.metas?.default?.fields || data.fields || []).map((field: any) => ({\r\n name: field.name,\r\n label: field.label || field.name,\r\n type: field.type,\r\n description: field.description || '',\r\n }))\r\n setCache(fieldsKey, fields, 24 * 60 * 60 * 1000)\r\n return fields\r\n }\r\n\r\n // Self-contained getFeatures with caching\r\n const getFeatures = async (): Promise<AllGeoJSON> => {\r\n const featuresKey = `features:opendatasoft:${datasetId}:${datasetModified ?? ''}`\r\n const cached = getCache<AllGeoJSON>(featuresKey)\r\n if (cached) {\r\n return cached\r\n }\r\n\r\n try {\r\n const features: any[] = []\r\n let offset = 0\r\n let totalCount = 0\r\n const maxFeatures = 5_000\r\n\r\n do {\r\n const recordsUrl = `${recordsBaseUrl}?limit=100&offset=${offset}`\r\n const response = await fetch(buildUrl(recordsUrl))\r\n if (!response.ok) {\r\n console.error(`❌ OpenDataSoft: Failed to fetch records - status ${response.status}`, {\r\n url: recordsUrl,\r\n statusText: response.statusText,\r\n })\r\n break\r\n }\r\n let data;\r\n try {\r\n data = await response.json()\r\n } catch (parseError) {\r\n console.error(`❌ OpenDataSoft: Failed to parse JSON response:`, parseError)\r\n break\r\n }\r\n if (offset === 0) {\r\n totalCount = Math.min(data.total_count || data.nhits || 0, maxFeatures)\r\n }\r\n\r\n const recArray: any[] = data.records || data.results || [];\r\n if (Array.isArray(recArray)) {\r\n recArray.forEach((record: any) => {\r\n if (features.length >= maxFeatures) return;\r\n\r\n // Extract geometry from possible fields\r\n let geom: any = null;\r\n if (record.geometry) {\r\n geom = record.geometry;\r\n } else if (record.geom) {\r\n geom = record.geom.type === 'Feature' && record.geom.geometry ? record.geom.geometry : record.geom;\r\n } else if (record.geo_shape) {\r\n geom = record.geo_shape;\r\n } else if (record.geo_point_2d && typeof record.geo_point_2d.lat === 'number' && typeof record.geo_point_2d.lon === 'number') {\r\n geom = { type: 'Point', coordinates: [record.geo_point_2d.lon, record.geo_point_2d.lat] };\r\n }\r\n\r\n if (geom) {\r\n const props: any = { ...record };\r\n delete props.geometry;\r\n delete props.geom;\r\n delete props.geo_shape;\r\n delete props.geo_point_2d;\r\n\r\n features.push({\r\n type: 'Feature',\r\n geometry: geom,\r\n properties: props,\r\n });\r\n }\r\n });\r\n }\r\n\r\n offset += 100\r\n const fetchedCount = recArray.length;\r\n if (fetchedCount === 0 || features.length >= maxFeatures) break\r\n } while (offset < totalCount)\r\n\r\n const result = {\r\n type: 'FeatureCollection',\r\n features,\r\n } as AllGeoJSON\r\n\r\n // Cache the result for 1 hour\r\n setCache(featuresKey, result, 60 * 60 * 1000)\r\n\r\n return result\r\n } catch (error) {\r\n console.error(`❌ OpenDataSoft getFeatures: Error fetching features for dataset \"${datasetId}\":`, error)\r\n // Return empty feature collection on error instead of throwing\r\n return {\r\n type: 'FeatureCollection',\r\n features: [],\r\n } as AllGeoJSON\r\n }\r\n }\r\n\r\n const name = formatName(metas.title || dataset.title || datasetId)\r\n\r\n return {\r\n id: datasetId,\r\n name,\r\n publisher:\r\n metas.publisher\r\n || custom['data-owner']?.[0]\r\n || dataset.publisher\r\n || 'Unknown',\r\n dataManagementSystem: 'Opendatasoft',\r\n license: metas.license || dataset.license || 'Unknown',\r\n contact: custom['data-team']?.[0] || dataset.contact || 'Unknown',\r\n keywords: metas.keyword || dataset.keywords || [],\r\n description: stripHtml(\r\n metas.description || dataset.description || 'No description available',\r\n ),\r\n dateReleased: metas.created\r\n ? formatDate(metas.created)\r\n : (dataset.created\r\n ? formatDate(dataset.created)\r\n : ''),\r\n dateUpdated: metas.modified\r\n ? formatDate(metas.modified)\r\n : (dataset.modified\r\n ? formatDate(dataset.modified)\r\n : ''),\r\n information: `${apiUrl}/explore/dataset/${datasetId}/`,\r\n countrySubdivision,\r\n municipality,\r\n type: portal.name,\r\n datasetType: dataset.type || 'GeoJSON',\r\n portal,\r\n getFields,\r\n getFeatures,\r\n group,\r\n layerColor: layerColorByName(name),\r\n }\r\n })))\r\n .filter(Boolean) as Dataset[]\r\n\r\n return transformedDatasets\r\n }\r\n catch (error) {\r\n console.error(`Error fetching OpenDataSoft datasets for ${portal.name}:`, error)\r\n return []\r\n }\r\n}\r\n"],"mappings":"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","names":["_a","_b","offset","totalCount"]}
@@ -1 +1 @@
1
- {"version":3,"file":"BuildingsTools.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAC;AAiB1B,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,UAAU,kBAAkB;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EACpC,SAAS,EACT,OAAO,EACP,QAAQ,EACR,OAAO,EACP,aAAa,EACd,EAAE,kBAAkB,qBAyWpB"}
1
+ {"version":3,"file":"BuildingsTools.d.ts","sourceRoot":"","sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,MAAM,OAAO,CAAC;AAiB1B,OAAO,KAAK,EAAE,QAAQ,EAAU,MAAM,uCAAuC,CAAC;AAE9E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,UAAU,kBAAkB;IAC1B,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,iBAAiB,CAAC;IAC3B,aAAa,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,CAAC,OAAO,UAAU,aAAa,CAAC,EACpC,SAAS,EACT,OAAO,EACP,QAAQ,EACR,OAAO,EACP,aAAa,EACd,EAAE,kBAAkB,qBAwWpB"}
@@ -281,7 +281,6 @@ function BuildingTools({
281
281
  ] }),
282
282
  bimFiles.length === 1 && bimModelsAddedToMap.some((model) => model.building.id === buildingId) && (() => {
283
283
  const selectedModel = bimModelsAddedToMap.find((model) => model.building.id === buildingId);
284
- console.log("selectedModel", selectedModel);
285
284
  return true ? /* @__PURE__ */ jsx(
286
285
  Button,
287
286
  {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.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 React from \"react\";\r\n\r\nimport { Button, Label, Skeleton, Switch, Checkbox } from \"../../../../../../../../components/ui/\";\r\nimport {\r\n DropdownMenu,\r\n DropdownMenuContent,\r\n DropdownMenuItem,\r\n DropdownMenuTrigger,\r\n} from \"../../../../../../../../components/ui/DropdownMenu\";\r\nimport { useFileUploadHandler } from \"../../../../../../../../components/ui/FilesManager/src/useFileUploadHandler\";\r\nimport { useFilesByBuildingId, useUploadFileToBuilding } from \"../../../../../../../../hooks/files/files\";\r\nimport { usePermissions } from '../../../../../../../../store'\r\nimport { BuildingsContext, BimContext, MapContext, MenusContext, useMenusContext } from \"../../../../../../../../store\";\r\nimport { ViewerNames } from \"../../../../../../../../types/\";\r\nimport { toggleBimToMap as dispatchToggleBimToMap } from '../../../../../utils/toggleBimToMap';\r\nimport Compare from '../../../../compare';\r\n\r\nimport type { Building, DbFile } from \"../../../../../../../../types/dbTypes\";\r\nimport type { Tool } from \"../../../../../../../../types/tools\";\r\nimport type { MapGeoJSONFeature } from \"maplibre-gl\";\r\n\r\ninterface BuildingToolsProps {\r\n isLoading: boolean;\r\n isError: boolean;\r\n building: Building | null;\r\n feature: MapGeoJSONFeature;\r\n onCloseAction: () => void;\r\n}\r\n\r\nexport default function BuildingTools({\r\n isLoading,\r\n isError,\r\n building,\r\n feature,\r\n onCloseAction\r\n}: BuildingToolsProps) {\r\n\r\n const t = useTranslations('BuildingTools')\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { dispatch: menusDispatch } = React.useContext(MenusContext);\r\n const { state: mapState, } = React.useContext(MapContext);\r\n const { dispatch: bimDispatch, state: bimState } = React.useContext(BimContext);\r\n const { bimModelsAddedToMap } = bimState.bim;\r\n const { dispatch: buildingsDispatch } = React.useContext(BuildingsContext);\r\n\r\n const fileInputRef = React.useRef<HTMLInputElement>(null);\r\n const { setView } = useMenusContext();\r\n const { setSelectedItem } = useMenusContext();\r\n\r\n const buildingId = React.useMemo(\r\n () => {\r\n\r\n const featureProps = feature?.properties || {};\r\n const id = Number(featureProps.dbId);\r\n return isNaN(id)\r\n ? null\r\n : id;\r\n\r\n },\r\n [feature?.properties],\r\n );\r\n\r\n const { files } = useFilesByBuildingId(buildingId);\r\n\r\n const bimAdded = React.useMemo(\r\n () => (buildingId\r\n ? mapState.map.bimModelsAddedToMap.includes(buildingId)\r\n : false),\r\n [\r\n mapState.map.bimModelsAddedToMap,\r\n buildingId,\r\n ],\r\n );\r\n\r\n const { uploadFile, isMutating: isUploadingFromHook } = useUploadFileToBuilding(building?.id || null);\r\n\r\n // Local state to track the entire upload process from file selection to completion\r\n const [\r\n isLocallyUploading,\r\n setIsLocallyUploading,\r\n ] = React.useState(false);\r\n\r\n // Combined uploading state\r\n const isUploading = isLocallyUploading || isUploadingFromHook;\r\n\r\n // Use the reusable upload handler without passing bimComponents\r\n const { handleFileUpload } = useFileUploadHandler({\r\n buildingId: buildingId || -1,\r\n uploadFile,\r\n onUploadStart: () => setIsLocallyUploading(true),\r\n onUploadEnd: () => setIsLocallyUploading(false),\r\n customMessages: {\r\n success: t('toastFIleUploadSuccess'),\r\n errorPrefix: t('toastFIleUploadError'),\r\n errorSuffix: t('toastUnknownError'),\r\n noIdError: t('buildingIdRequired')\r\n }\r\n });\r\n\r\n const changeViewer = React.useCallback(\r\n (viewer: ViewerNames) => {\r\n\r\n menusDispatch({\r\n type: \"SET_VIEWER\",\r\n payload: { \"currentViewer\": viewer },\r\n });\r\n\r\n buildingsDispatch({\r\n type: \"SET-CURRENT-BUILDING\",\r\n payload: { building },\r\n });\r\n onCloseAction();\r\n\r\n },\r\n [\r\n menusDispatch,\r\n buildingsDispatch,\r\n building,\r\n onCloseAction\r\n ]\r\n );\r\n\r\n const triggerFileInput = React.useCallback(\r\n () => {\r\n\r\n fileInputRef.current?.click();\r\n\r\n },\r\n [],\r\n );\r\n\r\n const toggleBimToMap = React.useCallback(\r\n (bimFile: DbFile) => {\r\n dispatchToggleBimToMap(bimDispatch, bimFile, building);\r\n\r\n },\r\n [\r\n building,\r\n bimDispatch\r\n ]\r\n );\r\n\r\n const onOpenBimViewer = () => {\r\n buildingsDispatch({ type: \"SET-CURRENT-BUILDING\", payload: { building } });\r\n changeViewer(ViewerNames.bim);\r\n setSelectedItem(building);\r\n }\r\n\r\n const buildingFiles = React.useMemo(\r\n () => files || [],\r\n [files],\r\n );\r\n\r\n const bimFiles = React.useMemo(\r\n () => buildingFiles.filter((file: DbFile) => file.extension?.toLowerCase() === \"frag\"),\r\n [buildingFiles],\r\n )\r\n\r\n const pcFiles = React.useMemo(\r\n () => buildingFiles.filter((file: DbFile) => file.type === 'point-cloud-file' || ['laz', 'las'].includes(file.extension || '')),\r\n [buildingFiles],\r\n )\r\n\r\n const buildingButtons: Tool[] = React.useMemo(\r\n () => [\r\n {\r\n id: \"map-compare-buildings\",\r\n icon: LR.Columns3,\r\n title: t('compareTitle'),\r\n component: Compare,\r\n disabled: !ability.can('read', 'Building')\r\n },\r\n {\r\n id: \"open-building-page\",\r\n icon: LR.Building2,\r\n title: t('openBIMTitle'),\r\n onClick: () => {\r\n\r\n changeViewer(ViewerNames.buildings);\r\n setSelectedItem(building);\r\n setView(\"detail\");\r\n\r\n },\r\n disabled: !ability.can('read', 'Building')\r\n },\r\n {\r\n id: \"open-bim-viewer\",\r\n icon: LR.Box,\r\n title: t('OpenBIMViewerTitle'),\r\n onClick: onOpenBimViewer,\r\n disabled: !ability.can('read', 'File')\r\n },\r\n {\r\n id: 'open-pointcloud',\r\n title: t('openPointCloudTitle'),\r\n icon: LR.Grip,\r\n onClick: () => changeViewer(ViewerNames.pointcloud),\r\n disabled: pcFiles.length === 0 || !ability.can('read', 'File')\r\n },\r\n {\r\n id: \"map-add-file\",\r\n icon: isUploading\r\n ? LR.Loader\r\n : LR.FilePlus,\r\n title: t('addFileTitle'),\r\n onClick: triggerFileInput,\r\n disabled: !ability.can('create', 'File')\r\n },\r\n ],\r\n [\r\n changeViewer,\r\n building,\r\n setSelectedItem,\r\n setView,\r\n isUploading,\r\n triggerFileInput\r\n ]\r\n );\r\n\r\n const onLoadBIM = React.useCallback(\r\n (file: DbFile = null) => {\r\n if (bimFiles.length === 0) return;\r\n toggleBimToMap(bimFiles[0]);\r\n },\r\n [\r\n bimFiles,\r\n toggleBimToMap\r\n ]\r\n );\r\n\r\n const handleEditClick = (fileName: string) => {\r\n bimDispatch({\r\n type: \"EDIT_BIM_MODEL_BY_NAME\",\r\n payload: { editingBimModel: fileName }\r\n });\r\n\r\n onCloseAction();\r\n }\r\n\r\n\r\n return (\r\n <div className=\"space-y-4\">\r\n {/* Hidden file input */}\r\n <input\r\n onChange={(e) => handleFileUpload(e.target.files?.[0] || null)}\r\n ref={fileInputRef}\r\n style={{ \"display\": \"none\" }}\r\n type=\"file\"\r\n />\r\n\r\n {/* BIM Controls */}\r\n {!isError &&\r\n <div className=\"flex items-center space-x-2\">\r\n {isLoading\r\n ? <>\r\n <Skeleton className=\"h-4 w-20\" />\r\n\r\n <Skeleton className=\"h-6 w-10\" />\r\n </>\r\n : (\r\n <div className=\"flex items-center justify-between w-full\">\r\n {bimFiles.length > 1 ? (\r\n <div className=\"flex items-center space-x-2\">\r\n <Label className=\"font-semibold\" htmlFor=\"bim-file-dropdown\">\r\n {t('selectBimLabel')}\r\n </Label>\r\n <DropdownMenu>\r\n <DropdownMenuTrigger asChild>\r\n <Button variant=\"outline\" size=\"sm\" id=\"bim-file-dropdown\">\r\n {bimModelsAddedToMap.length > 0\r\n ? `${bimModelsAddedToMap.filter(model => model.bimFile.attachedFilesBuildingId === buildingId).length} ${t('filesSelected')}`\r\n : t('selectFiles')\r\n }\r\n </Button>\r\n </DropdownMenuTrigger>\r\n <DropdownMenuContent>\r\n {bimFiles.map((file: DbFile) => {\r\n const isSelected = bimModelsAddedToMap.some(model => model.bimFile.name === file.name);\r\n return (\r\n <DropdownMenuItem\r\n key={file.name}\r\n onClick={(e) => {\r\n e.preventDefault();\r\n toggleBimToMap(file);\r\n }}\r\n className=\"flex items-center justify-between cursor-pointer\"\r\n >\r\n <div className=\"flex items-center space-x-2\">\r\n <Checkbox\r\n checked={isSelected}\r\n onClick={(e) => e.stopPropagation()}\r\n onCheckedChange={() => {\r\n toggleBimToMap(file);\r\n }}\r\n />\r\n <span>{file.name}</span>\r\n </div>\r\n {isSelected && (\r\n <Button\r\n onClick={(e) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n handleEditClick(file.name);\r\n }}\r\n variant=\"ghost\"\r\n size=\"sm\"\r\n className=\"h-6 w-6 p-0\"\r\n title={t('editBimButton')}\r\n >\r\n <LR.LocationEdit className=\"h-3 w-3\" />\r\n </Button>\r\n )}\r\n </DropdownMenuItem>\r\n );\r\n })}\r\n </DropdownMenuContent>\r\n </DropdownMenu>\r\n </div>\r\n ) : (\r\n <div className=\"flex items-center space-x-2\">\r\n <Label\r\n className={`font-semibold ${bimFiles.length === 0 ? 'text-muted-foreground' : ''}`}\r\n htmlFor=\"show-bim\"\r\n >\r\n {bimAdded ? t('hideBIM') : t('showBIM')}\r\n </Label>\r\n <Switch\r\n id=\"show-bim\"\r\n checked={bimFiles.length === 1 && bimModelsAddedToMap.some(model => model.bimFile.name === bimFiles[0]?.name)}\r\n disabled={bimFiles.length === 0 || !ability.can('read', 'File')}\r\n onCheckedChange={() => onLoadBIM()}\r\n />\r\n </div>\r\n )}\r\n {bimFiles.length === 1 && bimModelsAddedToMap.some(model => model.building.id === buildingId) && (() => {\r\n const selectedModel = bimModelsAddedToMap.find(model => model.building.id === buildingId);\r\n console.log('selectedModel', selectedModel);\r\n return true ? (\r\n <Button\r\n onClick={() => handleEditClick(selectedModel.bimFile.name)}\r\n variant=\"ghost\"\r\n size=\"sm\"\r\n title={t('editBimButton')}\r\n disabled={!ability.can('update', 'File')}\r\n >\r\n <LR.LocationEdit className=\"h-4 w-4\" />\r\n </Button>\r\n ) : null;\r\n })()}\r\n </div>\r\n )\r\n }\r\n </div>}\r\n\r\n {/* Tool Buttons */}\r\n <div className=\"flex items-center gap-3\">\r\n {isLoading\r\n ? Array.from({ \"length\": 4 }).map((_, index) => (<Skeleton\r\n key={index}\r\n className=\"w-8 h-8 rounded-sm\"\r\n />),)\r\n : (!isError && building ? (\r\n buildingButtons.map((button) => {\r\n if (button.component) {\r\n const ButtonComponent = button.component\r\n const shouldAnimateSpin = button.id === 'map-add-file' && isUploading\r\n return (\r\n <div\r\n key={button.id}\r\n className={`w-8 h-8 rounded-sm border-2 border-gray-200 inline-flex justify-center items-center ${shouldAnimateSpin ? 'animate-spin' : ''}`}\r\n >\r\n <ButtonComponent tool={button} building={building} />\r\n </div>\r\n )\r\n }\r\n return (\r\n <Button\r\n key={button.id}\r\n variant=\"ghost\"\r\n className=\"w-8 h-8 rounded-sm border-2 border-gray-200 inline-flex justify-center items-center\"\r\n aria-label={button.title}\r\n onClick={button.onClick}\r\n title={button.title}\r\n disabled={button.disabled}\r\n >\r\n <button.icon className={`w-6 h-6 ${button.id === 'map-add-file' && isUploading ? 'animate-spin' : ''}`} />\r\n </Button>\r\n )\r\n })\r\n ) : null)}\r\n </div>\r\n </div>\r\n );\r\n\r\n}\r\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../../../../../../../../src/core/components/viewers/map/src/MapFeaturePopoverMenu/src/DatabaseBuildingPopover/src/BuildingsTools.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 React from \"react\";\r\n\r\nimport { Button, Label, Skeleton, Switch, Checkbox } from \"../../../../../../../../components/ui/\";\r\nimport {\r\n DropdownMenu,\r\n DropdownMenuContent,\r\n DropdownMenuItem,\r\n DropdownMenuTrigger,\r\n} from \"../../../../../../../../components/ui/DropdownMenu\";\r\nimport { useFileUploadHandler } from \"../../../../../../../../components/ui/FilesManager/src/useFileUploadHandler\";\r\nimport { useFilesByBuildingId, useUploadFileToBuilding } from \"../../../../../../../../hooks/files/files\";\r\nimport { usePermissions } from '../../../../../../../../store'\r\nimport { BuildingsContext, BimContext, MapContext, MenusContext, useMenusContext } from \"../../../../../../../../store\";\r\nimport { ViewerNames } from \"../../../../../../../../types/\";\r\nimport { toggleBimToMap as dispatchToggleBimToMap } from '../../../../../utils/toggleBimToMap';\r\nimport Compare from '../../../../compare';\r\n\r\nimport type { Building, DbFile } from \"../../../../../../../../types/dbTypes\";\r\nimport type { Tool } from \"../../../../../../../../types/tools\";\r\nimport type { MapGeoJSONFeature } from \"maplibre-gl\";\r\n\r\ninterface BuildingToolsProps {\r\n isLoading: boolean;\r\n isError: boolean;\r\n building: Building | null;\r\n feature: MapGeoJSONFeature;\r\n onCloseAction: () => void;\r\n}\r\n\r\nexport default function BuildingTools({\r\n isLoading,\r\n isError,\r\n building,\r\n feature,\r\n onCloseAction\r\n}: BuildingToolsProps) {\r\n\r\n const t = useTranslations('BuildingTools')\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { dispatch: menusDispatch } = React.useContext(MenusContext);\r\n const { state: mapState, } = React.useContext(MapContext);\r\n const { dispatch: bimDispatch, state: bimState } = React.useContext(BimContext);\r\n const { bimModelsAddedToMap } = bimState.bim;\r\n const { dispatch: buildingsDispatch } = React.useContext(BuildingsContext);\r\n\r\n const fileInputRef = React.useRef<HTMLInputElement>(null);\r\n const { setView } = useMenusContext();\r\n const { setSelectedItem } = useMenusContext();\r\n\r\n const buildingId = React.useMemo(\r\n () => {\r\n\r\n const featureProps = feature?.properties || {};\r\n const id = Number(featureProps.dbId);\r\n return isNaN(id)\r\n ? null\r\n : id;\r\n\r\n },\r\n [feature?.properties],\r\n );\r\n\r\n const { files } = useFilesByBuildingId(buildingId);\r\n\r\n const bimAdded = React.useMemo(\r\n () => (buildingId\r\n ? mapState.map.bimModelsAddedToMap.includes(buildingId)\r\n : false),\r\n [\r\n mapState.map.bimModelsAddedToMap,\r\n buildingId,\r\n ],\r\n );\r\n\r\n const { uploadFile, isMutating: isUploadingFromHook } = useUploadFileToBuilding(building?.id || null);\r\n\r\n // Local state to track the entire upload process from file selection to completion\r\n const [\r\n isLocallyUploading,\r\n setIsLocallyUploading,\r\n ] = React.useState(false);\r\n\r\n // Combined uploading state\r\n const isUploading = isLocallyUploading || isUploadingFromHook;\r\n\r\n // Use the reusable upload handler without passing bimComponents\r\n const { handleFileUpload } = useFileUploadHandler({\r\n buildingId: buildingId || -1,\r\n uploadFile,\r\n onUploadStart: () => setIsLocallyUploading(true),\r\n onUploadEnd: () => setIsLocallyUploading(false),\r\n customMessages: {\r\n success: t('toastFIleUploadSuccess'),\r\n errorPrefix: t('toastFIleUploadError'),\r\n errorSuffix: t('toastUnknownError'),\r\n noIdError: t('buildingIdRequired')\r\n }\r\n });\r\n\r\n const changeViewer = React.useCallback(\r\n (viewer: ViewerNames) => {\r\n\r\n menusDispatch({\r\n type: \"SET_VIEWER\",\r\n payload: { \"currentViewer\": viewer },\r\n });\r\n\r\n buildingsDispatch({\r\n type: \"SET-CURRENT-BUILDING\",\r\n payload: { building },\r\n });\r\n onCloseAction();\r\n\r\n },\r\n [\r\n menusDispatch,\r\n buildingsDispatch,\r\n building,\r\n onCloseAction\r\n ]\r\n );\r\n\r\n const triggerFileInput = React.useCallback(\r\n () => {\r\n\r\n fileInputRef.current?.click();\r\n\r\n },\r\n [],\r\n );\r\n\r\n const toggleBimToMap = React.useCallback(\r\n (bimFile: DbFile) => {\r\n dispatchToggleBimToMap(bimDispatch, bimFile, building);\r\n\r\n },\r\n [\r\n building,\r\n bimDispatch\r\n ]\r\n );\r\n\r\n const onOpenBimViewer = () => {\r\n buildingsDispatch({ type: \"SET-CURRENT-BUILDING\", payload: { building } });\r\n changeViewer(ViewerNames.bim);\r\n setSelectedItem(building);\r\n }\r\n\r\n const buildingFiles = React.useMemo(\r\n () => files || [],\r\n [files],\r\n );\r\n\r\n const bimFiles = React.useMemo(\r\n () => buildingFiles.filter((file: DbFile) => file.extension?.toLowerCase() === \"frag\"),\r\n [buildingFiles],\r\n )\r\n\r\n const pcFiles = React.useMemo(\r\n () => buildingFiles.filter((file: DbFile) => file.type === 'point-cloud-file' || ['laz', 'las'].includes(file.extension || '')),\r\n [buildingFiles],\r\n )\r\n\r\n const buildingButtons: Tool[] = React.useMemo(\r\n () => [\r\n {\r\n id: \"map-compare-buildings\",\r\n icon: LR.Columns3,\r\n title: t('compareTitle'),\r\n component: Compare,\r\n disabled: !ability.can('read', 'Building')\r\n },\r\n {\r\n id: \"open-building-page\",\r\n icon: LR.Building2,\r\n title: t('openBIMTitle'),\r\n onClick: () => {\r\n\r\n changeViewer(ViewerNames.buildings);\r\n setSelectedItem(building);\r\n setView(\"detail\");\r\n\r\n },\r\n disabled: !ability.can('read', 'Building')\r\n },\r\n {\r\n id: \"open-bim-viewer\",\r\n icon: LR.Box,\r\n title: t('OpenBIMViewerTitle'),\r\n onClick: onOpenBimViewer,\r\n disabled: !ability.can('read', 'File')\r\n },\r\n {\r\n id: 'open-pointcloud',\r\n title: t('openPointCloudTitle'),\r\n icon: LR.Grip,\r\n onClick: () => changeViewer(ViewerNames.pointcloud),\r\n disabled: pcFiles.length === 0 || !ability.can('read', 'File')\r\n },\r\n {\r\n id: \"map-add-file\",\r\n icon: isUploading\r\n ? LR.Loader\r\n : LR.FilePlus,\r\n title: t('addFileTitle'),\r\n onClick: triggerFileInput,\r\n disabled: !ability.can('create', 'File')\r\n },\r\n ],\r\n [\r\n changeViewer,\r\n building,\r\n setSelectedItem,\r\n setView,\r\n isUploading,\r\n triggerFileInput\r\n ]\r\n );\r\n\r\n const onLoadBIM = React.useCallback(\r\n (file: DbFile = null) => {\r\n if (bimFiles.length === 0) return;\r\n toggleBimToMap(bimFiles[0]);\r\n },\r\n [\r\n bimFiles,\r\n toggleBimToMap\r\n ]\r\n );\r\n\r\n const handleEditClick = (fileName: string) => {\r\n bimDispatch({\r\n type: \"EDIT_BIM_MODEL_BY_NAME\",\r\n payload: { editingBimModel: fileName }\r\n });\r\n\r\n onCloseAction();\r\n }\r\n\r\n\r\n return (\r\n <div className=\"space-y-4\">\r\n {/* Hidden file input */}\r\n <input\r\n onChange={(e) => handleFileUpload(e.target.files?.[0] || null)}\r\n ref={fileInputRef}\r\n style={{ \"display\": \"none\" }}\r\n type=\"file\"\r\n />\r\n\r\n {/* BIM Controls */}\r\n {!isError &&\r\n <div className=\"flex items-center space-x-2\">\r\n {isLoading\r\n ? <>\r\n <Skeleton className=\"h-4 w-20\" />\r\n\r\n <Skeleton className=\"h-6 w-10\" />\r\n </>\r\n : (\r\n <div className=\"flex items-center justify-between w-full\">\r\n {bimFiles.length > 1 ? (\r\n <div className=\"flex items-center space-x-2\">\r\n <Label className=\"font-semibold\" htmlFor=\"bim-file-dropdown\">\r\n {t('selectBimLabel')}\r\n </Label>\r\n <DropdownMenu>\r\n <DropdownMenuTrigger asChild>\r\n <Button variant=\"outline\" size=\"sm\" id=\"bim-file-dropdown\">\r\n {bimModelsAddedToMap.length > 0\r\n ? `${bimModelsAddedToMap.filter(model => model.bimFile.attachedFilesBuildingId === buildingId).length} ${t('filesSelected')}`\r\n : t('selectFiles')\r\n }\r\n </Button>\r\n </DropdownMenuTrigger>\r\n <DropdownMenuContent>\r\n {bimFiles.map((file: DbFile) => {\r\n const isSelected = bimModelsAddedToMap.some(model => model.bimFile.name === file.name);\r\n return (\r\n <DropdownMenuItem\r\n key={file.name}\r\n onClick={(e) => {\r\n e.preventDefault();\r\n toggleBimToMap(file);\r\n }}\r\n className=\"flex items-center justify-between cursor-pointer\"\r\n >\r\n <div className=\"flex items-center space-x-2\">\r\n <Checkbox\r\n checked={isSelected}\r\n onClick={(e) => e.stopPropagation()}\r\n onCheckedChange={() => {\r\n toggleBimToMap(file);\r\n }}\r\n />\r\n <span>{file.name}</span>\r\n </div>\r\n {isSelected && (\r\n <Button\r\n onClick={(e) => {\r\n e.preventDefault();\r\n e.stopPropagation();\r\n handleEditClick(file.name);\r\n }}\r\n variant=\"ghost\"\r\n size=\"sm\"\r\n className=\"h-6 w-6 p-0\"\r\n title={t('editBimButton')}\r\n >\r\n <LR.LocationEdit className=\"h-3 w-3\" />\r\n </Button>\r\n )}\r\n </DropdownMenuItem>\r\n );\r\n })}\r\n </DropdownMenuContent>\r\n </DropdownMenu>\r\n </div>\r\n ) : (\r\n <div className=\"flex items-center space-x-2\">\r\n <Label\r\n className={`font-semibold ${bimFiles.length === 0 ? 'text-muted-foreground' : ''}`}\r\n htmlFor=\"show-bim\"\r\n >\r\n {bimAdded ? t('hideBIM') : t('showBIM')}\r\n </Label>\r\n <Switch\r\n id=\"show-bim\"\r\n checked={bimFiles.length === 1 && bimModelsAddedToMap.some(model => model.bimFile.name === bimFiles[0]?.name)}\r\n disabled={bimFiles.length === 0 || !ability.can('read', 'File')}\r\n onCheckedChange={() => onLoadBIM()}\r\n />\r\n </div>\r\n )}\r\n {bimFiles.length === 1 && bimModelsAddedToMap.some(model => model.building.id === buildingId) && (() => {\r\n const selectedModel = bimModelsAddedToMap.find(model => model.building.id === buildingId);\r\n return true ? (\r\n <Button\r\n onClick={() => handleEditClick(selectedModel.bimFile.name)}\r\n variant=\"ghost\"\r\n size=\"sm\"\r\n title={t('editBimButton')}\r\n disabled={!ability.can('update', 'File')}\r\n >\r\n <LR.LocationEdit className=\"h-4 w-4\" />\r\n </Button>\r\n ) : null;\r\n })()}\r\n </div>\r\n )\r\n }\r\n </div>}\r\n\r\n {/* Tool Buttons */}\r\n <div className=\"flex items-center gap-3\">\r\n {isLoading\r\n ? Array.from({ \"length\": 4 }).map((_, index) => (<Skeleton\r\n key={index}\r\n className=\"w-8 h-8 rounded-sm\"\r\n />),)\r\n : (!isError && building ? (\r\n buildingButtons.map((button) => {\r\n if (button.component) {\r\n const ButtonComponent = button.component\r\n const shouldAnimateSpin = button.id === 'map-add-file' && isUploading\r\n return (\r\n <div\r\n key={button.id}\r\n className={`w-8 h-8 rounded-sm border-2 border-gray-200 inline-flex justify-center items-center ${shouldAnimateSpin ? 'animate-spin' : ''}`}\r\n >\r\n <ButtonComponent tool={button} building={building} />\r\n </div>\r\n )\r\n }\r\n return (\r\n <Button\r\n key={button.id}\r\n variant=\"ghost\"\r\n className=\"w-8 h-8 rounded-sm border-2 border-gray-200 inline-flex justify-center items-center\"\r\n aria-label={button.title}\r\n onClick={button.onClick}\r\n title={button.title}\r\n disabled={button.disabled}\r\n >\r\n <button.icon className={`w-6 h-6 ${button.id === 'map-add-file' && isUploading ? 'animate-spin' : ''}`} />\r\n </Button>\r\n )\r\n })\r\n ) : null)}\r\n </div>\r\n </div>\r\n );\r\n\r\n}\r\n"],"mappings":"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","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"NonDatabaseBuildingPopover.d.ts","sourceRoot":"","sources":["../../../../../../../../src/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA2B9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAMpD,UAAU,+BAA+B;IACvC,OAAO,EAAE,iBAAiB,CAAA;IAC1B,MAAM,EAAE,OAAO,CAAA;IACf,aAAa,EAAE,MAAM,IAAI,CAAA;CAC1B;AAED,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,EACjD,OAAO,EACP,aAAa,GACd,EAAE,+BAA+B,qBA+ajC"}
1
+ {"version":3,"file":"NonDatabaseBuildingPopover.d.ts","sourceRoot":"","sources":["../../../../../../../../src/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AA2B9B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAMpD,UAAU,+BAA+B;IACvC,OAAO,EAAE,iBAAiB,CAAA;IAC1B,MAAM,EAAE,OAAO,CAAA;IACf,aAAa,EAAE,MAAM,IAAI,CAAA;CAC1B;AAED,MAAM,CAAC,OAAO,UAAU,0BAA0B,CAAC,EACjD,OAAO,EACP,aAAa,GACd,EAAE,+BAA+B,qBA8ajC"}
@@ -227,7 +227,6 @@ function NonDatabaseBuildingPopover({
227
227
  };
228
228
  const handleLocationEdit = () => {
229
229
  if (markerManagerRef.current && coordinates && map) {
230
- console.log("Creating editable marker at coordinates:", coordinates);
231
230
  markerManagerRef.current.create(coordinates, map, {
232
231
  editing: true
233
232
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../src/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.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\n// Dependencies\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 { toast } from 'sonner'\r\n\r\nimport { useCreateBuilding, useBuildings, useBuilding } from '../../../../../../hooks/buildings/buildings'\r\nimport { usePermissions } from '../../../../../../store'\r\n\r\n// Shadcn Components\r\nimport { MapContext } from '../../../../../../store'\r\nimport { useMenusContext } from '../../../../../../store'\r\nimport { Button, Label } from '../../../../../ui/'\r\nimport { LoadingSpinner } from '../../../../../ui/LoadingSpinner'\r\nimport { PopoverContent } from '../../../../../ui/Popover'\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '../../../../../ui/Select'\r\n\r\n// Icons\r\n\r\n// Types / Utilities\r\nimport { getDetailedAddress } from '../../../utils/geocoder'\r\nimport { MarkerManager } from '../../../utils/MarkerManager'\r\n\r\nimport type { Building } from '../../../../../../types/dbTypes'\r\nimport type { MapGeoJSONFeature } from 'maplibre-gl'\r\n\r\n\r\n\r\n// Hooks\r\n\r\ninterface NonDatabaseBuildingPopoverProps {\r\n feature: MapGeoJSONFeature\r\n isOpen: boolean\r\n onCloseAction: () => void\r\n}\r\n\r\nexport default function NonDatabaseBuildingPopover({\r\n feature,\r\n onCloseAction,\r\n}: NonDatabaseBuildingPopoverProps) {\r\n // Translation\r\n const t = useTranslations('NonDatabaseBuildingPopover')\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { data: session } = useSession()\r\n const user = session?.user\r\n\r\n const { state: mapState } = React.useContext(MapContext)\r\n const { currentLocation, map, organizationCountry } = mapState.map\r\n\r\n const { setSelectedItem } = useMenusContext()\r\n\r\n // Use the create building hook\r\n const { createBuilding, isMutating, createError, createdData } = useCreateBuilding()\r\n\r\n // Get all buildings for searching\r\n const { buildings } = useBuildings()\r\n\r\n const featureProps = feature?.properties || {}\r\n const featureid = feature?.id\r\n const [name, setName] = React.useState('')\r\n const [address, setAddress] = React.useState('')\r\n const [matchingBuildings, setMatchingBuildings] = React.useState<Building[]>([])\r\n const [selectedMatchingBuildingId, setSelectedMatchingBuildingId] = React.useState<string>('')\r\n const [showExistingBuildings, setShowExistingBuildings] = React.useState(false)\r\n const [isAttaching, setIsAttaching] = React.useState(false)\r\n\r\n const { updateBuilding, isMutating: isUpdating } = useBuilding(selectedMatchingBuildingId ? Number(selectedMatchingBuildingId) : null)\r\n\r\n const normalizeAddress = React.useCallback((value: string) => {\r\n return value\r\n .toLowerCase()\r\n .replace(/\\./g, '')\r\n .replace(/,/g, ' ')\r\n .replace(/\\bdr\\b/g, 'drive')\r\n .replace(/\\brd\\b/g, 'road')\r\n .replace(/\\bst\\b/g, 'street')\r\n .replace(/\\bave\\b/g, 'avenue')\r\n .replace(/\\s+/g, ' ')\r\n .trim()\r\n }, [])\r\n\r\n const clearExistingBuildingMatches = React.useCallback(() => {\r\n setMatchingBuildings([])\r\n setSelectedMatchingBuildingId('')\r\n setShowExistingBuildings(false)\r\n\r\n if (didSetSelectedItemRef.current && setSelectedItem) {\r\n setSelectedItem(null)\r\n didSetSelectedItemRef.current = false\r\n }\r\n }, [setSelectedItem])\r\n\r\n // Track if we set the selected item in this component\r\n const didSetSelectedItemRef = React.useRef(false)\r\n\r\n // Cleanup on unmount\r\n React.useEffect(() => {\r\n return () => {\r\n // Clear selected item if we set it\r\n if (didSetSelectedItemRef.current && setSelectedItem) {\r\n setSelectedItem(null)\r\n didSetSelectedItemRef.current = false\r\n }\r\n }\r\n }, [setSelectedItem])\r\n\r\n // Get address and search for matching buildings\r\n React.useEffect(() => {\r\n const getAddress = async (longitude: number, latitude: number) => {\r\n try {\r\n // Reverse geocode through the shared provider (Geocode Earth / self-hosted\r\n // Pelias, falling back to public OSM) so this stays in sync with the geocoder.\r\n const result = await getDetailedAddress([longitude, latitude], organizationCountry)\r\n const geocodedAddress = result?.properties?.name as string | undefined\r\n\r\n if (!geocodedAddress) {\r\n clearExistingBuildingMatches()\r\n return\r\n }\r\n\r\n setAddress(geocodedAddress)\r\n\r\n // Search for existing buildings with this address\r\n if (buildings && buildings.length > 0) {\r\n const normalizedGeocodedAddress = normalizeAddress(geocodedAddress)\r\n\r\n const matches = buildings.filter((building) => {\r\n if (!building.buildingAddress) return false\r\n\r\n const normalizedBuildingAddress = normalizeAddress(building.buildingAddress)\r\n return (\r\n normalizedBuildingAddress === normalizedGeocodedAddress ||\r\n normalizedBuildingAddress.includes(normalizedGeocodedAddress) ||\r\n normalizedGeocodedAddress.includes(normalizedBuildingAddress)\r\n )\r\n })\r\n\r\n if (matches.length > 0) {\r\n setMatchingBuildings(matches)\r\n setSelectedMatchingBuildingId(matches[0].id.toString())\r\n setShowExistingBuildings(true)\r\n\r\n // Set the first match as the selected item for the update hook\r\n if (setSelectedItem) {\r\n setSelectedItem(matches[0])\r\n didSetSelectedItemRef.current = true\r\n }\r\n } else {\r\n clearExistingBuildingMatches()\r\n }\r\n } else {\r\n clearExistingBuildingMatches()\r\n }\r\n }\r\n catch (error) {\r\n console.error('Error fetching municipality and countrySubdivision:', error)\r\n }\r\n }\r\n\r\n if (feature.properties?.coordinates) {\r\n const longitude = feature.properties.coordinates[0]\r\n const latitude = feature.properties.coordinates[1]\r\n getAddress(longitude, latitude)\r\n }\r\n }, [feature, buildings, setSelectedItem, normalizeAddress, clearExistingBuildingMatches])\r\n\r\n React.useEffect(() => {\r\n if (!setSelectedItem || !selectedMatchingBuildingId) return\r\n\r\n const selectedBuilding = matchingBuildings.find(\r\n building => building.id.toString() === selectedMatchingBuildingId,\r\n )\r\n\r\n if (!selectedBuilding) return\r\n\r\n setSelectedItem(selectedBuilding)\r\n didSetSelectedItemRef.current = true\r\n }, [matchingBuildings, selectedMatchingBuildingId, setSelectedItem])\r\n\r\n // Fallbacks from feature or map state\r\n const buildingCountrySubdivision = featureProps.de_ao_prov || currentLocation.countrySubdivision || null\r\n const buildingMunicipality = featureProps.de_ao_city || currentLocation.municipality || null\r\n const buildingPostalCode = featureProps.de_ao_post || currentLocation.postalCode || null\r\n const site = currentLocation.site || null\r\n const buildingOsmId = featureid.toString()\r\n\r\n // Create MarkerManager instance\r\n const markerManagerRef = React.useRef<MarkerManager | null>(null)\r\n\r\n // Initialize MarkerManager\r\n React.useEffect(() => {\r\n if (!markerManagerRef.current) {\r\n markerManagerRef.current = new MarkerManager()\r\n }\r\n\r\n // Cleanup on unmount\r\n return () => {\r\n if (markerManagerRef.current) {\r\n markerManagerRef.current.destroy()\r\n markerManagerRef.current = null\r\n }\r\n }\r\n }, [])\r\n\r\n // Get coordinates data from feature\r\n const [coordinates, setCoordinates] = React.useState(featureProps.coordinates || null)\r\n\r\n // Listen for marker coordinate changes\r\n React.useEffect(() => {\r\n if (!markerManagerRef.current) return\r\n\r\n const cleanup = markerManagerRef.current.onMoved((event) => {\r\n const newCoordinates = event.detail.coordinates\r\n if (newCoordinates) {\r\n setCoordinates(newCoordinates)\r\n }\r\n })\r\n\r\n return cleanup\r\n }, [])\r\n\r\n const handleBuildingNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {\r\n setName(event.target.value)\r\n }\r\n\r\n const handleAddressChange = (event: React.ChangeEvent<HTMLInputElement>) => {\r\n setAddress(event.target.value)\r\n }\r\n\r\n const selectedMatchingBuilding = matchingBuildings.find(\r\n building => building.id.toString() === selectedMatchingBuildingId,\r\n )\r\n\r\n const handleAttachExistingBuilding = async (building: Building) => {\r\n setIsAttaching(true)\r\n\r\n try {\r\n // Add OSM ID to existing building\r\n const result = await updateBuilding({\r\n buildingOsmId,\r\n // Set default storey number of 2 if building doesn't have one\r\n ...(building.buildingStoreyNum == null && { buildingStoreyNum: 2 })\r\n })\r\n\r\n toast.success(t('toastAttachSuccess'))\r\n\r\n // Clear the selected item after successful update\r\n if (setSelectedItem) {\r\n setSelectedItem(null)\r\n didSetSelectedItemRef.current = false\r\n }\r\n\r\n // Close the popover\r\n onCloseAction()\r\n\r\n } catch (error) {\r\n console.error('Error attaching building:', error)\r\n\r\n if (error?.status !== 401) {\r\n toast.error(t('toastAttachError'))\r\n }\r\n } finally {\r\n setIsAttaching(false)\r\n }\r\n }\r\n\r\n const handleCreateNewBuilding = async (event: React.FormEvent) => {\r\n event.preventDefault()\r\n\r\n // Get final values (prioritize user input, then existing data)\r\n const buildingName = name.trim() || featureProps._name || ''\r\n const finalAddress = address.trim() || address || ''\r\n\r\n if (!user?.organizationId) {\r\n toast.error(t('userNotFound'))\r\n return\r\n }\r\n\r\n // Use updated coordinates from marker if available\r\n const buildingCoordinates = coordinates || [featureProps.longitude, featureProps.latitude]\r\n\r\n const newBuilding: Partial<Building> = {\r\n buildingName,\r\n buildingAddress: finalAddress,\r\n buildingType: ['Other'],\r\n buildingOsmId,\r\n buildingLatitude: buildingCoordinates ? buildingCoordinates[1] : null,\r\n buildingLongitude: buildingCoordinates ? buildingCoordinates[0] : null,\r\n buildingCountrySubdivision,\r\n buildingMunicipality,\r\n buildingPostalCode,\r\n }\r\n\r\n try {\r\n const result = await createBuilding({\r\n buildingData: newBuilding,\r\n organizationId: user.organizationId.toString(),\r\n })\r\n\r\n // Remove marker after creating building\r\n if (markerManagerRef.current) {\r\n markerManagerRef.current.remove()\r\n }\r\n\r\n toast.success(t('toastSuccess'))\r\n\r\n // Close the popover on success\r\n onCloseAction()\r\n }\r\n catch (error) {\r\n console.error('Error creating building:', error)\r\n toast.error(t('toastError'))\r\n }\r\n }\r\n\r\n const handleLocationEdit = () => {\r\n // Create marker at current coordinates with editing enabled\r\n if (markerManagerRef.current && coordinates && map) {\r\n console.log('Creating editable marker at coordinates:', coordinates)\r\n markerManagerRef.current.create(coordinates as [number, number], map, {\r\n editing: true\r\n })\r\n }\r\n else {\r\n console.warn('Cannot create marker: missing coordinates or map instance')\r\n }\r\n }\r\n\r\n return (\r\n <PopoverContent className=\"w-64 -m-1\" side=\"top\">\r\n <form onSubmit={handleCreateNewBuilding}>\r\n {/* Header row: close button, no overlap with content below */}\r\n <div className=\"flex items-center justify-end mb-1\">\r\n <Button\r\n type=\"button\"\r\n onClick={onCloseAction}\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className=\"shrink-0 p-0 m-0 text-muted-foreground hover:text-foreground\"\r\n aria-label={t('closeAriaLabel')}\r\n >\r\n <LR.X className=\"w-4 h-4\" />\r\n </Button>\r\n </div>\r\n\r\n <div className=\"grid gap-4\">\r\n {/* Show existing building if match found */}\r\n {showExistingBuildings && matchingBuildings.length > 0 && (\r\n <div className=\"space-y-1\">\r\n <div className=\"text-xs text-muted-foreground\">\r\n {t('existingBuildingFound')}\r\n </div>\r\n <div className=\"flex flex-col justify-between gap-2 p-2 bg-muted/50 border rounded-lg\">\r\n <div className=\"grid gap-1\">\r\n <Label htmlFor=\"existing-building-select\" className=\"text-xs text-muted-foreground\">\r\n {t('selectExistingBuildingLabel')}\r\n </Label>\r\n <Select\r\n value={selectedMatchingBuildingId}\r\n onValueChange={setSelectedMatchingBuildingId}\r\n >\r\n <SelectTrigger id=\"existing-building-select\" className=\"h-9 w-full\">\r\n <SelectValue placeholder={t('selectExistingBuildingPlaceholder')} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {matchingBuildings.map((building) => (\r\n <SelectItem key={building.id} value={building.id.toString()}>\r\n {building.buildingName || building.buildingAddress || `${t('na')} (${building.id})`}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n className='w-full'\r\n onClick={() => selectedMatchingBuilding && handleAttachExistingBuilding(selectedMatchingBuilding)}\r\n disabled={isAttaching || isUpdating || !selectedMatchingBuilding}\r\n >\r\n {(isAttaching || isUpdating) ? (\r\n <>\r\n <LoadingSpinner />\r\n <span>{t('attaching')}</span>\r\n </>\r\n ) : (\r\n <>\r\n <LR.Link />\r\n {t('attachBuilding')}\r\n </>\r\n )}\r\n </Button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n <div className=\"space-y-4\">\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"building-name\" className=\"text-xs text-muted-foreground\">\r\n {t('nameLabel')}\r\n </Label>\r\n <input\r\n id=\"building-name\"\r\n value={name}\r\n onChange={handleBuildingNameChange}\r\n placeholder={name || t('namePlaceholder')}\r\n className={`font-medium leading-none bg-transparent border-b border-gray-300 focus:outline-none focus:border-primary px-1 py-1 ${name ? 'placeholder:text-foreground' : 'placeholder:text-muted-foreground'}`}\r\n aria-label={t('nameInputAriaLabel')}\r\n required\r\n disabled={!ability.can('create', 'Building')}\r\n />\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"building-address\" className=\"text-xs text-muted-foreground\">\r\n {t('addressLabel')}\r\n </Label>\r\n <input\r\n id=\"building-address\"\r\n value={address}\r\n onChange={handleAddressChange}\r\n placeholder={address || t('addressPlaceholder')}\r\n className={`font-medium leading-none bg-transparent focus:outline-none focus:border-primary px-1 py-1 ${address ? 'placeholder:text-foreground' : 'placeholder:text-muted-foreground'}`}\r\n aria-label={t('addressAriaLabel')}\r\n required\r\n disabled={!ability.can('create', 'Building')}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-center gap-3 justify-center mt-4\">\r\n <Button\r\n type=\"button\"\r\n variant=\"outline\"\r\n aria-label=\"Edit Location\"\r\n title=\"Edit Location\"\r\n onClick={handleLocationEdit}\r\n disabled={!ability.can('update', 'Building')}\r\n >\r\n <LR.LocationEdit className=\"w-4 h-4\" />\r\n </Button>\r\n\r\n <Button\r\n type=\"submit\"\r\n variant=\"outline\"\r\n aria-label={t('addBuilding')}\r\n title={t('addBuilding')}\r\n disabled={isMutating || !ability.can('create', 'Building')}\r\n >\r\n {isMutating\r\n ? (\r\n <>``\r\n <LoadingSpinner />\r\n <span className=\"text-sm\">{t('creating')}</span>\r\n </>\r\n )\r\n : (\r\n <>\r\n <LR.Building2 className=\"w-4 h-4\" />\r\n <span className=\"text-sm\">{t('addBuilding')}</span>\r\n </>\r\n )}\r\n </Button>\r\n </div>\r\n </form>\r\n </PopoverContent>\r\n )\r\n}"],"mappings":"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","names":["_a"]}
1
+ {"version":3,"sources":["../../../../../../../../src/core/components/viewers/map/src/MapFeaturePopoverMenu/src/NonDatabaseBuildingPopover.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\n// Dependencies\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 { toast } from 'sonner'\r\n\r\nimport { useCreateBuilding, useBuildings, useBuilding } from '../../../../../../hooks/buildings/buildings'\r\nimport { usePermissions } from '../../../../../../store'\r\n\r\n// Shadcn Components\r\nimport { MapContext } from '../../../../../../store'\r\nimport { useMenusContext } from '../../../../../../store'\r\nimport { Button, Label } from '../../../../../ui/'\r\nimport { LoadingSpinner } from '../../../../../ui/LoadingSpinner'\r\nimport { PopoverContent } from '../../../../../ui/Popover'\r\nimport {\r\n Select,\r\n SelectContent,\r\n SelectItem,\r\n SelectTrigger,\r\n SelectValue,\r\n} from '../../../../../ui/Select'\r\n\r\n// Icons\r\n\r\n// Types / Utilities\r\nimport { getDetailedAddress } from '../../../utils/geocoder'\r\nimport { MarkerManager } from '../../../utils/MarkerManager'\r\n\r\nimport type { Building } from '../../../../../../types/dbTypes'\r\nimport type { MapGeoJSONFeature } from 'maplibre-gl'\r\n\r\n\r\n\r\n// Hooks\r\n\r\ninterface NonDatabaseBuildingPopoverProps {\r\n feature: MapGeoJSONFeature\r\n isOpen: boolean\r\n onCloseAction: () => void\r\n}\r\n\r\nexport default function NonDatabaseBuildingPopover({\r\n feature,\r\n onCloseAction,\r\n}: NonDatabaseBuildingPopoverProps) {\r\n // Translation\r\n const t = useTranslations('NonDatabaseBuildingPopover')\r\n // Permissions\r\n const { ability } = usePermissions()\r\n\r\n const { data: session } = useSession()\r\n const user = session?.user\r\n\r\n const { state: mapState } = React.useContext(MapContext)\r\n const { currentLocation, map, organizationCountry } = mapState.map\r\n\r\n const { setSelectedItem } = useMenusContext()\r\n\r\n // Use the create building hook\r\n const { createBuilding, isMutating, createError, createdData } = useCreateBuilding()\r\n\r\n // Get all buildings for searching\r\n const { buildings } = useBuildings()\r\n\r\n const featureProps = feature?.properties || {}\r\n const featureid = feature?.id\r\n const [name, setName] = React.useState('')\r\n const [address, setAddress] = React.useState('')\r\n const [matchingBuildings, setMatchingBuildings] = React.useState<Building[]>([])\r\n const [selectedMatchingBuildingId, setSelectedMatchingBuildingId] = React.useState<string>('')\r\n const [showExistingBuildings, setShowExistingBuildings] = React.useState(false)\r\n const [isAttaching, setIsAttaching] = React.useState(false)\r\n\r\n const { updateBuilding, isMutating: isUpdating } = useBuilding(selectedMatchingBuildingId ? Number(selectedMatchingBuildingId) : null)\r\n\r\n const normalizeAddress = React.useCallback((value: string) => {\r\n return value\r\n .toLowerCase()\r\n .replace(/\\./g, '')\r\n .replace(/,/g, ' ')\r\n .replace(/\\bdr\\b/g, 'drive')\r\n .replace(/\\brd\\b/g, 'road')\r\n .replace(/\\bst\\b/g, 'street')\r\n .replace(/\\bave\\b/g, 'avenue')\r\n .replace(/\\s+/g, ' ')\r\n .trim()\r\n }, [])\r\n\r\n const clearExistingBuildingMatches = React.useCallback(() => {\r\n setMatchingBuildings([])\r\n setSelectedMatchingBuildingId('')\r\n setShowExistingBuildings(false)\r\n\r\n if (didSetSelectedItemRef.current && setSelectedItem) {\r\n setSelectedItem(null)\r\n didSetSelectedItemRef.current = false\r\n }\r\n }, [setSelectedItem])\r\n\r\n // Track if we set the selected item in this component\r\n const didSetSelectedItemRef = React.useRef(false)\r\n\r\n // Cleanup on unmount\r\n React.useEffect(() => {\r\n return () => {\r\n // Clear selected item if we set it\r\n if (didSetSelectedItemRef.current && setSelectedItem) {\r\n setSelectedItem(null)\r\n didSetSelectedItemRef.current = false\r\n }\r\n }\r\n }, [setSelectedItem])\r\n\r\n // Get address and search for matching buildings\r\n React.useEffect(() => {\r\n const getAddress = async (longitude: number, latitude: number) => {\r\n try {\r\n // Reverse geocode through the shared provider (Geocode Earth / self-hosted\r\n // Pelias, falling back to public OSM) so this stays in sync with the geocoder.\r\n const result = await getDetailedAddress([longitude, latitude], organizationCountry)\r\n const geocodedAddress = result?.properties?.name as string | undefined\r\n\r\n if (!geocodedAddress) {\r\n clearExistingBuildingMatches()\r\n return\r\n }\r\n\r\n setAddress(geocodedAddress)\r\n\r\n // Search for existing buildings with this address\r\n if (buildings && buildings.length > 0) {\r\n const normalizedGeocodedAddress = normalizeAddress(geocodedAddress)\r\n\r\n const matches = buildings.filter((building) => {\r\n if (!building.buildingAddress) return false\r\n\r\n const normalizedBuildingAddress = normalizeAddress(building.buildingAddress)\r\n return (\r\n normalizedBuildingAddress === normalizedGeocodedAddress ||\r\n normalizedBuildingAddress.includes(normalizedGeocodedAddress) ||\r\n normalizedGeocodedAddress.includes(normalizedBuildingAddress)\r\n )\r\n })\r\n\r\n if (matches.length > 0) {\r\n setMatchingBuildings(matches)\r\n setSelectedMatchingBuildingId(matches[0].id.toString())\r\n setShowExistingBuildings(true)\r\n\r\n // Set the first match as the selected item for the update hook\r\n if (setSelectedItem) {\r\n setSelectedItem(matches[0])\r\n didSetSelectedItemRef.current = true\r\n }\r\n } else {\r\n clearExistingBuildingMatches()\r\n }\r\n } else {\r\n clearExistingBuildingMatches()\r\n }\r\n }\r\n catch (error) {\r\n console.error('Error fetching municipality and countrySubdivision:', error)\r\n }\r\n }\r\n\r\n if (feature.properties?.coordinates) {\r\n const longitude = feature.properties.coordinates[0]\r\n const latitude = feature.properties.coordinates[1]\r\n getAddress(longitude, latitude)\r\n }\r\n }, [feature, buildings, setSelectedItem, normalizeAddress, clearExistingBuildingMatches])\r\n\r\n React.useEffect(() => {\r\n if (!setSelectedItem || !selectedMatchingBuildingId) return\r\n\r\n const selectedBuilding = matchingBuildings.find(\r\n building => building.id.toString() === selectedMatchingBuildingId,\r\n )\r\n\r\n if (!selectedBuilding) return\r\n\r\n setSelectedItem(selectedBuilding)\r\n didSetSelectedItemRef.current = true\r\n }, [matchingBuildings, selectedMatchingBuildingId, setSelectedItem])\r\n\r\n // Fallbacks from feature or map state\r\n const buildingCountrySubdivision = featureProps.de_ao_prov || currentLocation.countrySubdivision || null\r\n const buildingMunicipality = featureProps.de_ao_city || currentLocation.municipality || null\r\n const buildingPostalCode = featureProps.de_ao_post || currentLocation.postalCode || null\r\n const site = currentLocation.site || null\r\n const buildingOsmId = featureid.toString()\r\n\r\n // Create MarkerManager instance\r\n const markerManagerRef = React.useRef<MarkerManager | null>(null)\r\n\r\n // Initialize MarkerManager\r\n React.useEffect(() => {\r\n if (!markerManagerRef.current) {\r\n markerManagerRef.current = new MarkerManager()\r\n }\r\n\r\n // Cleanup on unmount\r\n return () => {\r\n if (markerManagerRef.current) {\r\n markerManagerRef.current.destroy()\r\n markerManagerRef.current = null\r\n }\r\n }\r\n }, [])\r\n\r\n // Get coordinates data from feature\r\n const [coordinates, setCoordinates] = React.useState(featureProps.coordinates || null)\r\n\r\n // Listen for marker coordinate changes\r\n React.useEffect(() => {\r\n if (!markerManagerRef.current) return\r\n\r\n const cleanup = markerManagerRef.current.onMoved((event) => {\r\n const newCoordinates = event.detail.coordinates\r\n if (newCoordinates) {\r\n setCoordinates(newCoordinates)\r\n }\r\n })\r\n\r\n return cleanup\r\n }, [])\r\n\r\n const handleBuildingNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {\r\n setName(event.target.value)\r\n }\r\n\r\n const handleAddressChange = (event: React.ChangeEvent<HTMLInputElement>) => {\r\n setAddress(event.target.value)\r\n }\r\n\r\n const selectedMatchingBuilding = matchingBuildings.find(\r\n building => building.id.toString() === selectedMatchingBuildingId,\r\n )\r\n\r\n const handleAttachExistingBuilding = async (building: Building) => {\r\n setIsAttaching(true)\r\n\r\n try {\r\n // Add OSM ID to existing building\r\n const result = await updateBuilding({\r\n buildingOsmId,\r\n // Set default storey number of 2 if building doesn't have one\r\n ...(building.buildingStoreyNum == null && { buildingStoreyNum: 2 })\r\n })\r\n\r\n toast.success(t('toastAttachSuccess'))\r\n\r\n // Clear the selected item after successful update\r\n if (setSelectedItem) {\r\n setSelectedItem(null)\r\n didSetSelectedItemRef.current = false\r\n }\r\n\r\n // Close the popover\r\n onCloseAction()\r\n\r\n } catch (error) {\r\n console.error('Error attaching building:', error)\r\n\r\n if (error?.status !== 401) {\r\n toast.error(t('toastAttachError'))\r\n }\r\n } finally {\r\n setIsAttaching(false)\r\n }\r\n }\r\n\r\n const handleCreateNewBuilding = async (event: React.FormEvent) => {\r\n event.preventDefault()\r\n\r\n // Get final values (prioritize user input, then existing data)\r\n const buildingName = name.trim() || featureProps._name || ''\r\n const finalAddress = address.trim() || address || ''\r\n\r\n if (!user?.organizationId) {\r\n toast.error(t('userNotFound'))\r\n return\r\n }\r\n\r\n // Use updated coordinates from marker if available\r\n const buildingCoordinates = coordinates || [featureProps.longitude, featureProps.latitude]\r\n\r\n const newBuilding: Partial<Building> = {\r\n buildingName,\r\n buildingAddress: finalAddress,\r\n buildingType: ['Other'],\r\n buildingOsmId,\r\n buildingLatitude: buildingCoordinates ? buildingCoordinates[1] : null,\r\n buildingLongitude: buildingCoordinates ? buildingCoordinates[0] : null,\r\n buildingCountrySubdivision,\r\n buildingMunicipality,\r\n buildingPostalCode,\r\n }\r\n\r\n try {\r\n const result = await createBuilding({\r\n buildingData: newBuilding,\r\n organizationId: user.organizationId.toString(),\r\n })\r\n\r\n // Remove marker after creating building\r\n if (markerManagerRef.current) {\r\n markerManagerRef.current.remove()\r\n }\r\n\r\n toast.success(t('toastSuccess'))\r\n\r\n // Close the popover on success\r\n onCloseAction()\r\n }\r\n catch (error) {\r\n console.error('Error creating building:', error)\r\n toast.error(t('toastError'))\r\n }\r\n }\r\n\r\n const handleLocationEdit = () => {\r\n // Create marker at current coordinates with editing enabled\r\n if (markerManagerRef.current && coordinates && map) {\r\n markerManagerRef.current.create(coordinates as [number, number], map, {\r\n editing: true\r\n })\r\n }\r\n else {\r\n console.warn('Cannot create marker: missing coordinates or map instance')\r\n }\r\n }\r\n\r\n return (\r\n <PopoverContent className=\"w-64 -m-1\" side=\"top\">\r\n <form onSubmit={handleCreateNewBuilding}>\r\n {/* Header row: close button, no overlap with content below */}\r\n <div className=\"flex items-center justify-end mb-1\">\r\n <Button\r\n type=\"button\"\r\n onClick={onCloseAction}\r\n variant=\"ghost\"\r\n size=\"icon\"\r\n className=\"shrink-0 p-0 m-0 text-muted-foreground hover:text-foreground\"\r\n aria-label={t('closeAriaLabel')}\r\n >\r\n <LR.X className=\"w-4 h-4\" />\r\n </Button>\r\n </div>\r\n\r\n <div className=\"grid gap-4\">\r\n {/* Show existing building if match found */}\r\n {showExistingBuildings && matchingBuildings.length > 0 && (\r\n <div className=\"space-y-1\">\r\n <div className=\"text-xs text-muted-foreground\">\r\n {t('existingBuildingFound')}\r\n </div>\r\n <div className=\"flex flex-col justify-between gap-2 p-2 bg-muted/50 border rounded-lg\">\r\n <div className=\"grid gap-1\">\r\n <Label htmlFor=\"existing-building-select\" className=\"text-xs text-muted-foreground\">\r\n {t('selectExistingBuildingLabel')}\r\n </Label>\r\n <Select\r\n value={selectedMatchingBuildingId}\r\n onValueChange={setSelectedMatchingBuildingId}\r\n >\r\n <SelectTrigger id=\"existing-building-select\" className=\"h-9 w-full\">\r\n <SelectValue placeholder={t('selectExistingBuildingPlaceholder')} />\r\n </SelectTrigger>\r\n <SelectContent>\r\n {matchingBuildings.map((building) => (\r\n <SelectItem key={building.id} value={building.id.toString()}>\r\n {building.buildingName || building.buildingAddress || `${t('na')} (${building.id})`}\r\n </SelectItem>\r\n ))}\r\n </SelectContent>\r\n </Select>\r\n </div>\r\n <Button\r\n variant=\"outline\"\r\n size=\"sm\"\r\n className='w-full'\r\n onClick={() => selectedMatchingBuilding && handleAttachExistingBuilding(selectedMatchingBuilding)}\r\n disabled={isAttaching || isUpdating || !selectedMatchingBuilding}\r\n >\r\n {(isAttaching || isUpdating) ? (\r\n <>\r\n <LoadingSpinner />\r\n <span>{t('attaching')}</span>\r\n </>\r\n ) : (\r\n <>\r\n <LR.Link />\r\n {t('attachBuilding')}\r\n </>\r\n )}\r\n </Button>\r\n </div>\r\n </div>\r\n )}\r\n\r\n <div className=\"space-y-4\">\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"building-name\" className=\"text-xs text-muted-foreground\">\r\n {t('nameLabel')}\r\n </Label>\r\n <input\r\n id=\"building-name\"\r\n value={name}\r\n onChange={handleBuildingNameChange}\r\n placeholder={name || t('namePlaceholder')}\r\n className={`font-medium leading-none bg-transparent border-b border-gray-300 focus:outline-none focus:border-primary px-1 py-1 ${name ? 'placeholder:text-foreground' : 'placeholder:text-muted-foreground'}`}\r\n aria-label={t('nameInputAriaLabel')}\r\n required\r\n disabled={!ability.can('create', 'Building')}\r\n />\r\n </div>\r\n\r\n <div className=\"grid gap-2\">\r\n <Label htmlFor=\"building-address\" className=\"text-xs text-muted-foreground\">\r\n {t('addressLabel')}\r\n </Label>\r\n <input\r\n id=\"building-address\"\r\n value={address}\r\n onChange={handleAddressChange}\r\n placeholder={address || t('addressPlaceholder')}\r\n className={`font-medium leading-none bg-transparent focus:outline-none focus:border-primary px-1 py-1 ${address ? 'placeholder:text-foreground' : 'placeholder:text-muted-foreground'}`}\r\n aria-label={t('addressAriaLabel')}\r\n required\r\n disabled={!ability.can('create', 'Building')}\r\n />\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <div className=\"flex items-center gap-3 justify-center mt-4\">\r\n <Button\r\n type=\"button\"\r\n variant=\"outline\"\r\n aria-label=\"Edit Location\"\r\n title=\"Edit Location\"\r\n onClick={handleLocationEdit}\r\n disabled={!ability.can('update', 'Building')}\r\n >\r\n <LR.LocationEdit className=\"w-4 h-4\" />\r\n </Button>\r\n\r\n <Button\r\n type=\"submit\"\r\n variant=\"outline\"\r\n aria-label={t('addBuilding')}\r\n title={t('addBuilding')}\r\n disabled={isMutating || !ability.can('create', 'Building')}\r\n >\r\n {isMutating\r\n ? (\r\n <>``\r\n <LoadingSpinner />\r\n <span className=\"text-sm\">{t('creating')}</span>\r\n </>\r\n )\r\n : (\r\n <>\r\n <LR.Building2 className=\"w-4 h-4\" />\r\n <span className=\"text-sm\">{t('addBuilding')}</span>\r\n </>\r\n )}\r\n </Button>\r\n </div>\r\n </form>\r\n </PopoverContent>\r\n )\r\n}"],"mappings":"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","names":["_a"]}