Embeddable ParityBar (#4222) (#4287)

* Embeddable ParityBar

* Replacing storage with store

* Fixing  references.

* Addressing style issues

* Supporting parity background

Conflicts:
	js/src/views/ParityBar/parityBar.js
This commit is contained in:
Tomasz Drwięga 2017-01-24 20:19:05 +01:00 committed by Gav Wood
parent dcd9119452
commit 2b132c38d6
10 changed files with 243 additions and 54 deletions

120
js/src/embed.js Normal file
View File

@ -0,0 +1,120 @@
// Copyright 2015, 2016 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 <http://www.gnu.org/licenses/>.
import 'babel-polyfill';
import 'whatwg-fetch';
import es6Promise from 'es6-promise';
es6Promise.polyfill();
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import injectTapEventPlugin from 'react-tap-event-plugin';
import SecureApi from '~/secureApi';
import ContractInstances from '~/contracts';
import { initStore } from '~/redux';
import ContextProvider from '~/ui/ContextProvider';
import muiTheme from '~/ui/Theme';
import { patchApi } from '~/util/tx';
import { setApi } from '~/redux/providers/apiActions';
import '~/environment';
import '../assets/fonts/Roboto/font.css';
import '../assets/fonts/RobotoMono/font.css';
injectTapEventPlugin();
import ParityBar from '~/views/ParityBar';
// Test transport (std transport should be provided as global object)
class FakeTransport {
constructor () {
console.warn('Secure Transport not provided. Falling back to FakeTransport');
}
execute (method, ...params) {
console.log('Calling', method, params);
return Promise.reject('not connected');
}
on () {
}
}
class FrameSecureApi extends SecureApi {
constructor (transport) {
super('', null, () => {
return transport;
});
}
connect () {
// Do nothing - this API does not need connecting
this.emit('connecting');
// Fire connected event with some delay.
setTimeout(() => {
this.emit('connected');
});
}
needsToken () {
return false;
}
isConnecting () {
return false;
}
isConnected () {
return true;
}
}
const api = new FrameSecureApi(window.secureTransport || new FakeTransport());
patchApi(api);
ContractInstances.create(api);
const store = initStore(api, null, true);
store.dispatch({ type: 'initAll', api });
store.dispatch(setApi(api));
window.secureApi = api;
const app = (
<ParityBar dapp externalLink={ 'http://127.0.0.1:8180' } />
);
const container = document.querySelector('#container');
ReactDOM.render(
<AppContainer>
<ContextProvider
api={ api }
muiTheme={ muiTheme }
store={ store }
>
{ app }
</ContextProvider>
</AppContainer>,
container
);

View File

@ -27,7 +27,7 @@
</head> </head>
<body> <body>
<div id="container"> <div id="container">
<div class="loading">Loading</span> <div class="loading">Loading</div>
</div> </div>
<script src="vendor.js"></script> <script src="vendor.js"></script>
</body> </body>

View File

@ -25,24 +25,27 @@ import CertificationsMiddleware from './providers/certifications/middleware';
import ChainMiddleware from './providers/chainMiddleware'; import ChainMiddleware from './providers/chainMiddleware';
import RegistryMiddleware from './providers/registry/middleware'; import RegistryMiddleware from './providers/registry/middleware';
export default function (api, browserHistory) { export default function (api, browserHistory, forEmbed = false) {
const errors = new ErrorsMiddleware(); const errors = new ErrorsMiddleware();
const signer = new SignerMiddleware(api); const signer = new SignerMiddleware(api);
const settings = new SettingsMiddleware(); const settings = new SettingsMiddleware();
const status = statusMiddleware();
const certifications = new CertificationsMiddleware();
const routeMiddleware = routerMiddleware(browserHistory);
const chain = new ChainMiddleware(); const chain = new ChainMiddleware();
const registry = new RegistryMiddleware(api);
const middleware = [ const middleware = [
settings.toMiddleware(), settings.toMiddleware(),
signer.toMiddleware(), signer.toMiddleware(),
errors.toMiddleware(), errors.toMiddleware(),
certifications.toMiddleware(), chain.toMiddleware()
chain.toMiddleware(),
registry
]; ];
if (!forEmbed) {
const certifications = new CertificationsMiddleware().toMiddleware();
const registry = new RegistryMiddleware(api);
middleware.push(certifications, registry);
}
const status = statusMiddleware();
const routeMiddleware = browserHistory ? routerMiddleware(browserHistory) : [];
return middleware.concat(status, routeMiddleware, thunk); return middleware.concat(status, routeMiddleware, thunk);
} }

View File

@ -33,9 +33,9 @@ const storeCreation = window.devToolsExtension
? window.devToolsExtension()(createStore) ? window.devToolsExtension()(createStore)
: createStore; : createStore;
export default function (api, browserHistory) { export default function (api, browserHistory, forEmbed = false) {
const reducers = initReducers(); const reducers = initReducers();
const middleware = initMiddleware(api, browserHistory); const middleware = initMiddleware(api, browserHistory, forEmbed);
const store = applyMiddleware(...middleware)(storeCreation)(reducers); const store = applyMiddleware(...middleware)(storeCreation)(reducers);
BalancesProvider.instantiate(store, api); BalancesProvider.instantiate(store, api);

View File

@ -15,12 +15,12 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { uniq } from 'lodash'; import { uniq } from 'lodash';
import store from 'store';
import Api from './api'; import Api from './api';
import { LOG_KEYS, getLogger } from '~/config'; import { LOG_KEYS, getLogger } from '~/config';
const log = getLogger(LOG_KEYS.Signer); const log = getLogger(LOG_KEYS.Signer);
const sysuiToken = window.localStorage.getItem('sysuiToken');
export default class SecureApi extends Api { export default class SecureApi extends Api {
_isConnecting = false; _isConnecting = false;
@ -31,13 +31,18 @@ export default class SecureApi extends Api {
_dappsPort = 8080; _dappsPort = 8080;
_signerPort = 8180; _signerPort = 8180;
constructor (url, nextToken) { static getTransport (url, sysuiToken) {
const transport = new Api.Transport.Ws(url, sysuiToken, false); return new Api.Transport.Ws(url, sysuiToken, false);
}
constructor (url, nextToken, getTransport = SecureApi.getTransport) {
const sysuiToken = store.get('sysuiToken');
const transport = getTransport(url, sysuiToken);
super(transport); super(transport);
this._url = url; this._url = url;
// Try tokens from localStorage, from hash and 'initial'
// Try tokens from localstorage, from hash and 'initial'
this._tokens = uniq([sysuiToken, nextToken, 'initial']) this._tokens = uniq([sysuiToken, nextToken, 'initial'])
.filter((token) => token) .filter((token) => token)
.map((token) => ({ value: token, tried: false })); .map((token) => ({ value: token, tried: false }));
@ -308,7 +313,7 @@ export default class SecureApi extends Api {
} }
_saveToken (token) { _saveToken (token) {
window.localStorage.setItem('sysuiToken', token); store.set('sysuiToken', token);
} }
/** /**

View File

@ -14,13 +14,15 @@
// 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 store from 'store';
const LS_KEY = 'tooltips'; const LS_KEY = 'tooltips';
let currentId = -1; let currentId = -1;
let maxId = 0; let maxId = 0;
function closeTooltips (state, action) { function closeTooltips (state, action) {
window.localStorage.setItem(LS_KEY, '{"state":"off"}'); store.set(LS_KEY, '{"state":"off"}');
currentId = -1; currentId = -1;
@ -41,7 +43,7 @@ function newTooltip (state, action) {
} }
function nextTooltip (state, action) { function nextTooltip (state, action) {
const hideTips = window.localStorage.getItem(LS_KEY); const hideTips = store.get(LS_KEY);
currentId = hideTips currentId = hideTips
? -1 ? -1

View File

@ -15,17 +15,14 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>. // along with Parity. If not, see <http://www.gnu.org/licenses/>.
import { action, observable } from 'mobx'; import { action, observable } from 'mobx';
import store from 'store';
export default class Store { export default class Store {
@observable firstrunVisible = false; @observable firstrunVisible = false;
constructor (api) { constructor (api) {
this._api = api; this._api = api;
this.firstrunVisible = store.get('showFirstRun');
const value = window.localStorage.getItem('showFirstRun');
if (value) {
this.firstrunVisible = JSON.parse(value);
}
this._checkAccounts(); this._checkAccounts();
} }
@ -36,7 +33,7 @@ export default class Store {
@action toggleFirstrun = (visible = false) => { @action toggleFirstrun = (visible = false) => {
this.firstrunVisible = visible; this.firstrunVisible = visible;
window.localStorage.setItem('showFirstRun', JSON.stringify(!!visible)); store.set('showFirstRun', !!visible);
} }
_checkAccounts () { _checkAccounts () {

View File

@ -23,15 +23,16 @@ import ContentClear from 'material-ui/svg-icons/content/clear';
import { Badge, Button, ContainerTitle, ParityBackground } from '~/ui'; import { Badge, Button, ContainerTitle, ParityBackground } from '~/ui';
import { Embedded as Signer } from '../Signer'; import { Embedded as Signer } from '../Signer';
import imagesEthcoreBlock from '../../../assets/images/parity-logo-white-no-text.svg'; import imagesEthcoreBlock from '!url-loader!../../../assets/images/parity-logo-white-no-text.svg';
import styles from './parityBar.css'; import styles from './parityBar.css';
class ParityBar extends Component { class ParityBar extends Component {
static propTypes = { static propTypes = {
pending: PropTypes.array, dapp: PropTypes.bool,
dapp: PropTypes.bool externalLink: PropTypes.string,
} pending: PropTypes.array
};
state = { state = {
opened: false opened: false
@ -46,12 +47,32 @@ class ParityBar extends Component {
} }
if (count < newCount) { if (count < newCount) {
this.setState({ opened: true }); this.setOpened(true);
} else if (newCount === 0 && count === 1) { } else if (newCount === 0 && count === 1) {
this.setState({ opened: false }); this.setOpened(false);
} }
} }
setOpened (opened) {
this.setState({ opened });
if (!this.bar) {
return;
}
// Fire up custom even to support having parity bar iframed.
const event = new CustomEvent('parity.bar.visibility', {
bubbles: true,
detail: { opened }
});
this.bar.dispatchEvent(event);
}
onRef = (element) => {
this.bar = element;
}
render () { render () {
const { opened } = this.state; const { opened } = this.state;
@ -73,27 +94,55 @@ class ParityBar extends Component {
className={ styles.parityIcon } /> className={ styles.parityIcon } />
); );
return ( const parityButton = (
<div className={ styles.bar }>
<ParityBackground className={ styles.corner }>
<div className={ styles.cornercolor }>
<Link to='/apps'>
<Button <Button
className={ styles.parityButton } className={ styles.parityButton }
icon={ parityIcon } icon={ parityIcon }
label={ this.renderLabel('Parity') } /> label={ this.renderLabel('Parity') }
</Link> />
);
return (
<div
className={ styles.bar }
ref={ this.onRef }
>
<ParityBackground className={ styles.corner }>
<div className={ styles.cornercolor }>
{ this.renderLink(parityButton) }
<Button <Button
className={ styles.button } className={ styles.button }
icon={ <ActionFingerprint /> } icon={ <ActionFingerprint /> }
label={ this.renderSignerLabel() } label={ this.renderSignerLabel() }
onClick={ this.toggleDisplay } /> onClick={ this.toggleDisplay }
/>
</div> </div>
</ParityBackground> </ParityBackground>
</div> </div>
); );
} }
renderLink (button) {
const { externalLink } = this.props;
if (!externalLink) {
return (
<Link to='/apps'>
{ button }
</Link>
);
}
return (
<a
href={ externalLink }
target='_parent'
>
{ button }
</a>
);
}
renderExpanded () { renderExpanded () {
return ( return (
<div className={ styles.overlay }> <div className={ styles.overlay }>
@ -147,9 +196,7 @@ class ParityBar extends Component {
toggleDisplay = () => { toggleDisplay = () => {
const { opened } = this.state; const { opened } = this.state;
this.setState({ this.setOpened(!opened);
opened: !opened
});
} }
} }

View File

@ -14,6 +14,8 @@
// 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 store from 'store';
import defaultViews from './Views/defaults'; import defaultViews from './Views/defaults';
function initBackground (store, api) { function initBackground (store, api) {
@ -26,11 +28,12 @@ function initBackground (store, api) {
} }
function loadBackground () { function loadBackground () {
return window.localStorage.getItem('backgroundSeed'); // Check global object to support embedding
return store.get('backgroundSeed') || window.backgroundSeed;
} }
function saveBackground (backgroundSeed) { function saveBackground (backgroundSeed) {
window.localStorage.setItem('backgroundSeed', backgroundSeed); store.set('backgroundSeed', backgroundSeed);
} }
function initViews (store) { function initViews (store) {
@ -75,9 +78,9 @@ function loadViews () {
let data; let data;
try { try {
const json = window.localStorage.getItem('views') || '{}'; const json = store.get('views') || {};
data = Object.assign(defaults, JSON.parse(json), fixed); data = Object.assign(defaults, json, fixed);
} catch (e) { } catch (e) {
data = defaults; data = defaults;
} }
@ -85,16 +88,16 @@ function loadViews () {
return data; return data;
} }
function saveViews (store) { function saveViews () {
window.localStorage.setItem('views', JSON.stringify(getDefaultViews())); store.set('views', getDefaultViews());
} }
function toggleViews (store, viewIds) { function toggleViews (viewIds) {
viewIds.forEach((id) => { viewIds.forEach((id) => {
defaultViews[id].active = !defaultViews[id].active; defaultViews[id].active = !defaultViews[id].active;
}); });
saveViews(store); saveViews();
} }
export default class SettingsMiddleware { export default class SettingsMiddleware {
@ -107,11 +110,11 @@ export default class SettingsMiddleware {
break; break;
case 'toggleView': case 'toggleView':
toggleViews(store, [action.viewId]); toggleViews([action.viewId]);
break; break;
case 'toggleViews': case 'toggleViews':
toggleViews(store, action.viewIds); toggleViews(action.viewIds);
break; break;
case 'updateBackground': case 'updateBackground':

View File

@ -40,7 +40,8 @@ module.exports = {
context: path.join(__dirname, '../src'), context: path.join(__dirname, '../src'),
entry: Object.assign({}, Shared.dappsEntry, { entry: Object.assign({}, Shared.dappsEntry, {
index: './index.js' index: './index.js',
embed: './embed.js'
}), }),
output: { output: {
// publicPath: '/', // publicPath: '/',
@ -173,6 +174,17 @@ module.exports = {
] ]
}), }),
new HtmlWebpackPlugin({
title: 'Parity Bar',
filename: 'embed.html',
template: './index.ejs',
favicon: FAVICON,
chunks: [
isProd ? null : 'commons',
'embed'
]
}),
new ScriptExtHtmlWebpackPlugin({ new ScriptExtHtmlWebpackPlugin({
sync: [ 'commons', 'vendor.js' ], sync: [ 'commons', 'vendor.js' ],
defaultAttribute: 'defer' defaultAttribute: 'defer'