@teselagen/ui 0.7.33-beta.5 → 0.7.33
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/AdvancedOptions.js +33 -0
- package/AssignDefaultsModeContext.js +22 -0
- package/CellDragHandle.js +132 -0
- package/ColumnFilterMenu.js +62 -0
- package/Columns.js +979 -0
- package/DataTable/utils/queryParams.d.ts +18 -9
- package/DisabledLoadingComponent.js +15 -0
- package/DisplayOptions.js +199 -0
- package/DropdownButton.js +36 -0
- package/DropdownCell.js +61 -0
- package/EditableCell.js +44 -0
- package/FillWindow.css +6 -0
- package/FillWindow.js +69 -0
- package/FilterAndSortMenu.js +391 -0
- package/FormSeparator.js +9 -0
- package/LoadingDots.js +14 -0
- package/MatchHeaders.js +234 -0
- package/PagingTool.js +225 -0
- package/RenderCell.js +191 -0
- package/SearchBar.js +69 -0
- package/SimpleStepViz.js +22 -0
- package/SortableColumns.js +100 -0
- package/TableFormTrackerContext.js +10 -0
- package/Tag.js +112 -0
- package/ThComponent.js +44 -0
- package/TimelineEvent.js +31 -0
- package/UploadCsvWizard.css +4 -0
- package/UploadCsvWizard.js +719 -0
- package/Uploader.js +1278 -0
- package/adHoc.js +10 -0
- package/autoTooltip.js +201 -0
- package/basicHandleActionsWithFullState.js +14 -0
- package/browserUtils.js +3 -0
- package/combineReducersWithFullState.js +14 -0
- package/commandControls.js +82 -0
- package/commandUtils.js +112 -0
- package/constants.js +1 -0
- package/convertSchema.js +69 -0
- package/customIcons.js +361 -0
- package/dataTableEnhancer.js +41 -0
- package/defaultFormatters.js +32 -0
- package/defaultValidators.js +40 -0
- package/determineBlackOrWhiteTextColor.js +4 -0
- package/editCellHelper.js +44 -0
- package/formatPasteData.js +16 -0
- package/getAllRows.js +11 -0
- package/getCellCopyText.js +7 -0
- package/getCellInfo.js +36 -0
- package/getCellVal.js +20 -0
- package/getDayjsFormatter.js +35 -0
- package/getFieldPathToField.js +7 -0
- package/getIdOrCodeOrIndex.js +9 -0
- package/getLastSelectedEntity.js +11 -0
- package/getNewEntToSelect.js +25 -0
- package/getNewName.js +31 -0
- package/getRowCopyText.js +28 -0
- package/getTableConfigFromStorage.js +5 -0
- package/getTextFromEl.js +28 -0
- package/getVals.js +8 -0
- package/handleCopyColumn.js +21 -0
- package/handleCopyHelper.js +15 -0
- package/handleCopyRows.js +23 -0
- package/handleCopyTable.js +16 -0
- package/handlerHelpers.js +24 -0
- package/hotkeyUtils.js +131 -0
- package/index.cjs.js +970 -826
- package/index.d.ts +0 -1
- package/index.es.js +970 -826
- package/index.js +196 -0
- package/isBeingCalledExcessively.js +31 -0
- package/isBottomRightCornerOfRectangle.js +20 -0
- package/isEntityClean.js +15 -0
- package/isTruthy.js +12 -0
- package/isValueEmpty.js +3 -0
- package/itemUpload.js +84 -0
- package/menuUtils.js +433 -0
- package/package.json +1 -2
- package/popoverOverflowModifiers.js +11 -0
- package/primarySelectedValue.js +1 -0
- package/pureNoFunc.js +31 -0
- package/queryParams.js +1058 -0
- package/removeCleanRows.js +22 -0
- package/renderOnDoc.js +32 -0
- package/rerenderOnWindowResize.js +26 -0
- package/rowClick.js +181 -0
- package/selection.js +8 -0
- package/showAppSpinner.js +12 -0
- package/showDialogOnDocBody.js +33 -0
- package/showProgressToast.js +22 -0
- package/sortify.js +73 -0
- package/src/DataTable/index.js +1 -1
- package/src/DataTable/utils/filterLocalEntitiesToHasura.js +14 -0
- package/src/DataTable/utils/filterLocalEntitiesToHasura.test.js +49 -0
- package/src/DataTable/utils/queryParams.js +12 -9
- package/src/DataTable/utils/tableQueryParamsToHasuraClauses.js +146 -143
- package/style.css +29 -0
- package/tagUtils.js +45 -0
- package/tgFormValues.js +35 -0
- package/tg_modalState.js +47 -0
- package/throwFormError.js +16 -0
- package/toastr.js +148 -0
- package/tryToMatchSchemas.js +264 -0
- package/typeToCommonType.js +6 -0
- package/useDeepEqualMemo.js +15 -0
- package/useDialog.js +63 -0
- package/useStableReference.js +9 -0
- package/useTableEntities.js +38 -0
- package/useTraceUpdate.js +19 -0
- package/utils.js +37 -0
- package/validateTableWideErrors.js +160 -0
- package/viewColumn.js +97 -0
- package/withField.js +20 -0
- package/withFields.js +11 -0
- package/withLocalStorage.js +11 -0
- package/withSelectTableRecords.js +43 -0
- package/withSelectedEntities.js +65 -0
- package/withStore.js +10 -0
- package/withTableParams.js +301 -0
- package/wrapDialog.js +116 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { isEntityClean } from "./isEntityClean";
|
|
2
|
+
import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex";
|
|
3
|
+
|
|
4
|
+
export const removeCleanRows = (entities, cellValidation) => {
|
|
5
|
+
const toFilterOut = {};
|
|
6
|
+
const entsToUse = (entities || []).filter(e => {
|
|
7
|
+
if (!(e._isClean || isEntityClean(e))) return true;
|
|
8
|
+
else {
|
|
9
|
+
toFilterOut[getIdOrCodeOrIndex(e)] = true;
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const validationToUse = {};
|
|
15
|
+
Object.entries(cellValidation || {}).forEach(([k, v]) => {
|
|
16
|
+
const [rowId] = k.split(":");
|
|
17
|
+
if (!toFilterOut[rowId]) {
|
|
18
|
+
validationToUse[k] = v;
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return { entsToUse, validationToUse };
|
|
22
|
+
};
|
package/renderOnDoc.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { createRoot } from "react-dom/client";
|
|
2
|
+
|
|
3
|
+
export function renderOnDoc(fn) {
|
|
4
|
+
const elemDiv = document.createElement("div");
|
|
5
|
+
elemDiv.style.cssText =
|
|
6
|
+
"position:absolute;width:100%;height:100%;top:0px;opacity:0.3;z-index:0;";
|
|
7
|
+
document.body.appendChild(elemDiv);
|
|
8
|
+
const root = createRoot(elemDiv);
|
|
9
|
+
const handleClose = () => {
|
|
10
|
+
setTimeout(() => {
|
|
11
|
+
root.unmount(elemDiv);
|
|
12
|
+
document.body.removeChild(elemDiv);
|
|
13
|
+
});
|
|
14
|
+
};
|
|
15
|
+
root.render(fn(handleClose));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function renderOnDocSimple(el) {
|
|
19
|
+
const elemDiv = document.createElement("div");
|
|
20
|
+
elemDiv.style.cssText =
|
|
21
|
+
"position:absolute;width:100%;height:100%;top:0px;opacity:1;z-index:10000;";
|
|
22
|
+
document.body.appendChild(elemDiv);
|
|
23
|
+
const root = createRoot(elemDiv);
|
|
24
|
+
root.render(el);
|
|
25
|
+
const handleClose = () => {
|
|
26
|
+
setTimeout(() => {
|
|
27
|
+
root.unmount();
|
|
28
|
+
document.body.removeChild(elemDiv);
|
|
29
|
+
});
|
|
30
|
+
};
|
|
31
|
+
return handleClose;
|
|
32
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { throttle } from "lodash-es";
|
|
2
|
+
// use like this within a react component:
|
|
3
|
+
|
|
4
|
+
// constructor(props){
|
|
5
|
+
// super(props)
|
|
6
|
+
// rerenderOnWindowResize(this)
|
|
7
|
+
// }
|
|
8
|
+
|
|
9
|
+
export default function rerenderOnWindowResize(that) {
|
|
10
|
+
that.updateDimensions = throttle(() => {
|
|
11
|
+
if (that.props.disabled) return;
|
|
12
|
+
that.forceUpdate();
|
|
13
|
+
}, 250);
|
|
14
|
+
const componentDidMount = that.componentDidMount;
|
|
15
|
+
const componentWillUnmount = that.componentWillUnmount;
|
|
16
|
+
|
|
17
|
+
that.componentDidMount = (...args) => {
|
|
18
|
+
componentDidMount && componentDidMount.bind(that)(...args);
|
|
19
|
+
window.addEventListener("resize", that.updateDimensions);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
that.componentWillUnmount = (...args) => {
|
|
23
|
+
componentWillUnmount && componentWillUnmount.bind(that)(...args);
|
|
24
|
+
window.removeEventListener("resize", that.updateDimensions);
|
|
25
|
+
};
|
|
26
|
+
}
|
package/rowClick.js
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { isEmpty, forEach, range } from "lodash-es";
|
|
2
|
+
import { getSelectedRowsFromEntities } from "./selection";
|
|
3
|
+
import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex";
|
|
4
|
+
import { getRecordsFromIdMap } from "./withSelectedEntities";
|
|
5
|
+
|
|
6
|
+
export default function rowClick(
|
|
7
|
+
e,
|
|
8
|
+
rowInfo,
|
|
9
|
+
entities,
|
|
10
|
+
{
|
|
11
|
+
reduxFormSelectedEntityIdMap,
|
|
12
|
+
isSingleSelect,
|
|
13
|
+
noSelect,
|
|
14
|
+
onRowClick,
|
|
15
|
+
isEntityDisabled,
|
|
16
|
+
withCheckboxes,
|
|
17
|
+
onDeselect,
|
|
18
|
+
onSingleRowSelect,
|
|
19
|
+
onMultiRowSelect,
|
|
20
|
+
noDeselectAll,
|
|
21
|
+
onRowSelect,
|
|
22
|
+
change
|
|
23
|
+
}
|
|
24
|
+
) {
|
|
25
|
+
const entity = rowInfo.original;
|
|
26
|
+
onRowClick(e, entity, rowInfo);
|
|
27
|
+
if (noSelect || isEntityDisabled(entity)) return;
|
|
28
|
+
const rowId = getIdOrCodeOrIndex(entity, rowInfo.index);
|
|
29
|
+
if (rowId === undefined) return;
|
|
30
|
+
const ctrl = e.metaKey || e.ctrlKey || (withCheckboxes && !e.shiftKey);
|
|
31
|
+
const oldIdMap = reduxFormSelectedEntityIdMap || {};
|
|
32
|
+
const rowSelected = oldIdMap[rowId];
|
|
33
|
+
let newIdMap = {
|
|
34
|
+
[rowId]: {
|
|
35
|
+
time: Date.now(),
|
|
36
|
+
entity
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
if (isSingleSelect) {
|
|
41
|
+
if (rowSelected) newIdMap = {};
|
|
42
|
+
} else if (rowSelected && e.shiftKey) return;
|
|
43
|
+
else if (rowSelected && ctrl) {
|
|
44
|
+
newIdMap = {
|
|
45
|
+
...oldIdMap
|
|
46
|
+
};
|
|
47
|
+
delete newIdMap[rowId];
|
|
48
|
+
} else if (rowSelected) {
|
|
49
|
+
newIdMap = {};
|
|
50
|
+
} else if (ctrl) {
|
|
51
|
+
newIdMap = {
|
|
52
|
+
...oldIdMap,
|
|
53
|
+
...newIdMap
|
|
54
|
+
};
|
|
55
|
+
} else if (e.shiftKey && !isEmpty(oldIdMap)) {
|
|
56
|
+
newIdMap = {
|
|
57
|
+
[rowId]: {
|
|
58
|
+
entity,
|
|
59
|
+
time: Date.now()
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const currentlySelectedRowIndices = getSelectedRowsFromEntities(
|
|
63
|
+
entities,
|
|
64
|
+
oldIdMap
|
|
65
|
+
);
|
|
66
|
+
if (currentlySelectedRowIndices.length) {
|
|
67
|
+
let timeToBeat = {
|
|
68
|
+
id: null,
|
|
69
|
+
time: null
|
|
70
|
+
};
|
|
71
|
+
forEach(oldIdMap, ({ time }, key) => {
|
|
72
|
+
if (time && time > timeToBeat.time)
|
|
73
|
+
timeToBeat = {
|
|
74
|
+
id: key,
|
|
75
|
+
time
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
const mostRecentlySelectedIndex = entities.findIndex((e, i) => {
|
|
79
|
+
let id = getIdOrCodeOrIndex(e, i);
|
|
80
|
+
if (!id && id !== 0) id = "";
|
|
81
|
+
return id.toString() === timeToBeat.id;
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (mostRecentlySelectedIndex !== -1) {
|
|
85
|
+
const highRange =
|
|
86
|
+
rowInfo.index < mostRecentlySelectedIndex
|
|
87
|
+
? mostRecentlySelectedIndex - 1
|
|
88
|
+
: rowInfo.index;
|
|
89
|
+
const lowRange =
|
|
90
|
+
rowInfo.index > mostRecentlySelectedIndex
|
|
91
|
+
? mostRecentlySelectedIndex + 1
|
|
92
|
+
: rowInfo.index;
|
|
93
|
+
range(lowRange, highRange + 1).forEach(i => {
|
|
94
|
+
if (isEntityDisabled && isEntityDisabled(entities[i])) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const recordId = entities[i] && getIdOrCodeOrIndex(entities[i], i);
|
|
98
|
+
if (recordId || recordId === 0)
|
|
99
|
+
// newIdMap[recordId] = { entity: entities[i] };
|
|
100
|
+
newIdMap[recordId] = { entity: entities[i], time: Date.now() };
|
|
101
|
+
});
|
|
102
|
+
newIdMap = {
|
|
103
|
+
...oldIdMap,
|
|
104
|
+
...newIdMap
|
|
105
|
+
};
|
|
106
|
+
if (newIdMap[rowId]) {
|
|
107
|
+
//the entity we just clicked on should have the "freshest" time
|
|
108
|
+
newIdMap[rowId].time = Date.now() + 1;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
finalizeSelection({
|
|
115
|
+
idMap: newIdMap,
|
|
116
|
+
entities,
|
|
117
|
+
props: {
|
|
118
|
+
onDeselect,
|
|
119
|
+
onSingleRowSelect,
|
|
120
|
+
onMultiRowSelect,
|
|
121
|
+
noDeselectAll,
|
|
122
|
+
onRowSelect,
|
|
123
|
+
noSelect,
|
|
124
|
+
change
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function changeSelectedEntities({ idMap, entities = [], change }) {
|
|
130
|
+
const newIdMap = { ...idMap };
|
|
131
|
+
const entityIdToIndex = {};
|
|
132
|
+
entities.forEach((e, i) => {
|
|
133
|
+
entityIdToIndex[getIdOrCodeOrIndex(e, 0)] = i;
|
|
134
|
+
});
|
|
135
|
+
// we want to store the index of the entity so that it can be used to preserve order in selection
|
|
136
|
+
Object.keys(newIdMap).forEach(key => {
|
|
137
|
+
if (!newIdMap[key]) return;
|
|
138
|
+
const rowIndex =
|
|
139
|
+
entityIdToIndex[getIdOrCodeOrIndex(newIdMap[key].entity, 0)];
|
|
140
|
+
newIdMap[key] = {
|
|
141
|
+
...newIdMap[key],
|
|
142
|
+
rowIndex: rowIndex === undefined ? 10000000 : rowIndex
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
change("reduxFormSelectedEntityIdMap", newIdMap);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function finalizeSelection({
|
|
150
|
+
idMap,
|
|
151
|
+
entities,
|
|
152
|
+
props: {
|
|
153
|
+
onDeselect,
|
|
154
|
+
onSingleRowSelect,
|
|
155
|
+
onMultiRowSelect,
|
|
156
|
+
noDeselectAll,
|
|
157
|
+
onRowSelect,
|
|
158
|
+
noSelect,
|
|
159
|
+
change
|
|
160
|
+
}
|
|
161
|
+
}) {
|
|
162
|
+
if (noSelect) return;
|
|
163
|
+
if (
|
|
164
|
+
noDeselectAll &&
|
|
165
|
+
Object.keys(idMap).filter(id => {
|
|
166
|
+
return idMap[id];
|
|
167
|
+
}).length === 0
|
|
168
|
+
) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
changeSelectedEntities({ idMap, entities, change });
|
|
173
|
+
const selectedRecords = getRecordsFromIdMap(idMap);
|
|
174
|
+
onRowSelect(selectedRecords);
|
|
175
|
+
|
|
176
|
+
selectedRecords.length === 0
|
|
177
|
+
? onDeselect()
|
|
178
|
+
: selectedRecords.length > 1
|
|
179
|
+
? onMultiRowSelect(selectedRecords)
|
|
180
|
+
: onSingleRowSelect(selectedRecords[0]);
|
|
181
|
+
}
|
package/selection.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex";
|
|
2
|
+
|
|
3
|
+
export const getSelectedRowsFromEntities = (entities, idMap) => {
|
|
4
|
+
if (!idMap) return [];
|
|
5
|
+
return entities.reduce((acc, entity, i) => {
|
|
6
|
+
return idMap[getIdOrCodeOrIndex(entity, i)] ? acc.concat(i) : acc;
|
|
7
|
+
}, []);
|
|
8
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Overlay } from "@blueprintjs/core";
|
|
3
|
+
import { renderOnDocSimple } from "./utils/renderOnDoc";
|
|
4
|
+
import Loading from "./Loading";
|
|
5
|
+
|
|
6
|
+
export default function showAppSpinner() {
|
|
7
|
+
return renderOnDocSimple(
|
|
8
|
+
<Overlay isOpen={true}>
|
|
9
|
+
<Loading centeredInPage loading></Loading>
|
|
10
|
+
</Overlay>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createRoot } from "react-dom/client";
|
|
2
|
+
import React from "react";
|
|
3
|
+
// import withDialog from "./enhancers/withDialog";
|
|
4
|
+
import { Dialog } from "@blueprintjs/core";
|
|
5
|
+
import { nanoid } from "nanoid";
|
|
6
|
+
|
|
7
|
+
//this is only really useful for unconnected standalone simple dialogs
|
|
8
|
+
//remember to pass usePortal={false} to the <Dialog/> component so it will close properly
|
|
9
|
+
export default function showDialogOnDocBody(DialogComp, options = {}) {
|
|
10
|
+
const dialogHolder = document.createElement("div");
|
|
11
|
+
const className = "myDialog" + nanoid();
|
|
12
|
+
dialogHolder.className = className;
|
|
13
|
+
document.body.appendChild(dialogHolder);
|
|
14
|
+
const onClose = () => {
|
|
15
|
+
document.querySelector("." + className).remove();
|
|
16
|
+
};
|
|
17
|
+
let DialogCompToUse;
|
|
18
|
+
if (options.addDialogContainer) {
|
|
19
|
+
DialogCompToUse = props => {
|
|
20
|
+
return (
|
|
21
|
+
<Dialog usePortal={false} title="pass a {title} prop" isOpen {...props}>
|
|
22
|
+
<DialogComp {...props} hideModal={onClose} onClose={onClose} />
|
|
23
|
+
</Dialog>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
} else {
|
|
27
|
+
DialogCompToUse = DialogComp;
|
|
28
|
+
}
|
|
29
|
+
const root = createRoot(dialogHolder);
|
|
30
|
+
root.render(
|
|
31
|
+
<DialogCompToUse hideModal={onClose} onClose={onClose} {...options} />
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* Copyright (C) 2018 TeselaGen Biotechnology, Inc. */
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { ProgressBar } from "@blueprintjs/core";
|
|
4
|
+
|
|
5
|
+
export default (message, value, key, opts) => {
|
|
6
|
+
return window.toastr.default(
|
|
7
|
+
<div>
|
|
8
|
+
<div style={{ marginBottom: 10 }}>{message}</div>
|
|
9
|
+
<ProgressBar
|
|
10
|
+
intent={value >= 1 ? "success" : "primary"}
|
|
11
|
+
animate={value < 1 || !value}
|
|
12
|
+
stripes={value < 1 || !value}
|
|
13
|
+
value={value}
|
|
14
|
+
/>
|
|
15
|
+
</div>,
|
|
16
|
+
{
|
|
17
|
+
timeout: value >= 1 ? 3000 : 100000,
|
|
18
|
+
key,
|
|
19
|
+
...opts
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
};
|
package/sortify.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright 2015-2016 Thomas Rosenau
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Create a “sorted” version of an object.
|
|
19
|
+
*
|
|
20
|
+
* JS engines internally keep track of an object's keys in the order of
|
|
21
|
+
* creation time, i.e. {a:1,b:2} is treated differently from {b:2,a:1}.
|
|
22
|
+
* That difference can be seen when JSON.stringify is called on that object.
|
|
23
|
+
* This function normalizes any kind of object by rearranging the keys in
|
|
24
|
+
* alphabetical order (numerical keys first, since v8 does so, and there's
|
|
25
|
+
* nothing we can do about it).
|
|
26
|
+
* @param {*} o The object to be sorted
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/* eslint-disable */
|
|
30
|
+
const sortKeys = o => {
|
|
31
|
+
if (Array.isArray(o)) {
|
|
32
|
+
return o.map(sortKeys);
|
|
33
|
+
} else if (o instanceof Object) {
|
|
34
|
+
// put numeric keys first
|
|
35
|
+
let numeric = [];
|
|
36
|
+
let nonNumeric = [];
|
|
37
|
+
Object.keys(o).forEach(key => {
|
|
38
|
+
if (/^(0|[1-9][0-9]*)$/.test(key)) {
|
|
39
|
+
numeric.push(+key);
|
|
40
|
+
} else {
|
|
41
|
+
nonNumeric.push(key);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
// do the rearrangement
|
|
45
|
+
return numeric
|
|
46
|
+
.sort(function (a, b) {
|
|
47
|
+
return a - b;
|
|
48
|
+
})
|
|
49
|
+
.concat(nonNumeric.sort())
|
|
50
|
+
.reduce((result, key) => {
|
|
51
|
+
result[key] = sortKeys(o[key]); // recurse!
|
|
52
|
+
return result;
|
|
53
|
+
}, {});
|
|
54
|
+
}
|
|
55
|
+
return o;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const jsonStringify = JSON.stringify.bind(JSON);
|
|
59
|
+
|
|
60
|
+
const sortify = (value, replacer, space) => {
|
|
61
|
+
// replacer, toJSON(), cyclic references and other stuff is better handled by native stringifier.
|
|
62
|
+
// So we do JSON.stringify(sortKeys( JSON.parse(JSON.stringify()) )).
|
|
63
|
+
// This approach is slightly slower but much safer than a manual stringification.
|
|
64
|
+
let nativeJson = jsonStringify(value, replacer, 0);
|
|
65
|
+
if (!nativeJson || (nativeJson[0] !== "{" && nativeJson[0] !== "[")) {
|
|
66
|
+
// if value is not an Object or Array
|
|
67
|
+
return nativeJson;
|
|
68
|
+
}
|
|
69
|
+
let cleanObj = JSON.parse(nativeJson);
|
|
70
|
+
return jsonStringify(sortKeys(cleanObj), null, space);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default sortify;
|
package/src/DataTable/index.js
CHANGED
|
@@ -77,6 +77,15 @@ function applyWhereClause(records, where) {
|
|
|
77
77
|
const value = record[key];
|
|
78
78
|
const conditions = filter[key];
|
|
79
79
|
|
|
80
|
+
// Handle nested object properties
|
|
81
|
+
if (
|
|
82
|
+
isObject(value) &&
|
|
83
|
+
isObject(conditions) &&
|
|
84
|
+
!hasOperator(conditions)
|
|
85
|
+
) {
|
|
86
|
+
return applyFilter(value, conditions);
|
|
87
|
+
}
|
|
88
|
+
|
|
80
89
|
for (const operator in conditions) {
|
|
81
90
|
const conditionValue = conditions[operator];
|
|
82
91
|
|
|
@@ -208,6 +217,11 @@ function applyWhereClause(records, where) {
|
|
|
208
217
|
return true;
|
|
209
218
|
}
|
|
210
219
|
|
|
220
|
+
// Helper to check if an object contains any Hasura operators
|
|
221
|
+
function hasOperator(obj) {
|
|
222
|
+
return Object.keys(obj).some(key => key.startsWith("_"));
|
|
223
|
+
}
|
|
224
|
+
|
|
211
225
|
return records.filter(record => applyFilter(record, where));
|
|
212
226
|
}
|
|
213
227
|
|
|
@@ -297,6 +297,55 @@ describe("filterLocalEntitiesToHasura", () => {
|
|
|
297
297
|
expect(result.entitiesAcrossPages).toEqual([records[0]]);
|
|
298
298
|
expect(result.entityCount).toBe(1);
|
|
299
299
|
});
|
|
300
|
+
it("should filter with nested filters in _and and _or properly ", () => {
|
|
301
|
+
const result = filterLocalEntitiesToHasura(
|
|
302
|
+
[
|
|
303
|
+
{
|
|
304
|
+
id: 1,
|
|
305
|
+
type: {
|
|
306
|
+
special: "01"
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: 2,
|
|
311
|
+
type: {
|
|
312
|
+
special: "02"
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
],
|
|
316
|
+
{
|
|
317
|
+
where: {
|
|
318
|
+
_and: [
|
|
319
|
+
{
|
|
320
|
+
type: {
|
|
321
|
+
special: {
|
|
322
|
+
_ilike: "%01%"
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
],
|
|
327
|
+
_or: []
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
expect(result.entities).toEqual([
|
|
332
|
+
{
|
|
333
|
+
id: 1,
|
|
334
|
+
type: {
|
|
335
|
+
special: "01"
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
]);
|
|
339
|
+
expect(result.entitiesAcrossPages).toEqual([
|
|
340
|
+
{
|
|
341
|
+
id: 1,
|
|
342
|
+
type: {
|
|
343
|
+
special: "01"
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
]);
|
|
347
|
+
expect(result.entityCount).toBe(1);
|
|
348
|
+
});
|
|
300
349
|
|
|
301
350
|
it("should handle empty where clause", () => {
|
|
302
351
|
const result = filterLocalEntitiesToHasura(records, {});
|
|
@@ -303,7 +303,7 @@ export function getQueryParams({
|
|
|
303
303
|
});
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
|
|
306
|
+
let toRet = {
|
|
307
307
|
//these are values that might be generally useful for the wrapped component
|
|
308
308
|
page,
|
|
309
309
|
pageSize: ownProps.controlled_pageSize || pageSize,
|
|
@@ -325,17 +325,20 @@ export function getQueryParams({
|
|
|
325
325
|
if (isLocalCall) {
|
|
326
326
|
//if the table is local (aka not directly connected to a db) then we need to
|
|
327
327
|
//handle filtering/paging/sorting all on the front end
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
328
|
+
toRet = {
|
|
329
|
+
...toRet,
|
|
330
|
+
...filterLocalEntitiesToHasura(entities, {
|
|
331
|
+
where,
|
|
332
|
+
order_by,
|
|
333
|
+
limit,
|
|
334
|
+
offset,
|
|
335
|
+
isInfinite
|
|
336
|
+
})
|
|
337
|
+
};
|
|
335
338
|
return toRet;
|
|
336
339
|
} else {
|
|
337
340
|
return {
|
|
338
|
-
...
|
|
341
|
+
...toRet,
|
|
339
342
|
variables: {
|
|
340
343
|
where,
|
|
341
344
|
order_by,
|