dce-reactkit 3.0.0 → 3.0.3

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 (166) hide show
  1. package/README.md +1 -546
  2. package/dist/cjs/index.js +1826 -0
  3. package/dist/cjs/index.js.map +1 -0
  4. package/{lib/esm → dist/cjs}/types/components/AppWrapper.d.ts +29 -4
  5. package/dist/cjs/types/components/ButtonInputGroup.d.ts +12 -0
  6. package/dist/cjs/types/components/CheckboxButton.d.ts +20 -0
  7. package/dist/cjs/types/components/Drawer.d.ts +10 -0
  8. package/{lib/cjs/lib → dist/cjs/types}/components/ErrorBox.d.ts +0 -0
  9. package/{lib/cjs/lib → dist/cjs/types}/components/LoadingSpinner.d.ts +0 -0
  10. package/{lib/cjs/lib → dist/cjs/types}/components/Modal.d.ts +0 -0
  11. package/dist/cjs/types/components/RadioButton.d.ts +20 -0
  12. package/dist/cjs/types/components/SimpleDateChooser.d.ts +22 -0
  13. package/{lib/esm/lib → dist/cjs/types}/components/TabBox.d.ts +1 -0
  14. package/{lib/cjs/lib → dist/cjs/types}/errors/ErrorWithCode.d.ts +0 -0
  15. package/{lib/cjs/lib → dist/cjs/types}/helpers/abbreviate.d.ts +0 -0
  16. package/{lib/cjs/lib → dist/cjs/types}/helpers/avg.d.ts +0 -0
  17. package/{lib/cjs/lib → dist/cjs/types}/helpers/ceilToNumDecimals.d.ts +0 -0
  18. package/{lib/cjs/lib → dist/cjs/types}/helpers/floorToNumDecimals.d.ts +0 -0
  19. package/{lib/cjs/lib → dist/cjs/types}/helpers/forceNumIntoBounds.d.ts +0 -0
  20. package/dist/cjs/types/helpers/genRouteHandler.d.ts +32 -0
  21. package/dist/cjs/types/helpers/getOrdinal.d.ts +8 -0
  22. package/dist/cjs/types/helpers/getTimeInfoInET.d.ts +17 -0
  23. package/{lib/cjs/lib → dist/cjs/types}/helpers/handleError.d.ts +5 -1
  24. package/{lib/cjs/lib → dist/cjs/types}/helpers/handleSuccess.d.ts +0 -0
  25. package/{lib/cjs/lib → dist/cjs/types}/helpers/padDecimalZeros.d.ts +0 -0
  26. package/{lib/cjs/lib → dist/cjs/types}/helpers/padZerosLeft.d.ts +0 -0
  27. package/{lib/cjs/lib → dist/cjs/types}/helpers/roundToNumDecimals.d.ts +0 -0
  28. package/{lib/cjs/lib → dist/cjs/types}/helpers/sum.d.ts +0 -0
  29. package/{lib/cjs/lib → dist/cjs/types}/helpers/visitServerEndpoint.d.ts +0 -0
  30. package/{lib/cjs/lib → dist/cjs/types}/helpers/waitMs.d.ts +0 -0
  31. package/{lib/cjs/lib → dist/cjs/types}/index.d.ts +14 -6
  32. package/dist/cjs/types/server/initServer.d.ts +21 -0
  33. package/{lib/cjs/lib → dist/cjs/types}/types/ModalButtonType.d.ts +0 -0
  34. package/{lib/cjs/lib → dist/cjs/types}/types/ModalSize.d.ts +0 -0
  35. package/{lib/cjs/lib → dist/cjs/types}/types/ModalType.d.ts +0 -0
  36. package/{lib → dist}/cjs/types/types/ParamType.d.ts +1 -1
  37. package/{lib → dist}/cjs/types/types/ReactKitErrorCode.d.ts +3 -1
  38. package/{lib/cjs/lib → dist/cjs/types}/types/Variant.d.ts +0 -0
  39. package/dist/esm/index.js +1782 -0
  40. package/dist/esm/index.js.map +1 -0
  41. package/{lib/cjs → dist/esm}/types/components/AppWrapper.d.ts +29 -4
  42. package/dist/esm/types/components/ButtonInputGroup.d.ts +12 -0
  43. package/dist/esm/types/components/CheckboxButton.d.ts +20 -0
  44. package/dist/esm/types/components/Drawer.d.ts +10 -0
  45. package/{lib/cjs → dist/esm}/types/components/ErrorBox.d.ts +0 -0
  46. package/{lib/cjs → dist/esm}/types/components/LoadingSpinner.d.ts +0 -0
  47. package/{lib/cjs → dist/esm}/types/components/Modal.d.ts +0 -0
  48. package/dist/esm/types/components/RadioButton.d.ts +20 -0
  49. package/dist/esm/types/components/SimpleDateChooser.d.ts +22 -0
  50. package/{lib/cjs/lib → dist/esm/types}/components/TabBox.d.ts +1 -0
  51. package/{lib/cjs → dist/esm}/types/errors/ErrorWithCode.d.ts +0 -0
  52. package/{lib/cjs → dist/esm}/types/helpers/abbreviate.d.ts +0 -0
  53. package/{lib/cjs → dist/esm}/types/helpers/avg.d.ts +0 -0
  54. package/{lib/cjs → dist/esm}/types/helpers/ceilToNumDecimals.d.ts +0 -0
  55. package/{lib/cjs → dist/esm}/types/helpers/floorToNumDecimals.d.ts +0 -0
  56. package/{lib/cjs → dist/esm}/types/helpers/forceNumIntoBounds.d.ts +0 -0
  57. package/dist/esm/types/helpers/genRouteHandler.d.ts +32 -0
  58. package/dist/esm/types/helpers/getOrdinal.d.ts +8 -0
  59. package/dist/esm/types/helpers/getTimeInfoInET.d.ts +17 -0
  60. package/{lib/cjs → dist/esm}/types/helpers/handleError.d.ts +5 -1
  61. package/{lib/cjs → dist/esm}/types/helpers/handleSuccess.d.ts +0 -0
  62. package/{lib/cjs → dist/esm}/types/helpers/padDecimalZeros.d.ts +0 -0
  63. package/{lib/cjs → dist/esm}/types/helpers/padZerosLeft.d.ts +0 -0
  64. package/{lib/cjs → dist/esm}/types/helpers/roundToNumDecimals.d.ts +0 -0
  65. package/{lib/cjs → dist/esm}/types/helpers/sum.d.ts +0 -0
  66. package/{lib/cjs → dist/esm}/types/helpers/visitServerEndpoint.d.ts +0 -0
  67. package/{lib/cjs → dist/esm}/types/helpers/waitMs.d.ts +0 -0
  68. package/{lib/esm/lib → dist/esm/types}/index.d.ts +14 -6
  69. package/dist/esm/types/server/initServer.d.ts +21 -0
  70. package/{lib/cjs → dist/esm}/types/types/ModalButtonType.d.ts +0 -0
  71. package/{lib/cjs → dist/esm}/types/types/ModalSize.d.ts +0 -0
  72. package/{lib/cjs → dist/esm}/types/types/ModalType.d.ts +0 -0
  73. package/{lib/esm/lib → dist/esm/types}/types/ParamType.d.ts +1 -1
  74. package/{lib/esm/lib → dist/esm/types}/types/ReactKitErrorCode.d.ts +3 -1
  75. package/{lib/cjs → dist/esm}/types/types/Variant.d.ts +0 -0
  76. package/dist/index.d.ts +505 -0
  77. package/package.json +18 -29
  78. package/rollup.config.js +14 -13
  79. package/src/components/AppWrapper.tsx +85 -18
  80. package/src/components/ButtonInputGroup.tsx +89 -0
  81. package/src/components/CheckboxButton.tsx +100 -0
  82. package/src/components/Drawer.tsx +76 -0
  83. package/src/components/ErrorBox.tsx +4 -4
  84. package/src/components/LoadingSpinner.tsx +3 -3
  85. package/src/components/Modal.tsx +214 -97
  86. package/src/components/RadioButton.tsx +102 -0
  87. package/src/components/SimpleDateChooser.tsx +222 -0
  88. package/src/components/TabBox.tsx +65 -58
  89. package/src/helpers/genRouteHandler.ts +329 -0
  90. package/src/helpers/getOrdinal.tsx +13 -0
  91. package/src/helpers/getTimeInfoInET.tsx +56 -0
  92. package/src/helpers/handleError.ts +65 -0
  93. package/src/helpers/handleSuccess.ts +18 -0
  94. package/src/helpers/visitServerEndpoint.tsx +110 -0
  95. package/src/index.ts +29 -0
  96. package/src/server/initServer.ts +53 -0
  97. package/src/types/ParamType.ts +18 -0
  98. package/src/types/ReactKitErrorCode.tsx +3 -1
  99. package/tsconfig.json +9 -7
  100. package/lib/cjs/index.js +0 -2
  101. package/lib/cjs/index.js.map +0 -1
  102. package/lib/cjs/lib/components/AppWrapper.d.ts +0 -35
  103. package/lib/cjs/lib/helpers/parseRequest.d.ts +0 -21
  104. package/lib/cjs/lib/types/ParamType.d.ts +0 -17
  105. package/lib/cjs/lib/types/ReactKitErrorCode.d.ts +0 -13
  106. package/lib/cjs/types/components/TabBox.d.ts +0 -11
  107. package/lib/cjs/types/helpers/alert.d.ts +0 -2
  108. package/lib/cjs/types/helpers/confirm.d.ts +0 -2
  109. package/lib/cjs/types/helpers/parseRequest.d.ts +0 -21
  110. package/lib/cjs/types/helpers/showFatalError.d.ts +0 -2
  111. package/lib/cjs/types/index.d.ts +0 -22
  112. package/lib/esm/index.js +0 -16
  113. package/lib/esm/index.js.map +0 -1
  114. package/lib/esm/lib/components/AppWrapper.d.ts +0 -35
  115. package/lib/esm/lib/components/ErrorBox.d.ts +0 -12
  116. package/lib/esm/lib/components/LoadingSpinner.d.ts +0 -7
  117. package/lib/esm/lib/components/Modal.d.ts +0 -40
  118. package/lib/esm/lib/errors/ErrorWithCode.d.ts +0 -9
  119. package/lib/esm/lib/helpers/abbreviate.d.ts +0 -10
  120. package/lib/esm/lib/helpers/avg.d.ts +0 -8
  121. package/lib/esm/lib/helpers/ceilToNumDecimals.d.ts +0 -9
  122. package/lib/esm/lib/helpers/floorToNumDecimals.d.ts +0 -9
  123. package/lib/esm/lib/helpers/forceNumIntoBounds.d.ts +0 -10
  124. package/lib/esm/lib/helpers/handleError.d.ts +0 -14
  125. package/lib/esm/lib/helpers/handleSuccess.d.ts +0 -8
  126. package/lib/esm/lib/helpers/padDecimalZeros.d.ts +0 -10
  127. package/lib/esm/lib/helpers/padZerosLeft.d.ts +0 -9
  128. package/lib/esm/lib/helpers/parseRequest.d.ts +0 -21
  129. package/lib/esm/lib/helpers/roundToNumDecimals.d.ts +0 -9
  130. package/lib/esm/lib/helpers/sum.d.ts +0 -8
  131. package/lib/esm/lib/helpers/visitServerEndpoint.d.ts +0 -23
  132. package/lib/esm/lib/helpers/waitMs.d.ts +0 -7
  133. package/lib/esm/lib/types/ModalButtonType.d.ts +0 -17
  134. package/lib/esm/lib/types/ModalSize.d.ts +0 -11
  135. package/lib/esm/lib/types/ModalType.d.ts +0 -16
  136. package/lib/esm/lib/types/Variant.d.ts +0 -15
  137. package/lib/esm/types/components/ErrorBox.d.ts +0 -12
  138. package/lib/esm/types/components/LoadingSpinner.d.ts +0 -7
  139. package/lib/esm/types/components/Modal.d.ts +0 -40
  140. package/lib/esm/types/components/TabBox.d.ts +0 -11
  141. package/lib/esm/types/errors/ErrorWithCode.d.ts +0 -9
  142. package/lib/esm/types/helpers/abbreviate.d.ts +0 -10
  143. package/lib/esm/types/helpers/alert.d.ts +0 -2
  144. package/lib/esm/types/helpers/avg.d.ts +0 -8
  145. package/lib/esm/types/helpers/ceilToNumDecimals.d.ts +0 -9
  146. package/lib/esm/types/helpers/confirm.d.ts +0 -2
  147. package/lib/esm/types/helpers/floorToNumDecimals.d.ts +0 -9
  148. package/lib/esm/types/helpers/forceNumIntoBounds.d.ts +0 -10
  149. package/lib/esm/types/helpers/handleError.d.ts +0 -14
  150. package/lib/esm/types/helpers/handleSuccess.d.ts +0 -8
  151. package/lib/esm/types/helpers/padDecimalZeros.d.ts +0 -10
  152. package/lib/esm/types/helpers/padZerosLeft.d.ts +0 -9
  153. package/lib/esm/types/helpers/parseRequest.d.ts +0 -21
  154. package/lib/esm/types/helpers/roundToNumDecimals.d.ts +0 -9
  155. package/lib/esm/types/helpers/showFatalError.d.ts +0 -2
  156. package/lib/esm/types/helpers/sum.d.ts +0 -8
  157. package/lib/esm/types/helpers/visitServerEndpoint.d.ts +0 -23
  158. package/lib/esm/types/helpers/waitMs.d.ts +0 -7
  159. package/lib/esm/types/index.d.ts +0 -22
  160. package/lib/esm/types/types/ModalButtonType.d.ts +0 -17
  161. package/lib/esm/types/types/ModalSize.d.ts +0 -11
  162. package/lib/esm/types/types/ModalType.d.ts +0 -16
  163. package/lib/esm/types/types/ParamType.d.ts +0 -17
  164. package/lib/esm/types/types/ReactKitErrorCode.d.ts +0 -13
  165. package/lib/esm/types/types/Variant.d.ts +0 -15
  166. package/lib/index.d.ts +0 -274
