UNPKG

quiz-interactions

Version:

A React UI component Library for quiz interaction types.

528 lines (448 loc) 21.4 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 _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray")); 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 _getPrototypeOf3 = _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 _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 _uiThemeable = require("@instructure/ui-themeable"); var _uiA11y = require("@instructure/ui-a11y"); var _uiLayout = require("@instructure/ui-layout"); var _uiForms = require("@instructure/ui-forms"); var _uiFormField = require("@instructure/ui-form-field"); var _uiElements = require("@instructure/ui-elements"); 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 _theme = _interopRequireDefault(require("./theme")); 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/lib/format-message")); var _QuestionSettingsPanel = _interopRequireDefault(require("../../common/edit/components/QuestionSettingsPanel")); var _CalculatorOptionWithOqaatAlert = _interopRequireDefault(require("../../common/edit/components/CalculatorOptionWithOqaatAlert")); 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: 'fEqyF', template: function template(theme) { return "\n\n.fEqyF_cloC{border-bottom:1px solid ".concat(theme.stemBorderColor || 'inherit', ";padding-bottom:").concat(theme.stemPadding || 'inherit', "}\n\n.fEqyF_eiZV,.fEqyF_YIqF{padding:").concat(theme.topLabelPadding || 'inherit', " 0}\n\n.fEqyF_cBkl{align-items:center;background-color:").concat(theme.choiceContainerInputColor || 'inherit', ";display:flex;flex:1;margin-right:").concat(theme.choiceContainerInputMargin || 'inherit', ";padding:").concat(theme.choiceContainerInputPadding || 'inherit', "}\n\n.fEqyF_fNII{text-align:center;width:").concat(theme.choiceContainerPositionWidth || 'inherit', "}\n\n[dir=ltr] .fEqyF_fNII,[dir=rtl] .fEqyF_fNII{text-align:center}\n\n.fEqyF_TnZk{flex:1}"); }, 'stem': 'fEqyF_cloC', 'bottomLabel': 'fEqyF_eiZV', 'topLabel': 'fEqyF_YIqF', 'choiceContainerInput': 'fEqyF_cBkl', 'choiceContainerPosition': 'fEqyF_fNII', 'choiceContainerAnswerWrapper': 'fEqyF_TnZk' }; var OrderingEdit = ( /** --- 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> ``` **/ _dec = (0, _uiThemeable.themeable)(_theme.default, styles), (0, _withEditTools.default)(_class = _dec(_class = (_temp = _class2 = /*#__PURE__*/ function (_Component) { (0, _inherits2.default)(OrderingEdit, _Component); function OrderingEdit() { var _getPrototypeOf2; var _this; (0, _classCallCheck2.default)(this, OrderingEdit); for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } _this = (0, _possibleConstructorReturn2.default)(this, (_getPrototypeOf2 = (0, _getPrototypeOf3.default)(OrderingEdit)).call.apply(_getPrototypeOf2, [this].concat(args))); _this.stemElement = null; _this.topLabelRef = null; _this.bottomLabelRef = null; _this.inputRefs = []; _this.reorderRefs = []; _this.removeChoiceRefs = []; _this._timeouts = []; _this._choiceWasCreated = false; _this.handleStemRef = function (node) { _this.stemElement = node; }; _this.handleTopLabelRef = function (node) { _this.topLabelRef = node; }; _this.handleBottomLabelRef = function (node) { _this.bottomLabelRef = node; }; _this.handleCalculatorTypeChange = function (e, value) { _this.props.changeItemState({ calculatorType: value }); }; _this.handleMoveChoice = function (currI, targetI) { // Create a new array to preserve immutability var choices = _this.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]; _this.props.changeItemState({ scoringData: _objectSpread({}, _this.props.scoringData, { value: choices }) }); _this._timeouts = [].concat((0, _toConsumableArray2.default)(_this._timeouts), [setTimeout(function () { return _this.reorderRefs[targetI].focus(); })]); }; _this.handleRemoveChoice = function (choiceId, index) { var choiceOrder = _this.props.scoringData.value; if (index === 0) { if (_this.props.properties.includeLabels) { _this.topLabelRef.focus(); } else { // added timeout to compensate for RCE sluggishness _this._timeouts = [].concat((0, _toConsumableArray2.default)(_this._timeouts), [setTimeout(function () { return _this.stemElement.focus(); })]); } } else { if (choiceOrder.length > 3) { _this.removeChoiceRefs[index - 1].focus(); } else { _this.reorderRefs[index - 1].focus(); } } _this.props.changeItemState({ interactionData: _objectSpread({}, _this.props.interactionData, { choices: (0, _omit.default)(_this.props.interactionData.choices, choiceId) }), scoringData: _objectSpread({}, _this.props.scoringData, { value: (0, _without.default)(choiceOrder, choiceId) }) }); }; _this.handleCreateChoice = function () { var id = (0, _uuid.v4)(); var newChoice = { id: id, itemBody: '' }; _this._choiceWasCreated = true; _this.props.changeItemState({ interactionData: _objectSpread({}, _this.props.interactionData, { choices: (0, _assignIn2.default)(_this.props.interactionData.choices, (0, _defineProperty2.default)({}, id, newChoice)) }), scoringData: _objectSpread({}, _this.props.scoringData, { value: [].concat((0, _toConsumableArray2.default)(_this.props.scoringData.value), [id]) }) }); _this.props.notifyScreenreader((0, _formatMessage.default)('Navigate up to find new choice input')); }; _this.handleIncludeLabelsChange = function (e) { var properties = _this.props.properties; if (!properties.includeLabels) { _this.props.notifyScreenreader((0, _formatMessage.default)('Navigate up to find label fields')); } _this.props.changeItemState({ properties: _objectSpread({}, _this.props.properties, { includeLabels: !properties['includeLabels'] }) }); }; _this.handleDisplayAnswersParagraphChange = function (e) { var properties = _this.props.properties; _this.props.changeItemState({ properties: _objectSpread({}, _this.props.properties, { displayAnswersParagraph: !properties['displayAnswersParagraph'] }) }); }; _this.handleInputChange = function (choiceId, event, _ref2) { var editorContent = _ref2.editorContent; _this.props.changeItemState({ interactionData: (0, _set.default)("choices[".concat(choiceId, "].itemBody"), editorContent, _this.props.interactionData) }); }; return _this; } (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(); } } // =========== // HANDLERS // =========== }, { key: "renderChoice", // =========== // RENDER // =========== value: function renderChoice(choice, isFinalChoice, index) { var _this2 = this; var choicesLength = this.props.scoringData.value.length; var nonEditable = this.props.overrideEditableForRegrading; return _react.default.createElement(_Card.default, { id: choice.id, index: index, key: "".concat(choice.id, "_").concat(index) // eslint-disable-line react/jsx-no-literals , moveCard: this.handleMoveChoice, margin: "0 0 0.5rem 0" }, _react.default.createElement(_uiLayout.Grid, { vAlign: "middle", colSpacing: "none" }, _react.default.createElement(_uiLayout.Grid.Row, null, _react.default.createElement(_uiLayout.Grid.Col, null, _react.default.createElement("div", { className: styles.choiceContainerInput }, _react.default.createElement("div", { className: styles.choiceContainerPosition }, _react.default.createElement(_uiA11y.ScreenReaderContent, null, (0, _formatMessage.default)('position {position, number}', { position: index + 1 })), _react.default.createElement(_uiA11y.PresentationContent, null, _react.default.createElement(_uiElements.Text, { color: "primary" }, _formatMessage.default.number(index + 1)))), _react.default.createElement("div", { className: styles.choiceContainerAnswerWrapper }, _react.default.createElement(_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) { _this2.inputRefs[index] = node; }, renderImportModal: this.props.renderImportModal })))), _react.default.createElement(_uiLayout.Grid.Col, { width: "auto" }, _react.default.createElement(_ReorderChoiceButton.default, { ref: function ref(node) { _this2.reorderRefs[index] = node; }, id: choice.id, isFinalChoice: isFinalChoice, isFirstChoice: index === 0, onMoveChoiceUp: function onMoveChoiceUp() { return _this2.handleMoveChoice(index, index - 1); }, onMoveChoiceDown: function onMoveChoiceDown() { return _this2.handleMoveChoice(index, index + 1); }, screenReaderText: (0, _formatMessage.default)('Reorder Choice: {choice}', { choice: (0, _striptags.default)(choice.itemBody) }) })), _react.default.createElement(_uiLayout.Grid.Col, { width: "auto" }, choicesLength > 2 && !nonEditable && _react.default.createElement(_RemoveChoiceButton.default, { padding: "0", choiceId: choice.id, ref: function ref(node) { _this2.removeChoiceRefs[index] = node; }, onRemoveChoice: function onRemoveChoice() { return _this2.handleRemoveChoice(choice.id, index); }, screenReaderText: (0, _formatMessage.default)('Remove Choice: {choice}', { choice: (0, _striptags.default)(choice.itemBody) }) }))))); } }, { key: "renderLabel", value: function renderLabel(labelName, description) { var _this3 = this; var automation = 'sdk-ordering-edit-' + labelName; return _react.default.createElement("div", { className: styles[labelName] }, _react.default.createElement(_uiForms.TextInput, { ref: labelName === 'topLabel' ? this.handleTopLabelRef : this.handleBottomLabelRef, label: _react.default.createElement(_uiA11y.ScreenReaderContent, null, description), placeholder: description, disabled: this.props.overrideEditableForRegrading, name: labelName, value: this.props.properties[labelName] || '', onChange: function onChange(event) { _this3.props.changeItemState({ properties: _objectSpread({}, _this3.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 _react.default.createElement(_uiA11y.ScreenReaderContent, null, (0, _formatMessage.default)('Ordering options')); } }, { key: "render", value: function render() { var _this4 = this; var _this$props$propertie = this.props.properties, includeLabels = _this$props$propertie.includeLabels, displayAnswersParagraph = _this$props$propertie.displayAnswersParagraph; var choiceOrder = this.props.scoringData.value; var choicesLength = choiceOrder.length; var nonEditable = this.props.overrideEditableForRegrading; // clean up the references this.inputRefs = []; this.removeChoiceRefs = []; this.reorderRefs = []; return _react.default.createElement("div", null, _react.default.createElement(_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, renderImportModal: this.props.renderImportModal, stemErrors: this.props.getErrors('itemBody'), textareaRef: this.handleStemRef }, includeLabels && this.renderLabel('topLabel', (0, _formatMessage.default)('Top Label')), _react.default.createElement("div", null, choiceOrder.map(function (choiceId, i) { var isFinalChoice = i + 1 === choicesLength; var choice = _this4.props.interactionData.choices[choiceId]; return _this4.renderChoice(choice, isFinalChoice, i); })), !nonEditable && _react.default.createElement(_Footer.default, { onCreateChoice: this.handleCreateChoice }), includeLabels && this.renderLabel('bottomLabel', (0, _formatMessage.default)('Bottom Label'))), _react.default.createElement(_QuestionSettingsContainer.default, { additionalOptions: this.props.additionalOptions }, _react.default.createElement(_QuestionSettingsPanel.default, { label: (0, _formatMessage.default)('Options'), defaultExpanded: true }, _react.default.createElement(_uiFormField.FormFieldGroup, { rowSpacing: "small", description: this.renderOptionsDescription() }, !this.props.hiddenOptions.calculator && _react.default.createElement(_CalculatorOptionWithOqaatAlert.default, { disabled: nonEditable, calculatorValue: this.props.calculatorType, onCalculatorTypeChange: this.handleCalculatorTypeChange, oqaatChecked: this.props.oneQuestionAtATime, onOqaatChange: this.props.setOneQuestionAtATime }), _react.default.createElement(_uiForms.Checkbox, { label: (0, _formatMessage.default)('Display Answers in a Paragraph'), onChange: this.handleDisplayAnswersParagraphChange, checked: displayAnswersParagraph || false, disabled: nonEditable }), _react.default.createElement(_uiForms.Checkbox, { label: (0, _formatMessage.default)('Include Labels'), onChange: this.handleIncludeLabelsChange, checked: includeLabels || false, disabled: nonEditable }))))); } }]); OrderingEdit.displayName = "OrderingEdit"; return OrderingEdit; }(_react.Component), _class2.interactionType = _ordering.default, _class2.propTypes = _objectSpread({ additionalOptions: _QuestionSettingsContainer.default.propTypes.additionalOptions, calculatorType: _propTypes.default.string, enableRichContentEditor: _propTypes.default.bool, hiddenOptions: _propTypes.default.shape({ calculator: _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, renderImportModal: _propTypes.default.func, scoringData: _propTypes.default.shape({ value: _propTypes.default.arrayOf(_propTypes.default.string).isRequired }).isRequired, setOneQuestionAtATime: _propTypes.default.func }, _withEditTools.default.injectedProps), _class2.defaultProps = { additionalOptions: void 0, calculatorType: 'none', enableRichContentEditor: true, hiddenOptions: { calculator: true }, itemBody: void 0, onModalClose: void 0, onModalOpen: void 0, oneQuestionAtATime: false, openImportModal: void 0, overrideEditableForRegrading: false, renderImportModal: void 0, notifyScreenreader: Function.prototype, setOneQuestionAtATime: Function.prototype }, _temp)) || _class) || _class); exports.default = OrderingEdit;