dce-reactkit 3.0.0-beta.4 → 3.0.0-beta.40

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 (60) hide show
  1. package/.eslintrc.js +95 -0
  2. package/README.md +1 -546
  3. package/dist/cjs/index.js +784 -85
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/types/components/AppWrapper.d.ts +25 -2
  6. package/dist/cjs/types/components/ButtonInputGroup.d.ts +12 -0
  7. package/dist/cjs/types/components/CheckboxButton.d.ts +20 -0
  8. package/dist/cjs/types/components/RadioButton.d.ts +20 -0
  9. package/dist/cjs/types/components/SimpleDateChooser.d.ts +21 -0
  10. package/dist/cjs/types/components/TabBox.d.ts +1 -0
  11. package/dist/cjs/types/helpers/genRouteHandler.d.ts +30 -0
  12. package/dist/cjs/types/helpers/getOrdinal.d.ts +8 -0
  13. package/dist/cjs/types/helpers/getTimeInfoInET.d.ts +17 -0
  14. package/dist/cjs/types/helpers/handleError.d.ts +18 -0
  15. package/dist/cjs/types/helpers/handleSuccess.d.ts +8 -0
  16. package/dist/cjs/types/helpers/visitServerEndpoint.d.ts +23 -0
  17. package/dist/cjs/types/index.d.ts +10 -1
  18. package/dist/cjs/types/server/initServer.d.ts +21 -0
  19. package/dist/cjs/types/types/ParamType.d.ts +17 -0
  20. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +3 -1
  21. package/dist/esm/index.js +776 -86
  22. package/dist/esm/index.js.map +1 -1
  23. package/dist/esm/types/components/AppWrapper.d.ts +25 -2
  24. package/dist/esm/types/components/ButtonInputGroup.d.ts +12 -0
  25. package/dist/esm/types/components/CheckboxButton.d.ts +20 -0
  26. package/dist/esm/types/components/RadioButton.d.ts +20 -0
  27. package/dist/esm/types/components/SimpleDateChooser.d.ts +21 -0
  28. package/dist/esm/types/components/TabBox.d.ts +1 -0
  29. package/dist/esm/types/helpers/genRouteHandler.d.ts +30 -0
  30. package/dist/esm/types/helpers/getOrdinal.d.ts +8 -0
  31. package/dist/esm/types/helpers/getTimeInfoInET.d.ts +17 -0
  32. package/dist/esm/types/helpers/handleError.d.ts +18 -0
  33. package/dist/esm/types/helpers/handleSuccess.d.ts +8 -0
  34. package/dist/esm/types/helpers/visitServerEndpoint.d.ts +23 -0
  35. package/dist/esm/types/index.d.ts +10 -1
  36. package/dist/esm/types/server/initServer.d.ts +21 -0
  37. package/dist/esm/types/types/ParamType.d.ts +17 -0
  38. package/dist/esm/types/types/ReactKitErrorCode.d.ts +3 -1
  39. package/dist/index.d.ts +182 -10
  40. package/package.json +13 -1
  41. package/rollup.config.js +0 -1
  42. package/src/components/AppWrapper.tsx +73 -15
  43. package/src/components/ButtonInputGroup.tsx +89 -0
  44. package/src/components/CheckboxButton.tsx +100 -0
  45. package/src/components/ErrorBox.tsx +4 -4
  46. package/src/components/LoadingSpinner.tsx +3 -3
  47. package/src/components/Modal.tsx +185 -71
  48. package/src/components/RadioButton.tsx +102 -0
  49. package/src/components/SimpleDateChooser.tsx +217 -0
  50. package/src/components/TabBox.tsx +65 -58
  51. package/src/helpers/genRouteHandler.ts +326 -0
  52. package/src/helpers/getOrdinal.tsx +13 -0
  53. package/src/helpers/getTimeInfoInET.tsx +56 -0
  54. package/src/helpers/handleError.ts +65 -0
  55. package/src/helpers/handleSuccess.ts +18 -0
  56. package/src/helpers/visitServerEndpoint.tsx +110 -0
  57. package/src/index.ts +21 -0
  58. package/src/server/initServer.ts +53 -0
  59. package/src/types/ParamType.ts +18 -0
  60. package/src/types/ReactKitErrorCode.tsx +3 -1
package/dist/cjs/index.js CHANGED
@@ -3,10 +3,9 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var React = require('react');
6
- require('bootstrap/dist/css/bootstrap.min.css');
7
- require('bootstrap/dist/js/bootstrap.min');
8
6
  var reactFontawesome = require('@fortawesome/react-fontawesome');
9
7
  var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons');
8
+ var freeRegularSvgIcons = require('@fortawesome/free-regular-svg-icons');
10
9
 
11
10
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
12
11
 
@@ -37,7 +36,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
37
36
  });
38
37
  }
39
38
 
