ghost-portal/packages/react-components/src/InputAddress.tsx
Jaco Greeff e6ca2db884
Rename ui-* -> react-* (#1495)
* Rename ui-* -> react-*

* Rename actual folders

* react-app -> react-components

* CSS links
2019-08-12 13:12:05 +02:00

315 lines
8.1 KiB
TypeScript

// Copyright 2017-2019 @polkadot/react-components 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 { BareProps } from './types';
import { KeyringOptions, KeyringSectionOption, KeyringSectionOptions, KeyringOption$Type } from '@polkadot/ui-keyring/options/types';
import React from 'react';
import store from 'store';
import styled from 'styled-components';
import createItem from '@polkadot/ui-keyring/options/item';
import keyring from '@polkadot/ui-keyring';
import keyringOption from '@polkadot/ui-keyring/options';
import { withMulti, withObservable } from '@polkadot/react-api';
import { classes, getAddressName } from './util';
import addressToAddress from './util/toAddress';
import Dropdown from './Dropdown';
interface Props extends BareProps {
defaultValue?: string | null;
help?: React.ReactNode;
hideAddress?: boolean;
isDisabled?: boolean;
isError?: boolean;
isInput?: boolean;
isMultiple?: boolean;
label?: string;
labelExtra?: React.ReactNode;
onChange?: (value: string | null) => void;
onChangeMulti?: (value: string[]) => void;
options?: KeyringSectionOption[];
optionsAll?: KeyringOptions;
placeholder?: string;
type?: KeyringOption$Type;
value?: string | Uint8Array | string[];
withEllipsis?: boolean;
withLabel?: boolean;
}
interface State {
value?: string;
}
const STORAGE_KEY = 'options:InputAddress';
const DEFAULT_TYPE = 'all';
const transformToAddress = (value: string | Uint8Array): string | null => {
try {
return addressToAddress(value) || null;
} catch (error) {
console.error('Unable to transform address', value);
}
return null;
};
const transformToAccountId = (value: string): string | null => {
if (!value) {
return null;
}
const accountId = transformToAddress(value);
return !accountId
? null
: accountId;
};
const createOption = (address: string): KeyringSectionOption => {
let isRecent: boolean | undefined;
const pair = keyring.getAccount(address);
let name: string | undefined;
if (pair) {
name = pair.meta.name;
} else {
const addr = keyring.getAddress(address);
if (addr) {
name = addr.meta.name;
isRecent = addr.meta.isRecent;
} else {
isRecent = true;
}
}
return createItem(address, name, !isRecent);
};
class InputAddress extends React.PureComponent<Props, State> {
public state: State = {};
public static getDerivedStateFromProps ({ value }: Props): Pick<State, never> | null {
try {
return {
value: Array.isArray(value)
? value.map(addressToAddress)
: (addressToAddress(value) || undefined)
};
} catch (error) {
return null;
}
}
public static readOptions (): Record<string, any> {
return store.get(STORAGE_KEY) || { defaults: {} };
}
public static getLastValue (type: KeyringOption$Type = DEFAULT_TYPE): any {
const options = InputAddress.readOptions();
return options.defaults[type];
}
public static setLastValue (type: KeyringOption$Type = DEFAULT_TYPE, value: string): void {
const options = InputAddress.readOptions();
options.defaults[type] = value;
store.set(STORAGE_KEY, options);
}
public render (): React.ReactNode {
const { className, defaultValue, help, hideAddress = false, isDisabled = false, isError, isMultiple, label, labelExtra, options, optionsAll, placeholder, type = DEFAULT_TYPE, style, withEllipsis, withLabel } = this.props;
const { value } = this.state;
const hasOptions = (options && options.length !== 0) || (optionsAll && Object.keys(optionsAll[type]).length !== 0);
if (!hasOptions && !isDisabled) {
return null;
}
const lastValue = InputAddress.getLastValue(type);
const lastOption = this.getLastOptionValue();
const actualValue = transformToAddress(
isDisabled || (defaultValue && this.hasValue(defaultValue))
? defaultValue
: (
this.hasValue(lastValue)
? lastValue
: (lastOption && lastOption.value)
)
);
const actualOptions = options || (
isDisabled && actualValue
? [createOption(actualValue)]
: optionsAll
? optionsAll[type]
: []
);
let _defaultValue;
if (value !== undefined) {
_defaultValue = undefined;
} else if (isMultiple) {
_defaultValue = undefined;
} else {
_defaultValue = actualValue;
}
return (
<Dropdown
className={classes('ui--InputAddress', hideAddress && 'hideAddress', className)}
defaultValue={_defaultValue}
help={help}
isDisabled={isDisabled}
isError={isError}
isMultiple={isMultiple}
label={label}
labelExtra={labelExtra}
onChange={
isMultiple
? this.onChangeMulti
: this.onChange
}
onSearch={this.onSearch}
options={actualOptions}
placeholder={placeholder}
renderLabel={
isMultiple
? this.renderLabel
: undefined
}
style={style}
value={
isMultiple
? undefined
: value
}
withEllipsis={withEllipsis}
withLabel={withLabel}
/>
);
}
private renderLabel = ({ value }: KeyringSectionOption): string | undefined => {
if (!value) {
return undefined;
}
return getAddressName(value, null, true);
}
private getLastOptionValue (): KeyringSectionOption | undefined {
const { optionsAll, type = DEFAULT_TYPE } = this.props;
if (!optionsAll) {
return;
}
const available = optionsAll[type].filter(({ value }): boolean => !!value);
return available.length
? available[available.length - 1]
: undefined;
}
private hasValue (test?: string): boolean {
const { optionsAll, type = DEFAULT_TYPE } = this.props;
if (!optionsAll) {
return false;
}
return !!optionsAll[type].find(({ value }): boolean => test === value);
}
private onChange = (address: string): void => {
const { onChange, type } = this.props;
InputAddress.setLastValue(type, address);
onChange && onChange(transformToAccountId(address));
}
private onChangeMulti = (addresses: string[]): void => {
const { onChangeMulti } = this.props;
if (onChangeMulti) {
onChangeMulti(
addresses
.map(transformToAccountId)
.filter((address): string => address as string) as string[]
);
}
}
private onSearch = (filteredOptions: KeyringSectionOptions, _query: string): KeyringSectionOptions => {
const { isInput = true } = this.props;
const query = _query.trim();
const queryLower = query.toLowerCase();
const matches = filteredOptions.filter((item): boolean =>
item.value !== null && (
item.name.toLowerCase().indexOf(queryLower) !== -1 ||
item.value.toLowerCase().indexOf(queryLower) !== -1
)
);
const valueMatches = matches.filter((item): boolean =>
item.value !== null
);
if (isInput && valueMatches.length === 0) {
const accountId = transformToAccountId(query);
if (accountId) {
matches.push(
keyring.saveRecent(
accountId.toString()
).option
);
}
}
return matches.filter((item, index): boolean => {
const isLast = index === matches.length - 1;
const nextItem = matches[index + 1];
const hasNext = nextItem && nextItem.value;
return item.value !== null || (!isLast && !!hasNext);
});
}
}
export { InputAddress };
export default withMulti(
styled(InputAddress)`
.ui.dropdown .text {
width: 100%;
}
.ui.search.selection.dropdown {
> .text > .ui--KeyPair {
.ui--IdentityIcon {
border: 1px solid #888;
border-radius: 50%;
left: -2.75rem;
top: -1.2rem;
}
.name {
margin-left: 0;
}
}
}
&.hideAddress .ui--KeyPair .address {
flex: 0;
max-width: 0;
}
`,
withObservable(keyringOption.optionsSubject, { propName: 'optionsAll' })
);