@teselagen/ui 0.7.33-beta.6 → 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 +14 -7
- 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 +972 -837
- package/index.d.ts +0 -1
- package/index.es.js +972 -837
- 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/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
package/toastr.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Position, Toaster, Intent, Classes } from "@blueprintjs/core";
|
|
2
|
+
import classNames from "classnames";
|
|
3
|
+
|
|
4
|
+
const TopToaster = Toaster.create({
|
|
5
|
+
className: "top-toaster",
|
|
6
|
+
position: Position.TOP
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
const BottomToaster = Toaster.create({
|
|
10
|
+
className: "bottom-toaster",
|
|
11
|
+
position: Position.BOTTOM
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
window.__tgClearAllToasts = () => {
|
|
15
|
+
TopToaster.clear();
|
|
16
|
+
BottomToaster.clear();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
let counter = 5000;
|
|
20
|
+
const generateToast = intent => (message, options) => {
|
|
21
|
+
options = options || {};
|
|
22
|
+
const toastToUse = options.bottom ? BottomToaster : TopToaster;
|
|
23
|
+
let updatedTimeout;
|
|
24
|
+
if (options.updateTimeout) {
|
|
25
|
+
//generate a slightly different than default timeout to make the update stay on the page for a full 5 seconds
|
|
26
|
+
if (counter > 5500) {
|
|
27
|
+
updatedTimeout = --counter;
|
|
28
|
+
} else {
|
|
29
|
+
updatedTimeout = ++counter;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
if (intent === Intent.DANGER) {
|
|
33
|
+
console.error("Toastr error message: ", message);
|
|
34
|
+
}
|
|
35
|
+
if (intent === Intent.WARNING) {
|
|
36
|
+
console.error("Toastr warning message: ", message);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const maybeAddClearAll = () => {
|
|
40
|
+
// wipe any existing clear all buttons
|
|
41
|
+
const existingClearAllButtons =
|
|
42
|
+
document.querySelectorAll(`.tg-clear-all-toasts`);
|
|
43
|
+
existingClearAllButtons.forEach(button => {
|
|
44
|
+
button.remove();
|
|
45
|
+
});
|
|
46
|
+
const activeToasts = document.querySelectorAll(
|
|
47
|
+
`.bp3-toast:not(.bp3-toast-exit)`
|
|
48
|
+
);
|
|
49
|
+
if (activeToasts.length > 1) {
|
|
50
|
+
// add custom clear all button
|
|
51
|
+
|
|
52
|
+
const topToaster = document.querySelector(`.bp3-toast`);
|
|
53
|
+
if (!topToaster) return;
|
|
54
|
+
const closeButton = document.createElement("div");
|
|
55
|
+
closeButton.classList.add(
|
|
56
|
+
Classes.BUTTON,
|
|
57
|
+
Classes.LARGE,
|
|
58
|
+
Classes.INTENT_PRIMARY,
|
|
59
|
+
"tg-clear-all-toasts"
|
|
60
|
+
);
|
|
61
|
+
closeButton.innerText = "Clear all";
|
|
62
|
+
closeButton.onclick = window.__tgClearAllToasts;
|
|
63
|
+
// position the button to the right of the message
|
|
64
|
+
closeButton.style.position = "absolute";
|
|
65
|
+
closeButton.style.right = "-100px";
|
|
66
|
+
|
|
67
|
+
topToaster.appendChild(closeButton);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const uniqKey = toastToUse.show(
|
|
71
|
+
{
|
|
72
|
+
intent,
|
|
73
|
+
message,
|
|
74
|
+
onDismiss: () => {
|
|
75
|
+
if (options.onDismiss) {
|
|
76
|
+
options.onDismiss();
|
|
77
|
+
}
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
maybeAddClearAll();
|
|
80
|
+
}, 0);
|
|
81
|
+
},
|
|
82
|
+
timeout:
|
|
83
|
+
options.timeout ||
|
|
84
|
+
updatedTimeout ||
|
|
85
|
+
(!window.Cypress &&
|
|
86
|
+
(intent === Intent.DANGER || intent === Intent.WARNING)
|
|
87
|
+
? 60000
|
|
88
|
+
: undefined),
|
|
89
|
+
action: options.action,
|
|
90
|
+
icon: options.icon,
|
|
91
|
+
className: classNames("preserve-newline", options.className)
|
|
92
|
+
},
|
|
93
|
+
options.key
|
|
94
|
+
);
|
|
95
|
+
setTimeout(() => {
|
|
96
|
+
maybeAddClearAll();
|
|
97
|
+
}, 0);
|
|
98
|
+
function clear() {
|
|
99
|
+
toastToUse.dismiss(uniqKey);
|
|
100
|
+
setTimeout(() => {
|
|
101
|
+
maybeAddClearAll();
|
|
102
|
+
}, 0);
|
|
103
|
+
}
|
|
104
|
+
clear.key = uniqKey;
|
|
105
|
+
return clear;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
function preventDuplicates(func) {
|
|
109
|
+
const previousToasts = {};
|
|
110
|
+
return (message, options = {}) => {
|
|
111
|
+
const clearToast = func(message, options);
|
|
112
|
+
// no duplicate check for toasts with updates
|
|
113
|
+
|
|
114
|
+
if (!options.key) {
|
|
115
|
+
if (!options.key && previousToasts[message]) {
|
|
116
|
+
previousToasts[message](); //clear it!
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
setTimeout(() => {
|
|
120
|
+
delete previousToasts[message];
|
|
121
|
+
}, options.timeout || 5000);
|
|
122
|
+
|
|
123
|
+
previousToasts[message] = clearToast;
|
|
124
|
+
}
|
|
125
|
+
return clearToast;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!window.toastr) window.toastr = {};
|
|
130
|
+
if (!window.toastr.success) {
|
|
131
|
+
window.toastr.success = preventDuplicates(generateToast(Intent.SUCCESS));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!window.toastr.error) {
|
|
135
|
+
window.toastr.error = preventDuplicates(generateToast(Intent.DANGER));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!window.toastr.warning) {
|
|
139
|
+
window.toastr.warning = preventDuplicates(generateToast(Intent.WARNING));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!window.toastr.info) {
|
|
143
|
+
window.toastr.info = preventDuplicates(generateToast(Intent.PRIMARY));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!window.toastr.default) {
|
|
147
|
+
window.toastr.default = preventDuplicates(generateToast(Intent.NONE));
|
|
148
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { forEach, isArray, isPlainObject, map, snakeCase } from "lodash-es";
|
|
2
|
+
import { nanoid } from "nanoid";
|
|
3
|
+
import Fuse from "fuse.js";
|
|
4
|
+
import { editCellHelper } from "../DataTable/editCellHelper";
|
|
5
|
+
import { validateTableWideErrors } from "../DataTable/validateTableWideErrors";
|
|
6
|
+
import { isEmpty } from "lodash-es";
|
|
7
|
+
import getTextFromEl from "../utils/getTextFromEl";
|
|
8
|
+
import { min } from "lodash-es";
|
|
9
|
+
import { camelCase } from "lodash-es";
|
|
10
|
+
import { startCase } from "lodash-es";
|
|
11
|
+
|
|
12
|
+
const getSchema = data => ({
|
|
13
|
+
fields: map(data[0], (val, path) => {
|
|
14
|
+
return { path, type: "string" };
|
|
15
|
+
}),
|
|
16
|
+
userData: data
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export default async function tryToMatchSchemas({
|
|
20
|
+
incomingData,
|
|
21
|
+
validateAgainstSchema
|
|
22
|
+
}) {
|
|
23
|
+
await resolveValidateAgainstSchema(validateAgainstSchema);
|
|
24
|
+
const userSchema = getSchema(incomingData);
|
|
25
|
+
|
|
26
|
+
const { searchResults, csvValidationIssue, ignoredHeadersMsg } =
|
|
27
|
+
await matchSchemas({
|
|
28
|
+
userSchema,
|
|
29
|
+
officialSchema: validateAgainstSchema
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const incomingHeadersToScores = {};
|
|
33
|
+
|
|
34
|
+
searchResults.forEach(r => {
|
|
35
|
+
r.matches.forEach(match => {
|
|
36
|
+
incomingHeadersToScores[match.item.path] =
|
|
37
|
+
incomingHeadersToScores[match.item.path] || [];
|
|
38
|
+
incomingHeadersToScores[match.item.path].push(match.score);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
searchResults.forEach(r => {
|
|
43
|
+
for (const match of r.matches) {
|
|
44
|
+
if (!incomingHeadersToScores[match.item.path]) continue;
|
|
45
|
+
const minScore = min(incomingHeadersToScores[match.item.path]);
|
|
46
|
+
if (minScore === match.score) {
|
|
47
|
+
r.topMatch = match.item.path;
|
|
48
|
+
r.matches.forEach(match => {
|
|
49
|
+
if (!incomingHeadersToScores[match.item.path]) return;
|
|
50
|
+
const arr = incomingHeadersToScores[match.item.path];
|
|
51
|
+
arr.splice(arr.indexOf(match.score), 1);
|
|
52
|
+
});
|
|
53
|
+
delete incomingHeadersToScores[match.item.path];
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const matchedHeaders = {};
|
|
59
|
+
searchResults.forEach(r => {
|
|
60
|
+
if (r.topMatch) {
|
|
61
|
+
matchedHeaders[r.path] = r.topMatch;
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
ignoredHeadersMsg,
|
|
67
|
+
csvValidationIssue,
|
|
68
|
+
matchedHeaders,
|
|
69
|
+
userSchema,
|
|
70
|
+
searchResults
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function matchSchemas({ userSchema, officialSchema }) {
|
|
75
|
+
const options = {
|
|
76
|
+
includeScore: true,
|
|
77
|
+
keys: ["path", "displayName"]
|
|
78
|
+
};
|
|
79
|
+
let csvValidationIssue = false;
|
|
80
|
+
const fuse = new Fuse(userSchema.fields, options);
|
|
81
|
+
|
|
82
|
+
const matchedAltPaths = [];
|
|
83
|
+
officialSchema.fields.forEach(h => {
|
|
84
|
+
let hasMatch = false;
|
|
85
|
+
//run fuse search for results
|
|
86
|
+
let result = fuse.search(h.path) || [];
|
|
87
|
+
|
|
88
|
+
const hadNormalPathMatch = userSchema.fields.some(
|
|
89
|
+
uh => norm(uh.path) === norm(h.path)
|
|
90
|
+
);
|
|
91
|
+
//if there are any exact matches, push them onto the results array
|
|
92
|
+
userSchema.fields.forEach((uh, i) => {
|
|
93
|
+
const pathMatch = norm(uh.path) === norm(h.path);
|
|
94
|
+
const displayNameMatch =
|
|
95
|
+
h.displayName && norm(uh.path) === norm(getTextFromEl(h.displayName));
|
|
96
|
+
const hasAlternatePathMatch =
|
|
97
|
+
!hadNormalPathMatch &&
|
|
98
|
+
h.alternatePathMatch &&
|
|
99
|
+
(isArray(h.alternatePathMatch)
|
|
100
|
+
? h.alternatePathMatch
|
|
101
|
+
: [h.alternatePathMatch]
|
|
102
|
+
).find(alternatePathMatch => {
|
|
103
|
+
let altPath = alternatePathMatch;
|
|
104
|
+
if (isPlainObject(alternatePathMatch)) {
|
|
105
|
+
altPath = alternatePathMatch.path;
|
|
106
|
+
}
|
|
107
|
+
return norm(uh.path) === norm(altPath);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (hasAlternatePathMatch) {
|
|
111
|
+
matchedAltPaths.push(
|
|
112
|
+
hasAlternatePathMatch.path || hasAlternatePathMatch
|
|
113
|
+
);
|
|
114
|
+
if (hasAlternatePathMatch.format) {
|
|
115
|
+
h.format = hasAlternatePathMatch.format;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (pathMatch || displayNameMatch || hasAlternatePathMatch) {
|
|
119
|
+
result = result.filter(({ path }) => path === uh.path);
|
|
120
|
+
//add a fake perfect match result to make sure we get the match
|
|
121
|
+
result.unshift({
|
|
122
|
+
item: {
|
|
123
|
+
path: uh.path,
|
|
124
|
+
type: h.type
|
|
125
|
+
},
|
|
126
|
+
refIndex: i,
|
|
127
|
+
score: 0
|
|
128
|
+
});
|
|
129
|
+
hasMatch = true;
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
h.hasMatch = hasMatch;
|
|
133
|
+
h.matches = result;
|
|
134
|
+
|
|
135
|
+
if (!hasMatch && h.isRequired) {
|
|
136
|
+
csvValidationIssue =
|
|
137
|
+
"It looks like some of the headers in your uploaded file(s) do not match the expected headers. Please look over and correct any issues with the mappings below.";
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
const ignoredUserSchemaFields = [];
|
|
141
|
+
userSchema.fields.forEach(uh => {
|
|
142
|
+
if (
|
|
143
|
+
!officialSchema.fields.find(
|
|
144
|
+
h =>
|
|
145
|
+
norm(h.path) === norm(uh.path) ||
|
|
146
|
+
norm(h.displayName) === norm(uh.path) ||
|
|
147
|
+
matchedAltPaths.includes(uh.path)
|
|
148
|
+
)
|
|
149
|
+
) {
|
|
150
|
+
// check that the column does contain data (if it doesn't, it's probably a blank column)
|
|
151
|
+
if (userSchema.userData.some(e => e[uh.path])) {
|
|
152
|
+
ignoredUserSchemaFields.push(uh);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
if (officialSchema.coerceUserSchema) {
|
|
158
|
+
officialSchema.coerceUserSchema({ userSchema, officialSchema });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
userSchema.userData = map(userSchema.userData, e => {
|
|
162
|
+
if (!e.id) {
|
|
163
|
+
return {
|
|
164
|
+
...e,
|
|
165
|
+
id: "__generated__" + nanoid()
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return e;
|
|
169
|
+
});
|
|
170
|
+
const editableFields = officialSchema.fields.filter(f => !f.isNotEditable);
|
|
171
|
+
let hasErr;
|
|
172
|
+
if (!csvValidationIssue) {
|
|
173
|
+
userSchema.userData.some(e => {
|
|
174
|
+
return editableFields.some(columnSchema => {
|
|
175
|
+
//mutative
|
|
176
|
+
const { error } = editCellHelper({
|
|
177
|
+
entity: e,
|
|
178
|
+
columnSchema,
|
|
179
|
+
newVal: columnSchema.hasMatch
|
|
180
|
+
? e[columnSchema.matches[0]?.item?.path]
|
|
181
|
+
: undefined
|
|
182
|
+
});
|
|
183
|
+
if (error) {
|
|
184
|
+
hasErr = `${
|
|
185
|
+
columnSchema.displayName || startCase(camelCase(columnSchema.path))
|
|
186
|
+
}: ${error}`;
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return false;
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
if (!hasErr) {
|
|
194
|
+
hasErr = Object.values(
|
|
195
|
+
validateTableWideErrors({
|
|
196
|
+
entities: userSchema.userData,
|
|
197
|
+
schema: officialSchema,
|
|
198
|
+
optionalUserSchema: userSchema,
|
|
199
|
+
newCellValidate: {}
|
|
200
|
+
})
|
|
201
|
+
)[0];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (officialSchema.tableWideAsyncValidation) {
|
|
206
|
+
//do the table wide validation
|
|
207
|
+
const res = await officialSchema.tableWideAsyncValidation({
|
|
208
|
+
entities: userSchema.userData
|
|
209
|
+
});
|
|
210
|
+
if (!isEmpty(res)) {
|
|
211
|
+
csvValidationIssue = addSpecialPropToAsyncErrs(res);
|
|
212
|
+
}
|
|
213
|
+
//return errors on the tables
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (hasErr && !csvValidationIssue) {
|
|
217
|
+
if (hasErr.message) {
|
|
218
|
+
csvValidationIssue = hasErr;
|
|
219
|
+
} else {
|
|
220
|
+
csvValidationIssue = {
|
|
221
|
+
message: hasErr
|
|
222
|
+
};
|
|
223
|
+
// csvValidationIssue = ` Some of the data doesn't look quite right. Do these header mappings look correct?`;
|
|
224
|
+
}
|
|
225
|
+
// csvValidationIssue = `Some of the data doesn't look quite right. Do these header mappings look correct?`;
|
|
226
|
+
}
|
|
227
|
+
let ignoredHeadersMsg;
|
|
228
|
+
if (ignoredUserSchemaFields.length) {
|
|
229
|
+
ignoredHeadersMsg = `It looks like the following headers in your file didn't map to any of the accepted headers: ${ignoredUserSchemaFields
|
|
230
|
+
.map(f => f.displayName || f.path)
|
|
231
|
+
.join(", ")}`;
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
searchResults: officialSchema.fields,
|
|
235
|
+
csvValidationIssue,
|
|
236
|
+
ignoredHeadersMsg
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export const addSpecialPropToAsyncErrs = res => {
|
|
241
|
+
forEach(res, (v, k) => {
|
|
242
|
+
res[k] = {
|
|
243
|
+
message: v,
|
|
244
|
+
_isTableAsyncWideError: true
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
return res;
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
async function resolveValidateAgainstSchema() {
|
|
251
|
+
//tnw: wip!
|
|
252
|
+
// mapSeries(validateAgainstSchema.fields, async f => {
|
|
253
|
+
// if (f.type === "dropdown") {
|
|
254
|
+
// console.log(`type:`, f.type)
|
|
255
|
+
// if (f.getValues) {
|
|
256
|
+
// f.values = await f.getValues(props);
|
|
257
|
+
// }
|
|
258
|
+
// }
|
|
259
|
+
// })
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function norm(h) {
|
|
263
|
+
return snakeCase(h).toLowerCase().replace(/ /g, "");
|
|
264
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { isEqual } from "lodash-es";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
3
|
+
|
|
4
|
+
export const useDeepEqualMemo = value => {
|
|
5
|
+
const ref = useRef();
|
|
6
|
+
if (!isEqual(value, ref.current)) {
|
|
7
|
+
ref.current = value;
|
|
8
|
+
}
|
|
9
|
+
return ref.current;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const useDeepEqualEffect = (effect, deps) => {
|
|
13
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
14
|
+
return useEffect(effect, useDeepEqualMemo(deps));
|
|
15
|
+
};
|
package/useDialog.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
|
|
5
|
+
const {toggleDialog, comp} = useDialog({
|
|
6
|
+
ModalComponent: SimpleInsertData,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
return <div>
|
|
10
|
+
{comp} //stick the returned dialog comp somewhere in the dom! (it should not effect layout)
|
|
11
|
+
{...your code here}
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
*/
|
|
15
|
+
export const useDialog = ({ ModalComponent }) => {
|
|
16
|
+
const [isOpen, setOpen] = useState(false);
|
|
17
|
+
const [additionalProps, setAdditionalProps] = useState(false);
|
|
18
|
+
const Comp = useCallback(
|
|
19
|
+
() => (
|
|
20
|
+
<ModalComponent
|
|
21
|
+
hideModal={() => {
|
|
22
|
+
setOpen(false);
|
|
23
|
+
}}
|
|
24
|
+
hideDialog={() => {
|
|
25
|
+
setOpen(false);
|
|
26
|
+
}}
|
|
27
|
+
{...additionalProps}
|
|
28
|
+
dialogProps={{
|
|
29
|
+
isOpen,
|
|
30
|
+
...additionalProps?.dialogProps
|
|
31
|
+
}}
|
|
32
|
+
/>
|
|
33
|
+
),
|
|
34
|
+
[ModalComponent, additionalProps, isOpen]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
const showDialogPromise = useCallback(async (handlerName, moreProps = {}) => {
|
|
38
|
+
return new Promise(resolve => {
|
|
39
|
+
//return a promise that can be awaited
|
|
40
|
+
setAdditionalProps({
|
|
41
|
+
hideModal: () => {
|
|
42
|
+
//override hideModal to resolve also
|
|
43
|
+
setOpen(false);
|
|
44
|
+
resolve({});
|
|
45
|
+
},
|
|
46
|
+
hideDialog: () => {
|
|
47
|
+
//override hideModal to resolve also
|
|
48
|
+
setOpen(false);
|
|
49
|
+
resolve({});
|
|
50
|
+
},
|
|
51
|
+
[handlerName]: r => {
|
|
52
|
+
setOpen(false);
|
|
53
|
+
resolve(r || {});
|
|
54
|
+
},
|
|
55
|
+
//pass any additional props to the dialog
|
|
56
|
+
...moreProps
|
|
57
|
+
});
|
|
58
|
+
setOpen(true); //open the dialog
|
|
59
|
+
});
|
|
60
|
+
}, []);
|
|
61
|
+
|
|
62
|
+
return { Comp, showDialogPromise };
|
|
63
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { useDispatch, useSelector } from "react-redux";
|
|
3
|
+
import { change, initialize } from "redux-form";
|
|
4
|
+
|
|
5
|
+
export const useTableEntities = tableFormName => {
|
|
6
|
+
const dispatch = useDispatch();
|
|
7
|
+
const selectTableEntities = useCallback(
|
|
8
|
+
(entities = []) => {
|
|
9
|
+
initialize(tableFormName, {}, true, {
|
|
10
|
+
keepDirty: true,
|
|
11
|
+
updateUnregisteredFields: true,
|
|
12
|
+
keepValues: true
|
|
13
|
+
});
|
|
14
|
+
const selectedEntityIdMap = {};
|
|
15
|
+
entities.forEach(entity => {
|
|
16
|
+
selectedEntityIdMap[entity.id] = {
|
|
17
|
+
entity,
|
|
18
|
+
time: Date.now()
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
dispatch(
|
|
22
|
+
change(
|
|
23
|
+
tableFormName,
|
|
24
|
+
"reduxFormSelectedEntityIdMap",
|
|
25
|
+
selectedEntityIdMap
|
|
26
|
+
)
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
[dispatch, tableFormName]
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const { allOrderedEntities, selectedEntities } = useSelector(state => ({
|
|
33
|
+
allOrderedEntities: state.form?.[tableFormName]?.values?.allOrderedEntities,
|
|
34
|
+
selectedEntities:
|
|
35
|
+
state.form?.[tableFormName]?.values?.reduxFormSelectedEntityIdMap
|
|
36
|
+
}));
|
|
37
|
+
return { selectTableEntities, allOrderedEntities, selectedEntities };
|
|
38
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
|
|
3
|
+
// useful for debugging rerender errors
|
|
4
|
+
export const useTraceUpdate = props => {
|
|
5
|
+
const prev = useRef(props);
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
|
|
8
|
+
if (prev.current[k] !== v) {
|
|
9
|
+
ps[k] = [prev.current[k], v];
|
|
10
|
+
}
|
|
11
|
+
return ps;
|
|
12
|
+
}, {});
|
|
13
|
+
if (Object.keys(changedProps).length > 0) {
|
|
14
|
+
// eslint-disable-next-line no-console
|
|
15
|
+
console.log("Changed props:", changedProps);
|
|
16
|
+
}
|
|
17
|
+
prev.current = props;
|
|
18
|
+
});
|
|
19
|
+
};
|
package/utils.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex";
|
|
2
|
+
|
|
3
|
+
export const getFieldPathToIndex = schema => {
|
|
4
|
+
const fieldToIndex = {};
|
|
5
|
+
schema.fields.forEach((f, i) => {
|
|
6
|
+
fieldToIndex[f.path] = i;
|
|
7
|
+
});
|
|
8
|
+
return fieldToIndex;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const defaultParsePaste = str => {
|
|
12
|
+
return str.split(/\r\n|\n|\r/).map(row => row.split("\t"));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const getEntityIdToEntity = entities => {
|
|
16
|
+
const entityIdToEntity = {};
|
|
17
|
+
entities.forEach((e, i) => {
|
|
18
|
+
entityIdToEntity[getIdOrCodeOrIndex(e, i)] = { e, i };
|
|
19
|
+
});
|
|
20
|
+
return entityIdToEntity;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const endsWithNumber = str => {
|
|
24
|
+
return /[0-9]+$/.test(str);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const getNumberStrAtEnd = str => {
|
|
28
|
+
if (endsWithNumber(str)) {
|
|
29
|
+
return str.match(/[0-9]+$/)[0];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const stripNumberAtEnd = str => {
|
|
36
|
+
return str?.replace?.(getNumberStrAtEnd(str), "");
|
|
37
|
+
};
|