@jupytergis/base 0.14.1 → 0.16.0-alpha.0

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 (397) hide show
  1. package/lib/commands/BaseCommandIDs.d.ts +6 -0
  2. package/lib/commands/BaseCommandIDs.js +6 -0
  3. package/lib/commands/index.js +245 -55
  4. package/lib/commands/operationCommands.js +2 -2
  5. package/lib/constants.js +6 -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/features/layers/forms/layer/geoTiffLayerForm.d.ts +3 -0
  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/heatmapLayerForm.js +4 -4
  40. package/lib/{formbuilder/objectform → features/layers/forms}/layer/hillshadeLayerForm.js +4 -4
  41. package/lib/{formbuilder/objectform → features/layers/forms}/layer/index.d.ts +1 -1
  42. package/lib/{formbuilder/objectform → features/layers/forms}/layer/index.js +1 -1
  43. package/lib/{formbuilder/objectform → features/layers/forms}/layer/layerform.d.ts +1 -1
  44. package/lib/{formbuilder/objectform → features/layers/forms}/layer/layerform.js +4 -4
  45. package/lib/features/layers/forms/layer/storySegmentLayerForm.js +150 -0
  46. package/lib/{formbuilder/objectform → features/layers/forms}/layer/vectorlayerform.js +4 -4
  47. package/lib/{formbuilder/objectform → features/layers/forms}/source/geojsonsource.js +5 -5
  48. package/lib/features/layers/forms/source/geopackagesource.d.ts +3 -0
  49. package/lib/features/layers/forms/source/geopackagesource.js +93 -0
  50. package/lib/{formbuilder/objectform → features/layers/forms}/source/geotiffsource.js +5 -5
  51. package/lib/{formbuilder/objectform → features/layers/forms}/source/index.d.ts +2 -0
  52. package/lib/{formbuilder/objectform → features/layers/forms}/source/index.js +2 -0
  53. package/lib/{formbuilder/objectform → features/layers/forms}/source/pathbasedsource.js +5 -5
  54. package/lib/{formbuilder/objectform → features/layers/forms}/source/sourceform.d.ts +1 -1
  55. package/lib/{formbuilder/objectform → features/layers/forms}/source/sourceform.js +4 -4
  56. package/lib/{formbuilder/objectform → features/layers/forms}/source/tilesourceform.js +4 -4
  57. package/lib/features/layers/forms/source/wmsTileSource.d.ts +4 -0
  58. package/lib/features/layers/forms/source/wmsTileSource.js +78 -0
  59. package/lib/{dialogs → features/layers}/layerCreationFormDialog.d.ts +1 -1
  60. package/lib/{dialogs → features/layers}/layerCreationFormDialog.js +1 -1
  61. package/lib/features/layers/symbology/Grammar.d.ts +11 -0
  62. package/lib/features/layers/symbology/Grammar.js +235 -0
  63. package/lib/{dialogs/symbology/vector_layer/types → features/layers/symbology}/Heatmap.d.ts +1 -1
  64. package/lib/{dialogs/symbology/vector_layer/types → features/layers/symbology}/Heatmap.js +30 -10
  65. package/lib/{dialogs → features/layers}/symbology/classificationModes.d.ts +6 -6
  66. package/lib/{dialogs → features/layers}/symbology/classificationModes.js +57 -57
  67. package/lib/features/layers/symbology/colorRampUtils.d.ts +65 -0
  68. package/lib/features/layers/symbology/colorRampUtils.js +242 -0
  69. package/lib/features/layers/symbology/components/MappingRow.d.ts +40 -0
  70. package/lib/features/layers/symbology/components/MappingRow.js +516 -0
  71. package/lib/features/layers/symbology/components/NumericInput.d.ts +20 -0
  72. package/lib/features/layers/symbology/components/NumericInput.js +44 -0
  73. package/lib/features/layers/symbology/components/ScaleEditor.d.ts +33 -0
  74. package/lib/features/layers/symbology/components/ScaleEditor.js +221 -0
  75. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampControls.d.ts +1 -1
  76. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampControls.js +3 -2
  77. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelector.d.ts +2 -1
  78. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelector.js +12 -15
  79. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelectorEntry.d.ts +2 -2
  80. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ColorRampSelectorEntry.js +3 -11
  81. package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ModeSelectRow.d.ts +1 -1
  82. package/lib/features/layers/symbology/components/color_ramp/RgbaColorPicker.d.ts +13 -0
  83. package/lib/features/layers/symbology/components/color_ramp/RgbaColorPicker.js +128 -0
  84. package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopContainer.js +3 -1
  85. package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopRow.d.ts +2 -2
  86. package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopRow.js +12 -7
  87. package/lib/features/layers/symbology/grammarToOLLayer.d.ts +27 -0
  88. package/lib/features/layers/symbology/grammarToOLLayer.js +145 -0
  89. package/lib/features/layers/symbology/grammarToOLStyle.d.ts +32 -0
  90. package/lib/features/layers/symbology/grammarToOLStyle.js +467 -0
  91. package/lib/{dialogs → features/layers}/symbology/hooks/useGetBandInfo.d.ts +1 -1
  92. package/lib/{dialogs → features/layers}/symbology/hooks/useGetBandInfo.js +1 -1
  93. package/lib/{dialogs → features/layers}/symbology/hooks/useGetProperties.js +1 -1
  94. package/lib/{dialogs → features/layers}/symbology/hooks/useGetSymbology.js +4 -2
  95. package/lib/features/layers/symbology/styleBuilder.d.ts +21 -0
  96. package/lib/features/layers/symbology/styleBuilder.js +145 -0
  97. package/lib/{dialogs → features/layers}/symbology/symbologyDialog.d.ts +3 -2
  98. package/lib/{dialogs → features/layers}/symbology/symbologyDialog.js +12 -12
  99. package/lib/features/layers/symbology/symbologyUtils.d.ts +41 -0
  100. package/lib/features/layers/symbology/symbologyUtils.js +114 -0
  101. package/lib/{panelview/objectproperties.d.ts → features/objectproperties/index.d.ts} +1 -1
  102. package/lib/{panelview/objectproperties.js → features/objectproperties/index.js} +5 -10
  103. package/lib/{dialogs → features/processing}/ProcessingFormDialog.d.ts +1 -1
  104. package/lib/{dialogs → features/processing}/ProcessingFormDialog.js +28 -14
  105. package/lib/features/processing/forms/MapExtentToggle.d.ts +13 -0
  106. package/lib/features/processing/forms/MapExtentToggle.js +20 -0
  107. package/lib/features/processing/forms/clipRasterByExtentForm.d.ts +10 -0
  108. package/lib/features/processing/forms/clipRasterByExtentForm.js +99 -0
  109. package/lib/{formbuilder/objectform/process → features/processing/forms}/dissolveProcessForm.js +5 -5
  110. package/lib/{formbuilder/objectform → features/processing/forms}/processingForm.d.ts +1 -1
  111. package/lib/{formbuilder/objectform → features/processing/forms}/processingForm.js +6 -6
  112. package/lib/features/processing/forms/rasterizeForm.d.ts +10 -0
  113. package/lib/features/processing/forms/rasterizeForm.js +75 -0
  114. package/lib/features/processing/forms/useMapExtent.d.ts +22 -0
  115. package/lib/features/processing/forms/useMapExtent.js +57 -0
  116. package/lib/{processing → features/processing}/index.d.ts +19 -2
  117. package/lib/features/processing/index.js +1246 -0
  118. package/lib/{processing → features/processing}/processingCommands.d.ts +1 -1
  119. package/lib/features/processing/processingCommands.js +168 -0
  120. package/lib/features/processing/serverProcessing.d.ts +51 -0
  121. package/lib/features/processing/serverProcessing.js +99 -0
  122. package/lib/{stacBrowser → features/stac-browser}/components/StacPanel.js +2 -2
  123. package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableComboBox.js +64 -21
  124. package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableRow.js +1 -1
  125. package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacFilterExtensionPanel.js +2 -2
  126. package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacQueryableFilters.js +1 -1
  127. package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacFilterSection.js +3 -3
  128. package/lib/{stacBrowser → features/stac-browser}/components/shared/StacPanelResults.js +3 -3
  129. package/lib/{stacBrowser → features/stac-browser}/components/shared/StacSpatialExtent.js +1 -1
  130. package/lib/{stacBrowser → features/stac-browser}/components/shared/StacTemporalExtent.js +1 -1
  131. package/lib/{stacBrowser → features/stac-browser}/context/StacResultsContext.js +2 -2
  132. package/lib/{stacBrowser → features/stac-browser}/hooks/useGeodesSearch.js +2 -2
  133. package/lib/{stacBrowser → features/stac-browser}/hooks/useStacFilterExtension.d.ts +1 -1
  134. package/lib/{stacBrowser → features/stac-browser}/hooks/useStacFilterExtension.js +198 -114
  135. package/lib/{stacBrowser → features/stac-browser}/hooks/useStacSearch.d.ts +1 -0
  136. package/lib/{stacBrowser → features/stac-browser}/hooks/useStacSearch.js +19 -11
  137. package/lib/features/stac-browser/types/types.js +1 -0
  138. package/lib/{panelview/story-maps → features/story}/SpectaPanel.d.ts +4 -1
  139. package/lib/{panelview/story-maps → features/story}/SpectaPanel.js +3 -4
  140. package/lib/{panelview/story-maps → features/story}/StoryEditorPanel.js +1 -1
  141. package/lib/{panelview/story-maps → features/story}/StoryViewerPanel.d.ts +12 -11
  142. package/lib/features/story/StoryViewerPanel.js +64 -0
  143. package/lib/features/story/__tests__/fixtures/listStoryTestItems.d.ts +9 -0
  144. package/lib/features/story/__tests__/fixtures/listStoryTestItems.js +21 -0
  145. package/lib/features/story/components/ListStoryMapOverlayPanel.d.ts +10 -0
  146. package/lib/features/story/components/ListStoryMapOverlayPanel.js +11 -0
  147. package/lib/features/story/components/ListStoryMarkdownMeasurePane.d.ts +11 -0
  148. package/lib/features/story/components/ListStoryMarkdownMeasurePane.js +55 -0
  149. package/lib/features/story/components/ListStoryOverlayMarkdown.d.ts +15 -0
  150. package/lib/features/story/components/ListStoryOverlayMarkdown.js +93 -0
  151. package/lib/features/story/components/ListStoryStageOverlay.d.ts +12 -0
  152. package/lib/features/story/components/ListStoryStageOverlay.js +132 -0
  153. package/lib/features/story/components/ListStoryVirtualScrollTrack.d.ts +6 -0
  154. package/lib/features/story/components/ListStoryVirtualScrollTrack.js +7 -0
  155. package/lib/{panelview/story-maps → features/story}/components/SpectaDesktopView.d.ts +4 -2
  156. package/lib/features/story/components/SpectaDesktopView.js +67 -0
  157. package/lib/{panelview/story-maps → features/story}/components/SpectaMobileView.d.ts +2 -4
  158. package/lib/{panelview/story-maps → features/story}/components/SpectaMobileView.js +3 -3
  159. package/lib/features/story/components/SpectaSingleModeContent.d.ts +18 -0
  160. package/lib/features/story/components/SpectaSingleModeContent.js +8 -0
  161. package/lib/features/story/context/ListStoryScrollTrackContext.d.ts +15 -0
  162. package/lib/features/story/context/ListStoryScrollTrackContext.js +142 -0
  163. package/lib/features/story/hooks/useCurrentSegmentIndex.d.ts +3 -0
  164. package/lib/features/story/hooks/useCurrentSegmentIndex.js +17 -0
  165. package/lib/features/story/hooks/useListStoryScroll.d.ts +15 -0
  166. package/lib/features/story/hooks/useListStoryScroll.js +107 -0
  167. package/lib/features/story/hooks/useQueuedMarkdownHeightMeasure.d.ts +19 -0
  168. package/lib/features/story/hooks/useQueuedMarkdownHeightMeasure.js +56 -0
  169. package/lib/features/story/hooks/useStoryImagePreload.d.ts +1 -0
  170. package/lib/features/story/hooks/useStoryImagePreload.js +24 -0
  171. package/lib/{panelview/story-maps → features/story}/hooks/useStoryMap.d.ts +2 -7
  172. package/lib/{panelview/story-maps → features/story}/hooks/useStoryMap.js +20 -44
  173. package/lib/features/story/hooks/useStoryScrollState.d.ts +21 -0
  174. package/lib/features/story/hooks/useStoryScrollState.js +39 -0
  175. package/lib/features/story/hooks/useStorySegmentSync.d.ts +8 -0
  176. package/lib/features/story/hooks/useStorySegmentSync.js +51 -0
  177. package/lib/features/story/types/types.d.ts +38 -0
  178. package/lib/features/story/types/types.js +1 -0
  179. package/lib/features/story/utils/computeListStoryScrollState.d.ts +12 -0
  180. package/lib/features/story/utils/computeListStoryScrollState.js +70 -0
  181. package/lib/features/story/utils/listStoryMeasureQueue.d.ts +11 -0
  182. package/lib/features/story/utils/listStoryMeasureQueue.js +14 -0
  183. package/lib/features/story/utils/listStoryScrollTrack.d.ts +17 -0
  184. package/lib/features/story/utils/listStoryScrollTrack.js +72 -0
  185. package/lib/features/story/utils/spectaPresentation.d.ts +9 -0
  186. package/lib/features/story/utils/spectaPresentation.js +37 -0
  187. package/lib/features/story/utils/storySegmentViewItems.d.ts +5 -0
  188. package/lib/features/story/utils/storySegmentViewItems.js +30 -0
  189. package/lib/index.d.ts +11 -9
  190. package/lib/index.js +11 -9
  191. package/lib/keybindings.json +5 -0
  192. package/lib/mainview/OpenEOTileLayer.d.ts +49 -0
  193. package/lib/mainview/OpenEOTileLayer.js +179 -0
  194. package/lib/mainview/TemporalSlider.js +11 -9
  195. package/lib/mainview/geoJsonFeaturePatch.d.ts +9 -0
  196. package/lib/mainview/geoJsonFeaturePatch.js +43 -0
  197. package/lib/mainview/mainView.d.ts +93 -8
  198. package/lib/mainview/mainView.js +1286 -529
  199. package/lib/mainview/mainviewwidget.d.ts +5 -1
  200. package/lib/mainview/mainviewwidget.js +4 -2
  201. package/lib/shared/components/Button.d.ts +1 -1
  202. package/lib/shared/components/DropdownMenu.d.ts +1 -0
  203. package/lib/shared/components/DropdownMenu.js +3 -2
  204. package/lib/shared/components/NativeSelect.d.ts +8 -0
  205. package/lib/shared/components/NativeSelect.js +29 -0
  206. package/lib/shared/components/Tabs.d.ts +3 -3
  207. package/lib/shared/components/Tabs.js +5 -5
  208. package/lib/{formbuilder → shared/formbuilder}/creationform.js +71 -4
  209. package/lib/{formbuilder → shared/formbuilder}/editform.js +1 -1
  210. package/lib/{formbuilder → shared/formbuilder}/formselectors.d.ts +2 -2
  211. package/lib/{formbuilder → shared/formbuilder}/formselectors.js +13 -4
  212. package/lib/shared/formbuilder/index.d.ts +4 -0
  213. package/lib/shared/formbuilder/index.js +4 -0
  214. package/lib/{formbuilder → shared/formbuilder}/objectform/SchemaForm.d.ts +1 -1
  215. package/lib/{formbuilder → shared/formbuilder}/objectform/StoryEditorForm.d.ts +1 -1
  216. package/lib/{formbuilder → shared/formbuilder}/objectform/StoryEditorForm.js +1 -1
  217. package/lib/{formbuilder → shared/formbuilder}/objectform/components/LayerSelect.js +1 -1
  218. package/lib/{formbuilder → shared/formbuilder}/objectform/components/SegmentFormSymbology.js +4 -4
  219. package/lib/{formbuilder → shared/formbuilder}/objectform/components/SourcePropertiesField.js +1 -1
  220. package/lib/{formbuilder → shared/formbuilder}/objectform/components/StorySegmentReset.js +1 -1
  221. package/lib/shared/formbuilder/objectform/components/WmsTileSourceUrlInput.d.ts +3 -0
  222. package/lib/shared/formbuilder/objectform/components/WmsTileSourceUrlInput.js +84 -0
  223. package/lib/{formbuilder → shared/formbuilder}/objectform/fileselectorwidget.js +8 -1
  224. package/lib/{formbuilder → shared/formbuilder}/objectform/schemaUtils.d.ts +3 -1
  225. package/lib/{formbuilder → shared/formbuilder}/objectform/schemaUtils.js +11 -0
  226. package/lib/{formbuilder → shared/formbuilder}/objectform/useSchemaFormState.d.ts +2 -2
  227. package/lib/{formbuilder → shared/formbuilder}/objectform/useSchemaFormState.js +1 -1
  228. package/lib/{icons.d.ts → shared/icons.d.ts} +2 -0
  229. package/lib/{icons.js → shared/icons.js} +28 -18
  230. package/lib/tools.d.ts +3 -1
  231. package/lib/tools.js +140 -6
  232. package/lib/types.d.ts +12 -1
  233. package/lib/types.js +6 -1
  234. package/lib/{menus.js → workspace/menus.js} +14 -2
  235. package/lib/workspace/panels/components/TabbedPanel.d.ts +17 -0
  236. package/lib/workspace/panels/components/TabbedPanel.js +19 -0
  237. package/lib/{panelview → workspace/panels}/components/layers.js +82 -20
  238. package/lib/workspace/panels/components/legendItem.js +680 -0
  239. package/lib/workspace/panels/hooks/useLayerTree.d.ts +19 -0
  240. package/lib/workspace/panels/hooks/useLayerTree.js +103 -0
  241. package/lib/workspace/panels/hooks/useRightPanelOptions.d.ts +27 -0
  242. package/lib/workspace/panels/hooks/useRightPanelOptions.js +72 -0
  243. package/lib/workspace/panels/hooks/useUIState.d.ts +2 -0
  244. package/lib/workspace/panels/hooks/useUIState.js +25 -0
  245. package/lib/{panelview → workspace/panels}/index.d.ts +1 -1
  246. package/lib/{panelview → workspace/panels}/index.js +1 -1
  247. package/lib/{panelview → workspace/panels}/leftpanel.d.ts +1 -1
  248. package/lib/workspace/panels/leftpanel.js +70 -0
  249. package/lib/{panelview/rightpanel.d.ts → workspace/panels/mergedpanel.d.ts} +6 -5
  250. package/lib/workspace/panels/mergedpanel.js +166 -0
  251. package/lib/workspace/panels/rightpanel.d.ts +25 -0
  252. package/lib/{panelview → workspace/panels}/rightpanel.js +53 -63
  253. package/lib/{statusbar → workspace/statusbar}/StatusBar.js +5 -4
  254. package/lib/{toolbar → workspace/toolbar}/widget.d.ts +2 -0
  255. package/lib/{toolbar → workspace/toolbar}/widget.js +33 -5
  256. package/lib/{widget.d.ts → workspace/widget.d.ts} +7 -5
  257. package/lib/{widget.js → workspace/widget.js} +16 -7
  258. package/package.json +19 -4
  259. package/style/base.css +109 -1
  260. package/style/icons/geopackage.svg +95 -0
  261. package/style/icons/pencil_solid.svg +7 -0
  262. package/style/identify.css +95 -0
  263. package/style/layerBrowser.css +28 -0
  264. package/style/leftPanel.css +25 -0
  265. package/style/shared/button.css +12 -0
  266. package/style/shared/dropdownMenu.css +9 -0
  267. package/style/shared/nativeSelect.css +75 -0
  268. package/style/shared/tabs.css +3 -3
  269. package/style/spectaProgressBar.css +0 -1
  270. package/style/storyPanel.css +142 -9
  271. package/style/storySpectaArticleOverlay.css +129 -0
  272. package/style/symbologyDialog.css +330 -28
  273. package/lib/dialogs/symbology/colorRampUtils.d.ts +0 -20
  274. package/lib/dialogs/symbology/colorRampUtils.js +0 -132
  275. package/lib/dialogs/symbology/symbologyUtils.d.ts +0 -33
  276. package/lib/dialogs/symbology/symbologyUtils.js +0 -180
  277. package/lib/dialogs/symbology/tiff_layer/TiffRendering.d.ts +0 -4
  278. package/lib/dialogs/symbology/tiff_layer/TiffRendering.js +0 -42
  279. package/lib/dialogs/symbology/tiff_layer/components/BandRow.d.ts +0 -23
  280. package/lib/dialogs/symbology/tiff_layer/components/BandRow.js +0 -59
  281. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.d.ts +0 -4
  282. package/lib/dialogs/symbology/tiff_layer/types/MultibandColor.js +0 -92
  283. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.d.ts +0 -5
  284. package/lib/dialogs/symbology/tiff_layer/types/SingleBandPseudoColor.js +0 -301
  285. package/lib/dialogs/symbology/vector_layer/VectorRendering.d.ts +0 -4
  286. package/lib/dialogs/symbology/vector_layer/VectorRendering.js +0 -112
  287. package/lib/dialogs/symbology/vector_layer/components/ValueSelect.d.ts +0 -8
  288. package/lib/dialogs/symbology/vector_layer/components/ValueSelect.js +0 -9
  289. package/lib/dialogs/symbology/vector_layer/types/Canonical.d.ts +0 -4
  290. package/lib/dialogs/symbology/vector_layer/types/Canonical.js +0 -65
  291. package/lib/dialogs/symbology/vector_layer/types/Categorized.d.ts +0 -4
  292. package/lib/dialogs/symbology/vector_layer/types/Categorized.js +0 -196
  293. package/lib/dialogs/symbology/vector_layer/types/Graduated.d.ts +0 -4
  294. package/lib/dialogs/symbology/vector_layer/types/Graduated.js +0 -250
  295. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.d.ts +0 -4
  296. package/lib/dialogs/symbology/vector_layer/types/SimpleSymbol.js +0 -105
  297. package/lib/formbuilder/index.d.ts +0 -4
  298. package/lib/formbuilder/index.js +0 -4
  299. package/lib/formbuilder/objectform/layer/storySegmentLayerForm.js +0 -95
  300. package/lib/formbuilder/objectform/layer/webGlLayerForm.d.ts +0 -3
  301. package/lib/formbuilder/objectform/process/index.d.ts +0 -1
  302. package/lib/formbuilder/objectform/process/index.js +0 -1
  303. package/lib/panelview/components/legendItem.js +0 -200
  304. package/lib/panelview/identify-panel/IdentifyPanel.js +0 -102
  305. package/lib/panelview/leftpanel.js +0 -139
  306. package/lib/panelview/story-maps/StoryViewerPanel.js +0 -116
  307. package/lib/panelview/story-maps/components/SpectaDesktopView.js +0 -49
  308. package/lib/processing/index.js +0 -200
  309. package/lib/processing/processingCommands.js +0 -67
  310. /package/lib/{panelview/annotationPanel.d.ts → features/annotations/AnnotationsPanel.d.ts} +0 -0
  311. /package/lib/{annotations → features/annotations}/components/Annotation.d.ts +0 -0
  312. /package/lib/{annotations → features/annotations}/components/Annotation.js +0 -0
  313. /package/lib/{annotations → features/annotations}/components/AnnotationFloater.d.ts +0 -0
  314. /package/lib/{annotations → features/annotations}/components/AnnotationFloater.js +0 -0
  315. /package/lib/{annotations → features/annotations}/components/Message.d.ts +0 -0
  316. /package/lib/{annotations → features/annotations}/components/Message.js +0 -0
  317. /package/lib/{annotations → features/annotations}/model.d.ts +0 -0
  318. /package/lib/{annotations → features/annotations}/model.js +0 -0
  319. /package/lib/{console → features/console}/consoleview.d.ts +0 -0
  320. /package/lib/{console → features/console}/index.d.ts +0 -0
  321. /package/lib/{console → features/console}/index.js +0 -0
  322. /package/lib/{panelview/filter-panel → features/filter}/Filter.d.ts +0 -0
  323. /package/lib/{stacBrowser/types/types.js → features/identify/types/editorTypes.js} +0 -0
  324. /package/lib/{dialogs/layerBrowserDialog.d.ts → features/layer-browser/index.d.ts} +0 -0
  325. /package/lib/{formbuilder/objectform → features/layers/forms}/layer/heatmapLayerForm.d.ts +0 -0
  326. /package/lib/{formbuilder/objectform → features/layers/forms}/layer/hillshadeLayerForm.d.ts +0 -0
  327. /package/lib/{formbuilder/objectform → features/layers/forms}/layer/storySegmentLayerForm.d.ts +0 -0
  328. /package/lib/{formbuilder/objectform → features/layers/forms}/layer/vectorlayerform.d.ts +0 -0
  329. /package/lib/{formbuilder/objectform → features/layers/forms}/source/geojsonsource.d.ts +0 -0
  330. /package/lib/{formbuilder/objectform → features/layers/forms}/source/geotiffsource.d.ts +0 -0
  331. /package/lib/{formbuilder/objectform → features/layers/forms}/source/pathbasedsource.d.ts +0 -0
  332. /package/lib/{formbuilder/objectform → features/layers/forms}/source/tilesourceform.d.ts +0 -0
  333. /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/ModeSelectRow.js +0 -0
  334. /package/lib/{dialogs → features/layers}/symbology/components/color_ramp/cmocean.json +0 -0
  335. /package/lib/{dialogs → features/layers}/symbology/components/color_stops/StopContainer.d.ts +0 -0
  336. /package/lib/{dialogs → features/layers}/symbology/hooks/useEffectiveSymbologyParams.d.ts +0 -0
  337. /package/lib/{dialogs → features/layers}/symbology/hooks/useEffectiveSymbologyParams.js +0 -0
  338. /package/lib/{dialogs → features/layers}/symbology/hooks/useGetProperties.d.ts +0 -0
  339. /package/lib/{dialogs → features/layers}/symbology/hooks/useGetSymbology.d.ts +0 -0
  340. /package/lib/{dialogs → features/layers}/symbology/hooks/useOkSignal.d.ts +0 -0
  341. /package/lib/{dialogs → features/layers}/symbology/hooks/useOkSignal.js +0 -0
  342. /package/lib/{formbuilder/objectform/process → features/processing/forms}/dissolveProcessForm.d.ts +0 -0
  343. /package/lib/{processing → features/processing}/processingFormToParam.d.ts +0 -0
  344. /package/lib/{processing → features/processing}/processingFormToParam.js +0 -0
  345. /package/lib/{stacBrowser → features/stac-browser}/components/StacPanel.d.ts +0 -0
  346. /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableComboBox.d.ts +0 -0
  347. /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/QueryableRow.d.ts +0 -0
  348. /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacFilterExtensionPanel.d.ts +0 -0
  349. /package/lib/{stacBrowser → features/stac-browser}/components/filter-extension/StacQueryableFilters.d.ts +0 -0
  350. /package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacFilterSection.d.ts +0 -0
  351. /package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacGeodesFilterPanel.d.ts +0 -0
  352. /package/lib/{stacBrowser → features/stac-browser}/components/geodes/StacGeodesFilterPanel.js +0 -0
  353. /package/lib/{stacBrowser → features/stac-browser}/components/shared/StacPanelResults.d.ts +0 -0
  354. /package/lib/{stacBrowser → features/stac-browser}/components/shared/StacSpatialExtent.d.ts +0 -0
  355. /package/lib/{stacBrowser → features/stac-browser}/components/shared/StacTemporalExtent.d.ts +0 -0
  356. /package/lib/{stacBrowser → features/stac-browser}/constants.d.ts +0 -0
  357. /package/lib/{stacBrowser → features/stac-browser}/constants.js +0 -0
  358. /package/lib/{stacBrowser → features/stac-browser}/context/StacResultsContext.d.ts +0 -0
  359. /package/lib/{stacBrowser → features/stac-browser}/hooks/useGeodesSearch.d.ts +0 -0
  360. /package/lib/{stacBrowser → features/stac-browser}/index.d.ts +0 -0
  361. /package/lib/{stacBrowser → features/stac-browser}/index.js +0 -0
  362. /package/lib/{stacBrowser → features/stac-browser}/types/types.d.ts +0 -0
  363. /package/lib/{panelview/story-maps → features/story}/StoryEditorPanel.d.ts +0 -0
  364. /package/lib/{panelview/story-maps → features/story}/components/PreviewModeSwitch.d.ts +0 -0
  365. /package/lib/{panelview/story-maps → features/story}/components/PreviewModeSwitch.js +0 -0
  366. /package/lib/{panelview/story-maps → features/story}/components/StoryContentSection.d.ts +0 -0
  367. /package/lib/{panelview/story-maps → features/story}/components/StoryContentSection.js +0 -0
  368. /package/lib/{panelview/story-maps → features/story}/components/StoryImageSection.d.ts +0 -0
  369. /package/lib/{panelview/story-maps → features/story}/components/StoryImageSection.js +0 -0
  370. /package/lib/{panelview/story-maps → features/story}/components/StoryNavBar.d.ts +0 -0
  371. /package/lib/{panelview/story-maps → features/story}/components/StoryNavBar.js +0 -0
  372. /package/lib/{panelview/story-maps → features/story}/components/StorySubtitleSection.d.ts +0 -0
  373. /package/lib/{panelview/story-maps → features/story}/components/StorySubtitleSection.js +0 -0
  374. /package/lib/{panelview/story-maps → features/story}/components/StoryTitleSection.d.ts +0 -0
  375. /package/lib/{panelview/story-maps → features/story}/components/StoryTitleSection.js +0 -0
  376. /package/lib/{formbuilder → shared/formbuilder}/creationform.d.ts +0 -0
  377. /package/lib/{formbuilder → shared/formbuilder}/editform.d.ts +0 -0
  378. /package/lib/{formbuilder → shared/formbuilder}/objectform/SchemaForm.js +0 -0
  379. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/LayerSelect.d.ts +0 -0
  380. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/OpacitySlider.d.ts +0 -0
  381. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/OpacitySlider.js +0 -0
  382. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/SegmentFormSymbology.d.ts +0 -0
  383. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/SourcePropertiesField.d.ts +0 -0
  384. /package/lib/{formbuilder → shared/formbuilder}/objectform/components/StorySegmentReset.d.ts +0 -0
  385. /package/lib/{formbuilder → shared/formbuilder}/objectform/fileselectorwidget.d.ts +0 -0
  386. /package/lib/{store.d.ts → shared/store.d.ts} +0 -0
  387. /package/lib/{store.js → shared/store.js} +0 -0
  388. /package/lib/{menus.d.ts → workspace/menus.d.ts} +0 -0
  389. /package/lib/{panelview → workspace/panels}/components/layers.d.ts +0 -0
  390. /package/lib/{panelview → workspace/panels}/components/legendItem.d.ts +0 -0
  391. /package/lib/{panelview → workspace/panels}/header.d.ts +0 -0
  392. /package/lib/{panelview → workspace/panels}/header.js +0 -0
  393. /package/lib/{statusbar → workspace/statusbar}/SpectaPresentationProgressBar.d.ts +0 -0
  394. /package/lib/{statusbar → workspace/statusbar}/SpectaPresentationProgressBar.js +0 -0
  395. /package/lib/{statusbar → workspace/statusbar}/StatusBar.d.ts +0 -0
  396. /package/lib/{toolbar → workspace/toolbar}/index.d.ts +0 -0
  397. /package/lib/{toolbar → workspace/toolbar}/index.js +0 -0
