UNPKG

quiz-interactions

Version:

A React UI component Library for quiz interaction types.

492 lines (412 loc) 19.2 kB
"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard"); Object.defineProperty(exports, "__esModule", { value: true }); exports.default = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); 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 _react = _interopRequireWildcard(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 _uiElements = require("@instructure/ui-elements"); var _uiThemeable = require("@instructure/ui-themeable"); 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 _CategorizationPresenter = _interopRequireDefault(require("./CategorizationPresenter")); var _CategoryForm = _interopRequireDefault(require("./CategoryForm")); var _utils = require("../common/utils"); var _theme = _interopRequireDefault(require("./theme")); var _withEditTools = _interopRequireDefault(require("../../../util/withEditTools")); var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/lib/format-message")); var _QuestionSettingsPanel = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsPanel")); var _CalculatorOptionWithOqaatAlert = _interopRequireDefault(require("../../common/edit/components/CalculatorOptionWithOqaatAlert")); var _uiFormField = require("@instructure/ui-form-field"); var _uiA11y = require("@instructure/ui-a11y"); var _dec, _class, _class2, _temp; 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(source, true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } var styles = { componentId: 'kAXHh', template: function template(theme) { return "\n\n.kAXHh_fubF{padding-top:".concat(theme.distractorPaddingTop || 'inherit', "}"); }, 'distractorsContainer': 'kAXHh_fubF' }; var CategorizationEdit = ( /** --- 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> ``` **/ _dec = (0, _uiThemeable.themeable)(_theme.default, styles), (0, _withEditTools.default)(_class = _dec(_class = (_temp = _class2 = /*#__PURE__*/ function (_Component) { (0, _inherits2.default)(CategorizationEdit, _Component); function CategorizationEdit(props) { var _this; (0, _classCallCheck2.default)(this, CategorizationEdit); _this = (0, _possibleConstructorReturn2.default)(this, (0, _getPrototypeOf2.default)(CategorizationEdit).call(this, props)); _this.distractorRefs = {}; _this.categoryRef = null; _this.stemElement = null; _this.getViewDistractors = function () { var answerIds = (0, _flatMap.default)(function (_ref) { var value = _ref.scoringData.value; return value; }, _this.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 _this.state.distractorOrder.indexOf(id); }))(_this.props.interactionData.distractors); }; _this.getCategoryErrors = function (categoryId) { return _this.props.getErrors("interactionData.categories.".concat(categoryId, ".itemBody")); }; _this.getDistractorErrors = function (distractorId) { return _this.props.getErrors("interactionData.distractors.".concat(distractorId, ".itemBody")); }; _this.getAnswerIds = function (categoryId) { var categoryScoringData = (0, _find.default)({ id: categoryId }, _this.props.scoringData.value); // since kinesis doesnt save empty array return categoryScoringData.scoringData.value || []; }; _this.handleCalculatorTypeChange = function (e, value) { _this.props.changeItemState({ calculatorType: value }); }; _this.handleRemoveDistractor = function (distractorId) { var nextDistractorId = _this.presenter.getNextItemIdFromArray(distractorId, _this.getViewDistractors()); var nextDistractorRef = _this.distractorRefs[nextDistractorId]; if (nextDistractorRef) { nextDistractorRef.removeChoicebutton.focus(); } else { // focus on add category button _this.categoryRef.footerRef.focus(); } _this.presenter.onRemoveDistractor(distractorId); }; _this.handleRemoveCategory = function (categoryId) { var categoryOrder = _this.props.interactionData.categoryOrder; var removingFirstCategory = categoryOrder && categoryOrder.indexOf(categoryId) === 0; if (removingFirstCategory) { _this.stemElement.focus(); } else { var nextCategoryId = _this.presenter.getNextItemIdFromArray(categoryId, _this.state.sortedCategories); var nextAddAnswerRef = _this.categoryRef.categoryRefs[nextCategoryId]; if (nextAddAnswerRef) { // will focus the previous category's "+Answer" button nextAddAnswerRef.focus(); } } _this.presenter.onRemoveCategory(categoryId); }; _this.handleRemoveAnswer = function (answerId, categoryId) { var answerIds = _this.getAnswerIds(categoryId); var removingFirstAnswer = answerIds.indexOf(answerId) === 0; if (removingFirstAnswer) { _this.categoryRef.categoryAnswerInputRefs[categoryId].answerInput.focus(); } else { var nextAnswerId = _this.presenter.getNextItemIdFromArray(answerId, answerIds); var nextAnswerRef = _this.categoryRef.answerRefs[nextAnswerId]; if (nextAnswerRef) { nextAnswerRef.removeChoicebutton.focus(); } } _this.presenter.onRemoveAnswer(answerId, categoryId); }; _this.handleCreateDistractor = function () { var id = _this.props.newId(); var newDistractor = { id: id, itemBody: '' }; _this.setState(function (prevState) { return { distractorOrder: prevState.distractorOrder.concat([id]) }; }); _this.props.changeItemState({ interactionData: (0, _set.default)("distractors[".concat(id, "]"), newDistractor, _this.props.interactionData) }); }; _this.handleStemRef = function (node) { _this.stemElement = node; }; _this.handleCategoryRef = function (node) { _this.categoryRef = node; }; _this.handleDescriptionChange = function (itemBody) { _this.props.changeItemState({ itemBody: itemBody }); }; _this.presenter = new _CategorizationPresenter.default(props); 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), distractorOrder: (0, _sortBy.default)('itemBody', props.interactionData.distractors).map(function (_ref4) { var id = _ref4.id; return id; }) }; return _this; } (0, _createClass2.default)(CategorizationEdit, [{ key: "componentWillReceiveProps", value: function componentWillReceiveProps(nextProps) { this.presenter.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) }); } } // =========== // HELPERS // =========== }, { key: "renderDistractor", // =========== // RENDER // =========== value: function renderDistractor(distractor, focusOnMount) { var _this2 = this; return _react.default.createElement(_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 _this2.handleRemoveDistractor(distractor.id); }, ref: function ref(node) { _this2.distractorRefs[distractor.id] = node; }, focusOnMount: focusOnMount, shouldRenderRemoveChoice: !this.props.overrideEditableForRegrading, noRCE: true, notifyScreenreader: this.props.notifyScreenreader }); } }, { key: "renderDistractors", value: function renderDistractors() { var _this3 = this; var distractors = this.getViewDistractors(); return _react.default.createElement("div", { className: styles.distractorsContainer }, _react.default.createElement(_uiElements.Text, { color: "primary" }, (0, _formatMessage.default)('Additional Distractors')), distractors.map(function (distractor, i, source) { var focusOnMount = i + 1 === source.length && !distractor.itemBody; return _this3.renderDistractor(distractor, focusOnMount); }), !this.props.overrideEditableForRegrading && _react.default.createElement(_Footer.default, { onCreateChoice: this.handleCreateDistractor, buttonText: (0, _formatMessage.default)('Distractor'), screenReaderText: (0, _formatMessage.default)('Add Distractor') })); } }, { key: "renderOptionsDescription", value: function renderOptionsDescription() { return _react.default.createElement(_uiA11y.ScreenReaderContent, null, (0, _formatMessage.default)('Categorization options')); } }, { key: "render", value: function render() { var _this4 = this; this.distractorRefs = {}; return _react.default.createElement("div", null, _react.default.createElement(_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, renderImportModal: this.props.renderImportModal, stemErrors: this.props.getErrors('itemBody'), textareaRef: this.handleStemRef }, _react.default.createElement(_CategoryForm.default // 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 _this4.getCategoryErrors(categoryId); }, distractorErrors: function distractorErrors(distractorId) { return _this4.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.renderDistractors()), _react.default.createElement(_QuestionSettingsContainer.default, { additionalOptions: this.props.additionalOptions }, !this.props.hiddenOptions.calculator && _react.default.createElement(_QuestionSettingsPanel.default, { label: (0, _formatMessage.default)('Options'), defaultExpanded: true }, _react.default.createElement(_uiFormField.FormFieldGroup, { rowSpacing: "small", description: this.renderOptionsDescription() }, _react.default.createElement(_CalculatorOptionWithOqaatAlert.default, { disabled: this.props.overrideEditableForRegrading, calculatorValue: this.props.calculatorType, onCalculatorTypeChange: this.handleCalculatorTypeChange, oqaatChecked: this.props.oneQuestionAtATime, onOqaatChange: this.props.setOneQuestionAtATime }))))); } }]); CategorizationEdit.displayName = "CategorizationEdit"; return CategorizationEdit; }(_react.Component), _class2.interactionType = _categorization.default, _class2.propTypes = _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) })) }) }), hiddenOptions: _propTypes.default.shape({ calculator: _propTypes.default.bool }), 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, renderImportModal: _propTypes.default.func.isRequired, 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), _class2.defaultProps = { calculatorType: 'none', hiddenOptions: { calculator: true }, 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 }, _temp)) || _class) || _class); exports.default = CategorizationEdit;