@mwater/visualization 5.6.0 → 5.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/lib/ColorComponent.js +2 -2
  2. package/lib/TranslationsTabComponent.d.ts +34 -0
  3. package/lib/TranslationsTabComponent.js +256 -0
  4. package/lib/dashboards/DashboardComponent.js +1 -1
  5. package/lib/dashboards/ServerDashboardDataSource.d.ts +0 -1
  6. package/lib/dashboards/ServerDashboardDataSource.js +0 -15
  7. package/lib/dashboards/SettingsModalComponent.js +9 -233
  8. package/lib/datagrids/DatagridComponent.js +5 -0
  9. package/lib/datagrids/DatagridViewComponent.js +30 -4
  10. package/lib/maps/BufferLayer.d.ts +0 -13
  11. package/lib/maps/BufferLayer.js +12 -237
  12. package/lib/maps/BufferLayerDesignerComponent.d.ts +1 -1
  13. package/lib/maps/BufferLayerDesignerComponent.js +0 -5
  14. package/lib/maps/ChoroplethLayer.d.ts +1 -16
  15. package/lib/maps/ChoroplethLayer.js +13 -358
  16. package/lib/maps/ClusterLayer.d.ts +0 -9
  17. package/lib/maps/ClusterLayer.js +0 -250
  18. package/lib/maps/DirectMapDataSource.js +1 -38
  19. package/lib/maps/GridLayer.d.ts +0 -15
  20. package/lib/maps/GridLayer.js +0 -212
  21. package/lib/maps/Layer.d.ts +1 -26
  22. package/lib/maps/Layer.js +0 -13
  23. package/lib/maps/MapComponent.d.ts +19 -35
  24. package/lib/maps/MapComponent.js +135 -76
  25. package/lib/maps/MapControlComponent.d.ts +4 -5
  26. package/lib/maps/MapControlComponent.js +5 -12
  27. package/lib/maps/MapDesign.d.ts +8 -0
  28. package/lib/maps/MapDesignerComponent.d.ts +2 -0
  29. package/lib/maps/MapDesignerComponent.js +7 -2
  30. package/lib/maps/MapLayerDataSource.d.ts +0 -4
  31. package/lib/maps/MapLayerViewDesignerComponent.d.ts +3 -1
  32. package/lib/maps/MapLayerViewDesignerComponent.js +5 -1
  33. package/lib/maps/MapLayersDesignerComponent.d.ts +2 -0
  34. package/lib/maps/MapLayersDesignerComponent.js +2 -1
  35. package/lib/maps/MapTranslationsTab.d.ts +15 -0
  36. package/lib/maps/MapTranslationsTab.js +47 -0
  37. package/lib/maps/MapUtils.d.ts +11 -0
  38. package/lib/maps/MapUtils.js +47 -0
  39. package/lib/maps/MapViewComponent.d.ts +1 -1
  40. package/lib/maps/MapViewComponent.js +1 -8
  41. package/lib/maps/MarkersLayer.d.ts +1 -14
  42. package/lib/maps/MarkersLayer.js +71 -252
  43. package/lib/maps/MarkersLayerDesign.d.ts +4 -0
  44. package/lib/maps/MarkersLayerDesignerComponent.d.ts +20 -16
  45. package/lib/maps/MarkersLayerDesignerComponent.js +77 -23
  46. package/lib/maps/ServerMapDataSource.d.ts +0 -1
  47. package/lib/maps/ServerMapDataSource.js +0 -15
  48. package/lib/maps/SwitchableTileUrlLayer.d.ts +0 -2
  49. package/lib/maps/SwitchableTileUrlLayer.js +0 -9
  50. package/lib/maps/TileUrlLayer.d.ts +0 -1
  51. package/lib/maps/TileUrlLayer.js +0 -5
  52. package/lib/maps/VectorMapViewComponent.js +12 -1
  53. package/lib/maps/vectorMaps.d.ts +5 -6
  54. package/lib/maps/vectorMaps.js +13 -9
  55. package/lib/widgets/MapWidget.js +2 -1
  56. package/package.json +2 -2
  57. package/src/ColorComponent.tsx +2 -2
  58. package/src/TranslationsTabComponent.tsx +429 -0
  59. package/src/dashboards/DashboardComponent.tsx +1 -1
  60. package/src/dashboards/ServerDashboardDataSource.ts +0 -19
  61. package/src/dashboards/SettingsModalComponent.tsx +27 -383
  62. package/src/datagrids/DatagridComponent.tsx +6 -0
  63. package/src/datagrids/DatagridViewComponent.tsx +41 -5
  64. package/src/maps/BufferLayer.ts +16 -262
  65. package/src/maps/BufferLayerDesignerComponent.tsx +0 -6
  66. package/src/maps/ChoroplethLayer.ts +16 -393
  67. package/src/maps/ClusterLayer.ts +0 -274
  68. package/src/maps/DirectMapDataSource.ts +2 -49
  69. package/src/maps/GridLayer.ts +0 -224
  70. package/src/maps/Layer.ts +1 -35
  71. package/src/maps/MapComponent.tsx +448 -0
  72. package/src/maps/MapControlComponent.tsx +41 -0
  73. package/src/maps/MapDesign.ts +6 -0
  74. package/src/maps/MapDesignerComponent.tsx +18 -1
  75. package/src/maps/MapLayerDataSource.ts +0 -5
  76. package/src/maps/MapLayerViewDesignerComponent.ts +9 -2
  77. package/src/maps/MapLayersDesignerComponent.ts +4 -1
  78. package/src/maps/MapTranslationsTab.tsx +53 -0
  79. package/src/maps/MapUtils.ts +48 -0
  80. package/src/maps/MapViewComponent.tsx +2 -8
  81. package/src/maps/MarkersLayer.ts +79 -270
  82. package/src/maps/MarkersLayerDesign.ts +6 -0
  83. package/src/maps/MarkersLayerDesignerComponent.tsx +114 -38
  84. package/src/maps/ServerMapDataSource.ts +0 -19
  85. package/src/maps/SwitchableTileUrlLayer.tsx +0 -11
  86. package/src/maps/TileUrlLayer.tsx +0 -6
  87. package/src/maps/VectorMapViewComponent.tsx +13 -2
  88. package/src/maps/vectorMaps.tsx +12 -9
  89. package/src/widgets/MapWidget.tsx +2 -0
  90. package/src/maps/MapComponent.ts +0 -311
  91. package/src/maps/MapControlComponent.ts +0 -46
  92. package/src/maps/RasterMapViewComponent.ts +0 -345
