Available Dapp selection alignment with Permissions (Portal) (#4374)

* Manage default accounts

* Portal

* Portal

* Allow Portal to be used in as both top-level and popover

* modal/popover variable naming

* Move to Portal

* export Portal in ~/ui

* WIP

* Tags handle empty values

* Export AccountCard in ~/ui

* Allow ETH-only & zero display

* Use ui/Balance for balance display

* Add tests for Balance & Tags component availability

* WIP

* Default overlay display to block (not flex)

* Revert block

* WIP

* Add className, optional handlers only

* WIP

* Properly handle optional onKeyDown

* Selection updated

* Align margins

* Remove old code

* Remove debug logging

* TransitionGroup for animations

* No anim

* Cleanups

* Revert addons removal

* Fix tests

* WIP

* Add onClick to Container

* Create ui/DappCard component

* WIP

* Pass dummy displayApps

* Rename DappsVisible back to AddDapps (easier git diff)

* Rename CSS

* Fix tests after merge
This commit is contained in:
Jaco Greeff 2017-02-02 16:02:45 +01:00 committed by Gav Wood
parent 1547af191b
commit 535ebb1f2f
10 changed files with 190 additions and 125 deletions

View File

@ -15,15 +15,24 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>. /* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/ */
.modal {
flex-direction: column;
}
.container {
margin-top: 1.5em;
overflow-y: auto;
}
.description { .description {
margin-top: .5em !important; margin-top: .5em !important;
} }
.list { .list {
margin-bottom: 1.5em;
.background { .background {
background: rgba(255, 255, 255, 0.2); padding: 0.5em 0;
margin: 0 -1.5em;
padding: 0.5em 1.5em;
} }
.header { .header {
@ -37,3 +46,26 @@
opacity: 0.75; opacity: 0.75;
} }
} }
.selectIcon {
position: absolute;
right: 0.5em;
top: 0.5em;
}
.selected,
.unselected {
position: relative;
}
.unselected {
background: rgba(0, 0, 0, 0.4) !important;
.selectIcon {
opacity: 0.15;
}
}
.selected {
background: rgba(255, 255, 255, 0.15) !important;
}

View File

@ -14,14 +14,12 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { Checkbox } from 'material-ui';
import { List, ListItem } from 'material-ui/List';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { Modal, Button } from '~/ui'; import { ContainerTitle, DappCard, Portal, SectionList } from '~/ui';
import { DoneIcon } from '~/ui/Icons'; import { CheckIcon } from '~/ui/Icons';
import styles from './addDapps.css'; import styles from './addDapps.css';
@ -39,32 +37,23 @@ export default class AddDapps extends Component {
} }
return ( return (
<Modal <Portal
actions={ [ className={ styles.modal }
<Button onClose={ store.closeModal }
icon={ <DoneIcon /> } open
key='done' >
label={ <ContainerTitle
<FormattedMessage
id='dapps.add.button.done'
defaultMessage='Done'
/>
}
onClick={ store.closeModal }
/>
] }
compact
title={ title={
<FormattedMessage <FormattedMessage
id='dapps.add.label' id='dapps.add.label'
defaultMessage='visible applications' defaultMessage='visible applications'
/> />
} }
visible />
> <div className={ styles.container }>
<div className={ styles.warning } /> <div className={ styles.warning } />
{ {
this.renderList(store.sortedLocal, this.renderList(store.sortedLocal, store.displayApps,
<FormattedMessage <FormattedMessage
id='dapps.add.local.label' id='dapps.add.local.label'
defaultMessage='Applications locally available' defaultMessage='Applications locally available'
@ -76,7 +65,7 @@ export default class AddDapps extends Component {
) )
} }
{ {
this.renderList(store.sortedBuiltin, this.renderList(store.sortedBuiltin, store.displayApps,
<FormattedMessage <FormattedMessage
id='dapps.add.builtin.label' id='dapps.add.builtin.label'
defaultMessage='Applications bundled with Parity' defaultMessage='Applications bundled with Parity'
@ -88,7 +77,7 @@ export default class AddDapps extends Component {
) )
} }
{ {
this.renderList(store.sortedNetwork, this.renderList(store.sortedNetwork, store.displayApps,
<FormattedMessage <FormattedMessage
id='dapps.add.network.label' id='dapps.add.network.label'
defaultMessage='Applications on the global network' defaultMessage='Applications on the global network'
@ -99,11 +88,12 @@ export default class AddDapps extends Component {
/> />
) )
} }
</Modal> </div>
</Portal>
); );
} }
renderList (items, header, byline) { renderList (items, visibleItems, header, byline) {
if (!items || !items.length) { if (!items || !items.length) {
return null; return null;
} }
@ -114,41 +104,40 @@ export default class AddDapps extends Component {
<div className={ styles.header }>{ header }</div> <div className={ styles.header }>{ header }</div>
<div className={ styles.byline }>{ byline }</div> <div className={ styles.byline }>{ byline }</div>
</div> </div>
<List> <SectionList
{ items.map(this.renderApp) } items={ items }
</List> noStretch
renderItem={ this.renderApp }
/>
</div> </div>
); );
} }
renderApp = (app) => { renderApp = (app) => {
const { store } = this.props; const { store } = this.props;
const isHidden = !store.displayApps[app.id].visible; const isVisible = store.displayApps[app.id].visible;
const onCheck = () => { const onClick = () => {
if (isHidden) { if (isVisible) {
store.showApp(app.id);
} else {
store.hideApp(app.id); store.hideApp(app.id);
} else {
store.showApp(app.id);
} }
}; };
return ( return (
<ListItem <DappCard
app={ app }
className={
isVisible
? styles.selected
: styles.unselected
}
key={ app.id } key={ app.id }
leftCheckbox={ onClick={ onClick }
<Checkbox >
checked={ !isHidden } <CheckIcon className={ styles.selectIcon } />
onCheck={ onCheck } </DappCard>
/>
}
primaryText={ app.name }
secondaryText={
<div className={ styles.description }>
{ app.description }
</div>
}
/>
); );
} }
} }

