@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.
Files changed (125) hide show
  1. package/README.md +7 -0
  2. package/cypress.config.ts +6 -0
  3. package/index.html +12 -0
  4. package/package.json +5 -11
  5. package/project.json +74 -0
  6. package/src/AdvancedOptions.js +33 -0
  7. package/src/AdvancedOptions.spec.js +24 -0
  8. package/src/AssignDefaultsModeContext.js +21 -0
  9. package/src/AsyncValidateFieldSpinner/index.js +12 -0
  10. package/src/BlueprintError/index.js +14 -0
  11. package/src/BounceLoader/index.js +16 -0
  12. package/src/BounceLoader/style.css +45 -0
  13. package/src/CollapsibleCard/index.js +92 -0
  14. package/src/CollapsibleCard/style.css +21 -0
  15. package/src/DNALoader/index.js +20 -0
  16. package/src/DNALoader/style.css +251 -0
  17. package/src/DataTable/CellDragHandle.js +130 -0
  18. package/src/DataTable/DisabledLoadingComponent.js +15 -0
  19. package/src/DataTable/DisplayOptions.js +218 -0
  20. package/src/DataTable/FilterAndSortMenu.js +397 -0
  21. package/src/DataTable/PagingTool.js +232 -0
  22. package/src/DataTable/SearchBar.js +57 -0
  23. package/src/DataTable/SortableColumns.js +53 -0
  24. package/src/DataTable/TableFormTrackerContext.js +10 -0
  25. package/src/DataTable/dataTableEnhancer.js +291 -0
  26. package/src/DataTable/defaultFormatters.js +32 -0
  27. package/src/DataTable/defaultProps.js +45 -0
  28. package/src/DataTable/defaultValidators.js +40 -0
  29. package/src/DataTable/editCellHelper.js +44 -0
  30. package/src/DataTable/getCellVal.js +20 -0
  31. package/src/DataTable/getVals.js +8 -0
  32. package/src/DataTable/index.js +3537 -0
  33. package/src/DataTable/isTruthy.js +12 -0
  34. package/src/DataTable/isValueEmpty.js +3 -0
  35. package/src/DataTable/style.css +600 -0
  36. package/src/DataTable/utils/computePresets.js +42 -0
  37. package/src/DataTable/utils/convertSchema.js +69 -0
  38. package/src/DataTable/utils/getIdOrCodeOrIndex.js +9 -0
  39. package/src/DataTable/utils/getTableConfigFromStorage.js +5 -0
  40. package/src/DataTable/utils/queryParams.js +1032 -0
  41. package/src/DataTable/utils/rowClick.js +156 -0
  42. package/src/DataTable/utils/selection.js +8 -0
  43. package/src/DataTable/utils/withSelectedEntities.js +65 -0
  44. package/src/DataTable/utils/withTableParams.js +328 -0
  45. package/src/DataTable/validateTableWideErrors.js +135 -0
  46. package/src/DataTable/viewColumn.js +37 -0
  47. package/src/DialogFooter/index.js +79 -0
  48. package/src/DialogFooter/style.css +9 -0
  49. package/src/DropdownButton.js +36 -0
  50. package/src/FillWindow.css +6 -0
  51. package/src/FillWindow.js +69 -0
  52. package/src/FormComponents/Uploader.js +1197 -0
  53. package/src/FormComponents/getNewName.js +31 -0
  54. package/src/FormComponents/index.js +1384 -0
  55. package/src/FormComponents/itemUpload.js +84 -0
  56. package/src/FormComponents/sortify.js +73 -0
  57. package/src/FormComponents/style.css +247 -0
  58. package/src/FormComponents/tryToMatchSchemas.js +222 -0
  59. package/src/FormComponents/utils.js +6 -0
  60. package/src/HotkeysDialog/index.js +79 -0
  61. package/src/HotkeysDialog/style.css +54 -0
  62. package/src/InfoHelper/index.js +83 -0
  63. package/src/InfoHelper/style.css +7 -0
  64. package/src/IntentText/index.js +18 -0
  65. package/src/Loading/index.js +74 -0
  66. package/src/Loading/style.css +4 -0
  67. package/src/MatchHeaders.js +223 -0
  68. package/src/MenuBar/index.js +416 -0
  69. package/src/MenuBar/style.css +45 -0
  70. package/src/PromptUnsavedChanges/index.js +40 -0
  71. package/src/ResizableDraggableDialog/index.js +138 -0
  72. package/src/ResizableDraggableDialog/style.css +42 -0
  73. package/src/ScrollToTop/index.js +72 -0
  74. package/src/SimpleStepViz.js +26 -0
  75. package/src/TgSelect/index.js +465 -0
  76. package/src/TgSelect/style.css +34 -0
  77. package/src/TgSuggest/index.js +121 -0
  78. package/src/Timeline/TimelineEvent.js +31 -0
  79. package/src/Timeline/index.js +22 -0
  80. package/src/Timeline/style.css +29 -0
  81. package/src/UploadCsvWizard.css +4 -0
  82. package/src/UploadCsvWizard.js +731 -0
  83. package/src/autoTooltip.js +89 -0
  84. package/src/constants.js +1 -0
  85. package/src/customIcons.js +361 -0
  86. package/src/enhancers/withDialog/index.js +196 -0
  87. package/src/enhancers/withDialog/tg_modalState.js +46 -0
  88. package/src/enhancers/withField.js +20 -0
  89. package/src/enhancers/withFields.js +11 -0
  90. package/src/enhancers/withLocalStorage.js +11 -0
  91. package/src/index.js +76 -0
  92. package/src/rerenderOnWindowResize.js +27 -0
  93. package/src/showAppSpinner.js +12 -0
  94. package/src/showConfirmationDialog/index.js +116 -0
  95. package/src/showDialogOnDocBody.js +37 -0
  96. package/src/style.css +214 -0
  97. package/src/toastr.js +92 -0
  98. package/src/typeToCommonType.js +6 -0
  99. package/src/useDialog.js +64 -0
  100. package/src/utils/S3Download.js +14 -0
  101. package/src/utils/adHoc.js +10 -0
  102. package/src/utils/basicHandleActionsWithFullState.js +14 -0
  103. package/src/utils/combineReducersWithFullState.js +14 -0
  104. package/src/utils/commandControls.js +83 -0
  105. package/src/utils/commandUtils.js +112 -0
  106. package/src/utils/determineBlackOrWhiteTextColor.js +4 -0
  107. package/src/utils/getDayjsFormatter.js +35 -0
  108. package/src/utils/getTextFromEl.js +28 -0
  109. package/src/utils/handlerHelpers.js +30 -0
  110. package/src/utils/hotkeyUtils.js +129 -0
  111. package/src/utils/menuUtils.js +402 -0
  112. package/src/utils/popoverOverflowModifiers.js +11 -0
  113. package/src/utils/pureNoFunc.js +31 -0
  114. package/src/utils/renderOnDoc.js +29 -0
  115. package/src/utils/showProgressToast.js +22 -0
  116. package/src/utils/tagUtils.js +45 -0
  117. package/src/utils/tgFormValues.js +32 -0
  118. package/src/utils/withSelectTableRecords.js +38 -0
  119. package/src/utils/withStore.js +10 -0
  120. package/src/wrapDialog.js +112 -0
  121. package/tsconfig.json +4 -0
  122. package/vite.config.ts +7 -0
  123. package/index.mjs +0 -109378
  124. package/index.umd.js +0 -109381
  125. 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
+ }