@mwater/visualization 5.4.1 → 5.4.3

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 (273) 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 +12 -20
  21. package/lib/dashboards/DashboardComponent.js +109 -69
  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/languages.js +6 -1
  47. package/lib/layouts/blocks/HorizontalBlockComponent.js +2 -2
  48. package/lib/mWaterLoader.d.ts +1 -1
  49. package/lib/maps/BufferLayer.d.ts +7 -5
  50. package/lib/maps/BufferLayer.js +69 -48
  51. package/lib/maps/BufferLayerDesign.d.ts +21 -14
  52. package/lib/maps/BufferLayerDesignerComponent.d.ts +16 -31
  53. package/lib/maps/BufferLayerDesignerComponent.js +68 -102
  54. package/lib/maps/ChoroplethLayer.d.ts +5 -4
  55. package/lib/maps/ChoroplethLayer.js +32 -9
  56. package/lib/maps/ChoroplethLayerDesign.d.ts +6 -2
  57. package/lib/maps/ChoroplethLayerDesigner.js +4 -2
  58. package/lib/maps/ClusterLayer.d.ts +3 -4
  59. package/lib/maps/ClusterLayer.js +2 -1
  60. package/lib/maps/DetailLevelSelectComponent.js +1 -1
  61. package/lib/maps/DirectMapDataSource.js +2 -1
  62. package/lib/maps/EditPopupComponent.js +5 -3
  63. package/lib/maps/GridLayer.d.ts +3 -4
  64. package/lib/maps/GridLayer.js +2 -1
  65. package/lib/maps/GridLayerDesigner.js +5 -3
  66. package/lib/maps/HoverContent.d.ts +11 -3
  67. package/lib/maps/HoverContent.js +25 -9
  68. package/lib/maps/Layer.d.ts +24 -3
  69. package/lib/maps/Layer.js +5 -1
  70. package/lib/maps/LayerFactory.js +0 -8
  71. package/lib/maps/LayerLegendComponent.js +0 -1
  72. package/lib/maps/LayerSwitcherComponent.d.ts +1 -0
  73. package/lib/maps/LayerSwitcherComponent.js +1 -1
  74. package/lib/maps/LeafletMapComponent.js +3 -1
  75. package/lib/maps/LegendComponent.d.ts +1 -0
  76. package/lib/maps/LegendComponent.js +9 -1
  77. package/lib/maps/MWaterServerLayer.d.ts +2 -2
  78. package/lib/maps/MWaterServerLayer.js +2 -2
  79. package/lib/maps/MapComponent.js +3 -3
  80. package/lib/maps/MapDesign.d.ts +2 -0
  81. package/lib/maps/MapDesignerComponent.d.ts +4 -3
  82. package/lib/maps/MapDesignerComponent.js +68 -74
  83. package/lib/maps/MapLayerViewDesignerComponent.js +2 -2
  84. package/lib/maps/MapUtils.d.ts +4 -0
  85. package/lib/maps/MapUtils.js +19 -0
  86. package/lib/maps/MapViewComponent.d.ts +8 -3
  87. package/lib/maps/MarkersLayer.d.ts +5 -4
  88. package/lib/maps/MarkersLayer.js +33 -7
  89. package/lib/maps/MarkersLayerDesign.d.ts +19 -16
  90. package/lib/maps/PopupFilterJoinsUtils.d.ts +6 -3
  91. package/lib/maps/PopupFilterJoinsUtils.js +0 -6
  92. package/lib/maps/RasterMapViewComponent.d.ts +3 -31
  93. package/lib/maps/RasterMapViewComponent.js +7 -2
  94. package/lib/maps/ServerMapDataSource.js +2 -1
  95. package/lib/maps/SwitchableTileUrlLayer.d.ts +3 -3
  96. package/lib/maps/SwitchableTileUrlLayer.js +2 -1
  97. package/lib/maps/TileUrlLayer.d.ts +4 -5
  98. package/lib/maps/TileUrlLayer.js +2 -1
  99. package/lib/maps/VectorMapViewComponent.d.ts +5 -37
  100. package/lib/maps/VectorMapViewComponent.js +19 -8
  101. package/lib/maps/maps.d.ts +3 -0
  102. package/lib/quickfilter/Quickfilter.d.ts +2 -0
  103. package/lib/quickfilter/QuickfiltersComponent.d.ts +2 -0
  104. package/lib/quickfilter/QuickfiltersComponent.js +9 -7
  105. package/lib/quickfilter/QuickfiltersDesignComponent.d.ts +5 -30
  106. package/lib/quickfilter/QuickfiltersDesignComponent.js +56 -63
  107. package/lib/richtext/ExprItemsHtmlConverter.d.ts +5 -2
  108. package/lib/richtext/ExprItemsHtmlConverter.js +4 -4
  109. package/lib/richtext/ExprItemsTranslator.d.ts +5 -0
  110. package/lib/richtext/ExprItemsTranslator.js +149 -0
  111. package/lib/richtext/ItemsHtmlConverter.d.ts +1 -1
  112. package/lib/richtext/ItemsHtmlConverter.js +31 -15
  113. package/lib/wellknown.js +12 -9
  114. package/lib/widgets/IFrameWidget.d.ts +4 -4
  115. package/lib/widgets/ImageWidget.d.ts +7 -4
  116. package/lib/widgets/ImageWidget.js +9 -1
  117. package/lib/widgets/ImageWidgetComponent.d.ts +1 -0
  118. package/lib/widgets/ImageWidgetComponent.js +1 -1
  119. package/lib/widgets/MapWidget.d.ts +5 -48
  120. package/lib/widgets/MapWidget.js +26 -63
  121. package/lib/widgets/MarkdownWidget.d.ts +3 -0
  122. package/lib/widgets/MarkdownWidget.js +3 -0
  123. package/lib/widgets/TOCWidget.d.ts +15 -27
  124. package/lib/widgets/TOCWidget.js +107 -183
  125. package/lib/widgets/Widget.d.ts +18 -7
  126. package/lib/widgets/Widget.js +4 -0
  127. package/lib/widgets/WidgetScopesViewComponent.js +1 -1
  128. package/lib/widgets/charts/Chart.d.ts +10 -1
  129. package/lib/widgets/charts/Chart.js +22 -11
  130. package/lib/widgets/charts/ChartViewComponent.d.ts +4 -0
  131. package/lib/widgets/charts/ChartViewComponent.js +6 -3
  132. package/lib/widgets/charts/ChartWidget.d.ts +2 -0
  133. package/lib/widgets/charts/ChartWidget.js +9 -1
  134. package/lib/widgets/charts/ChartWidgetComponent.d.ts +4 -0
  135. package/lib/widgets/charts/ChartWidgetComponent.js +2 -2
  136. package/lib/widgets/charts/calendar/CalendarChart.d.ts +1 -0
  137. package/lib/widgets/charts/calendar/CalendarChart.js +26 -0
  138. package/lib/widgets/charts/calendar/CalendarChartViewComponent.js +3 -1
  139. package/lib/widgets/charts/imagemosaic/ImageMosaicChart.d.ts +1 -0
  140. package/lib/widgets/charts/imagemosaic/ImageMosaicChart.js +8 -0
  141. package/lib/widgets/charts/layered/LayeredChart.d.ts +2 -0
  142. package/lib/widgets/charts/layered/LayeredChart.js +63 -3
  143. package/lib/widgets/charts/layered/LayeredChartCompiler.d.ts +1 -1
  144. package/lib/widgets/charts/layered/LayeredChartCompiler.js +1 -1
  145. package/lib/widgets/charts/layered/LayeredChartDesignerComponent.js +2 -2
  146. package/lib/widgets/charts/layered/LayeredChartViewComponent.js +8 -3
  147. package/lib/widgets/charts/pivot/PivotChart.d.ts +1 -0
  148. package/lib/widgets/charts/pivot/PivotChart.js +63 -0
  149. package/lib/widgets/charts/pivot/PivotChartLayoutComponent.js +1 -1
  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 +189 -125
  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/languages.ts +6 -1
  192. package/src/layouts/blocks/HorizontalBlockComponent.ts +2 -2
  193. package/src/mWaterLoader.ts +1 -1
  194. package/src/maps/BufferLayer.ts +83 -60
  195. package/src/maps/BufferLayerDesign.ts +20 -14
  196. package/src/maps/BufferLayerDesignerComponent.tsx +309 -0
  197. package/src/maps/ChoroplethLayer.ts +40 -19
  198. package/src/maps/ChoroplethLayerDesign.ts +4 -2
  199. package/src/maps/ChoroplethLayerDesigner.tsx +4 -2
  200. package/src/maps/ClusterLayer.ts +4 -10
  201. package/src/maps/DetailLevelSelectComponent.ts +1 -1
  202. package/src/maps/DirectMapDataSource.ts +2 -1
  203. package/src/maps/EditPopupComponent.ts +7 -3
  204. package/src/maps/GridLayer.ts +4 -10
  205. package/src/maps/GridLayerDesigner.tsx +5 -3
  206. package/src/maps/HoverContent.tsx +40 -16
  207. package/src/maps/Layer.ts +28 -10
  208. package/src/maps/LayerFactory.ts +0 -8
  209. package/src/maps/LayerLegendComponent.ts +2 -4
  210. package/src/maps/LayerSwitcherComponent.tsx +6 -2
  211. package/src/maps/LeafletMapComponent.tsx +3 -1
  212. package/src/maps/LegendComponent.tsx +10 -1
  213. package/src/maps/MWaterServerLayer.ts +3 -3
  214. package/src/maps/MapComponent.ts +3 -3
  215. package/src/maps/MapDesign.ts +3 -0
  216. package/src/maps/MapDesignerComponent.tsx +165 -162
  217. package/src/maps/MapLayerViewDesignerComponent.ts +2 -2
  218. package/src/maps/MapUtils.ts +24 -0
  219. package/src/maps/MapViewComponent.tsx +11 -3
  220. package/src/maps/MarkersLayer.ts +44 -18
  221. package/src/maps/MarkersLayerDesign.ts +19 -16
  222. package/src/maps/PopupFilterJoinsUtils.ts +6 -2
  223. package/src/maps/RasterMapViewComponent.ts +9 -45
  224. package/src/maps/ServerMapDataSource.ts +2 -2
  225. package/src/maps/SwitchableTileUrlLayer.tsx +4 -10
  226. package/src/maps/TileUrlLayer.tsx +4 -10
  227. package/src/maps/VectorMapViewComponent.tsx +28 -55
  228. package/src/maps/maps.ts +3 -0
  229. package/src/quickfilter/Quickfilter.ts +3 -0
  230. package/src/quickfilter/QuickfiltersComponent.ts +13 -7
  231. package/src/quickfilter/QuickfiltersDesignComponent.tsx +127 -128
  232. package/src/richtext/ExprItemsHtmlConverter.ts +9 -5
  233. package/src/richtext/ExprItemsTranslator.ts +176 -0
  234. package/src/richtext/ItemsHtmlConverter.ts +33 -18
  235. package/src/wellknown.ts +33 -30
  236. package/src/widgets/ImageWidget.ts +10 -1
  237. package/src/widgets/ImageWidgetComponent.ts +3 -2
  238. package/src/widgets/{MapWidget.ts → MapWidget.tsx} +90 -101
  239. package/src/widgets/MarkdownWidget.ts +3 -0
  240. package/src/widgets/TOCWidget.tsx +281 -0
  241. package/src/widgets/Widget.ts +25 -5
  242. package/src/widgets/WidgetScopesViewComponent.ts +2 -1
  243. package/src/widgets/charts/Chart.ts +31 -12
  244. package/src/widgets/charts/ChartViewComponent.ts +13 -3
  245. package/src/widgets/charts/ChartWidget.ts +11 -1
  246. package/src/widgets/charts/ChartWidgetComponent.tsx +9 -1
  247. package/src/widgets/charts/calendar/CalendarChart.ts +29 -0
  248. package/src/widgets/charts/calendar/CalendarChartViewComponent.tsx +3 -1
  249. package/src/widgets/charts/imagemosaic/ImageMosaicChart.ts +9 -0
  250. package/src/widgets/charts/layered/LayeredChart.ts +71 -3
  251. package/src/widgets/charts/layered/LayeredChartCompiler.ts +2 -2
  252. package/src/widgets/charts/layered/LayeredChartDesignerComponent.tsx +4 -2
  253. package/src/widgets/charts/layered/LayeredChartViewComponent.ts +10 -4
  254. package/src/widgets/charts/pivot/PivotChart.ts +73 -0
  255. package/src/widgets/charts/pivot/PivotChartLayoutComponent.tsx +1 -1
  256. package/src/widgets/charts/pivot/SegmentDesignerComponent.tsx +6 -4
  257. package/src/widgets/charts/table/OrderingsComponent.tsx +2 -1
  258. package/src/widgets/charts/table/TableChart.ts +17 -0
  259. package/src/widgets/text/TextComponent.tsx +22 -12
  260. package/src/widgets/text/TextWidget.ts +9 -2
  261. package/src/widgets/text/TextWidgetComponent.tsx +16 -1
  262. package/src/widgets/text/TextWidgetDesign.ts +4 -7
  263. package/test/IdSelectionTests.ts +54 -0
  264. package/test/LayeredChartCompilerTests.ts +0 -2
  265. package/test/richtext/ExprItemsTranslatorTests.ts +144 -0
  266. package/test/wellknownTests.ts +144 -0
  267. package/src/datagrids/DatagridComponent.ts +0 -478
  268. package/src/datagrids/DatagridViewComponent.ts +0 -464
  269. package/src/datagrids/EditExprCellComponent.tsx +0 -305
  270. package/src/datagrids/README.md +0 -3
  271. package/src/maps/BufferLayerDesignerComponent.ts +0 -311
  272. package/src/widgets/TOCWidget.ts +0 -326
  273. package/test/LegoLayoutEngineTests.ts +0 -69
