@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
@@ -0,0 +1,448 @@
1
+ import _ from "lodash"
2
+ import React, { ReactNode } from "react"
3
+
4
+ import { MapViewComponent } from "./MapViewComponent"
5
+ import MapDesignerComponent from "./MapDesignerComponent"
6
+ import MapControlComponent from "./MapControlComponent"
7
+ import AutoSizeComponent from "@mwater/react-library/lib/AutoSizeComponent"
8
+ import UndoStack from "../UndoStack"
9
+ import PopoverHelpComponent from "@mwater/react-library/lib/PopoverHelpComponent"
10
+ import { DataSource, Schema } from "@mwater/expressions"
11
+ import { MapDataSource } from "./MapDataSource"
12
+ import { MapDesign } from "./MapDesign"
13
+ import { JsonQLFilter, languages } from ".."
14
+ import QuickfilterCompiler from "../quickfilter/QuickfilterCompiler"
15
+ import QuickfiltersComponent from "../quickfilter/QuickfiltersComponent"
16
+ import { getCompiledFilters, getFilterableTables } from "./MapUtils"
17
+
18
+ export interface MapComponentProps {
19
+ schema: Schema
20
+ dataSource: DataSource
21
+ mapDataSource: MapDataSource
22
+
23
+ design: MapDesign
24
+ onDesignChange?: (design: MapDesign) => void
25
+
26
+ /** Called with (tableId, rowId) when item is clicked */
27
+ onRowClick?: (tableId: string, rowId: any) => void
28
+
29
+ /** Extra filters to apply to view */
30
+ extraFilters?: JsonQLFilter[]
31
+
32
+ /** Extra element to include in title at left */
33
+ titleElem?: ReactNode
34
+
35
+ /** Extra elements to add to right */
36
+ extraTitleButtonsElem?: ReactNode
37
+
38
+ /** True to enable quickfilters */
39
+ enableQuickfilters?: boolean
40
+
41
+ /** Locked quickfilter values. See README in quickfilters */
42
+ quickfilterLocks?: any[]
43
+
44
+ /** Initial quickfilter values */
45
+ quickfiltersValues?: any[]
46
+
47
+ /** True to hide title bar and related controls */
48
+ hideTitleBar?: boolean
49
+
50
+ /** Locale to prefer if available */
51
+ preferredLocale?: string
52
+ }
53
+
54
+ interface MapComponentState {
55
+ undoStack: UndoStack
56
+ transientDesign: MapDesign
57
+ zoomLocked: boolean
58
+
59
+ /** Values of quickfilters */
60
+ quickfiltersValues: any[] | null
61
+
62
+ /** True to hide quickfilters */
63
+ hideQuickfilters: boolean
64
+
65
+ /** Locale to use for display. Ignored if in edit mode */
66
+ locale: string
67
+
68
+ /** Locale being previewed while in edit mode. Non-null when previewing a translation. */
69
+ previewLocale: string | null
70
+ }
71
+
72
+ /** Map with designer on right */
73
+ export default class MapComponent extends React.Component<MapComponentProps, MapComponentState> {
74
+ constructor(props: MapComponentProps) {
75
+ super(props)
76
+
77
+ // Prefer the T.locale if available when loading the map
78
+ let initialLocale = props.design.locale || "en"
79
+ const otherLocales = props.design.otherLocales || []
80
+ if (props.preferredLocale && otherLocales.includes(props.preferredLocale)) {
81
+ initialLocale = props.preferredLocale
82
+ } else if (otherLocales.includes(T.locale)) {
83
+ initialLocale = T.locale
84
+ }
85
+
86
+ this.state = {
87
+ undoStack: new UndoStack().push(props.design),
88
+ transientDesign: props.design, // Temporary design for read-only maps
89
+ zoomLocked: true,
90
+ quickfiltersValues: props.quickfiltersValues ?? null,
91
+ hideQuickfilters: false,
92
+ locale: initialLocale,
93
+ previewLocale: null
94
+ }
95
+ }
96
+
97
+ componentDidUpdate(prevProps: MapComponentProps) {
98
+ // If design changes, save on stack and update transient design
99
+ if (!_.isEqual(prevProps.design, this.props.design)) {
100
+ // Save on stack
101
+ this.setState({ undoStack: this.state.undoStack.push(this.props.design) })
102
+
103
+ // Update transient design
104
+ this.setState({ transientDesign: this.props.design })
105
+ }
106
+ }
107
+
108
+ handleUndo = () => {
109
+ const undoStack = this.state.undoStack.undo()
110
+
111
+ // We need to use callback as state is applied later
112
+ return this.setState({ undoStack }, () => this.props.onDesignChange!(undoStack.getValue()))
113
+ }
114
+
115
+ handleRedo = () => {
116
+ const undoStack = this.state.undoStack.redo()
117
+
118
+ // We need to use callback as state is applied later
119
+ return this.setState({ undoStack }, () => this.props.onDesignChange!(undoStack.getValue()))
120
+ }
121
+
122
+ handleShowQuickfilters = () => {
123
+ return this.setState({ hideQuickfilters: false })
124
+ }
125
+
126
+ handleZoomLockClick = () => {
127
+ return this.setState({ zoomLocked: !this.state.zoomLocked })
128
+ }
129
+
130
+ /** Handle locale selection from dropdown */
131
+ handleLocaleSelect = (locale: string) => {
132
+ const baseLocale = this.props.design.locale || "en"
133
+ const isEditing = this.props.onDesignChange != null
134
+
135
+ if (isEditing) {
136
+ // In edit mode, selecting base language exits preview mode
137
+ // Selecting other language enters preview mode
138
+ if (locale === baseLocale) {
139
+ this.setState({ previewLocale: null })
140
+ } else {
141
+ this.setState({ previewLocale: locale })
142
+ }
143
+ } else {
144
+ // In readonly mode, just change the locale
145
+ this.setState({ locale: locale })
146
+ }
147
+ }
148
+
149
+ /** Render the language selector dropdown */
150
+ renderLanguageDropdown() {
151
+ const baseLocale = this.props.design.locale || "en"
152
+ const otherLocales = this.props.design.otherLocales || []
153
+ const isEditing = this.props.onDesignChange != null
154
+ const isPreviewMode = isEditing && this.state.previewLocale != null
155
+
156
+ // Determine which locale to display in the dropdown button
157
+ let displayedLocale: string
158
+ if (isEditing) {
159
+ displayedLocale = this.state.previewLocale || baseLocale
160
+ } else {
161
+ displayedLocale = this.state.locale
162
+ }
163
+
164
+ return (
165
+ <div key="translations" className="dropdown d-inline-block">
166
+ <a
167
+ className={`btn btn-link btn-sm dropdown-toggle ${isPreviewMode ? "text-info" : ""}`}
168
+ data-bs-toggle="dropdown"
169
+ >
170
+ <span className="fal fa-globe"/>
171
+ {" "}
172
+ {languages.find(l => l.code === displayedLocale)?.name || displayedLocale}
173
+ {isPreviewMode && <span className="badge bg-info ms-1">{T`Preview`}</span>}
174
+ </a>
175
+ <ul className="dropdown-menu dropdown-menu-end">
176
+ {/* Base language */}
177
+ <li key={baseLocale}>
178
+ <a
179
+ className={`dropdown-item ${displayedLocale === baseLocale ? "active" : ""}`}
180
+ onClick={() => this.handleLocaleSelect(baseLocale)}
181
+ >
182
+ {languages.find(l => l.code === baseLocale)?.name || baseLocale}
183
+ </a>
184
+ </li>
185
+
186
+ {/* Divider and preview header when editing */}
187
+ {isEditing && otherLocales.length > 0 && (
188
+ <>
189
+ <li><hr className="dropdown-divider" /></li>
190
+ <li><span className="dropdown-header">{T`Preview translations:`}</span></li>
191
+ </>
192
+ )}
193
+
194
+ {/* Other locales */}
195
+ {otherLocales.filter(loc => loc !== baseLocale).map(locale =>
196
+ <li key={locale}>
197
+ <a
198
+ className={`dropdown-item ${displayedLocale === locale ? "active" : ""}`}
199
+ onClick={() => this.handleLocaleSelect(locale)}
200
+ >
201
+ {languages.find(l => l.code === locale)?.name || locale}
202
+ </a>
203
+ </li>
204
+ )}
205
+ </ul>
206
+ </div>
207
+ )
208
+ }
209
+
210
+ renderActionLinks() {
211
+ return (
212
+ <div>
213
+ {this.props.onDesignChange != null && [
214
+ <a key="lock" className="btn btn-link btn-sm" onClick={this.handleZoomLockClick}>
215
+ <span
216
+ className={`fa ${this.state.zoomLocked ? "fa-lock red" : "fa-unlock green"}`}
217
+ style={{ marginRight: 5 }}
218
+ />
219
+ <PopoverHelpComponent placement="bottom">{T`Changes to zoom level wont be saved in locked mode`}</PopoverHelpComponent>
220
+ </a>,
221
+ <a
222
+ key="undo"
223
+ className={`btn btn-link btn-sm ${!this.state.undoStack.canUndo() ? "disabled" : ""}`}
224
+ onClick={this.handleUndo}
225
+ >
226
+ <span className="fas fa-caret-left" />
227
+ <span className="hide-600px"> {T`Undo`}</span>
228
+ </a>,
229
+ " ",
230
+ <a
231
+ key="redo"
232
+ className={`btn btn-link btn-sm ${!this.state.undoStack.canRedo() ? "disabled" : ""}`}
233
+ onClick={this.handleRedo}
234
+ >
235
+ <span className="fas fa-caret-right" />
236
+ <span className="hide-600px"> {T`Redo`}</span>
237
+ </a>
238
+ ]}
239
+ {this.props.design.otherLocales && this.props.design.otherLocales.length > 0
240
+ ? this.renderLanguageDropdown()
241
+ : undefined}
242
+ {this.state.hideQuickfilters && this.props.design.quickfilters && this.props.design.quickfilters.length > 0 && (
243
+ <a key="showQuickfilters" className="btn btn-link btn-sm" onClick={this.handleShowQuickfilters}>
244
+ <span className="fa fa-filter" />
245
+ <span className="hide-600px"> {T`Show Quickfilters`}</span>
246
+ </a>
247
+ )}
248
+ {this.props.extraTitleButtonsElem}
249
+ <a key="toggleDesign" className="btn btn-link btn-sm" onClick={this.handleToggleDesignPanel} title={T`Toggle design panel`}>
250
+ {this.getDesign().hideDesignPanel ? (
251
+ <span className="fas fa-angle-double-left" />
252
+ ) : (
253
+ <span className="fas fa-angle-double-right" />
254
+ )}
255
+ </a>
256
+ </div>
257
+ )
258
+ }
259
+
260
+ renderHeader() {
261
+ return (
262
+ <div
263
+ style={{
264
+ padding: 4,
265
+ borderBottom: "solid 1px #e8e8e8",
266
+ gridArea: "header"
267
+ }}
268
+ >
269
+ <div style={{ float: "right" }}>{this.renderActionLinks()}</div>
270
+ {this.props.titleElem}
271
+ </div>
272
+ )
273
+ }
274
+
275
+ handleDesignChange = (design: any) => {
276
+ if (this.props.onDesignChange) {
277
+ return this.props.onDesignChange(design)
278
+ } else {
279
+ return this.setState({ transientDesign: design })
280
+ }
281
+ }
282
+
283
+ getDesign() {
284
+ if (this.props.onDesignChange) {
285
+ return this.props.design
286
+ } else {
287
+ return this.state.transientDesign || this.props.design
288
+ }
289
+ }
290
+
291
+ handleToggleDesignPanel = () => {
292
+ this.handleDesignChange({ ...this.getDesign(), hideDesignPanel: !this.getDesign().hideDesignPanel })
293
+ }
294
+
295
+ // Get the values of the quick filters
296
+ getQuickfilterValues = () => {
297
+ return this.state.quickfiltersValues || []
298
+ }
299
+
300
+ renderView() {
301
+ let filters = this.props.extraFilters || []
302
+
303
+ // Compile quickfilters
304
+ filters = filters.concat(
305
+ new QuickfilterCompiler(this.props.schema).compile(
306
+ this.props.design.quickfilters || [],
307
+ this.state.quickfiltersValues,
308
+ this.props.quickfilterLocks
309
+ )
310
+ )
311
+
312
+ const isPreviewMode = this.props.onDesignChange != null && this.state.previewLocale != null
313
+
314
+ // Determine display locale:
315
+ // - In preview mode: use previewLocale
316
+ // - In edit mode (no preview): use design locale
317
+ // - In readonly mode: use state locale
318
+ const displayLocale = isPreviewMode
319
+ ? this.state.previewLocale!
320
+ : (this.props.onDesignChange != null
321
+ ? this.props.design.locale || "en"
322
+ : this.state.locale)
323
+
324
+ return (
325
+ <AutoSizeComponent injectWidth={true} injectHeight={true}>
326
+ {(size: {
327
+ width?: number
328
+ height?: number
329
+ }) => {
330
+ return (
331
+ <MapViewComponent
332
+ mapDataSource={this.props.mapDataSource}
333
+ schema={this.props.schema}
334
+ dataSource={this.props.dataSource}
335
+ design={this.getDesign()}
336
+ onDesignChange={isPreviewMode ? undefined : this.handleDesignChange}
337
+ zoomLocked={this.state.zoomLocked}
338
+ onRowClick={this.props.onRowClick}
339
+ extraFilters={filters}
340
+ locale={displayLocale}
341
+ width={size.width!}
342
+ height={size.height!}
343
+ />
344
+ )
345
+ }}
346
+ </AutoSizeComponent>
347
+ )
348
+ }
349
+
350
+ // Get filters from props filters combined with maps filters
351
+ getCompiledFilters() {
352
+ let compiledFilters = getCompiledFilters(
353
+ this.props.design,
354
+ this.props.schema,
355
+ getFilterableTables(this.props.design, this.props.schema)
356
+ )
357
+ compiledFilters = compiledFilters.concat(this.props.extraFilters || [])
358
+ return compiledFilters
359
+ }
360
+
361
+ renderQuickfilter() {
362
+ return (
363
+ <div style={{ gridArea: "quickfilters", borderBottom: "solid 1px #e8e8e8" }}>
364
+ <QuickfiltersComponent
365
+ design={this.props.design.quickfilters || []}
366
+ schema={this.props.schema}
367
+ dataSource={this.props.dataSource}
368
+ quickfiltersDataSource={this.props.mapDataSource.getQuickfiltersDataSource()}
369
+ values={this.state.quickfiltersValues || undefined}
370
+ onValuesChange={(values: any) => this.setState({ quickfiltersValues: values })}
371
+ locks={this.props.quickfilterLocks}
372
+ filters={this.getCompiledFilters()}
373
+ hideTopBorder={this.props.hideTitleBar}
374
+ onHide={() => this.setState({ hideQuickfilters: true })}
375
+ />
376
+ </div>
377
+ )
378
+ }
379
+
380
+ /** Translate function to use for display based on current locale */
381
+ translate = (input: string): string => {
382
+ const design = this.getDesign()
383
+ const designLocale = design.locale || "en"
384
+ const isPreviewMode = this.props.onDesignChange != null && this.state.previewLocale != null
385
+
386
+ // Determine display locale:
387
+ // - In preview mode: use previewLocale
388
+ // - In edit mode (no preview): use design locale
389
+ // - In readonly mode: use state locale
390
+ const displayLocale = isPreviewMode
391
+ ? this.state.previewLocale!
392
+ : (this.props.onDesignChange != null
393
+ ? designLocale
394
+ : this.state.locale)
395
+
396
+ if (designLocale === displayLocale) {
397
+ return input
398
+ }
399
+ return design.translations?.[displayLocale]?.[input] ?? input
400
+ }
401
+
402
+ renderDesigner() {
403
+ const isPreviewMode = this.props.onDesignChange != null && this.state.previewLocale != null
404
+
405
+ return (
406
+ <div style={{ gridArea: "designer", borderLeft: "solid 2px #e8e8e8", overflowY: 'scroll' }}>
407
+ {this.props.onDesignChange && !isPreviewMode ? (
408
+ <MapDesignerComponent
409
+ schema={this.props.schema}
410
+ dataSource={this.props.dataSource}
411
+ design={this.getDesign()}
412
+ onDesignChange={this.handleDesignChange}
413
+ enableQuickfilters={this.props.enableQuickfilters}
414
+ />
415
+ ) : (
416
+ <MapControlComponent
417
+ schema={this.props.schema}
418
+ dataSource={this.props.dataSource}
419
+ design={this.getDesign()}
420
+ onDesignChange={this.handleDesignChange}
421
+ translate={this.translate}
422
+ />
423
+ )}
424
+ </div>
425
+ )
426
+ }
427
+
428
+ render() {
429
+ const designerVisible = !this.getDesign().hideDesignPanel
430
+ return (
431
+ <div
432
+ style={{
433
+ width: "100%",
434
+ height: "100%",
435
+ display: "grid",
436
+ gridTemplateColumns: designerVisible ? "70% 30%" : "100%",
437
+ gridTemplateRows: "auto auto 1fr",
438
+ gridTemplateAreas: `"header designer" "quickfilters designer" "view designer"`
439
+ }}
440
+ >
441
+ {this.props.hideTitleBar != true ? this.renderHeader() : null}
442
+ {this.state.hideQuickfilters ? null : this.renderQuickfilter()}
443
+ <div style={{ width: "100%", height: "100%", gridArea: "view", overflow: "hidden" }}>{this.renderView()}</div>
444
+ {designerVisible ? this.renderDesigner() : null}
445
+ </div>
446
+ )
447
+ }
448
+ }
@@ -0,0 +1,41 @@
1
+ import React from "react"
2
+
3
+ import MapLayersDesignerComponent from "./MapLayersDesignerComponent"
4
+ import BaseLayerDesignerComponent from "./BaseLayerDesignerComponent"
5
+ import { DataSource, Schema } from "@mwater/expressions"
6
+
7
+ export interface MapControlComponentProps {
8
+ /** Schema to use */
9
+ schema: Schema
10
+ dataSource: DataSource
11
+ /** See Map Design.md */
12
+ design: any
13
+ onDesignChange: any
14
+ /** Translate function to use for display */
15
+ translate?: (input: string) => string
16
+ }
17
+
18
+ /** Allows controlling readonly map */
19
+ export default class MapControlComponent extends React.Component<MapControlComponentProps> {
20
+ render() {
21
+ return (
22
+ <div style={{ padding: 5 }}>
23
+ <MapLayersDesignerComponent
24
+ schema={this.props.schema}
25
+ dataSource={this.props.dataSource}
26
+ design={this.props.design}
27
+ onDesignChange={this.props.onDesignChange}
28
+ allowEditingLayers={false}
29
+ translate={this.props.translate}
30
+ />
31
+
32
+ <br />
33
+
34
+ <BaseLayerDesignerComponent
35
+ design={this.props.design}
36
+ onDesignChange={this.props.onDesignChange}
37
+ />
38
+ </div>
39
+ )
40
+ }
41
+ }
@@ -48,6 +48,12 @@ export interface MapDesign {
48
48
  /** optional locale (e.g. "fr") to use for display */
49
49
  locale?: string
50
50
 
51
+ /** Other locales that the map is available in. */
52
+ otherLocales?: string[]
53
+
54
+ /** Translation map for map. Maps locale to translation map. Does not include default locale. */
55
+ translations?: { [locale: string]: { [key: string]: string } }
56
+
51
57
  /** True if design panel is hidden */
52
58
  hideDesignPanel?: boolean
53
59
  }
