@mwater/visualization 5.2.0 → 5.3.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 (254) hide show
  1. package/lib/ColorComponent.d.ts +10 -11
  2. package/lib/ColorComponent.js +78 -29
  3. package/lib/ColorSchemeFactory.d.ts +13 -2
  4. package/lib/ColorSchemeFactory.js +7 -5
  5. package/lib/CustomColorsContext.d.ts +6 -0
  6. package/lib/CustomColorsContext.js +6 -0
  7. package/lib/FiltersDesignerComponent.d.ts +1 -4
  8. package/lib/FiltersDesignerComponent.js +2 -3
  9. package/lib/LocaleContextInjector.d.ts +5 -11
  10. package/lib/LocaleContextInjector.js +4 -12
  11. package/lib/MWaterAddRelatedFormComponent.js +3 -3
  12. package/lib/MWaterAddRelatedIndicatorComponent.d.ts +1 -4
  13. package/lib/MWaterAddRelatedIndicatorComponent.js +6 -6
  14. package/lib/MWaterCompleteTableSelectComponent.d.ts +5 -16
  15. package/lib/MWaterCompleteTableSelectComponent.js +36 -36
  16. package/lib/MWaterContextComponent.d.ts +4 -6
  17. package/lib/MWaterContextComponent.js +4 -13
  18. package/lib/MWaterLoaderComponent.d.ts +5 -3
  19. package/lib/MWaterLoaderComponent.js +2 -1
  20. package/lib/MWaterTableSelectComponent.d.ts +1 -4
  21. package/lib/MWaterTableSelectComponent.js +10 -12
  22. package/lib/UIComponents.d.ts +2 -2
  23. package/lib/UIComponents.js +4 -12
  24. package/lib/axes/AxisBuilder.d.ts +7 -4
  25. package/lib/axes/AxisBuilder.js +3 -1
  26. package/lib/axes/AxisComponent.d.ts +2 -5
  27. package/lib/axes/AxisComponent.js +1 -2
  28. package/lib/axes/ColorPaletteCollectionComponent.d.ts +5 -12
  29. package/lib/axes/ColorPaletteCollectionComponent.js +67 -36
  30. package/lib/dashboards/DashboardComponent.d.ts +4 -12
  31. package/lib/dashboards/DashboardComponent.js +18 -38
  32. package/lib/dashboards/DashboardDesign.d.ts +3 -3
  33. package/lib/dashboards/DashboardUpgrader.js +36 -1
  34. package/lib/dashboards/DashboardViewComponent.d.ts +5 -34
  35. package/lib/dashboards/DashboardViewComponent.js +109 -132
  36. package/lib/dashboards/FontStyleEditor.d.ts +8 -0
  37. package/lib/dashboards/FontStyleEditor.js +130 -0
  38. package/lib/dashboards/LayoutOptionsComponent.d.ts +0 -1
  39. package/lib/dashboards/LayoutOptionsComponent.js +211 -42
  40. package/lib/dashboards/ServerDashboardDataSource.d.ts +1 -2
  41. package/lib/dashboards/ServerDashboardDataSource.js +52 -33
  42. package/lib/dashboards/WidgetComponent.d.ts +3 -3
  43. package/lib/dashboards/WidgetComponent.js +3 -6
  44. package/lib/dashboards/WidgetDataSourcePrioritizer.d.ts +20 -0
  45. package/lib/dashboards/WidgetDataSourcePrioritizer.js +72 -0
  46. package/lib/dashboards/layoutOptions.d.ts +83 -0
  47. package/lib/dashboards/layoutOptions.js +436 -10
  48. package/lib/datagrids/DatagridDesign.d.ts +7 -6
  49. package/lib/datagrids/ServerDatagridDataSource.d.ts +7 -6
  50. package/lib/datagrids/ServerDatagridDataSource.js +87 -33
  51. package/lib/demo.js +3 -3
  52. package/lib/index.css +5 -0
  53. package/lib/index.d.ts +1 -1
  54. package/lib/index.js +0 -1
  55. package/lib/layouts/LayoutManager.d.ts +33 -29
  56. package/lib/layouts/LayoutManager.js +2 -8
  57. package/lib/layouts/blocks/BlocksDisplayComponent.d.ts +26 -57
  58. package/lib/layouts/blocks/BlocksDisplayComponent.js +122 -205
  59. package/lib/layouts/blocks/BlocksLayoutManager.d.ts +6 -22
  60. package/lib/layouts/blocks/BlocksLayoutManager.js +5 -14
  61. package/lib/layouts/blocks/HorizontalBlockComponent.d.ts +5 -4
  62. package/lib/layouts/blocks/HorizontalBlockComponent.js +5 -5
  63. package/lib/mWaterLoader.d.ts +2 -0
  64. package/lib/mWaterLoader.js +2 -1
  65. package/lib/maps/AddLayerComponent.d.ts +6 -8
  66. package/lib/maps/AddLayerComponent.js +6 -6
  67. package/lib/maps/BingLayer.js +10 -20
  68. package/lib/maps/BufferLayer.js +2 -1
  69. package/lib/maps/ChoroplethLayer.js +2 -1
  70. package/lib/maps/DirectMapDataSource.d.ts +5 -2
  71. package/lib/maps/DirectMapDataSource.js +2 -1
  72. package/lib/maps/EditPopupComponent.js +2 -1
  73. package/lib/maps/MapComponent.d.ts +1 -4
  74. package/lib/maps/MapComponent.js +3 -3
  75. package/lib/maps/MarkersLayer.js +30 -25
  76. package/lib/maps/RasterMapViewComponent.d.ts +1 -4
  77. package/lib/maps/RasterMapViewComponent.js +3 -3
  78. package/lib/maps/ServerMapDataSource.d.ts +2 -3
  79. package/lib/maps/ServerMapDataSource.js +5 -5
  80. package/lib/maps/VectorMapViewComponent.js +2 -1
  81. package/lib/maps/mapSymbols.js +2 -0
  82. package/lib/maps/symbols/font-awesome/cloud-rain.png +0 -0
  83. package/lib/maps/vectorMaps.js +61 -55
  84. package/lib/quickfilter/QuickfiltersComponent.d.ts +1 -4
  85. package/lib/quickfilter/QuickfiltersComponent.js +3 -3
  86. package/lib/richtext/DropdownPaletteItem.d.ts +32 -0
  87. package/lib/richtext/DropdownPaletteItem.js +82 -0
  88. package/lib/richtext/FontColorPaletteItem.d.ts +1 -5
  89. package/lib/richtext/FontColorPaletteItem.js +32 -27
  90. package/lib/richtext/ItemsHtmlConverter.js +12 -3
  91. package/lib/richtext/RichTextComponent.d.ts +26 -52
  92. package/lib/richtext/RichTextComponent.js +166 -128
  93. package/lib/valueFormatter.js +6 -1
  94. package/lib/wellknown.d.ts +5 -0
  95. package/lib/wellknown.js +288 -0
  96. package/lib/widgets/DropdownWidgetComponent.d.ts +8 -25
  97. package/lib/widgets/DropdownWidgetComponent.js +48 -25
  98. package/lib/widgets/IFrameWidgetComponent.d.ts +1 -2
  99. package/lib/widgets/ImageWidgetComponent.d.ts +2 -3
  100. package/lib/widgets/MapWidget.d.ts +2 -0
  101. package/lib/widgets/MapWidget.js +2 -1
  102. package/lib/widgets/TOCWidget.js +2 -1
  103. package/lib/widgets/Widget.d.ts +2 -0
  104. package/lib/widgets/WidgetDataSource.d.ts +3 -1
  105. package/lib/widgets/charts/Chart.d.ts +0 -1
  106. package/lib/widgets/charts/ChartViewComponent.d.ts +4 -0
  107. package/lib/widgets/charts/ChartViewComponent.js +11 -3
  108. package/lib/widgets/charts/ChartWidget.d.ts +1 -62
  109. package/lib/widgets/charts/ChartWidget.js +4 -183
  110. package/lib/widgets/charts/ChartWidgetComponent.d.ts +51 -0
  111. package/lib/widgets/charts/ChartWidgetComponent.js +167 -0
  112. package/lib/widgets/charts/calendar/CalendarChartViewComponent.d.ts +1 -4
  113. package/lib/widgets/charts/calendar/CalendarChartViewComponent.js +4 -4
  114. package/lib/widgets/charts/layered/LayeredChart.d.ts +5 -10
  115. package/lib/widgets/charts/layered/LayeredChart.js +6 -7
  116. package/lib/widgets/charts/layered/LayeredChartCompiler.d.ts +4 -2
  117. package/lib/widgets/charts/layered/LayeredChartCompiler.js +46 -32
  118. package/lib/widgets/charts/layered/LayeredChartDesign.d.ts +4 -0
  119. package/lib/widgets/charts/layered/LayeredChartDesignerComponent.d.ts +3 -0
  120. package/lib/widgets/charts/layered/LayeredChartDesignerComponent.js +21 -3
  121. package/lib/widgets/charts/layered/LayeredChartLayerDesignerComponent.d.ts +1 -2
  122. package/lib/widgets/charts/layered/LayeredChartLayerDesignerComponent.js +2 -1
  123. package/lib/widgets/charts/layered/LayeredChartViewComponent.d.ts +1 -4
  124. package/lib/widgets/charts/layered/LayeredChartViewComponent.js +89 -38
  125. package/lib/widgets/charts/pivot/IntersectionDesignerComponent.d.ts +5 -112
  126. package/lib/widgets/charts/pivot/IntersectionDesignerComponent.js +122 -166
  127. package/lib/widgets/charts/pivot/PivotChart.d.ts +6 -0
  128. package/lib/widgets/charts/pivot/PivotChart.js +47 -17
  129. package/lib/widgets/charts/pivot/PivotChartDesign.d.ts +11 -0
  130. package/lib/widgets/charts/pivot/PivotChartDesignerComponent.d.ts +1 -1
  131. package/lib/widgets/charts/pivot/PivotChartDesignerComponent.js +1 -1
  132. package/lib/widgets/charts/pivot/PivotChartLayoutBuilder.d.ts +2 -2
  133. package/lib/widgets/charts/pivot/PivotChartLayoutBuilder.js +20 -36
  134. package/lib/widgets/charts/pivot/PivotChartLayoutComponent.js +0 -1
  135. package/lib/widgets/charts/pivot/PivotChartQueryBuilder.d.ts +23 -2
  136. package/lib/widgets/charts/pivot/PivotChartQueryBuilder.js +215 -181
  137. package/lib/widgets/charts/pivot/PivotChartUtils.d.ts +2 -2
  138. package/lib/widgets/charts/pivot/PivotChartViewComponent.d.ts +9 -28
  139. package/lib/widgets/charts/pivot/PivotChartViewComponent.js +20 -60
  140. package/lib/widgets/charts/table/TableChart.js +8 -4
  141. package/lib/widgets/charts/table/TableChartDesignerComponent.js +3 -3
  142. package/lib/widgets/charts/table/TableChartViewComponent.js +11 -11
  143. package/lib/widgets/text/TextComponent.d.ts +5 -12
  144. package/lib/widgets/text/TextComponent.js +19 -39
  145. package/lib/widgets/text/TextWidget.d.ts +2 -1
  146. package/lib/widgets/text/TextWidget.js +5 -1
  147. package/lib/widgets/text/TextWidgetComponent.d.ts +15 -3
  148. package/lib/widgets/text/TextWidgetComponent.js +76 -19
  149. package/lib/widgets/text/TextWidgetDesign.d.ts +13 -1
  150. package/lib/widgets/text/TextWidgetDesign.js +6 -0
  151. package/package.json +4 -4
  152. package/src/ColorComponent.tsx +177 -0
  153. package/src/ColorSchemeFactory.ts +12 -6
  154. package/src/CustomColorsContext.tsx +9 -0
  155. package/src/FiltersDesignerComponent.ts +3 -4
  156. package/src/LocaleContextInjector.tsx +14 -13
  157. package/src/MWaterAddRelatedFormComponent.ts +3 -3
  158. package/src/MWaterAddRelatedIndicatorComponent.ts +6 -6
  159. package/src/MWaterCompleteTableSelectComponent.tsx +36 -36
  160. package/src/MWaterContextComponent.tsx +8 -17
  161. package/src/MWaterLoaderComponent.ts +6 -3
  162. package/src/MWaterTableSelectComponent.tsx +11 -12
  163. package/src/{UIComponents.ts → UIComponents.tsx} +7 -15
  164. package/src/axes/AxisBuilder.ts +7 -5
  165. package/src/axes/AxisComponent.ts +3 -4
  166. package/src/axes/{ColorPaletteCollectionComponent.ts → ColorPaletteCollectionComponent.tsx} +87 -61
  167. package/src/dashboards/DashboardComponent.tsx +71 -107
  168. package/src/dashboards/DashboardDesign.ts +3 -3
  169. package/src/dashboards/DashboardUpgrader.ts +41 -1
  170. package/src/dashboards/DashboardViewComponent.tsx +313 -0
  171. package/src/dashboards/FontStyleEditor.tsx +166 -0
  172. package/src/dashboards/LayoutOptionsComponent.tsx +380 -75
  173. package/src/dashboards/ServerDashboardDataSource.ts +52 -33
  174. package/src/dashboards/WidgetComponent.tsx +6 -12
  175. package/src/dashboards/WidgetDataSourcePrioritizer.ts +82 -0
  176. package/src/dashboards/layoutOptions.tsx +581 -0
  177. package/src/datagrids/DatagridDesign.ts +8 -3
  178. package/src/datagrids/ServerDatagridDataSource.ts +106 -43
  179. package/src/demo.ts +3 -3
  180. package/src/index.css +5 -0
  181. package/src/index.ts +1 -1
  182. package/src/layouts/LayoutManager.ts +44 -42
  183. package/src/layouts/blocks/BlocksDisplayComponent.tsx +498 -0
  184. package/src/layouts/blocks/BlocksLayoutManager.ts +6 -15
  185. package/src/layouts/blocks/HorizontalBlockComponent.ts +9 -8
  186. package/src/mWaterLoader.ts +4 -1
  187. package/src/maps/AddLayerComponent.ts +9 -9
  188. package/src/maps/BingLayer.ts +16 -26
  189. package/src/maps/BufferLayer.ts +2 -1
  190. package/src/maps/ChoroplethLayer.ts +2 -1
  191. package/src/maps/DirectMapDataSource.ts +12 -3
  192. package/src/maps/EditPopupComponent.ts +2 -1
  193. package/src/maps/MapComponent.ts +3 -3
  194. package/src/maps/MarkersLayer.ts +38 -41
  195. package/src/maps/RasterMapViewComponent.ts +3 -3
  196. package/src/maps/ServerMapDataSource.ts +7 -7
  197. package/src/maps/VectorMapViewComponent.tsx +2 -1
  198. package/src/maps/mapSymbols.ts +2 -0
  199. package/src/maps/symbols/font-awesome/cloud-rain.png +0 -0
  200. package/src/maps/vectorMaps.tsx +79 -74
  201. package/src/quickfilter/QuickfiltersComponent.ts +3 -3
  202. package/src/richtext/DropdownPaletteItem.tsx +144 -0
  203. package/src/richtext/FontColorPaletteItem.tsx +160 -0
  204. package/src/richtext/ItemsHtmlConverter.ts +15 -5
  205. package/src/richtext/RichTextComponent.tsx +274 -232
  206. package/src/valueFormatter.ts +5 -1
  207. package/src/wellknown.ts +286 -0
  208. package/src/widgets/DropdownWidgetComponent.tsx +75 -0
  209. package/src/widgets/MapWidget.ts +5 -2
  210. package/src/widgets/TOCWidget.ts +2 -1
  211. package/src/widgets/Widget.ts +3 -0
  212. package/src/widgets/WidgetDataSource.ts +3 -1
  213. package/src/widgets/charts/Chart.ts +1 -1
  214. package/src/widgets/charts/ChartViewComponent.ts +16 -3
  215. package/src/widgets/charts/ChartWidget.ts +3 -275
  216. package/src/widgets/charts/ChartWidgetComponent.tsx +281 -0
  217. package/src/widgets/charts/calendar/CalendarChartViewComponent.tsx +4 -4
  218. package/src/widgets/charts/layered/LayeredChart.ts +4 -6
  219. package/src/widgets/charts/layered/LayeredChartCompiler.ts +80 -63
  220. package/src/widgets/charts/layered/LayeredChartDesign.ts +7 -1
  221. package/src/widgets/charts/layered/LayeredChartDesignerComponent.tsx +43 -10
  222. package/src/widgets/charts/layered/LayeredChartLayerDesignerComponent.tsx +6 -6
  223. package/src/widgets/charts/layered/LayeredChartViewComponent.ts +140 -88
  224. package/src/widgets/charts/pivot/IntersectionDesignerComponent.tsx +305 -221
  225. package/src/widgets/charts/pivot/PivotChart.ts +56 -18
  226. package/src/widgets/charts/pivot/PivotChartDesign.ts +12 -0
  227. package/src/widgets/charts/pivot/PivotChartDesignerComponent.tsx +4 -3
  228. package/src/widgets/charts/pivot/PivotChartLayoutBuilder.ts +39 -76
  229. package/src/widgets/charts/pivot/PivotChartLayoutComponent.tsx +0 -1
  230. package/src/widgets/charts/pivot/PivotChartQueryBuilder.ts +230 -189
  231. package/src/widgets/charts/pivot/PivotChartUtils.ts +4 -4
  232. package/src/widgets/charts/pivot/{PivotChartViewComponent.ts → PivotChartViewComponent.tsx} +86 -89
  233. package/src/widgets/charts/table/TableChart.ts +8 -4
  234. package/src/widgets/charts/table/TableChartDesignerComponent.ts +4 -4
  235. package/src/widgets/charts/table/TableChartViewComponent.ts +13 -14
  236. package/src/widgets/text/TextComponent.tsx +47 -49
  237. package/src/widgets/text/TextWidget.ts +8 -3
  238. package/src/widgets/text/TextWidgetComponent.tsx +249 -0
  239. package/src/widgets/text/TextWidgetDesign.ts +22 -1
  240. package/src/ColorComponent.ts +0 -117
  241. package/src/dashboards/DashboardViewComponent.ts +0 -303
  242. package/src/dashboards/layoutOptions.ts +0 -40
  243. package/src/layout-styles.css +0 -263
  244. package/src/layouts/blocks/BlocksDisplayComponent.ts +0 -461
  245. package/src/layouts/grid/GridLayoutComponent.ts +0 -67
  246. package/src/layouts/grid/GridLayoutManager.ts +0 -185
  247. package/src/layouts/grid/LegoLayoutEngine.ts +0 -142
  248. package/src/layouts/grid/PaletteItemComponent.ts +0 -28
  249. package/src/layouts/grid/README.md +0 -14
  250. package/src/layouts/grid/WidgetContainerComponent.ts +0 -420
  251. package/src/richtext/FontColorPaletteItem.ts +0 -172
  252. package/src/richtext/FontSizePaletteItem.ts +0 -110
  253. package/src/widgets/DropdownWidgetComponent.ts +0 -78
  254. package/src/widgets/text/TextWidgetComponent.ts +0 -120
