diff --git a/js/src/modals/CreateAccount/NewImport/newImport.js b/js/src/modals/CreateAccount/NewImport/newImport.js index 7f4a6bd1a..121f0be57 100644 --- a/js/src/modals/CreateAccount/NewImport/newImport.js +++ b/js/src/modals/CreateAccount/NewImport/newImport.js @@ -14,19 +14,14 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import { FloatingActionButton } from 'material-ui'; import { observer } from 'mobx-react'; import React, { Component, PropTypes } from 'react'; -import ReactDOM from 'react-dom'; import { FormattedMessage } from 'react-intl'; -import { Form, Input } from '~/ui'; -import { AttachFileIcon } from '~/ui/Icons'; +import { Form, FileSelect, Input } from '~/ui'; import styles from '../createAccount.css'; -const STYLE_HIDDEN = { display: 'none' }; - @observer export default class NewImport extends Component { static propTypes = { @@ -34,7 +29,7 @@ export default class NewImport extends Component { } render () { - const { name, nameError, password, passwordHint, walletFile, walletFileError } = this.props.store; + const { name, nameError, password, passwordHint } = this.props.store; return (
@@ -93,58 +88,48 @@ export default class NewImport extends Component { /> -
- - } - label={ - - } - value={ walletFile } - /> -
- - - - -
-
+ { this.renderFileSelector() }
); } - onFileChange = (event) => { - const { store } = this.props; + renderFileSelector () { + const { walletFile, walletFileError } = this.props.store; - if (event.target.files.length) { - const reader = new FileReader(); - - reader.onload = (event) => store.setWalletJson(event.target.result); - reader.readAsText(event.target.files[0]); - } - - store.setWalletFile(event.target.value); + return walletFile + ? ( + + } + label={ + + } + value={ walletFile } + /> + ) + : ( + + ); } - openFileDialog = () => { - ReactDOM.findDOMNode(this.refs.fileUpload).click(); + onFileSelect = (fileName, fileContent) => { + const { store } = this.props; + + store.setWalletFile(fileName); + store.setWalletJson(fileContent); } onEditName = (event, name) => { diff --git a/js/src/modals/CreateAccount/createAccount.css b/js/src/modals/CreateAccount/createAccount.css index eb9ff1e5c..46e327e90 100644 --- a/js/src/modals/CreateAccount/createAccount.css +++ b/js/src/modals/CreateAccount/createAccount.css @@ -101,17 +101,6 @@ width: 10% !important; } -.upload { - text-align: right; - float: right; - margin-left: -100%; - margin-top: 28px; -} - -.upload>div { - margin-right: 0.5em; -} - .checkbox { margin-top: 2em; } @@ -131,6 +120,11 @@ } } +.fileImport { + height: 9em; + margin-top: 1em; +} + .summary { line-height: 1.618em; padding: 0 4em 1.5em 4em; diff --git a/js/src/modals/CreateAccount/store.js b/js/src/modals/CreateAccount/store.js index 1875ebe76..7371e8df3 100644 --- a/js/src/modals/CreateAccount/store.js +++ b/js/src/modals/CreateAccount/store.js @@ -93,8 +93,11 @@ export default class Store { this.phrase = ''; this.name = ''; this.nameError = null; + this.rawKey = ''; this.rawKeyError = null; + this.walletFile = ''; this.walletFileError = null; + this.walletJson = ''; }); } diff --git a/js/src/modals/CreateAccount/store.spec.js b/js/src/modals/CreateAccount/store.spec.js index 9c74365c9..67303fa21 100644 --- a/js/src/modals/CreateAccount/store.spec.js +++ b/js/src/modals/CreateAccount/store.spec.js @@ -64,13 +64,24 @@ describe('modals/CreateAccount/Store', () => { describe('@action', () => { describe('clearErrors', () => { + beforeEach(() => { + store.setName(''); + store.setPassword('123'); + store.setRawKey('test'); + store.setWalletFile('test'); + store.setWalletJson('test'); + }); + it('clears all errors', () => { store.clearErrors(); expect(store.nameError).to.be.null; expect(store.passwordRepeatError).to.be.null; + expect(store.rawKey).to.equal(''); expect(store.rawKeyError).to.be.null; + expect(store.walletFile).to.equal(''); expect(store.walletFileError).to.be.null; + expect(store.walletJson).to.equal(''); }); }); diff --git a/js/src/ui/Actionbar/Import/import.css b/js/src/ui/Actionbar/Import/import.css index da71e5f15..53a3e049a 100644 --- a/js/src/ui/Actionbar/Import/import.css +++ b/js/src/ui/Actionbar/Import/import.css @@ -15,30 +15,6 @@ /* along with Parity. If not, see . */ -.importZone { - width: 100%; - height: 200px; - border-width: 2px; - border-color: #666; - border-style: dashed; - border-radius: 10px; - - background-color: rgba(50, 50, 50, 0.2); - - display: flex; - align-items: center; - justify-content: center; - font-size: 1.2em; - - transition: all 0.5s ease; - - &:hover { - cursor: pointer; - border-radius: 0; - background-color: rgba(50, 50, 50, 0.5); - } -} - .desc { margin-top: 0; color: #ccc; diff --git a/js/src/ui/Actionbar/Import/import.js b/js/src/ui/Actionbar/Import/import.js index 91bdefe80..ada951cd9 100644 --- a/js/src/ui/Actionbar/Import/import.js +++ b/js/src/ui/Actionbar/Import/import.js @@ -15,12 +15,12 @@ // along with Parity. If not, see . import React, { Component, PropTypes } from 'react'; -import Dropzone from 'react-dropzone'; import { FormattedMessage } from 'react-intl'; import { nodeOrStringProptype } from '~/util/proptypes'; import Button from '../../Button'; +import FileSelect from '../../Form/FileSelect'; import { CancelIcon, DoneIcon, FileUploadIcon } from '../../Icons'; import Portal from '../../Portal'; @@ -184,25 +184,8 @@ export default class ActionbarImport extends Component { return this.renderValidation(); } - return this.renderFileSelect(); - } - - renderFileSelect () { return ( -
- -
- -
-
-
+ ); } @@ -224,39 +207,30 @@ export default class ActionbarImport extends Component { ); } - onDrop = (files) => { + onFileSelect = (file, content) => { const { renderValidation } = this.props; - const file = files[0]; - const reader = new FileReader(); + if (typeof renderValidation !== 'function') { + this.props.onConfirm(content); + return this.onCloseModal(); + } - reader.onload = (e) => { - const content = e.target.result; + const validationBody = renderValidation(content); - if (typeof renderValidation !== 'function') { - this.props.onConfirm(content); - return this.onCloseModal(); - } - - const validationBody = renderValidation(content); - - if (validationBody && validationBody.error) { - return this.setState({ - step: 1, - error: true, - errorText: validationBody.error - }); - } - - this.setState({ + if (validationBody && validationBody.error) { + return this.setState({ step: 1, - validate: true, - validationBody, - content + error: true, + errorText: validationBody.error }); - }; + } - reader.readAsText(file); + this.setState({ + step: 1, + validate: true, + validationBody, + content + }); } onConfirm = () => { diff --git a/js/src/ui/Form/FileSelect/fileSelect.css b/js/src/ui/Form/FileSelect/fileSelect.css new file mode 100644 index 000000000..1ac5981c4 --- /dev/null +++ b/js/src/ui/Form/FileSelect/fileSelect.css @@ -0,0 +1,52 @@ +/* Copyright 2015-2017 Parity Technologies (UK) Ltd. +/* This file is part of Parity. +/* +/* Parity is free software: you can redistribute it and/or modify +/* it under the terms of the GNU General Public License as published by +/* the Free Software Foundation, either version 3 of the License, or +/* (at your option) any later version. +/* +/* Parity is distributed in the hope that it will be useful, +/* but WITHOUT ANY WARRANTY; without even the implied warranty of +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/* GNU General Public License for more details. +/* +/* You should have received a copy of the GNU General Public License +/* along with Parity. If not, see . +*/ + +$backgroundNormal: rgba(0, 0, 0, 0.2); +$backgroundNormalHover: rgba(0, 0, 0, 0.5); +$backgroundError: rgba(255, 0, 0, 0.1); +$backgroundErrorHover: rgba(255, 0, 0, 0.2); + +.dropzone { + align-items: center; + background: $backgroundNormal; + border: 2px dashed #666; + border-radius: 0.5em; + display: flex; + justify-content: center; + font-size: 1.2em; + height: 12em; + transition: all 0.5s ease; + width: 100%; + + &:hover { + background: $backgroundNormalHover; + border-radius: 0; + cursor: pointer; + } + + &.error { + background: $backgroundError; + + &:hover { + background: $backgroundErrorHover; + } + } + + .label { + color: #aaa; + } +} diff --git a/js/src/ui/Form/FileSelect/fileSelect.js b/js/src/ui/Form/FileSelect/fileSelect.js new file mode 100644 index 000000000..18b2c8d8e --- /dev/null +++ b/js/src/ui/Form/FileSelect/fileSelect.js @@ -0,0 +1,76 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import React, { Component, PropTypes } from 'react'; +import Dropzone from 'react-dropzone'; +import { FormattedMessage } from 'react-intl'; + +import { nodeOrStringProptype } from '~/util/proptypes'; + +import styles from './fileSelect.css'; + +export default class FileSelect extends Component { + static propTypes = { + className: PropTypes.string, + error: nodeOrStringProptype(), + label: nodeOrStringProptype(), + onSelect: PropTypes.func.isRequired + } + + static defaultProps = { + label: ( + + ) + } + + render () { + const { className, error, label } = this.props; + + return ( + +
+ { error || label } +
+
+ ); + } + + onDrop = (files) => { + const { onSelect } = this.props; + const reader = new FileReader(); + const file = files[0]; + + reader.onload = (event) => { + onSelect(file.name, event.target.result); + }; + + reader.readAsText(file); + } +} diff --git a/js/src/ui/Form/FileSelect/fileSelect.spec.js b/js/src/ui/Form/FileSelect/fileSelect.spec.js new file mode 100644 index 000000000..f931fc655 --- /dev/null +++ b/js/src/ui/Form/FileSelect/fileSelect.spec.js @@ -0,0 +1,118 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +import { shallow } from 'enzyme'; +import React from 'react'; +import sinon from 'sinon'; + +import FileSelect from './'; + +const FILE = { + content: 'some test content', + name: 'someName' +}; + +let component; +let globalFileReader; +let instance; +let onSelect; +let processedFile; + +function stubReader () { + globalFileReader = global.FileReader; + + global.FileReader = class { + readAsText (file) { + processedFile = file; + + this.onload({ + target: { + result: file.content + } + }); + } + }; +} + +function restoreReader () { + global.FileReader = globalFileReader; +} + +function render (props = {}) { + onSelect = sinon.stub(); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('ui/Form/FileSelect', () => { + beforeEach(() => { + stubReader(); + render(); + }); + + afterEach(() => { + restoreReader(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + describe('DropZone', () => { + let label; + let zone; + + beforeEach(() => { + label = component.find('FormattedMessage'); + zone = component.find('Dropzone'); + }); + + it('renders the label', () => { + expect(label.props().id).to.equal('ui.fileSelect.defaultLabel'); + }); + + it('attaches the onDrop event', () => { + expect(zone.props().onDrop).to.equal(instance.onDrop); + }); + + it('does not allow multiples', () => { + expect(zone.props().multiple).to.be.false; + }); + }); + + describe('event methods', () => { + describe('onDrop', () => { + beforeEach(() => { + instance.onDrop([ FILE ]); + }); + + it('reads the file as dropped', () => { + expect(processedFile).to.deep.equal(FILE); + }); + + it('calls prop onSelect with file & content', () => { + expect(onSelect).to.have.been.calledWith(FILE.name, FILE.content); + }); + }); + }); +}); diff --git a/js/src/ui/Form/FileSelect/index.js b/js/src/ui/Form/FileSelect/index.js new file mode 100644 index 000000000..ff9614ace --- /dev/null +++ b/js/src/ui/Form/FileSelect/index.js @@ -0,0 +1,17 @@ +// Copyright 2015-2017 Parity Technologies (UK) Ltd. +// This file is part of Parity. + +// Parity is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Parity is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Parity. If not, see . + +export default from './fileSelect'; diff --git a/js/src/ui/Form/index.js b/js/src/ui/Form/index.js index 6a003c472..bb5516c89 100644 --- a/js/src/ui/Form/index.js +++ b/js/src/ui/Form/index.js @@ -16,6 +16,7 @@ export AddressSelect from './AddressSelect'; export DappUrlInput from './DappUrlInput'; +export FileSelect from './FileSelect'; export FormWrap from './FormWrap'; export Input from './Input'; export InputAddress from './InputAddress'; diff --git a/js/src/ui/index.js b/js/src/ui/index.js index 992bb2b05..ae1ce8451 100644 --- a/js/src/ui/index.js +++ b/js/src/ui/index.js @@ -30,7 +30,7 @@ export DappCard from './DappCard'; export DappIcon from './DappIcon'; export Errors from './Errors'; export Features, { FEATURES, FeaturesStore } from './Features'; -export Form, { AddressSelect, DappUrlInput, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form'; +export Form, { AddressSelect, DappUrlInput, FileSelect, FormWrap, Input, InputAddress, InputAddressSelect, InputChip, InputDate, InputInline, InputTime, Label, RadioButtons, Select, TypedInput } from './Form'; export GasPriceEditor from './GasPriceEditor'; export GasPriceSelector from './GasPriceSelector'; export Icons from './Icons';