@onehat/ui 0.2.73 → 0.2.75

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.73",
3
+ "version": "0.2.75",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -6,6 +6,7 @@ import {
6
6
  Tooltip,
7
7
  } from 'native-base';
8
8
  import styles from '../../Constants/Styles.js';
9
+ import _ from 'lodash';
9
10
 
10
11
  const IconButton = React.forwardRef((props, ref) => {
11
12
  const {
@@ -17,12 +18,16 @@ const IconButton = React.forwardRef((props, ref) => {
17
18
  tooltipPlacement = 'bottom',
18
19
  } = props;
19
20
  const propsIcon = props._icon || {};
20
- let icon = props.icon || <Icon {...propsIcon} />,
21
+ let icon = props.icon,
21
22
  ret;
22
23
  if (isLoading) {
23
24
  icon = <Spinner {..._spinner} />;
24
25
  }
25
- if (!React.isValidElement(icon)) {
26
+ if (React.isValidElement(icon)) {
27
+ if (!_.isEmpty(propsIcon)) {
28
+ icon = React.cloneElement(icon, {...propsIcon});
29
+ }
30
+ } else {
26
31
  icon = <Icon as={icon} {...propsIcon} />;
27
32
  }
28
33
  const pressable = <Pressable
@@ -35,6 +35,7 @@ import withMultiSelection from '../Hoc/withMultiSelection.js';
35
35
  import withSelection from '../Hoc/withSelection.js';
36
36
  import withWindowedEditor from '../Hoc/withWindowedEditor.js';
37
37
  import withInlineEditor from '../Hoc/withInlineEditor.js';
38
+ import getIconButtonFromConfig from '../../Functions/getIconButtonFromConfig.js';
38
39
  import testProps from '../../Functions/testProps.js';
39
40
  import nbToRgb from '../../Functions/nbToRgb.js';
40
41
  import GridHeaderRow from './GridHeaderRow.js';
@@ -52,7 +53,7 @@ import _ from 'lodash';
52
53
  // The default export is *with* the HOC. A separate *raw* component is
53
54
  // exported which can be combined with many HOCs for various functionality.
54
55
 
55
- export function GridComponent(props) {
56
+ function GridComponent(props) {
56
57
  const {
57
58
 
58
59
  columnsConfig = [], // json configurations for each column
@@ -217,42 +218,8 @@ export function GridComponent(props) {
217
218
  }
218
219
  },
219
220
  getFooterToolbarItems = () => {
220
- const
221
- iconButtonProps = {
222
- _hover: {
223
- bg: 'trueGray.400',
224
- },
225
- mx: 1,
226
- px: 3,
227
- },
228
- iconProps = {
229
- alignSelf: 'center',
230
- size: styles.GRID_TOOLBAR_ITEMS_ICON_SIZE,
231
- h: 20,
232
- w: 20,
233
- },
234
- items = _.map(additionalToolbarButtons, (config, ix) => {
235
- let {
236
- text,
237
- handler,
238
- icon = null,
239
- isDisabled = false,
240
- } = config;
241
- if (icon) {
242
- const thisIconProps = {
243
- color: isDisabled ? styles.GRID_TOOLBAR_ITEMS_DISABLED_COLOR : styles.GRID_TOOLBAR_ITEMS_COLOR,
244
- };
245
- icon = React.cloneElement(icon, {...iconProps, ...thisIconProps});
246
- }
247
- return <IconButton
248
- key={ix}
249
- {...iconButtonProps}
250
- onPress={handler}
251
- icon={icon}
252
- isDisabled={isDisabled}
253
- tooltip={text}
254
- />;
255
- });
221
+ const items = _.map(additionalToolbarButtons, getIconButtonFromConfig);
222
+
256
223
  if (canRowsReorder) {
257
224
  items.unshift(<IconButton
258
225
  key="reorderBtn"
@@ -851,21 +818,19 @@ export function GridComponent(props) {
851
818
 
852
819
  }
853
820
 
854
- const Grid = withAlert(
821
+ export const Grid = withAlert(
855
822
  withEvents(
856
823
  withData(
857
824
  withMultiSelection(
858
825
  withSelection(
859
- // withSideEditor(
860
- withFilters(
861
- withPresetButtons(
862
- withContextMenu(
863
- GridComponent
864
- ),
865
- true // isGrid
866
- )
826
+ withFilters(
827
+ withPresetButtons(
828
+ withContextMenu(
829
+ GridComponent
830
+ ),
831
+ true // isGrid
867
832
  )
868
- // )
833
+ )
869
834
  )
870
835
  )
871
836
  )
@@ -920,13 +885,13 @@ export const InlineGridEditor = withAlert(
920
885
  withMultiSelection(
921
886
  withSelection(
922
887
  withInlineEditor(
923
- withPresetButtons(
924
- withContextMenu(
925
- withFilters(
888
+ withFilters(
889
+ withPresetButtons(
890
+ withContextMenu(
926
891
  GridComponent
927
- ),
928
- true // isGrid
929
- )
892
+ )
893
+ ),
894
+ true // isGrid
930
895
  )
931
896
  )
932
897
  )
@@ -1,5 +1,6 @@
1
1
  import React, { useState, useRef, useEffect, } from 'react';
2
2
  import {
3
+ AlertDialog,
3
4
  Button,
4
5
  Column,
5
6
  Icon,
@@ -30,26 +31,29 @@ export default function withAlert(WrappedComponent) {
30
31
  [customButtons, setCustomButtons] = useState(),
31
32
  [mode, setMode] = useState(ALERT_MODE_OK),
32
33
  autoFocusRef = useRef(null),
33
- onAlert = (arg1, callback, includeCancel = false) => {
34
+ cancelRef = useRef(null),
35
+ onAlert = (arg1, okCallback, includeCancel = false) => {
34
36
  clearAll();
35
37
  if (_.isString(arg1)) {
36
38
  setMode(ALERT_MODE_OK);
37
39
  setTitle('Alert');
38
40
  setMessage(arg1);
39
- setOkCallback(() => callback);
41
+ setOkCallback(() => okCallback);
42
+ setIncludeCancel(includeCancel);
40
43
  } else if (_.isPlainObject(arg1)) {
41
44
  // custom
42
45
  const {
43
46
  title = 'Alert',
44
47
  message,
45
48
  buttons,
49
+ includeCancel,
46
50
  } = arg1;
47
51
  setMode(ALERT_MODE_CUSTOM);
48
52
  setTitle(title);
49
53
  setMessage(message);
50
54
  setCustomButtons(buttons);
55
+ setIncludeCancel(includeCancel);
51
56
  }
52
- setIncludeCancel(includeCancel);
53
57
  showAlert();
54
58
  },
55
59
  onConfirm = (message, callback, includeCancel = false) => {
@@ -65,31 +69,29 @@ export default function withAlert(WrappedComponent) {
65
69
  setIsAlertShown(false);
66
70
  },
67
71
  onOk = () => {
68
- const callback = okCallback;
69
- hideAlert();
70
- if (callback) {
71
- callback();
72
+ if (okCallback) {
73
+ okCallback();
72
74
  }
75
+ hideAlert();
73
76
  },
74
77
  onYes = () => {
75
- const callback = yesCallback;
76
- hideAlert();
77
- if (callback) {
78
- callback();
78
+ if (yesCallback) {
79
+ yesCallback();
79
80
  }
81
+ hideAlert();
80
82
  },
81
83
  onNo = () => {
82
- const callback = noCallback;
83
- hideAlert();
84
- if (callback) {
85
- callback();
84
+ if (noCallback) {
85
+ noCallback();
86
86
  }
87
+ hideAlert();
87
88
  },
88
89
  showAlert = () => {
89
90
  setIsAlertShown(true);
90
91
  },
91
92
  hideAlert = () => {
92
93
  setIsAlertShown(false);
94
+ clearAll();
93
95
  },
94
96
  clearAll = () => {
95
97
  setOkCallback();
@@ -104,7 +106,9 @@ export default function withAlert(WrappedComponent) {
104
106
  key="cancelBtn"
105
107
  onPress={onCancel}
106
108
  color="#fff"
107
- variant="ghost"
109
+ colorScheme="coolGray"
110
+ variant="ghost" // or unstyled
111
+ ref={cancelRef}
108
112
  >Cancel</Button>);
109
113
  }
110
114
  switch(mode) {
@@ -128,10 +132,13 @@ export default function withAlert(WrappedComponent) {
128
132
  ref={autoFocusRef}
129
133
  onPress={onYes}
130
134
  color="#fff"
135
+ colorScheme="danger"
131
136
  >Yes</Button>);
132
137
  break;
133
138
  case ALERT_MODE_CUSTOM:
134
- buttons = customButtons;
139
+ _.each(customButtons, (button) => {
140
+ buttons.push(button);
141
+ });
135
142
  break;
136
143
  default:
137
144
  }
@@ -141,36 +148,32 @@ export default function withAlert(WrappedComponent) {
141
148
  {...props}
142
149
  alert={onAlert}
143
150
  confirm={onConfirm}
151
+ hideAlert={hideAlert}
144
152
  />
145
- <Modal
153
+
154
+ <AlertDialog
155
+ leastDestructiveRef={cancelRef}
146
156
  isOpen={isAlertShown}
147
- onOpen={() => {debugger;}}
148
157
  onClose={() => setIsAlertShown(false)}
149
158
  >
150
- <Column bg="#fff" w={400} onLayout={() => {
151
- if (autoFocusRef.current) {
152
- autoFocusRef.current.focus();
153
- }
154
- }}>
155
- <Panel
156
- title={title}
157
- isCollapsible={false}
158
- p={0}
159
- footer={<Footer justifyContent="flex-end" >
160
- <Button.Group space={2}>
161
- {buttons}
162
- </Button.Group>
163
- </Footer>}
164
- >
165
- <Row flex={1} p={5}>
159
+ <AlertDialog.Content>
160
+ <AlertDialog.CloseButton />
161
+ <AlertDialog.Header>{title}</AlertDialog.Header>
162
+ <AlertDialog.Body>
163
+ <Row>
166
164
  <Column w="40px" p={0} mr={5}>
167
165
  <Icon as={TriangleExclamation} size={10} color="#f00" />
168
166
  </Column>
169
- <Text>{message}</Text>
167
+ <Text flex={1}>{message}</Text>
170
168
  </Row>
171
- </Panel>
172
- </Column>
173
- </Modal>
169
+ </AlertDialog.Body>
170
+ <AlertDialog.Footer>
171
+ <Button.Group space={2}>
172
+ {buttons}
173
+ </Button.Group>
174
+ </AlertDialog.Footer>
175
+ </AlertDialog.Content>
176
+ </AlertDialog>
174
177
  </>;
175
178
  };
176
179
  }
@@ -1,4 +1,7 @@
1
- import { useEffect, useState, } from 'react';
1
+ import { useEffect, useState, useRef, } from 'react';
2
+ import {
3
+ Button,
4
+ } from 'native-base';
2
5
  import {
3
6
  EDITOR_MODE__VIEW,
4
7
  EDITOR_MODE__ADD,
@@ -6,7 +9,7 @@ import {
6
9
  } from '../../Constants/Editor.js';
7
10
  import _ from 'lodash';
8
11
 
9
- export default function withEditor(WrappedComponent) {
12
+ export default function withEditor(WrappedComponent, isTree = false) {
10
13
  return (props) => {
11
14
 
12
15
  let [editorMode, setEditorMode] = useState(EDITOR_MODE__VIEW); // Can change below, so use 'let'
@@ -39,12 +42,23 @@ export default function withEditor(WrappedComponent) {
39
42
  setSelection,
40
43
 
41
44
  // withAlert
45
+ alert,
42
46
  confirm,
47
+ hideAlert,
43
48
  } = props,
49
+ listeners = useRef({}),
44
50
  [currentRecord, setCurrentRecord] = useState(null),
45
51
  [isEditorShown, setIsEditorShown] = useState(false),
46
52
  [isEditorViewOnly, setIsEditorViewOnly] = useState(false),
53
+ [isModalShown, setIsModalShown] = useState(false),
47
54
  [lastSelection, setLastSelection] = useState(),
55
+ getListeners = () => {
56
+ return listeners.current;
57
+ },
58
+ setListeners = (obj) => {
59
+ listeners.current = obj;
60
+ // forceUpdate(); // we don't want to get into an infinite loop of renders. Simply directly assign the listeners in every child render
61
+ },
48
62
  onAdd = async () => {
49
63
  const defaultValues = Repository.getSchema().model.defaultValues;
50
64
  let addValues = _.clone(defaultValues);
@@ -53,35 +67,84 @@ export default function withEditor(WrappedComponent) {
53
67
  addValues[selectorId] = selectorSelected.id;
54
68
  }
55
69
 
56
- // Set repository to sort by id DESC and switch to page 1, so this new entity is guaranteed to show up on the current page, even after saving
57
- const currentSorter = Repository.sorters[0];
58
- if (currentSorter.name !== Repository.schema.model.idProperty || currentSorter.direction !== 'DESC') {
59
- Repository.pauseEvents();
60
- Repository.sort(Repository.schema.model.idProperty, 'DESC');
61
- Repository.setPage(1);
62
- Repository.resumeEvents();
63
- await Repository.reload();
70
+ if (getListeners().onBeforeAdd) {
71
+ const listenerResult = await getListeners().onBeforeAdd();
72
+ if (listenerResult === false) {
73
+ return;
74
+ }
75
+ }
76
+
77
+ if (isTree) {
78
+ if (!selection[0]) {
79
+ throw Error('Must select a parent node.');
80
+ }
81
+ addValues.parentId = selection[0].id;
82
+ } else {
83
+ // Set repository to sort by id DESC and switch to page 1, so this new entity is guaranteed to show up on the current page, even after saving
84
+ const currentSorter = Repository.sorters[0];
85
+ if (currentSorter.name !== Repository.schema.model.idProperty || currentSorter.direction !== 'DESC') {
86
+ Repository.pauseEvents();
87
+ Repository.sort(Repository.schema.model.idProperty, 'DESC');
88
+ Repository.setPage(1);
89
+ Repository.resumeEvents();
90
+ await Repository.reload();
91
+ }
64
92
  }
65
93
 
66
94
  // Unmap the values, so we can input true originalData
67
95
  addValues = Repository.unmapData(addValues);
68
96
 
69
- const entity = await Repository.add(addValues, false, true, true);
97
+ const entity = await Repository.add(addValues, false, true);
70
98
  setSelection([entity]);
71
99
  setIsEditorViewOnly(false);
72
100
  setEditorMode(EDITOR_MODE__ADD);
73
101
  setIsEditorShown(true);
102
+
103
+ if (getListeners().onAfterAdd) {
104
+ await getListeners().onAfterAdd(entity);
105
+ }
74
106
  },
75
- onEdit = () => {
107
+ onEdit = async () => {
108
+ if (getListeners().onBeforeEdit) {
109
+ const listenerResult = await getListeners().onBeforeEdit();
110
+ if (listenerResult === false) {
111
+ return;
112
+ }
113
+ }
76
114
  setIsEditorViewOnly(false);
77
115
  setEditorMode(EDITOR_MODE__EDIT);
78
116
  setIsEditorShown(true);
79
117
  },
80
- onDelete = () => {
118
+ onDelete = async () => {
119
+ if (getListeners().onBeforeDelete) {
120
+ const listenerResult = await getListeners().onBeforeDelete();
121
+ if (listenerResult === false) {
122
+ return;
123
+ }
124
+ }
81
125
  const
82
126
  isSingle = selection.length === 1,
83
- isPhantom = selection[0] && selection[0].isPhantom;
127
+ firstSelection = selection[0],
128
+ isTree = firstSelection?.isTree,
129
+ hasChildren = firstSelection?.hasChildren,
130
+ isPhantom = firstSelection?.isPhantom;
84
131
 
132
+ if (isSingle && isTree && hasChildren) {
133
+ alert({
134
+ title: 'Move up children?',
135
+ message: 'The node you have selected for deletion has children. ' +
136
+ 'Should these children be moved up to this node\'s parent, or be deleted?',
137
+ buttons: [
138
+ <Button colorScheme="danger" onPress={onMoveChildren} key="moveBtn">
139
+ Move Children
140
+ </Button>,
141
+ <Button colorScheme="danger" onPress={onDeleteChildren} key="deleteBtn">
142
+ Delete Children
143
+ </Button>
144
+ ],
145
+ includeCancel: true,
146
+ });
147
+ } else
85
148
  if (isSingle && isPhantom) {
86
149
  deleteRecord();
87
150
  } else {
@@ -89,13 +152,28 @@ export default function withEditor(WrappedComponent) {
89
152
  confirm('Are you sure you want to delete the ' + identifier, deleteRecord);
90
153
  }
91
154
  },
92
- deleteRecord = async () => {
155
+ onMoveChildren = () => {
156
+ hideAlert();
157
+ deleteRecord(true);
158
+ },
159
+ onDeleteChildren = () => {
160
+ hideAlert();
161
+ deleteRecord();
162
+ },
163
+ deleteRecord = async (moveSubtreeUp) => {
164
+ if (getListeners().onBeforeDeleteSave) {
165
+ await getListeners().onBeforeDeleteSave(selection);
166
+ }
167
+
93
168
  await Repository.delete(selection);
94
169
  if (!Repository.isAutoSave) {
95
170
  await Repository.save();
96
171
  }
172
+ if (getListeners().onAfterDelete) {
173
+ await getListeners().onAfterDelete(selection);
174
+ }
97
175
  },
98
- viewRecord = () => {
176
+ viewRecord = async () => {
99
177
  if (!userCanView) {
100
178
  return;
101
179
  }
@@ -105,6 +183,10 @@ export default function withEditor(WrappedComponent) {
105
183
  setIsEditorViewOnly(true);
106
184
  setEditorMode(EDITOR_MODE__VIEW);
107
185
  setIsEditorShown(true);
186
+
187
+ if (getListeners().onAfterDelete) {
188
+ await getListeners().onAfterDelete(entity);
189
+ }
108
190
  },
109
191
  duplicateRecord = async () => {
110
192
  if (!userCanEdit || disableDuplicate) {
@@ -143,8 +225,17 @@ export default function withEditor(WrappedComponent) {
143
225
  }
144
226
  });
145
227
  }
228
+
229
+ if (getListeners().onBeforeEditSave) {
230
+ await getListeners().onBeforeEditSave(what);
231
+ }
232
+
146
233
  await Repository.save();
147
234
  setIsEditorShown(false);
235
+
236
+ if (getListeners().onAfterEdit) {
237
+ await getListeners().onAfterEdit(what);
238
+ }
148
239
  },
149
240
  onEditorCancel = async () => {
150
241
  const
@@ -160,9 +251,17 @@ export default function withEditor(WrappedComponent) {
160
251
  setIsEditorShown(false);
161
252
  },
162
253
  onEditorDelete = async () => {
254
+ if (getListeners().onBeforeDeleteSave) {
255
+ await getListeners().onBeforeDeleteSave(selection);
256
+ }
257
+
163
258
  await deleteRecord();
164
259
  setEditorMode(EDITOR_MODE__VIEW);
165
260
  setIsEditorShown(false);
261
+
262
+ if (getListeners().onAfterDelete) {
263
+ await getListeners().onAfterDelete(selection);
264
+ }
166
265
  },
167
266
  calculateEditorMode = () => {
168
267
  let mode = EDITOR_MODE__VIEW;
@@ -213,6 +312,7 @@ export default function withEditor(WrappedComponent) {
213
312
  onEditorCancel={onEditorCancel}
214
313
  onEditorDelete={(!userCanEdit || disableDelete || (editorMode === EDITOR_MODE__ADD && (selection[0]?.isPhantom || currentRecord?.isPhantom))) ? null : onEditorDelete}
215
314
  onEditorClose={onEditorClose}
315
+ setWithEditListeners={setListeners}
216
316
  isEditor={true}
217
317
  useEditor={useEditor}
218
318
  userCanEdit={userCanEdit}
@@ -34,6 +34,7 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
34
34
  {
35
35
  // for local use
36
36
  isEditor = false,
37
+ isTree = false,
37
38
  useEditor = true,
38
39
  disableAdd = !isEditor,
39
40
  disableEdit = !isEditor,
@@ -125,6 +126,9 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
125
126
  if (selectorId && !selectorSelected) {
126
127
  isDisabled = true;
127
128
  }
129
+ if (isTree && _.isEmpty(selection)) {
130
+ isDisabled = true;
131
+ }
128
132
  break;
129
133
  case 'edit':
130
134
  text = 'Edit';
@@ -6,7 +6,7 @@ import withEditor from './withEditor.js';
6
6
  import _ from 'lodash';
7
7
 
8
8
 
9
- export default function withSideEditor(WrappedComponent) {
9
+ export default function withSideEditor(WrappedComponent, isTree = false) {
10
10
  return withEditor((props) => {
11
11
  const {
12
12
  Editor,
@@ -19,7 +19,7 @@ export default function withSideEditor(WrappedComponent) {
19
19
  }
20
20
 
21
21
  return <Container
22
- center={<WrappedComponent {...props} />}
22
+ center={<WrappedComponent isTree={isTree} {...props} />}
23
23
  east={<Editor
24
24
  editorType={EDITOR_TYPE__SIDE}
25
25
  flex={sideFlex}
@@ -25,7 +25,7 @@ import _ from 'lodash';
25
25
  // then switch position to absolute, draggable area would be header of panel
26
26
  // const DraggableColumn = withAdditionalProps(withDraggable(Column));
27
27
 
28
- export default function withWindowedEditor(WrappedComponent) {
28
+ export default function withWindowedEditor(WrappedComponent, isTree = false) {
29
29
  return withEditor((props) => {
30
30
  const {
31
31
  useEditor = false,
@@ -40,7 +40,7 @@ export default function withWindowedEditor(WrappedComponent) {
40
40
  }
41
41
 
42
42
  return <>
43
- <WrappedComponent {...props} />
43
+ <WrappedComponent isTree={isTree} {...props} />
44
44
  {useEditor && isEditorShown &&
45
45
  <Modal
46
46
  isOpen={true}