@@ -0,0 +1,1826 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var React = require('react');
6
+ var reactFontawesome = require('@fortawesome/react-fontawesome');
7
+ var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons');
8
+ var freeRegularSvgIcons = require('@fortawesome/free-regular-svg-icons');
9
+
10
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
+
12
+ var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
13
+
14
+ /******************************************************************************
15
+ Copyright (c) Microsoft Corporation.
16
+
17
+ Permission to use, copy, modify, and/or distribute this software for any
18
+ purpose with or without fee is hereby granted.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
21
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
22
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
23
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
24
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
25
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
26
+ PERFORMANCE OF THIS SOFTWARE.
27
+ ***************************************************************************** */
28
+
29
+ function __awaiter(thisArg, _arguments, P, generator) {
30
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
31
+ return new (P || (P = Promise))(function (resolve, reject) {
32
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
33
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
34
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
35
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
36
+ });
37
+ }
38
+
39
+ // Highest error code = DRK8
40
+ /**
41
+ * List of error codes built into the react kit
42
+ * @author Gabe Abrams
43
+ */
44
+ var ReactKitErrorCode;
45
+ (function (ReactKitErrorCode) {
46
+ ReactKitErrorCode["NoResponse"] = "DRK1";
47
+ ReactKitErrorCode["NoCode"] = "DRK2";
48
+ ReactKitErrorCode["SessionExpired"] = "DRK3";
49
+ ReactKitErrorCode["MissingParameter"] = "DRK4";
50
+ ReactKitErrorCode["InvalidParameter"] = "DRK5";
51
+ ReactKitErrorCode["WrongCourse"] = "DRK6";
52
+ ReactKitErrorCode["NoCACCLSendRequestFunction"] = "DRK7";
53
+ ReactKitErrorCode["NoCACCLGetLaunchInfoFunction"] = "DRK8";
54
+ })(ReactKitErrorCode || (ReactKitErrorCode = {}));
55
+ var ReactKitErrorCode$1 = ReactKitErrorCode;
56
+
57
+ /**
58
+ * Displays an error
59
+ * @author Gabe Abrams
60
+ */
61
+ /*------------------------------------------------------------------------*/
62
+ /* Component */
63
+ /*------------------------------------------------------------------------*/
64
+ const ErrorBox = (props) => {
65
+ /*------------------------------------------------------------------------*/
66
+ /* Setup */
67
+ /*------------------------------------------------------------------------*/
68
+ var _a;
69
+ /* -------------- Props ------------- */
70
+ const { error, title = 'An Error Occurred', onClose, } = props;
71
+ /*------------------------------------------------------------------------*/
72
+ /* Render */
73
+ /*------------------------------------------------------------------------*/
74
+ // Determine error text
75
+ const errorText = (typeof error === 'string'
76
+ ? error.trim()
77
+ : String(error.message || 'An unknown error occurred. Please contact support.'));
78
+ // Error code box
79
+ let errorCodeBox;
80
+ if (error && error.code) {
81
+ errorCodeBox = (React__default["default"].createElement("span", null,
82
+ ' ',
83
+ React__default["default"].createElement("span", { style: {
84
+ backgroundColor: 'white',
85
+ borderRadius: '0.3rem',
86
+ paddingLeft: '0.2rem',
87
+ paddingRight: '0.2rem',
88
+ color: '#DC4150',
89
+ fontVariant: 'small-caps',
90
+ fontSize: '80%',
91
+ whiteSpace: 'nowrap',
92
+ } },
93
+ "code:",
94
+ ' ',
95
+ String((_a = error.code) !== null && _a !== void 0 ? _a : ReactKitErrorCode$1.NoCode).toUpperCase())));
96
+ }
97
+ // Main UI
98
+ return (React__default["default"].createElement("div", { className: "alert alert-danger text-center", style: {
99
+ maxWidth: '40rem',
100
+ margin: 'auto',
101
+ } },
102
+ React__default["default"].createElement("h4", { className: "mb-1" },
103
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faExclamationTriangle, className: "me-2" }),
104
+ title),
105
+ React__default["default"].createElement("div", null,
106
+ errorText,
107
+ errorCodeBox),
108
+ onClose && (React__default["default"].createElement("div", { className: "mt-2" },
109
+ React__default["default"].createElement("button", { type: "button", className: "btn btn-light", "aria-label": "dismiss error and close this activity or view", onClick: onClose }, "Close")))));
110
+ };
111
+
112
+ /**
113
+ * Types of buttons in the modal
114
+ * @author Gabe Abrams
115
+ */
116
+ var ModalButtonType;
117
+ (function (ModalButtonType) {
118
+ ModalButtonType["Okay"] = "okay";
119
+ ModalButtonType["Cancel"] = "cancel";
120
+ ModalButtonType["Yes"] = "yes";
121
+ ModalButtonType["No"] = "no";
122
+ ModalButtonType["Abandon"] = "abandon";
123
+ ModalButtonType["GoBack"] = "goBack";
124
+ ModalButtonType["Continue"] = "continue";
125
+ ModalButtonType["ImSure"] = "imSure";
126
+ ModalButtonType["Delete"] = "delete";
127
+ ModalButtonType["Confirm"] = "confirm";
128
+ })(ModalButtonType || (ModalButtonType = {}));
129
+ var ModalButtonType$1 = ModalButtonType;
130
+
131
+ /**
132
+ * Types of modals
133
+ * @author Gabe Abrams
134
+ */
135
+ var ModalType;
136
+ (function (ModalType) {
137
+ ModalType["Okay"] = "okay";
138
+ ModalType["OkayCancel"] = "okay-cancel";
139
+ ModalType["YesNo"] = "yes-no";
140
+ ModalType["YesNoCancel"] = "yes-no-cancel";
141
+ ModalType["AbandonGoBack"] = "abandon-goBack";
142
+ ModalType["ImSureCancel"] = "imSure-cancel";
143
+ ModalType["DeleteCancel"] = "delete-cancel";
144
+ ModalType["ConfirmCancel"] = "confirm-cancel";
145
+ ModalType["NoButtons"] = "-";
146
+ })(ModalType || (ModalType = {}));
147
+ var ModalType$1 = ModalType;
148
+
149
+ /**
150
+ * Wait for a certain number of ms
151
+ * @author Gabe Abrams
152
+ * @param ms number of ms to wait
153
+ */
154
+ const waitMs = (ms = 0) => __awaiter(void 0, void 0, void 0, function* () {
155
+ return new Promise((resolve) => {
156
+ setTimeout(resolve, ms);
157
+ });
158
+ });
159
+
160
+ /**
161
+ * Bootstrap variants
162
+ * @author Gabe Abrams
163
+ */
164
+ var Variant;
165
+ (function (Variant) {
166
+ Variant["Primary"] = "primary";
167
+ Variant["Secondary"] = "secondary";
168
+ Variant["Success"] = "success";
169
+ Variant["Warning"] = "warning";
170
+ Variant["Info"] = "info";
171
+ Variant["Danger"] = "danger";
172
+ Variant["Light"] = "light";
173
+ Variant["Dark"] = "dark";
174
+ })(Variant || (Variant = {}));
175
+ var Variant$1 = Variant;
176
+
177
+ /**
178
+ * Modal sizes
179
+ * @author Gabe Abrams
180
+ */
181
+ var ModalSize;
182
+ (function (ModalSize) {
183
+ ModalSize["Small"] = "sm";
184
+ ModalSize["Medium"] = "md";
185
+ ModalSize["Large"] = "lg";
186
+ ModalSize["ExtraLarge"] = "xl";
187
+ })(ModalSize || (ModalSize = {}));
188
+ var ModalSize$1 = ModalSize;
189
+
190
+ /**
191
+ * A generic popup modal
192
+ * @author Gabe Abrams
193
+ */
194
+ /*------------------------------------------------------------------------*/
195
+ /* Constants */
196
+ /*------------------------------------------------------------------------*/
197
+ // Constants
198
+ const MS_TO_ANIMATE = 200; // Animation duration
199
+ // Modal type to list of buttons
200
+ const modalTypeToModalButtonTypes = {
201
+ [ModalType$1.Okay]: [
202
+ ModalButtonType$1.Okay,
203
+ ],
204
+ [ModalType$1.OkayCancel]: [
205
+ ModalButtonType$1.Okay,
206
+ ModalButtonType$1.Cancel,
207
+ ],
208
+ [ModalType$1.YesNo]: [
209
+ ModalButtonType$1.Yes,
210
+ ModalButtonType$1.No,
211
+ ],
212
+ [ModalType$1.YesNoCancel]: [
213
+ ModalButtonType$1.Yes,
214
+ ModalButtonType$1.No,
215
+ ModalButtonType$1.Cancel,
216
+ ],
217
+ [ModalType$1.AbandonGoBack]: [
218
+ ModalButtonType$1.Abandon,
219
+ ModalButtonType$1.GoBack,
220
+ ],
221
+ [ModalType$1.ImSureCancel]: [
222
+ ModalButtonType$1.ImSure,
223
+ ModalButtonType$1.Cancel,
224
+ ],
225
+ [ModalType$1.DeleteCancel]: [
226
+ ModalButtonType$1.Delete,
227
+ ModalButtonType$1.Cancel,
228
+ ],
229
+ [ModalType$1.ConfirmCancel]: [
230
+ ModalButtonType$1.Confirm,
231
+ ModalButtonType$1.Cancel,
232
+ ],
233
+ };
234
+ // Button type styling and labels
235
+ const ModalButtonTypeToLabelAndVariant = {
236
+ [ModalButtonType$1.Okay]: {
237
+ label: 'Okay',
238
+ variant: Variant$1.Dark,
239
+ },
240
+ [ModalButtonType$1.Cancel]: {
241
+ label: 'Cancel',
242
+ variant: Variant$1.Secondary,
243
+ },
244
+ [ModalButtonType$1.Yes]: {
245
+ label: 'Yes',
246
+ variant: Variant$1.Dark,
247
+ },
248
+ [ModalButtonType$1.No]: {
249
+ label: 'No',
250
+ variant: Variant$1.Secondary,
251
+ },
252
+ [ModalButtonType$1.Abandon]: {
253
+ label: 'Abandon Changes',
254
+ variant: Variant$1.Warning,
255
+ },
256
+ [ModalButtonType$1.GoBack]: {
257
+ label: 'Go Back',
258
+ variant: Variant$1.Secondary,
259
+ },
260
+ [ModalButtonType$1.Continue]: {
261
+ label: 'Continue',
262
+ variant: Variant$1.Dark,
263
+ },
264
+ [ModalButtonType$1.ImSure]: {
265
+ label: 'I am sure',
266
+ variant: Variant$1.Warning,
267
+ },
268
+ [ModalButtonType$1.Delete]: {
269
+ label: 'Yes, Delete',
270
+ variant: Variant$1.Danger,
271
+ },
272
+ [ModalButtonType$1.Confirm]: {
273
+ label: 'Confirm',
274
+ variant: Variant$1.Dark,
275
+ },
276
+ };
277
+ /*------------------------------------------------------------------------*/
278
+ /* Style */
279
+ /*------------------------------------------------------------------------*/
280
+ const style$3 = `
281
+ .Modal-backdrop {
282
+ position: fixed;
283
+ top: 0;
284
+ left: 0;
285
+ width: 100vw;
286
+ height: 100vw;
287
+ background-color: rgba(0, 0, 0, 0.7);
288
+ }
289
+
290
+ .Modal-fading-in {
291
+ animation-name: Modal-fading-in;
292
+ animation-duration: ${Math.floor(MS_TO_ANIMATE * 2)}ms;
293
+ animation-iteration-count: 1;
294
+ animation-fill-mode: both;
295
+ animation-timing-function: ease-out;
296
+ }
297
+
298
+ @keyframes Modal-fading-in {
299
+ 0% {
300
+ opacity: 0;
301
+ }
302
+ 100% {
303
+ opacity: 1;
304
+ }
305
+ }
306
+
307
+ .Modal-fading-out {
308
+ animation-name: Modal-fading-out;
309
+ animation-duration: ${MS_TO_ANIMATE}ms;
310
+ animation-iteration-count: 1;
311
+ animation-fill-mode: both;
312
+ animation-timing-function: ease-in;
313
+ }
314
+
315
+ @keyframes Modal-fading-out {
316
+ 0% {
317
+ opacity: 1;
318
+ }
319
+ 100% {
320
+ opacity: 0;
321
+ }
322
+ }
323
+
324
+ .Modal-animating-in {
325
+ animation-name: Modal-animating-in;
326
+ animation-duration: ${MS_TO_ANIMATE}ms;
327
+ animation-iteration-count: 1;
328
+ animation-fill-mode: both;
329
+ animation-timing-function: ease-out;
330
+ }
331
+
332
+ @keyframes Modal-animating-in {
333
+ 0% {
334
+ transform: scale(1.05) translate(0, -1.5rem);
335
+ opacity: 0;
336
+ }
337
+ 100% {
338
+ transform: scale(1) translate(0, 0);
339
+ opacity: 1;
340
+ }
341
+ }
342
+
343
+ .Modal-animating-pop {
344
+ animation-name: Modal-animating-pop;
345
+ animation-duration: ${MS_TO_ANIMATE}ms;
346
+ animation-iteration-count: 1;
347
+ animation-fill-mode: both;
348
+ animation-timing-function: ease-in-out;
349
+ }
350
+
351
+ @keyframes Modal-animating-pop {
352
+ 0% {
353
+ transform: scale(1);
354
+ opacity: 1;
355
+ }
356
+ 50% {
357
+ transform: scale(1.05);
358
+ opacity: 0.9;
359
+ }
360
+ 100% {
361
+ transform: scale(1);
362
+ opacity: 1;
363
+ }
364
+ }
365
+
366
+ .Modal-animating-out {
367
+ animation-name: Modal-animating-out;
368
+ animation-duration: ${MS_TO_ANIMATE}ms;
369
+ animation-iteration-count: 1;
370
+ animation-fill-mode: both;
371
+ animation-timing-function: ease-in;
372
+ }
373
+
374
+ @keyframes Modal-animating-out {
375
+ 0% {
376
+ transform: scale(1) translate(0, 0);
377
+ opacity: 1;
378
+ }
379
+ 100% {
380
+ transform: scale(1.05) translate(0, -1.5rem);
381
+ opacity: 0;
382
+ }
383
+ }
384
+ `;
385
+ /*------------------------------------------------------------------------*/
386
+ /* Component */
387
+ /*------------------------------------------------------------------------*/
388
+ const Modal = (props) => {
389
+ /*------------------------------------------------------------------------*/
390
+ /* Setup */
391
+ /*------------------------------------------------------------------------*/
392
+ var _a;
393
+ /* -------------- Props ------------- */
394
+ const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, children, onClose, dontAllowBackdropExit, onTopOfOtherModals, } = props;
395
+ /* -------------- State ------------- */
396
+ // If true, the modal is completely gone
397
+ // (not just invisible, also removed from the dom)
398
+ const [gone, setGone] = React.useState(false);
399
+ // If true, the modal is shown
400
+ const [visible, setVisible] = React.useState(false);
401
+ // True if animation is in use
402
+ const [animatingIn, setAnimatingIn] = React.useState(true);
403
+ const [animatingPop, setAnimatingPop] = React.useState(false);
404
+ const [animatingOut, setAnimatingOut] = React.useState(false);
405
+ /*------------------------------------------------------------------------*/
406
+ /* Lifecycle Functions */
407
+ /*------------------------------------------------------------------------*/
408
+ /**
409
+ * Mount
410
+ * @author Gabe Abrams
411
+ */
412
+ React.useEffect(() => {
413
+ (() => __awaiter(void 0, void 0, void 0, function* () {
414
+ // Wait and then set visible to true
415
+ yield waitMs(MS_TO_ANIMATE);
416
+ setVisible(true);
417
+ setAnimatingIn(false);
418
+ }))();
419
+ }, []);
420
+ /*------------------------------------------------------------------------*/
421
+ /* Component Functions */
422
+ /*------------------------------------------------------------------------*/
423
+ /**
424
+ * Handles the closing of the modal
425
+ * @author Gabe Abrams
426
+ * @param ModalButtonType the button that was clicked when closing the
427
+ * modal
428
+ */
429
+ const handleClose = (ModalButtonType) => __awaiter(void 0, void 0, void 0, function* () {
430
+ // Don't close if no handler
431
+ if (!onClose) {
432
+ return;
433
+ }
434
+ // Don't close if animating in
435
+ if (animatingIn) {
436
+ return;
437
+ }
438
+ // Don't close if already closed
439
+ if (!visible) {
440
+ return;
441
+ }
442
+ // Update the state
443
+ setVisible(false);
444
+ setAnimatingOut(true);
445
+ // Call the handler after the modal has animated out
446
+ yield waitMs(MS_TO_ANIMATE);
447
+ onClose(ModalButtonType);
448
+ setGone(true);
449
+ });
450
+ /*------------------------------------------------------------------------*/
451
+ /* Render */
452
+ /*------------------------------------------------------------------------*/
453
+ // Return nothing if gone
454
+ if (gone) {
455
+ return null;
456
+ }
457
+ /*----------------------------------------*/
458
+ /* Footer */
459
+ /*----------------------------------------*/
460
+ // Get list of buttons for this modal type
461
+ const ModalButtonTypes = (_a = modalTypeToModalButtonTypes[type]) !== null && _a !== void 0 ? _a : [];
462
+ // Create buttons
463
+ const buttons = ModalButtonTypes.map((ModalButtonType, i) => {
464
+ // Get default style
465
+ let { label, variant, } = ModalButtonTypeToLabelAndVariant[ModalButtonType];
466
+ // Override with customizations
467
+ const newLabel = props[`${ModalButtonType}Label`];
468
+ if (newLabel) {
469
+ label = newLabel;
470
+ }
471
+ const newVariant = props[`${ModalButtonType}Variant`];
472
+ if (newVariant) {
473
+ variant = newVariant;
474
+ }
475
+ // Check if this button is last
476
+ const last = (i === ModalButtonTypes.length - 1);
477
+ // Create the button
478
+ return (React__default["default"].createElement("button", { key: ModalButtonType, type: "button", className: `Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'me-1'}`, onClick: () => {
479
+ handleClose(ModalButtonType);
480
+ } }, label));
481
+ });
482
+ // Put all buttons in a footer
483
+ const footer = ((buttons && buttons.length)
484
+ ? (React__default["default"].createElement("div", null, buttons))
485
+ : undefined);
486
+ // Choose an animation
487
+ let animationClass = '';
488
+ let backdropAnimationClass = '';
489
+ if (animatingIn) {
490
+ animationClass = 'Modal-animating-in';
491
+ backdropAnimationClass = 'Modal-fading-in';
492
+ }
493
+ else if (animatingOut) {
494
+ animationClass = 'Modal-animating-out';
495
+ backdropAnimationClass = 'Modal-fading-out';
496
+ }
497
+ else if (animatingPop) {
498
+ animationClass = 'Modal-animating-pop';
499
+ }
500
+ // Render the modal
501
+ return (React__default["default"].createElement("div", { className: `modal show modal-dialog-scrollable modal-dialog-centered modal-${size}`, tabIndex: -1, style: {
502
+ zIndex: (onTopOfOtherModals
503
+ ? 5000000001
504
+ : 5000000000),
505
+ display: 'block',
506
+ margin: 'auto',
507
+ left: 0,
508
+ right: 0,
509
+ } },
510
+ React__default["default"].createElement("style", null, style$3),
511
+ React__default["default"].createElement("div", { className: `Modal-backdrop ${backdropAnimationClass}`, style: {
512
+ zIndex: 5000000003,
513
+ }, onClick: () => __awaiter(void 0, void 0, void 0, function* () {
514
+ // Skip if exit via backdrop not allowed
515
+ if (dontAllowBackdropExit || !onClose) {
516
+ // Show pop animation
517
+ if (!animatingPop) {
518
+ setAnimatingPop(true);
519
+ // Wait then stop pop animation
520
+ yield waitMs(MS_TO_ANIMATE);
521
+ setAnimatingPop(false);
522
+ }
523
+ return;
524
+ }
525
+ // Handle close
526
+ handleClose(ModalButtonType$1.Cancel);
527
+ }) }),
528
+ React__default["default"].createElement("div", { className: `modal-dialog ${animationClass}`, style: {
529
+ zIndex: 5000000002,
530
+ } },
531
+ React__default["default"].createElement("div", { className: "modal-content" },
532
+ React__default["default"].createElement("div", { className: "modal-header" },
533
+ React__default["default"].createElement("h5", { className: "modal-title", style: {
534
+ fontWeight: 'bold',
535
+ } }, title),
536
+ onClose && (React__default["default"].createElement("button", { type: "button", className: "btn-close", "aria-label": "Close", onClick: () => {
537
+ // Handle close
538
+ handleClose(ModalButtonType$1.Cancel);
539
+ } }))),
540
+ children && (React__default["default"].createElement("div", { className: "modal-body" }, children)),
541
+ footer && (React__default["default"].createElement("div", { className: "modal-footer pt-1 pb-1" }, footer))))));
542
+ };
543
+
544
+ /**
545
+ * An error with a code
546
+ * @author Gabe Abrams
547
+ */
548
+ class ErrorWithCode extends Error {
549
+ constructor(message, code) {
550
+ super(message);
551
+ this.name = 'ErrorWithCode';
552
+ this.code = code;
553
+ }
554
+ }
555
+
556
+ /**
557
+ * A wrapper for the entire React app that adds global functionality like
558
+ * handling for fatal error messages, adds bootstrap support
559
+ * @author Gabe Abrams
560
+ */
561
+ /*------------------------------------------------------------------------*/
562
+ /* Static Helpers */
563
+ /*------------------------------------------------------------------------*/
564
+ /*----------------------------------------*/
565
+ /* Send Request */
566
+ /*----------------------------------------*/
567
+ // Store copy of caccl send request
568
+ let _cacclSendRequest;
569
+ /**
570
+ * Send a request using caccl's send request feature
571
+ * @author Gabe Abrams
572
+ * @param opts send request options
573
+ * @returns send request response
574
+ */
575
+ const cacclSendRequest = (opts) => __awaiter(void 0, void 0, void 0, function* () {
576
+ // Make sure send request has been passed in
577
+ if (!_cacclSendRequest) {
578
+ throw new ErrorWithCode('The request could not be sent because the AppWrapper component does not have a copy of sendRequest from CACCL.', ReactKitErrorCode$1.NoCACCLSendRequestFunction);
579
+ }
580
+ return _cacclSendRequest(opts);
581
+ });
582
+ /*----------------------------------------*/
583
+ /* Alert */
584
+ /*----------------------------------------*/
585
+ // Stored copies of setters
586
+ let setAlertInfo;
587
+ let onAlertClosed;
588
+ /**
589
+ * Show an alert modal with an "Okay" button
590
+ * @author Gabe Abrams
591
+ * @param title the title text to display at the top of the alert
592
+ * @param text the text to display in the alert
593
+ */
594
+ const alert$1 = (title, text) => __awaiter(void 0, void 0, void 0, function* () {
595
+ // Fallback if alert not available
596
+ if (!setAlertInfo) {
597
+ window.alert(`${title}\n\n${text}`);
598
+ return undefined;
599
+ }
600
+ // Return promise that resolves when alert is closed
601
+ return new Promise((resolve) => {
602
+ // Setup handler
603
+ onAlertClosed = () => {
604
+ resolve(undefined);
605
+ };
606
+ // Show the alert
607
+ setAlertInfo({
608
+ title,
609
+ text,
610
+ });
611
+ });
612
+ });
613
+ /*----------------------------------------*/
614
+ /* Confirm */
615
+ /*----------------------------------------*/
616
+ // Stored copies of setters
617
+ let setConfirmInfo;
618
+ let onConfirmClosed;
619
+ /**
620
+ * Show a confirmation modal with an "Okay" and a "Cancel" button
621
+ * @author Gabe Abrams
622
+ * @param title the title text to display at the top of the alert
623
+ * @param text the text to display in the alert
624
+ * @returns true if the user confirmed
625
+ */
626
+ const confirm = (title, text) => __awaiter(void 0, void 0, void 0, function* () {
627
+ // Fallback if confirm is not available
628
+ if (!setConfirmInfo) {
629
+ return window.confirm(`${title}\n\n${text}`);
630
+ }
631
+ // Return promise that resolves with result of confirmation
632
+ return new Promise((resolve) => {
633
+ // Setup handler
634
+ onConfirmClosed = (confirmed) => {
635
+ resolve(confirmed);
636
+ };
637
+ // Show the confirm
638
+ setConfirmInfo({
639
+ title,
640
+ text,
641
+ });
642
+ });
643
+ });
644
+ /*----------------------------------------*/
645
+ /* Fatal Error */
646
+ /*----------------------------------------*/
647
+ // Stored copies of setters
648
+ let setFatalErrorMessage;
649
+ let setFatalErrorCode;
650
+ let setFatalErrorTitle;
651
+ /**
652
+ * Show a fatal error message
653
+ * @author Gabe Abrams
654
+ * @param error the error to show
655
+ * @param [errorTitle] title of the error box
656
+ */
657
+ const showFatalError = (error, errorTitle = 'An Error Occurred') => {
658
+ var _a, _b;
659
+ // Determine message and code
660
+ const message = (typeof error === 'string'
661
+ ? error.trim()
662
+ : String((_a = error.message) !== null && _a !== void 0 ? _a : 'An unknown error occurred.'));
663
+ const code = (typeof error === 'string'
664
+ ? ReactKitErrorCode$1.NoCode
665
+ : String((_b = error.code) !== null && _b !== void 0 ? _b : ReactKitErrorCode$1.NoCode));
666
+ // Handle case where app hasn't loaded
667
+ if (!setFatalErrorMessage || !setFatalErrorCode) {
668
+ alert$1(errorTitle, `${message} (code: ${code}). Please contact support.`);
669
+ return undefined;
670
+ }
671
+ // Use setters
672
+ setFatalErrorMessage(message);
673
+ setFatalErrorCode(code);
674
+ setFatalErrorTitle(errorTitle);
675
+ return undefined;
676
+ };
677
+ /*------------------------------------------------------------------------*/
678
+ /* Component */
679
+ /*------------------------------------------------------------------------*/
680
+ const AppWrapper = (props) => {
681
+ /*------------------------------------------------------------------------*/
682
+ /* Setup */
683
+ /*------------------------------------------------------------------------*/
684
+ /* -------------- Props ------------- */
685
+ const { children, sendRequest, dark, sessionExpiredMessage = 'Your session has expired. Please go back to Canvas and start over.', } = props;
686
+ // Store copy of send request
687
+ _cacclSendRequest = sendRequest;
688
+ /* -------------- State ------------- */
689
+ // Fatal error
690
+ const [fatalErrorMessage, setFatalErrorMessageInner,] = React.useState();
691
+ setFatalErrorMessage = setFatalErrorMessageInner;
692
+ const [fatalErrorCode, setFatalErrorCodeInner,] = React.useState();
693
+ setFatalErrorCode = setFatalErrorCodeInner;
694
+ const [fatalErrorTitle, setFatalErrorTitleInner,] = React.useState();
695
+ setFatalErrorTitle = setFatalErrorTitleInner;
696
+ // Alert
697
+ const [alertInfo, setAlertInfoInner,] = React.useState(undefined);
698
+ setAlertInfo = setAlertInfoInner;
699
+ // Confirm
700
+ const [confirmInfo, setConfirmInfoInner,] = React.useState(undefined);
701
+ setConfirmInfo = setConfirmInfoInner;
702
+ // Session expired
703
+ const [sessionHasExpired, setSessionHasExpiredInner,] = React.useState(false);
704
+ /*------------------------------------------------------------------------*/
705
+ /* Render */
706
+ /*------------------------------------------------------------------------*/
707
+ /*----------------------------------------*/
708
+ /* Modal */
709
+ /*----------------------------------------*/
710
+ // Modal that may be defined
711
+ let modal;
712
+ /* -------------- Alert ------------- */
713
+ if (alertInfo) {
714
+ modal = (React__default["default"].createElement(Modal, { key: `alert-${alertInfo.title}-${alertInfo.text}`, title: alertInfo.title, type: ModalType$1.Okay, onClose: () => {
715
+ // Alert closed
716
+ setAlertInfo(undefined);
717
+ if (onAlertClosed) {
718
+ onAlertClosed();
719
+ }
720
+ }, onTopOfOtherModals: true }, alertInfo.text));
721
+ }
722
+ /* ------------- Confirm ------------ */
723
+ if (confirmInfo) {
724
+ modal = (React__default["default"].createElement(Modal, { key: `confirm-${confirmInfo.title}-${confirmInfo.text}`, title: confirmInfo.title, type: ModalType$1.OkayCancel, onClose: (buttonType) => {
725
+ setConfirmInfo(undefined);
726
+ if (onConfirmClosed) {
727
+ onConfirmClosed(buttonType === ModalButtonType$1.Okay);
728
+ }
729
+ }, dontAllowBackdropExit: true }, confirmInfo.text));
730
+ }
731
+ /*----------------------------------------*/
732
+ /* Views */
733
+ /*----------------------------------------*/
734
+ // Body that will be filled with the current view
735
+ let body;
736
+ /* ----------- Fatal Error ---------- */
737
+ if (fatalErrorMessage || fatalErrorCode || sessionHasExpired) {
738
+ // Re-encapsulate in an error
739
+ const error = (sessionHasExpired
740
+ ? new ErrorWithCode((fatalErrorMessage !== null && fatalErrorMessage !== void 0 ? fatalErrorMessage : 'An unknown error has occurred. Please contact support.'), (fatalErrorCode !== null && fatalErrorCode !== void 0 ? fatalErrorCode : ReactKitErrorCode$1.NoCode))
741
+ : new ErrorWithCode(sessionExpiredMessage, ReactKitErrorCode$1.SessionExpired));
742
+ // Build error screen
743
+ body = (React__default["default"].createElement("div", { style: {
744
+ display: 'block',
745
+ width: '100vw',
746
+ minHeight: '100vh',
747
+ paddingTop: '2rem',
748
+ backgroundColor: (dark
749
+ ? '#222'
750
+ : '#fff'),
751
+ } },
752
+ React__default["default"].createElement(ErrorBox, { title: (sessionHasExpired
753
+ ? 'Session Expired'
754
+ : fatalErrorTitle), error: error })));
755
+ }
756
+ /* --------------- App -------------- */
757
+ if (!body) {
758
+ body = (React__default["default"].createElement(React__default["default"].Fragment, null, children));
759
+ }
760
+ /*----------------------------------------*/
761
+ /* Main UI */
762
+ /*----------------------------------------*/
763
+ return (React__default["default"].createElement(React__default["default"].Fragment, null,
764
+ modal,
765
+ body));
766
+ };
767
+
768
+ /**
769
+ * Loading spinner/indicator
770
+ * @author Gabe Abrams
771
+ */
772
+ /*------------------------------------------------------------------------*/
773
+ /* Style */
774
+ /*------------------------------------------------------------------------*/
775
+ const style$2 = `
776
+ /* Blips */
777
+ .LoadingSpinner-blip-1,
778
+ .LoadingSpinner-blip-2,
779
+ .LoadingSpinner-blip-3,
780
+ .LoadingSpinner-blip-4 {
781
+ font-size: 1.8rem;
782
+ opacity: 0.6;
783
+ margin-top: 1rem;
784
+ margin-bottom: 1rem;
785
+ }
786
+
787
+ /* First Blip */
788
+ .LoadingSpinner-blip-1 {
789
+ animation: LoadingSpinner-pop-animation 2s infinite;
790
+ }
791
+
792
+ /* Second Blip */
793
+ .LoadingSpinner-blip-2 {
794
+ animation: LoadingSpinner-pop-animation 2s infinite;
795
+ -webkit-animation-delay: 0.1s;
796
+ animation-delay: 0.1s;
797
+ }
798
+
799
+ /* Third Blip */
800
+ .LoadingSpinner-blip-3 {
801
+ animation: LoadingSpinner-pop-animation 2s infinite;
802
+ animation-delay: 0.2s;
803
+ }
804
+
805
+ /* Fourth Blip */
806
+ .LoadingSpinner-blip-4 {
807
+ animation: LoadingSpinner-pop-animation 2s infinite;
808
+ animation-delay: 0.3s;
809
+ }
810
+
811
+ /* Pop Animation for Each Blip */
812
+ @keyframes LoadingSpinner-pop-animation {
813
+ 0% {
814
+ transform: scale(1.0);
815
+ }
816
+ 10% {
817
+ transform: scale(1.5);
818
+ }
819
+ 30% {
820
+ transform: scale(1.0);
821
+ }
822
+ 100% {
823
+ transform: scale(1.0);
824
+ }
825
+ }
826
+ `;
827
+ /*------------------------------------------------------------------------*/
828
+ /* Component */
829
+ /*------------------------------------------------------------------------*/
830
+ const LoadingSpinner = () => {
831
+ /*------------------------------------------------------------------------*/
832
+ /* Render */
833
+ /*------------------------------------------------------------------------*/
834
+ // Add all four blips to a container
835
+ return (React__default["default"].createElement("div", { className: "text-center LoadingSpinner LoadingSpinner-container" },
836
+ React__default["default"].createElement("style", null, style$2),
837
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-1 me-1" }),
838
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-2 me-1" }),
839
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-3 me-1" }),
840
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "LoadingSpinner-blip-4" })));
841
+ };
842
+
843
+ /**
844
+ * A box with a tab on the top that holds buttons and other content
845
+ * @author Gabe Abrams
846
+ */
847
+ /*------------------------------------------------------------------------*/
848
+ /* Style */
849
+ /*------------------------------------------------------------------------*/
850
+ const style$1 = `
851
+ /* Tab Box */
852
+ .TabBox-box {
853
+ /* Light Border */
854
+ border: 0.15rem solid #dedede;
855
+
856
+ /* Rounded Corners (except top-left) */
857
+ border-bottom-right-radius: 0.3rem;
858
+ border-bottom-left-radius: 0.3rem;
859
+ border-top-right-radius: 0.3rem;
860
+
861
+ /* Very Light Gray Border */
862
+ background: #fdfdfd;
863
+
864
+ /* Align Contents on Left */
865
+ text-align: left;
866
+
867
+ /* Add Text Padding */
868
+ padding-left: 0.3rem;
869
+ padding-right: 0.3rem;
870
+ }
871
+
872
+ /* Container for Title */
873
+ .TabBox-title-container {
874
+ /* Place on Left */
875
+ position: relative;
876
+ left: 0;
877
+ text-align: left;
878
+ }
879
+
880
+ /* Tab-style Title */
881
+ .TabBox-title {
882
+ /* Place so it Barely Overlaps the Box Border */
883
+ display: inline-block;
884
+ position: relative;
885
+ top: 0.15rem; /* Gives Illusion that Border Doesn't Exist Below Tab */
886
+
887
+ /* Title-sized Font */
888
+ font-size: 1.5rem;
889
+
890
+ /* Add Border on Top and Sides */
891
+ border-top: 0.15rem solid #dedede;
892
+ border-left: 0.15rem solid #dedede;
893
+ border-right: 0.15rem solid #dedede;
894
+
895
+ /* Round the Top Corners */
896
+ border-top-left-radius: 0.3rem;
897
+ border-top-right-radius: 0.3rem;
898
+
899
+ /* Add Text Padding */
900
+ padding-left: 0.5rem;
901
+ padding-right: 0.5rem;
902
+
903
+ /* Match Background Color of Box */
904
+ background: #fdfdfd;
905
+ }
906
+
907
+ /* Make the TabBox's Children Appear Above Title if Overlap Occurs */
908
+ .TabBox-children {
909
+ position: relative;
910
+ z-index: 1;
911
+ }
912
+ `;
913
+ /*------------------------------------------------------------------------*/
914
+ /* Component */
915
+ /*------------------------------------------------------------------------*/
916
+ const TabBox = (props) => {
917
+ /*------------------------------------------------------------------------*/
918
+ /* Setup */
919
+ /*------------------------------------------------------------------------*/
920
+ /* -------------- Props ------------- */
921
+ const { title, children, noBottomPadding, } = props;
922
+ /*------------------------------------------------------------------------*/
923
+ /* Render */
924
+ /*------------------------------------------------------------------------*/
925
+ /*----------------------------------------*/
926
+ /* Main UI */
927
+ /*----------------------------------------*/
928
+ // Full UI
929
+ return (React__default["default"].createElement("div", { className: `TabBox-container ${noBottomPadding ? '' : 'mb-2'}` },
930
+ React__default["default"].createElement("style", null, style$1),
931
+ React__default["default"].createElement("div", { className: "TabBox-title-container" },
932
+ React__default["default"].createElement("div", { className: "TabBox-title" }, title)),
933
+ React__default["default"].createElement("div", { className: "TabBox-box p-2" },
934
+ React__default["default"].createElement("div", { className: "TabBox-children" }, children))));
935
+ };
936
+
937
+ /**
938
+ * A radio selection button
939
+ * @author Gabe Abrams
940
+ */
941
+ /*------------------------------------------------------------------------*/
942
+ /* Component */
943
+ /*------------------------------------------------------------------------*/
944
+ const RadioButton = (props) => {
945
+ /*------------------------------------------------------------------------*/
946
+ /* Setup */
947
+ /*------------------------------------------------------------------------*/
948
+ /* -------------- Props ------------- */
949
+ const { text, onSelected, ariaLabel, title, selected, id, noMarginOnRight, selectedVariant = Variant$1.Secondary, unselectedVariant = Variant$1.Light, small, } = props;
950
+ /*------------------------------------------------------------------------*/
951
+ /* Render */
952
+ /*------------------------------------------------------------------------*/
953
+ /*----------------------------------------*/
954
+ /* Main UI */
955
+ /*----------------------------------------*/
956
+ return (React__default["default"].createElement("button", { type: "button", id: id, title: title, className: `btn btn-${selected ? selectedVariant : unselectedVariant}${selected ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`, "aria-label": `${ariaLabel}${selected ? ': currently selected' : ''}`, onClick: () => {
957
+ if (!selected) {
958
+ onSelected();
959
+ }
960
+ } },
961
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: selected ? freeSolidSvgIcons.faDotCircle : freeRegularSvgIcons.faCircle, className: "me-1" }),
962
+ text));
963
+ };
964
+
965
+ /**
966
+ * A checkbox button
967
+ * @author Gabe Abrams
968
+ */
969
+ /*------------------------------------------------------------------------*/
970
+ /* Component */
971
+ /*------------------------------------------------------------------------*/
972
+ const CheckboxButton = (props) => {
973
+ /*------------------------------------------------------------------------*/
974
+ /* Setup */
975
+ /*------------------------------------------------------------------------*/
976
+ /* -------------- Props ------------- */
977
+ const { text, onChanged, ariaLabel, title, checked, id, noMarginOnRight, checkedVariant = Variant$1.Secondary, uncheckedVariant = Variant$1.Light, small, } = props;
978
+ /*------------------------------------------------------------------------*/
979
+ /* Render */
980
+ /*------------------------------------------------------------------------*/
981
+ /*----------------------------------------*/
982
+ /* Main UI */
983
+ /*----------------------------------------*/
984
+ return (React__default["default"].createElement("button", { type: "button", id: id, title: title, className: `btn btn-${checked ? checkedVariant : uncheckedVariant}${checked ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`, "aria-label": `${ariaLabel}${checked ? ': currently checked' : ''}`, onClick: () => {
985
+ onChanged(!checked);
986
+ } },
987
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: checked ? freeSolidSvgIcons.faCheckSquare : freeRegularSvgIcons.faSquare, className: "me-1" }),
988
+ text));
989
+ };
990
+
991
+ /**
992
+ * Input group with a title and space for buttons
993
+ * @author Gabe Abrams
994
+ */
995
+ /*------------------------------------------------------------------------*/
996
+ /* Component */
997
+ /*------------------------------------------------------------------------*/
998
+ const ButtonInputGroup = (props) => {
999
+ /*------------------------------------------------------------------------*/
1000
+ /* Setup */
1001
+ /*------------------------------------------------------------------------*/
1002
+ /* -------------- Props ------------- */
1003
+ // Destructure all props
1004
+ const { label, minLabelWidth, children, } = props;
1005
+ /*------------------------------------------------------------------------*/
1006
+ /* Render */
1007
+ /*------------------------------------------------------------------------*/
1008
+ /*----------------------------------------*/
1009
+ /* Main UI */
1010
+ /*----------------------------------------*/
1011
+ return (React__default["default"].createElement("div", { className: "input-group" },
1012
+ React__default["default"].createElement("div", { className: "input-group-prepend d-flex w-100" },
1013
+ React__default["default"].createElement("span", { className: "input-group-text", style: {
1014
+ minWidth: (minLabelWidth !== null && minLabelWidth !== void 0 ? minLabelWidth : undefined),
1015
+ borderTopRightRadius: 0,
1016
+ borderBottomRightRadius: 0,
1017
+ } }, label),
1018
+ React__default["default"].createElement("span", { className: "input-group-text flex-grow-1 rounded-right", style: {
1019
+ backgroundColor: 'white',
1020
+ borderTopLeftRadius: 0,
1021
+ borderBottomLeftRadius: 0,
1022
+ borderLeftWidth: 0,
1023
+ } }, children))));
1024
+ };
1025
+
1026
+ const ORDINALS = ['th', 'st', 'nd', 'rd'];
1027
+ /**
1028
+ * Get a number's ordinal
1029
+ * @author Gabe Abrams
1030
+ * @param num the number being analyzed
1031
+ * @returns ordinal
1032
+ */
1033
+ const getOrdinal = (num) => {
1034
+ var _a, _b;
1035
+ return ((_b = (_a = ORDINALS[(num - 20) % 10]) !== null && _a !== void 0 ? _a : ORDINALS[num]) !== null && _b !== void 0 ? _b : ORDINALS[0]);
1036
+ };
1037
+
1038
+ /**
1039
+ * Get current time info in US Boston Eastern Time, independent of machine
1040
+ * timezone
1041
+ * @author Gabe Abrams
1042
+ * @param {Date} [date=now] the date to get info on
1043
+ * @returns object with timestamp (ms since epoch) and numbers
1044
+ * corresponding to ET time values for year, month, day, hour, minute
1045
+ */
1046
+ const getTimeInfoInET = (date) => {
1047
+ // Create a time string
1048
+ const d = (date || new Date());
1049
+ const str = d.toLocaleString('en-US', // Using US encoding (it's the only one installed on containers)
1050
+ { timeZone: 'America/New_York' } // Force EST timezone
1051
+ );
1052
+ // Parse the string for the date/time info
1053
+ const [dateStr, timeStr] = str.split(', '); // Format: MM/DD/YYYY, HH:MM:SS AM
1054
+ const [monthStr, dayStr, yearStr] = dateStr.split('/'); // Format: MM/DD/YYYY
1055
+ const [hourStr, minStr, ending] = timeStr.split(':'); // Format: HH:MM:SS AM
1056
+ // Create all time numbers
1057
+ const timestamp = d.getTime();
1058
+ const year = Number.parseInt(yearStr, 10);
1059
+ const month = Number.parseInt(monthStr, 10);
1060
+ const day = Number.parseInt(dayStr, 10);
1061
+ const minute = Number.parseInt(minStr, 10);
1062
+ let hour = Number.parseInt(hourStr, 10);
1063
+ // Convert from am/pm to 24hr
1064
+ const isAM = ending.toLowerCase().includes('am');
1065
+ const isPM = !isAM;
1066
+ if (isPM && hour !== 12) {
1067
+ hour += 12;
1068
+ }
1069
+ else if (isAM && hour === 12) {
1070
+ hour = 0;
1071
+ }
1072
+ // Return
1073
+ return {
1074
+ timestamp,
1075
+ year,
1076
+ month,
1077
+ day,
1078
+ hour,
1079
+ minute,
1080
+ };
1081
+ };
1082
+
1083
+ /**
1084
+ * A very simple, lightweight date chooser
1085
+ * @author Gabe Abrams
1086
+ */
1087
+ /*------------------------------------------------------------------------*/
1088
+ /* Constants */
1089
+ /*------------------------------------------------------------------------*/
1090
+ // Constants
1091
+ const MONTH_NAMES = [
1092
+ 'January',
1093
+ 'February',
1094
+ 'March',
1095
+ 'April',
1096
+ 'May',
1097
+ 'June',
1098
+ 'July',
1099
+ 'August',
1100
+ 'September',
1101
+ 'October',
1102
+ 'November',
1103
+ 'December',
1104
+ ];
1105
+ /*------------------------------------------------------------------------*/
1106
+ /* Component */
1107
+ /*------------------------------------------------------------------------*/
1108
+ const SimpleDateChooser = (props) => {
1109
+ /*------------------------------------------------------------------------*/
1110
+ /* Setup */
1111
+ /*------------------------------------------------------------------------*/
1112
+ var _a;
1113
+ /* -------------- Props ------------- */
1114
+ const { ariaLabel, name, month, day, year, onChange, } = props;
1115
+ const numMonthsToShow = Math.min((_a = props.numMonthsToShow) !== null && _a !== void 0 ? _a : 6, 12);
1116
+ /*------------------------------------------------------------------------*/
1117
+ /* Render */
1118
+ /*------------------------------------------------------------------------*/
1119
+ /*----------------------------------------*/
1120
+ /* Main UI */
1121
+ /*----------------------------------------*/
1122
+ // Determine the set of choices allowed
1123
+ const today = getTimeInfoInET();
1124
+ const choices = [];
1125
+ for (let i = 0; i < numMonthsToShow; i++) {
1126
+ // Get month and year info
1127
+ const unmoddedMonth = (today.month + i);
1128
+ const month = (unmoddedMonth > 12
1129
+ ? unmoddedMonth - 12
1130
+ : unmoddedMonth);
1131
+ const monthName = MONTH_NAMES[month - 1];
1132
+ const year = (unmoddedMonth > 12
1133
+ ? today.year + 1
1134
+ : today.year);
1135
+ // Figure out which days are allowed
1136
+ const days = [];
1137
+ const numDaysInMonth = (new Date(year, month, 0)).getDate();
1138
+ const firstDay = (month === today.month
1139
+ ? today.day // Current month: start at current date
1140
+ : 1 // Future month: start at beginning of month
1141
+ );
1142
+ for (let day = firstDay; day <= numDaysInMonth; day++) {
1143
+ days.push(day);
1144
+ }
1145
+ choices.push({
1146
+ choiceName: `${monthName} ${year}`,
1147
+ month,
1148
+ year,
1149
+ days,
1150
+ });
1151
+ }
1152
+ // Create choice options
1153
+ const monthOptions = [];
1154
+ const dayOptions = [];
1155
+ choices.forEach((choice) => {
1156
+ // Create month option
1157
+ monthOptions.push(React__default["default"].createElement("option", { key: choice.month, value: choice.month, "aria-label": `choose ${choice.choiceName}`, onSelect: () => {
1158
+ onChange(choice.month, choice.days[0], choice.year);
1159
+ } }, choice.choiceName));
1160
+ if (month === choice.month) {
1161
+ // This is the currently selected month
1162
+ // Create day options
1163
+ choice.days.forEach((dayChoice) => {
1164
+ const ordinal = getOrdinal(dayChoice);
1165
+ dayOptions.push(React__default["default"].createElement("option", { key: dayChoice, value: dayChoice, "aria-label": `choose date ${dayChoice}` },
1166
+ dayChoice,
1167
+ ordinal));
1168
+ });
1169
+ }
1170
+ });
1171
+ return (React__default["default"].createElement("div", { className: "SimpleDateChooser d-inline-block", "aria-label": `date chooser with selected date: ${month} ${day}, ${year}` },
1172
+ React__default["default"].createElement("select", { "aria-label": `month for ${ariaLabel}`, className: "custom-select d-inline-block mr-1", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-month`, value: month, onChange: (e) => {
1173
+ const choice = choices[e.target.selectedIndex];
1174
+ // Change day, month, and year
1175
+ onChange(choice.month, choice.days[0], choice.year);
1176
+ } }, monthOptions),
1177
+ React__default["default"].createElement("select", { "aria-label": `day for ${ariaLabel}`, className: "custom-select d-inline-block", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-day`, value: day, onChange: (e) => {
1178
+ // Only change the day
1179
+ onChange(month, Number.parseInt(e.target.value, 10), year);
1180
+ } }, dayOptions)));
1181
+ };
1182
+
1183
+ /**
1184
+ * Drawer container
1185
+ * @author Gabe Abrams
1186
+ */
1187
+ /*------------------------------------------------------------------------*/
1188
+ /* Style */
1189
+ /*------------------------------------------------------------------------*/
1190
+ const style = `
1191
+ .Drawer-container {
1192
+ margin-left: 1rem;
1193
+ margin-right: 1rem;
1194
+ margin-top: 0;
1195
+ margin-bottom: 1rem;
1196
+ padding: 0.75rem;
1197
+ border-left: 1px solid #dee2e6;
1198
+ border-bottom: 1px solid #dee2e6;
1199
+ border-right: 1px solid #dee2e6;
1200
+ border-bottom-left-radius: .25rem;
1201
+ border-bottom-right-radius: .25rem;
1202
+ box-shadow: inset 0 0 0.5rem 0 rgba(0, 0, 0, 0.05);
1203
+ }
1204
+ `;
1205
+ /*------------------------------------------------------------------------*/
1206
+ /* Component */
1207
+ /*------------------------------------------------------------------------*/
1208
+ const Drawer = (props) => {
1209
+ /*------------------------------------------------------------------------*/
1210
+ /* Setup */
1211
+ /*------------------------------------------------------------------------*/
1212
+ /* -------------- Props ------------- */
1213
+ // Destructure all props
1214
+ const { children, } = props;
1215
+ /*------------------------------------------------------------------------*/
1216
+ /* Render */
1217
+ /*------------------------------------------------------------------------*/
1218
+ /*----------------------------------------*/
1219
+ /* Main UI */
1220
+ /*----------------------------------------*/
1221
+ return (React__default["default"].createElement("div", { className: "Drawer-container" },
1222
+ React__default["default"].createElement("style", null, style),
1223
+ children));
1224
+ };
1225
+
1226
+ /**
1227
+ * Shorten text so it fits into a certain number of chars
1228
+ * @author Gabe Abrams
1229
+ * @param text the text to abbreviate
1230
+ * @param maxChars the maximum number of chars to include
1231
+ * @returns abbreviated text with length no greater than maxChars
1232
+ * (including ellipses if applicable)
1233
+ */
1234
+ const abbreviate = (text, maxChars) => {
1235
+ // Check if already short enough
1236
+ if (text.trim().length < maxChars) {
1237
+ return text.trim();
1238
+ }
1239
+ // Abbreviate
1240
+ const shortenedText = (text
1241
+ .trim()
1242
+ .substring(0, maxChars - 3)
1243
+ .trim());
1244
+ return `${shortenedText}...`;
1245
+ };
1246
+
1247
+ /**
1248
+ * Sum the numbers in an array
1249
+ * @author Gabe Abrams
1250
+ * @param nums the numbers to sum
1251
+ * @returns the sum of the numbers
1252
+ */
1253
+ const sum = (nums) => {
1254
+ return nums.reduce((a, b) => {
1255
+ return (a + b);
1256
+ }, 0);
1257
+ };
1258
+
1259
+ /**
1260
+ * Get the average of a set of numbers
1261
+ * @author Gabe Abrams
1262
+ * @param nums the numbers to average
1263
+ * @returns average value or 0 if no numbers
1264
+ */
1265
+ const avg = (nums) => {
1266
+ // Handle empty array case
1267
+ if (nums.length === 0) {
1268
+ return 0;
1269
+ }
1270
+ // Get the total value
1271
+ const total = sum(nums);
1272
+ // Get average
1273
+ return (total / nums.length);
1274
+ };
1275
+
1276
+ /**
1277
+ * Round a number (ceiling) to a certain number of decimals
1278
+ * @author Gabe Abrams
1279
+ * @param num the number to round
1280
+ * @param numDecimals the number of decimals to round to
1281
+ * @returns rounded number
1282
+ */
1283
+ const ceilToNumDecimals = (num, numDecimals) => {
1284
+ const rounder = 10 ** numDecimals;
1285
+ return (Math.ceil(num * rounder) / rounder);
1286
+ };
1287
+
1288
+ /**
1289
+ * Round a number (floor) to a certain number of decimals
1290
+ * @author Gabe Abrams
1291
+ * @param num the number to round
1292
+ * @param numDecimals the number of decimals to round to
1293
+ * @returns rounded number
1294
+ */
1295
+ const floorToNumDecimals = (num, numDecimals) => {
1296
+ const rounder = 10 ** numDecimals;
1297
+ return (Math.floor(num * rounder) / rounder);
1298
+ };
1299
+
1300
+ /**
1301
+ * Force a number to stay within specific bounds
1302
+ * @author Gabe Abrams
1303
+ * @param num the number to move into the bounds
1304
+ * @param min the minimum number in the bound
1305
+ * @param max the maximum number in the bound
1306
+ * @returns bounded number
1307
+ */
1308
+ const forceNumIntoBounds = (num, min, max) => {
1309
+ return Math.max(min, Math.min(max, num));
1310
+ };
1311
+
1312
+ /**
1313
+ * Pad a number's decimal with zeros on the right
1314
+ * (e.g. 5.2 becomes 5.20 with 2 digit padding)
1315
+ * @author Gabe Abrams
1316
+ * @param num the number to pad
1317
+ * @param numDigits the minimum number of digits after the decimal
1318
+ * @returns padded number
1319
+ */
1320
+ const padDecimalZeros = (num, numDigits) => {
1321
+ // Skip if nothing to do
1322
+ if (numDigits < 1) {
1323
+ return String(num);
1324
+ }
1325
+ // Convert to string
1326
+ let out = String(num);
1327
+ // Add a decimal point if there isn't one
1328
+ if (!out.includes('.')) {
1329
+ out += '.';
1330
+ }
1331
+ // Add zeros
1332
+ while (out.split('.')[1].length < numDigits) {
1333
+ out = `${out}0`;
1334
+ }
1335
+ // Return
1336
+ return out;
1337
+ };
1338
+
1339
+ /**
1340
+ * Pad a number with zeros on the left (e.g. 5 becomes 05 with 2 digit padding)
1341
+ * @author Gabe Abrams
1342
+ * @param num the number to pad
1343
+ * @param numDigits the minimum number of digits before the decimal
1344
+ * @returns padded number
1345
+ */
1346
+ const padZerosLeft = (num, numDigits) => {
1347
+ // Convert to string
1348
+ let out = String(num);
1349
+ // Add zeros
1350
+ while (out.split('.')[0].length < numDigits) {
1351
+ out = `0${out}`;
1352
+ }
1353
+ // Return
1354
+ return out;
1355
+ };
1356
+
1357
+ /**
1358
+ * Round a number to a certain number of decimals
1359
+ * @author Gabe Abrams
1360
+ * @param num the number to round
1361
+ * @param numDecimals the number of decimals to round to
1362
+ * @returns rounded number
1363
+ */
1364
+ const roundToNumDecimals = (num, numDecimals) => {
1365
+ const rounder = 10 ** numDecimals;
1366
+ return (Math.round(num * rounder) / rounder);
1367
+ };
1368
+
1369
+ // Keep track of whether or not session expiry has already been handled
1370
+ let sessionAlreadyExpired = false;
1371
+ /*------------------------------------------------------------------------*/
1372
+ /* Main */
1373
+ /*------------------------------------------------------------------------*/
1374
+ /**
1375
+ * Visit an endpoint on the server [for client only]
1376
+ * @author Gabe Abrams
1377
+ * @param opts object containing all arguments
1378
+ * @param opts.path - the path of the server endpoint
1379
+ * @param [opts.method=GET] - the method of the endpoint
1380
+ * @param [opts.params] - query/body parameters to include
1381
+ * @returns response from server
1382
+ */
1383
+ const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function* () {
1384
+ var _a;
1385
+ // Send the request
1386
+ const response = yield cacclSendRequest({
1387
+ path: opts.path,
1388
+ method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
1389
+ params: opts.params,
1390
+ });
1391
+ // Check for failure
1392
+ if (!response || !response.body) {
1393
+ throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
1394
+ }
1395
+ if (!response.body.success) {
1396
+ // Session expired
1397
+ if (response.body.code === ReactKitErrorCode$1.SessionExpired) {
1398
+ // Skip notice if session was already expired
1399
+ if (sessionAlreadyExpired) {
1400
+ // Never return (browser is already reloading)
1401
+ yield new Promise(() => {
1402
+ // Promise that never returns
1403
+ });
1404
+ }
1405
+ sessionAlreadyExpired = true;
1406
+ // Show session expiration message
1407
+ {
1408
+ // Fallback to alert
1409
+ // eslint-disable-next-line no-alert
1410
+ alert('Your session has expired. Please start over.');
1411
+ }
1412
+ // Never return (don't continue execution)
1413
+ yield new Promise(() => {
1414
+ // Promise that never returns
1415
+ });
1416
+ }
1417
+ // Other errors
1418
+ throw new ErrorWithCode((response.body.message
1419
+ || 'An unknown error occurred. Please contact an admin.'), (response.body.code
1420
+ || ReactKitErrorCode$1.NoCode));
1421
+ }
1422
+ // Success! Extract the body
1423
+ const { body } = response.body;
1424
+ // Return
1425
+ return body;
1426
+ });
1427
+
1428
+ // Import custom error
1429
+ // Stored copy of caccl functions
1430
+ let _cacclGetLaunchInfo;
1431
+ /*------------------------------------------------------------------------*/
1432
+ /* Helpers */
1433
+ /*------------------------------------------------------------------------*/
1434
+ /**
1435
+ * Get launch info via CACCL
1436
+ * @author Gabe Abrams
1437
+ * @param req express request object
1438
+ * @returns object { launched, launchInfo }
1439
+ */
1440
+ const cacclGetLaunchInfo = (req) => {
1441
+ if (!_cacclGetLaunchInfo) {
1442
+ throw new ErrorWithCode('Could not get launch info because server was not initialized with dce-reactkit\'s initServer function', ReactKitErrorCode$1.NoCACCLGetLaunchInfoFunction);
1443
+ }
1444
+ return _cacclGetLaunchInfo(req);
1445
+ };
1446
+ /*------------------------------------------------------------------------*/
1447
+ /* Main */
1448
+ /*------------------------------------------------------------------------*/
1449
+ /**
1450
+ * Prepare dce-reactkit to run on the server
1451
+ * @author Gabe Abrams
1452
+ * @param opts object containing all arguments
1453
+ * @param opts.getLaunchInfo CACCL LTI's get launch info function
1454
+ */
1455
+ const initServer = (opts) => {
1456
+ _cacclGetLaunchInfo = opts.getLaunchInfo;
1457
+ };
1458
+
1459
+ /**
1460
+ * Server-side API param types
1461
+ * @author Gabe Abrams
1462
+ */
1463
+ var ParamType;
1464
+ (function (ParamType) {
1465
+ ParamType["Boolean"] = "boolean";
1466
+ ParamType["BooleanOptional"] = "boolean-optional";
1467
+ ParamType["Float"] = "float";
1468
+ ParamType["FloatOptional"] = "float-optional";
1469
+ ParamType["Int"] = "int";
1470
+ ParamType["IntOptional"] = "int-optional";
1471
+ ParamType["JSON"] = "json";
1472
+ ParamType["JSONOptional"] = "json-optional";
1473
+ ParamType["String"] = "string";
1474
+ ParamType["StringOptional"] = "string-optional";
1475
+ })(ParamType || (ParamType = {}));
1476
+ var ParamType$1 = ParamType;
1477
+
1478
+ // Import shared types
1479
+ /**
1480
+ * Handle an error and respond to the client
1481
+ * @author Gabe Abrams
1482
+ * @param res express response
1483
+ * @param error error info
1484
+ * @param opts.err the error to send to the client
1485
+ * or the error message
1486
+ * @param [opts.code] an error code (only used if err.code is not
1487
+ * included)
1488
+ * @param [opts.status=500] the https status code to use
1489
+ * defined)
1490
+ */
1491
+ const handleError = (res, error) => {
1492
+ // Get the error message
1493
+ let message;
1494
+ if (error && error.message) {
1495
+ message = (error.message || 'An unknown error occurred.');
1496
+ }
1497
+ else if (typeof error === 'string') {
1498
+ message = (error.trim().length > 0
1499
+ ? error
1500
+ : 'An unknown error occurred.');
1501
+ }
1502
+ else {
1503
+ message = 'An unknown error occurred.';
1504
+ }
1505
+ // Get the error code
1506
+ const code = (error.code || ReactKitErrorCode$1.NoCode);
1507
+ // Get the status code
1508
+ const status = (error.status || 500);
1509
+ // Respond to user
1510
+ res
1511
+ // Set the http status code
1512
+ .status(status)
1513
+ // Send a JSON response
1514
+ .json({
1515
+ // Error message
1516
+ message,
1517
+ // Error code
1518
+ code,
1519
+ // Success = false flag so client can detect server-side errors
1520
+ success: false,
1521
+ });
1522
+ return undefined;
1523
+ };
1524
+
1525
+ /**
1526
+ * Send successful API response
1527
+ * @author Gabe Abrams
1528
+ * @param res express response
1529
+ * @param body the body of the response to send to the client
1530
+ */
1531
+ const handleSuccess = (res, body) => {
1532
+ // Send a http 200 json response
1533
+ res.json({
1534
+ // Include the body as a parameter
1535
+ body,
1536
+ // Success = true flag so client can detect successful responses
1537
+ success: true,
1538
+ });
1539
+ return undefined;
1540
+ };
1541
+
1542
+ /**
1543
+ * Generate an express API route handler
1544
+ * @author Gabe Abrams
1545
+ * @param opts object containing all arguments
1546
+ * @param opts.paramTypes map containing the types for each parameter that is
1547
+ * included in the request (map: param name => type)
1548
+ * @param opts.handler function that processes the request
1549
+ * @returns express route handler that takes the following arguments:
1550
+ * params (map: param name => value), handleSuccess (function for handling
1551
+ * successful requests), handleError (function for handling failed requests),
1552
+ * req (express request object), res (express response object),
1553
+ * next (express next function)
1554
+ */
1555
+ const genRouteHandler = (opts) => {
1556
+ // Return a route handler
1557
+ return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
1558
+ var _a;
1559
+ // Output params
1560
+ const output = {};
1561
+ /*----------------------------------------*/
1562
+ /* Parse Params */
1563
+ /*----------------------------------------*/
1564
+ // Process items one by one
1565
+ const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
1566
+ for (let i = 0; i < paramList.length; i++) {
1567
+ const [name, type] = paramList[i];
1568
+ // Find the value as a string
1569
+ const value = (req.params[name]
1570
+ || req.query[name]
1571
+ || req.body[name]);
1572
+ // Parse
1573
+ if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
1574
+ // Boolean
1575
+ // Handle case where value doesn't exist
1576
+ if (value === undefined) {
1577
+ if (type === ParamType$1.BooleanOptional) {
1578
+ output[name] = undefined;
1579
+ }
1580
+ else {
1581
+ return handleError(res, {
1582
+ message: `Parameter ${name} is required, but it was not included.`,
1583
+ code: ReactKitErrorCode$1.MissingParameter,
1584
+ status: 422,
1585
+ });
1586
+ }
1587
+ }
1588
+ else {
1589
+ // Value exists
1590
+ // Simplify value
1591
+ const simpleVal = (String(value)
1592
+ .trim()
1593
+ .toLowerCase());
1594
+ // Parse
1595
+ output[name] = ([
1596
+ 'true',
1597
+ 'yes',
1598
+ 'y',
1599
+ '1',
1600
+ 't',
1601
+ ].indexOf(simpleVal) >= 0);
1602
+ }
1603
+ }
1604
+ else if (type === ParamType$1.Float || type === ParamType$1.FloatOptional) {
1605
+ // Float
1606
+ // Handle case where value doesn't exist
1607
+ if (value === undefined) {
1608
+ if (type === ParamType$1.FloatOptional) {
1609
+ output[name] = undefined;
1610
+ }
1611
+ else {
1612
+ return handleError(res, {
1613
+ message: `Parameter ${name} is required, but it was not included.`,
1614
+ code: ReactKitErrorCode$1.MissingParameter,
1615
+ status: 422,
1616
+ });
1617
+ }
1618
+ }
1619
+ else if (!Number.isNaN(Number.parseFloat(String(value)))) {
1620
+ // Value is a number
1621
+ output[name] = Number.parseFloat(String(value));
1622
+ }
1623
+ else {
1624
+ // Issue!
1625
+ return handleError(res, {
1626
+ message: `Request data was malformed: ${name} was not a valid float.`,
1627
+ code: ReactKitErrorCode$1.InvalidParameter,
1628
+ status: 422,
1629
+ });
1630
+ }
1631
+ }
1632
+ else if (type === ParamType$1.Int || type === ParamType$1.IntOptional) {
1633
+ // Int
1634
+ // Handle case where value doesn't exist
1635
+ if (value === undefined) {
1636
+ if (type === ParamType$1.IntOptional) {
1637
+ output[name] = undefined;
1638
+ }
1639
+ else {
1640
+ return handleError(res, {
1641
+ message: `Parameter ${name} is required, but it was not included.`,
1642
+ code: ReactKitErrorCode$1.MissingParameter,
1643
+ status: 422,
1644
+ });
1645
+ }
1646
+ }
1647
+ else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
1648
+ // Value is a number
1649
+ output[name] = Number.parseInt(String(value), 10);
1650
+ }
1651
+ else {
1652
+ // Issue!
1653
+ return handleError(res, {
1654
+ message: `Request data was malformed: ${name} was not a valid int.`,
1655
+ code: ReactKitErrorCode$1.InvalidParameter,
1656
+ status: 422,
1657
+ });
1658
+ }
1659
+ }
1660
+ else if (type === ParamType$1.JSON || type === ParamType$1.JSONOptional) {
1661
+ // Stringified JSON
1662
+ // Handle case where value doesn't exist
1663
+ if (value === undefined) {
1664
+ if (type === ParamType$1.JSONOptional) {
1665
+ output[name] = undefined;
1666
+ }
1667
+ else {
1668
+ return handleError(res, {
1669
+ message: `Parameter ${name} is required, but it was not included.`,
1670
+ code: ReactKitErrorCode$1.MissingParameter,
1671
+ status: 422,
1672
+ });
1673
+ }
1674
+ }
1675
+ else {
1676
+ // Value exists
1677
+ // Parse
1678
+ try {
1679
+ output[name] = JSON.parse(String(value));
1680
+ }
1681
+ catch (err) {
1682
+ return handleError(res, {
1683
+ message: `Request data was malformed: ${name} was not a valid JSON payload.`,
1684
+ code: ReactKitErrorCode$1.InvalidParameter,
1685
+ status: 422,
1686
+ });
1687
+ }
1688
+ }
1689
+ }
1690
+ else if (type === ParamType$1.String || type === ParamType$1.StringOptional) {
1691
+ // String
1692
+ // Handle case where value doesn't exist
1693
+ if (value === undefined) {
1694
+ if (type === ParamType$1.StringOptional) {
1695
+ output[name] = undefined;
1696
+ }
1697
+ else {
1698
+ return handleError(res, {
1699
+ message: `Parameter ${name} is required, but it was not included.`,
1700
+ code: ReactKitErrorCode$1.MissingParameter,
1701
+ status: 422,
1702
+ });
1703
+ }
1704
+ }
1705
+ else {
1706
+ // Value exists
1707
+ // Leave as is
1708
+ output[name] = value;
1709
+ }
1710
+ }
1711
+ else {
1712
+ // No valid data type
1713
+ return handleError(res, {
1714
+ message: `An internal error occurred: we could not determine the type of ${name}.`,
1715
+ code: ReactKitErrorCode$1.InvalidParameter,
1716
+ status: 422,
1717
+ });
1718
+ }
1719
+ }
1720
+ /*----------------------------------------*/
1721
+ /* Launch Info */
1722
+ /*----------------------------------------*/
1723
+ // Get launch info
1724
+ const { launched, launchInfo } = cacclGetLaunchInfo(req);
1725
+ if (!launched || !launchInfo) {
1726
+ return handleError(res, {
1727
+ message: 'Your session has expired. Please refresh the page and try again.',
1728
+ code: ReactKitErrorCode$1.SessionExpired,
1729
+ status: 440,
1730
+ });
1731
+ }
1732
+ // Error if user info cannot be found
1733
+ if (!launchInfo.userId
1734
+ || !launchInfo.userFirstName
1735
+ || !launchInfo.userLastName
1736
+ || (launchInfo.notInCourse
1737
+ && !launchInfo.isAdmin)
1738
+ || (!launchInfo.isTTM
1739
+ && !launchInfo.isLearner
1740
+ && !launchInfo.isAdmin)) {
1741
+ return handleError(res, {
1742
+ message: 'Your session was invalid. Please refresh the page and try again.',
1743
+ code: ReactKitErrorCode$1.SessionExpired,
1744
+ status: 440,
1745
+ });
1746
+ }
1747
+ // Add launch info to output
1748
+ output.userId = launchInfo.userId;
1749
+ output.userFirstName = launchInfo.userFirstName;
1750
+ output.userLastName = launchInfo.userLastName;
1751
+ output.isLearner = !!launchInfo.isLearner;
1752
+ output.isTTM = !!launchInfo.isTTM;
1753
+ output.isAdmin = !!launchInfo.isAdmin;
1754
+ output.isWatchingInPrivate = !!(req.session.isWatchingInPrivate);
1755
+ /*----------------------------------------*/
1756
+ /* Require Course Consistency */
1757
+ /*----------------------------------------*/
1758
+ // Make sure the user actually launched from the appropriate course
1759
+ if (output.courseId
1760
+ && launchInfo.courseId
1761
+ && output.courseId !== launchInfo.courseId
1762
+ && !output.isTTM
1763
+ && !output.isAdmin) {
1764
+ // Course of interest is not the launch course
1765
+ return handleError(res, {
1766
+ message: 'You switched sessions by opening Immersive Classroom in another tab. Please refresh the page and try again.',
1767
+ code: ReactKitErrorCode$1.WrongCourse,
1768
+ status: 401,
1769
+ });
1770
+ }
1771
+ /*------------------------------------------------------------------------*/
1772
+ /* Call handler */
1773
+ /*------------------------------------------------------------------------*/
1774
+ opts.handler({
1775
+ params: output,
1776
+ handleSuccess: (body) => {
1777
+ return handleSuccess(res, body);
1778
+ },
1779
+ handleError: (error) => {
1780
+ return handleError(res, error);
1781
+ },
1782
+ req,
1783
+ res,
1784
+ next,
1785
+ });
1786
+ });
1787
+ };
1788
+
1789
+ exports.AppWrapper = AppWrapper;
1790
+ exports.ButtonInputGroup = ButtonInputGroup;
1791
+ exports.CheckboxButton = CheckboxButton;
1792
+ exports.Drawer = Drawer;
1793
+ exports.ErrorBox = ErrorBox;
1794
+ exports.ErrorWithCode = ErrorWithCode;
1795
+ exports.LoadingSpinner = LoadingSpinner;
1796
+ exports.Modal = Modal;
1797
+ exports.ModalButtonType = ModalButtonType$1;
1798
+ exports.ModalSize = ModalSize$1;
1799
+ exports.ModalType = ModalType$1;
1800
+ exports.ParamType = ParamType$1;
1801
+ exports.RadioButton = RadioButton;
1802
+ exports.ReactKitErrorCode = ReactKitErrorCode$1;
1803
+ exports.SimpleDateChooser = SimpleDateChooser;
1804
+ exports.TabBox = TabBox;
1805
+ exports.Variant = Variant$1;
1806
+ exports.abbreviate = abbreviate;
1807
+ exports.alert = alert$1;
1808
+ exports.avg = avg;
1809
+ exports.ceilToNumDecimals = ceilToNumDecimals;
1810
+ exports.confirm = confirm;
1811
+ exports.floorToNumDecimals = floorToNumDecimals;
1812
+ exports.forceNumIntoBounds = forceNumIntoBounds;
1813
+ exports.genRouteHandler = genRouteHandler;
1814
+ exports.getOrdinal = getOrdinal;
1815
+ exports.getTimeInfoInET = getTimeInfoInET;
1816
+ exports.handleError = handleError;
1817
+ exports.handleSuccess = handleSuccess;
1818
+ exports.initServer = initServer;
1819
+ exports.padDecimalZeros = padDecimalZeros;
1820
+ exports.padZerosLeft = padZerosLeft;
1821
+ exports.roundToNumDecimals = roundToNumDecimals;
1822
+ exports.showFatalError = showFatalError;
1823
+ exports.sum = sum;
1824
+ exports.visitServerEndpoint = visitServerEndpoint;
1825
+ exports.waitMs = waitMs;
1826
+ //# sourceMappingURL=index.js.map