@onehat/ui 0.4.97 → 0.4.99

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.4.97",
3
+ "version": "0.4.99",
4
4
  "description": "Base UI for OneHat apps",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,4 +1,4 @@
1
- import { cloneElement, useState, useEffect, useRef, } from 'react';
1
+ import { cloneElement, useState, useEffect, useRef, useCallback, } from 'react';
2
2
  import {
3
3
  BoxNative,
4
4
  HStack,
@@ -16,6 +16,7 @@ import {
16
16
  } from '../../Constants/UiModes.js';
17
17
  import withComponent from '../Hoc/withComponent.js';
18
18
  import useForceUpdate from '../../Hooks/useForceUpdate.js';
19
+ import getComponentFromType from '../../Functions/getComponentFromType.js';
19
20
  import getSaved from '../../Functions/getSaved.js';
20
21
  import setSaved from '../../Functions/setSaved.js';
21
22
  import Splitter from './Splitter.js';
@@ -106,6 +107,8 @@ function Container(props) {
106
107
  } = props,
107
108
  id = props.id || props.self?.path,
108
109
  isWeb = CURRENT_MODE === UI_MODE_WEB,
110
+ useWindowSize = getComponentFromType('useWindowSize'),
111
+ windowSize = useWindowSize(),
109
112
  forceUpdate = useForceUpdate(),
110
113
  centerRef = useRef(null),
111
114
  northRef = useRef(null),
@@ -122,6 +125,36 @@ function Container(props) {
122
125
  localSouthIsCollapsedRef = useRef(southInitialIsCollapsed),
123
126
  localEastIsCollapsedRef = useRef(eastInitialIsCollapsed),
124
127
  localWestIsCollapsedRef = useRef(westInitialIsCollapsed),
128
+ onLayout = async (e) => {
129
+ console.log('Container onLayout', e.nativeEvent.layout.width);
130
+ if (id) {
131
+ // save prevScreenSize if changed
132
+ const
133
+ height = parseFloat(e.nativeEvent.layout.height),
134
+ width = parseFloat(e.nativeEvent.layout.width),
135
+ key = id + '-prevScreenSize',
136
+ prevScreenSize = await getSaved(key);
137
+ if (!prevScreenSize || prevScreenSize.width !== width || prevScreenSize.height !== height) {
138
+ await setSaved(key, {
139
+ height,
140
+ width,
141
+ });
142
+
143
+ // reset all sizes to null, so they recalculate
144
+ setNorthHeight(null);
145
+ setSouthHeight(null);
146
+ setEastWidth(null);
147
+ setWestWidth(null);
148
+ forceUpdate();
149
+ }
150
+ }
151
+ },
152
+ debouncedOnLayout = useCallback(
153
+ _.debounce((e) => {
154
+ onLayout(e);
155
+ }, 2000), // delay is signficant, as all we're trying to do is catch screen size changes
156
+ []
157
+ ),
125
158
  setNorthIsCollapsed = (bool) => {
126
159
  if (setExternalNorthIsCollapsed) {
127
160
  setExternalNorthIsCollapsed(bool);
@@ -293,53 +326,72 @@ function Container(props) {
293
326
 
294
327
  if (id) {
295
328
  let key, val;
296
- key = id + '-northIsCollapsed';
297
- val = await getSaved(key);
298
- if (!_.isNil(val)) {
299
- setNorthIsCollapsed(val);
300
- }
301
329
 
302
- key = id + '-southIsCollapsed';
330
+ // does screensize from previous render exist?
331
+ key = id + '-prevScreenSize';
303
332
  val = await getSaved(key);
333
+ let prevScreenSize = null;
304
334
  if (!_.isNil(val)) {
305
- setSouthIsCollapsed(val);
335
+ prevScreenSize = val;
306
336
  }
337
+ const currentScreenSize = {
338
+ width: windowSize?.width ?? null,
339
+ height: windowSize?.height ?? null,
340
+ };
341
+ if (!prevScreenSize || (prevScreenSize.width === currentScreenSize.width && prevScreenSize.height === currentScreenSize.height)) {
342
+
343
+ // only load these saved settings if the screen size is the same as when they were saved
344
+ key = id + '-northIsCollapsed';
345
+ val = await getSaved(key);
346
+ if (!_.isNil(val)) {
347
+ setNorthIsCollapsed(val);
348
+ }
307
349
 
308
- key = id + '-eastIsCollapsed';
309
- val = await getSaved(key);
310
- if (!_.isNil(val)) {
311
- setEastIsCollapsed(val);
312
- }
350
+ key = id + '-southIsCollapsed';
351
+ val = await getSaved(key);
352
+ if (!_.isNil(val)) {
353
+ setSouthIsCollapsed(val);
354
+ }
313
355
 
314
- key = id + '-westIsCollapsed';
315
- val = await getSaved(key);
316
- if (!_.isNil(val)) {
317
- setWestIsCollapsed(val);
318
- }
356
+ key = id + '-eastIsCollapsed';
357
+ val = await getSaved(key);
358
+ if (!_.isNil(val)) {
359
+ setEastIsCollapsed(val);
360
+ }
319
361
 
320
- key = id + '-northHeight';
321
- val = await getSaved(key);
322
- if (!_.isNil(val)) {
323
- setNorthHeight(val);
324
- }
362
+ key = id + '-westIsCollapsed';
363
+ val = await getSaved(key);
364
+ if (!_.isNil(val)) {
365
+ setWestIsCollapsed(val);
366
+ }
325
367
 
326
- key = id + '-southHeight';
327
- val = await getSaved(key);
328
- if (!_.isNil(val)) {
329
- setSouthHeight(val);
330
- }
368
+ key = id + '-northHeight';
369
+ val = await getSaved(key);
370
+ if (!_.isNil(val)) {
371
+ setNorthHeight(val);
372
+ }
331
373
 
332
- key = id + '-eastWidth';
333
- val = await getSaved(key);
334
- if (!_.isNil(val)) {
335
- setEastWidth(val);
336
- }
374
+ key = id + '-southHeight';
375
+ val = await getSaved(key);
376
+ if (!_.isNil(val)) {
377
+ setSouthHeight(val);
378
+ }
337
379
 
338
- key = id + '-westWidth';
339
- val = await getSaved(key);
340
- if (!_.isNil(val)) {
341
- setWestWidth(val);
380
+ key = id + '-eastWidth';
381
+ val = await getSaved(key);
382
+ if (!_.isNil(val)) {
383
+ setEastWidth(val);
384
+ }
385
+
386
+ key = id + '-westWidth';
387
+ val = await getSaved(key);
388
+ if (!_.isNil(val)) {
389
+ setWestWidth(val);
390
+ }
391
+
342
392
  }
393
+
394
+
343
395
  }
344
396
 
345
397
  if (!isReady) {
@@ -366,6 +418,7 @@ function Container(props) {
366
418
 
367
419
  componentProps._panel.isCollapsible = false;
368
420
  componentProps._panel.isDisabled = isDisabled || isComponentsDisabled;
421
+ componentProps.onLayout = debouncedOnLayout;
369
422
  centerComponent = cloneElement(center, componentProps);
370
423
  if (north) {
371
424
  componentProps = { _panel: { ...north.props?._panel }, };
@@ -1398,6 +1398,7 @@ function Form(props) {
1398
1398
  onPress={() => doReset()}
1399
1399
  icon={Rotate}
1400
1400
  isDisabled={!formState.isDirty}
1401
+ tooltip="Reset Form"
1401
1402
  />}
1402
1403
 
1403
1404
  {showCancelBtn &&
@@ -208,6 +208,7 @@ function GridComponent(props) {
208
208
  onRowDrop,
209
209
  onDragStart,
210
210
  onDragEnd,
211
+ rowLongPressDelayMs,
211
212
 
212
213
  // withComponent
213
214
  self,
@@ -300,6 +301,7 @@ function GridComponent(props) {
300
301
  [localColumnsConfig, setLocalColumnsConfigRaw] = useState([]),
301
302
  [isReorderMode, setIsReorderMode] = useState(false),
302
303
  showRowHandle = showSelectHandle || areRowsDragSource || (canRowsReorder && isReorderMode),
304
+ rowLongPressDelay = rowLongPressDelayMs ?? ((areRowsDragSource || canRowsReorder) ? 800 : undefined),
303
305
  [lastMeasuredContainerHeight, setLastMeasuredContainerHeight] = useState(0),
304
306
  getMeasurementPhase = () => {
305
307
  return measurementPhaseRaw.current;
@@ -469,11 +471,12 @@ function GridComponent(props) {
469
471
  <Pressable
470
472
  dataSet={{ ix: index }}
471
473
  {...testProps(getRowTestId ? getRowTestId(row) : ((Repository ? Repository.schema.name : 'GridRow') + '-' + item?.id))}
474
+ delayLongPress={rowLongPressDelay}
472
475
  onPress={(e) => {
473
476
  if (e.preventDefault && e.cancelable) {
474
477
  e.preventDefault();
475
478
  }
476
- if (isHeaderRow || isReorderMode) {
479
+ if (isHeaderRow) {
477
480
  return
478
481
  }
479
482
  if (CURRENT_MODE === UI_MODE_WEB) {
@@ -485,6 +488,9 @@ function GridComponent(props) {
485
488
  }
486
489
  break;
487
490
  case DOUBLE_CLICK:
491
+ if (isReorderMode) {
492
+ return; // don't allow double-clicks while in reorder mode
493
+ }
488
494
  if (editorType === EDITOR_TYPE__SIDE) {
489
495
  // For side-editors, a double-click just acts like a single click
490
496
  if (!getIsEditorShown()) {
@@ -929,26 +935,58 @@ function GridComponent(props) {
929
935
  onRowReorderEnd = (item, monitor) => {
930
936
  const
931
937
  { ix: dropIx, useBottom, marker, rows, dragIx } = cachedDragElements.current,
932
- shouldMove = dropIx !== dragIx;
933
- if (shouldMove && dropIx !== -1) {
934
- // Update the row with the new ix
935
- let dragRecord,
936
- dropRecord;
938
+ selectionList = item?.getSelection ? item.getSelection() : (dragSelectionRef.current || selection || []);
939
+ if (dropIx === -1) {
940
+ marker.remove();
941
+ cachedDragElements.current = null;
942
+ return;
943
+ }
944
+
945
+ const
946
+ // normalize the current selection to ids
947
+ itemsList = Repository ? entities : data,
948
+ dragItem = Repository ? Repository.getByIx(dragIx) : itemsList?.[dragIx],
949
+ selectionIds = _.compact(_.map(selectionList, (selectedItem) => {
950
+ if (!selectedItem) {
951
+ return null;
952
+ }
953
+ if (_.isObject(selectedItem)) {
954
+ return Repository ? selectedItem.id : selectedItem[idIx];
955
+ }
956
+ return selectedItem;
957
+ })),
958
+ // decide if we're moving the selected items or just the dragged item
959
+ dragItemId = dragItem ? (Repository ? dragItem.id : dragItem[idIx]) : null,
960
+ useSelection = !!(dragItemId && selectionIds.includes(dragItemId)),
961
+ dragItems = useSelection ? selectionIds : (dragItemId ? [dragItemId] : []),
962
+ // get the indices of the selected items
963
+ selectedIndicesRaw = Repository
964
+ ? _.map(dragItems, (id) => itemsList?.findIndex((item) => item?.id === id))
965
+ : _.map(dragItems, (id) => itemsList?.findIndex((item) => item?.[idIx] === id)),
966
+ selectedIndices = _.uniq(_.filter(selectedIndicesRaw, (ix) => ix > -1)).sort((a, b) => a - b),
967
+ selectedIndicesSet = new Set(selectedIndices),
968
+ insertionIndexOriginal = useBottom ? dropIx + 1 : dropIx,
969
+ // get the drop record
970
+ dropRecord = Repository ? Repository.getByIx(dropIx) : itemsList?.[dropIx],
971
+ dropRecordId = dropRecord ? (Repository ? dropRecord.id : dropRecord[idIx]) : null,
972
+ // decide if we should move
973
+ shouldMove = !dropRecordId || !selectionIds.includes(dropRecordId);
974
+
975
+ if (shouldMove && selectedIndices.length) {
937
976
  if (Repository) {
938
977
  if (!Repository.isDestroyed) {
939
- dragRecord = Repository.getByIx(dragIx);
940
- dropRecord = Repository.getByIx(dropIx);
941
- if (dropRecord) {
942
- Repository.reorder(dragRecord, dropRecord, useBottom ? DROP_POSITION_AFTER : DROP_POSITION_BEFORE);
978
+ if (dropRecord && dragItems.length) {
979
+ Repository.reorder(dragItems, dropRecord, useBottom ? DROP_POSITION_AFTER : DROP_POSITION_BEFORE);
943
980
  }
944
981
  }
945
- } else {
946
- function arrayMove(arr, fromIndex, toIndex) {
947
- var element = arr[fromIndex];
948
- arr.splice(fromIndex, 1);
949
- arr.splice(toIndex, 0, element);
950
- }
951
- arrayMove(data, compensatedDragIx, finalDropIx);
982
+ } else if (itemsList && itemsList.length) {
983
+ const
984
+ removedBefore = _.filter(selectedIndices, (ix) => ix < insertionIndexOriginal).length,
985
+ insertionIndex = insertionIndexOriginal - removedBefore,
986
+ selectedItems = _.map(selectedIndices, (ix) => itemsList[ix]),
987
+ remainingItems = _.filter(itemsList, (entry, ix) => !selectedIndicesSet.has(ix));
988
+ remainingItems.splice(insertionIndex, 0, ...selectedItems);
989
+ itemsList.splice(0, itemsList.length, ...remainingItems);
952
990
  }
953
991
  }
954
992
 
@@ -339,6 +339,11 @@ export default function withPresetButtons(WrappedComponent, isGrid = false) {
339
339
  case DUPLICATE:
340
340
  key = 'duplicateBtn';
341
341
  text = 'Duplicate';
342
+ if (model) {
343
+ let inflected = Inflector.singularize(model); // can only add one at a time
344
+ inflected = Inflector.camel2words(Inflector.humanize(Inflector.underscore(inflected))); // Separate with spaces, capitalize each word
345
+ text += ' ' + inflected;
346
+ }
342
347
  handler = (parent, e) => {
343
348
  onDuplicate();
344
349
  };
@@ -110,6 +110,7 @@ function TreeComponent(props) {
110
110
  showHeaderToolbar = true,
111
111
  showHovers = true,
112
112
  showSelectHandle = true,
113
+ nodeLongPressDelayMs,
113
114
  isNodeSelectable = true,
114
115
  isNodeHoverable = true,
115
116
  allowToggleSelection = true, // i.e. single click with no shift key toggles the selection of the node clicked on
@@ -219,6 +220,7 @@ function TreeComponent(props) {
219
220
  [highlitedDatum, setHighlitedDatum] = useState(null),
220
221
  [treeSearchValue, setTreeSearchValue] = useState(''),
221
222
  showNodeHandle = showSelectHandle || areNodesDragSource,
223
+ nodeLongPressDelay = nodeLongPressDelayMs ?? ((areNodesDragSource || canNodesMoveInternally) ? 800 : undefined),
222
224
  // state getters & setters
223
225
  getTreeNodeData = () => {
224
226
  return treeNodeData.current;
@@ -1092,6 +1094,7 @@ function TreeComponent(props) {
1092
1094
  return <Pressable
1093
1095
  {...testProps((Repository ? Repository.schema.name : 'TreeNode') + '-' + item?.id)}
1094
1096
  key={item.hash}
1097
+ delayLongPress={nodeLongPressDelay}
1095
1098
  onPress={(e) => {
1096
1099
  if (e.preventDefault && e.cancelable) {
1097
1100
  e.preventDefault();
@@ -1,7 +1,16 @@
1
1
  export const PM_STATUSES__OK = 1;
2
+ export const PM_STATUSES__OK_TEXT = 'OK';
2
3
  export const PM_STATUSES__PM_DUE = 2;
4
+ export const PM_STATUSES__PM_DUE_TEXT = 'PM Due';
3
5
  export const PM_STATUSES__DELAYED = 3;
6
+ export const PM_STATUSES__DELAYED_TEXT = 'Delayed';
4
7
  export const PM_STATUSES__WILL_CALL = 4;
8
+ export const PM_STATUSES__WILL_CALL_TEXT = 'Will Call';
5
9
  export const PM_STATUSES__SCHEDULED = 5;
10
+ export const PM_STATUSES__SCHEDULED_TEXT = 'Scheduled';
6
11
  export const PM_STATUSES__OVERDUE = 6;
12
+ export const PM_STATUSES__OVERDUE_TEXT = 'Overdue';
7
13
  export const PM_STATUSES__COMPLETED = 7;
14
+ export const PM_STATUSES__COMPLETED_TEXT = 'Completed';
15
+ export const PM_STATUSES__DISABLED = 8;
16
+ export const PM_STATUSES__DISABLED_TEXT = 'Disabled';
@@ -1212,7 +1212,7 @@ function AttachmentsElement(props) {
1212
1212
  onChangeSelection: (selection) => {
1213
1213
  setTreeSelection(selection);
1214
1214
  },
1215
- additionalToolbarButtons: canCrud ? [
1215
+ additionalToolbarButtons: canCrud && treeSelection[0] && !treeSelection[0].isDestroyed ? [
1216
1216
  {
1217
1217
  key: 'Plus',
1218
1218
  text: 'New Directory',