UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

329 lines (293 loc) • 10.6 kB
/** @jsx jsx */ import {Component} from 'react' import PropTypes from 'prop-types' import {Checkbox} from '@instructure/ui-checkbox' import NumberInput from '@instructure/quiz-number-input/components/NumberInput/index' import {ScreenReaderContent} from '@instructure/ui-a11y-content' import {jsx} from '@instructure/emotion' import EssayInteractionType from '../../../records/interactions/essay' import QuestionSettingsContainer from '../../common/edit/components/QuestionSettingsContainer' import QuestionContainer from '../../common/edit/components/QuestionContainer' import generateStyle from './styles' import generateComponentTheme from './theme' import withEditTools from '../../../util/withEditTools' import t from '@instructure/quiz-i18n/format-message' import QuestionSettingsPanel from '../../common/edit/components/QuestionSettingsPanel' import CalculatorOptionWithOqaatAlert from '../../common/edit/components/CalculatorOptionWithOqaatAlert' import {TextArea} from '@instructure/quiz-common/components/TextArea/index' import {withStyleOverrides} from '@instructure/quiz-common/util/withStyleOverrides' import {FormFieldGroup} from '@instructure/quiz-common/components/FormFieldGroup/index' /** --- category: Essay --- Essay Edit component ```jsx_example function Example (props) { const exampleProps = { itemBody: 'Please enter Question/Prompt...', interactionData: { rce: false, spellCheck: false, wordCount: false, wordLimitEnabled: false, wordLimitMax: '', wordLimitMin: '', notes: '' }, scoringData: { value: 'goths and food shortage' }, additionalOptions: [] } return ( <EssayEdit {...exampleProps} {...props} /> ) } <SettingsSwitcher locales={LOCALES}> <EditStateProvider> <Example /> </EditStateProvider> </SettingsSwitcher> ``` **/ @withEditTools @withStyleOverrides(generateStyle, generateComponentTheme) export default class EssayEdit extends Component { static displayName = 'EssayEdit' static componentId = `Quizzes${this.displayName}` static interactionType = EssayInteractionType static propTypes = { additionalOptions: PropTypes.array, calculatorType: PropTypes.string, changeItemState: PropTypes.func.isRequired, enableRichContentEditor: PropTypes.bool, interactionData: PropTypes.object.isRequired, itemBody: PropTypes.string.isRequired, onModalClose: PropTypes.func, onModalOpen: PropTypes.func, oneQuestionAtATime: PropTypes.bool, openImportModal: PropTypes.func, overrideEditableForRegrading: PropTypes.bool, scoringData: PropTypes.shape({ value: PropTypes.string.isRequired, }).isRequired, setOneQuestionAtATime: PropTypes.func, ...withEditTools.injectedProps, styles: PropTypes.object, showCalculatorOption: PropTypes.bool, } static defaultProps = { calculatorType: 'none', enableRichContentEditor: true, oneQuestionAtATime: false, overrideEditableForRegrading: false, additionalOptions: [], setOneQuestionAtATime: Function.prototype, onModalClose: void 0, onModalOpen: void 0, openImportModal: void 0, showCalculatorOption: true, } static contextTypes = { disableTextAreaAutoGrow: PropTypes.bool, } constructor(props) { super(props) const {wordLimitMin, wordLimitMax} = props.interactionData this.state = { wordLimitMin: wordLimitMin && Number(wordLimitMin), wordLimitMax: wordLimitMax && Number(wordLimitMax), normalizedWordLimitMin: wordLimitMin, normalizedWordLimitMax: wordLimitMax, } } // ============= // HELPERS // ============= mergeInteractionData(intDataDiff) { this.props.changeItemState({ interactionData: Object.assign({}, this.props.interactionData, intDataDiff), }) } // ============= // HANDLERS // ============= handleCalculatorTypeChange = (e, value) => { this.props.changeItemState({ calculatorType: value, }) } handleCheckboxChange = event => { this.mergeInteractionData({ [event.target.value]: !this.props.interactionData[event.target.value], }) } handleNotesChange = event => { this.props.changeItemState({ scoringData: {value: event.target.value}, }) } handleWordLimitMinChange = (event, value, normalizedValue) => { this.setState({ wordLimitMin: value, normalizedWordLimitMin: normalizedValue, }) } handleWordLimitMaxChange = (event, value, normalizedValue) => { this.setState({ wordLimitMax: value, normalizedWordLimitMax: normalizedValue, }) } handleWordLimitBlur = (key, normalizedKey) => { if (this.state[normalizedKey] == null) { this.mergeInteractionData({[key]: null}) this.setState({[key]: null}) } else { const wordLimit = parseInt(this.state[normalizedKey], 10) || 0 this.mergeInteractionData({[key]: wordLimit.toString()}) this.setState({[key]: wordLimit}) } } handleWordLimitMinBlur = () => { this.handleWordLimitBlur('wordLimitMin', 'normalizedWordLimitMin') } handleWordLimitMaxBlur = () => { this.handleWordLimitBlur('wordLimitMax', 'normalizedWordLimitMax') } handleDescriptionChange = itemBody => this.props.changeItemState({itemBody}) // ============= // RENDERING // ============= renderGradingNotes() { return ( <TextArea disabled={this.props.overrideEditableForRegrading} label={<ScreenReaderContent>{t('Grading Notes')}</ScreenReaderContent>} defaultValue={this.props.scoringData.value} onChange={this.handleNotesChange} autoGrow={this.context.disableTextAreaAutoGrow ? false : null} data-automation="sdk-essay-grading-notes-text-area" /> ) } renderOptions() { const description = <ScreenReaderContent>{t('Essay options')}</ScreenReaderContent> return ( <FormFieldGroup rowSpacing="small" description={description}> {this.props.showCalculatorOption && ( <CalculatorOptionWithOqaatAlert disabled={this.props.overrideEditableForRegrading} calculatorValue={this.props.calculatorType} onCalculatorTypeChange={this.handleCalculatorTypeChange} oqaatChecked={this.props.oneQuestionAtATime} onOqaatChange={this.props.setOneQuestionAtATime} /> )} <Checkbox disabled={this.props.overrideEditableForRegrading} value="rce" data-automation="sdk-essay-rce-checkbox" label={t('Rich Content Editor')} onChange={this.handleCheckboxChange} checked={this.props.interactionData.rce} /> <Checkbox disabled={this.props.overrideEditableForRegrading} value="spellCheck" data-automation="sdk-essay-spellcheck-checkbox" label={t('Spell-check')} onChange={this.handleCheckboxChange} checked={this.props.interactionData.spellCheck} /> <Checkbox disabled={this.props.overrideEditableForRegrading} value="wordCount" data-automation="sdk-essay-wordcount-checkbox" label={t('Show Word Count')} onChange={this.handleCheckboxChange} checked={this.props.interactionData.wordCount} /> <Checkbox disabled={this.props.overrideEditableForRegrading} value="wordLimitEnabled" data-automation="sdk-essay-wordlimit-checkbox" label={t('Set Word Limit')} onChange={this.handleCheckboxChange} checked={this.props.interactionData.wordLimitEnabled} /> {this.renderWordLimitInput()} </FormFieldGroup> ) } renderWordLimitInput() { if (this.props.interactionData.wordLimitEnabled) { const currentMinValue = this.state.wordLimitMin == null ? '' : this.state.wordLimitMin const currentMaxValue = this.state.wordLimitMax == null ? '' : this.state.wordLimitMax return ( <div> <div css={this.props.styles.wordLimitInputsContainer}> <div css={this.props.styles.wordLimitInput}> <NumberInput disabled={this.props.overrideEditableForRegrading} data-automation="sdk-essay-wordlimit-min" min="0" width="8rem" renderLabel={t('Minimum')} placeholder={t('Limit')} value={currentMinValue} onChange={this.handleWordLimitMinChange} onBlur={this.handleWordLimitMinBlur} messages={this.props.getErrors('interactionData.$errors')} aria-valuetext={`${currentMinValue} ${t('Words minimum')}`} /> </div> <div css={this.props.styles.wordLimitInput}> <NumberInput disabled={this.props.overrideEditableForRegrading} data-automation="sdk-essay-wordlimit-max" min="0" width="8rem" renderLabel={t('Maximum')} placeholder={t('Limit')} value={currentMaxValue} onChange={this.handleWordLimitMaxChange} onBlur={this.handleWordLimitMaxBlur} aria-valuetext={`${currentMaxValue} ${t('Words maximum')}`} /> </div> </div> </div> ) } } renderQuestionConfigContainer() { return ( <QuestionSettingsContainer additionalOptions={this.props.additionalOptions}> <QuestionSettingsPanel label={t('Options')} defaultExpanded> {this.renderOptions()} </QuestionSettingsPanel> <QuestionSettingsPanel label={t('Grading Notes')}> {this.renderGradingNotes()} </QuestionSettingsPanel> </QuestionSettingsContainer> ) } render() { return ( <div> <QuestionContainer disabled={this.props.overrideEditableForRegrading} enableRichContentEditor={this.props.enableRichContentEditor} itemBody={this.props.itemBody} onDescriptionChange={this.handleDescriptionChange} onModalClose={this.props.onModalClose} onModalOpen={this.props.onModalOpen} openImportModal={this.props.openImportModal} stemErrors={this.props.getErrors('itemBody')} automationData="sdk-essay-rce" /> {this.renderQuestionConfigContainer()} </div> ) } }