dce-reactkit 3.5.11 → 3.5.13

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.
@@ -9,6 +9,14 @@ type Props = {
9
9
  children: React.ReactNode;
10
10
  dark?: boolean;
11
11
  };
12
+ /**
13
+ * Redirect to a new page
14
+ * @author Gabe Abrams
15
+ * @param url the url to redirect to
16
+ * @param destination the destination of the redirect, for example "YouTube"
17
+ * and will be used in the following text: `Redirecting to ${destination}...`
18
+ */
19
+ export declare const leaveToURL: (url: string, destination: string) => Promise<void>;
12
20
  /**
13
21
  * Show an alert modal with an "Okay" button
14
22
  * @author Gabe Abrams
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Get the current part of day (morning, evening, etc.)
3
3
  * @author Gabe Abrams
4
+ * @returns the part of day (morning, evening, etc.)
4
5
  */
5
6
  declare const getPartOfDay: () => string;
6
7
  export default getPartOfDay;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ /**
3
+ * Given some text, make the links clickable
4
+ * @author Gabe Abrams
5
+ * @param text the text to process
6
+ * @param [opts] options to customize behavior
7
+ * @param [opts.newTab] if true, links will open in a new tab
8
+ * @param [opts.preventPropagation] if true, clicks to link will prevent default
9
+ * and propagation
10
+ * @returns the processed text
11
+ */
12
+ declare const makeLinksClickable: (text: string, opts?: {
13
+ newTab?: boolean;
14
+ preventPropagation?: boolean;
15
+ }) => React.ReactNode;
16
+ export default makeLinksClickable;
@@ -1,4 +1,4 @@
1
- import AppWrapper, { alert, confirm, showFatalError, addFatalErrorHandler } from './components/AppWrapper';
1
+ import AppWrapper, { alert, confirm, showFatalError, addFatalErrorHandler, leaveToURL } from './components/AppWrapper';
2
2
  import LoadingSpinner from './components/LoadingSpinner';
3
3
  import ErrorBox from './components/ErrorBox';
4
4
  import Modal from './components/Modal';
@@ -65,6 +65,7 @@ import validateEmail from './helpers/validators/validateEmail';
65
65
  import validatePhoneNumber from './helpers/validators/validatePhoneNumber';
66
66
  import validateString from './helpers/validators/validateString';
67
67
  import idify from './helpers/idify';
68
+ import makeLinksClickable from './helpers/makeLinksClickable';
68
69
  import ModalButtonType from './types/ModalButtonType';
69
70
  import ModalSize from './types/ModalSize';
70
71
  import ModalType from './types/ModalType';
@@ -83,4 +84,4 @@ import PickableItem from './components/ItemPicker/types/PickableItem';
83
84
  import DBEntry from './components/DBEntryManagerPanel/types/DBEntry';
84
85
  import DBEntryField from './components/DBEntryManagerPanel/types/DBEntryField';
85
86
  import DBEntryFieldType from './components/DBEntryManagerPanel/types/DBEntryFieldType';
86
- export { AppWrapper, LoadingSpinner, ErrorBox, Modal, TabBox, RadioButton, CheckboxButton, ButtonInputGroup, SimpleDateChooser, Drawer, PopSuccessMark, PopFailureMark, PopPendingMark, CopiableBox, ItemPicker, LogReviewer, IntelliTable, CSVDownloadButton, DBEntryManagerPanel, Tooltip, ToggleSwitch, AutoscrollToBottomContainer, alert, confirm, showFatalError, ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, DynamicWord, abbreviate, avg, ceilToNumDecimals, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, stubServerEndpoint, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, canReviewLogs, isMobileOrTablet, extractProp, compareArraysByProp, genCommaList, validateEmail, validatePhoneNumber, validateString, getLocalTimeInfo, idify, initClient, visitServerEndpoint, logClientEvent, addFatalErrorHandler, initServer, genRouteHandler, handleError, handleSuccess, initLogCollection, addDBEditorEndpoints, ModalButtonType, ModalSize, ModalType, ReactKitErrorCode, Variant, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, IntelliTableColumn, PickableItem, DBEntry, DBEntryField, DBEntryFieldType, ParamType, };
87
+ export { AppWrapper, LoadingSpinner, ErrorBox, Modal, TabBox, RadioButton, CheckboxButton, ButtonInputGroup, SimpleDateChooser, Drawer, PopSuccessMark, PopFailureMark, PopPendingMark, CopiableBox, ItemPicker, LogReviewer, IntelliTable, CSVDownloadButton, DBEntryManagerPanel, Tooltip, ToggleSwitch, AutoscrollToBottomContainer, alert, confirm, showFatalError, ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, DynamicWord, abbreviate, avg, ceilToNumDecimals, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, stubServerEndpoint, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, canReviewLogs, isMobileOrTablet, extractProp, compareArraysByProp, genCommaList, validateEmail, validatePhoneNumber, validateString, getLocalTimeInfo, idify, makeLinksClickable, initClient, visitServerEndpoint, logClientEvent, addFatalErrorHandler, leaveToURL, initServer, genRouteHandler, handleError, handleSuccess, initLogCollection, addDBEditorEndpoints, ModalButtonType, ModalSize, ModalType, ReactKitErrorCode, Variant, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, IntelliTableColumn, PickableItem, DBEntry, DBEntryField, DBEntryFieldType, ParamType, };
package/dist/index.d.ts CHANGED
@@ -28,6 +28,14 @@ type Props$k = {
28
28
  children: React.ReactNode;
29
29
  dark?: boolean;
30
30
  };
