@mwater/visualization 5.4.5 → 5.5.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 (132) hide show
  1. package/.storybook/head.html +0 -1
  2. package/lib/MWaterContextComponent.js +1 -1
  3. package/lib/MWaterLoaderComponent.d.ts +2 -2
  4. package/lib/dashboards/DashboardComponent.js +2 -1
  5. package/lib/dashboards/LayoutOptionsComponent.js +18 -11
  6. package/lib/dashboards/ServerDashboardDataSource.d.ts +10 -1
  7. package/lib/dashboards/ServerDashboardDataSource.js +29 -0
  8. package/lib/dashboards/layoutOptions.d.ts +5 -1
  9. package/lib/datagrids/DatagridComponent.js +1 -1
  10. package/lib/datagrids/ExprCellComponent.d.ts +1 -0
  11. package/lib/datagrids/ExprCellComponent.js +22 -20
  12. package/lib/maps/BufferLayer.d.ts +18 -0
  13. package/lib/maps/BufferLayer.js +24 -14
  14. package/lib/maps/ChoroplethLayer.d.ts +18 -0
  15. package/lib/maps/ChoroplethLayer.js +34 -25
  16. package/lib/maps/ChoroplethLayerDesign.d.ts +3 -2
  17. package/lib/maps/ChoroplethLayerDesigner.d.ts +11 -1
  18. package/lib/maps/DirectMapDataSource.js +17 -0
  19. package/lib/maps/EditHoverOver.d.ts +1 -1
  20. package/lib/maps/EditHoverOver.js +62 -33
  21. package/lib/maps/HoverContent.d.ts +10 -5
  22. package/lib/maps/HoverContent.js +6 -35
  23. package/lib/maps/Layer.d.ts +37 -0
  24. package/lib/maps/Layer.js +30 -4
  25. package/lib/maps/MWaterServerLayer.d.ts +2 -2
  26. package/lib/maps/MWaterServerLayer.js +6 -6
  27. package/lib/maps/MapLayerDataSource.d.ts +9 -0
  28. package/lib/maps/MapUtils.d.ts +19 -1
  29. package/lib/maps/MapUtils.js +71 -1
  30. package/lib/maps/MarkersLayer.d.ts +18 -0
  31. package/lib/maps/MarkersLayer.js +24 -24
  32. package/lib/maps/MarkersLayerDesignerComponent.d.ts +14 -1
  33. package/lib/maps/RasterMapViewComponent.js +1 -1
  34. package/lib/maps/ServerMapDataSource.d.ts +9 -0
  35. package/lib/maps/ServerMapDataSource.js +29 -0
  36. package/lib/maps/VectorMapViewComponent.js +6 -6
  37. package/lib/maps/maps.d.ts +4 -2
  38. package/lib/mwater_table_selection/FormsListComponent.d.ts +33 -0
  39. package/lib/mwater_table_selection/FormsListComponent.js +141 -0
  40. package/lib/mwater_table_selection/IndicatorsListComponent.d.ts +47 -0
  41. package/lib/mwater_table_selection/IndicatorsListComponent.js +182 -0
  42. package/lib/mwater_table_selection/IssuesListComponent.d.ts +29 -0
  43. package/lib/mwater_table_selection/IssuesListComponent.js +123 -0
  44. package/lib/mwater_table_selection/MWaterAccountingSystemListComponent.d.ts +20 -0
  45. package/lib/mwater_table_selection/MWaterAccountingSystemListComponent.js +157 -0
  46. package/lib/mwater_table_selection/MWaterAssetSystemsListComponent.d.ts +17 -0
  47. package/lib/mwater_table_selection/MWaterAssetSystemsListComponent.js +79 -0
  48. package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.d.ts +37 -0
  49. package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.js +275 -0
  50. package/lib/mwater_table_selection/MWaterCustomTablesetListComponent.d.ts +17 -0
  51. package/lib/mwater_table_selection/MWaterCustomTablesetListComponent.js +94 -0
  52. package/lib/mwater_table_selection/MWaterMetricsTableListComponent.d.ts +17 -0
  53. package/lib/mwater_table_selection/MWaterMetricsTableListComponent.js +80 -0
  54. package/lib/mwater_table_selection/MWaterTableSelectComponent.d.ts +32 -0
  55. package/lib/mwater_table_selection/MWaterTableSelectComponent.js +158 -0
  56. package/lib/widgets/charts/Chart.d.ts +11 -0
  57. package/lib/widgets/charts/Chart.js +15 -0
  58. package/lib/widgets/charts/ChartWidgetComponent.d.ts +1 -0
  59. package/lib/widgets/charts/ChartWidgetComponent.js +27 -1
  60. package/lib/widgets/charts/layered/LayeredChartDesign.d.ts +1 -1
  61. package/lib/widgets/charts/layered/LayeredChartDesignerComponent.d.ts +1 -1
  62. package/lib/widgets/charts/layered/LayeredChartDesignerComponent.js +5 -12
  63. package/lib/widgets/charts/layered/LayeredChartLayerDesignerComponent.d.ts +43 -57
  64. package/lib/widgets/charts/layered/LayeredChartLayerDesignerComponent.js +113 -110
  65. package/lib/widgets/charts/layered/LayeredChartUtils.d.ts +2 -1
  66. package/lib/widgets/charts/layered/LayeredChartUtils.js +0 -2
  67. package/lib/widgets/charts/pivot/PivotChart.d.ts +2 -0
  68. package/lib/widgets/charts/pivot/PivotChart.js +156 -0
  69. package/lib/widgets/charts/pivot/PivotChartDesignerComponent.d.ts +5 -20
  70. package/lib/widgets/charts/pivot/PivotChartDesignerComponent.js +31 -61
  71. package/lib/widgets/charts/pivot/PivotChartLayoutBuilder.d.ts +4 -0
  72. package/lib/widgets/charts/pivot/PivotChartLayoutBuilder.js +4 -2
  73. package/lib/widgets/charts/pivot/PivotChartLayoutComponent.d.ts +5 -44
  74. package/lib/widgets/charts/pivot/PivotChartLayoutComponent.js +38 -63
  75. package/lib/widgets/charts/pivot/SegmentDesignerComponent.d.ts +7 -68
  76. package/lib/widgets/charts/pivot/SegmentDesignerComponent.js +58 -106
  77. package/lib/widgets/charts/table/TableChart.d.ts +2 -0
  78. package/lib/widgets/charts/table/TableChart.js +172 -1
  79. package/lib/widgets/charts/table/TableChartDesignerComponent.d.ts +7 -17
  80. package/lib/widgets/charts/table/TableChartDesignerComponent.js +79 -95
  81. package/lib/widgets/charts/table/TableChartViewComponent.d.ts +1 -7
  82. package/lib/widgets/charts/table/TableChartViewComponent.js +19 -27
  83. package/package.json +3 -8
  84. package/src/MWaterContextComponent.tsx +1 -1
  85. package/src/MWaterLoaderComponent.ts +1 -1
  86. package/src/dashboards/DashboardComponent.tsx +2 -1
  87. package/src/dashboards/LayoutOptionsComponent.tsx +22 -10
  88. package/src/dashboards/ServerDashboardDataSource.ts +36 -1
  89. package/src/dashboards/layoutOptions.tsx +5 -1
  90. package/src/datagrids/DatagridComponent.tsx +1 -1
  91. package/src/datagrids/ExprCellComponent.tsx +23 -20
  92. package/src/maps/BufferLayer.ts +35 -20
  93. package/src/maps/ChoroplethLayer.ts +51 -33
  94. package/src/maps/ChoroplethLayerDesign.ts +3 -2
  95. package/src/maps/ChoroplethLayerDesigner.tsx +2 -2
  96. package/src/maps/DirectMapDataSource.ts +21 -1
  97. package/src/maps/EditHoverOver.tsx +91 -51
  98. package/src/maps/HoverContent.tsx +16 -47
  99. package/src/maps/Layer.ts +42 -4
  100. package/src/maps/MWaterServerLayer.ts +6 -6
  101. package/src/maps/MapLayerDataSource.ts +8 -0
  102. package/src/maps/MapUtils.ts +70 -3
  103. package/src/maps/MarkersLayer.ts +34 -24
  104. package/src/maps/RasterMapViewComponent.ts +1 -1
  105. package/src/maps/ServerMapDataSource.ts +35 -0
  106. package/src/maps/VectorMapViewComponent.tsx +6 -6
  107. package/src/maps/maps.ts +4 -2
  108. package/src/mwater_table_selection/FormsListComponent.tsx +188 -0
  109. package/src/mwater_table_selection/IndicatorsListComponent.tsx +283 -0
  110. package/src/mwater_table_selection/IssuesListComponent.tsx +167 -0
  111. package/src/mwater_table_selection/MWaterAccountingSystemListComponent.tsx +225 -0
  112. package/src/{MWaterAssetSystemsListComponent.tsx → mwater_table_selection/MWaterAssetSystemsListComponent.tsx} +2 -2
  113. package/src/mwater_table_selection/MWaterCompleteTableSelectComponent.tsx +377 -0
  114. package/src/{MWaterCustomTablesetListComponent.tsx → mwater_table_selection/MWaterCustomTablesetListComponent.tsx} +1 -1
  115. package/src/{MWaterMetricsTableListComponent.tsx → mwater_table_selection/MWaterMetricsTableListComponent.tsx} +1 -1
  116. package/src/{MWaterTableSelectComponent.tsx → mwater_table_selection/MWaterTableSelectComponent.tsx} +83 -86
  117. package/src/widgets/charts/Chart.ts +17 -0
  118. package/src/widgets/charts/ChartWidgetComponent.tsx +36 -1
  119. package/src/widgets/charts/layered/LayeredChartDesign.ts +1 -1
  120. package/src/widgets/charts/layered/LayeredChartDesignerComponent.tsx +23 -24
  121. package/src/widgets/charts/layered/LayeredChartLayerDesignerComponent.tsx +260 -211
  122. package/src/widgets/charts/layered/LayeredChartUtils.ts +7 -7
  123. package/src/widgets/charts/pivot/PivotChart.ts +191 -0
  124. package/src/widgets/charts/pivot/PivotChartDesignerComponent.tsx +124 -129
  125. package/src/widgets/charts/pivot/PivotChartLayoutBuilder.ts +4 -2
  126. package/src/widgets/charts/pivot/PivotChartLayoutComponent.tsx +120 -149
  127. package/src/widgets/charts/pivot/SegmentDesignerComponent.tsx +178 -198
  128. package/src/widgets/charts/table/TableChart.ts +177 -1
  129. package/src/widgets/charts/table/TableChartDesignerComponent.tsx +422 -0
  130. package/src/widgets/charts/table/{TableChartViewComponent.ts → TableChartViewComponent.tsx} +65 -60
  131. package/src/MWaterCompleteTableSelectComponent.tsx +0 -975
  132. package/src/widgets/charts/table/TableChartDesignerComponent.ts +0 -441