@@ -9,45 +9,79 @@ var __rest = (this && this.__rest) || function (s, e) {
9
9
  }
10
10
  return t;
11
11
  };
12
- import { JupyterGISModel, } from '@jupytergis/schema';
12
+ import { JupyterGISModel, DEFAULT_PROJECTION, } from '@jupytergis/schema';
13
13
  import { showErrorMessage } from '@jupyterlab/apputils';
14
14
  import { CommandRegistry } from '@lumino/commands';
15
15
  import { UUID } from '@lumino/coreutils';
16
- import { ContextMenu } from '@lumino/widgets';
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 { Heatmap as HeatmapLayer, 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
- import { GeoTIFF as GeoTIFFSource, ImageTile as ImageTileSource, Vector as VectorSource, VectorTile as VectorTileSource, XYZ as XYZSource, Tile as TileSource, } from 'ol/source';
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";
46
+ import AnnotationFloater from "../features/annotations/components/AnnotationFloater";
47
+ import FeatureFloater from "../features/identify/components/FeatureFloater";
48
+ import { getFeatureIdentifier } from "../features/identify/utils/getFeatureIdentifier";
41
49
  import { LoadingOverlay } from "../shared/components/loading";
42
50
  import useMediaQuery from "../shared/hooks/useMediaQuery";
43
- import StatusBar from "../statusbar/StatusBar";
44
- import { debounce, isLightTheme, loadFile, throttle } from "../tools";
51
+ import { markerIcon } from "../shared/icons";
52
+ import { debounce, INTERNAL_PROXY_BASE, isJupyterLite, isLightTheme, loadFile, throttle, } from "../tools";
53
+ import StatusBar from "../workspace/statusbar/StatusBar";
45
54
  import CollaboratorPointers from './CollaboratorPointers';
46
55
  import { FollowIndicator } from './FollowIndicator';
56
+ import { OpenEOTileLayer, OpenEOTileSource } from './OpenEOTileLayer';
47
57
  import TemporalSlider from './TemporalSlider';
48
- import { markerIcon } from '../icons';
49
- import { LeftPanel, RightPanel } from '../panelview';
50
- import { SpectaPanel } from '../panelview/story-maps/SpectaPanel';
58
+ import { createGeoJSONFeaturePatcher, } from './geoJsonFeaturePatch';
59
+ import { ensureHighlightLayer } from '../features/identify/utils/highlightLayer';
60
+ import { buildHighlightStyle } from '../features/identify/utils/highlightStyle';
61
+ import { grammarToOLLayer } from '../features/layers/symbology/grammarToOLLayer';
62
+ import { extractEncodingFieldValues, grammarToOLStyle, } from '../features/layers/symbology/grammarToOLStyle';
63
+ import { DEFAULT_FLAT_STYLE } from '../features/layers/symbology/styleBuilder';
64
+ import { SpectaPanel } from '../features/story/SpectaPanel';
65
+ import { ListStoryStageOverlay } from '../features/story/components/ListStoryStageOverlay';
66
+ import { ListStoryScrollTrackProvider } from '../features/story/context/ListStoryScrollTrackContext';
67
+ import { STORY_TYPE } from '../types';
68
+ import { LeftPanel, MergedPanel, RightPanel } from '../workspace/panels';
69
+ const DRAW_GEOMETRIES = ['Point', 'LineString', 'Polygon'];
70
+ const drawInteractionStyle = new Style({
71
+ fill: new Fill({
72
+ color: 'rgba(255, 255, 255, 0.2)',
73
+ }),
74
+ stroke: new Stroke({
75
+ color: '#ffcc33',
76
+ width: 2,
77
+ }),
78
+ image: new CircleStyle({
79
+ radius: 7,
80
+ fill: new Fill({
81
+ color: '#ffcc33',
82
+ }),
83
+ }),
84
+ });
51
85
  export class MainView extends React.Component {
52
86
  constructor(props) {
53
87
  super(props);
@@ -64,48 +98,8 @@ export class MainView extends React.Component {
64
98
  return transformExtent(extent, view.getProjection(), targetProjection);
65
99
  };
66
100
  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
101
  const selectInteraction = new Select({
108
- hitTolerance: 5,
102
+ hitTolerance: 3,
109
103
  multi: true,
110
104
  layers: layer => {
111
105
  var _a, _b;
@@ -115,19 +109,84 @@ export class MainView extends React.Component {
115
109
  return false;
116
110
  }
117
111
  const selectedLayerId = Object.keys(selectedLayers)[0];
118
- return layer === this.getLayer(selectedLayerId);
112
+ const expected = this.getLayer(selectedLayerId);
113
+ if (layer === expected) {
114
+ return true;
115
+ }
116
+ // Grammar multi-layer symbology wraps sub-layers in a LayerGroup.
117
+ // OL Select flattens groups, so we receive leaf layers, not the group.
118
+ if (expected instanceof LayerGroup) {
119
+ return expected.getLayers().getArray().includes(layer);
120
+ }
121
+ return false;
119
122
  },
120
123
  condition: (event) => {
121
124
  return singleClick(event) && this._model.currentMode === 'identifying';
122
125
  },
123
- style: styleFunction,
126
+ // Use the layer's own style so selected features keep their original
127
+ // appearance. Visual highlight feedback comes from _highlightLayer.
128
+ style: null,
124
129
  });
125
130
  selectInteraction.on('select', event => {
131
+ var _a, _b, _c;
126
132
  const identifiedFeatures = [];
133
+ const highlightFeatures = [];
134
+ // Look up the selected layer's style function for adaptive highlights.
135
+ const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
136
+ const selectedLayers = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
137
+ const selectedLayerId = selectedLayers
138
+ ? Object.keys(selectedLayers)[0]
139
+ : undefined;
140
+ const mapLayer = selectedLayerId
141
+ ? this.getLayer(selectedLayerId)
142
+ : undefined;
143
+ // For LayerGroup (multi-layer grammar), collect style functions from
144
+ // all sub-layers so we can match the right one per feature.
145
+ const styleFnCandidates = [];
146
+ if (mapLayer instanceof LayerGroup) {
147
+ for (const sub of mapLayer.getLayers().getArray()) {
148
+ if ('getStyleFunction' in sub) {
149
+ styleFnCandidates.push(sub.getStyleFunction());
150
+ }
151
+ }
152
+ }
153
+ else if (mapLayer && 'getStyleFunction' in mapLayer) {
154
+ styleFnCandidates.push(mapLayer.getStyleFunction());
155
+ }
156
+ const resolution = (_c = this._Map.getView().getResolution()) !== null && _c !== void 0 ? _c : 1;
127
157
  selectInteraction.getFeatures().forEach(feature => {
128
- identifiedFeatures.push(feature.getProperties());
158
+ identifiedFeatures.push({
159
+ feature: feature.getProperties(),
160
+ floaterOpen: false,
161
+ });
162
+ const geom = feature.getGeometry();
163
+ if (geom) {
164
+ const hlFeature = new Feature({ geometry: geom });
165
+ // Try each style function candidate; use the first that resolves
166
+ // a non-empty style array (important for LayerGroup sub-layers
167
+ // where only one sub-layer's style applies to this feature).
168
+ for (const fn of styleFnCandidates) {
169
+ if (!fn) {
170
+ continue;
171
+ }
172
+ const resolved = fn(feature, resolution);
173
+ const styles = Array.isArray(resolved)
174
+ ? resolved
175
+ : resolved
176
+ ? [resolved]
177
+ : [];
178
+ if (styles.length > 0) {
179
+ const gType = geom.getType();
180
+ hlFeature.setStyle(styles.map(s => this._buildHighlightStyle(s, gType)));
181
+ break;
182
+ }
183
+ }
184
+ highlightFeatures.push(hlFeature);
185
+ }
129
186
  });
130
187
  this._model.syncIdentifiedFeatures(identifiedFeatures, this._mainViewModel.id);
188
+ // Sync _highlightLayer with the current selection (clears on deselect).
189
+ this._setHighlightFeatures(highlightFeatures);
131
190
  });
132
191
  this._Map.addInteraction(selectInteraction);
133
192
  };
@@ -161,31 +220,65 @@ export class MainView extends React.Component {
161
220
  });
162
221
  },
