@pie-lib/render-ui 7.0.2 → 8.0.0-next.20261002094057

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.
Files changed (105) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +11 -0
  2. package/dist/assets/enableAudioAutoplayImage.d.ts +10 -0
  3. package/dist/assets/enableAudioAutoplayImage.js +4 -0
  4. package/dist/collapsible/index.d.ts +31 -0
  5. package/dist/collapsible/index.js +53 -0
  6. package/dist/color.d.ts +128 -0
  7. package/dist/color.js +132 -0
  8. package/dist/feedback.d.ts +20 -0
  9. package/dist/feedback.js +65 -0
  10. package/dist/has-media.d.ts +9 -0
  11. package/dist/has-media.js +10 -0
  12. package/dist/has-text.d.ts +9 -0
  13. package/dist/has-text.js +10 -0
  14. package/dist/html-and-math.d.ts +22 -0
  15. package/dist/html-and-math.js +25 -0
  16. package/dist/index.d.ts +26 -0
  17. package/dist/index.js +18 -0
  18. package/dist/inline-menu.d.ts +33 -0
  19. package/dist/inline-menu.js +29 -0
  20. package/dist/input-container.d.ts +23 -0
  21. package/dist/input-container.js +39 -0
  22. package/dist/preview-layout.d.ts +24 -0
  23. package/dist/preview-layout.js +37 -0
  24. package/dist/preview-prompt.d.ts +39 -0
  25. package/dist/preview-prompt.js +153 -0
  26. package/dist/purpose.d.ts +18 -0
  27. package/dist/purpose.js +11 -0
  28. package/dist/readable.d.ts +18 -0
  29. package/dist/readable.js +11 -0
  30. package/dist/response-indicators.d.ts +121 -0
  31. package/dist/response-indicators.js +62 -0
  32. package/{src/transform-headings.js → dist/transform-headings.d.ts} +9 -30
  33. package/dist/transform-headings.js +15 -0
  34. package/dist/ui-layout.d.ts +31 -0
  35. package/dist/ui-layout.js +55 -0
  36. package/dist/withUndoReset.d.ts +47 -0
  37. package/dist/withUndoReset.js +84 -0
  38. package/package.json +29 -20
  39. package/CHANGELOG.json +0 -1
  40. package/CHANGELOG.md +0 -1184
  41. package/LICENSE.md +0 -5
  42. package/lib/assets/enableAudioAutoplayImage.js +0 -8
  43. package/lib/assets/enableAudioAutoplayImage.js.map +0 -1
  44. package/lib/collapsible/index.js +0 -105
  45. package/lib/collapsible/index.js.map +0 -1
  46. package/lib/color.js +0 -253
  47. package/lib/color.js.map +0 -1
  48. package/lib/feedback.js +0 -112
  49. package/lib/feedback.js.map +0 -1
  50. package/lib/has-media.js +0 -22
  51. package/lib/has-media.js.map +0 -1
  52. package/lib/has-text.js +0 -22
  53. package/lib/has-text.js.map +0 -1
  54. package/lib/html-and-math.js +0 -54
  55. package/lib/html-and-math.js.map +0 -1
  56. package/lib/index.js +0 -113
  57. package/lib/index.js.map +0 -1
  58. package/lib/input-container.js +0 -59
  59. package/lib/input-container.js.map +0 -1
  60. package/lib/preview-layout.js +0 -65
  61. package/lib/preview-layout.js.map +0 -1
  62. package/lib/preview-prompt.js +0 -316
  63. package/lib/preview-prompt.js.map +0 -1
  64. package/lib/purpose.js +0 -22
  65. package/lib/purpose.js.map +0 -1
  66. package/lib/readable.js +0 -22
  67. package/lib/readable.js.map +0 -1
  68. package/lib/response-indicators.js +0 -102
  69. package/lib/response-indicators.js.map +0 -1
  70. package/lib/transform-headings.js +0 -53
  71. package/lib/transform-headings.js.map +0 -1
  72. package/lib/ui-layout.js +0 -125
  73. package/lib/ui-layout.js.map +0 -1
  74. package/lib/withUndoReset.js +0 -135
  75. package/lib/withUndoReset.js.map +0 -1
  76. package/src/__tests__/color.test.js +0 -259
  77. package/src/__tests__/feedback.test.jsx +0 -279
  78. package/src/__tests__/has-media.test.js +0 -19
  79. package/src/__tests__/has-text.test.js +0 -20
  80. package/src/__tests__/html-and-math.test.js +0 -36
  81. package/src/__tests__/input-container.test.jsx +0 -328
  82. package/src/__tests__/preview-layout.test.jsx +0 -349
  83. package/src/__tests__/preview-prompt.test.jsx +0 -464
  84. package/src/__tests__/purpose.test.jsx +0 -51
  85. package/src/__tests__/readable.test.jsx +0 -69
  86. package/src/__tests__/response-indicators.test.jsx +0 -111
  87. package/src/__tests__/ui-layout.test.jsx +0 -52
  88. package/src/__tests__/withUndoReset.test.jsx +0 -176
  89. package/src/assets/enableAudioAutoplayImage.js +0 -1
  90. package/src/collapsible/__tests__/index.test.jsx +0 -39
  91. package/src/collapsible/index.jsx +0 -64
  92. package/src/color.js +0 -151
  93. package/src/feedback.jsx +0 -84
  94. package/src/has-media.js +0 -16
  95. package/src/has-text.js +0 -18
  96. package/src/html-and-math.js +0 -21
  97. package/src/index.js +0 -35
  98. package/src/input-container.jsx +0 -47
  99. package/src/preview-layout.jsx +0 -41
  100. package/src/preview-prompt.jsx +0 -300
  101. package/src/purpose.jsx +0 -17
  102. package/src/readable.jsx +0 -19
  103. package/src/response-indicators.jsx +0 -82
  104. package/src/ui-layout.jsx +0 -96
  105. package/src/withUndoReset.jsx +0 -114