@@ -0,0 +1,166 @@
1
+ import React, { useState } from "react"
2
+ import ActionCancelModalComponent from "@mwater/react-library/lib/ActionCancelModalComponent"
3
+ import { expandFontFamily, FontStyle } from "./layoutOptions"
4
+ import { default as ReactSelect } from "react-select"
5
+ import { Select, FormGroup } from "@mwater/react-library/lib/bootstrap"
6
+ import ColorComponent from "../ColorComponent"
7
+
8
+ interface FontStyleEditorProps {
9
+ value: FontStyle
10
+ onChange: (value: FontStyle) => void
11
+ }
12
+
13
+ export const FontStyleEditor = ({ value, onChange }: FontStyleEditorProps) => {
14
+ const [isModalOpen, setIsModalOpen] = useState(false)
15
+
16
+ const handleOpenModal = () => setIsModalOpen(true)
17
+ const handleCloseModal = () => setIsModalOpen(false)
18
+
19
+ const handleSave = (newValue: FontStyle) => {
20
+ onChange(newValue)
21
+ handleCloseModal()
22
+ }
23
+
24
+ return <>
25
+ <div style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', border: 'solid 1px #DDD', padding: '4px', borderRadius: '6px', justifyContent: 'space-between' }} onClick={handleOpenModal}>
26
+ <div style={{
27
+ fontFamily: expandFontFamily(value.family),
28
+ fontSize: `${value.size}px`,
29
+ fontWeight: value.weight,
30
+ color: value.color
31
+ }}>
32
+ Sample
33
+ </div>
34
+ <div style={{ fontSize: '10px', color: '#888', marginTop: '2px' }}>
35
+ {value.family}, {value.size}px
36
+ </div>
37
+ </div>
38
+ {isModalOpen && (
39
+ <FontEditorModal
40
+ initialValue={value}
41
+ onSave={handleSave}
42
+ onCancel={handleCloseModal}
43
+ />
44
+ )}
45
+ </>
46
+ }
47
+
48
+ interface FontEditorModalProps {
49
+ initialValue: FontStyle
50
+ onSave: (value: FontStyle) => void
51
+ onCancel: () => void
52
+ }
53
+
54
+ const FontEditorModal: React.FC<FontEditorModalProps> = ({ initialValue, onSave, onCancel }) => {
55
+ const [currentValue, setCurrentValue] = useState<FontStyle>(initialValue)
56
+
57
+ const handleChange = (field: keyof FontStyle, value: string | number) => {
58
+ if (field == "family") {
59
+ setCurrentValue(prev => ({ ...prev, [field]: value as string, weight: "400" }))
60
+ }
61
+ else {
62
+ setCurrentValue(prev => ({ ...prev, [field]: value }))
63
+ }
64
+ }
65
+
66
+ // Generate font size options from 5 to 48
67
+ const fontSizeOptions = Array.from({ length: 44 }, (_, i) => i + 5).map(size => ({
68
+ value: size,
69
+ label: `${size}px`
70
+ }))
71
+
72
+ // If the font is variable, then the font weight options are much wider
73
+ const fontWeightOptions: string[] = []
74
+ if (currentValue.family === "Roboto") {
75
+ fontWeightOptions.push("100", "300", "400", "500", "700", "900")
76
+ }
77
+ else if (currentValue.family === "Lora") {
78
+ fontWeightOptions.push("400", "500", "600", "700")
79
+ }
80
+ else if (currentValue.family === "Inter") {
81
+ fontWeightOptions.push("100", "200", "300", "400", "500", "600", "700", "800", "900")
82
+ }
83
+ else if (currentValue.family === "Merriweather") {
84
+ fontWeightOptions.push("300", "400", "700", "900")
85
+ }
86
+ else if (currentValue.family === "Lato") {
87
+ fontWeightOptions.push("100", "300", "400", "700", "900")
88
+ }
89
+ else {
90
+ fontWeightOptions.push("400", "700")
91
+ }
92
+
93
+ return (
94
+ <ActionCancelModalComponent
95
+ title="Edit Font"
96
+ onAction={() => onSave(currentValue)}
97
+ onCancel={onCancel}
98
+ >
99
+ <div style={{
100
+ border: '1px solid #ccc',
101
+ padding: '10px',
102
+ marginBottom: '15px',
103
+ borderRadius: '4px'
104
+ }}>
105
+ <div style={{
106
+ marginBottom: '10px',
107
+ fontSize: '14px',
108
+ fontWeight: 'bold'
109
+ }}>
110
+ Sample Text:
111
+ </div>
112
+ <div style={{
113
+ fontFamily: expandFontFamily(currentValue.family),
114
+ fontSize: `${currentValue.size}px`,
115
+ fontWeight: currentValue.weight,
116
+ color: currentValue.color
117
+ }}>
118
+ The quick brown fox jumps over the lazy dog.
119
+ </div>
120
+ </div>
121
+ <FormGroup label="Font Family">
122
+ <ReactSelect
123
+ value={{ value: currentValue.family, label: currentValue.family }}
124
+ onChange={(option) => handleChange('family', option?.value || '')}
125
+ options={[
126
+ { value: 'Helvetica Neue', label: 'Helvetica Neue' },
127
+ { value: 'Georgia', label: 'Georgia' },
128
+ { value: 'Inter', label: 'Inter' },
129
+ { value: 'Lato', label: 'Lato' },
130
+ { value: 'Lora', label: 'Lora' },
131
+ { value: 'Lucida Grande', label: 'Lucida Grande' },
132
+ { value: 'Merriweather', label: 'Merriweather' },
133
+ { value: 'Roboto', label: 'Roboto' },
134
+ ]}
135
+ formatOptionLabel={(option) => (
136
+ <span style={{ fontFamily: expandFontFamily(option.value) }}>{option.label}</span>
137
+ )}
138
+ isClearable={false}
139
+ menuPortalTarget={document.body}
140
+ styles={{ menuPortal: (style) => ({ ...style, zIndex: 2000 }) }}
141
+ />
142
+ </FormGroup>
143
+ <FormGroup label="Font Size:" horizontal>
144
+ <Select
145
+ value={currentValue.size}
146
+ onChange={(value) => handleChange('size', value || 12)}
147
+ options={fontSizeOptions}
148
+ />
149
+ </FormGroup>
150
+ <FormGroup label="Font Weight:" horizontal>
151
+ <Select
152
+ value={currentValue.weight}
153
+ onChange={(value) => handleChange('weight', value as string)}
154
+ options={fontWeightOptions.map(weight => ({ value: weight, label: weight }))}
155
+ />
156
+ </FormGroup>
157
+ <FormGroup label="Font Color:" horizontal>
158
+ <ColorComponent
159
+ color={currentValue.color}
160
+ onChange={(value) => handleChange('color', value!)}
161
+ disableReset
162
+ />
163
+ </FormGroup>
164
+ </ActionCancelModalComponent>
165
+ )
166
+ }
@@ -1,14 +1,19 @@
1
+ import _ from "lodash"
1
2
  import React, { ReactNode } from "react"
