@teselagen/ui 0.7.33-beta.5 → 0.7.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/AdvancedOptions.js +33 -0
  2. package/AssignDefaultsModeContext.js +22 -0
  3. package/CellDragHandle.js +132 -0
  4. package/ColumnFilterMenu.js +62 -0
  5. package/Columns.js +979 -0
  6. package/DataTable/utils/queryParams.d.ts +18 -9
  7. package/DisabledLoadingComponent.js +15 -0
  8. package/DisplayOptions.js +199 -0
  9. package/DropdownButton.js +36 -0
  10. package/DropdownCell.js +61 -0
  11. package/EditableCell.js +44 -0
  12. package/FillWindow.css +6 -0
  13. package/FillWindow.js +69 -0
  14. package/FilterAndSortMenu.js +391 -0
  15. package/FormSeparator.js +9 -0
  16. package/LoadingDots.js +14 -0
  17. package/MatchHeaders.js +234 -0
  18. package/PagingTool.js +225 -0
  19. package/RenderCell.js +191 -0
  20. package/SearchBar.js +69 -0
  21. package/SimpleStepViz.js +22 -0
  22. package/SortableColumns.js +100 -0
  23. package/TableFormTrackerContext.js +10 -0
  24. package/Tag.js +112 -0
  25. package/ThComponent.js +44 -0
  26. package/TimelineEvent.js +31 -0
  27. package/UploadCsvWizard.css +4 -0
  28. package/UploadCsvWizard.js +719 -0
  29. package/Uploader.js +1278 -0
  30. package/adHoc.js +10 -0
  31. package/autoTooltip.js +201 -0
  32. package/basicHandleActionsWithFullState.js +14 -0
  33. package/browserUtils.js +3 -0
  34. package/combineReducersWithFullState.js +14 -0
  35. package/commandControls.js +82 -0
  36. package/commandUtils.js +112 -0
  37. package/constants.js +1 -0
  38. package/convertSchema.js +69 -0
  39. package/customIcons.js +361 -0
  40. package/dataTableEnhancer.js +41 -0
  41. package/defaultFormatters.js +32 -0
  42. package/defaultValidators.js +40 -0
  43. package/determineBlackOrWhiteTextColor.js +4 -0
  44. package/editCellHelper.js +44 -0
  45. package/formatPasteData.js +16 -0
  46. package/getAllRows.js +11 -0
  47. package/getCellCopyText.js +7 -0
  48. package/getCellInfo.js +36 -0
  49. package/getCellVal.js +20 -0
  50. package/getDayjsFormatter.js +35 -0
  51. package/getFieldPathToField.js +7 -0
  52. package/getIdOrCodeOrIndex.js +9 -0
  53. package/getLastSelectedEntity.js +11 -0
  54. package/getNewEntToSelect.js +25 -0
  55. package/getNewName.js +31 -0
  56. package/getRowCopyText.js +28 -0
  57. package/getTableConfigFromStorage.js +5 -0
  58. package/getTextFromEl.js +28 -0
  59. package/getVals.js +8 -0
  60. package/handleCopyColumn.js +21 -0
  61. package/handleCopyHelper.js +15 -0
  62. package/handleCopyRows.js +23 -0
  63. package/handleCopyTable.js +16 -0
  64. package/handlerHelpers.js +24 -0
  65. package/hotkeyUtils.js +131 -0
  66. package/index.cjs.js +970 -826
  67. package/index.d.ts +0 -1
  68. package/index.es.js +970 -826
  69. package/index.js +196 -0
  70. package/isBeingCalledExcessively.js +31 -0
  71. package/isBottomRightCornerOfRectangle.js +20 -0
  72. package/isEntityClean.js +15 -0
  73. package/isTruthy.js +12 -0
  74. package/isValueEmpty.js +3 -0
  75. package/itemUpload.js +84 -0
  76. package/menuUtils.js +433 -0
  77. package/package.json +1 -2
  78. package/popoverOverflowModifiers.js +11 -0
  79. package/primarySelectedValue.js +1 -0
  80. package/pureNoFunc.js +31 -0
  81. package/queryParams.js +1058 -0
  82. package/removeCleanRows.js +22 -0
  83. package/renderOnDoc.js +32 -0
  84. package/rerenderOnWindowResize.js +26 -0
  85. package/rowClick.js +181 -0
  86. package/selection.js +8 -0
  87. package/showAppSpinner.js +12 -0
  88. package/showDialogOnDocBody.js +33 -0
  89. package/showProgressToast.js +22 -0
  90. package/sortify.js +73 -0
  91. package/src/DataTable/index.js +1 -1
  92. package/src/DataTable/utils/filterLocalEntitiesToHasura.js +14 -0
  93. package/src/DataTable/utils/filterLocalEntitiesToHasura.test.js +49 -0
  94. package/src/DataTable/utils/queryParams.js +12 -9
  95. package/src/DataTable/utils/tableQueryParamsToHasuraClauses.js +146 -143
  96. package/style.css +29 -0
  97. package/tagUtils.js +45 -0
  98. package/tgFormValues.js +35 -0
  99. package/tg_modalState.js +47 -0
  100. package/throwFormError.js +16 -0
  101. package/toastr.js +148 -0
  102. package/tryToMatchSchemas.js +264 -0
  103. package/typeToCommonType.js +6 -0
  104. package/useDeepEqualMemo.js +15 -0
  105. package/useDialog.js +63 -0
  106. package/useStableReference.js +9 -0
  107. package/useTableEntities.js +38 -0
  108. package/useTraceUpdate.js +19 -0
  109. package/utils.js +37 -0
  110. package/validateTableWideErrors.js +160 -0
  111. package/viewColumn.js +97 -0
  112. package/withField.js +20 -0
  113. package/withFields.js +11 -0
  114. package/withLocalStorage.js +11 -0
  115. package/withSelectTableRecords.js +43 -0
  116. package/withSelectedEntities.js +65 -0
  117. package/withStore.js +10 -0
  118. package/withTableParams.js +301 -0
  119. package/wrapDialog.js +116 -0
