dce-reactkit 4.0.0-beta.3 → 4.0.0
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/.vscode/settings.json +3 -1
- package/dist/cjs/index.js +218 -114
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/cjs/types/components/Modal/ModalProps.d.ts +2 -0
- package/dist/cjs/types/components/TabBox.d.ts +1 -0
- package/dist/cjs/types/helpers/getWordCount.d.ts +10 -0
- package/dist/cjs/types/index.d.ts +2 -1
- package/dist/esm/index.js +219 -116
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/esm/types/components/Modal/ModalProps.d.ts +2 -0
- package/dist/esm/types/components/TabBox.d.ts +1 -0
- package/dist/esm/types/helpers/getWordCount.d.ts +10 -0
- package/dist/esm/types/index.d.ts +2 -1
- package/dist/index.d.ts +15 -1
- package/package.json +2 -3
- package/rollup.config.js +0 -2
- package/src/components/IntelliTable.tsx +1 -1
- package/src/components/ItemPicker/NestableItemList.tsx +1 -0
- package/src/components/ItemPicker/index.tsx +96 -0
- package/src/components/Modal/ModalProps.ts +4 -0
- package/src/components/Modal/index.tsx +59 -20
- package/src/components/TabBox.tsx +27 -9
- package/src/helpers/getWordCount.ts +26 -0
- package/src/index.ts +2 -0
- package/genEncodedSecret.ts +0 -84
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get number of words in string
|
|
3
|
+
* @author Gardenia Liu
|
|
4
|
+
* @author Allison Zhang
|
|
5
|
+
* @author Gabe Abrams
|
|
6
|
+
* @param text the string to check
|
|
7
|
+
* @returns number of words in the string
|
|
8
|
+
*/
|
|
9
|
+
declare const getWordCount: (text: string) => number;
|
|
10
|
+
export default getWordCount;
|
|
@@ -75,6 +75,7 @@ import mapAsync from './helpers/asyncArrayFunctions/mapAsync';
|
|
|
75
75
|
import someAsync from './helpers/asyncArrayFunctions/someAsync';
|
|
76
76
|
import capitalize from './helpers/capitalize';
|
|
77
77
|
import shuffleArray from './helpers/shuffleArray';
|
|
78
|
+
import getWordCount from './helpers/getWordCount';
|
|
78
79
|
import ParamType from './types/ParamType';
|
|
79
80
|
import ModalButtonType from './types/ModalButtonType';
|
|
80
81
|
import ModalSize from './types/ModalSize';
|
|
@@ -99,4 +100,4 @@ import PickableItem from './components/ItemPicker/types/PickableItem';
|
|
|
99
100
|
import DBEntry from './components/DBEntryManagerPanel/types/DBEntry';
|
|
100
101
|
import DBEntryField from './components/DBEntryManagerPanel/types/DBEntryField';
|
|
101
102
|
import DBEntryFieldType from './components/DBEntryManagerPanel/types/DBEntryFieldType';
|
|
102
|
-
export { AppWrapper, LoadingSpinner, ErrorBox, Modal, TabBox, RadioButton, CheckboxButton, ButtonInputGroup, SimpleDateChooser, Drawer, PopSuccessMark, PopFailureMark, PopPendingMark, CopiableBox, ItemPicker, LogReviewer, IntelliTable, CSVDownloadButton, DBEntryManagerPanel, Tooltip, ToggleSwitch, AutoscrollToBottomContainer, MultiSwitch, Dropdown, alert, prompt, confirm, showFatalError, ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_ROUTE_PATH, LOG_REVIEW_STATUS_ROUTE, 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, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, initClient, visitServerEndpoint, logClientEvent, addFatalErrorHandler, leaveToURL, combineClassNames, useForceRender, setClientEventMetadataPopulator, ParamType, ModalButtonType, ModalSize, ModalType, ReactKitErrorCode, Variant, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, LogTypeSpecificInfo, LogMainInfo, LogSourceSpecificInfo, LogLevel, IntelliTableColumn, DropdownItemType, PickableItem, DBEntry, DBEntryField, DBEntryFieldType, };
|
|
103
|
+
export { AppWrapper, LoadingSpinner, ErrorBox, Modal, TabBox, RadioButton, CheckboxButton, ButtonInputGroup, SimpleDateChooser, Drawer, PopSuccessMark, PopFailureMark, PopPendingMark, CopiableBox, ItemPicker, LogReviewer, IntelliTable, CSVDownloadButton, DBEntryManagerPanel, Tooltip, ToggleSwitch, AutoscrollToBottomContainer, MultiSwitch, Dropdown, alert, prompt, confirm, showFatalError, ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_ROUTE_PATH, LOG_REVIEW_STATUS_ROUTE, 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, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, getWordCount, initClient, visitServerEndpoint, logClientEvent, addFatalErrorHandler, leaveToURL, combineClassNames, useForceRender, setClientEventMetadataPopulator, ParamType, ModalButtonType, ModalSize, ModalType, ReactKitErrorCode, Variant, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, LogTypeSpecificInfo, LogMainInfo, LogSourceSpecificInfo, LogLevel, IntelliTableColumn, DropdownItemType, PickableItem, DBEntry, DBEntryField, DBEntryFieldType, };
|
package/dist/index.d.ts
CHANGED
|
@@ -209,6 +209,8 @@ type ModalProps = {
|
|
|
209
209
|
confirmLabel?: string;
|
|
210
210
|
confirmVariant?: Variant;
|
|
211
211
|
onTopOfOtherModals?: boolean;
|
|
212
|
+
isLoading?: boolean;
|
|
213
|
+
isLoadingCancelable?: boolean;
|
|
212
214
|
};
|
|
213
215
|
|
|
214
216
|
/**
|
|
@@ -227,6 +229,7 @@ declare const Modal: React$1.FC<ModalProps>;
|
|
|
227
229
|
type Props$j = {
|
|
228
230
|
title: React$1.ReactNode;
|
|
229
231
|
children: React$1.ReactNode;
|
|
232
|
+
topRightChildren?: React$1.ReactNode;
|
|
230
233
|
noBottomMargin?: boolean;
|
|
231
234
|
noBottomPadding?: boolean;
|
|
232
235
|
};
|
|
@@ -412,6 +415,7 @@ type Props$9 = {
|
|
|
412
415
|
*/
|
|
413
416
|
onChanged: (updatedItems: PickableItem[]) => void;
|
|
414
417
|
noBottomMargin?: boolean;
|
|
418
|
+
hideSelectAllOrNoneButtons?: boolean;
|
|
415
419
|
};
|
|
416
420
|
declare const ItemPicker: React$1.FC<Props$9>;
|
|
417
421
|
|
|
@@ -1498,6 +1502,16 @@ declare const capitalize: (str: string) => string;
|
|
|
1498
1502
|
*/
|
|
1499
1503
|
declare const shuffleArray: <T>(arr: T[]) => T[];
|
|
1500
1504
|
|
|
1505
|
+
/**
|
|
1506
|
+
* Get number of words in string
|
|
1507
|
+
* @author Gardenia Liu
|
|
1508
|
+
* @author Allison Zhang
|
|
1509
|
+
* @author Gabe Abrams
|
|
1510
|
+
* @param text the string to check
|
|
1511
|
+
* @returns number of words in the string
|
|
1512
|
+
*/
|
|
1513
|
+
declare const getWordCount: (text: string) => number;
|
|
1514
|
+
|
|
1501
1515
|
/**
|
|
1502
1516
|
* Days of the week
|
|
1503
1517
|
* @author Gabe Abrams
|
|
@@ -1539,4 +1553,4 @@ declare enum ReactKitErrorCode {
|
|
|
1539
1553
|
NoCACCLSendRequestFunction = "DRK7"
|
|
1540
1554
|
}
|
|
1541
1555
|
|
|
1542
|
-
export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntry, DBEntryField, DBEntryFieldType, DBEntryManagerPanel, DayOfWeek, Drawer, Dropdown, DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, IntelliTableColumn, ItemPicker, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_REVIEW_STATUS_ROUTE, LOG_ROUTE_PATH, LoadingSpinner, Log, LogAction, LogBuiltInMetadata, LogFunction, LogLevel, LogMainInfo, LogMetadataType, LogReviewer, LogSource, LogSourceSpecificInfo, LogType, LogTypeSpecificInfo, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, MultiSwitch, ParamType, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, ToggleSwitch, Tooltip, Variant, abbreviate, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, idify, initClient, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
|
|
1556
|
+
export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntry, DBEntryField, DBEntryFieldType, DBEntryManagerPanel, DayOfWeek, Drawer, Dropdown, DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, IntelliTableColumn, ItemPicker, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_REVIEW_STATUS_ROUTE, LOG_ROUTE_PATH, LoadingSpinner, Log, LogAction, LogBuiltInMetadata, LogFunction, LogLevel, LogMainInfo, LogMetadataType, LogReviewer, LogSource, LogSourceSpecificInfo, LogType, LogTypeSpecificInfo, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, MultiSwitch, ParamType, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, ToggleSwitch, Tooltip, Variant, abbreviate, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, getWordCount, idify, initClient, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
|
package/package.json
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dce-reactkit",
|
|
3
|
-
"version": "4.0.0
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Shared components for Harvard DCE apps",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
9
|
"build": "rm -rf dist && rollup -c",
|
|
10
|
-
"test": "jest --runInBand"
|
|
11
|
-
"gen-reactkit-cross-server-secret": "npx tsx genEncodedSecret.ts"
|
|
10
|
+
"test": "jest --runInBand"
|
|
12
11
|
},
|
|
13
12
|
"jest": {
|
|
14
13
|
"preset": "ts-jest",
|
package/rollup.config.js
CHANGED
|
@@ -27,7 +27,6 @@ export default [
|
|
|
27
27
|
typescript({ tsconfig: "./tsconfig.json" }),
|
|
28
28
|
sourcemaps(),
|
|
29
29
|
],
|
|
30
|
-
inlineDynamicImports: true,
|
|
31
30
|
external: [
|
|
32
31
|
...Object.keys(packageJson.dependencies || {}),
|
|
33
32
|
...Object.keys(packageJson.peerDependencies || {}),
|
|
@@ -37,7 +36,6 @@ export default [
|
|
|
37
36
|
input: "dist/esm/types/index.d.ts",
|
|
38
37
|
output: [{ file: "dist/index.d.ts", format: "esm" }],
|
|
39
38
|
plugins: [dts()],
|
|
40
|
-
inlineDynamicImports: true,
|
|
41
39
|
external: [
|
|
42
40
|
...Object.keys(packageJson.dependencies || {}),
|
|
43
41
|
...Object.keys(packageJson.peerDependencies || {}),
|
|
@@ -315,7 +315,7 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
315
315
|
checked={columnVisibilityMap[column.param]}
|
|
316
316
|
ariaLabel={`show "${column.title}" column in the ${title} table`}
|
|
317
317
|
checkedVariant={Variant.Light}
|
|
318
|
-
uncheckedVariant={Variant.
|
|
318
|
+
uncheckedVariant={Variant.Light}
|
|
319
319
|
/>
|
|
320
320
|
);
|
|
321
321
|
})
|
|
@@ -33,6 +33,8 @@ type Props = {
|
|
|
33
33
|
onChanged: (updatedItems: PickableItem[]) => void,
|
|
34
34
|
// If true, don't add margin to bottom of item picker
|
|
35
35
|
noBottomMargin?: boolean,
|
|
36
|
+
// If true, hide select all or none buttons
|
|
37
|
+
hideSelectAllOrNoneButtons?: boolean,
|
|
36
38
|
};
|
|
37
39
|
|
|
38
40
|
/*------------------------------------------------------------------------*/
|
|
@@ -52,12 +54,61 @@ const ItemPicker: React.FC<Props> = (props) => {
|
|
|
52
54
|
items,
|
|
53
55
|
onChanged,
|
|
54
56
|
noBottomMargin,
|
|
57
|
+
hideSelectAllOrNoneButtons,
|
|
55
58
|
} = props;
|
|
56
59
|
|
|
57
60
|
/*------------------------------------------------------------------------*/
|
|
58
61
|
/* ------------------------- Component Functions ------------------------ */
|
|
59
62
|
/*------------------------------------------------------------------------*/
|
|
60
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Updates the checked state of an item (including all children)
|
|
66
|
+
* @author Yuen Ler Chow
|
|
67
|
+
* @param item the item to update
|
|
68
|
+
* @param checked the new checked state
|
|
69
|
+
* @returns the updated item
|
|
70
|
+
*/
|
|
71
|
+
const updateItemChecked = (
|
|
72
|
+
item: PickableItem,
|
|
73
|
+
checked: boolean,
|
|
74
|
+
): PickableItem => {
|
|
75
|
+
if (item.isGroup) {
|
|
76
|
+
return {
|
|
77
|
+
...item,
|
|
78
|
+
children: item.children.map((child) => {
|
|
79
|
+
return updateItemChecked(child, checked);
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { ...item, checked };
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Selects all items in the list
|
|
88
|
+
* @author Yuen Ler Chow
|
|
89
|
+
*/
|
|
90
|
+
const handleSelectAll = () => {
|
|
91
|
+
const updatedItems = items.map(
|
|
92
|
+
(item) => {
|
|
93
|
+
return updateItemChecked(item, true);
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
onChanged(updatedItems);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Deselects all items in the list
|
|
101
|
+
* @author Yuen Ler Chow
|
|
102
|
+
*/
|
|
103
|
+
const handleDeselectAll = () => {
|
|
104
|
+
const updatedItems = items.map(
|
|
105
|
+
(item) => {
|
|
106
|
+
return updateItemChecked(item, false);
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
onChanged(updatedItems);
|
|
110
|
+
};
|
|
111
|
+
|
|
61
112
|
/*------------------------------------------------------------------------*/
|
|
62
113
|
/* ------------------------------- Render ------------------------------- */
|
|
63
114
|
/*------------------------------------------------------------------------*/
|
|
@@ -66,10 +117,55 @@ const ItemPicker: React.FC<Props> = (props) => {
|
|
|
66
117
|
/* --------------- Main UI -------------- */
|
|
67
118
|
/*----------------------------------------*/
|
|
68
119
|
|
|
120
|
+
// Select all/none
|
|
121
|
+
const selectAllOrNone = (
|
|
122
|
+
!hideSelectAllOrNoneButtons
|
|
123
|
+
? (
|
|
124
|
+
<div
|
|
125
|
+
className="d-flex h-100 align-items-end flex-row"
|
|
126
|
+
>
|
|
127
|
+
<div className="d-flex justify-content-end">
|
|
128
|
+
{/* Label */}
|
|
129
|
+
<div
|
|
130
|
+
className="me-2"
|
|
131
|
+
style={{ fontSize: '1.2rem' }}
|
|
132
|
+
>
|
|
133
|
+
Select
|
|
134
|
+
</div>
|
|
135
|
+
{/* Buttons */}
|
|
136
|
+
<div className="btn-group" role="group">
|
|
137
|
+
{/* All */}
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
style={{ borderRight: '0.1rem solid white' }}
|
|
141
|
+
aria-label="Select all contexts"
|
|
142
|
+
className="btn btn-secondary py-0"
|
|
143
|
+
onClick={handleSelectAll}
|
|
144
|
+
>
|
|
145
|
+
All
|
|
146
|
+
</button>
|
|
147
|
+
{/* None */}
|
|
148
|
+
<button
|
|
149
|
+
type="button"
|
|
150
|
+
aria-label="Deselect all contexts"
|
|
151
|
+
className="btn btn-secondary py-0"
|
|
152
|
+
onClick={handleDeselectAll}
|
|
153
|
+
>
|
|
154
|
+
None
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
)
|
|
160
|
+
: undefined
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Main UI
|
|
69
164
|
return (
|
|
70
165
|
<TabBox
|
|
71
166
|
title={title}
|
|
72
167
|
noBottomMargin={noBottomMargin}
|
|
168
|
+
topRightChildren={selectAllOrNone}
|
|
73
169
|
>
|
|
74
170
|
<div style={{ overflowX: 'auto' }}>
|
|
75
171
|
<NestableItemList
|
|
@@ -69,6 +69,10 @@ type ModalProps = {
|
|
|
69
69
|
confirmVariant?: Variant,
|
|
70
70
|
// True if modal should be on top of other modals
|
|
71
71
|
onTopOfOtherModals?: boolean,
|
|
72
|
+
// If true, the modal is loading
|
|
73
|
+
isLoading?: boolean,
|
|
74
|
+
// If true, the loading is cancelable
|
|
75
|
+
isLoadingCancelable?: boolean,
|
|
72
76
|
};
|
|
73
77
|
|
|
74
78
|
export default ModalProps;
|
|
@@ -14,6 +14,9 @@ import React, { useState, useEffect, useRef } from 'react';
|
|
|
14
14
|
import ReactDOM from 'react-dom';
|
|
15
15
|
|
|
16
16
|
// Import other components
|
|
17
|
+
import LoadingSpinner from '../LoadingSpinner';
|
|
18
|
+
|
|
19
|
+
// Import helpers
|
|
17
20
|
import waitMs from '../../helpers/waitMs';
|
|
18
21
|
|
|
19
22
|
// Import types
|
|
@@ -41,6 +44,7 @@ const BASE_Z_INDEX_ON_TOP = 2000000000;
|
|
|
41
44
|
|
|
42
45
|
// Constants
|
|
43
46
|
const MS_TO_ANIMATE = 200; // Animation duration
|
|
47
|
+
const MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR = 1000;
|
|
44
48
|
|
|
45
49
|
// Modal type to list of buttons
|
|
46
50
|
const modalTypeToModalButtonTypes: {
|
|
@@ -256,6 +260,8 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
256
260
|
dontAllowBackdropExit,
|
|
257
261
|
dontShowXButton,
|
|
258
262
|
onTopOfOtherModals,
|
|
263
|
+
isLoading,
|
|
264
|
+
isLoadingCancelable,
|
|
259
265
|
} = props;
|
|
260
266
|
|
|
261
267
|
// Determine if no header either
|
|
@@ -266,6 +272,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
266
272
|
// True if animation is in use
|
|
267
273
|
const [animatingIn, setAnimatingIn] = useState(true);
|
|
268
274
|
const [animatingPop, setAnimatingPop] = useState(false);
|
|
275
|
+
const [showModal, setShowModal] = useState(false);
|
|
269
276
|
|
|
270
277
|
/* -------------- Refs -------------- */
|
|
271
278
|
|
|
@@ -298,6 +305,15 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
298
305
|
if (mounted.current) {
|
|
299
306
|
setAnimatingIn(false);
|
|
300
307
|
}
|
|
308
|
+
if (isLoading) {
|
|
309
|
+
// wait before showing modal
|
|
310
|
+
await waitMs(MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR);
|
|
311
|
+
if (mounted.current) {
|
|
312
|
+
setShowModal(true);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
setShowModal(true);
|
|
316
|
+
}
|
|
301
317
|
})();
|
|
302
318
|
|
|
303
319
|
return () => {
|
|
@@ -400,6 +416,17 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
400
416
|
animationClass = 'Modal-animating-pop';
|
|
401
417
|
}
|
|
402
418
|
|
|
419
|
+
// default to show close button when not loading and not show close button when loading
|
|
420
|
+
// unless downShowXButton or isLoadingCancelable
|
|
421
|
+
const showCloseButton = (
|
|
422
|
+
// The modal must have an onClose function
|
|
423
|
+
onClose
|
|
424
|
+
// ...and the close button is allowed to be visible
|
|
425
|
+
&& !dontShowXButton
|
|
426
|
+
// ...and should not be shown if the modal is loading and not cancelable
|
|
427
|
+
&& !(isLoading && !isLoadingCancelable)
|
|
428
|
+
);
|
|
429
|
+
|
|
403
430
|
// Render the modal
|
|
404
431
|
const contentToRender = (
|
|
405
432
|
<div
|
|
@@ -436,6 +463,8 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
436
463
|
handleClose(ModalButtonType.Cancel);
|
|
437
464
|
}}
|
|
438
465
|
/>
|
|
466
|
+
|
|
467
|
+
{showModal && (
|
|
439
468
|
<div
|
|
440
469
|
className={`modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`}
|
|
441
470
|
style={{
|
|
@@ -498,7 +527,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
498
527
|
{title}
|
|
499
528
|
</h5>
|
|
500
529
|
|
|
501
|
-
{
|
|
530
|
+
{showCloseButton && (
|
|
502
531
|
<button
|
|
503
532
|
type="button"
|
|
504
533
|
className="Modal-x-button btn-close"
|
|
@@ -518,25 +547,34 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
518
547
|
)}
|
|
519
548
|
</div>
|
|
520
549
|
)}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
550
|
+
|
|
551
|
+
<div
|
|
552
|
+
className="modal-body"
|
|
553
|
+
style={{
|
|
554
|
+
color: (
|
|
555
|
+
isDarkModeOn()
|
|
556
|
+
? 'white'
|
|
557
|
+
: undefined
|
|
558
|
+
),
|
|
559
|
+
backgroundColor: (
|
|
560
|
+
isDarkModeOn()
|
|
561
|
+
? '#444'
|
|
562
|
+
: undefined
|
|
563
|
+
),
|
|
564
|
+
}}
|
|
565
|
+
>
|
|
566
|
+
{
|
|
567
|
+
isLoading
|
|
568
|
+
? (
|
|
569
|
+
<>
|
|
570
|
+
<LoadingSpinner />
|
|
571
|
+
<span className="sr-only">Content loading</span>
|
|
572
|
+
</>
|
|
573
|
+
)
|
|
574
|
+
: children
|
|
575
|
+
}
|
|
576
|
+
</div>
|
|
577
|
+
|
|
540
578
|
{footer && (
|
|
541
579
|
<div
|
|
542
580
|
className="modal-footer pt-1 pb-1"
|
|
@@ -563,6 +601,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
563
601
|
)}
|
|
564
602
|
</div>
|
|
565
603
|
</div>
|
|
604
|
+
)}
|
|
566
605
|
</div>
|
|
567
606
|
);
|
|
568
607
|
|
|
@@ -15,6 +15,8 @@ type Props = {
|
|
|
15
15
|
title: React.ReactNode,
|
|
16
16
|
// Children/contents inside the box
|
|
17
17
|
children: React.ReactNode,
|
|
18
|
+
// Children to display on the top right of the tab box
|
|
19
|
+
topRightChildren?: React.ReactNode,
|
|
18
20
|
// If true, don't add margin below the tab box
|
|
19
21
|
noBottomMargin?: boolean,
|
|
20
22
|
// If true, don't add padding to bottom of tab box
|
|
@@ -49,7 +51,9 @@ const style = `
|
|
|
49
51
|
|
|
50
52
|
/* Container for Title */
|
|
51
53
|
.TabBox-title-container {
|
|
52
|
-
|
|
54
|
+
display: flex;
|
|
55
|
+
justify-content: space-between;
|
|
56
|
+
align-items: center;
|
|
53
57
|
position: relative;
|
|
54
58
|
left: 0;
|
|
55
59
|
text-align: left;
|
|
@@ -82,6 +86,19 @@ const style = `
|
|
|
82
86
|
background: #fdfdfd;
|
|
83
87
|
}
|
|
84
88
|
|
|
89
|
+
.TabBox-title-right-container {
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: row;
|
|
92
|
+
align-items: bottom;
|
|
93
|
+
height: 2.4rem;
|
|
94
|
+
overflow: visible;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.TabBox-title-right-contents {
|
|
98
|
+
margin-right: 0.5rem;
|
|
99
|
+
margin-bottom: 0.2rem;
|
|
100
|
+
}
|
|
101
|
+
|
|
85
102
|
/* Make the TabBox's Children Appear Above Title if Overlap Occurs */
|
|
86
103
|
.TabBox-children {
|
|
87
104
|
position: relative;
|
|
@@ -98,11 +115,10 @@ const TabBox: React.FC<Props> = (props) => {
|
|
|
98
115
|
/* -------------------------------- Setup ------------------------------- */
|
|
99
116
|
/*------------------------------------------------------------------------*/
|
|
100
117
|
|
|
101
|
-
/* -------------- Props ------------- */
|
|
102
|
-
|
|
103
118
|
const {
|
|
104
119
|
title,
|
|
105
120
|
children,
|
|
121
|
+
topRightChildren,
|
|
106
122
|
noBottomPadding,
|
|
107
123
|
noBottomMargin,
|
|
108
124
|
} = props;
|
|
@@ -111,21 +127,23 @@ const TabBox: React.FC<Props> = (props) => {
|
|
|
111
127
|
/* ------------------------------- Render ------------------------------- */
|
|
112
128
|
/*------------------------------------------------------------------------*/
|
|
113
129
|
|
|
114
|
-
/*----------------------------------------*/
|
|
115
|
-
/* --------------- Main UI -------------- */
|
|
116
|
-
/*----------------------------------------*/
|
|
117
|
-
|
|
118
|
-
// Full UI
|
|
119
130
|
return (
|
|
120
131
|
<div className={`TabBox-container ${noBottomMargin ? '' : 'mb-2'}`}>
|
|
121
132
|
{/* Style */}
|
|
122
133
|
<style>{style}</style>
|
|
123
134
|
|
|
124
|
-
{/* Title */}
|
|
135
|
+
{/* Title Row with Left and Right sections */}
|
|
125
136
|
<div className="TabBox-title-container">
|
|
126
137
|
<div className="TabBox-title">
|
|
127
138
|
{title}
|
|
128
139
|
</div>
|
|
140
|
+
{topRightChildren && (
|
|
141
|
+
<div className="TabBox-title-right-container">
|
|
142
|
+
<div className="TabBox-title-right-contents">
|
|
143
|
+
{topRightChildren}
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
)}
|
|
129
147
|
</div>
|
|
130
148
|
|
|
131
149
|
{/* Contents */}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const punctuationRegex = /[!@#$%^&*(),.?\/;:'"\-\[\]]/g;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Get number of words in string
|
|
5
|
+
* @author Gardenia Liu
|
|
6
|
+
* @author Allison Zhang
|
|
7
|
+
* @author Gabe Abrams
|
|
8
|
+
* @param text the string to check
|
|
9
|
+
* @returns number of words in the string
|
|
10
|
+
*/
|
|
11
|
+
const getWordCount = (text: string): number => {
|
|
12
|
+
const trimmedTextWithoutPunctuation = (
|
|
13
|
+
text
|
|
14
|
+
// Remove leading and trailing whitespace
|
|
15
|
+
.trim()
|
|
16
|
+
// Remove punctuation
|
|
17
|
+
.replace(punctuationRegex, '')
|
|
18
|
+
);
|
|
19
|
+
if (trimmedTextWithoutPunctuation.length === 0) {
|
|
20
|
+
return 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return trimmedTextWithoutPunctuation.split(/\s+/g).length;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export default getWordCount;
|
package/src/index.ts
CHANGED
|
@@ -91,6 +91,7 @@ import mapAsync from './helpers/asyncArrayFunctions/mapAsync';
|
|
|
91
91
|
import someAsync from './helpers/asyncArrayFunctions/someAsync';
|
|
92
92
|
import capitalize from './helpers/capitalize';
|
|
93
93
|
import shuffleArray from './helpers/shuffleArray';
|
|
94
|
+
import getWordCount from './helpers/getWordCount';
|
|
94
95
|
|
|
95
96
|
// Import types
|
|
96
97
|
import ParamType from './types/ParamType';
|
|
@@ -204,6 +205,7 @@ export {
|
|
|
204
205
|
someAsync,
|
|
205
206
|
capitalize,
|
|
206
207
|
shuffleArray,
|
|
208
|
+
getWordCount,
|
|
207
209
|
// Client helpers
|
|
208
210
|
initClient,
|
|
209
211
|
visitServerEndpoint,
|
package/genEncodedSecret.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
// Import crypto lib
|
|
2
|
-
import crypto from 'crypto';
|
|
3
|
-
|
|
4
|
-
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
5
|
-
|
|
6
|
-
// Get args
|
|
7
|
-
const REACTKIT_CRED_ENCODING_SALT = process.env.npm_config_salt;
|
|
8
|
-
if (!REACTKIT_CRED_ENCODING_SALT) {
|
|
9
|
-
console.log('Encoding salt is required: --salt=...');
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Get the key
|
|
14
|
-
let key = process.env.npm_config_key;
|
|
15
|
-
if (!key) {
|
|
16
|
-
console.log('Key is required: --key=...');
|
|
17
|
-
process.exit(1);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Get the description
|
|
21
|
-
let description = process.env.npm_config_description;
|
|
22
|
-
if (!description) {
|
|
23
|
-
console.log('Description is required: --description=...');
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Get secret
|
|
28
|
-
let secret = process.env.npm_config_secret;
|
|
29
|
-
if (!secret) {
|
|
30
|
-
// Generate a random secret
|
|
31
|
-
secret = '';
|
|
32
|
-
for (let i = 0; i < 32; i++) {
|
|
33
|
-
secret += chars.charAt(Math.floor(Math.random() * chars.length));
|
|
34
|
-
}
|
|
35
|
-
console.log('Generated a random secret. If you have one in mind, use --secret=...');
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Get the host name
|
|
39
|
-
const host = process.env.npm_config_host;
|
|
40
|
-
if (!host) {
|
|
41
|
-
console.log('Host of the receiving server is required: --host=...');
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Encryption process based on:
|
|
46
|
-
// https://medium.com/@tony.infisical/guide-to-nodes-crypto-module-for-encryption-decryption-65c077176980
|
|
47
|
-
|
|
48
|
-
// Create a random initialization vector
|
|
49
|
-
const iv = crypto.randomBytes(12).toString('base64');
|
|
50
|
-
|
|
51
|
-
// Create a cipher
|
|
52
|
-
const cipher = crypto.createCipheriv(
|
|
53
|
-
'aes-256-gcm',
|
|
54
|
-
Buffer.from(secret, 'base64'),
|
|
55
|
-
Buffer.from(iv, 'base64'),
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
// Encrypt the string
|
|
59
|
-
let ciphertext = cipher.update(secret, 'utf8', 'base64');
|
|
60
|
-
|
|
61
|
-
// Finalize the encryption
|
|
62
|
-
ciphertext += cipher.final('base64');
|
|
63
|
-
|
|
64
|
-
// Get the authentication tag
|
|
65
|
-
const tag = cipher.getAuthTag();
|
|
66
|
-
|
|
67
|
-
// JSONify the encrypted data
|
|
68
|
-
const encryptionPack = encodeURIComponent(JSON.stringify({
|
|
69
|
-
ciphertext,
|
|
70
|
-
iv,
|
|
71
|
-
tag,
|
|
72
|
-
}));
|
|
73
|
-
|
|
74
|
-
// Show the encrypted data
|
|
75
|
-
console.log('\n\n');
|
|
76
|
-
console.log('––––– Done! What\'s Next: –––––');
|
|
77
|
-
console.log('');
|
|
78
|
-
console.log('On the server *sending* the requests, append the following to the REACTKIT_CROSS_SERVER_CREDENTIALS env var:');
|
|
79
|
-
console.log(`|${host}:${key}:${secret}|`);
|
|
80
|
-
console.log('');
|
|
81
|
-
console.log('On the server *receiving* the requests, add an entry to the "CrossServerCredential" collection:');
|
|
82
|
-
console.log(`{ "description": "${description}", "key": "${key}", "encodedeSecret": "${encryptionPack}", "scopes": [] }`);
|
|
83
|
-
console.log('');
|
|
84
|
-
console.log('For all scopes that the server should have access to, add them to the "scopes" array.');
|