2
3
  import { useState } from "react"
3
- import { FormGroup, Select, Toggle } from "@mwater/react-library/lib/bootstrap"
4
+ import { FormGroup, Toggle, Select, CollapsibleSection, Checkbox, CollapsiblePanel } from "@mwater/react-library/lib/bootstrap"
4
5
  import { DashboardDesign } from "./DashboardDesign"
5
6
  import {
6
7
  BlocksLayoutOptions,
7
8
  DashboardTheme,
8
9
  getDefaultLayoutOptions,
9
10
  getLayoutOptions,
10
- WidthBucket
11
+ Spacing,
11
12
  } from "./layoutOptions"
13
+ import ColorComponent from "../ColorComponent"
14
+ import { FontStyleEditor } from "./FontStyleEditor"
15
+ import produce from "immer"
16
+ import FileSaver from "file-saver"
12
17
 
13
18
  interface Size {
14
19
  width: number
@@ -25,7 +30,6 @@ const sizeOptions: { value: Size; label: string }[] = [
25
30
  export function LayoutOptionsComponent(props: {
26
31
  design: DashboardDesign
27
32
  onDesignChange: (design: DashboardDesign) => void
28
- onClose: () => void
29
33
 
30
34
  /** Dashboard view to preview*/
31
35
  dashboardView: ReactNode
@@ -40,78 +44,29 @@ export function LayoutOptionsComponent(props: {
40
44
  props.onDesignChange({ ...props.design, layoutOptions })
41
45
  }
42
46
 
43
- function handleResetDefaults() {
44
- props.onDesignChange({ ...props.design, layoutOptions: getDefaultLayoutOptions(props.design.style) })
45
- }
47
+ const isCustomized = !_.isEqual(layoutOptions, getDefaultLayoutOptions(props.design.style))
46
48
 
47
49
  return (
48
- <div style={{ display: "grid", gridTemplateRows: "auto 1fr", gridTemplateColumns: "auto 1fr", height: "100%" }}>
49
- <div style={{ padding: 5, gridRow: "1 / 3" }}>
50
- <div key="back">
51
- <button className="btn btn-sm btn-link" onClick={props.onClose}>
52
- <i className="fa fa-arrow-left" /> Close
53
- </button>
54
- </div>
55
- <br />
50
+ <div style={{ display: "grid", gridTemplateRows: "auto 1fr", gridTemplateColumns: "360px 1fr", height: "100%" }}>
51
+ <div style={{ padding: "5px 10px 5px 0px", gridRow: "1 / 3", overflowY: "auto" }}>
52
+ <h5>Theme</h5>
56
53
  <ThemeToggle
57
54
  theme={props.design.style}
58
55
  onChange={(theme) => {
56
+ if (isCustomized) {
57
+ if (!confirm("Are you sure you want to change the theme? Your customizations will be lost.")) return
58
+ }
59
59
  props.onDesignChange({ ...props.design, style: theme, layoutOptions: getDefaultLayoutOptions(theme) })
60
60
  }}
61
+ isCustomized={isCustomized}
61
62
  />
62
63
  <br />
63
- <h4>Advanced</h4>
64
- <a className="btn btn-sm btn-link" style={{ float: "right" }} onClick={handleResetDefaults}>
65
- Reset to Defaults
66
- </a>
67
- <FormGroup label="Collapse to Single Column">
68
- <WidthSelector
69
- value={layoutOptions.collapseColumnsWidth}
70
- onChange={(collapseColumnsWidth) => {
71
- setLayoutOptions({ ...layoutOptions, collapseColumnsWidth })
72
- }}
73
- sign="< "
74
- />
75
- </FormGroup>
76
- <FormGroup label="Hide Quickfilters">
77
- <WidthSelector
78
- value={layoutOptions.hideQuickfiltersWidth}
79
- onChange={(hideQuickfiltersWidth) => {
80
- setLayoutOptions({ ...layoutOptions, hideQuickfiltersWidth })
81
- }}
82
- sign="< "
83
- />
84
- </FormGroup>
85
- <FormGroup label="Minimum Width (before scrolling or scaling)">
86
- <WidthSelector
87
- value={layoutOptions.minimumWidth}
88
- onChange={(minimumWidth) => {
89
- setLayoutOptions({ ...layoutOptions, minimumWidth })
90
- }}
91
- sign="< "
64
+ <CollapsibleSection label={<h5 style={{ display: "inline-block" }}>Customize Layout</h5>} initiallyOpen={isCustomized}>
65
+ <CustomizeLayout
66
+ layoutOptions={layoutOptions}
67
+ onLayoutOptionsChange={setLayoutOptions}
92
68
  />
93
- <FormGroup label="When Below Minimum Width">
94
- <Toggle
95
- value={layoutOptions.belowMinimumWidth}
96
- onChange={(belowMinimumWidth) => {
97
- setLayoutOptions({ ...layoutOptions, belowMinimumWidth: belowMinimumWidth as "scale" | "scroll" })
98
- }}
99
- options={[
100
- { value: "scroll", label: "Scroll" },
101
- { value: "scale", label: "Scale" }
102
- ]}
103
- />
104
- </FormGroup>
105
- </FormGroup>
106
- <FormGroup label="Maximum Width (before padding)">
107
- <WidthSelector
108
- value={layoutOptions.maximumWidth}
109
- onChange={(maximumWidth) => {
110
- setLayoutOptions({ ...layoutOptions, maximumWidth })
111
- }}
112
- sign="> "
113
- />
114
- </FormGroup>
69
+ </CollapsibleSection>
115
70
  </div>
116
71
  <div style={{ textAlign: "center", padding: 3 }}>
117
72
  <span className="text-muted">Preview As:&nbsp;</span>
@@ -151,9 +106,13 @@ export function LayoutOptionsComponent(props: {
151
106
  )
152
107
  }
153
108
 
154
- function ThemeToggle(props: { theme?: DashboardTheme; onChange: (theme: DashboardTheme) => void }) {
109
+ function ThemeToggle(props: {
110
+ theme?: DashboardTheme;
111
+ onChange: (theme: DashboardTheme) => void
112
+ isCustomized: boolean
113
+ }) {
155
114
  function renderStyleItem(theme: string) {
156
- const isActive = (props.theme || "default") == theme
115
+ const isActive = props.isCustomized ? theme == "custom" : (props.theme || "default") == theme
157
116
 
158
117
  if (theme == "default") {
159
118
  return (
@@ -191,17 +150,363 @@ function ThemeToggle(props: { theme?: DashboardTheme; onChange: (theme: Dashboar
191
150
  </a>
192
151
  )
193
152
  }
153
+ if (theme == "custom") {
154
+ return (
155
+ <a
156
+ key={theme}
157
+ className={isActive ? "list-group-item active" : "list-group-item"}
158
+ >
159
+ <div>Custom</div>
160
+ <div style={{ opacity: 0.6 }}>Customized theme</div>
161
+ </a>
162
+ )
163
+ }
194
164
  return null
195
165
  }
196
166
 
197
167
  return (
198
- <FormGroup label="Theme">
199
- <div className="list-group">
200
- {renderStyleItem("default")}
201
- {renderStyleItem("greybg")}
202
- {renderStyleItem("story")}
203
- </div>
204
- </FormGroup>
168
+ <div className="list-group">
169
+ {renderStyleItem("default")}
170
+ {renderStyleItem("greybg")}
171
+ {renderStyleItem("story")}
172
+ {props.isCustomized && renderStyleItem("custom")}
173
+ </div>
174
+ )
175
+ }
176
+
177
+ function CustomizeLayout(props: { layoutOptions: BlocksLayoutOptions; onLayoutOptionsChange: (layoutOptions: BlocksLayoutOptions) => void }) {
178
+ const { layoutOptions, onLayoutOptionsChange } = props
179
+
180
+ const handleDownloadTheme = () => {
181
+ const blob = new Blob([JSON.stringify(layoutOptions, null, 2)], { type: "application/json" })
182
+ FileSaver.saveAs(blob, "custom_theme.theme")
183
+ }
184
+
185
+ const handleUploadTheme = (event: React.ChangeEvent<HTMLInputElement>) => {
186
+ const file = event.target.files?.[0]
187
+ if (file) {
188
+ const reader = new FileReader()
189
+ reader.onload = (e) => {
190
+ try {
191
+ const uploadedTheme = JSON.parse(e.target?.result as string)
192
+ onLayoutOptionsChange(uploadedTheme)
193
+ } catch (error) {
194
+ alert("Invalid theme file")
195
+ }
196
+ }
197
+ reader.readAsText(file)
198
+ }
199
+ }
200
+
201
+ return (
202
+ <div>
203
+ <CollapsiblePanel title="Text">
204
+ <FormGroup label="Title">
205
+ <FontStyleEditor value={layoutOptions.titleWidgetFont} onChange={(titleWidgetFont) => {
206
+ onLayoutOptionsChange({ ...layoutOptions, titleWidgetFont })
207
+ }} />
208
+ </FormGroup>
209
+ <FormGroup label="Heading H1">
210
+ <FontStyleEditor value={layoutOptions.textWidgetH1Font} onChange={(textWidgetH1Font) => {
211
+ onLayoutOptionsChange({ ...layoutOptions, textWidgetH1Font })
212
+ }} />
213
+ <div className="mt-1">
214
+ <FormGroup label="Top Margin:" horizontal labelMuted>
215
+ <PixelsInput
216
+ value={layoutOptions.textWidgetH1MarginTop}
217
+ onChange={(textWidgetH1MarginTop) => {
218
+ onLayoutOptionsChange({ ...layoutOptions, textWidgetH1MarginTop: textWidgetH1MarginTop! })
219
+ }}
220
+ />
221
+ </FormGroup>
222
+ </div>
223
+ </FormGroup>
224
+ <FormGroup label="Heading H2">
225
+ <FontStyleEditor value={layoutOptions.textWidgetH2Font} onChange={(textWidgetH2Font) => {
226
+ onLayoutOptionsChange({ ...layoutOptions, textWidgetH2Font })
227
+ }} />
228
+ <div className="mt-1">
229
+ <FormGroup label="Top Margin:" horizontal labelMuted>
230
+ <PixelsInput
231
+ value={layoutOptions.textWidgetH2MarginTop}
232
+ onChange={(textWidgetH2MarginTop) => {
233
+ onLayoutOptionsChange({ ...layoutOptions, textWidgetH2MarginTop: textWidgetH2MarginTop! })
234
+ }}
235
+ />
236
+ </FormGroup>
237
+ </div>
238
+ </FormGroup>
239
+ <FormGroup label="Text">
240
+ <FontStyleEditor value={layoutOptions.textWidgetFont} onChange={(textWidgetFont) => {
241
+ onLayoutOptionsChange({ ...layoutOptions, textWidgetFont })
242
+ }} />
243
+ <div className="mt-1">
244
+ <FormGroup label="Line Height:" horizontal labelMuted>
245
+ <PixelsInput
246
+ value={layoutOptions.textWidgetLineHeight}
247
+ onChange={(textWidgetLineHeight) => {
248
+ onLayoutOptionsChange({ ...layoutOptions, textWidgetLineHeight: textWidgetLineHeight })
249
+ }}
250
+ allowDefault
251
+ defaultLabel="Default"
252
+ />
253
+ </FormGroup>
254
+ <FormGroup label="Paragraph Spacing:" horizontal labelMuted>
255
+ <PixelsInput
256
+ value={layoutOptions.textWidgetParagraphSpacing}
257
+ onChange={(textWidgetParagraphSpacing) => {
258
+ onLayoutOptionsChange({ ...layoutOptions, textWidgetParagraphSpacing: textWidgetParagraphSpacing! })
259
+ }}
260
+ />
261
+ </FormGroup>
262
+ </div>
263
+ </FormGroup>
264
+ <FormGroup label="List Line Height:" horizontal>
265
+ <PixelsInput
266
+ value={layoutOptions.textWidgetListLineHeight}
267
+ onChange={(textWidgetListLineHeight) => {
268
+ onLayoutOptionsChange({ ...layoutOptions, textWidgetListLineHeight: textWidgetListLineHeight })
269
+ }}
270
+ allowDefault
271
+ defaultLabel="Default"
272
+ />
273
+ </FormGroup>
274
+ <FormGroup label="Chart Header Font">
275
+ <FontStyleEditor value={layoutOptions.widgetHeaderFont} onChange={(widgetHeaderFont) => {
276
+ onLayoutOptionsChange({ ...layoutOptions, widgetHeaderFont })
277
+ }} />
278
+ </FormGroup>
279
+ <FormGroup label="Chart Footer Font">
280
+ <FontStyleEditor value={layoutOptions.widgetFooterFont} onChange={(widgetFooterFont) => {
281
+ onLayoutOptionsChange({ ...layoutOptions, widgetFooterFont })
282
+ }} />
283
+ </FormGroup>
284
+ <FormGroup label="Chart Font">
285
+ <FontStyleEditor value={layoutOptions.chartFont} onChange={(chartFont) => {
286
+ onLayoutOptionsChange({ ...layoutOptions, chartFont })
287
+ }} />
288
+ </FormGroup>
289
+ <FormGroup label="Table Font">
290
+ <FontStyleEditor value={layoutOptions.tableFont} onChange={(tableFont) => {
291
+ onLayoutOptionsChange({ ...layoutOptions, tableFont })
292
+ }} />
293
+ </FormGroup>
294
+ <FormGroup label="Pivot Table Font">
295
+ <FontStyleEditor value={layoutOptions.pivotTableFont} onChange={(pivotTableFont) => {
296
+ onLayoutOptionsChange({ ...layoutOptions, pivotTableFont })
297
+ }} />
298
+ </FormGroup>
299
+ </CollapsiblePanel>
300
+ <CollapsiblePanel title="Spacing">
301
+ <FormGroup label="Outer Padding:" horizontal>
302
+ <PixelsInput
303
+ value={layoutOptions.outerPadding}
304
+ onChange={(outerPadding) => {
305
+ onLayoutOptionsChange({ ...layoutOptions, outerPadding: outerPadding! })
306
+ }}
307
+ />
308
+ </FormGroup>
309
+ <FormGroup label="Block Margins">
310
+ <SpacingInput value={layoutOptions.blockMargin} onChange={(blockMargin) => {
311
+ onLayoutOptionsChange({ ...layoutOptions, blockMargin })
312
+ }} />
313
+ </FormGroup>
314
+ <FormGroup label="Block Padding">
315
+ <SpacingInput value={layoutOptions.blockPadding} onChange={(blockPadding) => {
316
+ onLayoutOptionsChange({ ...layoutOptions, blockPadding })
317
+ }} />
318
+ </FormGroup>
319
+ <FormGroup label="Block Border Radius:" horizontal>
320
+ <PixelsInput
321
+ value={layoutOptions.blockBorderRadius}
322
+ onChange={(blockBorderRadius) => {
323
+ onLayoutOptionsChange({ ...layoutOptions, blockBorderRadius: blockBorderRadius! })
324
+ }}
325
+ />
326
+ </FormGroup>
327
+ </CollapsiblePanel>
328
+ <CollapsiblePanel title="Colors">
329
+ <FormGroup label="Background Color:" horizontal>
330
+ <ColorComponent
331
+ color={layoutOptions.backgroundColor}
332
+ onChange={(backgroundColor) => {
333
+ onLayoutOptionsChange({ ...layoutOptions, backgroundColor: backgroundColor ?? "white" })
334
+ }}
335
+ />
336
+ </FormGroup>
337
+ <FormGroup label="Block Background Color:" horizontal>
338
+ <ColorComponent
339
+ color={layoutOptions.blockBackgroundColor}
340
+ onChange={(blockBackgroundColor) => {
341
+ onLayoutOptionsChange({ ...layoutOptions, blockBackgroundColor: blockBackgroundColor ?? "white" })
342
+ }}
343
+ />
344
+ </FormGroup>
345
+ <FormGroup label="Custom Colors">
346
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(6, 1fr)', gap: '5px' }}>
347
+ {/* 18 colors, 3 rows of 6 */}
348
+ {[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17].map((colorIndex) => (
349
+ <ColorComponent
350
+ key={colorIndex}
351
+ color={layoutOptions.customColors[colorIndex]}
352
+ onChange={(color) => {
353
+ onLayoutOptionsChange(produce(layoutOptions, (draft) => {
354
+ draft.customColors[colorIndex] = color
355
+ }))
356
+ }}
357
+ />
358
+ ))}
359
+ </div>
360
+ </FormGroup>
361
+ </CollapsiblePanel>
362
+ <CollapsiblePanel title="Responsive Layout">
363
+ <FormGroup label="Collapse to Single Column">
364
+ <WidthSelector
365
+ value={layoutOptions.collapseColumnsWidth}
366
+ onChange={(collapseColumnsWidth) => {
367
+ onLayoutOptionsChange({ ...layoutOptions, collapseColumnsWidth })
368
+ }}
369
+ sign="< "
370
+ />
371
+ </FormGroup>
372
+ <FormGroup label="Hide Quickfilters">
373
+ <WidthSelector
374
+ value={layoutOptions.hideQuickfiltersWidth}
375
+ onChange={(hideQuickfiltersWidth) => {
376
+ onLayoutOptionsChange({ ...layoutOptions, hideQuickfiltersWidth })
377
+ }}
378
+ sign="< "
379
+ />
380
+ </FormGroup>
381
+ <FormGroup label="Minimum Width (before scrolling or scaling)">
382
+ <WidthSelector
383
+ value={layoutOptions.minimumWidth}
384
+ onChange={(minimumWidth) => {
385
+ onLayoutOptionsChange({ ...layoutOptions, minimumWidth })
386
+ }}
387
+ sign="< "
388
+ />
389
+ <FormGroup label="When Below Minimum Width">
390
+ <Toggle
391
+ value={layoutOptions.belowMinimumWidth}
392
+ onChange={(belowMinimumWidth) => {
393
+ onLayoutOptionsChange({ ...layoutOptions, belowMinimumWidth: belowMinimumWidth as "scale" | "scroll" })
394
+ }}
395
+ size="sm"
396
+ options={[
397
+ { value: "scroll", label: "Scroll" },
398
+ { value: "scale", label: "Scale" }
399
+ ]}
400
+ />
401
+ </FormGroup>
402
+ </FormGroup>
403
+ <FormGroup label="Maximum Width (before padding)">
404
+ <WidthSelector
405
+ value={layoutOptions.maximumWidth}
406
+ onChange={(maximumWidth) => {
407
+ onLayoutOptionsChange({ ...layoutOptions, maximumWidth })
408
+ }}
409
+ sign="> "
410
+ />
411
+ </FormGroup>
412
+ <Checkbox value={layoutOptions.collapseSpacers} onChange={(collapseSpacers) => {
413
+ onLayoutOptionsChange({ ...layoutOptions, collapseSpacers })
414
+ }}>
415
+ Collapse Spacers on Mobile
416
+ </Checkbox>
417
+ </CollapsiblePanel>
418
+
419
+ <CollapsiblePanel title="Download/Upload Theme" initiallyClosed>
420
+ <div className="mb-2 text-muted">
421
+ Download or upload a custom theme to use for this dashboard.
422
+ This saves the current theme as a theme file that you can then use on other dashboards by uploading it there.
423
+ </div>
424
+ <div style={{ display: "flex", flexDirection: "column", marginTop: "10px", gap: "10px" }}>
425
+ <button className="btn btn-sm btn-secondary" onClick={handleDownloadTheme}>
426
+ <i className="fas fa-download" /> Download Custom Theme
427
+ </button>
428
+ <label className="btn btn-sm btn-secondary">
429
+ <i className="fas fa-upload" /> Upload Custom Theme
430
+ <input
431
+ type="file"
432
+ accept=".theme"
433
+ style={{ display: "none" }}
434
+ onChange={handleUploadTheme}
435
+ />
436
+ </label>
437
+ </div>
438
+ </CollapsiblePanel>
439
+ </div>
440
+ )
441
+ }
442
+
443
+ function SpacingInput(props: { value: Spacing; onChange: (value: Spacing) => void }) {
444
+ return (
445
+ <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', width: '100%' }}>
446
+ <div>Top</div>
447
+ <div>Bottom</div>
448
+ <div>Left</div>
449
+ <div>Right</div>
450
+ <PixelsInput value={props.value.top} onChange={(top) => props.onChange({ ...props.value, top: top! })} />
451
+ <PixelsInput value={props.value.bottom} onChange={(bottom) => props.onChange({ ...props.value, bottom: bottom! })} />
452
+ <PixelsInput value={props.value.left} onChange={(left) => props.onChange({ ...props.value, left: left! })} />
453
+ <PixelsInput value={props.value.right} onChange={(right) => props.onChange({ ...props.value, right: right! })} />
454
+ </div>
455
+ )
456
+ }
457
+
458
+ function PixelsInput(props: {
459
+ value: number | null
460
+ onChange: (value: number | null) => void
461
+ allowDefault?: boolean
462
+ defaultLabel?: string
463
+ }) {
464
+ const options = [
465
+ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40
466
+ ]
467
+
468
+ return (
469
+ <div className="dropdown">
470
+ <a
471
+ className="dropdown-toggle"
472
+ type="button"
473
+ id="pixelsInputDropdown"
474
+ data-bs-toggle="dropdown"
475
+ aria-expanded="false"
476
+ >
477
+ {props.value === null ? (props.defaultLabel || "Default") : `${props.value}px`}
478
+ </a>
479
+ <ul className="dropdown-menu" aria-labelledby="pixelsInputDropdown">
480
+ {props.allowDefault && (
481
+ <li key="default">
482
+ <a
483
+ className="dropdown-item"
484
+ href="#"
485
+ onClick={(e) => {
486
+ e.preventDefault()
487
+ props.onChange(null)
488
+ }}
489
+ >
490
+ {props.defaultLabel || "Default"}
491
+ </a>
492
+ </li>
493
+ )}
494
+ {options.map(option => (
495
+ <li key={option}>
496
+ <a
497
+ className="dropdown-item"
498
+ href="#"
499
+ onClick={(e) => {
500
+ e.preventDefault()
501
+ props.onChange(option)
502
+ }}
503
+ >
504
+ {option}px
505
+ </a>
506
+ </li>
507
+ ))}
508
+ </ul>
509
+ </div>
205
510
  )
206
511
  }
207
512
 
@@ -226,4 +531,4 @@ function WidthSelector(props: {
226
531
  ]}
227
532
  />
228
533
  )
229
- }
534
+ }