@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
329 lines (293 loc) • 10.6 kB
JavaScript
/** @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>
```
**/
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>
)
}
}