@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
205 lines (180 loc) • 6.85 kB
JavaScript
import React, {Component} from 'react'
import PropTypes from 'prop-types'
import {v4 as uuid} from 'uuid'
import update from 'immutability-helper'
import {Flex} from '@instructure/quiz-common/components/Flex/index'
import {FormFieldGroup} from '@instructure/quiz-common/components/FormFieldGroup/index'
import RichFillBlankInteractionType from '../../../../records/interactions/rich_fill_blank'
import ChoiceInput from '../../../common/edit/components/ChoiceInput'
import Footer from '../../../common/edit/components/Footer'
import {getNextItemIdFromArray} from '../../../../util/focusHelpers'
import {toErrors} from '../../../../util/instUIMessages'
import t from '@instructure/quiz-i18n/format-message'
import {normalizeErrors} from '../../../../util/normalizeErrors'
export default class WordBankDistractors extends Component {
static interactionType = RichFillBlankInteractionType
static propTypes = {
changeItemState: PropTypes.func.isRequired,
indexForWordBankChoice: PropTypes.func.isRequired,
interactionData: PropTypes.shape({
wordBankChoices: PropTypes.arrayOf(
PropTypes.shape({
itemBody: PropTypes.string,
id: PropTypes.string,
}),
),
}).isRequired,
interactionDataErrors: PropTypes.oneOfType([
PropTypes.shape({$errors: PropTypes.arrayOf(PropTypes.string)}),
PropTypes.objectOf(PropTypes.shape({itemBody: PropTypes.arrayOf(PropTypes.string)})),
]).isRequired,
notifyScreenreader: PropTypes.func.isRequired,
onModalClose: PropTypes.func,
onModalOpen: PropTypes.func,
overrideEditableForRegrading: PropTypes.bool,
scoringData: PropTypes.shape({
value: PropTypes.arrayOf(
PropTypes.shape({
scoringData: PropTypes.shape({
choiceId: PropTypes.string,
}),
}),
),
}).isRequired,
isSurvey: PropTypes.bool.isRequired,
}
static defaultProps = {
overrideEditableForRegrading: false,
onModalClose: void 0,
onModalOpen: void 0,
scoringData: void 0,
isSurvey: true,
}
distractorRefs = {}
getErrorsForChoice = id => {
const wordBankChoiceIndex = this.props.indexForWordBankChoice(id)
const errors = this.props.interactionDataErrors[wordBankChoiceIndex] || {}
return toErrors(errors.itemBody || [])
}
onWordBankDistractorInputChange = (id, e) => {
const wordBankChoiceIndex = this.props.indexForWordBankChoice(id)
const interactionData = update(this.props.interactionData, {
wordBankChoices: {
[wordBankChoiceIndex]: {
itemBody: {$set: e.target.value},
},
},
})
this.props.changeItemState({interactionData})
}
handleCreateWordBankDistractor = () => {
const id = uuid()
const newWordBankDistractor = {id, itemBody: ''}
const interactionData = update(this.props.interactionData, {
wordBankChoices: {
$push: [newWordBankDistractor],
},
})
this.props.changeItemState({interactionData})
}
handleRemoveWordBankDistractor = distractorId => {
const newWordBankChoices = this.props.interactionData.wordBankChoices.filter(wordBankChoice => {
return wordBankChoice.id !== distractorId
})
const interactionData = update(this.props.interactionData, {
wordBankChoices: {
$set: newWordBankChoices,
},
})
this.props.changeItemState({interactionData})
// focus handling:
const nextDistractorId = getNextItemIdFromArray(distractorId, this.getWordBankDistractors())
const nextDistractorRef = this.distractorRefs[nextDistractorId]
if (nextDistractorRef) {
nextDistractorRef.removeChoicebutton.focus()
} else {
this.addDistractorRef.focus()
}
}
getWordBankDistractors() {
const correctChoiceIds = this.props.scoringData.value
.map(scoringData => {
return scoringData.scoringData.choiceId
})
.filter(c => c)
return this.props.interactionData.wordBankChoices.filter(wordBankChoice => {
return !correctChoiceIds.includes(wordBankChoice.id)
})
}
handleRemoveChoice = distractorId => () => {
this.handleRemoveWordBankDistractor(distractorId)
}
handleDistractorRef = distractorId => node => {
this.distractorRefs[distractorId] = node
}
handleAddDistractorRef = node => {
this.addDistractorRef = node
}
render() {
const {
isSurvey,
interactionData,
interactionDataErrors,
notifyScreenreader,
overrideEditableForRegrading,
onModalOpen,
onModalClose,
} = this.props
if (!interactionData.wordBankChoices) return null
let noFocusOnFirstTime = true
return (
<FormFieldGroup
description={isSurvey ? t('Answers') : t('Word Bank Distractors')}
messages={normalizeErrors(interactionDataErrors.$errors)}
>
<Flex direction="column" gap="small">
<Flex.Item>
<Flex direction="column" gap="small">
{this.getWordBankDistractors().map((distractor, i, source) => {
const focusOnMount = noFocusOnFirstTime
? (noFocusOnFirstTime = !noFocusOnFirstTime)
: i + 1 === source.length && !distractor.itemBody
return (
<ChoiceInput
key={distractor.id}
id={distractor.id}
renderLabel={isSurvey ? t('Answer') : t('Distractor')}
isRequired={true}
disabledFields={overrideEditableForRegrading ? ['answerInput'] : []}
itemBody={distractor.itemBody}
errors={this.getErrorsForChoice(distractor.id)}
onInputChange={this.onWordBankDistractorInputChange}
onModalClose={onModalClose}
onModalOpen={onModalOpen}
onRemoveChoice={this.handleRemoveChoice(distractor.id)}
ref={this.handleDistractorRef(distractor.id)}
focusOnMount={focusOnMount}
shouldRenderRemoveChoice={!overrideEditableForRegrading}
noRCE
notifyScreenreader={notifyScreenreader}
/>
)
})}
</Flex>
</Flex.Item>
{!overrideEditableForRegrading && (
<Flex.Item>
<Footer
ref={this.handleAddDistractorRef}
onCreateChoice={this.handleCreateWordBankDistractor}
buttonText={isSurvey ? t('Answer') : t('Distractor')}
screenReaderText={isSurvey ? t('Add Answer') : t('Add Distractor')}
notifyScreenreader={notifyScreenreader}
/>
</Flex.Item>
)}
</Flex>
</FormFieldGroup>
)
}
}