dce-reactkit 3.0.1 → 3.0.2

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