@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.
Files changed (275) hide show
  1. package/lib/ColorComponent.js +2 -2
  2. package/lib/MWaterContextComponent.d.ts +1 -1
  3. package/lib/MWaterGlobalFiltersComponent.d.ts +2 -2
  4. package/lib/MWaterGlobalFiltersComponent.js +11 -20
  5. package/lib/MWaterLoaderComponent.d.ts +4 -13
  6. package/lib/MWaterLoaderComponent.js +2 -11
  7. package/lib/TranslationsTabComponent.d.ts +34 -0
  8. package/lib/TranslationsTabComponent.js +256 -0
  9. package/lib/UndoStack.d.ts +2 -1
  10. package/lib/UndoStack.js +12 -6
  11. package/lib/dashboards/DashboardComponent.js +6 -5
  12. package/lib/dashboards/DashboardDesign.d.ts +1 -1
  13. package/lib/dashboards/ServerDashboardDataSource.d.ts +0 -1
  14. package/lib/dashboards/ServerDashboardDataSource.js +0 -25
  15. package/lib/dashboards/SettingsModalComponent.js +9 -233
  16. package/lib/datagrids/DatagridComponent.js +27 -2
  17. package/lib/datagrids/DatagridDesignerComponent.d.ts +2 -3
  18. package/lib/datagrids/DatagridDesignerComponent.js +108 -120
  19. package/lib/datagrids/DatagridViewComponent.js +33 -6
  20. package/lib/datagrids/OrderBysDesignerComponent.d.ts +7 -7
  21. package/lib/datagrids/OrderBysDesignerComponent.js +19 -28
  22. package/lib/index.css +45 -2
  23. package/lib/index.d.ts +5 -5
  24. package/lib/index.js +2 -3
  25. package/lib/layouts/blocks/BlocksDisplayComponent.d.ts +8 -1
  26. package/lib/layouts/blocks/BlocksDisplayComponent.js +46 -4
  27. package/lib/maps/BufferLayer.d.ts +0 -13
  28. package/lib/maps/BufferLayer.js +24 -237
  29. package/lib/maps/BufferLayerDesign.d.ts +1 -1
  30. package/lib/maps/BufferLayerDesignerComponent.d.ts +1 -1
  31. package/lib/maps/BufferLayerDesignerComponent.js +2 -7
  32. package/lib/maps/ChoroplethLayer.d.ts +1 -16
  33. package/lib/maps/ChoroplethLayer.js +25 -358
  34. package/lib/maps/ChoroplethLayerDesign.d.ts +5 -2
  35. package/lib/maps/ChoroplethLayerDesigner.d.ts +10 -32
  36. package/lib/maps/ChoroplethLayerDesigner.js +58 -89
  37. package/lib/maps/ClusterLayer.d.ts +0 -9
  38. package/lib/maps/ClusterLayer.js +0 -250
  39. package/lib/maps/DirectMapDataSource.js +1 -48
  40. package/lib/maps/EditHoverOver.d.ts +4 -3
  41. package/lib/maps/EditHoverOver.js +3 -3
  42. package/lib/maps/GridLayer.d.ts +0 -15
  43. package/lib/maps/GridLayer.js +0 -212
  44. package/lib/maps/HoverContent.js +1 -1
  45. package/lib/maps/Layer.d.ts +1 -26
  46. package/lib/maps/Layer.js +0 -13
  47. package/lib/maps/LeafletMapComponent.js +10 -19
  48. package/lib/maps/MapComponent.d.ts +19 -35
  49. package/lib/maps/MapComponent.js +135 -77
  50. package/lib/maps/MapControlComponent.d.ts +4 -5
  51. package/lib/maps/MapControlComponent.js +5 -12
  52. package/lib/maps/MapDesign.d.ts +8 -0
  53. package/lib/maps/MapDesignerComponent.d.ts +2 -0
  54. package/lib/maps/MapDesignerComponent.js +7 -2
  55. package/lib/maps/MapLayerDataSource.d.ts +0 -4
  56. package/lib/maps/MapLayerViewDesignerComponent.d.ts +3 -1
  57. package/lib/maps/MapLayerViewDesignerComponent.js +5 -1
  58. package/lib/maps/MapLayersDesignerComponent.d.ts +2 -0
  59. package/lib/maps/MapLayersDesignerComponent.js +2 -1
  60. package/lib/maps/MapTranslationsTab.d.ts +15 -0
  61. package/lib/maps/MapTranslationsTab.js +47 -0
  62. package/lib/maps/MapUtils.d.ts +11 -0
  63. package/lib/maps/MapUtils.js +57 -1
  64. package/lib/maps/MapViewComponent.d.ts +1 -1
  65. package/lib/maps/MapViewComponent.js +1 -8
  66. package/lib/maps/MarkersLayer.d.ts +1 -14
  67. package/lib/maps/MarkersLayer.js +89 -254
  68. package/lib/maps/MarkersLayerDesign.d.ts +5 -1
  69. package/lib/maps/MarkersLayerDesignerComponent.d.ts +32 -57
  70. package/lib/maps/MarkersLayerDesignerComponent.js +158 -134
  71. package/lib/maps/ServerMapDataSource.d.ts +0 -1
  72. package/lib/maps/ServerMapDataSource.js +0 -25
  73. package/lib/maps/SwitchableTileUrlLayer.d.ts +0 -2
  74. package/lib/maps/SwitchableTileUrlLayer.js +0 -9
  75. package/lib/maps/TileUrlLayer.d.ts +0 -1
  76. package/lib/maps/TileUrlLayer.js +0 -5
  77. package/lib/maps/VectorMapViewComponent.js +13 -10
  78. package/lib/maps/symbols/font-awesome/asterisk.png +0 -0
  79. package/lib/maps/symbols/font-awesome/ban.png +0 -0
  80. package/lib/maps/symbols/font-awesome/beer.png +0 -0
  81. package/lib/maps/symbols/font-awesome/bell.png +0 -0
  82. package/lib/maps/symbols/font-awesome/bolt.png +0 -0
  83. package/lib/maps/symbols/font-awesome/building.png +0 -0
  84. package/lib/maps/symbols/font-awesome/bullseye.png +0 -0
  85. package/lib/maps/symbols/font-awesome/bus.png +0 -0
  86. package/lib/maps/symbols/font-awesome/caret-up.png +0 -0
  87. package/lib/maps/symbols/font-awesome/certificate.png +0 -0
  88. package/lib/maps/symbols/font-awesome/check-circle.png +0 -0
  89. package/lib/maps/symbols/font-awesome/check.png +0 -0
  90. package/lib/maps/symbols/font-awesome/chevron-circle-down.png +0 -0
  91. package/lib/maps/symbols/font-awesome/chevron-circle-up.png +0 -0
  92. package/lib/maps/symbols/font-awesome/cloud-rain.png +0 -0
  93. package/lib/maps/symbols/font-awesome/cloud.png +0 -0
  94. package/lib/maps/symbols/font-awesome/comment.png +0 -0
  95. package/lib/maps/symbols/font-awesome/crosshairs.png +0 -0
  96. package/lib/maps/symbols/font-awesome/dot-circle-o.png +0 -0
  97. package/lib/maps/symbols/font-awesome/exclamation-circle.png +0 -0
  98. package/lib/maps/symbols/font-awesome/exclamation-triangle.png +0 -0
  99. package/lib/maps/symbols/font-awesome/female.png +0 -0
  100. package/lib/maps/symbols/font-awesome/file.png +0 -0
  101. package/lib/maps/symbols/font-awesome/flag.png +0 -0
  102. package/lib/maps/symbols/font-awesome/flask.png +0 -0
  103. package/lib/maps/symbols/font-awesome/h-square.png +0 -0
  104. package/lib/maps/symbols/font-awesome/home.png +0 -0
  105. package/lib/maps/symbols/font-awesome/info-circle.png +0 -0
  106. package/lib/maps/symbols/font-awesome/male.png +0 -0
  107. package/lib/maps/symbols/font-awesome/medkit.png +0 -0
  108. package/lib/maps/symbols/font-awesome/mobile.png +0 -0
  109. package/lib/maps/symbols/font-awesome/plus-circle.png +0 -0
  110. package/lib/maps/symbols/font-awesome/plus-square.png +0 -0
  111. package/lib/maps/symbols/font-awesome/plus.png +0 -0
  112. package/lib/maps/symbols/font-awesome/square.png +0 -0
  113. package/lib/maps/symbols/font-awesome/star.png +0 -0
  114. package/lib/maps/symbols/font-awesome/thumbs-down.png +0 -0
  115. package/lib/maps/symbols/font-awesome/thumbs-up.png +0 -0
  116. package/lib/maps/symbols/font-awesome/ticket.png +0 -0
  117. package/lib/maps/symbols/font-awesome/times-circle.png +0 -0
  118. package/lib/maps/symbols/font-awesome/times.png +0 -0
  119. package/lib/maps/symbols/font-awesome/tint.png +0 -0
  120. package/lib/maps/symbols/font-awesome/tree.png +0 -0
  121. package/lib/maps/symbols/font-awesome/university.png +0 -0
  122. package/lib/maps/symbols/font-awesome/usd.png +0 -0
  123. package/lib/maps/symbols/font-awesome/user.png +0 -0
  124. package/lib/maps/symbols/font-awesome/users.png +0 -0
  125. package/lib/maps/symbols/font-awesome/wheelchair.png +0 -0
  126. package/lib/maps/symbols/sdf-ize.sh +93 -0
  127. package/lib/maps/vectorMaps.d.ts +6 -6
  128. package/lib/maps/vectorMaps.js +33 -45
  129. package/lib/mwater_table_selection/IndicatorsListComponent.d.ts +4 -2
  130. package/lib/mwater_table_selection/IndicatorsListComponent.js +103 -34
  131. package/lib/mwater_table_selection/MWaterCalculatedDataSourcesListComponent.d.ts +18 -0
  132. package/lib/mwater_table_selection/MWaterCalculatedDataSourcesListComponent.js +80 -0
  133. package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.d.ts +26 -0
  134. package/lib/mwater_table_selection/MWaterCompleteTableSelectComponent.js +237 -51
  135. package/lib/mwater_table_selection/MWaterTableSelectComponent.d.ts +2 -2
  136. package/lib/mwater_table_selection/MWaterTableSelectComponent.js +9 -4
  137. package/lib/mwater_table_selection/MWaterWorkflowsSelectComponent.d.ts +19 -0
  138. package/lib/mwater_table_selection/MWaterWorkflowsSelectComponent.js +111 -0
  139. package/lib/quickfilter/QuickfiltersComponent.d.ts +3 -102
  140. package/lib/quickfilter/QuickfiltersComponent.js +53 -110
  141. package/lib/quickfilter/TextLiteralComponent.d.ts +23 -47
  142. package/lib/quickfilter/TextLiteralComponent.js +85 -82
  143. package/lib/widgets/MapWidget.js +6 -3
  144. package/lib/widgets/text/ExprItemEditorComponent.d.ts +3 -8
  145. package/lib/widgets/text/ExprItemEditorComponent.js +36 -33
  146. package/lib/widgets/text/ExprUpdateModalComponent.d.ts +1 -0
  147. package/package.json +3 -4
  148. package/src/ColorComponent.tsx +2 -2
  149. package/src/MWaterContextComponent.tsx +1 -1
  150. package/src/{MWaterGlobalFiltersComponent.ts → MWaterGlobalFiltersComponent.tsx} +32 -33
  151. package/src/{MWaterLoaderComponent.ts → MWaterLoaderComponent.tsx} +17 -18
  152. package/src/TranslationsTabComponent.tsx +429 -0
  153. package/src/UndoStack.ts +14 -6
  154. package/src/dashboards/DashboardComponent.tsx +6 -5
  155. package/src/dashboards/DashboardDesign.ts +1 -1
  156. package/src/dashboards/ServerDashboardDataSource.ts +0 -31
  157. package/src/dashboards/SettingsModalComponent.tsx +27 -383
  158. package/src/datagrids/DatagridComponent.tsx +36 -2
  159. package/src/datagrids/DatagridDesignerComponent.tsx +241 -229
  160. package/src/datagrids/DatagridViewComponent.tsx +44 -7
  161. package/src/datagrids/OrderBysDesignerComponent.tsx +61 -70
  162. package/src/index.css +45 -2
  163. package/src/index.ts +5 -11
  164. package/src/layouts/blocks/BlocksDisplayComponent.tsx +60 -5
  165. package/src/maps/BufferLayer.ts +30 -263
  166. package/src/maps/BufferLayerDesign.ts +1 -1
  167. package/src/maps/BufferLayerDesignerComponent.tsx +2 -7
  168. package/src/maps/ChoroplethLayer.ts +30 -394
  169. package/src/maps/ChoroplethLayerDesign.ts +5 -2
  170. package/src/maps/ChoroplethLayerDesigner.tsx +169 -165
  171. package/src/maps/ClusterLayer.ts +0 -274
  172. package/src/maps/DirectMapDataSource.ts +2 -61
  173. package/src/maps/EditHoverOver.tsx +9 -5
  174. package/src/maps/GridLayer.ts +0 -224
  175. package/src/maps/HoverContent.tsx +1 -1
  176. package/src/maps/Layer.ts +1 -35
  177. package/src/maps/LeafletMapComponent.tsx +10 -19
  178. package/src/maps/MapComponent.tsx +448 -0
  179. package/src/maps/MapControlComponent.tsx +41 -0
  180. package/src/maps/MapDesign.ts +6 -0
  181. package/src/maps/MapDesignerComponent.tsx +18 -1
  182. package/src/maps/MapLayerDataSource.ts +0 -5
  183. package/src/maps/MapLayerViewDesignerComponent.ts +9 -2
  184. package/src/maps/MapLayersDesignerComponent.ts +4 -1
  185. package/src/maps/MapTranslationsTab.tsx +53 -0
  186. package/src/maps/MapUtils.ts +61 -1
  187. package/src/maps/MapViewComponent.tsx +2 -8
  188. package/src/maps/MarkersLayer.ts +101 -275
  189. package/src/maps/MarkersLayerDesign.ts +7 -1
  190. package/src/maps/MarkersLayerDesignerComponent.tsx +436 -0
  191. package/src/maps/ServerMapDataSource.ts +0 -31
  192. package/src/maps/SwitchableTileUrlLayer.tsx +0 -11
  193. package/src/maps/TileUrlLayer.tsx +0 -6
  194. package/src/maps/VectorMapViewComponent.tsx +15 -15
  195. package/src/maps/symbols/font-awesome/asterisk.png +0 -0
  196. package/src/maps/symbols/font-awesome/ban.png +0 -0
  197. package/src/maps/symbols/font-awesome/beer.png +0 -0
  198. package/src/maps/symbols/font-awesome/bell.png +0 -0
  199. package/src/maps/symbols/font-awesome/bolt.png +0 -0
  200. package/src/maps/symbols/font-awesome/building.png +0 -0
  201. package/src/maps/symbols/font-awesome/bullseye.png +0 -0
  202. package/src/maps/symbols/font-awesome/bus.png +0 -0
  203. package/src/maps/symbols/font-awesome/caret-up.png +0 -0
  204. package/src/maps/symbols/font-awesome/certificate.png +0 -0
  205. package/src/maps/symbols/font-awesome/check-circle.png +0 -0
  206. package/src/maps/symbols/font-awesome/check.png +0 -0
  207. package/src/maps/symbols/font-awesome/chevron-circle-down.png +0 -0
  208. package/src/maps/symbols/font-awesome/chevron-circle-up.png +0 -0
  209. package/src/maps/symbols/font-awesome/cloud-rain.png +0 -0
  210. package/src/maps/symbols/font-awesome/cloud.png +0 -0
  211. package/src/maps/symbols/font-awesome/comment.png +0 -0
  212. package/src/maps/symbols/font-awesome/crosshairs.png +0 -0
  213. package/src/maps/symbols/font-awesome/dot-circle-o.png +0 -0
  214. package/src/maps/symbols/font-awesome/exclamation-circle.png +0 -0
  215. package/src/maps/symbols/font-awesome/exclamation-triangle.png +0 -0
  216. package/src/maps/symbols/font-awesome/female.png +0 -0
  217. package/src/maps/symbols/font-awesome/file.png +0 -0
  218. package/src/maps/symbols/font-awesome/flag.png +0 -0
  219. package/src/maps/symbols/font-awesome/flask.png +0 -0
  220. package/src/maps/symbols/font-awesome/h-square.png +0 -0
  221. package/src/maps/symbols/font-awesome/home.png +0 -0
  222. package/src/maps/symbols/font-awesome/info-circle.png +0 -0
  223. package/src/maps/symbols/font-awesome/male.png +0 -0
  224. package/src/maps/symbols/font-awesome/medkit.png +0 -0
  225. package/src/maps/symbols/font-awesome/mobile.png +0 -0
  226. package/src/maps/symbols/font-awesome/plus-circle.png +0 -0
  227. package/src/maps/symbols/font-awesome/plus-square.png +0 -0
  228. package/src/maps/symbols/font-awesome/plus.png +0 -0
  229. package/src/maps/symbols/font-awesome/square.png +0 -0
  230. package/src/maps/symbols/font-awesome/star.png +0 -0
  231. package/src/maps/symbols/font-awesome/thumbs-down.png +0 -0
  232. package/src/maps/symbols/font-awesome/thumbs-up.png +0 -0
  233. package/src/maps/symbols/font-awesome/ticket.png +0 -0
  234. package/src/maps/symbols/font-awesome/times-circle.png +0 -0
  235. package/src/maps/symbols/font-awesome/times.png +0 -0
  236. package/src/maps/symbols/font-awesome/tint.png +0 -0
  237. package/src/maps/symbols/font-awesome/tree.png +0 -0
  238. package/src/maps/symbols/font-awesome/university.png +0 -0
  239. package/src/maps/symbols/font-awesome/usd.png +0 -0
  240. package/src/maps/symbols/font-awesome/user.png +0 -0
  241. package/src/maps/symbols/font-awesome/users.png +0 -0
  242. package/src/maps/symbols/font-awesome/wheelchair.png +0 -0
  243. package/src/maps/symbols/sdf-ize.sh +93 -0
  244. package/src/maps/vectorMaps.tsx +32 -53
  245. package/src/mwater_table_selection/IndicatorsListComponent.tsx +165 -37
  246. package/src/mwater_table_selection/MWaterCalculatedDataSourcesListComponent.tsx +111 -0
  247. package/src/mwater_table_selection/MWaterCompleteTableSelectComponent.tsx +373 -37
  248. package/src/mwater_table_selection/MWaterTableSelectComponent.tsx +12 -8
  249. package/src/mwater_table_selection/MWaterWorkflowsSelectComponent.tsx +159 -0
  250. package/src/quickfilter/{QuickfiltersComponent.ts → QuickfiltersComponent.tsx} +165 -158
  251. package/src/quickfilter/TextLiteralComponent.tsx +197 -0
  252. package/src/widgets/MapWidget.tsx +11 -1
  253. package/src/widgets/text/ExprItemEditorComponent.tsx +83 -77
  254. package/src/widgets/text/ExprUpdateModalComponent.tsx +1 -0
  255. package/test/UndoStackTests.ts +52 -1
  256. package/.storybook/config.js +0 -7
  257. package/.storybook/head.html +0 -3
  258. package/.storybook/webpack.config.js +0 -15
  259. package/src/maps/BingLayer.ts +0 -146
  260. package/src/maps/MapComponent.ts +0 -312
  261. package/src/maps/MapControlComponent.ts +0 -46
  262. package/src/maps/MarkersLayerDesignerComponent.ts +0 -374
  263. package/src/maps/RasterMapViewComponent.ts +0 -345
  264. package/src/quickfilter/TextLiteralComponent.ts +0 -165
  265. package/stories/UpdateableComponent.js +0 -29
  266. package/stories/consoles.js +0 -202
  267. package/stories/dashboards.js +0 -217
  268. package/stories/datagridDesign.js +0 -114
  269. package/stories/datagrids.js +0 -69
  270. package/stories/dates.js +0 -80
  271. package/stories/exprcomponent.js +0 -43
  272. package/stories/index.js +0 -18
  273. package/stories/leaflet.js +0 -59
  274. package/stories/maps.js +0 -24
  275. package/stories/pivotChart.js +0 -235
