@teselagen/ui 0.0.11 → 0.0.13
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/README.md +7 -0
- package/cypress.config.ts +6 -0
- package/index.html +12 -0
- package/package.json +5 -11
- package/project.json +74 -0
- package/src/AdvancedOptions.js +33 -0
- package/src/AdvancedOptions.spec.js +24 -0
- package/src/AssignDefaultsModeContext.js +21 -0
- package/src/AsyncValidateFieldSpinner/index.js +12 -0
- package/src/BlueprintError/index.js +14 -0
- package/src/BounceLoader/index.js +16 -0
- package/src/BounceLoader/style.css +45 -0
- package/src/CollapsibleCard/index.js +92 -0
- package/src/CollapsibleCard/style.css +21 -0
- package/src/DNALoader/index.js +20 -0
- package/src/DNALoader/style.css +251 -0
- package/src/DataTable/CellDragHandle.js +130 -0
- package/src/DataTable/DisabledLoadingComponent.js +15 -0
- package/src/DataTable/DisplayOptions.js +218 -0
- package/src/DataTable/FilterAndSortMenu.js +397 -0
- package/src/DataTable/PagingTool.js +232 -0
- package/src/DataTable/SearchBar.js +57 -0
- package/src/DataTable/SortableColumns.js +53 -0
- package/src/DataTable/TableFormTrackerContext.js +10 -0
- package/src/DataTable/dataTableEnhancer.js +291 -0
- package/src/DataTable/defaultFormatters.js +32 -0
- package/src/DataTable/defaultProps.js +45 -0
- package/src/DataTable/defaultValidators.js +40 -0
- package/src/DataTable/editCellHelper.js +44 -0
- package/src/DataTable/getCellVal.js +20 -0
- package/src/DataTable/getVals.js +8 -0
- package/src/DataTable/index.js +3537 -0
- package/src/DataTable/isTruthy.js +12 -0
- package/src/DataTable/isValueEmpty.js +3 -0
- package/src/DataTable/style.css +600 -0
- package/src/DataTable/utils/computePresets.js +42 -0
- package/src/DataTable/utils/convertSchema.js +69 -0
- package/src/DataTable/utils/getIdOrCodeOrIndex.js +9 -0
- package/src/DataTable/utils/getTableConfigFromStorage.js +5 -0
- package/src/DataTable/utils/queryParams.js +1032 -0
- package/src/DataTable/utils/rowClick.js +156 -0
- package/src/DataTable/utils/selection.js +8 -0
- package/src/DataTable/utils/withSelectedEntities.js +65 -0
- package/src/DataTable/utils/withTableParams.js +328 -0
- package/src/DataTable/validateTableWideErrors.js +135 -0
- package/src/DataTable/viewColumn.js +37 -0
- package/src/DialogFooter/index.js +79 -0
- package/src/DialogFooter/style.css +9 -0
- package/src/DropdownButton.js +36 -0
- package/src/FillWindow.css +6 -0
- package/src/FillWindow.js +69 -0
- package/src/FormComponents/Uploader.js +1197 -0
- package/src/FormComponents/getNewName.js +31 -0
- package/src/FormComponents/index.js +1384 -0
- package/src/FormComponents/itemUpload.js +84 -0
- package/src/FormComponents/sortify.js +73 -0
- package/src/FormComponents/style.css +247 -0
- package/src/FormComponents/tryToMatchSchemas.js +222 -0
- package/src/FormComponents/utils.js +6 -0
- package/src/HotkeysDialog/index.js +79 -0
- package/src/HotkeysDialog/style.css +54 -0
- package/src/InfoHelper/index.js +83 -0
- package/src/InfoHelper/style.css +7 -0
- package/src/IntentText/index.js +18 -0
- package/src/Loading/index.js +74 -0
- package/src/Loading/style.css +4 -0
- package/src/MatchHeaders.js +223 -0
- package/src/MenuBar/index.js +416 -0
- package/src/MenuBar/style.css +45 -0
- package/src/PromptUnsavedChanges/index.js +40 -0
- package/src/ResizableDraggableDialog/index.js +138 -0
- package/src/ResizableDraggableDialog/style.css +42 -0
- package/src/ScrollToTop/index.js +72 -0
- package/src/SimpleStepViz.js +26 -0
- package/src/TgSelect/index.js +465 -0
- package/src/TgSelect/style.css +34 -0
- package/src/TgSuggest/index.js +121 -0
- package/src/Timeline/TimelineEvent.js +31 -0
- package/src/Timeline/index.js +22 -0
- package/src/Timeline/style.css +29 -0
- package/src/UploadCsvWizard.css +4 -0
- package/src/UploadCsvWizard.js +731 -0
- package/src/autoTooltip.js +89 -0
- package/src/constants.js +1 -0
- package/src/customIcons.js +361 -0
- package/src/enhancers/withDialog/index.js +196 -0
- package/src/enhancers/withDialog/tg_modalState.js +46 -0
- package/src/enhancers/withField.js +20 -0
- package/src/enhancers/withFields.js +11 -0
- package/src/enhancers/withLocalStorage.js +11 -0
- package/src/index.js +76 -0
- package/src/rerenderOnWindowResize.js +27 -0
- package/src/showAppSpinner.js +12 -0
- package/src/showConfirmationDialog/index.js +116 -0
- package/src/showDialogOnDocBody.js +37 -0
- package/src/style.css +214 -0
- package/src/toastr.js +92 -0
- package/src/typeToCommonType.js +6 -0
- package/src/useDialog.js +64 -0
- package/src/utils/S3Download.js +14 -0
- package/src/utils/adHoc.js +10 -0
- package/src/utils/basicHandleActionsWithFullState.js +14 -0
- package/src/utils/combineReducersWithFullState.js +14 -0
- package/src/utils/commandControls.js +83 -0
- package/src/utils/commandUtils.js +112 -0
- package/src/utils/determineBlackOrWhiteTextColor.js +4 -0
- package/src/utils/getDayjsFormatter.js +35 -0
- package/src/utils/getTextFromEl.js +28 -0
- package/src/utils/handlerHelpers.js +30 -0
- package/src/utils/hotkeyUtils.js +129 -0
- package/src/utils/menuUtils.js +402 -0
- package/src/utils/popoverOverflowModifiers.js +11 -0
- package/src/utils/pureNoFunc.js +31 -0
- package/src/utils/renderOnDoc.js +29 -0
- package/src/utils/showProgressToast.js +22 -0
- package/src/utils/tagUtils.js +45 -0
- package/src/utils/tgFormValues.js +32 -0
- package/src/utils/withSelectTableRecords.js +38 -0
- package/src/utils/withStore.js +10 -0
- package/src/wrapDialog.js +112 -0
- package/tsconfig.json +4 -0
- package/vite.config.ts +7 -0
- package/index.mjs +0 -109378
- package/index.umd.js +0 -109381
- package/style.css +0 -10421
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { isEmpty, forEach, range } from "lodash";
|
|
2
|
+
import { getSelectedRowsFromEntities } from "./selection";
|
|
3
|
+
import getIdOrCodeOrIndex from "./getIdOrCodeOrIndex";
|
|
4
|
+
import { getRecordsFromIdMap } from "./withSelectedEntities";
|
|
5
|
+
|
|
6
|
+
export default function rowClick(e, rowInfo, entities, props) {
|
|
7
|
+
const {
|
|
8
|
+
reduxFormSelectedEntityIdMap,
|
|
9
|
+
isSingleSelect,
|
|
10
|
+
noSelect,
|
|
11
|
+
onRowClick,
|
|
12
|
+
isEntityDisabled,
|
|
13
|
+
withCheckboxes
|
|
14
|
+
} = props;
|
|
15
|
+
const entity = rowInfo.original;
|
|
16
|
+
onRowClick(e, entity, rowInfo);
|
|
17
|
+
if (noSelect || isEntityDisabled(entity)) return;
|
|
18
|
+
const rowId = getIdOrCodeOrIndex(entity, rowInfo.index);
|
|
19
|
+
if (rowId === undefined) return;
|
|
20
|
+
const ctrl = e.metaKey || e.ctrlKey || (withCheckboxes && !e.shiftKey);
|
|
21
|
+
const oldIdMap = reduxFormSelectedEntityIdMap || {};
|
|
22
|
+
const rowSelected = oldIdMap[rowId];
|
|
23
|
+
let newIdMap = {
|
|
24
|
+
[rowId]: {
|
|
25
|
+
time: Date.now(),
|
|
26
|
+
entity
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
if (isSingleSelect) {
|
|
31
|
+
if (rowSelected) newIdMap = {};
|
|
32
|
+
} else if (rowSelected && e.shiftKey) return;
|
|
33
|
+
else if (rowSelected && ctrl) {
|
|
34
|
+
newIdMap = {
|
|
35
|
+
...oldIdMap
|
|
36
|
+
};
|
|
37
|
+
delete newIdMap[rowId];
|
|
38
|
+
} else if (rowSelected) {
|
|
39
|
+
newIdMap = {};
|
|
40
|
+
} else if (ctrl) {
|
|
41
|
+
newIdMap = {
|
|
42
|
+
...oldIdMap,
|
|
43
|
+
...newIdMap
|
|
44
|
+
};
|
|
45
|
+
} else if (e.shiftKey && !isEmpty(oldIdMap)) {
|
|
46
|
+
newIdMap = {
|
|
47
|
+
[rowId]: {
|
|
48
|
+
entity,
|
|
49
|
+
time: Date.now()
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
const currentlySelectedRowIndices = getSelectedRowsFromEntities(
|
|
53
|
+
entities,
|
|
54
|
+
oldIdMap
|
|
55
|
+
);
|
|
56
|
+
if (currentlySelectedRowIndices.length) {
|
|
57
|
+
let timeToBeat = {
|
|
58
|
+
id: null,
|
|
59
|
+
time: null
|
|
60
|
+
};
|
|
61
|
+
forEach(oldIdMap, ({ time }, key) => {
|
|
62
|
+
if (time && time > timeToBeat.time)
|
|
63
|
+
timeToBeat = {
|
|
64
|
+
id: key,
|
|
65
|
+
time
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
const mostRecentlySelectedIndex = entities.findIndex((e, i) => {
|
|
69
|
+
let id = getIdOrCodeOrIndex(e, i);
|
|
70
|
+
if (!id && id !== 0) id = "";
|
|
71
|
+
return id.toString() === timeToBeat.id;
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (mostRecentlySelectedIndex !== -1) {
|
|
75
|
+
const highRange =
|
|
76
|
+
rowInfo.index < mostRecentlySelectedIndex
|
|
77
|
+
? mostRecentlySelectedIndex - 1
|
|
78
|
+
: rowInfo.index;
|
|
79
|
+
const lowRange =
|
|
80
|
+
rowInfo.index > mostRecentlySelectedIndex
|
|
81
|
+
? mostRecentlySelectedIndex + 1
|
|
82
|
+
: rowInfo.index;
|
|
83
|
+
range(lowRange, highRange + 1).forEach(i => {
|
|
84
|
+
if (isEntityDisabled && isEntityDisabled(entities[i])) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const recordId = entities[i] && getIdOrCodeOrIndex(entities[i], i);
|
|
88
|
+
if (recordId || recordId === 0)
|
|
89
|
+
// newIdMap[recordId] = { entity: entities[i] };
|
|
90
|
+
newIdMap[recordId] = { entity: entities[i], time: Date.now() };
|
|
91
|
+
});
|
|
92
|
+
newIdMap = {
|
|
93
|
+
...oldIdMap,
|
|
94
|
+
...newIdMap
|
|
95
|
+
};
|
|
96
|
+
if (newIdMap[rowId]) {
|
|
97
|
+
//the entity we just clicked on should have the "freshest" time
|
|
98
|
+
newIdMap[rowId].time = Date.now() + 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
finalizeSelection({ idMap: newIdMap, entities, props });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function changeSelectedEntities({ idMap, entities = [], change }) {
|
|
108
|
+
const newIdMap = { ...idMap };
|
|
109
|
+
const entityIdToIndex = {};
|
|
110
|
+
entities.forEach((e, i) => {
|
|
111
|
+
entityIdToIndex[getIdOrCodeOrIndex(e, 0)] = i;
|
|
112
|
+
});
|
|
113
|
+
// we want to store the index of the entity so that it can be used to preserve order in selection
|
|
114
|
+
Object.keys(newIdMap).forEach(key => {
|
|
115
|
+
if (!newIdMap[key]) return;
|
|
116
|
+
const rowIndex =
|
|
117
|
+
entityIdToIndex[getIdOrCodeOrIndex(newIdMap[key].entity, 0)];
|
|
118
|
+
newIdMap[key] = {
|
|
119
|
+
...newIdMap[key],
|
|
120
|
+
rowIndex: rowIndex === undefined ? 10000000 : rowIndex
|
|
121
|
+
};
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
change("reduxFormSelectedEntityIdMap", newIdMap);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function finalizeSelection({ idMap, entities, props }) {
|
|
128
|
+
const {
|
|
129
|
+
onDeselect,
|
|
130
|
+
onSingleRowSelect,
|
|
131
|
+
onMultiRowSelect,
|
|
132
|
+
noDeselectAll,
|
|
133
|
+
onRowSelect,
|
|
134
|
+
noSelect,
|
|
135
|
+
change
|
|
136
|
+
} = props;
|
|
137
|
+
if (noSelect) return;
|
|
138
|
+
if (
|
|
139
|
+
noDeselectAll &&
|
|
140
|
+
Object.keys(idMap).filter(id => {
|
|
141
|
+
return idMap[id];
|
|
142
|
+
}).length === 0
|
|
143
|
+
) {
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
changeSelectedEntities({ idMap, entities, change });
|
|
148
|
+
const selectedRecords = getRecordsFromIdMap(idMap);
|
|
149
|
+
onRowSelect(selectedRecords);
|
|
150
|
+
|
|
151
|
+
selectedRecords.length === 0
|
|
152
|
+
? onDeselect()
|
|
153
|
+
: selectedRecords.length > 1
|
|
154
|
+
? onMultiRowSelect(selectedRecords)
|
|
155
|
+
: onSingleRowSelect(selectedRecords[0]);
|
|
156
|
+
}
|
|
@@ -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,65 @@
|
|
|
1
|
+
import { formValueSelector } from "redux-form";
|
|
2
|
+
import { reduce } from "lodash";
|
|
3
|
+
import { connect } from "react-redux";
|
|
4
|
+
/**
|
|
5
|
+
* @param {*string} formName
|
|
6
|
+
* @param {*string} formName
|
|
7
|
+
* @param {*string} formName
|
|
8
|
+
* @param {*string} ...etc
|
|
9
|
+
* adds a new prop `${formName}SelectedEntities` eg sequenceTableSelectedEntities
|
|
10
|
+
*/
|
|
11
|
+
export default function withSelectedEntities(...formNames) {
|
|
12
|
+
if (!formNames.length) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"You need to pass at least one arg to withSelectedEntities"
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
if (typeof formNames[0] === "string") {
|
|
18
|
+
//NEW WAY
|
|
19
|
+
return connect(state => {
|
|
20
|
+
return formNames.reduce((acc, formName) => {
|
|
21
|
+
acc[formName + "SelectedEntities"] = getRecordsFromReduxForm(
|
|
22
|
+
state,
|
|
23
|
+
formName
|
|
24
|
+
);
|
|
25
|
+
return acc;
|
|
26
|
+
}, {});
|
|
27
|
+
});
|
|
28
|
+
} else {
|
|
29
|
+
//OLD WAY:
|
|
30
|
+
const { formName, name } = formNames[0];
|
|
31
|
+
if (!formName) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
"Please pass a {formName} option when using withSelectedEntities"
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
return connect(state => {
|
|
37
|
+
return {
|
|
38
|
+
[name || "selectedEntities"]: getRecordsFromReduxForm(state, formName)
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getRecordsFromReduxForm(state, formName) {
|
|
45
|
+
const selector = formValueSelector(formName);
|
|
46
|
+
return getRecordsFromIdMap(selector(state, "reduxFormSelectedEntityIdMap"));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getRecordsFromIdMap(idMap = {}) {
|
|
50
|
+
return reduce(
|
|
51
|
+
idMap,
|
|
52
|
+
(acc, item) => {
|
|
53
|
+
if (item && item.entity) acc.push(item);
|
|
54
|
+
return acc;
|
|
55
|
+
},
|
|
56
|
+
[]
|
|
57
|
+
)
|
|
58
|
+
.sort((a, b) => a.rowIndex - b.rowIndex)
|
|
59
|
+
.map(item => item.entity);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getSelectedEntities(storeOrState, formName) {
|
|
63
|
+
const state = storeOrState.getState ? storeOrState.getState() : storeOrState;
|
|
64
|
+
return getRecordsFromReduxForm(state, formName);
|
|
65
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import React, { useContext, useEffect } from "react";
|
|
2
|
+
import { change, formValueSelector } from "redux-form";
|
|
3
|
+
import { connect } from "react-redux";
|
|
4
|
+
import { isFunction } from "lodash";
|
|
5
|
+
import { withRouter } from "react-router-dom";
|
|
6
|
+
import { branch, compose } from "recompose";
|
|
7
|
+
|
|
8
|
+
import pureNoFunc from "../../utils/pureNoFunc";
|
|
9
|
+
import TableFormTrackerContext from "../TableFormTrackerContext";
|
|
10
|
+
import convertSchema from "./convertSchema";
|
|
11
|
+
import { getRecordsFromReduxForm } from "./withSelectedEntities";
|
|
12
|
+
import {
|
|
13
|
+
makeDataTableHandlers,
|
|
14
|
+
getQueryParams,
|
|
15
|
+
setCurrentParamsOnUrl,
|
|
16
|
+
getMergedOpts,
|
|
17
|
+
getCurrentParamsFromUrl
|
|
18
|
+
} from "./queryParams";
|
|
19
|
+
import getTableConfigFromStorage from "./getTableConfigFromStorage";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Note all these options can be passed at Design Time or at Runtime (like reduxForm())
|
|
23
|
+
*
|
|
24
|
+
* @export
|
|
25
|
+
*
|
|
26
|
+
* @param {compOrOpts} compOrOpts
|
|
27
|
+
* @typedef {object} compOrOpts
|
|
28
|
+
* @property {*string} formName - required unique identifier for the table
|
|
29
|
+
* @property {Object | Function} schema - The data table schema or a function returning it. The function wll be called with props as the argument.
|
|
30
|
+
* @property {boolean} urlConnected - whether the table should connect to/update the URL
|
|
31
|
+
* @property {boolean} withSelectedEntities - whether or not to pass the selected entities
|
|
32
|
+
* @property {boolean} isCodeModel - whether the model is keyed by code instead of id in the db
|
|
33
|
+
* @property {object} defaults - tableParam defaults such as pageSize, filter, etc
|
|
34
|
+
* @property {boolean} noOrderError - won't console an error if an order is not found on schema
|
|
35
|
+
*/
|
|
36
|
+
export default function withTableParams(compOrOpts, pTopLevelOpts) {
|
|
37
|
+
let topLevelOptions;
|
|
38
|
+
let Component;
|
|
39
|
+
if (!pTopLevelOpts) {
|
|
40
|
+
topLevelOptions = compOrOpts;
|
|
41
|
+
} else {
|
|
42
|
+
topLevelOptions = pTopLevelOpts;
|
|
43
|
+
Component = compOrOpts;
|
|
44
|
+
}
|
|
45
|
+
const { isLocalCall } = topLevelOptions;
|
|
46
|
+
const mapStateToProps = (state, ownProps) => {
|
|
47
|
+
const mergedOpts = getMergedOpts(topLevelOptions, ownProps);
|
|
48
|
+
const {
|
|
49
|
+
history,
|
|
50
|
+
urlConnected,
|
|
51
|
+
withSelectedEntities,
|
|
52
|
+
formName,
|
|
53
|
+
formNameFromWithTPCall,
|
|
54
|
+
syncDisplayOptionsToDb,
|
|
55
|
+
defaults,
|
|
56
|
+
isInfinite,
|
|
57
|
+
isSimple,
|
|
58
|
+
withPaging,
|
|
59
|
+
doNotCoercePageSize,
|
|
60
|
+
initialValues,
|
|
61
|
+
additionalFilter = {},
|
|
62
|
+
additionalOrFilter = {},
|
|
63
|
+
noOrderError,
|
|
64
|
+
withDisplayOptions,
|
|
65
|
+
cellRenderer,
|
|
66
|
+
model,
|
|
67
|
+
isCodeModel,
|
|
68
|
+
noForm
|
|
69
|
+
} = mergedOpts;
|
|
70
|
+
|
|
71
|
+
const schema = getSchema(mergedOpts);
|
|
72
|
+
|
|
73
|
+
if (ownProps.isTableParamsConnected) {
|
|
74
|
+
if (
|
|
75
|
+
formName &&
|
|
76
|
+
formNameFromWithTPCall &&
|
|
77
|
+
formName !== formNameFromWithTPCall
|
|
78
|
+
) {
|
|
79
|
+
console.error(
|
|
80
|
+
`You passed a formName prop, ${formName} to a <DataTable/> component that is already withTableParams() connected, formNameFromWithTableParamsCall: ${formNameFromWithTPCall}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (ownProps.tableParams && !ownProps.tableParams.entities) {
|
|
84
|
+
console.error(
|
|
85
|
+
`No entities array detected in tableParams object (<DataTable {...tableParams}/>). You need to call withQuery() after withTableParams() like: compose(withTableParams(), withQuery(something)). formNameFromWithTableParamsCall: ${formNameFromWithTPCall}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
//short circuit because we've already run this logic
|
|
89
|
+
return {};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let formNameFromWithTableParamsCall;
|
|
93
|
+
if (isLocalCall) {
|
|
94
|
+
if (!noForm && (!formName || formName === "tgDataTable")) {
|
|
95
|
+
console.error(
|
|
96
|
+
"Please pass a unique 'formName' prop to the locally connected <DataTable/> component with schema: ",
|
|
97
|
+
schema
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
} else {
|
|
101
|
+
//in user instantiated withTableParams() call
|
|
102
|
+
if (!formName || formName === "tgDataTable") {
|
|
103
|
+
console.error(
|
|
104
|
+
"Please pass a unique 'formName' prop to the withTableParams() with schema: ",
|
|
105
|
+
schema
|
|
106
|
+
);
|
|
107
|
+
} else {
|
|
108
|
+
formNameFromWithTableParamsCall = formName;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const tableConfig = getTableConfigFromStorage(formName);
|
|
113
|
+
const formSelector = formValueSelector(formName);
|
|
114
|
+
const currentParams =
|
|
115
|
+
(urlConnected
|
|
116
|
+
? getCurrentParamsFromUrl(history.location) //important to use history location and not ownProps.location because for some reason the location path lags one render behind!!
|
|
117
|
+
: formSelector(state, "reduxFormQueryParams")) || {};
|
|
118
|
+
|
|
119
|
+
const selectedEntities = withSelectedEntities
|
|
120
|
+
? getRecordsFromReduxForm(state, formName)
|
|
121
|
+
: undefined;
|
|
122
|
+
|
|
123
|
+
const additionalFilterToUse =
|
|
124
|
+
typeof additionalFilter === "function"
|
|
125
|
+
? additionalFilter.bind(this, ownProps)
|
|
126
|
+
: () => additionalFilter;
|
|
127
|
+
const additionalOrFilterToUse =
|
|
128
|
+
typeof additionalOrFilter === "function"
|
|
129
|
+
? additionalOrFilter.bind(this, ownProps)
|
|
130
|
+
: () => additionalOrFilter;
|
|
131
|
+
|
|
132
|
+
let defaultsToUse = defaults;
|
|
133
|
+
|
|
134
|
+
// make user set page size persist
|
|
135
|
+
const userSetPageSize =
|
|
136
|
+
tableConfig?.userSetPageSize && parseInt(tableConfig.userSetPageSize, 10);
|
|
137
|
+
if (!syncDisplayOptionsToDb && userSetPageSize) {
|
|
138
|
+
defaultsToUse = defaultsToUse || {};
|
|
139
|
+
defaultsToUse.pageSize = userSetPageSize;
|
|
140
|
+
}
|
|
141
|
+
const mapStateProps = {
|
|
142
|
+
history,
|
|
143
|
+
urlConnected,
|
|
144
|
+
withSelectedEntities,
|
|
145
|
+
formName,
|
|
146
|
+
defaults: defaultsToUse,
|
|
147
|
+
isInfinite,
|
|
148
|
+
isSimple,
|
|
149
|
+
withPaging,
|
|
150
|
+
doNotCoercePageSize,
|
|
151
|
+
additionalFilter,
|
|
152
|
+
additionalOrFilter,
|
|
153
|
+
noOrderError,
|
|
154
|
+
withDisplayOptions,
|
|
155
|
+
cellRenderer,
|
|
156
|
+
isLocalCall,
|
|
157
|
+
model,
|
|
158
|
+
schema,
|
|
159
|
+
mergedOpts,
|
|
160
|
+
...getQueryParams({
|
|
161
|
+
doNotCoercePageSize,
|
|
162
|
+
currentParams,
|
|
163
|
+
entities: mergedOpts.entities, // for local table
|
|
164
|
+
urlConnected,
|
|
165
|
+
defaults: defaultsToUse,
|
|
166
|
+
schema: convertSchema(schema),
|
|
167
|
+
isInfinite: isInfinite || (isSimple && !withPaging),
|
|
168
|
+
isLocalCall,
|
|
169
|
+
additionalFilter: additionalFilterToUse,
|
|
170
|
+
additionalOrFilter: additionalOrFilterToUse,
|
|
171
|
+
noOrderError,
|
|
172
|
+
isCodeModel,
|
|
173
|
+
ownProps: mergedOpts
|
|
174
|
+
}),
|
|
175
|
+
formNameFromWithTPCall: formNameFromWithTableParamsCall,
|
|
176
|
+
randomVarToForceLocalStorageUpdate: formSelector(
|
|
177
|
+
state,
|
|
178
|
+
"localStorageForceUpdate"
|
|
179
|
+
),
|
|
180
|
+
currentParams,
|
|
181
|
+
selectedEntities,
|
|
182
|
+
...(withSelectedEntities &&
|
|
183
|
+
typeof withSelectedEntities === "string" && {
|
|
184
|
+
[withSelectedEntities]: selectedEntities
|
|
185
|
+
}),
|
|
186
|
+
initialValues: {
|
|
187
|
+
...initialValues,
|
|
188
|
+
reduxFormSearchInput: currentParams.searchTerm
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
return mapStateProps;
|
|
192
|
+
// return { ...mergedOpts, ...mapStateProps };
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const mapDispatchToProps = (dispatch, ownProps) => {
|
|
196
|
+
if (ownProps.isTableParamsConnected) {
|
|
197
|
+
return {};
|
|
198
|
+
}
|
|
199
|
+
const mergedOpts = getMergedOpts(topLevelOptions, ownProps);
|
|
200
|
+
const {
|
|
201
|
+
formName,
|
|
202
|
+
urlConnected,
|
|
203
|
+
history,
|
|
204
|
+
defaults,
|
|
205
|
+
onlyOneFilter
|
|
206
|
+
} = mergedOpts;
|
|
207
|
+
|
|
208
|
+
function updateSearch(val) {
|
|
209
|
+
setTimeout(function() {
|
|
210
|
+
dispatch(change(formName, "reduxFormSearchInput", val || ""));
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let setNewParams;
|
|
215
|
+
if (urlConnected) {
|
|
216
|
+
setNewParams = function(newParams) {
|
|
217
|
+
setCurrentParamsOnUrl(newParams, history.replace);
|
|
218
|
+
dispatch(change(formName, "reduxFormQueryParams", newParams)); //we always will update the redux params as a workaround for withRouter not always working if inside a redux-connected container https://github.com/ReactTraining/react-router/issues/5037
|
|
219
|
+
};
|
|
220
|
+
} else {
|
|
221
|
+
setNewParams = function(newParams) {
|
|
222
|
+
dispatch(change(formName, "reduxFormQueryParams", newParams));
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
bindThese: makeDataTableHandlers({
|
|
227
|
+
setNewParams,
|
|
228
|
+
updateSearch,
|
|
229
|
+
defaults,
|
|
230
|
+
onlyOneFilter
|
|
231
|
+
}),
|
|
232
|
+
dispatch
|
|
233
|
+
};
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
function mergeProps(stateProps, dispatchProps, ownProps) {
|
|
237
|
+
if (ownProps.isTableParamsConnected) {
|
|
238
|
+
return ownProps;
|
|
239
|
+
}
|
|
240
|
+
const { currentParams, formName } = stateProps;
|
|
241
|
+
const boundDispatchProps = {};
|
|
242
|
+
//bind currentParams to actions
|
|
243
|
+
Object.keys(dispatchProps.bindThese).forEach(function(key) {
|
|
244
|
+
const action = dispatchProps.bindThese[key];
|
|
245
|
+
boundDispatchProps[key] = function(...args) {
|
|
246
|
+
action(...args, currentParams);
|
|
247
|
+
};
|
|
248
|
+
});
|
|
249
|
+
const {
|
|
250
|
+
variables,
|
|
251
|
+
selectedEntities,
|
|
252
|
+
mergedOpts,
|
|
253
|
+
...restStateProps
|
|
254
|
+
} = stateProps;
|
|
255
|
+
|
|
256
|
+
const changeFormValue = (...args) =>
|
|
257
|
+
dispatchProps.dispatch(change(formName, ...args));
|
|
258
|
+
|
|
259
|
+
const tableParams = {
|
|
260
|
+
changeFormValue,
|
|
261
|
+
selectedEntities,
|
|
262
|
+
...ownProps.tableParams,
|
|
263
|
+
...restStateProps,
|
|
264
|
+
...boundDispatchProps,
|
|
265
|
+
form: formName, //this will override the default redux form name
|
|
266
|
+
isTableParamsConnected: true //let the table know not to do local sorting/filtering etc.
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const allMergedProps = {
|
|
270
|
+
...ownProps,
|
|
271
|
+
...mergedOpts,
|
|
272
|
+
variables: stateProps.variables,
|
|
273
|
+
selectedEntities: stateProps.selectedEntities,
|
|
274
|
+
tableParams
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
return allMergedProps;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function addFormTracking(Component) {
|
|
281
|
+
return props => {
|
|
282
|
+
const formTracker = useContext(TableFormTrackerContext);
|
|
283
|
+
const { formName } = props;
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
if (formTracker.isActive && !formTracker.formNames.includes(formName)) {
|
|
286
|
+
formTracker.pushFormName(formName);
|
|
287
|
+
}
|
|
288
|
+
}, [formTracker, formName]);
|
|
289
|
+
|
|
290
|
+
return <Component {...props} />;
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const toReturn = compose(
|
|
295
|
+
connect((state, ownProps) => {
|
|
296
|
+
if (ownProps.isTableParamsConnected) {
|
|
297
|
+
return {};
|
|
298
|
+
}
|
|
299
|
+
const { formName } = getMergedOpts(topLevelOptions, ownProps);
|
|
300
|
+
return {
|
|
301
|
+
unusedProp:
|
|
302
|
+
formValueSelector(formName)(state, "reduxFormQueryParams") || {} //tnr: we need this to trigger withRouter and force it to update if it is nested in a redux-connected container.. very ugly but necessary
|
|
303
|
+
};
|
|
304
|
+
}),
|
|
305
|
+
branch(props => {
|
|
306
|
+
//don't use withRouter if noRouter is passed!
|
|
307
|
+
return !props.noRouter;
|
|
308
|
+
}, withRouter),
|
|
309
|
+
connect(mapStateToProps, mapDispatchToProps, mergeProps),
|
|
310
|
+
pureNoFunc,
|
|
311
|
+
addFormTracking
|
|
312
|
+
);
|
|
313
|
+
if (Component) {
|
|
314
|
+
return toReturn(Component);
|
|
315
|
+
}
|
|
316
|
+
return toReturn;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Given the options, get the schema. This enables the user to provide
|
|
321
|
+
* a function instead of an object for the schema.
|
|
322
|
+
* @param {Object} options Merged options
|
|
323
|
+
*/
|
|
324
|
+
function getSchema(options) {
|
|
325
|
+
const { schema } = options;
|
|
326
|
+
if (isFunction(schema)) return schema(options);
|
|
327
|
+
else return schema;
|
|
328
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import getIdOrCodeOrIndex from "./utils/getIdOrCodeOrIndex";
|
|
2
|
+
import { getCellVal } from "./getCellVal";
|
|
3
|
+
import { forEach, isArray } from "lodash";
|
|
4
|
+
import { startCase } from "lodash";
|
|
5
|
+
import { camelCase } from "lodash";
|
|
6
|
+
|
|
7
|
+
const uniqueMsg = "This value must be unique";
|
|
8
|
+
export function validateTableWideErrors({
|
|
9
|
+
entities,
|
|
10
|
+
schema,
|
|
11
|
+
optionalUserSchema,
|
|
12
|
+
newCellValidate
|
|
13
|
+
}) {
|
|
14
|
+
forEach(newCellValidate, (err, cellId) => {
|
|
15
|
+
if (err && err._isTableWideError) {
|
|
16
|
+
delete newCellValidate[cellId];
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
if (schema.tableWideValidation) {
|
|
20
|
+
const newErrs = schema.tableWideValidation({
|
|
21
|
+
entities
|
|
22
|
+
});
|
|
23
|
+
forEach(newErrs, (err, cellId) => {
|
|
24
|
+
newCellValidate[cellId] = {
|
|
25
|
+
message: err,
|
|
26
|
+
_isTableWideError: true
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
const displayNameMap = {};
|
|
31
|
+
forEach(schema.fields, f => {
|
|
32
|
+
displayNameMap[f.path] = f.displayName || startCase(camelCase(f.path));
|
|
33
|
+
});
|
|
34
|
+
function getDisplayName(path) {
|
|
35
|
+
return displayNameMap[path] || startCase(camelCase(path));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (schema.requireAtLeastOneOf) {
|
|
39
|
+
//make sure at least one of the required fields is present
|
|
40
|
+
(isArray(schema.requireAtLeastOneOf[0])
|
|
41
|
+
? schema.requireAtLeastOneOf
|
|
42
|
+
: [schema.requireAtLeastOneOf]
|
|
43
|
+
).forEach(alo => {
|
|
44
|
+
entities.forEach(entity => {
|
|
45
|
+
if (!alo.some(path => getCellVal(entity, path))) {
|
|
46
|
+
alo.forEach(path => {
|
|
47
|
+
const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
|
|
48
|
+
newCellValidate[cellId] = {
|
|
49
|
+
message: `At least one of these fields must be present - ${alo
|
|
50
|
+
.map(getDisplayName)
|
|
51
|
+
.join(", ")}`,
|
|
52
|
+
_isTableWideError: true
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (schema.requireExactlyOneOf) {
|
|
60
|
+
(isArray(schema.requireExactlyOneOf[0])
|
|
61
|
+
? schema.requireExactlyOneOf
|
|
62
|
+
: [schema.requireExactlyOneOf]
|
|
63
|
+
).forEach(reqs => {
|
|
64
|
+
entities.forEach(entity => {
|
|
65
|
+
const present = reqs.filter(path => getCellVal(entity, path));
|
|
66
|
+
if (present.length !== 1) {
|
|
67
|
+
reqs.forEach(path => {
|
|
68
|
+
const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
|
|
69
|
+
const fields = reqs.map(getDisplayName).join(", ");
|
|
70
|
+
const err = {
|
|
71
|
+
message:
|
|
72
|
+
present.length === 0
|
|
73
|
+
? `One of these fields is required - ${fields}`
|
|
74
|
+
: `Cannot have more than one of these fields - ${fields}`,
|
|
75
|
+
_isTableWideError: true
|
|
76
|
+
};
|
|
77
|
+
newCellValidate[cellId] = err;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
if (schema.requireAllOrNone) {
|
|
84
|
+
(isArray(schema.requireAllOrNone[0])
|
|
85
|
+
? schema.requireAllOrNone
|
|
86
|
+
: [schema.requireAllOrNone]
|
|
87
|
+
).forEach(reqs => {
|
|
88
|
+
entities.forEach(entity => {
|
|
89
|
+
const present = reqs.filter(path => getCellVal(entity, path));
|
|
90
|
+
if (present.length && present.length !== reqs.length) {
|
|
91
|
+
reqs.forEach(path => {
|
|
92
|
+
const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
|
|
93
|
+
const fields = reqs.map(getDisplayName).join(", ");
|
|
94
|
+
const err = {
|
|
95
|
+
message: `Please specify either ALL of the following fields or NONE of them - ${fields}`,
|
|
96
|
+
_isTableWideError: true
|
|
97
|
+
};
|
|
98
|
+
newCellValidate[cellId] = err;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
schema.fields.forEach(col => {
|
|
106
|
+
let { path, isUnique } = col;
|
|
107
|
+
if (isUnique) {
|
|
108
|
+
if (optionalUserSchema) {
|
|
109
|
+
path = col.matches[0].item.path;
|
|
110
|
+
}
|
|
111
|
+
const existingVals = {};
|
|
112
|
+
entities.forEach(entity => {
|
|
113
|
+
const val = getCellVal(entity, path, col);
|
|
114
|
+
if (!val) return;
|
|
115
|
+
const cellId = `${getIdOrCodeOrIndex(entity)}:${path}`;
|
|
116
|
+
if (existingVals[val]) {
|
|
117
|
+
newCellValidate[cellId] = {
|
|
118
|
+
message: uniqueMsg,
|
|
119
|
+
_isTableWideError: true
|
|
120
|
+
};
|
|
121
|
+
newCellValidate[existingVals[val]] = {
|
|
122
|
+
message: uniqueMsg,
|
|
123
|
+
_isTableWideError: true
|
|
124
|
+
};
|
|
125
|
+
} else {
|
|
126
|
+
if (newCellValidate[cellId] === uniqueMsg) {
|
|
127
|
+
delete newCellValidate[cellId];
|
|
128
|
+
}
|
|
129
|
+
existingVals[val] = cellId;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
return newCellValidate;
|
|
135
|
+
}
|