163
222
  });
223
+ this._commands.addCommand('Copy-Coordinates-Map-CRS', {
224
+ label: () => {
225
+ if (!this._Map || !this._clickCoords) {
226
+ return 'Map CRS';
227
+ }
228
+ const proj = this._Map.getView().getProjection().getCode();
229
+ const coord = this._clickCoords;
230
+ return `Map CRS — ${proj} (${coord[0].toFixed(0)}E, ${coord[1].toFixed(0)}N)`;
231
+ },
232
+ execute: async () => {
233
+ const coord = this._clickCoords;
234
+ const text = `${coord[0].toFixed(0)}, ${coord[1].toFixed(0)}`;
235
+ await navigator.clipboard.writeText(text);
236
+ },
237
+ });
238
+ this._commands.addCommand('Copy-Coordinates-LonLat', {
239
+ label: () => {
240
+ if (!this._Map || !this._clickCoords) {
241
+ return 'Latitude/Longitude';
242
+ }
243
+ const lonLat = toLonLat(this._clickCoords, this._Map.getView().getProjection());
244
+ return `Latitude/Longitude: (${lonLat[1].toFixed(6)}N, ${lonLat[0].toFixed(6)}E)`;
245
+ },
246
+ execute: async () => {
247
+ const lonLat = toLonLat(this._clickCoords, this._Map.getView().getProjection());
248
+ const text = `${lonLat[1].toFixed(6)}, ${lonLat[0].toFixed(6)}`;
249
+ await navigator.clipboard.writeText(text);
250
+ },
251
+ });
164
252
  this._contextMenu.addItem({
165
253
  command: CommandIDs.addAnnotation,
166
254
  selector: '.ol-viewport',
167
255
  rank: 1,
168
256
  });
257
+ const copyCoordinatesMenu = new Menu({ commands: this._commands });
258
+ copyCoordinatesMenu.title.label = 'Copy Coordinates';
259
+ copyCoordinatesMenu.addItem({
260
+ command: 'Copy-Coordinates-Map-CRS',
261
+ });
262
+ copyCoordinatesMenu.addItem({
263
+ command: 'Copy-Coordinates-LonLat',
264
+ });
265
+ this._contextMenu.addItem({
266
+ type: 'submenu',
267
+ submenu: copyCoordinatesMenu,
268
+ selector: '.ol-viewport',
269
+ rank: 2,
270
+ });
169
271
  };
272
+ // Used by VectorTileLayer (which shares a flat-style API with Grammar output).
170
273
  this.vectorLayerStyleRuleBuilder = (layer) => {
171
274
  var _a, _b;
172
275
  const layerParams = layer.parameters;
173
- if (!layerParams) {
174
- return;
276
+ const ss = layerParams === null || layerParams === void 0 ? void 0 : layerParams.symbologyState;
277
+ if (!ss || Object.keys(ss).length === 0) {
278
+ return [{ style: DEFAULT_FLAT_STYLE }];
175
279
  }
176
- const defaultStyle = {
177
- 'fill-color': 'rgba(255,255,255,0.4)',
178
- 'stroke-color': '#3399CC',
179
- 'stroke-width': 1.25,
180
- 'circle-radius': 5,
181
- 'circle-fill-color': 'rgba(255,255,255,0.4)',
182
- 'circle-stroke-width': 1.25,
183
- 'circle-stroke-color': '#3399CC',
184
- };
185
- const defaultRules = {
186
- style: defaultStyle,
187
- };
188
- const layerStyle = Object.assign({}, defaultRules);
280
+ const flatStyle = grammarToOLStyle(layerParams.symbologyState, []);
281
+ const layerStyle = { style: flatStyle };
189
282
  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) {
190
283
  const buildCondition = (filter) => {
191
284
  const base = [filter.operator, ['get', filter.feature]];
@@ -193,26 +286,14 @@ export class MainView extends React.Component {
193
286
  ? [...base, filter.betweenMin, filter.betweenMax]
194
287
  : [...base, filter.value];
195
288
  };
196
- let filterExpr;
197
- // 'Any' and 'All' operators require more than one argument
198
- // So if there's only one filter, skip that part to avoid error
199
- if (layer.filters.appliedFilters.length === 1) {
200
- filterExpr = buildCondition(layer.filters.appliedFilters[0]);
201
- }
202
- else {
203
- // Arguments for "Any" and 'All' need to be wrapped in brackets
204
- filterExpr = [
205
- layer.filters.logicalOp,
206
- ...layer.filters.appliedFilters.map(buildCondition),
207
- ];
208
- }
209
- layerStyle.filter = filterExpr;
289
+ layerStyle.filter =
290
+ layer.filters.appliedFilters.length === 1
291
+ ? buildCondition(layer.filters.appliedFilters[0])
292
+ : [
293
+ layer.filters.logicalOp,
294
+ ...layer.filters.appliedFilters.map(buildCondition),
295
+ ];
210
296
  }
211
- if (!layerParams.color) {
212
- return [layerStyle];
213
- }
214
- const newStyle = Object.assign(Object.assign({}, defaultStyle), layerParams.color);
215
- layerStyle.style = newStyle;
216
297
  return [layerStyle];
217
298
  };
218
299
  /**
@@ -268,7 +349,7 @@ export class MainView extends React.Component {
268
349
  * to work with the temporal controller
269
350
  */
270
351
  this.handleTemporalController = (id, layer) => {
271
- var _a, _b, _c, _d, _e, _f;
352
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
272
353
  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;
273
354
  // Temporal Controller shouldn't be active if more than one layer is selected
274
355
  if (!selectedLayer || Object.keys(selectedLayer).length !== 1) {
@@ -286,13 +367,13 @@ export class MainView extends React.Component {
286
367
  const activeFilter = layer.filters.appliedFilters[0];
287
368
  // Save original features on first filter application
288
369
  if (!Object.keys(this._originalFeatures).includes(id)) {
289
- this._originalFeatures[id] = source.getFeatures();
370
+ this._originalFeatures[id] = (_e = source.getFeatures()) !== null && _e !== void 0 ? _e : [];
290
371
  }
291
372
  // clear current features
292
373
  source.clear();
293
- const startTime = (_e = activeFilter.betweenMin) !== null && _e !== void 0 ? _e : 0;
294
- const endTime = (_f = activeFilter.betweenMax) !== null && _f !== void 0 ? _f : 1000;
295
- const filteredFeatures = this._originalFeatures[id].filter(feature => {
374
+ const startTime = (_f = activeFilter.betweenMin) !== null && _f !== void 0 ? _f : 0;
375
+ const endTime = (_g = activeFilter.betweenMax) !== null && _g !== void 0 ? _g : 1000;
376
+ const filteredFeatures = ((_h = this._originalFeatures[id]) !== null && _h !== void 0 ? _h : []).filter(feature => {
296
377
  const featureTime = feature.get(activeFilter.feature);
297
378
  return featureTime >= startTime && featureTime <= endTime;
298
379
  });
@@ -302,100 +383,40 @@ export class MainView extends React.Component {
302
383
  }
303
384
  else {
304
385
  // Restore original features when no filters are applied
305
- source.addFeatures(this._originalFeatures[id]);
386
+ source.addFeatures((_j = this._originalFeatures[id]) !== null && _j !== void 0 ? _j : []);
306
387
  delete this._originalFeatures[id];
307
388
  }
308
389
  };
309
- this._onClientSharedStateChanged = (sender, clients) => {
310
- var _a, _b, _c;
390
+ this._handleSelectedChanged = () => {
391
+ var _a;
311
392
  const localState = this._model.localState;
312
393
  if (!localState) {
313
394
  return;
314
395
  }
315
- const remoteUser = localState.remoteUser;
316
- // If we are in following mode, we update our position and selection
317
- if (remoteUser) {
318
- const remoteState = clients.get(remoteUser);
319
- if (!remoteState) {
320
- return;
321
- }
322
- if (((_a = remoteState.user) === null || _a === void 0 ? void 0 : _a.username) !== ((_b = this.state.remoteUser) === null || _b === void 0 ? void 0 : _b.username)) {
323
- this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: remoteState.user })));
324
- }
325
- const remoteViewport = remoteState.viewportState;
326
- if (remoteViewport.value) {
327
- const { x, y } = remoteViewport.value.coordinates;
328
- const zoom = remoteViewport.value.zoom;
329
- this._moveToPosition({ x, y }, zoom, 0);
330
- }
396
+ const selectedLayers = (_a = localState.selected) === null || _a === void 0 ? void 0 : _a.value;
397
+ if (!selectedLayers) {
398
+ return;
331
399
  }
332
- else {
333
- // If we are unfollowing a remote user, we reset our center and zoom to their previous values
334
- if (this.state.remoteUser !== null) {
335
- this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: null })));
336
- const viewportState = (_c = localState.viewportState) === null || _c === void 0 ? void 0 : _c.value;
337
- if (viewportState) {
338
- this._moveToPosition(viewportState.coordinates, viewportState.zoom);
339
- }
340
- }
400
+ const selectedLayerId = Object.keys(selectedLayers)[0];
401
+ const JGISLayer = this._model.getLayer(selectedLayerId);
402
+ if (!JGISLayer) {
403
+ return;
341
404
  }
342
- // cursors
343
- clients.forEach((client, clientId) => {
344
- var _a;
345
- if (!(client === null || client === void 0 ? void 0 : client.user)) {
346
- return;
347
- }
348
- const pointer = (_a = client.pointer) === null || _a === void 0 ? void 0 : _a.value;
349
- // We already display our own cursor on mouse move
350
- if (this._model.getClientId() === clientId) {
351
- return;
352
- }
353
- const clientPointers = Object.assign({}, this.state.clientPointers);
354
- let currentClientPointer = clientPointers[clientId];
355
- if (pointer) {
356
- const pixel = this._Map.getPixelFromCoordinate([
357
- pointer.coordinates.x,
358
- pointer.coordinates.y,
359
- ]);
360
- const lonLat = toLonLat([pointer.coordinates.x, pointer.coordinates.y]);
361
- if (!currentClientPointer) {
362
- currentClientPointer = {
363
- username: client.user.username,
364
- displayName: client.user.display_name,
365
- color: client.user.color,
366
- coordinates: {
367
- x: pixel[0],
368
- y: pixel[1],
369
- },
370
- lonLat: {
371
- longitude: lonLat[0],
372
- latitude: lonLat[1],
373
- },
374
- };
375
- }
376
- else {
377
- currentClientPointer = Object.assign(Object.assign({}, currentClientPointer), { coordinates: {
378
- x: pixel[0],
379
- y: pixel[1],
380
- }, lonLat: {
381
- longitude: lonLat[0],
382
- latitude: lonLat[1],
383
- } });
384
- }
385
- clientPointers[clientId] = currentClientPointer;
386
- }
387
- else {
388
- delete clientPointers[clientId];
389
- }
390
- this.setState(old => (Object.assign(Object.assign({}, old), { clientPointers })));
391
- });
392
- // Temporal controller bit
393
- // ? There's probably a better way to get changes in the model to trigger react rerenders
394
- const isTemporalControllerActive = localState.isTemporalControllerActive;
395
- if (isTemporalControllerActive !== this.state.displayTemporalController) {
396
- this.setState(old => (Object.assign(Object.assign({}, old), { displayTemporalController: isTemporalControllerActive })));
397
- this._mainViewModel.commands.notifyCommandChanged(CommandIDs.temporalController);
405
+ this._syncVectorDrawingFromSelection(JGISLayer, selectedLayerId);
406
+ };
407
+ this._syncVectorDrawingFromSelection = (layer, selectedLayerId) => {
408
+ const decision = this._getVectorDrawingSelectionDecision(layer, selectedLayerId);
409
+ if (decision.disableEditing) {
410
+ this._model.editingVectorLayer = false;
411
+ this._updateEditingVectorLayer();
412
+ return;
398
413
  }
414
+ if (!decision.shouldRebind) {
415
+ return;
416
+ }
417
+ this._previousDrawLayerID = selectedLayerId;
418
+ this._currentDrawLayerID = selectedLayerId;
419
+ this._editVectorLayer();
399
420
  };
400
421
  this._onSharedModelStateChange = (_, change) => {
401
422
  var _a;
@@ -414,6 +435,10 @@ export class MainView extends React.Component {
414
435
  }
415
436
  }
416
437
  };
