@mwater/visualization 5.4.0 → 5.4.2

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 (271) hide show
  1. package/lib/ColorComponent.js +2 -1
  2. package/lib/IdSelection.d.ts +16 -0
  3. package/lib/IdSelection.js +59 -0
  4. package/lib/MWaterAddRelatedIndicatorComponent.js +2 -2
  5. package/lib/MWaterCompleteTableSelectComponent.d.ts +3 -8
  6. package/lib/MWaterCompleteTableSelectComponent.js +36 -42
  7. package/lib/MWaterLoaderComponent.d.ts +11 -10
  8. package/lib/MWaterLoaderComponent.js +1 -1
  9. package/lib/MWaterResponsesFilterComponent.js +1 -1
  10. package/lib/MWaterTableSelectComponent.d.ts +0 -1
  11. package/lib/MWaterTableSelectComponent.js +4 -6
  12. package/lib/autotranslate.d.ts +20 -0
  13. package/lib/autotranslate.js +122 -0
  14. package/lib/axes/AxisBuilder.js +3 -3
  15. package/lib/axes/AxisColorEditorComponent.js +4 -0
  16. package/lib/axes/AxisComponent.d.ts +8 -12
  17. package/lib/axes/AxisComponent.js +32 -80
  18. package/lib/axes/CategoryMapComponent.js +4 -4
  19. package/lib/axes/RangesComponent.js +2 -2
  20. package/lib/dashboards/DashboardComponent.d.ts +6 -0
  21. package/lib/dashboards/DashboardComponent.js +44 -12
  22. package/lib/dashboards/DashboardDesign.d.ts +11 -2
  23. package/lib/dashboards/DashboardUtils.d.ts +5 -0
  24. package/lib/dashboards/DashboardUtils.js +30 -0
  25. package/lib/dashboards/DashboardViewComponent.d.ts +2 -0
  26. package/lib/dashboards/DashboardViewComponent.js +16 -3
  27. package/lib/dashboards/ServerDashboardDataSource.js +2 -1
  28. package/lib/dashboards/SettingsModalComponent.d.ts +1 -1
  29. package/lib/dashboards/SettingsModalComponent.js +256 -19
  30. package/lib/dashboards/WidgetComponent.d.ts +6 -3
  31. package/lib/dashboards/WidgetComponent.js +3 -1
  32. package/lib/datagrids/CellEditor.d.ts +19 -0
  33. package/lib/datagrids/CellEditor.js +223 -0
  34. package/lib/datagrids/DatagridComponent.d.ts +18 -87
  35. package/lib/datagrids/DatagridComponent.js +304 -222
  36. package/lib/datagrids/DatagridViewComponent.d.ts +15 -53
  37. package/lib/datagrids/DatagridViewComponent.js +256 -257
  38. package/lib/datagrids/DirectDatagridDataSource.js +2 -3
  39. package/lib/datagrids/ExprCellComponent.d.ts +8 -15
  40. package/lib/datagrids/ExprCellComponent.js +11 -15
  41. package/lib/datagrids/FindReplaceModalComponent.d.ts +4 -6
  42. package/lib/datagrids/FindReplaceModalComponent.js +38 -75
  43. package/lib/index.css +1 -1
  44. package/lib/index.d.ts +0 -1
  45. package/lib/index.js +0 -1
  46. package/lib/layouts/blocks/HorizontalBlockComponent.js +2 -2
  47. package/lib/mWaterLoader.d.ts +1 -1
  48. package/lib/maps/BufferLayer.d.ts +7 -5
  49. package/lib/maps/BufferLayer.js +69 -48
  50. package/lib/maps/BufferLayerDesign.d.ts +21 -14
  51. package/lib/maps/BufferLayerDesignerComponent.d.ts +16 -31
  52. package/lib/maps/BufferLayerDesignerComponent.js +68 -102
  53. package/lib/maps/ChoroplethLayer.d.ts +5 -4
  54. package/lib/maps/ChoroplethLayer.js +32 -9
  55. package/lib/maps/ChoroplethLayerDesign.d.ts +6 -2
  56. package/lib/maps/ChoroplethLayerDesigner.js +4 -2
  57. package/lib/maps/ClusterLayer.d.ts +3 -4
  58. package/lib/maps/ClusterLayer.js +2 -1
  59. package/lib/maps/DetailLevelSelectComponent.js +1 -1
  60. package/lib/maps/DirectMapDataSource.js +2 -1
  61. package/lib/maps/EditPopupComponent.js +5 -3
  62. package/lib/maps/GridLayer.d.ts +3 -4
  63. package/lib/maps/GridLayer.js +2 -1
  64. package/lib/maps/GridLayerDesigner.js +5 -3
  65. package/lib/maps/HoverContent.d.ts +11 -3
  66. package/lib/maps/HoverContent.js +25 -9
  67. package/lib/maps/Layer.d.ts +24 -3
  68. package/lib/maps/Layer.js +5 -1
  69. package/lib/maps/LayerFactory.js +0 -8
  70. package/lib/maps/LayerLegendComponent.js +0 -1
  71. package/lib/maps/LayerSwitcherComponent.d.ts +1 -0
  72. package/lib/maps/LayerSwitcherComponent.js +1 -1
  73. package/lib/maps/LeafletMapComponent.js +3 -1
  74. package/lib/maps/LegendComponent.d.ts +1 -0
  75. package/lib/maps/LegendComponent.js +9 -1
  76. package/lib/maps/MWaterServerLayer.d.ts +2 -2
  77. package/lib/maps/MWaterServerLayer.js +2 -2
  78. package/lib/maps/MapComponent.js +3 -3
  79. package/lib/maps/MapDesign.d.ts +2 -0
  80. package/lib/maps/MapDesignerComponent.d.ts +4 -3
  81. package/lib/maps/MapDesignerComponent.js +68 -74
  82. package/lib/maps/MapLayerViewDesignerComponent.js +2 -2
  83. package/lib/maps/MapUtils.d.ts +4 -0
  84. package/lib/maps/MapUtils.js +19 -0
  85. package/lib/maps/MapViewComponent.d.ts +8 -3
  86. package/lib/maps/MarkersLayer.d.ts +5 -4
  87. package/lib/maps/MarkersLayer.js +33 -7
  88. package/lib/maps/MarkersLayerDesign.d.ts +19 -16
  89. package/lib/maps/PopupFilterJoinsUtils.d.ts +6 -3
  90. package/lib/maps/PopupFilterJoinsUtils.js +0 -6
  91. package/lib/maps/RasterMapViewComponent.d.ts +3 -31
  92. package/lib/maps/RasterMapViewComponent.js +7 -2
  93. package/lib/maps/ServerMapDataSource.js +2 -1
  94. package/lib/maps/SwitchableTileUrlLayer.d.ts +3 -3
  95. package/lib/maps/SwitchableTileUrlLayer.js +2 -1
  96. package/lib/maps/TileUrlLayer.d.ts +4 -5
  97. package/lib/maps/TileUrlLayer.js +2 -1
  98. package/lib/maps/VectorMapViewComponent.d.ts +5 -37
  99. package/lib/maps/VectorMapViewComponent.js +19 -8
  100. package/lib/maps/maps.d.ts +3 -0
  101. package/lib/quickfilter/QuickfiltersComponent.d.ts +2 -0
  102. package/lib/quickfilter/QuickfiltersComponent.js +9 -7
  103. package/lib/quickfilter/QuickfiltersDesignComponent.d.ts +1 -1
  104. package/lib/quickfilter/QuickfiltersDesignComponent.js +19 -35
  105. package/lib/richtext/ExprItemsHtmlConverter.d.ts +5 -2
  106. package/lib/richtext/ExprItemsHtmlConverter.js +4 -4
  107. package/lib/richtext/ExprItemsTranslator.d.ts +5 -0
  108. package/lib/richtext/ExprItemsTranslator.js +149 -0
  109. package/lib/richtext/ItemsHtmlConverter.d.ts +1 -1
  110. package/lib/richtext/ItemsHtmlConverter.js +31 -15
  111. package/lib/wellknown.js +12 -9
  112. package/lib/widgets/IFrameWidget.d.ts +4 -4
  113. package/lib/widgets/ImageWidget.d.ts +7 -4
  114. package/lib/widgets/ImageWidget.js +9 -1
  115. package/lib/widgets/ImageWidgetComponent.d.ts +1 -0
  116. package/lib/widgets/ImageWidgetComponent.js +1 -1
  117. package/lib/widgets/MapWidget.d.ts +5 -48
  118. package/lib/widgets/MapWidget.js +26 -63
  119. package/lib/widgets/MarkdownWidget.d.ts +3 -0
  120. package/lib/widgets/MarkdownWidget.js +3 -0
  121. package/lib/widgets/TOCWidget.d.ts +15 -27
  122. package/lib/widgets/TOCWidget.js +107 -183
  123. package/lib/widgets/Widget.d.ts +18 -7
  124. package/lib/widgets/Widget.js +4 -0
  125. package/lib/widgets/WidgetScopesViewComponent.js +1 -1
  126. package/lib/widgets/charts/Chart.d.ts +10 -1
  127. package/lib/widgets/charts/Chart.js +22 -11
  128. package/lib/widgets/charts/ChartViewComponent.d.ts +4 -0
  129. package/lib/widgets/charts/ChartViewComponent.js +6 -3
  130. package/lib/widgets/charts/ChartWidget.d.ts +2 -0
  131. package/lib/widgets/charts/ChartWidget.js +9 -1
  132. package/lib/widgets/charts/ChartWidgetComponent.d.ts +4 -0
  133. package/lib/widgets/charts/ChartWidgetComponent.js +2 -2
  134. package/lib/widgets/charts/calendar/CalendarChart.d.ts +1 -0
  135. package/lib/widgets/charts/calendar/CalendarChart.js +26 -0
  136. package/lib/widgets/charts/calendar/CalendarChartViewComponent.js +3 -1
  137. package/lib/widgets/charts/imagemosaic/ImageMosaicChart.d.ts +1 -0
  138. package/lib/widgets/charts/imagemosaic/ImageMosaicChart.js +8 -0
  139. package/lib/widgets/charts/layered/LayeredChart.d.ts +2 -0
  140. package/lib/widgets/charts/layered/LayeredChart.js +63 -3
  141. package/lib/widgets/charts/layered/LayeredChartCompiler.d.ts +1 -1
  142. package/lib/widgets/charts/layered/LayeredChartCompiler.js +3 -3
  143. package/lib/widgets/charts/layered/LayeredChartDesignerComponent.js +2 -2
  144. package/lib/widgets/charts/layered/LayeredChartViewComponent.js +8 -3
  145. package/lib/widgets/charts/pivot/PivotChart.d.ts +1 -0
  146. package/lib/widgets/charts/pivot/PivotChart.js +63 -0
  147. package/lib/widgets/charts/pivot/PivotChartDesignerComponent.d.ts +1 -0
  148. package/lib/widgets/charts/pivot/PivotChartLayoutComponent.js +1 -1
  149. package/lib/widgets/charts/pivot/SegmentDesignerComponent.d.ts +6 -0
  150. package/lib/widgets/charts/pivot/SegmentDesignerComponent.js +7 -4
  151. package/lib/widgets/charts/table/OrderingsComponent.js +1 -1
  152. package/lib/widgets/charts/table/TableChart.d.ts +1 -0
  153. package/lib/widgets/charts/table/TableChart.js +15 -0
  154. package/lib/widgets/text/TextComponent.d.ts +11 -4
  155. package/lib/widgets/text/TextComponent.js +11 -8
  156. package/lib/widgets/text/TextWidget.d.ts +6 -3
  157. package/lib/widgets/text/TextWidget.js +7 -1
  158. package/lib/widgets/text/TextWidgetComponent.d.ts +4 -0
  159. package/lib/widgets/text/TextWidgetComponent.js +7 -1
  160. package/lib/widgets/text/TextWidgetDesign.d.ts +2 -4
  161. package/lib/widgets/text/TextWidgetDesign.js +1 -1
  162. package/package.json +7 -8
  163. package/src/ColorComponent.tsx +1 -2
  164. package/src/IdSelection.ts +62 -0
  165. package/src/MWaterAddRelatedIndicatorComponent.ts +3 -2
  166. package/src/MWaterCompleteTableSelectComponent.tsx +36 -46
  167. package/src/MWaterLoaderComponent.ts +28 -26
  168. package/src/MWaterResponsesFilterComponent.ts +5 -2
  169. package/src/MWaterTableSelectComponent.tsx +5 -9
  170. package/src/autotranslate.ts +141 -0
  171. package/src/axes/AxisBuilder.ts +3 -3
  172. package/src/axes/AxisColorEditorComponent.tsx +5 -0
  173. package/src/axes/{AxisComponent.ts → AxisComponent.tsx} +106 -106
  174. package/src/axes/CategoryMapComponent.ts +4 -4
  175. package/src/axes/RangesComponent.ts +3 -2
  176. package/src/dashboards/DashboardComponent.tsx +79 -14
  177. package/src/dashboards/DashboardDesign.ts +9 -2
  178. package/src/dashboards/DashboardUtils.ts +39 -0
  179. package/src/dashboards/DashboardViewComponent.tsx +22 -3
  180. package/src/dashboards/ServerDashboardDataSource.ts +2 -1
  181. package/src/dashboards/SettingsModalComponent.tsx +450 -35
  182. package/src/dashboards/WidgetComponent.tsx +12 -6
  183. package/src/datagrids/CellEditor.tsx +354 -0
  184. package/src/datagrids/DatagridComponent.tsx +646 -0
  185. package/src/datagrids/DatagridViewComponent.tsx +539 -0
  186. package/src/datagrids/DirectDatagridDataSource.ts +2 -3
  187. package/src/datagrids/{ExprCellComponent.ts → ExprCellComponent.tsx} +28 -23
  188. package/src/datagrids/{FindReplaceModalComponent.ts → FindReplaceModalComponent.tsx} +109 -122
  189. package/src/index.css +1 -1
  190. package/src/index.ts +0 -1
  191. package/src/layouts/blocks/HorizontalBlockComponent.ts +2 -2
  192. package/src/mWaterLoader.ts +1 -1
  193. package/src/maps/BufferLayer.ts +83 -60
  194. package/src/maps/BufferLayerDesign.ts +20 -14
  195. package/src/maps/BufferLayerDesignerComponent.tsx +309 -0
  196. package/src/maps/ChoroplethLayer.ts +40 -19
  197. package/src/maps/ChoroplethLayerDesign.ts +4 -2
  198. package/src/maps/ChoroplethLayerDesigner.tsx +4 -2
  199. package/src/maps/ClusterLayer.ts +4 -10
  200. package/src/maps/DetailLevelSelectComponent.ts +1 -1
  201. package/src/maps/DirectMapDataSource.ts +2 -1
  202. package/src/maps/EditPopupComponent.ts +7 -3
  203. package/src/maps/GridLayer.ts +4 -10
  204. package/src/maps/GridLayerDesigner.tsx +5 -3
  205. package/src/maps/HoverContent.tsx +40 -16
  206. package/src/maps/Layer.ts +28 -10
  207. package/src/maps/LayerFactory.ts +0 -8
  208. package/src/maps/LayerLegendComponent.ts +2 -4
  209. package/src/maps/LayerSwitcherComponent.tsx +6 -2
  210. package/src/maps/LeafletMapComponent.tsx +3 -1
  211. package/src/maps/LegendComponent.tsx +10 -1
  212. package/src/maps/MWaterServerLayer.ts +3 -3
  213. package/src/maps/MapComponent.ts +3 -3
  214. package/src/maps/MapDesign.ts +3 -0
  215. package/src/maps/MapDesignerComponent.tsx +165 -162
  216. package/src/maps/MapLayerViewDesignerComponent.ts +2 -2
  217. package/src/maps/MapUtils.ts +24 -0
  218. package/src/maps/MapViewComponent.tsx +11 -3
  219. package/src/maps/MarkersLayer.ts +44 -18
  220. package/src/maps/MarkersLayerDesign.ts +19 -16
  221. package/src/maps/PopupFilterJoinsUtils.ts +6 -2
  222. package/src/maps/RasterMapViewComponent.ts +9 -45
  223. package/src/maps/ServerMapDataSource.ts +2 -2
  224. package/src/maps/SwitchableTileUrlLayer.tsx +4 -10
  225. package/src/maps/TileUrlLayer.tsx +4 -10
  226. package/src/maps/VectorMapViewComponent.tsx +28 -55
  227. package/src/maps/maps.ts +3 -0
  228. package/src/quickfilter/QuickfiltersComponent.ts +13 -7
  229. package/src/quickfilter/QuickfiltersDesignComponent.tsx +56 -74
  230. package/src/richtext/ExprItemsHtmlConverter.ts +9 -5
  231. package/src/richtext/ExprItemsTranslator.ts +176 -0
  232. package/src/richtext/ItemsHtmlConverter.ts +33 -18
  233. package/src/wellknown.ts +33 -30
  234. package/src/widgets/ImageWidget.ts +10 -1
  235. package/src/widgets/ImageWidgetComponent.ts +3 -2
  236. package/src/widgets/{MapWidget.ts → MapWidget.tsx} +90 -101
  237. package/src/widgets/MarkdownWidget.ts +3 -0
  238. package/src/widgets/TOCWidget.tsx +281 -0
  239. package/src/widgets/Widget.ts +25 -5
  240. package/src/widgets/WidgetScopesViewComponent.ts +2 -1
  241. package/src/widgets/charts/Chart.ts +31 -12
  242. package/src/widgets/charts/ChartViewComponent.ts +13 -3
  243. package/src/widgets/charts/ChartWidget.ts +11 -1
  244. package/src/widgets/charts/ChartWidgetComponent.tsx +9 -1
  245. package/src/widgets/charts/calendar/CalendarChart.ts +29 -0
  246. package/src/widgets/charts/calendar/CalendarChartViewComponent.tsx +3 -1
  247. package/src/widgets/charts/imagemosaic/ImageMosaicChart.ts +9 -0
  248. package/src/widgets/charts/layered/LayeredChart.ts +71 -3
  249. package/src/widgets/charts/layered/LayeredChartCompiler.ts +4 -4
  250. package/src/widgets/charts/layered/LayeredChartDesignerComponent.tsx +4 -2
  251. package/src/widgets/charts/layered/LayeredChartViewComponent.ts +10 -4
  252. package/src/widgets/charts/pivot/PivotChart.ts +73 -0
  253. package/src/widgets/charts/pivot/PivotChartLayoutComponent.tsx +1 -1
  254. package/src/widgets/charts/pivot/SegmentDesignerComponent.tsx +6 -4
  255. package/src/widgets/charts/table/OrderingsComponent.tsx +2 -1
  256. package/src/widgets/charts/table/TableChart.ts +17 -0
  257. package/src/widgets/text/TextComponent.tsx +22 -12
  258. package/src/widgets/text/TextWidget.ts +9 -2
  259. package/src/widgets/text/TextWidgetComponent.tsx +16 -1
  260. package/src/widgets/text/TextWidgetDesign.ts +4 -7
  261. package/test/IdSelectionTests.ts +54 -0
  262. package/test/LayeredChartCompilerTests.ts +0 -2
  263. package/test/richtext/ExprItemsTranslatorTests.ts +144 -0
  264. package/test/wellknownTests.ts +144 -0
  265. package/src/datagrids/DatagridComponent.ts +0 -478
  266. package/src/datagrids/DatagridViewComponent.ts +0 -464
  267. package/src/datagrids/EditExprCellComponent.tsx +0 -305
  268. package/src/datagrids/README.md +0 -3
  269. package/src/maps/BufferLayerDesignerComponent.ts +0 -311
  270. package/src/widgets/TOCWidget.ts +0 -326
  271. package/test/LegoLayoutEngineTests.ts +0 -69