@@ -1,20 +1,16 @@
1
1
  import _ from "lodash"
2
- import React, { useMemo, useRef, useState } from "react"
3
- import { languages } from "../languages"
2
+ import React, { useMemo } from "react"
4
3
  import * as ui from "@mwater/react-library/lib/bootstrap"
5
- import { default as ReactSelect } from "react-select"
6
4
  import * as DashboardUtils from "./DashboardUtils"
7
5
  import ActionCancelModalComponent from "@mwater/react-library/lib/ActionCancelModalComponent"
8
6
  import QuickfiltersDesignComponent from "../quickfilter/QuickfiltersDesignComponent"
9
7
  import FiltersDesignerComponent from "../FiltersDesignerComponent"
10
- import { DataSource, LocalizedString, Schema } from "@mwater/expressions"
8
+ import { DataSource, Schema } from "@mwater/expressions"
11
9
  import { DashboardDesign } from "./DashboardDesign"
12
10
  import { GlobalFiltersElementFactoryContext } from "../MWaterContextComponent"
13
11
  import produce from "immer"
14
12
  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"
13
+ import { TranslationsTabComponent } from "../TranslationsTabComponent"
18
14
 
19
15
  export interface SettingsModalComponentProps {
20
16
  onDesignChange: (design: DashboardDesign) => void
@@ -225,388 +221,36 @@ interface LanguageTabProps {
225
221
  schema: Schema
226
222
  }
227
223
 
224
+ /** Wrapper around TranslationsTabComponent for dashboard-specific usage */
228
225
  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
- }
323
-
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()
226
+ const translatableStrings = useMemo(
227
+ () => DashboardUtils.getTranslatableStringsFromDashboard(design, schema),
228
+ [design, schema]
229
+ )
359
230
 
