Move in ui-qr as react-qr (from apps) (#164)

This commit is contained in:
Jaco Greeff
2019-07-29 10:55:02 +02:00
committed by GitHub
parent 1de68ac029
commit 9664b72253
16 changed files with 778 additions and 0 deletions
+160
View File
@@ -0,0 +1,160 @@
// Copyright 2017-2019 @polkadot/react-qr 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 { BaseProps } from './types';
import React from 'react';
import qrcode from 'qrcode-generator';
import styled from 'styled-components';
import { u8aConcat } from '@polkadot/util';
import { xxhashAsHex } from '@polkadot/util-crypto';
import { createSize } from './constants';
import { encodeNumber, decodeString } from './util';
interface Props extends BaseProps {
size?: number;
value: Uint8Array;
withMulti?: boolean;
}
interface State {
frames: string[];
frameIdx: number;
image: string | null;
timerId: NodeJS.Timeout | null;
valueHash: string | null;
}
const FRAME_DELAY = 2100;
const FRAME_SIZE = 1716;
const MULTIPART = new Uint8Array([0]);
function getDataUrl (value: string): string {
const qr = qrcode(0, 'M');
qr.addData(value, 'Byte');
qr.make();
return qr.createDataURL(16, 0);
}
function createFrames (input: Uint8Array): string[] {
const frames = [];
let idx = 0;
while (idx < input.length) {
frames.push(input.subarray(idx, idx + FRAME_SIZE));
idx += FRAME_SIZE;
}
return frames.map((frame, index: number): string =>
decodeString(u8aConcat(
MULTIPART,
encodeNumber(frames.length),
encodeNumber(index),
frame
))
);
}
class Display extends React.PureComponent<Props, State> {
public state: State = {
frames: [],
frameIdx: 0,
image: null,
timerId: null,
valueHash: null
};
public static getDerivedStateFromProps ({ value, withMulti = true }: Props, prevState: State): Pick<State, never> | null {
const valueHash = xxhashAsHex(value);
if (valueHash === prevState.valueHash) {
return null;
}
const frames: string[] = withMulti
? createFrames(value)
: [decodeString(value)];
return {
frames,
frameIdx: 0,
image: getDataUrl(frames[0]),
valueHash
};
}
public componentDidMount (): void {
this.setState({
timerId: setInterval(this.nextFrame, FRAME_DELAY)
});
}
public componentWillUnmount (): void {
const { timerId } = this.state;
if (timerId) {
clearInterval(timerId);
}
}
public render (): React.ReactNode {
const { className, size, style } = this.props;
const { image } = this.state;
if (!image) {
return null;
}
return (
<div
className={className}
style={createSize(size)}
>
<div
className='ui--qr-Display'
style={style}
>
<img src={image} />
</div>
</div>
);
}
private nextFrame = (): void => {
const { frames, frameIdx } = this.state;
if (!frames || frames.length <= 1) {
return;
}
const nextIdx = frameIdx === frames.length - 1
? 0
: frameIdx + 1;
this.setState({
frameIdx: nextIdx,
image: getDataUrl(frames[nextIdx])
});
}
}
export default styled(Display as React.ComponentClass<Props>)`
.ui--qr-Display {
height: 100%;
width: 100%;
img,
svg {
background: white;
height: auto !important;
max-height: 100%;
max-width: 100%;
width: auto !important;
}
}
`;
+62
View File
@@ -0,0 +1,62 @@
// Copyright 2017-2019 @polkadot/react-qr 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 { BaseProps } from './types';
import React from 'react';
import { u8aConcat } from '@polkadot/util';
import { xxhashAsHex } from '@polkadot/util-crypto';
import { ADDRESS_PREFIX } from './constants';
import { encodeString } from './util';
import QrDisplay from './Display';
interface Props extends BaseProps {
address: string;
}
interface State {
data: Uint8Array | null;
dataHash: string | null;
}
const PREFIX = encodeString(ADDRESS_PREFIX);
export default class DisplayExtrinsic extends React.PureComponent<Props, State> {
public state: State = {
data: null,
dataHash: null
};
public getDerivedStateFromProps ({ address }: Props, prevState: State): State | null {
const data = u8aConcat(
PREFIX,
encodeString(address)
);
const dataHash = xxhashAsHex(data);
if (dataHash === prevState.dataHash) {
return null;
}
return { data, dataHash };
}
public render (): React.ReactNode {
const { className, style } = this.props;
const { data } = this.state;
if (!data) {
return null;
}
return (
<QrDisplay
className={className}
style={style}
value={data}
/>
);
}
}
+66
View File
@@ -0,0 +1,66 @@
// Copyright 2017-2019 @polkadot/react-qr 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 { BaseProps } from './types';
import React from 'react';
import { u8aConcat } from '@polkadot/util';
import { decodeAddress, xxhashAsHex } from '@polkadot/util-crypto';
import QrDisplay from './Display';
interface Props extends BaseProps {
address: string;
payload: Uint8Array;
}
interface State {
data: Uint8Array | null;
dataHash: string | null;
}
const SUBSTRATE = new Uint8Array([0x53]);
const CRYPTO_SR25519 = new Uint8Array([0x01]);
const SIGN_TX = new Uint8Array([0x00]);
export default class DisplayPayload extends React.PureComponent<Props, State> {
public state: State = {
data: null,
dataHash: null
};
public getDerivedStateFromProps ({ address, payload }: Props, prevState: State): State | null {
const data = u8aConcat(
SUBSTRATE,
CRYPTO_SR25519,
SIGN_TX,
decodeAddress(address),
payload
);
const dataHash = xxhashAsHex(data);
if (dataHash === prevState.dataHash) {
return null;
}
return { data, dataHash };
}
public render (): React.ReactNode {
const { className, style } = this.props;
const { data } = this.state;
if (!data) {
return null;
}
return (
<QrDisplay
className={className}
style={style}
value={data}
/>
);
}
}
+72
View File
@@ -0,0 +1,72 @@
// Copyright 2017-2019 @polkadot/react-qr 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 { BaseProps } from './types';
import React from 'react';
import Reader from 'react-qr-reader';
import styled from 'styled-components';
import { createSize } from './constants';
interface Props extends BaseProps {
delay?: number;
onError?: (error: Error) => void;
onScan?: (data: string) => void;
size?: number;
}
const DEFAULT_DELAY = 150;
const DEFAULT_ERROR = (error: Error): void => {
console.error('@polkadot/react-qr:Scan', error.message);
};
class Scan extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { className, delay = DEFAULT_DELAY, size, style } = this.props;
return (
<div
className={className}
style={createSize(size)}
>
<Reader
className='ui--qr-Scan'
delay={delay}
onError={this.onError}
onScan={this.onScan}
style={style}
/>
</div>
);
}
private onError = (error: Error): void => {
const { onError = DEFAULT_ERROR } = this.props;
onError(error);
}
private onScan = (data: string | null): void => {
const { onScan } = this.props;
if (!data || !onScan) {
return;
}
onScan(data);
}
}
export default styled(Scan)`
.ui--qr-Scan {
display: inline-block;
height: 100%;
width: 100%;
video {
margin: 0;
}
}
`;
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2017-2019 @polkadot/react-qr 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 { BaseProps } from './types';
import React from 'react';
import { ADDRESS_PREFIX } from './constants';
import QrScan from './Scan';
import { decodeAddress } from '@polkadot/util-crypto';
interface Props extends BaseProps {
onError?: (error: Error) => void;
onScan?: (data: string) => void;
}
export default class ScanAddress extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { className, onError, style } = this.props;
return (
<QrScan
className={className}
onError={onError}
onScan={this.onScan}
style={style}
/>
);
}
private onScan = (data: string | null): void => {
const { onScan } = this.props;
if (!data || !onScan || !data.startsWith(ADDRESS_PREFIX)) {
return;
}
const address = data.substr(ADDRESS_PREFIX.length);
try {
decodeAddress(address);
onScan(address);
} catch (error) {
console.error('@polkadot/react-qr:QrScanAddress', error.message);
}
}
}
+40
View File
@@ -0,0 +1,40 @@
// Copyright 2017-2019 @polkadot/react-qr 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 { BaseProps } from './types';
import React from 'react';
import QrScan from './Scan';
import { encodeString } from './util';
interface Props extends BaseProps {
onError?: (error: Error) => void;
onScan?: (data: Uint8Array) => void;
}
export default class ScanSignature extends React.PureComponent<Props> {
public render (): React.ReactNode {
const { className, onError, style } = this.props;
return (
<QrScan
className={className}
onError={onError}
onScan={this.onScan}
style={style}
/>
);
}
private onScan = (data: string | null): void => {
const { onScan } = this.props;
if (!data || !onScan) {
return;
}
onScan(encodeString(data));
}
}
+21
View File
@@ -0,0 +1,21 @@
// Copyright 2017-2019 @polkadot/react-qr 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 DEFAULT_SIZE = 300;
const ADDRESS_PREFIX = 'substrate:';
function createSize (size: number = DEFAULT_SIZE): Record<string, string> {
const height = `${size}px`;
return {
height,
width: height
};
}
export {
ADDRESS_PREFIX,
DEFAULT_SIZE,
createSize
};
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2019 @polkadot/react-qr 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 QrDisplayAddress } from './DisplayAddress';
export { default as QrDisplayPayload } from './DisplayPayload';
export { default as QrScanAddress } from './ScanAddress';
export { default as QrScanSignature } from './ScanSignature';
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2019 @polkadot/react-qr 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 interface BaseProps {
className?: string;
style?: Record<string, string | number>;
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2017-2019 @polkadot/react-qr 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 function encodeNumber (value: number): Uint8Array {
return new Uint8Array([value >> 8, value & 256]);
}
export function encodeString (value: string): Uint8Array {
const u8a = new Uint8Array(value.length);
for (let i = 0; i < value.length; i++) {
u8a[i] = value.charCodeAt(i);
}
return u8a;
}
export function decodeString (value: Uint8Array): string {
return value.reduce((str, code): string => {
return str + String.fromCharCode(code);
}, '');
}