@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
481 lines (473 loc) • 22.6 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
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 _uuid = require("uuid");
var _striptags = _interopRequireDefault(require("striptags"));
var _assignIn2 = _interopRequireDefault(require("lodash/fp/assignIn"));
var _set = _interopRequireDefault(require("lodash/fp/set"));
var _last = _interopRequireDefault(require("lodash/fp/last"));
var _omit = _interopRequireDefault(require("lodash/omit"));
var _without = _interopRequireDefault(require("lodash/without"));
var _emotion = require("@instructure/emotion");
var _uiA11yContent = require("@instructure/ui-a11y-content");
var _uiGrid = require("@instructure/ui-grid");
var _uiCheckbox = require("@instructure/ui-checkbox");
var _uiText = require("@instructure/ui-text");
var _uiView = require("@instructure/ui-view");
var _AnswerInput = _interopRequireDefault(require("../../common/edit/components/AnswerInput"));
var _Footer = _interopRequireDefault(require("../../common/edit/components/Footer"));
var _ordering = _interopRequireDefault(require("../../../records/interactions/ordering"));
var _QuestionContainer = _interopRequireDefault(require("../../common/edit/components/QuestionContainer"));
var _RemoveChoiceButton = _interopRequireDefault(require("../../common/edit/components/RemoveChoiceButton"));
var _withEditTools = _interopRequireDefault(require("../../../util/withEditTools"));
var _Card = _interopRequireDefault(require("../../common/components/Card"));
var _ReorderChoiceButton = _interopRequireDefault(require("../common/ReorderChoiceButton"));
var _QuestionSettingsContainer = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsContainer"));
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 _quizCommon = require("@instructure/quiz-common");
var _class, _OrderingEdit;
/** @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: Ordering
---
Ordering Edit component
```jsx_example
const WrappedExample = DragDropContext(HTML5Backend)(OrderingEdit)
function Example (props) {
const exampleProps = {
itemBody: 'Order these characters from tallest to shortest:',
interactionData: {
choices: {
uuid6: { id: 'uuid6', itemBody: 'Gandalf' },
uuid5: { id: 'uuid5', itemBody: 'Legolas' },
uuid4: { id: 'uuid4', itemBody: 'Aragorn' },
uuid3: { id: 'uuid3', itemBody: 'Gimli' },
uuid2: { id: 'uuid2', itemBody: 'Frodo' },
uuid1: { id: 'uuid1', itemBody: 'Gollum' },
}
},
properties: {
displayAnswersParagraph: true,
includeLabels: true,
topLabel: 'Taller',
bottomLabel: 'Shorter'
},
scoringData: {
value: ['uuid6','uuid5','uuid4','uuid3','uuid2','uuid1']
}
}
return (
<WrappedExample {...exampleProps} {...props} />
)
}
<SettingsSwitcher locales={LOCALES}>
<EditStateProvider>
<Example />
</EditStateProvider>
</SettingsSwitcher>
```
**/
var OrderingEdit = exports["default"] = (0, _withEditTools["default"])(_class = (_OrderingEdit = /*#__PURE__*/function (_Component) {
function OrderingEdit() {
var _this2;
(0, _classCallCheck2["default"])(this, OrderingEdit);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this2 = _callSuper(this, OrderingEdit, [].concat(args));
(0, _defineProperty2["default"])(_this2, "stemElement", null);
(0, _defineProperty2["default"])(_this2, "topLabelRef", null);
(0, _defineProperty2["default"])(_this2, "bottomLabelRef", null);
(0, _defineProperty2["default"])(_this2, "inputRefs", []);
(0, _defineProperty2["default"])(_this2, "reorderRefs", []);
(0, _defineProperty2["default"])(_this2, "removeChoiceRefs", []);
(0, _defineProperty2["default"])(_this2, "_timeouts", []);
(0, _defineProperty2["default"])(_this2, "_choiceWasCreated", false);
// ===========
// HANDLERS
// ===========
(0, _defineProperty2["default"])(_this2, "handleStemRef", function (node) {
_this2.stemElement = node;
});
(0, _defineProperty2["default"])(_this2, "handleTopLabelRef", function (node) {
_this2.topLabelRef = node;
});
(0, _defineProperty2["default"])(_this2, "handleBottomLabelRef", function (node) {
_this2.bottomLabelRef = node;
});
(0, _defineProperty2["default"])(_this2, "handleCalculatorTypeChange", function (e, value) {
_this2.props.changeItemState({
calculatorType: value
});
});
(0, _defineProperty2["default"])(_this2, "handleMoveChoice", function (currI, targetI) {
// Create a new array to preserve immutability
var choices = _this2.props.scoringData.value.slice(0)
// Use destructuring to swap the two values
;
var _ref = [choices[currI], choices[targetI]];
choices[targetI] = _ref[0];
choices[currI] = _ref[1];
_this2.props.changeItemState({
scoringData: _objectSpread(_objectSpread({}, _this2.props.scoringData), {}, {
value: choices
})
});
_this2._timeouts = [].concat((0, _toConsumableArray2["default"])(_this2._timeouts), [setTimeout(function () {
return _this2.reorderRefs[targetI].focus();
})]);
});
(0, _defineProperty2["default"])(_this2, "handleRemoveChoice", function (choiceId, index) {
var choiceOrder = _this2.props.scoringData.value;
if (index === 0) {
if (_this2.props.properties.includeLabels) {
_this2.topLabelRef.focus();
} else {
// added timeout to compensate for RCE sluggishness
_this2._timeouts = [].concat((0, _toConsumableArray2["default"])(_this2._timeouts), [setTimeout(function () {
return _this2.stemElement.focus();
})]);
}
} else {
if (choiceOrder.length > 3) {
_this2.removeChoiceRefs[index - 1].focus();
} else {
_this2.reorderRefs[index - 1].focus();
}
}
_this2.props.changeItemState({
interactionData: _objectSpread(_objectSpread({}, _this2.props.interactionData), {}, {
choices: (0, _omit["default"])(_this2.props.interactionData.choices, choiceId)
}),
scoringData: _objectSpread(_objectSpread({}, _this2.props.scoringData), {}, {
value: (0, _without["default"])(choiceOrder, choiceId)
})
});
});
(0, _defineProperty2["default"])(_this2, "handleCreateChoice", function () {
var id = (0, _uuid.v4)();
var newChoice = {
id: id,
itemBody: ''
};
_this2._choiceWasCreated = true;
_this2.props.changeItemState({
interactionData: _objectSpread(_objectSpread({}, _this2.props.interactionData), {}, {
choices: (0, _assignIn2["default"])(_this2.props.interactionData.choices, (0, _defineProperty2["default"])({}, id, newChoice))
}),
scoringData: _objectSpread(_objectSpread({}, _this2.props.scoringData), {}, {
value: [].concat((0, _toConsumableArray2["default"])(_this2.props.scoringData.value), [id])
})
});
_this2.props.notifyScreenreader((0, _formatMessage["default"])('Navigate up to find new choice input'));
});
(0, _defineProperty2["default"])(_this2, "handleIncludeLabelsChange", function (e) {
var properties = _this2.props.properties;
if (!properties.includeLabels) {
_this2.props.notifyScreenreader((0, _formatMessage["default"])('Navigate up to find label fields'));
}
_this2.props.changeItemState({
properties: _objectSpread(_objectSpread({}, _this2.props.properties), {}, {
includeLabels: !properties['includeLabels']
})
});
});
(0, _defineProperty2["default"])(_this2, "handleDisplayAnswersParagraphChange", function (e) {
var properties = _this2.props.properties;
_this2.props.changeItemState({
properties: _objectSpread(_objectSpread({}, _this2.props.properties), {}, {
displayAnswersParagraph: !properties['displayAnswersParagraph']
})
});
});
(0, _defineProperty2["default"])(_this2, "handleInputChange", function (choiceId, event, _ref2) {
var editorContent = _ref2.editorContent;
_this2.props.changeItemState({
interactionData: (0, _set["default"])("choices[".concat(choiceId, "].itemBody"), editorContent, _this2.props.interactionData)
});
});
return _this2;
}
(0, _inherits2["default"])(OrderingEdit, _Component);
return (0, _createClass2["default"])(OrderingEdit, [{
key: "componentWillUnmount",
value: function componentWillUnmount() {
this._timeouts.forEach(clearTimeout);
}
}, {
key: "componentDidUpdate",
value: function componentDidUpdate() {
if (this._choiceWasCreated) {
this._choiceWasCreated = false;
(0, _last["default"])(this.inputRefs).focus();
}
}
}, {
key: "renderChoice",
value:
// ===========
// RENDER
// ===========
function renderChoice(choice, isFinalChoice, index) {
var _this3 = this;
var choicesLength = this.props.scoringData.value.length;
var nonEditable = this.props.overrideEditableForRegrading;
return (0, _emotion.jsx)(_Card["default"], {
id: choice.id,
index: index,
key: "".concat(choice.id, "_").concat(index),
moveCard: this.handleMoveChoice
}, (0, _emotion.jsx)(_uiGrid.Grid, {
colSpacing: "small"
}, (0, _emotion.jsx)(_uiGrid.Grid.Row, null, (0, _emotion.jsx)(_uiGrid.Grid.Col, {
width: "auto"
}, (0, _emotion.jsx)(_uiView.View, {
as: "div",
margin: "xx-large 0 0 0",
themeOverride: {
marginXxLarge: '39px'
}
}, (0, _emotion.jsx)(_quizCommon.Flex, {
direction: "row",
alignItems: "start"
}, (0, _emotion.jsx)(_quizCommon.Flex.Item, null, (0, _emotion.jsx)(_uiA11yContent.ScreenReaderContent, null, (0, _formatMessage["default"])('position {position, number}', {
position: index + 1
})), (0, _emotion.jsx)(_uiA11yContent.PresentationContent, null, (0, _emotion.jsx)(_uiText.Text, {
color: "primary"
}, _formatMessage["default"].number(index + 1))))))), (0, _emotion.jsx)(_uiGrid.Grid.Col, {
vAlign: "middle"
}, (0, _emotion.jsx)(_uiView.View, {
as: "div",
background: "primary"
}, (0, _emotion.jsx)(_AnswerInput["default"], {
disabled: nonEditable,
errors: this.props.getErrors("interactionData.choices[".concat(choice.id, "].itemBody")),
id: choice.id,
itemBody: choice.itemBody,
noRCE: !this.props.enableRichContentEditor,
onChangeHandler: this.handleInputChange,
onModalClose: this.props.onModalClose,
onModalOpen: this.props.onModalOpen,
openImportModal: this.props.openImportModal,
ref: function ref(node) {
_this3.inputRefs[index] = node;
},
automationData: "sdk-ordering-answer-".concat(choice.id),
isRequired: true
}))), (0, _emotion.jsx)(_uiGrid.Grid.Col, {
width: "auto"
}, (0, _emotion.jsx)(_uiView.View, {
as: "div",
margin: "xx-large 0 0 0",
themeOverride: {
marginXxLarge: '30px'
}
}, (0, _emotion.jsx)(_quizCommon.Flex, {
direction: "row",
justifyItems: "start"
}, (0, _emotion.jsx)(_quizCommon.Flex.Item, null, (0, _emotion.jsx)(_ReorderChoiceButton["default"], {
ref: function ref(node) {
_this3.reorderRefs[index] = node;
},
id: choice.id,
isFinalChoice: isFinalChoice,
isFirstChoice: index === 0,
onMoveChoiceUp: function onMoveChoiceUp() {
return _this3.handleMoveChoice(index, index - 1);
},
onMoveChoiceDown: function onMoveChoiceDown() {
return _this3.handleMoveChoice(index, index + 1);
},
screenReaderText: (0, _formatMessage["default"])('Reorder Choice: {choice}', {
choice: (0, _striptags["default"])(choice.itemBody)
})
})), choicesLength > 2 && !nonEditable && (0, _emotion.jsx)(_quizCommon.Flex.Item, null, (0, _emotion.jsx)(_RemoveChoiceButton["default"], {
choiceId: choice.id,
ref: function ref(node) {
_this3.removeChoiceRefs[index] = node;
},
onRemoveChoice: function onRemoveChoice() {
return _this3.handleRemoveChoice(choice.id, index);
},
screenReaderText: (0, _formatMessage["default"])('Remove Choice: {choice}', {
choice: (0, _striptags["default"])(choice.itemBody)
})
}))))))));
}
}, {
key: "renderChoices",
value: function renderChoices() {
var _this4 = this;
var choiceOrder = this.props.scoringData.value;
var choicesLength = choiceOrder.length;
return choiceOrder.map(function (choiceId, i) {
var isFinalChoice = i + 1 === choicesLength;
var choice = _this4.props.interactionData.choices[choiceId];
return (0, _emotion.jsx)(_uiView.View, {
key: "orderchoice-".concat(choiceId),
as: "div",
margin: "x-small 0"
}, _this4.renderChoice(choice, isFinalChoice, i));
});
}
}, {
key: "renderLabel",
value: function renderLabel(labelName, description) {
var _this5 = this;
var automation = 'sdk-ordering-edit-' + labelName;
return (0, _emotion.jsx)(_uiView.View, {
as: "div",
padding: "x-small 0"
}, (0, _emotion.jsx)(_quizCommon.TextInput, {
ref: labelName === 'topLabel' ? this.handleTopLabelRef : this.handleBottomLabelRef,
renderLabel: description,
isRequired: false,
interaction: this.props.overrideEditableForRegrading ? 'disabled' : 'enabled',
name: labelName,
defaultValue: this.props.properties[labelName] || '',
onChange: function onChange(event) {
_this5.props.changeItemState({
properties: _objectSpread(_objectSpread({}, _this5.props.properties), {}, (0, _defineProperty2["default"])({}, labelName, event.target.value))
});
},
width: "20rem",
messages: this.props.getErrors("properties.".concat(labelName)),
"data-automation": automation
}));
}
}, {
key: "renderOptionsDescription",
value: function renderOptionsDescription() {
return (0, _emotion.jsx)(_uiA11yContent.ScreenReaderContent, null, (0, _formatMessage["default"])('Ordering options'));
}
}, {
key: "render",
value: function render() {
var _this$props$propertie = this.props.properties,
includeLabels = _this$props$propertie.includeLabels,
displayAnswersParagraph = _this$props$propertie.displayAnswersParagraph;
var nonEditable = this.props.overrideEditableForRegrading;
// clean up the references
this.inputRefs = [];
this.removeChoiceRefs = [];
this.reorderRefs = [];
return (0, _emotion.jsx)("div", null, (0, _emotion.jsx)(_QuestionContainer["default"], {
disabled: nonEditable,
enableRichContentEditor: this.props.enableRichContentEditor,
itemBody: this.props.itemBody,
onDescriptionChange: this.props.onDescriptionChange,
onModalClose: this.props.onModalClose,
onModalOpen: this.props.onModalOpen,
openImportModal: this.props.openImportModal,
stemErrors: this.props.getErrors('itemBody'),
textareaRef: this.handleStemRef
}, includeLabels && this.renderLabel('topLabel', (0, _formatMessage["default"])('Top Label')), (0, _emotion.jsx)(_uiView.View, {
as: "div"
}, this.renderChoices()), !nonEditable && (0, _emotion.jsx)(_Footer["default"], {
onCreateChoice: this.handleCreateChoice,
notifyScreenreader: this.props.notifyScreenreader,
automationData: "sdk-ordering-add-answer"
}), includeLabels && this.renderLabel('bottomLabel', (0, _formatMessage["default"])('Bottom Label'))), (0, _emotion.jsx)(_QuestionSettingsContainer["default"], {
additionalOptions: this.props.additionalOptions
}, (0, _emotion.jsx)(_QuestionSettingsPanel["default"], {
label: (0, _formatMessage["default"])('Options'),
defaultExpanded: true
}, (0, _emotion.jsx)(_quizCommon.FormFieldGroup, {
rowSpacing: "small",
description: this.renderOptionsDescription()
}, this.props.showCalculatorOption && (0, _emotion.jsx)(_CalculatorOptionWithOqaatAlert["default"], {
disabled: nonEditable,
calculatorValue: this.props.calculatorType,
onCalculatorTypeChange: this.handleCalculatorTypeChange,
oqaatChecked: this.props.oneQuestionAtATime,
onOqaatChange: this.props.setOneQuestionAtATime
}), (0, _emotion.jsx)(_uiCheckbox.Checkbox, {
label: (0, _formatMessage["default"])('Display Answers in a Paragraph'),
onChange: this.handleDisplayAnswersParagraphChange,
checked: displayAnswersParagraph || false,
disabled: nonEditable,
"data-automation": "sdk-display-answer-in-paragraph-checkbox"
}), (0, _emotion.jsx)(_uiCheckbox.Checkbox, {
label: (0, _formatMessage["default"])('Include Labels'),
onChange: this.handleIncludeLabelsChange,
checked: includeLabels || false,
disabled: nonEditable,
"data-automation": "sdk-include-labels-checkbox"
})))));
}
}]);
}(_react.Component), (0, _defineProperty2["default"])(_OrderingEdit, "displayName", 'OrderingEdit'), (0, _defineProperty2["default"])(_OrderingEdit, "componentId", "Quizzes".concat(_OrderingEdit.displayName)), (0, _defineProperty2["default"])(_OrderingEdit, "interactionType", _ordering["default"]), (0, _defineProperty2["default"])(_OrderingEdit, "propTypes", _objectSpread(_objectSpread({
additionalOptions: _QuestionSettingsContainer["default"].propTypes.additionalOptions,
calculatorType: _propTypes["default"].string,
enableRichContentEditor: _propTypes["default"].bool,
interactionData: _propTypes["default"].shape({
choices: _propTypes["default"].objectOf(_propTypes["default"].shape({
id: _propTypes["default"].string.isRequired,
itemBody: _propTypes["default"].string.isRequired
})).isRequired
}).isRequired,
itemBody: _propTypes["default"].string,
notifyScreenreader: _propTypes["default"].func,
onModalClose: _propTypes["default"].func,
onModalOpen: _propTypes["default"].func,
oneQuestionAtATime: _propTypes["default"].bool,
openImportModal: _propTypes["default"].func,
overrideEditableForRegrading: _propTypes["default"].bool,
properties: _propTypes["default"].shape({
displayAnswersParagraph: _propTypes["default"].bool,
includeLabels: _propTypes["default"].bool,
topLabel: _propTypes["default"].string,
bottomLabel: _propTypes["default"].string
}).isRequired,
scoringData: _propTypes["default"].shape({
value: _propTypes["default"].arrayOf(_propTypes["default"].string).isRequired
}).isRequired,
setOneQuestionAtATime: _propTypes["default"].func
}, _withEditTools["default"].injectedProps), {}, {
styles: _propTypes["default"].object,
showCalculatorOption: _propTypes["default"].bool
})), (0, _defineProperty2["default"])(_OrderingEdit, "defaultProps", {
additionalOptions: void 0,
calculatorType: 'none',
enableRichContentEditor: true,
itemBody: void 0,
onModalClose: void 0,
onModalOpen: void 0,
oneQuestionAtATime: false,
openImportModal: void 0,
overrideEditableForRegrading: false,
notifyScreenreader: Function.prototype,
setOneQuestionAtATime: Function.prototype,
showCalculatorOption: true
}), _OrderingEdit)) || _class;