@instructure/quiz-grading
Version:
The Quiz React SDK by Instructure Inc.
407 lines (405 loc) • 16.3 kB
JavaScript
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
import _createClass from "@babel/runtime/helpers/esm/createClass";
import _possibleConstructorReturn from "@babel/runtime/helpers/esm/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/esm/getPrototypeOf";
import _inherits from "@babel/runtime/helpers/esm/inherits";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
var _dec, _class, _GradingBar;
function _callSuper(_this, derived, args) {
function isNativeReflectConstruct() {
if (typeof Reflect === "undefined" || !Reflect.construct) return false;
if (Reflect.construct.sham) return false;
if (typeof Proxy === "function") return true;
try {
return !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
} catch (e) {
return false;
}
}
derived = _getPrototypeOf(derived);
return _possibleConstructorReturn(_this, isNativeReflectConstruct() ? Reflect.construct(derived, args || [], _getPrototypeOf(_this).constructor) : derived.apply(_this, args));
}
/** @jsx jsx */
import { Component } from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Map } from 'immutable';
import debounce from 'lodash/debounce';
import { IconArrowOpenEndLine, IconArrowOpenStartLine, IconCheckLine, IconDiscussionLine, IconDiscussionSolid, IconXLine } from '@instructure/ui-icons';
import { Button } from '@instructure/ui-buttons';
import NumberInput from '@instructure/quiz-number-input';
import { Text } from '@instructure/ui-text';
import { View } from '@instructure/ui-view';
import { PresentationContent, ScreenReaderContent } from '@instructure/ui-a11y-content';
import { jsx } from '@instructure/emotion';
import t from '@instructure/quiz-i18n/es/format-message';
import { IfFeature } from '@instructure/quiz-core';
import { Flex, HideableButton, GRADE_BY_QUESTION_PREVIOUS, GRADE_BY_QUESTION_NEXT, withStyleOverrides } from '@instructure/quiz-common';
import { RichContentInput } from '@instructure/quiz-rce';
import generateStyle from './styles';
import generateComponentTheme from './theme';
export var GradingBar = (_dec = withStyleOverrides(generateStyle, generateComponentTheme), _dec(_class = (_GradingBar = /*#__PURE__*/function (_Component) {
function GradingBar(props) {
var _this2;
_classCallCheck(this, GradingBar);
_this2 = _callSuper(this, GradingBar, [props]);
_defineProperty(_this2, "regradeButtonRef", null);
_defineProperty(_this2, "feedbackButtonRef", null);
_defineProperty(_this2, "_timeouts", []);
// =============
// EXTERNALLY USED UTILS
// =============
_defineProperty(_this2, "focusRegradeButton", function () {
_this2.regradeButtonRef && _this2.regradeButtonRef.focus();
});
_defineProperty(_this2, "focusFeedbackButton", function () {
_this2.feedbackButtonRef && _this2.feedbackButtonRef.focus();
});
// =============
// HANDLERS
// =============
_defineProperty(_this2, "updatePoints", debounce(function () {
_this2.props.handleChangePoints(_this2.state.normalizedPoints);
}, 250, {
leading: true,
trailing: true
}));
_defineProperty(_this2, "handleChangePoints", function (event, points, normalizedPoints) {
var parsedPoints = parseFloat(normalizedPoints) || 0;
_this2.setState({
points: points,
normalizedPoints: parsedPoints
}, _this2.updatePoints);
var gradeStatus = _this2.getGradeStatus();
if (!gradeStatus || gradeStatus === 'waiting') {
_this2.props.handleChangeGradeStatus('graded');
}
});
_defineProperty(_this2, "handleBlurPoints", function () {
_this2.updatePoints.flush();
_this2.setState(function (_ref) {
var normalizedPoints = _ref.normalizedPoints;
return {
points: normalizedPoints || 0
};
});
});
_defineProperty(_this2, "handleStatusCorrectToggle", function () {
var newPoints = +_this2.props.sessionItemResult.pointsPossible;
_this2.props.handleChangePoints(newPoints);
_this2.setState({
points: "".concat(newPoints),
normalizedPoints: "".concat(newPoints)
});
var newStatus = _this2.getGradeStatus() === 'correct' ? 'graded' : 'correct';
_this2.props.handleChangeGradeStatus(newStatus, newStatus === 'graded');
});
_defineProperty(_this2, "handleStatusIncorrectToggle", function () {
_this2.props.handleChangePoints(0);
_this2.setState({
points: '0',
normalizedPoints: '0'
});
var newStatus = _this2.getGradeStatus() === 'incorrect' ? 'graded' : 'incorrect';
_this2.props.handleChangeGradeStatus(newStatus, newStatus === 'graded');
});
_defineProperty(_this2, "previousStudent", function (e) {
e.preventDefault();
_this2.props.postToParent({
subject: GRADE_BY_QUESTION_PREVIOUS
});
});
_defineProperty(_this2, "nextStudent", function (e) {
e.preventDefault();
_this2.props.postToParent({
subject: GRADE_BY_QUESTION_NEXT
});
});
_defineProperty(_this2, "jumpToSidebar", function (e) {
e.preventDefault();
var sidebarWrapper = document.getElementById('sidebar-wrapper');
if (sidebarWrapper && sidebarWrapper.focus) {
sidebarWrapper.focus();
}
});
_defineProperty(_this2, "handleRegradeButtonRef", function (node) {
_this2.regradeButtonRef = node;
});
_defineProperty(_this2, "handleFeedbackButtonRef", function (node) {
_this2.feedbackButtonRef = node;
});
var _points = _this2.props.sessionItemResult.getWorkingInstance().score;
if (typeof _points == 'number' || typeof _points == 'string') {
_points = Number(_points);
} else {
_points = null;
}
_this2.state = {
points: _points,
normalizedPoints: _points
};
return _this2;
}
_inherits(GradingBar, _Component);
return _createClass(GradingBar, [{
key: "regradeInfo",
get:
// =============
// INTERNAL UTILS
// =============
function get() {
return this.props.sessionItemResult.regradeInfo;
}
}, {
key: "pointsPossibleDescription",
value: function pointsPossibleDescription(pointsPossible) {
return t("{\n points, plural,\n one {# point}\n other {# points}\n }", {
points: pointsPossible
});
}
}, {
key: "getGradeStatus",
value: function getGradeStatus() {
var resultMods = this.props.itemResultsModifications || Map();
var workingGradeStatus = resultMods.getIn(['scoredData', 'gradeStatus']);
if (workingGradeStatus) {
return workingGradeStatus;
}
return this.props.sessionItemResult.getIn(['scoredData', 'gradeStatus']);
}
}, {
key: "renderToSidebarButton",
value:
// =============
// RENDERING
// =============
function renderToSidebarButton() {
return jsx(Flex, {
justifyItems: "end",
margin: "small medium"
}, jsx(HideableButton, {
href: "#",
onClick: this.jumpToSidebar
}, t('Jump to Question Navigator')));
}
}, {
key: "renderSubjectiveButtons",
value: function renderSubjectiveButtons() {
if (this.props.isSubjective) {
var correctVariant = this.getGradeStatus() === 'correct' ? 'success' : 'primary-inverse';
var correctText = this.getGradeStatus() === 'correct' ? t('toggle correct off') : t('toggle correct on');
var incorrectVariant = this.getGradeStatus() === 'incorrect' ? 'danger' : 'primary-inverse';
var incorrectText = this.getGradeStatus() === 'incorrect' ? t('toggle incorrect off') : t('toggle incorrect on');
return jsx("div", {
css: this.props.styles.subjectiveButtonsWrapper
}, jsx(Button, {
color: correctVariant,
onClick: this.handleStatusCorrectToggle,
renderIcon: IconCheckLine
}, jsx(ScreenReaderContent, null, correctText)), jsx(Button, {
color: incorrectVariant,
margin: "0 xx-small",
onClick: this.handleStatusIncorrectToggle,
renderIcon: IconXLine
}, jsx(ScreenReaderContent, null, incorrectText)));
}
}
}, {
key: "renderFeedbackSection",
value: function renderFeedbackSection() {
var workingSessionItemResult = this.props.sessionItemResult.getWorkingInstance();
var graderFeedbackContent = workingSessionItemResult.getIn(['feedback', 'graderFeedback', 'content'], '');
var icon, text;
if (graderFeedbackContent) {
icon = IconDiscussionSolid;
text = t('Edit Grader Level Feedback (present)');
} else {
icon = IconDiscussionLine;
text = t('Edit Grader Level Feedback');
}
return jsx(Button, {
ref: this.handleFeedbackButtonRef,
onClick: this.props.startFeedbackEdit,
renderIcon: icon,
"data-automation": "sdk-grading-edit-feedback-button"
}, jsx(ScreenReaderContent, null, text));
}
}, {
key: "renderRegradeInfo",
value: function renderRegradeInfo() {
return jsx("div", {
css: this.props.styles.regradeParent
}, jsx("div", {
css: this.props.styles.originalScore
}, jsx(Text, {
color: "secondary"
}, t("Previous score { possible, plural,\n one {{points, number} / # point}\n other {{points, number} / # points}\n }", {
points: this.regradeInfo.get('originalScore'),
possible: this.regradeInfo.get('originalPointsPossible')
})), jsx(PresentationContent, null, jsx("div", {
css: this.props.styles.divider
}, jsx(Text, {
color: "secondary"
}, '|' /* eslint-disable-line react/jsx-no-literals */)))), jsx("div", {
css: this.props.styles.regradeText
}, jsx(Text, {
color: "alert"
}, t('Regrade score:'))), this.renderNumberInput(), jsx(Text, {
color: "alert"
}, // eslint-disable-next-line react/jsx-no-literals
"/ ".concat(this.pointsPossibleDescription(this.regradeInfo.get('newPointsPossible')))));
}
}, {
key: "renderNumberInput",
value: function renderNumberInput() {
return jsx("div", {
css: this.props.styles.points
}, jsx(NumberInput, {
placeholder: "--",
renderLabel: jsx(ScreenReaderContent, null, t('Edit Score')),
onChange: this.handleChangePoints,
onBlur: this.handleBlurPoints,
value: this.state.points,
width: "6rem",
"data-automation": "sdk-grading-edit-score-input"
}));
}
}, {
key: "renderDefaultNumberInput",
value: function renderDefaultNumberInput() {
return jsx("div", {
css: this.props.styles.defaultNumberInput
}, this.renderNumberInput(), jsx("span", {
css: this.props.styles.pointsPossible
}, // eslint-disable-next-line react/jsx-no-literals
"/ ".concat(this.pointsPossibleDescription(this.props.sessionItemResult.pointsPossible))));
}
}, {
key: "renderRegradeButton",
value: function renderRegradeButton() {
if (this.props.isAutogradeable) {
return jsx(IfFeature, {
name: "regrading_from_speedgrader"
}, jsx("div", {
css: this.props.styles.regradeButtonWrapper
}, jsx(Button, {
margin: "0 small",
onClick: this.props.regradeToggleAction,
ref: this.handleRegradeButtonRef,
"data-automation": "sdk-regrade-request-button"
}, t.rich('Regrade <0>question {position, number}</0>', {
0: function _(_ref2) {
var children = _ref2.children;
return jsx(ScreenReaderContent, {
key: "0"
}, children);
},
position: this.props.sessionItem.position
}))));
}
}
}, {
key: "renderEditGraderFeedback",
value: function renderEditGraderFeedback() {
if (!this.props.isEditing) {
return;
}
var textareaId = "edit_grader_feedback_".concat(this.props.sessionItem.id);
return jsx("div", {
css: this.props.styles.editFeedback
}, jsx("label", {
htmlFor: "edit_grader_feedback"
}, t('Additional Comments')), jsx(RichContentInput, {
label: "",
defaultContent: this.props.graderFeedback.get('content'),
onKeyUp: this.props.updateFeedbackEdit,
onChange: this.props.updateFeedbackEdit,
textareaId: textareaId
}), jsx("div", {
className: "editFeedbackButtons",
css: this.props.styles.editFeedbackButtons
}, jsx(Button, {
size: "small",
margin: "x-small",
onClick: this.props.cancelFeedbackEdit,
"data-automation": "sdk-grading-edit-feedback-cancel"
}, t('Cancel')), jsx(Button, {
color: "primary",
size: "small",
margin: "x-small",
onClick: this.props.submitFeedbackEdit,
"data-automation": "sdk-grading-edit-feedback-submit"
}, t('Done'))));
}
}, {
key: "renderGradeByQuestionControls",
value: function renderGradeByQuestionControls() {
var _this$props = this.props,
activeItemId = _this$props.activeItemId,
gradeByQuestionEnabled = _this$props.gradeByQuestionEnabled,
sessionItem = _this$props.sessionItem;
if (gradeByQuestionEnabled && sessionItem.itemRecord.id === activeItemId) {
return jsx(Flex, {
justifyItems: "end",
margin: "small medium"
}, this.props.renderUpdateButton({
margin: '0 xx-small'
}), jsx(Button, {
margin: "0 xx-small",
href: "#",
onClick: this.previousStudent
}, jsx(Flex, {
alignItems: "center"
}, jsx(IconArrowOpenStartLine, null), jsx(View, {
margin: "0 0 0 x-small"
}, t('Previous Student')))), jsx(Button, {
margin: "0 xx-small",
href: "#",
onClick: this.nextStudent
}, jsx(Flex, {
alignItems: "center"
}, jsx(View, {
margin: "0 x-small 0 0"
}, t('Next Student')), jsx(IconArrowOpenEndLine, null))));
}
}
}, {
key: "render",
value: function render() {
var hasRegradeInfo = this.regradeInfo && this.regradeInfo.size > 1;
return jsx("div", null, this.renderEditGraderFeedback(), jsx("div", {
className: "root",
css: this.props.styles.root
}, jsx("div", {
css: this.props.styles.feedbackButtonWrapper
}, this.renderFeedbackSection()), this.renderRegradeButton(), jsx("div", {
className: "fs-mask",
css: this.props.styles.pointsSection
}, this.renderSubjectiveButtons(), hasRegradeInfo ? this.renderRegradeInfo() : this.renderDefaultNumberInput())), this.renderGradeByQuestionControls(), this.renderToSidebarButton());
}
}]);
}(Component), _defineProperty(_GradingBar, "displayName", 'GradingBar'), _defineProperty(_GradingBar, "componentId", "Quizzes".concat(_GradingBar.displayName)), _defineProperty(_GradingBar, "propTypes", {
activeItemId: PropTypes.string,
gradeByQuestionEnabled: PropTypes.bool,
graderFeedback: ImmutablePropTypes.map.isRequired,
handleChangeGradeStatus: PropTypes.func.isRequired,
handleChangePoints: PropTypes.func.isRequired,
isAutogradeable: PropTypes.bool.isRequired,
isEditing: PropTypes.bool.isRequired,
isSubjective: PropTypes.bool.isRequired,
itemResultsModifications: ImmutablePropTypes.map.isRequired,
postToParent: PropTypes.func.isRequired,
regradeToggleAction: PropTypes.func.isRequired,
renderUpdateButton: PropTypes.func.isRequired,
sessionItem: ImmutablePropTypes.record.isRequired,
sessionItemResult: ImmutablePropTypes.record.isRequired,
startFeedbackEdit: PropTypes.func.isRequired,
submitFeedbackEdit: PropTypes.func.isRequired,
updateFeedbackEdit: PropTypes.func.isRequired,
cancelFeedbackEdit: PropTypes.func.isRequired,
styles: PropTypes.object
}), _defineProperty(_GradingBar, "defaultProps", {
activeItemId: null,
gradeByQuestionEnabled: false
}), _GradingBar)) || _class);
export default GradingBar;