@instructure/quiz-interactions
Version:
A React UI component Library for quiz interaction types.
354 lines (353 loc) • 15.1 kB
JavaScript
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
import _createClass from "@babel/runtime/helpers/esm/createClass";
import _possibleConstructorReturn from "@babel/runtime/helpers/esm/possibleConstructorReturn";
import _getPrototypeOf from "@babel/runtime/helpers/esm/getPrototypeOf";
import _inherits from "@babel/runtime/helpers/esm/inherits";
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
var _dec, _class, _FormulaSection;
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 = _getPrototypeOf(derived);
return _possibleConstructorReturn(_this, isNativeReflectConstruct() ? Reflect.construct(derived, args || [], _getPrototypeOf(_this).constructor) : derived.apply(_this, args));
}
/** @jsx jsx */
import { Component } from 'react';
import PropTypes from 'prop-types';
import { Table } from '@instructure/ui-table';
import { Text } from '@instructure/ui-text';
import { Spinner } from '@instructure/ui-spinner';
import { Checkbox } from '@instructure/ui-checkbox';
import { PresentationContent, ScreenReaderContent } from '@instructure/ui-a11y-content';
import { Button } from '@instructure/ui-buttons';
import { jsx } from '@instructure/emotion';
import { View } from '@instructure/ui-view';
import { Grid } from '@instructure/ui-grid';
import { Decimal } from '@instructure/quiz-i18n';
import { isScientificNotation } from '@instructure/quiz-scientific-notation';
import { toErrors } from '../../../util/instUIMessages';
import * as util from './util';
import generateStyle from './styles';
import generateComponentTheme from './theme';
import t from '@instructure/quiz-i18n/es/format-message';
import { NumberInput } from '@instructure/quiz-number-input';
import { SimpleSelect, TextArea, withStyleOverrides } from '@instructure/quiz-common';
import { Alert } from '@instructure/ui-alerts';
var FormulaSection = (_dec = withStyleOverrides(generateStyle, generateComponentTheme), _dec(_class = (_FormulaSection = /*#__PURE__*/function (_Component) {
function FormulaSection() {
var _this2;
_classCallCheck(this, FormulaSection);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this2 = _callSuper(this, FormulaSection, [].concat(args));
_defineProperty(_this2, "state", {
marginOfErrorValue: null
});
_defineProperty(_this2, "handleMarginOfErrorChange", function (event, value, normalizedValue) {
_this2.setState({
marginOfErrorValue: value
});
_this2.props.handleMarginOfErrorChange(event, value, normalizedValue);
});
_defineProperty(_this2, "handleMarginOfErrorBlur", function () {
_this2.setState({
marginOfErrorValue: null
});
});
_defineProperty(_this2, "localizedScientificNotation", function (value) {
var _ref = (value === null || value === void 0 ? void 0 : value.toString().split('*')) || [],
_ref2 = _slicedToArray(_ref, 2),
mantissa = _ref2[0],
exponent = _ref2[1];
return "".concat(_this2.localizedValue(mantissa), "*").concat(exponent);
});
_defineProperty(_this2, "localizedValue", function (value) {
return value ? Decimal.toLocaleString(value.toString(), _this2.props.locale) : null;
});
_defineProperty(_this2, "renderRow", function (solution, idx) {
var inputValues = _this2.variableNames.map(function (variableName) {
var _solution$inputs$find;
return (_solution$inputs$find = solution.inputs.find(function (v) {
return v.name === variableName;
})) === null || _solution$inputs$find === void 0 ? void 0 : _solution$inputs$find.value;
});
var _this2$props$scoringD = _this2.props.scoringData.value.numeric,
margin = _this2$props$scoringD.margin,
marginType = _this2$props$scoringD.marginType;
var parsedMargin = Number.parseFloat(margin);
var marginString;
if (margin === '' || parsedMargin === 0 || Number.isNaN(parsedMargin)) {
marginString = null;
} else if (marginType === 'absolute') {
marginString = t(' +/- {margin}', {
margin: _this2.formatNumStr(parsedMargin)
});
} else {
marginString = t(' +/- {margin}%', {
margin: _this2.formatNumStr(parsedMargin)
});
}
return jsx(Table.Row, {
key: idx
}, inputValues.map(function (value, index) {
return (
// eslint-disable-next-line react/no-array-index-key
jsx(Table.Cell, {
key: index
}, _this2.formatNumStr(value))
);
}), jsx(Table.Cell, null, _this2.formatNumStr(solution.output), jsx("span", {
css: _this2.props.styles.marginPlusMinus
}, marginString)));
});
return _this2;
}
_inherits(FormulaSection, _Component);
return _createClass(FormulaSection, [{
key: "formatNumStr",
value: function formatNumStr(value) {
return isScientificNotation(value) ? this.localizedScientificNotation(value) : this.localizedValue(value);
}
}, {
key: "variableNames",
get: function get() {
return this.props.scoringData.value.variables.map(function (variable) {
return variable.name;
}).sort();
}
}, {
key: "renderGeneratedSolutionsTable",
value: function renderGeneratedSolutionsTable() {
if (this.props.status === util.STATUS_RUNNING) {
return jsx("div", null, jsx(Spinner, {
renderTitle: t('Running')
}));
}
var solutions = this.props.scoringData.value.generatedSolutions;
var errorMessage = null;
if (this.props.status === util.STATUS_FAILED && solutions.length === 0) {
return jsx("div", {
role: "alert"
}, jsx(Alert, {
hasShadow: false,
variant: "warning"
}, t('We were not able to find any solutions.')));
}
if (this.props.status === util.STATUS_FAILED) {
errorMessage = util.buildSolutionsGeneratedMessage(this.props.status, solutions.length);
} else if (solutions.length === 0) {
return null;
}
var idPrefix = 'generated-results-';
return jsx("div", null, errorMessage && jsx("div", {
role: "alert"
}, jsx(Alert, {
hasShadow: false,
variant: "warning"
}, errorMessage)), jsx("div", {
css: this.props.styles.tableWrapper
}, jsx(Table, {
caption: t('Generated Results'),
layout: "fixed"
}, jsx(Table.Head, null, jsx(Table.Row, null, this.variableNames.map(function (variableName, idx) {
return jsx(Table.ColHeader, {
id: idPrefix + idx,
key: idx
}, variableName);
}), jsx(Table.ColHeader, {
id: "generated-results-result"
}, t('Result')))), jsx(Table.Body, null, solutions.map(this.renderRow)))));
}
}, {
key: "renderGeneratedSolutionsSection",
value: function renderGeneratedSolutionsSection() {
if (this.props.status === util.STATUS_CANCELED) {
return null;
}
return jsx("div", {
css: this.props.styles.generatedSolutions
}, this.props.status === util.STATUS_FORMULA_SETUP_INVALID ? jsx(Alert, {
liveRegionPoliteness: "polite",
variant: "warning"
}, t('Error in formula setup. See above for details.')) : this.renderGeneratedSolutionsTable());
}
}, {
key: "formulaErrors",
value: function formulaErrors() {
if (this.props.status === util.STATUS_FORMULA_SETUP_INVALID) {
return toErrors(this.props.formulaErrors || []);
}
}
}, {
key: "renderNumberOfGeneratedSolutionsInput",
value: function renderNumberOfGeneratedSolutionsInput() {
var currentSolutionsNumber = String(Number.parseInt(this.props.scoringData.value.answerCount, 10) || 0);
return jsx(NumberInput, {
disabled: this.props.overrideEditableForRegrading,
value: currentSolutionsNumber,
onChange: this.props.handleAnswerCountChange,
renderLabel: t('Number of solutions'),
isRequired: true,
messages: toErrors(this.props.generatedSolutionsErrors),
min: "1",
max: "200",
showArrows: true,
"data-automation": "sdk-number-of-solutions-input",
"aria-valuetext": "".concat(currentSolutionsNumber, " ").concat(t('Solutions possible'))
});
}
}, {
key: "renderSolutionPrecisionInput",
value: function renderSolutionPrecisionInput() {
var currentDecimalPlaces = this.props.scoringData.value.answerPrecision || 0;
return jsx(NumberInput, {
decimalPrecision: 0,
disabled: this.props.overrideEditableForRegrading,
value: currentDecimalPlaces,
onChange: this.props.handleAnswerPrecisionChange,
renderLabel: t('Decimal places'),
min: "0",
max: "16",
showArrows: true,
"aria-valuetext": "".concat(currentDecimalPlaces, " ").concat(t('Decimal places'))
});
}
}, {
key: "renderScientificNotationCheckbox",
value: function renderScientificNotationCheckbox() {
return jsx(Checkbox, {
checked: this.props.scoringData.value.scientificNotation || false,
disabled: this.props.overrideEditableForRegrading,
label: t('Display as Scientific Notation'),
onChange: this.props.handleScientificNotationChange,
variant: "toggle"
});
}
}, {
key: "renderMarginOfErrorTypeSelect",
value: function renderMarginOfErrorTypeSelect() {
return jsx(SimpleSelect, {
onChange: this.props.handleMarginOfErrorTypeChange,
value: this.props.scoringData.value.numeric.marginType,
renderLabel: t('Margin type'),
"data-automation": "sdk-formula-margin-of-error-type"
}, jsx(SimpleSelect.Option, {
id: "formula-section-select-option-absolute",
value: "absolute"
}, t('Absolute')), jsx(SimpleSelect.Option, {
id: "formula-section-select-option-percent",
value: "percent"
}, t('Percent')));
}
}, {
key: "renderMarginOfErrorInput",
value: function renderMarginOfErrorInput() {
var value = this.state.marginOfErrorValue;
if (value === null) {
value = Number.parseFloat(this.props.scoringData.value.numeric.margin);
}
return jsx(NumberInput, {
value: value,
onChange: this.handleMarginOfErrorChange,
onBlur: this.handleMarginOfErrorBlur,
renderLabel: t('+/- margin of error'),
min: "0",
"data-automation": "sdk-formula-margin-of-error-value",
showArrows: true,
"aria-valuetext": "".concat(value, " ").concat(t('+/- margin of error'))
});
}
}, {
key: "renderGenerateSolutionsButton",
value: function renderGenerateSolutionsButton() {
return jsx(Button, {
type: "submit",
disabled: this.props.overrideEditableForRegrading,
color: "primary",
onClick: this.props.handleGenerateSolutions,
"data-automation": "sdk-generate-button"
}, jsx(PresentationContent, null, t('Generate')), jsx(ScreenReaderContent, null, t('Generate Solutions')));
}
}, {
key: "render",
value: function render() {
return jsx("div", null, jsx("div", {
css: this.props.styles.sectionHeading
}, jsx(Text, {
size: "large"
}, t('Formula Definition'))), jsx("div", {
css: this.props.styles.instructions
}, jsx(Text, {
color: "primary"
}, t('Next, write the formula or formulas used to compute' + ' the correct answer. Use the same variable names listed above. (e.g., "5 + x")'))), jsx(TextArea, {
disabled: this.props.overrideEditableForRegrading,
value: this.props.scoringData.value.formula,
onChange: this.props.handleFormulaChange,
messages: this.formulaErrors(),
label: jsx(ScreenReaderContent, null, t('Formula')),
autoGrow: this.context.disableTextAreaAutoGrow ? false : null,
"data-automation": "sdk-formula-definition-text-area"
}), jsx("div", null, jsx("div", {
css: this.props.styles.sectionHeading
}, jsx(Text, {
size: "large"
}, t('Generate Possible Solutions'))), jsx("div", {
css: this.props.styles.instructions
}, jsx(Text, {
color: "primary"
}, t('Finally, build as many variable-solution combinations as you need for your quiz.'))), jsx("div", {
css: this.props.styles.generateSolutionsInput
}, jsx(Grid, {
vAlign: "top",
startAt: "medium"
}, jsx(Grid.Row, null, jsx(Grid.Col, {
width: 4
}, this.renderNumberOfGeneratedSolutionsInput()), jsx(Grid.Col, {
width: 4
}, this.renderSolutionPrecisionInput()), jsx(Grid.Col, {
width: 4
}, jsx(View, {
as: "div",
margin: "medium 0 0 0",
padding: "xx-small 0 0 0"
}, this.renderScientificNotationCheckbox()))), !this.props.scoringData.value.scientificNotation && jsx(Grid.Row, null, jsx(Grid.Col, {
width: 4
}, this.renderMarginOfErrorTypeSelect()), jsx(Grid.Col, {
width: 4
}, this.renderMarginOfErrorInput())), jsx(Grid.Row, null, jsx(Grid.Col, {
width: 4
}, this.renderGenerateSolutionsButton())))), this.renderGeneratedSolutionsSection()));
}
}]);
}(Component), _defineProperty(_FormulaSection, "displayName", 'FormulaSection'), _defineProperty(_FormulaSection, "componentId", "Quizzes".concat(_FormulaSection.displayName)), _defineProperty(_FormulaSection, "propTypes", {
formulaErrors: PropTypes.arrayOf(PropTypes.string),
generatedSolutionsErrors: PropTypes.arrayOf(PropTypes.string),
handleAnswerCountChange: PropTypes.func.isRequired,
handleAnswerPrecisionChange: PropTypes.func.isRequired,
handleFormulaChange: PropTypes.func.isRequired,
handleGenerateSolutions: PropTypes.func.isRequired,
handleMarginOfErrorChange: PropTypes.func.isRequired,
handleMarginOfErrorTypeChange: PropTypes.func.isRequired,
handleScientificNotationChange: PropTypes.func.isRequired,
locale: PropTypes.string.isRequired,
overrideEditableForRegrading: PropTypes.bool.isRequired,
scoringData: PropTypes.object.isRequired,
status: PropTypes.string.isRequired,
styles: PropTypes.object
}), _defineProperty(_FormulaSection, "defaultProps", {
formulaErrors: void 0,
generatedSolutionsErrors: void 0
}), _defineProperty(_FormulaSection, "contextTypes", {
disableTextAreaAutoGrow: PropTypes.bool
}), _FormulaSection)) || _class);
export { FormulaSection as default };