View File

@ -33,13 +33,13 @@ describe('modals/AddDapps', () => {
it('does not render the modal with modalOpen = false', () => { it('does not render the modal with modalOpen = false', () => {
expect( expect(
renderShallow({ modalOpen: false }).find('Connect(Modal)') renderShallow({ modalOpen: false }).find('Portal')
).to.have.length(0); ).to.have.length(0);
}); });
it('does render the modal with modalOpen = true', () => { it('does render the modal with modalOpen = true', () => {
expect( expect(
renderShallow({ modalOpen: true }).find('Connect(Modal)') renderShallow({ modalOpen: true }).find('Portal')
).to.have.length(1); ).to.have.length(1);
}); });
}); });

View File

@ -16,10 +16,10 @@
import AddAddress from './AddAddress'; import AddAddress from './AddAddress';
import AddContract from './AddContract'; import AddContract from './AddContract';
import AddDapps from './AddDapps';
import CreateAccount from './CreateAccount'; import CreateAccount from './CreateAccount';
import CreateWallet from './CreateWallet'; import CreateWallet from './CreateWallet';
import DappPermissions from './DappPermissions'; import DappPermissions from './DappPermissions';
import DappsVisible from './AddDapps';
import DeleteAccount from './DeleteAccount'; import DeleteAccount from './DeleteAccount';
import DeployContract from './DeployContract'; import DeployContract from './DeployContract';
import EditMeta from './EditMeta'; import EditMeta from './EditMeta';
@ -37,10 +37,10 @@ import WalletSettings from './WalletSettings';
export { export {
AddAddress, AddAddress,
AddContract, AddContract,
AddDapps,
CreateAccount, CreateAccount,
CreateWallet, CreateWallet,
DappPermissions, DappPermissions,
DappsVisible,
DeleteAccount, DeleteAccount,
DeployContract, DeployContract,
EditMeta, EditMeta,

View File

@ -29,15 +29,14 @@ export default class Container extends Component {
className: PropTypes.string, className: PropTypes.string,
compact: PropTypes.bool, compact: PropTypes.bool,
light: PropTypes.bool, light: PropTypes.bool,
onClick: PropTypes.func,
style: PropTypes.object, style: PropTypes.object,
tabIndex: PropTypes.number, tabIndex: PropTypes.number,
title: nodeOrStringProptype() title: nodeOrStringProptype()
} }
render () { render () {
const { children, className, compact, light, style, tabIndex } = this.props; const { children, className, compact, light, onClick, style, tabIndex } = this.props;
const classes = `${styles.container} ${light ? styles.light : ''} ${className}`;
const props = {}; const props = {};
if (Number.isInteger(tabIndex)) { if (Number.isInteger(tabIndex)) {
@ -45,8 +44,27 @@ export default class Container extends Component {
} }
return ( return (
<div className={ classes } style={ style } { ...props }> <div
<Card className={ compact ? styles.compact : styles.padded }> className={
[
styles.container,
light
? styles.light
: '',
className
].join(' ')
}
style={ style }
{ ...props }
>
<Card
className={
compact
? styles.compact
: styles.padded
}
onClick={ onClick }
>
{ this.renderTitle() } { this.renderTitle() }
{ children } { children }
</Card> </Card>

View File

@ -17,59 +17,80 @@
import React, { Component, PropTypes } from 'react'; import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router'; import { Link } from 'react-router';
import { Container, ContainerTitle, DappIcon, Tags } from '~/ui'; import Container, { Title as ContainerTitle } from '~/ui/Container';
import DappIcon from '~/ui/DappIcon';
import Tags from '~/ui/Tags';
import styles from './summary.css'; import styles from './dappCard.css';
export default class Summary extends Component { export default class DappCard extends Component {
static propTypes = { static propTypes = {
app: PropTypes.object.isRequired, app: PropTypes.object.isRequired,
children: PropTypes.node children: PropTypes.node,
} className: PropTypes.string,
onClick: PropTypes.func,
showLink: PropTypes.bool,
showTags: PropTypes.bool
};
static defaultProps = {
showLink: false,
showTags: false
};
render () { render () {
const { app } = this.props; const { app, children, className, onClick, showLink, showTags } = this.props;
if (!app) { if (!app) {
return null; return null;
} }
const link = this.renderLink(app);
return ( return (
<Container className={ styles.container }> <Container
className={
[styles.container, className].join(' ')
}
onClick={ onClick }
>
<DappIcon <DappIcon
app={ app } app={ app }
className={ styles.image } className={ styles.image }
/> />
<Tags tags={ [app.type] } /> <Tags
tags={
showTags
? [app.type]
: null
}
/>
<div className={ styles.description }> <div className={ styles.description }>
<ContainerTitle <ContainerTitle
className={ styles.title } className={ styles.title }
title={ link } title={
showLink
? this.renderLink(app)
: app.name
}
byline={ app.description } byline={ app.description }
/> />
<div className={ styles.author }> <div className={ styles.author }>
{ app.author }, v{ app.version } { app.author }, v{ app.version }
</div> </div>
{ this.props.children } { children }
</div> </div>
</Container> </Container>
); );
} }
renderLink (app) { renderLink (app) {
// Special case for web dapp
if (app.url === 'web') {
return ( return (
<Link to={ `/web` }> <Link
{ app.name } to={
</Link> app.url === 'web'
); ? '/web'
: `/app/${app.id}`
} }
>
return (
<Link to={ `/app/${app.id}` }>
{ app.name } { app.name }
</Link> </Link>
); );

View File

@ -14,4 +14,4 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
export default from './summary'; export default from './dappCard';

View File

@ -30,6 +30,7 @@ import Container, { Title as ContainerTitle } from './Container';
import ContextProvider from './ContextProvider'; import ContextProvider from './ContextProvider';
import CopyToClipboard from './CopyToClipboard'; import CopyToClipboard from './CopyToClipboard';
import CurrencySymbol from './CurrencySymbol'; import CurrencySymbol from './CurrencySymbol';
import DappCard from './DappCard';
import DappIcon from './DappIcon'; import DappIcon from './DappIcon';
import Editor from './Editor'; import Editor from './Editor';
import Errors from './Errors'; import Errors from './Errors';
@ -79,6 +80,7 @@ export {
CopyToClipboard, CopyToClipboard,
CurrencySymbol, CurrencySymbol,
DappIcon, DappIcon,
DappCard,
Editor, Editor,
Errors, Errors,
FEATURES, FEATURES,

View File

@ -21,14 +21,13 @@ import React, { Component, PropTypes } from 'react';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { AddDapps, DappPermissions } from '~/modals'; import { DappPermissions, DappsVisible } from '~/modals';
import PermissionStore from '~/modals/DappPermissions/store'; import PermissionStore from '~/modals/DappPermissions/store';
import { Actionbar, Button, Page } from '~/ui'; import { Actionbar, Button, DappCard, Page } from '~/ui';
import { LockedIcon, VisibleIcon } from '~/ui/Icons'; import { LockedIcon, VisibleIcon } from '~/ui/Icons';
import UrlButton from './UrlButton'; import UrlButton from './UrlButton';
import DappsStore from './dappsStore'; import DappsStore from './dappsStore';
import Summary from './Summary';
import styles from './dapps.css'; import styles from './dapps.css';
@ -82,8 +81,8 @@ class Dapps extends Component {
return ( return (
<div> <div>
<AddDapps store={ this.store } />
<DappPermissions store={ this.permissionStore } /> <DappPermissions store={ this.permissionStore } />
<DappsVisible store={ this.store } />
<Actionbar <Actionbar
className={ styles.toolbar } className={ styles.toolbar }
title={ title={
@ -146,7 +145,11 @@ class Dapps extends Component {
className={ styles.item } className={ styles.item }
key={ app.id } key={ app.id }
> >
<Summary app={ app } /> <DappCard
app={ app }
showLink
showTags
/>
</div> </div>
); );
} }