dce-reactkit 3.5.10 → 3.5.12

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,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';
@@ -83,4 +83,4 @@ import PickableItem from './components/ItemPicker/types/PickableItem';
83
83
  import DBEntry from './components/DBEntryManagerPanel/types/DBEntry';
84
84
  import DBEntryField from './components/DBEntryManagerPanel/types/DBEntryField';
85
85
  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, };
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, 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
@@ -1414,4 +1422,4 @@ declare const LogBuiltInMetadata: {
1414
1422
  };
1415
1423
  };
1416
1424
 
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 };
1425
+ 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, 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.10",
3
+ "version": "3.5.12",
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
  </>
@@ -215,7 +215,7 @@ const AutoscrollToBottomContainer: React.FC<Props> = (props) => {
215
215
  // Initialize refs
216
216
  const lastItemIds = useRef<(string | number)[]>([]);
217
217
  const container = useRef<HTMLDivElement | null>(null);
218
- const wasScrolledToBottomBeforeItemsUpdate = useRef<boolean>(true);
218
+ const wasLastScrolledToBottom = useRef<boolean>(true);
219
219
 
220
220
  /*------------------------------------------------------------------------*/
221
221
  /* ------------------------- Component Functions ------------------------ */
@@ -253,7 +253,12 @@ const AutoscrollToBottomContainer: React.FC<Props> = (props) => {
253
253
  );
254
254
 
255
255
  // Figure out if we're scrolled to the bottom
256
- return (distanceToBottomRems < SCROLLED_TO_BOTTOM_THRESHOLD_REMS);
256
+ const atBottom = (distanceToBottomRems < SCROLLED_TO_BOTTOM_THRESHOLD_REMS);
257
+
258
+ // Remember if we scrolled to the bottom
259
+ wasLastScrolledToBottom.current = atBottom;
260
+
261
+ return atBottom;
257
262
  };
258
263
 
259
264
  /**
@@ -272,7 +277,13 @@ const AutoscrollToBottomContainer: React.FC<Props> = (props) => {
272
277
  });
273
278
 
274
279
  // Scroll to bottom
275
- container.current.scrollTop = container.current.scrollHeight;
280
+ container.current.scrollTop = (
281
+ container.current.scrollHeight
282
+ - container.current.clientHeight
283
+ );
284
+
285
+ // Remember that we scrolled to the bottom
286
+ wasLastScrolledToBottom.current = true;
276
287
  };
277
288
 
278
289
  /*----------------------------------------*/
@@ -289,8 +300,9 @@ const AutoscrollToBottomContainer: React.FC<Props> = (props) => {
289
300
  return;
290
301
  }
291
302
 
292
- // If scrolled to bottom, hide the button
303
+ // If scrolled to bottom, hide the button and remember position
293
304
  if (isScrolledToBottom()) {
305
+ // Hide button
294
306
  dispatch({
295
307
  type: ActionType.HideJumpToBottomButton,
296
308
  });
@@ -324,22 +336,24 @@ const AutoscrollToBottomContainer: React.FC<Props> = (props) => {
324
336
 
325
337
  // Check if new content appeared at bottom
326
338
  const newContentAppeared = (
339
+ // There are items
327
340
  currentItemIds.length > 0
328
- && lastItemIds.current
329
- && lastItemIds.current.length > 0
330
- && (
331
- currentItemIds[currentItemIds.length - 1]
332
- !== lastItemIds.current[lastItemIds.current.length - 1]
341
+ // The last item is new
342
+ && !lastItemIds.current.includes(
343
+ currentItemIds[currentItemIds.length - 1],
333
344
  )
334
345
  );
335
346
 
347
+ // Update last item ids
348
+ lastItemIds.current = currentItemIds;
349
+
336
350
  // Do nothing if no new content
337
351
  if (!newContentAppeared) {
338
352
  return;
339
353
  }
340
354
 
341
355
  // Check if used to be scrolled to the bottom
342
- if (wasScrolledToBottomBeforeItemsUpdate.current) {
356
+ if (wasLastScrolledToBottom.current) {
343
357
  // Was scrolled to the bottom! Autoscroll.
344
358
  scrollToBottom();
345
359
  } else {
@@ -348,14 +362,6 @@ const AutoscrollToBottomContainer: React.FC<Props> = (props) => {
348
362
  type: ActionType.ShowJumpToBottomButton,
349
363
  });
350
364
  }
351
-
352
- // Update last item ids
353
- lastItemIds.current = currentItemIds;
354
-
355
- // Remember if we were scrolled to the bottom before the update
356
- return () => {
357
- wasScrolledToBottomBeforeItemsUpdate.current = isScrolledToBottom();
358
- };
359
365
  },
360
366
  [items],
361
367
  );
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';
@@ -176,6 +177,7 @@ export {
176
177
  visitServerEndpoint,
177
178
  logClientEvent,
178
179
  addFatalErrorHandler,
180
+ leaveToURL,
179
181
  // Server helpers
180
182
  initServer,
181
183
  genRouteHandler,