@@ -1,4 +1,4 @@
1
- import { camelCase } from "lodash-es";
1
+ import { camelCase, set } from "lodash-es";
2
2
 
3
3
  export function tableQueryParamsToHasuraClauses({
4
4
  page,
@@ -23,9 +23,8 @@ export function tableQueryParamsToHasuraClauses({
23
23
  const filterValue = searchTerm; // No cleaning needed here, we're using _ilike
24
24
 
25
25
  if (type === "string" || type === "lookup") {
26
- searchTermFilters.push({
27
- [path]: { _ilike: `%${filterValue}%` }
28
- });
26
+ const o = set({}, path, { _ilike: `%${filterValue}%` });
27
+ searchTermFilters.push(o);
29
28
  } else if (type === "boolean") {
30
29
  let regex;
31
30
  try {
@@ -35,22 +34,19 @@ export function tableQueryParamsToHasuraClauses({
35
34
  }
36
35
  if (regex) {
37
36
  if ("true".replace(regex, "") !== "true") {
38
- searchTermFilters.push({
39
- [path]: { _eq: true }
40
- });
37
+ const o = set({}, path, { _eq: true });
38
+ searchTermFilters.push(o);
41
39
  } else if ("false".replace(regex, "") !== "false") {
42
- searchTermFilters.push({
43
- [path]: { _eq: false }
44
- });
40
+ const o = set({}, path, { _eq: false });
41
+ searchTermFilters.push(o);
45
42
  }
46
43
  }
47
44
  } else if (
48
45
  (type === "number" || type === "integer") &&
49
46
  !isNaN(filterValue)
50
47
  ) {
51
- searchTermFilters.push({
52
- [path]: { _eq: parseFloat(filterValue) }
53
- });
48
+ const o = set({}, path, { _eq: parseFloat(filterValue) });
49
+ searchTermFilters.push(o);
54
50
  }
55
51
  });
56
52
  if (searchTermFilters.length > 0) {
@@ -63,143 +59,150 @@ export function tableQueryParamsToHasuraClauses({
63
59
  }
64
60
 
65
61
  if (filters && filters.length > 0) {
66
- const filterClauses = filters.map(filter => {
67
- let { selectedFilter, filterOn, filterValue } = filter;
68
- const fieldSchema = ccFields[filterOn] || {};
62
+ const filterClauses = filters
63
+ .map(filter => {
64
+ let { selectedFilter, filterOn, filterValue } = filter;
65
+ const fieldSchema = ccFields[filterOn] || {};
69
66
 
70
- const { path, reference, type } = fieldSchema;
71
- let stringFilterValue =
72
- filterValue && filterValue.toString
73
- ? filterValue.toString()
74
- : filterValue;
75
- if (stringFilterValue === false) {
76
- // we still want to be able to search for the string "false" which will get parsed to false
77
- stringFilterValue = "false";
78
- } else {
79
- stringFilterValue = stringFilterValue || "";
80
- }
81
- const arrayFilterValue = Array.isArray(filterValue)
82
- ? filterValue
83
- : stringFilterValue.split(";");
84
-
85
- if (type === "number" || type === "integer") {
86
- filterValue = Array.isArray(filterValue)
87
- ? filterValue.map(val => Number(val))
88
- : Number(filterValue);
89
- }
67
+ const { path, reference, type } = fieldSchema;
68
+ let stringFilterValue =
69
+ filterValue && filterValue.toString
70
+ ? filterValue.toString()
71
+ : filterValue;
72
+ if (stringFilterValue === false) {
73
+ // we still want to be able to search for the string "false" which will get parsed to false
74
+ stringFilterValue = "false";
75
+ } else {
76
+ stringFilterValue = stringFilterValue || "";
77
+ }
78
+ const arrayFilterValue = Array.isArray(filterValue)
79
+ ? filterValue
80
+ : stringFilterValue.split(";");
90
81
 
91
- if (fieldSchema.normalizeFilter) {
92
- filterValue = fieldSchema.normalizeFilter(
93
- filterValue,
94
- selectedFilter,
95
- filterOn
96
- );
97
- }
82
+ if (type === "number" || type === "integer") {
83
+ filterValue = Array.isArray(filterValue)
84
+ ? filterValue.map(val => Number(val))
85
+ : Number(filterValue);
86
+ }
98
87
 
99
- if (reference) {
100
- filterOn = reference.sourceField;
101
- } else {
102
- filterOn = path || filterOn;
103
- }
88
+ if (fieldSchema.normalizeFilter) {
89
+ filterValue = fieldSchema.normalizeFilter(
90
+ filterValue,
91
+ selectedFilter,
92
+ filterOn
93
+ );
94
+ }
104
95
 
105
- switch (selectedFilter) {
106
- case "none":
107
- return {};
108
- case "startsWith":
109
- return { [filterOn]: { _ilike: `${filterValue}%` } };
110
- case "endsWith":
111
- return { [filterOn]: { _ilike: `%${filterValue}` } };
112
- case "contains":
113
- return { [filterOn]: { _ilike: `%${filterValue}%` } };
114
- case "notContains":
115
- return { [filterOn]: { _not_ilike: `%${filterValue}%` } };
116
- case "isExactly":
117
- return { [filterOn]: { _eq: filterValue } };
118
- case "isEmpty":
119
- return {
120
- _or: [
121
- { [filterOn]: { _eq: "" } },
122
- { [filterOn]: { _is_null: true } }
123
- ]
124
- };
125
- case "notEmpty":
126
- return {
127
- _and: [
128
- { [filterOn]: { _neq: "" } },
129
- { [filterOn]: { _is_null: false } }
130
- ]
131
- };
132
- case "inList":
133
- return { [filterOn]: { _in: filterValue } };
134
- case "notInList":
135
- return { [filterOn]: { _nin: filterValue } };
136
- case "true":
137
- return { [filterOn]: { _eq: true } };
138
- case "false":
139
- return { [filterOn]: { _eq: false } };
140
- case "dateIs":
141
- return { [filterOn]: { _eq: filterValue } };
142
- case "notBetween":
143
- return {
144
- _or: [
145
- {
146
- [filterOn]: {
147
- _lt: new Date(arrayFilterValue[0])
148
- }
149
- },
150
- {
151
- [filterOn]: {
152
- _gt: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
96
+ if (reference) {
97
+ filterOn = reference.sourceField;
98
+ } else {
99
+ filterOn = path || filterOn;
100
+ }
101
+ switch (selectedFilter) {
102
+ case "none":
103
+ return {};
104
+ case "startsWith":
105
+ return { [filterOn]: { _ilike: `${filterValue}%` } };
106
+ case "endsWith":
107
+ return { [filterOn]: { _ilike: `%${filterValue}` } };
108
+ case "contains":
109
+ return { [filterOn]: { _ilike: `%${filterValue}%` } };
110
+ case "notContains":
111
+ return { [filterOn]: { _not_ilike: `%${filterValue}%` } };
112
+ case "isExactly":
113
+ return { [filterOn]: { _eq: filterValue } };
114
+ case "isEmpty":
115
+ return {
116
+ _or: [
117
+ { [filterOn]: { _eq: "" } },
118
+ { [filterOn]: { _is_null: true } }
119
+ ]
120
+ };
121
+ case "notEmpty":
122
+ return {
123
+ _and: [
124
+ { [filterOn]: { _neq: "" } },
125
+ { [filterOn]: { _is_null: false } }
126
+ ]
127
+ };
128
+ case "inList":
129
+ return { [filterOn]: { _in: filterValue } };
130
+ case "notInList":
131
+ return { [filterOn]: { _nin: filterValue } };
132
+ case "true":
133
+ return { [filterOn]: { _eq: true } };
134
+ case "false":
135
+ return { [filterOn]: { _eq: false } };
136
+ case "dateIs":
137
+ return { [filterOn]: { _eq: filterValue } };
138
+ case "notBetween":
139
+ return {
140
+ _or: [
141
+ {
142
+ [filterOn]: {
143
+ _lt: new Date(arrayFilterValue[0])
144
+ }
145
+ },
146
+ {
147
+ [filterOn]: {
148
+ _gt: new Date(
149
+ new Date(arrayFilterValue[1]).setHours(23, 59)
150
+ )
151
+ }
153
152
  }
153
+ ]
154
+ };
155
+ case "isBetween":
156
+ return {
157
+ [filterOn]: {
158
+ _gte: new Date(arrayFilterValue[0]),
159
+ _lte: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
154
160
  }
155
- ]
156
- };
157
- case "isBetween":
158
- return {
159
- [filterOn]: {
160
- _gte: new Date(arrayFilterValue[0]),
161
- _lte: new Date(new Date(arrayFilterValue[1]).setHours(23, 59))
162
- }
163
- };
164
- case "isBefore":
165
- return { [filterOn]: { _lt: new Date(filterValue) } };
166
- case "isAfter":
167
- return { [filterOn]: { _gt: new Date(filterValue) } };
168
- case "greaterThan":
169
- return { [filterOn]: { _gt: parseFloat(filterValue) } };
170
- case "lessThan":
171
- return { [filterOn]: { _lt: parseFloat(filterValue) } };
172
- case "inRange":
173
- return {
174
- [filterOn]: {
175
- _gte: parseFloat(arrayFilterValue[0]),
176
- _lte: parseFloat(arrayFilterValue[1])
177
- }
178
- };
179
- case "outsideRange":
180
- return {
181
- _or: [
182
- {
183
- [filterOn]: {
184
- _lt: parseFloat(arrayFilterValue[0])
185
- }
186
- },
187
- {
188
- [filterOn]: {
189
- _gt: parseFloat(arrayFilterValue[1])
190
- }
161
+ };
162
+ case "isBefore":
163
+ return { [filterOn]: { _lt: new Date(filterValue) } };
164
+ case "isAfter":
165
+ return { [filterOn]: { _gt: new Date(filterValue) } };
166
+ case "greaterThan":
167
+ return { [filterOn]: { _gt: parseFloat(filterValue) } };
168
+ case "lessThan":
169
+ return { [filterOn]: { _lt: parseFloat(filterValue) } };
170
+ case "inRange":
171
+ return {
172
+ [filterOn]: {
173
+ _gte: parseFloat(arrayFilterValue[0]),
174
+ _lte: parseFloat(arrayFilterValue[1])
191
175
  }
192
- ]
193
- };
194
- case "equalTo":
195
- return { [filterOn]: { _eq: parseFloat(filterValue) } };
196
- case "regex":
197
- return { [filterOn]: { _regex: filterValue } };
198
- default:
199
- console.warn(`Unsupported filter type: ${selectedFilter}`);
200
- return {};
201
- }
202
- });
176
+ };
177
+ case "outsideRange":
178
+ return {
179
+ _or: [
180
+ {
181
+ [filterOn]: {
182
+ _lt: parseFloat(arrayFilterValue[0])
183
+ }
184
+ },
185
+ {
186
+ [filterOn]: {
187
+ _gt: parseFloat(arrayFilterValue[1])
188
+ }
189
+ }
190
+ ]
191
+ };
192
+ case "equalTo":
193
+ return { [filterOn]: { _eq: parseFloat(filterValue) } };
194
+ case "regex":
195
+ return { [filterOn]: { _regex: filterValue } };
196
+ default:
197
+ console.warn(`Unsupported filter type: ${selectedFilter}`);
198
+ return {};
199
+ }
200
+ })
201
+ .map(filter => {
202
+ const o = {};
203
+ set(o, Object.keys(filter)[0], filter[Object.keys(filter)[0]]);
204
+ return o;
205
+ });
203
206
 
204
207
  if (filterClauses.length > 0) {
205
208
  if (Object.keys(where).length > 0) {
package/style.css ADDED
@@ -0,0 +1,29 @@
1
+ /* Copyright (C) 2018 TeselaGen Biotechnology, Inc. */
2
+ .tg-timeline {
3
+ position: relative;
4
+ white-space: nowrap;
5
+ }
6
+
7
+ .tg-timeline-line {
8
+ position: absolute;
9
+ height: 90%;
10
+ border-left: 2px solid #bfccd6;
11
+ top: 3px;
12
+ left: 5px;
13
+ }
14
+
15
+ .tg-timeline-event {
16
+ margin-bottom: 15px;
17
+ }
18
+
19
+ .tg-timeline-circle {
20
+ width: 12px;
21
+ min-width: 12px;
22
+ height: 12px;
23
+ min-height: 12px;
24
+ z-index: 1;
25
+ margin-right: 10px;
26
+ border-radius: 100px;
27
+ background: #e1e8ed;
28
+ border: 2px solid #137cbd;
29
+ }
package/tagUtils.js ADDED
@@ -0,0 +1,45 @@
1
+ import { flatMap, keyBy } from "lodash-es";
2
+ import determineBlackOrWhiteTextColor from "./determineBlackOrWhiteTextColor";
3
+
4
+ export function getTagsAndTagOptions(allTags) {
5
+ return flatMap(allTags, tag => {
6
+ if (tag.tagOptions && tag.tagOptions.length) {
7
+ return tag.tagOptions.map(tagO => {
8
+ const fullname = `${tag.name}: ${tagO.name}`;
9
+ const value = `${tag.id}:${tagO.id}`;
10
+ return {
11
+ ...tagO,
12
+ label: fullname,
13
+ value,
14
+ id: tagO.id
15
+ };
16
+ });
17
+ }
18
+ return {
19
+ ...tag,
20
+ label: tag.name,
21
+ value: tag.id
22
+ };
23
+ });
24
+ }
25
+
26
+ export function getKeyedTagsAndTagOptions(tags) {
27
+ return keyBy(getTagsAndTagOptions(tags), "value");
28
+ }
29
+
30
+ export function getTagColorStyle(color) {
31
+ return color
32
+ ? {
33
+ style: {
34
+ backgroundColor: color,
35
+ color: determineBlackOrWhiteTextColor(color)
36
+ }
37
+ }
38
+ : {};
39
+ }
40
+ export function getTagProps({ color, label, name }) {
41
+ return {
42
+ ...getTagColorStyle(color),
43
+ children: label || name
44
+ };
45
+ }
@@ -0,0 +1,35 @@
1
+ import React, { useMemo } from "react";
2
+ import { connect } from "react-redux";
3
+ import { FormName, formValueSelector } from "redux-form";
4
+
5
+ const tgFormValues =
6
+ (...fieldNames) =>
7
+ Component =>
8
+ props => {
9
+ return (
10
+ <FormName>
11
+ {formName => {
12
+ const name = formName.form;
13
+ const Wrapped = useMemo(() => {
14
+ const selector = formValueSelector(name || "");
15
+ const wrapper = connect(state => {
16
+ const vals = {};
17
+ fieldNames.forEach(name => {
18
+ vals[name] = selector(state, name);
19
+ });
20
+ return vals;
21
+ });
22
+ return wrapper(Component);
23
+ }, [name]);
24
+ return <Wrapped {...props} />;
25
+ }}
26
+ </FormName>
27
+ );
28
+ };
29
+ export default tgFormValues;
30
+
31
+ export const tgFormValueSelector = (formName, ...fields) => {
32
+ return connect(state => {
33
+ return formValueSelector(formName)(state, ...fields);
34
+ });
35
+ };
@@ -0,0 +1,47 @@
1
+ import { omit } from "lodash-es";
2
+
3
+ export default function tg_modalState(
4
+ state = {},
5
+ { type, name, uniqueName, props = {} }
6
+ ) {
7
+ const existingModalState = state[name] || {};
8
+ const { __registeredAs = {} } = existingModalState;
9
+ if (type === "TG_REGISTER_MODAL") {
10
+ return {
11
+ ...state,
12
+ [name]: {
13
+ ...existingModalState,
14
+ __registeredAs: { ...__registeredAs, [uniqueName]: true }
15
+ }
16
+ };
17
+ }
18
+ if (type === "TG_UNREGISTER_MODAL") {
19
+ return {
20
+ ...state,
21
+ [name]: {
22
+ ...existingModalState,
23
+ __registeredAs: omit(__registeredAs, uniqueName)
24
+ }
25
+ };
26
+ }
27
+ if (type === "TG_SHOW_MODAL") {
28
+ return {
29
+ ...state,
30
+ [name]: {
31
+ ...existingModalState,
32
+ ...props,
33
+ open: true
34
+ }
35
+ };
36
+ }
37
+ if (type === "TG_HIDE_MODAL") {
38
+ return {
39
+ ...state,
40
+ [name]: {
41
+ __registeredAs: existingModalState.__registeredAs,
42
+ open: false
43
+ }
44
+ };
45
+ }
46
+ return state;
47
+ }
@@ -0,0 +1,16 @@
1
+ import { SubmissionError } from "redux-form";
2
+
3
+ export const throwFormError = error => {
4
+ if (error.message) {
5
+ console.error("error:", error);
6
+ }
7
+ const errorToUse = error.message
8
+ ? { _error: error.message }
9
+ : typeof error === "string"
10
+ ? { _error: error }
11
+ : error;
12
+ if (!errorToUse._error) {
13
+ errorToUse._error = "Error Submitting Form";
14
+ }
15
+ throw new SubmissionError(errorToUse);
16
+ };
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
+ }