@mwater/visualization 5.5.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.
- package/lib/ColorComponent.js +2 -2
- package/lib/MWaterContextComponent.d.ts +1 -1
- package/lib/MWaterGlobalFiltersComponent.d.ts +2 -2
- package/lib/MWaterGlobalFiltersComponent.js +11 -20
- package/lib/MWaterLoaderComponent.d.ts +4 -13
- package/lib/MWaterLoaderComponent.js +2 -11
- package/lib/TranslationsTabComponent.d.ts +34 -0
- package/lib/TranslationsTabComponent.js +256 -0
- package/lib/UndoStack.d.ts +2 -1
- package/lib/UndoStack.js +12 -6
- package/lib/dashboards/DashboardComponent.js +6 -5
- package/lib/dashboards/DashboardDesign.d.ts +1 -1
- package/lib/dashboards/ServerDashboardDataSource.d.ts +0 -1
- package/lib/dashboards/ServerDashboardDataSource.js +0 -25
- package/lib/dashboards/SettingsModalComponent.js +9 -233
- package/lib/datagrids/DatagridComponent.js +27 -2
- package/lib/datagrids/DatagridDesignerComponent.d.ts +2 -3
- package/lib/datagrids/DatagridDesignerComponent.js +108 -120
- package/lib/datagrids/DatagridViewComponent.js +33 -6
- package/lib/datagrids/OrderBysDesignerComponent.d.ts +7 -7
- package/lib/datagrids/OrderBysDesignerComponent.js +19 -28
- package/lib/index.css +45 -2
- package/lib/index.d.ts +5 -5
- package/lib/index.js +2 -3
- package/lib/layouts/blocks/BlocksDisplayComponent.d.ts +8 -1
- package/lib/layouts/blocks/BlocksDisplayComponent.js +46 -4
- package/lib/maps/BufferLayer.d.ts +0 -13
- package/lib/maps/BufferLayer.js +24 -237
- package/lib/maps/BufferLayerDesign.d.ts +1 -1
- package/lib/maps/BufferLayerDesignerComponent.d.ts +1 -1
- package/lib/maps/BufferLayerDesignerComponent.js +2 -7
- package/lib/maps/ChoroplethLayer.d.ts +1 -16
- package/lib/maps/ChoroplethLayer.js +25 -358
- package/lib/maps/ChoroplethLayerDesign.d.ts +5 -2
- package/lib/maps/ChoroplethLayerDesigner.d.ts +10 -32
- package/lib/maps/ChoroplethLayerDesigner.js +58 -89
- package/lib/maps/ClusterLayer.d.ts +0 -9
- package/lib/maps/ClusterLayer.js +0 -250
- package/lib/maps/DirectMapDataSource.js +1 -48
- package/lib/maps/EditHoverOver.d.ts +4 -3
- package/lib/maps/EditHoverOver.js +3 -3
- package/lib/maps/GridLayer.d.ts +0 -15
- package/lib/maps/GridLayer.js +0 -212
- package/lib/maps/HoverContent.js +1 -1
- package/lib/maps/Layer.d.ts +1 -26
- package/lib/maps/Layer.js +0 -13
- package/lib/maps/LeafletMapComponent.js +10 -19
- package/lib/maps/MapComponent.d.ts +19 -35
- package/lib/maps/MapComponent.js +135 -77
- package/lib/maps/MapControlComponent.d.ts +4 -5
- package/lib/maps/MapControlComponent.js +5 -12
- package/lib/maps/MapDesign.d.ts +8 -0
- package/lib/maps/MapDesignerComponent.d.ts +2 -0
- package/lib/maps/MapDesignerComponent.js +7 -2
- package/lib/maps/MapLayerDataSource.d.ts +0 -4
- package/lib/maps/MapLayerViewDesignerComponent.d.ts +3 -1
- package/lib/maps/MapLayerViewDesignerComponent.js +5 -1
- package/lib/maps/MapLayersDesignerComponent.d.ts +2 -0
- package/lib/maps/MapLayersDesignerComponent.js +2 -1
- package/lib/maps/MapTranslationsTab.d.ts +15 -0
- package/lib/maps/MapTranslationsTab.js +47 -0
- package/lib/maps/MapUtils.d.ts +11 -0
- package/lib/maps/MapUtils.js +57 -1
- package/lib/maps/MapViewComponent.d.ts +1 -1
- package/lib/maps/MapViewComponent.js +1 -8
- package/lib/maps/MarkersLayer.d.ts +1 -14
- package/lib/maps/MarkersLayer.js +89 -254
- package/lib/maps/MarkersLayerDesign.d.ts +5 -1
- package/lib/maps/MarkersLayerDesignerComponent.d.ts +32 -57
- package/lib/maps/MarkersLayerDesignerComponent.js +158 -134
- package/lib/maps/ServerMapDataSource.d.ts +0 -1
- package/lib/maps/ServerMapDataSource.js +0 -25
- package/lib/maps/SwitchableTileUrlLayer.d.ts +0 -2
- package/lib/maps/SwitchableTileUrlLayer.js +0 -9
- package/lib/maps/TileUrlLayer.d.ts +0 -1
- package/lib/maps/TileUrlLayer.js +0 -5
- package/lib/maps/VectorMapViewComponent.js +13 -10
- package/lib/maps/symbols/font-awesome/asterisk.png +0 -0
- package/lib/maps/symbols/font-awesome/ban.png +0 -0
- package/lib/maps/symbols/font-awesome/beer.png +0 -0
- package/lib/maps/symbols/font-awesome/bell.png +0 -0
- package/lib/maps/symbols/font-awesome/bolt.png +0 -0
- package/lib/maps/symbols/font-awesome/building.png +0 -0
- package/lib/maps/symbols/font-awesome/bullseye.png +0 -0
- package/lib/maps/symbols/font-awesome/bus.png +0 -0
- package/lib/maps/symbols/font-awesome/caret-up.png +0 -0
- package/lib/maps/symbols/font-awesome/certificate.png +0 -0
- package/lib/maps/symbols/font-awesome/check-circle.png +0 -0
- package/lib/maps/symbols/font-awesome/check.png +0 -0
- package/lib/maps/symbols/font-awesome/chevron-circle-down.png +0 -0
- package/lib/maps/symbols/font-awesome/chevron-circle-up.png +0 -0
- package/lib/maps/symbols/font-awesome/cloud-rain.png +0 -0
- package/lib/maps/symbols/font-awesome/cloud.png +0 -0
- package/lib/maps/symbols/font-awesome/comment.png +0 -0
- package/lib/maps/symbols/font-awesome/crosshairs.png +0 -0
- package/lib/maps/symbols/font-awesome/dot-circle-o.png +0 -0
- package/lib/maps/symbols/font-awesome/exclamation-circle.png +0 -0
- package/lib/maps/symbols/font-awesome/exclamation-triangle.png +0 -0
- package/lib/maps/symbols/font-awesome/female.png +0 -0
- package/lib/maps/symbols/font-awesome/file.png +0 -0
- package/lib/maps/symbols/font-awesome/flag.png +0 -0
- package/lib/maps/symbols/font-awesome/flask.png +0 -0
- package/lib/maps/symbols/font-awesome/h-square.png +0 -0
- package/lib/maps/symbols/font-awesome/home.png +0 -0
- package/lib/maps/symbols/font-awesome/info-circle.png +0 -0
- package/lib/maps/symbols/font-awesome/male.png +0 -0
- package/lib/maps/symbols/font-awesome/medkit.png +0 -0
- package/lib/maps/symbols/font-awesome/mobile.png +0 -0
- package/lib/maps/symbols/font-awesome/plus-circle.png +0 -0
- package/lib/maps/symbols/font-awesome/plus-square.png +0 -0
- package/lib/maps/symbols/font-awesome/plus.png +0 -0
- package/lib/maps/symbols/font-awesome/square.png +0 -0
- package/lib/maps/symbols/font-awesome/star.png +0 -0
- package/lib/maps/symbols/font-awesome/thumbs-down.png +0 -0
- package/lib/maps/symbols/font-awesome/thumbs-up.png +0 -0
- package/lib/maps/symbols/font-awesome/ticket.png +0 -0
- package/lib/maps/symbols/font-awesome/times-circle.png +0 -0
- package/lib/maps/symbols/font-awesome/times.png +0 -0
- package/lib/maps/symbols/font-awesome/tint.png +0 -0
- package/lib/maps/symbols/font-awesome/tree.png +0 -0
- package/lib/maps/symbols/font-awesome/university.png +0 -0
- package/lib/maps/symbols/font-awesome/usd.png +0 -0
- package/lib/maps/symbols/font-awesome/user.png +0 -0
- package/lib/maps/symbols/font-awesome/users.png +0 -0
- package/lib/maps/symbols/font-awesome/wheelchair.png +0 -0
- package/lib/maps/symbols/sdf-ize.sh +93 -0
- package/lib/maps/vectorMaps.d.ts +6 -6
- package/lib/maps/vectorMaps.js +33 -45
- package/lib/mwater_table_selection/IndicatorsListComponent.d.ts +4 -2
- package/lib/mwater_table_selection/IndicatorsListComponent.js +103 -34
- package/lib/mwater_table_selection/MWaterCalculatedDataSourcesListComponent.d.ts +18 -0
- package/lib/mwater_table_selection/MWaterCalculatedDataSourcesListComponent.js +80 -0
- package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.d.ts +26 -0
- package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.js +237 -51
- package/lib/mwater_table_selection/MWaterTableSelectComponent.d.ts +2 -2
- package/lib/mwater_table_selection/MWaterTableSelectComponent.js +9 -4
- package/lib/mwater_table_selection/MWaterWorkflowsSelectComponent.d.ts +19 -0
- package/lib/mwater_table_selection/MWaterWorkflowsSelectComponent.js +111 -0
- package/lib/quickfilter/QuickfiltersComponent.d.ts +3 -102
- package/lib/quickfilter/QuickfiltersComponent.js +53 -110
- package/lib/quickfilter/TextLiteralComponent.d.ts +23 -47
- package/lib/quickfilter/TextLiteralComponent.js +85 -82
- package/lib/widgets/MapWidget.js +6 -3
- package/lib/widgets/text/ExprItemEditorComponent.d.ts +3 -8
- package/lib/widgets/text/ExprItemEditorComponent.js +36 -33
- package/lib/widgets/text/ExprUpdateModalComponent.d.ts +1 -0
- package/package.json +3 -4
- package/src/ColorComponent.tsx +2 -2
- package/src/MWaterContextComponent.tsx +1 -1
- package/src/{MWaterGlobalFiltersComponent.ts → MWaterGlobalFiltersComponent.tsx} +32 -33
- package/src/{MWaterLoaderComponent.ts → MWaterLoaderComponent.tsx} +17 -18
- package/src/TranslationsTabComponent.tsx +429 -0
- package/src/UndoStack.ts +14 -6
- package/src/dashboards/DashboardComponent.tsx +6 -5
- package/src/dashboards/DashboardDesign.ts +1 -1
- package/src/dashboards/ServerDashboardDataSource.ts +0 -31
- package/src/dashboards/SettingsModalComponent.tsx +27 -383
- package/src/datagrids/DatagridComponent.tsx +36 -2
- package/src/datagrids/DatagridDesignerComponent.tsx +241 -229
- package/src/datagrids/DatagridViewComponent.tsx +44 -7
- package/src/datagrids/OrderBysDesignerComponent.tsx +61 -70
- package/src/index.css +45 -2
- package/src/index.ts +5 -11
- package/src/layouts/blocks/BlocksDisplayComponent.tsx +60 -5
- package/src/maps/BufferLayer.ts +30 -263
- package/src/maps/BufferLayerDesign.ts +1 -1
- package/src/maps/BufferLayerDesignerComponent.tsx +2 -7
- package/src/maps/ChoroplethLayer.ts +30 -394
- package/src/maps/ChoroplethLayerDesign.ts +5 -2
- package/src/maps/ChoroplethLayerDesigner.tsx +169 -165
- package/src/maps/ClusterLayer.ts +0 -274
- package/src/maps/DirectMapDataSource.ts +2 -61
- package/src/maps/EditHoverOver.tsx +9 -5
- package/src/maps/GridLayer.ts +0 -224
- package/src/maps/HoverContent.tsx +1 -1
- package/src/maps/Layer.ts +1 -35
- package/src/maps/LeafletMapComponent.tsx +10 -19
- package/src/maps/MapComponent.tsx +448 -0
- package/src/maps/MapControlComponent.tsx +41 -0
- package/src/maps/MapDesign.ts +6 -0
- package/src/maps/MapDesignerComponent.tsx +18 -1
- package/src/maps/MapLayerDataSource.ts +0 -5
- package/src/maps/MapLayerViewDesignerComponent.ts +9 -2
- package/src/maps/MapLayersDesignerComponent.ts +4 -1
- package/src/maps/MapTranslationsTab.tsx +53 -0
- package/src/maps/MapUtils.ts +61 -1
- package/src/maps/MapViewComponent.tsx +2 -8
- package/src/maps/MarkersLayer.ts +101 -275
- package/src/maps/MarkersLayerDesign.ts +7 -1
- package/src/maps/MarkersLayerDesignerComponent.tsx +436 -0
- package/src/maps/ServerMapDataSource.ts +0 -31
- package/src/maps/SwitchableTileUrlLayer.tsx +0 -11
- package/src/maps/TileUrlLayer.tsx +0 -6
- package/src/maps/VectorMapViewComponent.tsx +15 -15
- package/src/maps/symbols/font-awesome/asterisk.png +0 -0
- package/src/maps/symbols/font-awesome/ban.png +0 -0
- package/src/maps/symbols/font-awesome/beer.png +0 -0
- package/src/maps/symbols/font-awesome/bell.png +0 -0
- package/src/maps/symbols/font-awesome/bolt.png +0 -0
- package/src/maps/symbols/font-awesome/building.png +0 -0
- package/src/maps/symbols/font-awesome/bullseye.png +0 -0
- package/src/maps/symbols/font-awesome/bus.png +0 -0
- package/src/maps/symbols/font-awesome/caret-up.png +0 -0
- package/src/maps/symbols/font-awesome/certificate.png +0 -0
- package/src/maps/symbols/font-awesome/check-circle.png +0 -0
- package/src/maps/symbols/font-awesome/check.png +0 -0
- package/src/maps/symbols/font-awesome/chevron-circle-down.png +0 -0
- package/src/maps/symbols/font-awesome/chevron-circle-up.png +0 -0
- package/src/maps/symbols/font-awesome/cloud-rain.png +0 -0
- package/src/maps/symbols/font-awesome/cloud.png +0 -0
- package/src/maps/symbols/font-awesome/comment.png +0 -0
- package/src/maps/symbols/font-awesome/crosshairs.png +0 -0
- package/src/maps/symbols/font-awesome/dot-circle-o.png +0 -0
- package/src/maps/symbols/font-awesome/exclamation-circle.png +0 -0
- package/src/maps/symbols/font-awesome/exclamation-triangle.png +0 -0
- package/src/maps/symbols/font-awesome/female.png +0 -0
- package/src/maps/symbols/font-awesome/file.png +0 -0
- package/src/maps/symbols/font-awesome/flag.png +0 -0
- package/src/maps/symbols/font-awesome/flask.png +0 -0
- package/src/maps/symbols/font-awesome/h-square.png +0 -0
- package/src/maps/symbols/font-awesome/home.png +0 -0
- package/src/maps/symbols/font-awesome/info-circle.png +0 -0
- package/src/maps/symbols/font-awesome/male.png +0 -0
- package/src/maps/symbols/font-awesome/medkit.png +0 -0
- package/src/maps/symbols/font-awesome/mobile.png +0 -0
- package/src/maps/symbols/font-awesome/plus-circle.png +0 -0
- package/src/maps/symbols/font-awesome/plus-square.png +0 -0
- package/src/maps/symbols/font-awesome/plus.png +0 -0
- package/src/maps/symbols/font-awesome/square.png +0 -0
- package/src/maps/symbols/font-awesome/star.png +0 -0
- package/src/maps/symbols/font-awesome/thumbs-down.png +0 -0
- package/src/maps/symbols/font-awesome/thumbs-up.png +0 -0
- package/src/maps/symbols/font-awesome/ticket.png +0 -0
- package/src/maps/symbols/font-awesome/times-circle.png +0 -0
- package/src/maps/symbols/font-awesome/times.png +0 -0
- package/src/maps/symbols/font-awesome/tint.png +0 -0
- package/src/maps/symbols/font-awesome/tree.png +0 -0
- package/src/maps/symbols/font-awesome/university.png +0 -0
- package/src/maps/symbols/font-awesome/usd.png +0 -0
- package/src/maps/symbols/font-awesome/user.png +0 -0
- package/src/maps/symbols/font-awesome/users.png +0 -0
- package/src/maps/symbols/font-awesome/wheelchair.png +0 -0
- package/src/maps/symbols/sdf-ize.sh +93 -0
- package/src/maps/vectorMaps.tsx +32 -53
- package/src/mwater_table_selection/IndicatorsListComponent.tsx +165 -37
- package/src/mwater_table_selection/MWaterCalculatedDataSourcesListComponent.tsx +111 -0
- package/src/mwater_table_selection/MWaterCompleteTableSelectComponent.tsx +373 -37
- package/src/mwater_table_selection/MWaterTableSelectComponent.tsx +12 -8
- package/src/mwater_table_selection/MWaterWorkflowsSelectComponent.tsx +159 -0
- package/src/quickfilter/{QuickfiltersComponent.ts → QuickfiltersComponent.tsx} +165 -158
- package/src/quickfilter/TextLiteralComponent.tsx +197 -0
- package/src/widgets/MapWidget.tsx +11 -1
- package/src/widgets/text/ExprItemEditorComponent.tsx +83 -77
- package/src/widgets/text/ExprUpdateModalComponent.tsx +1 -0
- package/test/UndoStackTests.ts +52 -1
- package/.storybook/config.js +0 -7
- package/.storybook/head.html +0 -3
- package/.storybook/webpack.config.js +0 -15
- package/src/maps/BingLayer.ts +0 -146
- package/src/maps/MapComponent.ts +0 -312
- package/src/maps/MapControlComponent.ts +0 -46
- package/src/maps/MarkersLayerDesignerComponent.ts +0 -374
- package/src/maps/RasterMapViewComponent.ts +0 -345
- package/src/quickfilter/TextLiteralComponent.ts +0 -165
- package/stories/UpdateableComponent.js +0 -29
- package/stories/consoles.js +0 -202
- package/stories/dashboards.js +0 -217
- package/stories/datagridDesign.js +0 -114
- package/stories/datagrids.js +0 -69
- package/stories/dates.js +0 -80
- package/stories/exprcomponent.js +0 -43
- package/stories/index.js +0 -18
- package/stories/leaflet.js +0 -59
- package/stories/maps.js +0 -24
- package/stories/pivotChart.js +0 -235
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
import _ from "lodash"
|
|
2
|
+
import React, { useMemo, useRef, useState } from "react"
|
|
3
|
+
import { languages } from "./languages"
|
|
4
|
+
import { default as ReactSelect } from "react-select"
|
|
5
|
+
import { LocalizedString } from "@mwater/expressions"
|
|
6
|
+
import produce from "immer"
|
|
7
|
+
import FileSaver from "file-saver"
|
|
8
|
+
import * as localizeUtils from "ez-localize/lib/utils"
|
|
9
|
+
import { canAutoTranslate, translateStrings } from "./autotranslate"
|
|
10
|
+
import { FormGroup } from "@mwater/react-library/lib/bootstrap"
|
|
11
|
+
|
|
12
|
+
export interface TranslationsTabComponentProps {
|
|
13
|
+
/** Base locale of the design (e.g. "en") */
|
|
14
|
+
locale: string
|
|
15
|
+
|
|
16
|
+
/** Other locales the design is translated into */
|
|
17
|
+
otherLocales: string[]
|
|
18
|
+
|
|
19
|
+
/** Translation mappings per locale. Maps locale to { originalString: translatedString } */
|
|
20
|
+
translations: { [locale: string]: { [key: string]: string } }
|
|
21
|
+
|
|
22
|
+
/** All strings that need translation */
|
|
23
|
+
translatableStrings: string[]
|
|
24
|
+
|
|
25
|
+
/** Called when base locale changes */
|
|
26
|
+
onLocaleChange: (locale: string) => void
|
|
27
|
+
|
|
28
|
+
/** Called when other locales change */
|
|
29
|
+
onOtherLocalesChange: (locales: string[]) => void
|
|
30
|
+
|
|
31
|
+
/** Called when translations change */
|
|
32
|
+
onTranslationsChange: (translations: { [locale: string]: { [key: string]: string } }) => void
|
|
33
|
+
|
|
34
|
+
/** Custom filename for download. Defaults to "Translations.xlsx" */
|
|
35
|
+
downloadFilename?: string
|
|
36
|
+
|
|
37
|
+
/** Custom description for base language section */
|
|
38
|
+
baseLanguageDescription?: string
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Reusable translations tab component for managing localization of designs
|
|
43
|
+
* (dashboards, maps, etc.) that have locale, otherLocales, and translations properties.
|
|
44
|
+
*/
|
|
45
|
+
export function TranslationsTabComponent(props: TranslationsTabComponentProps) {
|
|
46
|
+
const {
|
|
47
|
+
locale,
|
|
48
|
+
otherLocales,
|
|
49
|
+
translations,
|
|
50
|
+
translatableStrings,
|
|
51
|
+
onLocaleChange,
|
|
52
|
+
onOtherLocalesChange,
|
|
53
|
+
onTranslationsChange,
|
|
54
|
+
downloadFilename = "Translations.xlsx",
|
|
55
|
+
baseLanguageDescription = T`This is the base language.`
|
|
56
|
+
} = props
|
|
57
|
+
|
|
58
|
+
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
59
|
+
|
|
60
|
+
const localeOptions = useMemo(() => {
|
|
61
|
+
return _.sortBy(
|
|
62
|
+
_.map(languages, (language) => ({
|
|
63
|
+
value: language.code,
|
|
64
|
+
label: `${language.en} (${language.name})`
|
|
65
|
+
})),
|
|
66
|
+
'label'
|
|
67
|
+
)
|
|
68
|
+
}, [])
|
|
69
|
+
|
|
70
|
+
// Get available languages that aren't already selected
|
|
71
|
+
const availableLocaleOptions = useMemo(() => {
|
|
72
|
+
const selectedLocales = new Set([locale, ...otherLocales])
|
|
73
|
+
return localeOptions.filter(opt => !selectedLocales.has(opt.value))
|
|
74
|
+
}, [localeOptions, locale, otherLocales])
|
|
75
|
+
|
|
76
|
+
// Calculate percentage of strings translated for each locale
|
|
77
|
+
const translationPercentages = useMemo(() => {
|
|
78
|
+
const percentages: { [locale: string]: number } = {}
|
|
79
|
+
const totalStrings = translatableStrings.length
|
|
80
|
+
|
|
81
|
+
for (const loc of otherLocales) {
|
|
82
|
+
const translatedCount = translatableStrings.filter(str =>
|
|
83
|
+
translations?.[loc]?.[str] != null
|
|
84
|
+
).length
|
|
85
|
+
|
|
86
|
+
// Round down to nearest percent
|
|
87
|
+
percentages[loc] = (totalStrings > 0) ? Math.floor((translatedCount / totalStrings) * 100) : 0
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return percentages
|
|
91
|
+
}, [translations, otherLocales, translatableStrings])
|
|
92
|
+
|
|
93
|
+
const handleAddLocale = (selectedLocale: any) => {
|
|
94
|
+
const newOtherLocales = [...otherLocales, selectedLocale.value]
|
|
95
|
+
onOtherLocalesChange(newOtherLocales)
|
|
96
|
+
// Note: We don't initialize translations here because calling both callbacks
|
|
97
|
+
// in sequence would cause a race condition where the second overwrites the first.
|
|
98
|
+
// The code handles undefined translations via optional chaining.
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const handleRemoveLocale = (localeToRemove: string) => {
|
|
102
|
+
const newOtherLocales = otherLocales.filter(loc => loc !== localeToRemove)
|
|
103
|
+
onOtherLocalesChange(newOtherLocales)
|
|
104
|
+
// Note: We don't remove translations here for the same reason as above.
|
|
105
|
+
// Orphaned translations are harmless and will be cleaned up on next save.
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Convert translations to LocalizedString format
|
|
109
|
+
const getLocalizedStrings = (): LocalizedString[] => {
|
|
110
|
+
const localizedStrings: LocalizedString[] = []
|
|
111
|
+
for (const str of translatableStrings) {
|
|
112
|
+
const localizedString: LocalizedString = { _base: locale }
|
|
113
|
+
localizedString[locale] = str
|
|
114
|
+
|
|
115
|
+
// Only add translations for other locales if they exist
|
|
116
|
+
for (const otherLocale of otherLocales) {
|
|
117
|
+
if (translations?.[otherLocale]?.[str]) {
|
|
118
|
+
localizedString[otherLocale] = translations[otherLocale][str]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
localizedStrings.push(localizedString)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return localizedStrings
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Convert LocalizedString format back to translations
|
|
129
|
+
const updateFromLocalizedStrings = (localizedStrings: LocalizedString[]) => {
|
|
130
|
+
const newTranslations: { [locale: string]: { [key: string]: string } } = {}
|
|
131
|
+
|
|
132
|
+
// Initialize translations object
|
|
133
|
+
for (const loc of otherLocales) {
|
|
134
|
+
newTranslations[loc] = {}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Add all translations
|
|
138
|
+
for (const str of localizedStrings) {
|
|
139
|
+
for (const loc of otherLocales) {
|
|
140
|
+
if (str[loc]) {
|
|
141
|
+
newTranslations[loc][str[str._base]] = str[loc]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return newTranslations
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const handleDownload = () => {
|
|
150
|
+
// Get strings in LocalizedString format
|
|
151
|
+
const strings = getLocalizedStrings()
|
|
152
|
+
|
|
153
|
+
// Create xlsx base64 using all locales (primary + other)
|
|
154
|
+
const locales = [{ code: locale, name: locale }]
|
|
155
|
+
.concat(otherLocales.map(code => ({ code, name: code })))
|
|
156
|
+
|
|
157
|
+
const base64 = localizeUtils.exportXlsx(locales, strings)
|
|
158
|
+
|
|
159
|
+
// Download
|
|
160
|
+
FileSaver.saveAs(
|
|
161
|
+
b64toBlob(base64, "application/octet-stream"),
|
|
162
|
+
downloadFilename
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const handleUpload = () => {
|
|
167
|
+
fileInputRef.current?.click()
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const handleUploadChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
|
171
|
+
const reader = new FileReader()
|
|
172
|
+
|
|
173
|
+
reader.onload = (file) => {
|
|
174
|
+
if (!file.target?.result) {
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const base64 = (file.target.result as string).split(",")[1]
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
// Create locales array for import
|
|
182
|
+
const locales = [{ code: locale, name: locale }]
|
|
183
|
+
.concat(otherLocales.map(code => ({ code, name: code })))
|
|
184
|
+
|
|
185
|
+
// Import updates
|
|
186
|
+
const updates = localizeUtils.importXlsx(locales, base64)
|
|
187
|
+
|
|
188
|
+
// If nothing localized
|
|
189
|
+
if (updates.length === 0) {
|
|
190
|
+
alert(T`No translation data found in file`)
|
|
191
|
+
return
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Convert back to translations format and update
|
|
195
|
+
const newTranslations = updateFromLocalizedStrings(updates)
|
|
196
|
+
onTranslationsChange(newTranslations)
|
|
197
|
+
|
|
198
|
+
alert(T`${updates.length} translations applied`)
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error("Invalid xlsx file:", error)
|
|
201
|
+
alert(T`Invalid xlsx file`)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (evt.target.files?.[0]) {
|
|
206
|
+
reader.readAsDataURL(evt.target.files[0])
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const handleTranslationsChangeForLocale = (loc: string, newLocaleTranslations: { [key: string]: string }) => {
|
|
211
|
+
const newTranslations = { ...translations }
|
|
212
|
+
newTranslations[loc] = newLocaleTranslations
|
|
213
|
+
onTranslationsChange(newTranslations)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<>
|
|
218
|
+
<FormGroup
|
|
219
|
+
label={T`Base Language`}
|
|
220
|
+
labelMuted={true}
|
|
221
|
+
help={baseLanguageDescription}
|
|
222
|
+
>
|
|
223
|
+
<ReactSelect
|
|
224
|
+
value={_.findWhere(localeOptions, { value: locale }) || null}
|
|
225
|
+
options={localeOptions}
|
|
226
|
+
onChange={(selected: any) => onLocaleChange(selected.value)}
|
|
227
|
+
/>
|
|
228
|
+
</FormGroup>
|
|
229
|
+
<FormGroup
|
|
230
|
+
label={T`Additional Languages`}
|
|
231
|
+
labelMuted={true}
|
|
232
|
+
help={T`Add languages to translate into`}
|
|
233
|
+
>
|
|
234
|
+
{/* Show current additional languages */}
|
|
235
|
+
<table>
|
|
236
|
+
<tbody>
|
|
237
|
+
{otherLocales.map(loc => {
|
|
238
|
+
const localeOption = _.findWhere(localeOptions, { value: loc })
|
|
239
|
+
if (!localeOption) {
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return (
|
|
244
|
+
<tr key={loc}>
|
|
245
|
+
<td style={{ paddingRight: 10 }}>{localeOption.label}</td>
|
|
246
|
+
<td className={translationPercentages[loc] === 100 ? "text-success" : "text-warning"} style={{ textAlign: "right" }}>
|
|
247
|
+
{T`${translationPercentages[loc]}% translated`}
|
|
248
|
+
</td>
|
|
249
|
+
<td>
|
|
250
|
+
{translationPercentages[loc] < 100 && (
|
|
251
|
+
<AutoTranslateLink
|
|
252
|
+
locale={loc}
|
|
253
|
+
baseLocale={locale}
|
|
254
|
+
strings={translatableStrings}
|
|
255
|
+
existingTranslations={translations?.[loc] || {}}
|
|
256
|
+
onTranslationsChange={(newLocaleTranslations) => handleTranslationsChangeForLocale(loc, newLocaleTranslations)}
|
|
257
|
+
/>
|
|
258
|
+
)}
|
|
259
|
+
</td>
|
|
260
|
+
<td>
|
|
261
|
+
<button
|
|
262
|
+
type="button"
|
|
263
|
+
className="btn btn-sm btn-link"
|
|
264
|
+
onClick={() => handleRemoveLocale(loc)}
|
|
265
|
+
>
|
|
266
|
+
<i className="fa fa-times" />
|
|
267
|
+
</button>
|
|
268
|
+
</td>
|
|
269
|
+
</tr>
|
|
270
|
+
)
|
|
271
|
+
})}
|
|
272
|
+
</tbody>
|
|
273
|
+
</table>
|
|
274
|
+
|
|
275
|
+
{/* Add new language dropdown */}
|
|
276
|
+
{availableLocaleOptions.length > 0 && (
|
|
277
|
+
<div className="mt-3">
|
|
278
|
+
<ReactSelect
|
|
279
|
+
value={null}
|
|
280
|
+
options={availableLocaleOptions}
|
|
281
|
+
onChange={handleAddLocale}
|
|
282
|
+
placeholder={T`Add language...`}
|
|
283
|
+
/>
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
|
+
</FormGroup>
|
|
287
|
+
|
|
288
|
+
{/* Add translation management section if there are additional languages */}
|
|
289
|
+
{otherLocales.length > 0 && (
|
|
290
|
+
<>
|
|
291
|
+
<FormGroup
|
|
292
|
+
label={T`Manage Translations`}
|
|
293
|
+
labelMuted={true}
|
|
294
|
+
help={T`Download and re-upload an Excel spreadsheet of text to translate:`}
|
|
295
|
+
>
|
|
296
|
+
<p>{T`Download and re-upload an Excel spreadsheet of text to translate:`}</p>
|
|
297
|
+
|
|
298
|
+
<div>
|
|
299
|
+
<button
|
|
300
|
+
type="button"
|
|
301
|
+
className="btn btn-secondary"
|
|
302
|
+
onClick={handleDownload}
|
|
303
|
+
>
|
|
304
|
+
<i className="fas fa-download me-2" />{T`Download XLSX`}
|
|
305
|
+
</button>
|
|
306
|
+
</div>
|
|
307
|
+
<div className="text-muted mt-2">
|
|
308
|
+
{T`This creates a spreadsheet that can be sent to a translator. Please do not change the first column or first row of the spreadsheet.`}
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<br />
|
|
312
|
+
<p>
|
|
313
|
+
{T`Once translation is complete, upload the file back using the button below:`}
|
|
314
|
+
</p>
|
|
315
|
+
<div>
|
|
316
|
+
<button
|
|
317
|
+
type="button"
|
|
318
|
+
className="btn btn-secondary"
|
|
319
|
+
onClick={handleUpload}
|
|
320
|
+
>
|
|
321
|
+
<i className="fas fa-upload me-2" />{T`Upload Translated XLSX`}
|
|
322
|
+
</button>
|
|
323
|
+
</div>
|
|
324
|
+
<input
|
|
325
|
+
type="file"
|
|
326
|
+
ref={fileInputRef}
|
|
327
|
+
style={{ display: "none" }}
|
|
328
|
+
onChange={handleUploadChange}
|
|
329
|
+
accept=".xlsx"
|
|
330
|
+
/>
|
|
331
|
+
</FormGroup>
|
|
332
|
+
</>
|
|
333
|
+
)}
|
|
334
|
+
</>
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
interface AutoTranslateLinkProps {
|
|
339
|
+
locale: string
|
|
340
|
+
baseLocale: string
|
|
341
|
+
strings: string[]
|
|
342
|
+
existingTranslations: { [key: string]: string }
|
|
343
|
+
onTranslationsChange: (newTranslations: { [key: string]: string }) => void
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Button that auto-translates untranslated strings using the translation service
|
|
348
|
+
*/
|
|
349
|
+
function AutoTranslateLink(props: AutoTranslateLinkProps) {
|
|
350
|
+
const { locale, baseLocale, strings, existingTranslations, onTranslationsChange } = props
|
|
351
|
+
const [isTranslating, setIsTranslating] = useState(false)
|
|
352
|
+
|
|
353
|
+
const untranslatedStrings = useMemo(() => {
|
|
354
|
+
return strings.filter(str => !existingTranslations[str])
|
|
355
|
+
}, [strings, existingTranslations])
|
|
356
|
+
|
|
357
|
+
const handleClick = async () => {
|
|
358
|
+
if (isTranslating) {
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
setIsTranslating(true)
|
|
363
|
+
try {
|
|
364
|
+
const translatedStrings = await translateStrings(untranslatedStrings, baseLocale, locale)
|
|
365
|
+
|
|
366
|
+
const newTranslations = { ...existingTranslations }
|
|
367
|
+
for (let i = 0; i < untranslatedStrings.length; i++) {
|
|
368
|
+
newTranslations[untranslatedStrings[i]] = translatedStrings[i]
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
onTranslationsChange(newTranslations)
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error("Error translating strings:", error)
|
|
374
|
+
alert(T`Error translating strings`)
|
|
375
|
+
} finally {
|
|
376
|
+
setIsTranslating(false)
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (untranslatedStrings.length === 0) {
|
|
381
|
+
return null
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!canAutoTranslate(locale)) {
|
|
385
|
+
return null
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return (
|
|
389
|
+
<button
|
|
390
|
+
type="button"
|
|
391
|
+
className="btn btn-sm btn-link"
|
|
392
|
+
onClick={handleClick}
|
|
393
|
+
disabled={isTranslating}
|
|
394
|
+
style={{ marginLeft: 5, padding: "0 5px" }}
|
|
395
|
+
>
|
|
396
|
+
{isTranslating ? (
|
|
397
|
+
<span>
|
|
398
|
+
<i className="fa fa-spinner fa-spin" />
|
|
399
|
+
{" "}
|
|
400
|
+
{T`Translating...`}
|
|
401
|
+
</span>
|
|
402
|
+
) : (
|
|
403
|
+
T`Autotranslate`
|
|
404
|
+
)}
|
|
405
|
+
</button>
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Helper function for base64 to blob conversion
|
|
411
|
+
*/
|
|
412
|
+
function b64toBlob(b64Data: string, contentType: string = "", sliceSize: number = 512) {
|
|
413
|
+
const byteCharacters = atob(b64Data)
|
|
414
|
+
const byteArrays = []
|
|
415
|
+
|
|
416
|
+
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
|
|
417
|
+
const slice = byteCharacters.slice(offset, offset + sliceSize)
|
|
418
|
+
const byteNumbers = new Array(slice.length)
|
|
419
|
+
|
|
420
|
+
for (let i = 0; i < slice.length; i++) {
|
|
421
|
+
byteNumbers[i] = slice.charCodeAt(i)
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const byteArray = new Uint8Array(byteNumbers)
|
|
425
|
+
byteArrays.push(byteArray)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return new Blob(byteArrays, { type: contentType })
|
|
429
|
+
}
|
package/src/UndoStack.ts
CHANGED
|
@@ -4,24 +4,32 @@ import _ from "lodash"
|
|
|
4
4
|
export default class UndoStack {
|
|
5
5
|
undoStack: any[]
|
|
6
6
|
redoStack: any[]
|
|
7
|
+
maxDepth?: number
|
|
7
8
|
|
|
8
|
-
constructor(undoStack?: any, redoStack?: any) {
|
|
9
|
+
constructor(undoStack?: any, redoStack?: any, maxDepth?: number) {
|
|
9
10
|
this.undoStack = undoStack || []
|
|
10
11
|
this.redoStack = redoStack || []
|
|
12
|
+
this.maxDepth = maxDepth
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
// Add a value to the stack
|
|
14
16
|
push(value: any) {
|
|
15
17
|
// No trivial pushes
|
|
16
|
-
if (
|
|
18
|
+
if (JSON.stringify(this.getValue()) === JSON.stringify(value)) {
|
|
17
19
|
return this
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
let undoStack = this.undoStack.slice()
|
|
21
23
|
undoStack.push(value)
|
|
24
|
+
|
|
25
|
+
// Limit stack depth if maxDepth is set
|
|
26
|
+
if (this.maxDepth && undoStack.length > this.maxDepth) {
|
|
27
|
+
undoStack = undoStack.slice(undoStack.length - this.maxDepth)
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
const redoStack: any = []
|
|
23
31
|
|
|
24
|
-
return new UndoStack(undoStack, redoStack)
|
|
32
|
+
return new UndoStack(undoStack, redoStack, this.maxDepth)
|
|
25
33
|
}
|
|
26
34
|
|
|
27
35
|
canUndo() {
|
|
@@ -39,7 +47,7 @@ export default class UndoStack {
|
|
|
39
47
|
|
|
40
48
|
const undoStack = _.initial(this.undoStack)
|
|
41
49
|
|
|
42
|
-
return new UndoStack(undoStack, redoStack)
|
|
50
|
+
return new UndoStack(undoStack, redoStack, this.maxDepth)
|
|
43
51
|
}
|
|
44
52
|
|
|
45
53
|
redo() {
|
|
@@ -49,7 +57,7 @@ export default class UndoStack {
|
|
|
49
57
|
|
|
50
58
|
const redoStack = _.initial(this.redoStack)
|
|
51
59
|
|
|
52
|
-
return new UndoStack(undoStack, redoStack)
|
|
60
|
+
return new UndoStack(undoStack, redoStack, this.maxDepth)
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
// Get the current value
|
|
@@ -289,7 +289,7 @@ export default class DashboardComponent extends React.Component<DashboardCompone
|
|
|
289
289
|
{this.state.locale}
|
|
290
290
|
</a>
|
|
291
291
|
<ul className="dropdown-menu dropdown-menu-end">
|
|
292
|
-
{[this.props.design.locale || "en", ...this.props.design.otherLocales].map(locale =>
|
|
292
|
+
{[...new Set([this.props.design.locale || "en", ...this.props.design.otherLocales])].map(locale =>
|
|
293
293
|
<li key={locale}>
|
|
294
294
|
<a
|
|
295
295
|
className="dropdown-item"
|
|
@@ -304,23 +304,23 @@ export default class DashboardComponent extends React.Component<DashboardCompone
|
|
|
304
304
|
: undefined}
|
|
305
305
|
<a key="print" className="btn btn-link btn-sm" onClick={this.handlePrint}>
|
|
306
306
|
<span className="fas fa-print"/>
|
|
307
|
-
<span className="hide-
|
|
307
|
+
<span className="hide-800px"> {T`Print`}</span>
|
|
308
308
|
</a>
|
|
309
309
|
<a key="refresh" className="btn btn-link btn-sm" onClick={this.handleRefreshData}>
|
|
310
310
|
<span className="fas fa-sync"/>
|
|
311
|
-
<span className="hide-
|
|
311
|
+
<span className="hide-800px"> {T`Refresh`}</span>
|
|
312
312
|
</a>
|
|
313
313
|
{this.state.hideQuickfilters && this.props.design.quickfilters && this.props.design.quickfilters.length > 0
|
|
314
314
|
? <a key="showQuickfilters" className="btn btn-link btn-sm" onClick={this.handleShowQuickfilters}>
|
|
315
315
|
<span className="fa fa-filter"/>
|
|
316
|
-
<span className="hide-
|
|
316
|
+
<span className="hide-800px"> {T`Show Quickfilters`}</span>
|
|
317
317
|
</a>
|
|
318
318
|
: undefined}
|
|
319
319
|
|
|
320
320
|
{this.state.editing
|
|
321
321
|
? <a key="settings" className="btn btn-link btn-sm" onClick={this.handleSettings}>
|
|
322
322
|
<span className="fas fa-cog"/>
|
|
323
|
-
<span className="hide-
|
|
323
|
+
<span className="hide-800px"> {T`Settings`}</span>
|
|
324
324
|
</a>
|
|
325
325
|
: undefined}
|
|
326
326
|
{this.state.editing ? this.renderStyle() : undefined}
|
|
@@ -435,6 +435,7 @@ export default class DashboardComponent extends React.Component<DashboardCompone
|
|
|
435
435
|
<div style={{
|
|
436
436
|
display: "grid",
|
|
437
437
|
gridTemplateRows: this.props.hideTitleBar ? "auto 1fr" : "auto auto 1fr",
|
|
438
|
+
gridTemplateColumns: "minmax(0, 1fr)",
|
|
438
439
|
height: "100%"
|
|
439
440
|
}}>
|
|
440
441
|
{!this.props.hideTitleBar ? this.renderTitleBar() : undefined}
|
|
@@ -39,6 +39,6 @@ export interface DashboardDesign {
|
|
|
39
39
|
/** true to enable implicit filtering (see ImplicitFilterBuilder). Defaults to true for older dashboards. */
|
|
40
40
|
implicitFiltersEnabled?: boolean
|
|
41
41
|
|
|
42
|
-
/**
|
|
42
|
+
/** Array of global filters */
|
|
43
43
|
globalFilters?: GlobalFilter[]
|
|
44
44
|
}
|
|
@@ -318,25 +318,6 @@ class ServerWidgetLayerDataSource implements MapLayerDataSource {
|
|
|
318
318
|
return this.createUrl(filters, "png")
|
|
319
319
|
}
|
|
320
320
|
|
|
321
|
-
// Get the url for the interactivity tiles with the specified filters applied
|
|
322
|
-
// Called with (design, filters) where design is the layer design and filters are filters to apply. Returns URL
|
|
323
|
-
getUtfGridUrl(design: any, filters: JsonQLFilter[]) {
|
|
324
|
-
// Handle special cases
|
|
325
|
-
if (this.options.layerView.type === "MWaterServer") {
|
|
326
|
-
return this.createLegacyUrl(this.options.layerView.design, "grid.json", filters)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
// Create layer
|
|
330
|
-
const layer = LayerFactory.createLayer(this.options.layerView.type)
|
|
331
|
-
|
|
332
|
-
// If layer has tiles url directly available
|
|
333
|
-
if (layer.getLayerDefinitionType() === "TileUrl") {
|
|
334
|
-
return layer.getUtfGridUrl(this.options.layerView.design, filters)
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return this.createUrl(filters, "grid.json")
|
|
338
|
-
}
|
|
339
|
-
|
|
340
321
|
/** Get the url for vector tile source with an expiry time. Only for layers of type "VectorTile"
|
|
341
322
|
* @param createdAfter ISO 8601 timestamp requiring that tile source on server is created after specified datetime
|
|
342
323
|
*/
|
|
@@ -407,12 +388,6 @@ class ServerWidgetLayerDataSource implements MapLayerDataSource {
|
|
|
407
388
|
|
|
408
389
|
let url = `${this.options.apiUrl}maps/tiles/{z}/{x}/{y}.${extension}?` + querystring.stringify(query)
|
|
409
390
|
|
|
410
|
-
// Add subdomains: {s} will be substituted with "a", "b" or "c" in leaflet for api.mwater.co only.
|
|
411
|
-
// Used to speed queries
|
|
412
|
-
if (url.match(/^https:\/\/api\.mwater\.co\//)) {
|
|
413
|
-
url = url.replace(/^https:\/\/api\.mwater\.co\//, "https://{s}-api.mwater.co/")
|
|
414
|
-
}
|
|
415
|
-
|
|
416
391
|
return url
|
|
417
392
|
}
|
|
418
393
|
|
|
@@ -421,12 +396,6 @@ class ServerWidgetLayerDataSource implements MapLayerDataSource {
|
|
|
421
396
|
let where
|
|
422
397
|
let url = `${this.options.apiUrl}maps/tiles/{z}/{x}/{y}.${extension}?type=${design.type}&radius=1000`
|
|
423
398
|
|
|
424
|
-
// Add subdomains: {s} will be substituted with "a", "b" or "c" in leaflet for api.mwater.co only.
|
|
425
|
-
// Used to speed queries
|
|
426
|
-
if (url.match(/^https:\/\/api\.mwater\.co\//)) {
|
|
427
|
-
url = url.replace(/^https:\/\/api\.mwater\.co\//, "https://{s}-api.mwater.co/")
|
|
428
|
-
}
|
|
429
|
-
|
|
430
399
|
if (this.options.client) {
|
|
431
400
|
url += `&client=${this.options.client}`
|
|
432
401
|
}
|