diff --git a/package-lock.json b/package-lock.json index cca8852..c2003ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,33 +18,26 @@ "integrity": "sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ==" }, "@babel/core": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", - "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", + "version": "7.13.13", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.13.tgz", + "integrity": "sha512-1xEs9jZAyKIouOoCmpsgk/I26PoKyvzQ2ixdRpRzfbcp1fL+ozw7TUgdDgwonbTovqRaTfRh50IXuw4QrWO0GA==", "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.1", - "@babel/parser": "^7.12.3", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.9", + "@babel/helper-compilation-targets": "^7.13.13", + "@babel/helper-module-transforms": "^7.13.12", + "@babel/helpers": "^7.13.10", + "@babel/parser": "^7.13.13", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.13", + "@babel/types": "^7.13.13", "convert-source-map": "^1.7.0", "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", + "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", + "semver": "^6.3.0", "source-map": "^0.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } } }, "@babel/generator": { @@ -1211,6 +1204,15 @@ "@hapi/hoek": "^8.3.0" } }, + "@hypnosphi/create-react-context": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@hypnosphi/create-react-context/-/create-react-context-0.3.1.tgz", + "integrity": "sha512-V1klUed202XahrWJLLOT3EXNeCpFHCcJntdFGI15ntCwau+jfT386w7OFTMaCqOgXUH1fa0w/I1oZs+i/Rfr0A==", + "requires": { + "gud": "^1.0.0", + "warning": "^4.0.3" + } + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3870,6 +3872,29 @@ "babel-plugin-transform-react-remove-prop-types": "0.4.24" }, "dependencies": { + "@babel/core": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", + "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.1", + "@babel/parser": "^7.12.3", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + } + }, "@babel/plugin-proposal-class-properties": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", @@ -4606,6 +4631,11 @@ } } }, + "classnames": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.6.tgz", + "integrity": "sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==" + }, "clean-css": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.3.tgz", @@ -7849,6 +7879,11 @@ "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", "optional": true }, + "gud": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gud/-/gud-1.0.0.tgz", + "integrity": "sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw==" + }, "gzip-size": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-5.1.1.tgz", @@ -14469,6 +14504,10 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.9.tgz", "integrity": "sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==" }, + "react-file-picker": { + "version": "git+https://github.com/dantheuber/react-file-picker.git#61c15b0c863987a80aa80c22a9b5caf6b975dc7e", + "from": "git+https://github.com/dantheuber/react-file-picker.git#master" + }, "react-ga": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/react-ga/-/react-ga-3.3.0.tgz", @@ -14479,6 +14518,37 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-notifications-component": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-notifications-component/-/react-notifications-component-3.1.0.tgz", + "integrity": "sha512-qq+zgqVIa2zhlw+RvO2QSPk7xHLvZWTHl9VKRO56sMUef/UrcUTqOswL0DSJtRIpZYZhclquQUfDxD6H2w8aWA==" + }, + "react-popper": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.11.tgz", + "integrity": "sha512-VSA/bS+pSndSF2fiasHK/PTEEAyOpX60+H5EPAjoArr8JGm+oihu4UbrqcEBpQibJxBVCpYyjAX7abJ+7DoYVg==", + "requires": { + "@babel/runtime": "^7.1.2", + "@hypnosphi/create-react-context": "^0.3.1", + "deep-equal": "^1.1.1", + "popper.js": "^1.14.4", + "prop-types": "^15.6.1", + "typed-styles": "^0.0.7", + "warning": "^4.0.2" + }, + "dependencies": { + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + } + } + }, "react-refresh": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz", @@ -14550,6 +14620,36 @@ "workbox-webpack-plugin": "5.1.4" }, "dependencies": { + "@babel/core": { + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", + "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.1", + "@babel/parser": "^7.12.3", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.19", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, "fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -14593,6 +14693,47 @@ "prop-types": "^15.6.2" } }, + "reactstrap": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/reactstrap/-/reactstrap-8.9.0.tgz", + "integrity": "sha512-pmf33YjpNZk1IfrjqpWCUMq9hk6GzSnMWBAofTBNIRJQB1zQ0Au2kzv3lPUAFsBYgWEuI9iYa/xKXHaboSiMkQ==", + "requires": { + "@babel/runtime": "^7.12.5", + "classnames": "^2.2.3", + "prop-types": "^15.5.8", + "react-popper": "^1.3.6", + "react-transition-group": "^2.3.1" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.10.tgz", + "integrity": "sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "dom-helpers": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz", + "integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==", + "requires": { + "@babel/runtime": "^7.1.2" + } + }, + "react-transition-group": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz", + "integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==", + "requires": { + "dom-helpers": "^3.4.0", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + } + } + } + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -16822,6 +16963,11 @@ "mime-types": "~2.1.24" } }, + "typed-styles": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", + "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -16835,6 +16981,11 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", + "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==" + }, "unbox-primitive": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", @@ -17173,6 +17324,14 @@ "makeerror": "1.0.x" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watchpack": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", diff --git a/package.json b/package.json index 8419c63..549d56f 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "version": "0.1.0", "dependencies": { + "@babel/core": "^7.13.13", "@material-ui/core": "^4.11.3", "@material-ui/icons": "^4.11.2", "@testing-library/jest-dom": "^4.2.4", @@ -13,8 +14,12 @@ "react-copy-to-clipboard": "^5.0.3", "react-dom": "^16.14.0", "react-dropzone": "^11.3.2", + "react-file-picker": "git+https://github.com/dantheuber/react-file-picker.git#master", "react-ga": "^3.3.0", - "react-scripts": "^4.0.3" + "react-notifications-component": "^3.1.0", + "react-scripts": "^4.0.3", + "reactstrap": "^8.9.0", + "typescript": "^4.2.3" }, "scripts": { "predeploy": "npm run build", diff --git a/src/ChecksumResolver.js b/src/ChecksumResolver.js index 96197b3..2493b2c 100644 --- a/src/ChecksumResolver.js +++ b/src/ChecksumResolver.js @@ -17,14 +17,21 @@ import FileCopyOutlined from '@material-ui/icons/FileCopyOutlined'; import PublishOutlined from '@material-ui/icons/PublishOutlined'; // import { useFilePicker } from 'react-sage' -//import { FilePicker } from 'react-file-picker' +// import { FilePicker } from 'react-file-picker' +import { FilePicker } from '../src/Components' import { CopyToClipboard } from 'react-copy-to-clipboard'; import ControlledAccordions from './ControlledAccordions' +import 'react-notifications-component/dist/theme.css' + +import ReactNotification from 'react-notifications-component' +import { store } from 'react-notifications-component'; +// import 'animate.css/animate.compat.css' + const useStyles = theme => ({ paper: { @@ -108,34 +115,51 @@ class ChecksumResolver extends React.Component { return !this.isChecksumValid(); }; - requestActivationBytes = () => { + addNotification = function (text, success = true) { + store.addNotification({ + message: text, + type: success ? "success" : "danger", + // type: "danger", + insert: "bottom-left", + container: "top-full", + animationIn: ["animate__animated", "animate__fadeIn"], + animationOut: ["animate__animated", "animate__fadeOut"], + dismiss: { + duration: 3000, + onScreen: false + } + }); + } + + requestActivationBytes = async () => { const { checksum } = this.state; - - fetch("https://aax.api.j-kit.me/api/v2/activation/" + checksum) - .then(res => res.json()) - .then( - (result) => { - const { success, activationBytes } = result; - if (success === true) { - this.setState({ activationBytes: result.activationBytes }); - } else { - this.setState({ activationBytes: 'UNKNOWN' }); - } - - - }, - (error) => { - this.setState({ activationBytes: 'UNKNOWN' }); - } - ) + try { + let request = await fetch("https://aax.api.j-kit.me/api/v2/activation/" + checksum); + let result = await request.json(); + const { success, activationBytes } = result; + if (success === true) { + this.setState({ activationBytes: activationBytes }); + this.addNotification("Successfully resolved the activation bytes"); + } else { + this.setState({ activationBytes: 'UNKNOWN' }); + this.addNotification("An error occured while resolving the activation bytes, please check your inputs", false); + } + } catch (error) { + this.setState({ activationBytes: error }); + this.addNotification("An error occured while resolving the activation bytes, please check your inputs", false); + } } buf2hex(buffer) { // buffer is an ArrayBuffer return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); } - acceptFile = async files => { + acceptFiles = async files => { const file = files[0]; + await this.acceptFile(file); + } + + acceptFile = async file => { // if (!file.name.toLowerCase().endsWith(".aax")) { // alert('FileType not supported!'); // return; @@ -177,7 +201,7 @@ class ChecksumResolver extends React.Component { noClick onDrop={acceptedFiles => { console.log(acceptedFiles); - this.acceptFile(acceptedFiles); + this.acceptFiles(acceptedFiles); }}> {({ getRootProps, getInputProps }) => (
@@ -196,35 +220,19 @@ class ChecksumResolver extends React.Component { autoFocus onChange={(x) => this.setChecksum(x.target.value)} value={checksum} - InputProps={{ - readOnly: true, -// endAdornment: ( -// // { - -// // alert('hi') -// // }}> -// // -// // - -// // -// // - -// // -// // -// //accept="image/*" -// // { }} -// // onError={errMsg => { }} -// // > -// // -// // -// // -// // -// ) + readOnly: false, + endAdornment: ( + + + + + + ) }} /> @@ -280,6 +288,7 @@ class ChecksumResolver extends React.Component { + ); } diff --git a/src/Components/FileInput/index.js b/src/Components/FileInput/index.js new file mode 100644 index 0000000..6e89907 --- /dev/null +++ b/src/Components/FileInput/index.js @@ -0,0 +1,45 @@ +// external imports +import React from 'react' +import PropTypes from 'prop-types' + +class FileInput extends React.Component { + constructor(props) { + super(props) + + this._handleUpload = this._handleUpload.bind(this) + } + + _handleUpload(evt) { + const file = evt.target.files[0] + this.props.onChange(file) + + // free up the fileInput again + this.fileInput.value = null + } + + render() { + return ( +
+ (this.fileInput = ele)} + /> + {React.cloneElement(this.props.children, { + onClick: () => this.fileInput.click() + })} +
+ ) + } +} + +FileInput.propTypes = { + style: PropTypes.object, + accept: PropTypes.string, + children: PropTypes.node.isRequired, + onChange: PropTypes.func.isRequired +} + +export default FileInput diff --git a/src/Components/FilePicker/index.js b/src/Components/FilePicker/index.js new file mode 100644 index 0000000..dfb18a7 --- /dev/null +++ b/src/Components/FilePicker/index.js @@ -0,0 +1,80 @@ +// external imports +import React from 'react' +import PropTypes from 'prop-types' +// local imports +import FileInput from '../FileInput' + +class FilePicker extends React.Component { + constructor(props) { + super(props) + + this._validate = this._validate.bind(this) + } + + _validate(file) { + const { onError, onChange, maxSize, extensions } = this.props + + // make sure a file was provided in the first place + if (!file) { + onError('Failed to upload a file.') + return + } + + // if we care about file extensions + if (extensions) { + const uploadedFileExt = file.name + .split('.') + .pop() + .toLowerCase() + const isValidFileExt = extensions + .map(ext => ext.toLowerCase()) + .includes(uploadedFileExt) + + if (!isValidFileExt) { + onError(`Must upload a file of type: ${extensions.join(' or ')}`) + return + } + } + + // convert maxSize from megabytes to bytes + const maxBytes = maxSize * 1000000 + + if (file.size > maxBytes) { + onError(`File size must be less than ${maxSize} MB.`) + return + } + + // return native file object + onChange(file) + } + + render() { + const { children, style } = this.props; + const accept = this.props.extensions.map(ext => `.${ext}`).join(',') + + return ( + + {children} + + ) + } +} + +FilePicker.propTypes = { + children: PropTypes.node.isRequired, + onChange: PropTypes.func.isRequired, + onError: PropTypes.func.isRequired, + // max file size in MB + maxSize: PropTypes.number, + // file extension + extensions: PropTypes.array, + // validate file contents + validateContent: PropTypes.func, + style: PropTypes.object +} + +FilePicker.defaultProps = { + maxSize: 2 +} + +export default FilePicker diff --git a/src/Components/FilePicker/test.js b/src/Components/FilePicker/test.js new file mode 100644 index 0000000..3bcb5eb --- /dev/null +++ b/src/Components/FilePicker/test.js @@ -0,0 +1,103 @@ +// external imports +import React from 'react' +import { mount } from 'enzyme' +// local imports +import FilePicker from '.' + +describe('File Picker', () => { + let onChange + let onError + + beforeEach(() => { + onChange = jest.fn() + onError = jest.fn() + }) + + test('returns a valid component with required props', () => { + const ele = ( + ({})} onError={() => ({})}> + + + ) + + expect(React.isValidElement(ele)).toBe(true) + }) + + test('call error handler when no file uploaded', () => { + // mount the select with a few options + const wrapper = mount( + +
Click here
+
+ ) + + // trigger the onChange callback on file input + wrapper.find('input').simulate('change', { target: { files: [] } }) + + expect(onError.mock.calls.length).toBe(1) + expect(onChange.mock.calls.length).toBe(0) + }) + + test('call error handler when a file with incorrect extension is uploaded', () => { + // mount the select with a few options + const wrapper = mount( + +
Click here
+
+ ) + + const file = new Blob(['file contents'], { type: 'text/plain' }) + file.name = 'file.txt' + + // trigger the onChange callback on file input + wrapper.find('input').simulate('change', { target: { files: [file] } }) + + expect(onError.mock.calls.length).toBe(1) + expect(onChange.mock.calls.length).toBe(0) + }) + + test('call error handler when a file that is too large is uploaded', () => { + // mount the select with a few options + const wrapper = mount( + +
Click here
+
+ ) + + const file = new Blob(['file contents'], { type: 'text/plain' }) + + // trigger the onChange callback on file input + wrapper.find('input').simulate('change', { target: { files: [file] } }) + + expect(onError.mock.calls.length).toBe(1) + expect(onChange.mock.calls.length).toBe(0) + }) + + test('call change handler when a file with correct size and extension is uploaded', () => { + // mount the select with a few options + const wrapper = mount( + +
Click here
+
+ ) + + const file = new Blob(['file contents'], { type: 'text/plain' }) + file.name = 'file.txt' + + // trigger the onChange callback on file input + wrapper.find('input').simulate('change', { target: { files: [file] } }) + + expect(onError.mock.calls.length).toBe(0) + expect(onChange.mock.calls.length).toBe(1) + }) +}) diff --git a/src/Components/index.js b/src/Components/index.js new file mode 100644 index 0000000..43fe994 --- /dev/null +++ b/src/Components/index.js @@ -0,0 +1 @@ +export { default as FilePicker } from './FilePicker' diff --git a/src/Components/utils/index.js b/src/Components/utils/index.js new file mode 100644 index 0000000..4f0b602 --- /dev/null +++ b/src/Components/utils/index.js @@ -0,0 +1,2 @@ +export { default as loadImage } from './load-image' +export { default as loadFile } from './load-file' diff --git a/src/Components/utils/load-file.js b/src/Components/utils/load-file.js new file mode 100644 index 0000000..bdf6f93 --- /dev/null +++ b/src/Components/utils/load-file.js @@ -0,0 +1,11 @@ +export default function loadFile(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader() + + reader.readAsDataURL(file) + + reader.onloadend = loadedFIle => resolve(loadedFIle.target.result) + + reader.onerror = () => reject(new Error('There was an error uploading the file')) + }) +} diff --git a/src/Components/utils/load-image.js b/src/Components/utils/load-image.js new file mode 100644 index 0000000..6a6586c --- /dev/null +++ b/src/Components/utils/load-image.js @@ -0,0 +1,35 @@ +export default function loadImg(dataUrl, dims) { + // destructure props from dims + const { minWidth, maxWidth, minHeight, maxHeight } = dims + + return new Promise((resolve, reject) => { + // create a new html image element + const img = new Image() + // set the image src attribute to our dataUrl + img.src = dataUrl + + // listen for onload event + img.onload = () => { + // validate the min and max image dimensions + if (img.width < minWidth || img.height < minHeight) { + reject( + new Error( + `The uploaded image is too small. Must be at least ${minWidth}px by ${minHeight}px.` + ) + ) + } + + if (img.width > maxWidth || img.height > maxHeight) { + reject( + new Error( + `The uploaded image is too large. Must be no more than ${maxWidth}px by ${maxHeight}px.` + ) + ) + } + + resolve(true) + } + + img.onerror = () => reject(new Error('There was an error uploading the image')) + }) +} diff --git a/src/index.js b/src/index.js index 54c5350..bdb5f4e 100644 --- a/src/index.js +++ b/src/index.js @@ -3,13 +3,21 @@ import ReactDOM from 'react-dom'; import './index.css'; import * as serviceWorker from './serviceWorker'; import ChecksumResolver from './ChecksumResolver'; +import ReactNotification from 'react-notifications-component' import ReactGA from 'react-ga'; ReactGA.initialize('UA-174657678-1'); ReactGA.pageview(window.location.pathname + window.location.search); ReactDOM.render( - , +
+
+ + +
+ + +
, document.getElementById('root') );