@teselagen/ui 0.7.33-beta.1 → 0.7.33-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/AdvancedOptions.js +33 -0
- package/src/AssignDefaultsModeContext.js +22 -0
- package/src/CellDragHandle.js +132 -0
- package/src/ColumnFilterMenu.js +62 -0
- package/src/Columns.js +979 -0
- package/src/DisabledLoadingComponent.js +15 -0
- package/src/DisplayOptions.js +199 -0
- package/src/DropdownButton.js +36 -0
- package/src/DropdownCell.js +61 -0
- package/src/EditableCell.js +44 -0
- package/src/FillWindow.css +6 -0
- package/src/FillWindow.js +69 -0
- package/src/FilterAndSortMenu.js +388 -0
- package/src/FormSeparator.js +9 -0
- package/src/LoadingDots.js +14 -0
- package/src/MatchHeaders.js +234 -0
- package/src/PagingTool.js +225 -0
- package/src/RenderCell.js +191 -0
- package/src/SearchBar.js +69 -0
- package/src/SimpleStepViz.js +22 -0
- package/src/SortableColumns.js +100 -0
- package/src/TableFormTrackerContext.js +10 -0
- package/src/Tag.js +112 -0
- package/src/ThComponent.js +44 -0
- package/src/TimelineEvent.js +31 -0
- package/src/UploadCsvWizard.css +4 -0
- package/src/UploadCsvWizard.js +719 -0
- package/src/Uploader.js +1278 -0
- package/src/adHoc.js +10 -0
- package/src/autoTooltip.js +201 -0
- package/src/basicHandleActionsWithFullState.js +14 -0
- package/src/browserUtils.js +3 -0
- package/src/combineReducersWithFullState.js +14 -0
- package/src/commandControls.js +82 -0
- package/src/commandUtils.js +112 -0
- package/src/constants.js +1 -0
- package/src/convertSchema.js +69 -0
- package/src/customIcons.js +361 -0
- package/src/dataTableEnhancer.js +41 -0
- package/src/defaultFormatters.js +32 -0
- package/src/defaultValidators.js +40 -0
- package/src/determineBlackOrWhiteTextColor.js +4 -0
- package/src/editCellHelper.js +44 -0
- package/src/filterLocalEntitiesToHasura.js +216 -0
- package/src/formatPasteData.js +16 -0
- package/src/getAllRows.js +11 -0
- package/src/getCellCopyText.js +7 -0
- package/src/getCellInfo.js +36 -0
- package/src/getCellVal.js +20 -0
- package/src/getDayjsFormatter.js +35 -0
- package/src/getFieldPathToField.js +7 -0
- package/src/getIdOrCodeOrIndex.js +9 -0
- package/src/getLastSelectedEntity.js +11 -0
- package/src/getNewEntToSelect.js +25 -0
- package/src/getNewName.js +31 -0
- package/src/getRowCopyText.js +28 -0
- package/src/getTableConfigFromStorage.js +5 -0
- package/src/getTextFromEl.js +28 -0
- package/src/getVals.js +8 -0
- package/src/handleCopyColumn.js +21 -0
- package/src/handleCopyHelper.js +15 -0
- package/src/handleCopyRows.js +23 -0
- package/src/handleCopyTable.js +16 -0
- package/src/handlerHelpers.js +24 -0
- package/src/hotkeyUtils.js +131 -0
- package/src/index.js +1 -0
- package/src/initializeHasuraWhereAndFilter.js +27 -0
- package/src/isBeingCalledExcessively.js +24 -0
- package/src/isBottomRightCornerOfRectangle.js +20 -0
- package/src/isEntityClean.js +15 -0
- package/src/isTruthy.js +12 -0
- package/src/isValueEmpty.js +3 -0
- package/src/itemUpload.js +84 -0
- package/src/menuUtils.js +433 -0
- package/src/popoverOverflowModifiers.js +11 -0
- package/src/primarySelectedValue.js +1 -0
- package/src/pureNoFunc.js +31 -0
- package/src/queryParams.js +336 -0
- package/src/removeCleanRows.js +22 -0
- package/src/renderOnDoc.js +32 -0
- package/src/rerenderOnWindowResize.js +26 -0
- package/src/rowClick.js +181 -0
- package/src/selection.js +8 -0
- package/src/showAppSpinner.js +12 -0
- package/src/showDialogOnDocBody.js +33 -0
- package/src/showProgressToast.js +22 -0
- package/src/simplifyHasuraWhere.js +80 -0
- package/src/sortify.js +73 -0
- package/src/style.css +29 -0
- package/src/tableQueryParamsToHasuraClauses.js +113 -0
- package/src/tagUtils.js +45 -0
- package/src/tgFormValues.js +35 -0
- package/src/tg_modalState.js +47 -0
- package/src/throwFormError.js +16 -0
- package/src/toastr.js +148 -0
- package/src/tryToMatchSchemas.js +264 -0
- package/src/typeToCommonType.js +6 -0
- package/src/useDeepEqualMemo.js +15 -0
- package/src/useDialog.js +63 -0
- package/src/useStableReference.js +9 -0
- package/src/useTableEntities.js +38 -0
- package/src/useTraceUpdate.js +19 -0
- package/src/utils.js +37 -0
- package/src/validateTableWideErrors.js +160 -0
- package/src/viewColumn.js +97 -0
- package/src/withField.js +20 -0
- package/src/withFields.js +11 -0
- package/src/withLocalStorage.js +11 -0
- package/src/withSelectTableRecords.js +43 -0
- package/src/withSelectedEntities.js +65 -0
- package/src/withStore.js +10 -0
- package/src/withTableParams.js +288 -0
- package/src/wrapDialog.js +116 -0
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import queryString from "qs";
|
|
2
|
+
import { uniqBy, clone, camelCase } from "lodash-es";
|
|
3
|
+
import { tableQueryParamsToHasuraClauses } from "./tableQueryParamsToHasuraClauses";
|
|
4
|
+
import { filterLocalEntitiesToHasura } from "./filterLocalEntitiesToHasura";
|
|
5
|
+
import {
|
|
6
|
+
addCustomColumnFilters,
|
|
7
|
+
initializeHasuraWhereAndFilter
|
|
8
|
+
} from "./initializeHasuraWhereAndFilter";
|
|
9
|
+
|
|
10
|
+
const defaultPageSizes = [5, 10, 15, 25, 50, 100, 200, 400];
|
|
11
|
+
|
|
12
|
+
export { defaultPageSizes };
|
|
13
|
+
|
|
14
|
+
export function getMergedOpts(topLevel = {}, instanceLevel = {}) {
|
|
15
|
+
const merged = {
|
|
16
|
+
...topLevel,
|
|
17
|
+
...instanceLevel
|
|
18
|
+
};
|
|
19
|
+
return {
|
|
20
|
+
formName: "tgDataTable",
|
|
21
|
+
...merged,
|
|
22
|
+
pageSize: merged.controlled_pageSize || merged.pageSize,
|
|
23
|
+
defaults: {
|
|
24
|
+
pageSize: merged.controlled_pageSize || 25,
|
|
25
|
+
order: [], //[-name, statusCode] //an array of camelCase display names with - sign to denote reverse
|
|
26
|
+
searchTerm: "",
|
|
27
|
+
page: 1,
|
|
28
|
+
filters: [
|
|
29
|
+
//filters look like this:
|
|
30
|
+
// {
|
|
31
|
+
// selectedFilter: 'textContains', //camel case
|
|
32
|
+
// filterOn: ccDisplayName, //camel case display name if available and string, otherwise path
|
|
33
|
+
// filterValue: 'thomas',
|
|
34
|
+
// }
|
|
35
|
+
],
|
|
36
|
+
...(topLevel.defaults || {}),
|
|
37
|
+
...(instanceLevel.defaults || {})
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function safeStringify(val) {
|
|
43
|
+
if (val !== null && typeof val === "object") {
|
|
44
|
+
return JSON.stringify(val);
|
|
45
|
+
}
|
|
46
|
+
return val;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function safeParse(val) {
|
|
50
|
+
try {
|
|
51
|
+
return JSON.parse(val);
|
|
52
|
+
} catch (e) {
|
|
53
|
+
return val;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
*
|
|
59
|
+
* @param {object} field
|
|
60
|
+
* @returns the camelCase display name of the field, to be used for filters, sorting, etc
|
|
61
|
+
*/
|
|
62
|
+
export function getCCDisplayName(field) {
|
|
63
|
+
return camelCase(
|
|
64
|
+
typeof field.displayName === "string" ? field.displayName : field.path
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function getCurrentParamsFromUrl(location, isSimple) {
|
|
69
|
+
let { search } = location;
|
|
70
|
+
if (isSimple) {
|
|
71
|
+
search = window.location.href.split("?")[1] || "";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return parseFilters(queryString.parse(search, { ignoreQueryPrefix: true }));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function setCurrentParamsOnUrl(newParams, replace, isSimple) {
|
|
78
|
+
const stringifiedFilters = stringifyFilters(newParams);
|
|
79
|
+
const search = `?${queryString.stringify(stringifiedFilters)}`;
|
|
80
|
+
if (isSimple) {
|
|
81
|
+
return window.location.replace(
|
|
82
|
+
`${window.location.href.split("?")[0]}${search}`
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
replace({
|
|
86
|
+
search
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function stringifyFilters(newParams) {
|
|
91
|
+
let filters;
|
|
92
|
+
if (newParams.filters && newParams.filters.length) {
|
|
93
|
+
filters = newParams.filters.reduce(
|
|
94
|
+
(acc, { filterOn, selectedFilter, filterValue }, index) => {
|
|
95
|
+
acc +=
|
|
96
|
+
(index > 0 ? "::" : "") +
|
|
97
|
+
`${filterOn}__${camelCase(selectedFilter)}__${safeStringify(
|
|
98
|
+
Array.isArray(filterValue) ? filterValue.join(";") : filterValue
|
|
99
|
+
)}`;
|
|
100
|
+
return acc;
|
|
101
|
+
},
|
|
102
|
+
""
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
let order;
|
|
106
|
+
if (newParams.order && newParams.order.length) {
|
|
107
|
+
order = newParams.order.reduce((acc, order, index) => {
|
|
108
|
+
acc += (index > 0 ? "___" : "") + order;
|
|
109
|
+
return acc;
|
|
110
|
+
}, "");
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
...newParams,
|
|
114
|
+
filters,
|
|
115
|
+
order
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function parseFilters(newParams) {
|
|
119
|
+
return {
|
|
120
|
+
...newParams,
|
|
121
|
+
order: newParams.order && newParams.order.split("___"),
|
|
122
|
+
filters:
|
|
123
|
+
newParams.filters &&
|
|
124
|
+
newParams.filters.split("::").map(filter => {
|
|
125
|
+
const splitFilter = filter.split("__");
|
|
126
|
+
const [filterOn, selectedFilter, filterValue] = splitFilter;
|
|
127
|
+
const parseFilterValue = filterValue => {
|
|
128
|
+
if (selectedFilter === "inList" || selectedFilter === "notInList") {
|
|
129
|
+
return filterValue.split(";");
|
|
130
|
+
}
|
|
131
|
+
if (
|
|
132
|
+
selectedFilter === "inRange" ||
|
|
133
|
+
selectedFilter === "outsideRange"
|
|
134
|
+
) {
|
|
135
|
+
return filterValue.split(";").map(Number);
|
|
136
|
+
}
|
|
137
|
+
return safeParse(filterValue);
|
|
138
|
+
};
|
|
139
|
+
return {
|
|
140
|
+
filterOn,
|
|
141
|
+
selectedFilter,
|
|
142
|
+
filterValue: parseFilterValue(filterValue)
|
|
143
|
+
};
|
|
144
|
+
})
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function makeDataTableHandlers({
|
|
149
|
+
setNewParams,
|
|
150
|
+
defaults,
|
|
151
|
+
onlyOneFilter
|
|
152
|
+
}) {
|
|
153
|
+
//all of these actions have currentParams bound to them as their last arg in withTableParams
|
|
154
|
+
const setSearchTerm = searchTerm => {
|
|
155
|
+
setNewParams(prev => ({
|
|
156
|
+
...(prev ?? {}),
|
|
157
|
+
page: undefined, //set page undefined to return the table to page 1
|
|
158
|
+
searchTerm: searchTerm === defaults.searchTerm ? undefined : searchTerm
|
|
159
|
+
}));
|
|
160
|
+
onlyOneFilter && clearFilters();
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const addFilters = newFilters => {
|
|
164
|
+
if (!newFilters) return;
|
|
165
|
+
setNewParams(prev => {
|
|
166
|
+
const filters = uniqBy(
|
|
167
|
+
[...newFilters, ...(onlyOneFilter ? [] : prev?.filters || [])],
|
|
168
|
+
"filterOn"
|
|
169
|
+
);
|
|
170
|
+
return {
|
|
171
|
+
...(prev ?? {}),
|
|
172
|
+
page: undefined, //set page undefined to return the table to page 1
|
|
173
|
+
filters
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const removeSingleFilter = filterOn =>
|
|
179
|
+
setNewParams(prev => {
|
|
180
|
+
const filters = prev?.filters
|
|
181
|
+
? prev.filters.filter(filter => {
|
|
182
|
+
return filter.filterOn !== filterOn;
|
|
183
|
+
})
|
|
184
|
+
: undefined;
|
|
185
|
+
return { ...(prev ?? {}), filters };
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const clearFilters = (additionalFilterKeys = []) => {
|
|
189
|
+
const toClear = {
|
|
190
|
+
filters: undefined,
|
|
191
|
+
searchTerm: undefined,
|
|
192
|
+
tags: undefined
|
|
193
|
+
};
|
|
194
|
+
additionalFilterKeys.forEach(key => {
|
|
195
|
+
toClear[key] = undefined;
|
|
196
|
+
});
|
|
197
|
+
setNewParams(toClear);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const setPageSize = pageSize =>
|
|
201
|
+
setNewParams(prev => ({
|
|
202
|
+
...(prev ?? {}),
|
|
203
|
+
pageSize: pageSize === defaults.pageSize ? undefined : pageSize,
|
|
204
|
+
page: undefined //set page undefined to return the table to page 1
|
|
205
|
+
}));
|
|
206
|
+
|
|
207
|
+
const setOrder = (order, isRemove, shiftHeld) =>
|
|
208
|
+
setNewParams(prev => {
|
|
209
|
+
let newOrder = [];
|
|
210
|
+
if (shiftHeld) {
|
|
211
|
+
//first remove the old order
|
|
212
|
+
newOrder = [...(prev?.order || [])].filter(value => {
|
|
213
|
+
const shouldRemove =
|
|
214
|
+
value.replace(/^-/, "") === order.replace(/^-/, "");
|
|
215
|
+
return !shouldRemove;
|
|
216
|
+
});
|
|
217
|
+
//then, if we are adding, pop the order onto the array
|
|
218
|
+
if (!isRemove) {
|
|
219
|
+
newOrder.push(order);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
if (isRemove) {
|
|
223
|
+
newOrder = [];
|
|
224
|
+
} else {
|
|
225
|
+
newOrder = [order];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
...(prev ?? {}),
|
|
230
|
+
order: newOrder
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
const setPage = page => {
|
|
235
|
+
setNewParams(prev => ({
|
|
236
|
+
...(prev ?? {}),
|
|
237
|
+
page: page === defaults.page ? undefined : page
|
|
238
|
+
}));
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
setSearchTerm,
|
|
243
|
+
addFilters,
|
|
244
|
+
clearFilters,
|
|
245
|
+
removeSingleFilter,
|
|
246
|
+
setPageSize,
|
|
247
|
+
setPage,
|
|
248
|
+
setOrder,
|
|
249
|
+
setNewParams
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function getQueryParams({
|
|
254
|
+
currentParams,
|
|
255
|
+
// urlConnected,
|
|
256
|
+
defaults,
|
|
257
|
+
schema,
|
|
258
|
+
isInfinite,
|
|
259
|
+
entities,
|
|
260
|
+
isLocalCall,
|
|
261
|
+
additionalFilter,
|
|
262
|
+
doNotCoercePageSize,
|
|
263
|
+
// noOrderError,
|
|
264
|
+
// isCodeModel,
|
|
265
|
+
ownProps
|
|
266
|
+
}) {
|
|
267
|
+
Object.keys(currentParams).forEach(function (key) {
|
|
268
|
+
if (currentParams[key] === undefined) {
|
|
269
|
+
delete currentParams[key]; //we want to use the default value if any of these are undefined
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
const tableQueryParams = {
|
|
273
|
+
...defaults,
|
|
274
|
+
...currentParams
|
|
275
|
+
};
|
|
276
|
+
let { page, pageSize, searchTerm, filters, order } = tableQueryParams;
|
|
277
|
+
if (page <= 0 || isNaN(page)) {
|
|
278
|
+
page = undefined;
|
|
279
|
+
}
|
|
280
|
+
if (isInfinite) {
|
|
281
|
+
page = undefined;
|
|
282
|
+
pageSize = undefined;
|
|
283
|
+
}
|
|
284
|
+
if (pageSize !== undefined && !doNotCoercePageSize) {
|
|
285
|
+
//pageSize might come in as an unexpected number so we coerce it to be one of the nums in our pageSizes array
|
|
286
|
+
const closest = clone(window.tgPageSizes || defaultPageSizes).sort(
|
|
287
|
+
(a, b) => Math.abs(pageSize - a) - Math.abs(pageSize - b)
|
|
288
|
+
)[0];
|
|
289
|
+
pageSize = closest;
|
|
290
|
+
}
|
|
291
|
+
const toReturn = {
|
|
292
|
+
//these are values that might be generally useful for the wrapped component
|
|
293
|
+
page,
|
|
294
|
+
pageSize: ownProps.controlled_pageSize || pageSize,
|
|
295
|
+
order,
|
|
296
|
+
filters,
|
|
297
|
+
searchTerm
|
|
298
|
+
};
|
|
299
|
+
// tnwtodo: need to make sure ignoreSearchTerm still works
|
|
300
|
+
// if (additionalOrFilterToUse && additionalOrFilterToUse.ignoreSearchTerm) {
|
|
301
|
+
// searchTerm = "";
|
|
302
|
+
// additionalOrFilterToUse = additionalOrFilterToUse.additionalOrFilterToUse;
|
|
303
|
+
// }
|
|
304
|
+
|
|
305
|
+
const { where, order_by, limit, offset } = tableQueryParamsToHasuraClauses({
|
|
306
|
+
page,
|
|
307
|
+
pageSize,
|
|
308
|
+
searchTerm,
|
|
309
|
+
filters,
|
|
310
|
+
order,
|
|
311
|
+
schema
|
|
312
|
+
});
|
|
313
|
+
initializeHasuraWhereAndFilter(additionalFilter, where, currentParams);
|
|
314
|
+
addCustomColumnFilters(where, schema.fields, currentParams);
|
|
315
|
+
if (isLocalCall) {
|
|
316
|
+
//if the table is local (aka not directly connected to a db) then we need to
|
|
317
|
+
//handle filtering/paging/sorting all on the front end
|
|
318
|
+
return filterLocalEntitiesToHasura(entities, {
|
|
319
|
+
where,
|
|
320
|
+
order_by,
|
|
321
|
+
limit,
|
|
322
|
+
offset,
|
|
323
|
+
isInfinite
|
|
324
|
+
});
|
|
325
|
+
} else {
|
|
326
|
+
return {
|
|
327
|
+
...toReturn,
|
|
328
|
+
variables: {
|
|
329
|
+
where,
|
|
330
|
+
order_by,
|
|
331
|
+
limit,
|
|
332
|
+
offset
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
@@ -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
|
+
};
|
|
@@ -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/src/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/src/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
|
+
};
|