@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.
- package/CHANGELOG.md +478 -0
- package/configure/CHANGELOG.md +390 -0
- package/configure/lib/defaults.js +16 -0
- package/configure/lib/defaults.js.map +1 -1
- package/configure/lib/index.js +17 -3
- package/configure/lib/index.js.map +1 -1
- package/configure/lib/root.js +43 -13
- package/configure/lib/root.js.map +1 -1
- package/configure/package.json +4 -3
- package/configure/src/__tests__/index.test.js +12 -15
- package/configure/src/defaults.js +20 -4
- package/configure/src/index.js +23 -3
- package/configure/src/root.jsx +41 -25
- package/controller/CHANGELOG.md +63 -0
- package/controller/lib/index.js +9 -8
- package/controller/lib/index.js.map +1 -1
- package/controller/lib/utils.js +4 -4
- package/controller/lib/utils.js.map +1 -1
- package/controller/package.json +2 -2
- package/controller/src/__tests__/index.test.js +108 -62
- package/controller/src/index.js +40 -28
- package/controller/src/utils.js +10 -10
- package/docs/config-schema.json +160 -0
- package/docs/config-schema.json.md +127 -0
- package/docs/demo/config.js +2 -2
- package/docs/demo/generate.js +30 -25
- package/docs/demo/session.js +1 -1
- package/docs/pie-schema.json +378 -0
- package/docs/pie-schema.json.md +295 -0
- package/lib/constants.js.map +1 -1
- package/lib/evaluation-icon.js.map +1 -1
- package/lib/image-container.js +13 -6
- package/lib/image-container.js.map +1 -1
- package/lib/image-drop-target.js +7 -2
- package/lib/image-drop-target.js.map +1 -1
- package/lib/index.js +4 -1
- package/lib/index.js.map +1 -1
- package/lib/interactive-section.js +1 -1
- package/lib/interactive-section.js.map +1 -1
- package/lib/possible-response.js +17 -5
- package/lib/possible-response.js.map +1 -1
- package/lib/possible-responses.js.map +1 -1
- package/lib/root.js +140 -19
- package/lib/root.js.map +1 -1
- package/lib/utils-correctness.js +33 -5
- package/lib/utils-correctness.js.map +1 -1
- package/package.json +8 -5
- package/src/__tests__/__snapshots__/{image-container-test.jsx.snap → image-container.test.jsx.snap} +20 -0
- package/src/__tests__/__snapshots__/possible-response.test.jsx.snap +5 -0
- package/src/__tests__/__snapshots__/root.test.jsx.snap +50 -0
- package/src/__tests__/{image-container-test.jsx → image-container.test.jsx} +12 -5
- package/src/__tests__/{possible-response-test.jsx → possible-response.test.jsx} +5 -3
- package/src/__tests__/{root-test.jsx → root.test.jsx} +4 -3
- package/src/__tests__/utils.test.js +207 -0
- package/src/constants.js +3 -3
- package/src/evaluation-icon.jsx +8 -17
- package/src/image-container.jsx +14 -19
- package/src/image-drop-target.jsx +45 -38
- package/src/index.js +10 -18
- package/src/interactive-section.jsx +15 -22
- package/src/possible-response.jsx +43 -23
- package/src/possible-responses.jsx +17 -25
- package/src/root.jsx +174 -100
- package/src/utils-correctness.js +44 -14
- package/src/__tests__/__snapshots__/possible-response-test.jsx.snap +0 -13
- package/src/__tests__/__snapshots__/root-test.jsx.snap +0 -38
- 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 {
|
|
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;
|
|
21
|
+
styleProp = null;
|
|
22
|
+
break;
|
|
18
23
|
case true:
|
|
19
|
-
styleProp = 'baseCorrect';
|
|
24
|
+
styleProp = 'baseCorrect';
|
|
25
|
+
break;
|
|
20
26
|
default:
|
|
21
|
-
styleProp = 'baseIncorrect';
|
|
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
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
containerStyle={evaluationStyle}
|
|
45
|
+
<span
|
|
46
|
+
className={classNames([classes.span, { [classes.hiddenSpan]: data.hidden }])}
|
|
47
|
+
dangerouslySetInnerHTML={{ __html: data.value }}
|
|
41
48
|
/>
|
|
42
|
-
|
|
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 {
|
|
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
|
|
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
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
92
|
-
answers.filter(
|
|
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
|
-
|
|
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
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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);
|