diff --git a/js/package.json b/js/package.json index 9097cd18a..efa3eac9d 100644 --- a/js/package.json +++ b/js/package.json @@ -183,6 +183,7 @@ "react-element-to-jsx-string": "6.0.0", "react-event-listener": "0.4.1", "react-intl": "2.1.5", + "react-markdown": "2.4.4", "react-portal": "3.0.0", "react-redux": "4.4.6", "react-router": "3.0.0", diff --git a/js/src/api/format/output.js b/js/src/api/format/output.js index dc8421516..92a363566 100644 --- a/js/src/api/format/output.js +++ b/js/src/api/format/output.js @@ -181,6 +181,16 @@ export function outReceipt (receipt) { return receipt; } +export function outRecentDapps (recentDapps) { + if (recentDapps) { + Object.keys(recentDapps).forEach((url) => { + recentDapps[url] = outDate(recentDapps[url]); + }); + } + + return recentDapps; +} + export function outSignerRequest (request) { if (request) { Object.keys(request).forEach((key) => { diff --git a/js/src/api/format/output.spec.js b/js/src/api/format/output.spec.js index 504cc0687..151353453 100644 --- a/js/src/api/format/output.spec.js +++ b/js/src/api/format/output.spec.js @@ -16,7 +16,7 @@ import BigNumber from 'bignumber.js'; -import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeer, outPeers, outReceipt, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; +import { outBlock, outAccountInfo, outAddress, outChainStatus, outDate, outHistogram, outNumber, outPeer, outPeers, outReceipt, outRecentDapps, outSyncing, outTransaction, outTrace, outVaultMeta } from './output'; import { isAddress, isBigNumber, isInstanceOf } from '../../../test/types'; describe('api/format/output', () => { @@ -337,6 +337,14 @@ describe('api/format/output', () => { }); }); + describe('outRecentDapps', () => { + it('formats the URLs with timestamps', () => { + expect(outRecentDapps({ testing: 0x57513668 })).to.deep.equal({ + testing: new Date('2016-06-03T07:48:56.000Z') + }); + }); + }); + describe('outSyncing', () => { ['currentBlock', 'highestBlock', 'startingBlock', 'warpChunksAmount', 'warpChunksProcessed'].forEach((input) => { it(`formats ${input} numbers as a number`, () => { diff --git a/js/src/api/rpc/parity/parity.js b/js/src/api/rpc/parity/parity.js index 7001a9c49..d7278d13a 100644 --- a/js/src/api/rpc/parity/parity.js +++ b/js/src/api/rpc/parity/parity.js @@ -15,7 +15,7 @@ // along with Parity. If not, see . import { inAddress, inAddresses, inData, inHex, inNumber16, inOptions, inBlockNumber } from '../../format/input'; -import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outTransaction, outVaultMeta } from '../../format/output'; +import { outAccountInfo, outAddress, outAddresses, outChainStatus, outHistogram, outNumber, outPeers, outRecentDapps, outTransaction, outVaultMeta } from '../../format/output'; export default class Parity { constructor (transport) { @@ -222,7 +222,8 @@ export default class Parity { listRecentDapps () { return this._transport - .execute('parity_listRecentDapps'); + .execute('parity_listRecentDapps') + .then(outRecentDapps); } listStorageKeys (address, count, hash = null, blockNumber = 'latest') { diff --git a/js/src/i18n/en/settings.js b/js/src/i18n/en/settings.js index c45131da2..48a656b93 100644 --- a/js/src/i18n/en/settings.js +++ b/js/src/i18n/en/settings.js @@ -48,6 +48,10 @@ export default { label: 'Contracts' }, + home: { + label: 'Home' + }, + status: { label: 'Status' }, diff --git a/js/src/modals/DeployContract/ParametersStep/parametersStep.js b/js/src/modals/DeployContract/ParametersStep/parametersStep.js index 8a2847b2f..0213bfa01 100644 --- a/js/src/modals/DeployContract/ParametersStep/parametersStep.js +++ b/js/src/modals/DeployContract/ParametersStep/parametersStep.js @@ -91,7 +91,7 @@ export default class ParametersStep extends Component { }); return ( -
+

{ - accountsHistory.add(params.address); + accountsHistory.add(params.address, 'account'); } }, - { path: '/wallet/:address', component: Wallet } + { + path: '/wallet/:address', + component: Wallet, + onEnter: ({ params }) => { + accountsHistory.add(params.address, 'wallet'); + } + } ]; const addressesRoutes = [ @@ -86,8 +92,8 @@ const routes = [ { path: '/address/:address', onEnter: handleDeprecatedRoute }, { path: '/contract/:address', onEnter: handleDeprecatedRoute }, - { path: '/', onEnter: redirectTo('/accounts') }, - { path: '/auth', onEnter: redirectTo('/accounts') }, + { path: '/', onEnter: redirectTo('/home') }, + { path: '/auth', onEnter: redirectTo('/home') }, { path: '/settings', onEnter: redirectTo('/settings/views') } ]; @@ -127,6 +133,7 @@ const childRoutes = [ } }, { path: 'apps', component: Dapps }, + { path: 'home', component: Home }, { path: 'web', component: Web }, { path: 'web/:url', component: Web }, { path: 'signer', component: Signer } diff --git a/js/src/ui/Container/container.css b/js/src/ui/Container/container.css index d329744b6..9f40897e1 100644 --- a/js/src/ui/Container/container.css +++ b/js/src/ui/Container/container.css @@ -14,11 +14,37 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ + +$background: rgba(18, 18, 18, 0.85); +$backgroundOverlay: rgba(18, 18, 18, 1); + .container { + background: $background; flex: 1; - padding: 0em; - background: rgba(0, 0, 0, 0.8); height: 100%; + padding: 0em; + transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1); + width: 100%; + + .hoverOverlay { + background: $backgroundOverlay; + display: none; + left: 0; + margin-top: -1.5em; + padding: 0 1.5em 1.5em 1.5em; + position: absolute; + right: 0; + top: 100%; + z-index: 100; + } + + &:hover { + background: $backgroundOverlay; + + .hoverOverlay { + display: block; + } + } } .compact, diff --git a/js/src/ui/Container/container.js b/js/src/ui/Container/container.js index 695695871..e58d93567 100644 --- a/js/src/ui/Container/container.js +++ b/js/src/ui/Container/container.js @@ -28,6 +28,7 @@ export default class Container extends Component { children: PropTypes.node, className: PropTypes.string, compact: PropTypes.bool, + hover: PropTypes.node, light: PropTypes.bool, onClick: PropTypes.func, style: PropTypes.object, @@ -68,10 +69,25 @@ export default class Container extends Component { { this.renderTitle() } { children } + { this.renderHover() }

); } + renderHover () { + const { hover } = this.props; + + if (!hover) { + return null; + } + + return ( + + { hover } + + ); + } + renderTitle () { const { title } = this.props; diff --git a/js/src/ui/Container/container.spec.js b/js/src/ui/Container/container.spec.js index 3cefb66c8..78b625e39 100644 --- a/js/src/ui/Container/container.spec.js +++ b/js/src/ui/Container/container.spec.js @@ -37,10 +37,17 @@ describe('ui/Container', () => { }); describe('sections', () => { - it('renders the Card', () => { + it('renders the default Card', () => { expect(render().find('Card')).to.have.length(1); }); + it('renders Hover Card when available', () => { + const cards = render({ hover:
testingHover
}).find('Card'); + + expect(cards).to.have.length(2); + expect(cards.get(1).props.children.props.children).to.equal('testingHover'); + }); + it('renders the Title', () => { const title = render({ title: 'title' }).find('Title'); diff --git a/js/src/ui/Icons/index.js b/js/src/ui/Icons/index.js index 59cb31de6..62cb02105 100644 --- a/js/src/ui/Icons/index.js +++ b/js/src/ui/Icons/index.js @@ -14,66 +14,33 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -import AddIcon from 'material-ui/svg-icons/content/add'; -import AttachFileIcon from 'material-ui/svg-icons/editor/attach-file'; -import CancelIcon from 'material-ui/svg-icons/content/clear'; -import CheckIcon from 'material-ui/svg-icons/navigation/check'; -import CloseIcon from 'material-ui/svg-icons/navigation/close'; -import CompareIcon from 'material-ui/svg-icons/action/compare-arrows'; -import ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac'; -import ContractIcon from 'material-ui/svg-icons/action/code'; -import CopyIcon from 'material-ui/svg-icons/content/content-copy'; -import DashboardIcon from 'material-ui/svg-icons/action/dashboard'; -import DeleteIcon from 'material-ui/svg-icons/action/delete'; -import DoneIcon from 'material-ui/svg-icons/action/done-all'; -import EditIcon from 'material-ui/svg-icons/content/create'; -import FingerprintIcon from 'material-ui/svg-icons/action/fingerprint'; -import LinkIcon from 'material-ui/svg-icons/content/link'; -import LockedIcon from 'material-ui/svg-icons/action/lock'; -import MoveIcon from 'material-ui/svg-icons/action/open-with'; -import NextIcon from 'material-ui/svg-icons/navigation/arrow-forward'; -import PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; -import PrintIcon from 'material-ui/svg-icons/action/print'; -import RefreshIcon from 'material-ui/svg-icons/action/autorenew'; -import SaveIcon from 'material-ui/svg-icons/content/save'; -import SendIcon from 'material-ui/svg-icons/content/send'; -import SnoozeIcon from 'material-ui/svg-icons/av/snooze'; -import StarCircleIcon from 'material-ui/svg-icons/action/stars'; -import StarIcon from 'material-ui/svg-icons/toggle/star'; -import StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border'; -import VerifyIcon from 'material-ui/svg-icons/action/verified-user'; -import VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye'; -import VpnIcon from 'material-ui/svg-icons/notification/vpn-lock'; - -export { - AddIcon, - AttachFileIcon, - CancelIcon, - CheckIcon, - CloseIcon, - CompareIcon, - ComputerIcon, - ContractIcon, - CopyIcon, - DashboardIcon, - DeleteIcon, - DoneIcon, - EditIcon, - FingerprintIcon, - LinkIcon, - LockedIcon, - MoveIcon, - NextIcon, - PrevIcon, - PrintIcon, - RefreshIcon, - SaveIcon, - SendIcon, - SnoozeIcon, - StarIcon, - StarCircleIcon, - StarOutlineIcon, - VerifyIcon, - VisibleIcon, - VpnIcon -}; +export AddIcon from 'material-ui/svg-icons/content/add'; +export AttachFileIcon from 'material-ui/svg-icons/editor/attach-file'; +export CancelIcon from 'material-ui/svg-icons/content/clear'; +export CheckIcon from 'material-ui/svg-icons/navigation/check'; +export CloseIcon from 'material-ui/svg-icons/navigation/close'; +export CompareIcon from 'material-ui/svg-icons/action/compare-arrows'; +export ComputerIcon from 'material-ui/svg-icons/hardware/desktop-mac'; +export ContractIcon from 'material-ui/svg-icons/action/code'; +export CopyIcon from 'material-ui/svg-icons/content/content-copy'; +export DashboardIcon from 'material-ui/svg-icons/action/dashboard'; +export DeleteIcon from 'material-ui/svg-icons/action/delete'; +export DoneIcon from 'material-ui/svg-icons/action/done-all'; +export EditIcon from 'material-ui/svg-icons/content/create'; +export FingerprintIcon from 'material-ui/svg-icons/action/fingerprint'; +export LinkIcon from 'material-ui/svg-icons/content/link'; +export LockedIcon from 'material-ui/svg-icons/action/lock'; +export MoveIcon from 'material-ui/svg-icons/action/open-with'; +export NextIcon from 'material-ui/svg-icons/navigation/arrow-forward'; +export PrevIcon from 'material-ui/svg-icons/navigation/arrow-back'; +export PrintIcon from 'material-ui/svg-icons/action/print'; +export RefreshIcon from 'material-ui/svg-icons/action/autorenew'; +export SaveIcon from 'material-ui/svg-icons/content/save'; +export SendIcon from 'material-ui/svg-icons/content/send'; +export SnoozeIcon from 'material-ui/svg-icons/av/snooze'; +export StarCircleIcon from 'material-ui/svg-icons/action/stars'; +export StarIcon from 'material-ui/svg-icons/toggle/star'; +export StarOutlineIcon from 'material-ui/svg-icons/toggle/star-border'; +export VerifyIcon from 'material-ui/svg-icons/action/verified-user'; +export VisibleIcon from 'material-ui/svg-icons/image/remove-red-eye'; +export VpnIcon from 'material-ui/svg-icons/notification/vpn-lock'; diff --git a/js/src/ui/SectionList/sectionList.css b/js/src/ui/SectionList/sectionList.css index 02340fd55..b338f981d 100644 --- a/js/src/ui/SectionList/sectionList.css +++ b/js/src/ui/SectionList/sectionList.css @@ -16,8 +16,8 @@ */ .section { - overflow-x: hidden; position: relative; + width: 100%; .overlay { background: rgba(0, 0, 0, 0.85); @@ -33,7 +33,6 @@ .row { display: flex; justify-content: center; - overflow-x: hidden; /* TODO: As per JS comments, the flex-base could be adjusted in the future to allow for */ /* case where <> 3 columns are required should the need arrise from a UI pov. */ @@ -42,38 +41,28 @@ cursor: pointer; display: flex; flex: 0 1 33.33%; - opacity: 0.75; - overflow-x: hidden; + max-width: 33.33%; + opacity: 0.85; padding: 0.25em; transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1); - /* TODO: The hover and no-hover states can be improved to not "just appear" */ - &:not(:hover) { - & [data-hover="hide"] { - } - - & [data-hover="show"] { - display: none; - } - } - &:hover { opacity: 1; z-index: 100; - - & [data-hover="hide"] { - display: none; - } - - & [data-hover="show"] { - } } + } - &.stretch-on:hover { - flex: 0 0 50%; - } + &:hover { + .item { + &.stretchOn { + flex: 0 1 29%; + max-width: 29%; - &.stretch-off:hover { + &:hover { + flex: 0 0 42%; + max-width: 42%; + } + } } } } diff --git a/js/src/ui/SectionList/sectionList.js b/js/src/ui/SectionList/sectionList.js index 5e9106549..b326b9ed8 100644 --- a/js/src/ui/SectionList/sectionList.js +++ b/js/src/ui/SectionList/sectionList.js @@ -74,29 +74,34 @@ export default class SectionList extends Component { className={ styles.row } key={ `row_${index}` } > - { row.map(this.renderItem) } + { + row + .map(this.renderItem) + .filter((item) => item) + }
); } renderItem = (item, index) => { const { noStretch, renderItem } = this.props; + const itemRendered = renderItem(item, index); + + if (!itemRendered) { + return null; + } - // NOTE: Any children that is to be showed or hidden (depending on hover state) - // should have the data-hover="show|hide" attributes. For the current implementation - // this does the trick, however there may be a case for adding a hover attribute - // to an item (mouseEnter/mouseLeave events) and then adjusting the styling with - // :root[hover]/:root:not[hover] for the tragetted elements. Currently it is a - // CSS-only solution to let the browser do all the work via selectors. return (
- { renderItem(item, index) } + { itemRendered }
); } diff --git a/js/src/ui/SectionList/sectionList.spec.js b/js/src/ui/SectionList/sectionList.spec.js index 80d66a04a..9022db79d 100644 --- a/js/src/ui/SectionList/sectionList.spec.js +++ b/js/src/ui/SectionList/sectionList.spec.js @@ -27,7 +27,7 @@ let instance; let renderItem; function render (props = {}) { - renderItem = sinon.stub(); + renderItem = sinon.stub().returns('someThing'); component = shallow( . */ + .list { display: flex; flex-wrap: wrap; -} -.item { - flex: 0 1 50%; - width: 50%; - position: relative; - padding-bottom: 0.25em; - box-sizing: border-box; -} + .item { + box-sizing: border-box; + flex: 0 1 50%; + padding-bottom: 0.25em; + position: relative; + width: 50%; -.item:nth-child(odd) { - padding-right: 0.125em; -} + &:nth-child(odd) { + padding-right: 0.125em; + } -.item:nth-child(even) { - padding-left: 0.125em; + &:nth-child(even) { + padding-left: 0.125em; + } + } } .empty { - width: 100%; display: block; -} + width: 100%; -.empty div { - color: #aaa; + div { + color: #aaa; + } } diff --git a/js/src/views/Accounts/Summary/summary.js b/js/src/views/Accounts/Summary/summary.js index 6829b8914..3717c1895 100644 --- a/js/src/views/Accounts/Summary/summary.js +++ b/js/src/views/Accounts/Summary/summary.js @@ -193,7 +193,11 @@ export default class Summary extends Component { const viewLink = `/${baseLink}/${address}`; const content = ( - + ); if (noLink) { diff --git a/js/src/views/Application/Extension/extension.js b/js/src/views/Application/Extension/extension.js index aff332f9a..0c033225b 100644 --- a/js/src/views/Application/Extension/extension.js +++ b/js/src/views/Application/Extension/extension.js @@ -26,7 +26,7 @@ import styles from './extension.css'; @observer export default class Extension extends Component { - store = new Store(); + store = Store.get(); render () { const { showWarning } = this.store; diff --git a/js/src/views/Application/Extension/store.js b/js/src/views/Application/Extension/store.js index 965598f03..d1df2eb5b 100644 --- a/js/src/views/Application/Extension/store.js +++ b/js/src/views/Application/Extension/store.js @@ -29,7 +29,10 @@ const NEXT_DISPLAY = '_parity::extensionWarning::nextDisplay'; // 'https://chrome.google.com/webstore/detail/parity-ethereum-integrati/himekenlppkgeaoeddcliojfddemadig'; const EXTENSION_PAGE = 'https://chrome.google.com/webstore/detail/himekenlppkgeaoeddcliojfddemadig'; +let instance; + export default class Store { + @observable hasExtension = false; @observable isInstalling = false; @observable nextDisplay = 0; @observable shouldInstall = false; @@ -43,6 +46,10 @@ export default class Store { return !this.isInstalling && this.shouldInstall && (Date.now() > this.nextDisplay); } + @action setExtensionActive = () => { + this.hasExtension = true; + } + @action setInstalling = (isInstalling) => { this.isInstalling = isInstalling; } @@ -61,6 +68,7 @@ export default class Store { const ua = browser.analyze(navigator.userAgent || ''); if (hasExtension) { + this.setExtensionActive(); return false; } @@ -97,4 +105,12 @@ export default class Store { } }); } + + static get () { + if (!instance) { + instance = new Store(); + } + + return instance; + } } diff --git a/js/src/views/Application/TabBar/tabBar.css b/js/src/views/Application/TabBar/tabBar.css index e9a190034..4ac00b848 100644 --- a/js/src/views/Application/TabBar/tabBar.css +++ b/js/src/views/Application/TabBar/tabBar.css @@ -14,6 +14,7 @@ /* You should have received a copy of the GNU General Public License /* along with Parity. If not, see . */ + .toolbar { background: none !important; height: 72px !important; @@ -53,7 +54,7 @@ .tabLink, .settings, -.logo, +.first, .last { background: rgba(0, 0, 0, 0.5) !important; /* rgba(0, 0, 0, 0.25) !important; */ } @@ -73,27 +74,17 @@ right: -12px; } -.logo { - margin: 0 0 0 -24px; - padding: 20px 24px; - white-space: nowrap; -} - -.logo img { - height: 28px; - margin-right: 0.75em; -} - -.logo div { - display: inline-block; - text-transform: uppercase; - line-height: 32px; - vertical-align: top; - letter-spacing: 0.2em; -} - +.first, .last { - margin: 0 -24px 0 0; + margin: 0; padding: 36px 12px; white-space: nowrap; } + +.first { + margin-left: -24px; +} + +.last { + margin-right: -24px; +} diff --git a/js/src/views/Application/TabBar/tabBar.js b/js/src/views/Application/TabBar/tabBar.js index b2347f854..939e1b298 100644 --- a/js/src/views/Application/TabBar/tabBar.js +++ b/js/src/views/Application/TabBar/tabBar.js @@ -20,13 +20,16 @@ import { Link } from 'react-router'; import { Toolbar, ToolbarGroup } from 'material-ui/Toolbar'; import { isEqual } from 'lodash'; -import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg'; import { Tooltip } from '~/ui'; import Tab from './Tab'; import styles from './tabBar.css'; class TabBar extends Component { + static contextTypes = { + router: PropTypes.object.isRequired + }; + static propTypes = { isTest: PropTypes.bool, netChain: PropTypes.string, @@ -41,72 +44,48 @@ class TabBar extends Component { render () { return ( - { this.renderLogo() } - { this.renderTabs() } - { this.renderLast() } + +
+ +
+ { this.renderTabItems() } +
+ +
+ ); } - renderLogo () { - return ( - -
- -
-
- ); - } - - renderLast () { - return ( - -
-
-
- - ); - } - - renderTabs () { + renderTabItems () { const { views, pending } = this.props; - const items = views - .map((view, index) => { - const body = (view.id === 'accounts') - ? ( - - ) - : null; + return views.map((view, index) => { + const body = (view.id === 'accounts') + ? ( + + ) + : null; - return ( - + - - { body } - - - ); - }); - - return ( -
- { items } -
- ); + { body } +
+ + ); + }); } } diff --git a/js/src/views/Dapps/UrlButton/urlButton.js b/js/src/views/Dapps/UrlButton/urlButton.js deleted file mode 100644 index 5f46225a9..000000000 --- a/js/src/views/Dapps/UrlButton/urlButton.js +++ /dev/null @@ -1,99 +0,0 @@ -// 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 { FormattedMessage } from 'react-intl'; -import { withRouter } from 'react-router'; - -import Button from '~/ui/Button'; -import { LinkIcon } from '~/ui/Icons'; -import Input from '~/ui/Form/Input'; - -import styles from './urlButton.css'; - -const INPUT_STYLE = { display: 'inline-block', width: '20em' }; - -class UrlButton extends Component { - static propTypes = { - router: PropTypes.object.isRequired // injected by withRouter - }; - - state = { - inputShown: false - }; - - render () { - const { inputShown } = this.state; - - return ( -
- { inputShown ? this.renderInput() : null } -
- ); - } - - renderInput () { - return ( - - } - onBlur={ this.hideInput } - onFocus={ this.showInput } - onSubmit={ this.inputOnSubmit } - style={ INPUT_STYLE } - /> - ); - } - - toggleInput = () => { - const { inputShown } = this.state; - - this.setState({ - inputShown: !inputShown - }); - } - - hideInput = () => { - this.setState({ inputShown: false }); - } - - showInput = () => { - this.setState({ inputShown: true }); - } - - inputOnSubmit = (url) => { - const { router } = this.props; - - router.push(`/web/${encodeURIComponent(url)}`); - } -} - -export default withRouter(UrlButton); diff --git a/js/src/views/Dapps/dapps.css b/js/src/views/Dapps/dapps.css index d16317d60..6e3bdc14f 100644 --- a/js/src/views/Dapps/dapps.css +++ b/js/src/views/Dapps/dapps.css @@ -19,18 +19,20 @@ flex-wrap: wrap; margin: -0.125em; position: relative; + + .item { + box-sizing: border-box; + flex: 0 1 50%; + opacity: 0.85; + padding: 0.125em; + } + } .list+.list { margin-top: -0.25em; } -.item { - padding: 0.125em; - flex: 0 1 50%; - box-sizing: border-box; -} - .overlay { background: rgba(0, 0, 0, 0.85); bottom: 0.5em; diff --git a/js/src/views/Dapps/dapps.js b/js/src/views/Dapps/dapps.js index fc196fad8..cfdb2e716 100644 --- a/js/src/views/Dapps/dapps.js +++ b/js/src/views/Dapps/dapps.js @@ -26,7 +26,6 @@ import PermissionStore from '~/modals/DappPermissions/store'; import { Actionbar, Button, DappCard, Page } from '~/ui'; import { LockedIcon, VisibleIcon } from '~/ui/Icons'; -import UrlButton from './UrlButton'; import DappsStore from './dappsStore'; import styles from './dapps.css'; @@ -92,7 +91,6 @@ class Dapps extends Component { /> } buttons={ [ - ,
+ } + > + + + + + + ); + } +} + +function mapStateToProps (state) { + const { accountsInfo } = state.personal; + + return { + accountsInfo + }; +} + +export default connect( + mapStateToProps, + null +)(Accounts); diff --git a/js/src/views/Home/Accounts/accounts.spec.js b/js/src/views/Home/Accounts/accounts.spec.js new file mode 100644 index 000000000..368ebcedb --- /dev/null +++ b/js/src/views/Home/Accounts/accounts.spec.js @@ -0,0 +1,71 @@ +// 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 Accounts from './'; + +let component; +let store; + +function createRedux () { + store = { + dispatch: sinon.stub(), + subscribe: sinon.stub(), + getState: () => { + return { + personal: { + accountsInfo: { '0x123': {} } + } + }; + } + }; + + return store; +} + +function render (history = []) { + component = shallow( + , + { + context: { + store: createRedux() + } + } + ).find('Accounts').shallow(); + + return component; +} + +describe('views/Home/Accounts', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); + + describe('no history', () => { + beforeEach(() => { + render(); + }); + + it('renders empty message', () => { + expect(component.find('FormattedMessage').props().id).to.equal('home.accounts.none'); + }); + }); +}); diff --git a/js/src/views/Dapps/UrlButton/index.js b/js/src/views/Home/Accounts/index.js similarity index 95% rename from js/src/views/Dapps/UrlButton/index.js rename to js/src/views/Home/Accounts/index.js index 173beaadd..027387e70 100644 --- a/js/src/views/Dapps/UrlButton/index.js +++ b/js/src/views/Home/Accounts/index.js @@ -14,4 +14,4 @@ // You should have received a copy of the GNU General Public License // along with Parity. If not, see . -export default from './urlButton'; +export default from './accounts'; diff --git a/js/src/views/Home/Dapps/dapp.js b/js/src/views/Home/Dapps/dapp.js new file mode 100644 index 000000000..51e5c2122 --- /dev/null +++ b/js/src/views/Home/Dapps/dapp.js @@ -0,0 +1,89 @@ +// 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 moment from 'moment'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router'; + +import { Container, DappIcon } from '~/ui'; + +import styles from './dapps.css'; + +export default class Dapp extends Component { + static propTypes = { + id: PropTypes.string.isRequired, + store: PropTypes.object.isRequired, + timestamp: PropTypes.number.isRequired + } + + state = { + dapp: null + } + + componentWillMount () { + return this.loadApp(); + } + + render () { + const { id, timestamp } = this.props; + const { dapp } = this.state; + + if (!dapp) { + return null; + } + + return ( + + +
+ } + > + + + + { dapp.name } + + + + ); + } + + loadApp = () => { + const { id, store } = this.props; + + return store + .loadApp(id) + .then((dapp) => { + this.setState({ dapp }); + }); + } +} diff --git a/js/src/views/Home/Dapps/dapp.spec.js b/js/src/views/Home/Dapps/dapp.spec.js new file mode 100644 index 000000000..d7c5b09bd --- /dev/null +++ b/js/src/views/Home/Dapps/dapp.spec.js @@ -0,0 +1,55 @@ +// 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 Dapp from './dapp'; + +import { createStore } from './dapps.test.js'; + +let component; +let instance; +let store; + +function render () { + store = createStore(); + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('views/Home/Dapp', () => { + beforeEach(() => { + render(); + return instance.componentWillMount(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('loads the dapp on mount', () => { + expect(store.loadApp).to.have.been.calledWith('testId'); + }); +}); diff --git a/js/src/views/Home/Dapps/dapps.css b/js/src/views/Home/Dapps/dapps.css new file mode 100644 index 000000000..b8e9c01df --- /dev/null +++ b/js/src/views/Home/Dapps/dapps.css @@ -0,0 +1,48 @@ +/* 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 . +*/ + +.dapps { + margin-top: 1.5em; + text-align: center; + + .dapp { + position: relative; + line-height: 2em; + vertical-align: middle; + + .icon { + margin: 0; + } + + .link, .name { + display: block; + overflow: hidden; + text-overflow: ellipsis; + } + + .name { + white-space: nowrap; + } + + .timestamp { + color: #aaa; + font-size: 0.75em; + line-height: 1em; + padding-top: 0; + } + } +} diff --git a/js/src/views/Home/Dapps/dapps.js b/js/src/views/Home/Dapps/dapps.js new file mode 100644 index 000000000..608d4435b --- /dev/null +++ b/js/src/views/Home/Dapps/dapps.js @@ -0,0 +1,86 @@ +// 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 { FormattedMessage } from 'react-intl'; + +import { ContainerTitle, SectionList } from '~/ui'; +import { arrayOrObjectProptype } from '~/util/proptypes'; + +import Dapp from './dapp'; +import styles from './dapps.css'; + +export default class Dapps extends Component { + static propTypes = { + history: arrayOrObjectProptype().isRequired, + store: PropTypes.object.isRequired + } + + render () { + return ( +
+ + } + /> + { this.renderHistory() } +
+ ); + } + + renderHistory () { + const { history } = this.props; + + if (!history.length) { + return ( +
+ +
+ ); + } + + return ( + + ); + } + + renderHistoryItem = (history) => { + if (!history || !history.entry) { + return null; + } + + const { store } = this.props; + + return ( + + ); + } +} diff --git a/js/src/views/Home/Dapps/dapps.spec.js b/js/src/views/Home/Dapps/dapps.spec.js new file mode 100644 index 000000000..8dcb938f4 --- /dev/null +++ b/js/src/views/Home/Dapps/dapps.spec.js @@ -0,0 +1,68 @@ +// 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 Dapps from './'; + +import { createStore } from './dapps.test.js'; + +let component; +let store; + +function render (history = []) { + store = createStore(); + component = shallow( + + ); + + return component; +} + +describe('views/Home/Dapps', () => { + it('renders defaults', () => { + expect(render()).to.be.ok; + }); + + describe('no history', () => { + beforeEach(() => { + render(); + }); + + it('renders empty message', () => { + expect(component.find('FormattedMessage').props().id).to.equal('home.dapps.none'); + }); + }); + + describe('with history', () => { + const HISTORY = [ + { timestamp: 1, entry: 'testABC' }, + { timestamp: 2, entry: 'testDEF' } + ]; + + beforeEach(() => { + render(HISTORY); + }); + + it('renders SectionList', () => { + expect(component.find('SectionList').length).to.equal(1); + }); + }); +}); diff --git a/js/src/views/Home/Dapps/dapps.test.js b/js/src/views/Home/Dapps/dapps.test.js new file mode 100644 index 000000000..3593d7f3e --- /dev/null +++ b/js/src/views/Home/Dapps/dapps.test.js @@ -0,0 +1,27 @@ +// 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 sinon from 'sinon'; + +function createStore () { + return { + loadApp: sinon.stub().resolves({ name: 'testName' }) + }; +} + +export { + createStore +}; diff --git a/js/src/views/Home/Dapps/index.js b/js/src/views/Home/Dapps/index.js new file mode 100644 index 000000000..9e6dddb63 --- /dev/null +++ b/js/src/views/Home/Dapps/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 './dapps'; diff --git a/js/src/views/Home/News/index.js b/js/src/views/Home/News/index.js new file mode 100644 index 000000000..437e53a6e --- /dev/null +++ b/js/src/views/Home/News/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 './news'; diff --git a/js/src/views/Home/News/news.css b/js/src/views/Home/News/news.css new file mode 100644 index 000000000..f6a19affa --- /dev/null +++ b/js/src/views/Home/News/news.css @@ -0,0 +1,73 @@ +/* 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 . +*/ + +.news { +} + +.markdown { + line-height: 1.2em; +} + +.item { + height: 240px; + opacity: 0.85; + position: relative; + width: 100%; + + .background { + background-repeat: no-repeat; + background-position: 50% 50%; + background-size: cover; + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + width: auto; + } + + .overlay { + background: white; + color: #333; + display: none; + left: 0; + position: absolute; + right: 0; + top: 100%; + padding: 0 1.5em 1em 1.5em; + } + + .title { + background: rgba(255, 255, 255, 0.85); + bottom: 0; + color: #333; + font-size: 1.17em; + left: 0; + padding: 1rem 1.5rem; + position: absolute; + right: 0; + text-transform: uppercase; + } + + &:hover { + opacity: 1; + + .overlay { + display: block; + } + } +} diff --git a/js/src/views/Home/News/news.js b/js/src/views/Home/News/news.js new file mode 100644 index 000000000..8af395a6b --- /dev/null +++ b/js/src/views/Home/News/news.js @@ -0,0 +1,92 @@ +// 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 { observer } from 'mobx-react'; +import React, { Component } from 'react'; +import ReactMarkdown from 'react-markdown'; + +import { SectionList } from '~/ui'; + +import { createRenderers } from './renderers'; +import Store from './store'; +import styles from './news.css'; + +const VERSION_ID = '1'; + +@observer +export default class News extends Component { + store = Store.get(); + + componentWillMount () { + return this.store.retrieveNews(VERSION_ID); + } + + render () { + const { newsItems } = this.store; + + if (!newsItems || !newsItems.length) { + return null; + } + + return ( + + ); + } + + renderItem = (item) => { + if (!item) { + return null; + } + + const inlineStyles = item.style || {}; + + return ( +
+
+
+ { item.title } +
+
+ +
+
+ ); + } +} + +export { + VERSION_ID +}; diff --git a/js/src/views/Home/News/news.spec.js b/js/src/views/Home/News/news.spec.js new file mode 100644 index 000000000..6da5c2919 --- /dev/null +++ b/js/src/views/Home/News/news.spec.js @@ -0,0 +1,54 @@ +// 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 News from './news'; +import { restoreGlobals, stubGlobals } from './news.test.js'; + +let component; +let instance; + +function render () { + component = shallow( + + ); + instance = component.instance(); + + return component; +} + +describe('views/Home/News', () => { + beforeEach(() => { + stubGlobals(); + render(); + + return instance.componentWillMount(); + }); + + afterEach(() => { + restoreGlobals(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('retrieves the content meta on mount', () => { + expect(instance.store.newsItems).to.equal('testContent'); + }); +}); diff --git a/js/src/views/Home/News/news.test.js b/js/src/views/Home/News/news.test.js new file mode 100644 index 000000000..4b45502a7 --- /dev/null +++ b/js/src/views/Home/News/news.test.js @@ -0,0 +1,54 @@ +// 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 sinon from 'sinon'; + +import Contracts from '~/contracts'; + +import { VERSION_ID } from './news'; + +let contracts; +let globalContractsGet; +let globalFetch; + +export function stubGlobals () { + contracts = { + githubHint: { + getEntry: sinon.stub().resolves(['testUrl', 'testOwner', 'testCommit']) + }, + registry: { + lookupMeta: sinon.stub().resolves('testMeta') + } + }; + + globalContractsGet = Contracts.get; + globalFetch = global.fetch; + + sinon.stub(Contracts, 'get', () => contracts); + sinon.stub(global, 'fetch').resolves({ + ok: true, + json: sinon.stub().resolves({ + [VERSION_ID]: { + items: 'testContent' + } + }) + }); +} + +export function restoreGlobals () { + Contracts.get = globalContractsGet; + global.fetch = globalFetch; +} diff --git a/js/src/views/Home/News/renderers.js b/js/src/views/Home/News/renderers.js new file mode 100644 index 000000000..fde476029 --- /dev/null +++ b/js/src/views/Home/News/renderers.js @@ -0,0 +1,37 @@ +// 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 { createElement } from 'react'; + +export function createRenderers (tagStyles = {}) { + return Object + .keys(tagStyles) + .reduce((renderers, tag) => { + switch (tag) { + case 'a': + case 'link': + renderers['link'] = (mdProps) => { + const { children, href, title } = mdProps; + const style = tagStyles[tag]; + + return createElement('a', { href, title, style }, children); + }; + break; + } + + return renderers; + }, {}); +} diff --git a/js/src/views/Home/News/store.js b/js/src/views/Home/News/store.js new file mode 100644 index 000000000..dd07d3b80 --- /dev/null +++ b/js/src/views/Home/News/store.js @@ -0,0 +1,67 @@ +// 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 { action, observable } from 'mobx'; +import Contracts from '~/contracts'; + +let instance = null; + +export default class Store { + @observable newsItems = null; + + @action setNewsItems = (newsItems) => { + this.newsItems = newsItems; + } + + retrieveNews (versionId) { + const contracts = Contracts.get(); + + return contracts.registry + .lookupMeta('paritynews', 'CONTENT') + .then((contentId) => { + return contracts.githubHint.getEntry(contentId); + }) + .then(([url, owner, commit]) => { + if (!url) { + return null; + } + + return fetch(url).then((response) => { + if (!response.ok) { + return null; + } + + return response.json(); + }); + }) + .then((news) => { + if (news && news[versionId]) { + this.setNewsItems(news[versionId].items); + } + }) + .catch((error) => { + console.warn('retrieveNews', error); + }); + } + + static get () { + if (!instance) { + instance = new Store(); + } + + return instance; + } +} diff --git a/js/src/views/Home/News/store.spec.js b/js/src/views/Home/News/store.spec.js new file mode 100644 index 000000000..6e77e0ee8 --- /dev/null +++ b/js/src/views/Home/News/store.spec.js @@ -0,0 +1,57 @@ +// 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 { VERSION_ID } from './news'; +import { restoreGlobals, stubGlobals } from './news.test.js'; +import Store from './store'; + +let store; + +function create () { + store = new Store(); + + return store; +} + +describe('views/Home/News/Store', () => { + beforeEach(() => { + stubGlobals(); + create(); + }); + + afterEach(() => { + restoreGlobals(); + }); + + describe('@action', () => { + describe('setNewsItems', () => { + it('sets the items', () => { + store.setNewsItems('testing'); + expect(store.newsItems).to.equal('testing'); + }); + }); + }); + + describe('operations', () => { + describe('retrieveNews', () => { + it('retrieves the items', () => { + return store.retrieveNews(VERSION_ID).then(() => { + expect(store.newsItems).to.equal('testContent'); + }); + }); + }); + }); +}); diff --git a/js/src/views/Home/Urls/index.js b/js/src/views/Home/Urls/index.js new file mode 100644 index 000000000..d28700edb --- /dev/null +++ b/js/src/views/Home/Urls/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 './urls'; diff --git a/js/src/views/Home/Urls/urls.css b/js/src/views/Home/Urls/urls.css new file mode 100644 index 000000000..5b5deeb5b --- /dev/null +++ b/js/src/views/Home/Urls/urls.css @@ -0,0 +1,80 @@ +/* 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 . +*/ + +.urls { + text-align: center; + + .layout { + box-sizing: border-box; + margin: 1.5em auto; + padding: 0 1em; + width: 50%; + + .empty { + margin-top: 0.5em; + opacity: 0.75; + } + + .historyItem { + height: auto; + margin-top: 1em; + position: relative; + width: 100%; + + .linkIcon { + opacity: 0; + position: absolute; + right: 0.5em; + top: 0.5em; + } + + .url { + display: block; + color: rgb(0, 151, 167); + overflow: hidden; + text-overflow: ellipsis; + } + + .timestamp { + color: #aaa; + font-size: 0.75em; + line-height: 1em; + padding-top: 0.5rem; + } + + &:hover { + opacity: 1; + + .linkIcon { + opacity: 1; + } + } + } + + .input { + background: rgba(255, 255, 255, 0.25); + border: 1px solid rgba(255, 255, 255, 0.5); + border-radius: 0.25em; + box-sizing: border-box; + color: white; + display: block; + font-size: 1.25em; + padding: 0.5em; + width: 100%; + } + } +} diff --git a/js/src/views/Home/Urls/urls.js b/js/src/views/Home/Urls/urls.js new file mode 100644 index 000000000..7ded1ecda --- /dev/null +++ b/js/src/views/Home/Urls/urls.js @@ -0,0 +1,139 @@ +// 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 { observer } from 'mobx-react'; +import moment from 'moment'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Container, ContainerTitle, DappUrlInput, SectionList } from '~/ui'; +import { LinkIcon } from '~/ui/Icons'; + +import styles from './urls.css'; + +@observer +export default class Urls extends Component { + static contextTypes = { + router: PropTypes.object.isRequired + }; + + static propTypes = { + extensionStore: PropTypes.object.isRequired, + store: PropTypes.object.isRequired + } + + render () { + const { nextUrl } = this.props.store; + + return ( +
+
+ + } + /> + + { this.renderHistory() } +
+
+ ); + } + + renderHistory () { + const { history } = this.props.store; + + if (!history.length) { + return ( +
+ +
+ ); + } + + return ( + + ); + } + + renderHistoryItem = (history) => { + if (!history || !history.url) { + return null; + } + + const onNavigate = () => this.onGotoUrl(history.url); + + return ( + + +
+ } + key={ history.timestamp } + onClick={ onNavigate } + > + +
+ { history.hostname } +
+ + ); + } + + onChangeUrl = (url) => { + this.props.store.setNextUrl(url); + } + + onGotoUrl = (url) => { + const { router } = this.context; + const { extensionStore } = this.props; + + this.props.store.gotoUrl(url); + + if (extensionStore.hasExtension) { + window.open(this.props.store.currentUrl, '_blank'); + } else { + router.push('/web'); + } + } + + onRestoreUrl = () => { + this.props.store.restoreUrl(); + } +} diff --git a/js/src/views/Home/Urls/urls.spec.js b/js/src/views/Home/Urls/urls.spec.js new file mode 100644 index 000000000..6c1438fe0 --- /dev/null +++ b/js/src/views/Home/Urls/urls.spec.js @@ -0,0 +1,124 @@ +// 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 Urls from './'; + +const NEXT_URL = 'http://somewhere.next'; + +let component; +let instance; +let router; +let store; + +function createRouter () { + router = { + push: sinon.stub() + }; + + return router; +} + +function createStore () { + store = { + history: [], + gotoUrl: sinon.stub(), + restoreUrl: sinon.stub(), + setNextUrl: sinon.stub(), + nextUrl: NEXT_URL + }; + + return store; +} + +function render () { + component = shallow( + , + { + context: { + router: createRouter() + } + } + ); + instance = component.instance(); + + return component; +} + +describe('views/Home/Urls', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + describe('input', () => { + let input; + + beforeEach(() => { + input = component.find('DappUrlInput'); + }); + + it('renders the input cmponent', () => { + expect(input.length).to.equal(1); + }); + + it('passes nextUrl as url', () => { + expect(input.props().url).to.equal(NEXT_URL); + }); + }); + + describe('events', () => { + describe('onChangeUrl', () => { + it('performs setNextUrl on store', () => { + instance.onChangeUrl('123'); + expect(store.setNextUrl).to.have.been.calledWith('123'); + }); + }); + + describe('onGotoUrl', () => { + it('performs gotoUrl on store', () => { + instance.onGotoUrl(); + expect(store.gotoUrl).to.have.been.called; + }); + + it('passed the URL when provided', () => { + instance.onGotoUrl('http://example.com'); + expect(store.gotoUrl).to.have.been.calledWith('http://example.com'); + }); + + it('does route navigation when executed', () => { + instance.onGotoUrl(); + expect(router.push).to.have.been.calledWith('/web'); + }); + }); + + describe('onRestoreUrl', () => { + it('performs restoreUrl on store', () => { + instance.onRestoreUrl(); + expect(store.restoreUrl).to.have.been.called; + }); + }); + }); +}); diff --git a/js/src/views/Dapps/UrlButton/urlButton.css b/js/src/views/Home/home.css similarity index 74% rename from js/src/views/Dapps/UrlButton/urlButton.css rename to js/src/views/Home/home.css index ce2f78693..c607e4993 100644 --- a/js/src/views/Dapps/UrlButton/urlButton.css +++ b/js/src/views/Home/home.css @@ -15,6 +15,26 @@ /* along with Parity. If not, see . */ -.button { - vertical-align: middle; +.accounts { + margin-top: 1.5em; +} + +.body { + padding-bottom: 3em; +} + +.empty { + margin-top: 1.5em; + opacity: 0.5; +} + +.row { + display: flex; + + .column { + box-sizing: border-box; + flex: 0 1 50%; + padding: 0 1.5em; + width: 50%; + } } diff --git a/js/src/views/Home/home.js b/js/src/views/Home/home.js new file mode 100644 index 000000000..e1ebd37a5 --- /dev/null +++ b/js/src/views/Home/home.js @@ -0,0 +1,81 @@ +// 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 { observer } from 'mobx-react'; +import React, { Component, PropTypes } from 'react'; +import { FormattedMessage } from 'react-intl'; + +import { Page } from '~/ui'; + +import DappsStore from '../Dapps/dappsStore'; +import ExtensionStore from '../Application/Extension/store'; +import HistoryStore from '../historyStore'; +import WebStore from '../Web/store'; + +import Accounts from './Accounts'; +import Dapps from './Dapps'; +import News from './News'; +import Urls from './Urls'; +import styles from './home.css'; + +@observer +export default class Home extends Component { + static contextTypes = { + api: PropTypes.object.isRequired + }; + + dappsStore = DappsStore.get(this.context.api); + extensionStore = ExtensionStore.get(); + webStore = WebStore.get(this.context.api); + + accountsHistory = HistoryStore.get('accounts'); + dappsHistory = HistoryStore.get('dapps'); + + componentWillMount () { + return this.webStore.loadHistory(); + } + + render () { + return ( + + } + > + + +
+
+ +
+
+ +
+
+
+ ); + } +} diff --git a/js/src/views/Home/home.spec.js b/js/src/views/Home/home.spec.js new file mode 100644 index 000000000..2fccccee1 --- /dev/null +++ b/js/src/views/Home/home.spec.js @@ -0,0 +1,96 @@ +// 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 Home from './'; + +const TEST_APP_HISTORY = []; + +let api; +let component; +let instance; + +function createApi () { + api = { + parity: { + listRecentDapps: sinon.stub().resolves(TEST_APP_HISTORY) + } + }; + + return api; +} + +function render () { + component = shallow( + , + { + context: { + api: createApi() + } + } + ); + instance = component.instance(); + + return component; +} + +describe('views/Home', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + describe('lifecycle', () => { + describe('componentWillMount', () => { + beforeEach(() => { + sinon.stub(instance.webStore, 'loadHistory'); + return instance.componentWillMount(); + }); + + afterEach(() => { + instance.webStore.loadHistory.restore(); + }); + + it('calls into webStore loadHistory', () => { + expect(instance.webStore.loadHistory).to.have.been.called; + }); + }); + }); + + describe('components', () => { + it('renders Accounts', () => { + expect(component.find('Connect(Accounts)').length).to.equal(1); + }); + + it('renders Dapps', () => { + expect(component.find('Dapps').length).to.equal(1); + }); + + it('renders News', () => { + expect(component.find('News').length).to.equal(1); + }); + + it('renders Urls', () => { + expect(component.find('Urls').length).to.equal(1); + }); + }); +}); diff --git a/js/src/views/Home/index.js b/js/src/views/Home/index.js new file mode 100644 index 000000000..12e31704a --- /dev/null +++ b/js/src/views/Home/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 './home'; diff --git a/js/src/views/ParityBar/parityBar.css b/js/src/views/ParityBar/parityBar.css index 6693f5727..64b0b3521 100644 --- a/js/src/views/ParityBar/parityBar.css +++ b/js/src/views/ParityBar/parityBar.css @@ -19,20 +19,7 @@ $overlayZ: 10000; $modalZ: 10001; .account { - display: flex; - flex: 1; - overflow: hidden; - position: relative; - - .accountOverlay { - position: absolute; - right: 0.5em; - top: 0.5em; - } - - .iconDisabled { - opacity: 0.15; - } + width: 100%; .selected, .unselected { diff --git a/js/src/views/Settings/Views/defaults.js b/js/src/views/Settings/Views/defaults.js index 5de5dc5f5..ef2bc910d 100644 --- a/js/src/views/Settings/Views/defaults.js +++ b/js/src/views/Settings/Views/defaults.js @@ -23,7 +23,24 @@ import CommunicationContacts from 'material-ui/svg-icons/communication/contacts' import ImageGridOn from 'material-ui/svg-icons/image/grid-on'; import NavigationApps from 'material-ui/svg-icons/navigation/apps'; +import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg'; + +import styles from './views.css'; + const defaultViews = { + home: { + active: true, + fixed: true, + icon: ( + + ), + route: '/home', + value: 'home' + }, + accounts: { active: true, fixed: true, diff --git a/js/src/views/Settings/Views/views.css b/js/src/views/Settings/Views/views.css index a04d04783..de4079608 100644 --- a/js/src/views/Settings/Views/views.css +++ b/js/src/views/Settings/Views/views.css @@ -43,6 +43,12 @@ color: white !important; } +.logoIcon { + height: 24px; + margin-bottom: 5px; + opacity: 0.5; +} + .info { color: #aaa; padding-left: 4.75em; diff --git a/js/src/views/Web/store.js b/js/src/views/Web/store.js index 542b47a7b..e62effb89 100644 --- a/js/src/views/Web/store.js +++ b/js/src/views/Web/store.js @@ -82,8 +82,31 @@ export default class Store { this.setNextUrl(this.currentUrl); } - @action setHistory = (history) => { - this.history = history; + @action setHistory = (urls) => { + this.history = Object + .keys(urls) + .filter((url) => url && !url.startsWith(this._api.dappsUrl) && url.indexOf('127.0.0.1') === -1) + .sort((urlA, urlB) => { + const timeA = urls[urlA].getTime(); + const timeB = urls[urlB].getTime(); + + if (timeA > timeB) { + return -1; + } else if (timeA < timeB) { + return 1; + } + + return 0; + }) + .map((url) => { + const hostname = url.replace(/^http[s]?:\/\//, '').split('/')[0]; + + return { + hostname, + timestamp: urls[url], + url + }; + }); } @action setLoading = (isLoading) => { diff --git a/js/src/views/Web/store.spec.js b/js/src/views/Web/store.spec.js index 8a8dd268c..58b2f1b3c 100644 --- a/js/src/views/Web/store.spec.js +++ b/js/src/views/Web/store.spec.js @@ -18,7 +18,13 @@ import sinon from 'sinon'; import Store from './store'; -const TEST_HISTORY = ['somethingA', 'somethingB']; +const TEST_HISTORY_URLA = 'http://testingA'; +const TEST_HISTORY_URLB = 'http://testingB'; +const TEST_HISTORY = { + '': new Date(678), + [TEST_HISTORY_URLA]: new Date(123), + [TEST_HISTORY_URLB]: new Date(456) +}; const TEST_TOKEN = 'testing-123'; const TEST_URL1 = 'http://some.test.domain.com'; const TEST_URL2 = 'http://something.different.com'; @@ -89,9 +95,28 @@ describe('views/Web/Store', () => { }); describe('setHistory', () => { - it('sets the history', () => { + let history; + + beforeEach(() => { store.setHistory(TEST_HISTORY); - expect(store.history.peek()).to.deep.equal(TEST_HISTORY); + history = store.history.peek(); + }); + + it('sets the history', () => { + expect(history.length).to.equal(2); + }); + + it('adds hostname to entries', () => { + expect(history[1].hostname).to.be.ok; + }); + + it('removes hostname http prefixes', () => { + expect(history[1].hostname.indexOf('http')).to.equal(-1); + }); + + it('sorts the entries according to recently accessed', () => { + expect(history[0].url).to.equal(TEST_HISTORY_URLB); + expect(history[1].url).to.equal(TEST_HISTORY_URLA); }); }); @@ -195,7 +220,7 @@ describe('views/Web/Store', () => { }); it('sets the history as retrieved', () => { - expect(store.history.peek()).to.deep.equal(TEST_HISTORY); + expect(store.history.peek().length).not.to.equal(0); }); }); }); diff --git a/js/src/views/historyStore.js b/js/src/views/historyStore.js index e28829b54..fd18fd931 100644 --- a/js/src/views/historyStore.js +++ b/js/src/views/historyStore.js @@ -30,10 +30,11 @@ export default class Store { this.load(); } - @action add = (entry) => { + @action add = (entry, type) => { this.history = [{ + entry, timestamp: Date.now(), - entry + type }].concat(this.history.filter((h) => h.entry !== entry)).slice(0, MAX_ENTRIES); this.save(); } diff --git a/js/src/views/index.js b/js/src/views/index.js index 69b5d25f9..4701a7356 100644 --- a/js/src/views/index.js +++ b/js/src/views/index.js @@ -24,6 +24,7 @@ export Contracts from './Contracts'; export Dapp from './Dapp'; export Dapps from './Dapps'; export HistoryStore from './historyStore'; +export Home from './Home'; export ParityBar from './ParityBar'; export Settings, { SettingsBackground, SettingsParity, SettingsProxy, SettingsViews } from './Settings'; export Signer from './Signer';