@@ -1,7 +1,7 @@
1
1
  import _ from "lodash"
2
2
  import React from "react"
3
3
  import { FilterExprComponent } from "@mwater/expressions-ui"
4
- import { DataSource, ExprUtils, OpExpr, Schema } from "@mwater/expressions"
4
+ import { DataSource, Expr, ExprUtils, OpExpr, Schema } from "@mwater/expressions"
5
5
  import { ExprCompiler } from "@mwater/expressions"
6
6
  import AxisComponent from "../axes/AxisComponent"
7
7
  import ColorComponent from "../ColorComponent"
@@ -14,6 +14,9 @@ import * as ui from "@mwater/react-library/lib/bootstrap"
14
14
  import { EditHoverOver } from "./EditHoverOver"
15
15
  import { MarkersLayerDesign } from "./MarkersLayerDesign"
16
16
  import { default as Rcslider } from "rc-slider"
17
+ import { produce } from "immer"
18
+ import { Axis } from "../axes/Axis"
19
+ import { JsonQLFilter } from "../JsonQLFilter"
17
20
 
18
21
  export interface MarkersLayerDesignerComponentProps {
19
22
  /** Schema to use */
@@ -22,55 +25,71 @@ export interface MarkersLayerDesignerComponentProps {
22
25
  /** Design of the marker layer */
23
26
  design: MarkersLayerDesign
24
27
  /** Called with new design */
25
- onDesignChange: any
26
- filters?: any
28
+ onDesignChange: (design: MarkersLayerDesign) => void
29
+ filters?: JsonQLFilter[]
27
30
  }
28
31
 
29
- // Designer for a markers layer
32
+ /** Designer for a markers layer */
30
33
  export default class MarkersLayerDesignerComponent extends React.Component<MarkersLayerDesignerComponentProps> {
31
- // Apply updates to design
32
- update(updates: any) {
33
- return this.props.onDesignChange(_.extend({}, this.props.design, updates))
34
+ handleTableChange = (table: string) => {
35
+ this.props.onDesignChange(produce(this.props.design, draft => {
36
+ draft.table = table
37
+ }))
34
38
  }
35
-
36
- // Update axes with specified changes
37
- updateAxes(changes: any) {
38
- const axes = _.extend({}, this.props.design.axes, changes)
39
- return this.update({ axes })
40
- }
41
-
42
- handleTableChange = (table: any) => {
43
- return this.update({ table })
39
+ handleGeometryAxisChange = (axis: Axis) => {
40
+ this.props.onDesignChange(produce(this.props.design, draft => {
41
+ draft.axes.geometry = axis
42
+ }))
44
43
  }
45
- handleGeometryAxisChange = (axis: any) => {
46
- return this.updateAxes({ geometry: axis })
44
+ handleColorAxisChange = (axis: Axis | null) => {
45
+ this.props.onDesignChange(produce(this.props.design, draft => {
46
+ draft.axes.color = axis ?? undefined
47
+ }))
47
48
  }
48
- handleColorAxisChange = (axis: any) => {
49
- return this.updateAxes({ color: axis })
49
+ handleFilterChange = (expr: Expr) => {
50
+ this.props.onDesignChange(produce(this.props.design, draft => {
51
+ draft.filter = expr
52
+ }))
50
53
  }
51
- handleFilterChange = (expr: any) => {
52
- return this.update({ filter: expr })
54
+ handleColorChange = (color: string) => {
55
+ this.props.onDesignChange(produce(this.props.design, draft => {
56
+ draft.color = color
57
+ }))
53
58
  }
54
- handleColorChange = (color: any) => {
55
- return this.update({ color })
59
+ handlePolygonBorderColorChange = (polygonBorderColor: string) => {
60
+ this.props.onDesignChange(produce(this.props.design, draft => {
61
+ draft.polygonBorderColor = polygonBorderColor
62
+ }))
56
63
  }
57
- handlePolygonBorderColorChange = (polygonBorderColor: any) => {
58
- return this.update({ polygonBorderColor })
64
+ handlePolygonFillOpacityChange = (polygonFillOpacity: number) => {
65
+ this.props.onDesignChange(produce(this.props.design, draft => {
66
+ draft.polygonFillOpacity = polygonFillOpacity / 100
67
+ }))
59
68
  }
60
- handlePolygonFillOpacityChange = (polygonFillOpacity: any) => {
61
- return this.update({ polygonFillOpacity: polygonFillOpacity / 100 })
69
+ handleSymbolChange = (symbol: string) => {
70
+ this.props.onDesignChange(produce(this.props.design, draft => {
71
+ draft.symbol = symbol
72
+ }))
62
73
  }
63
- handleSymbolChange = (symbol: any) => {
64
- return this.update({ symbol })
74
+ handleMarkerSizeChange = (markerSize: number) => {
75
+ this.props.onDesignChange(produce(this.props.design, draft => {
76
+ draft.markerSize = markerSize
77
+ }))
65
78
  }
66
- handleNameChange = (e: any) => {
67
- return this.update({ name: e.target.value })
79
+ handleLineWidthChange = (lineWidth: number) => {
80
+ this.props.onDesignChange(produce(this.props.design, draft => {
81
+ draft.lineWidth = lineWidth
82
+ }))
68
83
  }
69
- handleMarkerSizeChange = (markerSize: any) => {
70
- return this.update({ markerSize })
84
+ handleLabelAxisChange = (axis: Axis | null) => {
85
+ this.props.onDesignChange(produce(this.props.design, draft => {
86
+ draft.axes.label = axis ?? undefined
87
+ }))
71
88
  }
72
- handleLineWidthChange = (lineWidth: any) => {
73
- return this.update({ lineWidth })
89
+ handleLabelPositionChange = (labelPosition: "top" | "bottom" | "left" | "right") => {
90
+ this.props.onDesignChange(produce(this.props.design, draft => {
91
+ draft.labelPosition = labelPosition
92
+ }))
74
93
  }
75
94
 
76
95
  renderTable() {
@@ -103,7 +122,7 @@ export default class MarkersLayerDesignerComponent extends React.Component<Marke
103
122
  const exprCompiler = new ExprCompiler(this.props.schema)
104
123
  const jsonql = exprCompiler.compileExpr({ expr: this.props.design.filter, tableAlias: "{alias}" })
105
124
  if (jsonql) {
106
- filters.push({ table: (this.props.design.filter as OpExpr).table, jsonql })
125
+ filters.push({ table: (this.props.design.filter as OpExpr).table!, jsonql })
107
126
  }
108
127
  }
109
128
 
@@ -137,7 +156,7 @@ export default class MarkersLayerDesignerComponent extends React.Component<Marke
137
156
  const exprCompiler = new ExprCompiler(this.props.schema)
138
157
  const jsonql = exprCompiler.compileExpr({ expr: this.props.design.filter, tableAlias: "{alias}" })
139
158
  if (jsonql) {
140
- filters.push({ table: (this.props.design.filter as OpExpr).table, jsonql })
159
+ filters.push({ table: (this.props.design.filter as OpExpr).table!, jsonql })
141
160
  }
142
161
  }
143
162
 
@@ -295,6 +314,61 @@ export default class MarkersLayerDesignerComponent extends React.Component<Marke
295
314
  )
296
315
  }
297
316
 
317
+ renderLabelAxis() {
318
+ if (!this.props.design.axes.geometry) {
319
+ return
320
+ }
321
+
322
+ const filters = _.clone(this.props.filters) || []
323
+
324
+ if (this.props.design.filter != null) {
325
+ const exprCompiler = new ExprCompiler(this.props.schema)
326
+ const jsonql = exprCompiler.compileExpr({ expr: this.props.design.filter, tableAlias: "{alias}" })
327
+ if (jsonql) {
328
+ filters.push({ table: (this.props.design.filter as OpExpr).table!, jsonql })
329
+ }
330
+ }
331
+
332
+ return (
333
+ <div className="mb-3">
334
+ <label className="text-muted"><span className="fas fa-tag" /> {T`Label (points only)`}</label>
335
+ <AxisComponent
336
+ schema={this.props.schema}
337
+ dataSource={this.props.dataSource}
338
+ table={this.props.design.table}
339
+ types={["text", "number"]}
340
+ aggrNeed="none"
341
+ value={this.props.design.axes.label}
342
+ onChange={this.handleLabelAxisChange}
343
+ filters={filters}
344
+ />
345
+ </div>
346
+ )
347
+ }
348
+
349
+ renderLabelPosition() {
350
+ // Only show if label axis is set
351
+ if (!this.props.design.axes.label) {
352
+ return
353
+ }
354
+
355
+ return (
356
+ <div className="mb-3">
357
+ <label className="text-muted">{T`Label Position`}</label>
358
+ <ui.Select
359
+ value={this.props.design.labelPosition || "bottom"}
360
+ options={[
361
+ { value: "top", label: T`Top` },
362
+ { value: "bottom", label: T`Bottom` },
363
+ { value: "left", label: T`Left` },
364
+ { value: "right", label: T`Right` }
365
+ ]}
366
+ onChange={this.handleLabelPositionChange}
367
+ />
368
+ </div>
369
+ )
370
+ }
371
+
298
372
  renderPopup() {
299
373
  if (!this.props.design.table) {
300
374
  return null
@@ -340,6 +414,8 @@ export default class MarkersLayerDesignerComponent extends React.Component<Marke
340
414
  {this.renderColor()}
341
415
  {this.renderSymbol()}
342
416
  {this.renderMarkerSize()}
417
+ {this.renderLabelAxis()}
418
+ {this.renderLabelPosition()}
343
419
  <ui.CollapsibleSection
344
420
  label={T`Shape Options`}
345
421
  labelMuted={true}
@@ -126,25 +126,6 @@ class ServerLayerDataSource implements MapLayerDataSource {
126
126
  return this.createUrl(filters, "png")
127
127
  }
128
128
 
129
- // Get the url for the interactivity tiles with the specified filters applied
130
- // Called with (design, filters) where design is the design of the layer and filters are filters to apply. Returns URL
131
- getUtfGridUrl(design: any, filters: JsonQLFilter[]) {
132
- // Handle special cases
133
- if (this.options.layerView.type === "MWaterServer") {
134
- return this.createLegacyUrl(design, "grid.json", filters)
135
- }
136
-
137
- // Create layer
138
- const layer = LayerFactory.createLayer(this.options.layerView.type)
139
-
140
- // If layer has tiles url directly available
141
- if (layer.getLayerDefinitionType() === "TileUrl") {
142
- return layer.getUtfGridUrl(this.options.layerView.design, filters)
143
- }
144
-
145
- return this.createUrl(filters, "grid.json")
146
- }
147
-
148
129
  /** Get the url for vector tile source with an expiry time. Only for layers of type "VectorTile"
149
130
  * @param createdAfter ISO 8601 timestamp requiring that tile source on server is created after specified datetime
150
131
  */
@@ -68,17 +68,6 @@ export default class SwitchableTileUrlLayer extends Layer<SwitchableTileUrlLayer
68
68
  return option.tileUrl || null
69
69
  }
70
70
 
71
- /** Gets the utf grid url for definition type "TileUrl" */
72
- getUtfGridUrl(design: SwitchableTileUrlLayerDesign, filters: JsonQLFilter[]): string | null {
73
- // Find active option
74
- const option = design.options.find((d) => d.id === design.activeOption)
75
- if (!option) {
76
- return null
77
- }
78
-
79
- return option.utfGridUrl || null
80
- }
81
-
82
71
  getLegend(options: LegendOptions<SwitchableTileUrlLayerDesign>): ReactNode {
83
72
  const { design, name } = options
84
73
  // Find active option
@@ -31,7 +31,6 @@ Design is:
31
31
  legendUrl:
32
32
  */
33
33
  export default class TileUrlLayer extends Layer<TileUrlLayerDesign> {
34
- // Gets the type of layer definition ("JsonQLCss"/"TileUrl")
35
34
  getLayerDefinitionType(): "TileUrl" {
36
35
  return "TileUrl"
37
36
  }
@@ -41,11 +40,6 @@ export default class TileUrlLayer extends Layer<TileUrlLayerDesign> {
41
40
  return design.tileUrl
42
41
  }
43
42
 
44
- // Gets the utf grid url for definition type "TileUrl"
45
- getUtfGridUrl(design: any, filters: any) {
46
- return null
47
- }
48
-
49
43
  // Get min and max zoom levels
50
44
  getMinZoom(design: any) {
51
45
  return design.minZoom
@@ -1,6 +1,6 @@
1
1
  import _, { find } from "lodash"
2
2
  import { LayerSpecification, MapLayerMouseEvent } from "maplibre-gl"
3
- import React, { CSSProperties, ReactNode, useEffect, useMemo, useState } from "react"
3
+ import React, { CSSProperties, ReactNode, useCallback, useEffect, useMemo, useState } from "react"
4
4
  import { useRef } from "react"
5
5
  import { JsonQLFilter } from "../JsonQLFilter"
6
6
  import { default as LayerFactory } from "./LayerFactory"
@@ -75,7 +75,18 @@ export function VectorMapViewComponent(props: VectorMapViewComponentProps) {
75
75
  const locale = props.locale || props.design.locale || "en"
76
76
 
77
77
  // Translate function to use
78
- const translate = props.translate || ((input: string) => input)
78
+ const translate = useCallback((input: string) => {
79
+ // Use passed in translate function if present
80
+ if (props.translate) {
81
+ return props.translate(input)
82
+ }
83
+ // If locale is the same as the design locale, don't translate
84
+ if (locale === props.design.locale) {
85
+ return input
86
+ }
87
+ // Otherwise, use translation from design
88
+ return props.design.translations?.[locale]?.[input] ?? input
89
+ }, [props.translate, props.design.translations, props.design.locale, locale])
79
90
 
80
91
  // Last feature that mouse entered
81
92
  const lastFeature = useRef<string>()
@@ -7,6 +7,9 @@ import "maplibre-gl/dist/maplibre-gl.css"
7
7
  import "./VectorMapViewComponent.css"
8
8
  import React from "react"
9
9
 
10
+ // Re-export maplibregl for consumers
11
+ export { default as maplibregl } from "maplibre-gl"
12
+
10
13
  /** Set to true to enable printing by preserving the drawing buffer */
11
14
  let printingModeEnabled = false
12
15
 
@@ -29,11 +32,6 @@ export function getMapTilerApiKey(): string {
29
32
  return mapTilerApiKey
30
33
  }
31
34
 
32
- /** Check if vector maps are enabled by setting API key */
33
- export function areVectorMapsEnabled() {
34
- return mapTilerApiKey !== ""
35
- }
36
-
37
35
  export type BaseLayer = "bing_road" | "bing_aerial" | "cartodb_positron" | "cartodb_dark_matter" | "blank"
38
36
 
39
37
  /** Loads a vector map, refreshing the WebGL context as needed */
@@ -106,7 +104,10 @@ export function useVectorMap(options: {
106
104
  [-179.9, -85], // Southwest coordinates
107
105
  [179.9, 85] // Northeast coordinates
108
106
  ],
109
- preserveDrawingBuffer: printingModeEnabled
107
+ // In maplibre-gl v5+, WebGL context options must be in canvasContextAttributes
108
+ canvasContextAttributes: {
109
+ preserveDrawingBuffer: printingModeEnabled
110
+ }
110
111
  }
111
112
 
112
113
  if (bounds) {
@@ -143,10 +144,12 @@ export function useVectorMap(options: {
143
144
  // Check if known
144
145
  const mapSymbol = getMapSymbols().find((s) => s.value == ev.id)
145
146
  if (mapSymbol) {
146
- m.loadImage(mapSymbol.url, (err: any, image: any) => {
147
- if (image && !m.hasImage(mapSymbol.value)) {
148
- m.addImage(mapSymbol.value, image, { sdf: true })
147
+ m.loadImage(mapSymbol.url).then((response) => {
148
+ if (response && response.data && !m.hasImage(mapSymbol.value)) {
149
+ m.addImage(mapSymbol.value, response.data, { sdf: true })
149
150
  }
151
+ }).catch((err) => {
152
+ console.error("Error loading map symbol:", err)
150
153
  })
151
154
  }
152
155
  })
@@ -123,12 +123,14 @@ class MapWidgetComponent extends React.Component<MapWidgetComponentProps, MapWid
123
123
  const MapDesignerComponent = require("../maps/MapDesignerComponent").default
124
124
 
125
125
  // Create editor
126
+ // Note: enableTranslations is false because translations are managed at the dashboard level
126
127
  const editor = <MapDesignerComponent
127
128
  schema={this.props.schema}
128
129
  dataSource={this.props.dataSource}
129
130
  design={this.state.editDesign}
130
131
  onDesignChange={this.handleEditDesignChange}
131
132
  filters={this.props.filters}
133
+ enableTranslations={false}
132
134
  />
133
135
 
134
136
  // Create map (maxing out at half of width of screen)
@@ -1,311 +0,0 @@
1
- import _ from "lodash"
2
- import React, { ReactNode } from "react"
3
- const R = React.createElement
4
-
5
- import { MapViewComponent } from "./MapViewComponent"
6
- import MapDesignerComponent from "./MapDesignerComponent"
7
- import MapControlComponent from "./MapControlComponent"
8
- import AutoSizeComponent from "@mwater/react-library/lib/AutoSizeComponent"
9
- import UndoStack from "../UndoStack"
10
- import PopoverHelpComponent from "@mwater/react-library/lib/PopoverHelpComponent"
11
- import { DataSource, Schema } from "@mwater/expressions"
12
- import { MapDataSource } from "./MapDataSource"
13
- import { MapDesign } from "./MapDesign"
14
- import { JsonQLFilter } from "../JsonQLFilter"
15
- import QuickfilterCompiler from "../quickfilter/QuickfilterCompiler"
16
- import QuickfiltersComponent from "../quickfilter/QuickfiltersComponent"
17
- import { getCompiledFilters, getFilterableTables } from "./MapUtils"
18
- import { LocaleContext } from "@mwater/expressions-ui"
19
-
20
- export interface MapComponentProps {
21
- schema: Schema
22
- dataSource: DataSource
23
- mapDataSource: MapDataSource
24
-
25
- design: MapDesign
26
- onDesignChange?: (design: MapDesign) => void
27
-
28
- /** Called with (tableId, rowId) when item is clicked */
29
- onRowClick?: (tableId: string, rowId: any) => void
30
-
31
- /** Extra filters to apply to view */
32
- extraFilters?: JsonQLFilter[]
33
-
34
- /** Extra element to include in title at left */
35
- titleElem?: ReactNode
36
-
37
- /** Extra elements to add to right */
38
- extraTitleButtonsElem?: ReactNode
39
-
40
- /** True to enable quickfilters */
41
- enableQuickfilters?: boolean
42
-
43
- /** Locked quickfilter values. See README in quickfilters */
44
- quickfilterLocks?: any[]
45
-
46
- /** Initial quickfilter values */
47
- quickfiltersValues?: any[]
48
-
49
- /** True to hide title bar and related controls */
50
- hideTitleBar?: boolean
51
- }
52
-
53
- interface MapComponentState {
54
- undoStack: UndoStack
55
- transientDesign: MapDesign
56
- zoomLocked: boolean
57
-
58
- /** Values of quickfilters */
59
- quickfiltersValues: any[] | null
60
-
61
- /** True to hide quickfilters */
62
- hideQuickfilters: boolean
63
- }
64
-
65
- /** Map with designer on right */
66
- export default class MapComponent extends React.Component<MapComponentProps, MapComponentState> {
67
- static contextType = LocaleContext
68
-
69
- constructor(props: MapComponentProps) {
70
- super(props)
71
- this.state = {
72
- undoStack: new UndoStack().push(props.design),
73
- transientDesign: props.design, // Temporary design for read-only maps
74
- zoomLocked: true,
75
- quickfiltersValues: props.quickfiltersValues ?? null,
76
- hideQuickfilters: false
77
- }
78
- }
79
-
80
- componentDidUpdate(prevProps: MapComponentProps) {
81
- // If design changes, save on stack and update transient design
82
- if (!_.isEqual(prevProps.design, this.props.design)) {
83
- // Save on stack
84
- this.setState({ undoStack: this.state.undoStack.push(this.props.design) })
85
-
86
- // Update transient design
87
- this.setState({ transientDesign: this.props.design })
88
- }
89
- }
90
-
91
- handleUndo = () => {
92
- const undoStack = this.state.undoStack.undo()
93
-
94
- // We need to use callback as state is applied later
95
- return this.setState({ undoStack }, () => this.props.onDesignChange!(undoStack.getValue()))
96
- }
97
-
98
- handleRedo = () => {
99
- const undoStack = this.state.undoStack.redo()
100
-
101
- // We need to use callback as state is applied later
102
- return this.setState({ undoStack }, () => this.props.onDesignChange!(undoStack.getValue()))
103
- }
104
-
105
- handleShowQuickfilters = () => {
106
- return this.setState({ hideQuickfilters: false })
107
- }
108
-
109
- handleZoomLockClick = () => {
110
- return this.setState({ zoomLocked: !this.state.zoomLocked })
111
- }
112
-
113
- renderActionLinks() {
114
- return R(
115
- "div",
116
- null,
117
- this.props.onDesignChange != null
118
- ? [
119
- R(
120
- "a",
121
- { key: "lock", className: "btn btn-link btn-sm", onClick: this.handleZoomLockClick },
122
- R("span", {
123
- className: `fa ${this.state.zoomLocked ? "fa-lock red" : "fa-unlock green"}`,
124
- style: { marginRight: 5 }
125
- }),
126
- R(PopoverHelpComponent, { placement: "bottom" }, T`Changes to zoom level wont be saved in locked mode`)
127
- ),
128
- R(
129
- "a",
130
- {
131
- key: "undo",
132
- className: `btn btn-link btn-sm ${!this.state.undoStack.canUndo() ? "disabled" : ""}`,
133
- onClick: this.handleUndo
134
- },
135
- R("span", { className: "fas fa-caret-left" }),
136
- R("span", { className: "hide-600px" }, " ", T`Undo`)
137
- ),
138
- " ",
139
- R(
140
- "a",
141
- {
142
- key: "redo",
143
- className: `btn btn-link btn-sm ${!this.state.undoStack.canRedo() ? "disabled" : ""}`,
144
- onClick: this.handleRedo
145
- },
146
- R("span", { className: "fas fa-caret-right" }),
147
- R("span", { className: "hide-600px" }, " ", T`Redo`)
148
- )
149
- ]
150
- : undefined,
151
- this.state.hideQuickfilters && this.props.design.quickfilters && this.props.design.quickfilters.length > 0
152
- ? R(
153
- "a",
154
- { key: "showQuickfilters", className: "btn btn-link btn-sm", onClick: this.handleShowQuickfilters },
155
- R("span", { className: "fa fa-filter" }),
156
- R("span", { className: "hide-600px" }, " ", T`Show Quickfilters`)
157
- )
158
- : undefined,
159
- this.props.extraTitleButtonsElem,
160
- R("a", { key: "toggleDesign", className: "btn btn-link btn-sm", onClick: this.handleToggleDesignPanel, alt: T`Toggle design panel` },
161
- this.getDesign().hideDesignPanel ?
162
- R("span", { className: "fas fa-angle-double-left" })
163
- : R("span", { className: "fas fa-angle-double-right" }))
164
- )
165
- }
166
-
167
- renderHeader() {
168
- return R(
169
- "div",
170
- {
171
- style: {
172
- padding: 4,
173
- borderBottom: "solid 1px #e8e8e8",
174
- gridArea: "header"
175
- }
176
- },
177
- R("div", { style: { float: "right" } }, this.renderActionLinks()),
178
- this.props.titleElem
179
- )
180
- }
181
-
182
- handleDesignChange = (design: any) => {
183
- if (this.props.onDesignChange) {
184
- return this.props.onDesignChange(design)
185
- } else {
186
- return this.setState({ transientDesign: design })
187
- }
188
- }
189
-
190
- getDesign() {
191
- if (this.props.onDesignChange) {
192
- return this.props.design
193
- } else {
194
- return this.state.transientDesign || this.props.design
195
- }
196
- }
197
-
198
- handleToggleDesignPanel = () => {
199
- this.handleDesignChange({ ...this.getDesign(), hideDesignPanel: !this.getDesign().hideDesignPanel })
200
- }
201
-
202
- // Get the values of the quick filters
203
- getQuickfilterValues = () => {
204
- return this.state.quickfiltersValues || []
205
- }
206
-
207
- renderView() {
208
- let filters = this.props.extraFilters || []
209
-
210
- // Compile quickfilters
211
- filters = filters.concat(
212
- new QuickfilterCompiler(this.props.schema).compile(
213
- this.props.design.quickfilters || [],
214
- this.state.quickfiltersValues,
215
- this.props.quickfilterLocks
216
- )
217
- )
218
-
219
- return React.createElement(
220
- AutoSizeComponent,
221
- { injectWidth: true, injectHeight: true },
222
- (size: {
223
- width?: number;
224
- height?: number;
225
- }) => {
226
- return React.createElement(MapViewComponent, {
227
- mapDataSource: this.props.mapDataSource,
228
- schema: this.props.schema,
229
- dataSource: this.props.dataSource,
230
- design: this.getDesign(),
231
- onDesignChange: this.handleDesignChange,
232
- zoomLocked: this.state.zoomLocked,
233
- onRowClick: this.props.onRowClick,
234
- extraFilters: filters,
235
- locale: this.context,
236
- width: size.width!,
237
- height: size.height!
238
- })
239
- }
240
- )
241
- }
242
-
243
- // Get filters from props filters combined with maps filters
244
- getCompiledFilters() {
245
- let compiledFilters = getCompiledFilters(
246
- this.props.design,
247
- this.props.schema,
248
- getFilterableTables(this.props.design, this.props.schema)
249
- )
250
- compiledFilters = compiledFilters.concat(this.props.extraFilters || [])
251
- return compiledFilters
252
- }
253
-
254
- renderQuickfilter() {
255
- return R("div", { style: { gridArea: "quickfilters", borderBottom: "solid 1px #e8e8e8" } },
256
- R(QuickfiltersComponent, {
257
- design: this.props.design.quickfilters || [],
258
- schema: this.props.schema,
259
- dataSource: this.props.dataSource,
260
- quickfiltersDataSource: this.props.mapDataSource.getQuickfiltersDataSource(),
261
- values: this.state.quickfiltersValues || undefined,
262
- onValuesChange: (values: any) => this.setState({ quickfiltersValues: values }),
263
- locks: this.props.quickfilterLocks,
264
- filters: this.getCompiledFilters(),
265
- hideTopBorder: this.props.hideTitleBar,
266
- onHide: () => this.setState({ hideQuickfilters: true })
267
- })
268
- )
269
- }
270
-
271
- renderDesigner() {
272
- return R("div", { style: { gridArea: "designer", borderLeft: "solid 2px #e8e8e8", overflowY: 'scroll' } },
273
- this.props.onDesignChange ?
274
- React.createElement(MapDesignerComponent, {
275
- schema: this.props.schema,
276
- dataSource: this.props.dataSource,
277
- design: this.getDesign(),
278
- onDesignChange: this.handleDesignChange,
279
- enableQuickfilters: this.props.enableQuickfilters
280
- })
281
- :
282
- React.createElement(MapControlComponent, {
283
- schema: this.props.schema,
284
- dataSource: this.props.dataSource,
285
- design: this.getDesign(),
286
- onDesignChange: this.handleDesignChange
287
- })
288
- )
289
- }
290
-
291
- render() {
292
- const designerVisible = !this.getDesign().hideDesignPanel
293
- return R(
294
- "div",
295
- {
296
- style: {
297
- width: "100%",
298
- height: "100%",
299
- display: "grid",
300
- gridTemplateColumns: designerVisible ? "70% 30%" : "100%",
301
- gridTemplateRows: "auto auto 1fr",
302
- gridTemplateAreas: `"header designer" "quickfilters designer" "view designer"`
303
- }
304
- },
305
- this.props.hideTitleBar != true ? this.renderHeader() : null,
306
- this.state.hideQuickfilters ? null : this.renderQuickfilter(),
307
- R("div", { style: { width: "100%", height: "100%", gridArea: "view", overflow: "hidden" } }, this.renderView()),
308
- designerVisible ? this.renderDesigner() : null
309
- )
310
- }
311
- }