@mwater/visualization 5.5.0 → 5.6.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 (222) hide show
  1. package/lib/MWaterContextComponent.d.ts +1 -1
  2. package/lib/MWaterGlobalFiltersComponent.d.ts +2 -2
  3. package/lib/MWaterGlobalFiltersComponent.js +11 -20
  4. package/lib/MWaterLoaderComponent.d.ts +4 -13
  5. package/lib/MWaterLoaderComponent.js +2 -11
  6. package/lib/UndoStack.d.ts +2 -1
  7. package/lib/UndoStack.js +12 -6
  8. package/lib/dashboards/DashboardComponent.js +5 -4
  9. package/lib/dashboards/DashboardDesign.d.ts +1 -1
  10. package/lib/dashboards/ServerDashboardDataSource.js +0 -10
  11. package/lib/dashboards/SettingsModalComponent.js +1 -1
  12. package/lib/datagrids/DatagridComponent.js +22 -2
  13. package/lib/datagrids/DatagridDesignerComponent.d.ts +2 -3
  14. package/lib/datagrids/DatagridDesignerComponent.js +108 -120
  15. package/lib/datagrids/DatagridViewComponent.js +3 -2
  16. package/lib/datagrids/OrderBysDesignerComponent.d.ts +7 -7
  17. package/lib/datagrids/OrderBysDesignerComponent.js +19 -28
  18. package/lib/index.css +45 -2
  19. package/lib/index.d.ts +5 -5
  20. package/lib/index.js +2 -3
  21. package/lib/layouts/blocks/BlocksDisplayComponent.d.ts +8 -1
  22. package/lib/layouts/blocks/BlocksDisplayComponent.js +46 -4
  23. package/lib/maps/BufferLayer.js +12 -0
  24. package/lib/maps/BufferLayerDesign.d.ts +1 -1
  25. package/lib/maps/BufferLayerDesignerComponent.js +2 -2
  26. package/lib/maps/ChoroplethLayer.js +12 -0
  27. package/lib/maps/ChoroplethLayerDesign.d.ts +5 -2
  28. package/lib/maps/ChoroplethLayerDesigner.d.ts +10 -32
  29. package/lib/maps/ChoroplethLayerDesigner.js +58 -89
  30. package/lib/maps/DirectMapDataSource.js +0 -10
  31. package/lib/maps/EditHoverOver.d.ts +4 -3
  32. package/lib/maps/EditHoverOver.js +3 -3
  33. package/lib/maps/HoverContent.js +1 -1
  34. package/lib/maps/LeafletMapComponent.js +10 -19
  35. package/lib/maps/MapComponent.js +0 -1
  36. package/lib/maps/MapUtils.js +10 -1
  37. package/lib/maps/MarkersLayer.js +18 -2
  38. package/lib/maps/MarkersLayerDesign.d.ts +1 -1
  39. package/lib/maps/MarkersLayerDesignerComponent.d.ts +12 -41
  40. package/lib/maps/MarkersLayerDesignerComponent.js +81 -111
  41. package/lib/maps/ServerMapDataSource.js +0 -10
  42. package/lib/maps/VectorMapViewComponent.js +1 -9
  43. package/lib/maps/symbols/font-awesome/asterisk.png +0 -0
  44. package/lib/maps/symbols/font-awesome/ban.png +0 -0
  45. package/lib/maps/symbols/font-awesome/beer.png +0 -0
  46. package/lib/maps/symbols/font-awesome/bell.png +0 -0
  47. package/lib/maps/symbols/font-awesome/bolt.png +0 -0
  48. package/lib/maps/symbols/font-awesome/building.png +0 -0
  49. package/lib/maps/symbols/font-awesome/bullseye.png +0 -0
  50. package/lib/maps/symbols/font-awesome/bus.png +0 -0
  51. package/lib/maps/symbols/font-awesome/caret-up.png +0 -0
  52. package/lib/maps/symbols/font-awesome/certificate.png +0 -0
  53. package/lib/maps/symbols/font-awesome/check-circle.png +0 -0
  54. package/lib/maps/symbols/font-awesome/check.png +0 -0
  55. package/lib/maps/symbols/font-awesome/chevron-circle-down.png +0 -0
  56. package/lib/maps/symbols/font-awesome/chevron-circle-up.png +0 -0
  57. package/lib/maps/symbols/font-awesome/cloud-rain.png +0 -0
  58. package/lib/maps/symbols/font-awesome/cloud.png +0 -0
  59. package/lib/maps/symbols/font-awesome/comment.png +0 -0
  60. package/lib/maps/symbols/font-awesome/crosshairs.png +0 -0
  61. package/lib/maps/symbols/font-awesome/dot-circle-o.png +0 -0
  62. package/lib/maps/symbols/font-awesome/exclamation-circle.png +0 -0
  63. package/lib/maps/symbols/font-awesome/exclamation-triangle.png +0 -0
  64. package/lib/maps/symbols/font-awesome/female.png +0 -0
  65. package/lib/maps/symbols/font-awesome/file.png +0 -0
  66. package/lib/maps/symbols/font-awesome/flag.png +0 -0
  67. package/lib/maps/symbols/font-awesome/flask.png +0 -0
  68. package/lib/maps/symbols/font-awesome/h-square.png +0 -0
  69. package/lib/maps/symbols/font-awesome/home.png +0 -0
  70. package/lib/maps/symbols/font-awesome/info-circle.png +0 -0
  71. package/lib/maps/symbols/font-awesome/male.png +0 -0
  72. package/lib/maps/symbols/font-awesome/medkit.png +0 -0
  73. package/lib/maps/symbols/font-awesome/mobile.png +0 -0
  74. package/lib/maps/symbols/font-awesome/plus-circle.png +0 -0
  75. package/lib/maps/symbols/font-awesome/plus-square.png +0 -0
  76. package/lib/maps/symbols/font-awesome/plus.png +0 -0
  77. package/lib/maps/symbols/font-awesome/square.png +0 -0
  78. package/lib/maps/symbols/font-awesome/star.png +0 -0
  79. package/lib/maps/symbols/font-awesome/thumbs-down.png +0 -0
  80. package/lib/maps/symbols/font-awesome/thumbs-up.png +0 -0
  81. package/lib/maps/symbols/font-awesome/ticket.png +0 -0
  82. package/lib/maps/symbols/font-awesome/times-circle.png +0 -0
  83. package/lib/maps/symbols/font-awesome/times.png +0 -0
  84. package/lib/maps/symbols/font-awesome/tint.png +0 -0
  85. package/lib/maps/symbols/font-awesome/tree.png +0 -0
  86. package/lib/maps/symbols/font-awesome/university.png +0 -0
  87. package/lib/maps/symbols/font-awesome/usd.png +0 -0
  88. package/lib/maps/symbols/font-awesome/user.png +0 -0
  89. package/lib/maps/symbols/font-awesome/users.png +0 -0
  90. package/lib/maps/symbols/font-awesome/wheelchair.png +0 -0
  91. package/lib/maps/symbols/sdf-ize.sh +93 -0
  92. package/lib/maps/vectorMaps.d.ts +1 -0
  93. package/lib/maps/vectorMaps.js +20 -36
  94. package/lib/mwater_table_selection/IndicatorsListComponent.d.ts +4 -2
  95. package/lib/mwater_table_selection/IndicatorsListComponent.js +103 -34
  96. package/lib/mwater_table_selection/MWaterCalculatedDataSourcesListComponent.d.ts +18 -0
  97. package/lib/mwater_table_selection/MWaterCalculatedDataSourcesListComponent.js +80 -0
  98. package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.d.ts +26 -0
  99. package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.js +237 -51
  100. package/lib/mwater_table_selection/MWaterTableSelectComponent.d.ts +2 -2
  101. package/lib/mwater_table_selection/MWaterTableSelectComponent.js +9 -4
  102. package/lib/mwater_table_selection/MWaterWorkflowsSelectComponent.d.ts +19 -0
  103. package/lib/mwater_table_selection/MWaterWorkflowsSelectComponent.js +111 -0
  104. package/lib/quickfilter/QuickfiltersComponent.d.ts +3 -102
  105. package/lib/quickfilter/QuickfiltersComponent.js +53 -110
  106. package/lib/quickfilter/TextLiteralComponent.d.ts +23 -47
  107. package/lib/quickfilter/TextLiteralComponent.js +85 -82
  108. package/lib/widgets/MapWidget.js +4 -2
  109. package/lib/widgets/text/ExprItemEditorComponent.d.ts +3 -8
  110. package/lib/widgets/text/ExprItemEditorComponent.js +36 -33
  111. package/lib/widgets/text/ExprUpdateModalComponent.d.ts +1 -0
  112. package/package.json +2 -3
  113. package/src/MWaterContextComponent.tsx +1 -1
  114. package/src/{MWaterGlobalFiltersComponent.ts → MWaterGlobalFiltersComponent.tsx} +32 -33
  115. package/src/{MWaterLoaderComponent.ts → MWaterLoaderComponent.tsx} +17 -18
  116. package/src/UndoStack.ts +14 -6
  117. package/src/dashboards/DashboardComponent.tsx +5 -4
  118. package/src/dashboards/DashboardDesign.ts +1 -1
  119. package/src/dashboards/ServerDashboardDataSource.ts +0 -12
  120. package/src/dashboards/SettingsModalComponent.tsx +1 -1
  121. package/src/datagrids/DatagridComponent.tsx +30 -2
  122. package/src/datagrids/DatagridDesignerComponent.tsx +241 -229
  123. package/src/datagrids/DatagridViewComponent.tsx +3 -2
  124. package/src/datagrids/OrderBysDesignerComponent.tsx +61 -70
  125. package/src/index.css +45 -2
  126. package/src/index.ts +5 -11
  127. package/src/layouts/blocks/BlocksDisplayComponent.tsx +60 -5
  128. package/src/maps/BufferLayer.ts +14 -1
  129. package/src/maps/BufferLayerDesign.ts +1 -1
  130. package/src/maps/BufferLayerDesignerComponent.tsx +2 -1
  131. package/src/maps/ChoroplethLayer.ts +20 -7
  132. package/src/maps/ChoroplethLayerDesign.ts +5 -2
  133. package/src/maps/ChoroplethLayerDesigner.tsx +169 -165
  134. package/src/maps/DirectMapDataSource.ts +0 -12
  135. package/src/maps/EditHoverOver.tsx +9 -5
  136. package/src/maps/HoverContent.tsx +1 -1
  137. package/src/maps/LeafletMapComponent.tsx +10 -19
  138. package/src/maps/MapComponent.ts +0 -1
  139. package/src/maps/MapUtils.ts +13 -1
  140. package/src/maps/MarkersLayer.ts +22 -5
  141. package/src/maps/MarkersLayerDesign.ts +1 -1
  142. package/src/maps/MarkersLayerDesignerComponent.tsx +360 -0
  143. package/src/maps/ServerMapDataSource.ts +0 -12
  144. package/src/maps/VectorMapViewComponent.tsx +2 -13
  145. package/src/maps/symbols/font-awesome/asterisk.png +0 -0
  146. package/src/maps/symbols/font-awesome/ban.png +0 -0
  147. package/src/maps/symbols/font-awesome/beer.png +0 -0
  148. package/src/maps/symbols/font-awesome/bell.png +0 -0
  149. package/src/maps/symbols/font-awesome/bolt.png +0 -0
  150. package/src/maps/symbols/font-awesome/building.png +0 -0
  151. package/src/maps/symbols/font-awesome/bullseye.png +0 -0
  152. package/src/maps/symbols/font-awesome/bus.png +0 -0
  153. package/src/maps/symbols/font-awesome/caret-up.png +0 -0
  154. package/src/maps/symbols/font-awesome/certificate.png +0 -0
  155. package/src/maps/symbols/font-awesome/check-circle.png +0 -0
  156. package/src/maps/symbols/font-awesome/check.png +0 -0
  157. package/src/maps/symbols/font-awesome/chevron-circle-down.png +0 -0
  158. package/src/maps/symbols/font-awesome/chevron-circle-up.png +0 -0
  159. package/src/maps/symbols/font-awesome/cloud-rain.png +0 -0
  160. package/src/maps/symbols/font-awesome/cloud.png +0 -0
  161. package/src/maps/symbols/font-awesome/comment.png +0 -0
  162. package/src/maps/symbols/font-awesome/crosshairs.png +0 -0
  163. package/src/maps/symbols/font-awesome/dot-circle-o.png +0 -0
  164. package/src/maps/symbols/font-awesome/exclamation-circle.png +0 -0
  165. package/src/maps/symbols/font-awesome/exclamation-triangle.png +0 -0
  166. package/src/maps/symbols/font-awesome/female.png +0 -0
  167. package/src/maps/symbols/font-awesome/file.png +0 -0
  168. package/src/maps/symbols/font-awesome/flag.png +0 -0
  169. package/src/maps/symbols/font-awesome/flask.png +0 -0
  170. package/src/maps/symbols/font-awesome/h-square.png +0 -0
  171. package/src/maps/symbols/font-awesome/home.png +0 -0
  172. package/src/maps/symbols/font-awesome/info-circle.png +0 -0
  173. package/src/maps/symbols/font-awesome/male.png +0 -0
  174. package/src/maps/symbols/font-awesome/medkit.png +0 -0
  175. package/src/maps/symbols/font-awesome/mobile.png +0 -0
  176. package/src/maps/symbols/font-awesome/plus-circle.png +0 -0
  177. package/src/maps/symbols/font-awesome/plus-square.png +0 -0
  178. package/src/maps/symbols/font-awesome/plus.png +0 -0
  179. package/src/maps/symbols/font-awesome/square.png +0 -0
  180. package/src/maps/symbols/font-awesome/star.png +0 -0
  181. package/src/maps/symbols/font-awesome/thumbs-down.png +0 -0
  182. package/src/maps/symbols/font-awesome/thumbs-up.png +0 -0
  183. package/src/maps/symbols/font-awesome/ticket.png +0 -0
  184. package/src/maps/symbols/font-awesome/times-circle.png +0 -0
  185. package/src/maps/symbols/font-awesome/times.png +0 -0
  186. package/src/maps/symbols/font-awesome/tint.png +0 -0
  187. package/src/maps/symbols/font-awesome/tree.png +0 -0
  188. package/src/maps/symbols/font-awesome/university.png +0 -0
  189. package/src/maps/symbols/font-awesome/usd.png +0 -0
  190. package/src/maps/symbols/font-awesome/user.png +0 -0
  191. package/src/maps/symbols/font-awesome/users.png +0 -0
  192. package/src/maps/symbols/font-awesome/wheelchair.png +0 -0
  193. package/src/maps/symbols/sdf-ize.sh +93 -0
  194. package/src/maps/vectorMaps.tsx +20 -44
  195. package/src/mwater_table_selection/IndicatorsListComponent.tsx +165 -37
  196. package/src/mwater_table_selection/MWaterCalculatedDataSourcesListComponent.tsx +111 -0
  197. package/src/mwater_table_selection/MWaterCompleteTableSelectComponent.tsx +373 -37
  198. package/src/mwater_table_selection/MWaterTableSelectComponent.tsx +12 -8
  199. package/src/mwater_table_selection/MWaterWorkflowsSelectComponent.tsx +159 -0
  200. package/src/quickfilter/{QuickfiltersComponent.ts → QuickfiltersComponent.tsx} +165 -158
  201. package/src/quickfilter/TextLiteralComponent.tsx +197 -0
  202. package/src/widgets/MapWidget.tsx +9 -1
  203. package/src/widgets/text/ExprItemEditorComponent.tsx +83 -77
  204. package/src/widgets/text/ExprUpdateModalComponent.tsx +1 -0
  205. package/test/UndoStackTests.ts +52 -1
  206. package/.storybook/config.js +0 -7
  207. package/.storybook/head.html +0 -3
  208. package/.storybook/webpack.config.js +0 -15
  209. package/src/maps/BingLayer.ts +0 -146
  210. package/src/maps/MarkersLayerDesignerComponent.ts +0 -374
  211. package/src/quickfilter/TextLiteralComponent.ts +0 -165
  212. package/stories/UpdateableComponent.js +0 -29
  213. package/stories/consoles.js +0 -202
  214. package/stories/dashboards.js +0 -217
  215. package/stories/datagridDesign.js +0 -114
  216. package/stories/datagrids.js +0 -69
  217. package/stories/dates.js +0 -80
  218. package/stories/exprcomponent.js +0 -43
  219. package/stories/index.js +0 -18
  220. package/stories/leaflet.js +0 -59
  221. package/stories/maps.js +0 -24
  222. package/stories/pivotChart.js +0 -235