438
+ this._handleIdentifiedFeaturesChanged = () => {
439
+ this.setState(old => (Object.assign(Object.assign({}, old), { identifyFeatureFloatersVersion: old.identifyFeatureFloatersVersion + 1 })));
440
+ this._clearHighlightWhenIdentifyDisabled();
441
+ };
417
442
  /**
418
443
  * Handler for when story maps change in the model.
419
444
  * Updates specta state and presentation colors when story data becomes available.
@@ -421,6 +446,11 @@ export class MainView extends React.Component {
421
446
  this._setupSpectaMode = () => {
422
447
  this._removeAllInteractions();
423
448
  this._setupStoryScrollListener();
449
+ // Ensure keybindings have a focused target in Specta mode.
450
+ window.requestAnimationFrame(() => {
451
+ var _a;
452
+ (_a = this.mainViewRef.current) === null || _a === void 0 ? void 0 : _a.focus();
453
+ });
424
454
  };
425
455
  this._removeAllInteractions = () => {
426
456
  // Remove all default interactions
@@ -459,6 +489,7 @@ export class MainView extends React.Component {
459
489
  let accumulatedDeltaY = 0;
460
490
  let scrollContainer = (_b = (_a = this.storyViewerPanelRef.current) === null || _a === void 0 ? void 0 : _a.getScrollContainer()) !== null && _b !== void 0 ? _b : null;
461
491
  const processStoryScrollFrame = () => {
492
+ var _a;
462
493
  this._pendingStoryScrollRafId = null;
463
494
  const currentPanelHandle = this.storyViewerPanelRef.current;
464
495
  if (!currentPanelHandle || !scrollContainer) {
@@ -467,6 +498,12 @@ export class MainView extends React.Component {
467
498
  }
468
499
  const deltaY = accumulatedDeltaY;
469
500
  accumulatedDeltaY = 0;
501
+ const storyType = (_a = this._model.getSelectedStory().story) === null || _a === void 0 ? void 0 : _a.storyType;
502
+ // Don't want to handle next/prev logic in list mode
503
+ if (storyType === STORY_TYPE.verticalScroll) {
504
+ scrollContainer.scrollBy({ top: deltaY });
505
+ return;
506
+ }
470
507
  const isScrollingUp = deltaY < 0;
471
508
  const isScrollingDown = deltaY > 0;
472
509
  const isAtTop = currentPanelHandle.getAtTop();
@@ -540,7 +577,7 @@ export class MainView extends React.Component {
540
577
  jsonData = JSON.parse(data);
541
578
  }
542
579
  catch (e) {
543
- console.warn(`Failed to parse annotation data for ${key}:`, e);
580
+ this._log('warning', `Failed to parse annotation data for ${key}: ${e}`);
544
581
  return;
545
582
  }
546
583
  }
@@ -555,6 +592,7 @@ export class MainView extends React.Component {
555
592
  });
556
593
  this.setState(old => (Object.assign(Object.assign({}, old), { annotations: newState })));
557
594
  };
595
+ this._lastPointerCoord = null;
558
596
  this._syncPointer = throttle((coordinates) => {
559
597
  const pointer = {
560
598
  coordinates: { x: coordinates[0], y: coordinates[1] },
@@ -569,6 +607,9 @@ export class MainView extends React.Component {
569
607
  this._handleWindowResize = () => {
570
608
  // TODO SOMETHING
571
609
  };
610
+ this._handleSegmentTransitionChange = (payload) => {
611
+ this.setState({ segmentTransition: payload });
612
+ };
572
613
  this._handleSpectaTouchStart = (e) => {
573
614
  if (e.touches.length > 0) {
574
615
  this._spectaTouchStartX = e.touches[0].clientX;
@@ -595,14 +636,160 @@ export class MainView extends React.Component {
595
636
  this._model.setCurrentSegmentIndex(current + 1);
596
637
  }
597
638
  };
639
+ this._handleDrawGeometryTypeChange = (
640
+ /* handle with the change of geometry and instantiate new draw interaction and other ones accordingly*/
641
+ event) => {
642
+ const drawGeometryLabel = event.target.value;
643
+ this._currentDrawGeometry = drawGeometryLabel;
644
+ this._updateInteractions();
645
+ this._updateDrawSource();
646
+ this.setState(old => (Object.assign(Object.assign({}, old), { drawGeometryLabel })));
647
+ };
648
+ this._getVectorSourceFromLayerID = (layerID) => {
649
+ /* get the OpenLayers VectorSource corresponding to the JGIS currentDrawLayerID */
650
+ const layers = this._Map.getLayers();
651
+ const layerArray = layers.getArray();
652
+ const matchingLayer = layerArray.find(layer => layer.get('id') === layerID);
653
+ const source = matchingLayer === null || matchingLayer === void 0 ? void 0 : matchingLayer.get('source');
654
+ this._currentVectorSource = source;
655
+ return this._currentVectorSource;
656
+ };
657
+ this._getDrawSourceFromSelectedLayer = () => {
658
+ var _a, _b, _c, _d;
659
+ 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;
660
+ if (!selectedLayers) {
661
+ return;
662
+ }
663
+ const selectedLayerID = Object.keys(selectedLayers)[0];
664
+ this._currentDrawLayerID = selectedLayerID;
665
+ const JGISLayer = this._model.getLayer(selectedLayerID);
666
+ this._currentDrawSourceID = (_d = JGISLayer === null || JGISLayer === void 0 ? void 0 : JGISLayer.parameters) === null || _d === void 0 ? void 0 : _d.source;
667
+ if (this._currentDrawSourceID) {
668
+ this._currentDrawSource = this._model.getSource(this._currentDrawSourceID);
669
+ }
670
+ };
671
+ this._onVectorSourceChange = () => {
672
+ if (!this._currentVectorSource ||
673
+ !this._currentDrawSource ||
674
+ !this._currentDrawSourceID) {
675
+ return;
676
+ }
677
+ const geojsonWriter = new GeoJSON({
678
+ featureProjection: this._Map.getView().getProjection(),
679
+ });
680
+ const features = this._currentVectorSource
681
+ .getFeatures()
682
+ .map(feature => geojsonWriter.writeFeatureObject(feature));
683
+ const updatedData = {
684
+ type: 'FeatureCollection',
685
+ features: features,
686
+ };
687
+ const updatedJGISLayerSource = {
688
+ name: this._currentDrawSource.name,
689
+ type: this._currentDrawSource.type,
690
+ parameters: {
691
+ data: updatedData,
692
+ },
693
+ };
694
+ this._currentDrawSource = updatedJGISLayerSource;
695
+ this._model.sharedModel.updateSource(this._currentDrawSourceID, updatedJGISLayerSource);
696
+ };
697
+ this._updateDrawSource = () => {
698
+ if (this._currentVectorSource) {
699
+ this._currentVectorSource.on('change', this._onVectorSourceChange);
700
+ }
701
+ };
702
+ this._updateInteractions = () => {
703
+ if (this._draw) {
704
+ this._removeDrawInteraction();
705
+ }
706
+ if (this._select) {
707
+ this._removeSelectInteraction();
708
+ }
709
+ if (this._modify) {
710
+ this._removeModifyInteraction();
711
+ }
712
+ if (this._snap) {
713
+ this._removeSnapInteraction();
714
+ }
715
+ this._draw = new Draw({
716
+ style: drawInteractionStyle,
717
+ type: this._currentDrawGeometry,
718
+ source: this._currentVectorSource,
719
+ });
720
+ this._draw.on('drawend', this._handleDrawEnd);
721
+ this._select = new Select();
722
+ this._modify = new Modify({
723
+ features: this._select.getFeatures(),
724
+ });
725
+ this._snap = new Snap({
726
+ source: this._currentVectorSource,
727
+ });
728
+ this._Map.addInteraction(this._draw);
729
+ this._Map.addInteraction(this._select);
730
+ this._Map.addInteraction(this._modify);
731
+ this._Map.addInteraction(this._snap);
732
+ this._draw.setActive(true);
733
+ this._select.setActive(false);
734
+ this._modify.setActive(false);
735
+ this._snap.setActive(true);
736
+ };
737
+ this._handleDrawEnd = (event) => {
738
+ const feature = event.feature;
739
+ feature.set('_id', UUID.uuid4());
740
+ feature.set('_createdAt', new Date().toISOString());
741
+ feature.set('_creatorClientId', this._model.getClientId().toString());
742
+ feature.set('_fromDrawTool', true);
743
+ feature.set('Label', 'New Label');
744
+ };
745
+ this._editVectorLayer = () => {
746
+ this._getDrawSourceFromSelectedLayer();
747
+ if (!this._currentDrawLayerID) {
748
+ return;
749
+ }
750
+ this._currentVectorSource = this._getVectorSourceFromLayerID(this._currentDrawLayerID);
751
+ if (!this._currentVectorSource || !this._currentDrawGeometry) {
752
+ return;
753
+ }
754
+ this._updateInteractions(); /* remove previous interactions and instantiate new ones */
755
+ this._updateDrawSource(); /*add new features, update source and get changes reported to the JGIS Document in geoJSON format */
756
+ };
757
+ this._removeDrawInteraction = () => {
758
+ this._draw.setActive(false);
759
+ this._Map.removeInteraction(this._draw);
760
+ };
761
+ this._removeSelectInteraction = () => {
762
+ this._select.setActive(false);
763
+ this._Map.removeInteraction(this._select);
764
+ };
765
+ this._removeSnapInteraction = () => {
766
+ this._snap.setActive(false);
767
+ this._Map.removeInteraction(this._snap);
768
+ };
769
+ this._removeModifyInteraction = () => {
770
+ this._modify.setActive(false);
771
+ this._Map.removeInteraction(this._modify);
772
+ };
773
+ /**
774
+ * Shared source update wrapper for child components that need to mutate a
775
+ * source and refresh corresponding map layers.
776
+ */
777
+ this.persistAndRefreshSource = async (id, source) => {
778
+ this._model.sharedModel.updateSource(id, source);
779
+ await this.updateSource(id, source);
780
+ };
598
781
  this._isPositionInitialized = false;
599
782
  this.divRef = React.createRef(); // Reference of render div
783
+ this.mainViewRef = React.createRef();
600
784
  this.controlsToolbarRef = React.createRef();
601
785
  this.spectaContainerRef = React.createRef();
602
786
  this.storyViewerPanelRef = React.createRef();
603
787
  this._ready = false;
604
788
  this._sourceToLayerMap = new Map();
605
789
  this._originalFeatures = {};
790
+ this._highlightLayerRef = { current: null };
791
+ this._addLayerForPanels = (id, layer, index) => this.addLayer(id, layer, index);
792
+ this._removeLayerForPanels = (id) => this.removeLayer(id);
606
793
  this._featurePropertyCache = new Map();
607
794
  this._isSpectaPresentationInitialized = false;
608
795
  this._storyScrollHandler = null;
