@jupytergis/base 0.15.0 → 0.16.0-alpha.1

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 (414) hide show
  1. package/lib/commands/BaseCommandIDs.d.ts +5 -0
  2. package/lib/commands/BaseCommandIDs.js +5 -0
  3. package/lib/commands/index.js +218 -48
  4. package/lib/commands/operationCommands.js +2 -2
  5. package/lib/constants.js +9 -1
  6. package/lib/{panelview/annotationPanel.js → features/annotations/AnnotationsPanel.js} +1 -1
  7. package/lib/{annotations → features/annotations}/index.d.ts +1 -0
  8. package/lib/{annotations → features/annotations}/index.js +1 -0
  9. package/lib/{console → features/console}/consoleview.js +1 -1
  10. package/lib/{panelview/filter-panel → features/filter}/Filter.js +4 -4
  11. package/lib/{panelview/filter-panel → features/filter}/FilterRow.d.ts +3 -2
  12. package/lib/{panelview/filter-panel → features/filter}/FilterRow.js +2 -1
  13. package/lib/{panelview/identify-panel → features/identify}/IdentifyPanel.d.ts +2 -0
  14. package/lib/features/identify/IdentifyPanel.js +102 -0
  15. package/lib/features/identify/components/FeatureCard.d.ts +17 -0
  16. package/lib/features/identify/components/FeatureCard.js +26 -0
  17. package/lib/features/identify/components/FeatureCardHeader.d.ts +11 -0
  18. package/lib/features/identify/components/FeatureCardHeader.js +30 -0
  19. package/lib/features/identify/components/FeatureFloater.d.ts +7 -0
  20. package/lib/features/identify/components/FeatureFloater.js +19 -0
  21. package/lib/features/identify/components/FeaturePropertyList.d.ts +11 -0
  22. package/lib/features/identify/components/FeaturePropertyList.js +18 -0
  23. package/lib/features/identify/components/FeatureRow.d.ts +13 -0
  24. package/lib/features/identify/components/FeatureRow.js +25 -0
  25. package/lib/features/identify/components/PropertyEditors.d.ts +44 -0
  26. package/lib/features/identify/components/PropertyEditors.js +56 -0
  27. package/lib/features/identify/hooks/useIdentifyPropertyEditor.d.ts +11 -0
  28. package/lib/features/identify/hooks/useIdentifyPropertyEditor.js +132 -0
  29. package/lib/features/identify/types/editorTypes.d.ts +21 -0
  30. package/lib/features/identify/utils/getFeatureIdentifier.d.ts +5 -0
  31. package/lib/features/identify/utils/getFeatureIdentifier.js +14 -0
  32. package/lib/features/identify/utils/highlightLayer.d.ts +11 -0
  33. package/lib/features/identify/utils/highlightLayer.js +57 -0
  34. package/lib/features/identify/utils/highlightStyle.d.ts +7 -0
  35. package/lib/features/identify/utils/highlightStyle.js +40 -0
  36. package/lib/{dialogs/layerBrowserDialog.js → features/layer-browser/index.js} +2 -2
  37. package/lib/{formbuilder/objectform/layer/heatmapLayerForm.d.ts → features/layers/forms/layer/geoTiffLayerForm.d.ts} +1 -1
  38. package/lib/{formbuilder/objectform/layer/webGlLayerForm.js → features/layers/forms/layer/geoTiffLayerForm.js} +5 -5
  39. package/lib/{formbuilder/objectform → features/layers/forms}/layer/hillshadeLayerForm.js +4 -4
  40. package/lib/{formbuilder/objectform → features/layers/forms}/layer/index.d.ts +1 -2
  41. package/lib/{formbuilder/objectform → features/layers/forms}/layer/index.js +1 -2
  42. package/lib/{formbuilder/objectform → features/layers/forms}/layer/layerform.d.ts +1 -1
  43. package/lib/{formbuilder/objectform → features/layers/forms}/layer/layerform.js +4 -4
  44. package/lib/features/layers/forms/layer/storySegmentLayerForm.js +150 -0
  45. package/lib/{formbuilder/objectform → features/layers/forms}/layer/vectorlayerform.js +4 -4
  46. package/lib/{formbuilder/objectform → features/layers/forms}/source/geojsonsource.js +5 -5
  47. package/lib/features/layers/forms/source/geopackagesource.d.ts +3 -0
  48. package/lib/features/layers/forms/source/geopackagesource.js +93 -0
  49. package/lib/{formbuilder/objectform → features/layers/forms}/source/geotiffsource.js +5 -5
  50. package/lib/{formbuilder/objectform → features/layers/forms}/source/index.d.ts +1 -0
  51. package/lib/{formbuilder/objectform → features/layers/forms}/source/index.js +1 -0
  52. package/lib/{formbuilder/objectform → features/layers/forms}/source/pathbasedsource.js +5 -5
  53. package/lib/{formbuilder/objectform → features/layers/forms}/source/sourceform.d.ts +1 -1
  54. package/lib/{formbuilder/objectform → features/layers/forms}/source/sourceform.js +4 -4
  55. package/lib/{formbuilder/objectform → features/layers/forms}/source/tilesourceform.js +4 -4
  56. package/lib/{formbuilder/objectform → features/layers/forms}/source/wmsTileSource.d.ts +1 -1
  57. package/lib/{formbuilder/objectform → features/layers/forms}/source/wmsTileSource.js +6 -6
  58. package/lib/{dialogs → features/layers}/layerCreationFormDialog.d.ts +1 -1
  59. package/lib/{dialogs → features/layers}/layerCreationFormDialog.js +1 -1
  60. package/lib/features/layers/symbology/Grammar.d.ts +11 -0
  61. package/lib/features/layers/symbology/Grammar.js +316 -0
  62. package/lib/{dialogs → features/layers}/symbology/classificationModes.d.ts +6 -6
  63. package/lib/{dialogs → features/layers}/symbology/classificationModes.js +48 -44
  64. package/lib/{dialogs → features/layers}/symbology/colorRampUtils.d.ts +1 -0
  65. package/lib/{dialogs → features/layers}/symbology/colorRampUtils.js +12 -1
  66. package/lib/features/layers/symbology/components/MappingRow.d.ts +40 -0
  67. package/lib/features/layers/symbology/components/MappingRow.js +520 -0
  68. package/lib/features/layers/symbology/components/NumericInput.d.ts +20 -0
  69. package/lib/features/layers/symbology/components/NumericInput.js +44 -0
  70. package/lib/features/layers/symbology/components/ScaleEditor.d.ts +33 -0
  71. package/lib/features/layers/symbology/components/ScaleEditor.js +221 -0
  72. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampControls.d.ts +1 -1
  73. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampControls.js +4 -3
  74. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelector.d.ts +2 -1
  75. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelector.js +6 -1
  76. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ModeSelectRow.d.ts +1 -1
  77. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/RgbaColorPicker.js +39 -9
  78. package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopContainer.js +1 -1
  79. package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopRow.d.ts +1 -1
  80. package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopRow.js +14 -2
  81. package/lib/features/layers/symbology/grammarToOLLayer.d.ts +27 -0
  82. package/lib/features/layers/symbology/grammarToOLLayer.js +145 -0
  83. package/lib/features/layers/symbology/grammarToOLStyle.d.ts +32 -0
  84. package/lib/features/layers/symbology/grammarToOLStyle.js +487 -0
  85. package/lib/{dialogs → features/layers}/symbology/hooks/useGetBandInfo.d.ts +1 -1
  86. package/lib/{dialogs → features/layers}/symbology/hooks/useGetBandInfo.js +1 -1
  87. package/lib/{dialogs → features/layers}/symbology/hooks/useGetProperties.js +1 -1
  88. package/lib/{dialogs → features/layers}/symbology/hooks/useGetSymbology.js +4 -2
  89. package/lib/features/layers/symbology/styleBuilder.d.ts +24 -0
  90. package/lib/features/layers/symbology/styleBuilder.js +158 -0
  91. package/lib/{dialogs → features/layers}/symbology/symbologyDialog.d.ts +1 -1
  92. package/lib/{dialogs → features/layers}/symbology/symbologyDialog.js +11 -13
  93. package/lib/{dialogs → features/layers}/symbology/symbologyUtils.d.ts +18 -10
  94. package/lib/{dialogs → features/layers}/symbology/symbologyUtils.js +0 -84
  95. package/lib/{panelview/objectproperties.d.ts → features/objectproperties/index.d.ts} +1 -1
  96. package/lib/{panelview/objectproperties.js → features/objectproperties/index.js} +5 -10
  97. package/lib/{dialogs → features/processing}/ProcessingFormDialog.d.ts +1 -1
  98. package/lib/{dialogs → features/processing}/ProcessingFormDialog.js +28 -14
  99. package/lib/features/processing/forms/MapExtentToggle.d.ts +13 -0
  100. package/lib/features/processing/forms/MapExtentToggle.js +20 -0
  101. package/lib/features/processing/forms/clipRasterByExtentForm.d.ts +10 -0
  102. package/lib/features/processing/forms/clipRasterByExtentForm.js +99 -0
  103. package/lib/{formbuilder/objectform/process → features/processing/forms}/dissolveProcessForm.js +5 -5
  104. package/lib/{formbuilder/objectform → features/processing/forms}/processingForm.d.ts +1 -1
  105. package/lib/{formbuilder/objectform → features/processing/forms}/processingForm.js +6 -6
  106. package/lib/features/processing/forms/rasterizeForm.d.ts +10 -0
  107. package/lib/features/processing/forms/rasterizeForm.js +75 -0
  108. package/lib/features/processing/forms/useMapExtent.d.ts +22 -0
  109. package/lib/features/processing/forms/useMapExtent.js +57 -0
  110. package/lib/{processing → features/processing}/index.d.ts +19 -2
  111. package/lib/features/processing/index.js +1246 -0
  112. package/lib/{processing → features/processing}/processingCommands.d.ts +1 -1
  113. package/lib/features/processing/processingCommands.js +168 -0
  114. package/lib/features/processing/serverProcessing.d.ts +51 -0
  115. package/lib/features/processing/serverProcessing.js +99 -0
  116. package/lib/{stacBrowser → features/stac-browser}/components/StacPanel.js +2 -2
  117. package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableComboBox.js +5 -5
  118. package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableRow.js +1 -1
  119. package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacFilterExtensionPanel.js +2 -2
  120. package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacQueryableFilters.js +1 -1
  121. package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacFilterSection.js +3 -3
  122. package/lib/{stacBrowser → features/stac-browser}/components/shared/StacPanelResults.js +3 -3
  123. package/lib/{stacBrowser → features/stac-browser}/components/shared/StacSpatialExtent.js +1 -1
  124. package/lib/{stacBrowser → features/stac-browser}/components/shared/StacTemporalExtent.js +1 -1
  125. package/lib/{stacBrowser → features/stac-browser}/context/StacResultsContext.js +2 -2
  126. package/lib/{stacBrowser → features/stac-browser}/hooks/useGeodesSearch.js +2 -2
  127. package/lib/{stacBrowser → features/stac-browser}/hooks/useStacFilterExtension.js +3 -3
  128. package/lib/{stacBrowser → features/stac-browser}/hooks/useStacSearch.js +1 -1
  129. package/lib/features/stac-browser/types/types.js +1 -0
  130. package/lib/{panelview/story-maps → features/story}/SpectaPanel.d.ts +4 -1
  131. package/lib/{panelview/story-maps → features/story}/SpectaPanel.js +3 -4
  132. package/lib/{panelview/story-maps → features/story}/StoryEditorPanel.js +1 -1
  133. package/lib/{panelview/story-maps → features/story}/StoryViewerPanel.d.ts +12 -11
  134. package/lib/features/story/StoryViewerPanel.js +64 -0
  135. package/lib/features/story/__tests__/fixtures/listStoryTestItems.d.ts +9 -0
  136. package/lib/features/story/__tests__/fixtures/listStoryTestItems.js +21 -0
  137. package/lib/features/story/components/ListStoryMapOverlayPanel.d.ts +10 -0
  138. package/lib/features/story/components/ListStoryMapOverlayPanel.js +11 -0
  139. package/lib/features/story/components/ListStoryMarkdownMeasurePane.d.ts +11 -0
  140. package/lib/features/story/components/ListStoryMarkdownMeasurePane.js +55 -0
  141. package/lib/features/story/components/ListStoryOverlayMarkdown.d.ts +15 -0
  142. package/lib/features/story/components/ListStoryOverlayMarkdown.js +93 -0
  143. package/lib/features/story/components/ListStoryStageOverlay.d.ts +11 -0
  144. package/lib/features/story/components/ListStoryStageOverlay.js +150 -0
  145. package/lib/features/story/components/ListStoryTitleBar.d.ts +7 -0
  146. package/lib/features/story/components/ListStoryTitleBar.js +20 -0
  147. package/lib/features/story/components/ListStoryTitleBarDesktop.d.ts +2 -0
  148. package/lib/features/story/components/ListStoryTitleBarDesktop.js +55 -0
  149. package/lib/features/story/components/ListStoryTitleBarMobile.d.ts +2 -0
  150. package/lib/features/story/components/ListStoryTitleBarMobile.js +41 -0
  151. package/lib/features/story/components/ListStoryVirtualScrollTrack.d.ts +6 -0
  152. package/lib/features/story/components/ListStoryVirtualScrollTrack.js +7 -0
  153. package/lib/{panelview/story-maps → features/story}/components/SpectaDesktopView.d.ts +4 -2
  154. package/lib/features/story/components/SpectaDesktopView.js +67 -0
  155. package/lib/features/story/components/SpectaMobileListModeContent.d.ts +13 -0
  156. package/lib/features/story/components/SpectaMobileListModeContent.js +36 -0
  157. package/lib/features/story/components/SpectaMobileSingleModeContent.d.ts +14 -0
  158. package/lib/{panelview/story-maps/components/SpectaMobileView.js → features/story/components/SpectaMobileSingleModeContent.js} +4 -6
  159. package/lib/{panelview/story-maps → features/story}/components/SpectaMobileView.d.ts +5 -3
  160. package/lib/features/story/components/SpectaMobileView.js +12 -0
  161. package/lib/features/story/components/SpectaSingleModeContent.d.ts +18 -0
  162. package/lib/features/story/components/SpectaSingleModeContent.js +8 -0
  163. package/lib/features/story/context/ListStoryScrollTrackContext.d.ts +19 -0
  164. package/lib/features/story/context/ListStoryScrollTrackContext.js +186 -0
  165. package/lib/features/story/hooks/useCurrentSegmentIndex.d.ts +3 -0
  166. package/lib/features/story/hooks/useCurrentSegmentIndex.js +17 -0
  167. package/lib/features/story/hooks/useListStoryScroll.d.ts +15 -0
  168. package/lib/features/story/hooks/useListStoryScroll.js +107 -0
  169. package/lib/features/story/hooks/useQueuedMarkdownHeightMeasure.d.ts +19 -0
  170. package/lib/features/story/hooks/useQueuedMarkdownHeightMeasure.js +56 -0
  171. package/lib/features/story/hooks/useStoryImagePreload.d.ts +1 -0
  172. package/lib/features/story/hooks/useStoryImagePreload.js +24 -0
  173. package/lib/{panelview/story-maps → features/story}/hooks/useStoryMap.d.ts +2 -7
  174. package/lib/{panelview/story-maps → features/story}/hooks/useStoryMap.js +21 -60
  175. package/lib/features/story/hooks/useStoryScrollState.d.ts +21 -0
  176. package/lib/features/story/hooks/useStoryScrollState.js +39 -0
  177. package/lib/features/story/hooks/useStorySegmentSync.d.ts +8 -0
  178. package/lib/features/story/hooks/useStorySegmentSync.js +51 -0
  179. package/lib/features/story/types/types.d.ts +43 -0
  180. package/lib/features/story/types/types.js +1 -0
  181. package/lib/features/story/utils/computeListStoryScrollState.d.ts +14 -0
  182. package/lib/features/story/utils/computeListStoryScrollState.js +79 -0
  183. package/lib/features/story/utils/listStoryMeasureQueue.d.ts +11 -0
  184. package/lib/features/story/utils/listStoryMeasureQueue.js +14 -0
  185. package/lib/features/story/utils/listStoryScrollTrack.d.ts +17 -0
  186. package/lib/features/story/utils/listStoryScrollTrack.js +72 -0
  187. package/lib/features/story/utils/spectaPresentation.d.ts +9 -0
  188. package/lib/features/story/utils/spectaPresentation.js +37 -0
  189. package/lib/features/story/utils/storySegmentViewItems.d.ts +5 -0
  190. package/lib/features/story/utils/storySegmentViewItems.js +30 -0
  191. package/lib/index.d.ts +11 -9
  192. package/lib/index.js +11 -9
  193. package/lib/keybindings.json +5 -0
  194. package/lib/mainview/OpenEOTileLayer.d.ts +49 -0
  195. package/lib/mainview/OpenEOTileLayer.js +179 -0
  196. package/lib/mainview/TemporalSlider.js +11 -9
  197. package/lib/mainview/components/MainViewMapSurface.d.ts +15 -0
  198. package/lib/mainview/components/MainViewMapSurface.js +13 -0
  199. package/lib/mainview/components/MainViewOverlayLayer.d.ts +9 -0
  200. package/lib/mainview/components/MainViewOverlayLayer.js +11 -0
  201. package/lib/mainview/components/MainViewSidePanels.d.ts +17 -0
  202. package/lib/mainview/components/MainViewSidePanels.js +10 -0
  203. package/lib/mainview/components/MainViewSpectaPanel.d.ts +17 -0
  204. package/lib/mainview/components/MainViewSpectaPanel.js +8 -0
  205. package/lib/mainview/components/MainViewStoryStage.d.ts +13 -0
  206. package/lib/mainview/components/MainViewStoryStage.js +17 -0
  207. package/lib/mainview/components/PositionedFloater.d.ts +10 -0
  208. package/lib/mainview/components/PositionedFloater.js +7 -0
  209. package/lib/mainview/geoJsonFeaturePatch.d.ts +9 -0
  210. package/lib/mainview/geoJsonFeaturePatch.js +43 -0
  211. package/lib/mainview/mainView.d.ts +93 -14
  212. package/lib/mainview/mainView.js +1216 -686
  213. package/lib/mainview/mainviewwidget.d.ts +5 -1
  214. package/lib/mainview/mainviewwidget.js +4 -2
  215. package/lib/shared/components/Button.d.ts +1 -1
  216. package/lib/shared/components/DropdownMenu.d.ts +1 -0
  217. package/lib/shared/components/DropdownMenu.js +3 -2
  218. package/lib/shared/components/NativeSelect.d.ts +8 -0
  219. package/lib/shared/components/NativeSelect.js +29 -0
  220. package/lib/shared/components/Tabs.d.ts +3 -3
  221. package/lib/shared/components/Tabs.js +5 -5
  222. package/lib/{formbuilder → shared/formbuilder}/creationform.js +71 -4
  223. package/lib/{formbuilder → shared/formbuilder}/editform.js +1 -1
  224. package/lib/{formbuilder → shared/formbuilder}/formselectors.d.ts +2 -2
  225. package/lib/{formbuilder → shared/formbuilder}/formselectors.js +10 -7
  226. package/lib/shared/formbuilder/index.d.ts +4 -0
  227. package/lib/shared/formbuilder/index.js +4 -0
  228. package/lib/{formbuilder → shared/formbuilder}/objectform/SchemaForm.d.ts +1 -1
  229. package/lib/{formbuilder → shared/formbuilder}/objectform/StoryEditorForm.d.ts +1 -1
  230. package/lib/{formbuilder → shared/formbuilder}/objectform/StoryEditorForm.js +1 -1
  231. package/lib/{formbuilder → shared/formbuilder}/objectform/components/LayerSelect.js +1 -1
  232. package/lib/{formbuilder → shared/formbuilder}/objectform/components/SegmentFormSymbology.js +4 -4
  233. package/lib/{formbuilder → shared/formbuilder}/objectform/components/SourcePropertiesField.js +1 -1
  234. package/lib/{formbuilder → shared/formbuilder}/objectform/components/StorySegmentReset.js +1 -1
  235. package/lib/{formbuilder → shared/formbuilder}/objectform/components/WmsTileSourceUrlInput.js +4 -4
  236. package/lib/{formbuilder → shared/formbuilder}/objectform/fileselectorwidget.js +8 -1
  237. package/lib/{formbuilder → shared/formbuilder}/objectform/schemaUtils.d.ts +3 -1
  238. package/lib/{formbuilder → shared/formbuilder}/objectform/schemaUtils.js +11 -0
  239. package/lib/{formbuilder → shared/formbuilder}/objectform/useSchemaFormState.d.ts +1 -1
  240. package/lib/{formbuilder → shared/formbuilder}/objectform/useSchemaFormState.js +1 -1
  241. package/lib/{icons.d.ts → shared/icons.d.ts} +2 -0
  242. package/lib/{icons.js → shared/icons.js} +28 -18
  243. package/lib/tools.d.ts +2 -0
  244. package/lib/tools.js +138 -4
  245. package/lib/types.d.ts +6 -1
  246. package/lib/types.js +6 -2
  247. package/lib/{menus.js → workspace/menus.js} +10 -2
  248. package/lib/workspace/panels/components/TabbedPanel.d.ts +17 -0
  249. package/lib/workspace/panels/components/TabbedPanel.js +19 -0
  250. package/lib/{panelview → workspace/panels}/components/layers.js +63 -18
  251. package/lib/workspace/panels/components/legendItem.js +680 -0
  252. package/lib/workspace/panels/hooks/useLayerTree.d.ts +19 -0
  253. package/lib/workspace/panels/hooks/useLayerTree.js +103 -0
  254. package/lib/workspace/panels/hooks/useRightPanelOptions.d.ts +27 -0
  255. package/lib/workspace/panels/hooks/useRightPanelOptions.js +72 -0
  256. package/lib/workspace/panels/hooks/useUIState.d.ts +2 -0
  257. package/lib/workspace/panels/hooks/useUIState.js +25 -0
  258. package/lib/{panelview → workspace/panels}/index.d.ts +1 -1
  259. package/lib/{panelview → workspace/panels}/index.js +1 -1
  260. package/lib/{panelview → workspace/panels}/leftpanel.d.ts +1 -1
  261. package/lib/workspace/panels/leftpanel.js +70 -0
  262. package/lib/{panelview/rightpanel.d.ts → workspace/panels/mergedpanel.d.ts} +6 -5
  263. package/lib/workspace/panels/mergedpanel.js +166 -0
  264. package/lib/workspace/panels/rightpanel.d.ts +25 -0
  265. package/lib/{panelview → workspace/panels}/rightpanel.js +53 -63
  266. package/lib/{statusbar → workspace/statusbar}/StatusBar.js +5 -4
  267. package/lib/{toolbar → workspace/toolbar}/widget.d.ts +2 -0
  268. package/lib/{toolbar → workspace/toolbar}/widget.js +33 -5
  269. package/lib/{widget.d.ts → workspace/widget.d.ts} +7 -5
  270. package/lib/{widget.js → workspace/widget.js} +16 -7
  271. package/package.json +16 -4
  272. package/style/base.css +124 -2
  273. package/style/icons/geopackage.svg +95 -0
  274. package/style/icons/pencil_solid.svg +7 -0
  275. package/style/identify.css +95 -0
  276. package/style/layerBrowser.css +29 -1
  277. package/style/leftPanel.css +25 -0
  278. package/style/shared/button.css +12 -0
  279. package/style/shared/dropdownMenu.css +9 -0
  280. package/style/shared/nativeSelect.css +75 -0
  281. package/style/shared/tabs.css +1 -1
  282. package/style/spectaProgressBar.css +0 -1
  283. package/style/storyPanel.css +330 -9
  284. package/style/storySpectaArticleOverlay.css +129 -0
  285. package/style/symbologyDialog.css +522 -27
  286. package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +0 -4
  287. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +0 -42
  288. package/lib/dialogs/symbology/tiff_layer/components/BandRow.d.ts +0 -23
  289. package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +0 -59
  290. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +0 -4
  291. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +0 -92
  292. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +0 -5
  293. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +0 -313
  294. package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +0 -4
  295. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +0 -112
  296. package/lib/dialogs/symbology/vector_layer/components/ValueSelect.d.ts +0 -8
  297. package/lib/dialogs/symbology/vector_layer/components/ValueSelect.js +0 -9
  298. package/lib/dialogs/symbology/vector_layer/types/Canonical.d.ts +0 -4
  299. package/lib/dialogs/symbology/vector_layer/types/Canonical.js +0 -130
  300. package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +0 -4
  301. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +0 -243
  302. package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +0 -4
  303. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +0 -362
  304. package/lib/dialogs/symbology/vector_layer/types/Heatmap.d.ts +0 -4
  305. package/lib/dialogs/symbology/vector_layer/types/Heatmap.js +0 -89
  306. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +0 -4
  307. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +0 -120
  308. package/lib/formbuilder/index.d.ts +0 -4
  309. package/lib/formbuilder/index.js +0 -4
  310. package/lib/formbuilder/objectform/layer/heatmapLayerForm.js +0 -96
  311. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +0 -95
  312. package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +0 -3
  313. package/lib/formbuilder/objectform/process/index.d.ts +0 -1
  314. package/lib/formbuilder/objectform/process/index.js +0 -1
  315. package/lib/panelview/components/legendItem.js +0 -210
  316. package/lib/panelview/identify-panel/IdentifyPanel.js +0 -102
  317. package/lib/panelview/leftpanel.js +0 -139
  318. package/lib/panelview/story-maps/StoryViewerPanel.js +0 -116
  319. package/lib/panelview/story-maps/components/SpectaDesktopView.js +0 -49
  320. package/lib/processing/index.js +0 -200
  321. package/lib/processing/processingCommands.js +0 -67
  322. /package/lib/{panelview/annotationPanel.d.ts → features/annotations/AnnotationsPanel.d.ts} +0 -0
  323. /package/lib/{annotations → features/annotations}/components/Annotation.d.ts +0 -0
  324. /package/lib/{annotations → features/annotations}/components/Annotation.js +0 -0
  325. /package/lib/{annotations → features/annotations}/components/AnnotationFloater.d.ts +0 -0
  326. /package/lib/{annotations → features/annotations}/components/AnnotationFloater.js +0 -0
  327. /package/lib/{annotations → features/annotations}/components/Message.d.ts +0 -0
  328. /package/lib/{annotations → features/annotations}/components/Message.js +0 -0
  329. /package/lib/{annotations → features/annotations}/model.d.ts +0 -0
  330. /package/lib/{annotations → features/annotations}/model.js +0 -0
  331. /package/lib/{console → features/console}/consoleview.d.ts +0 -0
  332. /package/lib/{console → features/console}/index.d.ts +0 -0
  333. /package/lib/{console → features/console}/index.js +0 -0
  334. /package/lib/{panelview/filter-panel → features/filter}/Filter.d.ts +0 -0
  335. /package/lib/{stacBrowser/types/types.js → features/identify/types/editorTypes.js} +0 -0
  336. /package/lib/{dialogs/layerBrowserDialog.d.ts → features/layer-browser/index.d.ts} +0 -0
  337. /package/lib/{formbuilder/objectform → features/layers/forms}/layer/hillshadeLayerForm.d.ts +0 -0
  338. /package/lib/{formbuilder/objectform → features/layers/forms}/layer/storySegmentLayerForm.d.ts +0 -0
  339. /package/lib/{formbuilder/objectform → features/layers/forms}/layer/vectorlayerform.d.ts +0 -0
  340. /package/lib/{formbuilder/objectform → features/layers/forms}/source/geojsonsource.d.ts +0 -0
  341. /package/lib/{formbuilder/objectform → features/layers/forms}/source/geotiffsource.d.ts +0 -0
  342. /package/lib/{formbuilder/objectform → features/layers/forms}/source/pathbasedsource.d.ts +0 -0
  343. /package/lib/{formbuilder/objectform → features/layers/forms}/source/tilesourceform.d.ts +0 -0
  344. /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelectorEntry.d.ts +0 -0
  345. /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelectorEntry.js +0 -0
  346. /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ModeSelectRow.js +0 -0
  347. /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/RgbaColorPicker.d.ts +0 -0
  348. /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/cmocean.json +0 -0
  349. /package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopContainer.d.ts +0 -0
  350. /package/lib/{dialogs → features/layers}/symbology/hooks/useEffectiveSymbologyParams.d.ts +0 -0
  351. /package/lib/{dialogs → features/layers}/symbology/hooks/useEffectiveSymbologyParams.js +0 -0
  352. /package/lib/{dialogs → features/layers}/symbology/hooks/useGetProperties.d.ts +0 -0
  353. /package/lib/{dialogs → features/layers}/symbology/hooks/useGetSymbology.d.ts +0 -0
  354. /package/lib/{dialogs → features/layers}/symbology/hooks/useOkSignal.d.ts +0 -0
  355. /package/lib/{dialogs → features/layers}/symbology/hooks/useOkSignal.js +0 -0
  356. /package/lib/{formbuilder/objectform/process → features/processing/forms}/dissolveProcessForm.d.ts +0 -0
  357. /package/lib/{processing → features/processing}/processingFormToParam.d.ts +0 -0
  358. /package/lib/{processing → features/processing}/processingFormToParam.js +0 -0
  359. /package/lib/{stacBrowser → features/stac-browser}/components/StacPanel.d.ts +0 -0
  360. /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableComboBox.d.ts +0 -0
  361. /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableRow.d.ts +0 -0
  362. /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacFilterExtensionPanel.d.ts +0 -0
  363. /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacQueryableFilters.d.ts +0 -0
  364. /package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacFilterSection.d.ts +0 -0
  365. /package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacGeodesFilterPanel.d.ts +0 -0
  366. /package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacGeodesFilterPanel.js +0 -0
  367. /package/lib/{stacBrowser → features/stac-browser}/components/shared/StacPanelResults.d.ts +0 -0
  368. /package/lib/{stacBrowser → features/stac-browser}/components/shared/StacSpatialExtent.d.ts +0 -0
  369. /package/lib/{stacBrowser → features/stac-browser}/components/shared/StacTemporalExtent.d.ts +0 -0
  370. /package/lib/{stacBrowser → features/stac-browser}/constants.d.ts +0 -0
  371. /package/lib/{stacBrowser → features/stac-browser}/constants.js +0 -0
  372. /package/lib/{stacBrowser → features/stac-browser}/context/StacResultsContext.d.ts +0 -0
  373. /package/lib/{stacBrowser → features/stac-browser}/hooks/useGeodesSearch.d.ts +0 -0
  374. /package/lib/{stacBrowser → features/stac-browser}/hooks/useStacFilterExtension.d.ts +0 -0
  375. /package/lib/{stacBrowser → features/stac-browser}/hooks/useStacSearch.d.ts +0 -0
  376. /package/lib/{stacBrowser → features/stac-browser}/index.d.ts +0 -0
  377. /package/lib/{stacBrowser → features/stac-browser}/index.js +0 -0
  378. /package/lib/{stacBrowser → features/stac-browser}/types/types.d.ts +0 -0
  379. /package/lib/{panelview/story-maps → features/story}/StoryEditorPanel.d.ts +0 -0
  380. /package/lib/{panelview/story-maps → features/story}/components/PreviewModeSwitch.d.ts +0 -0
  381. /package/lib/{panelview/story-maps → features/story}/components/PreviewModeSwitch.js +0 -0
  382. /package/lib/{panelview/story-maps → features/story}/components/StoryContentSection.d.ts +0 -0
  383. /package/lib/{panelview/story-maps → features/story}/components/StoryContentSection.js +0 -0
  384. /package/lib/{panelview/story-maps → features/story}/components/StoryImageSection.d.ts +0 -0
  385. /package/lib/{panelview/story-maps → features/story}/components/StoryImageSection.js +0 -0
  386. /package/lib/{panelview/story-maps → features/story}/components/StoryNavBar.d.ts +0 -0
  387. /package/lib/{panelview/story-maps → features/story}/components/StoryNavBar.js +0 -0
  388. /package/lib/{panelview/story-maps → features/story}/components/StorySubtitleSection.d.ts +0 -0
  389. /package/lib/{panelview/story-maps → features/story}/components/StorySubtitleSection.js +0 -0
  390. /package/lib/{panelview/story-maps → features/story}/components/StoryTitleSection.d.ts +0 -0
  391. /package/lib/{panelview/story-maps → features/story}/components/StoryTitleSection.js +0 -0
  392. /package/lib/{formbuilder → shared/formbuilder}/creationform.d.ts +0 -0
  393. /package/lib/{formbuilder → shared/formbuilder}/editform.d.ts +0 -0
  394. /package/lib/{formbuilder → shared/formbuilder}/objectform/SchemaForm.js +0 -0
  395. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/LayerSelect.d.ts +0 -0
  396. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/OpacitySlider.d.ts +0 -0
  397. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/OpacitySlider.js +0 -0
  398. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/SegmentFormSymbology.d.ts +0 -0
  399. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/SourcePropertiesField.d.ts +0 -0
  400. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/StorySegmentReset.d.ts +0 -0
  401. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/WmsTileSourceUrlInput.d.ts +0 -0
  402. /package/lib/{formbuilder → shared/formbuilder}/objectform/fileselectorwidget.d.ts +0 -0
  403. /package/lib/{store.d.ts → shared/store.d.ts} +0 -0
  404. /package/lib/{store.js → shared/store.js} +0 -0
  405. /package/lib/{menus.d.ts → workspace/menus.d.ts} +0 -0
  406. /package/lib/{panelview → workspace/panels}/components/layers.d.ts +0 -0
  407. /package/lib/{panelview → workspace/panels}/components/legendItem.d.ts +0 -0
  408. /package/lib/{panelview → workspace/panels}/header.d.ts +0 -0
  409. /package/lib/{panelview → workspace/panels}/header.js +0 -0
  410. /package/lib/{statusbar → workspace/statusbar}/SpectaPresentationProgressBar.d.ts +0 -0
  411. /package/lib/{statusbar → workspace/statusbar}/SpectaPresentationProgressBar.js +0 -0
  412. /package/lib/{statusbar → workspace/statusbar}/StatusBar.d.ts +0 -0
  413. /package/lib/{toolbar → workspace/toolbar}/index.d.ts +0 -0
  414. /package/lib/{toolbar → workspace/toolbar}/index.js +0 -0