@@ -0,0 +1,539 @@
1
+ import _ from "lodash"
2
+ import React from "react"
3
+ import { useState, useEffect, useRef, forwardRef, useImperativeHandle, useCallback } from "react"
4
+ import { GridComponent, RenderCellProps, RenderColHeaderProps, RenderCellEditorProps, RenderRowHeaderProps } from "@mwater/react-library/lib/GridComponent"
5
+ import { DataSource, Expr, ExprUtils, Schema } from "@mwater/expressions"
6
+ import ExprCellComponent from "./ExprCellComponent"
7
+ import DatagridDataSource from "./DatagridDataSource"
8
+ import { DatagridDesign, JsonQLFilter } from ".."
9
+ import produce from "immer"
10
+ import { canEditType, CellEditor } from "./CellEditor"
11
+ import { useStableCallback } from "@mwater/react-library/lib/useStableCallback"
12
+ import { formatValue } from "../valueFormatter"
13
+ import { canFormatType } from "../valueFormatter"
14
+ import IdSelection from "../IdSelection"
15
+
16
+ export interface DatagridViewComponentProps {
17
+ /** Width of control */
18
+ width: number
19
+
20
+ /** Height of control */
21
+ height: number
22
+
23
+ /** Number of rows to load per page. Defaults to 100. */
24
+ pageSize?: number
25
+
26
+ schema: Schema
27
+ dataSource: DataSource
28
+ datagridDataSource: DatagridDataSource
29
+
30
+ design: DatagridDesign
31
+ onDesignChange?: (design: DatagridDesign) => void
32
+
33
+ filters?: JsonQLFilter[]
34
+
35
+ /** Current selection of rows */
36
+ selectedRows?: IdSelection
37
+
38
+ /** Called when selection changes */
39
+ onSelectedRowsChange?: (selection: IdSelection) => void
40
+
41
+ /** Check if a cell is editable by testing if underlying expression is editable */
42
+ canEditExpr?: (tableId: string, rowId: any, expr: Expr) => Promise<boolean>
43
+
44
+ /** Update cell values by updating set of expressions and values */
45
+ updateExprValues?: (tableId: string, rowUpdates: RowUpdate[]) => Promise<void>
46
+
47
+ /** Called when row is double-clicked with (tableId, rowId, rowIndex) */
48
+ onRowDoubleClick?: (tableId: string, rowId: any, rowIndex: number) => void
49
+
50
+ /** Called when a row is clicked with (tableId, rowId, rowIndex) */
51
+ onRowClick?: (tableId: string, rowId: any, rowIndex: number) => void
52
+
53
+ /** Change to force a refresh */
54
+ refreshKey?: any
55
+ }
56
+
57
+ /** Update to one row expression value */
58
+ export interface RowUpdate {
59
+ primaryKey: string
60
+ expr: Expr
61
+ value: any
62
+ }
63
+
64
+ interface LoadState {
65
+ design: DatagridDesign
66
+ offset: number
67
+ limit: number
68
+ filters?: JsonQLFilter[]
69
+ }
70
+
71
+ export interface DatagridViewComponentRef {
72
+ getRows: () => any[]
73
+ getEntirelyLoaded: () => boolean
74
+ loadMoreRows: (callback?: () => void) => void
75
+ reload: () => void
76
+ reloadRow: (rowIndex: number, rowId: any, callback: () => void) => void
77
+ deleteRow: (rowIndex: number, callback: () => void) => void
78
+ }
79
+
80
+ /** Datagrid view component. It does not show any quick filters, but only the grid itself. */
81
+ const DatagridViewComponent = forwardRef<DatagridViewComponentRef, DatagridViewComponentProps>((props, ref) => {
82
+ const [rows, setRows] = useState<any[]>([])
83
+ const [entirelyLoaded, setEntirelyLoaded] = useState(false)
84
+ const loadStateRef = useRef<LoadState | null>(null)
85
+
86
+ // Get row header width based on selection and showRowNumbers
87
+ const rowHeaderWidth = props.selectedRows
88
+ ? (props.design.showRowNumbers ? 70 : 30) // With selection: 70 for both, 30 for just checkbox
89
+ : (props.design.showRowNumbers ? 50 : 20) // Without selection: 50 for numbers, 20 for neither
90
+
91
+ const handleRowSelect = useCallback((rowId: string) => {
92
+ if (props.onSelectedRowsChange) {
93
+ props.onSelectedRowsChange(props.selectedRows!.toggle(rowId))
94
+ }
95
+ }, [props.selectedRows, props.onSelectedRowsChange])
96
+
97
+ const handleSelectAll = useCallback(() => {
98
+ if (props.onSelectedRowsChange) {
99
+ props.onSelectedRowsChange(props.selectedRows!.all())
100
+ }
101
+ }, [props.selectedRows, props.onSelectedRowsChange])
102
+
103
+ const handleSelectNone = useCallback(() => {
104
+ if (props.onSelectedRowsChange) {
105
+ props.onSelectedRowsChange(props.selectedRows!.none())
106
+ }
107
+ }, [props.selectedRows, props.onSelectedRowsChange])
108
+
109
+ const handleSelectInvert = useCallback(() => {
110
+ if (props.onSelectedRowsChange) {
111
+ props.onSelectedRowsChange(props.selectedRows!.invert())
112
+ }
113
+ }, [props.selectedRows, props.onSelectedRowsChange])
114
+
115
+ /** Render top left corner with optional selection dropdown */
116
+ const renderTopLeft = useCallback(() => {
117
+ if (!props.selectedRows) {
118
+ return null
119
+ }
120
+
121
+ if (!props.onSelectedRowsChange) {
122
+ return (
123
+ <div style={{ padding: 8, textAlign: "right" }}>
124
+ <input type="checkbox" checked={false} readOnly={true} />
125
+ </div>
126
+ )
127
+ }
128
+
129
+ return (
130
+ <div className="dropdown" style={{ marginTop: 6 }}>
131
+ <div style={{ textAlign: "center" }} className="dropdown-toggle" data-bs-toggle="dropdown">
132
+ <input type="checkbox" checked={false} readOnly={true} style={{ marginTop: 7 }} />
133
+ </div>
134
+ <ul className="dropdown-menu">
135
+ <li>
136
+ <a className="dropdown-item" onClick={handleSelectAll}>
137
+ {T`All`}
138
+ </a>
139
+ </li>
140
+ <li>
141
+ <a className="dropdown-item" onClick={handleSelectNone}>
142
+ {T`None`}
143
+ </a>
144
+ </li>
145
+ <li>
146
+ <a className="dropdown-item" onClick={handleSelectInvert}>
147
+ {T`Invert`}
148
+ </a>
149
+ </li>
150
+ </ul>
151
+ </div>
152
+ )
153
+ }, [props.selectedRows, props.onSelectedRowsChange, handleSelectAll, handleSelectNone, handleSelectInvert])
154
+
155
+ /** Render row headers with optional selection checkboxes */
156
+ const renderRowHeader = useCallback((options: RenderRowHeaderProps) => {
157
+ const rowHeaderStyle = {
158
+ width: options.width,
159
+ height: options.height,
160
+ display: "flex",
161
+ alignItems: "center",
162
+ overflow: "hidden",
163
+ textOverflow: "ellipsis",
164
+ whiteSpace: "nowrap",
165
+ padding: "0 8px"
166
+ }
167
+
168
+ const rowId = options.row < rows.length ? rows[options.row].id : null
169
+ const isSelected = rowId != null && props.selectedRows ? props.selectedRows.isSelected(rowId) : false
170
+ const isSubtable = options.row < rows.length ? rows[options.row].subtable >= 0 : false
171
+
172
+ return (
173
+ <div style={rowHeaderStyle}>
174
+ {props.selectedRows && !isSubtable && (
175
+ <input
176
+ type="checkbox"
177
+ style={{ marginRight: 4 }}
178
+ checked={isSelected}
179
+ onChange={rowId != null && props.onSelectedRowsChange ? () => handleRowSelect(rowId) : undefined}
180
+ readOnly={!props.onSelectedRowsChange}
181
+ />
182
+ )}
183
+ {props.design.showRowNumbers ? (
184
+ <div style={{ flex: 1, textAlign: "right" }}>{options.row + 1}</div>
185
+ ) : null}
186
+ </div>
187
+ )
188
+ }, [rows, props.selectedRows, props.onSelectedRowsChange, props.design.showRowNumbers, handleRowSelect])
189
+
190
+ const loadMoreRows = useStableCallback((callback?: () => void) => {
191
+ const loadState: LoadState = {
192
+ design: props.design,
193
+ offset: rows.length,
194
+ limit: props.pageSize || 100,
195
+ filters: props.filters
196
+ }
197
+
198
+ if (_.isEqual(loadState, loadStateRef.current)) {
199
+ return
200
+ }
201
+
202
+ loadStateRef.current = loadState
203
+
204
+ props.datagridDataSource.getRows(
205
+ loadState.design,
206
+ loadState.offset,
207
+ loadState.limit,
208
+ loadState.filters,
209
+ (error: any, newRows: any[]) => {
210
+ if (error) {
211
+ console.error(error)
212
+ alert(T`Error loading data`)
213
+ return
214
+ }
215
+
216
+ if (_.isEqual(loadState, loadStateRef.current)) {
217
+ loadStateRef.current = null
218
+ setRows(prev => [...prev, ...newRows])
219
+ setEntirelyLoaded(newRows.length < (props.pageSize || 100))
220
+ callback?.()
221
+ }
222
+ }
223
+ )
224
+ })
225
+
226
+ function reload() {
227
+ console.log("reload")
228
+ setRows([])
229
+ setEntirelyLoaded(false)
230
+ }
231
+
232
+ // Reset rows when design, filters or refreshKey changes
233
+ useEffect(() => {
234
+ reload()
235
+ }, [getDatagridReloadKey(props.design), JSON.stringify(props.filters), props.refreshKey])
236
+
237
+ function getRows() {
238
+ return rows
239
+ }
240
+
241
+ function reloadRow(rowIndex: number, rowId: any, callback: () => void) {
242
+ const filters = produce(props.filters || [], draft => {
243
+ draft.push({
244
+ table: props.design.table!,
245
+ jsonql: {
246
+ type: "op",
247
+ op: "=",
248
+ exprs: [
249
+ { type: "field", tableAlias: "{alias}", column: props.schema.getTable(props.design.table!)?.primaryKey! },
250
+ { type: "literal", value: rowId }
251
+ ]
252
+ }
253
+ })
254
+ })
255
+
256
+ props.datagridDataSource.getRows(
257
+ props.design,
258
+ 0,
259
+ 1,
260
+ filters,
261
+ (error, newRows) => {
262
+ if (error) {
263
+ console.error(error)
264
+ alert(T`Error loading data`)
265
+ callback()
266
+ return
267
+ }
268
+
269
+ setRows(currentRows =>
270
+ produce(currentRows, draft => {
271
+ if (newRows![0]) {
272
+ draft[rowIndex] = newRows![0]
273
+ } else {
274
+ draft.splice(rowIndex, 1)
275
+ }
276
+ })
277
+ )
278
+ callback()
279
+ }
280
+ )
281
+ }
282
+
283
+ function deleteRow(rowIndex: number, callback: () => void) {
284
+ const newRows = rows.slice()
285
+ newRows.splice(rowIndex, 1)
286
+ setRows(newRows)
287
+ callback()
288
+ }
289
+
290
+ // Expose methods via ref
291
+ useImperativeHandle(ref, () => ({
292
+ getRows: getRows,
293
+ getEntirelyLoaded: () => entirelyLoaded,
294
+ reload: reload,
295
+ reloadRow: reloadRow,
296
+ deleteRow: deleteRow,
297
+ loadMoreRows: loadMoreRows
298
+ }), [rows, entirelyLoaded, reload, reloadRow, deleteRow, loadMoreRows])
299
+
300
+ const handleColWidthsChange = useStableCallback((colWidths: number[]) => {
301
+ const columns = props.design.columns.map((col, index) => ({
302
+ ...col,
303
+ width: colWidths[index]
304
+ }))
305
+
306
+ props.onDesignChange?.({ ...props.design, columns })
307
+ })
308
+
309
+ const updateCellValue = useStableCallback(async (value: any, rowIndex: number, colIndex: number): Promise<boolean> => {
310
+ try {
311
+ const rowId = rows[rowIndex].id
312
+ const { expr } = props.design.columns[colIndex]
313
+
314
+ await props.updateExprValues!(props.design.table!, [{ primaryKey: rowId, expr, value }])
315
+
316
+ await new Promise<void>(resolve => {
317
+ reloadRow(rowIndex, rowId, resolve)
318
+ })
319
+
320
+ return true
321
+ } catch (error) {
322
+ alert(T`Error saving data`)
323
+ console.error(error)
324
+ return false
325
+ }
326
+ })
327
+
328
+ const handleRowDoubleClick = useStableCallback((rowIndex: number) => {
329
+ if (props.onRowDoubleClick != null && rows[rowIndex]?.id) {
330
+ props.onRowDoubleClick(props.design.table!, rows[rowIndex].id, rowIndex)
331
+ return true
332
+ }
333
+ return false
334
+ })
335
+
336
+ const handleRowClick = useStableCallback((rowIndex: number) => {
337
+ if (props.onRowClick != null && rows[rowIndex]?.id) {
338
+ props.onRowClick(props.design.table!, rows[rowIndex].id, rowIndex)
339
+ return true
340
+ }
341
+ return false
342
+ })
343
+
344
+ const handleCopy = useStableCallback((rowIndex: number, colIndex: number) => {
345
+ const row = rows[rowIndex]
346
+ const col = props.design.columns[colIndex]
347
+ const value = row[`c${colIndex}`]
348
+ // Format value as text using ExprCellComponent logic
349
+ const exprUtils = new ExprUtils(props.schema)
350
+ const exprType = exprUtils.getExprType(col.expr)
351
+ if (!exprType) {
352
+ return
353
+ }
354
+
355
+ let formattedValue = value
356
+
357
+ // Parse JSON values
358
+ if (["image", "imagelist", "geometry", "text[]"].includes(exprType) && _.isString(value)) {
359
+ formattedValue = JSON.parse(value)
360
+ }
361
+
362
+ // Format based on type
363
+ if (canFormatType(exprType)) {
364
+ formattedValue = formatValue(exprType, formattedValue, col.format)
365
+ } else {
366
+ formattedValue = exprUtils.stringifyExprLiteral(col.expr, formattedValue, props.design.locale)
367
+ }
368
+
369
+ navigator.clipboard.writeText(formattedValue || "")
370
+ })
371
+
372
+ const renderCell = useStableCallback((options: RenderCellProps) => {
373
+ if (options.row >= rows.length) {
374
+ _.defer(() => loadMoreRows())
375
+ return <div style={{ padding: 8 }}><i className="fa fa-spinner fa-spin text-muted" /></div>
376
+ }
377
+
378
+ if (options.col === -1) {
379
+ return (
380
+ <div style={{ padding: 8, textAlign: "right" }}>
381
+ {options.row + 1}
382
+ </div>
383
+ )
384
+ }
385
+
386
+ const column = props.design.columns[options.col]
387
+ const exprUtils = new ExprUtils(props.schema)
388
+ const exprType = exprUtils.getExprType(column.expr)
389
+ const value = rows[options.row][`c${options.col}`]
390
+
391
+ if (column.type === "expr" && exprType) {
392
+ const muted = !column.subtable && rows[options.row].subtable >= 0
393
+
394
+ return <div style={{ width: options.width, height: options.height }}>
395
+ <ExprCellComponent
396
+ schema={props.schema}
397
+ dataSource={props.dataSource}
398
+ locale={props.design.locale}
399
+ width={options.width}
400
+ height={options.height}
401
+ value={value}
402
+ expr={column.expr}
403
+ format={column.format}
404
+ exprType={exprType}
405
+ muted={muted}
406
+ />
407
+ </div>
408
+ }
409
+
410
+ return null
411
+ })
412
+
413
+ const renderColHeader = useStableCallback((options: RenderColHeaderProps) => {
414
+ if (options.col === -1) {
415
+ return (
416
+ <div style={{
417
+ padding: 5,
418
+ textAlign: "right",
419
+ fontWeight: "bold",
420
+ height: "100%",
421
+ display: "flex",
422
+ alignItems: "center",
423
+ justifyContent: "flex-end"
424
+ }}>
425
+ #
426
+ </div>
427
+ )
428
+ }
429
+
430
+ const column = props.design.columns[options.col]
431
+ const exprUtils = new ExprUtils(props.schema)
432
+
433
+ return (
434
+ <div style={{
435
+ padding: 5,
436
+ whiteSpace: "nowrap",
437
+ fontWeight: "bold",
438
+ height: "100%",
439
+ display: "flex",
440
+ alignItems: "center"
441
+ }}>
442
+ {column.label || exprUtils.summarizeExpr(column.expr, props.design.locale)}
443
+ </div>
444
+ )
445
+ })
446
+
447
+ const renderCellEditor = useStableCallback((options: RenderCellEditorProps) => {
448
+ if (options.row >= rows.length) {
449
+ return <div />
450
+ }
451
+
452
+ const column = props.design.columns[options.col]
453
+ const value = rows[options.row][`c${options.col}`]
454
+
455
+ const expr = column.expr!
456
+ const exprUtils = new ExprUtils(props.schema)
457
+ const exprType = exprUtils.getExprType(expr)!
458
+ const enumValues = exprUtils.getExprEnumValues(expr) ?? undefined
459
+ const idTable = exprUtils.getExprIdTable(expr) ?? undefined
460
+
461
+ return (
462
+ <CellEditor
463
+ type={exprType}
464
+ enumValues={enumValues}
465
+ idTable={idTable}
466
+ schema={props.schema}
467
+ dataSource={props.dataSource}
468
+ initialValue={value}
469
+ updateCellValue={value => updateCellValue(value, options.row, options.col)}
470
+ editorOptions={options}
471
+ />
472
+ )
473
+ })
474
+
475
+ const canEdit = useStableCallback(async (options: { row: number; col: number }) => {
476
+ if (options.col === -1) {
477
+ return false
478
+ }
479
+
480
+ const rowId = rows[options.row].id
481
+ if (rowId == null) {
482
+ return false
483
+ }
484
+
485
+ if (!props.canEditExpr) {
486
+ return false
487
+ }
488
+
489
+ const column = props.design.columns[options.col]
490
+ const exprType = new ExprUtils(props.schema).getExprType(column.expr)!
491
+ if (!canEditType(exprType)) {
492
+ return false
493
+ }
494
+
495
+ return props.canEditExpr(props.design.table!, rowId, column.expr)
496
+ })
497
+
498
+ let numRows = rows.length
499
+ if (!entirelyLoaded) {
500
+ numRows += 1
501
+ }
502
+
503
+ const colWidths = props.design.columns.map(col => col.width || 200)
504
+
505
+ return (
506
+ <GridComponent
507
+ width={props.width}
508
+ height={props.height}
509
+ numRows={numRows}
510
+ colWidths={colWidths}
511
+ colHeaderHeight={40}
512
+ rowHeight={40}
513
+ renderCell={renderCell}
514
+ renderColHeader={renderColHeader}
515
+ renderCellEditor={renderCellEditor}
516
+ canEdit={canEdit}
517
+ onColWidthsChange={handleColWidthsChange}
518
+ onRowDoubleClick={handleRowDoubleClick}
519
+ onRowClick={handleRowClick}
520
+ rowHeaderWidth={rowHeaderWidth}
521
+ renderRowHeader={renderRowHeader}
522
+ renderTopLeft={renderTopLeft}
523
+ onCopy={handleCopy}
524
+ />
525
+ )
526
+ })
527
+
528
+ export default DatagridViewComponent
529
+
530
+ /** Get a unique key for a datagrid design, ignoring column widths which don't affect data */
531
+ function getDatagridReloadKey(design: DatagridDesign): string {
532
+ // Create copy without column widths
533
+ const designWithoutWidths = produce(design, draft => {
534
+ for (const column of draft.columns) {
535
+ column.width = 0
536
+ }
537
+ })
538
+ return JSON.stringify(designWithoutWidths)
539
+ }
@@ -36,7 +36,7 @@ export default class DirectDatagridDataSource implements DatagridDataSource {
36
36
  extraFilters: filters
37
37
  })
