Keyring observable subject around available options (#240)
* Start of keyring observable * All update operations through observables * Cleanup create * onChange -> rxChange for observables * Options is observable * Fix swap bug with same params * Missing onChange -> rxChange * Cleanups * Remove extra comment
This commit is contained in:
parent
921621187c
commit
bf4b3b4ed9
@ -8,6 +8,7 @@ import React from 'react';
|
||||
|
||||
import Button from '@polkadot/ui-app/Button';
|
||||
import Input from '@polkadot/ui-app/Input';
|
||||
import { InputAddress } from '@polkadot/ui-app/InputAddress';
|
||||
import Password from '@polkadot/ui-app/Password';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import keyring from '@polkadot/ui-keyring/index';
|
||||
@ -212,11 +213,12 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
onCommit = (): void => {
|
||||
const { onBack } = this.props;
|
||||
const { name, password, seed } = this.state;
|
||||
|
||||
keyring.createAccount(
|
||||
const pair = keyring.createAccount(
|
||||
formatSeed(seed), password, { name }
|
||||
);
|
||||
|
||||
InputAddress.setLastValue('account', pair.address());
|
||||
|
||||
onBack();
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import React from 'react';
|
||||
|
||||
import Button from '@polkadot/ui-app/Button';
|
||||
import Input from '@polkadot/ui-app/Input';
|
||||
import { InputAddress } from '@polkadot/ui-app/InputAddress';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import keyring from '@polkadot/ui-keyring/index';
|
||||
import addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
@ -167,6 +168,7 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
const { address, name } = this.state;
|
||||
|
||||
keyring.saveAddress(address, { name });
|
||||
InputAddress.setLastValue('address', address);
|
||||
|
||||
onBack();
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { KeyringOption$Type } from '@polkadot/ui-keyring/types';
|
||||
import { KeyringOption$Type } from '@polkadot/ui-keyring/options/types';
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
@ -13,13 +13,13 @@ import RxNonce from '@polkadot/ui-react-rx/Nonce';
|
||||
|
||||
type Props = BareProps & {
|
||||
label: string,
|
||||
onChange: (value: BN) => void,
|
||||
rxChange: (value: BN) => void,
|
||||
value?: Uint8Array
|
||||
};
|
||||
|
||||
export default class Nonce extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { className, label, style, onChange, value } = this.props;
|
||||
const { className, label, style, rxChange, value } = this.props;
|
||||
|
||||
if (!value) {
|
||||
return null;
|
||||
@ -36,7 +36,7 @@ export default class Nonce extends React.PureComponent<Props> {
|
||||
>
|
||||
<RxNonce
|
||||
className='ui disabled dropdown selection'
|
||||
onChange={onChange}
|
||||
rxChange={rxChange}
|
||||
params={value}
|
||||
/>
|
||||
</Labelled>
|
||||
|
||||
@ -65,7 +65,7 @@ class Selection extends React.PureComponent<Props, State> {
|
||||
label={t('display.nonce', {
|
||||
defaultValue: 'with an index'
|
||||
})}
|
||||
onChange={this.onChangeNonce}
|
||||
rxChange={this.onChangeNonce}
|
||||
value={publicKey}
|
||||
/>
|
||||
<Button.Group>
|
||||
|
||||
@ -79,7 +79,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
>
|
||||
<Nonce
|
||||
className='ui disabled dropdown selection'
|
||||
onChange={this.onChangeNonce}
|
||||
rxChange={this.onChangeNonce}
|
||||
params={publicKey}
|
||||
/>
|
||||
</Labelled>
|
||||
|
||||
@ -59,6 +59,7 @@ class Selection extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
<Params
|
||||
item={key}
|
||||
key={`${key.section}.${key.name}`}
|
||||
onChange={this.onChangeParams}
|
||||
/>
|
||||
</div>
|
||||
@ -107,7 +108,11 @@ class Selection extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
onChangeKey = (key: SectionItem<Storages>): void => {
|
||||
this.nextState({ key } as State);
|
||||
this.nextState({
|
||||
isValid: false,
|
||||
key,
|
||||
params: []
|
||||
});
|
||||
}
|
||||
|
||||
onChangeParams = (params: RawParams = []): void => {
|
||||
|
||||
@ -19,8 +19,6 @@ type Props = I18nProps & {};
|
||||
|
||||
const pkgJson = require('../package.json');
|
||||
|
||||
console.log('@polkadot/apps', pkgJson.version);
|
||||
|
||||
function updateTestInfo (chain?: string) {
|
||||
keyring.setTestMode(isTestChain(chain));
|
||||
}
|
||||
@ -35,7 +33,7 @@ class NodeInfo extends React.PureComponent<Props> {
|
||||
label={t('info.chain', {
|
||||
defaultValue: 'chain: '
|
||||
})}
|
||||
onChange={updateTestInfo}
|
||||
rxChange={updateTestInfo}
|
||||
/>
|
||||
<NodeName label={t('info.clientName', {
|
||||
defaultValue: 'client: '
|
||||
|
||||
@ -6,6 +6,7 @@ import { BareProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import SUIDropdown from 'semantic-ui-react/dist/commonjs/modules/Dropdown/Dropdown';
|
||||
import isUndefined from '@polkadot/util/is/undefined';
|
||||
|
||||
import classes from './util/classes';
|
||||
import Labelled from './Labelled';
|
||||
@ -29,6 +30,17 @@ type SUIEvent = {
|
||||
};
|
||||
|
||||
export default class Dropdown<Option> extends React.PureComponent<Props<Option>> {
|
||||
componentDidMount () {
|
||||
const { defaultValue, value } = this.props;
|
||||
const startValue = isUndefined(value)
|
||||
? defaultValue
|
||||
: value;
|
||||
|
||||
if (startValue) {
|
||||
this.onChange(null, { value: startValue });
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, defaultValue, isDisabled, isError, label, onSearch, options, placeholder, style, withLabel, value } = this.props;
|
||||
|
||||
@ -56,7 +68,7 @@ export default class Dropdown<Option> extends React.PureComponent<Props<Option>>
|
||||
);
|
||||
}
|
||||
|
||||
onChange = (event: React.SyntheticEvent<Element>, { value }: SUIEvent): void => {
|
||||
onChange = (event: React.SyntheticEvent<Element> | null, { value }: SUIEvent): void => {
|
||||
const { onChange, transform } = this.props;
|
||||
|
||||
onChange(
|
||||
|
||||
@ -2,18 +2,20 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { KeyringOptions, KeyringOption$Type } from '@polkadot/ui-keyring/types';
|
||||
import { KeyringOptions, KeyringOptionsSection, KeyringOption$Type } from '@polkadot/ui-keyring/options/types';
|
||||
import { BareProps } from '../types';
|
||||
|
||||
import './InputAddress.css';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import store from 'store';
|
||||
import keyring from '@polkadot/ui-keyring/index';
|
||||
import createOptionHeader from '@polkadot/ui-keyring/options/header';
|
||||
import addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
import { optionsSubject } from '@polkadot/ui-keyring/options';
|
||||
import makeOption from '@polkadot/ui-keyring/options/item';
|
||||
import withObservableBase from '@polkadot/ui-react-rx/with/observableBase';
|
||||
import isHex from '@polkadot/util/is/hex';
|
||||
import hexToU8a from '@polkadot/util/hex/toU8a';
|
||||
|
||||
@ -22,13 +24,14 @@ import classes from '../util/classes';
|
||||
import addressToAddress from '../util/toAddress';
|
||||
|
||||
type Props = BareProps & {
|
||||
defaultValue?: string | Uint8Array | null,
|
||||
defaultValue?: string | null,
|
||||
hideAddress?: boolean;
|
||||
isDisabled?: boolean,
|
||||
isError?: boolean,
|
||||
isInput?: boolean,
|
||||
label?: string,
|
||||
onChange: (value: Uint8Array) => void,
|
||||
optionsAll?: KeyringOptions,
|
||||
placeholder?: string,
|
||||
type?: KeyringOption$Type,
|
||||
value?: string | Uint8Array,
|
||||
@ -36,12 +39,12 @@ type Props = BareProps & {
|
||||
};
|
||||
|
||||
type State = {
|
||||
defaultValue: string | undefined,
|
||||
options: KeyringOptions,
|
||||
value?: string
|
||||
};
|
||||
|
||||
const RECENT_KEY = 'header-recent';
|
||||
const STORAGE_KEY = 'options:InputAddress';
|
||||
const DEFAULT_TYPE = 'all';
|
||||
|
||||
const transform = (value: string): Uint8Array => {
|
||||
if (isHex(value)) {
|
||||
@ -55,14 +58,11 @@ const transform = (value: string): Uint8Array => {
|
||||
}
|
||||
};
|
||||
|
||||
// NOTE: We are not extending Component here since the options may change in the keyring (which needs a re-render), however the input props will be the same (so, no PureComponent with shallow compare here)
|
||||
export default class InputAddress extends React.Component<Props, State> {
|
||||
class InputAddress extends React.PureComponent<Props, State> {
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
defaultValue: addressToAddress(props.defaultValue as string)
|
||||
} as State;
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps ({ value }: Props): State | null {
|
||||
@ -75,13 +75,39 @@ export default class InputAddress extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
static readOptions () {
|
||||
return store.get(STORAGE_KEY) || { defaults: {} };
|
||||
}
|
||||
|
||||
static getLastValue (type: KeyringOption$Type = DEFAULT_TYPE) {
|
||||
const options = InputAddress.readOptions();
|
||||
|
||||
return options.defaults[type];
|
||||
}
|
||||
|
||||
static setLastValue (type: KeyringOption$Type = DEFAULT_TYPE, value: string) {
|
||||
const options = InputAddress.readOptions();
|
||||
|
||||
options.defaults[type] = value;
|
||||
store.set(STORAGE_KEY, options);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { className, hideAddress = false, isDisabled = false, isError, label, onChange, type = 'all', style, withLabel } = this.props;
|
||||
const { defaultValue, value } = this.state;
|
||||
const options = keyring.getOptions(type);
|
||||
const hasValue = !!options.find(({ key }) =>
|
||||
key === defaultValue
|
||||
);
|
||||
const { className, defaultValue, hideAddress = false, isDisabled = false, isError, label, optionsAll, type = DEFAULT_TYPE, style, withLabel } = this.props;
|
||||
const { value } = this.state;
|
||||
|
||||
if (!optionsAll) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lastValue = InputAddress.getLastValue(type);
|
||||
const actualValue = isDisabled || (defaultValue && this.hasValue(defaultValue))
|
||||
? defaultValue
|
||||
: (
|
||||
this.hasValue(lastValue)
|
||||
? lastValue
|
||||
: undefined
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
@ -89,42 +115,57 @@ export default class InputAddress extends React.Component<Props, State> {
|
||||
defaultValue={
|
||||
value !== undefined
|
||||
? undefined
|
||||
: defaultValue
|
||||
: actualValue
|
||||
}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
onChange={onChange}
|
||||
onChange={this.onChange}
|
||||
onSearch={this.onSearch}
|
||||
options={
|
||||
isDisabled && !hasValue && defaultValue
|
||||
? [makeOption(defaultValue)]
|
||||
: options
|
||||
isDisabled && actualValue
|
||||
? [makeOption(actualValue)]
|
||||
: optionsAll[type]
|
||||
}
|
||||
style={style}
|
||||
transform={transform}
|
||||
value={value}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onSearch = (filteredOptions: KeyringOptions, query: string): KeyringOptions => {
|
||||
private hasValue (value?: string): boolean {
|
||||
const { optionsAll, type = DEFAULT_TYPE } = this.props;
|
||||
|
||||
if (!optionsAll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!optionsAll[type].find(({ key }) =>
|
||||
key === value
|
||||
);
|
||||
}
|
||||
|
||||
onChange = (address: string) => {
|
||||
const { onChange, type } = this.props;
|
||||
|
||||
InputAddress.setLastValue(type, address);
|
||||
|
||||
onChange(transform(address));
|
||||
}
|
||||
|
||||
onSearch = (filteredOptions: KeyringOptionsSection, query: string): KeyringOptionsSection => {
|
||||
const { isInput = true } = this.props;
|
||||
const queryLower = query.toLowerCase();
|
||||
const matches = filteredOptions.filter((item) => {
|
||||
if (item.value === null) {
|
||||
return true;
|
||||
}
|
||||
const matches = filteredOptions.filter((item) =>
|
||||
item.value === null ||
|
||||
item.name.toLowerCase().indexOf(queryLower) !== -1 ||
|
||||
item.value.toLowerCase().indexOf(queryLower) !== -1
|
||||
);
|
||||
|
||||
const { name, value } = item;
|
||||
const hasMatch = name.toLowerCase().indexOf(queryLower) !== -1 ||
|
||||
value.toLowerCase().indexOf(queryLower) !== -1;
|
||||
|
||||
return hasMatch;
|
||||
});
|
||||
|
||||
const valueMatches = matches.filter((item) => item.value !== null);
|
||||
const valueMatches = matches.filter((item) =>
|
||||
item.value !== null
|
||||
);
|
||||
|
||||
if (isInput && valueMatches.length === 0) {
|
||||
const publicKey = transform(query);
|
||||
@ -139,7 +180,7 @@ export default class InputAddress extends React.Component<Props, State> {
|
||||
matches.push(
|
||||
keyring.saveRecent(
|
||||
addressEncode(publicKey)
|
||||
)
|
||||
).option
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -149,11 +190,12 @@ export default class InputAddress extends React.Component<Props, State> {
|
||||
const nextItem = matches[index + 1];
|
||||
const hasNext = nextItem && nextItem.value;
|
||||
|
||||
if (item.value !== null || (!isLast && hasNext)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
return item.value !== null || (!isLast && hasNext);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export { InputAddress };
|
||||
|
||||
// @ts-ignore There are still some issues with props and types - this is valid
|
||||
export default withObservableBase(optionsSubject, { propName: 'optionsAll' })(InputAddress);
|
||||
|
||||
@ -7,10 +7,14 @@ import { KeyringAddress, State } from '../types';
|
||||
import get from '../address/get';
|
||||
|
||||
export default function all (state: State): Array<KeyringAddress> {
|
||||
const available = state.accounts.subject.getValue();
|
||||
|
||||
return Object
|
||||
.keys(state.available.account)
|
||||
.keys(available)
|
||||
.map((address) =>
|
||||
get(state, address, 'account')
|
||||
)
|
||||
.filter((account) => !account.getMeta().isTesting);
|
||||
.filter((account) =>
|
||||
!account.getMeta().isTesting
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,17 +4,7 @@
|
||||
|
||||
import { State } from '../types';
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import { accountKey } from '../defaults';
|
||||
import createOptions from '../options';
|
||||
|
||||
export default function forgetAccount (state: State, address: string): void {
|
||||
state.keyring.removePair(address);
|
||||
|
||||
store.remove(accountKey(address));
|
||||
|
||||
delete state.available.account[address];
|
||||
|
||||
createOptions(state);
|
||||
state.accounts.remove(address);
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import { State } from '../types';
|
||||
import store from 'store';
|
||||
|
||||
import { accountKey } from '../defaults';
|
||||
import createOptions from '../options';
|
||||
|
||||
export default function saveAccountMeta (state: State, pair: KeyringPair, meta: KeyringPair$Meta): void {
|
||||
const address = pair.address();
|
||||
@ -17,7 +16,5 @@ export default function saveAccountMeta (state: State, pair: KeyringPair, meta:
|
||||
pair.setMeta(meta);
|
||||
json.meta = pair.getMeta();
|
||||
|
||||
store.set(accountKey(json.address), json);
|
||||
|
||||
createOptions(state);
|
||||
state.accounts.add(json.address, json);
|
||||
}
|
||||
|
||||
@ -5,11 +5,6 @@
|
||||
import { KeyringPair } from '@polkadot/util-keyring/types';
|
||||
import { State } from '../types';
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import { accountKey } from '../defaults';
|
||||
import createOptions from '../options';
|
||||
|
||||
export default function saveAccount (state: State, pair: KeyringPair, password?: string): void {
|
||||
const json = pair.toJson(password);
|
||||
|
||||
@ -17,7 +12,5 @@ export default function saveAccount (state: State, pair: KeyringPair, password?:
|
||||
json.meta.whenCreated = Date.now();
|
||||
}
|
||||
|
||||
store.set(accountKey(json.address), json);
|
||||
|
||||
createOptions(state);
|
||||
state.accounts.add(json.address, json);
|
||||
}
|
||||
|
||||
@ -7,8 +7,10 @@ import { KeyringAddress, State } from '../types';
|
||||
import get from './get';
|
||||
|
||||
export default function all (state: State): Array<KeyringAddress> {
|
||||
const available = state.addresses.subject.getValue();
|
||||
|
||||
return Object
|
||||
.keys(state.available.address)
|
||||
.keys(available)
|
||||
.map((address) =>
|
||||
get(state, address)
|
||||
);
|
||||
|
||||
@ -4,15 +4,6 @@
|
||||
|
||||
import { State } from '../types';
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import { addressKey } from '../defaults';
|
||||
import createOptions from '../options';
|
||||
|
||||
export default function forgetAddress (state: State, address: string): void {
|
||||
store.remove(addressKey(address));
|
||||
|
||||
delete state.available.address[address];
|
||||
|
||||
createOptions(state);
|
||||
state.addresses.remove(address);
|
||||
}
|
||||
|
||||
@ -13,15 +13,18 @@ export default function get (state: State, _address: string | Uint8Array, type:
|
||||
? _address
|
||||
: addressEncode(_address);
|
||||
const publicKey = addressDecode(address);
|
||||
const subject = type === 'account'
|
||||
? state.accounts.subject
|
||||
: state.addresses.subject;
|
||||
|
||||
return {
|
||||
address: (): string =>
|
||||
address,
|
||||
isValid: (): boolean =>
|
||||
!!state.available[type][address],
|
||||
!!subject.getValue()[address],
|
||||
publicKey: (): Uint8Array =>
|
||||
publicKey,
|
||||
getMeta: (): KeyringJson$Meta =>
|
||||
state.available[type][address].meta
|
||||
subject.getValue()[address].json.meta
|
||||
};
|
||||
}
|
||||
|
||||
@ -5,13 +5,10 @@
|
||||
import { KeyringPair$Meta } from '@polkadot/util-keyring/types';
|
||||
import { State } from '../types';
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import { addressKey } from '../defaults';
|
||||
import createOptions from '../options';
|
||||
|
||||
export default function saveMeta (state: State, address: string, meta: KeyringPair$Meta): void {
|
||||
const json = state.available.address[address] || {
|
||||
const available = state.addresses.subject.getValue();
|
||||
|
||||
const json = (available[address] && available[address].json) || {
|
||||
address,
|
||||
meta: {
|
||||
isRecent: void 0,
|
||||
@ -25,8 +22,5 @@ export default function saveMeta (state: State, address: string, meta: KeyringPa
|
||||
|
||||
delete json.meta.isRecent;
|
||||
|
||||
store.set(addressKey(address), json);
|
||||
state.available.address[address] = json;
|
||||
|
||||
createOptions(state);
|
||||
state.addresses.add(address, json);
|
||||
}
|
||||
|
||||
@ -2,15 +2,13 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { KeyringJson, KeyringOption, State } from '../types';
|
||||
import { SingleAddress } from '../observable/types';
|
||||
import { KeyringJson, State } from '../types';
|
||||
|
||||
import store from 'store';
|
||||
export default function saveRecent (state: State, address: string): SingleAddress {
|
||||
const available = state.addresses.subject.getValue();
|
||||
|
||||
import { addressKey } from '../defaults';
|
||||
import createOptions from '../options';
|
||||
|
||||
export default function saveRecent (state: State, address: string): KeyringOption {
|
||||
if (!state.available.address[address]) {
|
||||
if (!available[address]) {
|
||||
const json = {
|
||||
address,
|
||||
meta: {
|
||||
@ -19,12 +17,8 @@ export default function saveRecent (state: State, address: string): KeyringOptio
|
||||
}
|
||||
};
|
||||
|
||||
store.set(addressKey(json.address), json);
|
||||
state.available.address[address] = (json as KeyringJson);
|
||||
state.addresses.add(address, (json as KeyringJson));
|
||||
}
|
||||
|
||||
createOptions(state);
|
||||
|
||||
// @ts-ignore it should be there now...
|
||||
return state.options.recent.find(({ value }) => value === address);
|
||||
return state.addresses.subject.getValue()[address];
|
||||
}
|
||||
|
||||
@ -3,10 +3,13 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { KeyringPair, KeyringPair$Meta } from '@polkadot/util-keyring/types';
|
||||
import { KeyringAddress, KeyringInstance, KeyringOption$Type, KeyringOption, KeyringOptions, State } from './types';
|
||||
import { SingleAddress } from './observable/types';
|
||||
import { KeyringAddress, KeyringInstance, State } from './types';
|
||||
|
||||
import testKeyring from '@polkadot/util-keyring/testing';
|
||||
|
||||
import accounts from './observable/accounts';
|
||||
import addresses from './observable/addresses';
|
||||
import loadAll from './loadAll';
|
||||
import createAccount from './account/create';
|
||||
import forgetAccount from './account/forget';
|
||||
@ -23,12 +26,9 @@ import setTestMode from './setTestMode';
|
||||
|
||||
const state: State = {
|
||||
isTestMode: false,
|
||||
available: {
|
||||
account: {},
|
||||
address: {}
|
||||
},
|
||||
keyring: testKeyring(),
|
||||
options: {}
|
||||
accounts,
|
||||
addresses,
|
||||
keyring: testKeyring()
|
||||
};
|
||||
|
||||
loadAll(state);
|
||||
@ -48,8 +48,6 @@ export default ({
|
||||
getAddress(state, address),
|
||||
getAddresses: (): Array<KeyringAddress> =>
|
||||
getAddresses(state),
|
||||
getOptions: (type: KeyringOption$Type): KeyringOptions =>
|
||||
state.options[type],
|
||||
getPair: (address: string | Uint8Array): KeyringPair =>
|
||||
state.keyring.getPair(address),
|
||||
getPairs: (): Array<KeyringPair> =>
|
||||
@ -64,7 +62,7 @@ export default ({
|
||||
saveAccountMeta(state, pair, meta),
|
||||
saveAddress: (address: string, meta: KeyringPair$Meta): void =>
|
||||
saveAddress(state, address, meta),
|
||||
saveRecent: (address: string): KeyringOption =>
|
||||
saveRecent: (address: string): SingleAddress =>
|
||||
saveRecent(state, address),
|
||||
setTestMode: (isTest: boolean): void =>
|
||||
setTestMode(state, isTest)
|
||||
|
||||
@ -8,9 +8,12 @@ import isString from '@polkadot/util/is/string';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
export default function isAvailable (state: State, _address: Uint8Array | string): boolean {
|
||||
const accounts = state.accounts.subject.getValue();
|
||||
const addresses = state.addresses.subject.getValue();
|
||||
|
||||
const address = isString(_address)
|
||||
? _address
|
||||
: addressEncode(_address);
|
||||
|
||||
return !state.available.account[address] && !state.available.address[address];
|
||||
return !accounts[address] && !addresses[address];
|
||||
}
|
||||
|
||||
@ -11,16 +11,31 @@ import isHex from '@polkadot/util/is/hex';
|
||||
import hexToU8a from '@polkadot/util/hex/toU8a';
|
||||
|
||||
import saveAddress from './address/meta';
|
||||
import createOptions from './options';
|
||||
import initOptions from './options';
|
||||
import { accountRegex, addressRegex } from './defaults';
|
||||
|
||||
function addPairs ({ accounts, keyring }: State): void {
|
||||
keyring
|
||||
.getPairs()
|
||||
.forEach((pair) => {
|
||||
const address = pair.address();
|
||||
|
||||
accounts.add(address, {
|
||||
address,
|
||||
meta: pair.getMeta()
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default function loadAll (state: State): void {
|
||||
const { available, keyring } = state;
|
||||
const { accounts, addresses, keyring } = state;
|
||||
|
||||
addPairs(state);
|
||||
|
||||
store.each((json: KeyringJson, key: string) => {
|
||||
if (accountRegex.test(key)) {
|
||||
keyring.addFromJson(json as KeyringPair$Json);
|
||||
available.account[json.address] = json;
|
||||
accounts.add(json.address, json);
|
||||
} else if (addressRegex.test(key)) {
|
||||
const address = isHex(json.address)
|
||||
? addressEncode(hexToU8a(json.address))
|
||||
@ -35,9 +50,9 @@ export default function loadAll (state: State): void {
|
||||
saveAddress(state, address, json.meta);
|
||||
}
|
||||
|
||||
available.address[json.address] = json;
|
||||
addresses.add(json.address, json);
|
||||
}
|
||||
});
|
||||
|
||||
createOptions(state);
|
||||
initOptions(state);
|
||||
}
|
||||
|
||||
9
packages/ui-keyring/src/observable/accounts.ts
Normal file
9
packages/ui-keyring/src/observable/accounts.ts
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-keyring authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { accountKey } from '../defaults';
|
||||
|
||||
import genericSubject from './genericSubject';
|
||||
|
||||
export default genericSubject(accountKey);
|
||||
9
packages/ui-keyring/src/observable/addresses.ts
Normal file
9
packages/ui-keyring/src/observable/addresses.ts
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-keyring authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { addressKey } from '../defaults';
|
||||
|
||||
import genericSubject from './genericSubject';
|
||||
|
||||
export default genericSubject(addressKey);
|
||||
41
packages/ui-keyring/src/observable/genericSubject.ts
Normal file
41
packages/ui-keyring/src/observable/genericSubject.ts
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-keyring authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { SubjectInfo, AddressSubject, SingleAddress } from './types';
|
||||
import { KeyringJson } from '../types';
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import createOptionItem from '../options/item';
|
||||
|
||||
export default function genericSubject (keyCreator: (address: string) => string): AddressSubject {
|
||||
let current: SubjectInfo = {};
|
||||
const subject = new BehaviorSubject({});
|
||||
|
||||
return {
|
||||
add: (address: string, json: KeyringJson): SingleAddress => {
|
||||
current = { ...current };
|
||||
|
||||
current[address] = {
|
||||
json,
|
||||
option: createOptionItem(address, json.meta.name)
|
||||
};
|
||||
|
||||
store.set(keyCreator(address), json);
|
||||
subject.next(current);
|
||||
|
||||
return current[address];
|
||||
},
|
||||
remove: (address: string) => {
|
||||
current = { ...current };
|
||||
|
||||
delete current[address];
|
||||
|
||||
store.remove(keyCreator(address));
|
||||
subject.next(current);
|
||||
},
|
||||
subject
|
||||
};
|
||||
}
|
||||
19
packages/ui-keyring/src/observable/index.ts
Normal file
19
packages/ui-keyring/src/observable/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-keyring authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import accounts from './accounts';
|
||||
import addresses from './addresses';
|
||||
|
||||
export default combineLatest(
|
||||
accounts.subject,
|
||||
addresses.subject
|
||||
).pipe(
|
||||
map(([accounts, addresses]) => ({
|
||||
accounts,
|
||||
addresses
|
||||
}))
|
||||
);
|
||||
22
packages/ui-keyring/src/observable/types.d.ts
vendored
Normal file
22
packages/ui-keyring/src/observable/types.d.ts
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-keyring authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { KeyringSectionOption } from '../options/types';
|
||||
import { KeyringJson } from '../types';
|
||||
|
||||
export type SingleAddress = {
|
||||
json: KeyringJson,
|
||||
option: KeyringSectionOption
|
||||
}
|
||||
|
||||
export type SubjectInfo = {
|
||||
[index: string]: SingleAddress
|
||||
};
|
||||
|
||||
export type AddressSubject = {
|
||||
add: (address: string, json: KeyringJson) => SingleAddress,
|
||||
remove: (address: string) => void,
|
||||
subject: BehaviorSubject<SubjectInfo>
|
||||
};
|
||||
@ -2,9 +2,9 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { KeyringOption } from '../types';
|
||||
import { KeyringSectionOption } from './types';
|
||||
|
||||
export default function createHeader (name: string): KeyringOption {
|
||||
export default function createHeader (name: string): KeyringSectionOption {
|
||||
return {
|
||||
className: 'header disabled',
|
||||
name,
|
||||
|
||||
@ -2,31 +2,24 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { State, KeyringJson, KeyringOptions } from '../types';
|
||||
import { State } from '../types';
|
||||
import { SingleAddress } from '../observable/types';
|
||||
import { KeyringOptions, KeyringOptionsSection } from './types';
|
||||
|
||||
import createItem from './item';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import observableAll from '../observable';
|
||||
import createHeader from './header';
|
||||
|
||||
function addPairs ({ available, keyring }: State): void {
|
||||
keyring
|
||||
.getPairs()
|
||||
.forEach((pair) => {
|
||||
const address = pair.address();
|
||||
function addAccounts ({ accounts, isTestMode }: State, options: KeyringOptions): void {
|
||||
const available = accounts.subject.getValue();
|
||||
|
||||
available.account[address] = {
|
||||
address,
|
||||
meta: pair.getMeta()
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function addAccounts ({ available, isTestMode, options }: State): void {
|
||||
Object
|
||||
.keys(available.account)
|
||||
.map((address) => available.account[address])
|
||||
.forEach(({ address, meta: { name, isTesting = false } }: KeyringJson) => {
|
||||
const option = createItem(address, name);
|
||||
|
||||
.keys(available)
|
||||
.map((address) =>
|
||||
available[address]
|
||||
)
|
||||
.forEach(({ json: { meta: { isTesting = false } }, option }: SingleAddress) => {
|
||||
if (!isTesting) {
|
||||
options.account.push(option);
|
||||
} else if (isTestMode) {
|
||||
@ -35,17 +28,15 @@ function addAccounts ({ available, isTestMode, options }: State): void {
|
||||
});
|
||||
}
|
||||
|
||||
function addAddresses ({ available, options }: State): void {
|
||||
function addAddresses ({ addresses }: State, options: KeyringOptions): void {
|
||||
const available = addresses.subject.getValue();
|
||||
|
||||
Object
|
||||
.keys(available.address)
|
||||
.map((address) => available.address[address])
|
||||
.forEach(({ address, meta: { name, isRecent = false } }: KeyringJson) => {
|
||||
if (available.account[address]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const option = createItem(address, name);
|
||||
|
||||
.keys(available)
|
||||
.map((address) =>
|
||||
available[address]
|
||||
)
|
||||
.forEach(({ json: { meta: { isRecent = false } }, option }: SingleAddress) => {
|
||||
if (isRecent) {
|
||||
options.recent.push(option);
|
||||
} else {
|
||||
@ -54,36 +45,48 @@ function addAddresses ({ available, options }: State): void {
|
||||
});
|
||||
}
|
||||
|
||||
export default function createOptions (state: State): void {
|
||||
state.options = {
|
||||
function emptyOptions (): KeyringOptions {
|
||||
return {
|
||||
account: [],
|
||||
address: [],
|
||||
all: [],
|
||||
recent: [],
|
||||
testing: []
|
||||
};
|
||||
|
||||
addPairs(state);
|
||||
addAccounts(state);
|
||||
addAddresses(state);
|
||||
|
||||
const { options } = state;
|
||||
|
||||
options.address = ([] as KeyringOptions).concat(
|
||||
options.address.length ? [ createHeader('Addresses') ] : [],
|
||||
options.address,
|
||||
options.recent.length ? [ createHeader('Recent') ] : [],
|
||||
options.recent
|
||||
);
|
||||
options.account = ([] as KeyringOptions).concat(
|
||||
options.account.length ? [ createHeader('Accounts') ] : [],
|
||||
options.account,
|
||||
options.testing.length ? [ createHeader('Testing') ] : [],
|
||||
options.testing
|
||||
);
|
||||
|
||||
options.all = ([] as KeyringOptions).concat(
|
||||
options.account,
|
||||
options.address
|
||||
);
|
||||
}
|
||||
|
||||
const optionsSubject: BehaviorSubject<KeyringOptions> = new BehaviorSubject(emptyOptions());
|
||||
|
||||
// NOTE To be called _only_ once (should be addressed with https://github.com/polkadot-js/apps/issues/138)
|
||||
export default function initOptions (state: State): void {
|
||||
observableAll.subscribe((value) => {
|
||||
const options = emptyOptions();
|
||||
|
||||
addAccounts(state, options);
|
||||
addAddresses(state, options);
|
||||
|
||||
options.address = ([] as KeyringOptionsSection).concat(
|
||||
options.address.length ? [ createHeader('Addresses') ] : [],
|
||||
options.address,
|
||||
options.recent.length ? [ createHeader('Recent') ] : [],
|
||||
options.recent
|
||||
);
|
||||
options.account = ([] as KeyringOptionsSection).concat(
|
||||
options.account.length ? [ createHeader('Accounts') ] : [],
|
||||
options.account,
|
||||
options.testing.length ? [ createHeader('Testing') ] : [],
|
||||
options.testing
|
||||
);
|
||||
|
||||
options.all = ([] as KeyringOptionsSection).concat(
|
||||
options.account,
|
||||
options.address
|
||||
);
|
||||
|
||||
optionsSubject.next(options);
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
optionsSubject
|
||||
};
|
||||
|
||||
@ -2,15 +2,15 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { KeyringOption } from '../types';
|
||||
import { KeyringSectionOption } from './types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import KeyPair from './KeyPair';
|
||||
|
||||
export default function createItem (address: string, _name?: string): KeyringOption {
|
||||
export default function createItem (address: string, _name?: string): KeyringSectionOption {
|
||||
const name = _name === undefined
|
||||
? `${address.slice(0, 16)}…`
|
||||
? `${address.slice(0, 7)}…${address.slice(-7)}`
|
||||
: _name;
|
||||
|
||||
return {
|
||||
|
||||
25
packages/ui-keyring/src/options/types.d.ts
vendored
Normal file
25
packages/ui-keyring/src/options/types.d.ts
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-keyring authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
export type KeyringSectionOption = {
|
||||
className?: string,
|
||||
disabled?: boolean,
|
||||
content?: any | string, // node?
|
||||
key: string | null,
|
||||
name: string,
|
||||
text: any | string, // node?
|
||||
value: string | null
|
||||
};
|
||||
|
||||
export type KeyringOptionsSection = Array<KeyringSectionOption>;
|
||||
|
||||
export type KeyringOptions = {
|
||||
account: KeyringOptionsSection,
|
||||
address: KeyringOptionsSection,
|
||||
all: KeyringOptionsSection,
|
||||
recent: KeyringOptionsSection,
|
||||
testing: KeyringOptionsSection
|
||||
}
|
||||
|
||||
export type KeyringOption$Type = keyof KeyringOptions;
|
||||
@ -4,10 +4,6 @@
|
||||
|
||||
import { State } from './types';
|
||||
|
||||
import createOptions from './options';
|
||||
|
||||
export default function setTestMode (state: State, isTest: boolean): void {
|
||||
state.isTestMode = isTest;
|
||||
|
||||
createOptions(state);
|
||||
}
|
||||
|
||||
34
packages/ui-keyring/src/types.d.ts
vendored
34
packages/ui-keyring/src/types.d.ts
vendored
@ -3,20 +3,8 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { KeyringInstance as BaseKeyringInstance, KeyringPair, KeyringPair$Meta } from '@polkadot/util-keyring/types';
|
||||
|
||||
export type KeyringOption$Type = 'account' | 'address' | 'all' | 'recent' | 'testing';
|
||||
|
||||
export type KeyringOption = {
|
||||
className?: string,
|
||||
disabled?: boolean,
|
||||
content?: any | string, // node?
|
||||
key: string | null,
|
||||
name: string,
|
||||
text: any | string, // node?
|
||||
value: string | null
|
||||
};
|
||||
|
||||
export type KeyringOptions = Array<KeyringOption>;
|
||||
import { AddressSubject, SingleAddress } from './observable/types';
|
||||
import { KeyringSectionOption } from './options/types';
|
||||
|
||||
export type KeyringJson$Meta = {
|
||||
isRecent?: boolean,
|
||||
@ -42,18 +30,9 @@ export type KeyringAddress = {
|
||||
|
||||
export type State = {
|
||||
isTestMode: boolean,
|
||||
available: {
|
||||
account: {
|
||||
[index: string]: KeyringJson
|
||||
},
|
||||
address: {
|
||||
[index: string]: KeyringJson
|
||||
}
|
||||
},
|
||||
keyring: BaseKeyringInstance,
|
||||
options: {
|
||||
[index: string]: KeyringOptions // KeyringOption$Type
|
||||
}
|
||||
accounts: AddressSubject,
|
||||
addresses: AddressSubject,
|
||||
keyring: BaseKeyringInstance
|
||||
};
|
||||
|
||||
export type KeyringInstance = {
|
||||
@ -63,7 +42,6 @@ export type KeyringInstance = {
|
||||
getAccounts: () => Array<KeyringAddress>,
|
||||
getAddress: (address: string | Uint8Array) => KeyringAddress,
|
||||
getAddresses: () => Array<KeyringAddress>,
|
||||
getOptions: (type: KeyringOption$Type) => KeyringOptions,
|
||||
getPair: (address: string | Uint8Array) => KeyringPair,
|
||||
getPairs: () => Array<KeyringPair>,
|
||||
isAvailable: (address: string | Uint8Array) => boolean,
|
||||
@ -71,6 +49,6 @@ export type KeyringInstance = {
|
||||
saveAccount: (pair: KeyringPair, password?: string) => void,
|
||||
saveAccountMeta: (pair: KeyringPair, meta: KeyringPair$Meta) => void,
|
||||
saveAddress: (address: string, meta: KeyringPair$Meta) => void,
|
||||
saveRecent: (address: string) => KeyringOption,
|
||||
saveRecent: (address: string) => SingleAddress,
|
||||
setTestMode: (isTest: boolean) => void
|
||||
};
|
||||
|
||||
2
packages/ui-react-rx/src/types.d.ts
vendored
2
packages/ui-react-rx/src/types.d.ts
vendored
@ -36,7 +36,7 @@ type OnChangeCb$Fn<T> = (value?: T) => void;
|
||||
export type OnChangeCb<T> = OnChangeCb$Obs<T> | OnChangeCb$Fn<T> | undefined;
|
||||
|
||||
export type ChangeProps<T> = {
|
||||
onChange?: OnChangeCb<T>
|
||||
rxChange?: OnChangeCb<T>
|
||||
};
|
||||
|
||||
export type ParamProps = {
|
||||
|
||||
@ -2,6 +2,24 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
export default function isEqual <T> (a?: T, b?: T): boolean {
|
||||
return JSON.stringify({ test: a }) === JSON.stringify({ test: b });
|
||||
function flatten (key: string | null, value: any): any {
|
||||
if (!value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.$$typeof) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value.map((item) =>
|
||||
flatten(null, item)
|
||||
);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
export default function isEqual <T> (a?: T, b?: T): boolean {
|
||||
return JSON.stringify({ test: a }, flatten) === JSON.stringify({ test: b }, flatten);
|
||||
}
|
||||
|
||||
@ -7,16 +7,16 @@ import { OnChangeCb, OnChangeCb$Obs } 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) {
|
||||
export default function triggerChange<T> (value?: T, ...rxChange: Array<OnChangeCb<T>>): void {
|
||||
if (!rxChange || !rxChange.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange.forEach((onChange) => {
|
||||
if (isObservable(onChange)) {
|
||||
(onChange as OnChangeCb$Obs<T>).next(value);
|
||||
} else if (isFunction(onChange)) {
|
||||
onChange(value);
|
||||
rxChange.forEach((rxChange) => {
|
||||
if (isObservable(rxChange)) {
|
||||
(rxChange as OnChangeCb$Obs<T>).next(value);
|
||||
} else if (isFunction(rxChange)) {
|
||||
rxChange(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -9,22 +9,20 @@ import { RxProps } from '../types';
|
||||
import { HOC, Options, DefaultProps, RenderFn } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import { map } from 'rxjs/operators/map';
|
||||
import isUndefined from '@polkadot/util/is/undefined';
|
||||
|
||||
import intervalSubscribe from '../util/intervalSubscribe';
|
||||
import isEqual from '../util/isEqual';
|
||||
import triggerChange from '../util/triggerChange';
|
||||
import echoTransform from './transform/echo';
|
||||
import withApi from './api';
|
||||
import withObservableBase from './observableBase';
|
||||
|
||||
type State<T> = RxProps<T> & {
|
||||
subscriptions: Array<any>; // FIXME subscriptions
|
||||
Component?: React.ComponentType<any>
|
||||
};
|
||||
|
||||
// FIXME proper types for attributes
|
||||
|
||||
export default function withObservable<T> (observable: ObservableApiNames, { onChange, params = [], paramProp = 'params', propName = observable, transform = echoTransform }: Options<T> = {}): HOC<T> {
|
||||
export default function withObservable<T> (subscription: ObservableApiNames, { rxChange, params = [], paramProp = 'params', propName = subscription, transform = echoTransform }: Options<T> = {}): HOC<T> {
|
||||
return (Inner: React.ComponentType<any>, defaultProps: DefaultProps<T> = {}, render?: RenderFn): React.ComponentType<any> => {
|
||||
class WithObservable extends React.Component<any, State<T>> {
|
||||
state: State<T>;
|
||||
@ -32,12 +30,7 @@ export default function withObservable<T> (observable: ObservableApiNames, { onC
|
||||
constructor (props: any) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rxUpdated: false,
|
||||
rxUpdatedAt: 0,
|
||||
subscriptions: [],
|
||||
value: void 0
|
||||
};
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
private getParams (props: any): Array<any> {
|
||||
@ -64,63 +57,34 @@ export default function withObservable<T> (observable: ObservableApiNames, { onC
|
||||
this.subscribe(this.getParams(this.props));
|
||||
}
|
||||
|
||||
private subscribe (newParams: Array<any>) {
|
||||
const { apiObservable } = this.props;
|
||||
|
||||
this.unsubscribe();
|
||||
componentWillUnmount () {
|
||||
this.setState({
|
||||
subscriptions: [
|
||||
apiObservable[observable](...newParams)
|
||||
.pipe(map(transform))
|
||||
.subscribe((value: any) =>
|
||||
this.triggerUpdate(this.props, value)
|
||||
),
|
||||
intervalSubscribe(this)
|
||||
]
|
||||
Component: undefined
|
||||
});
|
||||
}
|
||||
|
||||
private unsubscribe () {
|
||||
this.state.subscriptions.forEach((subscription) =>
|
||||
subscription.unsubscribe()
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
triggerUpdate = (props: any, value?: T): void => {
|
||||
if (isEqual(value, this.state.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerChange(value, onChange, props.onChange || defaultProps.onChange);
|
||||
private subscribe (newParams: Array<any>) {
|
||||
const { apiObservable } = this.props;
|
||||
const observable = apiObservable[subscription](...newParams);
|
||||
|
||||
this.setState({
|
||||
rxUpdated: true,
|
||||
rxUpdatedAt: Date.now(),
|
||||
value
|
||||
Component: withObservableBase(observable, {
|
||||
rxChange,
|
||||
propName,
|
||||
transform
|
||||
})(Inner, defaultProps, render)
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children } = this.props;
|
||||
const { rxUpdated, rxUpdatedAt, value } = this.state;
|
||||
const _props = {
|
||||
...defaultProps,
|
||||
...this.props,
|
||||
rxUpdated,
|
||||
rxUpdatedAt,
|
||||
[propName]: value
|
||||
};
|
||||
const { Component } = this.state;
|
||||
|
||||
delete _props.onChange;
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Inner {..._props}>
|
||||
{render && render(value)}{children}
|
||||
</Inner>
|
||||
<Component {...this.props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
93
packages/ui-react-rx/src/with/observableBase.tsx
Normal file
93
packages/ui-react-rx/src/with/observableBase.tsx
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-react-rx authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
// TODO: Lots of duplicated code between this and withObservable, surely there ois a better way of doing this?
|
||||
|
||||
import { RxProps } from '../types';
|
||||
import { HOC, Options, DefaultProps, RenderFn } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import intervalSubscribe from '../util/intervalSubscribe';
|
||||
import isEqual from '../util/isEqual';
|
||||
import triggerChange from '../util/triggerChange';
|
||||
import echoTransform from './transform/echo';
|
||||
|
||||
type State<T> = RxProps<T> & {
|
||||
subscriptions: Array<any>; // FIXME subscriptions
|
||||
};
|
||||
|
||||
// FIXME proper types for attributes
|
||||
|
||||
export default function withObservableBase<T> (observable: Observable<any>, { rxChange, propName = 'value', transform = echoTransform }: Options<T> = {}): HOC<T> {
|
||||
return (Inner: React.ComponentType<any>, defaultProps: DefaultProps<T> = {}, render?: RenderFn): React.ComponentType<any> => {
|
||||
return class WithObservable extends React.Component<any, State<T>> {
|
||||
state: State<T>;
|
||||
|
||||
constructor (props: any) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rxUpdated: false,
|
||||
rxUpdatedAt: 0,
|
||||
subscriptions: [],
|
||||
value: void 0
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.setState({
|
||||
subscriptions: [
|
||||
observable
|
||||
.pipe(map(transform))
|
||||
.subscribe((value: any) =>
|
||||
this.triggerUpdate(this.props, value)
|
||||
),
|
||||
intervalSubscribe(this)
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.state.subscriptions.forEach((subscription) =>
|
||||
subscription.unsubscribe()
|
||||
);
|
||||
}
|
||||
|
||||
triggerUpdate = (props: any, value?: T): void => {
|
||||
if (isEqual(value, this.state.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerChange(value, rxChange, props.rxChange || defaultProps.rxChange);
|
||||
|
||||
this.setState({
|
||||
rxUpdated: true,
|
||||
rxUpdatedAt: Date.now(),
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { children } = this.props;
|
||||
const { rxUpdated, rxUpdatedAt, value } = this.state;
|
||||
const _props = {
|
||||
...defaultProps,
|
||||
...this.props,
|
||||
rxUpdated,
|
||||
rxUpdatedAt,
|
||||
[propName]: value
|
||||
};
|
||||
|
||||
return (
|
||||
<Inner {..._props}>
|
||||
{render && render(value)}{children}
|
||||
</Inner>
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
4
packages/ui-react-rx/src/with/types.d.ts
vendored
4
packages/ui-react-rx/src/with/types.d.ts
vendored
@ -11,12 +11,12 @@ import React from 'react';
|
||||
export type Transform = (value: any, index: number) => any;
|
||||
|
||||
export type DefaultProps<T> = {
|
||||
onChange?: OnChangeCb<T>,
|
||||
rxChange?: OnChangeCb<T>,
|
||||
[index: string]: any
|
||||
};
|
||||
|
||||
export type Options<T> = {
|
||||
onChange?: OnChangeCb<T>,
|
||||
rxChange?: OnChangeCb<T>,
|
||||
params?: Array<any>,
|
||||
paramProp?: string,
|
||||
propName?: string,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user