@@ -1,5 +1,5 @@
1
1
  import _ from "lodash"
2
- import React, { useMemo } from "react"
2
+ import React, { useMemo, useRef, useState } from "react"
3
3
  import { languages } from "../languages"
4
4
  import * as ui from "@mwater/react-library/lib/bootstrap"
5
5
  import { default as ReactSelect } from "react-select"
@@ -7,13 +7,17 @@ import * as DashboardUtils from "./DashboardUtils"
7
7
  import ActionCancelModalComponent from "@mwater/react-library/lib/ActionCancelModalComponent"
8
8
  import QuickfiltersDesignComponent from "../quickfilter/QuickfiltersDesignComponent"
9
9
  import FiltersDesignerComponent from "../FiltersDesignerComponent"
10
- import { DataSource, Schema } from "@mwater/expressions"
10
+ import { DataSource, LocalizedString, Schema } from "@mwater/expressions"
11
11
  import { DashboardDesign } from "./DashboardDesign"
12
12
  import { GlobalFiltersElementFactoryContext } from "../MWaterContextComponent"
13
13
  import produce from "immer"
14
+ import TabbedComponent from "@mwater/react-library/lib/TabbedComponent"
15
+ import FileSaver from "file-saver"
16
+ import * as localizeUtils from "ez-localize/lib/utils"
17
+ import { canAutoTranslate, translateStrings } from "../autotranslate"
14
18
 
