UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

205 lines (180 loc) • 6.85 kB
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> ) } }