quiz-interactions
Version:
A React UI component Library for quiz interaction types.
492 lines (412 loc) • 19.2 kB
JavaScript
"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;