@@ -16,38 +16,70 @@ import { UUID } from '@lumino/coreutils';
16
16
  import { ContextMenu, Menu } from '@lumino/widgets';
17
17
  import { Collection, Map as OlMap, View, getUid, } from 'ol';
18
18
  import Feature from 'ol/Feature';
19
+ import TileState from 'ol/TileState';
19
20
  import { FullScreen, ScaleLine, Zoom } from 'ol/control';
20
21
  import { singleClick } from 'ol/events/condition';
21
- import { getCenter } from 'ol/extent';
22
+ import { getCenter, getSize } from 'ol/extent';
22
23
  import { GeoJSON, MVT } from 'ol/format';
23
24
  import { Point } from 'ol/geom';
24
25
  import { DragAndDrop, DragPan, DragRotate, DragZoom, KeyboardPan, KeyboardZoom, MouseWheelZoom, PinchRotate, PinchZoom, DoubleClickZoom, Select, } from 'ol/interaction';
25
- import { Heatmap as HeatmapLayer, Image as ImageLayer, Layer, Vector as VectorLayer, VectorTile as VectorTileLayer, WebGLTile as WebGlTileLayer, } from 'ol/layer';
26
+ import Draw from 'ol/interaction/Draw';
27
+ import Modify from 'ol/interaction/Modify';
28
+ import Snap from 'ol/interaction/Snap';
29
+ import { Image as ImageLayer, Layer, VectorImage as VectorImageLayer, VectorTile as VectorTileLayer, WebGLTile as GeoTiffLayer, } from 'ol/layer';
30
+ import LayerGroup from 'ol/layer/Group';
26
31
  import TileLayer from 'ol/layer/Tile';
27
32
  import { fromLonLat, get as getProjection, toLonLat, transformExtent, } from 'ol/proj';
28
33
  import { register } from 'ol/proj/proj4.js';
29
34
  import RenderFeature, { toGeometry } from 'ol/render/Feature';
30
35
  import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, TileWMS as TileWMSSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource, Tile as TileSource, } from 'ol/source';
31
36
  import Static from 'ol/source/ImageStatic';
32
- import { Circle, Fill, Icon, Stroke, Style } from 'ol/style';
37
+ import { Fill, Icon, Stroke, Style } from 'ol/style';
38
+ import CircleStyle from 'ol/style/Circle';
33
39
  //@ts-expect-error no types for ol-pmtiles
34
40
  import { PMTilesRasterSource, PMTilesVectorSource } from 'ol-pmtiles';
35
41
  import StacLayer from 'ol-stac';
36
42
  import proj4 from 'proj4';
37
43
  import proj4list from 'proj4-list';
38
44
  import * as React from 'react';
39
- import AnnotationFloater from "../annotations/components/AnnotationFloater";
40
45
  import { CommandIDs } from "../constants";
41
- import { LoadingOverlay } from "../shared/components/loading";
46
+ import AnnotationFloater from "../features/annotations/components/AnnotationFloater";
47
+ import FeatureFloater from "../features/identify/components/FeatureFloater";
48
+ import { getFeatureIdentifier } from "../features/identify/utils/getFeatureIdentifier";
42
49
  import useMediaQuery from "../shared/hooks/useMediaQuery";
43
- import StatusBar from "../statusbar/StatusBar";
44
- import { debounce, isLightTheme, loadFile, throttle } from "../tools";
45
- import CollaboratorPointers from './CollaboratorPointers';
46
- import { FollowIndicator } from './FollowIndicator';
50
+ import { markerIcon } from "../shared/icons";
51
+ import { debounce, INTERNAL_PROXY_BASE, isJupyterLite, isLightTheme, loadFile, throttle, } from "../tools";
52
+ import StatusBar from "../workspace/statusbar/StatusBar";
53
+ import { OpenEOTileLayer, OpenEOTileSource } from './OpenEOTileLayer';
47
54
  import TemporalSlider from './TemporalSlider';
