@pie-element/image-cloze-association 3.5.0 → 3.5.1-beta.587

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 (67) hide show
  1. package/CHANGELOG.md +478 -0
  2. package/configure/CHANGELOG.md +390 -0
  3. package/configure/lib/defaults.js +16 -0
  4. package/configure/lib/defaults.js.map +1 -1
  5. package/configure/lib/index.js +17 -3
  6. package/configure/lib/index.js.map +1 -1
  7. package/configure/lib/root.js +43 -13
  8. package/configure/lib/root.js.map +1 -1
  9. package/configure/package.json +4 -3
  10. package/configure/src/__tests__/index.test.js +12 -15
  11. package/configure/src/defaults.js +20 -4
  12. package/configure/src/index.js +23 -3
  13. package/configure/src/root.jsx +41 -25
  14. package/controller/CHANGELOG.md +63 -0
  15. package/controller/lib/index.js +9 -8
  16. package/controller/lib/index.js.map +1 -1
  17. package/controller/lib/utils.js +4 -4
  18. package/controller/lib/utils.js.map +1 -1
  19. package/controller/package.json +2 -2
  20. package/controller/src/__tests__/index.test.js +108 -62
  21. package/controller/src/index.js +40 -28
  22. package/controller/src/utils.js +10 -10
  23. package/docs/config-schema.json +160 -0
  24. package/docs/config-schema.json.md +127 -0
  25. package/docs/demo/config.js +2 -2
  26. package/docs/demo/generate.js +30 -25
  27. package/docs/demo/session.js +1 -1
  28. package/docs/pie-schema.json +378 -0
  29. package/docs/pie-schema.json.md +295 -0
  30. package/lib/constants.js.map +1 -1
  31. package/lib/evaluation-icon.js.map +1 -1
  32. package/lib/image-container.js +13 -6
  33. package/lib/image-container.js.map +1 -1
  34. package/lib/image-drop-target.js +7 -2
  35. package/lib/image-drop-target.js.map +1 -1
  36. package/lib/index.js +4 -1
  37. package/lib/index.js.map +1 -1
  38. package/lib/interactive-section.js +1 -1
  39. package/lib/interactive-section.js.map +1 -1
  40. package/lib/possible-response.js +17 -5
  41. package/lib/possible-response.js.map +1 -1
  42. package/lib/possible-responses.js.map +1 -1
  43. package/lib/root.js +140 -19
  44. package/lib/root.js.map +1 -1
  45. package/lib/utils-correctness.js +33 -5
  46. package/lib/utils-correctness.js.map +1 -1
  47. package/package.json +8 -5
  48. package/src/__tests__/__snapshots__/{image-container-test.jsx.snap → image-container.test.jsx.snap} +20 -0
  49. package/src/__tests__/__snapshots__/possible-response.test.jsx.snap +5 -0
  50. package/src/__tests__/__snapshots__/root.test.jsx.snap +50 -0
  51. package/src/__tests__/{image-container-test.jsx → image-container.test.jsx} +12 -5
  52. package/src/__tests__/{possible-response-test.jsx → possible-response.test.jsx} +5 -3
  53. package/src/__tests__/{root-test.jsx → root.test.jsx} +4 -3
  54. package/src/__tests__/utils.test.js +207 -0
  55. package/src/constants.js +3 -3
  56. package/src/evaluation-icon.jsx +8 -17
  57. package/src/image-container.jsx +14 -19
  58. package/src/image-drop-target.jsx +45 -38
  59. package/src/index.js +10 -18
  60. package/src/interactive-section.jsx +15 -22
  61. package/src/possible-response.jsx +43 -23
  62. package/src/possible-responses.jsx +17 -25
  63. package/src/root.jsx +174 -100
  64. package/src/utils-correctness.js +44 -14
  65. package/src/__tests__/__snapshots__/possible-response-test.jsx.snap +0 -13
  66. package/src/__tests__/__snapshots__/root-test.jsx.snap +0 -38
  67. package/src/__tests__/utils-test.js +0 -211
@@ -3,22 +3,29 @@ import PropTypes from 'prop-types';
3
3
  import { DragSource } from '@pie-lib/drag';
4
4
  import { withStyles } from '@material-ui/core/styles';
5
5
  import { color } from '@pie-lib/render-ui';
6
+ import classNames from 'classnames';
6
7
 
