@onehat/ui 0.2.50 → 0.2.53

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.50",
3
+ "version": "0.2.53",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "license": "UNLICENSED",
21
21
  "dependencies": {
22
- "@onehat/data": "^1.16.0",
22
+ "@onehat/data": "^1.16.9",
23
23
  "@hookform/resolvers": "^2.9.11",
24
24
  "ckeditor5-custom-build": "file:ckeditor5",
25
25
  "js-cookie": "^3.0.1",
@@ -5,6 +5,7 @@ import {
5
5
  Spinner,
6
6
  Tooltip,
7
7
  } from 'native-base';
8
+ import styles from '../../Constants/Styles.js';
8
9
 
9
10
  const IconButton = React.forwardRef((props, ref) => {
10
11
  const {
@@ -29,7 +30,7 @@ const IconButton = React.forwardRef((props, ref) => {
29
30
  alignItems="center"
30
31
  p={2}
31
32
  _disabled={{
32
- bg: 'trueGray.300',
33
+ bg: styles.ICON_BUTTON_BG_DISABLED,
33
34
  }}
34
35
  {...props}
35
36
  >
@@ -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,
@@ -277,6 +277,7 @@ export function DateElement(props) {
277
277
  setTop(top + height);
278
278
  setLeft(left);
279
279
  }}
280
+ w={props.w || null}
280
281
  />
281
282
  {/* <Pressable
282
283
  flex={1}
@@ -1,4 +1,4 @@
1
- import { useState, useEffect, } from 'react';
1
+ import { useState, useEffect, useId, } from 'react';
2
2
  import {
3
3
  Column,
4
4
  Modal,
@@ -34,6 +34,8 @@ export default function withFilters(WrappedComponent) {
34
34
  customFilters = [], // of shape: { title, type, field, value, getRepoFilters(value) }
35
35
  minFilters = 3,
36
36
  maxFilters = 6,
37
+ getSaved,
38
+ setSaved,
37
39
 
38
40
  // withData
39
41
  Repository,
@@ -54,6 +56,7 @@ export default function withFilters(WrappedComponent) {
54
56
  excludeFields: modelExcludeFields,
55
57
  filteringDisabled: modelFilteringDisabled,
56
58
  } = Repository.getSchema().model,
59
+ id = useId(),
57
60
 
58
61
  // determine the starting filters
59
62
  startingFilters = !_.isEmpty(customFilters) ? customFilters : // custom filters override component filters
@@ -103,7 +106,7 @@ export default function withFilters(WrappedComponent) {
103
106
  }
104
107
 
105
108
  const
106
- [filters, setFilters] = useState(formattedStartingFilters), // array of formatted filters
109
+ [filters, setFiltersRaw] = useState(formattedStartingFilters), // array of formatted filters
107
110
  [slots, setSlots] = useState(startingSlots), // array of field names user is currently filtering on; blank slots have a null entry in array
108
111
  [modalFilters, setModalFilters] = useState([]),
109
112
  [modalSlots, setModalSlots] = useState([]),
@@ -144,6 +147,29 @@ export default function withFilters(WrappedComponent) {
144
147
  setModalFilters(newFilters);
145
148
  setModalSlots(newSlots);
146
149
  },
150
+ setFilters = (filters, doSetSlots = true, save = true) => {
151
+ setFiltersRaw(filters);
152
+
153
+ if (doSetSlots) {
154
+ const newSlots = [];
155
+ _.each(filters, (filter, ix) => {
156
+ if (searchAllText && ix === 0) {
157
+ return; // skip
158
+ }
159
+ newSlots.push(filter.field);
160
+ });
161
+ if (newSlots.length < minFilters) {
162
+ // Add more slots until we get to minFilters
163
+ for(let i = newSlots.length; i < minFilters; i++) {
164
+ newSlots.push(null);
165
+ }
166
+ }
167
+ setSlots(newSlots);
168
+ }
169
+ if (save && setSaved) {
170
+ setSaved(id + '-filters', filters);
171
+ }
172
+ },
147
173
  onFilterChangeValue = (field, value) => {
148
174
  // handler for when a filter value changes
149
175
  const newFilters = [];
@@ -153,7 +179,7 @@ export default function withFilters(WrappedComponent) {
153
179
  }
154
180
  newFilters.push(filter);
155
181
  });
156
- setFilters(newFilters);
182
+ setFilters(newFilters, false);
157
183
  },
158
184
  onClearFilters = () => {
159
185
  // Clears values for all active filters
@@ -162,7 +188,7 @@ export default function withFilters(WrappedComponent) {
162
188
  filter.value = null;
163
189
  newFilters.push(filter);
164
190
  });
165
- setFilters(newFilters);
191
+ setFilters(newFilters, false);
166
192
  },
167
193
  getFilterByField = (field) => {
168
194
  return _.find(filters, (filter) => {
@@ -240,61 +266,73 @@ export default function withFilters(WrappedComponent) {
240
266
  };
241
267
 
242
268
  useEffect(() => {
243
- // Whenever the filters change in some way, make repository conform to these new filters
244
- const newRepoFilters = [];
269
+ (async () => {
270
+
271
+ // Whenever the filters change in some way, make repository conform to these new filters
272
+ const newRepoFilters = [];
273
+ let filtersToUse = filters
274
+
275
+ if (!isReady && getSaved) {
276
+ const savedFilters = await getSaved(id + '-filters');
277
+ if (!_.isEmpty(savedFilters)) {
278
+ // load saved filters
279
+ filtersToUse = savedFilters;
280
+ setFilters(savedFilters, true, false); // false to skip save
281
+ }
282
+ }
245
283
 
246
- if (isUsingCustomFilters) {
247
- _.each(filters, (filter) => {
248
- const repoFiltersFromFilter = filter.getRepoFilters(value);
249
- _.each(repoFiltersFromFilter, (repoFilter) => { // one custom filter might generate multiple filters for the repository
250
- newRepoFilters.push(repoFilter);
284
+ if (isUsingCustomFilters) {
285
+ _.each(filtersToUse, (filter) => {
286
+ const repoFiltersFromFilter = filter.getRepoFilters(value);
287
+ _.each(repoFiltersFromFilter, (repoFilter) => { // one custom filter might generate multiple filters for the repository
288
+ newRepoFilters.push(repoFilter);
289
+ });
251
290
  });
252
- });
253
- } else {
254
- const newFilterNames = [];
255
- _.each(filters, (filter) => {
256
- const {
257
- field,
258
- value,
259
- } = filter,
260
- isFilterRange = getIsFilterRange(field);
261
- if (isFilterRange) {
262
- if (!!value) {
263
- const
264
- highField = field + ' <=',
265
- lowField = field + ' >=',
266
- highValue = value.high,
267
- lowValue = value.low;
268
- newFilterNames.push(highField);
269
- newFilterNames.push(lowField);
270
- newRepoFilters.push({ name: highField, value: highValue, });
271
- newRepoFilters.push({ name: lowField, value: lowValue, });
291
+ } else {
292
+ const newFilterNames = [];
293
+ _.each(filtersToUse, (filter) => {
294
+ const {
295
+ field,
296
+ value,
297
+ } = filter,
298
+ isFilterRange = getIsFilterRange(field);
299
+ if (isFilterRange) {
300
+ if (!!value) {
301
+ const
302
+ highField = field + ' <=',
303
+ lowField = field + ' >=',
304
+ highValue = value.high,
305
+ lowValue = value.low;
306
+ newFilterNames.push(highField);
307
+ newFilterNames.push(lowField);
308
+ newRepoFilters.push({ name: highField, value: highValue, });
309
+ newRepoFilters.push({ name: lowField, value: lowValue, });
310
+ }
311
+ } else {
312
+ newFilterNames.push(field);
313
+ newRepoFilters.push({ name: field, value, });
272
314
  }
273
- } else {
274
- newFilterNames.push(field);
275
- newRepoFilters.push({ name: field, value, });
276
- }
277
- });
315
+ });
278
316
 
279
- // Go through previousFilterNames and see if any are no longer used.
280
- _.each(previousFilterNames, (name) => {
281
- if (!inArray(name, newFilterNames)) {
282
- newRepoFilters.push({ name, value: null, }); // no longer used, so set it to null so it'll be deleted
283
- }
284
- });
285
- setPreviousFilterNames(newFilterNames);
286
- }
317
+ // Go through previousFilterNames and see if any are no longer used.
318
+ _.each(previousFilterNames, (name) => {
319
+ if (!inArray(name, newFilterNames)) {
320
+ newRepoFilters.push({ name, value: null, }); // no longer used, so set it to null so it'll be deleted
321
+ }
322
+ });
323
+ setPreviousFilterNames(newFilterNames);
324
+ }
287
325
 
288
- Repository.filter(newRepoFilters, null, false); // false so other filters remain
326
+ Repository.filter(newRepoFilters, null, false); // false so other filters remain
289
327
 
290
- if (searchAllText && Repository.searchAncillary && !Repository.hasBaseParam('searchAncillary')) {
291
- Repository.setBaseParam('searchAncillary', true);
292
- }
293
-
294
- if (!isReady) {
295
- setIsReady(true);
296
- }
328
+ if (searchAllText && Repository.searchAncillary && !Repository.hasBaseParam('searchAncillary')) {
329
+ Repository.setBaseParam('searchAncillary', true);
330
+ }
297
331
 
332
+ if (!isReady) {
333
+ setIsReady(true);
334
+ }
335
+ })();
298
336
  }, [filters]);
299
337
 
300
338
  if (!isReady) {
@@ -324,6 +362,8 @@ export default function withFilters(WrappedComponent) {
324
362
  }}
325
363
  ml={1}
326
364
  onPress={() => {
365
+ const f = filters;
366
+ const s = slots;
327
367
  setModalFilters(filters);
328
368
  setModalSlots(slots);
329
369
  setIsFilterSelectorShown(true);
@@ -420,9 +460,7 @@ export default function withFilters(WrappedComponent) {
420
460
  onSave={(data, e) => {
421
461
  // Conform filters to this new choice of filters
422
462
 
423
- const
424
- newFilters = [],
425
- newSlots = [];
463
+ const newFilters = [];
426
464
 
427
465
  if (searchAllText) {
428
466
  newFilters.push(filters[0]);
@@ -435,20 +473,10 @@ export default function withFilters(WrappedComponent) {
435
473
  }
436
474
 
437
475
  const newFilter = getFormattedFilter(field);
438
-
439
476
  newFilters.push(newFilter);
440
- newSlots.push(field);
441
477
  });
442
478
 
443
- if (newSlots.length < minFilters) {
444
- // Add more slots until we get to minFilters
445
- for(let i = newSlots.length; i < minFilters; i++) {
446
- newSlots.push(null);
447
- }
448
- }
449
-
450
479
  setFilters(newFilters);
451
- setSlots(newSlots);
452
480
 
453
481
  // Close the modal
454
482
  setIsFilterSelectorShown(false);
@@ -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,25 @@ export default function TabPanel(props) {
40
45
  if (onChangeIsCollapsed) {
41
46
  onChangeIsCollapsed(isCollapsed);
42
47
  }
48
+ if (setSaved) {
49
+ setSaved(id + '-isCollapsed', isCollapsed);
50
+ }
43
51
  },
44
52
  setCurrentTab = (ix) => {
45
53
  setCurrentTabRaw(ix);
46
54
  if (onChangeCurrentTab) {
47
55
  onChangeCurrentTab(ix);
48
56
  }
57
+ if (setSaved && saveCurrentTab) {
58
+ setSaved(id + '-currentTab', ix);
59
+ }
49
60
  },
50
61
  getButtonProps = () => {
51
62
  const
52
63
  iconProps = {
53
64
  size: 'md',
54
65
  },
55
- textProps = {
56
- ml: '-8px',
57
- mr: '8px',
58
- },
66
+ textProps = {},
59
67
  buttonProps = {
60
68
  bg: styles.TAB_BG,
61
69
  color: styles.TAB_COLOR,
@@ -279,54 +287,81 @@ export default function TabPanel(props) {
279
287
  onToggleCollapse = () => {
280
288
  setIsCollapsed(!isCollapsed);
281
289
  };
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
290
 
309
- // HORIZONTAL
310
- return <Panel flex={1} w="100%" {...propsToPass} {...props._panel}>
311
- <Column flex={1} w="100%">
312
- <Row
291
+ useEffect(() => {
292
+ // Restore saved settings
293
+ (async () => {
294
+ let key, val;
295
+ key = id + '-isCollapsed';
296
+ val = await getSaved(key);
297
+ if (!_.isNil(val)) {
298
+ setIsCollapsed(val);
299
+ }
300
+
301
+ key = id + '-currentTab';
302
+ val = await getSaved(key);
303
+ if (!_.isNil(val)) {
304
+ setCurrentTab(val);
305
+ }
306
+
307
+ if (!isReady) {
308
+ setIsReady(true);
309
+ }
310
+ })();
311
+ }, []);
312
+
313
+ if (!isReady) {
314
+ return null;
315
+ }
316
+
317
+ if (direction === VERTICAL) {
318
+ return <Panel {...propsToPass}>
319
+ <Row flex={1} w="100%">
320
+ <Column
313
321
  alignItems="center"
314
322
  justifyContent="flex-start"
315
- p={2}
316
- pb={0}
323
+ py={2}
324
+ pl={isCollapsed ? 1 : 4}
317
325
  bg={styles.TAB_BAR_BG}
318
- h={isCollapsed ? '30px' : tabHeight}
326
+ w={isCollapsed ? '50px' : tabWidth}
319
327
  >
320
328
  {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}>
329
+ <Column flex={1} w="100%" justifyContent="flex-end">
330
+ {renderToggleButton()}
331
+ </Column>
332
+ </Column>
333
+ <Column
334
+ alignItems="center"
335
+ justifyContent="flex-start"
336
+ flex={1}
337
+ >
328
338
  {renderCurrentTabContent()}
329
- </Row>
330
- </Column>
339
+ </Column>
340
+ </Row>
331
341
  </Panel>;
342
+ }
343
+
344
+ // HORIZONTAL
345
+ return <Panel flex={1} w="100%" {...propsToPass} {...props._panel}>
346
+ <Column flex={1} w="100%">
347
+ <Row
348
+ alignItems="center"
349
+ justifyContent="flex-start"
350
+ p={2}
351
+ pb={0}
352
+ bg={styles.TAB_BAR_BG}
353
+ h={isCollapsed ? '30px' : tabHeight}
354
+ >
355
+ {renderTabs()}
356
+ <Row flex={1} h="100%" justifyContent="flex-end">
357
+ <Row h="100%">
358
+ {renderToggleButton()}
359
+ </Row>
360
+ </Row>
361
+ </Row>
362
+ <Row flex={1}>
363
+ {renderCurrentTabContent()}
364
+ </Row>
365
+ </Column>
366
+ </Panel>;
332
367
  }
@@ -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}
@@ -246,5 +311,7 @@ export default function DataMgt(props) {
246
311
  east={east}
247
312
  isEastCollapsed={isEastCollapsed}
248
313
  setIsEastCollapsed={setIsEastCollapsed}
314
+ getSaved={getSaved}
315
+ setSaved={setSaved}
249
316
  />;
250
317
  }
@@ -61,6 +61,7 @@ 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_DISABLED: 'trueGray.200',
64
65
  PANEL_FOOTER_BG: 'primary.100', // :alpha.50
65
66
  PANEL_HEADER_BG: 'primary.100',
66
67
  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,56 @@
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
7
+ Repo = oneHatData.getRepository(UiGlobals.uiSavesRepo),
8
+ entity = Repo?.getById(key);
9
+
10
+ if (!entity) {
11
+ return null;
12
+ }
13
+
14
+ let isOneBuild = false,
15
+ isJson = false,
16
+ model = null;
17
+ if (!_.isNil(value) && typeof value !== 'string') {
18
+ if (_.isArray(value)) {
19
+ const objects = value;
20
+ if (objects[0]?.getDataForNewEntity) {
21
+ model = objects[0].repository.name;
22
+ isOneBuild = true;
23
+ }
24
+ const rawValues = [];
25
+ _.each(objects, (obj) => {
26
+ rawValues.push(isOneBuild ? obj.getDataForNewEntity() : obj);
27
+ });
28
+ value = JSON.stringify(rawValues);
29
+ } else {
30
+ if (value.getDataForNewEntity) {
31
+ model = value.repository.name;
32
+ value = value.getDataForNewEntity();
33
+ isOneBuild = true;
34
+ }
35
+ value = JSON.stringify(value);
36
+ }
37
+ isJson = true;
38
+ }
39
+ if (entity) {
40
+ entity.setValues({
41
+ value,
42
+ isOneBuild,
43
+ isJson,
44
+ model,
45
+ });
46
+ await Repo.save(entity);
47
+ } else {
48
+ await Repo.add({
49
+ key,
50
+ value,
51
+ isOneBuild,
52
+ isJson,
53
+ model,
54
+ });
55
+ }
56
+ }
@@ -0,0 +1,5 @@
1
+ import UiGlobals from '../UiGlobals.js';
2
+
3
+ export default function setUiSavesRepo(name) {
4
+ UiGlobals.uiSavesRepo = name;
5
+ }