48
- import { markerIcon } from '../icons';
49
- import { LeftPanel, RightPanel } from '../panelview';
50
- import { SpectaPanel } from '../panelview/story-maps/SpectaPanel';
55
+ import { MainViewMapSurface } from './components/MainViewMapSurface';
56
+ import { MainViewOverlayLayer } from './components/MainViewOverlayLayer';
57
+ import { MainViewSidePanels } from './components/MainViewSidePanels';
58
+ import { MainViewSpectaPanel } from './components/MainViewSpectaPanel';
59
+ import { MainViewStoryStage } from './components/MainViewStoryStage';
60
+ import { PositionedFloater } from './components/PositionedFloater';
61
+ import { createGeoJSONFeaturePatcher, } from './geoJsonFeaturePatch';
62
+ import { ensureHighlightLayer } from '../features/identify/utils/highlightLayer';
63
+ import { buildHighlightStyle } from '../features/identify/utils/highlightStyle';
64
+ import { grammarToOLLayer } from '../features/layers/symbology/grammarToOLLayer';
65
+ import { extractEncodingFieldValues, grammarToOLStyle, } from '../features/layers/symbology/grammarToOLStyle';
66
+ import { DEFAULT_FLAT_STYLE } from '../features/layers/symbology/styleBuilder';
67
+ import { STORY_TYPE } from '../types';
68
+ const drawInteractionStyle = new Style({
69
+ fill: new Fill({
70
+ color: 'rgba(255, 255, 255, 0.2)',
71
+ }),
72
+ stroke: new Stroke({
73
+ color: '#ffcc33',
74
+ width: 2,
75
+ }),
76
+ image: new CircleStyle({
77
+ radius: 7,
78
+ fill: new Fill({
79
+ color: '#ffcc33',
80
+ }),
81
+ }),
82
+ });
51
83
  export class MainView extends React.Component {
52
84
  constructor(props) {
53
85
  super(props);
@@ -64,48 +96,8 @@ export class MainView extends React.Component {
64
96
  return transformExtent(extent, view.getProjection(), targetProjection);
65
97
  };
66
98
  this.createSelectInteraction = () => {
67
- const pointStyle = new Style({
68
- image: new Circle({
69
- radius: 5,
70
- fill: new Fill({
71
- color: '#C52707',
72
- }),
73
- stroke: new Stroke({
74
- color: '#171717',
75
- width: 2,
76
- }),
77
- }),
78
- });
79
- const lineStyle = new Style({
80
- stroke: new Stroke({
81
- color: '#171717',
82
- width: 2,
83
- }),
84
- });
85
- const polygonStyle = new Style({
86
- fill: new Fill({ color: '#C5270780' }),
87
- stroke: new Stroke({
88
- color: '#171717',
89
- width: 2,
90
- }),
91
- });
92
- const styleFunction = (feature) => {
93
- var _a;
94
- const geometryType = (_a = feature.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType();
95
- switch (geometryType) {
96
- case 'Point':
97
- case 'MultiPoint':
98
- return pointStyle;
99
- case 'LineString':
100
- case 'MultiLineString':
101
- return lineStyle;
102
- case 'Polygon':
103
- case 'MultiPolygon':
104
- return polygonStyle;
105
- }
106
- };
107
99
  const selectInteraction = new Select({
108
- hitTolerance: 5,
100
+ hitTolerance: 3,
109
101
  multi: true,
110
102
  layers: layer => {
111
103
  var _a, _b;
@@ -115,19 +107,84 @@ export class MainView extends React.Component {
115
107
  return false;
116
108
  }
117
109
  const selectedLayerId = Object.keys(selectedLayers)[0];
118
- return layer === this.getLayer(selectedLayerId);
110
+ const expected = this.getLayer(selectedLayerId);
111
+ if (layer === expected) {
112
+ return true;
113
+ }
114
+ // Grammar multi-layer symbology wraps sub-layers in a LayerGroup.
115
+ // OL Select flattens groups, so we receive leaf layers, not the group.
116
+ if (expected instanceof LayerGroup) {
117
+ return expected.getLayers().getArray().includes(layer);
118
+ }
119
+ return false;
119
120
  },
120
121
  condition: (event) => {
121
122
  return singleClick(event) && this._model.currentMode === 'identifying';
122
123
  },
123
- style: styleFunction,
124
+ // Use the layer's own style so selected features keep their original
125
+ // appearance. Visual highlight feedback comes from _highlightLayer.
126
+ style: null,
124
127
  });
125
128
  selectInteraction.on('select', event => {
129
+ var _a, _b, _c;
126
130
  const identifiedFeatures = [];
131
+ const highlightFeatures = [];
132
+ // Look up the selected layer's style function for adaptive highlights.
133
+ const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
134
+ const selectedLayers = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
135
+ const selectedLayerId = selectedLayers
136
+ ? Object.keys(selectedLayers)[0]
137
+ : undefined;
138
+ const mapLayer = selectedLayerId
139
+ ? this.getLayer(selectedLayerId)
140
+ : undefined;
141
+ // For LayerGroup (multi-layer grammar), collect style functions from
142
+ // all sub-layers so we can match the right one per feature.
143
+ const styleFnCandidates = [];
144
+ if (mapLayer instanceof LayerGroup) {
145
+ for (const sub of mapLayer.getLayers().getArray()) {
146
+ if ('getStyleFunction' in sub) {
147
+ styleFnCandidates.push(sub.getStyleFunction());
148
+ }
149
+ }
150
+ }
151
+ else if (mapLayer && 'getStyleFunction' in mapLayer) {
152
+ styleFnCandidates.push(mapLayer.getStyleFunction());
153
+ }
154
+ const resolution = (_c = this._Map.getView().getResolution()) !== null && _c !== void 0 ? _c : 1;
127
155
  selectInteraction.getFeatures().forEach(feature => {
128
- identifiedFeatures.push(feature.getProperties());
156
+ identifiedFeatures.push({
157
+ feature: feature.getProperties(),
158
+ floaterOpen: false,
159
+ });
160
+ const geom = feature.getGeometry();
161
+ if (geom) {
162
+ const hlFeature = new Feature({ geometry: geom });
163
+ // Try each style function candidate; use the first that resolves
164
+ // a non-empty style array (important for LayerGroup sub-layers
165
+ // where only one sub-layer's style applies to this feature).
166
+ for (const fn of styleFnCandidates) {
167
+ if (!fn) {
168
+ continue;
169
+ }
170
+ const resolved = fn(feature, resolution);
171
+ const styles = Array.isArray(resolved)
172
+ ? resolved
173
+ : resolved
174
+ ? [resolved]
175
+ : [];
176
+ if (styles.length > 0) {
177
+ const gType = geom.getType();
178
+ hlFeature.setStyle(styles.map(s => this._buildHighlightStyle(s, gType)));
179
+ break;
180
+ }
181
+ }
182
+ highlightFeatures.push(hlFeature);
183
+ }
129
184
  });
130
185
  this._model.syncIdentifiedFeatures(identifiedFeatures, this._mainViewModel.id);
186
+ // Sync _highlightLayer with the current selection (clears on deselect).
187
+ this._setHighlightFeatures(highlightFeatures);
131
188
  });
132
189
  this._Map.addInteraction(selectInteraction);
133
190
  };
@@ -210,25 +267,16 @@ export class MainView extends React.Component {
210
267
  rank: 2,
211
268
  });
212
269
  };
270
+ // Used by VectorTileLayer (which shares a flat-style API with Grammar output).
213
271
  this.vectorLayerStyleRuleBuilder = (layer) => {
214
272
  var _a, _b;
215
273
  const layerParams = layer.parameters;
216
- if (!layerParams) {
217
- return;
274
+ const ss = layerParams === null || layerParams === void 0 ? void 0 : layerParams.symbologyState;
275
+ if (!ss || Object.keys(ss).length === 0) {
276
+ return [{ style: DEFAULT_FLAT_STYLE }];
218
277
  }
219
- const defaultStyle = {
220
- 'fill-color': 'rgba(255,255,255,0.4)',
221
- 'stroke-color': '#3399CC',
222
- 'stroke-width': 1.25,
223
- 'circle-radius': 5,
224
- 'circle-fill-color': 'rgba(255,255,255,0.4)',
225
- 'circle-stroke-width': 1.25,
226
- 'circle-stroke-color': '#3399CC',
227
- };
228
- const defaultRules = {
229
- style: defaultStyle,
230
- };
231
- const layerStyle = Object.assign({}, defaultRules);
278
+ const flatStyle = grammarToOLStyle(layerParams.symbologyState, []);
279
+ const layerStyle = { style: flatStyle };
232
280
  if (((_a = layer.filters) === null || _a === void 0 ? void 0 : _a.logicalOp) && ((_b = layer.filters.appliedFilters) === null || _b === void 0 ? void 0 : _b.length) > 0) {
233
281
  const buildCondition = (filter) => {
234
282
  const base = [filter.operator, ['get', filter.feature]];
@@ -236,89 +284,13 @@ export class MainView extends React.Component {
236
284
  ? [...base, filter.betweenMin, filter.betweenMax]
237
285
  : [...base, filter.value];
238
286
  };
239
- let filterExpr;
240
- // 'Any' and 'All' operators require more than one argument
241
- // So if there's only one filter, skip that part to avoid error
242
- if (layer.filters.appliedFilters.length === 1) {
243
- filterExpr = buildCondition(layer.filters.appliedFilters[0]);
244
- }
245
- else {
246
- // Arguments for "Any" and 'All' need to be wrapped in brackets
247
- filterExpr = [
248
- layer.filters.logicalOp,
249
- ...layer.filters.appliedFilters.map(buildCondition),
250
- ];
251
- }
252
- layerStyle.filter = filterExpr;
253
- }
254
- if (!layerParams.color) {
255
- return [layerStyle];
256
- }
257
- const newStyle = Object.assign(Object.assign({}, defaultStyle), layerParams.color);
258
- layerStyle.style = newStyle;
259
- // When fallbackColor[3] === 0, add an OL filter so features that would be
260
- // drawn with a transparent color are excluded from rendering entirely.
261
- // alpha === 0 is the contract: the default TRANSPARENT [0,0,0,0] triggers
262
- // this automatically, and a future dedicated picker option can set it.
263
- const symbologyState = layerParams.symbologyState;
264
- if (Array.isArray(symbologyState === null || symbologyState === void 0 ? void 0 : symbologyState.fallbackColor) &&
265
- symbologyState.fallbackColor[3] === 0) {
266
- let matchFilter;
267
- if (symbologyState.renderType === 'Categorized') {
268
- const fillExpr = layerParams.color['fill-color'];
269
- if (Array.isArray(fillExpr) &&
270
- fillExpr[0] === 'case' &&
271
- fillExpr.length >= 4) {
272
- // Extract conditions from ['case', cond, val, cond, val, ..., fallback],
273
- // skipping any whose matched output color is also fully transparent.
274
- const conditions = [];
275
- for (let i = 1; i < fillExpr.length - 1; i += 2) {
276
- const output = fillExpr[i + 1];
277
- if (Array.isArray(output) && output[3] === 0) {
278
- continue;
279
- }
280
- conditions.push(fillExpr[i]);
281
- }
282
- // If every stop is transparent, use a never-true filter to hide all.
283
- matchFilter =
284
- conditions.length === 0
285
- ? ['==', 0, 1]
286
- : conditions.length === 1
287
- ? conditions[0]
288
- : ['any', ...conditions];
289
- }
290
- }
291
- else if (symbologyState.renderType === 'Graduated') {
292
- const fillExpr = layerParams.color['fill-color'];
293
- // Graduated fill is ['case', ['has', field], interpolateExpr, fallback].
294
- // Features missing the attribute fall through to the fallback, so filter
295
- // them out with the same ['has', field] condition.
296
- if (Array.isArray(fillExpr) &&
297
- fillExpr[0] === 'case' &&
298
- Array.isArray(fillExpr[1]) &&
299
- fillExpr[1][0] === 'has') {
300
- matchFilter = fillExpr[1];
301
- }
302
- }
303
- else if (symbologyState.renderType === 'Canonical') {
304
- const fillExpr = layerParams.color['fill-color'];
305
- // Canonical fill is ['coalesce', ['get', field], fallback].
306
- // Features missing the attribute fall through to the fallback, so filter
307
- // them out with ['has', field]. Note: features that have the field but
308
- // with a non-color value also get the fallback — that gap is accepted.
309
- if (Array.isArray(fillExpr) &&
310
- fillExpr[0] === 'coalesce' &&
311
- Array.isArray(fillExpr[1]) &&
312
- fillExpr[1][0] === 'get') {
313
- matchFilter = ['has', fillExpr[1][1]];
314
- }
315
- }
316
- if (matchFilter) {
317
- // Combine with any existing user-applied filter.
318
- layerStyle.filter = layerStyle.filter
319
- ? ['all', layerStyle.filter, matchFilter]
320
- : matchFilter;
321
- }
287
+ layerStyle.filter =
288
+ layer.filters.appliedFilters.length === 1
289
+ ? buildCondition(layer.filters.appliedFilters[0])
290
+ : [
291
+ layer.filters.logicalOp,
292
+ ...layer.filters.appliedFilters.map(buildCondition),
293
+ ];
322
294
  }
323
295
  return [layerStyle];
324
296
  };
@@ -369,140 +341,36 @@ export class MainView extends React.Component {
369
341
  const scaled = ['*', 255, cosIncidence];
370
342
  return scaled;
371
343
  };
372
- /**
373
- * Heatmap layers don't work with style based filtering.
374
- * This modifies the features in the underlying source
375
- * to work with the temporal controller
376
- */
377
- this.handleTemporalController = (id, layer) => {
378
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
379
- const selectedLayer = (_c = (_b = (_a = this._model) === null || _a === void 0 ? void 0 : _a.localState) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value;
380
- // Temporal Controller shouldn't be active if more than one layer is selected
381
- if (!selectedLayer || Object.keys(selectedLayer).length !== 1) {
344
+ this._handleSelectedChanged = () => {
345
+ var _a;
346
+ const localState = this._model.localState;
347
+ if (!localState) {
382
348
  return;
383
349
  }
384
- const selectedLayerId = Object.keys(selectedLayer)[0];
385
- // Don't do anything to unselected layers
386
- if (selectedLayerId !== id) {
350
+ const selectedLayers = (_a = localState.selected) === null || _a === void 0 ? void 0 : _a.value;
351
+ if (!selectedLayers) {
387
352
  return;
388
353
  }
389
- const layerParams = layer.parameters;
390
- const source = this._sources[layerParams.source];
391
- if ((_d = layer.filters) === null || _d === void 0 ? void 0 : _d.appliedFilters.length) {
392
- // Heatmaps don't work with existing filter system so this should be fine
393
- const activeFilter = layer.filters.appliedFilters[0];
394
- // Save original features on first filter application
395
- if (!Object.keys(this._originalFeatures).includes(id)) {
396
- this._originalFeatures[id] = (_e = source.getFeatures()) !== null && _e !== void 0 ? _e : [];
397
- }
398
- // clear current features
399
- source.clear();
400
- const startTime = (_f = activeFilter.betweenMin) !== null && _f !== void 0 ? _f : 0;
401
- const endTime = (_g = activeFilter.betweenMax) !== null && _g !== void 0 ? _g : 1000;
402
- const filteredFeatures = ((_h = this._originalFeatures[id]) !== null && _h !== void 0 ? _h : []).filter(feature => {
403
- const featureTime = feature.get(activeFilter.feature);
404
- return featureTime >= startTime && featureTime <= endTime;
405
- });
406
- // set state for restoration
407
- this.setState(old => (Object.assign(Object.assign({}, old), { filterStates: Object.assign(Object.assign({}, this.state.filterStates), { [selectedLayerId]: activeFilter }) })));
408
- source.addFeatures(filteredFeatures);
409
- }
410
- else {
411
- // Restore original features when no filters are applied
412
- source.addFeatures((_j = this._originalFeatures[id]) !== null && _j !== void 0 ? _j : []);
413
- delete this._originalFeatures[id];
354
+ const selectedLayerId = Object.keys(selectedLayers)[0];
355
+ const JGISLayer = this._model.getLayer(selectedLayerId);
356
+ if (!JGISLayer) {
357
+ return;
414
358
  }
359
+ this._syncVectorDrawingFromSelection(JGISLayer, selectedLayerId);
415
360
  };
416
- this._onClientSharedStateChanged = (sender, clients) => {
417
- var _a, _b, _c;
418
- const localState = this._model.localState;
419
- if (!localState) {
361
+ this._syncVectorDrawingFromSelection = (layer, selectedLayerId) => {
362
+ const decision = this._getVectorDrawingSelectionDecision(layer, selectedLayerId);
363
+ if (decision.disableEditing) {
364
+ this._model.editingVectorLayer = false;
365
+ this._updateEditingVectorLayer();
420
366
  return;
421
367
  }
422
- const remoteUser = localState.remoteUser;
423
- // If we are in following mode, we update our position and selection
424
- if (remoteUser) {
425
- const remoteState = clients.get(remoteUser);
426
- if (!remoteState) {
427
- return;
428
- }
429
- if (((_a = remoteState.user) === null || _a === void 0 ? void 0 : _a.username) !== ((_b = this.state.remoteUser) === null || _b === void 0 ? void 0 : _b.username)) {
430
- this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: remoteState.user })));
431
- }
432
- const remoteViewport = remoteState.viewportState;
433
- if (remoteViewport.value) {
434
- const { x, y } = remoteViewport.value.coordinates;
435
- const zoom = remoteViewport.value.zoom;
436
- this._moveToPosition({ x, y }, zoom, 0);
437
- }
438
- }
439
- else {
440
- // If we are unfollowing a remote user, we reset our center and zoom to their previous values
441
- if (this.state.remoteUser !== null) {
442
- this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: null })));
443
- const viewportState = (_c = localState.viewportState) === null || _c === void 0 ? void 0 : _c.value;
444
- if (viewportState) {
445
- this._moveToPosition(viewportState.coordinates, viewportState.zoom);
446
- }
447
- }
448
- }
449
- // cursors
450
- clients.forEach((client, clientId) => {
451
- var _a;
452
- if (!(client === null || client === void 0 ? void 0 : client.user)) {
453
- return;
454
- }
455
- const pointer = (_a = client.pointer) === null || _a === void 0 ? void 0 : _a.value;
456
- // We already display our own cursor on mouse move
457
- if (this._model.getClientId() === clientId) {
458
- return;
459
- }
460
- const clientPointers = Object.assign({}, this.state.clientPointers);
461
- let currentClientPointer = clientPointers[clientId];
462
- if (pointer) {
463
- const pixel = this._Map.getPixelFromCoordinate([
464
- pointer.coordinates.x,
465
- pointer.coordinates.y,
466
- ]);
467
- const lonLat = toLonLat([pointer.coordinates.x, pointer.coordinates.y]);
468
- if (!currentClientPointer) {
469
- currentClientPointer = {
470
- username: client.user.username,
471
- displayName: client.user.display_name,
472
- color: client.user.color,
473
- coordinates: {
474
- x: pixel[0],
475
- y: pixel[1],
476
- },
477
- lonLat: {
478
- longitude: lonLat[0],
479
- latitude: lonLat[1],
480
- },
481
- };
482
- }
483
- else {
484
- currentClientPointer = Object.assign(Object.assign({}, currentClientPointer), { coordinates: {
485
- x: pixel[0],
486
- y: pixel[1],
487
- }, lonLat: {
488
- longitude: lonLat[0],
489
- latitude: lonLat[1],
490
- } });
491
- }
492
- clientPointers[clientId] = currentClientPointer;
493
- }
494
- else {
495
- delete clientPointers[clientId];
496
- }
497
- this.setState(old => (Object.assign(Object.assign({}, old), { clientPointers })));
498
- });
499
- // Temporal controller bit
500
- // ? There's probably a better way to get changes in the model to trigger react rerenders
501
- const isTemporalControllerActive = localState.isTemporalControllerActive;
502
- if (isTemporalControllerActive !== this.state.displayTemporalController) {
503
- this.setState(old => (Object.assign(Object.assign({}, old), { displayTemporalController: isTemporalControllerActive })));
504
- this._mainViewModel.commands.notifyCommandChanged(CommandIDs.temporalController);
368
+ if (!decision.shouldRebind) {
369
+ return;
505
370
  }
371
+ this._previousDrawLayerID = selectedLayerId;
372
+ this._currentDrawLayerID = selectedLayerId;
373
+ this._editVectorLayer();
506
374
  };
507
375
  this._onSharedModelStateChange = (_, change) => {
508
376
  var _a;
@@ -521,6 +389,10 @@ export class MainView extends React.Component {
521
389
  }
522
390
  }
523
391
  };
392
+ this._handleIdentifiedFeaturesChanged = () => {
393
+ this.setState(old => (Object.assign(Object.assign({}, old), { identifyFeatureFloatersVersion: old.identifyFeatureFloatersVersion + 1 })));
394
+ this._clearHighlightWhenIdentifyDisabled();
395
+ };
524
396
  /**
525
397
  * Handler for when story maps change in the model.
526
398
  * Updates specta state and presentation colors when story data becomes available.
@@ -561,7 +433,6 @@ export class MainView extends React.Component {
561
433
  });
562
434
  };
563
435
  this._setupStoryScrollListener = () => {
564
- var _a, _b;
565
436
  // Guard: block wheel-driven segment change until transition has ended
566
437
  let segmentChangeInProgress = false;
567
438
  const clearGuard = () => {
@@ -569,16 +440,42 @@ export class MainView extends React.Component {
569
440
  };
570
441
  this._clearStoryScrollGuard = clearGuard;
571
442
  let accumulatedDeltaY = 0;
572
- let scrollContainer = (_b = (_a = this.storyViewerPanelRef.current) === null || _a === void 0 ? void 0 : _a.getScrollContainer()) !== null && _b !== void 0 ? _b : null;
443
+ let scrollContainer = null;
444
+ const resolveStoryScrollContainer = () => {
445
+ var _a, _b;
446
+ const fromPanel = (_b = (_a = this.storyViewerPanelRef.current) === null || _a === void 0 ? void 0 : _a.getScrollContainer()) !== null && _b !== void 0 ? _b : null;
447
+ if (fromPanel && document.contains(fromPanel)) {
448
+ return fromPanel;
449
+ }
450
+ const mobileScroll = document.querySelector('.jgis-story-mobile-list-scroll');
451
+ if (mobileScroll instanceof HTMLDivElement) {
452
+ return mobileScroll;
453
+ }
454
+ return null;
455
+ };
456
+ scrollContainer = resolveStoryScrollContainer();
573
457
  const processStoryScrollFrame = () => {
458
+ var _a;
574
459
  this._pendingStoryScrollRafId = null;
460
+ if (!scrollContainer || !document.contains(scrollContainer)) {
461
+ scrollContainer = resolveStoryScrollContainer();
462
+ }
575
463
  const currentPanelHandle = this.storyViewerPanelRef.current;
576
- if (!currentPanelHandle || !scrollContainer) {
464
+ const storyType = (_a = this._model.getSelectedStory().story) === null || _a === void 0 ? void 0 : _a.storyType;
465
+ if (!scrollContainer) {
577
466
  accumulatedDeltaY = 0;
578
467
  return;
579
468
  }
580
469
  const deltaY = accumulatedDeltaY;
581
470
  accumulatedDeltaY = 0;
471
+ // Don't want to handle next/prev logic in list mode
472
+ if (storyType === STORY_TYPE.verticalScroll) {
473
+ scrollContainer.scrollBy({ top: deltaY });
474
+ return;
475
+ }
476
+ if (!currentPanelHandle) {
477
+ return;
478
+ }
582
479
  const isScrollingUp = deltaY < 0;
583
480
  const isScrollingDown = deltaY > 0;
584
481
  const isAtTop = currentPanelHandle.getAtTop();
@@ -601,11 +498,10 @@ export class MainView extends React.Component {
601
498
  scrollContainer.scrollBy({ top: deltaY });
602
499
  };
603
500
  const handleScroll = (event) => {
604
- var _a, _b;
501
+ const wheelEvent = event;
605
502
  event.preventDefault();
606
503
  if (!scrollContainer || !document.contains(scrollContainer)) {
607
- scrollContainer =
608
- (_b = (_a = this.storyViewerPanelRef.current) === null || _a === void 0 ? void 0 : _a.getScrollContainer()) !== null && _b !== void 0 ? _b : null;
504
+ scrollContainer = resolveStoryScrollContainer();
609
505
  }
610
506
  if (!scrollContainer) {
611
507
  return;
@@ -614,7 +510,7 @@ export class MainView extends React.Component {
614
510
  // frames on slow hardware). We accumulate deltaY and run flush once per
615
511
  // frame via rAF—the frame boundary batches events without adding delay.
616
512
  // So one scroll means one segment/scroll decision.
617
- accumulatedDeltaY += event.deltaY;
513
+ accumulatedDeltaY += wheelEvent.deltaY;
618
514
  if (this._pendingStoryScrollRafId === null) {
619
515
  this._pendingStoryScrollRafId = requestAnimationFrame(processStoryScrollFrame);
620
516
  }
@@ -652,7 +548,7 @@ export class MainView extends React.Component {
652
548
  jsonData = JSON.parse(data);
653
549
  }
654
550
  catch (e) {
655
- console.warn(`Failed to parse annotation data for ${key}:`, e);
551
+ this._log('warning', `Failed to parse annotation data for ${key}: ${e}`);
656
552
  return;
657
553
  }
658
554
  }
@@ -682,6 +578,9 @@ export class MainView extends React.Component {
682
578
  this._handleWindowResize = () => {
683
579
  // TODO SOMETHING
684
580
  };
581
+ this._handleSegmentTransitionChange = (payload) => {
582
+ this.setState({ segmentTransition: payload });
583
+ };
685
584
  this._handleSpectaTouchStart = (e) => {
686
585
  if (e.touches.length > 0) {
687
586
  this._spectaTouchStartX = e.touches[0].clientX;
@@ -708,6 +607,148 @@ export class MainView extends React.Component {
708
607
  this._model.setCurrentSegmentIndex(current + 1);
709
608
  }
710
609
  };
610
+ this._handleDrawGeometryTypeChange = (
611
+ /* handle with the change of geometry and instantiate new draw interaction and other ones accordingly*/
612
+ event) => {
613
+ const drawGeometryLabel = event.target.value;
614
+ this._currentDrawGeometry = drawGeometryLabel;
615
+ this._updateInteractions();
616
+ this._updateDrawSource();
617
+ this.setState(old => (Object.assign(Object.assign({}, old), { drawGeometryLabel })));
618
+ };
619
+ this._getVectorSourceFromLayerID = (layerID) => {
620
+ /* get the OpenLayers VectorSource corresponding to the JGIS currentDrawLayerID */
621
+ const layers = this._Map.getLayers();
622
+ const layerArray = layers.getArray();
623
+ const matchingLayer = layerArray.find(layer => layer.get('id') === layerID);
624
+ const source = matchingLayer === null || matchingLayer === void 0 ? void 0 : matchingLayer.get('source');
625
+ this._currentVectorSource = source;
626
+ return this._currentVectorSource;
627
+ };
628
+ this._getDrawSourceFromSelectedLayer = () => {
629
+ var _a, _b, _c, _d;
630
+ const selectedLayers = (_c = (_b = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState()) === null || _b === void 0 ? void 0 : _b.selected) === null || _c === void 0 ? void 0 : _c.value;
631
+ if (!selectedLayers) {
632
+ return;
633
+ }
634
+ const selectedLayerID = Object.keys(selectedLayers)[0];
635
+ this._currentDrawLayerID = selectedLayerID;
636
+ const JGISLayer = this._model.getLayer(selectedLayerID);
637
+ this._currentDrawSourceID = (_d = JGISLayer === null || JGISLayer === void 0 ? void 0 : JGISLayer.parameters) === null || _d === void 0 ? void 0 : _d.source;
638
+ if (this._currentDrawSourceID) {
639
+ this._currentDrawSource = this._model.getSource(this._currentDrawSourceID);
640
+ }
641
+ };
642
+ this._onVectorSourceChange = () => {
643
+ if (!this._currentVectorSource ||
644
+ !this._currentDrawSource ||
645
+ !this._currentDrawSourceID) {
646
+ return;
647
+ }
648
+ const geojsonWriter = new GeoJSON({
649
+ featureProjection: this._Map.getView().getProjection(),
650
+ });
651
+ const features = this._currentVectorSource
652
+ .getFeatures()
653
+ .map(feature => geojsonWriter.writeFeatureObject(feature));
654
+ const updatedData = {
655
+ type: 'FeatureCollection',
656
+ features: features,
657
+ };
658
+ const updatedJGISLayerSource = {
659
+ name: this._currentDrawSource.name,
660
+ type: this._currentDrawSource.type,
661
+ parameters: {
662
+ data: updatedData,
663
+ },
664
+ };
665
+ this._currentDrawSource = updatedJGISLayerSource;
666
+ this._model.sharedModel.updateSource(this._currentDrawSourceID, updatedJGISLayerSource);
667
+ };
668
+ this._updateDrawSource = () => {
669
+ if (this._currentVectorSource) {
670
+ this._currentVectorSource.on('change', this._onVectorSourceChange);
671
+ }
672
+ };
673
+ this._updateInteractions = () => {
674
+ if (this._draw) {
675
+ this._removeDrawInteraction();
676
+ }
677
+ if (this._select) {
678
+ this._removeSelectInteraction();
679
+ }
680
+ if (this._modify) {
681
+ this._removeModifyInteraction();
682
+ }
683
+ if (this._snap) {
684
+ this._removeSnapInteraction();
685
+ }
686
+ this._draw = new Draw({
687
+ style: drawInteractionStyle,
688
+ type: this._currentDrawGeometry,
689
+ source: this._currentVectorSource,
690
+ });
691
+ this._draw.on('drawend', this._handleDrawEnd);
692
+ this._select = new Select();
693
+ this._modify = new Modify({
694
+ features: this._select.getFeatures(),
695
+ });
696
+ this._snap = new Snap({
697
+ source: this._currentVectorSource,
698
+ });
699
+ this._Map.addInteraction(this._draw);
700
+ this._Map.addInteraction(this._select);
701
+ this._Map.addInteraction(this._modify);
702
+ this._Map.addInteraction(this._snap);
703
+ this._draw.setActive(true);
704
+ this._select.setActive(false);
705
+ this._modify.setActive(false);
706
+ this._snap.setActive(true);
707
+ };
708
+ this._handleDrawEnd = (event) => {
709
+ const feature = event.feature;
710
+ feature.set('_id', UUID.uuid4());
711
+ feature.set('_createdAt', new Date().toISOString());
712
+ feature.set('_creatorClientId', this._model.getClientId().toString());
713
+ feature.set('_fromDrawTool', true);
714
+ feature.set('Label', 'New Label');
715
+ };
716
+ this._editVectorLayer = () => {
717
+ this._getDrawSourceFromSelectedLayer();
718
+ if (!this._currentDrawLayerID) {
719
+ return;
720
+ }
721
+ this._currentVectorSource = this._getVectorSourceFromLayerID(this._currentDrawLayerID);
722
+ if (!this._currentVectorSource || !this._currentDrawGeometry) {
723
+ return;
724
+ }
725
+ this._updateInteractions(); /* remove previous interactions and instantiate new ones */
726
+ this._updateDrawSource(); /*add new features, update source and get changes reported to the JGIS Document in geoJSON format */
727
+ };
728
+ this._removeDrawInteraction = () => {
729
+ this._draw.setActive(false);
730
+ this._Map.removeInteraction(this._draw);
731
+ };
732
+ this._removeSelectInteraction = () => {
733
+ this._select.setActive(false);
734
+ this._Map.removeInteraction(this._select);
735
+ };
736
+ this._removeSnapInteraction = () => {
737
+ this._snap.setActive(false);
738
+ this._Map.removeInteraction(this._snap);
739
+ };
740
+ this._removeModifyInteraction = () => {
741
+ this._modify.setActive(false);
742
+ this._Map.removeInteraction(this._modify);
743
+ };
744
+ /**
745
+ * Shared source update wrapper for child components that need to mutate a
746
+ * source and refresh corresponding map layers.
747
+ */
748
+ this.persistAndRefreshSource = async (id, source) => {
749
+ this._model.sharedModel.updateSource(id, source);
750
+ await this.updateSource(id, source);
751
+ };
711
752
  this._isPositionInitialized = false;
712
753
  this.divRef = React.createRef(); // Reference of render div
713
754
  this.mainViewRef = React.createRef();
@@ -717,6 +758,9 @@ export class MainView extends React.Component {
717
758
  this._ready = false;
718
759
  this._sourceToLayerMap = new Map();
719
760
  this._originalFeatures = {};
761
+ this._highlightLayerRef = { current: null };
762
+ this._addLayerForPanels = (id, layer, index) => this.addLayer(id, layer, index);
763
+ this._removeLayerForPanels = (id) => this.removeLayer(id);
720
764
  this._featurePropertyCache = new Map();
721
765
  this._isSpectaPresentationInitialized = false;
722
766
  this._storyScrollHandler = null;
@@ -725,6 +769,7 @@ export class MainView extends React.Component {
725
769
  this._state = props.state;
726
770
  this._formSchemaRegistry = props.formSchemaRegistry;
727
771
  this._annotationModel = props.annotationModel;
772
+ this._loggerRegistry = props.loggerRegistry;
728
773
  // Enforce the map to take the full available width in the case of Jupyter Notebook viewer
729
774
  const el = document.getElementById('main-panel');
730
775
  if (el) {
@@ -747,31 +792,39 @@ export class MainView extends React.Component {
747
792
  this._mainViewModel = this.props.viewModel;
748
793
  this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this);
749
794
  this._model = this._mainViewModel.jGISModel;
795
+ this._patchGeoJSONFeatureProperties = createGeoJSONFeaturePatcher({
796
+ model: this._model,
797
+ persistAndRefreshSource: this.persistAndRefreshSource,
798
+ });
750
799
  this._model.themeChanged.connect(this._handleThemeChange, this);
751
800
  this._model.sharedOptionsChanged.connect(this._onSharedOptionsChanged, this);
752
- this._model.clientStateChanged.connect(this._onClientSharedStateChanged, this);
801
+ this._model.temporalControllerActiveChanged.connect(this._handleTemporalControllerActiveChanged, this);
802
+ const remoteUserSignals = [
803
+ this._model.remoteUserChanged,
804
+ this._model.viewportStateChanged,
805
+ ];
806
+ remoteUserSignals.forEach(signal => signal.connect(this._handleRemoteUserChanged, this));
807
+ this._model.pointerChanged.connect(this._handlePointerChanged, this);
808
+ this._model.selectedChanged.connect(this._handleTemporalControllerActiveChanged, this);
809
+ this._model.selectedChanged.connect(this._handleSelectedChanged, this);
753
810
  this._model.sharedLayersChanged.connect(this._onLayersChanged, this);
754
811
  this._model.sharedLayerTreeChanged.connect(this._onLayerTreeChange, this);
755
812
  this._model.sharedSourcesChanged.connect(this._onSourcesChange, this);
756
813
  this._model.sharedModel.changed.connect(this._onSharedModelStateChange);
757
814
  this._model.sharedMetadataChanged.connect(this._onSharedMetadataChanged, this);
815
+ this._model.identifiedFeaturesChanged.connect(this._handleIdentifiedFeaturesChanged, this);
758
816
  this._model.zoomToPositionSignal.connect(this._onZoomToPosition, this);
759
817
  this._model.settingsChanged.connect(this._onSettingsChanged, this);
760
818
  this._model.updateLayerSignal.connect(this._triggerLayerUpdate, this);
761
819
  this._model.addFeatureAsMsSignal.connect(this._convertFeatureToMs, this);
762
820
  this._model.geolocationChanged.connect(this._handleGeolocationChanged, this);
821
+ // Keep draw editing UI/interactions in sync with the shared editing mode.
822
+ this._model.editingVectorLayerChanged.connect(this._updateEditingVectorLayer, this);
763
823
  this._model.flyToGeometrySignal.connect(this.flyToGeometry, this);
764
824
  this._model.highlightFeatureSignal.connect(this.highlightFeatureOnMap, this);
765
825
  Promise.resolve().then(() => {
766
826
  this._syncSettingsFromRegistry();
767
827
  });
768
- // Watch isIdentifying and clear the highlight when Identify Tool is turned off
769
- this._model.sharedModel.awareness.on('change', () => {
770
- var _a;
771
- if (this._model.currentMode !== 'identifying' && this._highlightLayer) {
772
- (_a = this._highlightLayer.getSource()) === null || _a === void 0 ? void 0 : _a.clear();
773
- }
774
- });
775
828
  this.state = {
776
829
  id: this._mainViewModel.id,
777
830
  lightTheme: isLightTheme(),
@@ -784,9 +837,13 @@ export class MainView extends React.Component {
784
837
  loadingErrors: [],
785
838
  displayTemporalController: false,
786
839
  filterStates: {},
840
+ editingVectorLayer: false,
841
+ drawGeometryLabel: '',
787
842
  jgisSettings: this._model.jgisSettings,
788
843
  isSpectaPresentation: this._model.isSpectaMode(),
789
844
  initialLayersReady: false,
845
+ identifyFeatureFloatersVersion: 0,
846
+ segmentTransition: null,
790
847
  };
791
848
  this._sources = [];
792
849
  this._loadingLayers = new Set();
@@ -798,6 +855,10 @@ export class MainView extends React.Component {
798
855
  }
799
856
  async componentDidMount() {
800
857
  var _a;
858
+ if (this._loggerRegistry) {
859
+ const logger = this._loggerRegistry.getLogger(this._model.filePath);
860
+ logger.level = 'debug';
861
+ }
801
862
  window.addEventListener('resize', this._handleWindowResize);
802
863
  const options = this._model.getOptions();
803
864
  const projection = (_a = options.projection) !== null && _a !== void 0 ? _a : DEFAULT_PROJECTION;
@@ -806,6 +867,10 @@ export class MainView extends React.Component {
806
867
  : [0, 0];
807
868
  const zoom = options.zoom !== undefined ? options.zoom : 1;
808
869
  await this.generateMap(center, zoom, projection);
870
+ this._handleRemoteUserChanged();
871
+ this._handlePointerChanged();
872
+ this._handleTemporalControllerActiveChanged();
873
+ this._handleSelectedChanged();
809
874
  this._mainViewModel.initSignal();
810
875
  if (window.jupytergisMaps !== undefined && this._documentPath) {
811
876
  window.jupytergisMaps[this._documentPath] = this._Map;
@@ -828,7 +893,16 @@ export class MainView extends React.Component {
828
893
  this._model.themeChanged.disconnect(this._handleThemeChange, this);
829
894
  this._model.settingsChanged.disconnect(this._onSettingsChanged, this);
830
895
  this._model.sharedOptionsChanged.disconnect(this._onSharedOptionsChanged, this);
831
- this._model.clientStateChanged.disconnect(this._onClientSharedStateChanged, this);
896
+ this._model.temporalControllerActiveChanged.disconnect(this._handleTemporalControllerActiveChanged, this);
897
+ const remoteUserSignals = [
898
+ this._model.remoteUserChanged,
899
+ this._model.viewportStateChanged,
900
+ ];
901
+ remoteUserSignals.forEach(signal => signal.disconnect(this._handleRemoteUserChanged, this));
902
+ this._model.pointerChanged.disconnect(this._handlePointerChanged, this);
903
+ this._model.selectedChanged.disconnect(this._handleTemporalControllerActiveChanged, this);
904
+ this._model.selectedChanged.disconnect(this._handleSelectedChanged, this);
905
+ this._model.identifiedFeaturesChanged.disconnect(this._handleIdentifiedFeaturesChanged, this);
832
906
  // Clean up story scroll listener
833
907
  this._cleanupStoryScrollListener();
834
908
  this._mainViewModel.dispose();
@@ -836,17 +910,13 @@ export class MainView extends React.Component {
836
910
  async generateMap(center, zoom, projection = DEFAULT_PROJECTION) {
837
911
  const layers = this._model.getLayers();
838
912
  this._initialLayersCount = Object.values(layers).filter(layer => layer.type !== 'StorySegmentLayer').length;
839
- const scaleLine = new ScaleLine({
840
- target: this.controlsToolbarRef.current || undefined,
841
- });
842
- const fullScreen = new FullScreen({
843
- target: this.controlsToolbarRef.current || undefined,
844
- });
845
- const controls = [scaleLine, fullScreen];
913
+ const controlsToolbar = this.controlsToolbarRef.current || undefined;
914
+ const controls = [new ScaleLine({ target: controlsToolbar })];
915
+ if (!this._model.isSpectaMode()) {
916
+ controls.push(new FullScreen({ target: controlsToolbar }));
917
+ }
846
918
  if (this._model.jgisSettings.zoomButtonsEnabled) {
847
- this._zoomControl = new Zoom({
848
- target: this.controlsToolbarRef.current || undefined,
849
- });
919
+ this._zoomControl = new Zoom({ target: controlsToolbar });
850
920
  controls.push(this._zoomControl);
851
921
  }
852
922
  if (this.divRef.current) {
@@ -892,9 +962,7 @@ export class MainView extends React.Component {
892
962
  this._Map.addInteraction(dragAndDropInteraction);
893
963
  this.createSelectInteraction();
894
964
  const view = this._Map.getView();
895
- view.on('change:center', () => this._updateCenter());
896
- // TODO: Note for the future, will need to update listeners if view changes
897
- view.on('change:center', throttle(() => {
965
+ const syncViewportThrottled = throttle(() => {
898
966
  var _a;
899
967
  // Not syncing center if following someone else
900
968
  if ((_a = this._model.localState) === null || _a === void 0 ? void 0 : _a.remoteUser) {
@@ -906,18 +974,30 @@ export class MainView extends React.Component {
906
974
  if (!center || !zoom) {
907
975
  return;
908
976
  }
977
+ const currentExtent = view.calculateExtent(this._Map.getSize());
909
978
  this._model.syncViewport({
910
979
  coordinates: {
911
980
  x: center[0],
912
981
  y: center[1],
913
982
  },
914
983
  zoom,
984
+ extent: [
985
+ currentExtent[0],
986
+ currentExtent[1],
987
+ currentExtent[2],
988
+ currentExtent[3],
989
+ ],
915
990
  }, this._mainViewModel.id);
916
- }));
991
+ }, 200);
992
+ view.on('change:center', () => {
993
+ this._updateCenter();
994
+ syncViewportThrottled();
995
+ });
917
996
  this._Map.on('postrender', () => {
918
997
  if (this.state.annotations) {
919
998
  this._updateAnnotation();
920
999
  }
1000
+ this._updateFeatureFloaters();
921
1001
  });
922
1002
  this._Map.on('moveend', () => {
923
1003
  var _a;
@@ -981,241 +1061,359 @@ export class MainView extends React.Component {
981
1061
  * @param source - the source object.
982
1062
  */
983
1063
  async addSource(id, source) {
984
- var _a, _b, _c;
1064
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1065
+ this._log('info', `Loading source "${(_a = source.name) !== null && _a !== void 0 ? _a : id}" (${source.type})`);
985
1066
  let newSource;
986
- switch (source.type) {
987
- case 'RasterSource': {
988
- const sourceParameters = source.parameters;
989
- const pmTiles = sourceParameters.url.endsWith('.pmtiles') ||
990
- sourceParameters.url.endsWith('pmtiles.gz');
991
- const url = this.computeSourceUrl(source);
992
- if (!pmTiles) {
993
- newSource = new XYZSource({
1067
+ try {
1068
+ switch (source.type) {
1069
+ case 'RasterSource': {
1070
+ const sourceParameters = source.parameters;
1071
+ const pmTiles = sourceParameters.url.endsWith('.pmtiles') ||
1072
+ sourceParameters.url.endsWith('pmtiles.gz');
1073
+ const url = this.computeSourceUrl(source);
1074
+ if (!pmTiles) {
1075
+ newSource = new XYZSource({
1076
+ interpolate: sourceParameters.interpolate,
1077
+ attributions: sourceParameters.attribution,
1078
+ minZoom: sourceParameters.minZoom,
1079
+ maxZoom: sourceParameters.maxZoom,
1080
+ tileSize: 256,
1081
+ url: url,
1082
+ });
1083
+ }
1084
+ else {
1085
+ newSource = new PMTilesRasterSource({
1086
+ interpolate: sourceParameters.interpolate,
1087
+ attributions: sourceParameters.attribution,
1088
+ tileSize: 256,
1089
+ url: url,
1090
+ });
1091
+ }
1092
+ break;
1093
+ }
1094
+ case 'RasterDemSource': {
1095
+ const sourceParameters = source.parameters;
1096
+ newSource = new ImageTileSource({
994
1097
  interpolate: sourceParameters.interpolate,
1098
+ url: this.computeSourceUrl(source),
995
1099
  attributions: sourceParameters.attribution,
996
- minZoom: sourceParameters.minZoom,
997
- maxZoom: sourceParameters.maxZoom,
998
- tileSize: 256,
999
- url: url,
1000
1100
  });
1101
+ break;
1001
1102
  }
1002
- else {
1003
- newSource = new PMTilesRasterSource({
1004
- interpolate: sourceParameters.interpolate,
1005
- attributions: sourceParameters.attribution,
1006
- tileSize: 256,
1007
- url: url,
1103
+ case 'VectorTileSource': {
1104
+ const sourceParameters = source.parameters;
1105
+ const pmTiles = sourceParameters.url.endsWith('.pmtiles') ||
1106
+ sourceParameters.url.endsWith('pmtiles.gz');
1107
+ const url = this.computeSourceUrl(source);
1108
+ if (!pmTiles) {
1109
+ const vtSourceOptions = {
1110
+ attributions: sourceParameters.attribution,
1111
+ minZoom: sourceParameters.minZoom,
1112
+ maxZoom: sourceParameters.maxZoom,
1113
+ url: url,
1114
+ format: new MVT({
1115
+ featureClass: RenderFeature,
1116
+ }),
1117
+ };
1118
+ if (sourceParameters.useProxy) {
1119
+ const extraHeaders = (_b = sourceParameters.httpHeaders) !== null && _b !== void 0 ? _b : {};
1120
+ const headersParam = Object.keys(extraHeaders).length > 0
1121
+ ? `&headers=${encodeURIComponent(JSON.stringify(extraHeaders))}`
1122
+ : '';
1123
+ const proxyBase = isJupyterLite()
1124
+ ? `${this._model.jgisSettings.proxyUrl}/`
1125
+ : `${INTERNAL_PROXY_BASE}`;
1126
+ vtSourceOptions.tileLoadFunction = (tile, tileUrl) => {
1127
+ const vtTile = tile;
1128
+ const proxyUrl = `${proxyBase}?url=${encodeURIComponent(tileUrl)}${headersParam}`;
1129
+ vtTile.setLoader((extent, _resolution, projection) => {
1130
+ return fetch(proxyUrl)
1131
+ .then(response => {
1132
+ if (!response.ok) {
1133
+ throw new Error(`Tile proxy request failed: ${response.status} ${response.statusText}`);
1134
+ }
1135
+ return response.arrayBuffer();
1136
+ })
1137
+ .then(data => {
1138
+ const features = vtTile.getFormat().readFeatures(data, {
1139
+ extent,
1140
+ featureProjection: projection,
1141
+ });
1142
+ vtTile.setFeatures(features);
1143
+ this._log('debug', `Proxy tile loaded: ${tileUrl}`);
1144
+ return features;
1145
+ })
1146
+ .catch((err) => {
1147
+ this._log('error', `Proxy tile error for ${tileUrl}: ${err.message}`);
1148
+ tile.setState(TileState.ERROR);
1149
+ return [];
1150
+ });
1151
+ });
1152
+ };
1153
+ }
1154
+ newSource = new VectorTileSource(vtSourceOptions);
1155
+ }
1156
+ else {
1157
+ newSource = new PMTilesVectorSource({
1158
+ attributions: sourceParameters.attribution,
1159
+ url: url,
1160
+ });
1161
+ }
1162
+ newSource.on('tileloadend', (event) => {
1163
+ const tile = event.tile;
1164
+ const features = tile.getFeatures();
1165
+ if (features && features.length > 0) {
1166
+ this._model.syncTileFeatures({
1167
+ sourceId: id,
1168
+ features,
1169
+ });
1170
+ }
1008
1171
  });
1172
+ break;
1009
1173
  }
1010
- break;
1011
- }
1012
- case 'RasterDemSource': {
1013
- const sourceParameters = source.parameters;
1014
- newSource = new ImageTileSource({
1015
- interpolate: sourceParameters.interpolate,
1016
- url: this.computeSourceUrl(source),
1017
- attributions: sourceParameters.attribution,
1018
- });
1019
- break;
1020
- }
1021
- case 'VectorTileSource': {
1022
- const sourceParameters = source.parameters;
1023
- const pmTiles = sourceParameters.url.endsWith('.pmtiles') ||
1024
- sourceParameters.url.endsWith('pmtiles.gz');
1025
- const url = this.computeSourceUrl(source);
1026
- if (!pmTiles) {
1027
- newSource = new VectorTileSource({
1028
- attributions: sourceParameters.attribution,
1029
- minZoom: sourceParameters.minZoom,
1030
- maxZoom: sourceParameters.maxZoom,
1031
- url: url,
1032
- format: new MVT({
1033
- featureClass: RenderFeature,
1034
- }),
1174
+ case 'OpenEOTileSource': {
1175
+ const sourceParameters = source.parameters;
1176
+ newSource = new OpenEOTileSource({
1177
+ connectionInfo: {
1178
+ url: sourceParameters.serverUrl,
1179
+ authBearer: sourceParameters.authBearer,
1180
+ },
1181
+ processGraph: sourceParameters.processGraph,
1035
1182
  });
1183
+ break;
1036
1184
  }
1037
- else {
1038
- newSource = new PMTilesVectorSource({
1039
- attributions: sourceParameters.attribution,
1040
- url: url,
1185
+ case 'GeoJSONSource': {
1186
+ const data = ((_c = source.parameters) === null || _c === void 0 ? void 0 : _c.data) ||
1187
+ (await loadFile({
1188
+ filepath: (_d = source.parameters) === null || _d === void 0 ? void 0 : _d.path,
1189
+ type: 'GeoJSONSource',
1190
+ model: this._model,
1191
+ }));
1192
+ const format = new GeoJSON({
1193
+ featureProjection: this._Map.getView().getProjection(),
1194
+ });
1195
+ const featureArray = format.readFeatures(data, {
1196
+ featureProjection: this._Map.getView().getProjection(),
1197
+ });
1198
+ const featureCollection = new Collection(featureArray);
1199
+ featureCollection.forEach(feature => {
1200
+ feature.setId(getUid(feature));
1041
1201
  });
1202
+ newSource = new VectorSource({
1203
+ features: featureCollection,
1204
+ });
1205
+ break;
1042
1206
  }
1043
- newSource.on('tileloadend', (event) => {
1044
- const tile = event.tile;
1045
- const features = tile.getFeatures();
1046
- if (features && features.length > 0) {
1047
- this._model.syncTileFeatures({
1048
- sourceId: id,
1049
- features,
1050
- });
1051
- }
1052
- });
1053
- break;
1054
- }
1055
- case 'GeoJSONSource': {
1056
- const data = ((_a = source.parameters) === null || _a === void 0 ? void 0 : _a.data) ||
1057
- (await loadFile({
1058
- filepath: (_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path,
1059
- type: 'GeoJSONSource',
1207
+ case 'ShapefileSource': {
1208
+ const parameters = source.parameters;
1209
+ const geojson = await loadFile({
1210
+ filepath: parameters.path,
1211
+ type: 'ShapefileSource',
1060
1212
  model: this._model,
1213
+ });
1214
+ const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
1215
+ const format = new GeoJSON();
1216
+ newSource = new VectorSource({
1217
+ features: format.readFeatures(geojsonData, {
1218
+ dataProjection: 'EPSG:4326',
1219
+ featureProjection: this._Map.getView().getProjection(),
1220
+ }),
1221
+ });
1222
+ break;
1223
+ }
1224
+ case 'ImageSource': {
1225
+ const sourceParameters = source.parameters;
1226
+ // Convert lon/lat array to extent
1227
+ // Get lon/lat from source coordinates
1228
+ const leftSide = Math.min(...sourceParameters.coordinates.map(corner => corner[0]));
1229
+ const bottomSide = Math.min(...sourceParameters.coordinates.map(corner => corner[1]));
1230
+ const rightSide = Math.max(...sourceParameters.coordinates.map(corner => corner[0]));
1231
+ const topSide = Math.max(...sourceParameters.coordinates.map(corner => corner[1]));
1232
+ // Convert lon/lat to OpenLayer coordinates
1233
+ const topLeft = fromLonLat([leftSide, topSide]);
1234
+ const bottomRight = fromLonLat([rightSide, bottomSide]);
1235
+ // Get extent from coordinates
1236
+ const minX = topLeft[0];
1237
+ const maxY = topLeft[1];
1238
+ const maxX = bottomRight[0];
1239
+ const minY = bottomRight[1];
1240
+ const extent = [minX, minY, maxX, maxY];
1241
+ const imageUrl = await loadFile({
1242
+ filepath: sourceParameters.path,
1243
+ type: 'ImageSource',
1244
+ model: this._model,
1245
+ });
1246
+ newSource = new Static({
1247
+ interpolate: sourceParameters.interpolate,
1248
+ imageExtent: extent,
1249
+ url: imageUrl,
1250
+ crossOrigin: '',
1251
+ });
1252
+ break;
1253
+ }
1254
+ case 'VideoSource': {
1255
+ this._log('warning', 'Video Tiles not supported with Open Layers');
1256
+ break;
1257
+ }
1258
+ case 'GeoTiffSource': {
1259
+ const sourceParameters = source.parameters;
1260
+ const addNoData = (url) => {
1261
+ return Object.assign(Object.assign({}, url), { nodata: 0 });
1262
+ };
1263
+ const sources = await Promise.all(sourceParameters.urls.map(async (sourceInfo) => {
1264
+ var _a, _b, _c, _d;
1265
+ const isRemote = ((_a = sourceInfo.url) === null || _a === void 0 ? void 0 : _a.startsWith('http://')) ||
1266
+ ((_b = sourceInfo.url) === null || _b === void 0 ? void 0 : _b.startsWith('https://'));
1267
+ const isDataUrl = (_c = sourceInfo.url) === null || _c === void 0 ? void 0 : _c.startsWith('data:');
1268
+ if (isRemote) {
1269
+ return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, url: sourceInfo.url });
1270
+ }
1271
+ else if (isDataUrl) {
1272
+ // Inline base64 GeoTIFF embedded in the .jGIS doc.
1273
+ const blob = await (await fetch(sourceInfo.url)).blob();
1274
+ return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, url: URL.createObjectURL(blob) });
1275
+ }
1276
+ else {
1277
+ const geotiff = await loadFile({
1278
+ filepath: (_d = sourceInfo.url) !== null && _d !== void 0 ? _d : '',
1279
+ type: 'GeoTiffSource',
1280
+ model: this._model,
1281
+ });
1282
+ return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, geotiff, url: URL.createObjectURL(geotiff.file) });
1283
+ }
1061
1284
  }));
1062
- const format = new GeoJSON({
1063
- featureProjection: this._Map.getView().getProjection(),
1064
- });
1065
- const featureArray = format.readFeatures(data, {
1066
- featureProjection: this._Map.getView().getProjection(),
1067
- });
1068
- const featureCollection = new Collection(featureArray);
1069
- featureCollection.forEach(feature => {
1070
- feature.setId(getUid(feature));
1071
- });
1072
- newSource = new VectorSource({
1073
- features: featureCollection,
1074
- });
1075
- break;
1076
- }
1077
- case 'ShapefileSource': {
1078
- const parameters = source.parameters;
1079
- const geojson = await loadFile({
1080
- filepath: parameters.path,
1081
- type: 'ShapefileSource',
1082
- model: this._model,
1083
- });
1084
- const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
1085
- const format = new GeoJSON();
1086
- newSource = new VectorSource({
1087
- features: format.readFeatures(geojsonData, {
1088
- dataProjection: 'EPSG:4326',
1089
- featureProjection: this._Map.getView().getProjection(),
1090
- }),
1091
- });
1092
- break;
1093
- }
1094
- case 'ImageSource': {
1095
- const sourceParameters = source.parameters;
1096
- // Convert lon/lat array to extent
1097
- // Get lon/lat from source coordinates
1098
- const leftSide = Math.min(...sourceParameters.coordinates.map(corner => corner[0]));
1099
- const bottomSide = Math.min(...sourceParameters.coordinates.map(corner => corner[1]));
1100
- const rightSide = Math.max(...sourceParameters.coordinates.map(corner => corner[0]));
1101
- const topSide = Math.max(...sourceParameters.coordinates.map(corner => corner[1]));
1102
- // Convert lon/lat to OpenLayer coordinates
1103
- const topLeft = fromLonLat([leftSide, topSide]);
1104
- const bottomRight = fromLonLat([rightSide, bottomSide]);
1105
- // Get extent from coordinates
1106
- const minX = topLeft[0];
1107
- const maxY = topLeft[1];
1108
- const maxX = bottomRight[0];
1109
- const minY = bottomRight[1];
1110
- const extent = [minX, minY, maxX, maxY];
1111
- const imageUrl = await loadFile({
1112
- filepath: sourceParameters.path,
1113
- type: 'ImageSource',
1114
- model: this._model,
1115
- });
1116
- newSource = new Static({
1117
- interpolate: sourceParameters.interpolate,
1118
- imageExtent: extent,
1119
- url: imageUrl,
1120
- crossOrigin: '',
1121
- });
1122
- break;
1123
- }
1124
- case 'VideoSource': {
1125
- console.warn('Video Tiles not supported with Open Layers');
1126
- break;
1127
- }
1128
- case 'GeoTiffSource': {
1129
- const sourceParameters = source.parameters;
1130
- const addNoData = (url) => {
1131
- return Object.assign(Object.assign({}, url), { nodata: 0 });
1132
- };
1133
- const sources = await Promise.all(sourceParameters.urls.map(async (sourceInfo) => {
1134
- var _a, _b, _c;
1135
- const isRemote = ((_a = sourceInfo.url) === null || _a === void 0 ? void 0 : _a.startsWith('http://')) ||
1136
- ((_b = sourceInfo.url) === null || _b === void 0 ? void 0 : _b.startsWith('https://'));
1137
- if (isRemote) {
1138
- return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, url: sourceInfo.url });
1285
+ newSource = new GeoTIFFSource({
1286
+ interpolate: sourceParameters.interpolate,
1287
+ sources,
1288
+ normalize: sourceParameters.normalize,
1289
+ wrapX: sourceParameters.wrapX,
1290
+ });
1291
+ break;
1292
+ }
1293
+ case 'GeoPackageVectorSource': {
1294
+ const sourceParameters = source.parameters;
1295
+ if (!sourceParameters) {
1296
+ throw new Error('GeoPackageSource has no parameters');
1139
1297
  }
1140
- else {
1141
- const geotiff = await loadFile({
1142
- filepath: (_c = sourceInfo.url) !== null && _c !== void 0 ? _c : '',
1143
- type: 'GeoTiffSource',
1144
- model: this._model,
1145
- });
1146
- return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, geotiff, url: URL.createObjectURL(geotiff.file) });
1298
+ const tableMap = await loadFile({
1299
+ filepath: sourceParameters.path,
1300
+ type: 'GeoPackageVectorSource',
1301
+ model: this._model,
1302
+ });
1303
+ const table = tableMap[sourceParameters.tables];
1304
+ const vectorSource = table.source;
1305
+ vectorSource['projection'] = getProjection(sourceParameters.projection);
1306
+ newSource = vectorSource;
1307
+ break;
1308
+ }
1309
+ case 'GeoPackageRasterSource': {
1310
+ const sourceParameters = source.parameters;
1311
+ if (!sourceParameters) {
1312
+ throw new Error('GeoPackageSource has no parameters');
1147
1313
  }
1148
- }));
1149
- newSource = new GeoTIFFSource({
1150
- interpolate: sourceParameters.interpolate,
1151
- sources,
1152
- normalize: sourceParameters.normalize,
1153
- wrapX: sourceParameters.wrapX,
1154
- });
1155
- break;
1156
- }
1157
- case 'GeoParquetSource': {
1158
- const parameters = source.parameters;
1159
- const geojson = await loadFile({
1160
- filepath: parameters.path,
1161
- type: 'GeoParquetSource',
1162
- model: this._model,
1163
- });
1164
- const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
1165
- const format = new GeoJSON();
1166
- newSource = new VectorSource({
1167
- features: format.readFeatures(geojsonData, {
1168
- dataProjection: parameters.projection,
1169
- featureProjection: this._Map.getView().getProjection(),
1170
- }),
1171
- });
1172
- break;
1173
- }
1174
- case 'MarkerSource': {
1175
- const parameters = source.parameters;
1176
- const point = new Point(parameters.feature.coords);
1177
- const marker = new Feature({
1178
- type: 'icon',
1179
- geometry: point,
1180
- });
1181
- // Replace color placeholder in SVG with the parameter color
1182
- const markerColor = parameters.color || '#3463a0';
1183
- const svgString = markerIcon.svgstr
1184
- .replace('{{COLOR}}', markerColor)
1185
- .replace('<svg', '<svg width="128" height="128"');
1186
- const iconStyle = new Style({
1187
- image: new Icon({
1188
- src: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`,
1189
- scale: 0.25,
1190
- anchor: [0.5, 1],
1191
- anchorXUnits: 'fraction',
1192
- anchorYUnits: 'fraction',
1193
- }),
1194
- });
1195
- marker.setStyle(iconStyle);
1196
- newSource = new VectorSource({
1197
- features: [marker],
1198
- });
1199
- break;
1200
- }
1201
- case 'WmsTileSource': {
1202
- const sourceParameters = source.parameters;
1203
- const url = sourceParameters.url;
1204
- const selectedLayer = (_c = sourceParameters === null || sourceParameters === void 0 ? void 0 : sourceParameters.params) === null || _c === void 0 ? void 0 : _c.layers;
1205
- newSource = new TileWMSSource({
1206
- attributions: sourceParameters === null || sourceParameters === void 0 ? void 0 : sourceParameters.attribution,
1207
- url,
1208
- params: {
1209
- LAYERS: selectedLayer,
1210
- TILED: true,
1211
- },
1212
- });
1213
- break;
1314
+ const tableMap = await loadFile({
1315
+ filepath: sourceParameters.path,
1316
+ type: 'GeoPackageRasterSource',
1317
+ model: this._model,
1318
+ });
1319
+ const { gpr, tileDao } = tableMap[sourceParameters.tables];
1320
+ const rasterSource = new XYZSource({
1321
+ minZoom: (_e = sourceParameters.minZoom) !== null && _e !== void 0 ? _e : tileDao.minWebMapZoom,
1322
+ maxZoom: (_f = sourceParameters.maxZoom) !== null && _f !== void 0 ? _f : tileDao.maxWebMapZoom,
1323
+ interpolate: sourceParameters.interpolate,
1324
+ url: '{z},{x},{y}',
1325
+ tileLoadFunction(tile, src) {
1326
+ const [z, x, y] = src.split(',').map(Number);
1327
+ gpr
1328
+ .getTile(x, y, z)
1329
+ .then((dataUri) => (tile.getImage().src = dataUri));
1330
+ },
1331
+ attributions: sourceParameters.attribution,
1332
+ });
1333
+ newSource = rasterSource;
1334
+ break;
1335
+ }
1336
+ case 'GeoParquetSource': {
1337
+ const parameters = source.parameters;
1338
+ const geojson = await loadFile({
1339
+ filepath: parameters.path,
1340
+ type: 'GeoParquetSource',
1341
+ model: this._model,
1342
+ });
1343
+ const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
1344
+ const format = new GeoJSON();
1345
+ newSource = new VectorSource({
1346
+ features: format.readFeatures(geojsonData, {
1347
+ dataProjection: parameters.projection,
1348
+ featureProjection: this._Map.getView().getProjection(),
1349
+ }),
1350
+ });
1351
+ break;
1352
+ }
1353
+ case 'MarkerSource': {
1354
+ const parameters = source.parameters;
1355
+ const point = new Point(parameters.feature.coords);
1356
+ const marker = new Feature({
1357
+ type: 'icon',
1358
+ geometry: point,
1359
+ });
1360
+ // Replace color placeholder in SVG with the parameter color
1361
+ const markerColor = parameters.color || '#3463a0';
1362
+ const svgString = markerIcon.svgstr
1363
+ .replace('{{COLOR}}', markerColor)
1364
+ .replace('<svg', '<svg width="128" height="128"');
1365
+ const iconStyle = new Style({
1366
+ image: new Icon({
1367
+ src: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`,
1368
+ scale: 0.25,
1369
+ anchor: [0.5, 1],
1370
+ anchorXUnits: 'fraction',
1371
+ anchorYUnits: 'fraction',
1372
+ }),
1373
+ });
1374
+ marker.setStyle(iconStyle);
1375
+ newSource = new VectorSource({
1376
+ features: [marker],
1377
+ });
1378
+ break;
1379
+ }
1380
+ case 'WmsTileSource': {
1381
+ const sourceParameters = source.parameters;
1382
+ const url = sourceParameters.url;
1383
+ const selectedLayer = (_g = sourceParameters === null || sourceParameters === void 0 ? void 0 : sourceParameters.params) === null || _g === void 0 ? void 0 : _g.layers;
1384
+ newSource = new TileWMSSource({
1385
+ attributions: sourceParameters === null || sourceParameters === void 0 ? void 0 : sourceParameters.attribution,
1386
+ url,
1387
+ params: {
1388
+ LAYERS: selectedLayer,
1389
+ TILED: true,
1390
+ },
1391
+ });
1392
+ break;
1393
+ }
1214
1394
  }
1215
1395
  }
1396
+ catch (err) {
1397
+ this._log('error', `Failed to load source "${(_h = source.name) !== null && _h !== void 0 ? _h : id}" (${source.type}): ${err.message}`);
1398
+ return;
1399
+ }
1400
+ this._log('info', `Source "${(_j = source.name) !== null && _j !== void 0 ? _j : id}" (${source.type}) loaded successfully`);
1216
1401
  newSource.set('id', id);
1402
+ // Forward OL tile/feature load errors to the JupyterLab log console.
1403
+ // These errors (CORS failures, network errors, etc.) are written directly
1404
+ // by the browser to DevTools and cannot be captured by console patching —
1405
+ // OL's own events are the only reliable interception point.
1406
+ newSource.on('tileloaderror', (evt) => {
1407
+ var _a, _b, _c;
1408
+ const url = (_c = (_b = (_a = evt === null || evt === void 0 ? void 0 : evt.tile) === null || _a === void 0 ? void 0 : _a.getKey) === null || _b === void 0 ? void 0 : _b.call(_a)) !== null && _c !== void 0 ? _c : '';
1409
+ this._log('error', `Tile load error for source "${id}"${url ? ': ' + url : ''}`);
1410
+ });
1411
+ newSource.on('featuresloaderror', () => {
1412
+ this._log('error', `Features load error for source "${id}"`);
1413
+ });
1217
1414
  // _sources is a list of OpenLayers sources
1218
1415
  this._sources[id] = newSource;
1416
+ this._trackSourceExtZoom(id, newSource);
1219
1417
  }
1220
1418
  computeSourceUrl(source) {
1221
1419
  const parameters = source.parameters;
@@ -1286,8 +1484,11 @@ export class MainView extends React.Component {
1286
1484
  for (let targetLayerPosition = 0; targetLayerPosition < layerIds.length; targetLayerPosition++) {
1287
1485
  const layerId = layerIds[targetLayerPosition];
1288
1486
  const layer = this._model.sharedModel.getLayer(layerId);
1487
+ if (this._loadingLayers.has(layerId)) {
1488
+ continue;
1489
+ }
1289
1490
  if (!layer) {
1290
- console.warn(`Layer with ID ${layerId} does not exist in the shared model.`);
1491
+ this._log('warning', `Layer with ID ${layerId} does not exist in the shared model.`);
1291
1492
  continue;
1292
1493
  }
1293
1494
  const mapLayer = this.getLayer(layerId);
@@ -1319,7 +1520,7 @@ export class MainView extends React.Component {
1319
1520
  * @returns - the map layer.
1320
1521
  */
1321
1522
  async _buildMapLayer(id, layer) {
1322
- var _a, _b, _c;
1523
+ var _a, _b, _c, _d, _e;
1323
1524
  this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: true })));
1324
1525
  this._loadingLayers.add(id);
1325
1526
  let newMapLayer;
@@ -1354,12 +1555,23 @@ export class MainView extends React.Component {
1354
1555
  }
1355
1556
  case 'VectorLayer': {
1356
1557
  layerParameters = layer.parameters;
1357
- newMapLayer = new VectorLayer({
1358
- opacity: layerParameters.opacity,
1359
- visible: layer.visible,
1360
- source: this._sources[layerParameters.source],
1361
- style: this.vectorLayerStyleRuleBuilder(layer),
1362
- });
1558
+ if (Array.isArray((_b = layerParameters.symbologyState) === null || _b === void 0 ? void 0 : _b.layers)) {
1559
+ const olSource = this._sources[layerParameters.source];
1560
+ const grammarState = layerParameters.symbologyState;
1561
+ const rows = olSource instanceof VectorSource
1562
+ ? olSource.getFeatures().map(f => f.getProperties())
1563
+ : [];
1564
+ const featureValues = extractEncodingFieldValues(grammarState, rows);
1565
+ newMapLayer = grammarToOLLayer(layerParameters.symbologyState, olSource, layerParameters.opacity, layer.visible, featureValues);
1566
+ }
1567
+ else {
1568
+ newMapLayer = new VectorImageLayer({
1569
+ opacity: layerParameters.opacity,
1570
+ visible: layer.visible,
1571
+ source: this._sources[layerParameters.source],
1572
+ style: this.vectorLayerStyleRuleBuilder(layer),
1573
+ });
1574
+ }
1363
1575
  break;
1364
1576
  }
1365
1577
  case 'VectorTileLayer': {
@@ -1374,7 +1586,7 @@ export class MainView extends React.Component {
1374
1586
  }
1375
1587
  case 'HillshadeLayer': {
1376
1588
  layerParameters = layer.parameters;
1377
- newMapLayer = new WebGlTileLayer({
1589
+ newMapLayer = new GeoTiffLayer({
1378
1590
  opacity: 0.3,
1379
1591
  visible: layer.visible,
1380
1592
  source: this._sources[layerParameters.source],
@@ -1393,32 +1605,35 @@ export class MainView extends React.Component {
1393
1605
  });
1394
1606
  break;
1395
1607
  }
1396
- case 'WebGlLayer': {
1608
+ case 'OpenEOTileLayer': {
1397
1609
  layerParameters = layer.parameters;
1398
- // This is to handle python sending a None for the color
1399
- const layerOptions = {
1610
+ newMapLayer = new OpenEOTileLayer({
1400
1611
  opacity: layerParameters.opacity,
1401
1612
  visible: layer.visible,
1402
1613
  source: this._sources[layerParameters.source],
1403
- };
1404
- if (layerParameters.color) {
1405
- layerOptions['style'] = {
1406
- color: layerParameters.color,
1407
- };
1408
- }
1409
- newMapLayer = new WebGlTileLayer(layerOptions);
1614
+ });
1410
1615
  break;
1411
1616
  }
1412
- case 'HeatmapLayer': {
1617
+ case 'GeoTiffLayer': {
1413
1618
  layerParameters = layer.parameters;
1414
- newMapLayer = new HeatmapLayer({
1415
- opacity: layerParameters.opacity,
1416
- visible: layer.visible,
1417
- source: this._sources[layerParameters.source],
1418
- blur: (_b = layerParameters.blur) !== null && _b !== void 0 ? _b : 15,
1419
- radius: (_c = layerParameters.radius) !== null && _c !== void 0 ? _c : 8,
1420
- gradient: layerParameters.color,
1421
- });
1619
+ const geoTiffSource = this._sources[layerParameters.source];
1620
+ if (Array.isArray((_c = layerParameters.symbologyState) === null || _c === void 0 ? void 0 : _c.layers)) {
1621
+ newMapLayer = grammarToOLLayer(layerParameters.symbologyState, geoTiffSource, (_d = layerParameters.opacity) !== null && _d !== void 0 ? _d : 1, (_e = layer.visible) !== null && _e !== void 0 ? _e : true, [], true);
1622
+ }
1623
+ else {
1624
+ // This is to handle python sending a None for the color
1625
+ const layerOptions = {
1626
+ opacity: layerParameters.opacity,
1627
+ visible: layer.visible,
1628
+ source: geoTiffSource,
1629
+ };
1630
+ if (layerParameters.color) {
1631
+ layerOptions['style'] = {
1632
+ color: layerParameters.color,
1633
+ };
1634
+ }
1635
+ newMapLayer = new GeoTiffLayer(layerOptions);
1636
+ }
1422
1637
  break;
1423
1638
  }
1424
1639
  case 'StacLayer': {
@@ -1458,7 +1673,7 @@ export class MainView extends React.Component {
1458
1673
  var _a;
1459
1674
  const sourceProjection = (_a = newMapLayer.getSource()) === null || _a === void 0 ? void 0 : _a.getProjection();
1460
1675
  if (!sourceProjection) {
1461
- console.warn('Layer source projection is undefined or invalid');
1676
+ this._log('warning', 'Layer source projection is undefined or invalid');
1462
1677
  return;
1463
1678
  }
1464
1679
  const projectionCode = sourceProjection.getCode();
@@ -1466,7 +1681,7 @@ export class MainView extends React.Component {
1466
1681
  if (!isProjectionRegistered) {
1467
1682
  // Check if the projection exists in proj4list
1468
1683
  if (!proj4list[projectionCode]) {
1469
- console.warn(`Projection code '${projectionCode}' not found in proj4list`);
1684
+ this._log('warning', `Projection code '${projectionCode}' not found in proj4list`);
1470
1685
  return;
1471
1686
  }
1472
1687
  try {
@@ -1474,7 +1689,7 @@ export class MainView extends React.Component {
1474
1689
  register(proj4);
1475
1690
  }
1476
1691
  catch (error) {
1477
- console.warn(`Failed to register projection '${projectionCode}'. Error: ${error.message}`);
1692
+ this._log('warning', `Failed to register projection '${projectionCode}'. Error: ${error.message}`);
1478
1693
  return;
1479
1694
  }
1480
1695
  }
@@ -1499,12 +1714,14 @@ export class MainView extends React.Component {
1499
1714
  const numLayers = this._Map.getLayers().getLength();
1500
1715
  const safeIndex = Math.min(index, numLayers);
1501
1716
  this._Map.getLayers().insertAt(safeIndex, newMapLayer);
1717
+ this._trackLayerViewState(id, newMapLayer);
1502
1718
  // doing +1 instead of calling method again
1503
1719
  if (!this.state.initialLayersReady &&
1504
1720
  numLayers + 1 === this._initialLayersCount) {
1505
1721
  this.setState(old => (Object.assign(Object.assign({}, old), { initialLayersReady: true })));
1506
1722
  }
1507
1723
  }
1724
+ this._model.syncSelected({ [id]: { type: 'layer' } }, this._model.getClientId().toString());
1508
1725
  }
1509
1726
  catch (error) {
1510
1727
  if (this.state.loadingErrors.find(item => item.id === id && item.error === error.message)) {
@@ -1539,6 +1756,11 @@ export class MainView extends React.Component {
1539
1756
  }
1540
1757
  case 'VectorLayer': {
1541
1758
  const layerParams = layer.parameters;
1759
+ if (Array.isArray((_b = layerParams.symbologyState) === null || _b === void 0 ? void 0 : _b.layers)) {
1760
+ // Grammar layers may change structure (e.g. KDE added/removed) — rebuild.
1761
+ this.replaceLayer(id, layer);
1762
+ break;
1763
+ }
1542
1764
  mapLayer.setOpacity(layerParams.opacity || 1);
1543
1765
  mapLayer.setStyle(this.vectorLayerStyleRuleBuilder(layer));
1544
1766
  break;
@@ -1556,23 +1778,24 @@ export class MainView extends React.Component {
1556
1778
  case 'ImageLayer': {
1557
1779
  break;
1558
1780
  }
1559
- case 'WebGlLayer': {
1560
- mapLayer.setOpacity((_b = layer.parameters) === null || _b === void 0 ? void 0 : _b.opacity);
1561
- if ((_c = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _c === void 0 ? void 0 : _c.color) {
1562
- mapLayer.setStyle({
1563
- color: layer.parameters.color,
1564
- });
1781
+ case 'GeoTiffLayer': {
1782
+ if (Array.isArray((_d = (_c = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _c === void 0 ? void 0 : _c.symbologyState) === null || _d === void 0 ? void 0 : _d.layers)) {
1783
+ this.replaceLayer(id, layer);
1784
+ }
1785
+ else {
1786
+ mapLayer.setOpacity((_e = layer.parameters) === null || _e === void 0 ? void 0 : _e.opacity);
1787
+ if ((_f = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _f === void 0 ? void 0 : _f.color) {
1788
+ mapLayer.setStyle({
1789
+ color: layer.parameters.color,
1790
+ });
1791
+ }
1565
1792
  }
1566
1793
  break;
1567
1794
  }
1568
- case 'HeatmapLayer': {
1795
+ case 'OpenEOTileLayer': {
1569
1796
  const layerParams = layer.parameters;
1570
- const heatmap = mapLayer;
1571
- heatmap.setOpacity((_d = layerParams.opacity) !== null && _d !== void 0 ? _d : 1);
1572
- heatmap.setBlur((_e = layerParams.blur) !== null && _e !== void 0 ? _e : 15);
1573
- heatmap.setRadius((_f = layerParams.radius) !== null && _f !== void 0 ? _f : 8);
1574
- heatmap.setGradient((_g = layerParams.color) !== null && _g !== void 0 ? _g : ['#00f', '#0ff', '#0f0', '#ff0', '#f00']);
1575
- this.handleTemporalController(id, layer);
1797
+ const openeoLayer = mapLayer;
1798
+ openeoLayer.setOpacity((_g = layerParams.opacity) !== null && _g !== void 0 ? _g : 1);
1576
1799
  break;
1577
1800
  }
1578
1801
  case 'StacLayer':
@@ -1582,7 +1805,7 @@ export class MainView extends React.Component {
1582
1805
  }
1583
1806
  flyToGeometry(sender, geometry) {
1584
1807
  if (!geometry || typeof geometry.getExtent !== 'function') {
1585
- console.warn('Invalid geometry for flyToGeometry:', geometry);
1808
+ this._log('warning', `Invalid geometry for flyToGeometry: ${geometry}`);
1586
1809
  return;
1587
1810
  }
1588
1811
  const view = this._Map.getView();
@@ -1594,11 +1817,12 @@ export class MainView extends React.Component {
1594
1817
  });
1595
1818
  }
1596
1819
  highlightFeatureOnMap(sender, featureOrGeometry) {
1820
+ var _a;
1597
1821
  const geometry = (featureOrGeometry === null || featureOrGeometry === void 0 ? void 0 : featureOrGeometry.geometry) ||
1598
1822
  (featureOrGeometry === null || featureOrGeometry === void 0 ? void 0 : featureOrGeometry._geometry) ||
1599
1823
  featureOrGeometry;
1600
1824
  if (!geometry) {
1601
- console.warn('No geometry found in feature:', featureOrGeometry);
1825
+ this._log('warning', `No geometry found in feature: ${featureOrGeometry}`);
1602
1826
  return;
1603
1827
  }
1604
1828
  const isOlGeometry = typeof geometry.getCoordinates === 'function';
@@ -1608,63 +1832,128 @@ export class MainView extends React.Component {
1608
1832
  featureProjection: this._Map.getView().getProjection(),
1609
1833
  });
1610
1834
  const olFeature = new Feature(Object.assign({ geometry: parsedGeometry }, (geometry !== featureOrGeometry ? featureOrGeometry : {})));
1611
- if (!this._highlightLayer) {
1612
- this._highlightLayer = new VectorLayer({
1613
- source: new VectorSource(),
1614
- style: feature => {
1615
- var _a;
1616
- const geomType = (_a = feature.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType();
1617
- switch (geomType) {
1618
- case 'Point':
1619
- case 'MultiPoint':
1620
- return new Style({
1621
- image: new Circle({
1622
- radius: 6,
1623
- fill: new Fill({
1624
- color: 'rgba(255, 255, 0, 0.8)',
1625
- }),
1626
- stroke: new Stroke({
1627
- color: '#ff0',
1628
- width: 2,
1629
- }),
1630
- }),
1631
- });
1632
- case 'LineString':
1633
- case 'MultiLineString':
1634
- return new Style({
1635
- stroke: new Stroke({
1636
- color: 'rgba(255, 255, 0, 0.8)',
1637
- width: 3,
1638
- }),
1639
- });
1640
- case 'Polygon':
1641
- case 'MultiPolygon':
1642
- return new Style({
1643
- stroke: new Stroke({
1644
- color: '#f00',
1645
- width: 2,
1646
- }),
1647
- fill: new Fill({
1648
- color: 'rgba(255, 255, 0, 0.8)',
1649
- }),
1650
- });
1651
- default:
1652
- return new Style({
1653
- stroke: new Stroke({
1654
- color: '#000',
1655
- width: 2,
1656
- }),
1657
- });
1658
- }
1659
- },
1660
- zIndex: 999,
1661
- });
1662
- this._Map.addLayer(this._highlightLayer);
1663
- }
1664
- const source = this._highlightLayer.getSource();
1835
+ this._ensureHighlightLayer();
1836
+ const source = (_a = this._highlightLayerRef.current) === null || _a === void 0 ? void 0 : _a.getSource();
1665
1837
  source === null || source === void 0 ? void 0 : source.clear();
1666
1838
  source === null || source === void 0 ? void 0 : source.addFeature(olFeature);
1667
1839
  }
1840
+ _ensureHighlightLayer() {
1841
+ ensureHighlightLayer(this._Map, this._highlightLayerRef);
1842
+ }
1843
+ /**
1844
+ * Replace the highlight layer contents with the given geometries.
1845
+ * Clears the source first so that stale highlights are always removed,
1846
+ * including when the selection becomes empty (geometries = []).
1847
+ */
1848
+ _setHighlightGeometries(geometries) {
1849
+ var _a;
1850
+ this._ensureHighlightLayer();
1851
+ const source = (_a = this._highlightLayerRef.current) === null || _a === void 0 ? void 0 : _a.getSource();
1852
+ source === null || source === void 0 ? void 0 : source.clear();
1853
+ for (const geom of geometries) {
1854
+ source === null || source === void 0 ? void 0 : source.addFeature(new Feature({ geometry: geom }));
1855
+ }
1856
+ }
1857
+ /**
1858
+ * Replace the highlight layer contents with pre-styled features.
1859
+ * Each feature carries its own highlight style via feature.setStyle().
1860
+ */
1861
+ _setHighlightFeatures(features) {
1862
+ var _a;
1863
+ this._ensureHighlightLayer();
1864
+ const source = (_a = this._highlightLayerRef.current) === null || _a === void 0 ? void 0 : _a.getSource();
1865
+ source === null || source === void 0 ? void 0 : source.clear();
1866
+ for (const f of features) {
1867
+ source === null || source === void 0 ? void 0 : source.addFeature(f);
1868
+ }
1869
+ }
1870
+ _buildHighlightStyle(original, geomType) {
1871
+ return buildHighlightStyle(original, geomType);
1872
+ }
1873
+ /**
1874
+ * Compute extent for layer or source
1875
+ */
1876
+ _computeExtent(layer, source) {
1877
+ try {
1878
+ if (source instanceof VectorSource) {
1879
+ const extent = source.getExtent();
1880
+ if (extent) {
1881
+ return extent;
1882
+ }
1883
+ }
1884
+ if (source instanceof TileSource || source instanceof VectorTileSource) {
1885
+ const tileGrid = source.getTileGrid();
1886
+ const extent = tileGrid === null || tileGrid === void 0 ? void 0 : tileGrid.getExtent();
1887
+ if (extent) {
1888
+ return extent;
1889
+ }
1890
+ }
1891
+ if (layer instanceof StacLayer) {
1892
+ const extent = layer.getExtent();
1893
+ if (extent) {
1894
+ return extent;
1895
+ }
1896
+ }
1897
+ }
1898
+ catch (error) {
1899
+ this._log('warning', `Failed to compute extent: ${error}`);
1900
+ }
1901
+ return undefined;
1902
+ }
1903
+ _computeZoomFromExtent(extent) {
1904
+ var _a, _b;
1905
+ if (!this._Map) {
1906
+ return null;
1907
+ }
1908
+ const view = this._Map.getView();
1909
+ const size = (_a = this._Map.getSize()) !== null && _a !== void 0 ? _a : getSize(extent);
1910
+ const resolution = view.getResolutionForExtent(extent, size);
1911
+ const zoom = view.getZoomForResolution(resolution);
1912
+ return (_b = zoom !== null && zoom !== void 0 ? zoom : view.getZoom()) !== null && _b !== void 0 ? _b : 0;
1913
+ }
1914
+ /**
1915
+ * Track layer's extent and zoom in model's view state
1916
+ */
1917
+ _trackLayerViewState(layerId, olLayer) {
1918
+ var _a;
1919
+ const effectiveLayer = olLayer instanceof LayerGroup
1920
+ ? olLayer.getLayers().getArray()[0]
1921
+ : olLayer;
1922
+ if (!effectiveLayer) {
1923
+ return;
1924
+ }
1925
+ const source = effectiveLayer.getSource();
1926
+ const sourceId = (_a = source === null || source === void 0 ? void 0 : source.get) === null || _a === void 0 ? void 0 : _a.call(source, 'id');
1927
+ let extent = sourceId ? this._model.getExtent(sourceId) : undefined;
1928
+ if (!extent) {
1929
+ extent = this._computeExtent(effectiveLayer, source);
1930
+ }
1931
+ if (extent) {
1932
+ const zoom = this._computeZoomFromExtent(extent);
1933
+ if (zoom === null) {
1934
+ return;
1935
+ }
1936
+ const view = { extent, zoom };
1937
+ this._model.updateLayerViewState(layerId, view);
1938
+ }
1939
+ }
1940
+ /**
1941
+ * Track source's extent and zoom in model's view state
1942
+ */
1943
+ _trackSourceExtZoom(sourceId, olSource) {
1944
+ var _a, _b, _c;
1945
+ const extent = this._computeExtent(undefined, olSource);
1946
+ if (extent) {
1947
+ const projection = (_c = (_b = (_a = olSource === null || olSource === void 0 ? void 0 : olSource.getProjection) === null || _a === void 0 ? void 0 : _a.call(olSource)) === null || _b === void 0 ? void 0 : _b.getCode) === null || _c === void 0 ? void 0 : _c.call(_b);
1948
+ const zoom = this._computeZoomFromExtent(extent);
1949
+ if (zoom === null) {
1950
+ return;
1951
+ }
1952
+ const view = Object.assign({ extent,
1953
+ zoom }, (projection && { projection }));
1954
+ this._model.updateLayerViewState(sourceId, view);
1955
+ }
1956
+ }
1668
1957
  /**
1669
1958
  * Wait for all layers to be loaded.
1670
1959
  */
@@ -1716,6 +2005,133 @@ export class MainView extends React.Component {
1716
2005
  this._Map.removeLayer(mapLayer);
1717
2006
  }
1718
2007
  }
2008
+ /**
2009
+ * Decide how selection changes should affect vector drawing state.
2010
+ *
2011
+ * This helper only computes whether
2012
+ * draw mode must be disabled (non-draw layer selected) and whether draw
2013
+ * interactions should be rebound (draw mode enabled and selected draw layer
2014
+ * changed).
2015
+ */
2016
+ _getVectorDrawingSelectionDecision(layer, selectedLayerId) {
2017
+ const isDrawVectorLayer = this._model.checkIfIsADrawVectorLayer(layer);
2018
+ if (!isDrawVectorLayer) {
2019
+ return { disableEditing: true, shouldRebind: false };
2020
+ }
2021
+ if (!this._model.editingVectorLayer) {
2022
+ return { disableEditing: false, shouldRebind: false };
2023
+ }
2024
+ if (selectedLayerId === this._previousDrawLayerID) {
2025
+ return { disableEditing: false, shouldRebind: false };
2026
+ }
2027
+ return { disableEditing: false, shouldRebind: true };
2028
+ }
2029
+ _handleTemporalControllerActiveChanged() {
2030
+ var _a, _b, _c;
2031
+ const localState = this._model.localState;
2032
+ if (!localState) {
2033
+ return;
2034
+ }
2035
+ const isTemporalControllerActive = localState.isTemporalControllerActive === true;
2036
+ const selectedLayers = (_a = localState.selected) === null || _a === void 0 ? void 0 : _a.value;
2037
+ const selectedLayerId = selectedLayers
2038
+ ? ((_b = Object.keys(selectedLayers)[0]) !== null && _b !== void 0 ? _b : null)
2039
+ : null;
2040
+ const layerType = selectedLayerId
2041
+ ? (_c = this._model.getLayer(selectedLayerId)) === null || _c === void 0 ? void 0 : _c.type
2042
+ : null;
2043
+ const isSelectionValid = !!selectedLayers &&
2044
+ Object.keys(selectedLayers).length === 1 &&
2045
+ !this._model.getSource(selectedLayerId) &&
2046
+ layerType === 'VectorLayer';
2047
+ const displayTemporalController = isTemporalControllerActive && isSelectionValid;
2048
+ if (displayTemporalController !== this.state.displayTemporalController) {
2049
+ this.setState(old => (Object.assign(Object.assign({}, old), { displayTemporalController })));
2050
+ this._mainViewModel.commands.notifyCommandChanged(CommandIDs.temporalController);
2051
+ }
2052
+ }
2053
+ _handleRemoteUserChanged() {
2054
+ var _a, _b, _c;
2055
+ const localState = this._model.localState;
2056
+ if (!localState) {
2057
+ return;
2058
+ }
2059
+ const remoteUser = localState.remoteUser;
2060
+ const clients = this._model.sharedModel.awareness.getStates();
2061
+ // If we are in following mode, update UI and viewport from the remote user.
2062
+ if (remoteUser) {
2063
+ const remoteState = clients.get(remoteUser);
2064
+ if (!remoteState) {
2065
+ return;
2066
+ }
2067
+ if (((_a = remoteState.user) === null || _a === void 0 ? void 0 : _a.username) !== ((_b = this.state.remoteUser) === null || _b === void 0 ? void 0 : _b.username)) {
2068
+ this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: remoteState.user })));
2069
+ }
2070
+ const remoteViewport = remoteState.viewportState;
2071
+ if (remoteViewport.value) {
2072
+ const { x, y } = remoteViewport.value.coordinates;
2073
+ const zoom = remoteViewport.value.zoom;
2074
+ this._moveToPosition({ x, y }, zoom, 0);
2075
+ }
2076
+ return;
2077
+ }
2078
+ // If we are unfollowing, reset to local viewport and clear follow UI.
2079
+ if (this.state.remoteUser !== null) {
2080
+ this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: null })));
2081
+ const viewportState = (_c = localState.viewportState) === null || _c === void 0 ? void 0 : _c.value;
2082
+ if (viewportState) {
2083
+ this._moveToPosition(viewportState.coordinates, viewportState.zoom);
2084
+ }
2085
+ }
2086
+ }
2087
+ _handlePointerChanged() {
2088
+ const clients = this._model.sharedModel.awareness.getStates();
2089
+ const clientPointers = Object.assign({}, this.state.clientPointers);
2090
+ clients.forEach((client, clientId) => {
2091
+ var _a;
2092
+ if (!(client === null || client === void 0 ? void 0 : client.user) || this._model.getClientId() === clientId) {
2093
+ return;
2094
+ }
2095
+ const pointer = (_a = client.pointer) === null || _a === void 0 ? void 0 : _a.value;
2096
+ let currentClientPointer = clientPointers[clientId];
2097
+ if (pointer) {
2098
+ const pixel = this._Map.getPixelFromCoordinate([
2099
+ pointer.coordinates.x,
2100
+ pointer.coordinates.y,
2101
+ ]);
2102
+ const lonLat = toLonLat([pointer.coordinates.x, pointer.coordinates.y]);
2103
+ if (!currentClientPointer) {
2104
+ currentClientPointer = {
2105
+ username: client.user.username,
2106
+ displayName: client.user.display_name,
2107
+ color: client.user.color,
2108
+ coordinates: {
2109
+ x: pixel[0],
2110
+ y: pixel[1],
2111
+ },
2112
+ lonLat: {
2113
+ longitude: lonLat[0],
2114
+ latitude: lonLat[1],
2115
+ },
2116
+ };
2117
+ }
2118
+ else {
2119
+ currentClientPointer = Object.assign(Object.assign({}, currentClientPointer), { coordinates: {
2120
+ x: pixel[0],
2121
+ y: pixel[1],
2122
+ }, lonLat: {
2123
+ longitude: lonLat[0],
2124
+ latitude: lonLat[1],
2125
+ } });
2126
+ }
2127
+ clientPointers[clientId] = currentClientPointer;
2128
+ }
2129
+ else {
2130
+ delete clientPointers[clientId];
2131
+ }
2132
+ });
2133
+ this.setState(old => (Object.assign(Object.assign({}, old), { clientPointers })));
2134
+ }
1719
2135
  _onSharedOptionsChanged() {
1720
2136
  if (!this._Map) {
1721
2137
  return;
@@ -1781,7 +2197,7 @@ export class MainView extends React.Component {
1781
2197
  view = new View({ projection: newProjection });
1782
2198
  }
1783
2199
  else {
1784
- console.warn(`Invalid projection: ${projection}`);
2200
+ this._log('warning', `Invalid projection: ${projection}`);
1785
2201
  return;
1786
2202
  }
1787
2203
  }
@@ -1876,6 +2292,11 @@ export class MainView extends React.Component {
1876
2292
  const { id, oldValue: oldLayer, newValue: newLayer } = change;
1877
2293
  if (!newLayer || Object.keys(newLayer).length === 0) {
1878
2294
  this.removeLayer(id);
2295
+ if (this._model.checkIfIsADrawVectorLayer(oldLayer)) {
2296
+ this._model.editingVectorLayer = false;
2297
+ this._updateEditingVectorLayer();
2298
+ this._mainViewModel.commands.notifyCommandChanged(CommandIDs.toggleDrawFeatures);
2299
+ }
1879
2300
  return;
1880
2301
  }
1881
2302
  if (oldLayer && oldLayer.type !== newLayer.type) {
@@ -1886,6 +2307,9 @@ export class MainView extends React.Component {
1886
2307
  const layerTree = JupyterGISModel.getOrderedLayerIds(this._model);
1887
2308
  if (layerTree.includes(id)) {
1888
2309
  this.updateLayer(id, newLayer, mapLayer, oldLayer);
2310
+ if (mapLayer) {
2311
+ this._trackLayerViewState(id, mapLayer);
2312
+ }
1889
2313
  }
1890
2314
  else {
1891
2315
  this.updateLayers(layerTree);
@@ -1914,6 +2338,14 @@ export class MainView extends React.Component {
1914
2338
  }
1915
2339
  }
1916
2340
  });
2341
+ this.setState(old => (Object.assign(Object.assign({}, old), { identifyFeatureFloatersVersion: old.identifyFeatureFloatersVersion + 1 })));
2342
+ }
2343
+ _clearHighlightWhenIdentifyDisabled() {
2344
+ var _a, _b;
2345
+ if (this._model.currentMode !== 'identifying' &&
2346
+ this._highlightLayerRef.current) {
2347
+ (_b = (_a = this._highlightLayerRef.current) === null || _a === void 0 ? void 0 : _a.getSource()) === null || _b === void 0 ? void 0 : _b.clear();
2348
+ }
1917
2349
  }
1918
2350
  _computeAnnotationPosition(annotation) {
1919
2351
  const { x, y } = annotation.position;
@@ -1938,6 +2370,58 @@ export class MainView extends React.Component {
1938
2370
  }
1939
2371
  });
1940
2372
  }
2373
+ _computeFeatureFloaterPosition(feature) {
2374
+ var _a;
2375
+ const geometry = (_a = feature === null || feature === void 0 ? void 0 : feature.geometry) !== null && _a !== void 0 ? _a : feature === null || feature === void 0 ? void 0 : feature._geometry;
2376
+ if (!geometry) {
2377
+ return undefined;
2378
+ }
2379
+ if (typeof geometry.getExtent === 'function') {
2380
+ const extent = geometry.getExtent();
2381
+ const center = getCenter(extent);
2382
+ const pixels = this._Map.getPixelFromCoordinate(center);
2383
+ if (pixels) {
2384
+ return { x: pixels[0], y: pixels[1] };
2385
+ }
2386
+ return undefined;
2387
+ }
2388
+ if (geometry.type === 'Point' && Array.isArray(geometry.coordinates)) {
2389
+ const pixels = this._Map.getPixelFromCoordinate(geometry.coordinates);
2390
+ if (pixels) {
2391
+ return { x: pixels[0], y: pixels[1] };
2392
+ }
2393
+ }
2394
+ return undefined;
2395
+ }
2396
+ _getVisibleDrawIdentifiedFeatures() {
2397
+ var _a, _b, _c;
2398
+ const identifiedFeatures = (_c = (_b = (_a = this._model.localState) === null || _a === void 0 ? void 0 : _a.identifiedFeatures) === null || _b === void 0 ? void 0 : _b.value) !== null && _c !== void 0 ? _c : [];
2399
+ const drawEntries = identifiedFeatures.filter(entry => entry.floaterOpen === true);
2400
+ const visibleFeatures = drawEntries
2401
+ .map(entry => {
2402
+ const featureId = getFeatureIdentifier(entry.feature);
2403
+ if (!featureId) {
2404
+ return undefined;
2405
+ }
2406
+ return [featureId, entry.feature];
2407
+ })
2408
+ .filter((entry) => !!entry);
2409
+ return visibleFeatures;
2410
+ }
2411
+ _updateFeatureFloaters() {
2412
+ this._getVisibleDrawIdentifiedFeatures().forEach(([floaterKey, feature]) => {
2413
+ const el = document.getElementById(`feature-floater-${floaterKey}`);
2414
+ if (!el) {
2415
+ return;
2416
+ }
2417
+ const screenPosition = this._computeFeatureFloaterPosition(feature);
2418
+ if (!screenPosition) {
2419
+ return;
2420
+ }
2421
+ el.style.left = `${Math.round(screenPosition.x)}px`;
2422
+ el.style.top = `${Math.round(screenPosition.y)}px`;
2423
+ });
2424
+ }
1941
2425
  // TODO this and flyToPosition need a rework
1942
2426
  _onZoomToPosition(_, id) {
1943
2427
  var _a, _b, _c;
@@ -1948,7 +2432,6 @@ export class MainView extends React.Component {
1948
2432
  return;
1949
2433
  }
1950
2434
  // The id is a layer
1951
- let extent;
1952
2435
  const layer = this.getLayer(id);
1953
2436
  const source = layer === null || layer === void 0 ? void 0 : layer.getSource();
1954
2437
  const jgisLayer = this._model.getLayer(id);
@@ -1998,19 +2481,13 @@ export class MainView extends React.Component {
1998
2481
  return;
1999
2482
  }
2000
2483
  }
2001
- if (source instanceof VectorSource) {
2002
- extent = source.getExtent();
2003
- }
2004
- if (source instanceof TileSource) {
2005
- // Tiled sources don't have getExtent() so we get it from the grid
2006
- const tileGrid = source.getTileGrid();
2007
- extent = tileGrid === null || tileGrid === void 0 ? void 0 : tileGrid.getExtent();
2008
- }
2009
- if (layer instanceof StacLayer) {
2010
- extent = layer.getExtent();
2011
- }
2484
+ const extent = this._computeExtent(layer, source);
2012
2485
  if (!extent) {
2013
- console.warn('Layer has no extent.');
2486
+ this._log('warning', 'Layer ${id} has no extent.');
2487
+ return;
2488
+ }
2489
+ if (!extent.every(value => Number.isFinite(value))) {
2490
+ this._log('warning', `Layer ${id} has an invalid extent: ${extent.join(', ')}`);
2014
2491
  return;
2015
2492
  }
2016
2493
  // Convert layer extent value to view projection if needed
@@ -2019,6 +2496,10 @@ export class MainView extends React.Component {
2019
2496
  const transformedExtent = sourceProjection && sourceProjection !== viewProjection
2020
2497
  ? transformExtent(extent, sourceProjection, viewProjection)
2021
2498
  : extent;
2499
+ if (!transformedExtent.every(value => Number.isFinite(value))) {
2500
+ this._log('warning', `Layer ${id} has an invalid transformed extent: ${transformedExtent.join(', ')}`);
2501
+ return;
2502
+ }
2022
2503
  this._Map.getView().fit(transformedExtent, {
2023
2504
  size: this._Map.getSize(),
2024
2505
  duration: 500,
@@ -2082,7 +2563,8 @@ export class MainView extends React.Component {
2082
2563
  this._syncPointer(coordinates);
2083
2564
  }
2084
2565
  async _addMarker(e) {
2085
- if (this._model.currentMode !== 'marking') {
2566
+ if (this.state.editingVectorLayer ||
2567
+ this._model.currentMode !== 'marking') {
2086
2568
  return;
2087
2569
  }
2088
2570
  const coordinate = this._Map.getCoordinateFromPixel(e.pixel);
@@ -2094,7 +2576,7 @@ export class MainView extends React.Component {
2094
2576
  const layerParams = {
2095
2577
  opacity: 1.0,
2096
2578
  source: sourceId,
2097
- symbologyState: { renderType: 'Single Symbol' },
2579
+ symbologyState: { layers: [] },
2098
2580
  };
2099
2581
  const sourceModel = {
2100
2582
  type: 'MarkerSource',
@@ -2114,25 +2596,29 @@ export class MainView extends React.Component {
2114
2596
  }
2115
2597
  _identifyFeature(e) {
2116
2598
  var _a, _b;
2117
- if (this._model.currentMode !== 'identifying') {
2599
+ if (this.state.editingVectorLayer ||
2600
+ this._model.currentMode !== 'identifying') {
2118
2601
  return;
2119
2602
  }
2120
2603
  const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
2121
2604
  const selectedLayer = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
2122
2605
  if (!selectedLayer) {
2123
- console.warn('Layer must be selected to use identify tool');
2606
+ this._log('warning', 'Layer must be selected to use identify tool');
2124
2607
  return;
2125
2608
  }
2126
2609
  const layerId = Object.keys(selectedLayer)[0];
2127
2610
  const jgisLayer = this._model.getLayer(layerId);
2128
2611
  switch (jgisLayer === null || jgisLayer === void 0 ? void 0 : jgisLayer.type) {
2612
+ case 'VectorLayer':
2613
+ // Handled by selectInteraction (createSelectInteraction).
2614
+ break;
2129
2615
  case 'VectorTileLayer': {
2130
2616
  const geometries = [];
2131
2617
  const features = [];
2132
- let foundAny = false;
2618
+ let foundAnyFeatures = false;
2133
2619
  this._Map.forEachFeatureAtPixel(e.pixel, (feature) => {
2134
2620
  var _a, _b;
2135
- foundAny = true;
2621
+ foundAnyFeatures = true;
2136
2622
  let geom;
2137
2623
  let props = {};
2138
2624
  if (feature instanceof RenderFeature) {
@@ -2158,15 +2644,18 @@ export class MainView extends React.Component {
2158
2644
  geometries.push(geom);
2159
2645
  }
2160
2646
  if (props && Object.keys(props).length > 0) {
2161
- features.push(props);
2647
+ features.push({
2648
+ feature: props,
2649
+ floaterOpen: false,
2650
+ });
2162
2651
  }
2163
2652
  return true;
2164
2653
  });
2165
2654
  if (features.length > 0) {
2166
- this._model.syncIdentifiedFeatures(features, this._mainViewModel.id);
2655
+ this._model.syncIdentifiedFeatures(features, this._model.getClientId().toString());
2167
2656
  }
2168
- else if (!foundAny) {
2169
- this._model.syncIdentifiedFeatures([], this._mainViewModel.id);
2657
+ else if (!foundAnyFeatures) {
2658
+ this._model.syncIdentifiedFeatures([], this._model.getClientId().toString());
2170
2659
  }
2171
2660
  if (geometries.length > 0) {
2172
2661
  for (const geom of geometries) {
@@ -2180,7 +2669,7 @@ export class MainView extends React.Component {
2180
2669
  }
2181
2670
  break;
2182
2671
  }
2183
- case 'WebGlLayer': {
2672
+ case 'GeoTiffLayer': {
2184
2673
  const layer = this.getLayer(layerId);
2185
2674
  const data = layer.getData(e.pixel);
2186
2675
  // TODO: Handle dataviews?
@@ -2194,7 +2683,7 @@ export class MainView extends React.Component {
2194
2683
  }
2195
2684
  // last element is alpha
2196
2685
  bandValues['Alpha'] = data[data.length - 1];
2197
- this._model.syncIdentifiedFeatures([bandValues], this._mainViewModel.id);
2686
+ this._model.syncIdentifiedFeatures([{ feature: bandValues, floaterOpen: false }], this._mainViewModel.id);
2198
2687
  const coordinate = this._Map.getCoordinateFromPixel(e.pixel);
2199
2688
  const point = new Point(coordinate);
2200
2689
  // trigger highlight via signal
@@ -2213,7 +2702,7 @@ export class MainView extends React.Component {
2213
2702
  this.updateSource(layerId, jgisLayer);
2214
2703
  }
2215
2704
  if (!jgisLayer || !olLayer) {
2216
- console.error('Failed to update layer -- layer not found');
2705
+ this._log('error', 'Failed to update layer -- layer not found');
2217
2706
  return;
2218
2707
  }
2219
2708
  this.updateLayer(layerId, jgisLayer, olLayer);
@@ -2223,6 +2712,9 @@ export class MainView extends React.Component {
2223
2712
  const { id: layerId, selectedFeature } = json;
2224
2713
  const olLayer = this.getLayer(layerId);
2225
2714
  const source = olLayer.getSource();
2715
+ if (typeof source.forEachFeature !== 'function') {
2716
+ return;
2717
+ }
2226
2718
  source.forEachFeature(feature => {
2227
2719
  const time = feature.get(selectedFeature);
2228
2720
  const parsedTime = typeof time === 'string' ? Date.parse(time) : time;
@@ -2239,48 +2731,86 @@ export class MainView extends React.Component {
2239
2731
  throw new Error('Could not move to geolocation, because current zoom is not defined.');
2240
2732
  }
2241
2733
  }
2734
+ _updateEditingVectorLayer() {
2735
+ const editingVectorLayer = this._model.editingVectorLayer;
2736
+ this.setState(old => (Object.assign(Object.assign({}, old), { editingVectorLayer })));
2737
+ if (editingVectorLayer === true) {
2738
+ this._editVectorLayer();
2739
+ }
2740
+ if (editingVectorLayer === false && this._draw) {
2741
+ this._removeDrawInteraction();
2742
+ this._currentDrawLayerID = undefined;
2743
+ }
2744
+ }
2745
+ _renderAnnotationFloaters() {
2746
+ const annotationModel = this._model.annotationModel;
2747
+ if (!annotationModel) {
2748
+ return null;
2749
+ }
2750
+ return Object.entries(this.state.annotations).map(([key, annotation]) => {
2751
+ const screenPosition = this._computeAnnotationPosition(annotation);
2752
+ if (!screenPosition) {
2753
+ return null;
2754
+ }
2755
+ return (React.createElement(PositionedFloater, { key: key, id: key, className: "jGIS-Popup-Wrapper", left: screenPosition.x, top: screenPosition.y },
2756
+ React.createElement(AnnotationFloater, { itemId: key, annotationModel: annotationModel })));
2757
+ });
2758
+ }
2759
+ _renderFeatureFloaters() {
2760
+ return this._getVisibleDrawIdentifiedFeatures().map(([floaterKey, feature]) => {
2761
+ const screenPosition = this._computeFeatureFloaterPosition(feature);
2762
+ if (!screenPosition) {
2763
+ return null;
2764
+ }
2765
+ const id = `feature-floater-${floaterKey}`;
2766
+ return (React.createElement(PositionedFloater, { key: id, id: id, className: "jGIS-Popup-Wrapper jGIS-FeatureFloater-Wrapper", left: screenPosition.x, top: screenPosition.y },
2767
+ React.createElement(FeatureFloater, { feature: feature })));
2768
+ });
2769
+ }
2242
2770
  render() {
2771
+ const { clientPointers, displayTemporalController, drawGeometryLabel, editingVectorLayer, filterStates, initialLayersReady, isSpectaPresentation, jgisSettings, loading, loadingLayer, remoteUser, scale, segmentTransition, viewProjection, } = this.state;
2772
+ const { isMobile } = this.props;
2773
+ const selectedStory = this._model.getSelectedStory().story;
2774
+ const isListStory = isSpectaPresentation &&
2775
+ (selectedStory === null || selectedStory === void 0 ? void 0 : selectedStory.storyType) === STORY_TYPE.verticalScroll;
2776
+ const showSidePanels = !isSpectaPresentation;
2777
+ const showMergedMobilePanel = isMobile &&
2778
+ Boolean(this._state) &&
2779
+ Boolean(this._formSchemaRegistry) &&
2780
+ Boolean(this._annotationModel);
2781
+ const spectaMobileTouch = isSpectaPresentation && isMobile;
2243
2782
  return (React.createElement(React.Fragment, null,
2244
- Object.entries(this.state.annotations).map(([key, annotation]) => {
2245
- if (!this._model.annotationModel) {
2246
- return null;
2247
- }
2248
- const screenPosition = this._computeAnnotationPosition(annotation);
2249
- return (screenPosition && (React.createElement("div", { key: key, id: key, style: {
2250
- left: screenPosition.x,
2251
- top: screenPosition.y,
2252
- }, className: 'jGIS-Popup-Wrapper' },
2253
- React.createElement(AnnotationFloater, { itemId: key, annotationModel: this._model.annotationModel }))));
2254
- }),
2783
+ React.createElement(MainViewOverlayLayer, { annotationFloaters: this._renderAnnotationFloaters(), featureFloaters: this._renderFeatureFloaters(), editingVectorLayer: editingVectorLayer, drawGeometryLabel: drawGeometryLabel, onDrawGeometryTypeChange: this._handleDrawGeometryTypeChange }),
2255
2784
  React.createElement("div", { className: "jGIS-Mainview-Container" },
2256
- this.state.displayTemporalController && (React.createElement(TemporalSlider, { model: this._model, filterStates: this.state.filterStates })),
2257
- React.createElement("div", { ref: this.mainViewRef, className: "jGIS-Mainview data-jgis-keybinding", tabIndex: 0, style: {
2258
- border: this.state.remoteUser
2259
- ? `solid 3px ${this.state.remoteUser.color}`
2260
- : 'unset',
2261
- }, onTouchStart: this.state.isSpectaPresentation && this.props.isMobile
2262
- ? this._handleSpectaTouchStart
2263
- : undefined, onTouchEnd: this.state.isSpectaPresentation && this.props.isMobile
2264
- ? this._handleSpectaTouchEnd
2265
- : undefined },
2266
- React.createElement(LoadingOverlay, { loading: this.state.loading }),
2267
- React.createElement(FollowIndicator, { remoteUser: this.state.remoteUser }),
2268
- React.createElement(CollaboratorPointers, { clients: this.state.clientPointers }),
2269
- React.createElement("div", { ref: this.divRef, style: {
2270
- width: '100%',
2271
- height: '100%',
2272
- } },
2273
- React.createElement("div", { className: "jgis-panels-wrapper" }, !this.state.isSpectaPresentation ? (React.createElement(React.Fragment, null,
2274
- this._state && (React.createElement(LeftPanel, { model: this._model, commands: this._mainViewModel.commands, state: this._state, settings: this.state.jgisSettings })),
2275
- this._formSchemaRegistry && this._annotationModel && (React.createElement(RightPanel, { model: this._model, commands: this._mainViewModel.commands, formSchemaRegistry: this._formSchemaRegistry, annotationModel: this._annotationModel, addLayer: this.addLayer.bind(this), removeLayer: this.removeLayer.bind(this), settings: this.state.jgisSettings })))) : (this.state.initialLayersReady && (React.createElement(SpectaPanel, { model: this._model, isSpecta: this.state.isSpectaPresentation, isMobile: this.props.isMobile, onSegmentTransitionEnd: () => this._clearStoryScrollGuard(), containerRef: this.spectaContainerRef, storyViewerPanelRef: this.storyViewerPanelRef, addLayer: this.addLayer.bind(this), removeLayer: this.removeLayer.bind(this) })))),
2276
- React.createElement("div", { ref: this.controlsToolbarRef, className: "jgis-controls-toolbar" }))),
2277
- !this.state.isSpectaPresentation && (React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale })))));
2785
+ displayTemporalController ? (React.createElement(TemporalSlider, { model: this._model, filterStates: filterStates })) : null,
2786
+ React.createElement(MainViewMapSurface, { mainViewRef: this.mainViewRef, loading: loading, remoteUser: remoteUser, clientPointers: clientPointers, spectaMobileTouch: spectaMobileTouch, onTouchStart: this._handleSpectaTouchStart, onTouchEnd: this._handleSpectaTouchEnd },
2787
+ React.createElement(MainViewStoryStage, { model: this._model, isListStory: isListStory, isMobile: isMobile, segmentTransition: segmentTransition, stageRef: this.divRef, controlsToolbarRef: this.controlsToolbarRef, panels: showSidePanels ? (React.createElement(MainViewSidePanels, { model: this._model, commands: this._mainViewModel.commands, settings: jgisSettings, showMergedMobilePanel: showMergedMobilePanel, state: this._state, formSchemaRegistry: this._formSchemaRegistry, annotationModel: this._annotationModel, addLayer: this.addLayer.bind(this), removeLayer: this.removeLayer.bind(this), patchGeoJSONFeatureProperties: this._patchGeoJSONFeatureProperties })) : (React.createElement(MainViewSpectaPanel, { model: this._model, isSpecta: isSpectaPresentation, isMobile: isMobile, initialLayersReady: initialLayersReady, containerRef: this.spectaContainerRef, storyViewerPanelRef: this.storyViewerPanelRef, addLayer: this._addLayerForPanels, removeLayer: this._removeLayerForPanels, onSegmentTransitionEnd: this._clearStoryScrollGuard, onSegmentTransitionChange: this._handleSegmentTransitionChange })) })),
2788
+ !isSpectaPresentation ? (React.createElement(StatusBar, { jgisModel: this._model, loading: loadingLayer, projection: viewProjection, scale: scale })) : null)));
2789
+ }
2790
+ _log(level, message) {
2791
+ var _a;
2792
+ // Always mirror to the browser console regardless of whether the JupyterLab
2793
+ // logger is available.
2794
+ if (level === 'error' || level === 'critical') {
2795
+ // eslint-disable-next-line no-console
2796
+ console.error(message);
2797
+ }
2798
+ else if (level === 'warning') {
2799
+ // eslint-disable-next-line no-console
2800
+ console.warn(message);
2801
+ }
2802
+ else {
2803
+ // eslint-disable-next-line no-console
2804
+ console.log(message);
2805
+ }
2806
+ // Forward to JupyterLab log console when available.
2807
+ (_a = this._loggerRegistry) === null || _a === void 0 ? void 0 : _a.getLogger(this._model.filePath).log({ type: 'text', level, data: message });
2278
2808
  }
2279
2809
  }
2280
2810
  // ! TODO make mainview a modern react component instead of a class
2281
2811
  /** Thin wrapper that injects isMobile from useMediaQuery so MainView can use it in JSX. */
2282
2812
  function MainViewWithMediaQuery(props) {
2283
- const isMobile = useMediaQuery('(max-width: 768px)');
2813
+ const isMobile = useMediaQuery('(max-width: 960px)');
2284
2814
  return React.createElement(MainView, Object.assign({}, props, { isMobile: isMobile }));
2285
2815
  }
2286
2816
  export { MainViewWithMediaQuery };