Move ui-identicon -> react-identicon (#165)

* Move ui-identicon -> react-identicon

* ui-identicon import fix

* Update README package links

* Update doc sidebar links

* Adjust react-qr signatures, CHANGELOG
This commit is contained in:
Jaco Greeff
2019-07-29 11:30:37 +02:00
committed by GitHub
parent faf234fedc
commit c3f57c19e8
50 changed files with 56 additions and 52 deletions
+37
View File
@@ -0,0 +1,37 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import ReactDOM from 'react-dom';
import { encodeAddress, randomAsU8a } from '@polkadot/util-crypto';
import IdentityIcon from '.';
export default class Demo extends React.PureComponent {
public render (): React.ReactNode {
const identities: string[] = [];
while (identities.length !== 50) {
identities.push(
encodeAddress(randomAsU8a(32))
);
}
return identities.map((value): React.ReactNode => (
<IdentityIcon
key={value.toString()}
theme='jdenticon'
value={value}
/>
));
}
}
const rootElement = document.getElementById('demo');
if (!rootElement) {
throw new Error(`Unable to find element with id 'demo'`);
}
ReactDOM.render(<Demo />, rootElement);
+140
View File
@@ -0,0 +1,140 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Prefix } from '@polkadot/util-crypto/address/types';
import { IdentityProps as Props, Props as ComponentProps } from './types';
import React from 'react';
import CopyToClipboard from 'react-copy-to-clipboard';
import styled from 'styled-components';
import settings from '@polkadot/ui-settings';
import { isHex, isU8a, u8aToHex } from '@polkadot/util';
import { decodeAddress, encodeAddress } from '@polkadot/util-crypto';
import { Beachball, Empty, Jdenticon, Polkadot } from './icons';
const Fallback = Beachball;
interface State {
address: string;
publicKey: string;
}
const DEFAULT_SIZE = 64;
const Components: { [index: string]: React.ComponentType<ComponentProps> } = {
beachball: Beachball,
jdenticon: Jdenticon,
polkadot: Polkadot,
substrate: Jdenticon
};
const Wrapper = styled.div`
cursor: copy;
display: inline-block;
line-height: 0;
> .container {
position: relative;
> div,
> svg {
position: relative;
}
&:before {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 50%;
box-shadow: 0 0 5px 2px #e0e0e0;
content: '';
}
&.highlight:before {
box-shadow: 0 0 5px 2px red;
}
}
`;
export default class IdentityIcon extends React.PureComponent<Props, State> {
public state: State = {
address: '',
publicKey: '0x'
};
private static prefix?: Prefix = undefined;
public static setDefaultPrefix (prefix: Prefix): void {
IdentityIcon.prefix = prefix;
}
public static getDerivedStateFromProps ({ prefix = IdentityIcon.prefix, value }: Props, prevState: State): State | null {
try {
const address = isU8a(value) || isHex(value)
? encodeAddress(value, prefix)
: (value || '');
const publicKey = u8aToHex(decodeAddress(address, false, prefix));
return address === prevState.address
? null
: {
address,
publicKey
};
} catch (error) {
return {
address: '',
publicKey: '0x'
};
}
}
public render (): React.ReactNode {
const { address } = this.state;
const wrapped = this.getWrapped(this.state);
return !address
? wrapped
: (
<CopyToClipboard
onCopy={this.onCopy}
text={address}
>
{wrapped}
</CopyToClipboard>
);
}
private getWrapped ({ address, publicKey }: State): React.ReactNode {
const { className, isHighlight = false, size = DEFAULT_SIZE, style, theme = settings.uiTheme } = this.props;
const Component = !address
? Empty
: Components[theme] || Fallback;
return (
<Wrapper
className={`ui--IdentityIcon ${className}`}
key={address}
style={style}
>
<Component
address={address}
className={isHighlight ? 'highlight' : ''}
publicKey={publicKey}
size={size}
/>
</Wrapper>
);
}
private onCopy = (): void => {
const { onCopy } = this.props;
const { address } = this.state;
if (address && onCopy) {
onCopy(address);
}
}
}
@@ -0,0 +1,16 @@
Apache-2.0 License (Apache-2.0)
Copyright 2016 Dan Finlay
Copyright 2017-2019 @polkadot/react-identicon authors & contributors
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 DApache-2.0LAIMS 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.
@@ -0,0 +1,19 @@
# @polkadot/react-identicon/beachball
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 Apache-2.0)
## Usage
Also see [src/demo.js](src/demo.js) for a randomly generated example.
![demo](https://raw.githubusercontent.com/polkadot-js/ui/master/packages/react-identicon/demo.png)
@@ -0,0 +1,59 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ColorGen } from './types';
import newSeeder from './seeder';
import newColors from './colors';
describe('colors', (): void => {
let colors: ColorGen;
beforeEach((): void => {
colors = newColors(newSeeder());
});
it('generates using default alpha', (): void => {
expect(
colors()
).toEqual(
// 'hsla(166.70000000000005, 98.6%, 27.6%, 0.9)'
'hsl(37.19999999999999, 100%, 54.9%)'
);
});
it('applies specified alpha', (): void => {
expect(
colors(0.5)
).toEqual(
// 'hsla(166.70000000000005, 98.6%, 27.6%, 0.5)'
'hsla(37.19999999999999, 100%, 54.9%, 0.5)'
);
});
it('rolates colors', (): void => {
colors();
expect(
colors()
).not.toEqual('hsla(166.70000000000005, 98.6%, 27.6%, 0.9)');
});
it('works in edge conditions (0xff)', (): void => {
const u8a = new Uint8Array(32);
u8a.fill(255);
expect(
colors = newColors(newSeeder(u8a))
).not.toThrow();
expect(
colors()
).toEqual(
// 'hsla(234.39999999999998, 75.9%, 51.2%, 0.9)'
'hsl(15, 0%, 100%)'
);
});
});
@@ -0,0 +1,27 @@
// Copyright 2016 Dan Finlay
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ColorGen, Seeder } from './types';
import Color from 'color';
import { COLORS } from './defaults';
const WOBBLE = 30;
export default function colors (seeder: Seeder): ColorGen {
const amount = (seeder() * WOBBLE) - (WOBBLE / 2);
const all = COLORS.map((hex): Color =>
Color(hex).rotate(amount)
);
return (alpha: number = 1): string => {
const index = Math.floor(all.length * seeder());
return all.splice(index, 1)[0]
.alpha(alpha)
.string();
};
}
@@ -0,0 +1,45 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import container from './container';
describe('container', (): void => {
it('applies default styles', (): void => {
expect(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(container(100).style as any)._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', (): void => {
expect(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(container(50, 'black', '', { display: 'block' }).style as any)._values
).toMatchObject({
background: 'black',
'border-radius': '25px',
display: 'block',
height: '50px',
margin: '0px',
overflow: 'hidden',
padding: '0px',
width: '50px'
});
});
it('applies the specified className', (): void => {
expect(
container(100, 'blue', 'testClass').className
).toEqual('testClass');
});
});
@@ -0,0 +1,27 @@
// Copyright 2016 Dan Finlay
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export default function container (diameter: number, background: string = 'white', className: string = '', _style: { [index: 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;
Object.keys(style).forEach((key: unknown): void => {
element.style[key as number] = style[key as number];
});
return element;
}
@@ -0,0 +1,16 @@
// Copyright 2016 Dan Finlay
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
const COLORS: string[] = [
// https://sashat.me/2017/01/11/list-of-20-simple-distinct-colors/
'#ffe119', '#4363d8', '#f58231', '#fabebe', '#e6beff', '#800000', '#000075', '#a9a9a9', '#ffffff', '#000000'
];
const SHAPE_COUNT = 5;
export {
COLORS,
SHAPE_COUNT
};
@@ -0,0 +1,35 @@
// Copyright 2016 Dan Finlay
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { isNull } from '@polkadot/util';
import { encodeAddress, randomAsU8a } from '@polkadot/util-crypto';
import identicon from '.';
const element = document.getElementById('demo');
function generateIcon (seed: string = encodeAddress(randomAsU8a(32))): void {
const start = Date.now();
if (isNull(element)) {
throw new Error('Unable to find #demo element');
}
element.appendChild(
identicon(seed, 100, 'padded')
);
console.log(`Icon generated in ${(Date.now() - start)}ms`);
}
function generateIcons (count: number = 512): void {
generateIcon(encodeAddress(new Uint8Array(32)));
for (let index = 1; index < count; index++) {
generateIcon();
}
}
generateIcons();
@@ -0,0 +1,29 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import xmlserializer from 'xmlserializer';
import identicon from '.';
describe('identicon', (): void => {
it('generates a basic [0,..,0] identicon', (): void => {
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="background: hsl(37.19999999999999, 100%, 54%); 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="hsl(212.10000000000002, 65.6%, 55.5%)"/><circle cx="128" cy="153.6" r="102.4" fill="hsl(9.800000000000011, 90.7%, 57.6%)"/><circle cx="128" cy="166.4" r="76.8" fill="hsl(345, 85.7%, 86.3%)"/><circle cx="128" cy="179.2" r="51.2" fill="hsl(261.9, 100%, 87.3%)"/><circle cx="128" cy="192" r="25.6" fill="hsl(345, 100%, 25.1%)"/></svg></div></div>'
);
});
it('allows overrides', (): void => {
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="background: hsl(37.19999999999999, 100%, 54%); 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="hsl(212.10000000000002, 65.6%, 55.5%)"/><circle cx="50" cy="60" r="40" fill="hsl(9.800000000000011, 90.7%, 57.6%)"/><circle cx="50" cy="65" r="30" fill="hsl(345, 85.7%, 86.3%)"/><circle cx="50" cy="70" r="20" fill="hsl(261.9, 100%, 87.3%)"/><circle cx="50" cy="75" r="10" fill="hsl(345, 100%, 25.1%)"/></svg></div></div>'
);
});
});
@@ -0,0 +1,31 @@
// Copyright 2016 Dan Finlay
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import colors from './colors';
import newContainer from './container';
import newSeeder from './seeder';
import newShape from './shape/circle';
import newElement from './svg/element';
import { SHAPE_COUNT } from './defaults';
export default function identicon (seed: string | Uint8Array, diameter: number = 256, className: string = '', style?: { [index: 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;
}
@@ -0,0 +1,29 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Seeder } from './types';
import newSeeder from './seeder';
describe('seeder', (): void => {
let seeder: Seeder;
beforeEach((): void => {
seeder = newSeeder(new Uint8Array([1, 2, 3, 4]));
});
it('generates numbers using 2 spaces', (): void => {
expect(
seeder()
).toEqual(0.0156402587890625);
});
it('generates numbers using 2 spaces (incremented)', (): void => {
seeder();
expect(
seeder()
).toEqual(0.0078582763671875);
});
});
@@ -0,0 +1,31 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Seeder } from './types';
import { isU8a, stringToU8a } from '@polkadot/util';
const DIVISOR = 256 * 256;
export default function seeder (_seed: string | Uint8Array = new Uint8Array(32)): Seeder {
const seed: Uint8Array = isU8a(_seed)
? _seed
: stringToU8a(_seed);
let index = (seed[Math.floor(seed.length / 2)] % seed.length) - 1;
const next = (): number => {
index += 1;
if (index === seed.length) {
index = 0;
}
return seed[index];
};
return (): number => {
return ((next() * 256) + next()) / DIVISOR;
};
}
@@ -0,0 +1,18 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import xmlserializer from 'xmlserializer';
import seeder from '../seeder';
import circle from './circle';
describe('circle', (): void => {
it('creates a circle shape', (): void => {
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"/>');
});
});
@@ -0,0 +1,23 @@
// Copyright 2016 Dan Finlay
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Seeder } from '../types';
import newCircle from '../svg/circle';
import { SHAPE_COUNT } from '../defaults';
export default 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('', 'fill', fill);
return svg;
}
@@ -0,0 +1,26 @@
// Copyright 2016 Dan Finlay
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Seeder } from '../types';
import newRect from '../svg/rect';
import { SHAPE_COUNT } from '../defaults';
export default 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('', 'transform', `translate(${tx} ${ty}) rotate(${rot} ${center} ${center})`);
svg.setAttributeNS('', 'fill', fill);
return svg;
}
@@ -0,0 +1,17 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import xmlserializer from 'xmlserializer';
import circle from './circle';
describe('circle', (): void => {
it('creates a basic SVG circle element', (): void => {
expect(
xmlserializer.serializeToString(
circle(123, 12, 34)
)
).toEqual('<circle xmlns="http://www.w3.org/2000/svg" cx="12" cy="34" r="123"/>');
});
});
@@ -0,0 +1,15 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import createSvg from './svg';
export default function circle (r: number, cx: number, cy: number): Element {
const elem = createSvg('circle');
elem.setAttributeNS('', 'cx', `${cx}`);
elem.setAttributeNS('', 'cy', `${cy}`);
elem.setAttributeNS('', 'r', `${r}`);
return elem;
}
@@ -0,0 +1,17 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import xmlserializer from 'xmlserializer';
import element from './element';
describe('element', (): void => {
it('creates a basic SVG element', (): void => {
expect(
xmlserializer.serializeToString(
element(123)
)
).toEqual('<svg xmlns="http://www.w3.org/2000/svg" x="0" y="0" width="123" height="123"/>');
});
});
@@ -0,0 +1,17 @@
// Copyright 2016 Dan Finlay
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import createSvg from './svg';
export default function element (size: number, type: string = 'svg', x: number = 0, y: number = 0): Element {
const elem = createSvg(type);
elem.setAttributeNS('', 'x', `${x}`);
elem.setAttributeNS('', 'y', `${y}`);
elem.setAttributeNS('', 'width', `${size}`);
elem.setAttributeNS('', 'height', `${size}`);
return elem;
}
@@ -0,0 +1,17 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import xmlserializer from 'xmlserializer';
import rect from './rect';
describe('rect', (): void => {
it('creates a basic SVG rect element', (): void => {
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"/>');
});
});
@@ -0,0 +1,14 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import createElement from './element';
export default function rect (size: number): Element {
const elem = createElement(size, 'rect');
elem.setAttributeNS('', 'rx', `${size / 16}`);
elem.setAttributeNS('', 'ry', `${size / 16}`);
return elem;
}
@@ -0,0 +1,17 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import xmlserializer from 'xmlserializer';
import svg from './svg';
describe('svg', (): void => {
it('creates a basic SVG element', (): void => {
expect(
xmlserializer.serializeToString(
svg('rect')
)
).toEqual('<rect xmlns="http://www.w3.org/2000/svg"/>');
});
});
@@ -0,0 +1,10 @@
// Copyright 2016 Dan Finlay
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
const SVG_NS = 'http://www.w3.org/2000/svg';
export default function svg (type: string): Element {
return document.createElementNS(SVG_NS, type);
}
@@ -0,0 +1,9 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export type Seeder = () => number;
export interface ColorGen {
(alpha?: number): string;
}
@@ -0,0 +1,5 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
declare module 'xmlserializer';
@@ -0,0 +1,33 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Props } from '../types';
import React from 'react';
import identicon from '../beachball';
export default class Beachball extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { className, style } = this.props;
return (
<div
className={`container ${className}`}
ref={this.appendIcon}
style={style}
/>
);
}
private appendIcon = (node: Element | null): void => {
const { address, size } = this.props;
if (node) {
node.appendChild(
identicon(address, size)
);
}
}
}
@@ -0,0 +1,26 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Props } from '../types';
import React from 'react';
export default class Empty extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { className, size, style } = this.props;
return (
<div
className={`container ${className}`}
style={style}
>
<svg
height={size}
viewBox='0 0 64 64'
width={size}
/>
</div>
);
}
}
@@ -0,0 +1,24 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Props } from '../types';
import React from 'react';
import jdenticon from 'jdenticon';
export default class Jdenticon extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { className, publicKey, size, style } = this.props;
return (
<div
className={`container ${className}`}
style={style}
dangerouslySetInnerHTML={ {
__html: jdenticon.toSvg(publicKey.substr(2), size)
} }
/>
);
}
}
@@ -0,0 +1,174 @@
// Copyright 2018 Paritytech via paritytech/oo7/polkadot-identicon
// Copyright 2018 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
// This has been converted from the original version that can be found at
//
// https://github.com/paritytech/oo7/blob/251ba2b7c45503b68eab4320c270b5afa9bccb60/packages/polkadot-identicon/src/index.jsx
//
// Here we have done the following to convert the component -
// - Converted the code to TypeScript
// - Removed the oo7 dependencies (since not initialised properly, it makes calls to wrong endpoints)
// - Remove encoding functionality, these are catered for in the base
// - Remove copy functionality (this is catered from in the base components)
// - Split calculations into relevant functions
// - Move constants to file-level
// - Overall it is now just a static component, expecting an address as an input value
import { Props as BaseProps } from '../types';
import React from 'react';
import { blake2AsU8a, decodeAddress } from '@polkadot/util-crypto';
interface Props extends BaseProps {
sixPoint?: boolean;
}
interface Scheme {
freq: number;
colors: number[];
}
const blake2 = (value: Uint8Array): Uint8Array =>
blake2AsU8a(value, 512);
const s = 64;
const c = s / 2;
const z = s / 64 * 5;
const zero = blake2(new Uint8Array(32));
const schema: { [index: string]: Scheme } = {
target: { freq: 1, colors: [0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 0, 28, 0, 1] },
cube: { freq: 20, colors: [0, 1, 3, 2, 4, 3, 0, 1, 3, 2, 4, 3, 0, 1, 3, 2, 4, 3, 5] },
quazar: { freq: 16, colors: [1, 2, 3, 1, 2, 4, 5, 5, 4, 1, 2, 3, 1, 2, 4, 5, 5, 4, 0] },
flower: { freq: 32, colors: [0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, 3] },
cyclic: { freq: 32, colors: [0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5, 6] },
vmirror: { freq: 128, colors: [0, 1, 2, 3, 4, 5, 3, 4, 2, 0, 1, 6, 7, 8, 9, 7, 8, 6, 10] },
hmirror: { freq: 128, colors: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 8, 6, 7, 5, 3, 4, 2, 11] }
};
export default class Identicon extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { address, className, size, style } = this.props;
const xy = this.getCircleXY();
const colors = this.getColors();
return (
<div
className={`container ${className}`}
style={style}
>
<svg
id={address}
name={address}
width={size}
height={size}
viewBox='0 0 64 64'
>
{this.renderCircle(s / 2, s / 2, s / 2, '#eee', -1)}
{
xy.map(([x, y], index): React.ReactNode =>
this.renderCircle(x, y, z, colors[index], index)
)
}
</svg>
</div>
);
}
private renderCircle (cx: number, cy: number, r: number, fill: string, key: number): React.ReactNode {
return (
<circle
key={key}
cx={cx}
cy={cy}
r={r}
fill={fill}
/>
);
}
private getCircleXY (): [number, number][] {
const { r, ro2, r3o4, ro4, rroot3o2, rroot3o4 } = this.getRotation();
return [
[c, c - r],
[c, c - ro2],
[c - rroot3o4, c - r3o4],
[c - rroot3o2, c - ro2],
[c - rroot3o4, c - ro4],
[c - rroot3o2, c],
[c - rroot3o2, c + ro2],
[c - rroot3o4, c + ro4],
[c - rroot3o4, c + r3o4],
[c, c + r],
[c, c + ro2],
[c + rroot3o4, c + r3o4],
[c + rroot3o2, c + ro2],
[c + rroot3o4, c + ro4],
[c + rroot3o2, c],
[c + rroot3o2, c - ro2],
[c + rroot3o4, c - ro4],
[c + rroot3o4, c - r3o4],
[c, c]
];
}
private getRotation (): { r: number; ro2: number; r3o4: number; ro4: number; rroot3o2: number; rroot3o4: number } {
const { sixPoint = false } = this.props;
const r = sixPoint
? (s / 2 / 8 * 5)
: (s / 2 / 4 * 3);
const rroot3o2 = r * Math.sqrt(3) / 2;
const ro2 = r / 2;
const rroot3o4 = r * Math.sqrt(3) / 4;
const ro4 = r / 4;
const r3o4 = r * 3 / 4;
return { r, ro2, r3o4, ro4, rroot3o2, rroot3o4 };
}
private getColors (): string[] {
const { address } = this.props;
const total = Object.keys(schema).map((k): number => schema[k].freq).reduce((a, b): number => a + b);
const id = Array.from(blake2(decodeAddress(address))).map((x, i): number => (x + 256 - zero[i]) % 256);
const d = Math.floor((id[30] + id[31] * 256) % total);
const rot = (id[28] % 6) * 3;
const sat = (Math.floor(id[29] * 70 / 256 + 26) % 80) + 30;
const scheme = this.findScheme(d);
const palette = Array.from(id).map((x, i): string => {
const b = (x + i % 28 * 58) % 256;
if (b === 0) {
return '#444';
} else if (b === 255) {
return 'transparent';
}
const h = Math.floor(b % 64 * 360 / 64);
const l = [53, 15, 35, 75][Math.floor(b / 64)];
return `hsl(${h}, ${sat}%, ${l}%)`;
});
return scheme.colors.map((_, i): string =>
palette[scheme.colors[i < 18 ? (i + rot) % 18 : 18]]
);
}
private findScheme (d: number): Scheme {
let cum = 0;
const ks = Object.keys(schema);
for (const i in ks) {
cum += schema[ks[i]].freq;
if (d < cum) {
return schema[ks[i]];
}
}
throw new Error('Unable to find schema');
}
}
@@ -0,0 +1,8 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
export { default as Beachball } from './Beachball';
export { default as Empty } from './Empty';
export { default as Jdenticon } from './Jdenticon';
export { default as Polkadot } from './Polkadot';
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2019 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import Identicon from './Identicon';
export * from './icons';
export default Identicon;
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2018 @polkadot/react-identicon authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Prefix } from '@polkadot/util-crypto/address/types';
export interface BaseProps {
className?: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
style?: Record<string, any>;
}
export interface Props extends BaseProps {
address: string;
publicKey: string;
size: number;
}
export interface IdentityProps extends BaseProps {
isHighlight?: boolean;
onCopy?: (value: string) => void;
prefix?: Prefix;
size?: number;
theme?: 'beachball' | 'jdenticon' | 'polkadot' | 'substrate';
value?: string | Uint8Array | null;
}