@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
@@ -0,0 +1,646 @@
1
+ import _ from "lodash"
2
+ import React, { ReactNode, useEffect, useState, useRef, forwardRef, useImperativeHandle } from "react"
3
+
4
+ import AutoSizeComponent from "@mwater/react-library/lib/AutoSizeComponent"
5
+ import ActionCancelModalComponent from "@mwater/react-library/lib/ActionCancelModalComponent"
6
+ import { DataSource, Expr, ExprUtils, injectTableAlias, Schema } from "@mwater/expressions"
7
+ import { ExprCompiler } from "@mwater/expressions"
8
+ import { ExprCleaner } from "@mwater/expressions"
9
+ import DatagridViewComponent, { DatagridViewComponentRef, RowUpdate } from "./DatagridViewComponent"
10
+ import DatagridDesignerComponent from "./DatagridDesignerComponent"
11
+ import DatagridUtils from "./DatagridUtils"
12
+ import { Checkbox } from "@mwater/react-library/lib/bootstrap"
13
+
14
+ import QuickfiltersComponent from "../quickfilter/QuickfiltersComponent"
15
+ import QuickfilterCompiler from "../quickfilter/QuickfilterCompiler"
16
+ import FindReplaceModalComponent from "./FindReplaceModalComponent"
17
+ import DatagridDataSource from "./DatagridDataSource"
18
+ import { DatagridDesign, JsonQLFilter } from ".."
19
+ import { format as d3Format } from "d3-format"
20
+ import { LocaleContext } from "@mwater/expressions-ui"
21
+ import IdSelection from "../IdSelection"
22
+ import { useStableCallback } from "@mwater/react-library/lib/useStableCallback"
23
+ import DatagridQueryBuilder from "./DatagridQueryBuilder"
24
+ import produce from "immer"
25
+
26
+ export interface DatagridComponentRef {
27
+ getQuickfilterValues: () => any[]
28
+ getQuickfilterFilters: () => JsonQLFilter[]
29
+ reload: () => void
30
+ }
31
+
32
+ export interface DatagridComponentProps {
33
+ /** Schema to use */
34
+ schema: Schema
35
+
36
+ /** Data source to use */
37
+ dataSource: DataSource
38
+
39
+ /** Datagrid data source to use */
40
+ datagridDataSource: DatagridDataSource
41
+
42
+ /** Design of datagrid to use */
43
+ design: DatagridDesign
44
+
45
+ /** Called when design changes */
46
+ onDesignChange?: (design: DatagridDesign) => void
47
+
48
+ /** Extra element to include in title at left */
49
+ titleElem?: ReactNode
50
+
51
+ /** Extra elements to add to right */
52
+ extraTitleButtonsElem?: ReactNode
53
+
54
+ /** Check if a value is editable by testing if underlying expression is editable */
55
+ canEditExpr?: (tableId: string, rowId: any, expr: Expr) => Promise<boolean>
56
+
57
+ /** Update cell values by updating set of expressions and values */
58
+ updateExprValues?: (tableId: string, rowUpdates: RowUpdate[]) => Promise<void>
59
+
60
+ /** Called when row is clicked with (tableId, rowId) */
61
+ onRowClick?: (tableId: string, rowId: any) => void
62
+
63
+ /** Called when row is double-clicked with (tableId, rowId) */
64
+ onRowDoubleClick?: (tableId: string, rowId: any) => void
65
+
66
+ /** Called when a set of rows is deleted with (tableId, rowIds). Can be cancelled via the abort signal */
67
+ onRowsDelete?: (tableId: string, rowIds: any[], signal: AbortSignal) => Promise<void>
68
+
69
+ /** Locked quickfilter values. See README in quickfilters */
70
+ quickfilterLocks?: any[]
71
+
72
+ // Filters to add to the datagrid
73
+ filters?: JsonQLFilter[]
74
+ }
75
+
76
+ /**
77
+ * Datagrid with decorations
78
+ * Design should be cleaned already before being passed in (see DatagridUtils)
79
+ */
80
+ export default forwardRef<DatagridComponentRef, DatagridComponentProps>(function DatagridComponent(props: DatagridComponentProps, ref) {
81
+ const { schema, dataSource, datagridDataSource, design, onDesignChange, titleElem, extraTitleButtonsElem, canEditExpr, updateExprValues, onRowClick, onRowDoubleClick, quickfilterLocks, filters = [] } = props
82
+
83
+ // State management
84
+ /** is design being edited */
85
+ const [editingDesign, setEditingDesign] = useState(false)
86
+
87
+ /** True if cells can be edited directly */
88
+ const [dataEditingEnabled, setDataEditingEnabled] = useState(false)
89
+
90
+ /** Height of quickfilters */
91
+ const [quickfiltersHeight, setQuickfiltersHeight] = useState<number | null>(null)
92
+
93
+ /** Values of quickfilters */
94
+ const [quickfiltersValues, setQuickfiltersValues] = useState<any[] | null>(null)
95
+
96
+ /** Refresh key */
97
+ const [refreshKey, setRefreshKey] = useState(1)
98
+
99
+ /** Number of rows */
100
+ const [numRows, setNumRows] = useState<number>()
101
+
102
+ /** Selected rows */
103
+ const [selectedRows, setSelectedRows] = useState(new IdSelection())
104
+
105
+ /** True if delete confirmation modal is open */
106
+ const [showDeleteConfirmation, setShowDeleteConfirmation] = useState(false)
107
+
108
+ /** IDs of rows to be deleted */
109
+ const [rowIdsToDelete, setRowIdsToDelete] = useState<string[]>()
110
+
111
+ /** True when loading IDs of rows to delete */
112
+ const [loadingRowIdsToDelete, setLoadingRowIdsToDelete] = useState(false)
113
+
114
+ // Refs
115
+ const datagridViewRef = React.useRef<DatagridViewComponentRef | null>(null)
116
+ const quickfiltersRef = React.useRef<HTMLDivElement | null>(null)
117
+ const findReplaceModalRef = React.useRef<FindReplaceModalComponent | null>(null)
118
+
119
+ const reload = () => {
120
+ datagridViewRef.current?.reload()
121
+ }
122
+
123
+ const loadRowCount = useStableCallback(() => {
124
+ if (!design.showNumRows) {
125
+ return
126
+ }
127
+ let activeFilters = filters || []
128
+
129
+ // Compile quickfilters
130
+ activeFilters = activeFilters.concat(getQuickfilterFilters())
131
+ datagridDataSource.countRows(design,
132
+ activeFilters,
133
+ (error: any, count?: number) => {
134
+ if (error) {
135
+ console.error(error)
136
+ alert(T`Error loading data`)
137
+ return
138
+ }
139
+ setNumRows(count)
140
+ })
141
+ })
142
+
143
+ useEffect(() => {
144
+ loadRowCount()
145
+ }, [JSON.stringify(design), JSON.stringify(quickfiltersValues), refreshKey])
146
+
147
+ useEffect(() => {
148
+ // Calculate quickfilters height
149
+ if (quickfiltersRef.current) {
150
+ if (quickfiltersHeight !== quickfiltersRef.current.offsetHeight) {
151
+ setQuickfiltersHeight(quickfiltersRef.current.offsetHeight)
152
+ }
153
+ } else {
154
+ setQuickfiltersHeight(0)
155
+ }
156
+ }, [quickfiltersRef.current, JSON.stringify(design.quickfilters)])
157
+
158
+ const handleRefreshData = () => {
159
+ dataSource.clearCache?.()
160
+ setRefreshKey(refreshKey + 1)
161
+ }
162
+
163
+ const handleDeleteRows = async (signal: AbortSignal) => {
164
+ try {
165
+ if (!rowIdsToDelete) {
166
+ return
167
+ }
168
+
169
+ await props.onRowsDelete!(design.table!, rowIdsToDelete, signal)
170
+ setShowDeleteConfirmation(false)
171
+
172
+ // If not aborted, clear selected rows
173
+ if (!signal.aborted) {
174
+ setSelectedRows(new IdSelection())
175
+ handleRefreshData()
176
+ }
177
+ setRowIdsToDelete(undefined)
178
+ } catch (error) {
179
+ if (error.name !== "AbortError") {
180
+ alert(`Error deleting rows: ${error}`)
181
+ }
182
+ }
183
+ }
184
+
185
+ const handleShowDeleteConfirmation = async () => {
186
+ setShowDeleteConfirmation(true)
187
+ setLoadingRowIdsToDelete(true)
188
+ setRowIdsToDelete(undefined)
189
+
190
+ try {
191
+ // Get row ids
192
+ let rowIds = Object.keys(selectedRows.ids)
193
+
194
+ // If inverted, get all ids and remove selected ids
195
+ if (selectedRows.inverted) {
196
+ // Create modified design to just get ids
197
+ const idsDesign = produce(design, (draft) => {
198
+ draft.columns = []
199
+ draft.subtables = []
200
+ })
201
+
202
+ // Create query builder
203
+ const queryBuilder = new DatagridQueryBuilder(schema)
204
+ const query = queryBuilder.createQuery(idsDesign, {
205
+ extraFilters: filters
206
+ })
207
+
208
+ // Perform query
209
+ const rows = await dataSource.performQuery(query)
210
+ rowIds = rows.map((row: any) => row.id)
211
+
212
+ // Remove rows from rowIds
213
+ rowIds = rowIds.filter(id => !selectedRows.ids[id])
214
+ }
215
+
216
+ setRowIdsToDelete(rowIds)
217
+ } catch (error) {
218
+ alert(`Error loading rows to delete: ${error}`)
219
+ setShowDeleteConfirmation(false)
220
+ } finally {
221
+ setLoadingRowIdsToDelete(false)
222
+ }
223
+ }
224
+
225
+ // Get the values of the quick filters
226
+ const getQuickfilterValues = () => {
227
+ return quickfiltersValues || []
228
+ }
229
+
230
+ /** Get filters that are applied by the quickfilters */
231
+ const getQuickfilterFilters = () => {
232
+ return new QuickfilterCompiler(schema).compile(
233
+ design.quickfilters,
234
+ quickfiltersValues,
235
+ quickfilterLocks
236
+ )
237
+ }
238
+
239
+ const handleDataEditingToggle = () => {
240
+ if (dataEditingEnabled) {
241
+ setDataEditingEnabled(false)
242
+ } else {
243
+ if (confirm(T`Turn on data editing? This will allow you to edit the live data and is an advanced feature.`)) {
244
+ setDataEditingEnabled(true)
245
+ }
246
+ }
247
+ }
248
+
249
+ const handleEdit = () => {
250
+ setEditingDesign(true)
251
+ }
252
+
253
+ /** Get datagrid filter compiled for quickfilter filtering */
254
+ const getCompiledFilters = (): JsonQLFilter[] => {
255
+ let jsonql
256
+ const exprCompiler = new ExprCompiler(schema)
257
+ const exprUtils = new ExprUtils(schema)
258
+ const exprCleaner = new ExprCleaner(schema)
259
+
260
+ const compiledFilters: JsonQLFilter[] = []
261
+
262
+ if (design.filter) {
263
+ // First clean the filter
264
+ const cleanExpr = exprCleaner.cleanExpr(design.filter, { table: design.table! })
265
+
266
+ jsonql = exprCompiler.compileExpr({ expr: cleanExpr, tableAlias: "{alias}" })
267
+ if (jsonql) {
268
+ compiledFilters.push({
269
+ table: design.table!,
270
+ jsonql
271
+ })
272
+ }
273
+ }
274
+
275
+ // Add global filters
276
+ for (let filter of design.globalFilters || []) {
277
+ // Check if exists and is correct type
278
+ const column = schema.getColumn(design.table!, filter.columnId)
279
+ if (!column) {
280
+ continue
281
+ }
282
+
283
+ const columnExpr: Expr = { type: "field", table: design.table!, column: column.id }
284
+ if (exprUtils.getExprType(columnExpr) !== filter.columnType) {
285
+ continue
286
+ }
287
+
288
+ // Create expr
289
+ let expr: Expr = {
290
+ type: "op",
291
+ op: filter.op,
292
+ table: design.table!,
293
+ exprs: [columnExpr as Expr].concat(filter.exprs)
294
+ }
295
+
296
+ // Clean expr
297
+ expr = exprCleaner.cleanExpr(expr, { table: design.table! })
298
+
299
+ jsonql = exprCompiler.compileExpr({ expr, tableAlias: "{alias}" })
300
+ if (jsonql) {
301
+ compiledFilters.push({
302
+ table: design.table!,
303
+ jsonql
304
+ })
305
+ }
306
+ }
307
+
308
+ return compiledFilters
309
+ }
310
+
311
+ const renderDeleteRows = () => {
312
+ if (!props.onRowsDelete || selectedRows.isNone()) {
313
+ return null
314
+ }
315
+ return <button className="btn btn-link btn-sm" onClick={handleShowDeleteConfirmation}>
316
+ <i className="fas fa-times" />
317
+ {" "}
318
+ {T`Delete Selected Rows`}
319
+ </button>
320
+ }
321
+
322
+ /** Toggle to allow cell editing */
323
+ const renderCellEdit = () => {
324
+ if (!canEditExpr) {
325
+ return null
326
+ }
327
+
328
+ const label = <>
329
+ <i className={dataEditingEnabled ? "fa fa-fw fa-check-square" : "fa fa-fw fa-square-o"} />
330
+ {" "}
331
+ {T`Data Editing`}
332
+ </>
333
+
334
+ return (
335
+ <a
336
+ key="cell-edit"
337
+ className="btn btn-link btn-sm"
338
+ onClick={handleDataEditingToggle}
339
+ >
340
+ {label}
341
+ </a>
342
+ )
343
+ }
344
+
345
+ const renderEditButton = () => {
346
+ if (!onDesignChange) {
347
+ return null
348
+ }
349
+
350
+ return (
351
+ <button
352
+ type="button"
353
+ className="btn btn-primary"
354
+ onClick={handleEdit}
355
+ >
356
+ <span className="fas fa-cog" />
357
+ {" "}
358
+ {T`Settings`}
359
+ </button>
360
+ )
361
+ }
362
+
363
+ const renderFindReplace = () => {
364
+ if (!dataEditingEnabled) {
365
+ return null
366
+ }
367
+
368
+ return (
369
+ <a
370
+ key="findreplace"
371
+ className="btn btn-link btn-sm"
372
+ onClick={() => findReplaceModalRef.current!.show()}
373
+ >
374
+ {T`Find/Replace`}
375
+ </a>
376
+ )
377
+ }
378
+
379
+ const renderTitleBar = () => {
380
+ return (
381
+ <div style={{ position: "absolute", top: 0, left: 0, right: 0, height: 40, padding: 4 }}>
382
+ <div style={{ float: "right" }}>
383
+ {design.showNumRows && numRows ? <small className='text-muted text-sm'>{`${d3Format(',')(numRows)} rows`}</small> : undefined}
384
+ {renderDeleteRows()}
385
+ {renderFindReplace()}
386
+ {renderCellEdit()}
387
+ {renderEditButton()}
388
+ <a key="refresh" className="btn btn-link btn-sm" onClick={handleRefreshData}>
389
+ <span className="fas fa-sync" />
390
+ {" "}
391
+ <span className="hide-600px">{T`Refresh`}</span>
392
+ </a>
393
+ {extraTitleButtonsElem}
394
+ </div>
395
+ {titleElem}
396
+ </div>
397
+ )
398
+ }
399
+
400
+ const renderQuickfilter = () => {
401
+ return (
402
+ <div
403
+ style={{ position: "absolute", top: 40, left: 0, right: 0 }}
404
+ ref={quickfiltersRef}
405
+ >
406
+ <QuickfiltersComponent
407
+ design={design.quickfilters!}
408
+ schema={schema}
409
+ dataSource={dataSource}
410
+ quickfiltersDataSource={datagridDataSource.getQuickfiltersDataSource()}
411
+ values={quickfiltersValues || []}
412
+ onValuesChange={(values: any) => setQuickfiltersValues(values)}
413
+ locks={quickfilterLocks}
414
+ filters={getCompiledFilters()}
415
+ />
416
+ </div>
417
+ )
418
+ }
419
+
420
+ // Renders the editor modal
421
+ const renderEditor = () => {
422
+ if (!editingDesign) {
423
+ return null
424
+ }
425
+
426
+ return (
427
+ <DatagridEditorComponent
428
+ schema={schema}
429
+ dataSource={dataSource}
430
+ design={design}
431
+ onDesignChange={(newDesign: any) => {
432
+ // If quickfilters have changed, reset values
433
+ if (!_.isEqual(design.quickfilters, newDesign.quickfilters)) {
434
+ setQuickfiltersValues(null)
435
+ }
436
+
437
+ onDesignChange!(newDesign)
438
+ setEditingDesign(false)
439
+ }}
440
+ onCancel={() => setEditingDesign(false)}
441
+ />
442
+ )
443
+ }
444
+
445
+ const renderFindReplaceModal = (filters: any) => {
446
+ return (
447
+ <FindReplaceModalComponent
448
+ ref={findReplaceModalRef}
449
+ schema={schema}
450
+ dataSource={dataSource}
451
+ design={design}
452
+ filters={filters}
453
+ updateExprValues={updateExprValues!}
454
+ onUpdate={() => {
455
+ // Reload
456
+ datagridViewRef.current?.reload()
457
+ }}
458
+ />
459
+ )
460
+ }
461
+
462
+ let activeFilters = filters || []
463
+
464
+ // Compile quickfilters
465
+ activeFilters = activeFilters.concat(getQuickfilterFilters())
466
+
467
+ useImperativeHandle(
468
+ ref,
469
+ (): DatagridComponentRef => ({
470
+ getQuickfilterValues,
471
+ getQuickfilterFilters,
472
+ reload
473
+ })
474
+ )
475
+
476
+ return (
477
+ <LocaleContext.Provider value={design.locale || "en"}>
478
+ <div
479
+ style={{
480
+ width: "100%",
481
+ height: "100%",
482
+ position: "relative",
483
+ paddingTop: 40 + (quickfiltersHeight || 0)
484
+ }}
485
+ >
486
+ {renderTitleBar()}
487
+ {renderQuickfilter()}
488
+
489
+ {renderEditor()}
490
+ {renderFindReplaceModal(activeFilters)}
491
+ {showDeleteConfirmation && (
492
+ <DeleteConfirmationModal
493
+ onConfirm={handleDeleteRows}
494
+ onCancel={() => {
495
+ setShowDeleteConfirmation(false)
496
+ setRowIdsToDelete(undefined)
497
+ }}
498
+ numRows={rowIdsToDelete?.length}
499
+ loadingNumRows={loadingRowIdsToDelete}
500
+ />
501
+ )}
502
+
503
+ <AutoSizeComponent injectWidth injectHeight>
504
+ {(size: any) => {
505
+ // Clean before displaying
506
+ const cleanedDesign = new DatagridUtils(schema).cleanDesign(design)
507
+
508
+ if (!new DatagridUtils(schema).validateDesign(cleanedDesign)) {
509
+ return (
510
+ <DatagridViewComponent
511
+ ref={datagridViewRef}
512
+ width={size.width - 1} // minus 1 px to test if it solves the jitter with scroll
513
+ height={size.height - 1}
514
+ pageSize={100}
515
+ schema={schema}
516
+ dataSource={dataSource}
517
+ datagridDataSource={datagridDataSource}
518
+ design={cleanedDesign}
519
+ filters={activeFilters}
520
+ onDesignChange={onDesignChange}
521
+ onRowClick={onRowClick}
522
+ onRowDoubleClick={onRowDoubleClick}
523
+ selectedRows={dataEditingEnabled && props.onRowsDelete != null ? selectedRows : undefined}
524
+ onSelectedRowsChange={dataEditingEnabled && props.onRowsDelete != null ? setSelectedRows : undefined}
525
+ canEditExpr={dataEditingEnabled ? canEditExpr : undefined}
526
+ updateExprValues={dataEditingEnabled ? updateExprValues : undefined}
527
+ refreshKey={refreshKey}
528
+ />
529
+ )
530
+ } else if (onDesignChange) {
531
+ return (
532
+ <div style={{ textAlign: "center", marginTop: size.height / 2 }}>
533
+ <a className="btn btn-link" onClick={handleEdit}>{T`Click Here to Configure`}</a>
534
+ </div>
535
+ )
536
+ } else {
537
+ return null
538
+ }
539
+ }}
540
+ </AutoSizeComponent>
541
+ </div>
542
+ </LocaleContext.Provider>
543
+ )
544
+ })
545
+
546
+ interface DatagridEditorComponentProps {
547
+ schema: Schema
548
+ dataSource: DataSource
549
+ design: DatagridDesign
550
+ onDesignChange: (design: DatagridDesign) => void
551
+ onCancel: () => void
552
+ }
553
+
554
+ /** Popup editor */
555
+ function DatagridEditorComponent(props: DatagridEditorComponentProps) {
556
+ const { schema, dataSource, design, onDesignChange, onCancel } = props
557
+ const [localDesign, setLocalDesign] = useState(design)
558
+
559
+ return (
560
+ <ActionCancelModalComponent
561
+ onAction={() => {
562
+ onDesignChange(localDesign)
563
+ setLocalDesign(design)
564
+ }}
565
+ onCancel={onCancel}
566
+ size="large"
567
+ >
568
+ <DatagridDesignerComponent
569
+ schema={schema}
570
+ dataSource={dataSource}
571
+ design={localDesign}
572
+ onDesignChange={(design: any) => setLocalDesign(design)}
573
+ />
574
+ </ActionCancelModalComponent>
575
+ )
576
+ }
577
+
578
+ interface DeleteConfirmationModalProps {
579
+ /** Called when deletion is confirmed */
580
+ onConfirm: (signal: AbortSignal) => Promise<void>
581
+ /** Called when modal is cancelled */
582
+ onCancel: () => void
583
+ /** Number of rows to be deleted */
584
+ numRows?: number
585
+ /** True when loading number of rows */
586
+ loadingNumRows: boolean
587
+ }
588
+
589
+ /** Modal that confirms deletion of rows */
590
+ function DeleteConfirmationModal(props: DeleteConfirmationModalProps) {
591
+ const { onConfirm, onCancel, numRows, loadingNumRows } = props
592
+ const [confirmed, setConfirmed] = useState(false)
593
+ const [deleting, setDeleting] = useState(false)
594
+ const abortControllerRef = useRef<AbortController>()
595
+
596
+ // If more than 1000 rows, show an error
597
+ const tooManyRows = numRows && numRows > 1000
598
+
599
+ const handleConfirm = async () => {
600
+ setDeleting(true)
601
+ abortControllerRef.current = new AbortController()
602
+ try {
603
+ await onConfirm(abortControllerRef.current.signal)
604
+ }
605
+ finally {
606
+ setDeleting(false)
607
+ }
608
+ }
609
+
610
+ const handleCancel = () => {
611
+ if (deleting) {
612
+ abortControllerRef.current?.abort()
613
+ }
614
+ onCancel()
615
+ }
616
+
617
+ return (
618
+ <ActionCancelModalComponent
619
+ title={T`Confirm Delete`}
620
+ onAction={handleConfirm}
621
+ onCancel={handleCancel}
622
+ actionLabel={T`Delete`}
623
+ size="normal"
624
+ actionBusy={deleting}
625
+ actionDisabled={!confirmed || loadingNumRows}
626
+ >
627
+ <div>
628
+ {loadingNumRows ? (
629
+ <div className="text-muted"><i className="fa fa-spinner fa-spin" /> {T`Loading rows to delete...`}</div>
630
+ ) : (
631
+ <div>{T`This will permanently delete ${d3Format(",")(numRows || 0)} row(s).`}</div>
632
+ )}
633
+ {tooManyRows && (
634
+ <div className="alert alert-danger">{T`Cannot delete more than 1000 rows at a time.`}</div>
635
+ )}
636
+ {!tooManyRows && numRows && numRows > 0 && (
637
+ <div className="mt-3">
638
+ <Checkbox value={confirmed} onChange={setConfirmed}>
639
+ {T`I understand that this action cannot be undone`}
640
+ </Checkbox>
641
+ </div>
642
+ )}
643
+ </div>
644
+ </ActionCancelModalComponent>
645
+ )
646
+ }