UNPKG

@instructure/quiz-grading

Version:

The Quiz React SDK by Instructure Inc.

335 lines (304 loc) • 11 kB
/** @jsx jsx */ import {Component} from 'react' import PropTypes from 'prop-types' import ImmutablePropTypes from 'react-immutable-proptypes' import {List} from 'immutable' import {Alert} from '@instructure/ui-alerts' import {Button} from '@instructure/ui-buttons' import {ScreenReaderContent} from '@instructure/ui-a11y-content' import {Text} from '@instructure/ui-text' import {jsx} from '@instructure/emotion' import {CustomPropTypes} from '@instructure/quiz-core/common/util/CustomPropTypes' import {Spinner} from '@instructure/quiz-core/common/components/shared/spinner/Spinner' import {componentForItem} from '@instructure/quiz-core/common/util/componentForItem' import {propsForInteractionEdit} from '@instructure/quiz-core/common/util/interactionTypePropsHelper' import {UndecoratedQuizEntryEdit} from '@instructure/quiz-core/building/components/resources/quizEntry/QuizEntryEdit/presenter' import {COMPONENT_EDIT, PARENT_TYPE_QUIZ, REGRADE_MODAL} from '@instructure/quiz-common/constants' import {withStyleOverrides} from '@instructure/quiz-common/util/withStyleOverrides' import RegradingModal from '../../../RegradingModal' import generateStyle from './styles' import generateComponentTheme from './theme' import t from '@instructure/quiz-i18n/format-message' const noop = () => {} class BaseRegradeOverride extends Component { static displayName = 'RegradeOverride' static componentId = `Quizzes${this.displayName}` static propTypes = { appContainer: CustomPropTypes.selectors.isRequired, changeQuizEntryPoints: PropTypes.func.isRequired, entry: ImmutablePropTypes.record.isRequired, hideError: PropTypes.func.isRequired, guid: PropTypes.string.isRequired, workingEntry: ImmutablePropTypes.record.isRequired, clearModifications: PropTypes.func.isRequired, getItem: PropTypes.func.isRequired, getQuizEntry: PropTypes.func.isRequired, itemId: PropTypes.string.isRequired, onCancel: PropTypes.func.isRequired, openModal: PropTypes.func.isRequired, quizEntry: ImmutablePropTypes.record.isRequired, workingQuizEntry: ImmutablePropTypes.record.isRequired, quizId: PropTypes.string.isRequired, sessionItem: ImmutablePropTypes.record.isRequired, setRegradingPayload: PropTypes.func.isRequired, showError: PropTypes.func.isRequired, toggleRegradingEditMode: PropTypes.func.isRequired, handleValidationErrorsFromApi: PropTypes.func.isRequired, validationErrorsFromApi: PropTypes.objectOf( PropTypes.arrayOf( PropTypes.shape({ type: PropTypes.string, text: PropTypes.string, }), ), ), // required by propsForInteractionEdit addError: PropTypes.func.isRequired, changeItemState: PropTypes.func.isRequired, displayPosition: PropTypes.number.isRequired, errorsAreShowing: PropTypes.bool, interactionFileUpload: PropTypes.func.isRequired, oneQuestionAtATime: PropTypes.bool.isRequired, partialDeepScoringEnabled: PropTypes.bool, partialScoringEnabled: PropTypes.bool, parentId: PropTypes.string.isRequired, parentType: PropTypes.string.isRequired, screenreaderNotification: PropTypes.func.isRequired, setOneQuestionAtATime: PropTypes.func.isRequired, styles: PropTypes.object, } static defaultProps = { errorsAreShowing: false, partialScoringEnabled: false, partialDeepScoringEnabled: false, validationErrorsFromApi: {}, } quizEntryProps() { return { ...this.props, alternativeRightFooter: this.renderActionFooter, changeItemTitle: noop, clearModifications: noop, clearTemporaryItem: noop, cloneQuizEntry: noop, connectDragPreview: noop, connectDragSource: noop, createItem: noop, createQuizEntry: noop, disabledForRegrade: true, draggingAboveHalf: false, draggingOver: false, haveValidationErrorsFromApi: Boolean( Object.values(this.props.validationErrorsFromApi).length, ), hideError: noop, isOccluded: false, itemFeedbackOn: false, name: '', openModal: noop, openRegradeModal: noop, quizEntries: List(), removeQuizEntry: noop, screenreaderNotification: noop, setUi: noop, shouldClone: false, showError: noop, switchOffEditing: noop, updateItem: noop, updateQuizEntry: noop, withConfirm: noop, } } itemEditProps() { return { ...this.props, handleQuizEntryPointsChange: this.handleQuizEntryPointsChange, openImportModal: () => {}, parentId: this.props.quizId, parentType: PARENT_TYPE_QUIZ, title: this.props.entry.title, workingItem: this.props.workingEntry, } } handleQuizEntryPointsChange = points => { const {workingQuizEntry, workingEntry} = this.props const qerIndexToUpdate = workingQuizEntry.quizEntryRegrades.findIndex( qer => qer.get('itemId') == workingEntry.id, ) const newQuizEntryRegrades = qerIndexToUpdate > -1 ? workingQuizEntry.quizEntryRegrades.setIn([qerIndexToUpdate, 'pointsPossible'], null) : workingQuizEntry.quizEntryRegrades this.props.changeQuizEntryPoints(workingQuizEntry.id, points, newQuizEntryRegrades.toJS()) } navigateToTop = () => { setTimeout(() => { this.props.screenreaderNotification( t('Focus moved to top of question. Navigate down to continue.'), ) if (this.alertNode) { this.alertNode.focus() } }) } componentDidMount() { if (this.props.quizEntry.id === '0') { this.props.getQuizEntry(this.props.quizId, this.props.sessionItem.quizEntryId) } if (!this.props.entry.isLoaded()) { this.props.getItem(this.props.itemId) } else { this.navigateToTop() } } componentDidUpdate(prevProps) { if (!prevProps.entry.isLoaded() && this.props.entry.isLoaded()) { this.navigateToTop() } } turnOffRegradingEditMode = () => { this.props.clearModifications('items', this.props.itemId) this.props.clearModifications('quizEntries', this.props.sessionItem.quizEntryId) this.props.toggleRegradingEditMode('') this.props.onCancel(this.props.sessionItem.id) } triggerRegradingModal = e => { const {displayPosition, workingQuizEntry, itemId} = this.props e && e.preventDefault() if (this.isValid()) { this.props.hideError(this.props.guid) const {scoringAlgorithm, scoringData} = this.props.workingEntry const pointsPossible = workingQuizEntry.regradeAdjustedPointsPossible(itemId) || workingQuizEntry.pointsPossible this.props.setRegradingPayload({ itemId: this.props.itemId, quizEntryId: this.props.quizEntry.id, pointsPossible, scoringAlgorithm, scoringData, }) this.props.openModal(REGRADE_MODAL) } else { const msg = t( 'Question {displayPosition, number} cannot be saved due to invalid input data.', {displayPosition}, ) this.props.screenreaderNotification(msg) this.props.showError(this.props.guid) } } isValid() { return this.props.workingEntry.isValid() && this.props.workingQuizEntry.isValid() } setAlertRef = node => { this.alertNode = node } renderActionFooter = () => { return ( <div> <Button margin="small" onClick={this.turnOffRegradingEditMode}> {t('Cancel')} </Button> <Button margin="small" color="primary" onClick={this.triggerRegradingModal} data-automation="sdk-trigger-regrade-button" > {t.rich('Regrade <0>question {position, number}</0>', { 0: ({children}) => <ScreenReaderContent key="0">{children}</ScreenReaderContent>, position: this.props.quizEntry.position, })} </Button> </div> ) } renderRegradingAlert() { return ( <div> <Alert margin="small 0" transition="none" variant="info"> <div css={this.props.styles.alert}> {t.rich( '<0>Regrading only applies to completed submissions.</0><1>If all students are affected please wait for all submissions before Regrading.</1>', [ ({children}) => ( <Text as="div" weight="bold" key="0"> {children} </Text> ), ({children}) => ( <Text as="span" key="1"> {children} </Text> ), ], )} </div> </Alert> <Alert margin="small 0" transition="none" variant="info"> <div css={this.props.styles.alert}> {t.rich( `<0>You are regrading this question.</0> <1>Regrade options for this question type:</1> <2> <3>change the correct answer</3> <4>change points value for the whole question</4> <5>change points values on individual answers (if applicable)</5> </2>`, [ ({children}) => ( <Text as="div" weight="bold" key="0"> {children} </Text> ), ({children}) => ( <Text as="span" key="1"> {children} </Text> ), ({children}) => <ul key="2">{children}</ul>, ({children}) => <li key="3">{children}</li>, ({children}) => <li key="4">{children}</li>, ({children}) => <li key="5">{children}</li>, ], )} </div> </Alert> </div> ) } renderEditableView() { const propsForInteraction = propsForInteractionEdit(this.itemEditProps()) const interactionType = this.props.entry.getInteractionType().slug const Interaction = componentForItem(interactionType, COMPONENT_EDIT) return ( <UndecoratedQuizEntryEdit {...this.quizEntryProps()}> {this.renderRegradingAlert()} <div css={this.props.styles.container}> <Interaction {...propsForInteraction} itemId={this.props.sessionItem.id} overrideEditableForRegrading /> </div> </UndecoratedQuizEntryEdit> ) } render() { return ( <div tabIndex="-1" ref={this.setAlertRef}> <RegradingModal fromSpeedgrader quizId={this.props.quizId} handleValidationErrorsFromApi={this.props.handleValidationErrorsFromApi} /> {this.props.quizEntry.id !== '0' ? this.renderEditableView() : <Spinner />} </div> ) } } export const RegradeOverride = withStyleOverrides( generateStyle, generateComponentTheme, )(BaseRegradeOverride) export default RegradeOverride