Browse Source

Add utility/toolbox app (#51)

* Upgrades

* Add & use Output component

* Render app in content

* Use update @polkadot/dev-react

* Add app-toolbox

* DRY className concat

* Hashing in place

* Verify signature operational

* Entry updates

* Verification operational

* Final cleanups before PR

* Bump version
Jaco Greeff 6 years ago
parent
commit
5f884c4b65
100 changed files with 1158 additions and 271 deletions
  1. 1 2
      .babelrc
  2. 1 1
      .eslintrc.json
  3. 2 1
      .flowconfig
  4. 1 1
      .stylelintrc.json
  5. 0 5
      flow-typed/i18next-browser-languagedetector.js
  6. 0 5
      flow-typed/i18next-xhr-backend.js
  7. 0 23
      flow-typed/i18next.js
  8. 0 5
      flow-typed/react-copy-to-clipboard.js
  9. 0 6
      flow-typed/react-dropzone.js
  10. 0 19
      flow-typed/react-i18next.js
  11. 0 5
      flow-typed/react-remarkable.js
  12. 0 7
      flow-typed/react-router.js
  13. 1 1
      jest.config.js
  14. 2 2
      lerna.json
  15. 2 3
      package.json
  16. 2 2
      packages/app-accounts/package.json
  17. 2 1
      packages/app-accounts/src/Address.js
  18. 2 1
      packages/app-accounts/src/Creator.js
  19. 4 3
      packages/app-accounts/src/Editor.js
  20. 2 1
      packages/app-accounts/src/index.css
  21. 4 2
      packages/app-accounts/src/index.js
  22. 3 3
      packages/app-addresses/package.json
  23. 2 1
      packages/app-addresses/src/Creator.js
  24. 3 2
      packages/app-addresses/src/Editor.js
  25. 4 2
      packages/app-addresses/src/index.js
  26. 6 6
      packages/app-explorer/package.json
  27. 2 1
      packages/app-explorer/src/BlockHeader/index.js
  28. 2 1
      packages/app-explorer/src/index.js
  29. 4 4
      packages/app-extrinsics/package.json
  30. 2 1
      packages/app-extrinsics/src/Account.js
  31. 2 1
      packages/app-extrinsics/src/Extrinsic.js
  32. 2 1
      packages/app-extrinsics/src/Nonce.js
  33. 2 1
      packages/app-extrinsics/src/Selection.js
  34. 2 1
      packages/app-extrinsics/src/Signer/Decoded.js
  35. 2 1
      packages/app-extrinsics/src/Signer/Unlock.js
  36. 2 1
      packages/app-extrinsics/src/Signer/index.js
  37. 4 2
      packages/app-extrinsics/src/Status.js
  38. 3 1
      packages/app-extrinsics/src/index.js
  39. 2 2
      packages/app-storage/package.json
  40. 3 1
      packages/app-storage/src/Queries.js
  41. 7 4
      packages/app-storage/src/Query.js
  42. 2 1
      packages/app-storage/src/Selection.js
  43. 3 1
      packages/app-storage/src/index.js
  44. 2 1
      packages/app-storage/src/types.js
  45. 15 0
      packages/app-toolbox/LICENSE
  46. 1 0
      packages/app-toolbox/README.md
  47. 17 0
      packages/app-toolbox/package.json
  48. 95 0
      packages/app-toolbox/src/Hash.js
  49. 194 0
      packages/app-toolbox/src/Sign.js
  50. 182 0
      packages/app-toolbox/src/Unlock.js
  51. 177 0
      packages/app-toolbox/src/Verify.js
  52. 41 0
      packages/app-toolbox/src/index.css
  53. 94 0
      packages/app-toolbox/src/index.js
  54. 8 0
      packages/app-toolbox/src/translate.js
  55. 2 2
      packages/app-vanitygen/package.json
  56. 2 1
      packages/app-vanitygen/src/Match.js
  57. 8 10
      packages/app-vanitygen/src/index.js
  58. 9 8
      packages/apps/package.json
  59. 3 1
      packages/apps/src/App/index.js
  60. 2 1
      packages/apps/src/Connecting/index.js
  61. 10 11
      packages/apps/src/Content/index.js
  62. 3 1
      packages/apps/src/NotFound/index.js
  63. 2 1
      packages/apps/src/SideBar/index.js
  64. 1 18
      packages/apps/src/index.js
  65. 1 1
      packages/apps/src/routing/accounts.js
  66. 1 1
      packages/apps/src/routing/addresses.js
  67. 1 1
      packages/apps/src/routing/explorer.js
  68. 1 1
      packages/apps/src/routing/extrinsics.js
  69. 5 2
      packages/apps/src/routing/index.js
  70. 1 1
      packages/apps/src/routing/storage.js
  71. 21 0
      packages/apps/src/routing/toolbox.js
  72. 19 0
      packages/apps/src/routing/unknown.js
  73. 1 1
      packages/apps/src/routing/vanitygen.js
  74. 6 3
      packages/apps/src/types.js
  75. 0 24
      packages/apps/src/urlParams.js
  76. 9 11
      packages/ui-app/package.json
  77. 3 1
      packages/ui-app/src/Button/Divider.js
  78. 2 1
      packages/ui-app/src/Button/Group.js
  79. 1 1
      packages/ui-app/src/CopyButton.js
  80. 8 4
      packages/ui-app/src/Dropdown.js
  81. 22 3
      packages/ui-app/src/Input.js
  82. 2 2
      packages/ui-app/src/InputAddress/InputAddress.css
  83. 2 1
      packages/ui-app/src/InputAddress/index.js
  84. 2 1
      packages/ui-app/src/InputExtrinsic/SelectMethod.js
  85. 2 1
      packages/ui-app/src/InputExtrinsic/SelectSection.js
  86. 2 1
      packages/ui-app/src/InputExtrinsic/index.js
  87. 1 1
      packages/ui-app/src/InputExtrinsic/options/method.js
  88. 2 1
      packages/ui-app/src/InputExtrinsic/options/section.js
  89. 2 1
      packages/ui-app/src/InputStorage/SelectKey.js
  90. 2 1
      packages/ui-app/src/InputStorage/SelectSection.js
  91. 3 1
      packages/ui-app/src/InputStorage/index.js
  92. 1 1
      packages/ui-app/src/InputStorage/options/key.js
  93. 2 1
      packages/ui-app/src/InputStorage/options/section.js
  94. 9 2
      packages/ui-app/src/Labelled.js
  95. 47 0
      packages/ui-app/src/Output.js
  96. 3 1
      packages/ui-app/src/Params/Param/Bare.js
  97. 2 1
      packages/ui-app/src/Params/Param/File.js
  98. 2 1
      packages/ui-app/src/Params/Param/findComponent.js
  99. 10 6
      packages/ui-app/src/Params/Param/index.js
  100. 2 1
      packages/ui-app/src/Params/index.js

+ 1 - 2
.babelrc

@@ -1,4 +1,3 @@
 {
-  "extends": "@polkadot/dev/config/babel",
-  "presets": ["react"]
+  "extends": "@polkadot/dev-react/config/babel"
 }

+ 1 - 1
.eslintrc.json

@@ -1,5 +1,5 @@
 {
   "extends": [
-    "./node_modules/@polkadot/dev/config/eslint.js"
+    "./node_modules/@polkadot/dev-react/config/eslint.js"
   ]
 }

+ 2 - 1
.flowconfig

@@ -6,12 +6,13 @@
 
 [libs]
 node_modules/@polkadot/dev/flow-typed
+node_modules/@polkadot/dev-react/flow-typed
 
 [lints]
 all=warn
 
 [options]
 include_warnings=true
-module.name_mapper='^@polkadot/app-\(accounts\|addresses\|explorer\|extrinsics\|home\|rpc\|settings\|storage\|vanitygen\)\(.*\)$' -> '<PROJECT_ROOT>/packages/app-\1/src\2'
+module.name_mapper='^@polkadot/app-\(accounts\|addresses\|explorer\|extrinsics\|home\|rpc\|settings\|storage\|toolbox\|vanitygen\)\(.*\)$' -> '<PROJECT_ROOT>/packages/app-\1/src\2'
 module.name_mapper='^@polkadot/apps\(.*\)$' -> '<PROJECT_ROOT>/packages/apps/src\1'
 module.name_mapper='^@polkadot/ui-\(app\|keyring\)\(.*\)$' -> '<PROJECT_ROOT>/packages/ui-\1/src\2'

+ 1 - 1
.stylelintrc.json

@@ -1,3 +1,3 @@
 {
-  "extends": "@polkadot/dev/config/stylelint"
+  "extends": "@polkadot/dev-react/config/stylelint"
 }

+ 0 - 5
flow-typed/i18next-browser-languagedetector.js

@@ -1,5 +0,0 @@
-// @flow
-
-declare module 'i18next-browser-languagedetector' {
-  declare module.exports: {}
-}

+ 0 - 5
flow-typed/i18next-xhr-backend.js

@@ -1,5 +0,0 @@
-// @flow
-
-declare module 'i18next-xhr-backend' {
-  declare module.exports: {}
-}

+ 0 - 23
flow-typed/i18next.js

@@ -1,23 +0,0 @@
-// @flow
-
-declare type I18Next$Config = {
-  fallbackLng?: string,
-  ns?: Array<string>,
-  defaultNS?: string,
-  debug?: boolean,
-  interpolation?: {
-    escapeValue: boolean
-  },
-  react?: {
-    wait: boolean
-  }
-};
-
-declare module 'i18next' {
-  declare type I18Next = {
-    use: (any) => I18Next,
-    init: (I18Next$Config) => I18Next
-  };
-
-  declare module.exports: I18Next
-}

+ 0 - 5
flow-typed/react-copy-to-clipboard.js

@@ -1,5 +0,0 @@
-// @flow
-
-declare module 'react-copy-to-clipboard' {
-  declare module.exports: React$ComponentType<{ text?: string }>;
-}

+ 0 - 6
flow-typed/react-dropzone.js

@@ -1,6 +0,0 @@
-// @flow
-
-declare module 'react-dropzone' {
-  declare type Dropzone = React$StatelessFunctionalComponent<*>;
-  declare module.exports: Dropzone;
-}

+ 0 - 19
flow-typed/react-i18next.js

@@ -1,19 +0,0 @@
-// @flow
-
-declare type I18Next$Translate$Config = {
-  defaultValue: string,
-  replace?: {
-    [string]: any
-  }
-};
-
-declare type I18Next$Translate = (key: string, config: I18Next$Translate$Config) => string;
-
-declare module 'react-i18next' {
-  declare module.exports: {
-    I18nextProvider: React$StatelessFunctionalComponent<*>,
-    Trans: React$StatelessFunctionalComponent<*>,
-    reactI18nextModule: {},
-    translate: (context: string | Array<string>) => (Component: React$ComponentType<*>) => React$StatelessFunctionalComponent<*>
-  }
-}

+ 0 - 5
flow-typed/react-remarkable.js

@@ -1,5 +0,0 @@
-// @flow
-
-declare module 'react-remarkable' {
-  declare module.exports: React$StatelessFunctionalComponent<*>
-}

+ 0 - 7
flow-typed/react-router.js

@@ -1,7 +0,0 @@
-// @flow
-
-declare module 'react-router' {
-  declare module.exports: {
-    withRouter: (Component: React$ComponentType<*>) => React$StatelessFunctionalComponent<*>;
-  }
-}

+ 1 - 1
jest.config.js

@@ -1 +1 @@
-module.exports = require('@polkadot/dev/config/jest');
+module.exports = require('@polkadot/dev-react/config/jest');

+ 2 - 2
lerna.json

@@ -1,5 +1,5 @@
 {
-  "lerna": "2.5.1",
+  "lerna": "2.11.0",
   "npmClient": "yarn",
   "useWorkspaces": true,
   "command": {
@@ -10,5 +10,5 @@
   "packages": [
     "packages/*"
   ],
-  "version": "0.4.12"
+  "version": "0.5.0"
 }

+ 2 - 3
package.json

@@ -9,16 +9,15 @@
     "build": "polkadot-dev-build-babel && yarn run build:apps",
     "build:apps": "cd packages/apps && react-scripts build",
     "check": "eslint packages && flow check",
+    "clean": "polkadot-dev-clean-build",
     "deploy:ghpages": "gh-pages -d packages/apps/build",
     "vanitygen": "polkadot-dev-build-babel && node packages/app-vanitygen/build/generator/cli.js",
     "start": "cd packages/apps && react-scripts start",
     "test": "echo 'no tests executed'"
   },
   "devDependencies": {
-    "@polkadot/dev": "^0.17.13",
-    "babel-preset-react": "^6.24.1",
+    "@polkadot/dev-react": "^0.18.3",
     "gh-pages": "^1.1.0",
-    "lerna": "^2.5.1",
     "react-scripts": "2.0.0-next.66cc7a90"
   },
   "browserslist": {

+ 2 - 2
packages/app-accounts/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-accounts",
-  "version": "0.4.12",
+  "version": "0.5.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -11,6 +11,6 @@
     "test": "jest --coverage"
   },
   "devDependencies": {
-    "@polkadot/ui-app": "^0.4.12"
+    "@polkadot/ui-app": "^0.5.0"
   }
 }

