Include ui-* packages in repo (development speed) (#65)

* Include ui-* packages in repo (development speed)

* Bump

* Bump

* Cleanup dependencies
This commit is contained in:
Jaco Greeff 2018-05-29 17:33:29 +02:00 committed by GitHub
parent 4d1156bbfb
commit ba36eede0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
105 changed files with 2589 additions and 94 deletions

View File

@ -15,4 +15,4 @@ all=warn
include_warnings=true
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\|signer\)\(.*\)$' -> '<PROJECT_ROOT>/packages/ui-\1/src\2'
module.name_mapper='^@polkadot/ui-\(app\|identicon\|keyring\|react-rx\|react\|signer\)\(.*\)$' -> '<PROJECT_ROOT>/packages/ui-\1/src\2'

View File

@ -29,5 +29,21 @@ The repo is split into a number of packages, each representing an application. T
In addition the following libraries are also included in the repo. These are to be moved to the [@polkadot/ui](https://github.com/polkadot-js/ui/) repository once it reaches a base level of stability and usability. (At this point with the framework being tested on the apps above, it makes development easier having it close)
- [ui-keyring](packages/ui-keyring/) A browser-specific wrapper around the base [@polkadot/util-keyring](https://github.com/polkadot-js/util/) libary.
- [ui-app](packages/ui-app/) A reactive (using RxJS) application framework with a number of useful shared components.
- [ui-keyring](packages/ui-keyring/) A browser-specific wrapper around the base [@polkadot/util-keyring](https://github.com/polkadot-js/util/) library.
- [ui-signer](packages/ui-signer/) Signer implementation for apps.
- [packages/ui-identicon](packages/ui-identicon/) Identity icon generator with raw publicKey as input
- [packages/ui-react](packages/ui-react) A collection of base React components
- [packages/ui-react-rx](packages/ui-react-rx) Base components that use the RxJS Observable APIs
## Application
For development purposes, the apps entry point can be launched with `yarn run start` and accessing the application of [http://localhost:3000](http://localhost:3000)
## Demos
Demos for the different libraries can be viewed with `yarn run demo:<name>` and then browsing to [http://localhost:3000](http://localhost:3000). Available demo -
- `yarn run demo:identicon`
- `yarn run demo:ui`
- `yarn run demo:rx`

8
flow-typed/semver.js vendored Normal file
View File

@ -0,0 +1,8 @@
// @flow
declare module 'semver' {
declare module.exports: {
gt: (a: string, b: string) => boolean,
lt: (a: string, b: string) => boolean
}
}

View File

@ -1 +1,10 @@
module.exports = require('@polkadot/dev-react/config/jest');
const config = require('@polkadot/dev-react/config/jest');
module.exports = Object.assign({}, config, {
moduleNameMapper: {
'@polkadot/app-(accounts|addresses|explorer|extrinsics|rpc|storage|toolbox|vanitygen)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
'@polkadot/ui-(app|identicon|keyring|react-rx|react|signer)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'empty/object',
'\\.(css|less)$': 'empty/object'
}
});

View File

@ -10,5 +10,5 @@
"packages": [
"packages/*"
],
"version": "0.7.2"
"version": "0.17.0"
}

View File

@ -9,10 +9,13 @@
"build": "polkadot-dev-build-babel",
"check": "eslint packages && flow check",
"clean": "polkadot-dev-clean-build",
"demo:identicon": "webpack-serve --config packages/ui-identicon/webpack.config.js --content packages/ui-identicon --port 3000",
"demo:rx": "webpack-serve --config packages/ui-react-rx/webpack.config.js --content packages/ui-react-rx --port 3000",
"demo:ui": "webpack-serve --config packages/ui-react/webpack.config.js --content packages/ui-react --port 3000",
"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 && webpack-serve --config webpack.config.js --port 3000",
"test": "echo 'no tests executed'"
"test": "jest --coverage"
},
"devDependencies": {
"@polkadot/dev-react": "^0.19.11",

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-accounts",
"version": "0.7.2",
"version": "0.17.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -12,6 +12,6 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/ui-app": "^0.7.2"
"@polkadot/ui-app": "^0.17.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-addresses",
"version": "0.7.2",
"version": "0.17.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -12,7 +12,7 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/app-accounts": "^0.7.2",
"@polkadot/ui-app": "^0.7.2"
"@polkadot/app-accounts": "^0.17.0",
"@polkadot/ui-app": "^0.17.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-explorer",
"version": "0.7.2",
"version": "0.17.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -14,7 +14,7 @@
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/primitives-codec": "^0.17.2",
"@polkadot/primitives-json": "^0.17.2",
"@polkadot/ui-app": "^0.7.2",
"@polkadot/ui-app": "^0.17.0",
"@polkadot/util-crypto": "^0.22.2"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-extrinsics",
"version": "0.7.2",
"version": "0.17.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -13,8 +13,8 @@
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/extrinsics-codec": "^0.17.2",
"@polkadot/ui-app": "^0.7.2",
"@polkadot/ui-signer": "^0.7.2",
"@polkadot/ui-app": "^0.17.0",
"@polkadot/ui-signer": "^0.17.0",
"react-dropzone": "^4.2.9"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-rpc",
"version": "0.7.2",
"version": "0.17.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -12,8 +12,8 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/ui-app": "^0.7.2",
"@polkadot/ui-keyring": "^0.7.2",
"@polkadot/ui-signer": "^0.7.2"
"@polkadot/ui-app": "^0.17.0",
"@polkadot/ui-keyring": "^0.17.0",
"@polkadot/ui-signer": "^0.17.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-storage",
"version": "0.7.2",
"version": "0.17.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -12,6 +12,6 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/ui-app": "^0.7.2"
"@polkadot/ui-app": "^0.17.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-toolbox",
"version": "0.7.2",
"version": "0.17.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -12,7 +12,7 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/ui-app": "^0.7.2",
"@polkadot/ui-keyring": "^0.7.2"
"@polkadot/ui-app": "^0.17.0",
"@polkadot/ui-keyring": "^0.17.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-vanitygen",
"version": "0.7.2",
"version": "0.17.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -12,7 +12,7 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/ui-app": "^0.7.2",
"@polkadot/ui-app": "^0.17.0",
"chalk": "^2.4.1",
"yargs": "10.1.2"
},

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/apps",
"version": "0.7.2",
"version": "0.17.0",
"description": "An Apps portal into the Polkadot network",
"main": "index.js",
"homepage": ".",
@ -13,16 +13,16 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/app-accounts": "^0.7.2",
"@polkadot/app-addresses": "^0.7.2",
"@polkadot/app-explorer": "^0.7.2",
"@polkadot/app-extrinsics": "^0.7.2",
"@polkadot/app-rpc": "^0.7.2",
"@polkadot/app-storage": "^0.7.2",
"@polkadot/app-toolbox": "^0.7.2",
"@polkadot/app-vanitygen": "^0.7.2",
"@polkadot/ui-app": "^0.7.2",
"@polkadot/ui-signer": "^0.7.2"
"@polkadot/app-accounts": "^0.17.0",
"@polkadot/app-addresses": "^0.17.0",
"@polkadot/app-explorer": "^0.17.0",
"@polkadot/app-extrinsics": "^0.17.0",
"@polkadot/app-rpc": "^0.17.0",
"@polkadot/app-storage": "^0.17.0",
"@polkadot/app-toolbox": "^0.17.0",
"@polkadot/app-vanitygen": "^0.17.0",
"@polkadot/ui-app": "^0.17.0",
"@polkadot/ui-signer": "^0.17.0"
},
"devDependencies": {
"babel-loader": "^8.0.0-beta.2",

View File

@ -1,17 +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
import React from 'react';
import ReactDOM from 'react-dom';
import App from './index';
describe('App', () => {
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
});

View File

@ -10,7 +10,7 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const packages = ['app-accounts', 'app-addresses', 'app-explorer', 'app-extrinsics', 'app-rpc', 'app-storage', 'app-toolbox', 'app-vanitygen', 'ui-app', 'ui-keyring', 'ui-signer'];
const packages = ['app-accounts', 'app-addresses', 'app-explorer', 'app-extrinsics', 'app-rpc', 'app-storage', 'app-toolbox', 'app-vanitygen', 'ui-app', 'ui-identicon', 'ui-keyring', 'ui-react-rx', 'ui-react', 'ui-signer'];
function createWebpack ({ alias = {}, context, name = 'index' }) {
const pkgJson = require(path.join(context, 'package.json'));

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/ui-app",
"version": "0.7.2",
"version": "0.17.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -14,9 +14,9 @@
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/extrinsics": "^0.17.2",
"@polkadot/storage": "^0.17.2",
"@polkadot/ui-keyring": "^0.7.2",
"@polkadot/ui-react": "^0.14.3",
"@polkadot/ui-react-rx": "^0.14.3",
"@polkadot/ui-keyring": "^0.17.0",
"@polkadot/ui-react": "^0.17.0",
"@polkadot/ui-react-rx": "^0.17.0",
"i18next": "^11.1.1",
"i18next-browser-languagedetector": "^2.2.0",
"i18next-xhr-backend": "^1.5.1",

View File

@ -0,0 +1,16 @@
ISC License (ISC)
Copyright 2016 Dan Finlay
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.

View File

@ -0,0 +1,39 @@
[![polkadotjs](https://img.shields.io/badge/polkadot-js-orange.svg?style=flat-square)](https://polkadot.js.org)
![isc](https://img.shields.io/badge/license-ISC-lightgrey.svg?style=flat-square)
[![style](https://img.shields.io/badge/code%20style-semistandard-lightgrey.svg?style=flat-square)](https://github.com/Flet/semistandard)
[![npm](https://img.shields.io/npm/v/@polkadot/ui-identicon.svg?style=flat-square)](https://www.npmjs.com/package/@polkadot/ui-identicon)
[![travis](https://img.shields.io/travis/polkadot-js/apps.svg?style=flat-square)](https://travis-ci.org/polkadot-js/ui)
[![maintainability](https://img.shields.io/codeclimate/maintainability/polkadot-js/apps.svg?style=flat-square)](https://codeclimate.com/github/polkadot-js/apps/maintainability)
[![coverage](https://img.shields.io/coveralls/polkadot-js/apps.svg?style=flat-square)](https://coveralls.io/github/polkadot-js/apps?branch=master)
[![dependency](https://david-dm.org/polkadot-js/apps.svg?style=flat-square&path=packages/ui-identicon)](https://david-dm.org/polkadot-js/apps?path=packages/ui-identicon)
[![devDependency](https://david-dm.org/polkadot-js/apps/dev-status.svg?style=flat-square&path=packages/ui-identicon)](https://david-dm.org/polkadot-js/apps?path=packages/ui-identicon#info=devDependencies)
# @polkadot/ui-identicon
Adapted from [Jazzicon](https://github.com/danfinlay/jazzicon) by Dan Finlay with the following changes -
- Random values now is read from the Uint8Array supplied (as opposed to having the seed as a number). This allows us to give an publicKey/address as an input and use those values in the pattern generation.
- Upgrade to the underlying [color](https://github.com/Qix-/color) library
- Generate circles as shapes (instead of rectangles)
- Interface updated to take in optional className & style
- Update everywhere to use ES6
- Split source into self-contained functions (TODO: future testing)
- Everything has been updated to use flow
- Test the library functions
- Copyright headers added (original also under ISC)
## Usage
```js
import identicon from '@polkadot/ui-identicon';
const publicKey = new Uint8Array([...]); // 32 bytes
const body = document.querySelector('body');
const identity = identicon(publicKey, 100 /* diameter, optional */, 'my-class' /* className, optional */ /* style. optional */ /* colors, optional */);
body.append(identity);
```
Also see [src/demo.js](src/demo.js) for a randomly generated example.
![demo](https://raw.githubusercontent.com/polkadot-js/ui/master/packages/ui-identicon/demo.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 851 KiB

View File

@ -0,0 +1,17 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
<style>
#demo {
padding: 0.5em;
}
.padded {
margin: 0.5em !important;
}
</style>
</head>
<body>
<div id="demo"></div>
<script src="./demo.js"></script>
</body>
</html>

View File

@ -0,0 +1,21 @@
{
"name": "@polkadot/ui-identicon",
"version": "0.17.0",
"description": "Renders an SVG picture representing an address",
"main": "index.js",
"scripts": {
"build": "polkadot-dev-build-babel",
"check": "eslint src && flow check",
"test": "echo \"Tests only available from root wrapper\""
},
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"color": "^3.0.0"
},
"devDependencies": {
"@polkadot/util-crypto": "^0.22.2",
"xmlserializer": "^0.6.1"
}
}

View File

@ -0,0 +1,30 @@
// Copyright 2016 Dan Finlay
// 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 { Seeder } from './types';
type ColorGen = {
(alpha?: number): string
};
const Color = require('color');
const { COLORS } = require('./defaults');
const WOBBLE = 30;
module.exports = function colors (seeder: Seeder): ColorGen {
const amount = (seeder() * WOBBLE) - (WOBBLE / 2);
const all = COLORS.map((hex) => Color(hex).rotate(amount));
return (alpha: number = 0.9): string => {
const index = Math.floor(all.length * seeder());
return all.splice(index, 1)[0]
.alpha(alpha)
.string();
};
};

View File

@ -0,0 +1,34 @@
// 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.
const newSeeder = require('./seeder');
const newColors = require('./colors');
describe('colors', () => {
let colors;
beforeEach(() => {
colors = newColors(newSeeder());
});
it('generates using default alpha', () => {
expect(
colors()
).toEqual('hsla(166.70000000000005, 98.6%, 27.6%, 0.9)');
});
it('applies specified alpha', () => {
expect(
colors(0.5)
).toEqual('hsla(166.70000000000005, 98.6%, 27.6%, 0.5)');
});
it('rolates colors', () => {
colors();
expect(
colors()
).not.toEqual('hsla(166.70000000000005, 98.6%, 27.6%, 0.9)');
});
});

View File

@ -0,0 +1,30 @@
// Copyright 2016 Dan Finlay
// 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
module.exports = function container (diameter: number, background: string = 'white', className: string = '', _style: { [string]: string } = {}): HTMLElement {
const element = document.createElement('div');
const style = Object.assign({
background,
borderRadius: `${diameter / 2}px`,
display: 'inline-block',
height: `${diameter}px`,
margin: '0px',
overflow: 'hidden',
padding: '0px',
width: `${diameter}px`
}, _style);
element.className = className;
element.style.background = background;
// FIXME Flow seems to believe element.style[key] has number keys
// flowlint-next-line unclear-type:off
Object.keys(style).forEach((key: any) => {
element.style[key] = style[key];
});
return element;
};

View File

@ -0,0 +1,43 @@
// 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.
const container = require('./container');
describe('container', () => {
it('applies default styles', () => {
expect(
container(100).style._values
).toMatchObject({
'background': 'white',
'border-radius': '50px',
'display': 'inline-block',
'height': '100px',
'margin': '0px',
'overflow': 'hidden',
'padding': '0px',
'width': '100px'
});
});
it('overrides with supplied styles', () => {
expect(
container(50, 'black', '', { display: 'block' }).style._values
).toMatchObject({
'background': 'black',
'border-radius': '25px',
'display': 'block',
'height': '50px',
'margin': '0px',
'overflow': 'hidden',
'padding': '0px',
'width': '50px'
});
});
it('applies the specified className', () => {
expect(
container(100, 'blue', 'testClass').className
).toEqual('testClass');
});
});

View File

@ -0,0 +1,25 @@
// Copyright 2016 Dan Finlay
// 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
const COLORS: Array<string> = [
'#01888C', // teal
'#FC7500', // bright orange
'#034F5D', // dark teal
'#F73F01', // orangered
'#FC1960', // magenta
'#C7144C', // raspberry
'#F3C100', // goldenrod
'#1598F2', // lightning blue
'#2465E1', // sail blue
'#F19E02' // gold
];
const SHAPE_COUNT = 5;
module.exports = {
COLORS,
SHAPE_COUNT
};

View File

@ -0,0 +1,35 @@
// Copyright 2016 Dan Finlay
// 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
const randomAsU8a = require('@polkadot/util-crypto/random/asU8a');
const identicon = require('./index');
const element = document.getElementById('demo');
if (!element) {
throw new Error('Unable to find #demo element');
}
function generateIcon (seed: Uint8Array = randomAsU8a(32)): void {
const start = Date.now();
element.append(
identicon(seed, 100, 'padded')
);
console.log(`Icon generated in ${(Date.now() - start)}ms`);
}
function generateIcons (count: number = 512): void {
generateIcon(new Uint8Array(32));
for (let index = 1; index < count; index++) {
generateIcon();
}
}
generateIcons();

View File

@ -0,0 +1,32 @@
// Copyright 2016 Dan Finlay
// 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
const colors = require('./colors');
const newContainer = require('./container');
const newSeeder = require('./seeder');
const newShape = require('./shape/circle');
const newElement = require('./svg/element');
const { SHAPE_COUNT } = require('./defaults');
module.exports = function identicon (seed: string | Uint8Array, diameter: number = 256, className: string = '', style?: { [string]: string }): HTMLElement {
const seeder = newSeeder(seed);
const colorGen = colors(seeder);
const outer = newContainer(diameter, 'white', className, style);
const container = newContainer(diameter, colorGen());
const svg = newElement(diameter);
outer.appendChild(container);
container.appendChild(svg);
for (let count = 0; count < SHAPE_COUNT; count++) {
const fill = colorGen();
const shape = newShape(seeder, fill, diameter, count);
svg.appendChild(shape);
}
return outer;
};

View File

@ -0,0 +1,25 @@
// 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.
const xmlserializer = require('xmlserializer');
const identicon = require('./index');
describe('identicon', () => {
it('generates a basic [0,..,0] identicon', () => {
expect(
xmlserializer.serializeToString(
identicon(new Uint8Array(32))
)
).toEqual('<div xmlns="http://www.w3.org/1999/xhtml" class="" style="background: white; border-radius: 128px; display: inline-block; height: 256px; margin: 0px; overflow: hidden; padding: 0px; width: 256px;"><div class="" style="border-radius: 128px; display: inline-block; height: 256px; margin: 0px; overflow: hidden; padding: 0px; width: 256px;"><svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="256" height="256"><circle cx="128" cy="140.8" r="128" fill="hsla(12.899999999999977, 100%, 49.4%, 0.9)"/><circle cx="128" cy="153.6" r="102.4" fill="hsla(174.29999999999995, 93.7%, 18.8%, 0.9)"/><circle cx="128" cy="166.4" r="76.8" fill="hsla(0.10000000000002274, 99.2%, 48.6%, 0.9)"/><circle cx="128" cy="179.2" r="51.2" fill="hsla(326.20000000000005, 97.4%, 54.3%, 0.9)"/><circle cx="128" cy="192" r="25.6" fill="hsla(326.20000000000005, 81.7%, 42.9%, 0.9)"/></svg></div></div>');
});
it('allows overrides', () => {
expect(
xmlserializer.serializeToString(
identicon(new Uint8Array(32), 100, 'testClass', { display: 'block' })
)
).toEqual('<div xmlns="http://www.w3.org/1999/xhtml" class="testClass" style="background: white; border-radius: 50px; display: block; height: 100px; margin: 0px; overflow: hidden; padding: 0px; width: 100px;"><div class="" style="border-radius: 50px; display: inline-block; height: 100px; margin: 0px; overflow: hidden; padding: 0px; width: 100px;"><svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="100" height="100"><circle cx="50" cy="55" r="50" fill="hsla(12.899999999999977, 100%, 49.4%, 0.9)"/><circle cx="50" cy="60" r="40" fill="hsla(174.29999999999995, 93.7%, 18.8%, 0.9)"/><circle cx="50" cy="65" r="30" fill="hsla(0.10000000000002274, 99.2%, 48.6%, 0.9)"/><circle cx="50" cy="70" r="20" fill="hsla(326.20000000000005, 97.4%, 54.3%, 0.9)"/><circle cx="50" cy="75" r="10" fill="hsla(326.20000000000005, 81.7%, 42.9%, 0.9)"/></svg></div></div>');
});
});

View File

@ -0,0 +1,35 @@
// 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 { Seeder } from './types';
const isU8a = require('@polkadot/util/is/u8a');
const u8aFromString = require('@polkadot/util/u8a/fromString');
const DIVISOR = 256 * 256;
module.exports = function seeder (_seed: string | Uint8Array = new Uint8Array(32)): Seeder {
// $FlowFixMe type checking
const seed: Uint8Array = isU8a(_seed)
? _seed
// $FlowFixMe type has been determined
: u8aFromString(_seed);
let index = (seed[Math.floor(seed.length / 2)] % seed.length) - 1;
const next = () => {
index += 1;
if (index === seed.length) {
index = 0;
}
return seed[index];
};
return (): number => {
return ((next() * 256) + next()) / DIVISOR;
};
};

View File

@ -0,0 +1,27 @@
// 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.
const newSeeder = require('./seeder');
describe('seeder', () => {
let seeder;
beforeEach(() => {
seeder = newSeeder(new Uint8Array([1, 2, 3, 4]));
});
it('generates numbers using 2 spaces', () => {
expect(
seeder()
).toEqual(0.0156402587890625);
});
it('generates numbers using 2 spaces (incremented)', () => {
seeder();
expect(
seeder()
).toEqual(0.0078582763671875);
});
});

View File

@ -0,0 +1,24 @@
// Copyright 2016 Dan Finlay
// 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 { Seeder } from '../types';
const newCircle = require('../svg/circle');
const { SHAPE_COUNT } = require('../defaults');
module.exports = function circle (seeder: Seeder, fill: string, diameter: number, count: number): Element {
const center = diameter / 2;
const angle = seeder() * 360;
const radius = (((SHAPE_COUNT - count) / SHAPE_COUNT) * (diameter / 2)) + ((diameter / 8) * seeder());
const offset = (diameter / 4) * (seeder() + ((count + 1) / SHAPE_COUNT));
const cx = (offset * Math.sin(angle)) + center;
const cy = (offset * Math.cos(angle)) + center;
const svg = newCircle(radius, cx, cy);
svg.setAttributeNS(null, 'fill', fill);
return svg;
};

View File

@ -0,0 +1,18 @@
// 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.
const xmlserializer = require('xmlserializer');
const seeder = require('../seeder');
const circle = require('./circle');
describe('circle', () => {
it('creates a circle shape', () => {
expect(
xmlserializer.serializeToString(
circle(seeder(), 'blue', 50, 2)
)
).toEqual('<circle xmlns="http://www.w3.org/2000/svg" cx="25" cy="32.5" r="15" fill="blue"/>');
});
});

View File

@ -0,0 +1,27 @@
// Copyright 2016 Dan Finlay
// 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 { Seeder } from '../types';
const newRect = require('../svg/rect');
const { SHAPE_COUNT } = require('../defaults');
module.exports = function square (seeder: Seeder, fill: string, diameter: number, count: number): Element {
const center = diameter / 2;
const svg = newRect(diameter);
const firstRot = seeder();
const angle = Math.PI * 2 * firstRot;
const scale = count / SHAPE_COUNT;
const velocity = ((diameter / SHAPE_COUNT) * seeder()) + (scale * diameter);
const tx = (Math.cos(angle) * velocity).toFixed(3);
const ty = (Math.sin(angle) * velocity).toFixed(3);
const rot = ((firstRot * 360) + (seeder() * 180)).toFixed(1);
svg.setAttributeNS(null, 'transform', `translate(${tx} ${ty}) rotate(${rot} ${center} ${center})`);
svg.setAttributeNS(null, 'fill', fill);
return svg;
};

View File

@ -0,0 +1,16 @@
// 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
const svg = require('./svg');
module.exports = function circle (r: number, cx: number, cy: number): Element {
const elem = svg('circle');
elem.setAttributeNS(null, 'cx', `${cx}`);
elem.setAttributeNS(null, 'cy', `${cy}`);
elem.setAttributeNS(null, 'r', `${r}`);
return elem;
};

View File

@ -0,0 +1,17 @@
// 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.
const xmlserializer = require('xmlserializer');
const circle = require('./circle');
describe('circle', () => {
it('creates a basic SVG circle element', () => {
expect(
xmlserializer.serializeToString(
circle(123, 12, 34)
)
).toEqual('<circle xmlns="http://www.w3.org/2000/svg" cx="12" cy="34" r="123"/>');
});
});

View File

@ -0,0 +1,18 @@
// Copyright 2016 Dan Finlay
// 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
const svg = require('./svg');
module.exports = function element (size: number, type: string = 'svg', x: number = 0, y: number = 0): Element {
const elem = svg(type);
elem.setAttributeNS(null, 'x', `${x}`);
elem.setAttributeNS(null, 'y', `${y}`);
elem.setAttributeNS(null, 'width', `${size}`);
elem.setAttributeNS(null, 'height', `${size}`);
return elem;
};

View File

@ -0,0 +1,17 @@
// 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.
const xmlserializer = require('xmlserializer');
const element = require('./element');
describe('element', () => {
it('creates a basic SVG element', () => {
expect(
xmlserializer.serializeToString(
element(123)
)
).toEqual('<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="123" height="123"/>');
});
});

View File

@ -0,0 +1,15 @@
// 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
const element = require('./element');
module.exports = function rect (size: number): Element {
const elem = element(size, 'rect');
elem.setAttributeNS(null, 'rx', `${size / 16}`);
elem.setAttributeNS(null, 'ry', `${size / 16}`);
return elem;
};

View File

@ -0,0 +1,17 @@
// 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.
const xmlserializer = require('xmlserializer');
const rect = require('./rect');
describe('rect', () => {
it('creates a basic SVG rect element', () => {
expect(
xmlserializer.serializeToString(
rect(123)
)
).toEqual('<rect xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="123" height="123" rx="7.6875" ry="7.6875"/>');
});
});

View File

@ -0,0 +1,11 @@
// Copyright 2016 Dan Finlay
// 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
const SVG_NS = 'http://www.w3.org/2000/svg';
module.exports = function svg (type: string): Element {
return document.createElementNS(SVG_NS, type);
};

View File

@ -0,0 +1,17 @@
// 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.
const xmlserializer = require('xmlserializer');
const svg = require('./svg');
describe('svg', () => {
it('creates a basic SVG element', () => {
expect(
xmlserializer.serializeToString(
svg('rect')
)
).toEqual('<rect xmlns="http://www.w3.org/2000/svg"/>');
});
});

View File

@ -0,0 +1,6 @@
// 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
export type Seeder = () => number;

View File

@ -0,0 +1,26 @@
// 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
module.exports = {
context: __dirname,
entry: './src/demo.js',
mode: 'development',
output: {
path: __dirname,
filename: './demo.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: [
'babel-loader'
]
}
]
},
plugins: []
};

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/ui-keyring",
"version": "0.7.2",
"version": "0.17.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -12,7 +12,7 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/ui-react": "^0.14.3",
"@polkadot/ui-react": "^0.17.0",
"@polkadot/util-keyring": "^0.22.2",
"store": "^2.0.12"
},

View File

@ -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.

View File

@ -0,0 +1,46 @@
[![polkadotjs](https://img.shields.io/badge/polkadot-js-orange.svg?style=flat-square)](https://polkadot.js.org)
![isc](https://img.shields.io/badge/license-ISC-lightgrey.svg?style=flat-square)
[![style](https://img.shields.io/badge/code%20style-semistandard-lightgrey.svg?style=flat-square)](https://github.com/Flet/semistandard)
[![npm](https://img.shields.io/npm/v/@polkadot/ui-react-rx.svg?style=flat-square)](https://www.npmjs.com/package/@polkadot/ui-react-rx)
[![travis](https://img.shields.io/travis/polkadot-js/apps.svg?style=flat-square)](https://travis-ci.org/polkadot-js/ui)
[![maintainability](https://img.shields.io/codeclimate/maintainability/polkadot-js/apps.svg?style=flat-square)](https://codeclimate.com/github/polkadot-js/apps/maintainability)
[![coverage](https://img.shields.io/coveralls/polkadot-js/apps.svg?style=flat-square)](https://coveralls.io/github/polkadot-js/apps?branch=master)
[![dependency](https://david-dm.org/polkadot-js/apps.svg?style=flat-square&path=packages/ui-react-rx)](https://david-dm.org/polkadot-js/apps?path=packages/ui-react-rx)
[![devDependency](https://david-dm.org/polkadot-js/apps/dev-status.svg?style=flat-square&path=packages/ui-react-rx)](https://david-dm.org/polkadot-js/apps?path=packages/ui-react-rx#info=devDependencies)
# @polkadot/ui-react-rx
A collection of RxJS enabled React components that operate with the [@polkadot/api-rx](https://github.com/polkadot-js/api) library. It automatically manages subscriptions on behalf of the developer, providing a number of unstyled components that can be used to construct UIs.
## usage
Basic usage entails creating a `ContextProvider` and just using the components. For instance, to display the current node time,
```js
import React from 'react';
import ReactDOM from 'react-dom';
import { Api, NodeTime } from '@polkadot/ui-react-rx';
...
ReactDOM.render(
<Api>
<NodeTime />
</Api>,
document.querySelector('#container')
);
...
```
All components are provided unstyled making no assumptions on the actual use, however they all support (optionally) the `label`, `className` and `style` attributes, that can be used to style to component.
```js
...
ReactDOM.render(
<Api>
<NodeTime className='rx-time' label='current node time:' />
</Api>,
document.querySelector('#container')
);
...
```

View File

@ -0,0 +1,14 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
<style>
#demo {
padding: 0.5em;
}
</style>
</head>
<body>
<div id="demo"></div>
<script src="./demo.js"></script>
</body>
</html>

View File

@ -0,0 +1,50 @@
{
"name": "@polkadot/ui-react-rx",
"version": "0.17.0",
"description": "A collection of RxJs React components the Polkadot JS API",
"main": "index.js",
"keywords": [
"Polkadot",
"React",
"RxJs"
],
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "ISC",
"engines": {
"node": ">=8.0"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
},
"repository": {
"type": "git",
"url": "git+https://github.com/polkadot-js/ui.git"
},
"bugs": {
"url": "https://github.com/polkadot-js/ui/issues"
},
"homepage": "https://github.com/polkadot-js/ui/tree/master/packages/ui-react-rx#readme",
"scripts": {
"build": "polkadot-dev-build-babel",
"check": "eslint src && flow check",
"test": "echo \"Tests only available from root wrapper\""
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/api-rx": "^0.13.2",
"@polkadot/extrinsics-codec": "^0.17.2",
"@polkadot/storage": "^0.17.2",
"semver": "^5.5.0"
},
"devDependencies": {
"babel-loader": "^8.0.0-beta.2",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"react": "^16.4.0",
"react-dom": "^16.4.0"
},
"peerDependencies": {
"react": "^16.4.0"
}
}

View File

@ -0,0 +1,134 @@
// 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 { ProviderInterface } from '@polkadot/api-provider/types';
import type { RxApiInterface } from '@polkadot/api-rx/types';
import type { EncodingVersions } from '@polkadot/extrinsics-codec/types';
import type { ApiProps } from './types';
import React from 'react';
import semver from 'semver';
import createWsProvider from '@polkadot/api-provider/ws';
import createApi from '@polkadot/api-rx';
import defaults from '@polkadot/api-rx/defaults';
type Props = {
api?: RxApiInterface,
children: React$Node,
provider?: ProviderInterface,
url?: string
};
type State = ApiProps & {
subscriptions: Array<rxjs$ISubscription | null>;
};
const { Consumer, Provider } = React.createContext();
function apiSupport (version?: string): EncodingVersions {
return version === undefined || semver.lt(version, '0.2.0')
? 'poc-1'
: 'latest';
}
export default class Api extends React.Component<Props, State> {
static Provider = Provider;
static Consumer = Consumer
state: State;
constructor (props: Props) {
super(props);
const { provider, url = '' } = props;
const api = props.api || createApi(
url && url.length
? createWsProvider(url)
: provider
);
const setApi = (api: RxApiInterface): void => {
this.setState({ api }, () => {
this.updateSubscriptions();
});
};
const setApiProvider = (provider?: ProviderInterface): void =>
setApi(createApi(provider));
const setApiWsUrl = (url?: string = defaults.WS_URL): void =>
setApiProvider(createWsProvider(url));
this.state = {
api,
apiConnected: false,
apiSupport: 'poc-1',
setApi,
setApiProvider,
setApiWsUrl,
subscriptions: []
};
}
componentDidMount () {
this.updateSubscriptions();
}
componentWillUnmount () {
this.unsubscribe();
}
updateSubscriptions () {
const { api } = this.state;
this.unsubscribe();
this.setState({
subscriptions:
[
() => api.isConnected().subscribe((isConnected?: boolean) => {
this.setState({ apiConnected: !!isConnected });
}),
() => api.system.version().subscribe((version?: string) => {
this.setState({ apiSupport: apiSupport(version) });
})
].map((fn: () => rxjs$ISubscription): rxjs$ISubscription | null => {
try {
return fn();
} catch (error) {
console.error(error);
return null;
}
})
});
}
unsubscribe (): void {
const { subscriptions } = this.state;
subscriptions.forEach((subscription) => {
if (subscription) {
try {
subscription.unsubscribe();
} catch (error) {
console.error(error);
}
}
});
}
render () {
const { api, apiConnected, apiSupport, setApi, setApiProvider, setApiWsUrl } = this.state;
return (
<Provider value={{
api,
apiConnected,
apiSupport,
setApi,
setApiProvider,
setApiWsUrl
}}>
{this.props.children}
</Provider>
);
}
}

View File

@ -0,0 +1,14 @@
// 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 storage from '@polkadot/storage';
import numberFormat from './util/numberFormat';
import withStorageDiv from './with/storageDiv';
export default withStorageDiv(storage.staking.public.freeBalanceOf)(
numberFormat,
{ className: 'rx--Balance' }
);

View File

@ -0,0 +1,20 @@
// 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 { Header } from '@polkadot/primitives/header';
import rpcs from '@polkadot/jsonrpc';
import numberFormat from './util/numberFormat';
import withApiDiv from './with/apiDiv';
export default withApiDiv(rpcs.chain.public.newHead)(
(value?: Header): string => {
return value && value.number
? numberFormat(value.number)
: 'unknown';
},
{ className: 'rx--BestNumber' }
);

View File

@ -0,0 +1,14 @@
// 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 rpcs from '@polkadot/jsonrpc';
import withApiDiv from './with/apiDiv';
export default withApiDiv(rpcs.system.public.chain)(
(value: string = 'unknown'): string =>
value,
{ className: 'rx--Chain' }
);

View File

@ -0,0 +1,15 @@
// 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 withApiDiv from './with/apiDiv';
export default withApiDiv({ name: 'isConnected' })(
(value?: boolean = false): string => {
return value
? 'connected'
: 'disconnected';
},
{ className: 'rx--Connected' }
);

View File

@ -0,0 +1,30 @@
// 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 { BaseProps, BareProps } from './types';
import React from 'react';
type Props<T> = BaseProps<T>;
// FIXME Not 100% on how to push through the generic type when used
// flowlint-next-line unclear-type: off
const echoFormatter = (value?: any): any =>
value;
export default function Div<T> ({ children, className = 'rx--Div', render = echoFormatter, rxUpdated = false, label = '', style, value }: Props<T>): React$Node {
const props: BareProps = {
className: [className, rxUpdated ? 'rx--updated' : undefined]
.filter((c) => c)
.join(' ') || undefined,
style
};
return (
<div {...props}>
{label}{render(value) || ''}{children}
</div>
);
}

View File

@ -0,0 +1,14 @@
// 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 rpcs from '@polkadot/jsonrpc';
import withApiDiv from './with/apiDiv';
export default withApiDiv(rpcs.system.public.name)(
(value: string = 'unknown'): string =>
value,
{ className: 'rx--NodeName' }
);

View File

@ -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 storage from '@polkadot/storage';
import withStorageDiv from './with/storageDiv';
export default withStorageDiv(storage.timestamp.public.current)(
(value?: Date): string => {
if (!value || value.getTime() === 0) {
return 'unknown';
}
return value.toString();
},
{ className: 'rx--NodeTime' }
);

View File

@ -0,0 +1,14 @@
// 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 rpcs from '@polkadot/jsonrpc';
import withApiDiv from './with/apiDiv';
export default withApiDiv(rpcs.system.public.version)(
(value: string = 'unknown'): string =>
value,
{ className: 'rx--NodeVersion' }
);

View File

@ -0,0 +1,14 @@
// 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 storage from '@polkadot/storage';
import numberFormat from './util/numberFormat';
import withStorageDiv from './with/storageDiv';
export default withStorageDiv(storage.system.public.accountIndexOf)(
numberFormat,
{ className: 'rx--Nonce' }
);

View File

@ -0,0 +1,55 @@
// 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 React from 'react';
import ReactDOM from 'react-dom';
import mockProvider from '@polkadot/api-provider/mock';
import Api from './Api';
import Balance from './Balance';
import BestNumber from './BestNumber';
import Chain from './Chain';
import Connected from './Connected';
import NodeName from './NodeName';
import NodeTime from './NodeTime';
import NodeVersion from './NodeVersion';
import Nonce from './Nonce';
import withApi from './with/api';
const provider = mockProvider();
const ALICE_ADDR = '5GoKvZWG5ZPYL1WUovuHW3zJBWBP5eT8CbqjdRY4Q6iMaDtZ';
const ALICE_PUBLIC = '0xd172a74cda4c865912c32ba0a80a57ae69abae410e5ccb59dee84e2f4432db4f';
const element = document.getElementById('demo');
if (!element) {
throw new Error('Unable to find #demo element');
}
const WithApiDebug = withApi(
(apiProps) => (
<pre style={{ background: '#f5f5f5', padding: '0.5em' }}>
{JSON.stringify(apiProps, undefined, 2)}
</pre>
)
);
ReactDOM.render(
<Api provider={provider}>
<Connected label='status =' />
<NodeName label='name =' />
<NodeVersion label='version =' />
<Chain label='chain =' />
<br />
<BestNumber label='best block #' />
<Balance label='balance =' params={[ALICE_PUBLIC]} />
<Nonce label='nonce =' params={ALICE_ADDR} />
<NodeTime label='node time =' />
<br />
<WithApiDebug />
</Api>,
element
);

View File

@ -0,0 +1,30 @@
// 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 Api from './Api';
import Balance from './Balance';
import BestNumber from './BestNumber';
import Chain from './Chain';
import Connected from './Connected';
import Div from './Div';
import NodeName from './NodeName';
import NodeTime from './NodeTime';
import NodeVersion from './NodeVersion';
import Nonce from './Nonce';
export * from './with';
export {
Api,
Balance,
BestNumber,
Chain,
Connected,
Div,
NodeName,
NodeTime,
NodeVersion,
Nonce
};

View File

@ -0,0 +1,48 @@
// 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 { ProviderInterface } from '@polkadot/api-provider/types';
import type { RxApiInterface } from '@polkadot/api-rx/types';
import type { EncodingVersions } from '@polkadot/extrinsics-codec/types';
import type { Storage$Key$Values } from '@polkadot/storage/types';
export type BareProps = {
className?: string,
style?: { [string]: mixed }
};
export type ApiProps = {
api: RxApiInterface,
apiConnected: boolean,
apiSupport: EncodingVersions,
setApi: (api: RxApiInterface) => void,
setApiProvider: (provider?: ProviderInterface) => void,
setApiWsUrl: (url?: string) => void
};
export type OnChangeCb<T> = rxjs$Subject<T> | (value: T) => void;
export type ChangeProps<T> = {
onChange?: OnChangeCb<T>
};
export type ParamProps = {
params?: Storage$Key$Values
};
export type RxProps<T> = {
rxUpdated?: boolean;
rxUpdatedAt?: number;
value?: T;
}
export type BaseProps<T> = BareProps & ApiProps & ChangeProps<T> & ParamProps & RxProps<T> & {
children?: React$Node,
label?: string,
render?: (value?: T) => React$Node
};
// flowlint-next-line unclear-type:off
export type Formatter = (value?: any) => string;

View File

@ -0,0 +1,22 @@
// 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 { RxProps } from '../types';
import { interval } from 'rxjs/observable/interval';
const interval$ = interval(500);
export default function intervalSubscribe<T, Props, State: RxProps<T>> (that: React$Component<Props, State>): rxjs$ISubscription {
return interval$.subscribe(() => {
const rxUpdated = (Date.now() - (that.state.rxUpdatedAt || 0)) <= 1500;
if (rxUpdated !== that.state.rxUpdated) {
that.setState({
rxUpdated
});
}
});
}

View File

@ -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
export default function isEqual <T> (a?: T, b?: T): boolean {
return JSON.stringify({ test: a }) === JSON.stringify({ test: b });
}

View File

@ -0,0 +1,16 @@
// 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 BN from 'bn.js';
const NUMBER_REGEX = new RegExp('(\\d+?)(?=(\\d{3})+(?!\\d)|$)', 'g');
export default function numberFormat (value?: BN | number): string {
if (value === undefined) {
return '0';
}
return (value.toString().match(NUMBER_REGEX) || []).join(',');
}

View File

@ -0,0 +1,25 @@
// 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 { OnChangeCb } from '../types';
import isFunction from '@polkadot/util/is/function';
import isObservable from '@polkadot/util/is/observable';
export default function triggerChange<T> (value?: T, ...onChange: Array<?OnChangeCb<T>>): void {
if (!onChange || !onChange.length) {
return;
}
onChange.forEach((onChange) => {
if (isObservable(onChange)) {
// $FlowFixMe type determined
onChange.next(value);
} else if (isFunction(onChange)) {
// $FlowFixMe type determined
onChange(value);
}
});
}

View File

@ -0,0 +1,45 @@
// 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 { ApiProps, BareProps, ChangeProps } from '../types';
import type { DefaultProps } from './types';
import React from 'react';
import assert from '@polkadot/util/assert';
import Api from '../Api';
type InProps<T> = BareProps & ChangeProps<T>;
type OutProps<T> = InProps<T> & ApiProps;
export default function withApi<T, ComponentProps: $Shape<OutProps<T>>, InputProps: InProps<T>> (Component: React$ComponentType<ComponentProps>, defaultProps?: DefaultProps<T> = {}): Class<React.Component<InputProps>> {
return class WithApi extends React.Component<InputProps> {
constructor (props: InputProps) {
super(props);
assert(Component, `Expected 'withApi' to wrap a React Component`);
}
render () {
return (
<Api.Consumer>
{(apiProps?: ApiProps): React$Node => {
assert(apiProps && apiProps.api, `Application root must be wrapped inside 'rx-react/Api' to provide API context`);
return (
// flowlint-next-line inexact-spread:off
<Component
{...defaultProps}
{...apiProps}
{...this.props}
/>
);
}}
</Api.Consumer>
);
}
};
}

View File

@ -0,0 +1,74 @@
// 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 { RxApiInterface$Method } from '@polkadot/api-rx/types';
import type { ApiProps, BareProps, ChangeProps, ParamProps, RxProps } from '../types';
import type { ApiMethod, HOC, Options, DefaultProps } from './types';
import React from 'react';
import assert from '@polkadot/util/assert';
import isEqual from '../util/isEqual';
import withObservable from './observable';
import withApi from './api';
type InProps<T> = BareProps & ChangeProps<T> & ParamProps;
type WithApiProps<T> = InProps<T> & ApiProps;
type OutProps<T> = WithApiProps<T> & RxProps<T>;
type State<ComponentProps> = ParamProps & {
Component: React$ComponentType<ComponentProps>,
fn: RxApiInterface$Method
}
export default function withApiCall<T, ComponentProps: $Shape<OutProps<T>>, InputProps: InProps<T>, InputApiProps: WithApiProps<T>> ({ name, section }: ApiMethod, options?: Options<T>): HOC<T> {
return (Component: React$ComponentType<ComponentProps>, defaultProps?: DefaultProps<T> = {}): Class<React.Component<InputProps>> => {
class WithApiCall extends React.Component<InputApiProps, State<InputApiProps>> {
state: State<InputApiProps>;
constructor (props: InputApiProps) {
super(props);
const hasSection = !!section;
assert(!hasSection || props.api[section], `Unable to find 'api.${section}'`);
const fn: RxApiInterface$Method = hasSection
? props.api[section][name]
// $FlowFixMe method check to be done
: ((props.api[name]: any): RxApiInterface$Method);
assert(fn, `Unable to find 'api${hasSection ? '.' : ''}${section || ''}.${name}'`);
this.state = ({ fn }: $Shape<State<InputApiProps>>);
}
static getDerivedStateFromProps ({ params = [] }: InputApiProps, prevState: State<ComponentProps>): $Shape<State<InputApiProps>> | null {
if (isEqual(params, prevState.params)) {
return null;
}
const observable = prevState.fn.apply(null, params);
return {
Component: withObservable(observable, options)(Component, defaultProps),
params
};
}
render () {
const { Component } = this.state;
return (
<Component {...this.props} />
);
}
}
return withApi(WithApiCall);
};
}

View File

@ -0,0 +1,18 @@
// 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 { BaseProps } from '../types';
import type { ApiMethod, ComponentRenderer, DefaultProps, RenderFn, Options } from './types';
import Div from '../Div';
import withApiCall from './apiCall';
export default function withApiDiv<T, Props: BaseProps<T>> (call: ApiMethod, options?: Options<T>): ComponentRenderer<T> {
return (render: RenderFn, defaultProps?: DefaultProps<T> = {}): React$ComponentType<Props> =>
withApiCall(call, options)(Div, {
...defaultProps,
render
});
}

View File

@ -0,0 +1,18 @@
// 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 withApi from './api';
import withApiCall from './apiCall';
import withObservable from './observable';
import withObservableParams from './observableParams';
import withStorage from './storage';
export {
withApi,
withApiCall,
withObservable,
withObservableParams,
withStorage
};

View File

@ -0,0 +1,101 @@
// 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
// TODO: This is now way more messy and way longer than it should be. Maintainability is lacking - apply some effort and split it into managable parts
import type { BareProps, ChangeProps, RxProps } from '../types';
import type { HOC, Options, DefaultProps } from './types';
import React from 'react';
import { map } from 'rxjs/operators/map';
import assert from '@polkadot/util/assert';
import intervalSubscribe from '../util/intervalSubscribe';
import isEqual from '../util/isEqual';
import triggerChange from '../util/triggerChange';
import echoTransform from './transform/echo';
type InProps<T> = BareProps & ChangeProps<T>;
type OutProps<T> = InProps<T> & RxProps<T>;
type State<T> = RxProps<T> & {
subscriptions: Array<rxjs$ISubscription>;
}
export default function withObservable<T, ComponentProps: $Shape<OutProps<T>>, InputProps: InProps<T>> (observable: rxjs$Observable<T> | rxjs$Subject<T>, { onChange, propName = 'value', transform = echoTransform }: Options<T> = {}): HOC<T> {
return (Component: React$ComponentType<ComponentProps>, defaultProps?: DefaultProps<T> = {}): Class<React.Component<InputProps, State<T>>> =>
class WithObservable extends React.Component<InputProps, State<T>> {
state: State<T>;
constructor (props: InputProps) {
super(props);
assert(observable, `Component should have Observable to wrap`);
assert(Component, `Expected 'with*' to wrap a React Component`);
this.state = {
rxUpdated: false,
rxUpdatedAt: 0,
subscriptions: [],
value: void 0
};
}
componentDidMount () {
const subscriptions: Array<rxjs$ISubscription> = [
observable
.pipe(map(transform))
.subscribe((value) => {
this.triggerUpdate(this.props, value);
}),
intervalSubscribe(this)
];
this.setState({
subscriptions
});
}
componentWillUnmount () {
this.state.subscriptions.forEach((subscription) => {
subscription.unsubscribe();
});
}
triggerUpdate = (props: InProps<T>, value?: T): void => {
if (isEqual(value, this.state.value)) {
return;
}
triggerChange(value, onChange, props.onChange || defaultProps.onChange);
this.setState({
rxUpdated: true,
rxUpdatedAt: Date.now(),
value
});
}
render () {
const { rxUpdated, rxUpdatedAt, value } = this.state;
// flowlint-next-line inexact-spread:off
const _props = {
...defaultProps,
...this.props,
rxUpdated,
rxUpdatedAt,
[propName]: value
};
delete _props.onChange;
return (
<Component {..._props} />
);
}
};
}

View File

@ -0,0 +1,16 @@
// 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 { HOC, Options } from './types';
import withObservable from './observable';
export default function withObservableParams<T> (observable: rxjs$Observable<T> | rxjs$Subject<T>, options?: Options<T> = {}): HOC<T> {
return withObservable(observable, {
propName: 'params',
// flowlint-next-line inexact-spread:off
...options
});
}

View File

@ -0,0 +1,156 @@
// 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
// TODO: Lots of duplicated code between this and withObservable, surely there ois a better way of doing this?
import type { Storage$Key, Storage$Key$Values } from '@polkadot/storage/types';
import type { ApiProps, BareProps, ChangeProps, ParamProps, RxProps } from '../types';
import type { HOC, StorageOptions, DefaultProps, Transform } from './types';
import React from 'react';
import { map } from 'rxjs/operators/map';
import storageKey from '@polkadot/storage/key';
import intervalSubscribe from '../util/intervalSubscribe';
import isEqual from '../util/isEqual';
import triggerChange from '../util/triggerChange';
import echoTransform from './transform/echo';
import createTransform from './transform/storage';
import withApi from './api';
type InProps<T> = BareProps & ChangeProps<T> & ParamProps & {
transform?: Transform
};
type InApiProps<T> = ApiProps & InProps<T>;
type OutProps<T> = InApiProps<T> & RxProps<T>;
type State<T> = RxProps<T> & {
subscriptions: Array<rxjs$ISubscription>;
}
export default function withStorage<T, ComponentProps: $Shape<OutProps<T>>, InputProps: InProps<T>, InputApiProps: InApiProps<T>> (key: Storage$Key, { onChange, params, propName = 'value', transform }: StorageOptions<T> = {}): HOC<T> {
const keyCreator = storageKey(key);
const storageTransform = createTransform(key);
const createKey = (propParams?: Storage$Key$Values): Uint8Array => {
const values = [params, propParams].reduce((result, input = []) => {
if (Array.isArray(input)) {
return result.concat(input);
}
result.push(input);
return result;
}, []);
return keyCreator.apply(null, values);
};
return (Component: React$ComponentType<ComponentProps>, defaultProps?: DefaultProps<T> = {}): Class<React.Component<InputProps>> => {
class WithStorage extends React.Component<InputApiProps, State<T>> {
state: State<T>;
constructor (props: InputApiProps) {
super(props);
this.state = {
rxUpdated: false,
rxUpdatedAt: 0,
subscriptions: [],
value: void 0
};
}
componentDidUpdate (prevProps: InputApiProps) {
if (!isEqual(this.props.params, prevProps.params)) {
this.triggerUpdate();
}
}
componentDidMount () {
const subscriptions: Array<rxjs$ISubscription> = [
this.props.api.chain
.newHead()
.subscribe((value) => {
this.triggerUpdate();
}),
intervalSubscribe(this)
];
this.setState({
subscriptions
});
}
componentWillUnmount () {
this.state.subscriptions.forEach((subscription) => {
subscription.unsubscribe();
});
}
triggerUpdate = async (): Promise<void> => {
// flowlint-next-line unclear-type:off
const mapping = (value: Uint8Array, index: number): any => {
const outer = this.props.transform || echoTransform;
const inner = transform || echoTransform;
return outer(
inner(
storageTransform(value, index),
index
),
index
);
};
const key = createKey(this.props.params);
let value;
try {
value = await this.props.api.state
.getStorage(key)
.pipe(map(mapping))
.toPromise();
} catch (error) {
// swallow?
// console.error(error);
}
if (isEqual(value, this.state.value)) {
return;
}
triggerChange(value, onChange, this.props.onChange || defaultProps.onChange);
this.setState({
rxUpdated: true,
rxUpdatedAt: Date.now(),
value
});
}
render () {
const { rxUpdated, rxUpdatedAt, value } = this.state;
// flowlint-next-line inexact-spread:off
const _props = {
...defaultProps,
...this.props,
rxUpdated,
rxUpdatedAt,
[propName]: value
};
delete _props.onChange;
return (
<Component {..._props} />
);
}
}
return withApi(WithStorage);
};
}

View File

@ -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 { Storage$Key } from '@polkadot/storage/types';
import type { BaseProps } from '../types';
import type { ComponentRenderer, DefaultProps, RenderFn, StorageOptions } from './types';
import Div from '../Div';
import withStorage from './storage';
export default function withStorageDiv<T, Props: BaseProps<T>> (key: Storage$Key, options?: StorageOptions<T>): ComponentRenderer<T> {
return (render: RenderFn, defaultProps?: DefaultProps<T> = {}): React$ComponentType<Props> =>
withStorage(key, options)(Div, {
...defaultProps,
render
});
}

View File

@ -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
export default function echoTransform <T> (x: T, index: number): T {
return x;
}

View File

@ -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 { Param$Values } from '@polkadot/params/types';
import type { Storage$Key } from '@polkadot/storage/types';
import type { StorageTransform } from '../types';
import decodeParams from '@polkadot/params/decode';
export default function storageTransform ({ type }: Storage$Key): StorageTransform {
return (input?: Uint8Array = new Uint8Array(0), index: number): Param$Values | null => {
try {
return decodeParams(type, input).value;
} catch (error) {
console.error('Decoding', type, '::', error);
return null;
}
};
}

View File

@ -0,0 +1,42 @@
// 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 { Interface$Method } from '@polkadot/jsonrpc/types';
import type { Param$Values } from '@polkadot/params/types';
import type { Storage$Key$Values } from '@polkadot/storage/types';
import type { OnChangeCb } from '../types';
import React from 'react';
// flowlint-next-line unclear-type:off
export type Transform = (value: any, index: number) => any;
export type DefaultProps<T> = {
onChange?: OnChangeCb<T>,
[string]: mixed
};
export type Options<T> = {
onChange?: OnChangeCb<T>,
propName?: string,
transform?: Transform
};
export type StorageTransform = (value?: Uint8Array, index: number) => Param$Values | null;
export type StorageOptions<T> = Options<T> & {
params?: Storage$Key$Values;
};
// flowlint-next-line unclear-type:off
export type HOC<T> = (Component: React$ComponentType<any>, defaultProps?: DefaultProps<T>) => Class<React.Component<any, any>>;
export type ApiMethod = $Shape<Interface$Method>;
// flowlint-next-line unclear-type:off
export type RenderFn = (value?: any) => any;
// flowlint-next-line unclear-type:off
export type ComponentRenderer<T> = (render: RenderFn, defaultProps?: DefaultProps<T>) => React$ComponentType<any>;

View File

@ -0,0 +1,12 @@
// 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.
const Adapter = require('enzyme-adapter-react-16');
const Enzyme = require('enzyme');
Enzyme.configure({
adapter: new Adapter()
});
module.exports = Enzyme;

View File

@ -0,0 +1,15 @@
// 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.
const createObservable = require('@polkadot/api-rx/observable');
module.exports = function observable (method) {
const fn = () => Promise.resolve(12345);
fn.unsubscribe = () => Promise.resolve(true);
return createObservable(`section_${method}`, method, {
[method]: fn
})();
};

View File

@ -0,0 +1,29 @@
// 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
module.exports = {
context: __dirname,
entry: './src/demo.js',
mode: 'development',
output: {
path: __dirname,
filename: './demo.js'
},
resolve: {
alias: {}
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader'
}
}
]
},
plugins: []
};

15
packages/ui-react/LICENSE Normal file
View File

@ -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.

View File

@ -0,0 +1,13 @@
[![polkadotjs](https://img.shields.io/badge/polkadot-js-orange.svg?style=flat-square)](https://polkadot.js.org)
![isc](https://img.shields.io/badge/license-ISC-lightgrey.svg?style=flat-square)
[![style](https://img.shields.io/badge/code%20style-semistandard-lightgrey.svg?style=flat-square)](https://github.com/Flet/semistandard)
[![npm](https://img.shields.io/npm/v/@polkadot/ui-react.svg?style=flat-square)](https://www.npmjs.com/package/@polkadot/ui-react)
[![travis](https://img.shields.io/travis/polkadot-js/apps.svg?style=flat-square)](https://travis-ci.org/polkadot-js/ui)
[![maintainability](https://img.shields.io/codeclimate/maintainability/polkadot-js/apps.svg?style=flat-square)](https://codeclimate.com/github/polkadot-js/apps/maintainability)
[![coverage](https://img.shields.io/coveralls/polkadot-js/apps.svg?style=flat-square)](https://coveralls.io/github/polkadot-js/apps?branch=master)
[![dependency](https://david-dm.org/polkadot-js/apps.svg?style=flat-square&path=packages/ui-react)](https://david-dm.org/polkadot-js/apps?path=packages/ui-react)
[![devDependency](https://david-dm.org/polkadot-js/apps/dev-status.svg?style=flat-square&path=packages/ui-react)](https://david-dm.org/polkadot-js/apps?path=packages/ui-react#info=devDependencies)
# @polkadot/ui-react
A lightweight collection of React components used for construction of applications. See [src/demo.js](src/demo.js) for a sample utilising all components.

View File

@ -0,0 +1,9 @@
<html>
<head>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
</head>
<body>
<div id="demo"></div>
<script src="./demo.js"></script>
</body>
</html>

View File

@ -0,0 +1,35 @@
{
"name": "@polkadot/ui-react",
"version": "0.17.0",
"description": "A collection of base React components",
"main": "index.js",
"scripts": {
"build": "polkadot-dev-build-babel",
"check": "stylelint 'src/**/*.css' && eslint src && flow check",
"test": "echo \"Tests only available from root wrapper\""
},
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.0.0-beta.47",
"@polkadot/ui-identicon": "^0.17.0"
},
"devDependencies": {
"@polkadot/util-keyring": "^0.22.2",
"babel-loader": "^8.0.0-beta.2",
"css-loader": "0.28.9",
"empty": "^0.10.1",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"postcss": "^6.0.22",
"postcss-loader": "2.0.10",
"postcss-sass": "^0.3.0",
"react": "^16.4.0",
"react-dom": "^16.4.0",
"style-loader": "^0.19.1"
},
"peerDependencies": {
"react": "^16.4.0",
"react-router-dom": "^4.2.2"
}
}

View File

@ -0,0 +1 @@
module.exports = {};

View File

@ -0,0 +1,18 @@
/* 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. */
.ui--Container {
padding: 0.5em;
}
.ui--DemoContainer {
padding: 0.5em 0.5em 1.5em 5em;
}
.ui--DemoHeader {
background: #f5f5f5;
font-size: 0.75em;
margin: 0 0 1em -6em;
padding: 0.5em;
}

View File

@ -0,0 +1,39 @@
// 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 React from 'react';
import Container from './index';
type DemoProps = {
attrs: { [string]: mixed },
attrsReq: Array<string>,
children: React$Node,
component: string
};
export default function Demo ({ attrs, attrsReq, children, component }: DemoProps): React$Node {
const allAttrs = Object.assign({ className: '"..."', style: '{...}' }, attrs);
const attrsText = Object
.keys(allAttrs)
.map((attr) => {
const value = allAttrs[attr];
const bool = value === 'bool';
const opt = bool || !attrsReq.includes(attr);
return `${opt ? '[' : ''}${attr}${bool ? '' : '='}${bool ? '' : value}${opt ? ']' : ''}`;
})
.sort()
.join('\n ');
return (
<Container className='ui--DemoContainer'>
<pre className='ui--DemoHeader'>
{`<${component}\n ${attrsText}\n/>`}
</pre>
{children}
</Container>
);
}

View File

@ -0,0 +1,25 @@
// 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 { BaseProps } from '../types';
import './Container.css';
import React from 'react';
type Props = BaseProps & {
children: React$Node
};
export default function Container ({ children, className, style }: Props): React$Node {
return (
<div
className={['ui--Container', className].join(' ')}
style={style}
>
{children}
</div>
);
}

View File

@ -0,0 +1,26 @@
// 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.
import { shallow } from '../../test/enzyme';
import React from 'react';
import { Container } from '../index';
describe('Container', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(
<Container className='test' />,
{}
);
});
it('creates the element', () => {
expect(
wrapper
).toBeDefined();
});
});

View File

@ -0,0 +1,43 @@
// 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 React from 'react';
import randomAsU8a from '@polkadot/util-crypto/random/asU8a';
import addressEncode from '@polkadot/util-keyring/address/encode';
import Container from '../Container/Demo';
import IdentityIcon from './index';
export default function Demo () {
const identities = [new Uint8Array(32)];
while (identities.length !== 10) {
identities.push(
addressEncode(randomAsU8a(32))
);
}
return (
<Container
component='IdentityIcon'
attrs={{
size: '<number>',
value: '{...}'
}}
attrsReq={[
'value'
]}
>
{
identities.map((value) => (
<IdentityIcon
key={value.toString()}
value={value}
/>
))
}
</Container>
);
}

View File

@ -0,0 +1,7 @@
/* 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. */
.ui--IdentityIcon {
display: inline-block;
}

View File

@ -0,0 +1,22 @@
// 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 identicon from '@polkadot/ui-identicon';
type NodeRefSet = (node: ?Element) => void;
export default function appendIcon (address: string | Uint8Array, size: number): NodeRefSet {
return (node: ?Element): void => {
if (node) {
while (node.firstChild) {
node.removeChild(node.firstChild);
}
node.appendChild(
identicon(address, size)
);
}
};
}

View File

@ -0,0 +1,33 @@
// 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.
describe('autoFocus', () => {
let appendIcon;
let mockIdenticon;
let node;
beforeEach(() => {
node = {
appendChild: jest.fn()
};
mockIdenticon = jest.fn(() => 'genIdentitycon');
jest.mock('@polkadot/ui-identicon', () => mockIdenticon);
appendIcon = require('./appendIcon').default;
});
it('generates & appends icon to the node', () => {
appendIcon('testAddr', 'testSize')(node);
expect(node.appendChild).toHaveBeenCalledWith('genIdentitycon');
expect(mockIdenticon).toHaveBeenCalledWith('testAddr', 'testSize');
});
it('does not call identicon when no node', () => {
appendIcon('testAddr', 'testSize')();
expect(mockIdenticon).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,27 @@
// 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 { BaseProps } from '../types';
import './IdentityIcon.css';
import React from 'react';
import appendIcon from './appendIcon';
type Props = BaseProps & {
size?: number,
value: string | Uint8Array
};
export default function IdentityIcon ({ className, size = 64, style, value }: Props) {
return (
<div
className={['ui--IdentityIcon', className].join(' ')}
ref={appendIcon(value, size)}
style={style}
/>
);
}

View File

@ -0,0 +1,26 @@
// 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.
import { shallow } from '../../test/enzyme';
import React from 'react';
import { IdentityIcon } from '../index';
describe('IdentityIcon', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(
<IdentityIcon className='test' />,
{}
);
});
it('creates the element', () => {
expect(
wrapper
).toBeDefined();
});
});

View File

@ -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. */
.ui--Demo {
margin: 0;
padding: 0.5em;
}

View File

@ -0,0 +1,24 @@
// 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 './demo.css';
import React from 'react';
import ReactDOM from 'react-dom';
import IdentityIcon from './IdentityIcon/Demo';
const element = document.getElementById('demo');
if (!element) {
throw new Error('Unable to find #demo element');
}
ReactDOM.render(
<div className='ui--Demo'>
<IdentityIcon />
</div>,
element
);

View File

@ -0,0 +1,12 @@
// 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 Container from './Container';
import IdentityIcon from './IdentityIcon';
export {
Container,
IdentityIcon
};

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