package/src/maps/Layer.ts CHANGED
@@ -169,6 +169,25 @@ export default class Layer<LayerDesign> {
169
169
  return null
170
170
  }
171
171
 
172
+ /**
173
+ * Called when the interactivity grid is hovered over.
174
+ * arguments:
175
+ * ev: { data: interactivty data e.g. `{ id: 123 }` }
176
+ * options:
177
+ * design: design of layer
178
+ * schema: schema to use
179
+ * dataSource: data source to use
180
+ * layerDataSource: layer data source
181
+ * scopeData: current scope data if layer is scoping
182
+ * filters: compiled filters to apply to the popup
183
+ *
184
+ * Returns:
185
+ * null
186
+ * or
187
+ * {
188
+ * hoverOver: React element to put into a hover over
189
+ * }
190
+ */
172
191
  onGridHoverOver(ev: { data: any; event: any }, options: OnGridHoverOptions<LayerDesign>): OnGridHoverResults {
173
192
  return null
174
193
  }
@@ -316,10 +335,10 @@ export default class Layer<LayerDesign> {
316
335
  const [w, s, e, n] = bbox(results[0].bounds)
317
336
  // Pad bounds to prevent too small box (100m)
318
337
  bounds = {
319
- w: w - 0.001,
320
- s: s - 0.001,
321
- e: e + 0.001,
322
- n: n + 0.001
338
+ w: Math.max(w - 0.001, -180),
339
+ s: Math.max(s - 0.001, -90),
340
+ e: Math.min(e + 0.001, 180),
341
+ n: Math.min(n + 0.001, 90)
323
342
  }
324
343
  }