31
+ /**
32
+ * Redirect to a new page
33
+ * @author Gabe Abrams
34
+ * @param url the url to redirect to
35
+ * @param destination the destination of the redirect, for example "YouTube"
36
+ * and will be used in the following text: `Redirecting to ${destination}...`
37
+ */
38
+ declare const leaveToURL: (url: string, destination: string) => Promise<void>;
31
39
  /**
32
40
  * Show an alert modal with an "Okay" button
33
41
  * @author Gabe Abrams
@@ -1117,6 +1125,7 @@ declare const getHumanReadableDate: (dateOrTimestamp?: Date | number) => string;
1117
1125
  /**
1118
1126
  * Get the current part of day (morning, evening, etc.)
1119
1127
  * @author Gabe Abrams
1128
+ * @returns the part of day (morning, evening, etc.)
1120
1129
  */
1121
1130
  declare const getPartOfDay: () => string;
1122
1131
 
@@ -1364,6 +1373,21 @@ declare const validateString: (input: string, opts: {
1364
1373
  */
1365
1374
  declare const idify: (str: string) => string;
1366
1375
 
1376
+ /**
1377
+ * Given some text, make the links clickable
1378
+ * @author Gabe Abrams
1379
+ * @param text the text to process
1380
+ * @param [opts] options to customize behavior
1381
+ * @param [opts.newTab] if true, links will open in a new tab
1382
+ * @param [opts.preventPropagation] if true, clicks to link will prevent default
1383
+ * and propagation
1384
+ * @returns the processed text
1385
+ */
1386
+ declare const makeLinksClickable: (text: string, opts?: {
1387
+ newTab?: boolean;
1388
+ preventPropagation?: boolean;
1389
+ }) => React.ReactNode;
1390
+
1367
1391
  /**
1368
1392
  * List of error codes built into the react kit
1369
1393
  * @author Gabe Abrams
@@ -1414,4 +1438,4 @@ declare const LogBuiltInMetadata: {
1414
1438
  };
1415
1439
  };
1416
1440
 
1417
- export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntry, DBEntryField, DBEntryFieldType, DBEntryManagerPanel, DayOfWeek, Drawer, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, IntelliTableColumn, ItemPicker, LoadingSpinner, Log, LogAction, LogBuiltInMetadata, LogMetadataType, LogReviewer, LogSource, LogType, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, ParamType, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, ToggleSwitch, Tooltip, Variant, abbreviate, addDBEditorEndpoints, addFatalErrorHandler, alert, avg, canReviewLogs, ceilToNumDecimals, compareArraysByProp, confirm, extractProp, floorToNumDecimals, forceNumIntoBounds, genCSV, genCommaList, genRouteHandler, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, idify, initClient, initLogCollection, initServer, isMobileOrTablet, logClientEvent, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
1441
+ export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntry, DBEntryField, DBEntryFieldType, DBEntryManagerPanel, DayOfWeek, Drawer, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, IntelliTableColumn, ItemPicker, LoadingSpinner, Log, LogAction, LogBuiltInMetadata, LogMetadataType, LogReviewer, LogSource, LogType, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, ParamType, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, ToggleSwitch, Tooltip, Variant, abbreviate, addDBEditorEndpoints, addFatalErrorHandler, alert, avg, canReviewLogs, ceilToNumDecimals, compareArraysByProp, confirm, extractProp, floorToNumDecimals, forceNumIntoBounds, genCSV, genCommaList, genRouteHandler, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, idify, initClient, initLogCollection, initServer, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dce-reactkit",
3
- "version": "3.5.11",
3
+ "version": "3.5.13",
4
4
  "description": "Shared components for Harvard DCE apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -44,6 +44,47 @@ type Props = {
44
44
  /* --------------------------- Static Helpers --------------------------- */
45
45
  /*------------------------------------------------------------------------*/
46
46
 
47
+ /*----------------------------------------*/
48
+ /* ----------- Redirect/Leave ----------- */
49
+ /*----------------------------------------*/
50
+
51
+ // Stored copy of setter for url to leave to
52
+ let setURLToLeaveTo: (
53
+ opts: {
54
+ url: string,
55
+ destination: string,
56
+ },
57
+ ) => void;
58
+
59
+ /**
60
+ * Redirect to a new page
61
+ * @author Gabe Abrams
62
+ * @param url the url to redirect to
63
+ * @param destination the destination of the redirect, for example "YouTube"
64
+ * and will be used in the following text: `Redirecting to ${destination}...`
65
+ */
66
+ export const leaveToURL = async (url: string, destination: string) => {
67
+ if (setURLToLeaveTo) {
68
+ // Beautiful redirect
69
+ setURLToLeaveTo({ url, destination });
70
+ } else {
71
+ // Overwrite page in a janky way
72
+ window.document.body.innerHTML = `
73
+ <div>
74
+ <h1>
75
+ Redirecting to ${destination}...
76
+ </h1>
77
+ <p>
78
+ If you are not redirected in 5 seconds, please <a href="${url}">click here</a>.
79
+ </p>
80
+ </div>
81
+ `;
82
+ }
83
+
84
+ // Redirect to location
85
+ window.location.href = url;
86
+ };
87
+
47
88
  /*----------------------------------------*/
48
89
  /* ---------------- Alert --------------- */
49
90
  /*----------------------------------------*/
@@ -247,6 +288,32 @@ export const showSessionExpiredMessage = (): undefined => {
247
288
  return undefined;
248
289
  };
249
290
 
291
+ /*------------------------------------------------------------------------*/
292
+ /* -------------------------------- Style ------------------------------- */
293
+ /*------------------------------------------------------------------------*/
294
+
295
+ const style = `
296
+ .AppWrapper-leave-to-url-notice {
297
+ opacity: 0;
298
+
299
+ animation-name: AppWrapper-leave-to-url-notice-appear;
300
+ animation-duration: 0.5s;
301
+ animation-delay: 1s;
302
+ animation-fill-mode: both;
303
+ animation-timing-function: ease-out;
304
+ animation-iteration-count: 1;
305
+ }
306
+
307
+ @keyframes AppWrapper-leave-to-url-notice-appear {
308
+ 0% {
309
+ opacity: 0;
310
+ }
311
+ 100% {
312
+ opacity: 1;
313
+ }
314
+ }
315
+ `;
316
+
250
317
  /*------------------------------------------------------------------------*/
251
318
  /* ------------------------------ Component ----------------------------- */
252
319
  /*------------------------------------------------------------------------*/
@@ -265,6 +332,13 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
265
332
 
266
333
  /* -------------- State ------------- */
267
334
 
335
+ // Leave to URL
336
+ const [
337
+ urlToLeaveTo,
338
+ setURLToLeaveToInner,
339
+ ] = useState<{ url: string, destination: string } | undefined>();
340
+ setURLToLeaveTo = setURLToLeaveToInner;
341
+
268
342
  // Fatal error
269
343
  const [
270
344
  fatalErrorMessage,
@@ -386,9 +460,47 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
386
460
  // Body that will be filled with the current view
387
461
  let body: React.ReactNode;
388
462
 
463
+ /* ---------- Leave to URL ---------- */
464
+
465
+ if (!body && urlToLeaveTo) {
466
+ // Destructure url info
467
+ const {
468
+ url,
469
+ destination,
470
+ } = urlToLeaveTo;
471
+
472
+ // Show pretty redirect screen
473
+ body = (
474
+ <div className="AppWrapper-leave-to-url-container p-5 text-center">
475
+ <div className="AppWrapper-leave-to-url-notice d-inline-block">
476
+ <h3 className="text-start m-0">
477
+ Redirecting to
478
+ {' '}
479
+ {destination}
480
+ ...
481
+ </h3>
482
+ <div className="text-start">
483
+ If you are not automatically redirected in 5 seconds, please
484
+ {' '}
485
+ <a
486
+ href={url}
487
+ aria-label={`Click here to leave to ${destination}`}
488
+ >
489
+ click here
490
+ </a>
491
+ .
492
+ </div>
493
+ </div>
494
+ </div>
495
+ );
496
+ }
497
+
389
498
  /* ----------- Fatal Error ---------- */
390
499
 
391
- if (fatalErrorMessage || fatalErrorCode || sessionHasExpired) {
500
+ if (
501
+ !body
502
+ && (fatalErrorMessage || fatalErrorCode || sessionHasExpired)
503
+ ) {
392
504
  // Re-encapsulate in an error
393
505
  const error = (
394
506
  sessionHasExpired
@@ -441,6 +553,9 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
441
553
 
442
554
  return (
443
555
  <>
556
+ <style>
557
+ {style}
558
+ </style>
444
559
  {modal}
445
560
  {body}
446
561
  </>
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Get the current part of day (morning, evening, etc.)
3
3
  * @author Gabe Abrams
4
+ * @returns the part of day (morning, evening, etc.)
4
5
  */
5
6
  const getPartOfDay = () => {
6
7
  // Setup the post-it time of day
7
8
  let partOfDay = 'day';
8
- let hours = new Date().getHours();
9
+ const hours = new Date().getHours();
9
10
  if (hours < 12) {
10
11
  partOfDay = 'morning';
11
12
  } else if (hours >= 12 && hours <= 16) {
@@ -0,0 +1,98 @@
1
+ // Import React
2
+ import React from 'react';
3
+
4
+ // Regular express that matches urls
5
+ const urlRegex = /(https?:\/\/)?([a-z0-9-]+\.)+[a-z0-9]{2,6}(:[0-9]{1,5})?(\/\S*)?/g;
6
+
7
+ /**
8
+ * Given some text, make the links clickable
9
+ * @author Gabe Abrams
10
+ * @param text the text to process
11
+ * @param [opts] options to customize behavior
12
+ * @param [opts.newTab] if true, links will open in a new tab
13
+ * @param [opts.preventPropagation] if true, clicks to link will prevent default
14
+ * and propagation
15
+ * @returns the processed text
16
+ */
17
+ const makeLinksClickable = (
18
+ text: string,
19
+ opts?: {
20
+ newTab?: boolean,
21
+ preventPropagation?: boolean,
22
+ },
23
+ ): React.ReactNode => {
24
+ // Search text for links
25
+ let matches = urlRegex.exec(text);
26
+
27
+ // If no matches, just return the text
28
+ if (!matches) {
29
+ return text;
30
+ }
31
+
32
+ // Check if using new tab
33
+ const newTab = opts?.newTab;
34
+
35
+ // Next element key
36
+ let nextKey = 0;
37
+
38
+ // Process each link
39
+ const elements: React.ReactNode[] = [];
40
+ let lastIndex = 0;
41
+ while (matches) {
42
+ // Get the link
43
+ const link = matches[0];
44
+
45
+ // Get the index of the link
46
+ const { index } = matches;
47
+
48
+ // Add the text before the link
49
+ elements.push(
50
+ <span key={nextKey += 1}>
51
+ {text.substring(lastIndex, index)}
52
+ </span>,
53
+ );
54
+
55
+ // Add the link
56
+ elements.push(
57
+ <a
58
+ key={nextKey += 1}
59
+ href={link}
60
+ target={newTab ? '_blank' : undefined}
61
+ rel={newTab ? 'noopener noreferrer' : undefined}
62
+ style={{
63
+ textDecoration: 'underline',
64
+ }}
65
+ onClick={(e) => {
66
+ // Prevent default and propagation if requested
67
+ if (opts?.preventPropagation) {
68
+ e.preventDefault();
69
+ e.stopPropagation();
70
+ }
71
+ }}
72
+ >
73
+ {link}
74
+ </a>,
75
+ );
76
+
77
+ // Update the last index
78
+ lastIndex = index + link.length;
79
+
80
+ // Get the next match
81
+ matches = urlRegex.exec(text);
82
+ }
83
+
84
+ // Add the last bit of text
85
+ const remainingText = text.substring(lastIndex);
86
+ if (remainingText && remainingText.length > 0) {
87
+ elements.push(
88
+ <span key={nextKey += 1}>
89
+ {remainingText}
90
+ </span>,
91
+ );
92
+ }
93
+
94
+ // Return the elements
95
+ return elements;
96
+ };
97
+
98
+ export default makeLinksClickable;
package/src/index.ts CHANGED
@@ -4,6 +4,7 @@ import AppWrapper, {
4
4
  confirm,
5
5
  showFatalError,
6
6
  addFatalErrorHandler,
7
+ leaveToURL,
7
8
  } from './components/AppWrapper';
8
9
  import LoadingSpinner from './components/LoadingSpinner';
9
10
  import ErrorBox from './components/ErrorBox';
@@ -79,6 +80,7 @@ import validateEmail from './helpers/validators/validateEmail';
79
80
  import validatePhoneNumber from './helpers/validators/validatePhoneNumber';
80
81
  import validateString from './helpers/validators/validateString';
81
82
  import idify from './helpers/idify';
83
+ import makeLinksClickable from './helpers/makeLinksClickable';
82
84
 
83
85
  // Import types
84
86
  import ModalButtonType from './types/ModalButtonType';
@@ -171,11 +173,13 @@ export {
171
173
  validateString,
172
174
  getLocalTimeInfo,
173
175
  idify,
176
+ makeLinksClickable,
174
177
  // Client helpers
175
178
  initClient,
176
179
  visitServerEndpoint,
177
180
  logClientEvent,
178
181
  addFatalErrorHandler,
182
+ leaveToURL,
179
183
  // Server helpers
180
184
  initServer,
181
185
  genRouteHandler,