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