UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

456 lines (450 loc) • 21.8 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _react = require("react"); var _propTypes = _interopRequireDefault(require("prop-types")); var _find = _interopRequireDefault(require("lodash/fp/find")); var _flatMap = _interopRequireDefault(require("lodash/fp/flatMap")); var _isEqual = _interopRequireDefault(require("lodash/fp/isEqual")); var _flow = _interopRequireDefault(require("lodash/fp/flow")); var _filter = _interopRequireDefault(require("lodash/fp/filter")); var _sortBy = _interopRequireDefault(require("lodash/fp/sortBy")); var _set = _interopRequireDefault(require("lodash/fp/set")); var _uuid = require("uuid"); var _striptags = _interopRequireDefault(require("striptags")); var _uiText = require("@instructure/ui-text"); var _emotion = require("@instructure/emotion"); var _categorization = _interopRequireDefault(require("../../../records/interactions/categorization")); var _ChoiceInput = _interopRequireDefault(require("../../common/edit/components/ChoiceInput")); var _Footer = _interopRequireDefault(require("../../common/edit/components/Footer")); var _QuestionSettingsContainer = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsContainer")); var _QuestionContainer = _interopRequireDefault(require("../../common/edit/components/QuestionContainer")); var _focusHelpers = require("../../../util/focusHelpers"); var _CategorizationPresenter = _interopRequireDefault(require("./CategorizationPresenter")); var _CategoryForm = _interopRequireDefault(require("./CategoryForm")); var _utils = require("../common/utils"); var _withEditTools = _interopRequireDefault(require("../../../util/withEditTools")); var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/es/format-message")); var _QuestionSettingsPanel = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsPanel")); var _CalculatorOptionWithOqaatAlert = _interopRequireDefault(require("../../common/edit/components/CalculatorOptionWithOqaatAlert")); var _uiA11yContent = require("@instructure/ui-a11y-content"); var _quizCommon = require("@instructure/quiz-common"); var _class, _CategorizationEdit; /** @jsx jsx */ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) { symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); } keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { (0, _defineProperty2["default"])(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } 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 = (0, _getPrototypeOf2["default"])(derived); return (0, _possibleConstructorReturn2["default"])(_this, isNativeReflectConstruct() ? Reflect.construct(derived, args || [], (0, _getPrototypeOf2["default"])(_this).constructor) : derived.apply(_this, args)); } /** --- category: Categorization --- Categorization Edit component ```jsx_example function Example (props) { const exampleProps = { itemBody: 'Match the name of the celestial body on the left with its correct classification:', interactionData: { categoryOrder: ['uuid1', 'uuid2'], categories: { uuid1: { id: 'uuid1', itemBody: 'Planet' }, uuid2: { id: 'uuid2', itemBody: 'Moon' } }, distractors: { uuid3: { id: 'uuid3', itemBody: 'Mars' }, uuid4: { id: 'uuid4', itemBody: 'Europa' }, uuid5: { id: 'uuid5', itemBody: 'Venus' }, uuid6: { id: 'uuid6', itemBody: 'Phobos' }, uuid7: { id: 'uuid7', itemBody: 'Deimos' }, uuid8: { id: 'uuid8', itemBody: 'Jupiter' }, uuid9: { id: 'uuid9', itemBody: 'America' }, uuid10: { id: 'uuid10', itemBody: 'Asia' } } }, scoringData: { value: [{ id: 'uuid1', scoringAlgorithm: 'AllOrNothing', scoringData: { value: ['uuid3', 'uuid5', 'uuid8'] } }, { id: 'uuid2', scoringAlgorithm: 'AllOrNothing', scoringData: { value: ['uuid4', 'uuid6', 'uuid7'] } }] } } return ( <CategorizationEdit {...exampleProps} {...props} /> ) } <SettingsSwitcher locales={LOCALES}> <EditStateProvider> <Example /> </EditStateProvider> </SettingsSwitcher> ``` **/ var CategorizationEdit = exports["default"] = (0, _withEditTools["default"])(_class = (_CategorizationEdit = /*#__PURE__*/function (_Component) { function CategorizationEdit(props) { var _this2; (0, _classCallCheck2["default"])(this, CategorizationEdit); _this2 = _callSuper(this, CategorizationEdit, [props]); (0, _defineProperty2["default"])(_this2, "distractorRefs", {}); (0, _defineProperty2["default"])(_this2, "categoryRef", null); (0, _defineProperty2["default"])(_this2, "stemElement", null); // =========== // HELPERS // =========== (0, _defineProperty2["default"])(_this2, "getViewDistractors", function () { var answerIds = (0, _flatMap["default"])(function (_ref) { var value = _ref.scoringData.value; return value; }, _this2.props.scoringData.value); return (0, _flow["default"])(Object.values, (0, _filter["default"])(function (_ref2) { var id = _ref2.id; return !answerIds.includes(id); }), (0, _sortBy["default"])(function (_ref3) { var id = _ref3.id; return _this2.state.distractorOrder.indexOf(id); }))(_this2.props.interactionData.distractors); }); (0, _defineProperty2["default"])(_this2, "getCategoryErrors", function (categoryId) { return _this2.props.getErrors("interactionData.categories.".concat(categoryId, ".itemBody")); }); (0, _defineProperty2["default"])(_this2, "getDistractorErrors", function (distractorId) { return _this2.props.getErrors("interactionData.distractors.".concat(distractorId, ".itemBody")); }); (0, _defineProperty2["default"])(_this2, "getAnswerIds", function (categoryId) { var categoryScoringData = (0, _find["default"])({ id: categoryId }, _this2.props.scoringData.value); // since kinesis doesnt save empty array return categoryScoringData.scoringData.value || []; }); // =========== // HANDLERS // =========== (0, _defineProperty2["default"])(_this2, "handleCalculatorTypeChange", function (e, value) { _this2.props.changeItemState({ calculatorType: value }); }); (0, _defineProperty2["default"])(_this2, "handleRemoveDistractor", function (distractorId) { var nextDistractorId = (0, _focusHelpers.getNextItemIdFromArray)(distractorId, _this2.getViewDistractors()); var nextDistractorRef = _this2.distractorRefs[nextDistractorId]; if (nextDistractorRef) { nextDistractorRef.removeChoicebutton.focus(); } else { // focus on add category button _this2.categoryRef.footerRef.focus(); } _this2.presenter.onRemoveDistractor(distractorId); }); (0, _defineProperty2["default"])(_this2, "handleRemoveCategory", function (categoryId) { var categoryOrder = _this2.props.interactionData.categoryOrder; var removingFirstCategory = categoryOrder && categoryOrder.indexOf(categoryId) === 0; if (removingFirstCategory) { _this2.stemElement.focus(); } else { var nextCategoryId = (0, _focusHelpers.getNextItemIdFromArray)(categoryId, _this2.state.sortedCategories); var nextAddAnswerRef = _this2.categoryRef.categoryRefs[nextCategoryId]; if (nextAddAnswerRef) { // will focus the previous category's "+Answer" button nextAddAnswerRef.focus(); } } _this2.presenter.onRemoveCategory(categoryId); }); (0, _defineProperty2["default"])(_this2, "handleRemoveAnswer", function (answerId, categoryId) { var answerIds = _this2.getAnswerIds(categoryId); var removingFirstAnswer = answerIds.indexOf(answerId) === 0; if (removingFirstAnswer) { _this2.categoryRef.categoryAnswerInputRefs[categoryId].answerInput.focus(); } else { var nextAnswerId = (0, _focusHelpers.getNextItemIdFromArray)(answerId, answerIds); var nextAnswerRef = _this2.categoryRef.answerRefs[nextAnswerId]; if (nextAnswerRef) { nextAnswerRef.removeChoicebutton.focus(); } } _this2.presenter.onRemoveAnswer(answerId, categoryId); }); (0, _defineProperty2["default"])(_this2, "handleCreateDistractor", function () { var id = _this2.props.newId(); var newDistractor = { id: id, itemBody: '' }; _this2.setState(function (prevState) { return { distractorOrder: prevState.distractorOrder.concat([id]) }; }); _this2.props.changeItemState({ interactionData: (0, _set["default"])("distractors[".concat(id, "]"), newDistractor, _this2.props.interactionData) }); }); (0, _defineProperty2["default"])(_this2, "handleStemRef", function (node) { _this2.stemElement = node; }); (0, _defineProperty2["default"])(_this2, "handleCategoryRef", function (node) { _this2.categoryRef = node; }); (0, _defineProperty2["default"])(_this2, "handleDescriptionChange", function (itemBody) { _this2.props.changeItemState({ itemBody: itemBody }); }); _this2.presenter = new _CategorizationPresenter["default"](props); var _this2$props$interact = _this2.props.interactionData, categories = _this2$props$interact.categories, _categoryOrder = _this2$props$interact.categoryOrder; _this2.state = { sortedCategories: (0, _utils.getSortedCategories)(categories, _categoryOrder), distractorOrder: (0, _sortBy["default"])('itemBody', props.interactionData.distractors).map(function (_ref4) { var id = _ref4.id; return id; }) }; return _this2; } (0, _inherits2["default"])(CategorizationEdit, _Component); return (0, _createClass2["default"])(CategorizationEdit, [{ key: "UNSAFE_componentWillReceiveProps", value: function UNSAFE_componentWillReceiveProps(nextProps) { this.presenter.UNSAFE_componentWillReceiveProps(nextProps); var _nextProps$interactio = nextProps.interactionData, categories = _nextProps$interactio.categories, categoryOrder = _nextProps$interactio.categoryOrder; if (!(0, _isEqual["default"])(this.props.interactionData.categories, categories) || !(0, _isEqual["default"])(this.props.interactionData.categoryOrder, categoryOrder)) { this.setState({ sortedCategories: (0, _utils.getSortedCategories)(categories, categoryOrder) }); } } }, { key: "renderDistractor", value: // =========== // RENDER // =========== function renderDistractor(distractor, focusOnMount) { var _this3 = this; return (0, _emotion.jsx)(_ChoiceInput["default"], { key: distractor.id, id: distractor.id, disabledFields: this.props.overrideEditableForRegrading ? ['answerInput'] : [], itemBody: distractor.itemBody, errors: this.getDistractorErrors(distractor.id), onInputChange: this.presenter.onDistractorInputChange, onModalClose: this.props.onModalClose, onModalOpen: this.props.onModalOpen, onRemoveChoice: function onRemoveChoice() { return _this3.handleRemoveDistractor(distractor.id); }, ref: function ref(node) { _this3.distractorRefs[distractor.id] = node; }, renderLabel: (0, _formatMessage["default"])('Distractor'), isRequired: true, focusOnMount: focusOnMount, shouldRenderRemoveChoice: !this.props.overrideEditableForRegrading, noRCE: true, notifyScreenreader: this.props.notifyScreenreader, screenReaderText: (0, _formatMessage["default"])('Additional Distractor {itemBody}', { itemBody: (0, _striptags["default"])(distractor.itemBody) }) }); } }, { key: "renderDistractors", value: function renderDistractors(distractors) { var _this4 = this; return distractors.map(function (distractor, i, source) { var focusOnMount = i + 1 === source.length && !distractor.itemBody; return (0, _emotion.jsx)(_quizCommon.Flex.Item, { key: "".concat(distractor.id, "-i"), overflowY: "visible" }, _this4.renderDistractor(distractor, focusOnMount)); }); } }, { key: "renderDistractorSection", value: function renderDistractorSection() { var _this5 = this; var distractors = this.getViewDistractors(); return (0, _emotion.jsx)(_quizCommon.Flex, { direction: "column", gap: "small", className: "distractorsContainer", padding: "x-small 0 0 0" }, (0, _emotion.jsx)(_uiText.Text, { color: "primary" }, (0, _formatMessage["default"])('Additional Distractors')), (0, _emotion.jsx)(_quizCommon.Flex, { direction: "column", gap: "small" }, distractors.map(function (distractor, i, source) { var focusOnMount = i + 1 === source.length && !distractor.itemBody; return _this5.renderDistractor(distractor, focusOnMount); })), !this.props.overrideEditableForRegrading && (0, _emotion.jsx)(_quizCommon.Flex.Item, { overflowY: "visible" }, (0, _emotion.jsx)(_Footer["default"], { onCreateChoice: this.handleCreateDistractor, buttonText: (0, _formatMessage["default"])('Distractor'), screenReaderText: (0, _formatMessage["default"])('Add Distractor'), notifyScreenreader: this.props.notifyScreenreader, automationData: "add-categorization-distractor-button" }))); } }, { key: "renderOptionsDescription", value: function renderOptionsDescription() { return (0, _emotion.jsx)(_uiA11yContent.ScreenReaderContent, null, (0, _formatMessage["default"])('Categorization options')); } }, { key: "render", value: function render() { var _this6 = this; this.distractorRefs = {}; return (0, _emotion.jsx)("div", null, (0, _emotion.jsx)(_QuestionContainer["default"], { 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'), textareaRef: this.handleStemRef }, (0, _emotion.jsx)(_CategoryForm["default"], { categoriesErrors: this.props.getErrors('scoringData.value.$errors') // These error callbacks are passed in as new functions, to force // the component to re-render when errors change. Not ideal - we // should be passing the errors in directly , getCategoryErrors: function getCategoryErrors(categoryId) { return _this6.getCategoryErrors(categoryId); }, distractorErrors: function distractorErrors(distractorId) { return _this6.getDistractorErrors(distractorId); }, categoryScoringData: this.getAnswerIds, distractors: this.props.interactionData.distractors, disabled: this.props.overrideEditableForRegrading, sortedCategories: this.state.sortedCategories, notifyScreenreader: this.props.notifyScreenreader, onCategoryInputChange: this.presenter.onCategoryInputChange, onCreateAnswer: this.presenter.onCreateAnswer, onCreateCategory: this.presenter.onCreateCategory, onDistractorInputChange: this.presenter.onDistractorInputChange, onModalClose: this.props.onModalClose, onModalOpen: this.props.onModalOpen, onRemoveAnswer: this.handleRemoveAnswer, onRemoveCategory: this.handleRemoveCategory, ref: this.handleCategoryRef }), this.renderDistractorSection()), (0, _emotion.jsx)(_QuestionSettingsContainer["default"], { additionalOptions: this.props.additionalOptions }, this.props.showCalculatorOption && (0, _emotion.jsx)(_QuestionSettingsPanel["default"], { label: (0, _formatMessage["default"])('Options'), defaultExpanded: true }, (0, _emotion.jsx)(_quizCommon.FormFieldGroup, { rowSpacing: "small", description: this.renderOptionsDescription() }, (0, _emotion.jsx)(_CalculatorOptionWithOqaatAlert["default"], { disabled: this.props.overrideEditableForRegrading, calculatorValue: this.props.calculatorType, onCalculatorTypeChange: this.handleCalculatorTypeChange, oqaatChecked: this.props.oneQuestionAtATime, onOqaatChange: this.props.setOneQuestionAtATime }))))); } }]); }(_react.Component), (0, _defineProperty2["default"])(_CategorizationEdit, "displayName", 'CategorizationEdit'), (0, _defineProperty2["default"])(_CategorizationEdit, "componentId", "Quizzes".concat(_CategorizationEdit.displayName)), (0, _defineProperty2["default"])(_CategorizationEdit, "interactionType", _categorization["default"]), (0, _defineProperty2["default"])(_CategorizationEdit, "propTypes", _objectSpread(_objectSpread({ additionalOptions: _QuestionSettingsContainer["default"].propTypes.additionalOptions, calculatorType: _propTypes["default"].string, changeItemState: _propTypes["default"].func, enableRichContentEditor: _propTypes["default"].bool, errors: _propTypes["default"].shape({ itemBody: _propTypes["default"].arrayOf(_propTypes["default"].string), interactionData: _propTypes["default"].shape({ distractors: _propTypes["default"].objectOf(_propTypes["default"].shape({ itemBody: _propTypes["default"].arrayOf(_propTypes["default"].string) })), categories: _propTypes["default"].objectOf(_propTypes["default"].shape({ itemBody: _propTypes["default"].arrayOf(_propTypes["default"].string) })) }), scoringData: _propTypes["default"].shape({ value: _propTypes["default"].shape({ $errors: _propTypes["default"].arrayOf(_propTypes["default"].string) }) }) }), interactionData: _propTypes["default"].shape({ categoryOrder: _propTypes["default"].arrayOf(_propTypes["default"].string), categories: _propTypes["default"].objectOf(_propTypes["default"].shape({ id: _propTypes["default"].string, itemBody: _propTypes["default"].string })), distractors: _propTypes["default"].objectOf(_propTypes["default"].shape({ id: _propTypes["default"].string, itemBody: _propTypes["default"].string })) }).isRequired, itemBody: _propTypes["default"].string, notifyScreenreader: _propTypes["default"].func.isRequired, onModalClose: _propTypes["default"].func, onModalOpen: _propTypes["default"].func, oneQuestionAtATime: _propTypes["default"].bool, openImportModal: _propTypes["default"].func.isRequired, overrideEditableForRegrading: _propTypes["default"].bool, scoringData: _propTypes["default"].shape({ value: _propTypes["default"].arrayOf(_propTypes["default"].shape({ id: _propTypes["default"].string })) }).isRequired, setOneQuestionAtATime: _propTypes["default"].func, newId: _propTypes["default"].func }, _withEditTools["default"].injectedProps), {}, { styles: _propTypes["default"].object, showCalculatorOption: _propTypes["default"].bool })), (0, _defineProperty2["default"])(_CategorizationEdit, "defaultProps", { calculatorType: 'none', enableRichContentEditor: true, oneQuestionAtATime: false, overrideEditableForRegrading: false, newId: _uuid.v4, setOneQuestionAtATime: Function.prototype, additionalOptions: void 0, changeItemState: void 0, errors: void 0, itemBody: void 0, onModalClose: void 0, onModalOpen: void 0, showCalculatorOption: true }), _CategorizationEdit)) || _class;