+ 2 - 1
packages/app-accounts/src/Address.js

@@ -8,6 +8,7 @@ import type { BareProps } from '@polkadot/ui-app/types';
 import React from 'react';
 
 import CopyButton from '@polkadot/ui-app/src/CopyButton';
+import classes from '@polkadot/ui-app/src/util/classes';
 import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
 import Balance from '@polkadot/ui-react-rx/Balance';
 import Nonce from '@polkadot/ui-react-rx/Nonce';
@@ -58,7 +59,7 @@ export default class Address extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['accounts--Address', isValid ? '' : 'invalid', className].join(' ')}
+        className={classes('accounts--Address', isValid && 'invalid', className)}
         style={style}
       >
         <IdentityIcon

+ 2 - 1
packages/app-accounts/src/Creator.js

@@ -10,6 +10,7 @@ import React from 'react';
 import Button from '@polkadot/ui-app/src/Button';
 import Input from '@polkadot/ui-app/src/Input';
 import Password from '@polkadot/ui-app/src/Password';
+import classes from '@polkadot/ui-app/src/util/classes';
 import keyring from '@polkadot/ui-keyring/src';
 import isHex from '@polkadot/util/is/hex';
 import hexToU8a from '@polkadot/util/hex/toU8a';
@@ -66,7 +67,7 @@ class Creator extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['accounts--Creator', className].join(' ')}
+        className={classes('accounts--Creator', className)}
         style={style}
       >
         <div className='ui--grid'>

+ 4 - 3
packages/app-accounts/src/Editor.js

@@ -11,6 +11,7 @@ import React from 'react';
 import Button from '@polkadot/ui-app/src/Button';
 import Input from '@polkadot/ui-app/src/Input';
 import InputAddress from '@polkadot/ui-app/src/InputAddress';
+import classes from '@polkadot/ui-app/src/util/classes';
 import keyring from '@polkadot/ui-keyring/src';
 
 import Address from './Address';