7
8
  import EvaluationIcon from './evaluation-icon';
8
9
  import c from './constants';
9
10
 
10
- class PossibleResponse extends React.Component {
11
+ export class PossibleResponse extends React.Component {
11
12
  getClassname = () => {
12
- const { classes, data: { isCorrect } } = this.props;
13
+ const {
14
+ classes,
15
+ data: { isCorrect },
16
+ } = this.props;
13
17
  let styleProp;
14
18
 
15
19
  switch (isCorrect) {
16
20
  case undefined:
17
- styleProp = null; break;
21
+ styleProp = null;
22
+ break;
18
23
  case true:
19
- styleProp = 'baseCorrect'; break;
24
+ styleProp = 'baseCorrect';
25
+ break;
20
26
  default:
21
- styleProp = 'baseIncorrect'; break;
27
+ styleProp = 'baseIncorrect';
28
+ break;
22
29
  }
23
30
  return styleProp ? classes[styleProp] : '';
24
31
  };
@@ -27,19 +34,20 @@ class PossibleResponse extends React.Component {
27
34
  const { classes, connectDragSource, containerStyle, data } = this.props;
28
35
  const additionalClass = this.getClassname();
29
36
  const evaluationStyle = {
30
- alignSelf: 'center',
31
37
  fontSize: 14,
32
- paddingRight: 2
38
+ position: 'absolute',
39
+ bottom: '3px',
40
+ right: '3px',
33
41
  };
34
42
 
35
43
  return connectDragSource(
36
44
  <div className={`${classes.base} ${additionalClass}`} style={containerStyle}>
37
- <span className={classes.span} dangerouslySetInnerHTML={{__html: data.value}}/>
38
- <EvaluationIcon
39
- isCorrect={data.isCorrect}
40
- containerStyle={evaluationStyle}
45
+ <span
46
+ className={classNames([classes.span, { [classes.hiddenSpan]: data.hidden }])}
47
+ dangerouslySetInnerHTML={{ __html: data.value }}
41
48
  />
42
- </div>
49
+ <EvaluationIcon isCorrect={data.isCorrect} containerStyle={evaluationStyle} />
50
+ </div>,
43
51
  );
44
52
  }
45
53
  }
@@ -51,33 +59,41 @@ PossibleResponse.propTypes = {
51
59
  containerStyle: PropTypes.object,
52
60
  data: PropTypes.object.isRequired,
53
61
  onDragBegin: PropTypes.func.isRequired,
54
- onDragEnd: PropTypes.func.isRequired
62
+ onDragEnd: PropTypes.func.isRequired,
55
63
  };
56
64
 
57
65
  PossibleResponse.defaultProps = {
58
66
  classes: {},
59
67
  connectDragSource: () => {},
60
- containerStyle: {}
68
+ containerStyle: {},
61
69
  };
62
70
 
63
71
  const styles = () => ({
64
72
  base: {
73
+ position: 'relative',
65
74
  backgroundColor: color.background(),
66
75
  border: `1px solid ${color.primary()}`,
67
76
  display: 'flex',
77
+ alignItems: 'center',
78
+ justifyContent: 'center',
79
+ minHeight: '28px',
80
+ padding: '0 3px',
68
81
  marginLeft: 2,
69
82
  marginTop: 2,
70
- width: 'fit-content'
83
+ width: 'fit-content',
71
84
  },
72
85
  baseCorrect: {
73
- border: `2px solid ${color.correct()}`
86
+ border: `2px solid ${color.correct()}`,
74
87
  },
75
88
  baseIncorrect: {
76
- border: `2px solid ${color.incorrect()}`
89
+ border: `2px solid ${color.incorrect()}`,
77
90
  },
78
91
  span: {
79
- backgroundColor: color.background()
80
- }
92
+ backgroundColor: color.background(),
93
+ },
94
+ hiddenSpan: {
95
+ visibility: 'hidden',
96
+ },
81
97
  });
82
98
 
83
99
  const Styled = withStyles(styles)(PossibleResponse);
@@ -88,20 +104,24 @@ const tileSource = {
88
104
  return canDrag;
89
105
  },
90
106
  beginDrag(props) {
91
- const { data, data: { id, value, containerIndex }, onDragBegin } = props;
107
+ const {
108
+ data,
109
+ data: { id, value, containerIndex },
110
+ onDragBegin,
111
+ } = props;
92
112
  onDragBegin(data);
93
113
  return {
94
114
  id,
95
115
  value,
96
- containerIndex
116
+ containerIndex,
97
117
  };
98
118
  },