@@ -611,6 +798,7 @@ export class MainView extends React.Component {
611
798
  this._state = props.state;
612
799
  this._formSchemaRegistry = props.formSchemaRegistry;
613
800
  this._annotationModel = props.annotationModel;
801
+ this._loggerRegistry = props.loggerRegistry;
614
802
  // Enforce the map to take the full available width in the case of Jupyter Notebook viewer
615
803
  const el = document.getElementById('main-panel');
616
804
  if (el) {
@@ -633,31 +821,39 @@ export class MainView extends React.Component {
633
821
  this._mainViewModel = this.props.viewModel;
634
822
  this._mainViewModel.viewSettingChanged.connect(this._onViewChanged, this);
635
823
  this._model = this._mainViewModel.jGISModel;
824
+ this._patchGeoJSONFeatureProperties = createGeoJSONFeaturePatcher({
825
+ model: this._model,
826
+ persistAndRefreshSource: this.persistAndRefreshSource,
827
+ });
636
828
  this._model.themeChanged.connect(this._handleThemeChange, this);
637
829
  this._model.sharedOptionsChanged.connect(this._onSharedOptionsChanged, this);
638
- this._model.clientStateChanged.connect(this._onClientSharedStateChanged, this);
830
+ this._model.temporalControllerActiveChanged.connect(this._handleTemporalControllerActiveChanged, this);
831
+ const remoteUserSignals = [
832
+ this._model.remoteUserChanged,
833
+ this._model.viewportStateChanged,
834
+ ];
835
+ remoteUserSignals.forEach(signal => signal.connect(this._handleRemoteUserChanged, this));
836
+ this._model.pointerChanged.connect(this._handlePointerChanged, this);
837
+ this._model.selectedChanged.connect(this._handleTemporalControllerActiveChanged, this);
838
+ this._model.selectedChanged.connect(this._handleSelectedChanged, this);
639
839
  this._model.sharedLayersChanged.connect(this._onLayersChanged, this);
640
840
  this._model.sharedLayerTreeChanged.connect(this._onLayerTreeChange, this);
641
841
  this._model.sharedSourcesChanged.connect(this._onSourcesChange, this);
642
842
  this._model.sharedModel.changed.connect(this._onSharedModelStateChange);
643
843
  this._model.sharedMetadataChanged.connect(this._onSharedMetadataChanged, this);
844
+ this._model.identifiedFeaturesChanged.connect(this._handleIdentifiedFeaturesChanged, this);
644
845
  this._model.zoomToPositionSignal.connect(this._onZoomToPosition, this);
645
846
  this._model.settingsChanged.connect(this._onSettingsChanged, this);
646
847
  this._model.updateLayerSignal.connect(this._triggerLayerUpdate, this);
647
848
  this._model.addFeatureAsMsSignal.connect(this._convertFeatureToMs, this);
648
849
  this._model.geolocationChanged.connect(this._handleGeolocationChanged, this);
850
+ // Keep draw editing UI/interactions in sync with the shared editing mode.
851
+ this._model.editingVectorLayerChanged.connect(this._updateEditingVectorLayer, this);
649
852
  this._model.flyToGeometrySignal.connect(this.flyToGeometry, this);
650
853
  this._model.highlightFeatureSignal.connect(this.highlightFeatureOnMap, this);
651
854
  Promise.resolve().then(() => {
652
855
  this._syncSettingsFromRegistry();
653
856
  });
654
- // Watch isIdentifying and clear the highlight when Identify Tool is turned off
655
- this._model.sharedModel.awareness.on('change', () => {
656
- var _a;
657
- if (this._model.currentMode !== 'identifying' && this._highlightLayer) {
658
- (_a = this._highlightLayer.getSource()) === null || _a === void 0 ? void 0 : _a.clear();
659
- }
660
- });
661
857
  this.state = {
662
858
  id: this._mainViewModel.id,
663
859
  lightTheme: isLightTheme(),
@@ -670,9 +866,13 @@ export class MainView extends React.Component {
670
866
  loadingErrors: [],
671
867
  displayTemporalController: false,
672
868
  filterStates: {},
869
+ editingVectorLayer: false,
870
+ drawGeometryLabel: '',
673
871
  jgisSettings: this._model.jgisSettings,
674
872
  isSpectaPresentation: this._model.isSpectaMode(),
675
873
  initialLayersReady: false,
874
+ identifyFeatureFloatersVersion: 0,
875
+ segmentTransition: null,
676
876
  };
677
877
  this._sources = [];
678
878
  this._loadingLayers = new Set();
@@ -683,13 +883,23 @@ export class MainView extends React.Component {
683
883
  this._updateCenter = debounce(this.updateCenter, 100);
684
884
  }
685
885
  async componentDidMount() {
886
+ var _a;
887
+ if (this._loggerRegistry) {
888
+ const logger = this._loggerRegistry.getLogger(this._model.filePath);
889
+ logger.level = 'debug';
890
+ }
686
891
  window.addEventListener('resize', this._handleWindowResize);
687
892
  const options = this._model.getOptions();
893
+ const projection = (_a = options.projection) !== null && _a !== void 0 ? _a : DEFAULT_PROJECTION;
688
894
  const center = options.longitude !== undefined && options.latitude !== undefined
689
- ? fromLonLat([options.longitude, options.latitude])
895
+ ? fromLonLat([options.longitude, options.latitude], projection)
690
896
  : [0, 0];
691
897
  const zoom = options.zoom !== undefined ? options.zoom : 1;
692
- await this.generateMap(center, zoom);
898
+ await this.generateMap(center, zoom, projection);
899
+ this._handleRemoteUserChanged();
900
+ this._handlePointerChanged();
901
+ this._handleTemporalControllerActiveChanged();
902
+ this._handleSelectedChanged();
693
903
  this._mainViewModel.initSignal();
694
904
  if (window.jupytergisMaps !== undefined && this._documentPath) {
695
905
  window.jupytergisMaps[this._documentPath] = this._Map;
@@ -712,12 +922,21 @@ export class MainView extends React.Component {
712
922
  this._model.themeChanged.disconnect(this._handleThemeChange, this);
713
923
  this._model.settingsChanged.disconnect(this._onSettingsChanged, this);
714
924
  this._model.sharedOptionsChanged.disconnect(this._onSharedOptionsChanged, this);
715
- this._model.clientStateChanged.disconnect(this._onClientSharedStateChanged, this);
925
+ this._model.temporalControllerActiveChanged.disconnect(this._handleTemporalControllerActiveChanged, this);
926
+ const remoteUserSignals = [
927
+ this._model.remoteUserChanged,
928
+ this._model.viewportStateChanged,
929
+ ];
930
+ remoteUserSignals.forEach(signal => signal.disconnect(this._handleRemoteUserChanged, this));
931
+ this._model.pointerChanged.disconnect(this._handlePointerChanged, this);
932
+ this._model.selectedChanged.disconnect(this._handleTemporalControllerActiveChanged, this);
933
+ this._model.selectedChanged.disconnect(this._handleSelectedChanged, this);
934
+ this._model.identifiedFeaturesChanged.disconnect(this._handleIdentifiedFeaturesChanged, this);
716
935
  // Clean up story scroll listener
717
936
  this._cleanupStoryScrollListener();
718
937
  this._mainViewModel.dispose();
719
938
  }
720
- async generateMap(center, zoom) {
939
+ async generateMap(center, zoom, projection = DEFAULT_PROJECTION) {
721
940
  const layers = this._model.getLayers();
722
941
  this._initialLayersCount = Object.values(layers).filter(layer => layer.type !== 'StorySegmentLayer').length;
723
942
  const scaleLine = new ScaleLine({
@@ -741,6 +960,7 @@ export class MainView extends React.Component {
741
960
  view: new View({
742
961
  center,
743
962
  zoom,
963
+ projection,
744
964
  }),
745
965
  controls,
746
966
  });
@@ -775,9 +995,7 @@ export class MainView extends React.Component {
775
995
  this._Map.addInteraction(dragAndDropInteraction);
776
996
  this.createSelectInteraction();
777
997
  const view = this._Map.getView();
778
- view.on('change:center', () => this._updateCenter());
779
- // TODO: Note for the future, will need to update listeners if view changes
780
- view.on('change:center', throttle(() => {
998
+ const syncViewportThrottled = throttle(() => {
781
999
  var _a;
782
1000
  // Not syncing center if following someone else
783
1001
  if ((_a = this._model.localState) === null || _a === void 0 ? void 0 : _a.remoteUser) {
@@ -789,25 +1007,38 @@ export class MainView extends React.Component {
789
1007
  if (!center || !zoom) {
790
1008
  return;
791
1009
  }
1010
+ const currentExtent = view.calculateExtent(this._Map.getSize());
792
1011
  this._model.syncViewport({
793
1012
  coordinates: {
794
1013
  x: center[0],
795
1014
  y: center[1],
796
1015
  },
797
1016
  zoom,
1017
+ extent: [
1018
+ currentExtent[0],
1019
+ currentExtent[1],
1020
+ currentExtent[2],
1021
+ currentExtent[3],
1022
+ ],
798
1023
  }, this._mainViewModel.id);
799
- }));
1024
+ }, 200);
1025
+ view.on('change:center', () => {
1026
+ this._updateCenter();
1027
+ syncViewportThrottled();
1028
+ });
800
1029
  this._Map.on('postrender', () => {
801
1030
  if (this.state.annotations) {
802
1031
  this._updateAnnotation();
803
1032
  }
1033
+ this._updateFeatureFloaters();
804
1034
  });
805
1035
  this._Map.on('moveend', () => {
1036
+ var _a;
806
1037
  const currentOptions = this._model.getOptions();
807
1038
  const view = this._Map.getView();
808
1039
  const center = view.getCenter() || [0, 0];
809
1040
  const zoom = view.getZoom() || 0;
810
- const projection = view.getProjection();
1041
+ const projection = (_a = getProjection(currentOptions.projection)) !== null && _a !== void 0 ? _a : view.getProjection();
811
1042
  const latLng = toLonLat(center, projection);
812
1043
  const bearing = view.getRotation();
813
1044
  const resolution = view.getResolution();
@@ -842,14 +1073,18 @@ export class MainView extends React.Component {
842
1073
  this._Map.getViewport().addEventListener('contextmenu', event => {
843
1074
  event.preventDefault();
844
1075
  event.stopPropagation();
845
- const coordinate = this._Map.getEventCoordinate(event);
846
- this._clickCoords = coordinate;
1076
+ if (this._lastPointerCoord) {
1077
+ this._clickCoords = this._lastPointerCoord;
1078
+ }
847
1079
  this._contextMenu.open(event);
848
1080
  });
849
- this.setState(old => (Object.assign(Object.assign({}, old), { loading: false, viewProjection: {
850
- code: view.getProjection().getCode(),
851
- units: view.getProjection().getUnits(),
852
- } })));
1081
+ this.setState(old => {
1082
+ var _a;
1083
+ return (Object.assign(Object.assign({}, old), { loading: false, viewProjection: {
1084
+ code: projection,
1085
+ units: ((_a = getProjection(projection)) !== null && _a !== void 0 ? _a : view.getProjection()).getUnits(),
1086
+ } }));
1087
+ });
853
1088
  }
854
1089
  }
855
1090
  /**
@@ -859,226 +1094,359 @@ export class MainView extends React.Component {
859
1094
  * @param source - the source object.
860
1095
  */
861
1096
  async addSource(id, source) {
862
- var _a, _b;
1097
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1098
+ this._log('info', `Loading source "${(_a = source.name) !== null && _a !== void 0 ? _a : id}" (${source.type})`);
863
1099
  let newSource;
864
- switch (source.type) {
865
- case 'RasterSource': {
866
- const sourceParameters = source.parameters;
867
- const pmTiles = sourceParameters.url.endsWith('.pmtiles') ||
868
- sourceParameters.url.endsWith('pmtiles.gz');
869
- const url = this.computeSourceUrl(source);
870
- if (!pmTiles) {
871
- newSource = new XYZSource({
1100
+ try {
1101
+ switch (source.type) {
1102
+ case 'RasterSource': {
1103
+ const sourceParameters = source.parameters;
1104
+ const pmTiles = sourceParameters.url.endsWith('.pmtiles') ||
1105
+ sourceParameters.url.endsWith('pmtiles.gz');
1106
+ const url = this.computeSourceUrl(source);
1107
+ if (!pmTiles) {
1108
+ newSource = new XYZSource({
1109
+ interpolate: sourceParameters.interpolate,
1110
+ attributions: sourceParameters.attribution,
1111
+ minZoom: sourceParameters.minZoom,
1112
+ maxZoom: sourceParameters.maxZoom,
1113
+ tileSize: 256,
1114
+ url: url,
1115
+ });
1116
+ }
1117
+ else {
1118
+ newSource = new PMTilesRasterSource({
1119
+ interpolate: sourceParameters.interpolate,
1120
+ attributions: sourceParameters.attribution,
1121
+ tileSize: 256,
1122
+ url: url,
1123
+ });
1124
+ }
1125
+ break;
1126
+ }
1127
+ case 'RasterDemSource': {
1128
+ const sourceParameters = source.parameters;
1129
+ newSource = new ImageTileSource({
872
1130
  interpolate: sourceParameters.interpolate,
1131
+ url: this.computeSourceUrl(source),
873
1132
  attributions: sourceParameters.attribution,
874
- minZoom: sourceParameters.minZoom,
875
- maxZoom: sourceParameters.maxZoom,
876
- tileSize: 256,
877
- url: url,
878
1133
  });
1134
+ break;
879
1135
  }
880
- else {
881
- newSource = new PMTilesRasterSource({
882
- interpolate: sourceParameters.interpolate,
883
- attributions: sourceParameters.attribution,
884
- tileSize: 256,
885
- url: url,
1136
+ case 'VectorTileSource': {
1137
+ const sourceParameters = source.parameters;
1138
+ const pmTiles = sourceParameters.url.endsWith('.pmtiles') ||
1139
+ sourceParameters.url.endsWith('pmtiles.gz');
1140
+ const url = this.computeSourceUrl(source);
1141
+ if (!pmTiles) {
1142
+ const vtSourceOptions = {
1143
+ attributions: sourceParameters.attribution,
1144
+ minZoom: sourceParameters.minZoom,
1145
+ maxZoom: sourceParameters.maxZoom,
1146
+ url: url,
1147
+ format: new MVT({
1148
+ featureClass: RenderFeature,
1149
+ }),
1150
+ };
1151
+ if (sourceParameters.useProxy) {
1152
+ const extraHeaders = (_b = sourceParameters.httpHeaders) !== null && _b !== void 0 ? _b : {};
1153
+ const headersParam = Object.keys(extraHeaders).length > 0
1154
+ ? `&headers=${encodeURIComponent(JSON.stringify(extraHeaders))}`
1155
+ : '';
1156
+ const proxyBase = isJupyterLite()
1157
+ ? `${this._model.jgisSettings.proxyUrl}/`
1158
+ : `${INTERNAL_PROXY_BASE}`;
1159
+ vtSourceOptions.tileLoadFunction = (tile, tileUrl) => {
1160
+ const vtTile = tile;
1161
+ const proxyUrl = `${proxyBase}?url=${encodeURIComponent(tileUrl)}${headersParam}`;
1162
+ vtTile.setLoader((extent, _resolution, projection) => {
1163
+ return fetch(proxyUrl)
1164
+ .then(response => {
1165
+ if (!response.ok) {
1166
+ throw new Error(`Tile proxy request failed: ${response.status} ${response.statusText}`);
1167
+ }
1168
+ return response.arrayBuffer();
1169
+ })
1170
+ .then(data => {
1171
+ const features = vtTile.getFormat().readFeatures(data, {
1172
+ extent,
1173
+ featureProjection: projection,
1174
+ });
1175
+ vtTile.setFeatures(features);
1176
+ this._log('debug', `Proxy tile loaded: ${tileUrl}`);
1177
+ return features;
1178
+ })
1179
+ .catch((err) => {
1180
+ this._log('error', `Proxy tile error for ${tileUrl}: ${err.message}`);
1181
+ tile.setState(TileState.ERROR);
1182
+ return [];
1183
+ });
1184
+ });
1185
+ };
1186
+ }
1187
+ newSource = new VectorTileSource(vtSourceOptions);
1188
+ }
1189
+ else {
1190
+ newSource = new PMTilesVectorSource({
1191
+ attributions: sourceParameters.attribution,
1192
+ url: url,
1193
+ });
1194
+ }
1195
+ newSource.on('tileloadend', (event) => {
1196
+ const tile = event.tile;
1197
+ const features = tile.getFeatures();
1198
+ if (features && features.length > 0) {
1199
+ this._model.syncTileFeatures({
1200
+ sourceId: id,
1201
+ features,
1202
+ });
1203
+ }
886
1204
  });
1205
+ break;
887
1206
  }
888
- break;
889
- }
890
- case 'RasterDemSource': {
891
- const sourceParameters = source.parameters;
892
- newSource = new ImageTileSource({
893
- interpolate: sourceParameters.interpolate,
894
- url: this.computeSourceUrl(source),
895
- attributions: sourceParameters.attribution,
896
- });
897
- break;
898
- }
899
- case 'VectorTileSource': {
900
- const sourceParameters = source.parameters;
901
- const pmTiles = sourceParameters.url.endsWith('.pmtiles') ||
902
- sourceParameters.url.endsWith('pmtiles.gz');
903
- const url = this.computeSourceUrl(source);
904
- if (!pmTiles) {
905
- newSource = new VectorTileSource({
906
- attributions: sourceParameters.attribution,
907
- minZoom: sourceParameters.minZoom,
908
- maxZoom: sourceParameters.maxZoom,
909
- url: url,
910
- format: new MVT({
911
- featureClass: RenderFeature,
912
- }),
1207
+ case 'OpenEOTileSource': {
1208
+ const sourceParameters = source.parameters;
1209
+ newSource = new OpenEOTileSource({
1210
+ connectionInfo: {
1211
+ url: sourceParameters.serverUrl,
1212
+ authBearer: sourceParameters.authBearer,
1213
+ },
1214
+ processGraph: sourceParameters.processGraph,
913
1215
  });
1216
+ break;
914
1217
  }
915
- else {
916
- newSource = new PMTilesVectorSource({
917
- attributions: sourceParameters.attribution,
918
- url: url,
1218
+ case 'GeoJSONSource': {
1219
+ const data = ((_c = source.parameters) === null || _c === void 0 ? void 0 : _c.data) ||
1220
+ (await loadFile({
1221
+ filepath: (_d = source.parameters) === null || _d === void 0 ? void 0 : _d.path,
1222
+ type: 'GeoJSONSource',
1223
+ model: this._model,
1224
+ }));
1225
+ const format = new GeoJSON({
1226
+ featureProjection: this._Map.getView().getProjection(),
919
1227
  });
1228
+ const featureArray = format.readFeatures(data, {
1229
+ featureProjection: this._Map.getView().getProjection(),
1230
+ });
1231
+ const featureCollection = new Collection(featureArray);
1232
+ featureCollection.forEach(feature => {
1233
+ feature.setId(getUid(feature));
1234
+ });
1235
+ newSource = new VectorSource({
1236
+ features: featureCollection,
1237
+ });
1238
+ break;
920
1239
  }
921
- newSource.on('tileloadend', (event) => {
922
- const tile = event.tile;
923
- const features = tile.getFeatures();
924
- if (features && features.length > 0) {
925
- this._model.syncTileFeatures({
926
- sourceId: id,
927
- features,
928
- });
929
- }
930
- });
931
- break;
932
- }
933
- case 'GeoJSONSource': {
934
- const data = ((_a = source.parameters) === null || _a === void 0 ? void 0 : _a.data) ||
935
- (await loadFile({
936
- filepath: (_b = source.parameters) === null || _b === void 0 ? void 0 : _b.path,
937
- type: 'GeoJSONSource',
1240
+ case 'ShapefileSource': {
1241
+ const parameters = source.parameters;
1242
+ const geojson = await loadFile({
1243
+ filepath: parameters.path,
1244
+ type: 'ShapefileSource',
1245
+ model: this._model,
1246
+ });
1247
+ const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
1248
+ const format = new GeoJSON();
1249
+ newSource = new VectorSource({
1250
+ features: format.readFeatures(geojsonData, {
1251
+ dataProjection: 'EPSG:4326',
1252
+ featureProjection: this._Map.getView().getProjection(),
1253
+ }),
1254
+ });
1255
+ break;
1256
+ }
1257
+ case 'ImageSource': {
1258
+ const sourceParameters = source.parameters;
1259
+ // Convert lon/lat array to extent
1260
+ // Get lon/lat from source coordinates
1261
+ const leftSide = Math.min(...sourceParameters.coordinates.map(corner => corner[0]));
1262
+ const bottomSide = Math.min(...sourceParameters.coordinates.map(corner => corner[1]));
1263
+ const rightSide = Math.max(...sourceParameters.coordinates.map(corner => corner[0]));
1264
+ const topSide = Math.max(...sourceParameters.coordinates.map(corner => corner[1]));
1265
+ // Convert lon/lat to OpenLayer coordinates
1266
+ const topLeft = fromLonLat([leftSide, topSide]);
1267
+ const bottomRight = fromLonLat([rightSide, bottomSide]);
1268
+ // Get extent from coordinates
1269
+ const minX = topLeft[0];
1270
+ const maxY = topLeft[1];
1271
+ const maxX = bottomRight[0];
1272
+ const minY = bottomRight[1];
1273
+ const extent = [minX, minY, maxX, maxY];
1274
+ const imageUrl = await loadFile({
1275
+ filepath: sourceParameters.path,
1276
+ type: 'ImageSource',
938
1277
  model: this._model,
1278
+ });
1279
+ newSource = new Static({
1280
+ interpolate: sourceParameters.interpolate,
1281
+ imageExtent: extent,
1282
+ url: imageUrl,
1283
+ crossOrigin: '',
1284
+ });
1285
+ break;
1286
+ }
1287
+ case 'VideoSource': {
1288
+ this._log('warning', 'Video Tiles not supported with Open Layers');
1289
+ break;
1290
+ }
1291
+ case 'GeoTiffSource': {
1292
+ const sourceParameters = source.parameters;
1293
+ const addNoData = (url) => {
1294
+ return Object.assign(Object.assign({}, url), { nodata: 0 });
1295
+ };
1296
+ const sources = await Promise.all(sourceParameters.urls.map(async (sourceInfo) => {
1297
+ var _a, _b, _c, _d;
1298
+ const isRemote = ((_a = sourceInfo.url) === null || _a === void 0 ? void 0 : _a.startsWith('http://')) ||
1299
+ ((_b = sourceInfo.url) === null || _b === void 0 ? void 0 : _b.startsWith('https://'));
1300
+ const isDataUrl = (_c = sourceInfo.url) === null || _c === void 0 ? void 0 : _c.startsWith('data:');
1301
+ if (isRemote) {
1302
+ return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, url: sourceInfo.url });
1303
+ }
1304
+ else if (isDataUrl) {
1305
+ // Inline base64 GeoTIFF embedded in the .jGIS doc.
1306
+ const blob = await (await fetch(sourceInfo.url)).blob();
1307
+ return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, url: URL.createObjectURL(blob) });
1308
+ }
1309
+ else {
1310
+ const geotiff = await loadFile({
1311
+ filepath: (_d = sourceInfo.url) !== null && _d !== void 0 ? _d : '',
1312
+ type: 'GeoTiffSource',
1313
+ model: this._model,
1314
+ });
1315
+ return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, geotiff, url: URL.createObjectURL(geotiff.file) });
1316
+ }
939
1317
  }));
940
- const format = new GeoJSON({
941
- featureProjection: this._Map.getView().getProjection(),
942
- });
943
- const featureArray = format.readFeatures(data, {
944
- featureProjection: this._Map.getView().getProjection(),
945
- });
946
- const featureCollection = new Collection(featureArray);
947
- featureCollection.forEach(feature => {
948
- feature.setId(getUid(feature));
949
- });
950
- newSource = new VectorSource({
951
- features: featureCollection,
952
- });
953
- break;
954
- }
955
- case 'ShapefileSource': {
956
- const parameters = source.parameters;
957
- const geojson = await loadFile({
958
- filepath: parameters.path,
959
- type: 'ShapefileSource',
960
- model: this._model,
961
- });
962
- const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
963
- const format = new GeoJSON();
964
- newSource = new VectorSource({
965
- features: format.readFeatures(geojsonData, {
966
- dataProjection: 'EPSG:4326',
967
- featureProjection: this._Map.getView().getProjection(),
968
- }),
969
- });
970
- break;
971
- }
972
- case 'ImageSource': {
973
- const sourceParameters = source.parameters;
974
- // Convert lon/lat array to extent
975
- // Get lon/lat from source coordinates
976
- const leftSide = Math.min(...sourceParameters.coordinates.map(corner => corner[0]));
977
- const bottomSide = Math.min(...sourceParameters.coordinates.map(corner => corner[1]));
978
- const rightSide = Math.max(...sourceParameters.coordinates.map(corner => corner[0]));
979
- const topSide = Math.max(...sourceParameters.coordinates.map(corner => corner[1]));
980
- // Convert lon/lat to OpenLayer coordinates
981
- const topLeft = fromLonLat([leftSide, topSide]);
982
- const bottomRight = fromLonLat([rightSide, bottomSide]);
983
- // Get extent from coordinates
984
- const minX = topLeft[0];
985
- const maxY = topLeft[1];
986
- const maxX = bottomRight[0];
987
- const minY = bottomRight[1];
988
- const extent = [minX, minY, maxX, maxY];
989
- const imageUrl = await loadFile({
990
- filepath: sourceParameters.path,
991
- type: 'ImageSource',
992
- model: this._model,
993
- });
994
- newSource = new Static({
995
- interpolate: sourceParameters.interpolate,
996
- imageExtent: extent,
997
- url: imageUrl,
998
- crossOrigin: '',
999
- });
1000
- break;
1001
- }
1002
- case 'VideoSource': {
1003
- console.warn('Video Tiles not supported with Open Layers');
1004
- break;
1005
- }
1006
- case 'GeoTiffSource': {
1007
- const sourceParameters = source.parameters;
1008
- const addNoData = (url) => {
1009
- return Object.assign(Object.assign({}, url), { nodata: 0 });
1010
- };
1011
- const sources = await Promise.all(sourceParameters.urls.map(async (sourceInfo) => {
1012
- var _a, _b, _c;
1013
- const isRemote = ((_a = sourceInfo.url) === null || _a === void 0 ? void 0 : _a.startsWith('http://')) ||
1014
- ((_b = sourceInfo.url) === null || _b === void 0 ? void 0 : _b.startsWith('https://'));
1015
- if (isRemote) {
1016
- return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, url: sourceInfo.url });
1318
+ newSource = new GeoTIFFSource({
1319
+ interpolate: sourceParameters.interpolate,
1320
+ sources,
1321
+ normalize: sourceParameters.normalize,
1322
+ wrapX: sourceParameters.wrapX,
1323
+ });
1324
+ break;
1325
+ }
1326
+ case 'GeoPackageVectorSource': {
1327
+ const sourceParameters = source.parameters;
1328
+ if (!sourceParameters) {
1329
+ throw new Error('GeoPackageSource has no parameters');
1017
1330
  }
1018
- else {
1019
- const geotiff = await loadFile({
1020
- filepath: (_c = sourceInfo.url) !== null && _c !== void 0 ? _c : '',
1021
- type: 'GeoTiffSource',
1022
- model: this._model,
1023
- });
1024
- return Object.assign(Object.assign({}, addNoData(sourceInfo)), { min: sourceInfo.min, max: sourceInfo.max, geotiff, url: URL.createObjectURL(geotiff.file) });
1331
+ const tableMap = await loadFile({
1332
+ filepath: sourceParameters.path,
1333
+ type: 'GeoPackageVectorSource',
1334
+ model: this._model,
1335
+ });
1336
+ const table = tableMap[sourceParameters.tables];
1337
+ const vectorSource = table.source;
1338
+ vectorSource['projection'] = getProjection(sourceParameters.projection);
1339
+ newSource = vectorSource;
1340
+ break;
1341
+ }
1342
+ case 'GeoPackageRasterSource': {
1343
+ const sourceParameters = source.parameters;
1344
+ if (!sourceParameters) {
1345
+ throw new Error('GeoPackageSource has no parameters');
1025
1346
  }
1026
- }));
1027
- newSource = new GeoTIFFSource({
1028
- interpolate: sourceParameters.interpolate,
1029
- sources,
1030
- normalize: sourceParameters.normalize,
1031
- wrapX: sourceParameters.wrapX,
1032
- });
1033
- break;
1034
- }
1035
- case 'GeoParquetSource': {
1036
- const parameters = source.parameters;
1037
- const geojson = await loadFile({
1038
- filepath: parameters.path,
1039
- type: 'GeoParquetSource',
1040
- model: this._model,
1041
- });
1042
- const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
1043
- const format = new GeoJSON();
1044
- newSource = new VectorSource({
1045
- features: format.readFeatures(geojsonData, {
1046
- dataProjection: parameters.projection,
1047
- featureProjection: this._Map.getView().getProjection(),
1048
- }),
1049
- });
1050
- break;
1051
- }
1052
- case 'MarkerSource': {
1053
- const parameters = source.parameters;
1054
- const point = new Point(parameters.feature.coords);
1055
- const marker = new Feature({
1056
- type: 'icon',
1057
- geometry: point,
1058
- });
1059
- // Replace color placeholder in SVG with the parameter color
1060
- const markerColor = parameters.color || '#3463a0';
1061
- const svgString = markerIcon.svgstr
1062
- .replace('{{COLOR}}', markerColor)
1063
- .replace('<svg', '<svg width="128" height="128"');
1064
- const iconStyle = new Style({
1065
- image: new Icon({
1066
- src: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`,
1067
- scale: 0.25,
1068
- anchor: [0.5, 1],
1069
- anchorXUnits: 'fraction',
1070
- anchorYUnits: 'fraction',
1071
- }),
1072
- });
1073
- marker.setStyle(iconStyle);
1074
- newSource = new VectorSource({
1075
- features: [marker],
1076
- });
1347
+ const tableMap = await loadFile({
1348
+ filepath: sourceParameters.path,
1349
+ type: 'GeoPackageRasterSource',
1350
+ model: this._model,
1351
+ });
1352
+ const { gpr, tileDao } = tableMap[sourceParameters.tables];
1353
+ const rasterSource = new XYZSource({
1354
+ minZoom: (_e = sourceParameters.minZoom) !== null && _e !== void 0 ? _e : tileDao.minWebMapZoom,
1355
+ maxZoom: (_f = sourceParameters.maxZoom) !== null && _f !== void 0 ? _f : tileDao.maxWebMapZoom,
1356
+ interpolate: sourceParameters.interpolate,
1357
+ url: '{z},{x},{y}',
1358
+ tileLoadFunction(tile, src) {
1359
+ const [z, x, y] = src.split(',').map(Number);
1360
+ gpr
1361
+ .getTile(x, y, z)
1362
+ .then((dataUri) => (tile.getImage().src = dataUri));
1363
+ },
1364
+ attributions: sourceParameters.attribution,
1365
+ });
1366
+ newSource = rasterSource;
1367
+ break;
1368
+ }
1369
+ case 'GeoParquetSource': {
1370
+ const parameters = source.parameters;
1371
+ const geojson = await loadFile({
1372
+ filepath: parameters.path,
1373
+ type: 'GeoParquetSource',
1374
+ model: this._model,
1375
+ });
1376
+ const geojsonData = Array.isArray(geojson) ? geojson[0] : geojson;
1377
+ const format = new GeoJSON();
1378
+ newSource = new VectorSource({
1379
+ features: format.readFeatures(geojsonData, {
1380
+ dataProjection: parameters.projection,
1381
+ featureProjection: this._Map.getView().getProjection(),
1382
+ }),
1383
+ });
1384
+ break;
1385
+ }
1386
+ case 'MarkerSource': {
1387
+ const parameters = source.parameters;
1388
+ const point = new Point(parameters.feature.coords);
1389
+ const marker = new Feature({
1390
+ type: 'icon',
1391
+ geometry: point,
1392
+ });
1393
+ // Replace color placeholder in SVG with the parameter color
1394
+ const markerColor = parameters.color || '#3463a0';
1395
+ const svgString = markerIcon.svgstr
1396
+ .replace('{{COLOR}}', markerColor)
1397
+ .replace('<svg', '<svg width="128" height="128"');
1398
+ const iconStyle = new Style({
1399
+ image: new Icon({
1400
+ src: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgString)}`,
1401
+ scale: 0.25,
1402
+ anchor: [0.5, 1],
1403
+ anchorXUnits: 'fraction',
1404
+ anchorYUnits: 'fraction',
1405
+ }),
1406
+ });
1407
+ marker.setStyle(iconStyle);
1408
+ newSource = new VectorSource({
1409
+ features: [marker],
1410
+ });
1411
+ break;
1412
+ }
1413
+ case 'WmsTileSource': {
1414
+ const sourceParameters = source.parameters;
1415
+ const url = sourceParameters.url;
1416
+ const selectedLayer = (_g = sourceParameters === null || sourceParameters === void 0 ? void 0 : sourceParameters.params) === null || _g === void 0 ? void 0 : _g.layers;
1417
+ newSource = new TileWMSSource({
1418
+ attributions: sourceParameters === null || sourceParameters === void 0 ? void 0 : sourceParameters.attribution,
1419
+ url,
1420
+ params: {
1421
+ LAYERS: selectedLayer,
1422
+ TILED: true,
1423
+ },
1424
+ });
1425
+ break;
1426
+ }
1077
1427
  }
1078
1428
  }
1429
+ catch (err) {
1430
+ this._log('error', `Failed to load source "${(_h = source.name) !== null && _h !== void 0 ? _h : id}" (${source.type}): ${err.message}`);
1431
+ return;
1432
+ }
1433
+ this._log('info', `Source "${(_j = source.name) !== null && _j !== void 0 ? _j : id}" (${source.type}) loaded successfully`);
1079
1434
  newSource.set('id', id);
1435
+ // Forward OL tile/feature load errors to the JupyterLab log console.
1436
+ // These errors (CORS failures, network errors, etc.) are written directly
1437
+ // by the browser to DevTools and cannot be captured by console patching —
1438
+ // OL's own events are the only reliable interception point.
1439
+ newSource.on('tileloaderror', (evt) => {
1440
+ var _a, _b, _c;
1441
+ 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 : '';
1442
+ this._log('error', `Tile load error for source "${id}"${url ? ': ' + url : ''}`);
1443
+ });
1444
+ newSource.on('featuresloaderror', () => {
1445
+ this._log('error', `Features load error for source "${id}"`);
1446
+ });
1080
1447
  // _sources is a list of OpenLayers sources
1081
1448
  this._sources[id] = newSource;
1449
+ this._trackSourceExtZoom(id, newSource);
1082
1450
  }
1083
1451
  computeSourceUrl(source) {
1084
1452
  const parameters = source.parameters;
@@ -1149,8 +1517,11 @@ export class MainView extends React.Component {
1149
1517
  for (let targetLayerPosition = 0; targetLayerPosition < layerIds.length; targetLayerPosition++) {
1150
1518
  const layerId = layerIds[targetLayerPosition];
1151
1519
  const layer = this._model.sharedModel.getLayer(layerId);
1520
+ if (this._loadingLayers.has(layerId)) {
1521
+ continue;
1522
+ }
1152
1523
  if (!layer) {
1153
- console.warn(`Layer with ID ${layerId} does not exist in the shared model.`);
1524
+ this._log('warning', `Layer with ID ${layerId} does not exist in the shared model.`);
1154
1525
  continue;
1155
1526
  }
1156
1527
  const mapLayer = this.getLayer(layerId);
@@ -1182,7 +1553,7 @@ export class MainView extends React.Component {
1182
1553
  * @returns - the map layer.
1183
1554
  */
1184
1555
  async _buildMapLayer(id, layer) {
1185
- var _a, _b, _c;
1556
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1186
1557
  this.setState(old => (Object.assign(Object.assign({}, old), { loadingLayer: true })));
1187
1558
  this._loadingLayers.add(id);
1188
1559
  let newMapLayer;
@@ -1217,12 +1588,23 @@ export class MainView extends React.Component {
1217
1588
  }
1218
1589
  case 'VectorLayer': {
1219
1590
  layerParameters = layer.parameters;
1220
- newMapLayer = new VectorLayer({
1221
- opacity: layerParameters.opacity,
1222
- visible: layer.visible,
1223
- source: this._sources[layerParameters.source],
1224
- style: this.vectorLayerStyleRuleBuilder(layer),
1225
- });
1591
+ if (Array.isArray((_b = layerParameters.symbologyState) === null || _b === void 0 ? void 0 : _b.layers)) {
1592
+ const olSource = this._sources[layerParameters.source];
1593
+ const grammarState = layerParameters.symbologyState;
1594
+ const rows = olSource instanceof VectorSource
1595
+ ? olSource.getFeatures().map(f => f.getProperties())
1596
+ : [];
1597
+ const featureValues = extractEncodingFieldValues(grammarState, rows);
1598
+ newMapLayer = grammarToOLLayer(layerParameters.symbologyState, olSource, layerParameters.opacity, layer.visible, featureValues);
1599
+ }
1600
+ else {
1601
+ newMapLayer = new VectorImageLayer({
1602
+ opacity: layerParameters.opacity,
1603
+ visible: layer.visible,
1604
+ source: this._sources[layerParameters.source],
1605
+ style: this.vectorLayerStyleRuleBuilder(layer),
1606
+ });
1607
+ }
1226
1608
  break;
1227
1609
  }
1228
1610
  case 'VectorTileLayer': {
@@ -1237,7 +1619,7 @@ export class MainView extends React.Component {
1237
1619
  }
1238
1620
  case 'HillshadeLayer': {
1239
1621
  layerParameters = layer.parameters;
1240
- newMapLayer = new WebGlTileLayer({
1622
+ newMapLayer = new GeoTiffLayer({
1241
1623
  opacity: 0.3,
1242
1624
  visible: layer.visible,
1243
1625
  source: this._sources[layerParameters.source],
@@ -1256,20 +1638,35 @@ export class MainView extends React.Component {
1256
1638
  });
1257
1639
  break;
1258
1640
  }
1259
- case 'WebGlLayer': {
1641
+ case 'OpenEOTileLayer': {
1260
1642
  layerParameters = layer.parameters;
1261
- // This is to handle python sending a None for the color
1262
- const layerOptions = {
1643
+ newMapLayer = new OpenEOTileLayer({
1263
1644
  opacity: layerParameters.opacity,
1264
1645
  visible: layer.visible,
1265
1646
  source: this._sources[layerParameters.source],
1266
- };
1267
- if (layerParameters.color) {
1268
- layerOptions['style'] = {
1269
- color: layerParameters.color,
1647
+ });
1648
+ break;
1649
+ }
1650
+ case 'GeoTiffLayer': {
1651
+ layerParameters = layer.parameters;
1652
+ const geoTiffSource = this._sources[layerParameters.source];
1653
+ if (Array.isArray((_c = layerParameters.symbologyState) === null || _c === void 0 ? void 0 : _c.layers)) {
1654
+ newMapLayer = grammarToOLLayer(layerParameters.symbologyState, geoTiffSource, (_d = layerParameters.opacity) !== null && _d !== void 0 ? _d : 1, (_e = layer.visible) !== null && _e !== void 0 ? _e : true, [], true);
1655
+ }
1656
+ else {
1657
+ // This is to handle python sending a None for the color
1658
+ const layerOptions = {
1659
+ opacity: layerParameters.opacity,
1660
+ visible: layer.visible,
1661
+ source: geoTiffSource,
1270
1662
  };
1663
+ if (layerParameters.color) {
1664
+ layerOptions['style'] = {
1665
+ color: layerParameters.color,
1666
+ };
1667
+ }
1668
+ newMapLayer = new GeoTiffLayer(layerOptions);
1271
1669
  }
1272
- newMapLayer = new WebGlTileLayer(layerOptions);
1273
1670
  break;
1274
1671
  }
1275
1672
  case 'HeatmapLayer': {
@@ -1278,9 +1675,9 @@ export class MainView extends React.Component {
1278
1675
  opacity: layerParameters.opacity,
1279
1676
  visible: layer.visible,
1280
1677
  source: this._sources[layerParameters.source],
1281
- blur: (_b = layerParameters.blur) !== null && _b !== void 0 ? _b : 15,
1282
- radius: (_c = layerParameters.radius) !== null && _c !== void 0 ? _c : 8,
1283
- gradient: layerParameters.color,
1678
+ blur: (_f = layerParameters.blur) !== null && _f !== void 0 ? _f : 15,
1679
+ radius: (_g = layerParameters.radius) !== null && _g !== void 0 ? _g : 8,
1680
+ gradient: (_h = layerParameters.symbologyState) === null || _h === void 0 ? void 0 : _h.gradient,
1284
1681
  });
1285
1682
  break;
1286
1683
  }
@@ -1321,7 +1718,7 @@ export class MainView extends React.Component {
1321
1718
  var _a;
1322
1719
  const sourceProjection = (_a = newMapLayer.getSource()) === null || _a === void 0 ? void 0 : _a.getProjection();
1323
1720
  if (!sourceProjection) {
1324
- console.warn('Layer source projection is undefined or invalid');
1721
+ this._log('warning', 'Layer source projection is undefined or invalid');
1325
1722
  return;
1326
1723
  }
1327
1724
  const projectionCode = sourceProjection.getCode();
@@ -1329,7 +1726,7 @@ export class MainView extends React.Component {
1329
1726
  if (!isProjectionRegistered) {
1330
1727
  // Check if the projection exists in proj4list
1331
1728
  if (!proj4list[projectionCode]) {
1332
- console.warn(`Projection code '${projectionCode}' not found in proj4list`);
1729
+ this._log('warning', `Projection code '${projectionCode}' not found in proj4list`);
1333
1730
  return;
1334
1731
  }
1335
1732
  try {
@@ -1337,7 +1734,7 @@ export class MainView extends React.Component {
1337
1734
  register(proj4);
1338
1735
  }
1339
1736
  catch (error) {
1340
- console.warn(`Failed to register projection '${projectionCode}'. Error: ${error.message}`);
1737
+ this._log('warning', `Failed to register projection '${projectionCode}'. Error: ${error.message}`);
1341
1738
  return;
1342
1739
  }
1343
1740
  }
@@ -1362,12 +1759,14 @@ export class MainView extends React.Component {
1362
1759
  const numLayers = this._Map.getLayers().getLength();
1363
1760
  const safeIndex = Math.min(index, numLayers);
1364
1761
  this._Map.getLayers().insertAt(safeIndex, newMapLayer);
1762
+ this._trackLayerViewState(id, newMapLayer);
1365
1763
  // doing +1 instead of calling method again
1366
1764
  if (!this.state.initialLayersReady &&
1367
1765
  numLayers + 1 === this._initialLayersCount) {
1368
1766
  this.setState(old => (Object.assign(Object.assign({}, old), { initialLayersReady: true })));
1369
1767
  }
1370
1768
  }
1769
+ this._model.syncSelected({ [id]: { type: 'layer' } }, this._model.getClientId().toString());
1371
1770
  }
1372
1771
  catch (error) {
1373
1772
  if (this.state.loadingErrors.find(item => item.id === id && item.error === error.message)) {
@@ -1393,7 +1792,7 @@ export class MainView extends React.Component {
1393
1792
  * @param layer - the layer object.
1394
1793
  */
1395
1794
  async updateLayer(id, layer, mapLayer, oldLayer) {
1396
- var _a, _b, _c, _d, _e, _f, _g, _h;
1795
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
1397
1796
  layer.type !== 'StorySegmentLayer' && mapLayer.setVisible(layer.visible);
1398
1797
  switch (layer.type) {
1399
1798
  case 'RasterLayer': {
@@ -1402,6 +1801,11 @@ export class MainView extends React.Component {
1402
1801
  }
1403
1802
  case 'VectorLayer': {
1404
1803
  const layerParams = layer.parameters;
1804
+ if (Array.isArray((_b = layerParams.symbologyState) === null || _b === void 0 ? void 0 : _b.layers)) {
1805
+ // Grammar layers may change structure (e.g. KDE added/removed) — rebuild.
1806
+ this.replaceLayer(id, layer);
1807
+ break;
1808
+ }
1405
1809
  mapLayer.setOpacity(layerParams.opacity || 1);
1406
1810
  mapLayer.setStyle(this.vectorLayerStyleRuleBuilder(layer));
1407
1811
  break;
@@ -1419,33 +1823,50 @@ export class MainView extends React.Component {
1419
1823
  case 'ImageLayer': {
1420
1824
  break;
1421
1825
  }
1422
- case 'WebGlLayer': {
1423
- mapLayer.setOpacity((_b = layer.parameters) === null || _b === void 0 ? void 0 : _b.opacity);
1424
- if ((_c = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _c === void 0 ? void 0 : _c.color) {
1425
- mapLayer.setStyle({
1426
- color: layer.parameters.color,
1427
- });
1826
+ case 'GeoTiffLayer': {
1827
+ 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)) {
1828
+ this.replaceLayer(id, layer);
1829
+ }
1830
+ else {
1831
+ mapLayer.setOpacity((_e = layer.parameters) === null || _e === void 0 ? void 0 : _e.opacity);
1832
+ if ((_f = layer === null || layer === void 0 ? void 0 : layer.parameters) === null || _f === void 0 ? void 0 : _f.color) {
1833
+ mapLayer.setStyle({
1834
+ color: layer.parameters.color,
1835
+ });
1836
+ }
1428
1837
  }
1429
1838
  break;
1430
1839
  }
1840
+ case 'OpenEOTileLayer': {
1841
+ const layerParams = layer.parameters;
1842
+ const openeoLayer = mapLayer;
1843
+ openeoLayer.setOpacity((_g = layerParams.opacity) !== null && _g !== void 0 ? _g : 1);
1844
+ break;
1845
+ }
1431
1846
  case 'HeatmapLayer': {
1432
1847
  const layerParams = layer.parameters;
1433
1848
  const heatmap = mapLayer;
1434
- heatmap.setOpacity((_d = layerParams.opacity) !== null && _d !== void 0 ? _d : 1);
1435
- heatmap.setBlur((_e = layerParams.blur) !== null && _e !== void 0 ? _e : 15);
1436
- heatmap.setRadius((_f = layerParams.radius) !== null && _f !== void 0 ? _f : 8);
1437
- heatmap.setGradient((_g = layerParams.color) !== null && _g !== void 0 ? _g : ['#00f', '#0ff', '#0f0', '#ff0', '#f00']);
1849
+ heatmap.setOpacity((_h = layerParams.opacity) !== null && _h !== void 0 ? _h : 1);
1850
+ heatmap.setBlur((_j = layerParams.blur) !== null && _j !== void 0 ? _j : 15);
1851
+ heatmap.setRadius((_k = layerParams.radius) !== null && _k !== void 0 ? _k : 8);
1852
+ heatmap.setGradient((_m = (_l = layerParams.symbologyState) === null || _l === void 0 ? void 0 : _l.gradient) !== null && _m !== void 0 ? _m : [
1853
+ '#00f',
1854
+ '#0ff',
1855
+ '#0f0',
1856
+ '#ff0',
1857
+ '#f00',
1858
+ ]);
1438
1859
  this.handleTemporalController(id, layer);
1439
1860
  break;
1440
1861
  }
1441
1862
  case 'StacLayer':
1442
- mapLayer.setOpacity(((_h = layer.parameters) === null || _h === void 0 ? void 0 : _h.opacity) || 1);
1863
+ mapLayer.setOpacity(((_o = layer.parameters) === null || _o === void 0 ? void 0 : _o.opacity) || 1);
1443
1864
  break;
1444
1865
  }
1445
1866
  }
1446
1867
  flyToGeometry(sender, geometry) {
1447
1868
  if (!geometry || typeof geometry.getExtent !== 'function') {
1448
- console.warn('Invalid geometry for flyToGeometry:', geometry);
1869
+ this._log('warning', `Invalid geometry for flyToGeometry: ${geometry}`);
1449
1870
  return;
1450
1871
  }
1451
1872
  const view = this._Map.getView();
@@ -1457,11 +1878,12 @@ export class MainView extends React.Component {
1457
1878
  });
1458
1879
  }
1459
1880
  highlightFeatureOnMap(sender, featureOrGeometry) {
1881
+ var _a;
1460
1882
  const geometry = (featureOrGeometry === null || featureOrGeometry === void 0 ? void 0 : featureOrGeometry.geometry) ||
1461
1883
  (featureOrGeometry === null || featureOrGeometry === void 0 ? void 0 : featureOrGeometry._geometry) ||
1462
1884
  featureOrGeometry;
1463
1885
  if (!geometry) {
1464
- console.warn('No geometry found in feature:', featureOrGeometry);
1886
+ this._log('warning', `No geometry found in feature: ${featureOrGeometry}`);
1465
1887
  return;
1466
1888
  }
1467
1889
  const isOlGeometry = typeof geometry.getCoordinates === 'function';
@@ -1471,63 +1893,128 @@ export class MainView extends React.Component {
1471
1893
  featureProjection: this._Map.getView().getProjection(),
1472
1894
  });
1473
1895
  const olFeature = new Feature(Object.assign({ geometry: parsedGeometry }, (geometry !== featureOrGeometry ? featureOrGeometry : {})));
1474
- if (!this._highlightLayer) {
1475
- this._highlightLayer = new VectorLayer({
1476
- source: new VectorSource(),
1477
- style: feature => {
1478
- var _a;
1479
- const geomType = (_a = feature.getGeometry()) === null || _a === void 0 ? void 0 : _a.getType();
1480
- switch (geomType) {
1481
- case 'Point':
1482
- case 'MultiPoint':
1483
- return new Style({
1484
- image: new Circle({
1485
- radius: 6,
1486
- fill: new Fill({
1487
- color: 'rgba(255, 255, 0, 0.8)',
1488
- }),
1489
- stroke: new Stroke({
1490
- color: '#ff0',
1491
- width: 2,
1492
- }),
1493
- }),
1494
- });
1495
- case 'LineString':
1496
- case 'MultiLineString':
1497
- return new Style({
1498
- stroke: new Stroke({
1499
- color: 'rgba(255, 255, 0, 0.8)',
1500
- width: 3,
1501
- }),
1502
- });
1503
- case 'Polygon':
1504
- case 'MultiPolygon':
1505
- return new Style({
1506
- stroke: new Stroke({
1507
- color: '#f00',
1508
- width: 2,
1509
- }),
1510
- fill: new Fill({
1511
- color: 'rgba(255, 255, 0, 0.8)',
1512
- }),
1513
- });
1514
- default:
1515
- return new Style({
1516
- stroke: new Stroke({
1517
- color: '#000',
1518
- width: 2,
1519
- }),
1520
- });
1521
- }
1522
- },
1523
- zIndex: 999,
1524
- });
1525
- this._Map.addLayer(this._highlightLayer);
1526
- }
1527
- const source = this._highlightLayer.getSource();
1896
+ this._ensureHighlightLayer();
1897
+ const source = (_a = this._highlightLayerRef.current) === null || _a === void 0 ? void 0 : _a.getSource();
1528
1898
  source === null || source === void 0 ? void 0 : source.clear();
1529
1899
  source === null || source === void 0 ? void 0 : source.addFeature(olFeature);
1530
1900
  }
1901
+ _ensureHighlightLayer() {
1902
+ ensureHighlightLayer(this._Map, this._highlightLayerRef);
1903
+ }
1904
+ /**
1905
+ * Replace the highlight layer contents with the given geometries.
1906
+ * Clears the source first so that stale highlights are always removed,
1907
+ * including when the selection becomes empty (geometries = []).
1908
+ */
1909
+ _setHighlightGeometries(geometries) {
1910
+ var _a;
1911
+ this._ensureHighlightLayer();
1912
+ const source = (_a = this._highlightLayerRef.current) === null || _a === void 0 ? void 0 : _a.getSource();
1913
+ source === null || source === void 0 ? void 0 : source.clear();
1914
+ for (const geom of geometries) {
1915
+ source === null || source === void 0 ? void 0 : source.addFeature(new Feature({ geometry: geom }));
1916
+ }
1917
+ }
1918
+ /**
1919
+ * Replace the highlight layer contents with pre-styled features.
1920
+ * Each feature carries its own highlight style via feature.setStyle().
1921
+ */
1922
+ _setHighlightFeatures(features) {
1923
+ var _a;
1924
+ this._ensureHighlightLayer();
1925
+ const source = (_a = this._highlightLayerRef.current) === null || _a === void 0 ? void 0 : _a.getSource();
1926
+ source === null || source === void 0 ? void 0 : source.clear();
1927
+ for (const f of features) {
1928
+ source === null || source === void 0 ? void 0 : source.addFeature(f);
1929
+ }
1930
+ }
1931
+ _buildHighlightStyle(original, geomType) {
1932
+ return buildHighlightStyle(original, geomType);
1933
+ }
1934
+ /**
1935
+ * Compute extent for layer or source
1936
+ */
1937
+ _computeExtent(layer, source) {
1938
+ try {
1939
+ if (source instanceof VectorSource) {
1940
+ const extent = source.getExtent();
1941
+ if (extent) {
1942
+ return extent;
1943
+ }
1944
+ }
1945
+ if (source instanceof TileSource || source instanceof VectorTileSource) {
1946
+ const tileGrid = source.getTileGrid();
1947
+ const extent = tileGrid === null || tileGrid === void 0 ? void 0 : tileGrid.getExtent();
1948
+ if (extent) {
1949
+ return extent;
1950
+ }
1951
+ }
1952
+ if (layer instanceof StacLayer) {
1953
+ const extent = layer.getExtent();
1954
+ if (extent) {
1955
+ return extent;
1956
+ }
1957
+ }
1958
+ }
1959
+ catch (error) {
1960
+ this._log('warning', `Failed to compute extent: ${error}`);
1961
+ }
1962
+ return undefined;
1963
+ }
1964
+ _computeZoomFromExtent(extent) {
1965
+ var _a, _b;
1966
+ if (!this._Map) {
1967
+ return null;
1968
+ }
1969
+ const view = this._Map.getView();
1970
+ const size = (_a = this._Map.getSize()) !== null && _a !== void 0 ? _a : getSize(extent);
1971
+ const resolution = view.getResolutionForExtent(extent, size);
1972
+ const zoom = view.getZoomForResolution(resolution);
1973
+ return (_b = zoom !== null && zoom !== void 0 ? zoom : view.getZoom()) !== null && _b !== void 0 ? _b : 0;
1974
+ }
1975
+ /**
1976
+ * Track layer's extent and zoom in model's view state
1977
+ */
1978
+ _trackLayerViewState(layerId, olLayer) {
1979
+ var _a;
1980
+ const effectiveLayer = olLayer instanceof LayerGroup
1981
+ ? olLayer.getLayers().getArray()[0]
1982
+ : olLayer;
1983
+ if (!effectiveLayer) {
1984
+ return;
1985
+ }
1986
+ const source = effectiveLayer.getSource();
1987
+ const sourceId = (_a = source === null || source === void 0 ? void 0 : source.get) === null || _a === void 0 ? void 0 : _a.call(source, 'id');
1988
+ let extent = sourceId ? this._model.getExtent(sourceId) : undefined;
1989
+ if (!extent) {
1990
+ extent = this._computeExtent(effectiveLayer, source);
1991
+ }
1992
+ if (extent) {
1993
+ const zoom = this._computeZoomFromExtent(extent);
1994
+ if (zoom === null) {
1995
+ return;
1996
+ }
1997
+ const view = { extent, zoom };
1998
+ this._model.updateLayerViewState(layerId, view);
1999
+ }
2000
+ }
2001
+ /**
2002
+ * Track source's extent and zoom in model's view state
2003
+ */
2004
+ _trackSourceExtZoom(sourceId, olSource) {
2005
+ var _a, _b, _c;
2006
+ const extent = this._computeExtent(undefined, olSource);
2007
+ if (extent) {
2008
+ 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);
2009
+ const zoom = this._computeZoomFromExtent(extent);
2010
+ if (zoom === null) {
2011
+ return;
2012
+ }
2013
+ const view = Object.assign({ extent,
2014
+ zoom }, (projection && { projection }));
2015
+ this._model.updateLayerViewState(sourceId, view);
2016
+ }
2017
+ }
1531
2018
  /**
1532
2019
  * Wait for all layers to be loaded.
1533
2020
  */
@@ -1579,7 +2066,137 @@ export class MainView extends React.Component {
1579
2066
  this._Map.removeLayer(mapLayer);
1580
2067
  }
1581
2068
  }
2069
+ /**
2070
+ * Decide how selection changes should affect vector drawing state.
2071
+ *
2072
+ * This helper only computes whether
2073
+ * draw mode must be disabled (non-draw layer selected) and whether draw
2074
+ * interactions should be rebound (draw mode enabled and selected draw layer
2075
+ * changed).
2076
+ */
2077
+ _getVectorDrawingSelectionDecision(layer, selectedLayerId) {
2078
+ const isDrawVectorLayer = this._model.checkIfIsADrawVectorLayer(layer);
2079
+ if (!isDrawVectorLayer) {
2080
+ return { disableEditing: true, shouldRebind: false };
2081
+ }
2082
+ if (!this._model.editingVectorLayer) {
2083
+ return { disableEditing: false, shouldRebind: false };
2084
+ }
2085
+ if (selectedLayerId === this._previousDrawLayerID) {
2086
+ return { disableEditing: false, shouldRebind: false };
2087
+ }
2088
+ return { disableEditing: false, shouldRebind: true };
2089
+ }
2090
+ _handleTemporalControllerActiveChanged() {
2091
+ var _a, _b, _c;
2092
+ const localState = this._model.localState;
2093
+ if (!localState) {
2094
+ return;
2095
+ }
2096
+ const isTemporalControllerActive = localState.isTemporalControllerActive === true;
2097
+ const selectedLayers = (_a = localState.selected) === null || _a === void 0 ? void 0 : _a.value;
2098
+ const selectedLayerId = selectedLayers
2099
+ ? ((_b = Object.keys(selectedLayers)[0]) !== null && _b !== void 0 ? _b : null)
2100
+ : null;
2101
+ const layerType = selectedLayerId
2102
+ ? (_c = this._model.getLayer(selectedLayerId)) === null || _c === void 0 ? void 0 : _c.type
2103
+ : null;
2104
+ const isSelectionValid = !!selectedLayers &&
2105
+ Object.keys(selectedLayers).length === 1 &&
2106
+ !this._model.getSource(selectedLayerId) &&
2107
+ ['VectorLayer', 'HeatmapLayer'].includes(layerType !== null && layerType !== void 0 ? layerType : '');
2108
+ const displayTemporalController = isTemporalControllerActive && isSelectionValid;
2109
+ if (displayTemporalController !== this.state.displayTemporalController) {
2110
+ this.setState(old => (Object.assign(Object.assign({}, old), { displayTemporalController })));
2111
+ this._mainViewModel.commands.notifyCommandChanged(CommandIDs.temporalController);
2112
+ }
2113
+ }
2114
+ _handleRemoteUserChanged() {
2115
+ var _a, _b, _c;
2116
+ const localState = this._model.localState;
2117
+ if (!localState) {
2118
+ return;
2119
+ }
2120
+ const remoteUser = localState.remoteUser;
2121
+ const clients = this._model.sharedModel.awareness.getStates();
2122
+ // If we are in following mode, update UI and viewport from the remote user.
2123
+ if (remoteUser) {
2124
+ const remoteState = clients.get(remoteUser);
2125
+ if (!remoteState) {
2126
+ return;
2127
+ }
2128
+ if (((_a = remoteState.user) === null || _a === void 0 ? void 0 : _a.username) !== ((_b = this.state.remoteUser) === null || _b === void 0 ? void 0 : _b.username)) {
2129
+ this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: remoteState.user })));
2130
+ }
2131
+ const remoteViewport = remoteState.viewportState;
2132
+ if (remoteViewport.value) {
2133
+ const { x, y } = remoteViewport.value.coordinates;
2134
+ const zoom = remoteViewport.value.zoom;
2135
+ this._moveToPosition({ x, y }, zoom, 0);
2136
+ }
2137
+ return;
2138
+ }
2139
+ // If we are unfollowing, reset to local viewport and clear follow UI.
2140
+ if (this.state.remoteUser !== null) {
2141
+ this.setState(old => (Object.assign(Object.assign({}, old), { remoteUser: null })));
2142
+ const viewportState = (_c = localState.viewportState) === null || _c === void 0 ? void 0 : _c.value;
2143
+ if (viewportState) {
2144
+ this._moveToPosition(viewportState.coordinates, viewportState.zoom);
2145
+ }
2146
+ }
2147
+ }
2148
+ _handlePointerChanged() {
2149
+ const clients = this._model.sharedModel.awareness.getStates();
2150
+ const clientPointers = Object.assign({}, this.state.clientPointers);
2151
+ clients.forEach((client, clientId) => {
2152
+ var _a;
2153
+ if (!(client === null || client === void 0 ? void 0 : client.user) || this._model.getClientId() === clientId) {
2154
+ return;
2155
+ }
2156
+ const pointer = (_a = client.pointer) === null || _a === void 0 ? void 0 : _a.value;
2157
+ let currentClientPointer = clientPointers[clientId];
2158
+ if (pointer) {
2159
+ const pixel = this._Map.getPixelFromCoordinate([
2160
+ pointer.coordinates.x,
2161
+ pointer.coordinates.y,
2162
+ ]);
2163
+ const lonLat = toLonLat([pointer.coordinates.x, pointer.coordinates.y]);
2164
+ if (!currentClientPointer) {
2165
+ currentClientPointer = {
2166
+ username: client.user.username,
2167
+ displayName: client.user.display_name,
2168
+ color: client.user.color,
2169
+ coordinates: {
2170
+ x: pixel[0],
2171
+ y: pixel[1],
2172
+ },
2173
+ lonLat: {
2174
+ longitude: lonLat[0],
2175
+ latitude: lonLat[1],
2176
+ },
2177
+ };
2178
+ }
2179
+ else {
2180
+ currentClientPointer = Object.assign(Object.assign({}, currentClientPointer), { coordinates: {
2181
+ x: pixel[0],
2182
+ y: pixel[1],
2183
+ }, lonLat: {
2184
+ longitude: lonLat[0],
2185
+ latitude: lonLat[1],
2186
+ } });
2187
+ }
2188
+ clientPointers[clientId] = currentClientPointer;
2189
+ }
2190
+ else {
2191
+ delete clientPointers[clientId];
2192
+ }
2193
+ });
2194
+ this.setState(old => (Object.assign(Object.assign({}, old), { clientPointers })));
2195
+ }
1582
2196
  _onSharedOptionsChanged() {
2197
+ if (!this._Map) {
2198
+ return;
2199
+ }
1583
2200
  // ! would prefer a model ready signal or something, this feels hacky
1584
2201
  const enableSpectaPresentation = this._model.isSpectaMode();
1585
2202
  // Handle initialization based on specta presentation state
@@ -1632,13 +2249,21 @@ export class MainView extends React.Component {
1632
2249
  if (projection !== undefined && currentProjection !== projection) {
1633
2250
  const newProjection = getProjection(projection);
1634
2251
  if (newProjection) {
2252
+ this.setState(old => ({
2253
+ viewProjection: {
2254
+ code: newProjection.getCode(),
2255
+ units: newProjection.getUnits(),
2256
+ },
2257
+ }));
1635
2258
  view = new View({ projection: newProjection });
1636
2259
  }
1637
2260
  else {
1638
- console.warn(`Invalid projection: ${projection}`);
2261
+ this._log('warning', `Invalid projection: ${projection}`);
1639
2262
  return;
1640
2263
  }
1641
2264
  }
2265
+ view.setRotation(bearing || 0);
2266
+ this._Map.setView(view);
1642
2267
  // Use the extent only if explicitly requested (QGIS files).
1643
2268
  if (useExtent && extent) {
1644
2269
  view.fit(extent);
@@ -1652,8 +2277,6 @@ export class MainView extends React.Component {
1652
2277
  this._model.setOptions(options);
1653
2278
  }
1654
2279
  }
1655
- view.setRotation(bearing || 0);
1656
- this._Map.setView(view);
1657
2280
  }
1658
2281
  _onViewChanged(sender, change) {
1659
2282
  // TODO SOMETHING
@@ -1730,6 +2353,11 @@ export class MainView extends React.Component {
1730
2353
  const { id, oldValue: oldLayer, newValue: newLayer } = change;
1731
2354
  if (!newLayer || Object.keys(newLayer).length === 0) {
1732
2355
  this.removeLayer(id);
2356
+ if (this._model.checkIfIsADrawVectorLayer(oldLayer)) {
2357
+ this._model.editingVectorLayer = false;
2358
+ this._updateEditingVectorLayer();
2359
+ this._mainViewModel.commands.notifyCommandChanged(CommandIDs.toggleDrawFeatures);
2360
+ }
1733
2361
  return;
1734
2362
  }
1735
2363
  if (oldLayer && oldLayer.type !== newLayer.type) {
@@ -1740,6 +2368,9 @@ export class MainView extends React.Component {
1740
2368
  const layerTree = JupyterGISModel.getOrderedLayerIds(this._model);
1741
2369
  if (layerTree.includes(id)) {
1742
2370
  this.updateLayer(id, newLayer, mapLayer, oldLayer);
2371
+ if (mapLayer) {
2372
+ this._trackLayerViewState(id, mapLayer);
2373
+ }
1743
2374
  }
1744
2375
  else {
1745
2376
  this.updateLayers(layerTree);
@@ -1768,6 +2399,14 @@ export class MainView extends React.Component {
1768
2399
  }
1769
2400
  }
1770
2401
  });
2402
+ this.setState(old => (Object.assign(Object.assign({}, old), { identifyFeatureFloatersVersion: old.identifyFeatureFloatersVersion + 1 })));
2403
+ }
2404
+ _clearHighlightWhenIdentifyDisabled() {
2405
+ var _a, _b;
2406
+ if (this._model.currentMode !== 'identifying' &&
2407
+ this._highlightLayerRef.current) {
2408
+ (_b = (_a = this._highlightLayerRef.current) === null || _a === void 0 ? void 0 : _a.getSource()) === null || _b === void 0 ? void 0 : _b.clear();
2409
+ }
1771
2410
  }
1772
2411
  _computeAnnotationPosition(annotation) {
1773
2412
  const { x, y } = annotation.position;
@@ -1792,6 +2431,58 @@ export class MainView extends React.Component {
1792
2431
  }
1793
2432
  });
1794
2433
  }
2434
+ _computeFeatureFloaterPosition(feature) {
2435
+ var _a;
2436
+ 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;
2437
+ if (!geometry) {
2438
+ return undefined;
2439
+ }
2440
+ if (typeof geometry.getExtent === 'function') {
2441
+ const extent = geometry.getExtent();
2442
+ const center = getCenter(extent);
2443
+ const pixels = this._Map.getPixelFromCoordinate(center);
2444
+ if (pixels) {
2445
+ return { x: pixels[0], y: pixels[1] };
2446
+ }
2447
+ return undefined;
2448
+ }
2449
+ if (geometry.type === 'Point' && Array.isArray(geometry.coordinates)) {
2450
+ const pixels = this._Map.getPixelFromCoordinate(geometry.coordinates);
2451
+ if (pixels) {
2452
+ return { x: pixels[0], y: pixels[1] };
2453
+ }
2454
+ }
2455
+ return undefined;
2456
+ }
2457
+ _getVisibleDrawIdentifiedFeatures() {
2458
+ var _a, _b, _c;
2459
+ 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 : [];
2460
+ const drawEntries = identifiedFeatures.filter(entry => entry.floaterOpen === true);
2461
+ const visibleFeatures = drawEntries
2462
+ .map(entry => {
2463
+ const featureId = getFeatureIdentifier(entry.feature);
2464
+ if (!featureId) {
2465
+ return undefined;
2466
+ }
2467
+ return [featureId, entry.feature];
2468
+ })
2469
+ .filter((entry) => !!entry);
2470
+ return visibleFeatures;
2471
+ }
2472
+ _updateFeatureFloaters() {
2473
+ this._getVisibleDrawIdentifiedFeatures().forEach(([floaterKey, feature]) => {
2474
+ const el = document.getElementById(`feature-floater-${floaterKey}`);
2475
+ if (!el) {
2476
+ return;
2477
+ }
2478
+ const screenPosition = this._computeFeatureFloaterPosition(feature);
2479
+ if (!screenPosition) {
2480
+ return;
2481
+ }
2482
+ el.style.left = `${Math.round(screenPosition.x)}px`;
2483
+ el.style.top = `${Math.round(screenPosition.y)}px`;
2484
+ });
2485
+ }
1795
2486
  // TODO this and flyToPosition need a rework
1796
2487
  _onZoomToPosition(_, id) {
1797
2488
  var _a, _b, _c;
@@ -1802,7 +2493,6 @@ export class MainView extends React.Component {
1802
2493
  return;
1803
2494
  }
1804
2495
  // The id is a layer
1805
- let extent;
1806
2496
  const layer = this.getLayer(id);
1807
2497
  const source = layer === null || layer === void 0 ? void 0 : layer.getSource();
1808
2498
  const jgisLayer = this._model.getLayer(id);
@@ -1852,19 +2542,13 @@ export class MainView extends React.Component {
1852
2542
  return;
1853
2543
  }
1854
2544
  }
1855
- if (source instanceof VectorSource) {
1856
- extent = source.getExtent();
1857
- }
1858
- if (source instanceof TileSource) {
1859
- // Tiled sources don't have getExtent() so we get it from the grid
1860
- const tileGrid = source.getTileGrid();
1861
- extent = tileGrid === null || tileGrid === void 0 ? void 0 : tileGrid.getExtent();
1862
- }
1863
- if (layer instanceof StacLayer) {
1864
- extent = layer.getExtent();
1865
- }
2545
+ const extent = this._computeExtent(layer, source);
1866
2546
  if (!extent) {
1867
- console.warn('Layer has no extent.');
2547
+ this._log('warning', 'Layer ${id} has no extent.');
2548
+ return;
2549
+ }
2550
+ if (!extent.every(value => Number.isFinite(value))) {
2551
+ this._log('warning', `Layer ${id} has an invalid extent: ${extent.join(', ')}`);
1868
2552
  return;
1869
2553
  }
1870
2554
  // Convert layer extent value to view projection if needed
@@ -1873,6 +2557,10 @@ export class MainView extends React.Component {
1873
2557
  const transformedExtent = sourceProjection && sourceProjection !== viewProjection
1874
2558
  ? transformExtent(extent, sourceProjection, viewProjection)
1875
2559
  : extent;
2560
+ if (!transformedExtent.every(value => Number.isFinite(value))) {
2561
+ this._log('warning', `Layer ${id} has an invalid transformed extent: ${transformedExtent.join(', ')}`);
2562
+ return;
2563
+ }
1876
2564
  this._Map.getView().fit(transformedExtent, {
1877
2565
  size: this._Map.getSize(),
1878
2566
  duration: 500,
@@ -1932,10 +2620,12 @@ export class MainView extends React.Component {
1932
2620
  _onPointerMove(e) {
1933
2621
  const pixel = this._Map.getEventPixel(e);
1934
2622
  const coordinates = this._Map.getCoordinateFromPixel(pixel);
2623
+ this._lastPointerCoord = coordinates;
1935
2624
  this._syncPointer(coordinates);
1936
2625
  }
1937
2626
  async _addMarker(e) {
1938
- if (this._model.currentMode !== 'marking') {
2627
+ if (this.state.editingVectorLayer ||
2628
+ this._model.currentMode !== 'marking') {
1939
2629
  return;
1940
2630
  }
1941
2631
  const coordinate = this._Map.getCoordinateFromPixel(e.pixel);
@@ -1947,7 +2637,7 @@ export class MainView extends React.Component {
1947
2637
  const layerParams = {
1948
2638
  opacity: 1.0,
1949
2639
  source: sourceId,
1950
- symbologyState: { renderType: 'Single Symbol' },
2640
+ symbologyState: { layers: [] },
1951
2641
  };
1952
2642
  const sourceModel = {
1953
2643
  type: 'MarkerSource',
@@ -1967,25 +2657,29 @@ export class MainView extends React.Component {
1967
2657
  }
1968
2658
  _identifyFeature(e) {
1969
2659
  var _a, _b;
1970
- if (this._model.currentMode !== 'identifying') {
2660
+ if (this.state.editingVectorLayer ||
2661
+ this._model.currentMode !== 'identifying') {
1971
2662
  return;
1972
2663
  }
1973
2664
  const localState = (_a = this._model) === null || _a === void 0 ? void 0 : _a.sharedModel.awareness.getLocalState();
1974
2665
  const selectedLayer = (_b = localState === null || localState === void 0 ? void 0 : localState.selected) === null || _b === void 0 ? void 0 : _b.value;
1975
2666
  if (!selectedLayer) {
1976
- console.warn('Layer must be selected to use identify tool');
2667
+ this._log('warning', 'Layer must be selected to use identify tool');
1977
2668
  return;
1978
2669
  }
1979
2670
  const layerId = Object.keys(selectedLayer)[0];
1980
2671
  const jgisLayer = this._model.getLayer(layerId);
1981
2672
  switch (jgisLayer === null || jgisLayer === void 0 ? void 0 : jgisLayer.type) {
2673
+ case 'VectorLayer':
2674
+ // Handled by selectInteraction (createSelectInteraction).
2675
+ break;
1982
2676
  case 'VectorTileLayer': {
1983
2677
  const geometries = [];
1984
2678
  const features = [];
1985
- let foundAny = false;
2679
+ let foundAnyFeatures = false;
1986
2680
  this._Map.forEachFeatureAtPixel(e.pixel, (feature) => {
1987
2681
  var _a, _b;
1988
- foundAny = true;
2682
+ foundAnyFeatures = true;
1989
2683
  let geom;
1990
2684
  let props = {};
1991
2685
  if (feature instanceof RenderFeature) {
@@ -2011,15 +2705,18 @@ export class MainView extends React.Component {
2011
2705
  geometries.push(geom);
2012
2706
  }
2013
2707
  if (props && Object.keys(props).length > 0) {
2014
- features.push(props);
2708
+ features.push({
2709
+ feature: props,
2710
+ floaterOpen: false,
2711
+ });
2015
2712
  }
2016
2713
  return true;
2017
2714
  });
2018
2715
  if (features.length > 0) {
2019
- this._model.syncIdentifiedFeatures(features, this._mainViewModel.id);
2716
+ this._model.syncIdentifiedFeatures(features, this._model.getClientId().toString());
2020
2717
  }
2021
- else if (!foundAny) {
2022
- this._model.syncIdentifiedFeatures([], this._mainViewModel.id);
2718
+ else if (!foundAnyFeatures) {
2719
+ this._model.syncIdentifiedFeatures([], this._model.getClientId().toString());
2023
2720
  }
2024
2721
  if (geometries.length > 0) {
2025
2722
  for (const geom of geometries) {
@@ -2033,7 +2730,7 @@ export class MainView extends React.Component {
2033
2730
  }
2034
2731
  break;
2035
2732
  }
2036
- case 'WebGlLayer': {
2733
+ case 'GeoTiffLayer': {
2037
2734
  const layer = this.getLayer(layerId);
2038
2735
  const data = layer.getData(e.pixel);
2039
2736
  // TODO: Handle dataviews?
@@ -2047,7 +2744,7 @@ export class MainView extends React.Component {
2047
2744
  }
2048
2745
  // last element is alpha
2049
2746
  bandValues['Alpha'] = data[data.length - 1];
2050
- this._model.syncIdentifiedFeatures([bandValues], this._mainViewModel.id);
2747
+ this._model.syncIdentifiedFeatures([{ feature: bandValues, floaterOpen: false }], this._mainViewModel.id);
2051
2748
  const coordinate = this._Map.getCoordinateFromPixel(e.pixel);
2052
2749
  const point = new Point(coordinate);
2053
2750
  // trigger highlight via signal
@@ -2066,7 +2763,7 @@ export class MainView extends React.Component {
2066
2763
  this.updateSource(layerId, jgisLayer);
2067
2764
  }
2068
2765
  if (!jgisLayer || !olLayer) {
2069
- console.error('Failed to update layer -- layer not found');
2766
+ this._log('error', 'Failed to update layer -- layer not found');
2070
2767
  return;
2071
2768
  }
2072
2769
  this.updateLayer(layerId, jgisLayer, olLayer);
@@ -2076,6 +2773,9 @@ export class MainView extends React.Component {
2076
2773
  const { id: layerId, selectedFeature } = json;
2077
2774
  const olLayer = this.getLayer(layerId);
2078
2775
  const source = olLayer.getSource();
2776
+ if (typeof source.forEachFeature !== 'function') {
2777
+ return;
2778
+ }
2079
2779
  source.forEachFeature(feature => {
2080
2780
  const time = feature.get(selectedFeature);
2081
2781
  const parsedTime = typeof time === 'string' ? Date.parse(time) : time;
@@ -2092,7 +2792,19 @@ export class MainView extends React.Component {
2092
2792
  throw new Error('Could not move to geolocation, because current zoom is not defined.');
2093
2793
  }
2094
2794
  }
2795
+ _updateEditingVectorLayer() {
2796
+ const editingVectorLayer = this._model.editingVectorLayer;
2797
+ this.setState(old => (Object.assign(Object.assign({}, old), { editingVectorLayer })));
2798
+ if (editingVectorLayer === true) {
2799
+ this._editVectorLayer();
2800
+ }
2801
+ if (editingVectorLayer === false && this._draw) {
2802
+ this._removeDrawInteraction();
2803
+ this._currentDrawLayerID = undefined;
2804
+ }
2805
+ }
2095
2806
  render() {
2807
+ var _a, _b, _c;
2096
2808
  return (React.createElement(React.Fragment, null,
2097
2809
  Object.entries(this.state.annotations).map(([key, annotation]) => {
2098
2810
  if (!this._model.annotationModel) {
@@ -2105,9 +2817,24 @@ export class MainView extends React.Component {
2105
2817
  }, className: 'jGIS-Popup-Wrapper' },
2106
2818
  React.createElement(AnnotationFloater, { itemId: key, annotationModel: this._model.annotationModel }))));
2107
2819
  }),
2820
+ this._getVisibleDrawIdentifiedFeatures().map(([floaterKey, feature]) => {
2821
+ const screenPosition = this._computeFeatureFloaterPosition(feature);
2822
+ if (!screenPosition) {
2823
+ return null;
2824
+ }
2825
+ return (React.createElement("div", { key: `feature-floater-${floaterKey}`, id: `feature-floater-${floaterKey}`, style: {
2826
+ left: screenPosition.x,
2827
+ top: screenPosition.y,
2828
+ }, className: "jGIS-Popup-Wrapper jGIS-FeatureFloater-Wrapper" },
2829
+ React.createElement(FeatureFloater, { feature: feature })));
2830
+ }),
2831
+ this.state.editingVectorLayer && (React.createElement("div", { className: "jgis-geometry-type-selector-overlay" },
2832
+ React.createElement("select", { className: "geometry-type-selector", id: "geometry-type-selector", value: (_a = this.state.drawGeometryLabel) !== null && _a !== void 0 ? _a : '', onChange: this._handleDrawGeometryTypeChange },
2833
+ React.createElement("option", { value: "", disabled: true, hidden: true }, "Geometry type"),
2834
+ DRAW_GEOMETRIES.map(geometryType => (React.createElement("option", { key: geometryType, value: geometryType }, geometryType)))))),
2108
2835
  React.createElement("div", { className: "jGIS-Mainview-Container" },
2109
2836
  this.state.displayTemporalController && (React.createElement(TemporalSlider, { model: this._model, filterStates: this.state.filterStates })),
2110
- React.createElement("div", { className: "jGIS-Mainview data-jgis-keybinding", tabIndex: 0, style: {
2837
+ React.createElement("div", { ref: this.mainViewRef, className: "jGIS-Mainview data-jgis-keybinding", tabIndex: 0, style: {
2111
2838
  border: this.state.remoteUser
2112
2839
  ? `solid 3px ${this.state.remoteUser.color}`
2113
2840
  : 'unset',
@@ -2119,16 +2846,46 @@ export class MainView extends React.Component {
2119
2846
  React.createElement(LoadingOverlay, { loading: this.state.loading }),
2120
2847
  React.createElement(FollowIndicator, { remoteUser: this.state.remoteUser }),
2121
2848
  React.createElement(CollaboratorPointers, { clients: this.state.clientPointers }),
2122
- React.createElement("div", { ref: this.divRef, style: {
2849
+ React.createElement("div", { ref: this.divRef, className: "jgis-mainview-stage", style: {
2123
2850
  width: '100%',
2124
2851
  height: '100%',
2852
+ position: 'relative',
2125
2853
  } },
2126
- React.createElement("div", { className: "jgis-panels-wrapper" }, !this.state.isSpectaPresentation ? (React.createElement(React.Fragment, null,
2127
- this._state && (React.createElement(LeftPanel, { model: this._model, commands: this._mainViewModel.commands, state: this._state, settings: this.state.jgisSettings })),
2128
- 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) })))),
2129
- React.createElement("div", { ref: this.controlsToolbarRef, className: "jgis-controls-toolbar" }))),
2854
+ React.createElement(ListStoryScrollTrackProvider, { model: this._model, enabled: this.state.isSpectaPresentation &&
2855
+ ((_b = this._model.getSelectedStory().story) === null || _b === void 0 ? void 0 : _b.storyType) ===
2856
+ STORY_TYPE.verticalScroll },
2857
+ this.state.isSpectaPresentation &&
2858
+ ((_c = this._model.getSelectedStory().story) === null || _c === void 0 ? void 0 : _c.storyType) ===
2859
+ STORY_TYPE.verticalScroll ? (React.createElement(ListStoryStageOverlay, { model: this._model, segmentTransition: this.state.segmentTransition })) : null,
2860
+ React.createElement("div", { className: "jgis-panels-wrapper" }, !this.state.isSpectaPresentation ? (React.createElement(React.Fragment, null, this.props.isMobile &&
2861
+ this._state &&
2862
+ this._formSchemaRegistry &&
2863
+ this._annotationModel ? (React.createElement(MergedPanel, { model: this._model, commands: this._mainViewModel.commands, state: this._state, settings: this.state.jgisSettings, formSchemaRegistry: this._formSchemaRegistry, annotationModel: this._annotationModel, addLayer: this.addLayer.bind(this), removeLayer: this.removeLayer.bind(this) })) : (React.createElement(React.Fragment, null,
2864
+ this._state && (React.createElement(LeftPanel, { model: this._model, commands: this._mainViewModel.commands, state: this._state, settings: this.state.jgisSettings })),
2865
+ this._formSchemaRegistry &&
2866
+ 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, patchGeoJSONFeatureProperties: this._patchGeoJSONFeatureProperties })))))) : (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._addLayerForPanels, removeLayer: this._removeLayerForPanels, onSegmentTransitionChange: this._handleSegmentTransitionChange })))),
2867
+ React.createElement("div", { ref: this.controlsToolbarRef, className: "jgis-controls-toolbar" })))),
2130
2868
  !this.state.isSpectaPresentation && (React.createElement(StatusBar, { jgisModel: this._model, loading: this.state.loadingLayer, projection: this.state.viewProjection, scale: this.state.scale })))));
2131
2869
  }
2870
+ _log(level, message) {
2871
+ var _a;
2872
+ // Always mirror to the browser console regardless of whether the JupyterLab
2873
+ // logger is available.
2874
+ if (level === 'error' || level === 'critical') {
2875
+ // eslint-disable-next-line no-console
2876
+ console.error(message);
2877
+ }
2878
+ else if (level === 'warning') {
2879
+ // eslint-disable-next-line no-console
2880
+ console.warn(message);
2881
+ }
2882
+ else {
2883
+ // eslint-disable-next-line no-console
2884
+ console.log(message);
2885
+ }
2886
+ // Forward to JupyterLab log console when available.
2887
+ (_a = this._loggerRegistry) === null || _a === void 0 ? void 0 : _a.getLogger(this._model.filePath).log({ type: 'text', level, data: message });
2888
+ }
2132
2889
  }
2133
2890
  // ! TODO make mainview a modern react component instead of a class
2134
2891
  /** Thin wrapper that injects isMobile from useMediaQuery so MainView can use it in JSX. */