@onehat/ui 0.2.51 → 0.2.54

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onehat/ui",
3
- "version": "0.2.51",
3
+ "version": "0.2.54",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -19,8 +19,9 @@
19
19
  },
20
20
  "license": "UNLICENSED",
21
21
  "dependencies": {
22
- "@onehat/data": "^1.16.0",
22
+ "@onehat/data": "^1.16.10",
23
23
  "@hookform/resolvers": "^2.9.11",
24
+ "@k-renwick/colour-mixer": "^1.2.1",
24
25
  "ckeditor5-custom-build": "file:ckeditor5",
25
26
  "js-cookie": "^3.0.1",
26
27
  "native-base": "^3.4.25",
@@ -0,0 +1,15 @@
1
+ import IconButton from './IconButton.js';
2
+ import SquareCheck from '../Icons/SquareCheck.js';
3
+ import Square from '../Icons/Square.js';
4
+
5
+ export default function CheckboxButton(props) {
6
+ const {
7
+ isChecked,
8
+ } = props;
9
+
10
+ return <IconButton
11
+ icon={isChecked ? SquareCheck : Square }
12
+ {...props}
13
+ />;
14
+ }
15
+
@@ -17,10 +17,14 @@ const IconButton = React.forwardRef((props, ref) => {
17
17
  tooltipPlacement = 'bottom',
18
18
  } = props;
19
19
  const propsIcon = props._icon || {};
20
- let icon = props.icon || <Icon {...propsIcon} />;
20
+ let icon = props.icon || <Icon {...propsIcon} />,
21
+ ret;
21
22
  if (isLoading) {
22
23
  icon = <Spinner {..._spinner} />;
23
24
  }
25
+ if (!React.isValidElement(icon)) {
26
+ icon = <Icon as={icon} {...propsIcon} />;
27
+ }
24
28
  const pressable = <Pressable
25
29
  ref={ref}
26
30
  borderRadius="md"
@@ -29,16 +33,23 @@ const IconButton = React.forwardRef((props, ref) => {
29
33
  justifyContent="center"
30
34
  alignItems="center"
31
35
  p={2}
36
+ // bg={styles.ICON_BUTTON_BG}
37
+ _hover={{
38
+ bg: styles.ICON_BUTTON_BG_HOVER,
39
+ }}
32
40
  _disabled={{
33
41
  bg: styles.ICON_BUTTON_BG_DISABLED,
34
42
  }}
43
+ _pressed={{
44
+ bg: styles.ICON_BUTTON_BG_PRESSED,
45
+ }}
35
46
  {...props}
36
47
  >
37
- {icon}
48
+ {icon}
38
49
  </Pressable>;
39
- let ret = pressable;
50
+ ret = pressable;
40
51
  if (tooltip) {
41
- ret = <Tooltip label={tooltip} placement={tooltipPlacement}>{pressable}</Tooltip>;
52
+ ret = <Tooltip label={tooltip} placement={tooltipPlacement}>{ret}</Tooltip>;
42
53
  }
43
54
  return ret;
44
55
  });
@@ -1,4 +1,4 @@
1
- import React, { useState, } from 'react';
1
+ import React, { useState, useEffect, useId, } from 'react';
2
2
  import {
3
3
  Column,
4
4
  Row,
@@ -12,6 +12,8 @@ import {
12
12
  UI_MODE_REACT_NATIVE,
13
13
  CURRENT_MODE,
14
14
  } from '../../Constants/UiModes.js';
15
+ import getSaved from '../../Functions/getSaved.js';
16
+ import setSaved from '../../Functions/setSaved.js';
15
17
  import Splitter from './Splitter.js';
16
18
 
17
19
  export default function Container(props) {
@@ -31,15 +33,49 @@ export default function Container(props) {
31
33
  isWestCollapsed,
32
34
  setIsWestCollapsed,
33
35
  } = props,
36
+ id = useId(),
34
37
  canResize = CURRENT_MODE === UI_MODE_WEB,
35
- [localIsNorthCollapsed, setLocalIsNorthCollapsed] = useState(north ? north.props.startsCollapsed : false),
36
- [localIsSouthCollapsed, setLocalIsSouthCollapsed] = useState(south ? south.props.startsCollapsed : false),
37
- [localIsEastCollapsed, setLocalIsEastCollapsed] = useState(east ? east.props.startsCollapsed : false),
38
- [localIsWestCollapsed, setLocalIsWestCollapsed] = useState(west ? west.props.startsCollapsed : false),
39
- [northHeight, setNorthHeight] = useState(north ? north.props.h : 0),
40
- [southHeight, setSouthHeight] = useState(south ? south.props.h : 0),
41
- [eastWidth, setEastWidth] = useState(east ? east.props.w : 0),
42
- [westWidth, setWestWidth] = useState(west ? west.props.w : 0),
38
+ [isReady, setIsReady] = useState(false),
39
+ [localIsNorthCollapsed, setLocalIsNorthCollapsedRaw] = useState(north ? north.props.startsCollapsed : false),
40
+ [localIsSouthCollapsed, setLocalIsSouthCollapsedRaw] = useState(south ? south.props.startsCollapsed : false),
41
+ [localIsEastCollapsed, setLocalIsEastCollapsedRaw] = useState(east ? east.props.startsCollapsed : false),
42
+ [localIsWestCollapsed, setLocalIsWestCollapsedRaw] = useState(west ? west.props.startsCollapsed : false),
43
+ [northHeight, setNorthHeightRaw] = useState(north ? north.props.h : 0),
44
+ [southHeight, setSouthHeightRaw] = useState(south ? south.props.h : 0),
45
+ [eastWidth, setEastWidthRaw] = useState(east ? east.props.w : 0),
46
+ [westWidth, setWestWidthRaw] = useState(west ? west.props.w : 0),
47
+ setLocalIsNorthCollapsed = (bool) => {
48
+ setLocalIsNorthCollapsedRaw(bool);
49
+ setSaved(id + '-localIsNorthCollapsed', bool);
50
+ },
51
+ setLocalIsSouthCollapsed = (bool) => {
52
+ setLocalIsSouthCollapsedRaw(bool);
53
+ setSaved(id + '-localIsSouthCollapsed', bool);
54
+ },
55
+ setLocalIsEastCollapsed = (bool) => {
56
+ setLocalIsEastCollapsedRaw(bool);
57
+ setSaved(id + '-localIsEastCollapsed', bool);
58
+ },
59
+ setLocalIsWestCollapsed = (bool) => {
60
+ setLocalIsWestCollapsedRaw(bool);
61
+ setSaved(id + '-localIsWestCollapsed', bool);
62
+ },
63
+ setNorthHeight = (height) => {
64
+ setNorthHeightRaw(height);
65
+ setSaved(id + '-northHeight', height);
66
+ },
67
+ setSouthHeight = (height) => {
68
+ setSouthHeightRaw(height);
69
+ setSaved(id + '-southHeight', height);
70
+ },
71
+ setEastWidth = (width) => {
72
+ setEastWidthRaw(width);
73
+ setSaved(id + '-eastWidth', width);
74
+ },
75
+ setWestWidth = (width) => {
76
+ setWestWidthRaw(width);
77
+ setSaved(id + '-westWidth', width);
78
+ },
43
79
  onNorthResize = (delta) => {
44
80
  const newHeight = northHeight + delta;
45
81
  setNorthHeight(newHeight);
@@ -56,6 +92,68 @@ export default function Container(props) {
56
92
  const newWidth = westWidth + delta;
57
93
  setWestWidth(newWidth);
58
94
  };
95
+
96
+ useEffect(() => {
97
+ // Restore saved settings
98
+ (async () => {
99
+ let key, val;
100
+ key = id + '-localIsNorthCollapsed';
101
+ val = await getSaved(key);
102
+ if (!_.isNil(val)) {
103
+ setLocalIsNorthCollapsedRaw(val);
104
+ }
105
+
106
+ key = id + '-localIsSouthCollapsed';
107
+ val = await getSaved(key);
108
+ if (!_.isNil(val)) {
109
+ setLocalIsSouthCollapsedRaw(val);
110
+ }
111
+
112
+ key = id + '-localIsEastCollapsed';
113
+ val = await getSaved(key);
114
+ if (!_.isNil(val)) {
115
+ setLocalIsEastCollapsedRaw(val);
116
+ }
117
+
118
+ key = id + '-localIsWestCollapsed';
119
+ val = await getSaved(key);
120
+ if (!_.isNil(val)) {
121
+ setLocalIsWestCollapsedRaw(val);
122
+ }
123
+
124
+ key = id + '-northHeight';
125
+ val = await getSaved(key);
126
+ if (!_.isNil(val)) {
127
+ setNorthHeightRaw(val);
128
+ }
129
+
130
+ key = id + '-southHeight';
131
+ val = await getSaved(key);
132
+ if (!_.isNil(val)) {
133
+ setSouthHeightRaw(val);
134
+ }
135
+
136
+ key = id + '-eastWidth';
137
+ val = await getSaved(key);
138
+ if (!_.isNil(val)) {
139
+ setEastWidthRaw(val);
140
+ }
141
+
142
+ key = id + '-westWidth';
143
+ val = await getSaved(key);
144
+ if (!_.isNil(val)) {
145
+ setWestWidthRaw(val);
146
+ }
147
+
148
+ if (!isReady) {
149
+ setIsReady(true);
150
+ }
151
+ })();
152
+ }, []);
153
+
154
+ if (!isReady) {
155
+ return null;
156
+ }
59
157
 
60
158
  let componentProps = {},
61
159
  centerComponent = null,
@@ -412,6 +412,7 @@ export function Combo(props) {
412
412
  flex={1}
413
413
  h="100%"
414
414
  m={0}
415
+ autoSubmitDelay={0}
415
416
  borderTopRightRadius={0}
416
417
  borderBottomRightRadius={0}
417
418
  fontSize={styles.FORM_COMBO_INPUT_FONTSIZE}
@@ -21,6 +21,7 @@ import {
21
21
  DROP_POSITION_BEFORE,
22
22
  DROP_POSITION_AFTER,
23
23
  } from '../../Constants/Grid.js';
24
+ import * as colourMixer from '@k-renwick/colour-mixer'
24
25
  import UiGlobals from '../../UiGlobals.js';
25
26
  import useForceUpdate from '../../Hooks/useForceUpdate.js';
26
27
  import withContextMenu from '../Hoc/withContextMenu.js';
@@ -35,6 +36,7 @@ import withSelection from '../Hoc/withSelection.js';
35
36
  import withWindowedEditor from '../Hoc/withWindowedEditor.js';
36
37
  import withInlineEditor from '../Hoc/withInlineEditor.js';
37
38
  import testProps from '../../Functions/testProps.js';
39
+ import nbToRgb from '../../Functions/nbToRgb.js';
38
40
  import GridHeaderRow from './GridHeaderRow.js';
39
41
  import GridRow, { ReorderableGridRow } from './GridRow.js';
40
42
  import IconButton from '../Buttons/IconButton.js';
@@ -45,6 +47,7 @@ import NoReorderRows from '../Icons/NoReorderRows.js';
45
47
  import ReorderRows from '../Icons/ReorderRows.js';
46
48
  import _ from 'lodash';
47
49
 
50
+
48
51
  // Grid requires the use of HOC withSelection() whenever it's used.
49
52
  // The default export is *with* the HOC. A separate *raw* component is
50
53
  // exported which can be combined with many HOCs for various functionality.
@@ -67,7 +70,7 @@ export function Grid(props) {
67
70
  columnsConfig = [], // json configurations for each column
68
71
 
69
72
  columnProps = {},
70
- getRowProps = () => {
73
+ getRowProps = (item) => {
71
74
  return {
72
75
  borderBottomWidth: 1,
73
76
  borderBottomColor: 'trueGray.500',
@@ -348,19 +351,20 @@ export function Grid(props) {
348
351
  />;
349
352
  }
350
353
 
351
- let bg = styles.GRID_ROW_BG;
354
+ let bg = rowProps.bg || styles.GRID_ROW_BG,
355
+ mixWith;
352
356
  if (isSelected) {
353
357
  if (showHovers && isHovered) {
354
- bg = styles.GRID_ROW_SELECTED_HOVER_BG;
358
+ mixWith = styles.GRID_ROW_SELECTED_HOVER_BG;
355
359
  } else {
356
- bg = styles.GRID_ROW_SELECTED_BG;
357
- }
358
- } else {
359
- if (showHovers && isHovered) {
360
- bg = styles.GRID_ROW_HOVER_BG;
361
- } else {
362
- bg = styles.GRID_ROW_BG;
360
+ mixWith = styles.GRID_ROW_SELECTED_BG;
363
361
  }
362
+ } else if (showHovers && isHovered) {
363
+ mixWith = styles.GRID_ROW_HOVER_BG;
364
+ }
365
+ if (mixWith) {
366
+ const mixWithObj = nbToRgb(mixWith);
367
+ bg = colourMixer.blend(bg, 0.9, mixWithObj.color);
364
368
  }
365
369
  let WhichGridRow = GridRow,
366
370
  rowReorderProps = {};
@@ -12,6 +12,8 @@ import FormPanel from '../Panel/FormPanel.js';
12
12
  import Ban from '../Icons/Ban.js';
13
13
  import Gear from '../Icons/Gear.js';
14
14
  import Toolbar from '../Toolbar/Toolbar.js';
15
+ import getSaved from '../../Functions/getSaved.js';
16
+ import setSaved from '../../Functions/setSaved.js';
15
17
  import UiGlobals from '../../UiGlobals.js';
16
18
  import _ from 'lodash';
17
19
 
@@ -34,8 +36,6 @@ export default function withFilters(WrappedComponent) {
34
36
  customFilters = [], // of shape: { title, type, field, value, getRepoFilters(value) }
35
37
  minFilters = 3,
36
38
  maxFilters = 6,
37
- getSaved,
38
- setSaved,
39
39
 
40
40
  // withData
41
41
  Repository,
@@ -166,7 +166,7 @@ export default function withFilters(WrappedComponent) {
166
166
  }
167
167
  setSlots(newSlots);
168
168
  }
169
- if (save && setSaved) {
169
+ if (save) {
170
170
  setSaved(id + '-filters', filters);
171
171
  }
172
172
  },
@@ -204,9 +204,12 @@ export default function withFilters(WrappedComponent) {
204
204
  const filter = getFilterByField(field);
205
205
  return filter?.type;
206
206
  },
207
- getIsFilterRange = (field) => {
208
- // determines if filter is a "range" filter
207
+ getIsFilterRange = (filter) => {
208
+ let field = _.isString(filter) ? filter : filter.field;
209
209
  const filterType = getFilterType(field);
210
+ if (filterType?.type) {
211
+ return inArray(filterType.type, ['NumberRange', 'DateRange'])
212
+ }
210
213
  return inArray(filterType, ['NumberRange', 'DateRange']);
211
214
  },
212
215
  renderFilters = () => {
@@ -272,7 +275,7 @@ export default function withFilters(WrappedComponent) {
272
275
  const newRepoFilters = [];
273
276
  let filtersToUse = filters
274
277
 
275
- if (!isReady && getSaved) {
278
+ if (!isReady) {
276
279
  const savedFilters = await getSaved(id + '-filters');
277
280
  if (!_.isEmpty(savedFilters)) {
278
281
  // load saved filters
@@ -295,7 +298,7 @@ export default function withFilters(WrappedComponent) {
295
298
  field,
296
299
  value,
297
300
  } = filter,
298
- isFilterRange = getIsFilterRange(field);
301
+ isFilterRange = getIsFilterRange(filter);
299
302
  if (isFilterRange) {
300
303
  if (!!value) {
301
304
  const
@@ -1,4 +1,4 @@
1
- import React, { useState, } from 'react';
1
+ import React, { useState, useEffect, useId, } from 'react';
2
2
  import {
3
3
  Button,
4
4
  Column,
@@ -16,6 +16,8 @@ import IconButton from '../Buttons/IconButton.js';
16
16
  import Minimize from '../Icons/Minimize.js';
17
17
  import Maximize from '../Icons/Maximize.js';
18
18
  import Panel from './Panel.js';
19
+ import getSaved from '../../Functions/getSaved.js';
20
+ import setSaved from '../../Functions/setSaved.js';
19
21
  import _ from 'lodash';
20
22
 
21
23
 
@@ -30,9 +32,12 @@ export default function TabPanel(props) {
30
32
  startsCollapsed = true,
31
33
  onChangeCurrentTab,
32
34
  onChangeIsCollapsed,
35
+ saveCurrentTab = true,
33
36
  ...propsToPass
34
37
  } = props,
35
38
  styles = UiGlobals.styles,
39
+ id = useId(),
40
+ [isReady, setIsReady] = useState(false),
36
41
  [currentTab, setCurrentTabRaw] = useState(initialTab),
37
42
  [isCollapsed, setIsCollapsedRaw] = useState(startsCollapsed),
38
43
  setIsCollapsed = (isCollapsed) => {
@@ -40,22 +45,23 @@ export default function TabPanel(props) {
40
45
  if (onChangeIsCollapsed) {
41
46
  onChangeIsCollapsed(isCollapsed);
42
47
  }
48
+ setSaved(id + '-isCollapsed', isCollapsed);
43
49
  },
44
50
  setCurrentTab = (ix) => {
45
51
  setCurrentTabRaw(ix);
46
52
  if (onChangeCurrentTab) {
47
53
  onChangeCurrentTab(ix);
48
54
  }
55
+ if (saveCurrentTab) {
56
+ setSaved(id + '-currentTab', ix);
57
+ }
49
58
  },
50
59
  getButtonProps = () => {
51
60
  const
52
61
  iconProps = {
53
62
  size: 'md',
54
63
  },
55
- textProps = {
56
- ml: '-8px',
57
- mr: '8px',
58
- },
64
+ textProps = {},
59
65
  buttonProps = {
60
66
  bg: styles.TAB_BG,
61
67
  color: styles.TAB_COLOR,
@@ -279,54 +285,81 @@ export default function TabPanel(props) {
279
285
  onToggleCollapse = () => {
280
286
  setIsCollapsed(!isCollapsed);
281
287
  };
282
- if (direction === VERTICAL) {
283
- return <Panel {...propsToPass}>
284
- <Row flex={1} w="100%">
285
- <Column
286
- alignItems="center"
287
- justifyContent="flex-start"
288
- py={2}
289
- pl={isCollapsed ? 1 : 4}
290
- bg={styles.TAB_BAR_BG}
291
- w={isCollapsed ? '50px' : tabWidth}
292
- >
293
- {renderTabs()}
294
- <Column flex={1} w="100%" justifyContent="flex-end">
295
- {renderToggleButton()}
296
- </Column>
297
- </Column>
298
- <Column
299
- alignItems="center"
300
- justifyContent="flex-start"
301
- flex={1}
302
- >
303
- {renderCurrentTabContent()}
304
- </Column>
305
- </Row>
306
- </Panel>;
307
- }
308
288
 
309
- // HORIZONTAL
310
- return <Panel flex={1} w="100%" {...propsToPass} {...props._panel}>
311
- <Column flex={1} w="100%">
312
- <Row
289
+ useEffect(() => {
290
+ // Restore saved settings
291
+ (async () => {
292
+ let key, val;
293
+ key = id + '-isCollapsed';
294
+ val = await getSaved(key);
295
+ if (!_.isNil(val)) {
296
+ setIsCollapsed(val);
297
+ }
298
+
299
+ key = id + '-currentTab';
300
+ val = await getSaved(key);
301
+ if (!_.isNil(val)) {
302
+ setCurrentTab(val);
303
+ }
304
+
305
+ if (!isReady) {
306
+ setIsReady(true);
307
+ }
308
+ })();
309
+ }, []);
310
+
311
+ if (!isReady) {
312
+ return null;
313
+ }
314
+
315
+ if (direction === VERTICAL) {
316
+ return <Panel {...propsToPass}>
317
+ <Row flex={1} w="100%">
318
+ <Column
313
319
  alignItems="center"
314
320
  justifyContent="flex-start"
315
- p={2}
316
- pb={0}
321
+ py={2}
322
+ pl={isCollapsed ? 1 : 4}
317
323
  bg={styles.TAB_BAR_BG}
318
- h={isCollapsed ? '30px' : tabHeight}
324
+ w={isCollapsed ? '50px' : tabWidth}
319
325
  >
320
326
  {renderTabs()}
321
- <Row flex={1} h="100%" justifyContent="flex-end">
322
- <Row h="100%">
323
- {renderToggleButton()}
324
- </Row>
325
- </Row>
326
- </Row>
327
- <Row flex={1}>
327
+ <Column flex={1} w="100%" justifyContent="flex-end">
328
+ {renderToggleButton()}
329
+ </Column>
330
+ </Column>
331
+ <Column
332
+ alignItems="center"
333
+ justifyContent="flex-start"
334
+ flex={1}
335
+ >
328
336
  {renderCurrentTabContent()}
329
- </Row>
330
- </Column>
337
+ </Column>
338
+ </Row>
331
339
  </Panel>;
340
+ }
341
+
342
+ // HORIZONTAL
343
+ return <Panel flex={1} w="100%" {...propsToPass} {...props._panel}>
344
+ <Column flex={1} w="100%">
345
+ <Row
346
+ alignItems="center"
347
+ justifyContent="flex-start"
348
+ p={2}
349
+ pb={0}
350
+ bg={styles.TAB_BAR_BG}
351
+ h={isCollapsed ? '30px' : tabHeight}
352
+ >
353
+ {renderTabs()}
354
+ <Row flex={1} h="100%" justifyContent="flex-end">
355
+ <Row h="100%">
356
+ {renderToggleButton()}
357
+ </Row>
358
+ </Row>
359
+ </Row>
360
+ <Row flex={1}>
361
+ {renderCurrentTabContent()}
362
+ </Row>
363
+ </Column>
364
+ </Panel>;
332
365
  }
@@ -1,4 +1,4 @@
1
- import React, { useState, useMemo, } from 'react';
1
+ import React, { useState, useEffect, useMemo, useId, } from 'react';
2
2
  import {
3
3
  HORIZONTAL,
4
4
  VERTICAL,
@@ -12,6 +12,8 @@ import Container from '../Container/Container.js';
12
12
  import Panel from '../Panel/Panel.js';
13
13
  import TabPanel from '../Panel/TabPanel.js';
14
14
  import UploadDownload from '../Panel/UploadDownload.js';
15
+ import getSaved from '../../Functions/getSaved.js';
16
+ import setSaved from '../../Functions/setSaved.js';
15
17
  import _ from 'lodash';
16
18
 
17
19
  export default function DataMgt(props) {
@@ -40,12 +42,26 @@ export default function DataMgt(props) {
40
42
  } = props;
41
43
 
42
44
  const
45
+ id = useId(),
43
46
  // westRef = useRef(),
44
- [isWestCollapsed, setIsWestCollapsed] = useState(westStartsCollapsed),
45
- [isEastCollapsed, setIsEastCollapsed] = useState(eastStartsCollapsed),
46
- [isFullscreen, setIsFullscreen] = useState(false),
47
+ [isReady, setIsReady] = useState(false),
48
+ [isWestCollapsed, setIsWestCollapsedRaw] = useState(westStartsCollapsed),
49
+ [isEastCollapsed, setIsEastCollapsedRaw] = useState(eastStartsCollapsed),
50
+ [isFullscreen, setIsFullscreenRaw] = useState(false),
47
51
  [westSelected, setWestSelectedRaw] = useState(),
48
52
  [centerSelected, setCenterSelected] = useState(),
53
+ setIsWestCollapsed = (bool) => {
54
+ setIsWestCollapsedRaw(bool);
55
+ setSaved(id + '-isWestCollapsed', bool);
56
+ },
57
+ setIsEastCollapsed = (bool) => {
58
+ setIsEastCollapsedRaw(bool);
59
+ setSaved(id + '-isEastCollapsed', bool);
60
+ },
61
+ setIsFullscreen = (bool) => {
62
+ setIsFullscreenRaw(bool);
63
+ setSaved(id + '-isFullscreen', isFullscreen);
64
+ },
49
65
  setWestSelected = (selected) => {
50
66
  setWestSelectedRaw(selected);
51
67
  setCenterSelected(); // clear selection in center
@@ -85,6 +101,51 @@ export default function DataMgt(props) {
85
101
  }
86
102
  };
87
103
 
104
+ useEffect(() => {
105
+ if (!getSaved) {
106
+ setIsReady(true);
107
+ return () => {};
108
+ }
109
+
110
+ // Restore saved settings
111
+ (async () => {
112
+
113
+ let key, val;
114
+ key = id + '-isWestCollapsed';
115
+ val = await getSaved(key);
116
+ if (!_.isNil(val)) {
117
+ setIsWestCollapsedRaw(val);
118
+ }
119
+
120
+ key = id + '-isEastCollapsed';
121
+ val = await getSaved(key);
122
+ if (!_.isNil(val)) {
123
+ setIsEastCollapsedRaw(val);
124
+ }
125
+
126
+ key = id + '-isFullscreen';
127
+ val = await getSaved(key);
128
+ if (!_.isNil(val)) {
129
+ setIsFullscreenRaw(val);
130
+ }
131
+
132
+ key = id + '-westSelected';
133
+ val = await getSaved(key);
134
+ if (!_.isNil(val)) {
135
+ setWestSelectedRaw(val);
136
+ }
137
+
138
+ key = id + '-centerSelected';
139
+ val = await getSaved(key);
140
+ if (!_.isNil(val)) {
141
+ setCenterSelectedRaw(val);
142
+ }
143
+
144
+ if (!isReady) {
145
+ setIsReady(true);
146
+ }
147
+ })();
148
+ }, []);
88
149
 
89
150
  // REGIONS -------------------------------------------------------
90
151
  // [ ] [ ] [ ]
@@ -238,6 +299,10 @@ export default function DataMgt(props) {
238
299
  }
239
300
  }
240
301
 
302
+ if (!isReady) {
303
+ return null;
304
+ }
305
+
241
306
  return <Container
242
307
  west={west}
243
308
  isWestCollapsed={isWestCollapsed}
@@ -41,9 +41,6 @@ export default function Pagination(props) {
41
41
  return useMemo(() => {
42
42
  const
43
43
  iconButtonProps = {
44
- _hover: {
45
- bg: 'trueGray.400',
46
- },
47
44
  // mx: 1,
48
45
  },
49
46
  iconProps = {
@@ -12,6 +12,7 @@ export default function PaginationToolbar(props) {
12
12
  } = props,
13
13
  [minimize, setMinimize] = useState(false),
14
14
  propsToPass = _.omit(props, 'toolbarItems'),
15
+ showPagination = props.Repository?.totalPages > 1,
15
16
  onLayout = (e) => {
16
17
  // Note to future self: this is using hard-coded values.
17
18
  // Eventually might want to make it responsive to actual sizes
@@ -30,6 +31,16 @@ export default function PaginationToolbar(props) {
30
31
  }
31
32
  };
32
33
 
34
+ let toolbarProps = {};
35
+ if (showPagination) {
36
+ toolbarProps = {
37
+ borderLeftWidth: 1,
38
+ borderLeftColor: 'trueGray.400',
39
+ pl: 3,
40
+ ml: 3,
41
+ };
42
+ }
43
+
33
44
  return <Toolbar
34
45
  bg="trueGray.200"
35
46
  borderTopWidth={1}
@@ -37,7 +48,7 @@ export default function PaginationToolbar(props) {
37
48
  w="100%"
38
49
  onLayout={(e) => onLayout(e)}
39
50
  >
40
- <Pagination {...propsToPass} w={toolbarItems.length ? null : '100%'} minimize={minimize} />
41
- {toolbarItems.length ? <Row flex={1} borderLeftWidth={1} borderLeftColor="trueGray.400" pl={3} ml={3}>{toolbarItems}</Row> : null}
51
+ {showPagination && <Pagination {...propsToPass} w={toolbarItems.length ? null : '100%'} minimize={minimize} />}
52
+ {toolbarItems.length ? <Row flex={1} {...toolbarProps}>{toolbarItems}</Row> : null}
42
53
  </Toolbar>;
43
54
  };
@@ -61,7 +61,10 @@ const defaults = {
61
61
  GRID_TOOLBAR_ITEMS_COLOR: 'trueGray.800',
62
62
  GRID_TOOLBAR_ITEMS_DISABLED_COLOR: 'disabled',
63
63
  GRID_TOOLBAR_ITEMS_ICON_SIZE: 'sm',
64
+ ICON_BUTTON_BG: 'trueGray.200:alpha.0',
64
65
  ICON_BUTTON_BG_DISABLED: 'trueGray.200',
66
+ ICON_BUTTON_BG_HOVER: '#000:alpha.20',
67
+ ICON_BUTTON_BG_PRESSED: '#000:alpha.30',
65
68
  PANEL_FOOTER_BG: 'primary.100', // :alpha.50
66
69
  PANEL_HEADER_BG: 'primary.100',
67
70
  PANEL_HEADER_BG_VERTICAL: 'primary.100',
@@ -0,0 +1,10 @@
1
+ import oneHatData from '@onehat/data';
2
+ import UiGlobals from '../UiGlobals.js';
3
+
4
+ export default async function deleteSaved(key) {
5
+ const Repo = oneHatData.getRepository(UiGlobals.uiSavesRepo);
6
+ if (!Repo) {
7
+ return null;
8
+ }
9
+ await Repo.deleteById(key);
10
+ }
@@ -0,0 +1,38 @@
1
+ import oneHatData from '@onehat/data';
2
+ import UiGlobals from '../UiGlobals.js';
3
+ import _ from 'lodash';
4
+
5
+ export default async function getSaved(key) {
6
+ const
7
+ Repo = oneHatData.getRepository(UiGlobals.uiSavesRepo),
8
+ entity = Repo?.getById(key);
9
+
10
+ if (!entity) {
11
+ return null;
12
+ }
13
+
14
+ let value = entity.value;
15
+
16
+ if (entity.isJson) {
17
+ value = JSON.parse(value);
18
+ if (entity.isOneBuild) {
19
+ // Convert the data to an actual entity (or entities) of the correct type
20
+ const
21
+ Repository = oneHatData.getRepository(entity.model),
22
+ entities = [];
23
+ let i, data, entity;
24
+ if (_.isArray(value)) {
25
+ for (i = 0; i = value.length; i++) {
26
+ data = value[i];
27
+ entity = await Repository.createStandaloneEntity(data);
28
+ entities.push(entity);
29
+ }
30
+ value = entities;
31
+ } else {
32
+ value = await Repository.createStandaloneEntity(value);
33
+ }
34
+
35
+ }
36
+ }
37
+ return value;
38
+ }
@@ -0,0 +1,63 @@
1
+ import UiGlobals from '../UiGlobals.js';
2
+ import _ from 'lodash';
3
+
4
+ function isRgb(color) {
5
+ const
6
+ regex = /^#[\w]{3,6}$/,
7
+ matches = color.match(regex);
8
+ return !!matches?.[0];
9
+ }
10
+
11
+ // 'color' might be in a format NativeBase uses, like '#000:alpha.20' or 'primary.200'
12
+ // Try to convert this to actual RGB colors.
13
+ export default function nbToRgb(color) {
14
+
15
+ if (isRgb(color)) {
16
+ // already in RGB format; simply return it
17
+ return {
18
+ color,
19
+ alpha,
20
+ };
21
+ }
22
+
23
+ const themeOverrideColors = UiGlobals?.ThemeOverrides?.colors || {};
24
+ if (themeOverrideColors[color]) {
25
+ color = themeOverrideColors[color];
26
+ }
27
+
28
+ let regex, alpha, matches;
29
+
30
+ regex = /^([\w#\.]+)(:alpha\.([\d]{1,2}))?$/;
31
+ matches = color.match(regex);
32
+ if (matches[3]) {
33
+ // alpha part exists. parse it
34
+ alpha = parseInt(matches[3], 10) / 100;
35
+ }
36
+ if (matches[1]) {
37
+ // color part exists. parse it
38
+ color = matches[1];
39
+ regex = /^(.+)\.([\d]{3})$/;
40
+ matches = color.match(regex);
41
+ if (matches) {
42
+ // color is in dot notation, like 'primary.200'
43
+ color = matches[1];
44
+ const whichValue = parseInt(matches[2], 10);
45
+ if (themeOverrideColors[color]?.[whichValue]) {
46
+ color = themeOverrideColors[color][whichValue];
47
+ }
48
+ } else if (themeOverrideColors[color]) {
49
+ // color is of form 'hover'
50
+ color = themeOverrideColors[color];
51
+ }
52
+
53
+ if (!isRgb(color)) {
54
+ color = nbToRgb(color).color;
55
+ }
56
+ }
57
+
58
+
59
+ return {
60
+ color,
61
+ alpha,
62
+ };
63
+ }
@@ -0,0 +1,55 @@
1
+ import oneHatData from '@onehat/data';
2
+ import UiGlobals from '../UiGlobals.js';
3
+ import _ from 'lodash';
4
+
5
+ export default async function setSaved(key, value) {
6
+ const Repo = oneHatData.getRepository(UiGlobals.uiSavesRepo);
7
+ if (!Repo) {
8
+ return null;
9
+ }
10
+
11
+ let isOneBuild = false,
12
+ isJson = false,
13
+ model = null;
14
+ if (!_.isNil(value) && typeof value !== 'string') {
15
+ if (_.isArray(value)) {
16
+ const objects = value;
17
+ if (objects[0]?.getDataForNewEntity) {
18
+ model = objects[0].repository.name;
19
+ isOneBuild = true;
20
+ }
21
+ const rawValues = [];
22
+ _.each(objects, (obj) => {
23
+ rawValues.push(isOneBuild ? obj.getDataForNewEntity() : obj);
24
+ });
25
+ value = JSON.stringify(rawValues);
26
+ } else {
27
+ if (value.getDataForNewEntity) {
28
+ model = value.repository.name;
29
+ value = value.getDataForNewEntity();
30
+ isOneBuild = true;
31
+ }
32
+ value = JSON.stringify(value);
33
+ }
34
+ isJson = true;
35
+ }
36
+
37
+ const entity = Repo.getById(key);
38
+ if (entity) {
39
+ entity.setValues({
40
+ value,
41
+ isOneBuild,
42
+ isJson,
43
+ model,
44
+ });
45
+ await Repo.save(entity);
46
+ } else {
47
+ await Repo.add({
48
+ key,
49
+ value,
50
+ isOneBuild,
51
+ isJson,
52
+ model,
53
+ });
54
+ }
55
+ }
@@ -0,0 +1,5 @@
1
+ import UiGlobals from '../UiGlobals.js';
2
+
3
+ export default function setThemeOverrides(ThemeOverrides) {
4
+ UiGlobals.ThemeOverrides = ThemeOverrides;
5
+ }
@@ -0,0 +1,5 @@
1
+ import UiGlobals from '../UiGlobals.js';
2
+
3
+ export default function setUiSavesRepo(name) {
4
+ UiGlobals.uiSavesRepo = name;
5
+ }