99
119
  endDrag(props) {
100
120
  props.onDragEnd();
101
- }
121
+ },
102
122
  };
103
123
 
104
124
  export default DragSource(c.types.response, tileSource, (connect, monitor) => ({
105
125
  connectDragSource: connect.dragSource(),
106
- isDragging: monitor.isDragging()
126
+ isDragging: monitor.isDragging(),
107
127
  }))(Styled);
@@ -1,4 +1,4 @@
1
- import React from 'react';
1
+ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { DropTarget } from '@pie-lib/drag';
4
4
  import { withStyles } from '@material-ui/core';
@@ -7,21 +7,14 @@ import { color } from '@pie-lib/render-ui';
7
7
  import PossibleResponse from './possible-response';
8
8
  import c from './constants';
9
9
 
10
- const PossibleResponses = ({
11
- canDrag, classes, connectDropTarget, data, isOverCurrent, onDragBegin, onDragEnd
12
- }) => connectDropTarget(
13
- <div className={`${classes.base} ${isOverCurrent ? classes.active : ''}`}>
14
- {data.map(item => (
15
- <PossibleResponse
16
- canDrag={canDrag}
17
- key={item.id}
18
- data={item}
19
- onDragBegin={onDragBegin}
20
- onDragEnd={onDragEnd}
21
- />
22
- ))}
23
- </div>
24
- );
10
+ const PossibleResponses = ({ canDrag, classes, connectDropTarget, data, isOverCurrent, onDragBegin, onDragEnd }) =>
11
+ connectDropTarget(
12
+ <div className={`${classes.base} ${isOverCurrent ? classes.active : ''}`}>
13
+ {data.map((item) => (
14
+ <PossibleResponse canDrag={canDrag} key={item.id} data={item} onDragBegin={onDragBegin} onDragEnd={onDragEnd} />
15
+ ))}
16
+ </div>,
17
+ );
25
18
 
