UNPKG

@instructure/quiz-interactions

Version:

A React UI component Library for quiz interaction types.

395 lines (394 loc) • 17.2 kB
/** @jsx jsx */ function _assert_this_initialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _call_super(_this, derived, args) { derived = _get_prototype_of(derived); return _possible_constructor_return(_this, _is_native_reflect_construct() ? Reflect.construct(derived, args || [], _get_prototype_of(_this).constructor) : derived.apply(_this, args)); } function _class_call_check(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for(var i = 0; i < props.length; i++){ var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _create_class(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _define_property(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _get_prototype_of(o) { _get_prototype_of = Object.setPrototypeOf ? Object.getPrototypeOf : function getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _get_prototype_of(o); } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _set_prototype_of(subClass, superClass); } function _possible_constructor_return(self, call) { if (call && (_type_of(call) === "object" || typeof call === "function")) { return call; } return _assert_this_initialized(self); } function _set_prototype_of(o, p) { _set_prototype_of = Object.setPrototypeOf || function setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _set_prototype_of(o, p); } function _type_of(obj) { "@swc/helpers - typeof"; return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; } function _is_native_reflect_construct() { try { var result = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {})); } catch (_) {} return (_is_native_reflect_construct = function() { return !!result; })(); } function _ts_decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } import { Component } from 'react'; import PropTypes from 'prop-types'; import assign from 'lodash/fp/assign'; import find from 'lodash/fp/find'; import map from 'lodash/fp/map'; import concat from 'lodash/fp/concat'; import sortBy from 'lodash/fp/sortBy'; import remove from 'lodash/fp/remove'; import uniqBy from 'lodash/fp/uniqBy'; import flow from 'lodash/fp/flow'; import { jsx } from '@instructure/emotion'; import { Text } from '@instructure/ui-text'; import { IconUploadLine } from '@instructure/ui-icons'; import { Alert } from '@instructure/ui-alerts'; import { ItemBodyWrapper } from '@instructure/quiz-rce'; import isImage from '../common/isImage'; import FocusGroup from '../../common/components/FocusGroup'; import FilesList from '../common/FilesList'; import generateStyle from './styles'; import generateComponentTheme from './theme'; import t from '@instructure/quiz-i18n/format-message'; import { ScreenReaderContent } from '@instructure/ui-a11y-content'; import { withStyleOverrides, FileDrop } from '@instructure/quiz-common'; var FILEDROP_SELECTOR = 'input[type=file]'; var REMOVE_BUTTON_SELECTOR = '[data-role=removeButton] button'; var FileUploadTake = /*#__PURE__*/ function(Component) { "use strict"; _inherits(FileUploadTake, Component); function FileUploadTake() { _class_call_check(this, FileUploadTake); var _this; _this = _call_super(this, FileUploadTake, arguments), _define_property(_this, "state", { files: [], messages: [] }), _define_property(_this, "focusGroup", null), _define_property(_this, "renderDescription", function() { return "".concat(_this.fileCountA11yDescription(), " ").concat(_this.renderAllowedTypesDescription() || ''); }), _define_property(_this, "fileCountA11yDescription", function() { if (_this.props.interactionData.restrictCount) { var fileResponseLength = _this.allResponses().length; if (fileResponseLength === 0) return t('Maximum { fileCount } file(s) allowed', { fileCount: _this.fileCountNumber() }); var requiredFilesCount = _this.parseFilesCount(); return t('{ filesSubmitted } of a maximum of { filesRequired } file(s) submitted.', { filesRequired: requiredFilesCount, filesSubmitted: fileResponseLength }); } return t('{ fileCount } file(s) submitted.', { fileCount: _this.fileCountNumber() }); }), _define_property(_this, "fileCountNumber", function() { return _this.props.interactionData.restrictCount ? _this.parseFilesCount() - _this.allResponses().length : _this.allResponses().length; }), _define_property(_this, "makeLocalLoadHandler", function(id) { return function(e) { var files = map(function(file) { if (file.id !== id) { return file; } return assign(file, { url: e.target.result }); }, _this.state.files); _this.setState({ files: files }); }; }), _define_property(_this, "handleDropRejected", function(files) { _this.setState({ messages: [ { text: t('Invalid file type'), type: 'error' } ] }); }), _define_property(_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 (isImage(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: concat(_this.state.files, parsedFiles), messages: [] }, function() { // After browsing & selecting 1 or more files, focus on the last one _this.focusGroup.focusLast('div'); }); }), _define_property(_this, "makeRemoveHandler", function(param) { var id = param.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 (find({ id: id }, stateFiles)) { _this.props.cancelMediaUpload(id); _this.setState({ files: remove({ id: id }, stateFiles) }); } else { _this.props.handleResponseUpdate(remove({ id: id }, response)); } }; }), _define_property(_this, "handleShifterRef", function(node) { _this.focusGroup = node; }), _define_property(_this, "allResponses", function() { return flow(concat(_this.state.files), sortBy('id'), uniqBy('id'))(_this.parseResponse()); }); return _this; } _create_class(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, param) { var _this = this; var name = param.name, size = param.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 = _this.parseResponse(); _this.props.handleResponseUpdate(concat(response, { id: id, url: url, name: name, size: size })); _this.setState({ files: remove({ id: id }, _this.state.files) }); }; } }, { key: "renderFileDropContent", value: function renderFileDropContent() { var _this = this; return /*#__PURE__*/ jsx("div", { css: this.props.styles.fileDropContent }, /*#__PURE__*/ jsx("div", { css: this.props.styles.fileDropContentIcon }, /*#__PURE__*/ jsx(IconUploadLine, null)), /*#__PURE__*/ jsx("div", { css: this.props.styles.fileDropContentLabel }, /*#__PURE__*/ jsx(Text, { color: "primary" }, t.rich('Drag and Drop here or <0>Browse</0>', [ function(param) { var children = param.children; return /*#__PURE__*/ jsx("span", { key: "1", css: _this.props.styles.fileDropContentLabelBrowse }, children); } ])))); } }, { key: "renderAllowedTypesDescription", value: function renderAllowedTypesDescription() { var _this_props_properties = this.props.properties, restrictTypes = _this_props_properties.restrictTypes, allowedTypes = _this_props_properties.allowedTypes; return restrictTypes && allowedTypes ? t('{ types } only!', { types: allowedTypes }) : null; } }, { 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 hideFileDrop = restrictCount && this.allResponses().length === this.parseFilesCount(); return /*#__PURE__*/ jsx("div", null, /*#__PURE__*/ jsx("div", { css: this.props.styles.printOnly }, /*#__PURE__*/ jsx(Alert, { variant: "warning" }, t('This question type cannot be printed'))), /*#__PURE__*/ jsx(FocusGroup, { ref: this.handleShifterRef, asComponent: ItemBodyWrapper, asProps: { itemBody: itemBody } }, /*#__PURE__*/ jsx(FilesList, { files: this.allResponses(), makeRemoveHandler: this.makeRemoveHandler, readOnly: this.props.readOnly }), hideFileDrop ? null : /*#__PURE__*/ jsx("div", null, /*#__PURE__*/ jsx("div", null, /*#__PURE__*/ jsx(FileDrop, { messages: this.state.messages, accept: accept, onDropAccepted: this.handleDropAccepted, onDropRejected: this.handleDropRejected, shouldAllowMultiple: true, readOnly: readOnly, renderLabel: this.renderFileDropContent() })), restrictCount ? /*#__PURE__*/ jsx("div", null, /*#__PURE__*/ jsx(Alert, { variant: "info", margin: "x-small 0", hasShadow: false }, this.renderDescription())) : null)), /*#__PURE__*/ jsx(ScreenReaderContent, null, this.renderDescription())); } } ]); return FileUploadTake; }(Component); _define_property(FileUploadTake, "displayName", 'FileUploadTake'); _define_property(FileUploadTake, "componentId", "Quizzes".concat(FileUploadTake.displayName)); _define_property(FileUploadTake, "propTypes", { itemBody: PropTypes.string.isRequired, readOnly: PropTypes.bool, handleResponseUpdate: PropTypes.func.isRequired, mediaUpload: PropTypes.func.isRequired, cancelMediaUpload: PropTypes.func, interactionData: PropTypes.shape({ restrictCount: PropTypes.bool.isRequired, filesCount: PropTypes.string.isRequired }).isRequired, properties: PropTypes.shape({ restrictTypes: PropTypes.bool.isRequired, allowedTypes: PropTypes.oneOfType([ PropTypes.string, PropTypes.arrayOf(PropTypes.string) ]) }).isRequired, userResponse: PropTypes.shape({ value: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.number.isRequired, name: PropTypes.string.isRequired, url: PropTypes.string.isRequired, size: PropTypes.number.isRequired })) }).isRequired, styles: PropTypes.object }); _define_property(FileUploadTake, "defaultProps", { cancelMediaUpload: function() {}, readOnly: false }); export { FileUploadTake as default }; FileUploadTake = _ts_decorate([ withStyleOverrides(generateStyle, generateComponentTheme) ], FileUploadTake);