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,1782 @@
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$3 = `
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$3),
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$2 = `
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$2),
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$1 = `
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$1),
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
+ * Drawer container
1177
+ * @author Gabe Abrams
1178
+ */
1179
+ /*------------------------------------------------------------------------*/
1180
+ /* Style */
1181
+ /*------------------------------------------------------------------------*/
1182
+ const style = `
1183
+ .Drawer-container {
1184
+ margin-left: 1rem;
1185
+ margin-right: 1rem;
1186
+ margin-top: 0;
1187
+ margin-bottom: 1rem;
1188
+ padding: 0.75rem;
1189
+ border-left: 1px solid #dee2e6;
1190
+ border-bottom: 1px solid #dee2e6;
1191
+ border-right: 1px solid #dee2e6;
1192
+ border-bottom-left-radius: .25rem;
1193
+ border-bottom-right-radius: .25rem;
1194
+ box-shadow: inset 0 0 0.5rem 0 rgba(0, 0, 0, 0.05);
1195
+ }
1196
+ `;
1197
+ /*------------------------------------------------------------------------*/
1198
+ /* Component */
1199
+ /*------------------------------------------------------------------------*/
1200
+ const Drawer = (props) => {
1201
+ /*------------------------------------------------------------------------*/
1202
+ /* Setup */
1203
+ /*------------------------------------------------------------------------*/
1204
+ /* -------------- Props ------------- */
1205
+ // Destructure all props
1206
+ const { children, } = props;
1207
+ /*------------------------------------------------------------------------*/
1208
+ /* Render */
1209
+ /*------------------------------------------------------------------------*/
1210
+ /*----------------------------------------*/
1211
+ /* Main UI */
1212
+ /*----------------------------------------*/
1213
+ return (React.createElement("div", { className: "Drawer-container" },
1214
+ React.createElement("style", null, style),
1215
+ children));
1216
+ };
1217
+
1218
+ /**
1219
+ * Shorten text so it fits into a certain number of chars
1220
+ * @author Gabe Abrams
1221
+ * @param text the text to abbreviate
1222
+ * @param maxChars the maximum number of chars to include
1223
+ * @returns abbreviated text with length no greater than maxChars
1224
+ * (including ellipses if applicable)
1225
+ */
1226
+ const abbreviate = (text, maxChars) => {
1227
+ // Check if already short enough
1228
+ if (text.trim().length < maxChars) {
1229
+ return text.trim();
1230
+ }
1231
+ // Abbreviate
1232
+ const shortenedText = (text
1233
+ .trim()
1234
+ .substring(0, maxChars - 3)
1235
+ .trim());
1236
+ return `${shortenedText}...`;
1237
+ };
1238
+
1239
+ /**
1240
+ * Sum the numbers in an array
1241
+ * @author Gabe Abrams
1242
+ * @param nums the numbers to sum
1243
+ * @returns the sum of the numbers
1244
+ */
1245
+ const sum = (nums) => {
1246
+ return nums.reduce((a, b) => {
1247
+ return (a + b);
1248
+ }, 0);
1249
+ };
1250
+
1251
+ /**
1252
+ * Get the average of a set of numbers
1253
+ * @author Gabe Abrams
1254
+ * @param nums the numbers to average
1255
+ * @returns average value or 0 if no numbers
1256
+ */
1257
+ const avg = (nums) => {
1258
+ // Handle empty array case
1259
+ if (nums.length === 0) {
1260
+ return 0;
1261
+ }
1262
+ // Get the total value
1263
+ const total = sum(nums);
1264
+ // Get average
1265
+ return (total / nums.length);
1266
+ };
1267
+
1268
+ /**
1269
+ * Round a number (ceiling) to a certain number of decimals
1270
+ * @author Gabe Abrams
1271
+ * @param num the number to round
1272
+ * @param numDecimals the number of decimals to round to
1273
+ * @returns rounded number
1274
+ */
1275
+ const ceilToNumDecimals = (num, numDecimals) => {
1276
+ const rounder = 10 ** numDecimals;
1277
+ return (Math.ceil(num * rounder) / rounder);
1278
+ };
1279
+
1280
+ /**
1281
+ * Round a number (floor) to a certain number of decimals
1282
+ * @author Gabe Abrams
1283
+ * @param num the number to round
1284
+ * @param numDecimals the number of decimals to round to
1285
+ * @returns rounded number
1286
+ */
1287
+ const floorToNumDecimals = (num, numDecimals) => {
1288
+ const rounder = 10 ** numDecimals;
1289
+ return (Math.floor(num * rounder) / rounder);
1290
+ };
1291
+
1292
+ /**
1293
+ * Force a number to stay within specific bounds
1294
+ * @author Gabe Abrams
1295
+ * @param num the number to move into the bounds
1296
+ * @param min the minimum number in the bound
1297
+ * @param max the maximum number in the bound
1298
+ * @returns bounded number
1299
+ */
1300
+ const forceNumIntoBounds = (num, min, max) => {
1301
+ return Math.max(min, Math.min(max, num));
1302
+ };
1303
+
1304
+ /**
1305
+ * Pad a number's decimal with zeros on the right
1306
+ * (e.g. 5.2 becomes 5.20 with 2 digit padding)
1307
+ * @author Gabe Abrams
1308
+ * @param num the number to pad
1309
+ * @param numDigits the minimum number of digits after the decimal
1310
+ * @returns padded number
1311
+ */
1312
+ const padDecimalZeros = (num, numDigits) => {
1313
+ // Skip if nothing to do
1314
+ if (numDigits < 1) {
1315
+ return String(num);
1316
+ }
1317
+ // Convert to string
1318
+ let out = String(num);
1319
+ // Add a decimal point if there isn't one
1320
+ if (!out.includes('.')) {
1321
+ out += '.';
1322
+ }
1323
+ // Add zeros
1324
+ while (out.split('.')[1].length < numDigits) {
1325
+ out = `${out}0`;
1326
+ }
1327
+ // Return
1328
+ return out;
1329
+ };
1330
+
1331
+ /**
1332
+ * Pad a number with zeros on the left (e.g. 5 becomes 05 with 2 digit padding)
1333
+ * @author Gabe Abrams
1334
+ * @param num the number to pad
1335
+ * @param numDigits the minimum number of digits before the decimal
1336
+ * @returns padded number
1337
+ */
1338
+ const padZerosLeft = (num, numDigits) => {
1339
+ // Convert to string
1340
+ let out = String(num);
1341
+ // Add zeros
1342
+ while (out.split('.')[0].length < numDigits) {
1343
+ out = `0${out}`;
1344
+ }
1345
+ // Return
1346
+ return out;
1347
+ };
1348
+
1349
+ /**
1350
+ * Round a number to a certain number of decimals
1351
+ * @author Gabe Abrams
1352
+ * @param num the number to round
1353
+ * @param numDecimals the number of decimals to round to
1354
+ * @returns rounded number
1355
+ */
1356
+ const roundToNumDecimals = (num, numDecimals) => {
1357
+ const rounder = 10 ** numDecimals;
1358
+ return (Math.round(num * rounder) / rounder);
1359
+ };
1360
+
1361
+ // Keep track of whether or not session expiry has already been handled
1362
+ let sessionAlreadyExpired = false;
1363
+ /*------------------------------------------------------------------------*/
1364
+ /* Main */
1365
+ /*------------------------------------------------------------------------*/
1366
+ /**
1367
+ * Visit an endpoint on the server [for client only]
1368
+ * @author Gabe Abrams
1369
+ * @param opts object containing all arguments
1370
+ * @param opts.path - the path of the server endpoint
1371
+ * @param [opts.method=GET] - the method of the endpoint
1372
+ * @param [opts.params] - query/body parameters to include
1373
+ * @returns response from server
1374
+ */
1375
+ const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function* () {
1376
+ var _a;
1377
+ // Send the request
1378
+ const response = yield cacclSendRequest({
1379
+ path: opts.path,
1380
+ method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
1381
+ params: opts.params,
1382
+ });
1383
+ // Check for failure
1384
+ if (!response || !response.body) {
1385
+ throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
1386
+ }
1387
+ if (!response.body.success) {
1388
+ // Session expired
1389
+ if (response.body.code === ReactKitErrorCode$1.SessionExpired) {
1390
+ // Skip notice if session was already expired
1391
+ if (sessionAlreadyExpired) {
1392
+ // Never return (browser is already reloading)
1393
+ yield new Promise(() => {
1394
+ // Promise that never returns
1395
+ });
1396
+ }
1397
+ sessionAlreadyExpired = true;
1398
+ // Show session expiration message
1399
+ {
1400
+ // Fallback to alert
1401
+ // eslint-disable-next-line no-alert
1402
+ alert('Your session has expired. Please start over.');
1403
+ }
1404
+ // Never return (don't continue execution)
1405
+ yield new Promise(() => {
1406
+ // Promise that never returns
1407
+ });
1408
+ }
1409
+ // Other errors
1410
+ throw new ErrorWithCode((response.body.message
1411
+ || 'An unknown error occurred. Please contact an admin.'), (response.body.code
1412
+ || ReactKitErrorCode$1.NoCode));
1413
+ }
1414
+ // Success! Extract the body
1415
+ const { body } = response.body;
1416
+ // Return
1417
+ return body;
1418
+ });
1419
+
1420
+ // Import custom error
1421
+ // Stored copy of caccl functions
1422
+ let _cacclGetLaunchInfo;
1423
+ /*------------------------------------------------------------------------*/
1424
+ /* Helpers */
1425
+ /*------------------------------------------------------------------------*/
1426
+ /**
1427
+ * Get launch info via CACCL
1428
+ * @author Gabe Abrams
1429
+ * @param req express request object
1430
+ * @returns object { launched, launchInfo }
1431
+ */
1432
+ const cacclGetLaunchInfo = (req) => {
1433
+ if (!_cacclGetLaunchInfo) {
1434
+ throw new ErrorWithCode('Could not get launch info because server was not initialized with dce-reactkit\'s initServer function', ReactKitErrorCode$1.NoCACCLGetLaunchInfoFunction);
1435
+ }
1436
+ return _cacclGetLaunchInfo(req);
1437
+ };
1438
+ /*------------------------------------------------------------------------*/
1439
+ /* Main */
1440
+ /*------------------------------------------------------------------------*/
1441
+ /**
1442
+ * Prepare dce-reactkit to run on the server
1443
+ * @author Gabe Abrams
1444
+ * @param opts object containing all arguments
1445
+ * @param opts.getLaunchInfo CACCL LTI's get launch info function
1446
+ */
1447
+ const initServer = (opts) => {
1448
+ _cacclGetLaunchInfo = opts.getLaunchInfo;
1449
+ };
1450
+
1451
+ /**
1452
+ * Server-side API param types
1453
+ * @author Gabe Abrams
1454
+ */
1455
+ var ParamType;
1456
+ (function (ParamType) {
1457
+ ParamType["Boolean"] = "boolean";
1458
+ ParamType["BooleanOptional"] = "boolean-optional";
1459
+ ParamType["Float"] = "float";
1460
+ ParamType["FloatOptional"] = "float-optional";
1461
+ ParamType["Int"] = "int";
1462
+ ParamType["IntOptional"] = "int-optional";
1463
+ ParamType["JSON"] = "json";
1464
+ ParamType["JSONOptional"] = "json-optional";
1465
+ ParamType["String"] = "string";
1466
+ ParamType["StringOptional"] = "string-optional";
1467
+ })(ParamType || (ParamType = {}));
1468
+ var ParamType$1 = ParamType;
1469
+
1470
+ // Import shared types
1471
+ /**
1472
+ * Handle an error and respond to the client
1473
+ * @author Gabe Abrams
1474
+ * @param res express response
1475
+ * @param error error info
1476
+ * @param opts.err the error to send to the client
1477
+ * or the error message
1478
+ * @param [opts.code] an error code (only used if err.code is not
1479
+ * included)
1480
+ * @param [opts.status=500] the https status code to use
1481
+ * defined)
1482
+ */
1483
+ const handleError = (res, error) => {
1484
+ // Get the error message
1485
+ let message;
1486
+ if (error && error.message) {
1487
+ message = (error.message || 'An unknown error occurred.');
1488
+ }
1489
+ else if (typeof error === 'string') {
1490
+ message = (error.trim().length > 0
1491
+ ? error
1492
+ : 'An unknown error occurred.');
1493
+ }
1494
+ else {
1495
+ message = 'An unknown error occurred.';
1496
+ }
1497
+ // Get the error code
1498
+ const code = (error.code || ReactKitErrorCode$1.NoCode);
1499
+ // Get the status code
1500
+ const status = (error.status || 500);
1501
+ // Respond to user
1502
+ res
1503
+ // Set the http status code
1504
+ .status(status)
1505
+ // Send a JSON response
1506
+ .json({
1507
+ // Error message
1508
+ message,
1509
+ // Error code
1510
+ code,
1511
+ // Success = false flag so client can detect server-side errors
1512
+ success: false,
1513
+ });
1514
+ return undefined;
1515
+ };
1516
+
1517
+ /**
1518
+ * Send successful API response
1519
+ * @author Gabe Abrams
1520
+ * @param res express response
1521
+ * @param body the body of the response to send to the client
1522
+ */
1523
+ const handleSuccess = (res, body) => {
1524
+ // Send a http 200 json response
1525
+ res.json({
1526
+ // Include the body as a parameter
1527
+ body,
1528
+ // Success = true flag so client can detect successful responses
1529
+ success: true,
1530
+ });
1531
+ return undefined;
1532
+ };
1533
+
1534
+ /**
1535
+ * Generate an express API route handler
1536
+ * @author Gabe Abrams
1537
+ * @param opts object containing all arguments
1538
+ * @param opts.paramTypes map containing the types for each parameter that is
1539
+ * included in the request (map: param name => type)
1540
+ * @param opts.handler function that processes the request
1541
+ * @returns express route handler that takes the following arguments:
1542
+ * params (map: param name => value), handleSuccess (function for handling
1543
+ * successful requests), handleError (function for handling failed requests),
1544
+ * req (express request object), res (express response object),
1545
+ * next (express next function)
1546
+ */
1547
+ const genRouteHandler = (opts) => {
1548
+ // Return a route handler
1549
+ return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
1550
+ var _a;
1551
+ // Output params
1552
+ const output = {};
1553
+ /*----------------------------------------*/
1554
+ /* Parse Params */
1555
+ /*----------------------------------------*/
1556
+ // Process items one by one
1557
+ const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
1558
+ for (let i = 0; i < paramList.length; i++) {
1559
+ const [name, type] = paramList[i];
1560
+ // Find the value as a string
1561
+ const value = (req.params[name]
1562
+ || req.query[name]
1563
+ || req.body[name]);
1564
+ // Parse
1565
+ if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
1566
+ // Boolean
1567
+ // Handle case where value doesn't exist
1568
+ if (value === undefined) {
1569
+ if (type === ParamType$1.BooleanOptional) {
1570
+ output[name] = undefined;
1571
+ }
1572
+ else {
1573
+ return handleError(res, {
1574
+ message: `Parameter ${name} is required, but it was not included.`,
1575
+ code: ReactKitErrorCode$1.MissingParameter,
1576
+ status: 422,
1577
+ });
1578
+ }
1579
+ }
1580
+ else {
1581
+ // Value exists
1582
+ // Simplify value
1583
+ const simpleVal = (String(value)
1584
+ .trim()
1585
+ .toLowerCase());
1586
+ // Parse
1587
+ output[name] = ([
1588
+ 'true',
1589
+ 'yes',
1590
+ 'y',
1591
+ '1',
1592
+ 't',
1593
+ ].indexOf(simpleVal) >= 0);
1594
+ }
1595
+ }
1596
+ else if (type === ParamType$1.Float || type === ParamType$1.FloatOptional) {
1597
+ // Float
1598
+ // Handle case where value doesn't exist
1599
+ if (value === undefined) {
1600
+ if (type === ParamType$1.FloatOptional) {
1601
+ output[name] = undefined;
1602
+ }
1603
+ else {
1604
+ return handleError(res, {
1605
+ message: `Parameter ${name} is required, but it was not included.`,
1606
+ code: ReactKitErrorCode$1.MissingParameter,
1607
+ status: 422,
1608
+ });
1609
+ }
1610
+ }
1611
+ else if (!Number.isNaN(Number.parseFloat(String(value)))) {
1612
+ // Value is a number
1613
+ output[name] = Number.parseFloat(String(value));
1614
+ }
1615
+ else {
1616
+ // Issue!
1617
+ return handleError(res, {
1618
+ message: `Request data was malformed: ${name} was not a valid float.`,
1619
+ code: ReactKitErrorCode$1.InvalidParameter,
1620
+ status: 422,
1621
+ });
1622
+ }
1623
+ }
1624
+ else if (type === ParamType$1.Int || type === ParamType$1.IntOptional) {
1625
+ // Int
1626
+ // Handle case where value doesn't exist
1627
+ if (value === undefined) {
1628
+ if (type === ParamType$1.IntOptional) {
1629
+ output[name] = undefined;
1630
+ }
1631
+ else {
1632
+ return handleError(res, {
1633
+ message: `Parameter ${name} is required, but it was not included.`,
1634
+ code: ReactKitErrorCode$1.MissingParameter,
1635
+ status: 422,
1636
+ });
1637
+ }
1638
+ }
1639
+ else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
1640
+ // Value is a number
1641
+ output[name] = Number.parseInt(String(value), 10);
1642
+ }
1643
+ else {
1644
+ // Issue!
1645
+ return handleError(res, {
1646
+ message: `Request data was malformed: ${name} was not a valid int.`,
1647
+ code: ReactKitErrorCode$1.InvalidParameter,
1648
+ status: 422,
1649
+ });
1650
+ }
1651
+ }
1652
+ else if (type === ParamType$1.JSON || type === ParamType$1.JSONOptional) {
1653
+ // Stringified JSON
1654
+ // Handle case where value doesn't exist
1655
+ if (value === undefined) {
1656
+ if (type === ParamType$1.JSONOptional) {
1657
+ output[name] = undefined;
1658
+ }
1659
+ else {
1660
+ return handleError(res, {
1661
+ message: `Parameter ${name} is required, but it was not included.`,
1662
+ code: ReactKitErrorCode$1.MissingParameter,
1663
+ status: 422,
1664
+ });
1665
+ }
1666
+ }
1667
+ else {
1668
+ // Value exists
1669
+ // Parse
1670
+ try {
1671
+ output[name] = JSON.parse(String(value));
1672
+ }
1673
+ catch (err) {
1674
+ return handleError(res, {
1675
+ message: `Request data was malformed: ${name} was not a valid JSON payload.`,
1676
+ code: ReactKitErrorCode$1.InvalidParameter,
1677
+ status: 422,
1678
+ });
1679
+ }
1680
+ }
1681
+ }
1682
+ else if (type === ParamType$1.String || type === ParamType$1.StringOptional) {
1683
+ // String
1684
+ // Handle case where value doesn't exist
1685
+ if (value === undefined) {
1686
+ if (type === ParamType$1.StringOptional) {
1687
+ output[name] = undefined;
1688
+ }
1689
+ else {
1690
+ return handleError(res, {
1691
+ message: `Parameter ${name} is required, but it was not included.`,
1692
+ code: ReactKitErrorCode$1.MissingParameter,
1693
+ status: 422,
1694
+ });
1695
+ }
1696
+ }
1697
+ else {
1698
+ // Value exists
1699
+ // Leave as is
1700
+ output[name] = value;
1701
+ }
1702
+ }
1703
+ else {
1704
+ // No valid data type
1705
+ return handleError(res, {
1706
+ message: `An internal error occurred: we could not determine the type of ${name}.`,
1707
+ code: ReactKitErrorCode$1.InvalidParameter,
1708
+ status: 422,
1709
+ });
1710
+ }
1711
+ }
1712
+ /*----------------------------------------*/
1713
+ /* Launch Info */
1714
+ /*----------------------------------------*/
1715
+ // Get launch info
1716
+ const { launched, launchInfo } = cacclGetLaunchInfo(req);
1717
+ if (!launched || !launchInfo) {
1718
+ return handleError(res, {
1719
+ message: 'Your session has expired. Please refresh the page and try again.',
1720
+ code: ReactKitErrorCode$1.SessionExpired,
1721
+ status: 440,
1722
+ });
1723
+ }
1724
+ // Error if user info cannot be found
1725
+ if (!launchInfo.userId
1726
+ || !launchInfo.userFirstName
1727
+ || !launchInfo.userLastName
1728
+ || (launchInfo.notInCourse
1729
+ && !launchInfo.isAdmin)
1730
+ || (!launchInfo.isTTM
1731
+ && !launchInfo.isLearner
1732
+ && !launchInfo.isAdmin)) {
1733
+ return handleError(res, {
1734
+ message: 'Your session was invalid. Please refresh the page and try again.',
1735
+ code: ReactKitErrorCode$1.SessionExpired,
1736
+ status: 440,
1737
+ });
1738
+ }
1739
+ // Add launch info to output
1740
+ output.userId = launchInfo.userId;
1741
+ output.userFirstName = launchInfo.userFirstName;
1742
+ output.userLastName = launchInfo.userLastName;
1743
+ output.isLearner = !!launchInfo.isLearner;
1744
+ output.isTTM = !!launchInfo.isTTM;
1745
+ output.isAdmin = !!launchInfo.isAdmin;
1746
+ output.isWatchingInPrivate = !!(req.session.isWatchingInPrivate);
1747
+ /*----------------------------------------*/
1748
+ /* Require Course Consistency */
1749
+ /*----------------------------------------*/
1750
+ // Make sure the user actually launched from the appropriate course
1751
+ if (output.courseId
1752
+ && launchInfo.courseId
1753
+ && output.courseId !== launchInfo.courseId
1754
+ && !output.isTTM
1755
+ && !output.isAdmin) {
1756
+ // Course of interest is not the launch course
1757
+ return handleError(res, {
1758
+ message: 'You switched sessions by opening Immersive Classroom in another tab. Please refresh the page and try again.',
1759
+ code: ReactKitErrorCode$1.WrongCourse,
1760
+ status: 401,
1761
+ });
1762
+ }
1763
+ /*------------------------------------------------------------------------*/
1764
+ /* Call handler */
1765
+ /*------------------------------------------------------------------------*/
1766
+ opts.handler({
1767
+ params: output,
1768
+ handleSuccess: (body) => {
1769
+ return handleSuccess(res, body);
1770
+ },
1771
+ handleError: (error) => {
1772
+ return handleError(res, error);
1773
+ },
1774
+ req,
1775
+ res,
1776
+ next,
1777
+ });
1778
+ });
1779
+ };
1780
+
1781
+ export { AppWrapper, ButtonInputGroup, CheckboxButton, Drawer, 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 };
1782
+ //# sourceMappingURL=index.js.map