dce-reactkit 3.0.0-beta.9 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -546
- package/dist/cjs/index.js +945 -83
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/AppWrapper.d.ts +25 -0
- 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 +22 -0
- package/dist/cjs/types/components/TabBox.d.ts +1 -0
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +32 -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 +13 -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 +934 -84
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/AppWrapper.d.ts +25 -0
- 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 +22 -0
- package/dist/esm/types/components/TabBox.d.ts +1 -0
- package/dist/esm/types/helpers/genRouteHandler.d.ts +32 -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 +13 -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 +231 -10
- package/package.json +3 -1
- package/rollup.config.js +0 -1
- package/src/components/AppWrapper.tsx +73 -11
- 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 +184 -23
- package/src/components/RadioButton.tsx +102 -0
- package/src/components/SimpleDateChooser.tsx +222 -0
- package/src/components/TabBox.tsx +65 -58
- package/src/helpers/genRouteHandler.ts +329 -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 +27 -0
- package/src/server/initServer.ts +53 -0
- package/src/types/ParamType.ts +18 -0
- package/src/types/ReactKitErrorCode.tsx +3 -1
package/dist/cjs/index.js
CHANGED
|
@@ -5,6 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
5
5
|
var React = require('react');
|
|
6
6
|
var reactFontawesome = require('@fortawesome/react-fontawesome');
|
|
7
7
|
var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons');
|
|
8
|
+
var freeRegularSvgIcons = require('@fortawesome/free-regular-svg-icons');
|
|
8
9
|
|
|
9
10
|
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
10
11
|
|
|
@@ -35,7 +36,7 @@ function __awaiter(thisArg, _arguments, P, generator) {
|
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
38
|
|
|
38
|
-
// Highest error code =
|
|
39
|
+
// Highest error code = DRK8
|
|
39
40
|
/**
|
|
40
41
|
* List of error codes built into the react kit
|
|
41
42
|
* @author Gabe Abrams
|
|
@@ -48,6 +49,8 @@ var ReactKitErrorCode;
|
|
|
48
49
|
ReactKitErrorCode["MissingParameter"] = "DRK4";
|
|
49
50
|
ReactKitErrorCode["InvalidParameter"] = "DRK5";
|
|
50
51
|
ReactKitErrorCode["WrongCourse"] = "DRK6";
|
|
52
|
+
ReactKitErrorCode["NoCACCLSendRequestFunction"] = "DRK7";
|
|
53
|
+
ReactKitErrorCode["NoCACCLGetLaunchInfoFunction"] = "DRK8";
|
|
51
54
|
})(ReactKitErrorCode || (ReactKitErrorCode = {}));
|
|
52
55
|
var ReactKitErrorCode$1 = ReactKitErrorCode;
|
|
53
56
|
|
|
@@ -79,9 +82,9 @@ const ErrorBox = (props) => {
|
|
|
79
82
|
' ',
|
|
80
83
|
React__default["default"].createElement("span", { style: {
|
|
81
84
|
backgroundColor: 'white',
|
|
82
|
-
borderRadius: '
|
|
83
|
-
paddingLeft: '
|
|
84
|
-
paddingRight: '
|
|
85
|
+
borderRadius: '0.3rem',
|
|
86
|
+
paddingLeft: '0.2rem',
|
|
87
|
+
paddingRight: '0.2rem',
|
|
85
88
|
color: '#DC4150',
|
|
86
89
|
fontVariant: 'small-caps',
|
|
87
90
|
fontSize: '80%',
|
|
@@ -93,7 +96,7 @@ const ErrorBox = (props) => {
|
|
|
93
96
|
}
|
|
94
97
|
// Main UI
|
|
95
98
|
return (React__default["default"].createElement("div", { className: "alert alert-danger text-center", style: {
|
|
96
|
-
maxWidth: '
|
|
99
|
+
maxWidth: '40rem',
|
|
97
100
|
margin: 'auto',
|
|
98
101
|
} },
|
|
99
102
|
React__default["default"].createElement("h4", { className: "mb-1" },
|
|
@@ -192,9 +195,7 @@ var ModalSize$1 = ModalSize;
|
|
|
192
195
|
/* Constants */
|
|
193
196
|
/*------------------------------------------------------------------------*/
|
|
194
197
|
// Constants
|
|
195
|
-
const MS_TO_ANIMATE =
|
|
196
|
-
const MS_ANIMATE_IN_DELAY = 10;
|
|
197
|
-
// Time to wait before animating in (must be >0 or animation won't trigger)
|
|
198
|
+
const MS_TO_ANIMATE = 200; // Animation duration
|
|
198
199
|
// Modal type to list of buttons
|
|
199
200
|
const modalTypeToModalButtonTypes = {
|
|
200
201
|
[ModalType$1.Okay]: [
|
|
@@ -274,6 +275,114 @@ const ModalButtonTypeToLabelAndVariant = {
|
|
|
274
275
|
},
|
|
275
276
|
};
|
|
276
277
|
/*------------------------------------------------------------------------*/
|
|
278
|
+
/* Style */
|
|
279
|
+
/*------------------------------------------------------------------------*/
|
|
280
|
+
const style$2 = `
|
|
281
|
+
.Modal-backdrop {
|
|
282
|
+
position: fixed;
|
|
283
|
+
top: 0;
|
|
284
|
+
left: 0;
|
|
285
|
+
width: 100vw;
|
|
286
|
+
height: 100vw;
|
|
287
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
.Modal-fading-in {
|
|
291
|
+
animation-name: Modal-fading-in;
|
|
292
|
+
animation-duration: ${Math.floor(MS_TO_ANIMATE * 2)}ms;
|
|
293
|
+
animation-iteration-count: 1;
|
|
294
|
+
animation-fill-mode: both;
|
|
295
|
+
animation-timing-function: ease-out;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
@keyframes Modal-fading-in {
|
|
299
|
+
0% {
|
|
300
|
+
opacity: 0;
|
|
301
|
+
}
|
|
302
|
+
100% {
|
|
303
|
+
opacity: 1;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.Modal-fading-out {
|
|
308
|
+
animation-name: Modal-fading-out;
|
|
309
|
+
animation-duration: ${MS_TO_ANIMATE}ms;
|
|
310
|
+
animation-iteration-count: 1;
|
|
311
|
+
animation-fill-mode: both;
|
|
312
|
+
animation-timing-function: ease-in;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
@keyframes Modal-fading-out {
|
|
316
|
+
0% {
|
|
317
|
+
opacity: 1;
|
|
318
|
+
}
|
|
319
|
+
100% {
|
|
320
|
+
opacity: 0;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.Modal-animating-in {
|
|
325
|
+
animation-name: Modal-animating-in;
|
|
326
|
+
animation-duration: ${MS_TO_ANIMATE}ms;
|
|
327
|
+
animation-iteration-count: 1;
|
|
328
|
+
animation-fill-mode: both;
|
|
329
|
+
animation-timing-function: ease-out;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@keyframes Modal-animating-in {
|
|
333
|
+
0% {
|
|
334
|
+
transform: scale(1.05) translate(0, -1.5rem);
|
|
335
|
+
opacity: 0;
|
|
336
|
+
}
|
|
337
|
+
100% {
|
|
338
|
+
transform: scale(1) translate(0, 0);
|
|
339
|
+
opacity: 1;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.Modal-animating-pop {
|
|
344
|
+
animation-name: Modal-animating-pop;
|
|
345
|
+
animation-duration: ${MS_TO_ANIMATE}ms;
|
|
346
|
+
animation-iteration-count: 1;
|
|
347
|
+
animation-fill-mode: both;
|
|
348
|
+
animation-timing-function: ease-in-out;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
@keyframes Modal-animating-pop {
|
|
352
|
+
0% {
|
|
353
|
+
transform: scale(1);
|
|
354
|
+
opacity: 1;
|
|
355
|
+
}
|
|
356
|
+
50% {
|
|
357
|
+
transform: scale(1.05);
|
|
358
|
+
opacity: 0.9;
|
|
359
|
+
}
|
|
360
|
+
100% {
|
|
361
|
+
transform: scale(1);
|
|
362
|
+
opacity: 1;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.Modal-animating-out {
|
|
367
|
+
animation-name: Modal-animating-out;
|
|
368
|
+
animation-duration: ${MS_TO_ANIMATE}ms;
|
|
369
|
+
animation-iteration-count: 1;
|
|
370
|
+
animation-fill-mode: both;
|
|
371
|
+
animation-timing-function: ease-in;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
@keyframes Modal-animating-out {
|
|
375
|
+
0% {
|
|
376
|
+
transform: scale(1) translate(0, 0);
|
|
377
|
+
opacity: 1;
|
|
378
|
+
}
|
|
379
|
+
100% {
|
|
380
|
+
transform: scale(1.05) translate(0, -1.5rem);
|
|
381
|
+
opacity: 0;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
`;
|
|
385
|
+
/*------------------------------------------------------------------------*/
|
|
277
386
|
/* Component */
|
|
278
387
|
/*------------------------------------------------------------------------*/
|
|
279
388
|
const Modal = (props) => {
|
|
@@ -284,10 +393,15 @@ const Modal = (props) => {
|
|
|
284
393
|
/* -------------- Props ------------- */
|
|
285
394
|
const { type = ModalType$1.NoButtons, size = ModalSize$1.Large, title, children, onClose, dontAllowBackdropExit, onTopOfOtherModals, } = props;
|
|
286
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);
|
|
287
399
|
// If true, the modal is shown
|
|
288
400
|
const [visible, setVisible] = React.useState(false);
|
|
289
|
-
// True if
|
|
290
|
-
const [animatingIn, setAnimatingIn] = React.useState(
|
|
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);
|
|
291
405
|
/*------------------------------------------------------------------------*/
|
|
292
406
|
/* Lifecycle Functions */
|
|
293
407
|
/*------------------------------------------------------------------------*/
|
|
@@ -297,12 +411,10 @@ const Modal = (props) => {
|
|
|
297
411
|
*/
|
|
298
412
|
React.useEffect(() => {
|
|
299
413
|
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
300
|
-
// Start the component animating in
|
|
301
|
-
yield waitMs(MS_ANIMATE_IN_DELAY);
|
|
302
|
-
setAnimatingIn(true);
|
|
303
414
|
// Wait and then set visible to true
|
|
304
415
|
yield waitMs(MS_TO_ANIMATE);
|
|
305
416
|
setVisible(true);
|
|
417
|
+
setAnimatingIn(false);
|
|
306
418
|
}))();
|
|
307
419
|
}, []);
|
|
308
420
|
/*------------------------------------------------------------------------*/
|
|
@@ -329,13 +441,19 @@ const Modal = (props) => {
|
|
|
329
441
|
}
|
|
330
442
|
// Update the state
|
|
331
443
|
setVisible(false);
|
|
444
|
+
setAnimatingOut(true);
|
|
332
445
|
// Call the handler after the modal has animated out
|
|
333
446
|
yield waitMs(MS_TO_ANIMATE);
|
|
334
447
|
onClose(ModalButtonType);
|
|
448
|
+
setGone(true);
|
|
335
449
|
});
|
|
336
450
|
/*------------------------------------------------------------------------*/
|
|
337
451
|
/* Render */
|
|
338
452
|
/*------------------------------------------------------------------------*/
|
|
453
|
+
// Return nothing if gone
|
|
454
|
+
if (gone) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
339
457
|
/*----------------------------------------*/
|
|
340
458
|
/* Footer */
|
|
341
459
|
/*----------------------------------------*/
|
|
@@ -357,7 +475,7 @@ const Modal = (props) => {
|
|
|
357
475
|
// Check if this button is last
|
|
358
476
|
const last = (i === ModalButtonTypes.length - 1);
|
|
359
477
|
// Create the button
|
|
360
|
-
return (React__default["default"].createElement("button", { type: "button", className: `Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : '
|
|
478
|
+
return (React__default["default"].createElement("button", { key: ModalButtonType, type: "button", className: `Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'me-1'}`, onClick: () => {
|
|
361
479
|
handleClose(ModalButtonType);
|
|
362
480
|
} }, label));
|
|
363
481
|
});
|
|
@@ -365,22 +483,62 @@ const Modal = (props) => {
|
|
|
365
483
|
const footer = ((buttons && buttons.length)
|
|
366
484
|
? (React__default["default"].createElement("div", null, buttons))
|
|
367
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
|
+
}
|
|
368
500
|
// Render the modal
|
|
369
|
-
return (React__default["default"].createElement("div", { className: `modal
|
|
501
|
+
return (React__default["default"].createElement("div", { className: `modal show modal-dialog-scrollable modal-dialog-centered modal-${size}`, tabIndex: -1, style: {
|
|
370
502
|
zIndex: (onTopOfOtherModals
|
|
371
|
-
?
|
|
503
|
+
? 5000000001
|
|
372
504
|
: 5000000000),
|
|
373
505
|
display: 'block',
|
|
506
|
+
margin: 'auto',
|
|
507
|
+
left: 0,
|
|
508
|
+
right: 0,
|
|
374
509
|
} },
|
|
375
|
-
React__default["default"].createElement("
|
|
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
|
+
} },
|
|
376
531
|
React__default["default"].createElement("div", { className: "modal-content" },
|
|
377
532
|
React__default["default"].createElement("div", { className: "modal-header" },
|
|
378
|
-
React__default["default"].createElement("h5", { className: "modal-title"
|
|
379
|
-
|
|
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
|
|
380
538
|
handleClose(ModalButtonType$1.Cancel);
|
|
381
539
|
} }))),
|
|
382
540
|
children && (React__default["default"].createElement("div", { className: "modal-body" }, children)),
|
|
383
|
-
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))))));
|
|
384
542
|
};
|
|
385
543
|
|
|
386
544
|
/**
|
|
@@ -404,6 +562,24 @@ class ErrorWithCode extends Error {
|
|
|
404
562
|
/* Static Helpers */
|
|
405
563
|
/*------------------------------------------------------------------------*/
|
|
406
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
|
+
/*----------------------------------------*/
|
|
407
583
|
/* Alert */
|
|
408
584
|
/*----------------------------------------*/
|
|
409
585
|
// Stored copies of setters
|
|
@@ -415,7 +591,7 @@ let onAlertClosed;
|
|
|
415
591
|
* @param title the title text to display at the top of the alert
|
|
416
592
|
* @param text the text to display in the alert
|
|
417
593
|
*/
|
|
418
|
-
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* () {
|
|
419
595
|
// Fallback if alert not available
|
|
420
596
|
if (!setAlertInfo) {
|
|
421
597
|
window.alert(`${title}\n\n${text}`);
|
|
@@ -489,7 +665,7 @@ const showFatalError = (error, errorTitle = 'An Error Occurred') => {
|
|
|
489
665
|
: String((_b = error.code) !== null && _b !== void 0 ? _b : ReactKitErrorCode$1.NoCode));
|
|
490
666
|
// Handle case where app hasn't loaded
|
|
491
667
|
if (!setFatalErrorMessage || !setFatalErrorCode) {
|
|
492
|
-
alert(errorTitle, `${message} (code: ${code}). Please contact support.`);
|
|
668
|
+
alert$1(errorTitle, `${message} (code: ${code}). Please contact support.`);
|
|
493
669
|
return undefined;
|
|
494
670
|
}
|
|
495
671
|
// Use setters
|
|
@@ -506,7 +682,9 @@ const AppWrapper = (props) => {
|
|
|
506
682
|
/* Setup */
|
|
507
683
|
/*------------------------------------------------------------------------*/
|
|
508
684
|
/* -------------- Props ------------- */
|
|
509
|
-
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;
|
|
510
688
|
/* -------------- State ------------- */
|
|
511
689
|
// Fatal error
|
|
512
690
|
const [fatalErrorMessage, setFatalErrorMessageInner,] = React.useState();
|
|
@@ -516,10 +694,10 @@ const AppWrapper = (props) => {
|
|
|
516
694
|
const [fatalErrorTitle, setFatalErrorTitleInner,] = React.useState();
|
|
517
695
|
setFatalErrorTitle = setFatalErrorTitleInner;
|
|
518
696
|
// Alert
|
|
519
|
-
const [alertInfo, setAlertInfoInner,] = React.useState();
|
|
697
|
+
const [alertInfo, setAlertInfoInner,] = React.useState(undefined);
|
|
520
698
|
setAlertInfo = setAlertInfoInner;
|
|
521
699
|
// Confirm
|
|
522
|
-
const [confirmInfo, setConfirmInfoInner,] = React.useState();
|
|
700
|
+
const [confirmInfo, setConfirmInfoInner,] = React.useState(undefined);
|
|
523
701
|
setConfirmInfo = setConfirmInfoInner;
|
|
524
702
|
// Session expired
|
|
525
703
|
const [sessionHasExpired, setSessionHasExpiredInner,] = React.useState(false);
|
|
@@ -533,8 +711,9 @@ const AppWrapper = (props) => {
|
|
|
533
711
|
let modal;
|
|
534
712
|
/* -------------- Alert ------------- */
|
|
535
713
|
if (alertInfo) {
|
|
536
|
-
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: () => {
|
|
537
715
|
// Alert closed
|
|
716
|
+
setAlertInfo(undefined);
|
|
538
717
|
if (onAlertClosed) {
|
|
539
718
|
onAlertClosed();
|
|
540
719
|
}
|
|
@@ -542,11 +721,12 @@ const AppWrapper = (props) => {
|
|
|
542
721
|
}
|
|
543
722
|
/* ------------- Confirm ------------ */
|
|
544
723
|
if (confirmInfo) {
|
|
545
|
-
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);
|
|
546
726
|
if (onConfirmClosed) {
|
|
547
727
|
onConfirmClosed(buttonType === ModalButtonType$1.Okay);
|
|
548
728
|
}
|
|
549
|
-
}, dontAllowBackdropExit: true }));
|
|
729
|
+
}, dontAllowBackdropExit: true }, confirmInfo.text));
|
|
550
730
|
}
|
|
551
731
|
/*----------------------------------------*/
|
|
552
732
|
/* Views */
|
|
@@ -598,10 +778,10 @@ const style$1 = `
|
|
|
598
778
|
.LoadingSpinner-blip-2,
|
|
599
779
|
.LoadingSpinner-blip-3,
|
|
600
780
|
.LoadingSpinner-blip-4 {
|
|
601
|
-
font-size:
|
|
781
|
+
font-size: 1.8rem;
|
|
602
782
|
opacity: 0.6;
|
|
603
|
-
margin-top:
|
|
604
|
-
margin-bottom:
|
|
783
|
+
margin-top: 1rem;
|
|
784
|
+
margin-bottom: 1rem;
|
|
605
785
|
}
|
|
606
786
|
|
|
607
787
|
/* First Blip */
|
|
@@ -668,63 +848,67 @@ const LoadingSpinner = () => {
|
|
|
668
848
|
/* Style */
|
|
669
849
|
/*------------------------------------------------------------------------*/
|
|
670
850
|
const style = `
|
|
671
|
-
/* Tab Box */
|
|
672
|
-
.TabBox-box {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
/* Very Light Gray Border */
|
|
682
|
-
background: #fdfdfd;
|
|
683
|
-
|
|
684
|
-
/* Align Contents on Left */
|
|
685
|
-
text-align: left;
|
|
686
|
-
}
|
|
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;
|
|
687
860
|
|
|
688
|
-
/*
|
|
689
|
-
|
|
690
|
-
/* Place on Left */
|
|
691
|
-
position: relative;
|
|
692
|
-
left: 0;
|
|
693
|
-
text-align: left;
|
|
694
|
-
}
|
|
861
|
+
/* Very Light Gray Border */
|
|
862
|
+
background: #fdfdfd;
|
|
695
863
|
|
|
696
|
-
/*
|
|
697
|
-
|
|
698
|
-
/* Place so it Barely Overlaps the Box Border */
|
|
699
|
-
display: inline-block;
|
|
700
|
-
position: relative;
|
|
701
|
-
top: 2px; /* Gives Illusion that Border Doesn't Exist Below Tab */
|
|
864
|
+
/* Align Contents on Left */
|
|
865
|
+
text-align: left;
|
|
702
866
|
|
|
703
|
-
|
|
704
|
-
|
|
867
|
+
/* Add Text Padding */
|
|
868
|
+
padding-left: 0.3rem;
|
|
869
|
+
padding-right: 0.3rem;
|
|
870
|
+
}
|
|
705
871
|
|
|
706
|
-
/*
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
872
|
+
/* Container for Title */
|
|
873
|
+
.TabBox-title-container {
|
|
874
|
+
/* Place on Left */
|
|
875
|
+
position: relative;
|
|
876
|
+
left: 0;
|
|
877
|
+
text-align: left;
|
|
878
|
+
}
|
|
710
879
|
|
|
711
|
-
/*
|
|
712
|
-
|
|
713
|
-
|
|
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 */
|
|
714
886
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
padding-right: 12px;
|
|
887
|
+
/* Title-sized Font */
|
|
888
|
+
font-size: 1.5rem;
|
|
718
889
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
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;
|
|
722
894
|
|
|
723
|
-
/*
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
895
|
+
/* Round the Top Corners */
|
|
896
|
+
border-top-left-radius: 0.3rem;
|
|
897
|
+
border-top-right-radius: 0.3rem;
|
|
898
|
+
|
|
899
|
+
/* Add Text Padding */
|
|
900
|
+
padding-left: 0.5rem;
|
|
901
|
+
padding-right: 0.5rem;
|
|
902
|
+
|
|
903
|
+
/* Match Background Color of Box */
|
|
904
|
+
background: #fdfdfd;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/* Make the TabBox's Children Appear Above Title if Overlap Occurs */
|
|
908
|
+
.TabBox-children {
|
|
909
|
+
position: relative;
|
|
910
|
+
z-index: 1;
|
|
911
|
+
}
|
|
728
912
|
`;
|
|
729
913
|
/*------------------------------------------------------------------------*/
|
|
730
914
|
/* Component */
|
|
@@ -734,7 +918,7 @@ const TabBox = (props) => {
|
|
|
734
918
|
/* Setup */
|
|
735
919
|
/*------------------------------------------------------------------------*/
|
|
736
920
|
/* -------------- Props ------------- */
|
|
737
|
-
const { title, children, } = props;
|
|
921
|
+
const { title, children, noBottomPadding, } = props;
|
|
738
922
|
/*------------------------------------------------------------------------*/
|
|
739
923
|
/* Render */
|
|
740
924
|
/*------------------------------------------------------------------------*/
|
|
@@ -742,7 +926,7 @@ const TabBox = (props) => {
|
|
|
742
926
|
/* Main UI */
|
|
743
927
|
/*----------------------------------------*/
|
|
744
928
|
// Full UI
|
|
745
|
-
return (React__default["default"].createElement("div",
|
|
929
|
+
return (React__default["default"].createElement("div", { className: `TabBox-container ${noBottomPadding ? '' : 'mb-2'}` },
|
|
746
930
|
React__default["default"].createElement("style", null, style),
|
|
747
931
|
React__default["default"].createElement("div", { className: "TabBox-title-container" },
|
|
748
932
|
React__default["default"].createElement("div", { className: "TabBox-title" }, title)),
|
|
@@ -750,6 +934,252 @@ const TabBox = (props) => {
|
|
|
750
934
|
React__default["default"].createElement("div", { className: "TabBox-children" }, children))));
|
|
751
935
|
};
|
|
752
936
|
|
|
937
|
+
/**
|
|
938
|
+
* A radio selection button
|
|
939
|
+
* @author Gabe Abrams
|
|
940
|
+
*/
|
|
941
|
+
/*------------------------------------------------------------------------*/
|
|
942
|
+
/* Component */
|
|
943
|
+
/*------------------------------------------------------------------------*/
|
|
944
|
+
const RadioButton = (props) => {
|
|
945
|
+
/*------------------------------------------------------------------------*/
|
|
946
|
+
/* Setup */
|
|
947
|
+
/*------------------------------------------------------------------------*/
|
|
948
|
+
/* -------------- Props ------------- */
|
|
949
|
+
const { text, onSelected, ariaLabel, title, selected, id, noMarginOnRight, selectedVariant = Variant$1.Secondary, unselectedVariant = Variant$1.Light, small, } = props;
|
|
950
|
+
/*------------------------------------------------------------------------*/
|
|
951
|
+
/* Render */
|
|
952
|
+
/*------------------------------------------------------------------------*/
|
|
953
|
+
/*----------------------------------------*/
|
|
954
|
+
/* Main UI */
|
|
955
|
+
/*----------------------------------------*/
|
|
956
|
+
return (React__default["default"].createElement("button", { type: "button", id: id, title: title, className: `btn btn-${selected ? selectedVariant : unselectedVariant}${selected ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`, "aria-label": `${ariaLabel}${selected ? ': currently selected' : ''}`, onClick: () => {
|
|
957
|
+
if (!selected) {
|
|
958
|
+
onSelected();
|
|
959
|
+
}
|
|
960
|
+
} },
|
|
961
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: selected ? freeSolidSvgIcons.faDotCircle : freeRegularSvgIcons.faCircle, className: "me-1" }),
|
|
962
|
+
text));
|
|
963
|
+
};
|
|
964
|
+
|
|
965
|
+
/**
|
|
966
|
+
* A checkbox button
|
|
967
|
+
* @author Gabe Abrams
|
|
968
|
+
*/
|
|
969
|
+
/*------------------------------------------------------------------------*/
|
|
970
|
+
/* Component */
|
|
971
|
+
/*------------------------------------------------------------------------*/
|
|
972
|
+
const CheckboxButton = (props) => {
|
|
973
|
+
/*------------------------------------------------------------------------*/
|
|
974
|
+
/* Setup */
|
|
975
|
+
/*------------------------------------------------------------------------*/
|
|
976
|
+
/* -------------- Props ------------- */
|
|
977
|
+
const { text, onChanged, ariaLabel, title, checked, id, noMarginOnRight, checkedVariant = Variant$1.Secondary, uncheckedVariant = Variant$1.Light, small, } = props;
|
|
978
|
+
/*------------------------------------------------------------------------*/
|
|
979
|
+
/* Render */
|
|
980
|
+
/*------------------------------------------------------------------------*/
|
|
981
|
+
/*----------------------------------------*/
|
|
982
|
+
/* Main UI */
|
|
983
|
+
/*----------------------------------------*/
|
|
984
|
+
return (React__default["default"].createElement("button", { type: "button", id: id, title: title, className: `btn btn-${checked ? checkedVariant : uncheckedVariant}${checked ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`, "aria-label": `${ariaLabel}${checked ? ': currently checked' : ''}`, onClick: () => {
|
|
985
|
+
onChanged(!checked);
|
|
986
|
+
} },
|
|
987
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: checked ? freeSolidSvgIcons.faCheckSquare : freeRegularSvgIcons.faSquare, className: "me-1" }),
|
|
988
|
+
text));
|
|
989
|
+
};
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* Input group with a title and space for buttons
|
|
993
|
+
* @author Gabe Abrams
|
|
994
|
+
*/
|
|
995
|
+
/*------------------------------------------------------------------------*/
|
|
996
|
+
/* Component */
|
|
997
|
+
/*------------------------------------------------------------------------*/
|
|
998
|
+
const ButtonInputGroup = (props) => {
|
|
999
|
+
/*------------------------------------------------------------------------*/
|
|
1000
|
+
/* Setup */
|
|
1001
|
+
/*------------------------------------------------------------------------*/
|
|
1002
|
+
/* -------------- Props ------------- */
|
|
1003
|
+
// Destructure all props
|
|
1004
|
+
const { label, minLabelWidth, children, } = props;
|
|
1005
|
+
/*------------------------------------------------------------------------*/
|
|
1006
|
+
/* Render */
|
|
1007
|
+
/*------------------------------------------------------------------------*/
|
|
1008
|
+
/*----------------------------------------*/
|
|
1009
|
+
/* Main UI */
|
|
1010
|
+
/*----------------------------------------*/
|
|
1011
|
+
return (React__default["default"].createElement("div", { className: "input-group" },
|
|
1012
|
+
React__default["default"].createElement("div", { className: "input-group-prepend d-flex w-100" },
|
|
1013
|
+
React__default["default"].createElement("span", { className: "input-group-text", style: {
|
|
1014
|
+
minWidth: (minLabelWidth !== null && minLabelWidth !== void 0 ? minLabelWidth : undefined),
|
|
1015
|
+
borderTopRightRadius: 0,
|
|
1016
|
+
borderBottomRightRadius: 0,
|
|
1017
|
+
} }, label),
|
|
1018
|
+
React__default["default"].createElement("span", { className: "input-group-text flex-grow-1 rounded-right", style: {
|
|
1019
|
+
backgroundColor: 'white',
|
|
1020
|
+
borderTopLeftRadius: 0,
|
|
1021
|
+
borderBottomLeftRadius: 0,
|
|
1022
|
+
borderLeftWidth: 0,
|
|
1023
|
+
} }, children))));
|
|
1024
|
+
};
|
|
1025
|
+
|
|
1026
|
+
const ORDINALS = ['th', 'st', 'nd', 'rd'];
|
|
1027
|
+
/**
|
|
1028
|
+
* Get a number's ordinal
|
|
1029
|
+
* @author Gabe Abrams
|
|
1030
|
+
* @param num the number being analyzed
|
|
1031
|
+
* @returns ordinal
|
|
1032
|
+
*/
|
|
1033
|
+
const getOrdinal = (num) => {
|
|
1034
|
+
var _a, _b;
|
|
1035
|
+
return ((_b = (_a = ORDINALS[(num - 20) % 10]) !== null && _a !== void 0 ? _a : ORDINALS[num]) !== null && _b !== void 0 ? _b : ORDINALS[0]);
|
|
1036
|
+
};
|
|
1037
|
+
|
|
1038
|
+
/**
|
|
1039
|
+
* Get current time info in US Boston Eastern Time, independent of machine
|
|
1040
|
+
* timezone
|
|
1041
|
+
* @author Gabe Abrams
|
|
1042
|
+
* @param {Date} [date=now] the date to get info on
|
|
1043
|
+
* @returns object with timestamp (ms since epoch) and numbers
|
|
1044
|
+
* corresponding to ET time values for year, month, day, hour, minute
|
|
1045
|
+
*/
|
|
1046
|
+
const getTimeInfoInET = (date) => {
|
|
1047
|
+
// Create a time string
|
|
1048
|
+
const d = (date || new Date());
|
|
1049
|
+
const str = d.toLocaleString('en-US', // Using US encoding (it's the only one installed on containers)
|
|
1050
|
+
{ timeZone: 'America/New_York' } // Force EST timezone
|
|
1051
|
+
);
|
|
1052
|
+
// Parse the string for the date/time info
|
|
1053
|
+
const [dateStr, timeStr] = str.split(', '); // Format: MM/DD/YYYY, HH:MM:SS AM
|
|
1054
|
+
const [monthStr, dayStr, yearStr] = dateStr.split('/'); // Format: MM/DD/YYYY
|
|
1055
|
+
const [hourStr, minStr, ending] = timeStr.split(':'); // Format: HH:MM:SS AM
|
|
1056
|
+
// Create all time numbers
|
|
1057
|
+
const timestamp = d.getTime();
|
|
1058
|
+
const year = Number.parseInt(yearStr, 10);
|
|
1059
|
+
const month = Number.parseInt(monthStr, 10);
|
|
1060
|
+
const day = Number.parseInt(dayStr, 10);
|
|
1061
|
+
const minute = Number.parseInt(minStr, 10);
|
|
1062
|
+
let hour = Number.parseInt(hourStr, 10);
|
|
1063
|
+
// Convert from am/pm to 24hr
|
|
1064
|
+
const isAM = ending.toLowerCase().includes('am');
|
|
1065
|
+
const isPM = !isAM;
|
|
1066
|
+
if (isPM && hour !== 12) {
|
|
1067
|
+
hour += 12;
|
|
1068
|
+
}
|
|
1069
|
+
else if (isAM && hour === 12) {
|
|
1070
|
+
hour = 0;
|
|
1071
|
+
}
|
|
1072
|
+
// Return
|
|
1073
|
+
return {
|
|
1074
|
+
timestamp,
|
|
1075
|
+
year,
|
|
1076
|
+
month,
|
|
1077
|
+
day,
|
|
1078
|
+
hour,
|
|
1079
|
+
minute,
|
|
1080
|
+
};
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* A very simple, lightweight date chooser
|
|
1085
|
+
* @author Gabe Abrams
|
|
1086
|
+
*/
|
|
1087
|
+
/*------------------------------------------------------------------------*/
|
|
1088
|
+
/* Constants */
|
|
1089
|
+
/*------------------------------------------------------------------------*/
|
|
1090
|
+
// Constants
|
|
1091
|
+
const MONTH_NAMES = [
|
|
1092
|
+
'January',
|
|
1093
|
+
'February',
|
|
1094
|
+
'March',
|
|
1095
|
+
'April',
|
|
1096
|
+
'May',
|
|
1097
|
+
'June',
|
|
1098
|
+
'July',
|
|
1099
|
+
'August',
|
|
1100
|
+
'September',
|
|
1101
|
+
'October',
|
|
1102
|
+
'November',
|
|
1103
|
+
'December',
|
|
1104
|
+
];
|
|
1105
|
+
/*------------------------------------------------------------------------*/
|
|
1106
|
+
/* Component */
|
|
1107
|
+
/*------------------------------------------------------------------------*/
|
|
1108
|
+
const SimpleDateChooser = (props) => {
|
|
1109
|
+
/*------------------------------------------------------------------------*/
|
|
1110
|
+
/* Setup */
|
|
1111
|
+
/*------------------------------------------------------------------------*/
|
|
1112
|
+
var _a;
|
|
1113
|
+
/* -------------- Props ------------- */
|
|
1114
|
+
const { ariaLabel, name, month, day, year, onChange, } = props;
|
|
1115
|
+
const numMonthsToShow = Math.min((_a = props.numMonthsToShow) !== null && _a !== void 0 ? _a : 6, 12);
|
|
1116
|
+
/*------------------------------------------------------------------------*/
|
|
1117
|
+
/* Render */
|
|
1118
|
+
/*------------------------------------------------------------------------*/
|
|
1119
|
+
/*----------------------------------------*/
|
|
1120
|
+
/* Main UI */
|
|
1121
|
+
/*----------------------------------------*/
|
|
1122
|
+
// Determine the set of choices allowed
|
|
1123
|
+
const today = getTimeInfoInET();
|
|
1124
|
+
const choices = [];
|
|
1125
|
+
for (let i = 0; i < numMonthsToShow; i++) {
|
|
1126
|
+
// Get month and year info
|
|
1127
|
+
const unmoddedMonth = (today.month + i);
|
|
1128
|
+
const month = (unmoddedMonth > 12
|
|
1129
|
+
? unmoddedMonth - 12
|
|
1130
|
+
: unmoddedMonth);
|
|
1131
|
+
const monthName = MONTH_NAMES[month - 1];
|
|
1132
|
+
const year = (unmoddedMonth > 12
|
|
1133
|
+
? today.year + 1
|
|
1134
|
+
: today.year);
|
|
1135
|
+
// Figure out which days are allowed
|
|
1136
|
+
const days = [];
|
|
1137
|
+
const numDaysInMonth = (new Date(year, month, 0)).getDate();
|
|
1138
|
+
const firstDay = (month === today.month
|
|
1139
|
+
? today.day // Current month: start at current date
|
|
1140
|
+
: 1 // Future month: start at beginning of month
|
|
1141
|
+
);
|
|
1142
|
+
for (let day = firstDay; day <= numDaysInMonth; day++) {
|
|
1143
|
+
days.push(day);
|
|
1144
|
+
}
|
|
1145
|
+
choices.push({
|
|
1146
|
+
choiceName: `${monthName} ${year}`,
|
|
1147
|
+
month,
|
|
1148
|
+
year,
|
|
1149
|
+
days,
|
|
1150
|
+
});
|
|
1151
|
+
}
|
|
1152
|
+
// Create choice options
|
|
1153
|
+
const monthOptions = [];
|
|
1154
|
+
const dayOptions = [];
|
|
1155
|
+
choices.forEach((choice) => {
|
|
1156
|
+
// Create month option
|
|
1157
|
+
monthOptions.push(React__default["default"].createElement("option", { key: choice.month, value: choice.month, "aria-label": `choose ${choice.choiceName}`, onSelect: () => {
|
|
1158
|
+
onChange(choice.month, choice.days[0], choice.year);
|
|
1159
|
+
} }, choice.choiceName));
|
|
1160
|
+
if (month === choice.month) {
|
|
1161
|
+
// This is the currently selected month
|
|
1162
|
+
// Create day options
|
|
1163
|
+
choice.days.forEach((dayChoice) => {
|
|
1164
|
+
const ordinal = getOrdinal(dayChoice);
|
|
1165
|
+
dayOptions.push(React__default["default"].createElement("option", { key: dayChoice, value: dayChoice, "aria-label": `choose date ${dayChoice}` },
|
|
1166
|
+
dayChoice,
|
|
1167
|
+
ordinal));
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
return (React__default["default"].createElement("div", { className: "SimpleDateChooser d-inline-block", "aria-label": `date chooser with selected date: ${month} ${day}, ${year}` },
|
|
1172
|
+
React__default["default"].createElement("select", { "aria-label": `month for ${ariaLabel}`, className: "custom-select d-inline-block mr-1", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-month`, value: month, onChange: (e) => {
|
|
1173
|
+
const choice = choices[e.target.selectedIndex];
|
|
1174
|
+
// Change day, month, and year
|
|
1175
|
+
onChange(choice.month, choice.days[0], choice.year);
|
|
1176
|
+
} }, monthOptions),
|
|
1177
|
+
React__default["default"].createElement("select", { "aria-label": `day for ${ariaLabel}`, className: "custom-select d-inline-block", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-day`, value: day, onChange: (e) => {
|
|
1178
|
+
// Only change the day
|
|
1179
|
+
onChange(month, Number.parseInt(e.target.value, 10), year);
|
|
1180
|
+
} }, dayOptions)));
|
|
1181
|
+
};
|
|
1182
|
+
|
|
753
1183
|
/**
|
|
754
1184
|
* Shorten text so it fits into a certain number of chars
|
|
755
1185
|
* @author Gabe Abrams
|
|
@@ -893,7 +1323,429 @@ const roundToNumDecimals = (num, numDecimals) => {
|
|
|
893
1323
|
return (Math.round(num * rounder) / rounder);
|
|
894
1324
|
};
|
|
895
1325
|
|
|
1326
|
+
// Keep track of whether or not session expiry has already been handled
|
|
1327
|
+
let sessionAlreadyExpired = false;
|
|
1328
|
+
/*------------------------------------------------------------------------*/
|
|
1329
|
+
/* Main */
|
|
1330
|
+
/*------------------------------------------------------------------------*/
|
|
1331
|
+
/**
|
|
1332
|
+
* Visit an endpoint on the server [for client only]
|
|
1333
|
+
* @author Gabe Abrams
|
|
1334
|
+
* @param opts object containing all arguments
|
|
1335
|
+
* @param opts.path - the path of the server endpoint
|
|
1336
|
+
* @param [opts.method=GET] - the method of the endpoint
|
|
1337
|
+
* @param [opts.params] - query/body parameters to include
|
|
1338
|
+
* @returns response from server
|
|
1339
|
+
*/
|
|
1340
|
+
const visitServerEndpoint = (opts) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1341
|
+
var _a;
|
|
1342
|
+
// Send the request
|
|
1343
|
+
const response = yield cacclSendRequest({
|
|
1344
|
+
path: opts.path,
|
|
1345
|
+
method: (_a = opts.method) !== null && _a !== void 0 ? _a : 'GET',
|
|
1346
|
+
params: opts.params,
|
|
1347
|
+
});
|
|
1348
|
+
// Check for failure
|
|
1349
|
+
if (!response || !response.body) {
|
|
1350
|
+
throw new ErrorWithCode('We didn\'t get a response from the server. Please check your internet connection.', ReactKitErrorCode$1.NoResponse);
|
|
1351
|
+
}
|
|
1352
|
+
if (!response.body.success) {
|
|
1353
|
+
// Session expired
|
|
1354
|
+
if (response.body.code === ReactKitErrorCode$1.SessionExpired) {
|
|
1355
|
+
// Skip notice if session was already expired
|
|
1356
|
+
if (sessionAlreadyExpired) {
|
|
1357
|
+
// Never return (browser is already reloading)
|
|
1358
|
+
yield new Promise(() => {
|
|
1359
|
+
// Promise that never returns
|
|
1360
|
+
});
|
|
1361
|
+
}
|
|
1362
|
+
sessionAlreadyExpired = true;
|
|
1363
|
+
// Show session expiration message
|
|
1364
|
+
{
|
|
1365
|
+
// Fallback to alert
|
|
1366
|
+
// eslint-disable-next-line no-alert
|
|
1367
|
+
alert('Your session has expired. Please start over.');
|
|
1368
|
+
}
|
|
1369
|
+
// Never return (don't continue execution)
|
|
1370
|
+
yield new Promise(() => {
|
|
1371
|
+
// Promise that never returns
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
// Other errors
|
|
1375
|
+
throw new ErrorWithCode((response.body.message
|
|
1376
|
+
|| 'An unknown error occurred. Please contact an admin.'), (response.body.code
|
|
1377
|
+
|| ReactKitErrorCode$1.NoCode));
|
|
1378
|
+
}
|
|
1379
|
+
// Success! Extract the body
|
|
1380
|
+
const { body } = response.body;
|
|
1381
|
+
// Return
|
|
1382
|
+
return body;
|
|
1383
|
+
});
|
|
1384
|
+
|
|
1385
|
+
// Import custom error
|
|
1386
|
+
// Stored copy of caccl functions
|
|
1387
|
+
let _cacclGetLaunchInfo;
|
|
1388
|
+
/*------------------------------------------------------------------------*/
|
|
1389
|
+
/* Helpers */
|
|
1390
|
+
/*------------------------------------------------------------------------*/
|
|
1391
|
+
/**
|
|
1392
|
+
* Get launch info via CACCL
|
|
1393
|
+
* @author Gabe Abrams
|
|
1394
|
+
* @param req express request object
|
|
1395
|
+
* @returns object { launched, launchInfo }
|
|
1396
|
+
*/
|
|
1397
|
+
const cacclGetLaunchInfo = (req) => {
|
|
1398
|
+
if (!_cacclGetLaunchInfo) {
|
|
1399
|
+
throw new ErrorWithCode('Could not get launch info because server was not initialized with dce-reactkit\'s initServer function', ReactKitErrorCode$1.NoCACCLGetLaunchInfoFunction);
|
|
1400
|
+
}
|
|
1401
|
+
return _cacclGetLaunchInfo(req);
|
|
1402
|
+
};
|
|
1403
|
+
/*------------------------------------------------------------------------*/
|
|
1404
|
+
/* Main */
|
|
1405
|
+
/*------------------------------------------------------------------------*/
|
|
1406
|
+
/**
|
|
1407
|
+
* Prepare dce-reactkit to run on the server
|
|
1408
|
+
* @author Gabe Abrams
|
|
1409
|
+
* @param opts object containing all arguments
|
|
1410
|
+
* @param opts.getLaunchInfo CACCL LTI's get launch info function
|
|
1411
|
+
*/
|
|
1412
|
+
const initServer = (opts) => {
|
|
1413
|
+
_cacclGetLaunchInfo = opts.getLaunchInfo;
|
|
1414
|
+
};
|
|
1415
|
+
|
|
1416
|
+
/**
|
|
1417
|
+
* Server-side API param types
|
|
1418
|
+
* @author Gabe Abrams
|
|
1419
|
+
*/
|
|
1420
|
+
var ParamType;
|
|
1421
|
+
(function (ParamType) {
|
|
1422
|
+
ParamType["Boolean"] = "boolean";
|
|
1423
|
+
ParamType["BooleanOptional"] = "boolean-optional";
|
|
1424
|
+
ParamType["Float"] = "float";
|
|
1425
|
+
ParamType["FloatOptional"] = "float-optional";
|
|
1426
|
+
ParamType["Int"] = "int";
|
|
1427
|
+
ParamType["IntOptional"] = "int-optional";
|
|
1428
|
+
ParamType["JSON"] = "json";
|
|
1429
|
+
ParamType["JSONOptional"] = "json-optional";
|
|
1430
|
+
ParamType["String"] = "string";
|
|
1431
|
+
ParamType["StringOptional"] = "string-optional";
|
|
1432
|
+
})(ParamType || (ParamType = {}));
|
|
1433
|
+
var ParamType$1 = ParamType;
|
|
1434
|
+
|
|
1435
|
+
// Import shared types
|
|
1436
|
+
/**
|
|
1437
|
+
* Handle an error and respond to the client
|
|
1438
|
+
* @author Gabe Abrams
|
|
1439
|
+
* @param res express response
|
|
1440
|
+
* @param error error info
|
|
1441
|
+
* @param opts.err the error to send to the client
|
|
1442
|
+
* or the error message
|
|
1443
|
+
* @param [opts.code] an error code (only used if err.code is not
|
|
1444
|
+
* included)
|
|
1445
|
+
* @param [opts.status=500] the https status code to use
|
|
1446
|
+
* defined)
|
|
1447
|
+
*/
|
|
1448
|
+
const handleError = (res, error) => {
|
|
1449
|
+
// Get the error message
|
|
1450
|
+
let message;
|
|
1451
|
+
if (error && error.message) {
|
|
1452
|
+
message = (error.message || 'An unknown error occurred.');
|
|
1453
|
+
}
|
|
1454
|
+
else if (typeof error === 'string') {
|
|
1455
|
+
message = (error.trim().length > 0
|
|
1456
|
+
? error
|
|
1457
|
+
: 'An unknown error occurred.');
|
|
1458
|
+
}
|
|
1459
|
+
else {
|
|
1460
|
+
message = 'An unknown error occurred.';
|
|
1461
|
+
}
|
|
1462
|
+
// Get the error code
|
|
1463
|
+
const code = (error.code || ReactKitErrorCode$1.NoCode);
|
|
1464
|
+
// Get the status code
|
|
1465
|
+
const status = (error.status || 500);
|
|
1466
|
+
// Respond to user
|
|
1467
|
+
res
|
|
1468
|
+
// Set the http status code
|
|
1469
|
+
.status(status)
|
|
1470
|
+
// Send a JSON response
|
|
1471
|
+
.json({
|
|
1472
|
+
// Error message
|
|
1473
|
+
message,
|
|
1474
|
+
// Error code
|
|
1475
|
+
code,
|
|
1476
|
+
// Success = false flag so client can detect server-side errors
|
|
1477
|
+
success: false,
|
|
1478
|
+
});
|
|
1479
|
+
return undefined;
|
|
1480
|
+
};
|
|
1481
|
+
|
|
1482
|
+
/**
|
|
1483
|
+
* Send successful API response
|
|
1484
|
+
* @author Gabe Abrams
|
|
1485
|
+
* @param res express response
|
|
1486
|
+
* @param body the body of the response to send to the client
|
|
1487
|
+
*/
|
|
1488
|
+
const handleSuccess = (res, body) => {
|
|
1489
|
+
// Send a http 200 json response
|
|
1490
|
+
res.json({
|
|
1491
|
+
// Include the body as a parameter
|
|
1492
|
+
body,
|
|
1493
|
+
// Success = true flag so client can detect successful responses
|
|
1494
|
+
success: true,
|
|
1495
|
+
});
|
|
1496
|
+
return undefined;
|
|
1497
|
+
};
|
|
1498
|
+
|
|
1499
|
+
/**
|
|
1500
|
+
* Generate an express API route handler
|
|
1501
|
+
* @author Gabe Abrams
|
|
1502
|
+
* @param opts object containing all arguments
|
|
1503
|
+
* @param opts.paramTypes map containing the types for each parameter that is
|
|
1504
|
+
* included in the request (map: param name => type)
|
|
1505
|
+
* @param opts.handler function that processes the request
|
|
1506
|
+
* @returns express route handler that takes the following arguments:
|
|
1507
|
+
* params (map: param name => value), handleSuccess (function for handling
|
|
1508
|
+
* successful requests), handleError (function for handling failed requests),
|
|
1509
|
+
* req (express request object), res (express response object),
|
|
1510
|
+
* next (express next function)
|
|
1511
|
+
*/
|
|
1512
|
+
const genRouteHandler = (opts) => {
|
|
1513
|
+
// Return a route handler
|
|
1514
|
+
return (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
|
|
1515
|
+
var _a;
|
|
1516
|
+
// Output params
|
|
1517
|
+
const output = {};
|
|
1518
|
+
/*----------------------------------------*/
|
|
1519
|
+
/* Parse Params */
|
|
1520
|
+
/*----------------------------------------*/
|
|
1521
|
+
// Process items one by one
|
|
1522
|
+
const paramList = Object.entries((_a = opts.paramTypes) !== null && _a !== void 0 ? _a : {});
|
|
1523
|
+
for (let i = 0; i < paramList.length; i++) {
|
|
1524
|
+
const [name, type] = paramList[i];
|
|
1525
|
+
// Find the value as a string
|
|
1526
|
+
const value = (req.params[name]
|
|
1527
|
+
|| req.query[name]
|
|
1528
|
+
|| req.body[name]);
|
|
1529
|
+
// Parse
|
|
1530
|
+
if (type === ParamType$1.Boolean || type === ParamType$1.BooleanOptional) {
|
|
1531
|
+
// Boolean
|
|
1532
|
+
// Handle case where value doesn't exist
|
|
1533
|
+
if (value === undefined) {
|
|
1534
|
+
if (type === ParamType$1.BooleanOptional) {
|
|
1535
|
+
output[name] = undefined;
|
|
1536
|
+
}
|
|
1537
|
+
else {
|
|
1538
|
+
return handleError(res, {
|
|
1539
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
1540
|
+
code: ReactKitErrorCode$1.MissingParameter,
|
|
1541
|
+
status: 422,
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
else {
|
|
1546
|
+
// Value exists
|
|
1547
|
+
// Simplify value
|
|
1548
|
+
const simpleVal = (String(value)
|
|
1549
|
+
.trim()
|
|
1550
|
+
.toLowerCase());
|
|
1551
|
+
// Parse
|
|
1552
|
+
output[name] = ([
|
|
1553
|
+
'true',
|
|
1554
|
+
'yes',
|
|
1555
|
+
'y',
|
|
1556
|
+
'1',
|
|
1557
|
+
't',
|
|
1558
|
+
].indexOf(simpleVal) >= 0);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
else if (type === ParamType$1.Float || type === ParamType$1.FloatOptional) {
|
|
1562
|
+
// Float
|
|
1563
|
+
// Handle case where value doesn't exist
|
|
1564
|
+
if (value === undefined) {
|
|
1565
|
+
if (type === ParamType$1.FloatOptional) {
|
|
1566
|
+
output[name] = undefined;
|
|
1567
|
+
}
|
|
1568
|
+
else {
|
|
1569
|
+
return handleError(res, {
|
|
1570
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
1571
|
+
code: ReactKitErrorCode$1.MissingParameter,
|
|
1572
|
+
status: 422,
|
|
1573
|
+
});
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
else if (!Number.isNaN(Number.parseFloat(String(value)))) {
|
|
1577
|
+
// Value is a number
|
|
1578
|
+
output[name] = Number.parseFloat(String(value));
|
|
1579
|
+
}
|
|
1580
|
+
else {
|
|
1581
|
+
// Issue!
|
|
1582
|
+
return handleError(res, {
|
|
1583
|
+
message: `Request data was malformed: ${name} was not a valid float.`,
|
|
1584
|
+
code: ReactKitErrorCode$1.InvalidParameter,
|
|
1585
|
+
status: 422,
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
else if (type === ParamType$1.Int || type === ParamType$1.IntOptional) {
|
|
1590
|
+
// Int
|
|
1591
|
+
// Handle case where value doesn't exist
|
|
1592
|
+
if (value === undefined) {
|
|
1593
|
+
if (type === ParamType$1.IntOptional) {
|
|
1594
|
+
output[name] = undefined;
|
|
1595
|
+
}
|
|
1596
|
+
else {
|
|
1597
|
+
return handleError(res, {
|
|
1598
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
1599
|
+
code: ReactKitErrorCode$1.MissingParameter,
|
|
1600
|
+
status: 422,
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
else if (!Number.isNaN(Number.parseInt(String(value), 10))) {
|
|
1605
|
+
// Value is a number
|
|
1606
|
+
output[name] = Number.parseInt(String(value), 10);
|
|
1607
|
+
}
|
|
1608
|
+
else {
|
|
1609
|
+
// Issue!
|
|
1610
|
+
return handleError(res, {
|
|
1611
|
+
message: `Request data was malformed: ${name} was not a valid int.`,
|
|
1612
|
+
code: ReactKitErrorCode$1.InvalidParameter,
|
|
1613
|
+
status: 422,
|
|
1614
|
+
});
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
else if (type === ParamType$1.JSON || type === ParamType$1.JSONOptional) {
|
|
1618
|
+
// Stringified JSON
|
|
1619
|
+
// Handle case where value doesn't exist
|
|
1620
|
+
if (value === undefined) {
|
|
1621
|
+
if (type === ParamType$1.JSONOptional) {
|
|
1622
|
+
output[name] = undefined;
|
|
1623
|
+
}
|
|
1624
|
+
else {
|
|
1625
|
+
return handleError(res, {
|
|
1626
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
1627
|
+
code: ReactKitErrorCode$1.MissingParameter,
|
|
1628
|
+
status: 422,
|
|
1629
|
+
});
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
else {
|
|
1633
|
+
// Value exists
|
|
1634
|
+
// Parse
|
|
1635
|
+
try {
|
|
1636
|
+
output[name] = JSON.parse(String(value));
|
|
1637
|
+
}
|
|
1638
|
+
catch (err) {
|
|
1639
|
+
return handleError(res, {
|
|
1640
|
+
message: `Request data was malformed: ${name} was not a valid JSON payload.`,
|
|
1641
|
+
code: ReactKitErrorCode$1.InvalidParameter,
|
|
1642
|
+
status: 422,
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
else if (type === ParamType$1.String || type === ParamType$1.StringOptional) {
|
|
1648
|
+
// String
|
|
1649
|
+
// Handle case where value doesn't exist
|
|
1650
|
+
if (value === undefined) {
|
|
1651
|
+
if (type === ParamType$1.StringOptional) {
|
|
1652
|
+
output[name] = undefined;
|
|
1653
|
+
}
|
|
1654
|
+
else {
|
|
1655
|
+
return handleError(res, {
|
|
1656
|
+
message: `Parameter ${name} is required, but it was not included.`,
|
|
1657
|
+
code: ReactKitErrorCode$1.MissingParameter,
|
|
1658
|
+
status: 422,
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
else {
|
|
1663
|
+
// Value exists
|
|
1664
|
+
// Leave as is
|
|
1665
|
+
output[name] = value;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
else {
|
|
1669
|
+
// No valid data type
|
|
1670
|
+
return handleError(res, {
|
|
1671
|
+
message: `An internal error occurred: we could not determine the type of ${name}.`,
|
|
1672
|
+
code: ReactKitErrorCode$1.InvalidParameter,
|
|
1673
|
+
status: 422,
|
|
1674
|
+
});
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
/*----------------------------------------*/
|
|
1678
|
+
/* Launch Info */
|
|
1679
|
+
/*----------------------------------------*/
|
|
1680
|
+
// Get launch info
|
|
1681
|
+
const { launched, launchInfo } = cacclGetLaunchInfo(req);
|
|
1682
|
+
if (!launched || !launchInfo) {
|
|
1683
|
+
return handleError(res, {
|
|
1684
|
+
message: 'Your session has expired. Please refresh the page and try again.',
|
|
1685
|
+
code: ReactKitErrorCode$1.SessionExpired,
|
|
1686
|
+
status: 440,
|
|
1687
|
+
});
|
|
1688
|
+
}
|
|
1689
|
+
// Error if user info cannot be found
|
|
1690
|
+
if (!launchInfo.userId
|
|
1691
|
+
|| !launchInfo.userFirstName
|
|
1692
|
+
|| !launchInfo.userLastName
|
|
1693
|
+
|| (launchInfo.notInCourse
|
|
1694
|
+
&& !launchInfo.isAdmin)
|
|
1695
|
+
|| (!launchInfo.isTTM
|
|
1696
|
+
&& !launchInfo.isLearner
|
|
1697
|
+
&& !launchInfo.isAdmin)) {
|
|
1698
|
+
return handleError(res, {
|
|
1699
|
+
message: 'Your session was invalid. Please refresh the page and try again.',
|
|
1700
|
+
code: ReactKitErrorCode$1.SessionExpired,
|
|
1701
|
+
status: 440,
|
|
1702
|
+
});
|
|
1703
|
+
}
|
|
1704
|
+
// Add launch info to output
|
|
1705
|
+
output.userId = launchInfo.userId;
|
|
1706
|
+
output.userFirstName = launchInfo.userFirstName;
|
|
1707
|
+
output.userLastName = launchInfo.userLastName;
|
|
1708
|
+
output.isLearner = !!launchInfo.isLearner;
|
|
1709
|
+
output.isTTM = !!launchInfo.isTTM;
|
|
1710
|
+
output.isAdmin = !!launchInfo.isAdmin;
|
|
1711
|
+
output.isWatchingInPrivate = !!(req.session.isWatchingInPrivate);
|
|
1712
|
+
/*----------------------------------------*/
|
|
1713
|
+
/* Require Course Consistency */
|
|
1714
|
+
/*----------------------------------------*/
|
|
1715
|
+
// Make sure the user actually launched from the appropriate course
|
|
1716
|
+
if (output.courseId
|
|
1717
|
+
&& launchInfo.courseId
|
|
1718
|
+
&& output.courseId !== launchInfo.courseId
|
|
1719
|
+
&& !output.isTTM
|
|
1720
|
+
&& !output.isAdmin) {
|
|
1721
|
+
// Course of interest is not the launch course
|
|
1722
|
+
return handleError(res, {
|
|
1723
|
+
message: 'You switched sessions by opening Immersive Classroom in another tab. Please refresh the page and try again.',
|
|
1724
|
+
code: ReactKitErrorCode$1.WrongCourse,
|
|
1725
|
+
status: 401,
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
/*------------------------------------------------------------------------*/
|
|
1729
|
+
/* Call handler */
|
|
1730
|
+
/*------------------------------------------------------------------------*/
|
|
1731
|
+
opts.handler({
|
|
1732
|
+
params: output,
|
|
1733
|
+
handleSuccess: (body) => {
|
|
1734
|
+
return handleSuccess(res, body);
|
|
1735
|
+
},
|
|
1736
|
+
handleError: (error) => {
|
|
1737
|
+
return handleError(res, error);
|
|
1738
|
+
},
|
|
1739
|
+
req,
|
|
1740
|
+
res,
|
|
1741
|
+
next,
|
|
1742
|
+
});
|
|
1743
|
+
});
|
|
1744
|
+
};
|
|
1745
|
+
|
|
896
1746
|
exports.AppWrapper = AppWrapper;
|
|
1747
|
+
exports.ButtonInputGroup = ButtonInputGroup;
|
|
1748
|
+
exports.CheckboxButton = CheckboxButton;
|
|
897
1749
|
exports.ErrorBox = ErrorBox;
|
|
898
1750
|
exports.ErrorWithCode = ErrorWithCode;
|
|
899
1751
|
exports.LoadingSpinner = LoadingSpinner;
|
|
@@ -901,20 +1753,30 @@ exports.Modal = Modal;
|
|
|
901
1753
|
exports.ModalButtonType = ModalButtonType$1;
|
|
902
1754
|
exports.ModalSize = ModalSize$1;
|
|
903
1755
|
exports.ModalType = ModalType$1;
|
|
1756
|
+
exports.ParamType = ParamType$1;
|
|
1757
|
+
exports.RadioButton = RadioButton;
|
|
904
1758
|
exports.ReactKitErrorCode = ReactKitErrorCode$1;
|
|
1759
|
+
exports.SimpleDateChooser = SimpleDateChooser;
|
|
905
1760
|
exports.TabBox = TabBox;
|
|
906
1761
|
exports.Variant = Variant$1;
|
|
907
1762
|
exports.abbreviate = abbreviate;
|
|
908
|
-
exports.alert = alert;
|
|
1763
|
+
exports.alert = alert$1;
|
|
909
1764
|
exports.avg = avg;
|
|
910
1765
|
exports.ceilToNumDecimals = ceilToNumDecimals;
|
|
911
1766
|
exports.confirm = confirm;
|
|
912
1767
|
exports.floorToNumDecimals = floorToNumDecimals;
|
|
913
1768
|
exports.forceNumIntoBounds = forceNumIntoBounds;
|
|
1769
|
+
exports.genRouteHandler = genRouteHandler;
|
|
1770
|
+
exports.getOrdinal = getOrdinal;
|
|
1771
|
+
exports.getTimeInfoInET = getTimeInfoInET;
|
|
1772
|
+
exports.handleError = handleError;
|
|
1773
|
+
exports.handleSuccess = handleSuccess;
|
|
1774
|
+
exports.initServer = initServer;
|
|
914
1775
|
exports.padDecimalZeros = padDecimalZeros;
|
|
915
1776
|
exports.padZerosLeft = padZerosLeft;
|
|
916
1777
|
exports.roundToNumDecimals = roundToNumDecimals;
|
|
917
1778
|
exports.showFatalError = showFatalError;
|
|
918
1779
|
exports.sum = sum;
|
|
1780
|
+
exports.visitServerEndpoint = visitServerEndpoint;
|
|
919
1781
|
exports.waitMs = waitMs;
|
|
920
1782
|
//# sourceMappingURL=index.js.map
|