40
- // Highest error code = DRK2
39
+ // Highest error code = DRK8
41
40
  /**
42
41
  * List of error codes built into the react kit
43
42
  * @author Gabe Abrams
@@ -50,6 +49,8 @@ var ReactKitErrorCode;
50
49
  ReactKitErrorCode["MissingParameter"] = "DRK4";
51
50
  ReactKitErrorCode["InvalidParameter"] = "DRK5";
52
51
  ReactKitErrorCode["WrongCourse"] = "DRK6";
52
+ ReactKitErrorCode["NoCACCLSendRequestFunction"] = "DRK7";
53
+ ReactKitErrorCode["NoCACCLGetLaunchInfoFunction"] = "DRK8";
53
54
  })(ReactKitErrorCode || (ReactKitErrorCode = {}));
54
55
  var ReactKitErrorCode$1 = ReactKitErrorCode;
55
56
 
@@ -81,9 +82,9 @@ const ErrorBox = (props) => {
81
82
  ' ',
82
83
  React__default["default"].createElement("span", { style: {
83
84
  backgroundColor: 'white',
84
- borderRadius: '5px',
85
- paddingLeft: '3px',
86
- paddingRight: '3px',
85
+ borderRadius: '0.3rem',
86
+ paddingLeft: '0.2rem',
87
+ paddingRight: '0.2rem',
87
88
  color: '#DC4150',
88
89
  fontVariant: 'small-caps',
89
90
  fontSize: '80%',
@@ -95,7 +96,7 @@ const ErrorBox = (props) => {
95
96
  }
96
97
  // Main UI
97
98
  return (React__default["default"].createElement("div", { className: "alert alert-danger text-center", style: {
98
- maxWidth: '650px',
99
+ maxWidth: '40rem',
99
100
  margin: 'auto',
100
101
  } },
101
102
  React__default["default"].createElement("h4", { className: "mb-1" },
@@ -194,9 +195,7 @@ var ModalSize$1 = ModalSize;
194
195
  /* Constants */
195
196
  /*------------------------------------------------------------------------*/
196
197
  // Constants
197
- const MS_TO_ANIMATE = 400; // Time to animate in/out (defined by bootstrap)
198
- const MS_ANIMATE_IN_DELAY = 10;
199
- // Time to wait before animating in (must be >0 or animation won't trigger)
198
+ const MS_TO_ANIMATE = 200; // Animation duration
200
199
  // Modal type to list of buttons
201
200
  const modalTypeToModalButtonTypes = {
202
201
  [ModalType$1.Okay]: [
@@ -276,6 +275,114 @@ const ModalButtonTypeToLabelAndVariant = {
276
275
  },
277
276
  };
278
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
+ /*------------------------------------------------------------------------*/
279
386
  /* Component */
280
387
  /*------------------------------------------------------------------------*/
281
388
  const Modal = (props) => {
@@ -286,10 +393,15 @@ const Modal = (props) => {
286
393
  /* -------------- Props ------------- */
287
394
  const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, children, onClose, dontAllowBackdropExit, onTopOfOtherModals, } = props;
288
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);
289
399
  // If true, the modal is shown
290
400
  const [visible, setVisible] = React.useState(false);
291
- // True if currently animating in
292
- const [animatingIn, setAnimatingIn] = 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);
293
405
  /*------------------------------------------------------------------------*/
294
406
  /* Lifecycle Functions */
295
407
  /*------------------------------------------------------------------------*/
@@ -299,12 +411,10 @@ const Modal = (props) => {
299
411
  */
300
412
  React.useEffect(() => {
301
413
  (() => __awaiter(void 0, void 0, void 0, function* () {
302
- // Start the component animating in
303
- yield waitMs(MS_ANIMATE_IN_DELAY);
304
- setAnimatingIn(true);
305
414
  // Wait and then set visible to true
306
415
  yield waitMs(MS_TO_ANIMATE);
307
416
  setVisible(true);
417
+ setAnimatingIn(false);
308
418
  }))();
309
419
  }, []);
310
420
  /*------------------------------------------------------------------------*/
@@ -331,13 +441,19 @@ const Modal = (props) => {
331
441
  }
332
442
  // Update the state
333
443
  setVisible(false);
444
+ setAnimatingOut(true);
334
445
  // Call the handler after the modal has animated out
335
446
  yield waitMs(MS_TO_ANIMATE);
336
447
  onClose(ModalButtonType);
448
+ setGone(true);
337
449
  });
338
450
  /*------------------------------------------------------------------------*/
339
451
  /* Render */
340
452
  /*------------------------------------------------------------------------*/
453
+ // Return nothing if gone
454
+ if (gone) {
455
+ return null;
456
+ }
341
457
  /*----------------------------------------*/
342
458
  /* Footer */
343
459
  /*----------------------------------------*/
