From d6cb0c07786d1d527324312d90b53d6222719694 Mon Sep 17 00:00:00 2001 From: Nicolas Gotchac Date: Thu, 26 Jan 2017 16:15:49 +0100 Subject: [PATCH] Add a Playground for the UI Components (#4301) * Playground // WIP * Linting * Add Examples with code * CSS Linting * Linting * Add Connected Currency Symbol * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * 2015-2017 * Added `renderSymbol` tests * PR grumbles * Add Eth and Btc QRCode examples * 2015-2017 * Add tests for playground * Fixing tests --- js/package.json | 1 + js/src/playground/index.js | 17 ++++ js/src/playground/playground.css | 90 ++++++++++++++++++ js/src/playground/playground.js | 88 +++++++++++++++++ js/src/playground/playground.spec.js | 47 ++++++++++ js/src/playground/playgroundExample.js | 55 +++++++++++ js/src/playground/store.js | 51 ++++++++++ js/src/playground/store.spec.js | 41 ++++++++ js/src/routes.js | 86 +++++++++-------- .../CurrencySymbol/currencySymbol.example.js | 51 ++++++++++ js/src/ui/CurrencySymbol/currencySymbol.js | 40 ++++---- .../ui/CurrencySymbol/currencySymbol.spec.js | 18 ++++ js/src/ui/QrCode/qrCode.example.js | 63 +++++++++++++ js/src/ui/SectionList/sectionList.example.js | 94 +++++++++++++++++++ js/src/views/Application/application.js | 8 ++ 15 files changed, 692 insertions(+), 58 deletions(-) create mode 100644 js/src/playground/index.js create mode 100644 js/src/playground/playground.css create mode 100644 js/src/playground/playground.js create mode 100644 js/src/playground/playground.spec.js create mode 100644 js/src/playground/playgroundExample.js create mode 100644 js/src/playground/store.js create mode 100644 js/src/playground/store.spec.js create mode 100644 js/src/ui/CurrencySymbol/currencySymbol.example.js create mode 100644 js/src/ui/QrCode/qrCode.example.js create mode 100644 js/src/ui/SectionList/sectionList.example.js diff --git a/js/package.json b/js/package.json index 1de147b5c90..082ad9c9c8a 100644 --- a/js/package.json +++ b/js/package.json @@ -171,6 +171,7 @@ "react-copy-to-clipboard": "4.2.3", "react-dom": "15.4.1", "react-dropzone": "3.7.3", + "react-element-to-jsx-string": "6.0.0", "react-intl": "2.1.5", "react-portal": "3.0.0", "react-redux": "4.4.6", diff --git a/js/src/playground/index.js b/js/src/playground/index.js new file mode 100644 index 00000000000..04d43a76bc4 --- /dev/null +++ b/js/src/playground/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 './playground'; diff --git a/js/src/playground/playground.css b/js/src/playground/playground.css new file mode 100644 index 00000000000..f4e6e55d42e --- /dev/null +++ b/js/src/playground/playground.css @@ -0,0 +1,90 @@ +/* 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 . +*/ + +$codeBackground: #002b36; +$codeColor: #93a1a1; + +.container { + background-color: rgba(0, 0, 0, 0.5); + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1em; + display: flex; + flex-direction: column; + + .examples { + flex: 1; + overflow: auto; + } +} + +.title { + font-size: 2.25em; + margin-bottom: 1em; + + .select { + font-size: 0.85em; + font-family: monospace; + display: inline-block; + height: 1.5em; + border: 1px solid #aaa; + padding: 0 0.5em; + color: #555; + appearance: none; + } +} + +.exampleContainer { + background-color: rgba(0, 0, 0, 0.5); + padding: 1em; + margin-bottom: 1em; + + &:last-child { + margin-bottom: 0; + } + + p { + font-size: 1.25em; + margin-top: 0; + } +} + +.example { + display: flex; + flex-direction: row; + + .code { + flex: 1; + overflow: auto; + padding: 0.5em; + background-color: #$codeBackground; + color: $codeColor; + font-size: 0.75em; + + code { + white-space: pre; + } + } + + .component { + flex: 3; + padding-left: 0.5em; + } +} diff --git a/js/src/playground/playground.js b/js/src/playground/playground.js new file mode 100644 index 00000000000..65535ca4152 --- /dev/null +++ b/js/src/playground/playground.js @@ -0,0 +1,88 @@ +// 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 CurrencySymbol from '~/ui/CurrencySymbol/currencySymbol.example'; +import QrCode from '~/ui/QrCode/qrCode.example'; +import SectionList from '~/ui/SectionList/sectionList.example'; + +import PlaygroundStore from './store'; +import styles from './playground.css'; + +PlaygroundStore.register(); +PlaygroundStore.register(); +PlaygroundStore.register(); + +@observer +export default class Playground extends Component { + state = { + selectedIndex: 0 + }; + + store = PlaygroundStore.get(); + + render () { + return ( +
+
+ Playground > + +
+ +
+ { this.renderComponent() } +
+
+ ); + } + + renderOptions () { + const { components } = this.store; + + return components.map((element, index) => { + const name = element.type.displayName || element.type.name; + + return ( + + ); + }); + } + + renderComponent () { + const { components } = this.store; + const { selectedIndex } = this.state; + + return components[selectedIndex]; + } + + handleChange = (event) => { + const { value } = event.target; + + this.setState({ selectedIndex: value }); + } +} diff --git a/js/src/playground/playground.spec.js b/js/src/playground/playground.spec.js new file mode 100644 index 00000000000..6ea23af6577 --- /dev/null +++ b/js/src/playground/playground.spec.js @@ -0,0 +1,47 @@ +// 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 Playground from './playground'; + +let component; +let options; + +function render (props = {}) { + component = shallow( + + ); + + options = component.find('option'); + + return component; +} + +describe('playground', () => { + beforeEach(() => { + render(); + }); + + it('renders defaults', () => { + expect(component).to.be.ok; + }); + + it('renders multiple options', () => { + expect(options.length).to.be.greaterThan(2); + }); +}); diff --git a/js/src/playground/playgroundExample.js b/js/src/playground/playgroundExample.js new file mode 100644 index 00000000000..9d16ef3ff2a --- /dev/null +++ b/js/src/playground/playgroundExample.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 React, { Component, PropTypes } from 'react'; +import reactElementToJSXString from 'react-element-to-jsx-string'; + +import styles from './playground.css'; + +export default class PlaygroundExample extends Component { + static propTypes = { + children: PropTypes.node, + name: PropTypes.string + }; + + render () { + const { children, name } = this.props; + + return ( +
+ { this.renderName(name) } +
+
+ { reactElementToJSXString(children) } +
+
+ { children } +
+
+
+ ); + } + + renderName (name) { + if (!name) { + return null; + } + + return ( +

{ name }

+ ); + } +} diff --git a/js/src/playground/store.js b/js/src/playground/store.js new file mode 100644 index 00000000000..e627cdcc174 --- /dev/null +++ b/js/src/playground/store.js @@ -0,0 +1,51 @@ +// 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'; + +let instance = null; + +export default class PlaygroundStore { + @observable components = []; + + static get () { + if (!instance) { + instance = new PlaygroundStore(); + } + + return instance; + } + + static register (component) { + PlaygroundStore.get().add(component); + } + + @action + add (component) { + const name = component.type.displayName || component.type.name; + const hasComponent = this.components.find((c) => { + const cName = c.type.displayName || c.type.name; + + return name && cName && cName === name; + }); + + if (hasComponent) { + return; + } + + this.components.push(component); + } +} diff --git a/js/src/playground/store.spec.js b/js/src/playground/store.spec.js new file mode 100644 index 00000000000..27db2411b1c --- /dev/null +++ b/js/src/playground/store.spec.js @@ -0,0 +1,41 @@ +// 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 from 'react'; + +import QrCode from '~/ui/QrCode/qrCode.example'; + +import PlaygroundStore from './store'; + +describe('playground/store', () => { + let store = PlaygroundStore.get(); + + it('is available', () => { + expect(PlaygroundStore.get()).to.be.ok; + }); + + it('adds new Components', () => { + PlaygroundStore.register(); + expect(store.components.length).greaterThan(0); + }); + + it('adds new Components only once', () => { + PlaygroundStore.register(); + PlaygroundStore.register(); + + expect(store.components.filter((c) => /QrCode/i.test(c.type.name)).length).equal(1); + }); +}); diff --git a/js/src/routes.js b/js/src/routes.js index 20fb3dba61b..e38cefd07b6 100644 --- a/js/src/routes.js +++ b/js/src/routes.js @@ -78,45 +78,57 @@ const routes = [ { path: '/', onEnter: redirectTo('/accounts') }, { path: '/auth', onEnter: redirectTo('/accounts') }, - { path: '/settings', onEnter: redirectTo('/settings/views') }, + { path: '/settings', onEnter: redirectTo('/settings/views') } +]; +const appRoutes = [ + { + path: 'accounts', + indexRoute: { component: Accounts }, + childRoutes: accountsRoutes + }, + { + path: 'addresses', + indexRoute: { component: Addresses }, + childRoutes: addressesRoutes + }, + { + path: 'contracts', + indexRoute: { component: Contracts }, + childRoutes: contractsRoutes + }, + { + path: 'status', + indexRoute: { component: Status }, + childRoutes: statusRoutes + }, { - path: '/', - component: Application, - childRoutes: [ - { - path: 'accounts', - indexRoute: { component: Accounts }, - childRoutes: accountsRoutes - }, - { - path: 'addresses', - indexRoute: { component: Addresses }, - childRoutes: addressesRoutes - }, - { - path: 'contracts', - indexRoute: { component: Contracts }, - childRoutes: contractsRoutes - }, - { - path: 'status', - indexRoute: { component: Status }, - childRoutes: statusRoutes - }, - { - path: 'settings', - component: Settings, - childRoutes: settingsRoutes - }, - - { path: 'apps', component: Dapps }, - { path: 'app/:id', component: Dapp }, - { path: 'web', component: Web }, - { path: 'web/:url', component: Web }, - { path: 'signer', component: Signer } - ] - } + path: 'settings', + component: Settings, + childRoutes: settingsRoutes + }, + + { path: 'apps', component: Dapps }, + { path: 'app/:id', component: Dapp }, + { path: 'web', component: Web }, + { path: 'web/:url', component: Web }, + { path: 'signer', component: Signer } ]; +// TODO : use ES6 imports when supported +if (process.env.NODE_ENV !== 'production') { + const Playground = require('./playground').default; + + appRoutes.push({ + path: 'playground', + component: Playground + }); +} + +routes.push({ + path: '/', + component: Application, + childRoutes: appRoutes +}); + export default routes; diff --git a/js/src/ui/CurrencySymbol/currencySymbol.example.js b/js/src/ui/CurrencySymbol/currencySymbol.example.js new file mode 100644 index 00000000000..c1b56ed5cfc --- /dev/null +++ b/js/src/ui/CurrencySymbol/currencySymbol.example.js @@ -0,0 +1,51 @@ +// 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 } from 'react'; + +import PlaygroundExample from '~/playground/playgroundExample'; + +import ConnectedCurrencySymbol, { CurrencySymbol } from './currencySymbol'; + +export default class CurrencySymbolExample extends Component { + render () { + return ( +
+ + + + + + + + + + + + + + + +
+ ); + } +} diff --git a/js/src/ui/CurrencySymbol/currencySymbol.js b/js/src/ui/CurrencySymbol/currencySymbol.js index b6366a63e00..3dfbbc8fd45 100644 --- a/js/src/ui/CurrencySymbol/currencySymbol.js +++ b/js/src/ui/CurrencySymbol/currencySymbol.js @@ -21,43 +21,41 @@ const SYMBOL_ETC = 'ETC'; const SYMBOL_ETH = 'ETH'; const SYMBOL_EXP = 'EXP'; -class CurrencySymbol extends Component { +export class CurrencySymbol extends Component { static propTypes = { className: PropTypes.string, - netChain: PropTypes.string.isRequired, - netSymbol: PropTypes.string.isRequired + netChain: PropTypes.string.isRequired } render () { - const { className, netSymbol } = this.props; + const { className } = this.props; return ( - { netSymbol } + { this.renderSymbol() } ); } -} -function mapStateToProps (state) { - const { netChain } = state.nodeStatus; - let netSymbol; + renderSymbol () { + const { netChain } = this.props; - switch (netChain) { - case 'classic': - netSymbol = SYMBOL_ETC; - break; + switch (netChain) { + case 'classic': + return SYMBOL_ETC; - case 'expanse': - netSymbol = SYMBOL_EXP; - break; + case 'expanse': + return SYMBOL_EXP; - default: - netSymbol = SYMBOL_ETH; - break; + default: + return SYMBOL_ETH; + } } +} + +function mapStateToProps (state) { + const { netChain } = state.nodeStatus; return { - netChain, - netSymbol + netChain }; } diff --git a/js/src/ui/CurrencySymbol/currencySymbol.spec.js b/js/src/ui/CurrencySymbol/currencySymbol.spec.js index c06782466b5..bcc97822f70 100644 --- a/js/src/ui/CurrencySymbol/currencySymbol.spec.js +++ b/js/src/ui/CurrencySymbol/currencySymbol.spec.js @@ -78,4 +78,22 @@ describe('ui/CurrencySymbol', () => { expect(render('somethingElse').text()).equal('ETH'); }); }); + + describe('renderSymbol', () => { + it('render defaults', () => { + expect(render().instance().renderSymbol()).to.be.ok; + }); + + it('render ETH as default', () => { + expect(render().instance().renderSymbol()).equal('ETH'); + }); + + it('render ETC', () => { + expect(render('classic').instance().renderSymbol()).equal('ETC'); + }); + + it('render EXP', () => { + expect(render('expanse').instance().renderSymbol()).equal('EXP'); + }); + }); }); diff --git a/js/src/ui/QrCode/qrCode.example.js b/js/src/ui/QrCode/qrCode.example.js new file mode 100644 index 00000000000..8f85f81478e --- /dev/null +++ b/js/src/ui/QrCode/qrCode.example.js @@ -0,0 +1,63 @@ +// 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 } from 'react'; + +import PlaygroundExample from '~/playground/playgroundExample'; + +import QrCode from './'; + +export default class QrCodeExample extends Component { + render () { + return ( +
+ + + + + + + + + + + + + + + + + + + +
+ ); + } +} diff --git a/js/src/ui/SectionList/sectionList.example.js b/js/src/ui/SectionList/sectionList.example.js new file mode 100644 index 00000000000..a0bd71b58c0 --- /dev/null +++ b/js/src/ui/SectionList/sectionList.example.js @@ -0,0 +1,94 @@ +// 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 } from 'react'; + +import PlaygroundExample from '~/playground/playgroundExample'; +import SectionList from './'; + +const ITEM_STYLE = { + backgroundColor: 'rgba(0, 0, 0, 0.75)', + padding: '1em' +}; + +const items = [ + { name: 'Jack', desc: 'Item number 1' }, + { name: 'Paul', desc: 'Item number 2' }, + { name: 'Matt', desc: 'Item number 3' }, + { name: 'Titi', desc: 'Item number 4' } +]; + +export default class SectionListExample extends Component { + state = { + showOverlay: true + }; + + render () { + return ( +
+ + { this.renderSimple() } + + + + { this.renderWithOverlay() } + +
+ ); + } + + renderSimple () { + return ( + + ); + } + + renderWithOverlay () { + const { showOverlay } = this.state; + const overlay = ( +
+

Overlay

+ +
+ ); + + return ( + + ); + } + + renderItem (item, index) { + const { desc, name } = item; + + return ( +
+

{ name }

+

{ desc }

+
+ ); + } + + hideOverlay = () => { + this.setState({ showOverlay: false }); + } +} diff --git a/js/src/views/Application/application.js b/js/src/views/Application/application.js index 2b32cf64e66..17a331f149e 100644 --- a/js/src/views/Application/application.js +++ b/js/src/views/Application/application.js @@ -57,6 +57,14 @@ class Application extends Component { const [root] = (window.location.hash || '').replace('#/', '').split('/'); const isMinimized = root === 'app' || root === 'web'; + if (process.env.NODE_ENV !== 'production' && root === 'playground') { + return ( +
+ { this.props.children } +
+ ); + } + if (inFrame) { return (