@onehat/ui 0.2.74 → 0.2.76

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.74",
3
+ "version": "0.2.76",
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
@@ -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,8 +1,6 @@
1
- import { useEffect, useState, } from 'react';
1
+ import { useEffect, useState, useRef, } from 'react';
2
2
  import {
3
- Column,
4
- Modal,
5
- Text,
3
+ Button,
6
4
  } from 'native-base';
7
5
  import {
8
6
  EDITOR_MODE__VIEW,
@@ -11,7 +9,7 @@ import {
11
9
  } from '../../Constants/Editor.js';
12
10
  import _ from 'lodash';
13
11
 
14
- export default function withEditor(WrappedComponent) {
12
+ export default function withEditor(WrappedComponent, isTree = false) {
15
13
  return (props) => {
16
14
 
17
15
  let [editorMode, setEditorMode] = useState(EDITOR_MODE__VIEW); // Can change below, so use 'let'
@@ -24,7 +22,6 @@ export default function withEditor(WrappedComponent) {
24
22
  disableDelete = false,
25
23
  disableDuplicate = false,
26
24
  disableView = false,
27
- isTree = false,
28
25
  getRecordIdentifier = (selection) => {
29
26
  if (selection.length > 1) {
30
27
  return 'records?';
@@ -45,13 +42,23 @@ export default function withEditor(WrappedComponent) {
45
42
  setSelection,
46
43
 
47
44
  // withAlert
45
+ alert,
48
46
  confirm,
47
+ hideAlert,
49
48
  } = props,
49
+ listeners = useRef({}),
50
50
  [currentRecord, setCurrentRecord] = useState(null),
51
51
  [isEditorShown, setIsEditorShown] = useState(false),
52
52
  [isEditorViewOnly, setIsEditorViewOnly] = useState(false),
53
53
  [isModalShown, setIsModalShown] = useState(false),
54
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
+ },
55
62
  onAdd = async () => {
56
63
  const defaultValues = Repository.getSchema().model.defaultValues;
57
64
  let addValues = _.clone(defaultValues);
@@ -60,42 +67,84 @@ export default function withEditor(WrappedComponent) {
60
67
  addValues[selectorId] = selectorSelected.id;
61
68
  }
62
69
 
70
+ if (getListeners().onBeforeAdd) {
71
+ const listenerResult = await getListeners().onBeforeAdd();
72
+ if (listenerResult === false) {
73
+ return;
74
+ }
75
+ }
76
+
63
77
  if (isTree) {
64
78
  if (!selection[0]) {
65
79
  throw Error('Must select a parent node.');
66
80
  }
67
81
  addValues.parentId = selection[0].id;
68
- }
69
-
70
- // 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
71
- const currentSorter = Repository.sorters[0];
72
- if (currentSorter.name !== Repository.schema.model.idProperty || currentSorter.direction !== 'DESC') {
73
- Repository.pauseEvents();
74
- Repository.sort(Repository.schema.model.idProperty, 'DESC');
75
- Repository.setPage(1);
76
- Repository.resumeEvents();
77
- await Repository.reload();
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
+ }
78
92
  }
79
93
 
80
94
  // Unmap the values, so we can input true originalData
81
95
  addValues = Repository.unmapData(addValues);
82
96
 
83
- const entity = await Repository.add(addValues, false, true, true);
97
+ const entity = await Repository.add(addValues, false, true);
84
98
  setSelection([entity]);
85
99
  setIsEditorViewOnly(false);
86
100
  setEditorMode(EDITOR_MODE__ADD);
87
101
  setIsEditorShown(true);
102
+
103
+ if (getListeners().onAfterAdd) {
104
+ await getListeners().onAfterAdd(entity);
105
+ }
88
106
  },
89
- onEdit = () => {
107
+ onEdit = async () => {
108
+ if (getListeners().onBeforeEdit) {
109
+ const listenerResult = await getListeners().onBeforeEdit();
110
+ if (listenerResult === false) {
111
+ return;
112
+ }
113
+ }
90
114
  setIsEditorViewOnly(false);
91
115
  setEditorMode(EDITOR_MODE__EDIT);
92
116
  setIsEditorShown(true);
93
117
  },
94
- onDelete = () => {
118
+ onDelete = async () => {
119
+ if (getListeners().onBeforeDelete) {
120
+ const listenerResult = await getListeners().onBeforeDelete();
121
+ if (listenerResult === false) {
122
+ return;
123
+ }
124
+ }
95
125
  const
96
126
  isSingle = selection.length === 1,
97
- isPhantom = selection[0] && selection[0].isPhantom;
127
+ firstSelection = selection[0],
128
+ isTree = firstSelection?.isTree,
129
+ hasChildren = firstSelection?.hasChildren,
130
+ isPhantom = firstSelection?.isPhantom;
98
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
99
148
  if (isSingle && isPhantom) {
100
149
  deleteRecord();
101
150
  } else {
@@ -103,13 +152,28 @@ export default function withEditor(WrappedComponent) {
103
152
  confirm('Are you sure you want to delete the ' + identifier, deleteRecord);
104
153
  }
105
154
  },
106
- 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
+
107
168
  await Repository.delete(selection);
108
169
  if (!Repository.isAutoSave) {
109
170
  await Repository.save();
110
171
  }
172
+ if (getListeners().onAfterDelete) {
173
+ await getListeners().onAfterDelete(selection);
174
+ }
111
175
  },
112
- viewRecord = () => {
176
+ viewRecord = async () => {
113
177
  if (!userCanView) {
114
178
  return;
115
179
  }
@@ -119,6 +183,10 @@ export default function withEditor(WrappedComponent) {
119
183
  setIsEditorViewOnly(true);
120
184
  setEditorMode(EDITOR_MODE__VIEW);
121
185
  setIsEditorShown(true);
186
+
187
+ if (getListeners().onAfterDelete) {
188
+ await getListeners().onAfterDelete(entity);
189
+ }
122
190
  },
123
191
  duplicateRecord = async () => {
124
192
  if (!userCanEdit || disableDuplicate) {
@@ -157,8 +225,17 @@ export default function withEditor(WrappedComponent) {
157
225
  }
158
226
  });
159
227
  }
228
+
229
+ if (getListeners().onBeforeEditSave) {
230
+ await getListeners().onBeforeEditSave(what);
231
+ }
232
+
160
233
  await Repository.save();
161
234
  setIsEditorShown(false);
235
+
236
+ if (getListeners().onAfterEdit) {
237
+ await getListeners().onAfterEdit(what);
238
+ }
162
239
  },
163
240
  onEditorCancel = async () => {
164
241
  const
@@ -174,9 +251,17 @@ export default function withEditor(WrappedComponent) {
174
251
  setIsEditorShown(false);
175
252
  },
176
253
  onEditorDelete = async () => {
254
+ if (getListeners().onBeforeDeleteSave) {
255
+ await getListeners().onBeforeDeleteSave(selection);
256
+ }
257
+
177
258
  await deleteRecord();
178
259
  setEditorMode(EDITOR_MODE__VIEW);
179
260
  setIsEditorShown(false);
261
+
262
+ if (getListeners().onAfterDelete) {
263
+ await getListeners().onAfterDelete(selection);
264
+ }
180
265
  },
181
266
  calculateEditorMode = () => {
182
267
  let mode = EDITOR_MODE__VIEW;
@@ -209,42 +294,34 @@ export default function withEditor(WrappedComponent) {
209
294
  editorMode = calculateEditorMode();
210
295
  }
211
296
 
212
- return <>
213
- <WrappedComponent
214
- {...props}
215
- currentRecord={currentRecord}
216
- setCurrentRecord={setCurrentRecord}
217
- isEditorShown={isEditorShown}
218
- isEditorViewOnly={isEditorViewOnly}
219
- editorMode={editorMode}
220
- setEditorMode={setEditorMode}
221
- setIsEditorShown={setIsEditorShown}
222
- onAdd={(!userCanEdit || disableAdd) ? null : onAdd}
223
- onEdit={(!userCanEdit || disableEdit) ? null : onEdit}
224
- onDelete={(!userCanEdit || disableDelete || (editorMode === EDITOR_MODE__ADD && (selection[0]?.isPhantom || currentRecord?.isPhantom))) ? null : onDelete}
225
- onView={viewRecord}
226
- onDuplicate={duplicateRecord}
227
- onEditorSave={onEditorSave}
228
- onEditorCancel={onEditorCancel}
229
- onEditorDelete={(!userCanEdit || disableDelete || (editorMode === EDITOR_MODE__ADD && (selection[0]?.isPhantom || currentRecord?.isPhantom))) ? null : onEditorDelete}
230
- onEditorClose={onEditorClose}
231
- isEditor={true}
232
- useEditor={useEditor}
233
- userCanEdit={userCanEdit}
234
- userCanView={userCanView}
235
- disableAdd={disableAdd}
236
- disableEdit={disableEdit}
237
- disableDelete={disableDelete}
238
- disableDuplicate={disableDuplicate}
239
- disableView ={disableView}
240
- />
241
- {isTree && isModalShown &&
242
- <Modal
243
- isOpen={true}
244
- onClose={() => setIsModalShown(false)}
245
- >
246
-
247
- </Modal>}
248
- </>;
297
+ return <WrappedComponent
298
+ {...props}
299
+ currentRecord={currentRecord}
300
+ setCurrentRecord={setCurrentRecord}
301
+ isEditorShown={isEditorShown}
302
+ isEditorViewOnly={isEditorViewOnly}
303
+ editorMode={editorMode}
304
+ setEditorMode={setEditorMode}
305
+ setIsEditorShown={setIsEditorShown}
306
+ onAdd={(!userCanEdit || disableAdd) ? null : onAdd}
307
+ onEdit={(!userCanEdit || disableEdit) ? null : onEdit}
308
+ onDelete={(!userCanEdit || disableDelete || (editorMode === EDITOR_MODE__ADD && (selection[0]?.isPhantom || currentRecord?.isPhantom))) ? null : onDelete}
309
+ onView={viewRecord}
310
+ onDuplicate={duplicateRecord}
311
+ onEditorSave={onEditorSave}
312
+ onEditorCancel={onEditorCancel}
313
+ onEditorDelete={(!userCanEdit || disableDelete || (editorMode === EDITOR_MODE__ADD && (selection[0]?.isPhantom || currentRecord?.isPhantom))) ? null : onEditorDelete}
314
+ onEditorClose={onEditorClose}
315
+ setWithEditListeners={setListeners}
316
+ isEditor={true}
317
+ useEditor={useEditor}
318
+ userCanEdit={userCanEdit}
319
+ userCanView={userCanView}
320
+ disableAdd={disableAdd}
321
+ disableEdit={disableEdit}
322
+ disableDelete={disableDelete}
323
+ disableDuplicate={disableDuplicate}
324
+ disableView ={disableView}
325
+ />;
249
326
  };
250
327
  }
@@ -19,7 +19,7 @@ export default function withSideEditor(WrappedComponent, isTree = false) {
19
19
  }
20
20
 
21
21
  return <Container
22
- center={<WrappedComponent isTree={isTree} {...props} />}
22
+ center={<WrappedComponent {...props} />}
23
23
  east={<Editor
24
24
  editorType={EDITOR_TYPE__SIDE}
25
25
  flex={sideFlex}
@@ -27,5 +27,5 @@ export default function withSideEditor(WrappedComponent, isTree = false) {
27
27
  {...editorProps}
28
28
  />}
29
29
  />;
30
- });
30
+ }, isTree);
31
31
  }
@@ -40,7 +40,7 @@ export default function withWindowedEditor(WrappedComponent, isTree = false) {
40
40
  }
41
41
 
42
42
  return <>
43
- <WrappedComponent isTree={isTree} {...props} />
43
+ <WrappedComponent {...props} />
44
44
  {useEditor && isEditorShown &&
45
45
  <Modal
46
46
  isOpen={true}
@@ -53,5 +53,5 @@ export default function withWindowedEditor(WrappedComponent, isTree = false) {
53
53
  />
54
54
  </Modal>}
55
55
  </>;
56
- });
56
+ }, isTree);
57
57
  }