@@ -359,7 +475,7 @@ const Modal = (props) => {
359
475
  // Check if this button is last
360
476
  const last = (i === ModalButtonTypes.length - 1);
361
477
  // Create the button
362
- return (React__default["default"].createElement("button", { type: "button", className: `Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'mr-1'}`, onClick: () => {
478
+ return (React__default["default"].createElement("button", { key: ModalButtonType, type: "button", className: `Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'me-1'}`, onClick: () => {
363
479
  handleClose(ModalButtonType);
364
480
  } }, label));
365
481
  });
@@ -367,21 +483,62 @@ const Modal = (props) => {
367
483
  const footer = ((buttons && buttons.length)
368
484
  ? (React__default["default"].createElement("div", null, buttons))
369
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
+ }
370
500
  // Render the modal
371
- return (React__default["default"].createElement("div", { className: `modal fade modal-${size}`, tabIndex: -1, style: {
501
+ return (React__default["default"].createElement("div", { className: `modal show modal-dialog-scrollable modal-dialog-centered modal-${size}`, tabIndex: -1, style: {
372
502
  zIndex: (onTopOfOtherModals
373
- ? 6000000000
503
+ ? 5000000001
374
504
  : 5000000000),
505
+ display: 'block',
506
+ margin: 'auto',
507
+ left: 0,
508
+ right: 0,
375
509
  } },
376
- React__default["default"].createElement("div", { className: "modal-dialog" },
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
+ } },
377
531
  React__default["default"].createElement("div", { className: "modal-content" },
378
532
  React__default["default"].createElement("div", { className: "modal-header" },
379
- React__default["default"].createElement("h5", { className: "modal-title" }, title),
380
- onClose && (React__default["default"].createElement("button", { type: "button", className: "btn-close", "data-bs-dismiss": "modal", "aria-label": "Close", onClick: () => {
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
381
538
  handleClose(ModalButtonType$1.Cancel);
382
539
  } }))),
383
540
  children && (React__default["default"].createElement("div", { className: "modal-body" }, children)),
384
- footer && (React__default["default"].createElement("div", { className: "modal-footer" }, footer))))));
541
+ footer && (React__default["default"].createElement("div", { className: "modal-footer pt-1 pb-1" }, footer))))));
385
542
  };
386
543
 
387
544
  /**
@@ -405,6 +562,24 @@ class ErrorWithCode extends Error {
405
562
  /* Static Helpers */
406
563
  /*------------------------------------------------------------------------*/
407
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
+ /*----------------------------------------*/
408
583
  /* Alert */
409
584
  /*----------------------------------------*/
410
585
  // Stored copies of setters
@@ -416,7 +591,7 @@ let onAlertClosed;
416
591
  * @param title the title text to display at the top of the alert
417
592
  * @param text the text to display in the alert
418
593
  */
419
- const alert = (title, text) => __awaiter(void 0, void 0, void 0, function* () {
594
+ const alert$1 = (title, text) => __awaiter(void 0, void 0, void 0, function* () {
420
595
  // Fallback if alert not available
421
596
  if (!setAlertInfo) {
422
597
  window.alert(`${title}\n\n${text}`);
@@ -490,7 +665,7 @@ const showFatalError = (error, errorTitle = 'An Error Occurred') => {
490
665
  : String((_b = error.code) !== null && _b !== void 0 ? _b : ReactKitErrorCode$1.NoCode));
491
666
  // Handle case where app hasn't loaded
492
667
  if (!setFatalErrorMessage || !setFatalErrorCode) {
493
- alert(errorTitle, `${message} (code: ${code}). Please contact support.`);
668
+ alert$1(errorTitle, `${message} (code: ${code}). Please contact support.`);
494
669
  return undefined;
495
670
  }
496
671
  // Use setters
@@ -507,7 +682,9 @@ const AppWrapper = (props) => {
507
682
  /* Setup */
508
683
  /*------------------------------------------------------------------------*/
509
684
  /* -------------- Props ------------- */
510
- const { children, dark, sessionExpiredMessage = 'Your session has expired. Please go back to Canvas and start over.', } = 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;
511
688
  /* -------------- State ------------- */
512
689
  // Fatal error
513
690
  const [fatalErrorMessage, setFatalErrorMessageInner,] = React.useState();
@@ -517,10 +694,10 @@ const AppWrapper = (props) => {
517
694
  const [fatalErrorTitle, setFatalErrorTitleInner,] = React.useState();
518
695
  setFatalErrorTitle = setFatalErrorTitleInner;
519
696
  // Alert
520
- const [alertInfo, setAlertInfoInner,] = React.useState();
697
+ const [alertInfo, setAlertInfoInner,] = React.useState(undefined);
521
698
  setAlertInfo = setAlertInfoInner;
522
699
  // Confirm
523
- const [confirmInfo, setConfirmInfoInner,] = React.useState();
700
+ const [confirmInfo, setConfirmInfoInner,] = React.useState(undefined);
524
701
  setConfirmInfo = setConfirmInfoInner;
525
702
  // Session expired
526
703
  const [sessionHasExpired, setSessionHasExpiredInner,] = React.useState(false);
@@ -534,8 +711,9 @@ const AppWrapper = (props) => {
534
711
  let modal;
535
712
  /* -------------- Alert ------------- */
536
713
  if (alertInfo) {
537
- modal = (React__default["default"].createElement(Modal, { title: alertInfo.title, type: ModalType$1.Okay, onClose: () => {
714
+ modal = (React__default["default"].createElement(Modal, { key: `alert-${alertInfo.title}-${alertInfo.text}`, title: alertInfo.title, type: ModalType$1.Okay, onClose: () => {
538
715
  // Alert closed
716
+ setAlertInfo(undefined);
539
717
  if (onAlertClosed) {
540
718
  onAlertClosed();
541
719
  }
@@ -543,11 +721,12 @@ const AppWrapper = (props) => {
543
721
  }
544
722
  /* ------------- Confirm ------------ */
545
723
  if (confirmInfo) {
546
- modal = (React__default["default"].createElement(Modal, { title: confirmInfo.title, type: ModalType$1.OkayCancel, onClose: (buttonType) => {
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);
547
726
  if (onConfirmClosed) {
548
727
  onConfirmClosed(buttonType === ModalButtonType$1.Okay);
549
728
  }
550
- }, dontAllowBackdropExit: true }));
729
+ }, dontAllowBackdropExit: true }, confirmInfo.text));
551
730
  }
552
731
  /*----------------------------------------*/
553
732
  /* Views */
@@ -599,10 +778,10 @@ const style$1 = `
599
778
  .LoadingSpinner-blip-2,
600
779
  .LoadingSpinner-blip-3,
601
780
  .LoadingSpinner-blip-4 {
602
- font-size: 25px;
781
+ font-size: 1.8rem;
603
782
  opacity: 0.6;
604
- margin-top: 20px;
605
- margin-bottom: 20px;
783
+ margin-top: 1rem;
784
+ margin-bottom: 1rem;
606
785
  }
607
786
 
608
787
  /* First Blip */
@@ -669,63 +848,67 @@ const LoadingSpinner = () => {
669
848
  /* Style */
670
849
  /*------------------------------------------------------------------------*/
671
850
  const style = `
672
- /* Tab Box */
673
- .TabBox-box {
674
- /* Light Border */
675
- border: 2px solid #dedede;
676
-
677
- /* Rounded Corners (except top-left) */
678
- border-bottom-right-radius: 5px;
679
- border-bottom-left-radius: 5px;
680
- border-top-right-radius: 5px;
681
-
682
- /* Very Light Gray Border */
683
- background: #fdfdfd;
684
-
685
- /* Align Contents on Left */
686
- text-align: left;
687
- }
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;
688
860
 
689
- /* Container for Title */
690
- .TabBox-title-container {
691
- /* Place on Left */
692
- position: relative;
693
- left: 0;
694
- text-align: left;
695
- }
861
+ /* Very Light Gray Border */
862
+ background: #fdfdfd;
863
+
864
+ /* Align Contents on Left */
865
+ text-align: left;
696
866
 
697
- /* Tab-style Title */
698
- .TabBox-title {
699
- /* Place so it Barely Overlaps the Box Border */
700
- display: inline-block;
701
- position: relative;
702
- top: 2px; /* Gives Illusion that Border Doesn't Exist Below Tab */
867
+ /* Add Text Padding */
868
+ padding-left: 0.3rem;
869
+ padding-right: 0.3rem;
870
+ }
703
871
 
704
- /* Title-sized Font */
705
- font-size: 25px;
872
+ /* Container for Title */
873
+ .TabBox-title-container {
874
+ /* Place on Left */
875
+ position: relative;
876
+ left: 0;
877
+ text-align: left;
878
+ }
706
879
 
707
- /* Add Border on Top and Sides */
708
- border-top: 2px solid #dedede;
709
- border-left: 2px solid #dedede;
710
- border-right: 2px solid #dedede;
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 */
711
886
 
712
- /* Round the Top Corners */
713
- border-top-left-radius: 5px;
714
- border-top-right-radius: 5px;
887
+ /* Title-sized Font */
888
+ font-size: 1.5rem;
715
889
 
716
- /* Add Text Padding */
717
- padding-left: 12px;
718
- padding-right: 12px;
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;
719
894
 
720
- /* Match Background Color of Box */
721
- background: #fdfdfd;
722
- }
895
+ /* Round the Top Corners */
896
+ border-top-left-radius: 0.3rem;
897
+ border-top-right-radius: 0.3rem;
723
898
 
724
- /* Make the TabBox's Children Appear Above Title if Overlap Occurs */
725
- .TabBox-children {
726
- position: relative;
727
- z-index: 1;
728
- }
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
+ }
729
912
  `;
730
913
  /*------------------------------------------------------------------------*/
731
914
  /* Component */
@@ -735,7 +918,7 @@ const TabBox = (props) => {
735
918
  /* Setup */
736
919
  /*------------------------------------------------------------------------*/
737
920
  /* -------------- Props ------------- */
738
- const { title, children, } = props;
921
+ const { title, children, noBottomPadding, } = props;
739
922
  /*------------------------------------------------------------------------*/
740
923
  /* Render */
741
924
  /*------------------------------------------------------------------------*/
@@ -743,7 +926,7 @@ const TabBox = (props) => {
743
926
  /* Main UI */
744
927
  /*----------------------------------------*/
745
928
  // Full UI
746
- return (React__default["default"].createElement("div", null,
929
+ return (React__default["default"].createElement("div", { className: `TabBox-container ${noBottomPadding ? '' : 'mb-2'}` },
747
930
  React__default["default"].createElement("style", null, style),
748
931
  React__default["default"].createElement("div", { className: "TabBox-title-container" },
749
932
  React__default["default"].createElement("div", { className: "TabBox-title" }, title)),
@@ -751,6 +934,95 @@ const TabBox = (props) => {
751
934
  React__default["default"].createElement("div", { className: "TabBox-children" }, children))));
752
935
  };
753
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
+
754
1026
  /**
755
1027
  * Shorten text so it fits into a certain number of chars
756
1028
  * @author Gabe Abrams
@@ -894,7 +1166,427 @@ const roundToNumDecimals = (num, numDecimals) => {
894
1166
  return (Math.round(num * rounder) / rounder);
895
1167
  };
896
1168
 
1169
+ // Keep track of whether or not session expiry has already been handled
1170
+ let sessionAlreadyExpired = false;
1171
+ /*------------------------------------------------------------------------*/
1172
+ /* Main */
1173
+ /*------------------------------------------------------------------------*/
1174
+ /**
1175
+ * Visit an endpoint on the server [for client only]
1176
+ * @author Gabe Abrams
1177
+ * @param opts object containing all arguments
1178
+ * @param opts.path - the path of the server endpoint
1179
+ * @param [opts.method=GET] - the method of the endpoint
1180
+ * @param [opts.params] - query/body parameters to include
1181
+ * @returns response from server
1182
+ */
1183
+ const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function* () {
1184
+ var _a;
1185
+ // Send the request
1186
+ const response = yield cacclSendRequest({
1187
+ path: opts.path,
1188
+ method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
1189
+ params: opts.params,
1190
+ });
1191
+ // Check for failure
1192
+ if (!response || !response.body) {
1193
+ throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
1194
+ }
1195
+ if (!response.body.success) {
1196
+ // Session expired
1197
+ if (response.body.code === ReactKitErrorCode$1.SessionExpired) {
1198
+ // Skip notice if session was already expired
1199
+ if (sessionAlreadyExpired) {
1200
+ // Never return (browser is already reloading)
1201
+ yield new Promise(() => {
1202
+ // Promise that never returns
1203
+ });
1204
+ }
1205
+ sessionAlreadyExpired = true;
1206
+ // Show session expiration message
1207
+ {
1208
+ // Fallback to alert
1209
+ // eslint-disable-next-line no-alert
1210
+ alert('Your session has expired. Please start over.');
1211
+ }
1212
+ // Never return (don't continue execution)
1213
+ yield new Promise(() => {
1214
+ // Promise that never returns
1215
+ });
1216
+ }
1217
+ // Other errors
1218
+ throw new ErrorWithCode((response.body.message
1219
+ || 'An unknown error occurred. Please contact an admin.'), (response.body.code
1220
+ || ReactKitErrorCode$1.NoCode));
1221
+ }
1222
+ // Success! Extract the body
1223
+ const { body } = response.body;
1224
+ // Return
1225
+ return body;
1226
+ });
1227
+
1228
+ // Import custom error
1229
+ // Stored copy of caccl functions
1230
+ let _cacclGetLaunchInfo;
1231
+ /*------------------------------------------------------------------------*/
1232
+ /* Helpers */
1233
+ /*------------------------------------------------------------------------*/
1234
+ /**
1235
+ * Get launch info via CACCL
1236
+ * @author Gabe Abrams
1237
+ * @param req express request object
1238
+ * @returns object { launched, launchInfo }
1239
+ */
1240
+ const cacclGetLaunchInfo = (req) => {
1241
+ if (!_cacclGetLaunchInfo) {
1242
+ throw new ErrorWithCode('Could not get launch info because server was not initialized with dce-reactkit\'s initServer function', ReactKitErrorCode$1.NoCACCLGetLaunchInfoFunction);
1243
+ }
1244
+ return _cacclGetLaunchInfo(req);
1245
+ };
1246
+ /*------------------------------------------------------------------------*/
1247
+ /* Main */
1248
+ /*------------------------------------------------------------------------*/
1249
+ /**
1250
+ * Prepare dce-reactkit to run on the server
1251
+ * @author Gabe Abrams
1252
+ * @param opts object containing all arguments
1253
+ * @param opts.getLaunchInfo CACCL LTI's get launch info function
1254
+ */
1255
+ const initServer = (opts) => {
1256
+ _cacclGetLaunchInfo = opts.getLaunchInfo;
1257
+ };
1258
+
1259
+ /**
1260
+ * Server-side API param types
1261
+ * @author Gabe Abrams
1262
+ */
1263
+ var ParamType;
1264
+ (function (ParamType) {
1265
+ ParamType["Boolean"] = "boolean";
1266
+ ParamType["BooleanOptional"] = "boolean-optional";
1267
+ ParamType["Float"] = "float";
1268
+ ParamType["FloatOptional"] = "float-optional";
1269
+ ParamType["Int"] = "int";
1270
+ ParamType["IntOptional"] = "int-optional";
1271
+ ParamType["JSON"] = "json";
1272
+ ParamType["JSONOptional"] = "json-optional";
1273
+ ParamType["String"] = "string";
1274
+ ParamType["StringOptional"] = "string-optional";
1275
+ })(ParamType || (ParamType = {}));
1276
+ var ParamType$1 = ParamType;
1277
+
1278
+ // Import shared types
1279
+ /**
1280
+ * Handle an error and respond to the client
1281
+ * @author Gabe Abrams
1282
+ * @param res express response
1283
+ * @param error error info
1284
+ * @param opts.err the error to send to the client
1285
+ * or the error message
1286
+ * @param [opts.code] an error code (only used if err.code is not
1287
+ * included)
1288
+ * @param [opts.status=500] the https status code to use
1289
+ * defined)
1290
+ */
1291
+ const handleError = (res, error) => {
1292
+ // Get the error message
1293
+ let message;
1294
+ if (error && error.message) {
1295
+ message = (error.message || 'An unknown error occurred.');
1296
+ }
1297
+ else if (typeof error === 'string') {
1298
+ message = (error.trim().length > 0
1299
+ ? error
1300
+ : 'An unknown error occurred.');
1301
+ }
1302
+ else {
1303
+ message = 'An unknown error occurred.';
1304
+ }
1305
+ // Get the error code
1306
+ const code = (error.code || ReactKitErrorCode$1.NoCode);
1307
+ // Get the status code
1308
+ const status = (error.status || 500);
1309
+ // Respond to user
1310
+ res
1311
+ // Set the http status code
1312
+ .status(status)
1313
+ // Send a JSON response
1314
+ .json({
1315
+ // Error message
1316
+ message,
1317
+ // Error code
1318
+ code,
1319
+ // Success = false flag so client can detect server-side errors
1320
+ success: false,
1321
+ });
1322
+ return undefined;
1323
+ };
1324
+
1325
+ /**
1326
+ * Send successful API response
1327
+ * @author Gabe Abrams
1328
+ * @param res express response
1329
+ * @param body the body of the response to send to the client
1330
+ */
1331
+ const handleSuccess = (res, body) => {
1332
+ // Send a http 200 json response
1333
+ res.json({
1334
+ // Include the body as a parameter
1335
+ body,
1336
+ // Success = true flag so client can detect successful responses
1337
+ success: true,
1338
+ });
1339
+ return undefined;
1340
+ };
1341
+
1342
+ /**
1343
+ * Generate an express API route handler
1344
+ * @author Gabe Abrams
1345
+ * @param opts object containing all arguments
1346
+ * @param opts.paramTypes map containing the types for each parameter that is
1347
+ * included in the request (map: param name => type)
1348
+ * @param opts.handler function that processes the request
1349
+ * @returns express route handler that takes the following arguments:
1350
+ * params (map: param name => value), handleSuccess (function for handling
1351
+ * successful requests), handleError (function for handling failed requests),
1352
+ * req (express request object), res (express response object)
1353
+ */
1354
+ const genRouteHandler = (opts) => {
1355
+ // Return a route handler
1356
+ return (req, res) => __awaiter(void 0, void 0, void 0, function* () {
1357
+ var _a;
1358
+ // Output params
1359
+ const output = {};
1360
+ /*----------------------------------------*/
1361
+ /* Parse Params */
1362
+ /*----------------------------------------*/
1363
+ // Process items one by one
1364
+ const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
1365
+ for (let i = 0; i < paramList.length; i++) {
1366
+ const [name, type] = paramList[i];
1367
+ // Find the value as a string
1368
+ const value = (req.params[name]
1369
+ || req.query[name]
1370
+ || req.body[name]);
1371
+ // Parse
1372
+ if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
1373
+ // Boolean
1374
+ // Handle case where value doesn't exist
1375
+ if (value === undefined) {
1376
+ if (type === ParamType$1.BooleanOptional) {
1377
+ output[name] = undefined;
1378
+ }
1379
+ else {
1380
+ return handleError(res, {
1381
+ message: `Parameter ${name} is required, but it was not included.`,
1382
+ code: ReactKitErrorCode$1.MissingParameter,
1383
+ status: 422,
1384
+ });
1385
+ }
1386
+ }
1387
+ else {
1388
+ // Value exists
1389
+ // Simplify value
1390
+ const simpleVal = (String(value)
1391
+ .trim()
1392
+ .toLowerCase());
1393
+ // Parse
1394
+ output[name] = ([
1395
+ 'true',
1396
+ 'yes',
1397
+ 'y',
1398
+ '1',
1399
+ 't',
1400
+ ].indexOf(simpleVal) >= 0);
1401
+ }
1402
+ }
1403
+ else if (type === ParamType$1.Float || type === ParamType$1.FloatOptional) {
1404
+ // Float
1405
+ // Handle case where value doesn't exist
1406
+ if (value === undefined) {
1407
+ if (type === ParamType$1.FloatOptional) {
1408
+ output[name] = undefined;
1409
+ }
1410
+ else {
1411
+ return handleError(res, {
1412
+ message: `Parameter ${name} is required, but it was not included.`,
1413
+ code: ReactKitErrorCode$1.MissingParameter,
1414
+ status: 422,
1415
+ });
1416
+ }
1417
+ }
1418
+ else if (!Number.isNaN(Number.parseFloat(String(value)))) {
1419
+ // Value is a number
1420
+ output[name] = Number.parseFloat(String(value));
1421
+ }
1422
+ else {
1423
+ // Issue!
1424
+ return handleError(res, {
1425
+ message: `Request data was malformed: ${name} was not a valid float.`,
1426
+ code: ReactKitErrorCode$1.InvalidParameter,
1427
+ status: 422,
1428
+ });
1429
+ }
1430
+ }
1431
+ else if (type === ParamType$1.Int || type === ParamType$1.IntOptional) {
1432
+ // Int
1433
+ // Handle case where value doesn't exist
1434
+ if (value === undefined) {
1435
+ if (type === ParamType$1.IntOptional) {
1436
+ output[name] = undefined;
1437
+ }
1438
+ else {
1439
+ return handleError(res, {
1440
+ message: `Parameter ${name} is required, but it was not included.`,
1441
+ code: ReactKitErrorCode$1.MissingParameter,
1442
+ status: 422,
1443
+ });
1444
+ }
1445
+ }
1446
+ else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
1447
+ // Value is a number
1448
+ output[name] = Number.parseInt(String(value), 10);
1449
+ }
1450
+ else {
1451
+ // Issue!
1452
+ return handleError(res, {
1453
+ message: `Request data was malformed: ${name} was not a valid int.`,
1454
+ code: ReactKitErrorCode$1.InvalidParameter,
1455
+ status: 422,
1456
+ });
1457
+ }
1458
+ }
1459
+ else if (type === ParamType$1.JSON || type === ParamType$1.JSONOptional) {
1460
+ // Stringified JSON
1461
+ // Handle case where value doesn't exist
1462
+ if (value === undefined) {
1463
+ if (type === ParamType$1.JSONOptional) {
1464
+ output[name] = undefined;
1465
+ }
1466
+ else {
1467
+ return handleError(res, {
1468
+ message: `Parameter ${name} is required, but it was not included.`,
1469
+ code: ReactKitErrorCode$1.MissingParameter,
1470
+ status: 422,
1471
+ });
1472
+ }
1473
+ }
1474
+ else {
1475
+ // Value exists
1476
+ // Parse
1477
+ try {
1478
+ output[name] = JSON.parse(String(value));
1479
+ }
1480
+ catch (err) {
1481
+ return handleError(res, {
1482
+ message: `Request data was malformed: ${name} was not a valid JSON payload.`,
1483
+ code: ReactKitErrorCode$1.InvalidParameter,
1484
+ status: 422,
1485
+ });
1486
+ }
1487
+ }
1488
+ }
1489
+ else if (type === ParamType$1.String || type === ParamType$1.StringOptional) {
1490
+ // String
1491
+ // Handle case where value doesn't exist
1492
+ if (value === undefined) {
1493
+ if (type === ParamType$1.StringOptional) {
1494
+ output[name] = undefined;
1495
+ }
1496
+ else {
1497
+ return handleError(res, {
1498
+ message: `Parameter ${name} is required, but it was not included.`,
1499
+ code: ReactKitErrorCode$1.MissingParameter,
1500
+ status: 422,
1501
+ });
1502
+ }
1503
+ }
1504
+ else {
1505
+ // Value exists
1506
+ // Leave as is
1507
+ output[name] = value;
1508
+ }
1509
+ }
1510
+ else {
1511
+ // No valid data type
1512
+ return handleError(res, {
1513
+ message: `An internal error occurred: we could not determine the type of ${name}.`,
1514
+ code: ReactKitErrorCode$1.InvalidParameter,
1515
+ status: 422,
1516
+ });
1517
+ }
1518
+ }
1519
+ /*----------------------------------------*/
1520
+ /* Launch Info */
1521
+ /*----------------------------------------*/
1522
+ // Get launch info
1523
+ const { launched, launchInfo } = cacclGetLaunchInfo(req);
1524
+ if (!launched || !launchInfo) {
1525
+ return handleError(res, {
1526
+ message: 'Your session has expired. Please refresh the page and try again.',
1527
+ code: ReactKitErrorCode$1.SessionExpired,
1528
+ status: 440,
1529
+ });
1530
+ }
1531
+ // Error if user info cannot be found
1532
+ if (!launchInfo.userId
1533
+ || !launchInfo.userFirstName
1534
+ || !launchInfo.userLastName
1535
+ || (launchInfo.notInCourse
1536
+ && !launchInfo.isAdmin)
1537
+ || (!launchInfo.isTTM
1538
+ && !launchInfo.isLearner
1539
+ && !launchInfo.isAdmin)) {
1540
+ return handleError(res, {
1541
+ message: 'Your session was invalid. Please refresh the page and try again.',
1542
+ code: ReactKitErrorCode$1.SessionExpired,
1543
+ status: 440,
1544
+ });
1545
+ }
1546
+ // Add launch info to output
1547
+ output.userId = launchInfo.userId;
1548
+ output.userFirstName = launchInfo.userFirstName;
1549
+ output.userLastName = launchInfo.userLastName;
1550
+ output.isLearner = !!launchInfo.isLearner;
1551
+ output.isTTM = !!launchInfo.isTTM;
1552
+ output.isAdmin = !!launchInfo.isAdmin;
1553
+ output.isWatchingInPrivate = !!(req.session.isWatchingInPrivate);
1554
+ /*----------------------------------------*/
1555
+ /* Require Course Consistency */
1556
+ /*----------------------------------------*/
1557
+ // Make sure the user actually launched from the appropriate course
1558
+ if (output.courseId
1559
+ && launchInfo.courseId
1560
+ && output.courseId !== launchInfo.courseId
1561
+ && !output.isTTM
1562
+ && !output.isAdmin) {
1563
+ // Course of interest is not the launch course
1564
+ return handleError(res, {
1565
+ message: 'You switched sessions by opening Immersive Classroom in another tab. Please refresh the page and try again.',
1566
+ code: ReactKitErrorCode$1.WrongCourse,
1567
+ status: 401,
1568
+ });
1569
+ }
1570
+ /*------------------------------------------------------------------------*/
1571
+ /* Call handler */
1572
+ /*------------------------------------------------------------------------*/
1573
+ opts.handler({
1574
+ params: output,
1575
+ handleSuccess: (body) => {
1576
+ return handleSuccess(res, body);
1577
+ },
1578
+ handleError: (error) => {
1579
+ return handleError(res, error);
1580
+ },
1581
+ req,
1582
+ res,
1583
+ });
1584
+ });
1585
+ };
1586
+
897
1587
  exports.AppWrapper = AppWrapper;
1588
+ exports.ButtonInputGroup = ButtonInputGroup;
1589
+ exports.CheckboxButton = CheckboxButton;
898
1590
  exports.ErrorBox = ErrorBox;
899
1591
  exports.ErrorWithCode = ErrorWithCode;
900
1592
  exports.LoadingSpinner = LoadingSpinner;
@@ -902,20 +1594,27 @@ exports.Modal = Modal;
902
1594
  exports.ModalButtonType = ModalButtonType$1;
903
1595
  exports.ModalSize = ModalSize$1;
904
1596
  exports.ModalType = ModalType$1;
1597
+ exports.ParamType = ParamType$1;
1598
+ exports.RadioButton = RadioButton;
905
1599
  exports.ReactKitErrorCode = ReactKitErrorCode$1;
906
1600
  exports.TabBox = TabBox;
907
1601
  exports.Variant = Variant$1;
908
1602
  exports.abbreviate = abbreviate;
909
- exports.alert = alert;
1603
+ exports.alert = alert$1;
910
1604
  exports.avg = avg;
911
1605
  exports.ceilToNumDecimals = ceilToNumDecimals;
912
1606
  exports.confirm = confirm;
913
1607
  exports.floorToNumDecimals = floorToNumDecimals;
914
1608
  exports.forceNumIntoBounds = forceNumIntoBounds;
1609
+ exports.genRouteHandler = genRouteHandler;
1610
+ exports.handleError = handleError;
1611
+ exports.handleSuccess = handleSuccess;
1612
+ exports.initServer = initServer;
915
1613
  exports.padDecimalZeros = padDecimalZeros;
916
1614
  exports.padZerosLeft = padZerosLeft;
917
1615
  exports.roundToNumDecimals = roundToNumDecimals;
918
1616
  exports.showFatalError = showFatalError;
919
1617
  exports.sum = sum;
1618
+ exports.visitServerEndpoint = visitServerEndpoint;
920
1619
  exports.waitMs = waitMs;
921
1620
  //# sourceMappingURL=index.js.map