quiz-interactions
Version:
A React UI component Library for quiz interaction types.
393 lines (325 loc) • 13 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 _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 _assign = _interopRequireDefault(require("lodash/fp/assign"));
var _find = _interopRequireDefault(require("lodash/fp/find"));
var _map = _interopRequireDefault(require("lodash/fp/map"));
var _concat = _interopRequireDefault(require("lodash/fp/concat"));
var _sortBy = _interopRequireDefault(require("lodash/fp/sortBy"));
var _remove = _interopRequireDefault(require("lodash/fp/remove"));
var _uniqBy = _interopRequireDefault(require("lodash/fp/uniqBy"));
var _flow = _interopRequireDefault(require("lodash/fp/flow"));
var _uiThemeable = require("@instructure/ui-themeable");
var _uiForms = require("@instructure/ui-forms");
var _uiElements = require("@instructure/ui-elements");
var _uiIcons = require("@instructure/ui-icons");
var _quizRce = require("@instructure/quiz-rce");
var _isImage = _interopRequireDefault(require("../common/isImage"));
var _FocusGroup = _interopRequireDefault(require("../../common/components/FocusGroup"));
var _FilesList = _interopRequireDefault(require("../common/FilesList"));
var _theme = _interopRequireDefault(require("./theme"));
var _formatMessage = _interopRequireDefault(require("@instructure/quiz-i18n/lib/format-message"));
var _dec, _class, _class2, _temp;
var styles = {
componentId: 'lwHAW',
template: function template(theme) {
return "\n\n.lwHAW_DxCw{align-items:center;background-color:".concat(theme.fileDropContentBackgroundColor || 'inherit', ";display:flex;justify-content:center;padding:").concat(theme.fileDropContentPadding || 'inherit', " 0}\n\n.lwHAW_eeJQ{display:flex;font-size:").concat(theme.fileDropContentIconFontSize || 'inherit', "}\n\n.lwHAW_cmNy{display:flex;font-family:").concat(theme.fileDropContentFontFamily || 'inherit', ";padding-left:").concat(theme.fileDropContentLabelPadding || 'inherit', "}\n\n.lwHAW_eItU{color:").concat(theme.fileDropContentLabelBrowseColor || 'inherit', ";padding-left:").concat(theme.fileDropContentLabelBrowsePadding || 'inherit', ";text-decoration:underline}");
},
'fileDropContent': 'lwHAW_DxCw',
'fileDropContentIcon': 'lwHAW_eeJQ',
'fileDropContentLabel': 'lwHAW_cmNy',
'fileDropContentLabelBrowse': 'lwHAW_eItU'
};
var FILEDROP_SELECTOR = 'input[type=file]';
var REMOVE_BUTTON_SELECTOR = '[data-role=removeButton] button';
/**
---
category: FileUpload
---
File Upload Take component
```jsx_example
function Example (props) {
const exampleProps = {
itemBody: 'Give three examples of people',
interactionData: {
restrictCount: true,
filesCount: 3
},
properties: {
restrictTypes: false,
allowedTypes: ''
},
userResponse: {
value: [{
id: Date.now(),
name: 'slide-3.jpg',
url: 'http://instructure.github.io/images/slide-3.jpg',
size: 49067
}]
}
}
return (
<FileUploadTake {...exampleProps} {...props} />
)
}
<SettingsSwitcher locales={LOCALES}>
<TakeStateProvider>
<Example />
</TakeStateProvider>
</SettingsSwitcher>
```
**/
var _ref3 =
/*#__PURE__*/
_react.default.createElement(_uiIcons.IconUploadLine, null);
var FileUploadTake = (_dec = (0, _uiThemeable.themeable)(_theme.default, styles), _dec(_class = (_temp = _class2 =
/*#__PURE__*/
function (_Component) {
(0, _inherits2.default)(FileUploadTake, _Component);
function FileUploadTake() {
var _getPrototypeOf2;
var _this;
(0, _classCallCheck2.default)(this, FileUploadTake);
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)(FileUploadTake)).call.apply(_getPrototypeOf2, [this].concat(args)));
_this.state = {
files: [],
messages: []
};
_this.focusGroup = null;
_this.makeLocalLoadHandler = function (id) {
return function (e) {
var files = (0, _map.default)(function (file) {
if (file.id !== id) {
return file;
}
return (0, _assign.default)(file, {
url: e.target.result
});
}, _this.state.files);
_this.setState({
files: files
});
};
};
_this.handleDropRejected = function (files) {
_this.setState({
messages: [{
text: (0, _formatMessage.default)('Invalid file type'),
type: 'error'
}]
});
};
_this.handleDropAccepted = function (files) {
if (_this.props.readOnly) {
return;
}
var uploadFiles;
if (_this.props.interactionData.restrictCount) {
var responseLength = _this.parseResponse().length;
uploadFiles = files.slice(0, _this.parseFilesCount() - responseLength);
} else {
uploadFiles = files;
}
var parsedFiles = uploadFiles.map(function (file, index) {
var id = Date.now() + index;
var callback = _this.makeUploadCallback(id, file);
_this.props.mediaUpload(id, file, callback);
if ((0, _isImage.default)(file.name)) {
var reader = new FileReader();
reader.onload = _this.makeLocalLoadHandler(id);
reader.readAsDataURL(file);
}
return {
id: id,
url: null,
name: file.name,
size: file.size,
isUploading: true
};
});
_this.setState({
files: (0, _concat.default)(_this.state.files, parsedFiles),
messages: []
}, function () {
// After browsing & selecting 1 or more files, focus on the last one
_this.focusGroup.focusLast('div');
});
};
_this.makeRemoveHandler = function (_ref) {
var id = _ref.id;
return function () {
var stateFiles = _this.state.files;
var response = _this.parseResponse();
if (stateFiles.length + response.length === 1) {
// Focus on FileDrop
_this.focusGroup.focusNext(FILEDROP_SELECTOR);
} else {
// Focus on another remove button
if (_this.focusGroup.previousExists(REMOVE_BUTTON_SELECTOR)) {
_this.focusGroup.focusPrevious(REMOVE_BUTTON_SELECTOR);
} else {
_this.focusGroup.focusNext(REMOVE_BUTTON_SELECTOR);
}
}
if ((0, _find.default)({
id: id
}, stateFiles)) {
_this.props.cancelMediaUpload(id);
_this.setState({
files: (0, _remove.default)({
id: id
}, stateFiles)
});
} else {
_this.props.handleResponseUpdate((0, _remove.default)({
id: id
}, response));
}
};
};
_this.handleShifterRef = function (node) {
_this.focusGroup = node;
};
return _this;
}
(0, _createClass2.default)(FileUploadTake, [{
key: "parseFilesCount",
value: function parseFilesCount() {
return parseInt(this.props.interactionData.filesCount, 10);
}
}, {
key: "parseResponse",
value: function parseResponse() {
var userResponse = this.props.userResponse;
if (!userResponse || !userResponse.value || !userResponse.value.length) {
return [];
}
return userResponse.value.filter(function (f) {
return !!f;
});
}
}, {
key: "makeUploadCallback",
value: function makeUploadCallback(id, _ref2) {
var _this2 = this;
var name = _ref2.name,
size = _ref2.size;
return function (url) {
// First add the new file to userResponse and THEN delete from state
// So that we never lose track of a file (causes lost focus issues)
// This means that for one rerender we'll have a file both in state & userResponse
var response = _this2.parseResponse();
_this2.props.handleResponseUpdate((0, _concat.default)(response, {
id: id,
url: url,
name: name,
size: size
}));
_this2.setState({
files: (0, _remove.default)({
id: id
}, _this2.state.files)
});
};
}
}, {
key: "renderFileDropContent",
value: function renderFileDropContent() {
return _react.default.createElement("div", {
className: styles.fileDropContent
}, _react.default.createElement("div", {
className: styles.fileDropContentIcon
}, _ref3), _react.default.createElement("div", {
className: styles.fileDropContentLabel
}, _react.default.createElement(_uiElements.Text, {
color: "primary"
}, _formatMessage.default.rich("Drag n' Drop here or <0>Browse</0>", [function (_ref4) {
var children = _ref4.children;
return _react.default.createElement("span", {
key: "1",
className: styles.fileDropContentLabelBrowse
}, children);
}]))));
}
}, {
key: "render",
value: function render() {
var restrictCount = this.props.interactionData.restrictCount;
var _this$props = this.props,
itemBody = _this$props.itemBody,
properties = _this$props.properties,
readOnly = _this$props.readOnly;
var restrictTypes = properties.restrictTypes,
allowedTypes = properties.allowedTypes;
var accept = restrictTypes ? allowedTypes : ''; // The uniqBy is needed because in one rerender there's a file in both state & userResponse
var allResponses = (0, _flow.default)((0, _concat.default)(this.state.files), (0, _sortBy.default)('id'), (0, _uniqBy.default)('id'))(this.parseResponse());
var hideFileDrop = restrictCount && allResponses.length === this.parseFilesCount();
var allowedTypesDescription = restrictTypes && allowedTypes ? _react.default.createElement(_uiElements.Text, {
color: "secondary"
}, (0, _formatMessage.default)('{ types } files only!', {
types: allowedTypes
})) : null;
return _react.default.createElement(_FocusGroup.default, {
ref: this.handleShifterRef,
asComponent: _quizRce.ItemBodyWrapper,
asProps: {
itemBody: itemBody
}
}, _react.default.createElement(_FilesList.default, {
files: allResponses,
makeRemoveHandler: this.makeRemoveHandler,
readOnly: this.props.readOnly
}), hideFileDrop ? null : _react.default.createElement("div", null, _react.default.createElement(_uiForms.FileDrop, {
messages: this.state.messages,
accept: accept,
onDropAccepted: this.handleDropAccepted,
onDropRejected: this.handleDropRejected,
allowMultiple: true,
readOnly: readOnly,
label: this.renderFileDropContent()
}), allowedTypesDescription));
}
}]);
FileUploadTake.displayName = "FileUploadTake";
return FileUploadTake;
}(_react.Component), _class2.propTypes = {
itemBody: _propTypes.default.string.isRequired,
readOnly: _propTypes.default.bool,
handleResponseUpdate: _propTypes.default.func.isRequired,
mediaUpload: _propTypes.default.func.isRequired,
cancelMediaUpload: _propTypes.default.func,
interactionData: _propTypes.default.shape({
restrictCount: _propTypes.default.bool.isRequired,
filesCount: _propTypes.default.number.isRequired
}).isRequired,
properties: _propTypes.default.shape({
restrictTypes: _propTypes.default.bool.isRequired,
allowedTypes: _propTypes.default.oneOfType([_propTypes.default.string, _propTypes.default.arrayOf(_propTypes.default.string)])
}).isRequired,
userResponse: _propTypes.default.shape({
value: _propTypes.default.arrayOf(_propTypes.default.shape({
id: _propTypes.default.number.isRequired,
name: _propTypes.default.string.isRequired,
url: _propTypes.default.string.isRequired,
size: _propTypes.default.number.isRequired
}))
}).isRequired
}, _class2.defaultProps = {
cancelMediaUpload: function cancelMediaUpload() {},
readOnly: false
}, _temp)) || _class);
exports.default = FileUploadTake;