360
- reader.onload = (file) => {
361
- if (!file.target?.result) {
362
- return
231
+ return (
232
+ <TranslationsTabComponent
233
+ locale={design.locale || "en"}
234
+ otherLocales={design.otherLocales || []}
235
+ translations={design.translations || {}}
236
+ translatableStrings={translatableStrings}
237
+ onLocaleChange={(locale) =>
238
+ onDesignChange(produce(design, draft => {
239
+ draft.locale = locale
240
+ }))
363
241
  }
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)
242
+ onOtherLocalesChange={(otherLocales) =>
383
243
  onDesignChange(produce(design, draft => {
384
- draft.translations = newTranslations
244
+ draft.otherLocales = otherLocales
385
245
  }))
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
246
  }
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.`}
404
- </div>
405
-
406
- <ReactSelect
407
- value={_.findWhere(localeOptions, { value: design.locale || "en" }) || null}
408
- options={localeOptions}
409
- onChange={(locale: any) =>
410
- onDesignChange(produce(design, draft => {
411
- draft.locale = locale.value
412
- }))
413
- }
414
- />
415
-
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
428
- }
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
- />
520
- </>
521
- )}
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]
247
+ onTranslationsChange={(translations) =>
248
+ onDesignChange(produce(design, draft => {
249
+ draft.translations = translations
250
+ }))
574
251
  }
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>
252
+ downloadFilename="Dashboard Translations.xlsx"
253
+ baseLanguageDescription={T`This is the language that the dashboard is written in.`}
254
+ />
611
255
  )
612
256
  }
@@ -126,6 +126,12 @@ export default forwardRef<DatagridComponentRef, DatagridComponentProps>(function
126
126
  }
127
127
  let activeFilters = filters || []
128
128
 
129
+ // Clean before counting
130
+ const cleanedDesign = new DatagridUtils(schema).cleanDesign(design)
131
+ if (new DatagridUtils(schema).validateDesign(cleanedDesign)) {
132
+ return
133
+ }
134
+
129
135
  // Compile quickfilters
130
136
  activeFilters = activeFilters.concat(getQuickfilterFilters())
131
137
  datagridDataSource.countRows(design,
@@ -529,8 +535,36 @@ export default forwardRef<DatagridComponentRef, DatagridComponentProps>(function
529
535
  )
530
536
  } else if (onDesignChange) {
531
537
  return (
532
- <div style={{ textAlign: "center", marginTop: size.height / 2 }}>
533
- <a className="btn btn-link" onClick={handleEdit}>{T`Click Here to Configure`}</a>
538
+ <div style={{
539
+ display: "flex",
540
+ flexDirection: "column",
541
+ alignItems: "center",
542
+ justifyContent: "center",
543
+ height: "100%",
544
+ padding: "40px"
545
+ }}>
546
+ <div style={{
547
+ textAlign: "center",
548
+ marginBottom: "24px"
549
+ }}>
550
+ <i className="fas fa-table text-muted" style={{
551
+ fontSize: "48px",
552
+ marginBottom: "16px"
553
+ }} />
554
+ <div style={{
555
+ fontSize: "14px"
556
+ }} className="text-muted">
557
+ {T`Configure a data source and columns to get started.`}
558
+ </div>
559
+ </div>
560
+
561
+ <button
562
+ className="btn btn-primary"
563
+ onClick={handleEdit}
564
+ >
565
+ <i className="fas fa-cog" style={{ marginRight: "8px" }} />
566
+ {T`Configure`}
567
+ </button>
534
568
  </div>
535
569
  )
536
570
  } else {