325
344
 
@@ -332,6 +351,25 @@ export default class Layer<LayerDesign> {
332
351
  getTranslatableStrings(design: LayerDesign, schema: Schema): string[] {
333
352
  return []
334
353
  }
354
+
355
+ /** Gets hover over data for hover over items. This should be implemented by layers that have hover over items.
356
+ * It will be called on the server side if using a server map data source, or on the client side if using a direct
357
+ * map data source.
358
+ */
359
+ getHoverOverData(options: {
360
+ /** Design of the layer */
361
+ design: LayerDesign,
362
+ /** Data of the current item being hovered over. e.g. { id: 123 } */
363
+ data: any,
364
+ /** Filters to apply to the hover over data, not including filtering down to the current item */
365
+ filters: JsonQLFilter[],
366
+ /** Schema to use */
367
+ schema: Schema,
368
+ /** Data source to use */
369
+ dataSource: DataSource,
370
+ }): Promise<{ [key: string]: any }> {
371
+ return Promise.resolve({})
372
+ }
335
373
  }
336
374
 
337
375
  export interface LegendOptions<LayerDesign> {
@@ -94,15 +94,15 @@ class LoadingLegend extends React.Component<LoadingLegendProps, LoadingLegendSta
94
94
  }
95
95
 
96
96
  componentDidMount() {
97
- return $.get(this.props.url).done((data: any) => {
98
- return this.setState({ html: data })
97
+ $.get(this.props.url).done((data: any) => {
98
+ this.setState({ html: data })
99
99
  })
100
100
  }
101
101
 
102
- componentWillReceiveProps(nextProps: any) {
103
- if (nextProps.url !== this.props.url) {
104
- return $.get(nextProps.url).done((data: any) => {
105
- return this.setState({ html: data })
102
+ componentDidUpdate(prevProps: any) {
103
+ if (prevProps.url !== this.props.url) {
104
+ $.get(this.props.url).done((data: any) => {
105
+ this.setState({ html: data })
106
106
  })
107
107
  }
108
108
  }
@@ -24,4 +24,12 @@ export interface MapLayerDataSource {
24
24
 
25
25
  /** Gets widget data source for a popup widget */
26
26
  getPopupWidgetDataSource(design: any, widgetId: string): WidgetDataSource
27
+
28
+ /** Gets hover over data for hover over items
29
+ * @param design The design of the layer
30
+ * @param data The data of the current item being hovered over. e.g. { id: 123 }
31
+ * @param filters The filters to apply to the layer does not include filters that narrow down to a specific item
32
+ * @returns A promise that resolves to the hover over data, indexed by the id of the hover over item
33
+ */
34
+ getHoverOverData(design: any, data: any, filters: JsonQLFilter[]): Promise<{ [key: string]: any }>
27
35
  }
@@ -1,12 +1,13 @@
1
1
  // General utilities for a map
2
2
 
3
- import { JsonQLExpr } from "@mwater/jsonql"
4
- import _ from "lodash"
5
- import { Expr, ExprCleaner, ExprCompiler, ExprUtils, FieldExpr, Schema } from "@mwater/expressions"
3
+ import { JsonQLExpr, JsonQLSelectQuery } from "@mwater/jsonql"
4
+ import _, { compact } from "lodash"
5
+ import { DataSource, Expr, ExprCleaner, ExprCompiler, ExprUtils, FieldExpr, injectTableAlias, Schema } from "@mwater/expressions"
6
6
  import { JsonQLFilter } from "../JsonQLFilter"
7
7
  import LayerFactory from "./LayerFactory"
8
8
  import { MapDesign } from "./MapDesign"
9
9
  import { produce } from "immer"
10
+ import { HoverOverItem } from "./maps"
10
11
 
11
12
  export interface MapScope {
12
13
  name: string
@@ -170,3 +171,69 @@ export function getTranslatableStrings(design: MapDesign, schema: Schema): strin
170
171
  // Remove duplicates
171
172
  return _.uniq(strings)
172
173
  }
174
+
175
+ /**
176
+ * Convenience function to get hover over data for a map given an id and a list of hover over items
177
+ */
178
+ export async function getSimpleHoverOverData(options: {
179
+ /** Id of the item to get hover over data for. If null, will not filter by id */
180
+ id: any,
181
+ /** Table of the item to get hover over data for */
182
+ table: string,
183
+ /** Extra filters to apply to the hover over data, not including filtering down to the current item */
184
+ filters: JsonQLFilter[],
185
+ /** Schema to use */
186
+ schema: Schema,
187
+ /** Data source to use */
188
+ dataSource: DataSource,
189
+ /** Hover over items */
190
+ hoverOverItems: HoverOverItem[]
191
+ }) {
192
+ const { id, table, filters, schema, dataSource, hoverOverItems } = options
193
+
194
+ const exprCompiler = new ExprCompiler(schema)
195
+ const query: JsonQLSelectQuery = {
196
+ type: "query",
197
+ selects: [],
198
+ from: exprCompiler.compileTable(table, "main"),
199
+ limit: 1
200
+ }
201
+
202
+ for (const item of hoverOverItems) {
203
+ if (item.value) {
204
+ query.selects.push({
205
+ type: "select",
206
+ expr: exprCompiler.compileExpr({ expr: item.value, tableAlias: "main" }),
207
+ alias: item.id
208
+ })
209
+ }
210
+ }
211
+
212
+ if (filters) {
213
+ let whereClauses = filters.filter(f => f.table === table).map(f => injectTableAlias(f.jsonql, "main"))
214
+
215
+ // Add id filter
216
+ if (id != null) {
217
+ whereClauses.push({
218
+ type: "op",
219
+ op: "=",
220
+ exprs: [
221
+ exprCompiler.compileExpr({ expr: { type: "id", table }, tableAlias: "main" }),
222
+ { type: "literal", value: id }
223
+ ]
224
+ })
225
+ }
226
+
227
+ whereClauses = compact(whereClauses)
228
+
229
+ // Wrap if multiple
230
+ if (whereClauses.length > 1) {
231
+ query.where = { type: "op", op: "and", exprs: whereClauses }
232
+ } else {
233
+ query.where = whereClauses[0]
234
+ }
235
+ }
236
+
237
+ const rows = await dataSource.performQuery(query)
238
+ return rows?.[0] ?? {}
239
+ }
@@ -20,6 +20,7 @@ import Widget from "../widgets/Widget"
20
20
  import { WidgetDataSource } from "../widgets/WidgetDataSource"
21
21
  import BlocksLayoutManager from "../layouts/blocks/BlocksLayoutManager"
22
22
  import { getTranslatableStringsFromLayoutManager } from "../dashboards/DashboardUtils"
23
+ import { getSimpleHoverOverData } from "./MapUtils"
23
24
 
24
25
  export default class MarkersLayer extends Layer<MarkersLayerDesign> {
25
26
  /** Gets the type of layer definition */
@@ -471,33 +472,16 @@ ${design.polygonBorderColor ? "line-color: " + design.polygonBorderColor + ";" :
471
472
  ev: { data: any; event: any },
472
473
  hoverOptions: OnGridHoverOptions<MarkersLayerDesign>
473
474
  ): OnGridHoverResults {
474
- if (ev.data && ev.data.id) {
475
- const { table } = hoverOptions.design
476
- const results: OnGridHoverResults = {}
477
-
478
- // Popup
479
- if (hoverOptions.design.hoverOver) {
480
- const exprCompiler = new ExprCompiler(hoverOptions.schema)
481
-
482
- results.hoverOver = React.createElement(HoverContent, {
475
+ if (ev.data && ev.data.id && hoverOptions.design.hoverOver && hoverOptions.design.hoverOver.items.length > 0) {
476
+ const results: OnGridHoverResults = {
477
+ hoverOver: React.createElement(HoverContent, {
483
478
  key: ev.data.id,
484
479
  schema: hoverOptions.schema,
485
- dataSource: hoverOptions.dataSource,
486
- table,
480
+ mapLayerDataSource: hoverOptions.layerDataSource,
487
481
  items: hoverOptions.design.hoverOver.items,
488
- filters: [
489
- {
490
- table,
491
- jsonql: {
492
- type: "op",
493
- op: "=",
494
- exprs: [
495
- exprCompiler.compileExpr({ expr: { type: "id", table }, tableAlias: "{alias}" }),
496
- { type: "literal", value: ev.data.id }
497
- ]
498
- }
499
- }
500
- ],
482
+ design: hoverOptions.design,
483
+ data: ev.data,
484
+ filters: hoverOptions.filters,
501
485
  locale: hoverOptions.locale,
502
486
  translate: hoverOptions.translate
503
487
  })
@@ -818,6 +802,32 @@ ${design.polygonBorderColor ? "line-color: " + design.polygonBorderColor + ";" :
818
802
  // Remove duplicates
819
803
  return _.uniq(strings)
820
804
  }
805
+
806
+ /** Gets hover over data for hover over items. This should be implemented by layers that have hover over items.
807
+ * It will be called on the server side if using a server map data source, or on the client side if using a direct
808
+ * map data source.
809
+ */
810
+ getHoverOverData(options: {
811
+ /** Design of the layer */
812
+ design: MarkersLayerDesign,
813
+ /** Data of the current item being hovered over. e.g. { id: 123 } */
814
+ data: any,
815
+ /** Filters to apply to the hover over data, not including filtering down to the current item */
816
+ filters: JsonQLFilter[],
817
+ /** Schema to use */
818
+ schema: Schema,
819
+ /** Data source to use */
820
+ dataSource: DataSource,
821
+ }): Promise<{ [key: string]: any }> {
822
+ return getSimpleHoverOverData({
823
+ id: options.data.id,
824
+ table: options.design.table,
825
+ filters: options.filters,
826
+ schema: options.schema,
827
+ dataSource: options.dataSource,
828
+ hoverOverItems: options.design.hoverOver.items,
829
+ })
830
+ }
821
831
  }
822
832
 
823
833
  /**
@@ -178,7 +178,7 @@ export default class RasterMapViewComponent extends React.Component<
178
178
  filters: this.getCompiledFilters(),
179
179
  dataSource: this.props.dataSource,
180
180
  locale: this.context,
181
- translate: this.props.translate ?? (() => ""),
181
+ translate: this.props.translate ?? ((input: string) => input),
182
182
  onHide: () => this.setState({ legendHidden: true }),
183
183
  zoom: null
184
184
  })
@@ -261,6 +261,41 @@ class ServerLayerDataSource implements MapLayerDataSource {
261
261
 
262
262
  return url
263
263
  }
264
+
265
+ /** Gets hover over data for hover over items
266
+ * @param design The design of the layer
267
+ * @param data The data of the current item being hovered over. e.g. { id: 123 }
268
+ * @param filters The filters to apply to the layer does not include filters that narrow down to a specific item
269
+ * @returns A promise that resolves to the hover over data, indexed by the id of the hover over item
270
+ */
271
+ async getHoverOverData(design: any, data: any, filters: JsonQLFilter[]): Promise<{ [key: string]: any }> {
272
+ const query = {
273
+ client: this.options.client,
274
+ share: this.options.share,
275
+ filters: compressJson(filters || []),
276
+ data: compressJson(data),
277
+ rev: this.options.rev
278
+ }
279
+
280
+ const url =
281
+ `${this.options.apiUrl}maps/${this.options.mapId}/layers/${this.options.layerView.id}/hoverdata?` +
282
+ querystring.stringify(query)
283
+
284
+ const response = await fetch(url, {
285
+ method: "GET",
286
+ headers: {
287
+ Accept: "application/json"
288
+ }
289
+ })
290
+
291
+ if (!response.ok) {
292
+ const errorText = await response.text()
293
+ console.error(errorText)
294
+ throw new Error(`Error fetching hover data: ${response.statusText}`)
295
+ }
296
+
297
+ return await response.json()
298
+ }
264
299
  }
265
300
 
266
301
  interface ServerMapLayerPopupWidgetDataSourceOptions {
@@ -76,7 +76,7 @@ export function VectorMapViewComponent(props: VectorMapViewComponentProps) {
76
76
  // Locale to use
77
77
  const locale = props.locale || props.design.locale || "en"
78
78
 
79
- // Translate function to use TODO
79
+ // Translate function to use
80
80
  const translate = props.translate || ((input: string) => input)
81
81
 
82
82
  // Last feature that mouse entered
@@ -505,13 +505,13 @@ export function VectorMapViewComponent(props: VectorMapViewComponentProps) {
505
505
  }
506
506
 
507
507
  if (!props.design.autoBounds && props.design.bounds) {
508
- // If we set the new bounds, do not update map bounds
508
+ // If we set the new bounds, do not update map bounds unless they differ by more than 0.0001 degrees (roughly 10m)
509
509
  if (
510
510
  boundsRef.current == null ||
511
- props.design.bounds.n != boundsRef.current.n ||
512
- props.design.bounds.e != boundsRef.current.e ||
513
- props.design.bounds.s != boundsRef.current.s ||
514
- props.design.bounds.w != boundsRef.current.w
511
+ Math.abs(props.design.bounds.n - boundsRef.current.n) > 0.0001 ||
512
+ Math.abs(props.design.bounds.e - boundsRef.current.e) > 0.0001 ||
513
+ Math.abs(props.design.bounds.s - boundsRef.current.s) > 0.0001 ||
514
+ Math.abs(props.design.bounds.w - boundsRef.current.w) > 0.0001
515
515
  ) {
516
516
  map.fitBounds([props.design.bounds.w, props.design.bounds.s, props.design.bounds.e, props.design.bounds.n])
517
517
  boundsRef.current = props.design.bounds
package/src/maps/maps.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { JsonQLQuery } from "@mwater/jsonql"
2
2
  import { Expr } from "@mwater/expressions"
3
+ import { ReactNode } from "react"
3
4
 
4
5
  export interface LayerDefinition {
5
6
  layers: Array<{
@@ -20,15 +21,16 @@ export interface LayerDefinition {
20
21
  }
21
22
  }
22
23
 
24
+ /** Results from onGridClick that can be used to display a popup */
23
25
  export type OnGridClickResults = {
24
26
  scope?: any
25
27
  row?: { tableId: string; primaryKey: any }
26
28
  popup?: React.ReactElement<{}>
27
29
  } | null
28
30
 
31
+ /** Results from onGridHover that can be used to display a hover over */
29
32
  export type OnGridHoverResults = {
30
- scope?: any
31
- hoverOver?: React.ReactElement<{}>
33
+ hoverOver?: ReactNode
32
34
  } | null
33
35
 
34
36
  /** Item in hover over */
@@ -0,0 +1,188 @@
1
+ import _ from "lodash"
2
+ import $ from "jquery"
3
+ import React from "react"
4
+ import querystring from "querystring"
5
+ import * as uiComponents from "../UIComponents"
6
+ import { ExprUtils, Schema } from "@mwater/expressions"
7
+ import moment from "moment"
8
+ import { Form } from "@mwater/forms"
9
+
10
+
11
+ interface FormsListComponentProps {
12
+ /** Url to hit api */
13
+ apiUrl: string
14
+ /** Optional client */
15
+ client?: string
16
+ schema: Schema
17
+ /** User id */
18
+ user?: string
19
+ /** Called with table selected */
20
+ onChange: any
21
+ extraTables: any
22
+ onExtraTableAdd: any
23
+ onExtraTableRemove: any
24
+ }
25
+
26
+ interface FormsListComponentState {
27
+ error?: any
28
+ search: any
29
+ forms: { id: string, name: string, desc?: string }[] | null
30
+ }
31
+
32
+ // Searchable list of forms
33
+ export class FormsListComponent extends React.Component<FormsListComponentProps, FormsListComponentState> {
34
+ constructor(props: any) {
35
+ super(props)
36
+ this.state = {
37
+ forms: null,
38
+ search: ""
39
+ }
40
+ }
41
+
42
+ componentDidMount() {
43
+ // Get names and basic of forms
44
+ const query: any = {}
45
+ query.fields = JSON.stringify({
46
+ "design.name": 1,
47
+ "design.description": 1,
48
+ roles: 1,
49
+ created: 1,
50
+ modified: 1,
51
+ state: 1,
52
+ isMaster: 1
53
+ })
54
+ query.selector = JSON.stringify({ design: { $exists: true }, state: { $ne: "deleted" } })
55
+ query.client = this.props.client
56
+
57
+ // Get list of all form names
58
+ $.getJSON(this.props.apiUrl + "forms?" + querystring.stringify(query), (forms: Form[]) => {
59
+ // Sort by modified.on desc but first by user
60
+ forms = _.sortByOrder(
61
+ forms,
62
+ [
63
+ (form: Form) => ((this.props.extraTables || []).includes("responses:" + form._id) ? 1 : 0),
64
+ (form: Form) => (form.created.by === this.props.user ? 1 : 0),
65
+ (form: Form) => form.modified?.on
66
+ ],
67
+ ["desc", "desc", "desc"]
68
+ )
69
+
70
+ // TODO use name instead of design.name
71
+ this.setState({
72
+ forms: _.map(forms, (form) => {
73
+ let desc = ExprUtils.localizeString(form.design.description, null) || ""
74
+ if (desc) {
75
+ desc += " - "
76
+ }
77
+ desc += T`Modified ${moment(form.modified?.on, moment.ISO_8601).format("ll")}`
78
+
79
+ return {
80
+ id: form._id,
81
+ name: ExprUtils.localizeString(form.design.name, null),
82
+ desc
83
+ }
84
+ })
85
+ })
86
+ }).fail((xhr: any) => {
87
+ this.setState({ error: xhr.responseText })
88
+ })
89
+ }
90
+
91
+ handleTableRemove = (table: any) => {
92
+ if (
93
+ confirm(
94
+ T`Remove ${ExprUtils.localizeString(
95
+ table.name,
96
+ T.locale
97
+ )}? Any widgets that depend on it will no longer work properly.`
98
+ )
99
+ ) {
100
+ return this.props.onExtraTableRemove(table.id)
101
+ }
102
+ }
103
+
104
+ searchRef = (comp: any) => {
105
+ // Focus
106
+ if (comp) {
107
+ return comp.focus()
108
+ }
109
+ }
110
+
111
+ render() {
112
+ let forms
113
+ if (this.state.error) {
114
+ return <div className="alert alert-danger">{this.state.error}</div>
115
+ }
116
+
117
+ // Filter forms
118
+ if (this.state.search) {
119
+ const escapeRegExp = (s: any) => s.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&")
120
+
121
+ const searchStringRegExp = new RegExp(escapeRegExp(this.state.search), "i")
122
+
123
+ forms = _.filter(this.state.forms!, (form) => form.name.match(searchStringRegExp))
124
+ } else {
125
+ ;({ forms } = this.state)
126
+ }
127
+
128
+ // Remove if already included
129
+ forms = _.filter(forms || [], (f) => !(this.props.extraTables || []).includes(`responses:${f.id}`))
130
+
131
+ let tables = _.filter(
132
+ this.props.schema.getTables(),
133
+ (table) => (table.id.match(/^responses:/) || table.id.match(/^master_responses:/)) && !table.deprecated
134
+ )
135
+ tables = _.sortBy(tables, (t) => t.name.en)
136
+
137
+ return (
138
+ <div>
139
+ <label>{T`Included Surveys:`}</label>
140
+ {tables.length > 0 ? (
141
+ <uiComponents.OptionListComponent
142
+ items={_.map(tables, (table) => {
143
+ return {
144
+ name: ExprUtils.localizeString(table.name, T.locale),
145
+ desc: ExprUtils.localizeString(table.desc, T.locale),
146
+ onClick: this.props.onChange.bind(null, table.id),
147
+ onRemove: this.handleTableRemove.bind(null, table)
148
+ }
149
+ })}
150
+ />
151
+ ) : (
152
+ <div>{T`None`}</div>
153
+ )}
154
+
155
+ <br />
156
+
157
+ <label>{T`All Surveys:`}</label>
158
+ {!this.state.forms || this.state.forms.length === 0 ? (
159
+ <div className="alert alert-info">
160
+ <i className="fa fa-spinner fa-spin" />
161
+ &nbsp;{T`Loading...`}
162
+ </div>
163
+ ) : (
164
+ <>
165
+ <input
166
+ type="text"
167
+ className="form-control form-control-sm"
168
+ placeholder={T`Search...`}
169
+ key="search"
170
+ ref={this.searchRef}
171
+ style={{ maxWidth: "20em", marginBottom: 10 }}
172
+ value={this.state.search}
173
+ onChange={(ev: any) => this.setState({ search: ev.target.value })}
174
+ />
175
+
176
+ <uiComponents.OptionListComponent
177
+ items={_.map(forms, (form) => ({
178
+ name: form.name,
179
+ desc: form.desc,
180
+ onClick: this.props.onChange.bind(null, "responses:" + form.id)
181
+ }))}
182
+ />
183
+ </>
184
+ )}
185
+ </div>
186
+ )
187
+ }
188
+ }