@@ -19,6 +19,7 @@ import { JsonQLFilter } from "../JsonQLFilter"
19
19
  import QuickfiltersDesignComponent from "../quickfilter/QuickfiltersDesignComponent"
20
20
  import produce from "immer"
21
21
  import { ActiveTablesContext } from "@mwater/expressions-ui"
22
+ import { MapTranslationsTab } from "./MapTranslationsTab"
22
23
 
23
24
  export interface MapDesignerComponentProps {
24
25
  /** Schema to use */
@@ -34,6 +35,9 @@ export interface MapDesignerComponentProps {
34
35
 
35
36
  /** True to enable quickfilters */
36
37
  enableQuickfilters?: boolean
38
+
39
+ /** True to enable translations tab. Defaults to true. Set to false when map is embedded in a dashboard. */
40
+ enableTranslations?: boolean
37
41
  }
38
42
 
39
43
  export default class MapDesignerComponent extends React.Component<MapDesignerComponentProps> {
@@ -72,7 +76,7 @@ export default class MapDesignerComponent extends React.Component<MapDesignerCom
72
76
 
73
77
  renderOptionsTab() {
74
78
  return (
75
- <div>
79
+ <div style={{ marginBottom: 200 }}>
76
80
  <BaseLayerDesignerComponent
77
81
  design={this.props.design}
78
82
  onDesignChange={this.props.onDesignChange}
@@ -154,6 +158,19 @@ export default class MapDesignerComponent extends React.Component<MapDesignerCom
154
158
  design={this.props.design}
155
159
  onDesignChange={this.props.onDesignChange}
156
160
  />
161
+
162
+ {/* Translations section - only shown when enableTranslations is true (default) */}
163
+ {this.props.enableTranslations !== false && (
164
+ <>
165
+ <hr />
166
+ <h5>{T`Translations`}</h5>
167
+ <MapTranslationsTab
168
+ schema={this.props.schema}
169
+ design={this.props.design}
170
+ onDesignChange={this.props.onDesignChange}
171
+ />
172
+ </>
173
+ )}
157
174
  </div>
158
175
  )
159
176
  }
@@ -8,11 +8,6 @@ export interface MapLayerDataSource {
8
8
  * Only called on layers that are valid */
9
9
  getTileUrl(design: any, filters: JsonQLFilter[]): string | null
10
10
 
11
- /** Get the url for the interactivity tiles with the specified filters applied
12
- * Called with (design, filters) where design is the design of the layer and filters are filters to apply. Returns URL
13
- * Only called on layers that are valid */
14
- getUtfGridUrl(design: any, filters: JsonQLFilter[]): string | null
15
-
16
11
  /** Get the url for vector tile source with an expiry time. Only for layers of type "VectorTile"
17
12
  * @param createdAfter ISO 8601 timestamp requiring that tile source on server is created after specified datetime
18
13
  */
@@ -25,6 +25,8 @@ export interface MapLayerViewDesignerComponentProps {
25
25
  connectDropTarget?: any
26
26
  allowEditingLayer: boolean
27
27
  filters?: any
28
+ /** Translate function to use for display */
29
+ translate?: (input: string) => string
28
30
  }
29
31
 
30
32
  interface MapLayerViewDesignerComponentState {
@@ -36,7 +38,7 @@ export default class MapLayerViewDesignerComponent extends React.Component<
36
38
  MapLayerViewDesignerComponentProps,
37
39
  MapLayerViewDesignerComponentState
38
40
  > {
39
- constructor(props: any) {
41
+ constructor(props: MapLayerViewDesignerComponentProps) {
40
42
  super(props)
41
43
 
42
44
  const layer = LayerFactory.createLayer(this.props.layerView.type)
@@ -127,10 +129,15 @@ export default class MapLayerViewDesignerComponent extends React.Component<
127
129
  }
128
130
 
129
131
  renderName() {
132
+ // Translate the name if a translate function is provided
133
+ const displayName = this.props.translate
134
+ ? this.props.translate(this.props.layerView.name)
135
+ : this.props.layerView.name
136
+
130
137
  return R(
131
138
  "span",
132
139
  { className: "hover-display-parent", onClick: this.handleRename, style: { cursor: "pointer" } },
133
- this.props.layerView.name,
140
+ displayName,
134
141
  " ",
135
142
  R("span", { className: "hover-display-child fas fa-pencil-alt text-muted" })
136
143
  )
@@ -21,6 +21,8 @@ export interface MapLayersDesignerComponentProps {
21
21
  /** True to allow editing layers */
22
22
  allowEditingLayers: boolean
23
23
  filters?: any
24
+ /** Translate function to use for display */
25
+ translate?: (input: string) => string
24
26
  }
25
27
 
26
28
  // Designer for layer selection in the map
@@ -102,7 +104,8 @@ export default class MapLayersDesignerComponent extends React.Component<MapLayer
102
104
  connectDragPreview,
103
105
  connectDropTarget,
104
106
  allowEditingLayer: this.props.allowEditingLayers,
105
- filters: _.compact(filters)
107
+ filters: _.compact(filters),
108
+ translate: this.props.translate
106
109
  })
107
110
  )
108
111
  }
@@ -0,0 +1,53 @@
1
+ import React, { useMemo } from "react"
2
+ import { Schema } from "@mwater/expressions"
3
+ import { MapDesign } from "./MapDesign"
4
+ import { TranslationsTabComponent } from "../TranslationsTabComponent"
5
+ import * as MapUtils from "./MapUtils"
6
+ import produce from "immer"
7
+
8
+ export interface MapTranslationsTabProps {
9
+ /** Schema to use */
10
+ schema: Schema
11
+ /** Map design */
12
+ design: MapDesign
13
+ /** Called with new design */
14
+ onDesignChange: (design: MapDesign) => void
15
+ }
16
+
17
+ /**
18
+ * Translations tab for map designer that wraps the reusable TranslationsTabComponent
19
+ */
20
+ export function MapTranslationsTab(props: MapTranslationsTabProps) {
21
+ const { design, onDesignChange, schema } = props
22
+
23
+ const translatableStrings = useMemo(
24
+ () => MapUtils.getTranslatableStrings(design, schema),
25
+ [design, schema]
26
+ )
27
+
28
+ return (
29
+ <TranslationsTabComponent
30
+ locale={design.locale || "en"}
31
+ otherLocales={design.otherLocales || []}
32
+ translations={design.translations || {}}
33
+ translatableStrings={translatableStrings}
34
+ onLocaleChange={(locale) =>
35
+ onDesignChange(produce(design, draft => {
36
+ draft.locale = locale
37
+ }))
38
+ }
39
+ onOtherLocalesChange={(otherLocales) =>
40
+ onDesignChange(produce(design, draft => {
41
+ draft.otherLocales = otherLocales
42
+ }))
43
+ }
44
+ onTranslationsChange={(translations) =>
45
+ onDesignChange(produce(design, draft => {
46
+ draft.translations = translations
47
+ }))
48
+ }
49
+ downloadFilename="Map Translations.xlsx"
50
+ baseLanguageDescription={T`This is the language that the map is written in.`}
51
+ />
52
+ )
53
+ }