@@ -1,300 +0,0 @@
1
- import React, { Component } from 'react';
2
- import { styled } from '@mui/material/styles';
3
- import PropTypes from 'prop-types';
4
- import * as color from './color';
5
- import { renderMath } from '@pie-lib/math-rendering';
6
-
7
- const StyledPromptContainer = styled('div')(({ theme, tagName }) => ({
8
- // presentation tables should not have any custom style
9
- // Base promptTable styles
10
- '&:not(.MathJax) > table:not([role="presentation"])': {
11
- borderCollapse: 'collapse',
12
- },
13
- /*
14
- * The editor writes border="1", and the UA answers it with `border-style: outset`
15
- * on the table and `inset` on the cells -- which browsers paint as synthesized 3D
16
- * shades rather than the colour the border resolves to, so the grid comes out grey
17
- * on every scheme (1.19:1 on black-on-violet). `solid` is what removes the
18
- * shading; without it, setting the colour has no effect.
19
- */
20
- '&:not(.MathJax) table[border]:not([role="presentation"]), &:not(.MathJax) table[border]:not([role="presentation"]) td, &:not(.MathJax) table[border]:not([role="presentation"]) th':
21
- {
22
- borderStyle: 'solid',
23
- borderColor: color.tableGrid(),
24
- },
25
- // Apply vertical striping when first column is a header (th) and NOT mixed with td.
26
- // Ink stays at 5.44:1 or better on tableStripe in every scheme, so the cell inherits
27
- // its text colour rather than pinning the `color: black` this used to carry.
28
- '&:not(.MathJax) > table:not([role="presentation"]):has(tbody tr > th:first-child):not(:has(tbody tr > td:first-child)) tbody td:nth-child(even)':
29
- {
30
- backgroundColor: color.tableStripe(),
31
- },
32
- // Apply horizontal striping for tables where first element is a data cell (td)
33
- '&:not(.MathJax) > table:not([role="presentation"]):has(tbody tr > td:first-child) tbody tr:nth-child(even) td': {
34
- backgroundColor: color.tableStripe(),
35
- },
36
- // align table content to left as per STAR requirement PD-3687
37
- '&:not(.MathJax) table:not([role="presentation"]) td, &:not(.MathJax) table:not([role="presentation"]) th': {
38
- padding: '.6em 1em',
39
- textAlign: 'left',
40
- },
41
- // added this to fix alignment of text in prompt imported from studio (PD-3423)
42
- '&:not(.MathJax) > table td > p.kds-indent': {
43
- textAlign: 'initial',
44
- },
45
-
46
- // Conditional styles based on class names
47
- '&.prompt': {
48
- verticalAlign: 'middle',
49
- color: color.text(),
50
- },
51
- '&.legend': {
52
- width: '100%',
53
- fontSize: 'inherit !important',
54
- },
55
- '&.rationale': {
56
- paddingLeft: theme.spacing(4),
57
- paddingBottom: theme.spacing(1),
58
- },
59
- '&.prompt-label': {
60
- color: `${color.text()} !important`,
61
- display: 'flex',
62
- flexDirection: 'column',
63
- verticalAlign: 'middle',
64
- cursor: 'pointer',
65
- '& > p': {
66
- margin: '0 0 0 0 !important',
67
- },
68
- },
69
- }));
70
-
71
- //Used these below to replace \\embed{newLine} with \\newline from prompt which will get parsed in MathJax
72
- const NEWLINE_BLOCK_REGEX = /\\embed\{newLine\}\[\]/g;
73
- const NEWLINE_LATEX = '\\newline ';
74
-
75
- // stable hook for 'is this node inside a prompt' checks - a class rather than an id,
76
- // so it stays valid when a page renders more than one prompt
77
- const PROMPT_CLASS = 'preview-prompt';
78
-
79
- export class PreviewPrompt extends Component {
80
- static propTypes = {
81
- prompt: PropTypes.string,
82
- tagName: PropTypes.string,
83
- className: PropTypes.string,
84
- onClick: PropTypes.func,
85
- defaultClassName: PropTypes.string,
86
- autoplayAudioEnabled: PropTypes.bool,
87
- customAudioButton: {
88
- playImage: PropTypes.string,
89
- pauseImage: PropTypes.string,
90
- },
91
- };
92
-
93
- static defaultProps = {
94
- onClick: () => {},
95
- };
96
-
97
- promptRef = React.createRef();
98
-
99
- parsedText = (text) => {
100
- const { customAudioButton } = this.props;
101
- const div = document.createElement('div');
102
- div.innerHTML = text;
103
-
104
- const audio = div.querySelector('audio');
105
- if (audio) {
106
- const source = document.createElement('source');
107
-
108
- source.setAttribute('type', 'audio/mp3');
109
- source.setAttribute('src', audio.getAttribute('src'));
110
-
111
- audio.removeAttribute('src');
112
- audio.setAttribute('id', 'pie-prompt-audio-player');
113
-
114
- audio.appendChild(source);
115
-
116
- if (customAudioButton) {
117
- audio.style.display = 'none';
118
-
119
- const playButton = document.createElement('div');
120
- playButton.id = 'play-audio-button';
121
-
122
- Object.assign(playButton.style, {
123
- cursor: 'pointer',
124
- display: 'block',
125
- width: '128px',
126
- height: '128px',
127
- backgroundImage: `url(${customAudioButton.pauseImage})`,
128
- backgroundSize: 'cover',
129
- borderRadius: '50%',
130
- border: '1px solid #326295',
131
- });
132
-
133
- audio.parentNode.insertBefore(playButton, audio);
134
- }
135
- }
136
-
137
- return div.innerHTML;
138
- };
139
-
140
- addCustomAudioButtonControls() {
141
- const { autoplayAudioEnabled, customAudioButton } = this.props;
142
- const playButton = document.getElementById('play-audio-button');
143
- const audio = document.getElementById('pie-prompt-audio-player');
144
-
145
- if (autoplayAudioEnabled && audio) {
146
- audio
147
- .play()
148
- .then(() => {
149
- if (playButton && customAudioButton) {
150
- audio.addEventListener('ended', handleAudioEnded);
151
- }
152
- })
153
- .catch((error) => {
154
- console.error('Error playing audio', error);
155
- });
156
- }
157
-
158
- if (!playButton || !audio || !customAudioButton) return;
159
-
160
- const handlePlayClick = () => {
161
- // if already playing, don't play again
162
- if (!audio.paused) return;
163
- if (playButton.style.backgroundImage.includes(customAudioButton.pauseImage)) return;
164
-
165
- audio.play();
166
- };
167
-
168
- const handleAudioEnded = () => {
169
- playButton.style.backgroundImage = `url(${customAudioButton.playImage})`;
170
- };
171
-
172
- const handleAudioPlay = () => {
173
- Object.assign(playButton.style, {
174
- backgroundImage: `url(${customAudioButton.pauseImage})`,
175
- border: '1px solid #ccc',
176
- });
177
- };
178
-
179
- const handleAudioPause = () => {
180
- Object.assign(playButton.style, {
181
- backgroundImage: `url(${customAudioButton.playImage})`,
182
- border: '1px solid #326295',
183
- });
184
- };
185
-
186
- playButton.addEventListener('click', handlePlayClick);
187
- audio.addEventListener('play', handleAudioPlay);
188
- audio.addEventListener('pause', handleAudioPause);
189
- audio.addEventListener('ended', handleAudioEnded);
190
-
191
- // store event handler references so they can be removed later
192
- this._handlePlayClick = handlePlayClick;
193
- this._handleAudioPlay = handleAudioPlay;
194
- this._handleAudioPause = handleAudioPause;
195
- this._handleAudioEnded = handleAudioEnded;
196
- }
197
-
198
- removeCustomAudioButtonListeners() {
199
- const playButton = document.getElementById('play-audio-button');
200
- const audio = document.querySelector('audio');
201
-
202
- if (!playButton || !audio) return;
203
-
204
- // remove event listeners using stored references
205
- playButton.removeEventListener('click', this._handlePlayClick);
206
- audio.removeEventListener('play', this._handleAudioPlay);
207
- audio.removeEventListener('pause', this._handleAudioPause);
208
- audio.removeEventListener('ended', this._handleAudioEnded);
209
- }
210
-
211
- componentDidMount() {
212
- this.alignImages();
213
- this.addCustomAudioButtonControls();
214
- this.setupMathRendering();
215
- }
216
-
217
- componentDidUpdate(prevProps) {
218
- this.alignImages();
219
-
220
- if (prevProps.prompt !== this.props.prompt) {
221
- this.renderMathContent();
222
- }
223
- }
224
-
225
- componentWillUnmount() {
226
- this.removeCustomAudioButtonListeners();
227
- }
228
-
229
- setupMathRendering() {
230
- this.renderMathContent();
231
- }
232
-
233
- renderMathContent() {
234
- const container = this.promptRef.current;
235
- if (container && typeof renderMath === 'function') {
236
- renderMath(container);
237
- }
238
- }
239
-
240
- alignImages() {
241
- const previewPrompt = this.promptRef.current;
242
-
243
- if (!previewPrompt) {
244
- return;
245
- }
246
-
247
- const images = previewPrompt.getElementsByTagName('img');
248
-
249
- if (images && images.length) {
250
- for (let image of images) {
251
- if (image.attributes && image.attributes.alignment && image.attributes.alignment.value) {
252
- const alignment = image.attributes.alignment.value;
253
- const justifyContent =
254
- alignment === 'center' ? 'center' : alignment === 'right' ? 'flex-end' : 'flex-start';
255
-
256
- const parentNode = image.parentElement;
257
-
258
- if (
259
- parentNode.tagName === 'DIV' &&
260
- parentNode.style.display === 'flex' &&
261
- parentNode.style.width === '100%'
262
- ) {
263
- parentNode.style.justifyContent = justifyContent;
264
- } else {
265
- const div = document.createElement('div');
266
- div.style.display = 'flex';
267
- div.style.width = '100%';
268
- div.style.justifyContent = justifyContent;
269
-
270
- const copyImage = image.cloneNode(true);
271
- div.appendChild(copyImage);
272
- parentNode.replaceChild(div, image);
273
- }
274
- }
275
- }
276
- }
277
- }
278
-
279
- render() {
280
- const { prompt, tagName, className, onClick, defaultClassName } = this.props;
281
- // legend tag was added once with accessibility tasks, we need extra style to make it work with images alignment
282
- const legendClass = tagName === 'legend' ? 'legend' : '';
283
- const customClasses = `${className || ''} ${defaultClassName || ''} ${legendClass} ${PROMPT_CLASS}`.trim();
284
-
285
- return (
286
- <StyledPromptContainer
287
- as={tagName || 'div'}
288
- ref={this.promptRef}
289
- onClick={onClick}
290
- className={customClasses}
291
- tagName={tagName}
292
- dangerouslySetInnerHTML={{
293
- __html: this.parsedText(prompt || '').replace(NEWLINE_BLOCK_REGEX, NEWLINE_LATEX),
294
- }}
295
- />
296
- );
297
- }
298
- }
299
-
300
- export default PreviewPrompt;
package/src/purpose.jsx DELETED
@@ -1,17 +0,0 @@
1
- import PropTypes from 'prop-types';
2
- import React from 'react';
3
-
4
- const Purpose = (props) => {
5
- return (
6
- <>
7
- {React.Children.map(props.children, (child) => React.cloneElement(child, { 'data-pie-purpose': props.purpose }))}
8
- </>
9
- );
10
- };
11
-
12
- Purpose.propTypes = {
13
- children: PropTypes.node,
14
- purpose: PropTypes.string,
15
- };
16
-
17
- export default Purpose;
package/src/readable.jsx DELETED
@@ -1,19 +0,0 @@
1
- import PropTypes from 'prop-types';
2
- import React from 'react';
3
-
4
- const Readable = (props) => {
5
- return (
6
- <>
7
- {React.Children.map(props.children, (child) =>
8
- React.cloneElement(child, { 'data-pie-readable': props.false === undefined }),
9
- )}
10
- </>
11
- );
12
- };
13
-
14
- Readable.propTypes = {
15
- children: PropTypes.node,
16
- false: PropTypes.bool,
17
- };
18
-
19
- export default Readable;
@@ -1,82 +0,0 @@
1
- import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import * as icons from '@pie-lib/icons';
4
- import Popover from '@mui/material/Popover';
5
- import { styled } from '@mui/material/styles';
6
- import Feedback from './feedback';
7
- import debug from 'debug';
8
-
9
- const log = debug('pie-libs:render-ui:response-indicators');
10
-
11
- const ResponseIndicatorContainer = styled('div')(({ hasFeedback }) => ({
12
- cursor: hasFeedback ? 'pointer' : 'default',
13
- }));
14
-
15
- const StyledPopover = styled(Popover)({
16
- cursor: 'pointer',
17
- });
18
-
19
- const PopoverPaper = styled('div')({
20
- padding: '0',
21
- borderRadius: '4px',
22
- });
23
-
24
- const BuildIndicator = (Icon, correctness) => {
25
- class Indicator extends React.Component {
26
- constructor(props) {
27
- super(props);
28
- this.state = {};
29
- }
30
-
31
- handlePopoverOpen = (event) => {
32
- log('[handlePopoverOpen]', event.target);
33
- this.setState({ anchorEl: event.target });
34
- };
35
-
36
- handlePopoverClose = () => {
37
- this.setState({ anchorEl: null });
38
- };
39
-
40
- render() {
41
- const { feedback } = this.props;
42
- const { anchorEl } = this.state;
43
- return (
44
- <ResponseIndicatorContainer hasFeedback={!!feedback}>
45
- <span ref={(r) => (this.icon = r)} onClick={this.handlePopoverOpen}>
46
- <Icon />
47
- </span>
48
-
49
- {feedback && (
50
- <StyledPopover
51
- PaperComponent={PopoverPaper}
52
- open={!!anchorEl}
53
- anchorEl={anchorEl}
54
- anchorOrigin={{
55
- vertical: 'bottom',
56
- horizontal: 'left',
57
- }}
58
- transformOrigin={{
59
- vertical: 'top',
60
- horizontal: 'left',
61
- }}
62
- onClose={this.handlePopoverClose}
63
- >
64
- <Feedback feedback={feedback} correctness={correctness} />
65
- </StyledPopover>
66
- )}
67
- </ResponseIndicatorContainer>
68
- );
69
- }
70
- }
71
-
72
- Indicator.propTypes = {
73
- feedback: PropTypes.string,
74
- };
75
-
76
- return Indicator;
77
- };
78
-
79
- export const Correct = BuildIndicator(icons.Correct, 'correct');
80
- export const Incorrect = BuildIndicator(icons.Incorrect, 'incorrect');
81
- export const PartiallyCorrect = BuildIndicator(icons.PartiallyCorrect, 'partially-correct');
82
- export const NothingSubmitted = BuildIndicator(icons.NothingSubmitted, 'nothing-submitted');
package/src/ui-layout.jsx DELETED
@@ -1,96 +0,0 @@
1
- import React from 'react';
2
- import { createTheme, styled, StyledEngineProvider, ThemeProvider } from '@mui/material/styles';
3
- import PropTypes from 'prop-types';
4
-
5
- const theme = createTheme({
6
- typography: {
7
- fontFamily: 'inherit',
8
- },
9
- palette: {
10
- action: {
11
- disabled: 'rgba(0, 0, 0, 0.54);',
12
- },
13
- },
14
- components: {
15
- MuiTypography: {
16
- styleOverrides: {
17
- root: { fontFamily: 'inherit' },
18
- },
19
- },
20
- MuiButton: {
21
- styleOverrides: {
22
- contained: {
23
- backgroundColor: '#e0e0e0',
24
- color: '#000000',
25
- '&:hover': {
26
- backgroundColor: '#bdbdbd',
27
- },
28
- },
29
- },
30
- },
31
- },
32
- });
33
-
34
- const StyledContainer = styled('div')({
35
- // need this because some browsers set their own style on table
36
- '& table, th, td': {
37
- fontSize: 'inherit' /* Ensure table elements inherit font size */,
38
- },
39
- });
40
-
41
- class UiLayout extends React.Component {
42
- static propTypes = {
43
- className: PropTypes.string,
44
- children: PropTypes.array,
45
- extraCSSRules: PropTypes.shape({
46
- names: PropTypes.arrayOf(PropTypes.string),
47
- rules: PropTypes.string,
48
- }),
49
- fontSizeFactor: PropTypes.number,
50
- };
51
-
52
- static defaultProps = {
53
- extraCSSRules: {},
54
- fontSizeFactor: 1,
55
- };
56
-
57
- constructor(props) {
58
- super(props);
59
- this.classesSheet = document.createElement('style');
60
- }
61
-
62
- computeStyle(fontSizeFactor) {
63
- const getFontSize = (element) => parseFloat(getComputedStyle(element).fontSize);
64
-
65
- const rootFontSize = getFontSize(document.documentElement);
66
- const bodyFontSize = getFontSize(document.body);
67
- const effectiveFontSize = Math.max(rootFontSize, bodyFontSize);
68
-
69
- // Handle null, undefined, or invalid values by defaulting to 1
70
- const factor = fontSizeFactor != null && typeof fontSizeFactor === 'number' ? fontSizeFactor : 1;
71
- return factor !== 1 ? { fontSize: `${effectiveFontSize * factor}px` } : null;
72
- }
73
-
74
- render() {
75
- const { children, className, fontSizeFactor, ...rest } = this.props;
76
-
77
- const { extraCSSRules, ...restProps } = rest;
78
- const style = this.computeStyle(fontSizeFactor);
79
-
80
- return (
81
- <StyledEngineProvider injectFirst>
82
- <ThemeProvider theme={theme}>
83
- {extraCSSRules?.rules ? (
84
- <style dangerouslySetInnerHTML={{ __html: `.extraCSSRules { ${extraCSSRules.rules} }` }} />
85
- ) : null}
86
-
87
- <StyledContainer className={`${className} extraCSSRules`} {...restProps} {...(style && { style })}>
88
- {children}
89
- </StyledContainer>
90
- </ThemeProvider>
91
- </StyledEngineProvider>
92
- );
93
- }
94
- }
95
-
96
- export default UiLayout;
@@ -1,114 +0,0 @@
1
- import * as React from 'react';
2
- import PropTypes from 'prop-types';
3
- import Button from '@mui/material/Button';
4
- import { styled } from '@mui/material/styles';
5
- import Restore from '@mui/icons-material/Restore';
6
- import Undo from '@mui/icons-material/Undo';
7
-
8
- const Wrapper = styled('div')({
9
- display: 'flex',
10
- flexDirection: 'column',
11
- });
12
-
13
- const ResetUndoContainer = styled('div')({
14
- display: 'flex',
15
- alignItems: 'center',
16
- justifyContent: 'center',
17
- });
18
-
19
- const StyledIcon = styled('div')(({ theme }) => ({
20
- width: '24px',
21
- height: '24px',
22
- color: 'gray',
23
- marginRight: theme.spacing(1),
24
- display: 'flex',
25
- alignItems: 'center',
26
- }));
27
-
28
- const StyledButton = styled(Button)(({ theme }) => ({
29
- display: 'flex',
30
- alignItems: 'center',
31
- marginLeft: theme.spacing(3),
32
- marginRight: theme.spacing(3),
33
- }));
34
-
35
- /**
36
- * HOC that adds undo and reset functionality for session values
37
- */
38
- const withUndoReset = (WrappedComponent) => {
39
- class WithUndoReset extends React.Component {
40
- static propTypes = {
41
- session: PropTypes.object,
42
- onSessionChange: PropTypes.func,
43
- };
44
-
45
- constructor(props) {
46
- super(props);
47
-
48
- this.state = {
49
- sessionInitialValues: JSON.parse(JSON.stringify(props.session)),
50
- session: props.session,
51
- changes: [],
52
- };
53
- }
54
-
55
- onSessionChange = (session) => {
56
- this.setState(
57
- (state) => ({ session, changes: [...state.changes, session] }),
58
- () => this.props.onSessionChange(session),
59
- );
60
- };
61
-
62
- onUndo = () => {
63
- this.setState(
64
- (state) => {
65
- const newChanges = [...state.changes];
66
-
67
- newChanges.pop();
68
-
69
- return {
70
- changes: newChanges,
71
- session: newChanges.length ? newChanges[newChanges.length - 1] : state.sessionInitialValues,
72
- };
73
- },
74
- () => this.props.onSessionChange(this.state.session),
75
- );
76
- };
77
-
78
- onReset = () => {
79
- this.setState(
80
- (state) => ({ session: state.sessionInitialValues, changes: [] }),
81
- () => this.props.onSessionChange(this.state.sessionInitialValues),
82
- );
83
- };
84
-
85
- render() {
86
- const { ...rest } = this.props;
87
- const { changes, session } = this.state;
88
-
89
- return (
90
- <Wrapper>
91
- <ResetUndoContainer>
92
- <StyledButton color="primary" disabled={changes.length === 0} onClick={this.onUndo}>
93
- <StyledIcon>
94
- <Undo />
95
- </StyledIcon>
96
- Undo
97
- </StyledButton>
98
- <StyledButton color="primary" disabled={changes.length === 0} onClick={this.onReset}>
99
- <StyledIcon>
100
- <Restore />
101
- </StyledIcon>
102
- Start Over
103
- </StyledButton>
104
- </ResetUndoContainer>
105
- <WrappedComponent {...rest} session={session} onSessionChange={this.onSessionChange} />
106
- </Wrapper>
107
- );
108
- }
109
- }
110
-
111
- return WithUndoReset;
112
- };
113
-
114
- export default withUndoReset;