38
38
 
39
- return this.options.dataSource.performQuery(query, callback)
39
+ this.options.dataSource.performQuery(query, callback)
40
40
  }
41
41
 
42
42
  countRows(
@@ -66,8 +66,7 @@ export default class DirectDatagridDataSource implements DatagridDataSource {
66
66
  ]
67
67
  };
68
68
 
69
- console.log(countQuery)
70
- return this.options.dataSource.performQuery(countQuery, (error, rows) => {
69
+ this.options.dataSource.performQuery(countQuery, (error, rows) => {
71
70
  callback(error, rows?.[0]?.cnt)
72
71
  })
73
72
  }
@@ -1,10 +1,8 @@
1
1
  import _ from "lodash"
2
2
  import React from "react"
3
- const R = React.createElement
4
3
  import moment from "moment"
5
- import { DataSource, ExprUtils, LiteralType, Schema } from "@mwater/expressions"
4
+ import { DataSource, Expr, ExprUtils, LiteralType, Schema } from "@mwater/expressions"
6
5
  import { default as Linkify } from "react-linkify"
7
- import { Cell } from "fixed-data-table-2"
8
6
  import { canFormatType } from "../valueFormatter"
9
7
  import { formatValue } from "../valueFormatter"
10
8
 
@@ -22,21 +20,23 @@ export interface ExprCellComponentProps {
22
20
  width: number
23
21
  height: number
24
22
  value?: any
25
- expr?: any
23
+ expr?: Expr
24
+
26
25
  /** True to show muted */
27
26
  muted?: boolean
28
- onClick?: any
27
+
28
+ /** Called when clicked */
29
+ onClick?: () => void
30
+
31
+ /** Called when double clicked */
32
+ onDoubleClick?: () => void
29
33
  }
30
34
 
31
35
  // Cell that displays an expression column cell
32
36
  export default class ExprCellComponent extends React.Component<ExprCellComponentProps> {
33
- handleClick = () => {
34
- return this.setState({ editing: true })
35
- }
36
-
37
- renderImage(id: any) {
37
+ renderImage(id: string) {
38
38
  const url = this.props.dataSource.getImageUrl(id)
39
- return R("a", { href: url, key: id, target: "_blank", style: { paddingLeft: 5, paddingRight: 5 } }, T`Image`)
39
+ return <a href={url} key={id} target="_blank" style={{ paddingLeft: 5, paddingRight: 5 }}>{T`Image`}</a>
40
40
  }
41
41
 
42
42
  render() {
@@ -59,7 +59,7 @@ export default class ExprCellComponent extends React.Component<ExprCellComponent
59
59
  // Convert to node based on type
60
60
  switch (this.props.exprType) {
61
61
  case "text":
62
- node = R(Linkify, { properties: { target: "_blank" } }, value)
62
+ node = <Linkify properties={{ target: "_blank" }}>{value}</Linkify>
63
63
  break
64
64
  case "boolean":
65
65
  case "enum":
@@ -85,19 +85,24 @@ export default class ExprCellComponent extends React.Component<ExprCellComponent
85
85
  }
86
86
  }
87
87
 
88
- return R(
89
- Cell,
90
- {
91
- width: this.props.width,
92
- height: this.props.height,
93
- onClick: this.props.onClick,
94
- style: {
88
+ return (
89
+ <div
90
+ style={{
91
+ width: this.props.width,
92
+ height: this.props.height,
95
93
  whiteSpace: "nowrap",
96
94
  textAlign: ["number"].includes(this.props.exprType) ? "right" : "left",
97
- opacity: this.props.muted ? 0.4 : undefined
98
- }
99
- },
100
- node
95
+ opacity: this.props.muted ? 0.4 : undefined,
96
+ padding: 8,
97
+ cursor: this.props.onClick ? "pointer" : undefined,
98
+ overflow: "hidden",
99
+ textOverflow: "ellipsis"
100
+ }}
101
+ onClick={this.props.onClick}
102
+ onDoubleClick={this.props.onDoubleClick}
103
+ >
104
+ {node}
105
+ </div>
101
106
  )
102
107
  }
103
108
  }