@@ -0,0 +1,93 @@
1
+ #!/bin/bash
2
+
3
+
4
+ # https://stackoverflow.com/questions/63299999/how-can-i-create-sdf-icons-used-in-mapbox-from-png#63314688
5
+
6
+ set -o pipefail
7
+ set -exu
8
+
9
+ cd "$(dirname "$0")"
10
+
11
+ MKTEMP="$(which gmktemp || which mktemp)"
12
+ TMPDIR1="$("$MKTEMP" -d)"
13
+ TMPDIR2="$("$MKTEMP" -d)"
14
+
15
+ trap "rm -rf '$TMPDIR1' '$TMPDIR2' ; pkill -P '$$'" EXIT
16
+
17
+ declare -a IMGFILES
18
+
19
+ IMGFILES=(
20
+ asterisk.png
21
+ ban.png
22
+ beer.png
23
+ bell.png
24
+ bolt.png
25
+ building.png
26
+ bullseye.png
27
+ bus.png
28
+ caret-up.png
29
+ certificate.png
30
+ check-circle.png
31
+ check.png
32
+ chevron-circle-down.png
33
+ chevron-circle-up.png
34
+ cloud-rain.png
35
+ cloud.png
36
+ comment.png
37
+ crosshairs.png
38
+ dot-circle-o.png
39
+ exclamation-circle.png
40
+ exclamation-triangle.png
41
+ female.png
42
+ file.png
43
+ flag.png
44
+ flask.png
45
+ h-square.png
46
+ home.png
47
+ info-circle.png
48
+ male.png
49
+ medkit.png
50
+ mobile.png
51
+ plus-circle.png
52
+ plus-square.png
53
+ plus.png
54
+ square.png
55
+ star.png
56
+ thumbs-down.png
57
+ thumbs-up.png
58
+ ticket.png
59
+ times-circle.png
60
+ times.png
61
+ tint.png
62
+ tree.png
63
+ university.png
64
+ usd.png
65
+ user.png
66
+ users.png
67
+ wheelchair.png
68
+ )
69
+
70
+ SVGINDIR="$(realpath -e "../../../../../apps/mwater-server/lib/routes/maps/marker-symbols/font-awesome")"
71
+ PNGOUTDIR="$(realpath "./font-awesome")"
72
+
73
+ rm -rvf "$PNGOUTDIR"
74
+ mkdir -pv "$PNGOUTDIR"
75
+
76
+ which image-sdf || npm install -g image-sdf
77
+
78
+ for IMGFILE in "${IMGFILES[@]}"
79
+ do
80
+ BASENAME="$(basename "$IMGFILE" .png)"
81
+ nice magick "$SVGINDIR/$BASENAME.svg" -channel RGB -negate -size 2000x2000 "$TMPDIR1/$BASENAME.png"
82
+ image-sdf "$TMPDIR1/$BASENAME.png" --spread 32 --downscale 2 --color black --output "$TMPDIR2/$BASENAME.png" &
83
+ done
84
+ wait
85
+
86
+ for IMGFILE in "$TMPDIR2"/*.png
87
+ do
88
+ magick "$IMGFILE" -resize 20x20 "$PNGOUTDIR/$(basename "$IMGFILE")" &
89
+ done
90
+ wait
91
+
92
+ cd "$PNGOUTDIR"
93
+ identify ./*.png
@@ -43,8 +43,9 @@ export function useVectorMap(options: {
43
43
  scrollZoom?: boolean
44
44
  dragPan?: boolean
45
45
  touchZoomRotate?: boolean
46
+ padding?: number
46
47
  }) {
47
- const { divRef, bounds, scrollZoom, dragPan, touchZoomRotate } = options
48
+ const { divRef, bounds, scrollZoom, dragPan, touchZoomRotate, padding } = options
48
49
 
49
50
  // Maplibre map
50
51
  const [map, setMap] = useState<maplibregl.Map>()
@@ -89,9 +90,8 @@ export function useVectorMap(options: {
89
90
  }
90
91
 
91
92
  try {
92
- const m = new maplibregl.Map({
93
+ const mapConstructorOptions: maplibregl.MapOptions = {
93
94
  container: divRef,
94
- bounds: bounds,
95
95
  scrollZoom: scrollZoom === false ? false : true,
96
96
  dragPan: dragPan === false ? false : true,
97
97
  touchZoomRotate: touchZoomRotate === false ? false : true,
@@ -102,13 +102,21 @@ export function useVectorMap(options: {
102
102
  layers: [],
103
103
  sources: {}
104
104
  },
105
- // Prevent scrolling outside of world bounds
106
105
  maxBounds: [
107
106
  [-179.9, -85], // Southwest coordinates
108
107
  [179.9, 85] // Northeast coordinates
109
108
  ],
110
109
  preserveDrawingBuffer: printingModeEnabled
111
- })
110
+ }
111
+
112
+ if (bounds) {
113
+ mapConstructorOptions.bounds = bounds
114
+ if (padding !== undefined) {
115
+ mapConstructorOptions.fitBoundsOptions = { padding }
116
+ }
117
+ }
118
+
119
+ const m = new maplibregl.Map(mapConstructorOptions)
112
120
 
113
121
  setHasWebGLContext(true)
114
122
 
@@ -240,9 +248,8 @@ export function useBaseStyle(baseLayer: BaseLayer) {
240
248
  } else if (baseLayer == "bing_road") {
241
249
  loadStyle(`https://api.maptiler.com/maps/streets-v2/style.json?key=${mapTilerApiKey}`)
242
250
  } else if (baseLayer == "bing_aerial") {
243
- // Switched to Bing for superior aerial imagery
244
- loadBingBasemap("AerialWithLabels", 1).then(setBaseStyle)
245
- // loadStyle(`https://api.maptiler.com/maps/hybrid/style.json?key=${mapTilerApiKey}`)
251
+ // Stadia Maps
252
+ loadStyle("https://tiles.stadiamaps.com/styles/alidade_satellite.json?api_key=835a418e-91f9-4eb8-9856-0883c3656c9d")
246
253
  } else if (baseLayer == "blank") {
247
254
  setBaseStyle({
248
255
  version: 8,
@@ -310,7 +317,10 @@ export function AttributionControl(props: {
310
317
  if (props.baseLayer == "bing_aerial") {
311
318
  return (
312
319
  <div className="newmap-attribution-control">
313
- Copyright © 2022 Microsoft and its suppliers.
320
+ &copy; <a href="https://stadiamaps.com/" target="_blank">Stadia Maps</a>
321
+ &copy; <a href="https://openmaptiles.org/" target="_blank">OpenMapTiles</a>
322
+ &copy; <a href="https://www.openstreetmap.org/copyright" target="_blank">OpenStreetMap</a>
323
+ &copy; CNES, Distribution Airbus DS, &copy; Airbus DS, &copy; PlanetObserver (Contains Copernicus Data)
314
324
  {props.extraText ? " " + props.extraText : null}
315
325
  </div>
316
326
  )
@@ -338,10 +348,7 @@ export function VectorMapLogo(props: {
338
348
  }
339
349
 
340
350
  if (props.baseLayer == "bing_aerial") {
341
- return <img
342
- src="https://dev.virtualearth.net/Branding/logo_powered_by.png"
343
- style={{ position: "absolute", bottom: 38, left: 11, height: 22, zIndex: 1000, pointerEvents: "none" }}
344
- />
351
+ return null
345
352
  }
346
353
 
347
354
  return <img
@@ -350,36 +357,6 @@ export function VectorMapLogo(props: {
350
357
  />
351
358
  }
352
359
 
353
- async function loadBingBasemap(basemapType: "AerialWithLabels", opacity: number): Promise<maplibregl.StyleSpecification> {
354
- // Load metadata
355
- const bingApiKey = "Ao26dWY2IC8PjorsJKFaoR85EPXCnCohrJdisCWXIULAXFo0JAXquGauppTMQbyU"
356
-
357
- const metadata = await fetch(`https://dev.virtualearth.net/REST/v1/Imagery/Metadata/${basemapType}?key=${bingApiKey}`).then((response) => response.json())
358
- const resource = metadata.resourceSets[0].resources[0]
359
-
360
- return {
361
- sources: {
362
- "bing_raster": {
363
- type: "raster",
364
- tiles: resource.imageUrlSubdomains.map((subdomain: string) =>
365
- resource.imageUrl.replace("{subdomain}", subdomain).replace("{culture}", "").replace("http:", "https:")),
366
- tileSize: resource.imageHeight,
367
- }
368
- },
369
- layers: [
370
- {
371
- id: "bing_raster",
372
- type: "raster",
373
- source: "bing_raster",
374
- paint: {
375
- "raster-opacity": opacity
376
- }
377
- }
378
- ],
379
- version: 8
380
- }
381
- }
382
-
383
360
  /** Persists map bounds to local storage */
