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.
- package/.eslintrc.js +95 -0
- package/README.md +1 -546
- package/dist/cjs/index.js +784 -85
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/AppWrapper.d.ts +25 -2
- package/dist/cjs/types/components/ButtonInputGroup.d.ts +12 -0
- package/dist/cjs/types/components/CheckboxButton.d.ts +20 -0
- package/dist/cjs/types/components/RadioButton.d.ts +20 -0
- package/dist/cjs/types/components/SimpleDateChooser.d.ts +21 -0
- package/dist/cjs/types/components/TabBox.d.ts +1 -0
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +30 -0
- package/dist/cjs/types/helpers/getOrdinal.d.ts +8 -0
- package/dist/cjs/types/helpers/getTimeInfoInET.d.ts +17 -0
- package/dist/cjs/types/helpers/handleError.d.ts +18 -0
- package/dist/cjs/types/helpers/handleSuccess.d.ts +8 -0
- package/dist/cjs/types/helpers/visitServerEndpoint.d.ts +23 -0
- package/dist/cjs/types/index.d.ts +10 -1
- package/dist/cjs/types/server/initServer.d.ts +21 -0
- package/dist/cjs/types/types/ParamType.d.ts +17 -0
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +3 -1
- package/dist/esm/index.js +776 -86
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/AppWrapper.d.ts +25 -2
- package/dist/esm/types/components/ButtonInputGroup.d.ts +12 -0
- package/dist/esm/types/components/CheckboxButton.d.ts +20 -0
- package/dist/esm/types/components/RadioButton.d.ts +20 -0
- package/dist/esm/types/components/SimpleDateChooser.d.ts +21 -0
- package/dist/esm/types/components/TabBox.d.ts +1 -0
- package/dist/esm/types/helpers/genRouteHandler.d.ts +30 -0
- package/dist/esm/types/helpers/getOrdinal.d.ts +8 -0
- package/dist/esm/types/helpers/getTimeInfoInET.d.ts +17 -0
- package/dist/esm/types/helpers/handleError.d.ts +18 -0
- package/dist/esm/types/helpers/handleSuccess.d.ts +8 -0
- package/dist/esm/types/helpers/visitServerEndpoint.d.ts +23 -0
- package/dist/esm/types/index.d.ts +10 -1
- package/dist/esm/types/server/initServer.d.ts +21 -0
- package/dist/esm/types/types/ParamType.d.ts +17 -0
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +3 -1
- package/dist/index.d.ts +182 -10
- package/package.json +13 -1
- package/rollup.config.js +0 -1
- package/src/components/AppWrapper.tsx +73 -15
- package/src/components/ButtonInputGroup.tsx +89 -0
- package/src/components/CheckboxButton.tsx +100 -0
- package/src/components/ErrorBox.tsx +4 -4
- package/src/components/LoadingSpinner.tsx +3 -3
- package/src/components/Modal.tsx +185 -71
- package/src/components/RadioButton.tsx +102 -0
- package/src/components/SimpleDateChooser.tsx +217 -0
- package/src/components/TabBox.tsx +65 -58
- package/src/helpers/genRouteHandler.ts +326 -0
- package/src/helpers/getOrdinal.tsx +13 -0
- package/src/helpers/getTimeInfoInET.tsx +56 -0
- package/src/helpers/handleError.ts +65 -0
- package/src/helpers/handleSuccess.ts +18 -0
- package/src/helpers/visitServerEndpoint.tsx +110 -0
- package/src/index.ts +21 -0
- package/src/server/initServer.ts +53 -0
- package/src/types/ParamType.ts +18 -0
- package/src/types/ReactKitErrorCode.tsx +3 -1
package/dist/esm/index.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import 'bootstrap/dist/css/bootstrap.min.css';
|
|
3
|
-
import 'bootstrap/dist/js/bootstrap.min';
|
|
4
2
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
5
|
-
import { faExclamationTriangle, faCircle } from '@fortawesome/free-solid-svg-icons';
|
|
3
|
+
import { faExclamationTriangle, faCircle, faDotCircle, faCheckSquare } from '@fortawesome/free-solid-svg-icons';
|
|
4
|
+
import { faCircle as faCircle$1, faSquare } from '@fortawesome/free-regular-svg-icons';
|
|
6
5
|
|
|
7
6
|
/******************************************************************************
|
|
8
7
|
Copyright (c) Microsoft Corporation.
|
|
@@ -29,7 +28,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
29
28
|
});
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
// Highest error code =
|
|
31
|
+
// Highest error code = DRK8
|
|
33
32
|
/**
|
|
34
33
|
* List of error codes built into the react kit
|
|
35
34
|
* @author Gabe Abrams
|
|
@@ -42,6 +41,8 @@ var ReactKitErrorCode;
|
|
|
42
41
|
ReactKitErrorCode["MissingParameter"] = "DRK4";
|
|
43
42
|
ReactKitErrorCode["InvalidParameter"] = "DRK5";
|
|
44
43
|
ReactKitErrorCode["WrongCourse"] = "DRK6";
|
|
44
|
+
ReactKitErrorCode["NoCACCLSendRequestFunction"] = "DRK7";
|
|
45
|
+
ReactKitErrorCode["NoCACCLGetLaunchInfoFunction"] = "DRK8";
|
|
45
46
|
})(ReactKitErrorCode || (ReactKitErrorCode = {}));
|
|
46
47
|
var ReactKitErrorCode$1 = ReactKitErrorCode;
|
|
47
48
|
|
|
@@ -73,9 +74,9 @@ const ErrorBox = (props) => {
|
|
|
73
74
|
' ',
|
|
74
75
|
React.createElement("span", { style: {
|
|
75
76
|
backgroundColor: 'white',
|
|
76
|
-
borderRadius: '
|
|
77
|
-
paddingLeft: '
|
|
78
|
-
paddingRight: '
|
|
77
|
+
borderRadius: '0.3rem',
|
|
78
|
+
paddingLeft: '0.2rem',
|
|
79
|
+
paddingRight: '0.2rem',
|
|
79
80
|
color: '#DC4150',
|
|
80
81
|
fontVariant: 'small-caps',
|
|
81
82
|
fontSize: '80%',
|
|
@@ -87,7 +88,7 @@ const ErrorBox = (props) => {
|
|
|
87
88
|
}
|
|
88
89
|
// Main UI
|
|
89
90
|
return (React.createElement("div", { className: "alert alert-danger text-center", style: {
|
|
90
|
-
maxWidth: '
|
|
91
|
+
maxWidth: '40rem',
|
|
91
92
|
margin: 'auto',
|
|
92
93
|
} },
|
|
93
94
|
React.createElement("h4", { className: "mb-1" },
|
|
@@ -186,9 +187,7 @@ var ModalSize$1 = ModalSize;
|
|
|
186
187
|
/* Constants */
|
|
187
188
|
/*------------------------------------------------------------------------*/
|
|
188
189
|
// Constants
|
|
189
|
-
const MS_TO_ANIMATE =
|
|
190
|
-
const MS_ANIMATE_IN_DELAY = 10;
|
|
191
|
-
// Time to wait before animating in (must be >0 or animation won't trigger)
|
|
190
|
+
const MS_TO_ANIMATE = 200; // Animation duration
|
|
192
191
|
// Modal type to list of buttons
|
|
193
192
|
const modalTypeToModalButtonTypes = {
|
|
194
193
|
[ModalType$1.Okay]: [
|
|
@@ -268,6 +267,114 @@ const ModalButtonTypeToLabelAndVariant = {
|
|
|
268
267
|
},
|
|
269
268
|
};
|
|
270
269
|
/*------------------------------------------------------------------------*/
|
|
270
|
+
/* Style */
|
|
271
|
+
/*------------------------------------------------------------------------*/
|
|
272
|
+
const style$2 = `
|
|
273
|
+
.Modal-backdrop {
|
|
274
|
+
position: fixed;
|
|
275
|
+
top: 0;
|
|
276
|
+
left: 0;
|
|
277
|
+
width: 100vw;
|
|
278
|
+
height: 100vw;
|
|
279
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.Modal-fading-in {
|
|
283
|
+
animation-name: Modal-fading-in;
|
|
284
|
+
animation-duration: ${Math.floor(MS_TO_ANIMATE * 2)}ms;
|
|
285
|
+
animation-iteration-count: 1;
|
|
286
|
+
animation-fill-mode: both;
|
|
287
|
+
animation-timing-function: ease-out;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
@keyframes Modal-fading-in {
|
|
291
|
+
0% {
|
|
292
|
+
opacity: 0;
|
|
293
|
+
}
|
|
294
|
+
100% {
|
|
295
|
+
opacity: 1;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.Modal-fading-out {
|
|
300
|
+
animation-name: Modal-fading-out;
|
|
301
|
+
animation-duration: ${MS_TO_ANIMATE}ms;
|
|
302
|
+
animation-iteration-count: 1;
|
|
303
|
+
animation-fill-mode: both;
|
|
304
|
+
animation-timing-function: ease-in;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
@keyframes Modal-fading-out {
|
|
308
|
+
0% {
|
|
309
|
+
opacity: 1;
|
|
310
|
+
}
|
|
311
|
+
100% {
|
|
312
|
+
opacity: 0;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.Modal-animating-in {
|
|
317
|
+
animation-name: Modal-animating-in;
|
|
318
|
+
animation-duration: ${MS_TO_ANIMATE}ms;
|
|
319
|
+
animation-iteration-count: 1;
|
|
320
|
+
animation-fill-mode: both;
|
|
321
|
+
animation-timing-function: ease-out;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@keyframes Modal-animating-in {
|
|
325
|
+
0% {
|
|
326
|
+
transform: scale(1.05) translate(0, -1.5rem);
|
|
327
|
+
opacity: 0;
|
|
328
|
+
}
|
|
329
|
+
100% {
|
|
330
|
+
transform: scale(1) translate(0, 0);
|
|
331
|
+
opacity: 1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.Modal-animating-pop {
|
|
336
|
+
animation-name: Modal-animating-pop;
|
|
337
|
+
animation-duration: ${MS_TO_ANIMATE}ms;
|
|
338
|
+
animation-iteration-count: 1;
|
|
339
|
+
animation-fill-mode: both;
|
|
340
|
+
animation-timing-function: ease-in-out;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
@keyframes Modal-animating-pop {
|
|
344
|
+
0% {
|
|
345
|
+
transform: scale(1);
|
|
346
|
+
opacity: 1;
|
|
347
|
+
}
|
|
348
|
+
50% {
|
|
349
|
+
transform: scale(1.05);
|
|
350
|
+
opacity: 0.9;
|
|
351
|
+
}
|
|
352
|
+
100% {
|
|
353
|
+
transform: scale(1);
|
|
354
|
+
opacity: 1;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.Modal-animating-out {
|
|
359
|
+
animation-name: Modal-animating-out;
|
|
360
|
+
animation-duration: ${MS_TO_ANIMATE}ms;
|
|
361
|
+
animation-iteration-count: 1;
|
|
362
|
+
animation-fill-mode: both;
|
|
363
|
+
animation-timing-function: ease-in;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
@keyframes Modal-animating-out {
|
|
367
|
+
0% {
|
|
368
|
+
transform: scale(1) translate(0, 0);
|
|
369
|
+
opacity: 1;
|
|
370
|
+
}
|
|
371
|
+
100% {
|
|
372
|
+
transform: scale(1.05) translate(0, -1.5rem);
|
|
373
|
+
opacity: 0;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
`;
|
|
377
|
+
/*------------------------------------------------------------------------*/
|
|
271
378
|
/* Component */
|
|
272
379
|
/*------------------------------------------------------------------------*/
|
|
273
380
|
const Modal = (props) => {
|
|
@@ -278,10 +385,15 @@ const Modal = (props) => {
|
|
|
278
385
|
/* -------------- Props ------------- */
|
|
279
386
|
const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, children, onClose, dontAllowBackdropExit, onTopOfOtherModals, } = props;
|
|
280
387
|
/* -------------- State ------------- */
|
|
388
|
+
// If true, the modal is completely gone
|
|
389
|
+
// (not just invisible, also removed from the dom)
|
|
390
|
+
const [gone, setGone] = useState(false);
|
|
281
391
|
// If true, the modal is shown
|
|
282
392
|
const [visible, setVisible] = useState(false);
|
|
283
|
-
// True if
|
|
284
|
-
const [animatingIn, setAnimatingIn] = useState(
|
|
393
|
+
// True if animation is in use
|
|
394
|
+
const [animatingIn, setAnimatingIn] = useState(true);
|
|
395
|
+
const [animatingPop, setAnimatingPop] = useState(false);
|
|
396
|
+
const [animatingOut, setAnimatingOut] = useState(false);
|
|
285
397
|
/*------------------------------------------------------------------------*/
|
|
286
398
|
/* Lifecycle Functions */
|
|
287
399
|
/*------------------------------------------------------------------------*/
|
|
@@ -291,12 +403,10 @@ const Modal = (props) => {
|
|
|
291
403
|
*/
|
|
292
404
|
useEffect(() => {
|
|
293
405
|
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
294
|
-
// Start the component animating in
|
|
295
|
-
yield waitMs(MS_ANIMATE_IN_DELAY);
|
|
296
|
-
setAnimatingIn(true);
|
|
297
406
|
// Wait and then set visible to true
|
|
298
407
|
yield waitMs(MS_TO_ANIMATE);
|
|
299
408
|
setVisible(true);
|
|
409
|
+
setAnimatingIn(false);
|
|
300
410
|
}))();
|
|
301
411
|
}, []);
|
|
302
412
|
/*------------------------------------------------------------------------*/
|
|
@@ -323,13 +433,19 @@ const Modal = (props) => {
|
|
|
323
433
|
}
|
|
324
434
|
// Update the state
|
|
325
435
|
setVisible(false);
|
|
436
|
+
setAnimatingOut(true);
|
|
326
437
|
// Call the handler after the modal has animated out
|
|
327
438
|
yield waitMs(MS_TO_ANIMATE);
|
|
328
439
|
onClose(ModalButtonType);
|
|
440
|
+
setGone(true);
|
|
329
441
|
});
|
|
330
442
|
/*------------------------------------------------------------------------*/
|
|
331
443
|
/* Render */
|
|
332
444
|
/*------------------------------------------------------------------------*/
|
|
445
|
+
// Return nothing if gone
|
|
446
|
+
if (gone) {
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
333
449
|
/*----------------------------------------*/
|
|
334
450
|
/* Footer */
|
|
335
451
|
/*----------------------------------------*/
|
|
@@ -351,7 +467,7 @@ const Modal = (props) => {
|
|
|
351
467
|
// Check if this button is last
|
|
352
468
|
const last = (i === ModalButtonTypes.length - 1);
|
|
353
469
|
// Create the button
|
|
354
|
-
return (React.createElement("button", { type: "button", className: `Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : '
|
|
470
|
+
return (React.createElement("button", { key: ModalButtonType, type: "button", className: `Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'me-1'}`, onClick: () => {
|
|
355
471
|
handleClose(ModalButtonType);
|
|
356
472
|
} }, label));
|
|
357
473
|
});
|
|
@@ -359,21 +475,62 @@ const Modal = (props) => {
|
|
|
359
475
|
const footer = ((buttons && buttons.length)
|
|
360
476
|
? (React.createElement("div", null, buttons))
|
|
361
477
|
: undefined);
|
|
478
|
+
// Choose an animation
|
|
479
|
+
let animationClass = '';
|
|
480
|
+
let backdropAnimationClass = '';
|
|
481
|
+
if (animatingIn) {
|
|
482
|
+
animationClass = 'Modal-animating-in';
|
|
483
|
+
backdropAnimationClass = 'Modal-fading-in';
|
|
484
|
+
}
|
|
485
|
+
else if (animatingOut) {
|
|
486
|
+
animationClass = 'Modal-animating-out';
|
|
487
|
+
backdropAnimationClass = 'Modal-fading-out';
|
|
488
|
+
}
|
|
489
|
+
else if (animatingPop) {
|
|
490
|
+
animationClass = 'Modal-animating-pop';
|
|
491
|
+
}
|
|
362
492
|
// Render the modal
|
|
363
|
-
return (React.createElement("div", { className: `modal
|
|
493
|
+
return (React.createElement("div", { className: `modal show modal-dialog-scrollable modal-dialog-centered modal-${size}`, tabIndex: -1, style: {
|
|
364
494
|
zIndex: (onTopOfOtherModals
|
|
365
|
-
?
|
|
495
|
+
? 5000000001
|
|
366
496
|
: 5000000000),
|
|
497
|
+
display: 'block',
|
|
498
|
+
margin: 'auto',
|
|
499
|
+
left: 0,
|
|
500
|
+
right: 0,
|
|
367
501
|
} },
|
|
368
|
-
React.createElement("
|
|
502
|
+
React.createElement("style", null, style$2),
|
|
503
|
+
React.createElement("div", { className: `Modal-backdrop ${backdropAnimationClass}`, style: {
|
|
504
|
+
zIndex: 5000000003,
|
|
505
|
+
}, onClick: () => __awaiter(void 0, void 0, void 0, function* () {
|
|
506
|
+
// Skip if exit via backdrop not allowed
|
|
507
|
+
if (dontAllowBackdropExit || !onClose) {
|
|
508
|
+
// Show pop animation
|
|
509
|
+
if (!animatingPop) {
|
|
510
|
+
setAnimatingPop(true);
|
|
511
|
+
// Wait then stop pop animation
|
|
512
|
+
yield waitMs(MS_TO_ANIMATE);
|
|
513
|
+
setAnimatingPop(false);
|
|
514
|
+
}
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
// Handle close
|
|
518
|
+
handleClose(ModalButtonType$1.Cancel);
|
|
519
|
+
}) }),
|
|
520
|
+
React.createElement("div", { className: `modal-dialog ${animationClass}`, style: {
|
|
521
|
+
zIndex: 5000000002,
|
|
522
|
+
} },
|
|
369
523
|
React.createElement("div", { className: "modal-content" },
|
|
370
524
|
React.createElement("div", { className: "modal-header" },
|
|
371
|
-
React.createElement("h5", { className: "modal-title"
|
|
372
|
-
|
|
525
|
+
React.createElement("h5", { className: "modal-title", style: {
|
|
526
|
+
fontWeight: 'bold',
|
|
527
|
+
} }, title),
|
|
528
|
+
onClose && (React.createElement("button", { type: "button", className: "btn-close", "aria-label": "Close", onClick: () => {
|
|
529
|
+
// Handle close
|
|
373
530
|
handleClose(ModalButtonType$1.Cancel);
|
|
374
531
|
} }))),
|
|
375
532
|
children && (React.createElement("div", { className: "modal-body" }, children)),
|
|
376
|
-
footer && (React.createElement("div", { className: "modal-footer" }, footer))))));
|
|
533
|
+
footer && (React.createElement("div", { className: "modal-footer pt-1 pb-1" }, footer))))));
|
|
377
534
|
};
|
|
378
535
|
|
|
379
536
|
/**
|
|
@@ -397,6 +554,24 @@ class ErrorWithCode extends Error {
|
|
|
397
554
|
/* Static Helpers */
|
|
398
555
|
/*------------------------------------------------------------------------*/
|
|
399
556
|
/*----------------------------------------*/
|
|
557
|
+
/* Send Request */
|
|
558
|
+
/*----------------------------------------*/
|
|
559
|
+
// Store copy of caccl send request
|
|
560
|
+
let _cacclSendRequest;
|
|
561
|
+
/**
|
|
562
|
+
* Send a request using caccl's send request feature
|
|
563
|
+
* @author Gabe Abrams
|
|
564
|
+
* @param opts send request options
|
|
565
|
+
* @returns send request response
|
|
566
|
+
*/
|
|
567
|
+
const cacclSendRequest = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
568
|
+
// Make sure send request has been passed in
|
|
569
|
+
if (!_cacclSendRequest) {
|
|
570
|
+
throw new ErrorWithCode('The request could not be sent because the AppWrapper component does not have a copy of sendRequest from CACCL.', ReactKitErrorCode$1.NoCACCLSendRequestFunction);
|
|
571
|
+
}
|
|
572
|
+
return _cacclSendRequest(opts);
|
|
573
|
+
});
|
|
574
|
+
/*----------------------------------------*/
|
|
400
575
|
/* Alert */
|
|
401
576
|
/*----------------------------------------*/
|
|
402
577
|
// Stored copies of setters
|
|
@@ -408,7 +583,7 @@ let onAlertClosed;
|
|
|
408
583
|
* @param title the title text to display at the top of the alert
|
|
409
584
|
* @param text the text to display in the alert
|
|
410
585
|
*/
|
|
411
|
-
const alert = (title, text) => __awaiter(void 0, void 0, void 0, function* () {
|
|
586
|
+
const alert$1 = (title, text) => __awaiter(void 0, void 0, void 0, function* () {
|
|
412
587
|
// Fallback if alert not available
|
|
413
588
|
if (!setAlertInfo) {
|
|
414
589
|
window.alert(`${title}\n\n${text}`);
|
|
@@ -482,7 +657,7 @@ const showFatalError = (error, errorTitle = 'An Error Occurred') => {
|
|
|
482
657
|
: String((_b = error.code) !== null && _b !== void 0 ? _b : ReactKitErrorCode$1.NoCode));
|
|
483
658
|
// Handle case where app hasn't loaded
|
|
484
659
|
if (!setFatalErrorMessage || !setFatalErrorCode) {
|
|
485
|
-
alert(errorTitle, `${message} (code: ${code}). Please contact support.`);
|
|
660
|
+
alert$1(errorTitle, `${message} (code: ${code}). Please contact support.`);
|
|
486
661
|
return undefined;
|
|
487
662
|
}
|
|
488
663
|
// Use setters
|
|
@@ -499,7 +674,9 @@ const AppWrapper = (props) => {
|
|
|
499
674
|
/* Setup */
|
|
500
675
|
/*------------------------------------------------------------------------*/
|
|
501
676
|
/* -------------- Props ------------- */
|
|
502
|
-
const { children, dark, sessionExpiredMessage = 'Your session has expired. Please go back to Canvas and start over.', } = props;
|
|
677
|
+
const { children, sendRequest, dark, sessionExpiredMessage = 'Your session has expired. Please go back to Canvas and start over.', } = props;
|
|
678
|
+
// Store copy of send request
|
|
679
|
+
_cacclSendRequest = sendRequest;
|
|
503
680
|
/* -------------- State ------------- */
|
|
504
681
|
// Fatal error
|
|
505
682
|
const [fatalErrorMessage, setFatalErrorMessageInner,] = useState();
|
|
@@ -509,10 +686,10 @@ const AppWrapper = (props) => {
|
|
|
509
686
|
const [fatalErrorTitle, setFatalErrorTitleInner,] = useState();
|
|
510
687
|
setFatalErrorTitle = setFatalErrorTitleInner;
|
|
511
688
|
// Alert
|
|
512
|
-
const [alertInfo, setAlertInfoInner,] = useState();
|
|
689
|
+
const [alertInfo, setAlertInfoInner,] = useState(undefined);
|
|
513
690
|
setAlertInfo = setAlertInfoInner;
|
|
514
691
|
// Confirm
|
|
515
|
-
const [confirmInfo, setConfirmInfoInner,] = useState();
|
|
692
|
+
const [confirmInfo, setConfirmInfoInner,] = useState(undefined);
|
|
516
693
|
setConfirmInfo = setConfirmInfoInner;
|
|
517
694
|
// Session expired
|
|
518
695
|
const [sessionHasExpired, setSessionHasExpiredInner,] = useState(false);
|
|
@@ -526,8 +703,9 @@ const AppWrapper = (props) => {
|
|
|
526
703
|
let modal;
|
|
527
704
|
/* -------------- Alert ------------- */
|
|
528
705
|
if (alertInfo) {
|
|
529
|
-
modal = (React.createElement(Modal, { title: alertInfo.title, type: ModalType$1.Okay, onClose: () => {
|
|
706
|
+
modal = (React.createElement(Modal, { key: `alert-${alertInfo.title}-${alertInfo.text}`, title: alertInfo.title, type: ModalType$1.Okay, onClose: () => {
|
|
530
707
|
// Alert closed
|
|
708
|
+
setAlertInfo(undefined);
|
|
531
709
|
if (onAlertClosed) {
|
|
532
710
|
onAlertClosed();
|
|
533
711
|
}
|
|
@@ -535,11 +713,12 @@ const AppWrapper = (props) => {
|
|
|
535
713
|
}
|
|
536
714
|
/* ------------- Confirm ------------ */
|
|
537
715
|
if (confirmInfo) {
|
|
538
|
-
modal = (React.createElement(Modal, { title: confirmInfo.title, type: ModalType$1.OkayCancel, onClose: (buttonType) => {
|
|
716
|
+
modal = (React.createElement(Modal, { key: `confirm-${confirmInfo.title}-${confirmInfo.text}`, title: confirmInfo.title, type: ModalType$1.OkayCancel, onClose: (buttonType) => {
|
|
717
|
+
setConfirmInfo(undefined);
|
|
539
718
|
if (onConfirmClosed) {
|
|
540
719
|
onConfirmClosed(buttonType === ModalButtonType$1.Okay);
|
|
541
720
|
}
|
|
542
|
-
}, dontAllowBackdropExit: true }));
|
|
721
|
+
}, dontAllowBackdropExit: true }, confirmInfo.text));
|
|
543
722
|
}
|
|
544
723
|
/*----------------------------------------*/
|
|
545
724
|
/* Views */
|
|
@@ -591,10 +770,10 @@ const style$1 = `
|
|
|
591
770
|
.LoadingSpinner-blip-2,
|
|
592
771
|
.LoadingSpinner-blip-3,
|
|
593
772
|
.LoadingSpinner-blip-4 {
|
|
594
|
-
font-size:
|
|
773
|
+
font-size: 1.8rem;
|
|
595
774
|
opacity: 0.6;
|
|
596
|
-
margin-top:
|
|
597
|
-
margin-bottom:
|
|
775
|
+
margin-top: 1rem;
|
|
776
|
+
margin-bottom: 1rem;
|
|
598
777
|
}
|
|
599
778
|
|
|
600
779
|
/* First Blip */
|
|
@@ -661,63 +840,67 @@ const LoadingSpinner = () => {
|
|
|
661
840
|
/* Style */
|
|
662
841
|
/*------------------------------------------------------------------------*/
|
|
663
842
|
const style = `
|
|
664
|
-
/* Tab Box */
|
|
665
|
-
.TabBox-box {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
/* Very Light Gray Border */
|
|
675
|
-
background: #fdfdfd;
|
|
676
|
-
|
|
677
|
-
/* Align Contents on Left */
|
|
678
|
-
text-align: left;
|
|
679
|
-
}
|
|
843
|
+
/* Tab Box */
|
|
844
|
+
.TabBox-box {
|
|
845
|
+
/* Light Border */
|
|
846
|
+
border: 0.15rem solid #dedede;
|
|
847
|
+
|
|
848
|
+
/* Rounded Corners (except top-left) */
|
|
849
|
+
border-bottom-right-radius: 0.3rem;
|
|
850
|
+
border-bottom-left-radius: 0.3rem;
|
|
851
|
+
border-top-right-radius: 0.3rem;
|
|
680
852
|
|
|
681
|
-
/*
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
text-align: left;
|
|
687
|
-
}
|
|
853
|
+
/* Very Light Gray Border */
|
|
854
|
+
background: #fdfdfd;
|
|
855
|
+
|
|
856
|
+
/* Align Contents on Left */
|
|
857
|
+
text-align: left;
|
|
688
858
|
|
|
689
|
-
/*
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
position: relative;
|
|
694
|
-
top: 2px; /* Gives Illusion that Border Doesn't Exist Below Tab */
|
|
859
|
+
/* Add Text Padding */
|
|
860
|
+
padding-left: 0.3rem;
|
|
861
|
+
padding-right: 0.3rem;
|
|
862
|
+
}
|
|
695
863
|
|
|
696
|
-
/* Title
|
|
697
|
-
|
|
864
|
+
/* Container for Title */
|
|
865
|
+
.TabBox-title-container {
|
|
866
|
+
/* Place on Left */
|
|
867
|
+
position: relative;
|
|
868
|
+
left: 0;
|
|
869
|
+
text-align: left;
|
|
870
|
+
}
|
|
698
871
|
|
|
699
|
-
/*
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
872
|
+
/* Tab-style Title */
|
|
873
|
+
.TabBox-title {
|
|
874
|
+
/* Place so it Barely Overlaps the Box Border */
|
|
875
|
+
display: inline-block;
|
|
876
|
+
position: relative;
|
|
877
|
+
top: 0.15rem; /* Gives Illusion that Border Doesn't Exist Below Tab */
|
|
703
878
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
border-top-right-radius: 5px;
|
|
879
|
+
/* Title-sized Font */
|
|
880
|
+
font-size: 1.5rem;
|
|
707
881
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
882
|
+
/* Add Border on Top and Sides */
|
|
883
|
+
border-top: 0.15rem solid #dedede;
|
|
884
|
+
border-left: 0.15rem solid #dedede;
|
|
885
|
+
border-right: 0.15rem solid #dedede;
|
|
711
886
|
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
887
|
+
/* Round the Top Corners */
|
|
888
|
+
border-top-left-radius: 0.3rem;
|
|
889
|
+
border-top-right-radius: 0.3rem;
|
|
715
890
|
|
|
716
|
-
/*
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
891
|
+
/* Add Text Padding */
|
|
892
|
+
padding-left: 0.5rem;
|
|
893
|
+
padding-right: 0.5rem;
|
|
894
|
+
|
|
895
|
+
/* Match Background Color of Box */
|
|
896
|
+
background: #fdfdfd;
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/* Make the TabBox's Children Appear Above Title if Overlap Occurs */
|
|
900
|
+
.TabBox-children {
|
|
901
|
+
position: relative;
|
|
902
|
+
z-index: 1;
|
|
903
|
+
}
|
|
721
904
|
`;
|
|
722
905
|
/*------------------------------------------------------------------------*/
|
|
723
906
|
/* Component */
|
|
@@ -727,7 +910,7 @@ const TabBox = (props) => {
|
|
|
727
910
|
/* Setup */
|
|
728
911
|
/*------------------------------------------------------------------------*/
|
|
729
912
|
/* -------------- Props ------------- */
|
|
730
|
-
const { title, children, } = props;
|
|
913
|
+
const { title, children, noBottomPadding, } = props;
|
|
731
914
|
/*------------------------------------------------------------------------*/
|
|
732
915
|
/* Render */
|
|
733
916
|
/*------------------------------------------------------------------------*/
|
|
@@ -735,7 +918,7 @@ const TabBox = (props) => {
|
|
|
735
918
|
/* Main UI */
|
|
736
919
|
/*----------------------------------------*/
|
|
737
920
|
// Full UI
|
|
738
|
-
return (React.createElement("div",
|
|
921
|
+
return (React.createElement("div", { className: `TabBox-container ${noBottomPadding ? '' : 'mb-2'}` },
|
|
739
922
|
React.createElement("style", null, style),
|
|
740
923
|
React.createElement("div", { className: "TabBox-title-container" },
|
|
741
924
|
React.createElement("div", { className: "TabBox-title" }, title)),
|
|
@@ -743,6 +926,95 @@ const TabBox = (props) => {
|
|
|
743
926
|
React.createElement("div", { className: "TabBox-children" }, children))));
|
|
744
927
|
};
|
|
745
928
|
|
|
929
|
+
/**
|
|
930
|
+
* A radio selection button
|
|
931
|
+
* @author Gabe Abrams
|
|
932
|
+
*/
|
|
933
|
+
/*------------------------------------------------------------------------*/
|
|
934
|
+
/* Component */
|
|
935
|
+
/*------------------------------------------------------------------------*/
|
|
936
|
+
const RadioButton = (props) => {
|
|
937
|
+
/*------------------------------------------------------------------------*/
|
|
938
|
+
/* Setup */
|
|
939
|
+
/*------------------------------------------------------------------------*/
|
|
940
|
+
/* -------------- Props ------------- */
|
|
941
|
+
const { text, onSelected, ariaLabel, title, selected, id, noMarginOnRight, selectedVariant = Variant$1.Secondary, unselectedVariant = Variant$1.Light, small, } = props;
|
|
942
|
+
/*------------------------------------------------------------------------*/
|
|
943
|
+
/* Render */
|
|
944
|
+
/*------------------------------------------------------------------------*/
|
|
945
|
+
/*----------------------------------------*/
|
|
946
|
+
/* Main UI */
|
|
947
|
+
/*----------------------------------------*/
|
|
948
|
+
return (React.createElement("button", { type: "button", id: id, title: title, className: `btn btn-${selected ? selectedVariant : unselectedVariant}${selected ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`, "aria-label": `${ariaLabel}${selected ? ': currently selected' : ''}`, onClick: () => {
|
|
949
|
+
if (!selected) {
|
|
950
|
+
onSelected();
|
|
951
|
+
}
|
|
952
|
+
} },
|
|
953
|
+
React.createElement(FontAwesomeIcon, { icon: selected ? faDotCircle : faCircle$1, className: "me-1" }),
|
|
954
|
+
text));
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
/**
|
|
958
|
+
* A checkbox button
|
|
959
|
+
* @author Gabe Abrams
|
|
960
|
+
*/
|
|
961
|
+
/*------------------------------------------------------------------------*/
|
|
962
|
+
/* Component */
|
|
963
|
+
/*------------------------------------------------------------------------*/
|
|
964
|
+
const CheckboxButton = (props) => {
|
|
965
|
+
/*------------------------------------------------------------------------*/
|
|
966
|
+
/* Setup */
|
|
967
|
+
/*------------------------------------------------------------------------*/
|
|
968
|
+
/* -------------- Props ------------- */
|
|
969
|
+
const { text, onChanged, ariaLabel, title, checked, id, noMarginOnRight, checkedVariant = Variant$1.Secondary, uncheckedVariant = Variant$1.Light, small, } = props;
|
|
970
|
+
/*------------------------------------------------------------------------*/
|
|
971
|
+
/* Render */
|
|
972
|
+
/*------------------------------------------------------------------------*/
|
|
973
|
+
/*----------------------------------------*/
|
|
974
|
+
/* Main UI */
|
|
975
|
+
/*----------------------------------------*/
|
|
976
|
+
return (React.createElement("button", { type: "button", id: id, title: title, className: `btn btn-${checked ? checkedVariant : uncheckedVariant}${checked ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`, "aria-label": `${ariaLabel}${checked ? ': currently checked' : ''}`, onClick: () => {
|
|
977
|
+
onChanged(!checked);
|
|
978
|
+
} },
|
|
979
|
+
React.createElement(FontAwesomeIcon, { icon: checked ? faCheckSquare : faSquare, className: "me-1" }),
|
|
980
|
+
text));
|
|
981
|
+
};
|
|
982
|
+
|
|
983
|
+
/**
|
|
984
|
+
* Input group with a title and space for buttons
|
|
985
|
+
* @author Gabe Abrams
|
|
986
|
+
*/
|
|
987
|
+
/*------------------------------------------------------------------------*/
|
|
988
|
+
/* Component */
|
|
989
|
+
/*------------------------------------------------------------------------*/
|
|
990
|
+
const ButtonInputGroup = (props) => {
|
|
991
|
+
/*------------------------------------------------------------------------*/
|
|
992
|
+
/* Setup */
|
|
993
|
+
/*------------------------------------------------------------------------*/
|
|
994
|
+
/* -------------- Props ------------- */
|
|
995
|
+
// Destructure all props
|
|
996
|
+
const { label, minLabelWidth, children, } = props;
|
|
997
|
+
/*------------------------------------------------------------------------*/
|
|
998
|
+
/* Render */
|
|
999
|
+
/*------------------------------------------------------------------------*/
|
|
1000
|
+
/*----------------------------------------*/
|
|
1001
|
+
/* Main UI */
|
|
1002
|
+
/*----------------------------------------*/
|
|
1003
|
+
return (React.createElement("div", { className: "input-group" },
|
|
1004
|
+
React.createElement("div", { className: "input-group-prepend d-flex w-100" },
|
|
1005
|
+
React.createElement("span", { className: "input-group-text", style: {
|
|
1006
|
+
minWidth: (minLabelWidth !== null && minLabelWidth !== void 0 ? minLabelWidth : undefined),
|
|
1007
|
+
borderTopRightRadius: 0,
|
|
1008
|
+
borderBottomRightRadius: 0,
|
|
1009
|
+
} }, label),
|
|
1010
|
+
React.createElement("span", { className: "input-group-text flex-grow-1 rounded-right", style: {
|
|
1011
|
+
backgroundColor: 'white',
|
|
1012
|
+
borderTopLeftRadius: 0,
|
|
1013
|
+
borderBottomLeftRadius: 0,
|
|
1014
|
+
borderLeftWidth: 0,
|
|
1015
|
+
} }, children))));
|
|
1016
|
+
};
|
|
1017
|
+
|
|
746
1018
|
/**
|
|
747
1019
|
* Shorten text so it fits into a certain number of chars
|
|
748
1020
|
* @author Gabe Abrams
|
|
@@ -886,5 +1158,423 @@ const roundToNumDecimals = (num, numDecimals) => {
|
|
|
886
1158
|
return (Math.round(num * rounder) / rounder);
|
|
887
1159
|
};
|
|
888
1160
|
|
|
889
|
-
|
|
1161
|
+
// Keep track of whether or not session expiry has already been handled
|
|
1162
|
+
let sessionAlreadyExpired = false;
|
|
1163
|
+
/*------------------------------------------------------------------------*/
|
|
1164
|
+
/* Main */
|
|
1165
|
+
/*------------------------------------------------------------------------*/
|
|
1166
|
+
/**
|
|
1167
|
+
* Visit an endpoint on the server [for client only]
|
|
1168
|
+
* @author Gabe Abrams
|
|
1169
|
+
* @param opts object containing all arguments
|
|
1170
|
+
* @param opts.path - the path of the server endpoint
|
|
1171
|
+
* @param [opts.method=GET] - the method of the endpoint
|
|
1172
|
+
* @param [opts.params] - query/body parameters to include
|
|
1173
|
+
* @returns response from server
|
|
1174
|
+
*/
|
|
1175
|
+
const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1176
|
+
var _a;
|
|
1177
|
+
// Send the request
|
|
1178
|
+
const response = yield cacclSendRequest({
|
|
1179
|
+
path: opts.path,
|
|
1180
|
+
method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
|
|
1181
|
+
params: opts.params,
|
|
1182
|
+
});
|
|
1183
|
+
// Check for failure
|
|
1184
|
+
if (!response || !response.body) {
|
|
1185
|
+
throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
|
|
1186
|
+
}
|
|
1187
|
+
if (!response.body.success) {
|
|
1188
|
+
// Session expired
|
|
1189
|
+
if (response.body.code === ReactKitErrorCode$1.SessionExpired) {
|
|
1190
|
+
// Skip notice if session was already expired
|
|
1191
|
+
if (sessionAlreadyExpired) {
|
|
1192
|
+
// Never return (browser is already reloading)
|
|
1193
|
+
yield new Promise(() => {
|
|
1194
|
+
// Promise that never returns
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
sessionAlreadyExpired = true;
|
|
1198
|
+
// Show session expiration message
|
|
1199
|
+
{
|
|
1200
|
+
// Fallback to alert
|
|
1201
|
+
// eslint-disable-next-line no-alert
|
|
1202
|
+
alert('Your session has expired. Please start over.');
|
|
1203
|
+
}
|
|
1204
|
+
// Never return (don't continue execution)
|
|
1205
|
+
yield new Promise(() => {
|
|
1206
|
+
// Promise that never returns
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
// Other errors
|
|
1210
|
+
throw new ErrorWithCode((response.body.message
|
|
1211
|
+
|| 'An unknown error occurred. Please contact an admin.'), (response.body.code
|
|
1212
|
+
|| ReactKitErrorCode$1.NoCode));
|
|
1213
|
+
}
|
|
1214
|
+
// Success! Extract the body
|
|
1215
|
+
const { body } = response.body;
|
|
1216
|
+
// Return
|
|
1217
|
+
return body;
|
|
1218
|
+
});
|
|
1219
|
+
|
|
1220
|
+
// Import custom error
|
|
1221
|
+
// Stored copy of caccl functions
|
|
1222
|
+
let _cacclGetLaunchInfo;
|
|
1223
|
+
/*------------------------------------------------------------------------*/
|
|
1224
|
+
/* Helpers */
|
|
1225
|
+
/*------------------------------------------------------------------------*/
|
|
1226
|
+
/**
|
|
1227
|
+
* Get launch info via CACCL
|
|
1228
|
+
* @author Gabe Abrams
|
|
1229
|
+
* @param req express request object
|
|
1230
|
+
* @returns object { launched, launchInfo }
|
|
1231
|
+
*/
|
|
1232
|
+
const cacclGetLaunchInfo = (req) => {
|
|
1233
|
+
if (!_cacclGetLaunchInfo) {
|
|
1234
|
+
throw new ErrorWithCode('Could not get launch info because server was not initialized with dce-reactkit\'s initServer function', ReactKitErrorCode$1.NoCACCLGetLaunchInfoFunction);
|
|
1235
|
+
}
|
|
1236
|
+
return _cacclGetLaunchInfo(req);
|
|
1237
|
+
};
|
|
1238
|
+
/*------------------------------------------------------------------------*/
|
|
1239
|
+
/* Main */
|
|
1240
|
+
/*------------------------------------------------------------------------*/
|
|
1241
|
+
/**
|
|
1242
|
+
* Prepare dce-reactkit to run on the server
|
|
1243
|
+
* @author Gabe Abrams
|
|
1244
|
+
* @param opts object containing all arguments
|
|
1245
|
+
* @param opts.getLaunchInfo CACCL LTI's get launch info function
|
|
1246
|
+
*/
|
|
1247
|
+
const initServer = (opts) => {
|
|
1248
|
+
_cacclGetLaunchInfo = opts.getLaunchInfo;
|
|
1249
|
+
};
|
|
1250
|
+
|
|
1251
|
+
/**
|
|
1252
|
+
* Server-side API param types
|
|
1253
|
+
* @author Gabe Abrams
|
|
1254
|
+
*/
|
|
1255
|
+
var ParamType;
|
|
1256
|
+
(function (ParamType) {
|
|
1257
|
+
ParamType["Boolean"] = "boolean";
|
|
1258
|
+
ParamType["BooleanOptional"] = "boolean-optional";
|
|
1259
|
+
ParamType["Float"] = "float";
|
|
1260
|
+
ParamType["FloatOptional"] = "float-optional";
|
|
1261
|
+
ParamType["Int"] = "int";
|
|
1262
|
+
ParamType["IntOptional"] = "int-optional";
|
|
1263
|
+
ParamType["JSON"] = "json";
|
|
1264
|
+
ParamType["JSONOptional"] = "json-optional";
|
|
1265
|
+
ParamType["String"] = "string";
|
|
1266
|
+
ParamType["StringOptional"] = "string-optional";
|
|
1267
|
+
})(ParamType || (ParamType = {}));
|
|
1268
|
+
var ParamType$1 = ParamType;
|
|
1269
|
+
|
|
1270
|
+
// Import shared types
|
|
1271
|
+
/**
|
|
1272
|
+
* Handle an error and respond to the client
|
|
1273
|
+
* @author Gabe Abrams
|
|
1274
|
+
* @param res express response
|
|
1275
|
+
* @param error error info
|
|
1276
|
+
* @param opts.err the error to send to the client
|
|
1277
|
+
* or the error message
|
|
1278
|
+
* @param [opts.code] an error code (only used if err.code is not
|
|
1279
|
+
* included)
|
|
1280
|
+
* @param [opts.status=500] the https status code to use
|
|
1281
|
+
* defined)
|
|
1282
|
+
*/
|
|
1283
|
+
const handleError = (res, error) => {
|
|
1284
|
+
// Get the error message
|
|
1285
|
+
let message;
|
|
1286
|
+
if (error && error.message) {
|
|
1287
|
+
message = (error.message || 'An unknown error occurred.');
|
|
1288
|
+
}
|
|
1289
|
+
else if (typeof error === 'string') {
|
|
1290
|
+
message = (error.trim().length > 0
|
|
1291
|
+
? error
|
|
1292
|
+
: 'An unknown error occurred.');
|
|
1293
|
+
}
|
|
1294
|
+
else {
|
|
1295
|
+
message = 'An unknown error occurred.';
|
|
1296
|
+
}
|
|
1297
|
+
// Get the error code
|
|
1298
|
+
const code = (error.code || ReactKitErrorCode$1.NoCode);
|
|
1299
|
+
// Get the status code
|
|
1300
|
+
const status = (error.status || 500);
|
|
1301
|
+
// Respond to user
|
|
1302
|
+
res
|
|
1303
|
+
// Set the http status code
|
|
1304
|
+
.status(status)
|
|
1305
|
+
// Send a JSON response
|
|
1306
|
+
.json({
|
|
1307
|
+
// Error message
|
|
1308
|
+
message,
|
|
1309
|
+
// Error code
|
|
1310
|
+
code,
|
|
1311
|
+
// Success = false flag so client can detect server-side errors
|
|
1312
|
+
success: false,
|
|
1313
|
+
});
|
|
1314
|
+
return undefined;
|
|
1315
|
+
};
|
|
1316
|
+
|
|
1317
|
+
/**
|
|
1318
|
+
* Send successful API response
|
|
1319
|
+
* @author Gabe Abrams
|
|
1320
|
+
* @param res express response
|
|
1321
|
+
* @param body the body of the response to send to the client
|
|
1322
|
+
*/
|
|
1323
|
+
const handleSuccess = (res, body) => {
|
|
1324
|
+
// Send a http 200 json response
|
|
1325
|
+
res.json({
|
|
1326
|
+
// Include the body as a parameter
|
|
1327
|
+
body,
|
|
1328
|
+
// Success = true flag so client can detect successful responses
|
|
1329
|
+
success: true,
|
|
1330
|
+
});
|
|
1331
|
+
return undefined;
|
|
1332
|
+
};
|
|
1333
|
+
|
|
1334
|
+
/**
|
|
1335
|
+
* Generate an express API route handler
|
|
1336
|
+
* @author Gabe Abrams
|
|
1337
|
+
* @param opts object containing all arguments
|
|
1338
|
+
* @param opts.paramTypes map containing the types for each parameter that is
|
|
1339
|
+
* included in the request (map: param name => type)
|
|
1340
|
+
* @param opts.handler function that processes the request
|
|
1341
|
+
* @returns express route handler that takes the following arguments:
|
|
1342
|
+
* params (map: param name => value), handleSuccess (function for handling
|
|
1343
|
+
* successful requests), handleError (function for handling failed requests),
|
|
1344
|
+
* req (express request object), res (express response object)
|
|
1345
|
+
*/
|
|
1346
|
+
const genRouteHandler = (opts) => {
|
|
1347
|
+
// Return a route handler
|
|
1348
|
+
return (req, res) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1349
|
+
var _a;
|
|
1350
|
+
// Output params
|
|
1351
|
+
const output = {};
|
|
1352
|
+
/*----------------------------------------*/
|
|
1353
|
+
/* Parse Params */
|
|
1354
|
+
/*----------------------------------------*/
|
|
1355
|
+
// Process items one by one
|
|
1356
|
+
const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
|
|
1357
|
+
for (let i = 0; i < paramList.length; i++) {
|
|
1358
|
+
const [name, type] = paramList[i];
|
|
1359
|
+
// Find the value as a string
|
|
1360
|
+
const value = (req.params[name]
|
|
1361
|
+
|| req.query[name]
|
|
1362
|
+
|| req.body[name]);
|
|
1363
|
+
// Parse
|
|
1364
|
+
if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
|
|
1365
|
+
// Boolean
|
|
1366
|
+
// Handle case where value doesn't exist
|
|
1367
|
+
if (value === undefined) {
|
|
1368
|
+
if (type === ParamType$1.BooleanOptional) {
|
|
1369
|
+
output[name] = undefined;
|
|
1370
|
+
}
|
|
1371
|
+
else {
|
|
1372
|
+
return handleError(res, {
|
|
1373
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
1374
|
+
code: ReactKitErrorCode$1.MissingParameter,
|
|
1375
|
+
status: 422,
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
else {
|
|
1380
|
+
// Value exists
|
|
1381
|
+
// Simplify value
|
|
1382
|
+
const simpleVal = (String(value)
|
|
1383
|
+
.trim()
|
|
1384
|
+
.toLowerCase());
|
|
1385
|
+
// Parse
|
|
1386
|
+
output[name] = ([
|
|
1387
|
+
'true',
|
|
1388
|
+
'yes',
|
|
1389
|
+
'y',
|
|
1390
|
+
'1',
|
|
1391
|
+
't',
|
|
1392
|
+
].indexOf(simpleVal) >= 0);
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
else if (type === ParamType$1.Float || type === ParamType$1.FloatOptional) {
|
|
1396
|
+
// Float
|
|
1397
|
+
// Handle case where value doesn't exist
|
|
1398
|
+
if (value === undefined) {
|
|
1399
|
+
if (type === ParamType$1.FloatOptional) {
|
|
1400
|
+
output[name] = undefined;
|
|
1401
|
+
}
|
|
1402
|
+
else {
|
|
1403
|
+
return handleError(res, {
|
|
1404
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
1405
|
+
code: ReactKitErrorCode$1.MissingParameter,
|
|
1406
|
+
status: 422,
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
else if (!Number.isNaN(Number.parseFloat(String(value)))) {
|
|
1411
|
+
// Value is a number
|
|
1412
|
+
output[name] = Number.parseFloat(String(value));
|
|
1413
|
+
}
|
|
1414
|
+
else {
|
|
1415
|
+
// Issue!
|
|
1416
|
+
return handleError(res, {
|
|
1417
|
+
message: `Request data was malformed: ${name} was not a valid float.`,
|
|
1418
|
+
code: ReactKitErrorCode$1.InvalidParameter,
|
|
1419
|
+
status: 422,
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
else if (type === ParamType$1.Int || type === ParamType$1.IntOptional) {
|
|
1424
|
+
// Int
|
|
1425
|
+
// Handle case where value doesn't exist
|
|
1426
|
+
if (value === undefined) {
|
|
1427
|
+
if (type === ParamType$1.IntOptional) {
|
|
1428
|
+
output[name] = undefined;
|
|
1429
|
+
}
|
|
1430
|
+
else {
|
|
1431
|
+
return handleError(res, {
|
|
1432
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
1433
|
+
code: ReactKitErrorCode$1.MissingParameter,
|
|
1434
|
+
status: 422,
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
|
|
1439
|
+
// Value is a number
|
|
1440
|
+
output[name] = Number.parseInt(String(value), 10);
|
|
1441
|
+
}
|
|
1442
|
+
else {
|
|
1443
|
+
// Issue!
|
|
1444
|
+
return handleError(res, {
|
|
1445
|
+
message: `Request data was malformed: ${name} was not a valid int.`,
|
|
1446
|
+
code: ReactKitErrorCode$1.InvalidParameter,
|
|
1447
|
+
status: 422,
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
else if (type === ParamType$1.JSON || type === ParamType$1.JSONOptional) {
|
|
1452
|
+
// Stringified JSON
|
|
1453
|
+
// Handle case where value doesn't exist
|
|
1454
|
+
if (value === undefined) {
|
|
1455
|
+
if (type === ParamType$1.JSONOptional) {
|
|
1456
|
+
output[name] = undefined;
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
return handleError(res, {
|
|
1460
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
1461
|
+
code: ReactKitErrorCode$1.MissingParameter,
|
|
1462
|
+
status: 422,
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
else {
|
|
1467
|
+
// Value exists
|
|
1468
|
+
// Parse
|
|
1469
|
+
try {
|
|
1470
|
+
output[name] = JSON.parse(String(value));
|
|
1471
|
+
}
|
|
1472
|
+
catch (err) {
|
|
1473
|
+
return handleError(res, {
|
|
1474
|
+
message: `Request data was malformed: ${name} was not a valid JSON payload.`,
|
|
1475
|
+
code: ReactKitErrorCode$1.InvalidParameter,
|
|
1476
|
+
status: 422,
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
else if (type === ParamType$1.String || type === ParamType$1.StringOptional) {
|
|
1482
|
+
// String
|
|
1483
|
+
// Handle case where value doesn't exist
|
|
1484
|
+
if (value === undefined) {
|
|
1485
|
+
if (type === ParamType$1.StringOptional) {
|
|
1486
|
+
output[name] = undefined;
|
|
1487
|
+
}
|
|
1488
|
+
else {
|
|
1489
|
+
return handleError(res, {
|
|
1490
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
1491
|
+
code: ReactKitErrorCode$1.MissingParameter,
|
|
1492
|
+
status: 422,
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
else {
|
|
1497
|
+
// Value exists
|
|
1498
|
+
// Leave as is
|
|
1499
|
+
output[name] = value;
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
else {
|
|
1503
|
+
// No valid data type
|
|
1504
|
+
return handleError(res, {
|
|
1505
|
+
message: `An internal error occurred: we could not determine the type of ${name}.`,
|
|
1506
|
+
code: ReactKitErrorCode$1.InvalidParameter,
|
|
1507
|
+
status: 422,
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
/*----------------------------------------*/
|
|
1512
|
+
/* Launch Info */
|
|
1513
|
+
/*----------------------------------------*/
|
|
1514
|
+
// Get launch info
|
|
1515
|
+
const { launched, launchInfo } = cacclGetLaunchInfo(req);
|
|
1516
|
+
if (!launched || !launchInfo) {
|
|
1517
|
+
return handleError(res, {
|
|
1518
|
+
message: 'Your session has expired. Please refresh the page and try again.',
|
|
1519
|
+
code: ReactKitErrorCode$1.SessionExpired,
|
|
1520
|
+
status: 440,
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
// Error if user info cannot be found
|
|
1524
|
+
if (!launchInfo.userId
|
|
1525
|
+
|| !launchInfo.userFirstName
|
|
1526
|
+
|| !launchInfo.userLastName
|
|
1527
|
+
|| (launchInfo.notInCourse
|
|
1528
|
+
&& !launchInfo.isAdmin)
|
|
1529
|
+
|| (!launchInfo.isTTM
|
|
1530
|
+
&& !launchInfo.isLearner
|
|
1531
|
+
&& !launchInfo.isAdmin)) {
|
|
1532
|
+
return handleError(res, {
|
|
1533
|
+
message: 'Your session was invalid. Please refresh the page and try again.',
|
|
1534
|
+
code: ReactKitErrorCode$1.SessionExpired,
|
|
1535
|
+
status: 440,
|
|
1536
|
+
});
|
|
1537
|
+
}
|
|
1538
|
+
// Add launch info to output
|
|
1539
|
+
output.userId = launchInfo.userId;
|
|
1540
|
+
output.userFirstName = launchInfo.userFirstName;
|
|
1541
|
+
output.userLastName = launchInfo.userLastName;
|
|
1542
|
+
output.isLearner = !!launchInfo.isLearner;
|
|
1543
|
+
output.isTTM = !!launchInfo.isTTM;
|
|
1544
|
+
output.isAdmin = !!launchInfo.isAdmin;
|
|
1545
|
+
output.isWatchingInPrivate = !!(req.session.isWatchingInPrivate);
|
|
1546
|
+
/*----------------------------------------*/
|
|
1547
|
+
/* Require Course Consistency */
|
|
1548
|
+
/*----------------------------------------*/
|
|
1549
|
+
// Make sure the user actually launched from the appropriate course
|
|
1550
|
+
if (output.courseId
|
|
1551
|
+
&& launchInfo.courseId
|
|
1552
|
+
&& output.courseId !== launchInfo.courseId
|
|
1553
|
+
&& !output.isTTM
|
|
1554
|
+
&& !output.isAdmin) {
|
|
1555
|
+
// Course of interest is not the launch course
|
|
1556
|
+
return handleError(res, {
|
|
1557
|
+
message: 'You switched sessions by opening Immersive Classroom in another tab. Please refresh the page and try again.',
|
|
1558
|
+
code: ReactKitErrorCode$1.WrongCourse,
|
|
1559
|
+
status: 401,
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
/*------------------------------------------------------------------------*/
|
|
1563
|
+
/* Call handler */
|
|
1564
|
+
/*------------------------------------------------------------------------*/
|
|
1565
|
+
opts.handler({
|
|
1566
|
+
params: output,
|
|
1567
|
+
handleSuccess: (body) => {
|
|
1568
|
+
return handleSuccess(res, body);
|
|
1569
|
+
},
|
|
1570
|
+
handleError: (error) => {
|
|
1571
|
+
return handleError(res, error);
|
|
1572
|
+
},
|
|
1573
|
+
req,
|
|
1574
|
+
res,
|
|
1575
|
+
});
|
|
1576
|
+
});
|
|
1577
|
+
};
|
|
1578
|
+
|
|
1579
|
+
export { AppWrapper, ButtonInputGroup, CheckboxButton, ErrorBox, ErrorWithCode, LoadingSpinner, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, ParamType$1 as ParamType, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, TabBox, Variant$1 as Variant, abbreviate, alert$1 as alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genRouteHandler, handleError, handleSuccess, initServer, padDecimalZeros, padZerosLeft, roundToNumDecimals, showFatalError, sum, visitServerEndpoint, waitMs };
|
|
890
1580
|
//# sourceMappingURL=index.js.map
|