15
19
  export interface SettingsModalComponentProps {
16
- onDesignChange: any
20
+ onDesignChange: (design: DashboardDesign) => void
17
21
  schema: Schema
18
22
  dataSource: DataSource
19
23
  }
@@ -39,7 +43,7 @@ export default class SettingsModalComponent extends React.Component<
39
43
  }
40
44
 
41
45
  handleSave = () => {
42
- this.props.onDesignChange(this.state.design)
46
+ this.props.onDesignChange(this.state.design!)
43
47
  return this.setState({ design: null })
44
48
  }
45
49
 
@@ -85,22 +89,57 @@ interface SettingsComponentProps {
85
89
  function SettingsComponent(props: SettingsComponentProps) {
86
90
  const { design, onDesignChange, schema, dataSource } = props
87
91
 
88
- // Get filterable tables
89
- const filterableTables = DashboardUtils.getFilterableTables(design, schema)
90
-
91
- const localeOptions = useMemo(() => {
92
- return _.map(languages, (language) => {
93
- return {
94
- value: language.code,
95
- label: language.en + " (" + language.name + ")"
96
- }
97
- })
98
- }, [languages])
92
+ const tabs = [
93
+ {
94
+ id: "filters",
95
+ label: T`Filters`,
96
+ elem: (
97
+ <FiltersTab
98
+ design={design}
99
+ onDesignChange={onDesignChange}
100
+ schema={schema}
101
+ dataSource={dataSource}
102
+ />
103
+ )
104
+ },
105
+ {
106
+ id: "language",
107
+ label: T`Translation`,
108
+ elem: (
109
+ <LanguageTab
110
+ design={design}
111
+ onDesignChange={onDesignChange}
112
+ schema={schema}
113
+ />
114
+ )
115
+ }
116
+ ]
99
117
 
100
118
  return (
101
119
  <div style={{ paddingBottom: 200 }}>
120
+ <TabbedComponent
121
+ tabs={tabs}
122
+ initialTabId="filters"
123
+ />
124
+ </div>
125
+ )
126
+ }
127
+
128
+ interface FiltersTabProps {
129
+ design: DashboardDesign
130
+ onDesignChange: (design: DashboardDesign) => void
131
+ schema: Schema
132
+ dataSource: DataSource
133
+ }
134
+
135
+ function FiltersTab({ design, onDesignChange, schema, dataSource }: FiltersTabProps) {
136
+ // Get filterable tables
137
+ const filterableTables = useMemo(() => DashboardUtils.getFilterableTables(design, schema), [design, schema])
138
+
139
+ return (
140
+ <>
102
141
  <h4>{T`Quick Filters`}</h4>
103
- <div className="text-muted">
142
+ <div className="mb-2">
104
143
  {T`Quick filters are shown to the user as a dropdown at the top of the dashboard and can be used to filter data of widgets.`}
105
144
  </div>
106
145
 
@@ -120,8 +159,8 @@ function SettingsComponent(props: SettingsComponentProps) {
120
159
  T`Nothing to quickfilter. Add widgets to the dashboard`
121
160
  )}
122
161
 
123
- <h4 style={{ paddingTop: 10 }}>{T`Filters`}</h4>
124
- <div className="text-muted">
162
+ <h4 style={{ paddingTop: 15 }}>{T`Filters`}</h4>
163
+ <div className="mb-2">
125
164
  {T`Filters are built in to the dashboard and cannot be changed by viewers of the dashboard.`}
126
165
  </div>
127
166
 
@@ -145,7 +184,7 @@ function SettingsComponent(props: SettingsComponentProps) {
145
184
  {globalFiltersElementFactory =>
146
185
  globalFiltersElementFactory ? (
147
186
  <div>
148
- <h4 style={{ paddingTop: 10 }}>{T`Global Filters`}</h4>
187
+ <h4 style={{ paddingTop: 15 }}>{T`Global Filters`}</h4>
149
188
  {globalFiltersElementFactory({
150
189
  schema,
151
190
  dataSource,
@@ -161,10 +200,207 @@ function SettingsComponent(props: SettingsComponentProps) {
161
200
  ) : undefined
162
201
  }
163
202
  </GlobalFiltersElementFactoryContext.Consumer>
203
+ {design.implicitFiltersEnabled && (
204
+ <>
205
+ <h4 style={{ paddingTop: 10 }}>{T`Advanced`}</h4>
206
+ <ui.Checkbox
207
+ value={design.implicitFiltersEnabled != null ? design.implicitFiltersEnabled : true}
208
+ onChange={(value: boolean) =>
209
+ onDesignChange(produce(design, draft => {
210
+ draft.implicitFiltersEnabled = value
211
+ }))
212
+ }
213
+ >
214
+ {T`Enable Implicit Filtering (leave unchecked for new dashboards)`}
215
+ </ui.Checkbox>
216
+ </>
217
+ )}
218
+ </>
219
+ )
220
+ }
221
+
222
+ interface LanguageTabProps {
223
+ design: DashboardDesign
224
+ onDesignChange: (design: DashboardDesign) => void
225
+ schema: Schema
226
+ }
227
+
228
+ function LanguageTab({ design, onDesignChange, schema }: LanguageTabProps) {
229
+ const fileInputRef = useRef<HTMLInputElement>(null)
230
+
231
+ const locale = design.locale || "en"
232
+
233
+ const localeOptions = useMemo(() => {
234
+ return _.sortBy(
235
+ _.map(languages, (language) => ({
236
+ value: language.code,
237
+ label: `${language.en} (${language.name})`
238
+ })),
239
+ 'label'
240
+ )
241
+ }, [languages])
242
+
243
+ // Get available languages that aren't already selected
244
+ const availableLocaleOptions = useMemo(() => {
245
+ const selectedLocales = new Set([design.locale, ...(design.otherLocales || [])])
246
+ return localeOptions.filter(opt => !selectedLocales.has(opt.value))
247
+ }, [localeOptions, design.locale, design.otherLocales])
248
+
249
+ const translatableStrings = useMemo(() => DashboardUtils.getTranslatableStringsFromDashboard(design, schema), [design, schema])
250
+
251
+ // Calculate percentage of strings translated for each locale
252
+ const translationPercentages = useMemo(() => {
253
+ const percentages: { [locale: string]: number } = {}
254
+ const totalStrings = translatableStrings.length
255
+
256
+ for (const locale of design.otherLocales || []) {
257
+ const translatedCount = translatableStrings.filter(str =>
258
+ design.translations?.[locale]?.[str] != null
259
+ ).length
260
+
261
+ // Round down to nearest percent
262
+ percentages[locale] = Math.floor((translatedCount / totalStrings) * 100)
263
+ }
264
+
265
+ return percentages
266
+ }, [design.translations, design.otherLocales, translatableStrings])
267
+
268
+ const handleAddLocale = (locale: any) => {
269
+ onDesignChange(produce(design, draft => {
270
+ draft.otherLocales = [...(draft.otherLocales || []), locale.value]
271
+ // Initialize empty translations object if needed
272
+ if (!draft.translations) {
273
+ draft.translations = {}
274
+ }
275
+ if (!draft.translations[locale.value]) {
276
+ draft.translations[locale.value] = {}
277
+ }
278
+ }))
279
+ }
280
+
281
+ const handleRemoveLocale = (localeToRemove: string) => {
282
+ onDesignChange(produce(design, draft => {
283
+ draft.otherLocales = (draft.otherLocales || []).filter(locale => locale !== localeToRemove)
284
+ // Remove translations for this locale
285
+ if (draft.translations) {
286
+ delete draft.translations[localeToRemove]
287
+ }
288
+ }))
289
+ }
290
+
291
+ // Convert dashboard translations to LocalizedString format
292
+ const getLocalizedStrings = (): LocalizedString[] => {
293
+ // For each string, create a localized string
294
+ const localizedStrings: LocalizedString[] = []
295
+ for (const str of translatableStrings) {
296
+ const localizedString: LocalizedString = { _base: locale }
297
+ localizedString[locale] = str
298
+
299
+ // Only add translations for other locales if they exist
300
+ for (const otherLocale of design.otherLocales || []) {
301
+ if (design.translations?.[otherLocale]?.[str]) {
302
+ localizedString[otherLocale] = design.translations?.[otherLocale]?.[str]
303
+ }
304
+ }
305
+
306
+ localizedStrings.push(localizedString)
307
+ }
308
+
309
+ return localizedStrings
310
+ }
311
+
312
+ // Convert LocalizedString format back to dashboard translations
313
+ const updateFromLocalizedStrings = (localizedStrings: LocalizedString[]) => {
314
+ const newTranslations: { [locale: string]: { [key: string]: string } } = {}
315
+
316
+ // Get all locales except base
317
+ const locales = design.otherLocales || []
318
+
319
+ // Initialize translations object
320
+ for (const locale of locales) {
321
+ newTranslations[locale] = {}
322
+ }
164
323
 
165
- <h4 style={{ paddingTop: 10 }}>{T`Language`}</h4>
166
- <div className="text-muted">
167
- {T`Controls the preferred language of widgets and uses specified language when available`}
324
+ // Add all translations
325
+ for (const str of localizedStrings) {
326
+ for (const locale of locales) {
327
+ if (str[locale]) {
328
+ newTranslations[locale][str[str._base]] = str[locale]
329
+ }
330
+ }
331
+ }
332
+
333
+ return newTranslations
334
+ }
335
+
336
+ const handleDownload = () => {
337
+ // Get strings in LocalizedString format
338
+ const strings = getLocalizedStrings()
339
+
340
+ // Create xlsx base64 using all locales (primary + other)
341
+ const locales = [{ code: locale, name: locale }]
342
+ .concat((design.otherLocales || []).map(code => ({ code, name: code })))
343
+
344
+ const base64 = localizeUtils.exportXlsx(locales, strings)
345
+
346
+ // Download
347
+ FileSaver.saveAs(
348
+ b64toBlob(base64, "application/octet-stream"),
349
+ "Dashboard Translations.xlsx"
350
+ )
351
+ }
352
+
353
+ const handleUpload = () => {
354
+ fileInputRef.current?.click()
355
+ }
356
+
357
+ const handleUploadChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
358
+ const reader = new FileReader()
359
+
360
+ reader.onload = (file) => {
361
+ if (!file.target?.result) {
362
+ return
363
+ }
364
+
365
+ const base64 = (file.target.result as string).split(",")[1]
366
+
367
+ try {
368
+ // Create locales array for import
369
+ const locales = [{ code: locale, name: locale }]
370
+ .concat((design.otherLocales || []).map(code => ({ code, name: code })))
371
+
372
+ // Import updates
373
+ const updates = localizeUtils.importXlsx(locales, base64)
374
+
375
+ // If nothing localized
376
+ if (updates.length === 0) {
377
+ alert(T`No translation data found in file`)
378
+ return
379
+ }
380
+
381
+ // Convert back to dashboard format and update
382
+ const newTranslations = updateFromLocalizedStrings(updates)
383
+ onDesignChange(produce(design, draft => {
384
+ draft.translations = newTranslations
385
+ }))
386
+
387
+ alert(T`${updates.length} translations applied`)
388
+ } catch (error) {
389
+ console.error("Invalid xlsx file:", error)
390
+ alert(T`Invalid xlsx file`)
391
+ }
392
+ }
393
+
394
+ if (evt.target.files?.[0]) {
395
+ reader.readAsDataURL(evt.target.files[0])
396
+ }
397
+ }
398
+
399
+ return (
400
+ <>
401
+ <h4>{T`Base Language`}</h4>
402
+ <div className="mb-2">
403
+ {T`This is the language that the dashboard is written in.`}
168
404
  </div>
169
405
 
170
406
  <ReactSelect
@@ -177,21 +413,200 @@ function SettingsComponent(props: SettingsComponentProps) {
177
413
  }
178
414
  />
179
415
 
180
- {design.implicitFiltersEnabled && (
181
- <>
182
- <h4 style={{ paddingTop: 10 }}>{T`Advanced`}</h4>
183
- <ui.Checkbox
184
- value={design.implicitFiltersEnabled != null ? design.implicitFiltersEnabled : true}
185
- onChange={(value: boolean) =>
186
- onDesignChange(produce(design, draft => {
187
- draft.implicitFiltersEnabled = value
188
- }))
416
+ <h4 className="mt-4">{T`Additional Languages`}</h4>
417
+ <div className="mb-2">
418
+ {T`Add languages that this dashboard will be translated into`}
419
+ </div>
420
+
421
+ {/* Show current additional languages */}
422
+ <table>
423
+ <tbody>
424
+ {(design.otherLocales || []).map(locale => {
425
+ const localeOption = _.findWhere(localeOptions, { value: locale })
426
+ if (!localeOption) {
427
+ return null
189
428
  }
190
- >
191
- {T`Enable Implicit Filtering (leave unchecked for new dashboards)`}
192
- </ui.Checkbox>
429
+
430
+ return (
431
+ <tr key={locale}>
432
+ <td style={{ paddingRight: 10 }}>{localeOption.label}</td>
433
+ <td className={translationPercentages[locale] === 100 ? "text-success" : "text-warning"} style={{ textAlign: "right" }}>
434
+ {T`${translationPercentages[locale]}% translated`}
435
+ </td>
436
+ <td>
437
+ {translationPercentages[locale] < 100 && (
438
+ <AutoTranslateLink
439
+ locale={locale}
440
+ baseLocale={design.locale || "en"}
441
+ strings={translatableStrings}
442
+ existingTranslations={design.translations?.[locale] || {}}
443
+ onTranslationsChange={(newTranslations) => {
444
+ onDesignChange(produce(design, draft => {
445
+ if (!draft.translations) {
446
+ draft.translations = {}
447
+ }
448
+ draft.translations[locale] = newTranslations
449
+ }))
450
+ }}
451
+ />
452
+ )}
453
+ </td>
454
+ <td>
455
+ <button
456
+ type="button"
457
+ className="btn btn-sm btn-link"
458
+ onClick={() => handleRemoveLocale(locale)}
459
+ >
460
+ <i className="fa fa-times" />
461
+ </button>
462
+ </td>
463
+ </tr>
464
+ )
465
+ })}
466
+ </tbody>
467
+ </table>
468
+
469
+ {/* Add new language dropdown */}
470
+ {availableLocaleOptions.length > 0 && (
471
+ <div className="mt-3">
472
+ <ReactSelect
473
+ value={null}
474
+ options={availableLocaleOptions}
475
+ onChange={handleAddLocale}
476
+ placeholder={T`Add language...`}
477
+ />
478
+ </div>
479
+ )}
480
+
481
+ {/* Add translation management section if there are additional languages */}
482
+ {(design.otherLocales || []).length > 0 && (
483
+ <>
484
+ <h4 className="mt-4">{T`Manage Translations`}</h4>
485
+ <p>{T`Download and re-upload an Excel spreadsheet of text to translate:`}</p>
486
+
487
+ <div>
488
+ <button
489
+ type="button"
490
+ className="btn btn-secondary"
491
+ onClick={handleDownload}
492
+ >
493
+ <i className="fas fa-download me-2"/>{T`Download XLSX`}
494
+ </button>
495
+ </div>
496
+ <div className="text-muted mt-2">
497
+ {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.`}
498
+ </div>
499
+
500
+ <br />
501
+ <p>
502
+ {T`Once translation is complete, upload the file back using the button below:`}
503
+ </p>
504
+ <div>
505
+ <button
506
+ type="button"
507
+ className="btn btn-secondary"
508
+ onClick={handleUpload}
509
+ >
510
+ <i className="fas fa-upload me-2"/>{T`Upload Translated XLSX`}
511
+ </button>
512
+ </div>
513
+ <input
514
+ type="file"
515
+ ref={fileInputRef}
516
+ style={{ display: "none" }}
517
+ onChange={handleUploadChange}
518
+ accept=".xlsx"
519
+ />
193
520
  </>
194
521
  )}
195
- </div>
522
+ </>
523
+ )
524
+ }
525
+
526
+ /** Helper function for base64 to blob conversion */
527
+ function b64toBlob(b64Data: string, contentType: string = "", sliceSize: number = 512) {
528
+ const byteCharacters = atob(b64Data)
529
+ const byteArrays = []
530
+
531
+ for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
532
+ const slice = byteCharacters.slice(offset, offset + sliceSize)
533
+ const byteNumbers = new Array(slice.length)
534
+
535
+ for (let i = 0; i < slice.length; i++) {
536
+ byteNumbers[i] = slice.charCodeAt(i)
537
+ }
538
+
539
+ const byteArray = new Uint8Array(byteNumbers)
540
+ byteArrays.push(byteArray)
541
+ }
542
+
543
+ return new Blob(byteArrays, { type: contentType })
544
+ }
545
+
546
+ interface AutoTranslateLinkProps {
547
+ locale: string
548
+ baseLocale: string
549
+ strings: string[]
550
+ existingTranslations: { [key: string]: string }
551
+ onTranslationsChange: (newTranslations: { [key: string]: string }) => void
552
+ }
553
+
554
+ function AutoTranslateLink(props: AutoTranslateLinkProps) {
555
+ const { locale, baseLocale, strings, existingTranslations, onTranslationsChange } = props
556
+ const [isTranslating, setIsTranslating] = useState(false)
557
+
558
+ const untranslatedStrings = useMemo(() => {
559
+ return strings.filter(str => !existingTranslations[str])
560
+ }, [strings, existingTranslations])
561
+
562
+ const handleClick = async () => {
563
+ if (isTranslating) {
564
+ return
565
+ }
566
+
567
+ setIsTranslating(true)
568
+ try {
569
+ const translatedStrings = await translateStrings(untranslatedStrings, baseLocale, locale)
570
+
571
+ const newTranslations = { ...existingTranslations }
572
+ for (let i = 0; i < untranslatedStrings.length; i++) {
573
+ newTranslations[untranslatedStrings[i]] = translatedStrings[i]
574
+ }
575
+
576
+ onTranslationsChange(newTranslations)
577
+ } catch (error) {
578
+ console.error("Error translating strings:", error)
579
+ alert(T`Error translating strings`)
580
+ } finally {
581
+ setIsTranslating(false)
582
+ }
583
+ }
584
+
585
+ if (untranslatedStrings.length === 0) {
586
+ return null
587
+ }
588
+
589
+ if (!canAutoTranslate(locale)) {
590
+ return null
591
+ }
592
+
593
+ return (
594
+ <button
595
+ type="button"
596
+ className="btn btn-sm btn-link"
597
+ onClick={handleClick}
598
+ disabled={isTranslating}
599
+ style={{ marginLeft: 5, padding: "0 5px" }}
600
+ >
601
+ {isTranslating ? (
602
+ <span>
603
+ <i className="fa fa-spinner fa-spin" />
604
+ {" "}
605
+ {T`Translating...`}
606
+ </span>
607
+ ) : (
608
+ T`Autotranslate`
609
+ )}
610
+ </button>
196
611
  )
197
612
  }
@@ -1,18 +1,16 @@
1
1
  import { Schema, DataSource } from "@mwater/expressions"
2
- import { useMemo, useRef } from "react"
2
+ import { useRef } from "react"
3
3
  import { JsonQLFilter } from "../JsonQLFilter"
4
4
  import WidgetFactory from "../widgets/WidgetFactory"
5
5
  import { WidgetScope } from "../WidgetScope"
6
- import DashboardDataSource from "./DashboardDataSource"
7
6
  import { WidgetDataSource } from "../widgets/WidgetDataSource"
7
+ import { TOCEntry } from "../widgets/Widget"
8
8
 
9
9
  /**
10
10
  * Component which renders a widget and ensures that props do not change
11
11
  * unnecessarily.
12
12
  */
13
13
  export function WidgetComponent(props: {
14
- /** Widget id */
15
- id: string
16
14
  /** Widget type */
17
15
  type: string
18
16
 
@@ -56,7 +54,7 @@ export function WidgetComponent(props: {
56
54
  namedStrings?: { [key: string]: string }
57
55
 
58
56
  /** Entries in the table of content */
59
- tocEntries?: string[]
57
+ tocEntries?: TOCEntry[]
60
58
 
61
59
  /** the widget callback ref */
62
60
  widgetRef: (widget: any) => void
@@ -66,6 +64,12 @@ export function WidgetComponent(props: {
66
64
 
67
65
  /** Change to force a refresh */
68
66
  refreshKey?: any
67
+
68
+ /** Locale to use for display */
69
+ locale: string
70
+
71
+ /** Translate function to use for display. Returns same string when editing. */
72
+ translate: (input: string) => string
69
73
  }) {
70
74
  const widget = WidgetFactory.createWidget(props.type)
71
75
 
@@ -96,7 +100,9 @@ export function WidgetComponent(props: {
96
100
  tocEntries: props.tocEntries,
97
101
  onScrollToTOCEntry: props.onScrollToTOCEntry,
98
102
  widgetRef,
99
- refreshKey: props.refreshKey
103
+ refreshKey: props.refreshKey,
104
+ locale: props.locale,
105
+ translate: props.translate
100
106
  })
101
107
  }
102
108