@pie-element/multiple-choice 11.0.1-next.0 → 11.0.2-esm.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.
@@ -0,0 +1,902 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import debug from 'debug';
4
+ import { ModelUpdatedEvent, InsertImageEvent, DeleteImageEvent, InsertSoundEvent, DeleteSoundEvent } from '@pie-framework/pie-configure-events';
5
+ import PropTypes from 'prop-types';
6
+ import EditableHtml from '@pie-lib/editable-html';
7
+ import { layout, InputContainer, ChoiceConfiguration, choiceUtils, AlertDialog, settings } from '@pie-lib/config-ui';
8
+ import { color } from '@pie-lib/render-ui';
9
+ import { withStyles } from '@material-ui/core/styles';
10
+ import Button from '@material-ui/core/Button';
11
+ import Tooltip from '@material-ui/core/Tooltip';
12
+ import Typography from '@material-ui/core/Typography';
13
+ import Info from '@material-ui/icons/Info';
14
+ import merge from 'lodash/merge';
15
+ import defaults from 'lodash/defaults';
16
+
17
+ function _extends() {
18
+ _extends = Object.assign || function (target) {
19
+ for (var i = 1; i < arguments.length; i++) {
20
+ var source = arguments[i];
21
+
22
+ for (var key in source) {
23
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
24
+ target[key] = source[key];
25
+ }
26
+ }
27
+ }
28
+
29
+ return target;
30
+ };
31
+
32
+ return _extends.apply(this, arguments);
33
+ }
34
+
35
+ const generateValidationMessage = config => {
36
+ const {
37
+ minAnswerChoices,
38
+ maxAnswerChoices
39
+ } = config;
40
+ const answerChoicesMessage = `\nThere should be at least ${minAnswerChoices} ` + (maxAnswerChoices ? `and at most ${maxAnswerChoices} ` : '') + 'answer choices defined.' + '\nEvery answer choice should be non-blank and unique.';
41
+ const correctAnswerMessage = '\nA correct answer must be defined.';
42
+ const message = 'Validation requirements:' + answerChoicesMessage + correctAnswerMessage;
43
+ return message;
44
+ };
45
+
46
+ const {
47
+ Panel,
48
+ toggle,
49
+ radio,
50
+ dropdown
51
+ } = settings;
52
+ const MAX_CHOICES = 9;
53
+
54
+ const styles = theme => ({
55
+ promptHolder: {
56
+ width: '100%',
57
+ paddingTop: theme.spacing.unit * 2,
58
+ marginBottom: theme.spacing.unit * 2
59
+ },
60
+ rationaleHolder: {
61
+ flex: 1,
62
+ marginTop: theme.spacing.unit * 1.5,
63
+ paddingTop: theme.spacing.unit * 2,
64
+ marginLeft: theme.spacing.unit * 3.5
65
+ },
66
+ choiceConfigurationHolder: {
67
+ display: 'flex',
68
+ flexDirection: 'column',
69
+ marginBottom: theme.spacing.unit
70
+ },
71
+ choiceConfiguration: {
72
+ width: '100%'
73
+ },
74
+ switchElement: {
75
+ justifyContent: 'space-between',
76
+ margin: 0
77
+ },
78
+ addButton: {
79
+ marginTop: theme.spacing.unit,
80
+ float: 'right'
81
+ },
82
+ disableButton: {
83
+ cursor: 'not-allowed',
84
+ pointerEvents: 'all',
85
+ backgroundColor: color.disabled(),
86
+ '&:hover': {
87
+ backgroundColor: color.disabled()
88
+ },
89
+ '&:focus': {
90
+ backgroundColor: color.disabled()
91
+ }
92
+ },
93
+ flexContainer: {
94
+ display: 'flex',
95
+ alignItems: 'center'
96
+ },
97
+ titleText: {
98
+ fontSize: theme.typography.fontSize + 2,
99
+ marginRight: theme.spacing.unit
100
+ },
101
+ tooltip: {
102
+ fontSize: theme.typography.fontSize - 2,
103
+ whiteSpace: 'pre',
104
+ maxWidth: '500px'
105
+ },
106
+ errorText: {
107
+ fontSize: theme.typography.fontSize - 2,
108
+ color: theme.palette.error.main,
109
+ paddingTop: theme.spacing.unit
110
+ }
111
+ });
112
+
113
+ const Design = withStyles(styles)(props => {
114
+ var _configuration$teache, _configuration$prompt;
115
+
116
+ const {
117
+ classes,
118
+ model,
119
+ configuration,
120
+ onPromptChanged,
121
+ onChoiceChanged,
122
+ onRemoveChoice,
123
+ onAddChoice,
124
+ imageSupport,
125
+ uploadSoundSupport,
126
+ onChangeModel,
127
+ onConfigurationChanged,
128
+ onTeacherInstructionsChanged
129
+ } = props;
130
+ const {
131
+ addChoiceButton = {},
132
+ contentDimensions = {},
133
+ feedback = {},
134
+ deleteChoice = {},
135
+ choiceMode = {},
136
+ choicePrefix = {},
137
+ partialScoring = {},
138
+ lockChoiceOrder = {},
139
+ teacherInstructions = {},
140
+ studentInstructions = {},
141
+ rationale = {},
142
+ scoringType = {},
143
+ sequentialChoiceLabels = {},
144
+ settingsPanelDisabled,
145
+ choicesLayout,
146
+ spellCheck = {},
147
+ gridColumns,
148
+ maxImageWidth = {},
149
+ maxImageHeight = {},
150
+ prompt = {},
151
+ withRubric = {},
152
+ mathMlOptions = {},
153
+ language = {},
154
+ languageChoices = {}
155
+ } = configuration || {};
156
+ let {
157
+ maxAnswerChoices
158
+ } = configuration || {};
159
+ const {
160
+ limitChoicesNumber,
161
+ teacherInstructionsEnabled,
162
+ rationaleEnabled,
163
+ feedbackEnabled,
164
+ promptEnabled,
165
+ spellCheckEnabled,
166
+ choices,
167
+ errors,
168
+ toolbarEditorPosition,
169
+ extraCSSRules
170
+ } = model || {};
171
+ const {
172
+ answerChoices: answerChoicesError,
173
+ choices: choicesErrors,
174
+ correctResponse: correctResponseError,
175
+ prompt: promptError,
176
+ rationale: rationaleErrors,
177
+ teacherInstructions: teacherInstructionsError
178
+ } = errors || {};
179
+ const nrOfColumnsAvailable = choices != null && choices.length ? Array.from({
180
+ length: choices.length
181
+ }, (_, i) => `${i + 1}`) : [];
182
+ const {
183
+ baseInputConfiguration = {}
184
+ } = configuration;
185
+ const toolbarOpts = {
186
+ position: toolbarEditorPosition === 'top' ? 'top' : 'bottom'
187
+ }; // if old property is used, set maxAnswerChoices to 9
188
+
189
+ if (limitChoicesNumber) {
190
+ maxAnswerChoices = MAX_CHOICES;
191
+ }
192
+
193
+ const getPluginProps = (props = {}) => _extends({}, baseInputConfiguration, props);
194
+
195
+ const validationMessage = generateValidationMessage(configuration);
196
+ const defaultImageMaxWidth = maxImageWidth && maxImageWidth.prompt;
197
+ const defaultImageMaxHeight = maxImageHeight && maxImageHeight.prompt;
198
+ const addChoiceButtonTooltip = maxAnswerChoices && (choices == null ? void 0 : choices.length) >= maxAnswerChoices ? `Only ${maxAnswerChoices} allowed maximum` : '';
199
+ const panelSettings = {
200
+ choiceMode: choiceMode.settings && radio(choiceMode.label, ['checkbox', 'radio']),
201
+ 'sequentialChoiceLabels.enabled': sequentialChoiceLabels.settings && toggle(sequentialChoiceLabels.label, true),
202
+ choicePrefix: choicePrefix.settings && radio(choicePrefix.label, ['numbers', 'letters']),
203
+ partialScoring: partialScoring.settings && toggle(partialScoring.label),
204
+ lockChoiceOrder: lockChoiceOrder.settings && toggle(lockChoiceOrder.label),
205
+ feedbackEnabled: feedback.settings && toggle(feedback.label),
206
+ choicesLayout: choicesLayout.settings && dropdown(choicesLayout.label, ['vertical', 'grid', 'horizontal']),
207
+ gridColumns: choicesLayout.settings && model.choicesLayout === 'grid' && nrOfColumnsAvailable.length > 0 && dropdown(gridColumns.label, nrOfColumnsAvailable),
208
+ 'language.enabled': language.settings && toggle(language.label, true),
209
+ language: language.settings && language.enabled && dropdown(languageChoices.label, languageChoices.options)
210
+ };
211
+ const panelProperties = {
212
+ teacherInstructionsEnabled: teacherInstructions.settings && toggle(teacherInstructions.label),
213
+ studentInstructionsEnabled: studentInstructions.settings && toggle(studentInstructions.label),
214
+ promptEnabled: prompt.settings && toggle(prompt.label),
215
+ rationaleEnabled: rationale.settings && toggle(rationale.label),
216
+ spellCheckEnabled: spellCheck.settings && toggle(spellCheck.label),
217
+ scoringType: scoringType.settings && radio(scoringType.label, ['auto', 'rubric']),
218
+ rubricEnabled: (withRubric == null ? void 0 : withRubric.settings) && toggle(withRubric == null ? void 0 : withRubric.label)
219
+ };
220
+ return /*#__PURE__*/React.createElement(layout.ConfigLayout, {
221
+ dimensions: contentDimensions,
222
+ hideSettings: settingsPanelDisabled,
223
+ extraCSSRules: extraCSSRules,
224
+ settings: /*#__PURE__*/React.createElement(Panel, {
225
+ model: model,
226
+ onChangeModel: onChangeModel,
227
+ configuration: configuration,
228
+ onChangeConfiguration: onConfigurationChanged,
229
+ groups: {
230
+ Settings: panelSettings,
231
+ Properties: panelProperties
232
+ }
233
+ })
234
+ }, teacherInstructionsEnabled && /*#__PURE__*/React.createElement(InputContainer, {
235
+ label: teacherInstructions.label,
236
+ className: classes.promptHolder
237
+ }, /*#__PURE__*/React.createElement(EditableHtml, {
238
+ className: classes.prompt,
239
+ markup: model.teacherInstructions || '',
240
+ onChange: onTeacherInstructionsChanged,
241
+ imageSupport: imageSupport,
242
+ nonEmpty: false,
243
+ error: teacherInstructionsError,
244
+ toolbarOpts: toolbarOpts,
245
+ pluginProps: getPluginProps(configuration == null ? void 0 : (_configuration$teache = configuration.teacherInstructions) == null ? void 0 : _configuration$teache.inputConfiguration),
246
+ spellCheck: spellCheckEnabled,
247
+ maxImageWidth: maxImageWidth && maxImageWidth.teacherInstructions || defaultImageMaxWidth,
248
+ maxImageHeight: maxImageHeight && maxImageHeight.teacherInstructions || defaultImageMaxHeight,
249
+ uploadSoundSupport: uploadSoundSupport,
250
+ languageCharactersProps: [{
251
+ language: 'spanish'
252
+ }, {
253
+ language: 'special'
254
+ }],
255
+ mathMlOptions: mathMlOptions
256
+ }), teacherInstructionsError && /*#__PURE__*/React.createElement("div", {
257
+ className: classes.errorText
258
+ }, teacherInstructionsError)), promptEnabled && /*#__PURE__*/React.createElement(InputContainer, {
259
+ label: prompt.label,
260
+ className: classes.promptHolder
261
+ }, /*#__PURE__*/React.createElement(EditableHtml, {
262
+ className: classes.prompt,
263
+ markup: model.prompt,
264
+ onChange: onPromptChanged,
265
+ imageSupport: imageSupport,
266
+ nonEmpty: false,
267
+ disableUnderline: true,
268
+ error: promptError,
269
+ toolbarOpts: toolbarOpts,
270
+ pluginProps: getPluginProps(configuration == null ? void 0 : (_configuration$prompt = configuration.prompt) == null ? void 0 : _configuration$prompt.inputConfiguration),
271
+ spellCheck: spellCheckEnabled,
272
+ maxImageWidth: maxImageWidth && maxImageWidth.prompt,
273
+ maxImageHeight: maxImageHeight && maxImageHeight.prompt,
274
+ uploadSoundSupport: uploadSoundSupport,
275
+ languageCharactersProps: [{
276
+ language: 'spanish'
277
+ }, {
278
+ language: 'special'
279
+ }],
280
+ mathMlOptions: mathMlOptions
281
+ }), promptError && /*#__PURE__*/React.createElement("div", {
282
+ className: classes.errorText
283
+ }, promptError)), /*#__PURE__*/React.createElement("div", {
284
+ className: classes.flexContainer
285
+ }, /*#__PURE__*/React.createElement(Typography, {
286
+ className: classes.titleText,
287
+ component: 'div'
288
+ }, "Choices"), /*#__PURE__*/React.createElement(Tooltip, {
289
+ classes: {
290
+ tooltip: classes.tooltip
291
+ },
292
+ disableFocusListener: true,
293
+ disableTouchListener: true,
294
+ placement: 'right',
295
+ title: validationMessage
296
+ }, /*#__PURE__*/React.createElement(Info, {
297
+ fontSize: 'small',
298
+ color: 'primary'
299
+ }))), choices.map((choice, index) => {
300
+ var _configuration$choice, _configuration$ration;
301
+
302
+ return /*#__PURE__*/React.createElement("div", {
303
+ key: `choice-${index}`,
304
+ className: classes.choiceConfigurationHolder
305
+ }, /*#__PURE__*/React.createElement(ChoiceConfiguration, {
306
+ key: index,
307
+ index: index + 1,
308
+ useLetterOrdering: model.choicePrefix === 'letters',
309
+ className: classes.choiceConfiguration,
310
+ mode: model.choiceMode,
311
+ data: choice,
312
+ defaultFeedback: {},
313
+ imageSupport: imageSupport,
314
+ disableImageAlignmentButtons: true,
315
+ onDelete: () => onRemoveChoice(index),
316
+ onChange: c => onChoiceChanged(index, c),
317
+ allowFeedBack: feedbackEnabled,
318
+ allowDelete: deleteChoice.settings,
319
+ noLabels: true,
320
+ pluginOpts: getPluginProps(configuration == null ? void 0 : (_configuration$choice = configuration.choices) == null ? void 0 : _configuration$choice.inputConfiguration),
321
+ toolbarOpts: toolbarOpts,
322
+ spellCheck: spellCheckEnabled,
323
+ error: (choicesErrors == null ? void 0 : choicesErrors[choice.value]) || null,
324
+ noCorrectAnswerError: correctResponseError,
325
+ maxImageWidth: maxImageWidth && maxImageWidth.choices || defaultImageMaxWidth,
326
+ maxImageHeight: maxImageHeight && maxImageHeight.choices || defaultImageMaxHeight,
327
+ uploadSoundSupport: uploadSoundSupport,
328
+ mathMlOptions: mathMlOptions
329
+ }), rationaleEnabled && /*#__PURE__*/React.createElement(InputContainer, {
330
+ key: `rationale-${index}`,
331
+ label: rationale.label,
332
+ className: classes.rationaleHolder
333
+ }, /*#__PURE__*/React.createElement(EditableHtml, {
334
+ className: classes.rationale,
335
+ markup: choice.rationale || '',
336
+ onChange: c => onChoiceChanged(index, _extends({}, choice, {
337
+ rationale: c
338
+ })),
339
+ imageSupport: imageSupport,
340
+ error: (rationaleErrors == null ? void 0 : rationaleErrors[choice.value]) || null,
341
+ toolbarOpts: toolbarOpts,
342
+ pluginProps: getPluginProps(configuration == null ? void 0 : (_configuration$ration = configuration.rationale) == null ? void 0 : _configuration$ration.inputConfiguration),
343
+ spellCheck: spellCheckEnabled,
344
+ maxImageWidth: maxImageWidth && maxImageWidth.rationale || defaultImageMaxWidth,
345
+ maxImageHeight: maxImageHeight && maxImageHeight.rationale || defaultImageMaxHeight,
346
+ uploadSoundSupport: uploadSoundSupport,
347
+ languageCharactersProps: [{
348
+ language: 'spanish'
349
+ }, {
350
+ language: 'special'
351
+ }],
352
+ mathMlOptions: mathMlOptions
353
+ }), (rationaleErrors == null ? void 0 : rationaleErrors[choice.value]) && /*#__PURE__*/React.createElement("div", {
354
+ className: classes.errorText
355
+ }, rationaleErrors == null ? void 0 : rationaleErrors[choice.value])));
356
+ }), correctResponseError && /*#__PURE__*/React.createElement("div", {
357
+ className: classes.errorText
358
+ }, correctResponseError), answerChoicesError && /*#__PURE__*/React.createElement("div", {
359
+ className: classes.errorText
360
+ }, answerChoicesError), addChoiceButton.settings && /*#__PURE__*/React.createElement(Tooltip, {
361
+ title: addChoiceButtonTooltip,
362
+ classes: {
363
+ tooltip: classes.tooltip
364
+ }
365
+ }, /*#__PURE__*/React.createElement(Button, {
366
+ classes: {
367
+ root: maxAnswerChoices && (choices == null ? void 0 : choices.length) >= maxAnswerChoices && classes.disableButton
368
+ },
369
+ className: classes.addButton,
370
+ variant: "contained",
371
+ color: "primary",
372
+ onClick: onAddChoice
373
+ }, addChoiceButton.label)));
374
+ });
375
+ class Main extends React.Component {
376
+ constructor(...args) {
377
+ super(...args);
378
+ this.state = {
379
+ showWarning: false
380
+ };
381
+
382
+ this.onRemoveChoice = index => {
383
+ const {
384
+ model,
385
+ configuration,
386
+ onModelChanged
387
+ } = this.props;
388
+ const {
389
+ minAnswerChoices
390
+ } = configuration || {};
391
+
392
+ if (minAnswerChoices && model.choices.length === minAnswerChoices) {
393
+ this.setState({
394
+ showWarning: true
395
+ });
396
+ return;
397
+ }
398
+
399
+ model.choices.splice(index, 1);
400
+ onModelChanged(model);
401
+ };
402
+
403
+ this.onAddChoice = () => {
404
+ const {
405
+ model,
406
+ configuration,
407
+ onModelChanged
408
+ } = this.props;
409
+ let {
410
+ maxAnswerChoices
411
+ } = configuration || {};
412
+ const {
413
+ limitChoicesNumber
414
+ } = model || {}; // if old property is used, set maxAnswerChoices to 9
415
+
416
+ if (limitChoicesNumber) {
417
+ maxAnswerChoices = MAX_CHOICES;
418
+ }
419
+
420
+ if (maxAnswerChoices && model.choices.length >= maxAnswerChoices) {
421
+ return;
422
+ }
423
+
424
+ model.choices.push({
425
+ label: '',
426
+ value: choiceUtils.firstAvailableIndex(model.choices.map(c => c.value), 0),
427
+ feedback: {
428
+ type: 'none'
429
+ }
430
+ });
431
+ onModelChanged(model);
432
+ };
433
+
434
+ this.onChoiceChanged = (index, choice) => {
435
+ const {
436
+ model,
437
+ onModelChanged
438
+ } = this.props;
439
+
440
+ if (choice.correct && model.choiceMode === 'radio') {
441
+ model.choices = model.choices.map(c => merge({}, c, {
442
+ correct: false
443
+ }));
444
+ }
445
+
446
+ model.choices.splice(index, 1, choice);
447
+ onModelChanged(model);
448
+ };
449
+
450
+ this.onPromptChanged = prompt => {
451
+ this.props.onModelChanged(_extends({}, this.props.model, {
452
+ prompt
453
+ }));
454
+ };
455
+
456
+ this.onTeacherInstructionsChanged = teacherInstructions => {
457
+ this.props.onModelChanged(_extends({}, this.props.model, {
458
+ teacherInstructions
459
+ }));
460
+ };
461
+
462
+ this.onModelChanged = (model, key) => {
463
+ const {
464
+ onModelChanged
465
+ } = this.props;
466
+
467
+ switch (key) {
468
+ case 'choiceMode':
469
+ {
470
+ let value = model.choiceMode;
471
+
472
+ if (value === 'radio') {
473
+ let correctFound = false;
474
+ model.choices = model.choices.map(c => {
475
+ if (correctFound) {
476
+ c.correct = false;
477
+ return c;
478
+ }
479
+
480
+ if (c.correct) {
481
+ correctFound = true;
482
+ }
483
+
484
+ return c;
485
+ });
486
+ }
487
+
488
+ onModelChanged(model, true);
489
+ break;
490
+ }
491
+
492
+ default:
493
+ onModelChanged(model);
494
+ break;
495
+ }
496
+ };
497
+ }
498
+
499
+ render() {
500
+ const {
501
+ configuration: {
502
+ minAnswerChoices
503
+ } = {}
504
+ } = this.props;
505
+ const {
506
+ showWarning
507
+ } = this.state;
508
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(AlertDialog, {
509
+ open: showWarning,
510
+ title: "Warning",
511
+ text: `There can't be less than ${minAnswerChoices || 0} choices.`,
512
+ onConfirm: () => this.setState({
513
+ showWarning: false
514
+ })
515
+ }), /*#__PURE__*/React.createElement(Design, _extends({}, this.props, {
516
+ onChangeModel: this.onModelChanged,
517
+ onRemoveChoice: this.onRemoveChoice,
518
+ onChoiceChanged: this.onChoiceChanged,
519
+ onAddChoice: this.onAddChoice,
520
+ onPromptChanged: this.onPromptChanged,
521
+ onTeacherInstructionsChanged: this.onTeacherInstructionsChanged
522
+ })));
523
+ }
524
+
525
+ }
526
+ Main.propTypes = {
527
+ model: PropTypes.object.isRequired,
528
+ configuration: PropTypes.object.isRequired,
529
+ disableSidePanel: PropTypes.bool,
530
+ onModelChanged: PropTypes.func.isRequired,
531
+ onConfigurationChanged: PropTypes.func.isRequired,
532
+ classes: PropTypes.object.isRequired,
533
+ imageSupport: PropTypes.shape({
534
+ add: PropTypes.func.isRequired,
535
+ delete: PropTypes.func.isRequired
536
+ })
537
+ };
538
+ const Styled = withStyles(styles)(Main);
539
+
540
+ /** NOTE: teacherInstructions, studentInstructions, rationale & scoringType
541
+ * functionalities are not defined yet - the value for those can belong to
542
+ * model or to configure
543
+ */
544
+ var sensibleDefaults = {
545
+ model: {
546
+ choiceMode: 'checkbox',
547
+ choicePrefix: 'letters',
548
+ choices: [],
549
+ choicesLayout: 'vertical',
550
+ feedbackEnabled: false,
551
+ gridColumns: 2,
552
+ lockChoiceOrder: true,
553
+ partialScoring: true,
554
+ prompt: '',
555
+ promptEnabled: true,
556
+ rationale: '',
557
+ rationaleEnabled: true,
558
+ scoringType: 'auto',
559
+ studentInstructionsEnabled: true,
560
+ teacherInstructions: '',
561
+ teacherInstructionsEnabled: true,
562
+ toolbarEditorPosition: 'bottom',
563
+ selectedAnswerBackgroundColor: 'initial',
564
+ keyboardEventsEnabled: false
565
+ },
566
+ configuration: {
567
+ baseInputConfiguration: {
568
+ audio: {
569
+ disabled: false
570
+ },
571
+ video: {
572
+ disabled: false
573
+ },
574
+ image: {
575
+ disabled: false
576
+ },
577
+ textAlign: {
578
+ disabled: true
579
+ },
580
+ showParagraphs: {
581
+ disabled: false
582
+ },
583
+ separateParagraphs: {
584
+ disabled: true
585
+ }
586
+ },
587
+ choices: {
588
+ inputConfiguration: {
589
+ audio: {
590
+ disabled: false
591
+ },
592
+ video: {
593
+ disabled: false
594
+ },
595
+ image: {
596
+ disabled: false
597
+ }
598
+ }
599
+ },
600
+ spellCheck: {
601
+ label: 'Spellcheck',
602
+ settings: false,
603
+ enabled: true
604
+ },
605
+ choicesLayout: {
606
+ settings: false,
607
+ label: 'Choices Layout'
608
+ },
609
+ gridColumns: {
610
+ label: 'Grid columns'
611
+ },
612
+ answerChoiceCount: 0,
613
+ addChoiceButton: {
614
+ settings: true,
615
+ label: 'Add a Choice'
616
+ },
617
+ choiceMode: {
618
+ settings: true,
619
+ label: 'Response Type'
620
+ },
621
+ choicePrefix: {
622
+ settings: true,
623
+ label: 'Choice Labels'
624
+ },
625
+ deleteChoice: {
626
+ settings: true
627
+ },
628
+ feedback: {
629
+ settings: true,
630
+ label: 'Feedback'
631
+ },
632
+ prompt: {
633
+ settings: true,
634
+ label: 'Prompt',
635
+ inputConfiguration: {
636
+ audio: {
637
+ disabled: false
638
+ },
639
+ video: {
640
+ disabled: false
641
+ },
642
+ image: {
643
+ disabled: false
644
+ }
645
+ },
646
+ required: false
647
+ },
648
+ lockChoiceOrder: {
649
+ settings: true,
650
+ label: 'Lock Choice Order'
651
+ },
652
+ partialScoring: {
653
+ settings: false,
654
+ label: 'Allow Partial Scoring'
655
+ },
656
+ rationale: {
657
+ settings: true,
658
+ label: 'Rationale',
659
+ inputConfiguration: {
660
+ audio: {
661
+ disabled: true
662
+ },
663
+ video: {
664
+ disabled: true
665
+ },
666
+ image: {
667
+ disabled: false
668
+ }
669
+ },
670
+ required: false
671
+ },
672
+ scoringType: {
673
+ settings: false,
674
+ label: 'Scoring Type'
675
+ },
676
+ studentInstructions: {
677
+ settings: false,
678
+ label: 'Student Instructions'
679
+ },
680
+ teacherInstructions: {
681
+ settings: true,
682
+ label: 'Teacher Instructions',
683
+ inputConfiguration: {
684
+ audio: {
685
+ disabled: false
686
+ },
687
+ video: {
688
+ disabled: false
689
+ },
690
+ image: {
691
+ disabled: false
692
+ }
693
+ },
694
+ required: false
695
+ },
696
+ toolbarEditorPosition: {
697
+ settings: false,
698
+ label: 'Toolbar Editor Position'
699
+ },
700
+ minAnswerChoices: 2,
701
+ maxAnswerChoices: 5,
702
+ maxImageWidth: {
703
+ teacherInstructions: 300,
704
+ prompt: 300,
705
+ rationale: 636,
706
+ choices: 900
707
+ },
708
+ maxImageHeight: {
709
+ teacherInstructions: 300,
710
+ prompt: 300,
711
+ rationale: 300,
712
+ choices: 300
713
+ },
714
+ withRubric: {
715
+ settings: false,
716
+ label: 'Add Rubric'
717
+ },
718
+ mathMlOptions: {
719
+ mmlOutput: false,
720
+ mmlEditing: false
721
+ },
722
+ language: {
723
+ settings: false,
724
+ label: 'Specify Language',
725
+ enabled: false
726
+ },
727
+ languageChoices: {
728
+ label: 'Language Choices',
729
+ options: []
730
+ }
731
+ }
732
+ };
733
+
734
+ const log = debug('multiple-choice:configure');
735
+
736
+ const generateFormattedChoices = (choices, choiceCount = 0) => {
737
+ if (!choices || choices.length === 0) {
738
+ let formattedChoices = [];
739
+
740
+ for (let i = 0; i < choiceCount; i++) {
741
+ formattedChoices.push({
742
+ value: `${i}`,
743
+ label: '',
744
+ feedback: {
745
+ type: 'none',
746
+ value: ''
747
+ }
748
+ });
749
+ }
750
+
751
+ return formattedChoices;
752
+ }
753
+
754
+ return choices;
755
+ };
756
+
757
+ const prepareCustomizationObject = (config, model) => {
758
+ const configuration = defaults(config, sensibleDefaults.configuration);
759
+ return {
760
+ configuration,
761
+ model: _extends({}, model, {
762
+ choices: generateFormattedChoices(model && model.choices || [], configuration && configuration.answerChoiceCount)
763
+ })
764
+ };
765
+ };
766
+
767
+ class MultipleChoice extends HTMLElement {
768
+ constructor() {
769
+ super();
770
+ this._model = MultipleChoice.createDefaultModel();
771
+ this._configuration = sensibleDefaults.configuration;
772
+ this.onModelChanged = this.onModelChanged.bind(this);
773
+ this.onConfigurationChanged = this.onConfigurationChanged.bind(this);
774
+ }
775
+
776
+ set model(s) {
777
+ this._model = MultipleChoice.createDefaultModel(s);
778
+
779
+ this._render();
780
+ }
781
+
782
+ set configuration(c) {
783
+ var _newConfiguration$lan;
784
+
785
+ const info = prepareCustomizationObject(c, this._model);
786
+ this.onModelChanged(info.model);
787
+
788
+ const newConfiguration = _extends({}, sensibleDefaults.configuration, info.configuration);
789
+
790
+ this._configuration = newConfiguration; // if language:enabled is true, then the corresponding default item model should include a language value;
791
+ // if it is false, then the language field should be omitted from the item model.
792
+ // if a default item model includes a language value (e.g., en_US) and the corresponding authoring view settings have language:settings = true,
793
+ // then (a) language:enabled should also be true, and (b) that default language value should be represented in languageChoices[] (as a key).
794
+
795
+ if (newConfiguration != null && (_newConfiguration$lan = newConfiguration.language) != null && _newConfiguration$lan.enabled) {
796
+ var _newConfiguration$lan2, _newConfiguration$lan3;
797
+
798
+ if (newConfiguration != null && (_newConfiguration$lan2 = newConfiguration.languageChoices) != null && (_newConfiguration$lan3 = _newConfiguration$lan2.options) != null && _newConfiguration$lan3.length) {
799
+ this._model.language = newConfiguration == null ? void 0 : newConfiguration.languageChoices.options[0].value;
800
+ }
801
+ } else if (newConfiguration.language.settings && this._model.language) {
802
+ this._configuration.language.enabled = true;
803
+
804
+ if (!this._configuration.languageChoices.options || !this._configuration.languageChoices.options.length) {
805
+ this._configuration.languageChoices.options = [];
806
+ } // check if the language is already included in the languageChoices.options array
807
+ // and if not, then add it.
808
+
809
+
810
+ if (!this._configuration.languageChoices.options.find(option => option.value === this._model.language)) {
811
+ this._configuration.languageChoices.options.push({
812
+ value: this._model.language,
813
+ label: this._model.language
814
+ });
815
+ }
816
+ } else {
817
+ delete this._model.language;
818
+ }
819
+
820
+ this._render();
821
+ }
822
+
823
+ set disableSidePanel(s) {
824
+ this._disableSidePanel = s;
825
+
826
+ this._render();
827
+ }
828
+
829
+ dispatchModelUpdated(reset) {
830
+ const resetValue = !!reset;
831
+ this.dispatchEvent(new ModelUpdatedEvent(this._model, resetValue));
832
+ }
833
+
834
+ onModelChanged(m, reset) {
835
+ this._model = m;
836
+
837
+ this._render();
838
+
839
+ this.dispatchModelUpdated(reset);
840
+ }
841
+
842
+ onConfigurationChanged(c) {
843
+ this._configuration = prepareCustomizationObject(c, this._model).configuration;
844
+
845
+ if (this._model) {
846
+ this.onModelChanged(this._model);
847
+ }
848
+
849
+ this._render();
850
+ }
851
+ /** @param {done, progress, file} handler */
852
+
853
+
854
+ insertImage(handler) {
855
+ this.dispatchEvent(new InsertImageEvent(handler));
856
+ }
857
+
858
+ onDeleteImage(src, done) {
859
+ this.dispatchEvent(new DeleteImageEvent(src, done));
860
+ }
861
+
862
+ insertSound(handler) {
863
+ this.dispatchEvent(new InsertSoundEvent(handler));
864
+ }
865
+
866
+ onDeleteSound(src, done) {
867
+ this.dispatchEvent(new DeleteSoundEvent(src, done));
868
+ }
869
+
870
+ _render() {
871
+ log('_render');
872
+ let element = /*#__PURE__*/React.createElement(Styled, {
873
+ model: this._model,
874
+ configuration: this._configuration,
875
+ onModelChanged: this.onModelChanged,
876
+ onConfigurationChanged: this.onConfigurationChanged,
877
+ disableSidePanel: this._disableSidePanel,
878
+ imageSupport: {
879
+ add: this.insertImage.bind(this),
880
+ delete: this.onDeleteImage.bind(this)
881
+ },
882
+ uploadSoundSupport: {
883
+ add: this.insertSound.bind(this),
884
+ delete: this.onDeleteSound.bind(this)
885
+ }
886
+ });
887
+ ReactDOM.render(element, this);
888
+ }
889
+
890
+ }
891
+
892
+ MultipleChoice.createDefaultModel = (model = {}) => {
893
+ const normalizedModel = choiceUtils.normalizeChoices(_extends({}, sensibleDefaults.model, model, {
894
+ choices: generateFormattedChoices(model && model.choices || [])
895
+ })); // This is used for offering support for old models which have the property "verticalMode"
896
+
897
+ normalizedModel.choicesLayout = model.choicesLayout || model.verticalMode === false && 'horizontal' || sensibleDefaults.model.choicesLayout;
898
+ return normalizedModel;
899
+ };
900
+
901
+ export { MultipleChoice as default };
902
+ //# sourceMappingURL=configure.js.map