26
19
  PossibleResponses.propTypes = {
27
20
  canDrag: PropTypes.bool.isRequired,
@@ -29,24 +22,24 @@ PossibleResponses.propTypes = {
29
22
  data: PropTypes.array.isRequired,
30
23
  onAnswerRemove: PropTypes.func.isRequired,
31
24
  onDragBegin: PropTypes.func.isRequired,
32
- onDragEnd: PropTypes.func.isRequired
25
+ onDragEnd: PropTypes.func.isRequired,
33
26
  };
34
27
 
35
28
  PossibleResponses.defaultProps = {
36
- classes: {}
29
+ classes: {},
37
30
  };
38
31
 
39
- const styles = theme => ({
32
+ const styles = (theme) => ({
40
33
  active: {
41
- border: `1px solid ${color.primary()}`
34
+ border: `1px solid ${color.primary()}`,
42
35
  },
43
36
  base: {
44
37
  backgroundColor: color.background(),
45
38
  display: 'flex',
46
39
  flexWrap: 'wrap',
47
40
  justifyContent: 'center',
48
- padding: theme.spacing.unit * 2
49
- }
41
+ padding: theme.spacing.unit * 2,
42
+ },
50
43
  });
51
44
 
52
45
  const Styled = withStyles(styles)(PossibleResponses);
@@ -58,11 +51,10 @@ const tileSource = {
58
51
  drop(props, monitor) {
59
52
  const item = monitor.getItem();
60
53
  props.onAnswerRemove(item);
61
- }
54
+ },
62
55
  };
63
56
 
64
57
  export default DropTarget(c.types.response, tileSource, (connect, monitor) => ({
65
58
  connectDropTarget: connect.dropTarget(),
66
- isOverCurrent: monitor.isOver({ shallow: true })
59
+ isOverCurrent: monitor.isOver({ shallow: true }),
67
60
  }))(Styled);
68
-
package/src/root.jsx CHANGED
@@ -2,113 +2,101 @@ import React from 'react';
2
2
  import PropTypes from 'prop-types';
3
3
  import Typography from '@material-ui/core/Typography';
4
4
  import { withDragContext } from '@pie-lib/drag';
5
+ import { CSSTransition, TransitionGroup } from 'react-transition-group';
6
+ import { ShowRationale } from '@pie-lib/icons';
7
+ import { Collapsible, PreviewPrompt } from '@pie-lib/render-ui';
8
+ import { withStyles } from '@material-ui/core/styles';
9
+ import CorrectAnswerToggle from '@pie-lib/correct-answer-toggle';
5
10
 
6
11
  import Image from './image-container';
7
12
  import InteractiveSection from './interactive-section';
8
13
  import PossibleResponses from './possible-responses';
9
-
10
- import { getAnswersCorrectness } from './utils-correctness';
11
- import { Collapsible } from '@pie-lib/render-ui';
14
+ import { getUnansweredAnswers, getAnswersCorrectness } from './utils-correctness';
12
15
  import _ from 'lodash';
13
16
 
14
- const generateId = () =>
15
- Math.random()
16
- .toString(36)
17
- .substring(2) + new Date().getTime().toString(36);
17
+ const generateId = () => Math.random().toString(36).substring(2) + new Date().getTime().toString(36);
18
18
 
19
19
  class ImageClozeAssociationComponent extends React.Component {
20
20
  constructor(props) {
21
21
  super(props);
22
22
  const {
23
- model: {
24
- possibleResponses,
25
- responseContainers,
26
- duplicateResponses,
27
- maxResponsePerZone
28
- },
29
- session
23
+ model: { possibleResponses, responseContainers, duplicateResponses, maxResponsePerZone },
24
+ session,
30
25
  } = props;
31
26
  let { answers } = session || {};
32
27
  // set id for each possible response
33
- const possibleResponsesWithIds = (possibleResponses || []).map(
34
- (item, index) => ({
35
- value: item,
36
- id: `${index}`
37
- })
38
- );
28
+ const possibleResponsesWithIds = (possibleResponses || []).map((item, index) => ({
29
+ value: item,
30
+ id: `${index}`,
31
+ }));
39
32
 
40
33
  answers = _(answers || [])
41
34
  .groupBy('containerIndex')
42
35
  // keep only last maxResponsePerZone answers for each zone
43
- .map(grp => grp.slice(-(maxResponsePerZone || 1)))
36
+ .map((grp) => grp.slice(-(maxResponsePerZone || 1)))
44
37
  .flatMap()
45
38
  // set id for each answer
46
39
  .map((answer, index) => ({ ...answer, id: `${index}` }))
47
40
  // return only answer which have a valid container index
48
- .filter(answer => answer.containerIndex < responseContainers.length)
41
+ .filter((answer) => answer.containerIndex < responseContainers.length)
49
42
  .value();
50
43
 
51
44
  const possibleResponsesFiltered = possibleResponsesWithIds.filter(
52
- response => !answers.find(answer => answer.value === response.value)
45
+ (response) => !answers.find((answer) => answer.value === response.value),
53
46
  );
54
47
  this.state = {
55
48
  answers: answers || [],
56
49
  draggingElement: { id: '', value: '' },
57
- possibleResponses: duplicateResponses
58
- ? possibleResponsesWithIds
59
- : possibleResponsesFiltered,
50
+ possibleResponses: duplicateResponses ? possibleResponsesWithIds : possibleResponsesFiltered,
60
51
  // set id for each response containers
61
52
  responseContainers: responseContainers.map((item, index) => ({
62
53
  index,
63
54
  ...item,
64
- id: `${index}`
55
+ id: `${index}`,
65
56
  })),
66
- maxResponsePerZone: maxResponsePerZone || 1
57
+ maxResponsePerZone: maxResponsePerZone || 1,
58
+ showCorrect: false,
67
59
  };
68
60
  }
69
61
 
70
- beginDrag = draggingElement => {
62
+ beginDrag = (draggingElement) => {
71
63
  this.setState({
72
- draggingElement
64
+ draggingElement,
73
65
  });
74
66
  };
75
67
 
76
68
  handleOnDragEnd = () => {
77
69
  this.setState({
78
- draggingElement: { id: '', value: '' }
70
+ draggingElement: { id: '', value: '' },
79
71
  });
80
72
  };
81
73
 
82
74
  handleOnAnswerSelect = (answer, responseContainerIndex) => {
83
75
  const {
84
76
  model: { duplicateResponses },
85
- updateAnswer
77
+ updateAnswer,
86
78
  } = this.props;
87
79
  const { answers, possibleResponses, maxResponsePerZone } = this.state;
88
80
  let answersToStore;
89
81
 
90
- if (
91
- maxResponsePerZone ===
92
- answers.filter(a => a.containerIndex === responseContainerIndex).length
93
- ) {
94
- const answersInThisContainer = answers.filter(
95
- a => a.containerIndex === responseContainerIndex
96
- );
97
- const answersInOtherContainers = answers.filter(
98
- b => b.containerIndex !== responseContainerIndex
99
- );
82
+ if (maxResponsePerZone === answers.filter((a) => a.containerIndex === responseContainerIndex).length) {
83
+ const answersInThisContainer = answers.filter((a) => a.containerIndex === responseContainerIndex);
84
+ const answersInOtherContainers = answers.filter((b) => b.containerIndex !== responseContainerIndex);
100
85
 
101
86
  const shiftedItem = answersInThisContainer[0];
102
- answersInThisContainer.shift(); // FIFO
87
+ if (maxResponsePerZone === 1) {
88
+ answersInThisContainer.shift(); // FIFO
89
+ } else {
90
+ this.setState({ maxResponsePerZoneWarning: true });
91
+ return;
92
+ }
103
93
 
104
94
  // if duplicates are not allowed, make sure to put the shifted value back in possible responses
105
95
  if (!duplicateResponses) {
106
96
  possibleResponses.push({
107
97
  ...shiftedItem,
108
98
  containerIndex: '',
109
- id: `${_.max(
110
- possibleResponses.map(c => parseInt(c.id)).filter(id => !isNaN(id))
111
- ) + 1}`
99
+ id: `${_.max(possibleResponses.map((c) => parseInt(c.id)).filter((id) => !isNaN(id))) + 1}`,
112
100
  });
113
101
  }
114
102
 
@@ -121,58 +109,53 @@ class ImageClozeAssociationComponent extends React.Component {
121
109
  ...answersInThisContainer, // shifted
122
110
  // TODO allow duplicates case Question: should we remove answer from a container if dragged to another container?
123
111
  // if yes, this should do it: add a.id !== answer.id instead of 'true'
124
- ...answersInOtherContainers.filter(a =>
125
- duplicateResponses ? true : a.value !== answer.value
126
- ), // un-shifted
112
+ ...answersInOtherContainers.filter((a) => (duplicateResponses ? true : a.value !== answer.value)), // un-shifted
127
113
  {
128
114
  ...answer,
129
115
  containerIndex: responseContainerIndex,
130
- ...(duplicateResponses ? { id: generateId() } : {})
131
- }
116
+ ...(duplicateResponses ? { id: generateId() } : {}),
117
+ },
132
118
  ];
133
119
  } else {
134
120
  // answers will be:
135
- // + if duplicatesAllowed, all the other answers
121
+ // + if duplicatesAllowed, all the other answers, except the one that was dragged
136
122
  // else: all the answers that are not having the same value
137
123
  // + new answer
138
124
  answersToStore = [
139
125
  // TODO allow duplicates case Question: should we remove answer from a container if dragged to another container?
140
126
  // if yes, this should do it: add a.id !== answer.id instead of 'true'
141
- ...answers.filter(a =>
142
- duplicateResponses ? true : a.value !== answer.value
143
- ),
127
+ ...answers.filter((a) => (duplicateResponses ? a.id !== answer.id : a.value !== answer.value)),
144
128
  {
145
129
  ...answer,
146
130
  containerIndex: responseContainerIndex,
147
- ...(duplicateResponses ? { id: generateId() } : {})
148
- }
131
+ ...(duplicateResponses ? { id: generateId() } : {}),
132
+ },
149
133
  ];
150
134
  }
151
135
 
152
136
  this.setState({
137
+ maxResponsePerZoneWarning: false,
153
138
  answers: answersToStore,
154
139
  possibleResponses:
155
140
  // for single response per container remove answer from possible responses
156
141
  duplicateResponses
157
142
  ? possibleResponses
158
- : possibleResponses.filter(
159
- response => response.value !== answer.value
160
- )
143
+ : possibleResponses.filter((response) => response.value !== answer.value),
161
144
  });
162
145
  updateAnswer(answersToStore);
163
146
  };
164
147
 
165
- handleOnAnswerRemove = answer => {
148
+ handleOnAnswerRemove = (answer) => {
166
149
  const {
167
150
  model: { duplicateResponses },
168
- updateAnswer
151
+ updateAnswer,
169
152
  } = this.props;
170
153
  const { answers, possibleResponses } = this.state;
171
- const answersToStore = answers.filter(a => a.id !== answer.id);
172
- const shouldNotPushInPossibleResponses =
173
- answer.containerIndex === undefined; // don't duplicate possible responses
154
+ const answersToStore = answers.filter((a) => a.id !== answer.id);
155
+ const shouldNotPushInPossibleResponses = answer.containerIndex === undefined; // don't duplicate possible responses
174
156
 
175
157
  this.setState({
158
+ maxResponsePerZoneWarning: false,
176
159
  answers: answersToStore,
177
160
  // push back into possible responses the removed answer if responses cannot be duplicated
178
161
  possibleResponses:
@@ -182,13 +165,15 @@ class ImageClozeAssociationComponent extends React.Component {
182
165
  ...possibleResponses,
183
166
  {
184
167
  ...answer,
185
- containerIndex: undefined
186
- }
187
- ]
168
+ containerIndex: undefined,
169
+ },
170
+ ],
188
171
  });
189
172
  updateAnswer(answersToStore);
190
173
  };
191
174
 
175
+ toggleCorrect = (showCorrect) => this.setState({ showCorrect });
176
+
192
177
  render() {
193
178
  const {
194
179
  model: {
@@ -198,31 +183,61 @@ class ImageClozeAssociationComponent extends React.Component {
198
183
  stimulus,
199
184
  responseCorrect,
200
185
  validation,
201
- teacherInstructions
202
- }
186
+ teacherInstructions,
187
+ prompt,
188
+ showDashedBorder,
189
+ mode,
190
+ },
203
191
  } = this.props;
204
192
  const {
205
193
  answers,
206
194
  draggingElement,
207
195
  possibleResponses,
208
- responseContainers
196
+ responseContainers,
197
+ maxResponsePerZone,
198
+ maxResponsePerZoneWarning,
199
+ showCorrect,
209
200
  } = this.state;
201
+ const isEvaluateMode = mode === 'evaluate';
202
+ const showToggle = isEvaluateMode && !responseCorrect;
210
203
 
211
- const answersToShow =
212
- responseCorrect !== undefined
213
- ? getAnswersCorrectness(answers, validation, duplicateResponses)
214
- : answers;
204
+ const { validResponse } = validation || {};
205
+ const correctAnswers = [];
206
+
207
+ if (validResponse) {
208
+ (validResponse.value || []).forEach((container, i) => {
209
+ (container.images || []).forEach((v) => {
210
+ correctAnswers.push({
211
+ value: v,
212
+ containerIndex: i,
213
+ });
214
+ });
215
+ });
216
+ }
217
+
218
+ const warningMessage =
219
+ `You’ve reached the limit of ${maxResponsePerZone} responses per area.` +
220
+ 'To add another response, one must first be removed.';
221
+
222
+ let answersToShow =
223
+ responseCorrect !== undefined ? getAnswersCorrectness(answers, validation, duplicateResponses) : answers;
224
+
225
+ if (responseCorrect === false && maxResponsePerZone === 1) {
226
+ answersToShow = [...answersToShow, ...getUnansweredAnswers(answersToShow, validation)];
227
+ }
215
228
 
216
229
  return (
217
230
  <div>
231
+ <PreviewPrompt className="prompt" prompt={prompt} />
232
+
218
233
  {teacherInstructions && (
219
234
  <Collapsible
220
235
  labels={{
221
236
  hidden: 'Show Teacher Instructions',
222
- visible: 'Hide Teacher Instructions'
237
+ visible: 'Hide Teacher Instructions',
223
238
  }}
224
239
  >
225
- <div dangerouslySetInnerHTML={{ __html: teacherInstructions }} />
240
+ <PreviewPrompt prompt={teacherInstructions} />
226
241
  </Collapsible>
227
242
  )}
228
243
 
@@ -230,41 +245,100 @@ class ImageClozeAssociationComponent extends React.Component {
230
245
  <span dangerouslySetInnerHTML={{ __html: stimulus }} />
231
246
  </Typography>
232
247
 
233
- <InteractiveSection responseCorrect={responseCorrect}>
234
- <Image
235
- canDrag={!disabled}
236
- answers={answersToShow}
237
- draggingElement={draggingElement}
238
- duplicateResponses={duplicateResponses}
239
- image={image}
240
- onAnswerSelect={this.handleOnAnswerSelect}
241
- onDragAnswerBegin={this.beginDrag}
242
- onDragAnswerEnd={this.handleOnDragEnd}
243
- responseContainers={responseContainers}
244
- />
245
-
246
- <PossibleResponses
247
- canDrag={!disabled}
248
- data={possibleResponses}
249
- onAnswerRemove={this.handleOnAnswerRemove}
250
- onDragBegin={this.beginDrag}
251
- onDragEnd={this.handleOnDragEnd}
252
- />
253
- </InteractiveSection>
248
+ <CorrectAnswerToggle show={showToggle} toggled={showCorrect} onToggle={this.toggleCorrect} />
249
+ <br />
250
+
251
+ {showCorrect && showToggle ? (
252
+ <InteractiveSection responseCorrect={true}>
253
+ <Image
254
+ canDrag={false}
255
+ answers={correctAnswers}
256
+ draggingElement={draggingElement}
257
+ duplicateResponses={duplicateResponses}
258
+ image={image}
259
+ onAnswerSelect={this.handleOnAnswerSelect}
260
+ onDragAnswerBegin={this.beginDrag}
261
+ onDragAnswerEnd={this.handleOnDragEnd}
262
+ responseContainers={responseContainers}
263
+ showDashedBorder={showDashedBorder}
264
+ />
265
+ </InteractiveSection>
266
+ ) : (
267
+ <InteractiveSection responseCorrect={responseCorrect}>
268
+ <Image
269
+ canDrag={!disabled}
270
+ answers={answersToShow}
271
+ draggingElement={draggingElement}
272
+ duplicateResponses={duplicateResponses}
273
+ image={image}
274
+ onAnswerSelect={this.handleOnAnswerSelect}
275
+ onDragAnswerBegin={this.beginDrag}
276
+ onDragAnswerEnd={this.handleOnDragEnd}
277
+ responseContainers={responseContainers}
278
+ showDashedBorder={showDashedBorder}
279
+ />
280
+
281
+ {maxResponsePerZoneWarning && <WarningInfo message={warningMessage} />}
282
+
283
+ <PossibleResponses
284
+ canDrag={!disabled}
285
+ data={possibleResponses}
286
+ onAnswerRemove={this.handleOnAnswerRemove}
287
+ onDragBegin={this.beginDrag}
288
+ onDragEnd={this.handleOnDragEnd}
289
+ />
290
+ </InteractiveSection>
291
+ )}
254
292
  </div>
255
293
  );
256
294
  }
257
295
  }
258
296
 
297
+ const WarningInfo = withStyles({
298
+ warning: {
299
+ margin: '0 16px',
300
+ backgroundColor: '#dddddd',
301
+ padding: '10px',
302
+ display: 'flex',
303
+ alignItems: 'center',
304
+ width: 'fit-content',
305
+ '& svg': {
306
+ height: '30px',
307
+ },
308
+ '& h1': {
309
+ padding: '0px',
310
+ margin: '0px',
311
+ },
312
+ },
313
+ message: {
314
+ paddingLeft: '5px',
315
+ userSelect: 'none',
316
+ },
317
+ })(({ classes, message }) => (
318
+ <TransitionGroup>
319
+ <CSSTransition classNames={'fb'} key="fb" timeout={300}>
320
+ <div key="panel" className={classes.warning}>
321
+ <ShowRationale iconSet="emoji" shape="square" />
322
+ <span className={classes.message} dangerouslySetInnerHTML={{ __html: message }} />
323
+ </div>
324
+ </CSSTransition>
325
+ </TransitionGroup>
326
+ ));
327
+
328
+ WarningInfo.propTypes = {
329
+ message: PropTypes.string,
330
+ classes: PropTypes.object.isRequired,
331
+ };
332
+
259
333
  ImageClozeAssociationComponent.propTypes = {
260
334
  classes: PropTypes.object,
261
335
  model: PropTypes.object.isRequired,
262
336
  session: PropTypes.object,
263
- updateAnswer: PropTypes.func.isRequired
337
+ updateAnswer: PropTypes.func.isRequired,
264
338
  };
265
339
 
266
340
  ImageClozeAssociationComponent.defaultProps = {
267
- classes: {}
341
+ classes: {},
268
342
  };
269
343
 
270
344
  export default withDragContext(ImageClozeAssociationComponent);