UNPKG

quiz-interactions

Version:

A React UI component Library for quiz interaction types.

367 lines (310 loc) 13 kB
"use strict"; var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck")); var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn")); var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf")); var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits")); var _react = _interopRequireWildcard(require("react")); var _propTypes = _interopRequireDefault(require("prop-types")); var _immutabilityHelper = _interopRequireDefault(require("immutability-helper")); var _findIndex = _interopRequireDefault(require("lodash/findIndex")); var _uiButtons = require("@instructure/ui-buttons"); var _uiMenu = require("@instructure/ui-menu"); var _uiA11y = require("@instructure/ui-a11y"); var _uiIcons = require("@instructure/ui-icons"); var _quizRce = require("@instructure/quiz-rce"); var _ChoicesList = _interopRequireDefault(require("../common/ChoicesList")); var _CategoriesContainer = _interopRequireDefault(require("../common/CategoriesContainer")); var _FocusGroup = _interopRequireDefault(require("../../common/components/FocusGroup")); var _utils = require("../common/utils"); var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/lib/format-message")); /** --- category: Categorization --- Categorization Take component ```jsx_example const WrappedExample = DragDropContext(HTML5Backend)(CategorizationTake) function Example (props) { const exampleProps = { itemBody: 'Match the name of the celestial body on the left with its correct classification:', interactionData: { categoryOrder: ['uuid1', 'uuid2', 'uuid11'], categories: { uuid1: { id: 'uuid1', itemBody: 'Planet' }, uuid2: { id: 'uuid2', itemBody: 'Moon' }, uuid11: { id: 'uuid11', itemBody: 'Galaxy' } }, 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' } } }, userResponse: { value: [{ id: 'uuid1', value: ['uuid3', 'uuid5', 'uuid8'], type: 'ArrayText' },{ id: 'uuid2', value: ['uuid4', 'uuid6', 'uuid7'], type: 'ArrayText' }, { id: 'uuid11', value: [], type: 'ArrayText' }] } } return ( <WrappedExample {...exampleProps} {...props} /> ) } <SettingsSwitcher locales={LOCALES}> <TakeStateProvider> <Example /> </TakeStateProvider> </SettingsSwitcher> ``` **/ var CategorizationTake = /*#__PURE__*/ function (_Component) { (0, _inherits2.default)(CategorizationTake, _Component); (0, _createClass2.default)(CategorizationTake, [{ key: "componentWillUnmount", value: function componentWillUnmount() { this._timeouts.forEach(clearTimeout); } }]); function CategorizationTake(props) { var _this; (0, _classCallCheck2.default)(this, CategorizationTake); _this = (0, _possibleConstructorReturn2.default)(this, (0, _getPrototypeOf2.default)(CategorizationTake).call(this, props)); _this._timeouts = []; _this.categoryRefs = []; _this.resetFocus = function () { _this._timeouts = _this._timeouts.concat(setTimeout(function () { return _this.focusGroup.focusFirst('[role="button"]'); }, 100)); }; _this.onMenuItemSelected = function (answerId, categoryId, removed, event, focusNewCategory) { event && event.preventDefault && event.preventDefault(); var distractorDoesntMatchOptions = !_this.distractorExists(answerId); if (distractorDoesntMatchOptions) { // if distractor for different question // is dropped into this component return; } var newCategoryIndex = _this.categoryIndex(removed ? _utils.UNCATEGORIZED : categoryId); var newUserResponse = _this.props.userResponse.value || []; // remove answer from former category if exists var oldCategoryIndex = newUserResponse.findIndex(function (category) { return category.value.includes(answerId); }); if (oldCategoryIndex !== -1) { newUserResponse = _this.removeAnswerFromCategory(newUserResponse, answerId, oldCategoryIndex); } // add answer on new category if (newCategoryIndex !== -1) { newUserResponse = _this.addAnswerOnCategory(newUserResponse, answerId, newCategoryIndex); } if (removed) { _this.props.notifyScreenreader((0, _formatMessage.default)('Answer removed from category')); } else { _this.props.notifyScreenreader((0, _formatMessage.default)('Answer added to category')); } _this.props.handleResponseUpdate(newUserResponse); if (focusNewCategory && _this.categoryRefs[categoryId]) { _this.categoryRefs[categoryId].focus(); } }; _this.onDrop = function (idNewCategory, answer, focusNewCategory) { _this.onMenuItemSelected(answer.id, idNewCategory, false, null, focusNewCategory); }; _this.onDropOut = function (answer) { var categ = _this.props.userResponse.value.find(function (categAux) { return categAux.value.includes(answer.id); }); // remove the answer from it's current category if (categ) { _this.onMenuItemSelected(answer.id, categ.id, true); } }; _this.filterDistractors = function (idCategory) { var _this$props = _this.props, interactionData = _this$props.interactionData, userResponse = _this$props.userResponse; if (!interactionData.distractors || !userResponse.value) return []; return userResponse.value.filter(function (category) { return category.id === idCategory || category.id === _utils.UNCATEGORIZED; }).reduce(function (distractors, category) { return [].concat((0, _toConsumableArray2.default)(distractors), (0, _toConsumableArray2.default)(category.value.map(function (id) { return interactionData.distractors[id]; }))); }, []); }; _this.focusGroupRef = function (node) { _this.focusGroup = node; }; _this.handleCategoryRef = function (categoryId) { return function (node) { _this.categoryRefs[categoryId] = node; }; }; _this.renderPopover = function (itemId) { return _react.default.createElement(_uiMenu.Menu, { trigger: _react.default.createElement(_uiButtons.Button, { variant: "icon", size: "small", as: "span", icon: _uiIcons.IconDragHandleLine }, _react.default.createElement(_uiA11y.ScreenReaderContent, null, (0, _formatMessage.default)('Select Category for {distractorBody}', { distractorBody: _this.getDistractorBody(itemId) }))) }, _this.state.sortedCategories.map(function (categ) { return _react.default.createElement(_uiMenu.Menu.Item, { key: categ.id, onSelect: function onSelect() { return _this.onDrop(categ.id, { id: itemId }, true); } }, categ.itemBody); })); }; var _this$props$interacti = _this.props.interactionData, categories = _this$props$interacti.categories, categoryOrder = _this$props$interacti.categoryOrder; _this.state = { sortedCategories: (0, _utils.getSortedCategories)(categories, categoryOrder) }; return _this; } (0, _createClass2.default)(CategorizationTake, [{ key: "componentDidMount", value: function componentDidMount() { var userResponse = this.props.userResponse; // Add an "uncategorized" category for uncategorized distractors, if it doesn't already exist if (userResponse.value && !userResponse.value.find(function (category) { return category.id === _utils.UNCATEGORIZED; })) { this.props.handleResponseUpdate(this.includeUncategorized(userResponse.value)); } } }, { key: "categoryIndex", value: function categoryIndex(idCategory) { return (0, _findIndex.default)(this.props.userResponse.value, { id: idCategory }); } }, { key: "removeAnswerFromCategory", value: function removeAnswerFromCategory(source, idAnswer, categoryIndex) { var answerIndex = source[categoryIndex].value.indexOf(idAnswer); return (0, _immutabilityHelper.default)(source, (0, _defineProperty2.default)({}, categoryIndex, { value: { $splice: [[answerIndex, 1]] } })); } }, { key: "addAnswerOnCategory", value: function addAnswerOnCategory(source, idAnswer, categoryIndex) { return (0, _immutabilityHelper.default)(source, (0, _defineProperty2.default)({}, categoryIndex, { value: { $push: [idAnswer] } })); } }, { key: "getDistractorBody", value: function getDistractorBody(distractorId) { return this.props.interactionData.distractors[distractorId].itemBody; } }, { key: "categorizedDistractors", value: function categorizedDistractors(userResponseValue) { if (!userResponseValue) return new Set(); return new Set(userResponseValue.filter(function (category) { return category.id !== _utils.UNCATEGORIZED; }).reduce(function (ids, category) { return [].concat((0, _toConsumableArray2.default)(ids), (0, _toConsumableArray2.default)(category.value)); }, [])); } }, { key: "uncategorizedDistractors", value: function uncategorizedDistractors(userResponseValue) { var interactionData = this.props.interactionData; if (!interactionData.distractors) return []; var categorized = this.categorizedDistractors(userResponseValue); return Object.keys(interactionData.distractors).filter(function (id) { return !categorized.has(id); }); } }, { key: "includeUncategorized", value: function includeUncategorized(userResponseValue) { return [].concat((0, _toConsumableArray2.default)(userResponseValue.filter(function (category) { return category.id !== _utils.UNCATEGORIZED; })), [{ id: _utils.UNCATEGORIZED, value: this.uncategorizedDistractors(userResponseValue), type: 'ArrayText' }]); } // =========== // ACTIONS // =========== }, { key: "distractorExists", value: function distractorExists(answerId) { var distractorIds = Object.keys(this.props.interactionData.distractors); return distractorIds.includes(answerId); } }, { key: "render", value: function render() { return _react.default.createElement(_quizRce.ItemBodyWrapper, { itemBody: this.props.itemBody }, _react.default.createElement(_FocusGroup.default, { ref: this.focusGroupRef }, _react.default.createElement(_CategoriesContainer.default, { sortedCategories: this.state.sortedCategories, distractors: this.props.interactionData.distractors, filterDistractors: this.filterDistractors, handleCategoryRef: this.handleCategoryRef, isDraggable: true, onDrop: this.onDrop, onDropOut: this.onDropOut, onMenuItemSelected: this.onMenuItemSelected, userResponseValue: this.props.userResponse.value }), _react.default.createElement(_ChoicesList.default, { actionsContent: this.renderPopover, distractors: this.filterDistractors(), onDrop: this.onDropOut, isDraggable: true }))); } }]); CategorizationTake.displayName = "CategorizationTake"; return CategorizationTake; }(_react.Component); exports.default = CategorizationTake; CategorizationTake.propTypes = { handleResponseUpdate: _propTypes.default.func.isRequired, interactionData: _propTypes.default.object.isRequired, itemBody: _propTypes.default.string.isRequired, notifyScreenreader: _propTypes.default.func.isRequired, userResponse: _propTypes.default.object.isRequired };