UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

343 lines (338 loc) • 14.2 kB
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray"; 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, _CategorizationResult; 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 get from 'lodash/fp/get'; import { Text } from '@instructure/ui-text'; import { jsx } from '@instructure/emotion'; import t from '@instructure/quiz-i18n/es/format-message'; import { ItemBodyWrapper } from '@instructure/quiz-rce'; import { FeedbackWrapper } from '@instructure/quiz-results-feedback'; import CategoriesContainer from '../common/CategoriesContainer'; import { getSortedCategories } from '../common/utils'; import generateStyle from './styles'; import generateComponentTheme from './theme'; import { withStyleOverrides } from '@instructure/quiz-common'; /** --- category: Categorization --- Categorization Result component ```jsx_example <SettingsSwitcher locales={LOCALES}> <CategorizationResult itemBody="Match the name of the celestial body on the left with its correct classification:" interactionData={{ categoryOrder: ['category_uuid1', 'category_uuid2', 'category_uuid3', 'category_uuid4'], categories: { category_uuid1: { id: 'category_uuid1', itemBody: 'Planet' }, category_uuid2: { id: 'category_uuid2', itemBody: 'Moon' }, category_uuid3: { id: 'category_uuid3', itemBody: 'Quasar' }, category_uuid4: { id: 'category_uuid4', itemBody: 'Stars' }, }, distractors: { answer_uuid3: { id: 'answer_uuid3', itemBody: 'Mars' }, answer_uuid4: { id: 'answer_uuid4', itemBody: 'Europa' }, answer_uuid5: { id: 'answer_uuid5', itemBody: 'Venus' }, answer_uuid6: { id: 'answer_uuid6', itemBody: 'Phobos' }, answer_uuid7: { id: 'answer_uuid7', itemBody: 'Deimos' }, answer_uuid8: { id: 'answer_uuid8', itemBody: 'Jupiter' }, answer_uuid9: { id: 'answer_uuid9', itemBody: 'America' }, answer_uuid10: { id: 'answer_uuid10', itemBody: 'Asia' }, answer_uuid11: { id: 'answer_uuid11', itemBody: 'The Sun' } } }} scoredData={{ value: { category_uuid1: { value: { answer_uuid3: { resultScore: 1, userResponded: false }, answer_uuid5: { resultScore: 1, userResponded: true }, answer_uuid8: { resultScore: 1, userResponded: false }, answer_uuid7: { resultScore: 0, userResponded: true } } }, category_uuid3: { value: {} }, category_uuid4: { value: { answer_uuid11: { resultScore: 1, userResponded: false } } }, category_uuid2: { value: { answer_uuid4: { resultScore: 1, userResponded: true }, answer_uuid6: { resultScore: 1, userResponded: true }, answer_uuid7: { resultScore: 1, userResponded: false }, answer_uuid8: { resultScore: 0, userResponded: true }, answer_uuid9: { resultScore: 0, userResponded: true } } } } }} /> </SettingsSwitcher> ``` **/ var CategorizationResult = (_dec = withStyleOverrides(generateStyle, generateComponentTheme), _dec(_class = (_CategorizationResult = /*#__PURE__*/function (_Component) { function CategorizationResult(props) { var _this2; _classCallCheck(this, CategorizationResult); _this2 = _callSuper(this, CategorizationResult, [props]); _defineProperty(_this2, "renderCategoryBody", function (categoryId) { var categoryValue = get("scoredData.value[".concat(categoryId, "].value"), _this2.props) || {}; var answers = Object.entries(categoryValue).filter(function (_ref) { var _ref2 = _slicedToArray(_ref, 2), _ = _ref2[0], answer = _ref2[1]; return answer.userResponded; }); if (answers.length) { return jsx("div", { className: "categoryBody fs-mask", css: _this2.props.styles.categoryBody }, answers.map(function (_ref3) { var _ref4 = _slicedToArray(_ref3, 2), answerId = _ref4[0], resultScore = _ref4[1].resultScore; return _this2.renderCategoryAnswer(answerId, resultScore, categoryId); })); } return jsx("div", { className: "categoryBody fs-mask", css: _this2.props.styles.categoryBody }, jsx("div", { css: _this2.props.styles.noChoicesText }, jsx(Text, { color: "primary" }, _this2.responseHidden() ? t('Choices not displayed') : t('No Choices Selected')))); }); _defineProperty(_this2, "renderUnansweredChoice", function (answerId) { var itemBody = _this2.props.interactionData.distractors[answerId].itemBody; var status = 'unknown', correctAnswerText; if (_this2.correctnessKnown()) { var scoredData = _this2.props.scoredData; correctAnswerText = _this2.getCorrectAnswer(answerId); if (correctAnswerText) { status = 'incorrect'; } else if (!scoredData.uncategorized) { if (_this2.includesAllCorrectAnswers()) { status = 'correct'; // for backward-compatibility } else { status = 'unknown'; } } else { status = get("uncategorized[".concat(answerId, "].resultScore"), scoredData) ? 'correct' : 'incorrect'; } } return jsx("div", { key: answerId, css: _this2.props.styles.unanswered }, jsx("div", { css: _this2.props.styles.feedbackWrapper }, jsx(FeedbackWrapper, { correctAnswer: correctAnswerText, hiddenCorrectAnswerText: t('Correct answer: '), hiddenIncorrectAnswerText: t('Incorrect answer: '), status: status }, jsx("div", { css: _this2.props.styles.userResponse }, jsx(Text, { color: "primary" }, itemBody))))); }); var _props$interactionDat = props.interactionData, categories = _props$interactionDat.categories, categoryOrder = _props$interactionDat.categoryOrder; _this2.state = { sortedCategories: getSortedCategories(categories, categoryOrder) }; return _this2; } // =========== // UTILS // =========== _inherits(CategorizationResult, _Component); return _createClass(CategorizationResult, [{ key: "hasMissedCorrectAnswerInObject", value: function hasMissedCorrectAnswerInObject(answers) { return answers && Object.keys(answers).some(function (answerId) { return answers[answerId] && answers[answerId].resultScore > 0 && answers[answerId].userResponded === false; }); } }, { key: "hasMissedCorrectAnswerInUncategorized", value: function hasMissedCorrectAnswerInUncategorized() { var uncategorized = this.props.scoredData.uncategorized; return this.hasMissedCorrectAnswerInObject(uncategorized); } }, { key: "hasMissedCorrectAnswerInCategories", value: function hasMissedCorrectAnswerInCategories() { var _this3 = this; var categories = this.props.scoredData.value; return categories && Object.keys(categories).some(function (categoryKey) { var answers = categories[categoryKey].value; return _this3.hasMissedCorrectAnswerInObject(answers); }); } }, { key: "includesAllCorrectAnswers", value: function includesAllCorrectAnswers() { // we know a response includes all the correct answers if it is either // correct, or it contains correct responses not chosen by the user var scoredData = this.props.scoredData; if (scoredData.correct) { return true; } return this.hasMissedCorrectAnswerInUncategorized() || this.hasMissedCorrectAnswerInCategories(); } }, { key: "getCorrectAnswer", value: function getCorrectAnswer(answerId, categoryId) { var scoredData = this.props.scoredData; var correctCategoryId = scoredData.value && Object.keys(scoredData.value).find(function (categoryKey) { if (categoryKey !== categoryId) { return get("value[".concat(categoryKey, "].value[").concat(answerId, "].resultScore"), scoredData); } else { return null; } }); if (correctCategoryId) { return this.props.interactionData.categories[correctCategoryId].itemBody; } } }, { key: "getUnansweredAnswersIds", value: function getUnansweredAnswersIds() { var _this4 = this; if (this.responseHidden()) return []; var distractorsArray = Object.keys(this.props.interactionData.distractors); return distractorsArray.filter(function (answerId) { var category = Object.values(_this4.props.scoredData.value).find(function (scoredDataCateg) { return get("value[".concat(answerId, "].userResponded"), scoredDataCateg); }); return category === void 0; }); } }, { key: "responseHidden", value: function responseHidden() { var scoredData = this.props.scoredData; return !scoredData.value || Object.keys(scoredData.value).length === 0; } }, { key: "correctnessKnown", value: function correctnessKnown() { return this.props.scoredData.correct != null; } // =========== // RENDER // =========== }, { key: "renderCategoryAnswer", value: function renderCategoryAnswer(answerId, resultScore, categoryId) { var status, correctAnswerText; if (resultScore > 0) { status = 'correct'; } else if (resultScore <= 0) { status = 'incorrect'; if (this.correctnessKnown()) { correctAnswerText = this.getCorrectAnswer(answerId); if (!correctAnswerText) { // If a correct answer can't be found, it means that either the // answer should have been left uncategorized, or the results are // restricted and the correct answer is hidden. In the first case, we // want to display "No Category" feedback, but in the latter case we // don't want to provide any feedback beyond whether the answer was // correct. To distinguish between these 2 cases, we check whether // any missed correct answers are present in the response, and whether // the `uncategorized` scoredData attribute exists, which is essentially a // category for answers that should remain uncategorized. If the // `uncategorized` attribute is absent, but we show other correct answers, // we display the "No Category" feedback for backward-compatibility. var uncategorized = this.props.scoredData.uncategorized; if (this.includesAllCorrectAnswers() && (!uncategorized || get("".concat(answerId, ".resultScore"), uncategorized))) { correctAnswerText = t('No Category'); } } } } else { status = 'unknown'; } return jsx("div", { key: answerId, css: this.props.styles.feedbackWrapper }, jsx(FeedbackWrapper, { correctAnswer: correctAnswerText, hiddenCorrectAnswerText: t('Correct answer: '), hiddenIncorrectAnswerText: t('Incorrect answer: '), status: status }, jsx("div", { css: this.props.styles.userResponse }, jsx(Text, { color: "primary" }, this.props.interactionData.distractors[answerId].itemBody)))); } }, { key: "render", value: function render() { var unansweredList = this.getUnansweredAnswersIds(); return jsx(ItemBodyWrapper, { itemBody: this.props.itemBody }, jsx(CategoriesContainer, { sortedCategories: this.state.sortedCategories, categoryBody: this.renderCategoryBody, distractors: this.props.interactionData.distractors }), unansweredList.length === 0 ? null : jsx("div", { css: this.props.styles.mainContainer }, jsx(Text, { color: "primary" }, t('Uncategorized answers')), jsx("div", { className: "categoryBody fs-mask", css: this.props.styles.categoryBody }, unansweredList.map(this.renderUnansweredChoice)))); } }]); }(Component), _defineProperty(_CategorizationResult, "displayName", 'CategorizationResult'), _defineProperty(_CategorizationResult, "componentId", "Quizzes".concat(_CategorizationResult.displayName)), _defineProperty(_CategorizationResult, "propTypes", { interactionData: PropTypes.object.isRequired, itemBody: PropTypes.string.isRequired, scoredData: PropTypes.shape({ correct: PropTypes.bool, uncategorized: PropTypes.objectOf(PropTypes.shape({ resultScore: PropTypes.number, userResponded: PropTypes.bool })), value: PropTypes.objectOf(PropTypes.shape({ value: PropTypes.objectOf(PropTypes.shape({ resultScore: PropTypes.number, userResponded: PropTypes.bool })) })) }).isRequired, styles: PropTypes.object }), _CategorizationResult)) || _class); export { CategorizationResult as default };