384
361
  export function usePersistedMapBounds(map: Map | undefined, localStorageKey: string) {
385
362
  const [bounds, setBounds] = useState<{ n: number, e: number, s: number, w: number } | null>(null)
@@ -421,4 +398,3 @@ export function usePersistedMapBounds(map: Map | undefined, localStorageKey: str
421
398
  }
422
399
  }, [map])
423
400
  }
424
-
@@ -6,7 +6,7 @@ import * as uiComponents from "../UIComponents"
6
6
  import { ExprUtils, Schema } from "@mwater/expressions"
7
7
  import ModalPopupComponent from "@mwater/react-library/lib/ModalPopupComponent"
8
8
 
9
- interface IndicatorsListComponentProps {
9
+ export interface IndicatorsListComponentProps {
10
10
  /** Url to hit api */
11
11
  apiUrl: string
12
12
  /** Optional client */
@@ -31,7 +31,7 @@ interface IndicatorsListComponentState {
31
31
  export class IndicatorsListComponent extends React.Component<IndicatorsListComponentProps, IndicatorsListComponentState> {
32
32
  addIndicatorConfirmPopup: AddIndicatorConfirmPopupComponent | null
33
33
 
34
- constructor(props: any) {
34
+ constructor(props: IndicatorsListComponentProps) {
35
35
  super(props)
36
36
  this.state = {
37
37
  indicators: null,
@@ -42,7 +42,7 @@ export class IndicatorsListComponent extends React.Component<IndicatorsListCompo
42
42
  componentDidMount() {
43
43
  // Get names and basic of forms
44
44
  const query: any = {}
45
- query.fields = JSON.stringify({ "design.name": 1, "design.desc": 1, "design.recommended": 1, deprecated: 1 })
45
+ query.fields = JSON.stringify({ "design.name": 1, "design.desc": 1, "design.recommended": 1, deprecated: 1, "design.category": 1, "design.subcategory": 1, "design.code": 1, "design.organization": 1 })
46
46
  query.client = this.props.client
47
47
 
48
48
  // Get list of all indicator names
@@ -62,11 +62,7 @@ export class IndicatorsListComponent extends React.Component<IndicatorsListCompo
62
62
  )
63
63
 
64
64
  return this.setState({
65
- indicators: _.map(indicators, (indicator) => ({
66
- id: indicator._id,
67
- name: ExprUtils.localizeString(indicator.design.name, T.locale),
68
- desc: ExprUtils.localizeString(indicator.design.desc, T.locale)
69
- }))
65
+ indicators: indicators
70
66
  })
71
67
  }).fail((xhr: any) => {
72
68
  return this.setState({ error: xhr.responseText })
@@ -102,25 +98,110 @@ export class IndicatorsListComponent extends React.Component<IndicatorsListCompo
102
98
  this.addIndicatorConfirmPopup!.show(tableId)
103
99
  }
104
100
 
105
- render() {
106
- let indicators
107
- if (this.state.error) {
108
- return <div className="alert alert-danger">{this.state.error}</div>
101
+ handleOpenIndicator = (id: string) => {
102
+ this.handleSelect("indicator_values:" + id)
103
+ }
104
+
105
+ // Render a simplified version of IndicatorListComponent from mwater-portal
106
+ renderIndicatorList(indicators: any[], searchText: string) {
107
+ // Filter indicators based on search text
108
+ let filteredIndicators = indicators
109
+ if (searchText) {
110
+ const searchTerms = searchText.toLowerCase().split(" ")
111
+ filteredIndicators = indicators.filter(indicator => {
112
+ const searchFields = [
113
+ ExprUtils.localizeString(indicator.design.name, T.locale),
114
+ indicator.design.code,
115
+ indicator.design.organization,
116
+ indicator.design.category,
117
+ indicator.design.subcategory
118
+ ]
119
+
120
+ return searchTerms.every(term =>
121
+ searchFields.some(field =>
122
+ field && field.toLowerCase().includes(term)
123
+ )
124
+ )
125
+ })
109
126
  }
110
127
 
111
- // Filter indicators
112
- if (this.state.search) {
113
- const escapeRegExp = (s: any) => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")
128
+ // Split into recommended and all indicators
129
+ const recommendedIndicators = filteredIndicators.filter(indicator => indicator.design.recommended)
130
+ const otherIndicators = filteredIndicators.filter(indicator => !indicator.design.recommended)
114
131
 
115
- const searchStringRegExp = new RegExp(escapeRegExp(this.state.search), "i")
132
+ // Group by category
133
+ const groupByCategory = (indicators: any[]) => {
134
+ const categories: Record<string, any[]> = {}
116
135
 
117
- indicators = _.filter(this.state.indicators || [], (indicator) => indicator.name.match(searchStringRegExp))
118
- } else {
119
- ;({ indicators } = this.state)
136
+ indicators.forEach(indicator => {
137
+ const category = indicator.design.category || T`Uncategorized`
138
+ if (!categories[category]) {
139
+ categories[category] = []
140
+ }
141
+ categories[category].push(indicator)
142
+ })
143
+
144
+ return categories
120
145
  }
121
146
 
122
- // Remove if already included
123
- indicators = _.filter(indicators || [], (f) => !(this.props.extraTables || []).includes(`indicator_values:${f.id}`))
147
+ const recommendedByCategory = groupByCategory(recommendedIndicators)
148
+ const otherByCategory = groupByCategory(otherIndicators)
149
+
150
+ return (
151
+ <div>
152
+ {recommendedIndicators.length > 0 && (
153
+ <div>
154
+ <h4>{T`Recommended Indicators`}</h4>
155
+ {_.map(recommendedByCategory, (categoryIndicators, category) => (
156
+ <div key={category}>
157
+ <h5>{category}</h5>
158
+ <table className="table table-hover">
159
+ <tbody>
160
+ {_.map(categoryIndicators, indicator => (
161
+ <tr key={indicator._id} onClick={() => this.handleOpenIndicator(indicator._id)}>
162
+ <td>
163
+ <IndicatorListItemComponent
164
+ indicator={indicator}
165
+ onClick={() => this.handleOpenIndicator(indicator._id)}
166
+ />
167
+ </td>
168
+ </tr>
169
+ ))}
170
+ </tbody>
171
+ </table>
172
+ </div>
173
+ ))}
174
+ </div>
175
+ )}
176
+
177
+ <h4>{T`All Indicators`}</h4>
178
+ {_.map(otherByCategory, (categoryIndicators, category) => (
179
+ <div key={category}>
180
+ <h5>{category}</h5>
181
+ <table className="table table-hover">
182
+ <tbody>
183
+ {_.map(categoryIndicators, indicator => (
184
+ <tr key={indicator._id} onClick={() => this.handleOpenIndicator(indicator._id)}>
185
+ <td>
186
+ <IndicatorListItemComponent
187
+ indicator={indicator}
188
+ onClick={() => this.handleOpenIndicator(indicator._id)}
189
+ />
190
+ </td>
191
+ </tr>
192
+ ))}
193
+ </tbody>
194
+ </table>
195
+ </div>
196
+ ))}
197
+ </div>
198
+ )
199
+ }
200
+
201
+ render() {
202
+ if (this.state.error) {
203
+ return <div className="alert alert-danger">{this.state.error}</div>
204
+ }
124
205
 
125
206
  let tables = _.filter(
126
207
  this.props.schema.getTables(),
@@ -142,14 +223,12 @@ export class IndicatorsListComponent extends React.Component<IndicatorsListCompo
142
223
  <label>{T`Included Indicators:`}</label>
143
224
  {tables.length > 0 ? (
144
225
  <uiComponents.OptionListComponent
145
- items={_.map(tables, (table) => {
146
- return {
147
- name: ExprUtils.localizeString(table.name, T.locale),
148
- desc: ExprUtils.localizeString(table.desc, T.locale),
149
- onClick: this.handleSelect.bind(null, table.id),
150
- onRemove: this.handleTableRemove.bind(null, table)
151
- }
152
- })}
226
+ items={_.map(tables, (table) => ({
227
+ name: ExprUtils.localizeString(table.name, T.locale),
228
+ desc: ExprUtils.localizeString(table.desc, T.locale),
229
+ onClick: () => this.handleSelect(table.id),
230
+ onRemove: () => this.handleTableRemove(table)
231
+ }))}
153
232
  />
154
233
  ) : (
155
234
  <div>{T`None`}</div>
@@ -161,7 +240,7 @@ export class IndicatorsListComponent extends React.Component<IndicatorsListCompo
161
240
  {!this.state.indicators || this.state.indicators.length === 0 ? (
162
241
  <div className="alert alert-info">
163
242
  <i className="fa fa-spinner fa-spin" />
164
- &nbsp;{T`Loading...`}
243
+ {"\u00A0" + T`Loading...`}
165
244
  </div>
166
245
  ) : (
167
246
  <>
@@ -176,13 +255,7 @@ export class IndicatorsListComponent extends React.Component<IndicatorsListCompo
176
255
  onChange={(ev: any) => this.setState({ search: ev.target.value })}
177
256
  />
178
257
 
179
- <uiComponents.OptionListComponent
180
- items={_.map(indicators, (indicator) => ({
181
- name: indicator.name,
182
- desc: indicator.desc,
183
- onClick: this.handleSelect.bind(null, "indicator_values:" + indicator.id)
184
- }))}
185
- />
258
+ {this.renderIndicatorList(this.state.indicators, this.state.search)}
186
259
  </>
187
260
  )}
188
261
  </div>
@@ -190,6 +263,7 @@ export class IndicatorsListComponent extends React.Component<IndicatorsListCompo
190
263
  }
191
264
  }
192
265
 
266
+
193
267
  interface AddIndicatorConfirmPopupComponentProps {
194
268
  schema: Schema
195
269
  /** Called with table selected */
@@ -281,3 +355,57 @@ are certain that you want to use the raw indicator table`}
281
355
  )
282
356
  }
283
357
  }
358
+
359
+ // Single indicator item in a list
360
+ // Local port of the class found in mwater-portal
361
+ class IndicatorListItemComponent extends React.Component<{
362
+ indicator: any
363
+ onClick: any
364
+ }> {
365
+
366
+ renderIcon() {
367
+ switch (this.props.indicator.type) {
368
+ case "expression":
369
+ // R 'i', className: "text-muted fa fa-fw fa-calculator"
370
+ return null
371
+ case "response":
372
+ // R 'i', className: "text-muted fa fa-fw fa-list-alt"
373
+ return null
374
+ case "set":
375
+ return (
376
+ <span className="fa-stack" style={{ paddingRight: 6 }}>
377
+ <i className="fa fa-th-list fa-stack-1x text-muted" />
378
+ <i className="fa fa-circle fa-stack-1x" style={{ color: "white", paddingLeft: 15 }} />
379
+ <i className="fa fa-check-circle fa-stack-1x text-primary" style={{ paddingLeft: 15 }} />
380
+ </span>
381
+ )
382
+ }
383
+ return null
384
+ }
385
+
386
+ render() {
387
+ return (
388
+ <div onClick={this.props.onClick} style={{ cursor: "pointer" }}>
389
+ <div key="name" style={{ fontWeight: "bold" }}>
390
+ {this.renderIcon()}
391
+ {ExprUtils.localizeString(this.props.indicator.design.name, T.locale)}
392
+ {this.props.indicator.design.code ? ` (${this.props.indicator.design.code})` : undefined}
393
+ {this.props.indicator.design.organization ? (
394
+ <span className="text-muted"> {T`by ${this.props.indicator.design.organization}`}</span>
395
+ ) : undefined}
396
+ </div>
397
+
398
+ <div key="desc" className="text-muted">
399
+ {ExprUtils.localizeString(this.props.indicator.design.desc, T.locale)}
400
+ {this.props.indicator.design.category
401
+ ? [
402
+ ` (${this.props.indicator.design.category}`,
403
+ this.props.indicator.design.subcategory ? `/${this.props.indicator.design.subcategory}` : undefined,
404
+ `)`
405
+ ]
406
+ : undefined}
407
+ </div>
408
+ </div>
409
+ )
410
+ }
411
+ }
@@ -0,0 +1,111 @@
1
+ import _ from "lodash"
2
+ import { Schema, ExprUtils } from "@mwater/expressions"
3
+ import { useState, useEffect } from "react"
4
+ import React from "react"
5
+ import { OptionListComponent } from "../UIComponents"
6
+ import { TextInput } from "@mwater/react-library/lib/bootstrap"
7
+
8
+ interface MWaterCalculatedDataSourcesListComponentProps {
9
+ apiUrl: string
10
+ schema: Schema
11
+ client?: string
12
+ user?: string
13
+ onChange: (tableId: string | null) => void
14
+ extraTables: string[]
15
+ onExtraTableAdd: (tableId: string) => void
16
+ onExtraTableRemove: (tableId: string) => void
17
+ locale?: string
18
+ }
19
+
20
+ /**
21
+ * A list of calculated data sources that can be selected.
22
+ */
23
+ export const MWaterCalculatedDataSourcesListComponent = (props: MWaterCalculatedDataSourcesListComponentProps) => {
24
+ const [calculatedSources, setCalculatedSources] = useState<CalculatedSource[]>()
25
+ const [search, setSearch] = useState<string | null>("")
26
+ const [extraTableNeeded, setExtraTableNeeded] = useState<string>()
27
+
28
+ useEffect(() => {
29
+ fetch(`${props.apiUrl}calculated_data_sources?client=${props.client || ""}`)
30
+ .then((response) => response.json())
31
+ .then((body: CalculatedSource[]) => {
32
+ // Filter out calculated data sources that have never been calculated
33
+ const calculatedSources = body.filter((s) => s.last_calculated)
34
+
35
+ setCalculatedSources(
36
+ _.sortByAll(calculatedSources, [
37
+ (s) => (props.extraTables.some((t) => t == `calculated:${s._sid}`) ? 0 : 1),
38
+ (s) => ExprUtils.localizeString(s.name, props.locale)
39
+ ])
40
+ )
41
+ })
42
+ }, [])
43
+
44
+ useEffect(() => {
45
+ if (extraTableNeeded && props.schema.getTable(extraTableNeeded)) {
46
+ props.onChange(extraTableNeeded)
47
+ }
48
+ })
49
+
50
+ const selectTable = (source: CalculatedSource) => {
51
+ const qualifiedTableId = `calculated:${source._sid}`
52
+
53
+ if (props.schema.getTable(qualifiedTableId)) {
54
+ props.onChange(qualifiedTableId)
55
+ return
56
+ }
57
+
58
+ setExtraTableNeeded(qualifiedTableId)
59
+ props.onExtraTableAdd(qualifiedTableId)
60
+ }
61
+
62
+ const handleRemove = (source: CalculatedSource) => {
63
+ const match = props.extraTables.find((t) => t == `calculated:${source._sid}`)
64
+ if (match) {
65
+ if (confirm(T`Remove this table? Some widgets may not work correctly.`)) {
66
+ props.onChange(null)
67
+ props.onExtraTableRemove(match)
68
+ }
69
+ }
70
+ }
71
+
72
+ if (!calculatedSources || extraTableNeeded) {
73
+ return (
74
+ <div>
75
+ <i className="fa fa-spin fa-spinner" /> {T`Loading...`}
76
+ </div>
77
+ )
78
+ }
79
+
80
+ const renderCalculatedSources = () => {
81
+ const items = calculatedSources
82
+ .map((s) => {
83
+ const alreadyIncluded = props.extraTables.some((t) => t == `calculated:${s._sid}`)
84
+ return {
85
+ name: ExprUtils.localizeString(s.name, props.locale) || "",
86
+ desc: s.description ? ExprUtils.localizeString(s.description, props.locale) || "" : undefined,
87
+ onClick: () => selectTable(s),
88
+ onRemove: alreadyIncluded ? handleRemove.bind(null, s) : undefined
89
+ }
90
+ })
91
+ .filter((item) => !search || item.name.toLowerCase().includes(search.toLowerCase()))
92
+
93
+ return <OptionListComponent items={items} />
94
+ }
95
+
96
+ return (
97
+ <div>
98
+ <TextInput value={search} onChange={setSearch} placeholder={T`Search...`} searchIcon />
99
+ <br/>
100
+ {renderCalculatedSources()}
101
+ </div>
102
+ )
103
+ }
104
+
105
+ interface CalculatedSource {
106
+ _id: string
107
+ _sid: number
108
+ name: { _base: string; [code: string]: string }
109
+ description?: { _base: string; [code: string]: string }
110
+ last_calculated?: string
111
+ }