@@ -49,7 +50,7 @@ class Editor extends React.PureComponent<Props, State> {
     if (!currentPair) {
       return (
         <div
-          className={['accounts--Editor', className].join(' ')}
+          className={classes('accounts--Editor', className)}
           style={style}
         >
           {t('editor.none', {
@@ -63,7 +64,7 @@ class Editor extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['accounts--Editor', className].join(' ')}
+        className={classes('accounts--Editor', className)}
         style={style}
       >
         <div className='ui--grid'>
@@ -77,7 +78,7 @@ class Editor extends React.PureComponent<Props, State> {
                 className='full'
                 defaultValue={defaultPublicKey}
                 hideAddress
-                isSUIInput={false}
+                isInput={false}
                 label={t('editor.select', {
                   defaultValue: 'using my account'
                 })}

+ 2 - 1
packages/app-accounts/src/index.css

@@ -12,6 +12,7 @@
 }
 
 .accounts--Address {
+  padding: 0 2em;
   text-align: center;
 }
 
@@ -39,7 +40,7 @@
 }
 
 .accounts--Address-data {
-  padding: 0.5em 2em;
+  padding: 0.5em 0;
 }
 
 .accounts--Address-data * {

+ 4 - 2
packages/app-accounts/src/index.js

@@ -10,6 +10,7 @@ import './index.css';
 import React from 'react';
 
 import Button from '@polkadot/ui-app/src/Button';
+import classes from '@polkadot/ui-app/src/util/classes';
 
 import Creator from './Creator';
 import Editor from './Editor';
@@ -24,7 +25,8 @@ type State = {
 }
 
 // FIXME React-router would probably be the best route, not home-grown
-const Components: { [Actions]: React$ComponentType<*> } = {
+// flowlint-next-line unclear-type:off
+const Components: { [Actions]: React$ComponentType<any> } = {
   'create': Creator,
   'edit': Editor
 };
@@ -39,7 +41,7 @@ class App extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['accounts--App', className].join(' ')}
+        className={classes('accounts--App', className)}
         style={style}
       >
         <Button.Group className='accounts--App-navigation'>

+ 3 - 3
packages/app-addresses/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-addresses",
-  "version": "0.4.12",
+  "version": "0.5.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -11,7 +11,7 @@
     "test": "jest --coverage"
   },
   "devDependencies": {
-    "@polkadot/app-accounts": "^0.4.12",
-    "@polkadot/ui-app": "^0.4.12"
+    "@polkadot/app-accounts": "^0.5.0",
+    "@polkadot/ui-app": "^0.5.0"
   }
 }

+ 2 - 1
packages/app-addresses/src/Creator.js

@@ -9,6 +9,7 @@ import React from 'react';
 
 import Button from '@polkadot/ui-app/src/Button';
 import Input from '@polkadot/ui-app/src/Input';
+import classes from '@polkadot/ui-app/src/util/classes';
 import keyring from '@polkadot/ui-keyring/src';
 import addressDecode from '@polkadot/util-keyring/address/decode';
 import addressEncode from '@polkadot/util-keyring/address/encode';
@@ -43,7 +44,7 @@ class Creator extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['addresses--Creator', className].join(' ')}
+        className={classes('addresses--Creator', className)}
         style={style}
       >
         <div className='ui--grid'>

+ 3 - 2
packages/app-addresses/src/Editor.js

@@ -11,6 +11,7 @@ import React from 'react';
 import Button from '@polkadot/ui-app/src/Button';
 import Input from '@polkadot/ui-app/src/Input';
 import InputAddress from '@polkadot/ui-app/src/InputAddress';
+import classes from '@polkadot/ui-app/src/util/classes';
 import keyring from '@polkadot/ui-keyring/src';
 
 import Address from '@polkadot/app-accounts/src/Address';
@@ -50,7 +51,7 @@ class Editor extends React.PureComponent<Props, State> {
     if (!currentAddress) {
       return (
         <div
-          className={['addresses--Editor', className].join(' ')}
+          className={classes('addresses--Editor', className)}
           style={style}
         >
           {t('editor.none', {
@@ -64,7 +65,7 @@ class Editor extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['addresses--Editor', className].join(' ')}
+        className={classes('addresses--Editor', className)}
         style={style}
       >
         <div className='ui--grid'>

+ 4 - 2
packages/app-addresses/src/index.js

@@ -10,6 +10,7 @@ import './index.css';
 import React from 'react';
 
 import Button from '@polkadot/ui-app/src/Button';
+import classes from '@polkadot/ui-app/src/util/classes';
 
 import Creator from './Creator';
 import Editor from './Editor';
@@ -24,7 +25,8 @@ type State = {
 }
 
 // FIXME React-router would probably be the best route, not home-grown
-const Components: { [Actions]: React$ComponentType<*> } = {
+// flowlint-next-line unclear-type:off
+const Components: { [Actions]: React$ComponentType<any> } = {
   'create': Creator,
   'edit': Editor
 };
@@ -39,7 +41,7 @@ class App extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['addresses--App', className].join(' ')}
+        className={classes('addresses--App', className)}
         style={style}
       >
         <Button.Group className='addresses--App-navigation'>

+ 6 - 6
packages/app-explorer/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-explorer",
-  "version": "0.4.12",
+  "version": "0.5.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -11,10 +11,10 @@
     "test": "jest --coverage"
   },
   "devDependencies": {
-    "@polkadot/primitives": "^0.13.8",
-    "@polkadot/primitives-codec": "^0.13.8",
-    "@polkadot/primitives-json": "^0.13.8",
-    "@polkadot/ui-app": "^0.4.12",
-    "@polkadot/util-crypto": "^0.19.6"
+    "@polkadot/primitives": "^0.13.10",
+    "@polkadot/primitives-codec": "^0.13.10",
+    "@polkadot/primitives-json": "^0.13.10",
+    "@polkadot/ui-app": "^0.5.0",
+    "@polkadot/util-crypto": "^0.19.7"
   }
 }

+ 2 - 1
packages/app-explorer/src/BlockHeader/index.js

@@ -11,6 +11,7 @@ import './BlockHeader.css';
 import React from 'react';
 
 import headerHash from '@polkadot/primitives-codec/header/hash';
+import classes from '@polkadot/ui-app/src/util/classes';
 import u8aToHex from '@polkadot/util/u8a/toHex';
 
 import translate from '../translate';
@@ -30,7 +31,7 @@ function BlockHeader ({ className, label = '#', value, style }: Props): React$No
 
   return (
     <div
-      className={['explorer--BlockHeader', className].join(' ')}
+      className={classes('explorer--BlockHeader', className)}
       style={style}
     >
       <div className='number'>

+ 2 - 1
packages/app-explorer/src/index.js

@@ -9,6 +9,7 @@ import './index.css';
 
 import React from 'react';
 
+import classes from '@polkadot/ui-app/src/util/classes';
 import BestNumber from '@polkadot/ui-react-rx/BestNumber';
 
 import BestHash from './BestHash';
@@ -20,7 +21,7 @@ type Props = I18nProps & {};
 function App ({ className, style, t }: Props): React$Node {
   return (
     <div
-      className={['explorer--App', className].join(' ')}
+      className={classes('explorer--App', className)}
       style={style}
     >
       <BestNumber

+ 4 - 4
packages/app-extrinsics/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-extrinsics",
-  "version": "0.4.12",
+  "version": "0.5.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -11,9 +11,9 @@
     "test": "jest --coverage"
   },
   "devDependencies": {
-    "@polkadot/extrinsics": "^0.13.8",
-    "@polkadot/extrinsics-codec": "^0.13.8",
-    "@polkadot/ui-app": "^0.4.12",
+    "@polkadot/extrinsics": "^0.13.10",
+    "@polkadot/extrinsics-codec": "^0.13.10",
+    "@polkadot/ui-app": "^0.5.0",
     "react-dropzone": "^4.2.9"
   }
 }

+ 2 - 1
packages/app-extrinsics/src/Account.js

@@ -10,6 +10,7 @@ import React from 'react';
 
 import InputAddress from '@polkadot/ui-app/src/InputAddress';
 import Labelled from '@polkadot/ui-app/src/Labelled';
+import classes from '@polkadot/ui-app/src/util/classes';
 import Balance from '@polkadot/ui-react-rx/Balance';
 
 import translate from './translate';
@@ -45,7 +46,7 @@ class Account extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['extrinsics--Account', 'ui--row', className].join(' ')}
+        className={classes('extrinsics--Account', 'ui--row', className)}
         style={style}
       >
         <div className='large'>

+ 2 - 1
packages/app-extrinsics/src/Extrinsic.js

@@ -13,6 +13,7 @@ import React from 'react';
 import encode from '@polkadot/extrinsics-codec/encode/extrinsic';
 import InputExtrinsic from '@polkadot/ui-app/src/InputExtrinsic';
 import Params from '@polkadot/ui-app/src/Params';
+import classes from '@polkadot/ui-app/src/util/classes';
 import isUndefined from '@polkadot/util/is/undefined';
 
 import paramComponents from './Params';
@@ -48,7 +49,7 @@ export default class ExtrinsicDisplay extends React.PureComponent<Props, State>
 
     return (
       <div
-        className={['extrinsics--Extrinsic', className].join(' ')}
+        className={classes('extrinsics--Extrinsic', className)}
         style={style}
       >
         <InputExtrinsic

+ 2 - 1
packages/app-extrinsics/src/Nonce.js

@@ -9,6 +9,7 @@ import type { BareProps } from '@polkadot/ui-app/types';
 import React from 'react';
 
 import Labelled from '@polkadot/ui-app/src/Labelled';
+import classes from '@polkadot/ui-app/src/util/classes';
 import RxNonce from '@polkadot/ui-react-rx/Nonce';
 
 type Props = BareProps & {
@@ -20,7 +21,7 @@ type Props = BareProps & {
 export default function Nonce ({ className, label, style, onChange, value }: Props): React$Node {
   return (
     <div
-      className={['ui--row', className].join(' ')}
+      className={classes('ui--row', className)}
       style={style}
     >
       <Labelled

+ 2 - 1
packages/app-extrinsics/src/Selection.js

@@ -11,6 +11,7 @@ import React from 'react';
 
 import extrinsics from '@polkadot/extrinsics-substrate';
 import Button from '@polkadot/ui-app/src/Button';
+import classes from '@polkadot/ui-app/src/util/classes';
 
 import Account from './Account';
 import Extrinsic from './Extrinsic';
@@ -43,7 +44,7 @@ class Selection extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['extrinsics--Selection', className].join(' ')}
+        className={classes('extrinsics--Selection', className)}
         style={style}
       >
         <Account

+ 2 - 1
packages/app-extrinsics/src/Signer/Decoded.js

@@ -10,6 +10,7 @@ import BN from 'bn.js';
 import React from 'react';
 import { Trans } from 'react-i18next';
 
+import classes from '@polkadot/ui-app/src/util/classes';
 import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
 import u8aToHexShort from '@polkadot/util/u8a/toHexShort';
 import addressEncode from '@polkadot/util-keyring/address/encode';
@@ -25,7 +26,7 @@ function Decoded ({ className, style, t, value: { extrinsic: { name, section },
 
   return (
     <div
-      className={['extrinsics--Signer-Decoded', className].join(' ')}
+      className={classes('extrinsics--Signer-Decoded', className)}
       style={style}
     >
       <div className='expanded'>

+ 2 - 1
packages/app-extrinsics/src/Signer/Unlock.js

@@ -9,6 +9,7 @@ import type { KeyringPair } from '@polkadot/util-keyring/types';
 import React from 'react';
 
 import Password from '@polkadot/ui-app/src/Password';
+import classes from '@polkadot/ui-app/src/util/classes';
 import keyring from '@polkadot/ui-keyring/src';
 
 import translate from '../translate';
@@ -51,7 +52,7 @@ class Unlock extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['extrinsics--Signer-Unlock', className].join(' ')}
+        className={classes('extrinsics--Signer-Unlock', className)}
         style={style}
       >
         <div className='ui--row'>

+ 2 - 1
packages/app-extrinsics/src/Signer/index.js

@@ -13,6 +13,7 @@ import React from 'react';
 
 import Button from '@polkadot/ui-app/src/Button';
 import Modal from '@polkadot/ui-app/src/Modal';
+import classes from '@polkadot/ui-app/src/util/classes';
 import keyring from '@polkadot/ui-keyring/src';
 import withApi from '@polkadot/ui-react-rx/with/api';
 
@@ -69,7 +70,7 @@ class Signer extends React.PureComponent<Props, State> {
 
     return (
       <Modal
-        className={['extrinsics--Signer', className].join(' ')}
+        className={classes('extrinsics--Signer', className)}
         dimmer='inverted'
         open
         style={style}

+ 4 - 2
packages/app-extrinsics/src/Status.js

@@ -8,6 +8,8 @@ import type { QueueTx } from './types';
 
 import React from 'react';
 
+import classes from '@polkadot/ui-app/src/util/classes';
+
 type Props = BareProps & {
   queue: Array<QueueTx>
 }
@@ -23,12 +25,12 @@ export default function Status ({ className, queue, style }: Props): React$Node
 
   return (
     <div
-      className={['extrinsics--Status', className].join(' ')}
+      className={classes('extrinsics--Status', className)}
       style={style}
     >
       {available.map(({ extrinsic: { name, section }, id, status }) =>
         <div
-          className={['extrinsics--Status-Item', status].join(' ')}
+          className={classes('extrinsics--Status-Item', status)}
           key={id}
         >
           <div className='header'>

+ 3 - 1
packages/app-extrinsics/src/index.js

@@ -10,6 +10,8 @@ import './index.css';
 
 import React from 'react';
 
+import classes from '@polkadot/ui-app/src/util/classes';
+
 import Selection from './Selection';
 import Signer from './Signer';
 import Status from './Status';
@@ -31,7 +33,7 @@ export default class App extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['extrinsics--App', className].join(' ')}
+        className={classes('extrinsics--App', className)}
         style={style}
       >
         <Selection onQueue={this.onQueue} />

+ 2 - 2
packages/app-storage/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-storage",
-  "version": "0.4.12",
+  "version": "0.5.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -11,6 +11,6 @@
     "test": "jest --coverage"
   },
   "devDependencies": {
-    "@polkadot/ui-app": "^0.4.12"
+    "@polkadot/ui-app": "^0.5.0"
   }
 }

+ 3 - 1
packages/app-storage/src/Queries.js

@@ -8,6 +8,8 @@ import type { StorageQuery } from './types';
 
 import React from 'react';
 
+import classes from '@polkadot/ui-app/src/util/classes';
+
 import translate from './translate';
 import Query from './Query';
 
@@ -23,7 +25,7 @@ function Queries ({ className, onRemove, style, value }: Props): React$Node {
 
   return (
     <div
-      className={['storage--Queries', className].join(' ')}
+      className={classes('storage--Queries', className)}
       style={style}
     >
       {value.map((query) =>

+ 7 - 4
packages/app-storage/src/Query.js

@@ -12,6 +12,7 @@ import Button from '@polkadot/ui-app/src/Button';
 import Labelled from '@polkadot/ui-app/src/Labelled';
 import typeToText from '@polkadot/ui-app/src/Params/typeToText';
 import valueToText from '@polkadot/ui-app/src/Params/valueToText';
+import classes from '@polkadot/ui-app/src/util/classes';
 import withStorageDiv from '@polkadot/ui-react-rx/with/storageDiv';
 
 import translate from './translate';
@@ -23,7 +24,8 @@ type Props = I18nProps & {
 
 type State = {
   inputs: Array<React$Node>,
-  Component: React$ComponentType<*>;
+  // flowlint-next-line unclear-type:off
+  Component: React$ComponentType<any>;
 };
 
 const cache = [];
@@ -32,14 +34,15 @@ class Query extends React.PureComponent<Props, State> {
   state: State = ({}: $Shape<State>);
 
   static getDerivedStateFromProps ({ value: { id, key, params } }: Props, prevState: State): State | null {
-    const Component = ((): React$ComponentType<*> => {
+    // flowlint-next-line unclear-type:off
+    const Component = ((): React$ComponentType<any> => {
       if (!cache[id]) {
         const values = params.map(({ value }) => value);
 
         cache[id] = withStorageDiv(key, { params: values })(
           (value) =>
             valueToText(key.type, value),
-          { className: 'ui disabled dropdown selection' }
+          { className: 'ui--output' }
         );
       }
 
@@ -67,7 +70,7 @@ class Query extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['storage--Query', 'storage--actionrow', className].join(' ')}
+        className={classes('storage--Query', 'storage--actionrow', className)}
         style={style}
       >
         <Labelled

+ 2 - 1
packages/app-storage/src/Selection.js

@@ -15,6 +15,7 @@ import Button from '@polkadot/ui-app/src/Button';
 import InputStorage from '@polkadot/ui-app/src/InputStorage';
 import Labelled from '@polkadot/ui-app/src/Labelled';
 import Params from '@polkadot/ui-app/src/Params';
+import classes from '@polkadot/ui-app/src/util/classes';
 import isUndefined from '@polkadot/util/is/undefined';
 
 import translate from './translate';
@@ -44,7 +45,7 @@ class Selection extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['storage--Selection', 'storage--actionrow', className].join(' ')}
+        className={classes('storage--Selection', 'storage--actionrow', className)}
         style={style}
       >
         <div className='storage--actionrow-value'>

+ 3 - 1
packages/app-storage/src/index.js

@@ -10,6 +10,8 @@ import './index.css';
 
 import React from 'react';
 
+import classes from '@polkadot/ui-app/src/util/classes';
+
 import Queries from './Queries';
 import Selection from './Selection';
 import translate from './translate';
@@ -31,7 +33,7 @@ class App extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['storage--App', className].join(' ')}
+        className={classes('storage--App', className)}
         style={style}
       >
         <Selection onAdd={this.onAdd} />

+ 2 - 1
packages/app-storage/src/types.js

@@ -8,5 +8,6 @@ import type { StorageDef$Key } from '@polkadot/storage/types';
 export type StorageQuery = {
   id: number,
   key: StorageDef$Key,
-  params: Array<*>
+  // flowlint-next-line unclear-type:off
+  params: Array<any>
 }

+ 15 - 0
packages/app-toolbox/LICENSE

@@ -0,0 +1,15 @@
+ISC License (ISC)
+
+Copyright 2017-2018 Jaco Greeff <jacogr@gmail.com>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.

+ 1 - 0
packages/app-toolbox/README.md

@@ -0,0 +1 @@
+# @polkadot/app-toolbox

+ 17 - 0
packages/app-toolbox/package.json

@@ -0,0 +1,17 @@
+{
+  "name": "@polkadot/app-toolbox",
+  "version": "0.5.0",
+  "main": "index.js",
+  "repository": "https://github.com/polkadot-js/apps.git",
+  "author": "Jaco Greeff <jacogr@gmail.com>",
+  "license": "ISC",
+  "scripts": {
+    "build": "polkadot-dev-build-babel",
+    "check": "stylelint 'src/**/*.css' && eslint src && flow check",
+    "test": "jest --coverage"
+  },
+  "devDependencies": {
+    "@polkadot/ui-app": "^0.5.0",
+    "@polkadot/ui-keyring": "^0.5.0"
+  }
+}

+ 95 - 0
packages/app-toolbox/src/Hash.js

@@ -0,0 +1,95 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { I18nProps as Props } from '@polkadot/ui-app/types';
+
+import React from 'react';
+
+import Input from '@polkadot/ui-app/src/Input';
+import Output from '@polkadot/ui-app/src/Output';
+import Static from '@polkadot/ui-app/src/Static';
+import classes from '@polkadot/ui-app/src/util/classes';
+import hexToU8a from '@polkadot/util/hex/toU8a';
+import isHex from '@polkadot/util/is/hex';
+import u8aFromString from '@polkadot/util/u8a/fromString';
+import xxhashAsHex256 from '@polkadot/util-crypto/xxhash/asHex256';
+
+import translate from './translate';
+
+type State = {
+  data: string,
+  hash: string,
+  isHexData: boolean
+};
+
+class Hash extends React.PureComponent<Props, State> {
+  state: State = {
+    data: '',
+    hash: xxhashAsHex256(u8aFromString('')),
+    isHexData: false
+  };
+
+  render (): React$Node {
+    const { className, style, t } = this.props;
+    const { data, hash, isHexData } = this.state;
+
+    return (
+      <div
+        className={classes('toolbox--Hash', className)}
+        style={style}
+      >
+        <div className='ui--row'>
+          <Input
+            className='large'
+            label={t('hash.data', {
+              defaultValue: 'from the following data (hex or string)'
+            })}
+            onChange={this.onChangeData}
+            value={data}
+          />
+          <Static
+            className='small'
+            label={t('hash.isHex', {
+              defaultValue: 'hex input data'
+            })}
+            value={
+              isHexData
+                ? t('hash.isHex.yes', {
+                  defaultValue: 'Yes'
+                })
+                : t('hash.isHex.no', {
+                  defaultValue: 'No'
+                })
+            }
+          />
+        </div>
+        <div className='ui--row'>
+          <Output
+            className='full toolbox--hex'
+            isHidden={hash.length === 0}
+            label={t('hash.output', {
+              defaultValue: 'the resulting hash is'
+            })}
+            value={hash}
+            withCopy
+          />
+        </div>
+      </div>
+    );
+  }
+
+  onChangeData = (data: string): void => {
+    const isHexData = isHex(data);
+    const hash = xxhashAsHex256(
+      isHexData
+        ? hexToU8a(data)
+        : u8aFromString(data)
+    );
+
+    this.setState({ data, hash, isHexData });
+  }
+}
+
+export default translate(Hash);

+ 194 - 0
packages/app-toolbox/src/Sign.js

@@ -0,0 +1,194 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { I18nProps as Props } from '@polkadot/ui-app/types';
+import type { KeyringPair } from '@polkadot/util-keyring/types';
+
+import React from 'react';
+
+import Button from '@polkadot/ui-app/src/Button';
+import Input from '@polkadot/ui-app/src/Input';
+import InputAddress from '@polkadot/ui-app/src/InputAddress';
+import Output from '@polkadot/ui-app/src/Output';
+import Static from '@polkadot/ui-app/src/Static';
+import classes from '@polkadot/ui-app/src/util/classes';
+import keyring from '@polkadot/ui-keyring/src';
+import hexToU8a from '@polkadot/util/hex/toU8a';
+import isHex from '@polkadot/util/is/hex';
+import u8aFromString from '@polkadot/util/u8a/fromString';
+import u8aToHex from '@polkadot/util/u8a/toHex';
+
+import Unlock from './Unlock';
+import translate from './translate';
+
+type State = {
+  currentPair: KeyringPair | null,
+  defaultPublicKey?: Uint8Array,
+  data: string,
+  isHexData: boolean,
+  isLocked: boolean,
+  isUnlockVisible: boolean,
+  signature: string
+};
+
+class Sign extends React.PureComponent<Props, State> {
+  state: State;
+
+  constructor (props: Props) {
+    super(props);
+
+    const pairs = keyring.getPairs();
+    const currentPair = pairs[0] || null;
+
+    this.state = {
+      currentPair,
+      defaultPublicKey: currentPair
+        ? currentPair.publicKey()
+        : void 0,
+      data: '',
+      isHexData: false,
+      isLocked: currentPair
+        ? !currentPair.hasSecretKey()
+        : false,
+      isUnlockVisible: false,
+      signature: ''
+    };
+  }
+
+  render (): React$Node {
+    const { className, style, t } = this.props;
+    const { currentPair, data, defaultPublicKey, isHexData, isLocked, isUnlockVisible, signature } = this.state;
+
+    return (
+      <div
+        className={classes('toolbox--Sign', className)}
+        style={style}
+      >
+        <div className='ui--row'>
+          <InputAddress
+            className='full'
+            defaultValue={defaultPublicKey}
+            isInput={false}
+            label={t('sign.account', {
+              defaultValue: 'using my account'
+            })}
+            onChange={this.onChangeAccount}
+            type='account'
+          />
+        </div>
+        <div className='ui--row'>
+          <Input
+            className='large'
+            label={t('sign.data', {
+              defaultValue: 'sign the following data (hex or string)'
+            })}
+            onChange={this.onChangeData}
+            value={data}
+          />
+          <Static
+            className='small'
+            label={t('sign.isHex', {
+              defaultValue: 'hex input data'
+            })}
+            value={
+              isHexData
+                ? t('sign.isHex.yes', {
+                  defaultValue: 'Yes'
+                })
+                : t('sign.isHex.no', {
+                  defaultValue: 'No'
+                })
+            }
+          />
+        </div>
+        <div className='ui--row'>
+          <Output
+            className='full toolbox--hex'
+            isHidden={signature.length === 0}
+            label={t('sign.signed', {
+              defaultValue: 'signature of supplied data'
+            })}
+            value={signature}
+            withCopy
+          />
+        </div>
+        <Unlock
+          isVisible={isUnlockVisible}
+          onClose={this.toggleUnlock}
+          pair={currentPair}
+        />
+        {
+          isLocked
+            ? (
+              <Button.Group>
+                <Button
+                  isPrimary
+                  onClick={this.toggleUnlock}
+                  text={t('sign.unlock', {
+                    defaultValue: 'Unlock account'
+                  })}
+                />
+              </Button.Group>
+            )
+            : null
+        }
+      </div>
+    );
+  }
+
+  nextState = (newState: $Shape<State>): void => {
+    this.setState(
+      (prevState: State): $Shape<State> => {
+        const { currentPair = prevState.currentPair, data = prevState.data, isHexData = prevState.isHexData, isUnlockVisible = prevState.isUnlockVisible } = newState;
+        const isLocked = !currentPair || !currentPair.hasSecretKey();
+        let signature = '';
+
+        if (!isLocked && currentPair) {
+          signature = u8aToHex(
+            currentPair.sign(
+              isHexData
+                ? hexToU8a(data)
+                : u8aFromString(data)
+            )
+          );
+        }
+
+        return {
+          currentPair,
+          data,
+          isHexData,
+          isLocked,
+          isUnlockVisible,
+          signature
+        };
+      }
+    );
+  }
+
+  toggleUnlock = (): void => {
+    const { isUnlockVisible } = this.state;
+
+    this.nextState({
+      isUnlockVisible: !isUnlockVisible
+    });
+  }
+
+  onChangeAccount = (publicKey: Uint8Array): void => {
+    const currentPair = keyring.getPair(publicKey);
+
+    this.nextState({ currentPair });
+  }
+
+  onChangeData = (data: string): void => {
+    const isHexData = isHex(data);
+
+    this.nextState({ data, isHexData });
+  }
+
+  onUnlock = (): void => {
+  }
+}
+
+export default translate(Sign);

+ 182 - 0
packages/app-toolbox/src/Unlock.js

@@ -0,0 +1,182 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { I18nProps } from '@polkadot/ui-app/types';
+import type { KeyringPair } from '@polkadot/util-keyring/types';
+
+import React from 'react';
+import { Trans } from 'react-i18next';
+
+import Button from '@polkadot/ui-app/src/Button';
+import Modal from '@polkadot/ui-app/src/Modal';
+import Password from '@polkadot/ui-app/src/Password';
+import classes from '@polkadot/ui-app/src/util/classes';
+import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
+
+import translate from './translate';
+
+type Props = I18nProps & {
+  isVisible: boolean,
+  onClose: () => void,
+  pair: KeyringPair
+};
+
+type UnlockI18n = {
+  key: string,
+  value: I18Next$Translate$Config
+}
+
+type State = {
+  address: string,
+  password: string,
+  unlockError: UnlockI18n | null
+};
+
+class Unlock extends React.PureComponent<Props, State> {
+  state: State = {
+    address: '',
+    password: '',
+    unlockError: null
+  };
+
+  static getDerivedStateFromProps ({ pair }: Props, prevState: State): $Shape<State> {
+    const address = pair
+      ? pair.address()
+      : '';
+
+    const isSame = address !== prevState.address;
+
+    return {
+      address,
+      password: isSame
+        ? prevState.password
+        : '',
+      unlockError: isSame
+        ? prevState.unlockError
+        : null
+    };
+  }
+
+  render (): React$Node {
+    const { className, isVisible, style, t } = this.props;
+    const { address, password, unlockError } = this.state;
+
+    if (!isVisible) {
+      return null;
+    }
+
+    return (
+      <Modal
+        className={classes('toolbox--Unlock', className)}
+        dimmer='inverted'
+        open
+        style={style}
+      >
+        <Modal.Header>
+          {t('unlock.header', {
+            defaultValue: 'Unlock account'
+          })}
+        </Modal.Header>
+        <Modal.Content>
+          <div className='toolbox--Unlock-Content'>
+            <div className='expanded'>
+              <p>
+                <Trans i18nkey='unlock.info'>
+                  You are about to unlock your account <span className='code'>{address}</span> to allow for the signing of messages.
+                </Trans>
+              </p>
+            </div>
+            <IdentityIcon
+              className='icon'
+              value={address}
+            />
+          </div>
+          <div className='toolbox--Unlock-Entry'>
+            <div className='ui--row'>
+              <Password
+                className='medium'
+                isError={!!unlockError}
+                label={t('unlock.password', {
+                  defaultValue: 'unlock account using'
+                })}
+                onChange={this.onChangePassword}
+                value={password}
+              />
+            </div>
+          </div>
+        </Modal.Content>
+        <Modal.Actions>
+          <Button.Group>
+            <Button
+              isNegative
+              onClick={this.onCancel}
+              text={t('unlock.cancel', {
+                defaultValue: 'Cancel'
+              })}
+            />
+            <Button.Or />
+            <Button
+              isPrimary
+              onClick={this.onUnlock}
+              text={t('unlock.doit', {
+                defaultValue: 'Unlock'
+              })}
+            />
+          </Button.Group>
+        </Modal.Actions>
+      </Modal>
+    );
+  }
+
+  unlockAccount (password?: string): ?UnlockI18n {
+    const { pair } = this.props;
+
+    if (pair.hasSecretKey()) {
+      return null;
+    }
+
+    try {
+      // $FlowFixMe typo in underlying type, fixed at base (upgrades)
+      pair.decodePkcs8(void 0, password);
+    } catch (error) {
+      return {
+        key: 'unlock.generic',
+        value: {
+          defaultValue: error.message
+        }
+      };
+    }
+
+    return null;
+  }
+
+  onChangePassword = (password: string): void => {
+    this.setState({
+      password,
+      unlockError: null
+    });
+  }
+
+  onCancel = (): void => {
+    const { onClose } = this.props;
+
+    onClose();
+  }
+
+  onUnlock = (): void => {
+    const { onClose } = this.props;
+    const { password } = this.state;
+    const unlockError = this.unlockAccount(password);
+
+    if (unlockError) {
+      this.setState({ unlockError });
+      return;
+    }
+
+    onClose();
+  };
+}
+
+export default translate(Unlock);

+ 177 - 0
packages/app-toolbox/src/Verify.js

@@ -0,0 +1,177 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { I18nProps as Props } from '@polkadot/ui-app/types';
+
+import React from 'react';
+
+import Icon from '@polkadot/ui-app/src/Icon';
+import Input from '@polkadot/ui-app/src/Input';
+import InputAddress from '@polkadot/ui-app/src/InputAddress';
+import Static from '@polkadot/ui-app/src/Static';
+import classes from '@polkadot/ui-app/src/util/classes';
+import keyring from '@polkadot/ui-keyring/src';
+import hexToU8a from '@polkadot/util/hex/toU8a';
+import isHex from '@polkadot/util/is/hex';
+import u8aFromString from '@polkadot/util/u8a/fromString';
+import naclVerify from '@polkadot/util-crypto/nacl/verify';
+
+import translate from './translate';
+
+type State = {
+  currentPublicKey: Uint8Array | null,
+  defaultPublicKey?: Uint8Array,
+  data: string,
+  isHexData: boolean,
+  isValidAddress: boolean,
+  isValidSignature: boolean,
+  isValid: boolean,
+  signature: string
+};
+
+class Verify extends React.PureComponent<Props, State> {
+  state: State;
+
+  constructor (props: Props) {
+    super(props);
+
+    const pairs = keyring.getPairs();
+    const currentPair = pairs[0];
+    const currentPublicKey = currentPair
+      ? currentPair.publicKey()
+      : null;
+
+    this.state = {
+      currentPublicKey,
+      defaultPublicKey: currentPublicKey || void 0,
+      data: '',
+      isHexData: false,
+      isValidAddress: !!currentPair,
+      isValidSignature: false,
+      isValid: false,
+      signature: ''
+    };
+  }
+
+  render (): React$Node {
+    const { className, style, t } = this.props;
+    const { defaultPublicKey, data, isHexData, isValid, isValidAddress, isValidSignature, signature } = this.state;
+
+    return (
+      <div
+        className={classes('toolbox--Verify', className)}
+        style={style}
+      >
+        <div className='ui--row'>
+          <Input
+            className='large'
+            label={t('verify.data', {
+              defaultValue: 'using the following data (hex or string)'
+            })}
+            onChange={this.onChangeData}
+            value={data}
+          />
+          <Static
+            className='small'
+            label={t('verify.isHex', {
+              defaultValue: 'hex input data'
+            })}
+            value={
+              isHexData
+                ? t('verify.isHex.yes', {
+                  defaultValue: 'Yes'
+                })
+                : t('verify.isHex.no', {
+                  defaultValue: 'No'
+                })
+            }
+          />
+        </div>
+        <div className='ui--row'>
+          <InputAddress
+            className='full'
+            defaultValue={defaultPublicKey}
+            isError={!isValidAddress}
+            isInput
+            label={t('verify.account', {
+              defaultValue: 'verify using address'
+            })}
+            onChange={this.onChangeAddress}
+          />
+        </div>
+        <div className='ui--row'>
+          <Input
+            className='full'
+            icon={
+              <Icon
+                color={isValid ? 'green' : (isValidSignature ? 'red' : void 0)}
+                name={isValid ? 'check circle' : (isValidSignature ? 'exclamation circle' : 'help circle')}
+                size='big'
+              />
+            }
+            isError={!isValidSignature}
+            label={t('verify.signature', {
+              defaultValue: 'checking the supplied signature'
+            })}
+            onChange={this.onChangeSignature}
+            value={signature}
+          />
+        </div>
+      </div>
+    );
+  }
+
+  nextState (newState: $Shape<State>): void {
+    this.setState(
+      (prevState: State): $Shape<State> => {
+        const { isHexData = prevState.isHexData, isValidAddress = prevState.isValidAddress, isValidSignature = prevState.isValidSignature, currentPublicKey = prevState.currentPublicKey, data = prevState.data, signature = prevState.signature } = newState;
+
+        let isValid = isValidAddress && isValidSignature;
+
+        if (isValid && currentPublicKey) {
+          isValid = naclVerify(
+            isHexData
+              ? hexToU8a(data)
+              : u8aFromString(data),
+            hexToU8a(signature),
+            currentPublicKey
+          );
+        }
+
+        return {
+          isHexData,
+          isValid,
+          isValidAddress,
+          isValidSignature,
+          currentPublicKey,
+          data,
+          signature
+        };
+      }
+    );
+  }
+
+  onChangeData = (data: string): void => {
+    const isHexData = isHex(data);
+
+    this.nextState({ data, isHexData });
+  }
+
+  onChangeSignature = (signature: string): void => {
+    console.log('onChangeSig', isHex(signature), signature.length);
+
+    const isValidSignature = isHex(signature) && signature.length === 130;
+
+    this.nextState({ signature, isValidSignature });
+  }
+
+  onChangeAddress = (currentPublicKey: Uint8Array): void => {
+    const isValidAddress = currentPublicKey && currentPublicKey.length === 32;
+
+    this.nextState({ currentPublicKey, isValidAddress });
+  }
+}
+
+export default translate(Verify);

+ 41 - 0
packages/app-toolbox/src/index.css

@@ -0,0 +1,41 @@
+/* Copyright 2017-2018 Jaco Greeff
+/* This software may be modified and distributed under the terms
+/* of the ISC license. See the LICENSE file for details. */
+
+.toolbox--App {
+  padding: 1em 2em;
+}
+
+.toolbox--App-navigation.ui--Button-Group {
+  margin-bottom: 2em;
+  text-align: center;
+}
+
+.toolbox--hex .ui--output {
+  font-family: monospace;
+}
+
+.toolbox--Unlock-Content {
+  align-content: center;
+  display: flex;
+  line-height: 1.5em;
+}
+
+.toolbox--Unlock-Content .expanded {
+  padding-right: 2em;
+}
+
+.toolbox--Unlock-Content .expanded .code {
+  background: #f5f5f5;
+  font-family: monospace;
+  margin: 0 0.25em;
+  overflow-wrap: break-word;
+  padding: 0.25em 0.5em;
+  word-break: break-all;
+  word-break: break-word;
+  word-wrap: break-word;
+}
+
+.toolbox--Unlock-Entry {
+  padding-top: 1.5rem;
+}

+ 94 - 0
packages/app-toolbox/src/index.js

@@ -0,0 +1,94 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { I18nProps } from '@polkadot/ui-app/types';
+
+import './index.css';
+
+import React from 'react';
+
+import Button from '@polkadot/ui-app/src/Button';
+import classes from '@polkadot/ui-app/src/util/classes';
+
+import Hash from './Hash';
+import Sign from './Sign';
+import Verify from './Verify';
+import translate from './translate';
+
+type Actions = 'hash' | 'sign' | 'verify';
+
+type Props = I18nProps & {};
+
+type State = {
+  action: Actions
+}
+
+// FIXME React-router would probably be the best route, not home-grown
+// flowlint-next-line unclear-type:off
+const Components: { [Actions]: React$ComponentType<any> } = {
+  'hash': Hash,
+  'sign': Sign,
+  'verify': Verify
+};
+
+class App extends React.PureComponent<Props, State> {
+  state: State = {
+    action: 'hash'
+  };
+
+  render (): React$Node {
+    const { className, style, t } = this.props;
+    const { action } = this.state;
+    const Component = Components[action];
+
+    return (
+      <div
+        className={classes('toolbox--App', className)}
+        style={style}
+      >
+        <Button.Group className='accounts--App-navigation'>
+          <Button
+            isPrimary={action === 'hash'}
+            onClick={this.selectHash}
+            text={t('app.hash', {
+              defaultValue: 'Hash data'
+            })}
+          />
+          <Button.Or />
+          <Button
+            isPrimary={action === 'sign'}
+            onClick={this.selectSign}
+            text={t('app.sign', {
+              defaultValue: 'Sign message'
+            })}
+          />
+          <Button.Or />
+          <Button
+            isPrimary={action === 'verify'}
+            onClick={this.selectVerify}
+            text={t('app.verify', {
+              defaultValue: 'Verify signature'
+            })}
+          />
+        </Button.Group>
+        <Component />
+      </div>
+    );
+  }
+
+  selectHash = (): void => {
+    this.setState({ action: 'hash' });
+  }
+
+  selectSign = (): void => {
+    this.setState({ action: 'sign' });
+  }
+
+  selectVerify = (): void => {
+    this.setState({ action: 'verify' });
+  }
+}
+
+export default translate(App);

+ 8 - 0
packages/app-toolbox/src/translate.js

@@ -0,0 +1,8 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import { translate } from 'react-i18next';
+
+export default translate(['toolbox', 'ui']);

+ 2 - 2
packages/app-vanitygen/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/app-vanitygen",
-  "version": "0.4.12",
+  "version": "0.5.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -11,7 +11,7 @@
     "test": "jest --coverage"
   },
   "devDependencies": {
-    "@polkadot/ui-app": "^0.4.12",
+    "@polkadot/ui-app": "^0.5.0",
     "chalk": "^2.4.1",
     "yargs": "10.1.2"
   },

+ 2 - 1
packages/app-vanitygen/src/Match.js

@@ -9,6 +9,7 @@ import React from 'react';
 
 import CopyButton from '@polkadot/ui-app/src/CopyButton';
 import Button from '@polkadot/ui-app/src/Button';
+import classes from '@polkadot/ui-app/src/util/classes';
 import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
 import u8aToHex from '@polkadot/util/u8a/toHex';
 
@@ -39,7 +40,7 @@ export default class Match extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['vanity--Match', className].join(' ')}
+        className={classes('vanity--Match', className)}
         style={style}
       >
         <div className='vanity--Match-item'>

+ 8 - 10
packages/app-vanitygen/src/index.js

@@ -13,7 +13,8 @@ import React from 'react';
 import Button from '@polkadot/ui-app/src/Button';
 import Dropdown from '@polkadot/ui-app/src/Dropdown';
 import Input from '@polkadot/ui-app/src/Input';
-import Labelled from '@polkadot/ui-app/src/Labelled';
+import Static from '@polkadot/ui-app/src/Static';
+import classes from '@polkadot/ui-app/src/util/classes';
 
 import Match from './Match';
 import generator from './generator';
@@ -61,7 +62,7 @@ class App extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['vanity--App', className].join(' ')}
+        className={classes('vanity--App', className)}
         style={style}
       >
         <div className='ui--row'>
@@ -85,18 +86,15 @@ class App extends React.PureComponent<Props, State> {
             onChange={this.onChangeCase}
             value={withCase}
           />
-          <Labelled
+          <Static
             className='small'
             label={t('vanity.offset', {
               defaultValue: 'exact offset'
             })}
-          >
-            <div className='ui dropdown selection disabled'>
-              {t('vanity.offset.off', {
-                defaultValue: 'No'
-              })}
-            </div>
-          </Labelled>
+            value={t('vanity.offset.off', {
+              defaultValue: 'No'
+            })}
+          />
         </div>
         <Button.Group>
           <Button

+ 9 - 8
packages/apps/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/apps",
-  "version": "0.4.12",
+  "version": "0.5.0",
   "description": "An Apps portal into the Polkadot network",
   "main": "index.js",
   "homepage": ".",
@@ -14,12 +14,13 @@
     "test": "react-scripts test --env=jsdom --coverage"
   },
   "devDependencies": {
-    "@polkadot/app-accounts": "^0.4.12",
-    "@polkadot/app-addresses": "^0.4.12",
-    "@polkadot/app-explorer": "^0.4.12",
-    "@polkadot/app-extrinsics": "^0.4.12",
-    "@polkadot/app-storage": "^0.4.12",
-    "@polkadot/ui-app": "^0.4.12",
-    "react-remarkable": "^1.1.3"
+    "@polkadot/app-accounts": "^0.5.0",
+    "@polkadot/app-addresses": "^0.5.0",
+    "@polkadot/app-explorer": "^0.5.0",
+    "@polkadot/app-extrinsics": "^0.5.0",
+    "@polkadot/app-storage": "^0.5.0",
+    "@polkadot/app-toolbox": "^0.5.0",
+    "@polkadot/app-vanitygen": "^0.5.0",
+    "@polkadot/ui-app": "^0.5.0"
   }
 }

+ 3 - 1
packages/apps/src/App/index.js

@@ -9,6 +9,8 @@ import './App.css';
 
 import React from 'react';
 
+import classes from '@polkadot/ui-app/src/util/classes';
+
 import Connecting from '../Connecting';
 import Content from '../Content';
 import SideBar from '../SideBar';
@@ -19,7 +21,7 @@ type Props = I18nProps & {};
 function App ({ className, style }: Props): React$Node {
   return (
     <div
-      className={['apps--App', className].join(' ')}
+      className={classes('apps--App', className)}
       style={style}
     >
       <SideBar className='apps--App-column' />

+ 2 - 1
packages/apps/src/Connecting/index.js

@@ -9,6 +9,7 @@ import './Connecting.css';
 
 import React from 'react';
 
+import classes from '@polkadot/ui-app/src/util/classes';
 import withApiCall from '@polkadot/ui-react-rx/with/apiCall';
 
 import translate from '../translate';
@@ -25,7 +26,7 @@ function Connecting ({ className, style, value, t }: Props): React$Node {
   // 'ui inverted page modals dimmer transition visible active'
   return (
     <div
-      className={['apps--Connecting', className].join(' ')}
+      className={classes('apps--Connecting', className)}
       style={style}
     >
       <div className='apps--Connecting-text'>

+ 10 - 11
packages/apps/src/Content/index.js

@@ -11,29 +11,28 @@ import './Content.css';
 import React from 'react';
 import { withRouter } from 'react-router';
 
+import classes from '@polkadot/ui-app/src/util/classes';
+
 import routing from '../routing';
-import translate from '../translate';
 
 type Props = I18nProps & {
   location: Location
 };
 
-function Content ({ className, location, style }: Props): React$Node {
+function Content ({ children, className, location, style }: Props): React$Node {
   const app = location.pathname.slice(1) || routing.default;
+  const { Component } = routing.routes.find((route) =>
+    route && route.name === app
+  ) || routing.unknown;
 
   return (
     <div
-      className={['apps--Content', className].join(' ')}
+      className={classes('apps--Content', className)}
       style={style}
     >
-      <iframe
-        className='apps--Content-Frame'
-        src={`index.html?app=${app}`}
-        title={app}
-      />
+      <Component />
     </div>
   );
 }
-export default translate(
-  withRouter(Content)
-);
+
+export default withRouter(Content);

+ 3 - 1
packages/apps/src/NotFound/index.js

@@ -9,6 +9,8 @@ import './NotFound.css';
 
 import React from 'react';
 
+import classes from '@polkadot/ui-app/src/util/classes';
+
 import translate from '../translate';
 
 type Props = I18nProps & {};
@@ -16,7 +18,7 @@ type Props = I18nProps & {};
 function NotFound ({ className, style, t }: Props): React$Node {
   return (
     <div
-      className={['apps--NotFound', className].join(' ')}
+      className={classes('apps--NotFound', className)}
       style={style}
     >
       {t('notfound.error', {

+ 2 - 1
packages/apps/src/SideBar/index.js

@@ -11,6 +11,7 @@ import React from 'react';
 
 import Icon from '@polkadot/ui-app/src/Icon';
 import Menu from '@polkadot/ui-app/src/Menu';
+import classes from '@polkadot/ui-app/src/util/classes';
 
 import routing from '../routing';
 import translate from '../translate';
@@ -21,7 +22,7 @@ type Props = I18nProps & {};
 function SideBar ({ className, style, t }: Props): React$Node {
   return (
     <div
-      className={['apps--SideBar', className].join(' ')}
+      className={classes('apps--SideBar', className)}
       style={style}
     >
       <Menu

+ 1 - 18
packages/apps/src/index.js

@@ -6,22 +6,5 @@
 import createApp from '@polkadot/ui-app/src';
 
 import App from './App';
-import NotFound from './NotFound';
-import routing from './routing';
-import urlParams from './urlParams';
 
-createApp((() => {
-  const { app } = urlParams();
-
-  if (app) {
-    const route = routing.routes.find((route) =>
-      route && route.name === app
-    );
-
-    return route
-      ? route.component
-      : NotFound;
-  }
-
-  return App;
-})());
+createApp(App);

+ 1 - 1
packages/apps/src/routing/accounts.js

@@ -9,7 +9,7 @@ import Accounts from '@polkadot/app-accounts/src';
 
 export default ([
   {
-    component: Accounts,
+    Component: Accounts,
     i18n: {
       defaultValue: 'Accounts'
     },

+ 1 - 1
packages/apps/src/routing/addresses.js

@@ -9,7 +9,7 @@ import Addresses from '@polkadot/app-addresses/src';
 
 export default ([
   {
-    component: Addresses,
+    Component: Addresses,
     i18n: {
       defaultValue: 'Addresses'
     },

+ 1 - 1
packages/apps/src/routing/explorer.js

@@ -9,7 +9,7 @@ import Explorer from '@polkadot/app-explorer/src';
 
 export default ([
   {
-    component: Explorer,
+    Component: Explorer,
     i18n: {
       defaultValue: 'Explorer'
     },

+ 1 - 1
packages/apps/src/routing/extrinsics.js

@@ -9,7 +9,7 @@ import Extrinsics from '@polkadot/app-extrinsics/src';
 
 export default ([
   {
-    component: Extrinsics,
+    Component: Extrinsics,
     i18n: {
       defaultValue: 'Extrinsics'
     },

+ 5 - 2
packages/apps/src/routing/index.js

@@ -12,11 +12,14 @@ import extrinsics from './extrinsics';
 // import home from './home';
 // import settings from './settings';
 import storage from './storage';
+import toolbox from './toolbox';
+import unknown from './unknown';
 import vanitygen from './vanitygen';
 
 export default ({
   default: 'explorer',
   routes: [].concat(
-    explorer, extrinsics, storage, null, accounts, addresses, vanitygen
-  )
+    explorer, extrinsics, storage, toolbox, null, accounts, addresses, vanitygen
+  ),
+  unknown
 }: Routing);

+ 1 - 1
packages/apps/src/routing/storage.js

@@ -9,7 +9,7 @@ import Storage from '@polkadot/app-storage/src';
 
 export default ([
   {
-    component: Storage,
+    Component: Storage,
     i18n: {
       defaultValue: 'Storage'
     },

+ 21 - 0
packages/apps/src/routing/toolbox.js

@@ -0,0 +1,21 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { Routes } from '../types';
+
+import Toolbox from '@polkadot/app-toolbox/src';
+
+export default ([
+  {
+    Component: Toolbox,
+    i18n: {
+      defaultValue: 'Toolbox'
+    },
+    icon: 'configure',
+    isExact: false,
+    isHidden: false,
+    name: 'toolbox'
+  }
+]: Routes);

+ 19 - 0
packages/apps/src/routing/unknown.js

@@ -0,0 +1,19 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { Route } from '../types';
+
+import NotFound from '../NotFound';
+
+export default ({
+  Component: NotFound,
+  i18n: {
+    defaultValue: 'Not found'
+  },
+  icon: 'error',
+  isExact: false,
+  isHidden: true,
+  name: 'unknown'
+}: Route);

+ 1 - 1
packages/apps/src/routing/vanitygen.js

@@ -9,7 +9,7 @@ import Vanity from '@polkadot/app-vanitygen/src';
 
 export default ([
   {
-    component: Vanity,
+    Component: Vanity,
     i18n: {
       defaultValue: 'Vanity'
     },

+ 6 - 3
packages/apps/src/types.js

@@ -4,9 +4,11 @@
 // @flow
 
 export type Route = {
-  component: React$ComponentType<*>,
+  // flowlint-next-line unclear-type:off
+  Component: React$ComponentType<any>,
   i18n: I18Next$Translate$Config,
-  icon: React$ComponentType<*> | string,
+  // flowlint-next-line unclear-type:off
+  icon: React$ComponentType<any> | string,
   isExact: boolean,
   isHidden: boolean,
   name: string,
@@ -17,5 +19,6 @@ export type Routes = Array<Route | null>;
 
 export type Routing = {
   default: string,
-  routes: Routes
+  routes: Routes,
+  unknown: Route
 }

+ 0 - 24
packages/apps/src/urlParams.js

@@ -1,24 +0,0 @@
-// Copyright 2017-2018 Jaco Greeff
-// This software may be modified and distributed under the terms
-// of the ISC license. See the LICENSE file for details.
-// @flow
-
-type Params = {
-  [string]: string
-};
-
-export default function urlParams (): Params {
-  if (window.location.search.indexOf('?') !== 0) {
-    return {};
-  }
-
-  const parts = window.location.search.slice(1).split('&');
-
-  return parts.reduce((params, param) => {
-    const [name, value] = param.split('=');
-
-    params[name] = value;
-
-    return params;
-  }, {});
-}

+ 9 - 11
packages/ui-app/package.json

@@ -1,6 +1,6 @@
 {
   "name": "@polkadot/ui-app",
-  "version": "0.4.12",
+  "version": "0.5.0",
   "main": "index.js",
   "repository": "https://github.com/polkadot-js/apps.git",
   "author": "Jaco Greeff <jacogr@gmail.com>",
@@ -11,13 +11,14 @@
     "test": "jest --coverage"
   },
   "dependencies": {
-    "@polkadot/extrinsics-substrate": "^0.13.8",
-    "@polkadot/params": "^0.13.8",
-    "@polkadot/storage-substrate": "^0.13.8",
-    "@polkadot/ui-keyring": "^0.4.12",
-    "@polkadot/ui-react": "^0.11.9",
-    "@polkadot/ui-react-rx": "^0.11.9",
-    "@polkadot/util": "^0.19.6",
+    "@polkadot/extrinsics-substrate": "^0.13.10",
+    "@polkadot/params": "^0.13.10",
+    "@polkadot/primitives": "^0.13.10",
+    "@polkadot/storage-substrate": "^0.13.10",
+    "@polkadot/ui-keyring": "^0.5.0",
+    "@polkadot/ui-react": "^0.11.10",
+    "@polkadot/ui-react-rx": "^0.11.10",
+    "@polkadot/util": "^0.19.7",
     "i18next": "^11.1.1",
     "i18next-browser-languagedetector": "^2.2.0",
     "i18next-xhr-backend": "^1.5.1",
@@ -29,8 +30,5 @@
     "semantic-ui-css": "^2.3.1",
     "semantic-ui-react": "^0.79.1",
     "store": "^2.0.12"
-  },
-  "devDepependencies": {
-    "@polkadot/primitives": "^0.12.6"
   }
 }

+ 3 - 1
packages/ui-app/src/Button/Divider.js

@@ -7,12 +7,14 @@ import type { BareProps } from '../types';
 
 import React from 'react';
 
+import classes from '../util/classes';
+
 type Props = BareProps;
 
 function ButtonDivider ({ className, style }: Props): React$Node {
   return (
     <div
-      className={['ui button compact mini basic', className].join(' ')}
+      className={classes('ui button compact mini basic', className)}
       style={style}
     />
   );

+ 2 - 1
packages/ui-app/src/Button/Group.js

@@ -8,6 +8,7 @@ import type { BareProps } from '../types';
 import React from 'react';
 import SUIButton from 'semantic-ui-react/dist/es/elements/Button';
 
+import classes from '../util/classes';
 import Divider from './Divider';
 
 type Props = BareProps & {
@@ -17,7 +18,7 @@ type Props = BareProps & {
 function ButtonGroup ({ children, className, style }: Props): React$Node {
   return (
     <div
-      className={['ui--Button-Group', className].join(' ')}
+      className={classes('ui--Button-Group', className)}
       style={style}
     >
       <SUIButton.Group>

+ 1 - 1
packages/ui-app/src/CopyButton.js

@@ -16,7 +16,7 @@ type Props = BareProps & {
   isCircular?: boolean,
   isPrimary?: boolean,
   size?: Button$Sizes,
-  value?: string
+  value?: mixed
 }
 
 export default function CopyButton ({ className, icon = 'copy', isCircular = true, isPrimary = true, size = 'tiny', style, value }: Props): React$Node {

+ 8 - 4
packages/ui-app/src/Dropdown.js

@@ -8,6 +8,7 @@ import type { BareProps } from './types';
 import React from 'react';
 import SUIDropdown from 'semantic-ui-react/dist/es/modules/Dropdown';
 
+import classes from './util/classes';
 import Labelled from './Labelled';
 
 type Props = BareProps & {
@@ -17,8 +18,10 @@ type Props = BareProps & {
   label?: React$Node,
   // flowlint-next-line unclear-type:off
   onChange: (value: any) => void,
-  onSearch?: (filteredOptions: Array<*>, query: string) => Array<*>,
-  options: Array<*>,
+  // flowlint-next-line unclear-type:off
+  onSearch?: (filteredOptions: Array<any>, query: string) => Array<any>,
+  // flowlint-next-line unclear-type:off
+  options: Array<any>,
   // flowlint-next-line unclear-type:off
   transform?: (value: any) => any,
   value?: mixed,
@@ -36,7 +39,7 @@ export default class Dropdown extends React.PureComponent<Props> {
 
     return (
       <Labelled
-        className={['ui--Dropdown', className].join(' ')}
+        className={classes('ui--Dropdown', className)}
         label={label}
         style={style}
         withLabel={withLabel}
@@ -55,7 +58,8 @@ export default class Dropdown extends React.PureComponent<Props> {
     );
   }
 
-  onChange = (event: SyntheticEvent<*>, { value }: SUIEvent): void => {
+  // flowlint-next-line unclear-type:off
+  onChange = (event: SyntheticEvent<any>, { value }: SUIEvent): void => {
     const { onChange, transform } = this.props;
 
     onChange(

+ 22 - 3
packages/ui-app/src/Input.js

@@ -8,6 +8,8 @@ import type { BareProps } from './types';
 import React from 'react';
 import SUIInput from 'semantic-ui-react/dist/es/elements/Input';
 
+import isUndefined from '@polkadot/util/is/undefined';
+
 import Labelled from './Labelled';
 
 type Input$Type = 'number' | 'password' | 'text';
@@ -15,9 +17,11 @@ type Input$Type = 'number' | 'password' | 'text';
 type Props = BareProps & {
   children?: React$Node,
   defaultValue?: mixed,
+  icon?: React$Node,
   isAction?: boolean,
   isDisabled?: boolean,
   isError?: boolean,
+  isHidden?: boolean,
   label?: React$Node,
   max?: mixed,
   min?: mixed,
@@ -46,7 +50,7 @@ export default class Input extends React.PureComponent<Props, State> {
   };
 
   render (): React$Node {
-    const { children, className, defaultValue, isAction = false, isDisabled = false, isError = false, label, max, min, placeholder, style, type = 'text', value, withLabel } = this.props;
+    const { children, className, defaultValue, icon, isAction = false, isDisabled = false, isError = false, isHidden = false, label, max, min, placeholder, style, type = 'text', value, withLabel } = this.props;
     const { name } = this.state;
 
     return (
@@ -60,7 +64,14 @@ export default class Input extends React.PureComponent<Props, State> {
           action={isAction}
           defaultValue={defaultValue}
           disabled={isDisabled}
+          id={name}
+          iconPosition={
+            isUndefined(icon)
+              ? void 0
+              : 'left'
+          }
           error={isError}
+          hidden={isHidden}
           max={max}
           min={min}
           name={name}
@@ -69,14 +80,22 @@ export default class Input extends React.PureComponent<Props, State> {
           type={type}
           value={value}
         >
-          <input />
+          <input
+            autoComplete={
+              type === 'password'
+                ? 'new-password'
+                : 'off'
+            }
+          />
+          {icon}
           {children}
         </SUIInput>
       </Labelled>
     );
   }
 
-  onChange = (event: SyntheticEvent<*>, { value }: SUIEvent): void => {
+  // flowlint-next-line unclear-type:off
+  onChange = (event: SyntheticEvent<any>, { value }: SUIEvent): void => {
     this.props.onChange(value);
   }
 }

+ 2 - 2
packages/ui-app/src/InputAddress/InputAddress.css

@@ -8,8 +8,8 @@
   width: 100%;
 }
 
-.ui--InputAddress input.search {
-  margin-left: 3rem;
+.ui--InputAddress .ui.search.selection.dropdown > input.search {
+  padding-left: 4rem;
 }
 
 .flag--hideAddress .ui--KeyPair .ui--KeyPair-address {

+ 2 - 1
packages/ui-app/src/InputAddress/index.js

@@ -15,6 +15,7 @@ import createOptionHeader from '@polkadot/ui-keyring/src/options/header';
 import addressDecode from '@polkadot/util-keyring/address/decode';
 
 import Dropdown from '../Dropdown';
+import classes from '../util/classes';
 import addressToAddress from './addressToAddress';
 
 type Props = BareProps & {
@@ -68,7 +69,7 @@ export default class InputAddress extends React.Component<Props, State> {
 
     return (
       <Dropdown
-        className={['ui--InputAddress', hideAddress ? 'flag--hideAddress' : '', className].join(' ')}
+        className={classes('ui--InputAddress', hideAddress ? 'flag--hideAddress' : '', className)}
         defaultValue={defaultValue}
         isError={isError}
         label={label}

+ 2 - 1
packages/ui-app/src/InputExtrinsic/SelectMethod.js

@@ -11,6 +11,7 @@ import React from 'react';
 import extrinsics from '@polkadot/extrinsics-substrate';
 
 import Dropdown from '../Dropdown';
+import classes from '../util/classes';
 import translate from '../translate';
 import createOptions from './options/method';
 
@@ -36,7 +37,7 @@ function SelectMethod ({ className, isError, label = '', onChange, style, t, typ
 
   return (
     <Dropdown
-      className={['ui--DropdownLinked-Items', className].join(' ')}
+      className={classes('ui--DropdownLinked-Items', className)}
       isError={isError}
       label={label || t('input.extrinsic.method', {
         defaultValue: 'with the extrinsic'

+ 2 - 1
packages/ui-app/src/InputExtrinsic/SelectSection.js

@@ -9,6 +9,7 @@ import type { I18nProps } from '../types';
 import React from 'react';
 
 import Dropdown from '../Dropdown';
+import classes from '../util/classes';
 import translate from '../translate';
 import createOptions from './options/section';
 
@@ -27,7 +28,7 @@ function SelectSection ({ className, defaultValue, isError, label = '', onChange
 
   return (
     <Dropdown
-      className={['ui--DropdownLinked-Sections', className].join(' ')}
+      className={classes('ui--DropdownLinked-Sections', className)}
       defaultValue={defaultValue}
       isError={isError}
       label={label || t('input.extrinsic.section', {

+ 2 - 1
packages/ui-app/src/InputExtrinsic/index.js

@@ -13,6 +13,7 @@ import './InputExtrinsic.css';
 import React from 'react';
 import map from '@polkadot/extrinsics-substrate';
 
+import classes from '../util/classes';
 import translate from '../translate';
 import SelectMethod from './SelectMethod';
 import SelectSection from './SelectSection';
@@ -50,7 +51,7 @@ class InputExtrinsic extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['ui--DropdownLinked', 'ui--row', className].join(' ')}
+        className={classes('ui--DropdownLinked', 'ui--row', className)}
         style={style}
       >
         <SelectSection

+ 1 - 1
packages/ui-app/src/InputExtrinsic/options/method.js

@@ -10,7 +10,7 @@ import React from 'react';
 import map from '@polkadot/extrinsics-substrate';
 
 // flowlint-next-line unclear-type:off
-export default function createOptions (sectionName: any, type: 'private' | 'public'): Array<*> {
+export default function createOptions (sectionName: any, type: 'private' | 'public'): Array<any> {
   const section = map[(sectionName: ExtrinsicSectionName)];
 
   if (!section) {

+ 2 - 1
packages/ui-app/src/InputExtrinsic/options/section.js

@@ -5,7 +5,8 @@
 
 import map from '@polkadot/extrinsics-substrate';
 
-export default function createOptions (type: 'private' | 'public'): Array<*> {
+// flowlint-next-line unclear-type:off
+export default function createOptions (type: 'private' | 'public'): Array<any> {
   return Object
     .keys(map)
     .sort()

+ 2 - 1
packages/ui-app/src/InputStorage/SelectKey.js

@@ -11,6 +11,7 @@ import React from 'react';
 import map from '@polkadot/storage-substrate/keys';
 
 import Dropdown from '../Dropdown';
+import classes from '../util/classes';
 import translate from '../translate';
 import createOptions from './options/key';
 
@@ -35,7 +36,7 @@ function SelectKey ({ className, isError, label = '', onChange, style, t, value:
 
   return (
     <Dropdown
-      className={['ui--DropdownLinked-Items', className].join(' ')}
+      className={classes('ui--DropdownLinked-Items', className)}
       isError={isError}
       label={label || t('input.storage.key', {
         defaultValue: 'with storage key'

+ 2 - 1
packages/ui-app/src/InputStorage/SelectSection.js

@@ -9,6 +9,7 @@ import type { I18nProps } from '../types';
 import React from 'react';
 
 import Dropdown from '../Dropdown';
+import classes from '../util/classes';
 import translate from '../translate';
 import createOptions from './options/section';
 
@@ -26,7 +27,7 @@ const options = createOptions();
 function SelectSection ({ className, defaultValue, isError, label, onChange, style, t, value: { section }, withLabel }: Props): React$Node {
   return (
     <Dropdown
-      className={['ui--DropdownLinked-Sections', className].join(' ')}
+      className={classes('ui--DropdownLinked-Sections', className)}
       defaultValue={defaultValue}
       isError={isError}
       label={label || t('input.storage.section', {

+ 3 - 1
packages/ui-app/src/InputStorage/index.js

@@ -11,8 +11,10 @@ import type { I18nProps } from '../types';
 import '../InputExtrinsic/InputExtrinsic.css';
 
 import React from 'react';
+
 import map from '@polkadot/storage-substrate/keys';
 
+import classes from '../util/classes';
 import translate from '../translate';
 import SelectKey from './SelectKey';
 import SelectSection from './SelectSection';
@@ -48,7 +50,7 @@ class InputStorage extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['ui--DropdownLinked', 'ui--row', className].join(' ')}
+        className={classes('ui--DropdownLinked', 'ui--row', className)}
         style={style}
       >
         <SelectSection

+ 1 - 1
packages/ui-app/src/InputStorage/options/key.js

@@ -10,7 +10,7 @@ import React from 'react';
 import map from '@polkadot/storage-substrate/keys';
 
 // flowlint-next-line unclear-type:off
-export default function createOptions (sectionName: any): Array<*> {
+export default function createOptions (sectionName: any): Array<any> {
   const section = map[(sectionName: StateDb$SectionNames)];
 
   if (!section) {

+ 2 - 1
packages/ui-app/src/InputStorage/options/section.js

@@ -5,7 +5,8 @@
 
 import map from '@polkadot/storage-substrate/keys';
 
-export default function createOptions (): Array<*> {
+// flowlint-next-line unclear-type:off
+export default function createOptions (): Array<any> {
   return Object
     .keys(map)
     .sort()

+ 9 - 2
packages/ui-app/src/Labelled.js

@@ -8,7 +8,10 @@ import type { BareProps } from './types';
 import React from 'react';
 import Label from 'semantic-ui-react/dist/es/elements/Label';
 
+import classes from './util/classes';
+
 type Props = BareProps & {
+  isHidden?: boolean,
   // flowlint-next-line unclear-type:off
   label?: any,
   children: React$Node,
@@ -19,10 +22,14 @@ const defaultLabel: React$Node = (
   <div>&nbsp;</div>
 );
 
-export default function Labelled ({ className, children, label = defaultLabel, style, withLabel = true }: Props): React$Node {
+export default function Labelled ({ className, children, isHidden = false, label = defaultLabel, style, withLabel = true }: Props): React$Node {
+  if (isHidden) {
+    return false;
+  }
+
   return (
     <div
-      className={className}
+      className={classes('ui--Labelled', className)}
       style={style}
     >
       {

+ 47 - 0
packages/ui-app/src/Output.js

@@ -0,0 +1,47 @@
+// Copyright 2017-2018 Jaco Greeff
+// This software may be modified and distributed under the terms
+// of the ISC license. See the LICENSE file for details.
+// @flow
+
+import type { BareProps } from './types';
+
+import React from 'react';
+
+import CopyButton from './CopyButton';
+import Labelled from './Labelled';
+
+type Props = BareProps & {
+  children?: React$Node,
+  isHidden?: boolean,
+  label?: React$Node,
+  value?: mixed,
+  withCopy?: boolean,
+  withLabel?: boolean
+};
+
+export default function Output ({ className, children, isHidden, label, style, value, withCopy = false, withLabel }: Props): React$Node {
+  return (
+    <Labelled
+      className={className}
+      isHidden={isHidden}
+      label={label}
+      style={style}
+      withLabel={withLabel}
+    >
+      <div className='ui--output'>
+        {
+          // flowlint-next-line unclear-type:off
+          ((value: any): React$Node)
+        }
+        {children}
+        {
+          withCopy
+            ? (
+              <CopyButton className='ui--output-button' value={value} />
+            )
+            : null
+        }
+      </div>
+    </Labelled>
+  );
+}

+ 3 - 1
packages/ui-app/src/Params/Param/Bare.js

@@ -7,6 +7,8 @@ import type { BareProps } from '../../types';
 
 import React from 'react';
 
+import classes from '../../util/classes';
+
 type Props = BareProps & {
   children: React$Node
 };
@@ -14,7 +16,7 @@ type Props = BareProps & {
 export default function Bare ({ children, className, style }: Props): React$Node {
   return (
     <div
-      className={['ui--row', className].join(' ')}
+      className={classes('ui--row', className)}
       style={style}
     >
       {children}

+ 2 - 1
packages/ui-app/src/Params/Param/File.js

@@ -8,6 +8,7 @@ import type { BareProps } from '@polkadot/ui-app/types';
 import React from 'react';
 import Dropzone from 'react-dropzone';
 
+import classes from '../../util/classes';
 import translate from '../../translate';
 import Base from './Base';
 
@@ -41,7 +42,7 @@ class BytesFile extends React.PureComponent<Props, State> {
         withLabel={withLabel}
       >
         <Dropzone
-          className={['ui--Param-File', isError ? 'error' : '', className].join(' ')}
+          className={classes('ui--Param-File', isError ? 'error' : '', className)}
           disabled={isDisabled}
           multiple={false}
           onDrop={this.onDrop}

+ 2 - 1
packages/ui-app/src/Params/Param/findComponent.js

@@ -41,7 +41,8 @@ const components: ComponentMap = {
   'VoteThreshold': VoteThreshold
 };
 
-export default function findComponent (type: Param$Types, overrides?: ComponentMap = {}): React$ComponentType<*> | Array<React$ComponentType<*>> {
+// flowlint-next-line unclear-type:off
+export default function findComponent (type: Param$Types, overrides?: ComponentMap = {}): React$ComponentType<any> | Array<React$ComponentType<any>> {
   if (Array.isArray(type)) {
     return type
       .map((type) =>

+ 10 - 6
packages/ui-app/src/Params/Param/index.js

@@ -9,6 +9,7 @@ import type { BaseProps, ComponentMap, RawParam } from '../types';
 
 import React from 'react';
 
+import classes from '../../util/classes';
 import translate from '../../translate';
 import typeToText from '../typeToText';
 import findComponent from './findComponent';
@@ -28,7 +29,8 @@ type ComponentProps = BaseProps & {
 };
 
 type State = {
-  Components: React$ComponentType<*> | Array<React$ComponentType<*>>
+  // flowlint-next-line unclear-type:off
+  Components: React$ComponentType<any> | Array<React$ComponentType<any>>
   | null
 }
 
@@ -60,7 +62,8 @@ class ParamComponent extends React.PureComponent<Props, State> {
       : this.renderComponents();
   }
 
-  renderComponents = (_Components: React$ComponentType<*> | Array<React$ComponentType<*>> | null = this.state.Components, startIndex: string = '0', { name, type, options = {} } = this.props.value) => {
+  // flowlint-next-line unclear-type:off
+  renderComponents = (_Components: React$ComponentType<any> | Array<React$ComponentType<any>> | null = this.state.Components, startIndex: string = '0', { name, type, options = {} } = this.props.value) => {
     if (!_Components) {
       return null;
     }
@@ -69,7 +72,7 @@ class ParamComponent extends React.PureComponent<Props, State> {
 
     if (!Array.isArray(type)) {
       // flowlint-next-line unclear-type:off
-      const Component = ((_Components: any): React$ComponentType<*>);
+      const Component = ((_Components: any): React$ComponentType<any>);
 
       return this.renderComponent(Component, startIndex, {
         className,
@@ -85,7 +88,7 @@ class ParamComponent extends React.PureComponent<Props, State> {
     }
 
     // flowlint-next-line unclear-type:off
-    const Components = ((_Components: any): Array<React$ComponentType<*>>);
+    const Components = ((_Components: any): Array<React$ComponentType<any>>);
 
     return Components.map((Component, index) => {
       return this.renderComponents(Component, `${startIndex}-${index}`, {
@@ -96,13 +99,14 @@ class ParamComponent extends React.PureComponent<Props, State> {
     });
   }
 
-  renderComponent = (Component: React$ComponentType<*>, sub: string, props: ComponentProps): React$Node => {
+  // flowlint-next-line unclear-type:off
+  renderComponent = (Component: React$ComponentType<any>, sub: string, props: ComponentProps): React$Node => {
     const { className, index, onChange, style, value: { name, type, options = {} } } = props;
     const text = typeToText(type);
 
     return (
       <Component
-        className={['ui--Param', className].join(' ')}
+        className={classes('ui--Param', className)}
         index={index}
         key={`${name}:${text}:${index}}`}
         label={`${name}: ${text} (${index})`}

+ 2 - 1
packages/ui-app/src/Params/index.js

@@ -11,6 +11,7 @@ import './Params.css';
 
 import React from 'react';
 
+import classes from '../util/classes';
 import translate from '../translate';
 import Param from './Param';
 import createValues from './values';
@@ -62,7 +63,7 @@ class Params extends React.PureComponent<Props, State> {
 
     return (
       <div
-        className={['ui--Params', className].join(' ')}
+        className={classes('ui--Params', className)}
         style={style}
       >
         <div className='ui--Params-Content'>

Some files were not shown because too many files changed in this diff