useAccounts hook (#1898)

* useAccounts hook

* Drop some withRouter

* Add setImmediate to trackStream

* Combine unsubscribe into subscribe

* Use before define

* Ass trackPromise

* Cleanup mapping for params

* Addressbook display
This commit is contained in:
Jaco Greeff 2019-11-16 10:13:07 +01:00 committed by GitHub
parent ce141a4b4c
commit e0acd15b43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 435 additions and 470 deletions

View File

@ -3,13 +3,12 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { ComponentProps } from './types';
import React, { useState } from 'react';
import keyring from '@polkadot/ui-keyring';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { getLedger, isLedger, withMulti, withObservable } from '@polkadot/react-api';
import { getLedger, isLedger } from '@polkadot/react-api';
import { useAccounts } from '@polkadot/react-hooks';
import { Button, CardGrid } from '@polkadot/react-components';
import CreateModal from './modals/Create';
@ -20,7 +19,6 @@ import Banner from './Banner';
import translate from './translate';
interface Props extends ComponentProps, I18nProps {
accounts?: SubjectInfo[];
}
// query the ledger for the address, adding it to the keyring
@ -36,11 +34,12 @@ async function queryLedger (): Promise<void> {
}
}
function Overview ({ accounts, onStatusChange, t }: Props): React.ReactElement<Props> {
function Overview ({ onStatusChange, t }: Props): React.ReactElement<Props> {
const { allAccounts, hasAccounts } = useAccounts();
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [isImportOpen, setIsImportOpen] = useState(false);
const [isQrOpen, setIsQrOpen] = useState(false);
const emptyScreen = !(isCreateOpen || isImportOpen || isQrOpen) && accounts && (Object.keys(accounts).length === 0);
const emptyScreen = !(isCreateOpen || isImportOpen || isQrOpen) && !hasAccounts;
const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
const _toggleImport = (): void => setIsImportOpen(!isImportOpen);
@ -105,7 +104,7 @@ function Overview ({ accounts, onStatusChange, t }: Props): React.ReactElement<P
onStatusChange={onStatusChange}
/>
)}
{accounts && Object.keys(accounts).map((address): React.ReactNode => (
{allAccounts.map((address): React.ReactNode => (
<Account
address={address}
key={address}
@ -115,8 +114,4 @@ function Overview ({ accounts, onStatusChange, t }: Props): React.ReactElement<P
);
}
export default withMulti(
Overview,
translate,
withObservable(accountObservable.subject, { propName: 'accounts' })
);
export default translate(Overview);

View File

@ -3,14 +3,12 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { AppProps, I18nProps } from '@polkadot/react-components/types';
import { ComponentProps, LocationProps } from './types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { ComponentProps } from './types';
import React, { useEffect, useState } from 'react';
import { Route, Switch } from 'react-router';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { useAccounts } from '@polkadot/react-hooks';
import { HelpOverlay, Tabs } from '@polkadot/react-components';
import { withMulti, withObservable } from '@polkadot/react-api';
import basicMd from './md/basic.md';
import Overview from './Overview';
@ -18,34 +16,26 @@ import translate from './translate';
import Vanity from './Vanity';
interface Props extends AppProps, I18nProps {
allAccounts?: SubjectInfo;
location: any;
}
function AccountsApp ({ allAccounts = {}, basePath, location, onStatusChange, t }: Props): React.ReactElement<Props> {
function AccountsApp ({ basePath, onStatusChange, t }: Props): React.ReactElement<Props> {
const { hasAccounts } = useAccounts();
const [hidden, setHidden] = useState<string[]>(['vanity']);
useEffect((): void => {
setHidden(
Object.keys(allAccounts).length !== 0
hasAccounts
? []
: ['vanity']
);
}, [allAccounts]);
}, [hasAccounts]);
const _renderComponent = (Component: React.ComponentType<ComponentProps>): (props: LocationProps) => React.ReactNode => {
// eslint-disable-next-line react/display-name
return ({ match }: LocationProps): React.ReactNode => {
return (
<Component
basePath={basePath}
location={location}
match={match}
onStatusChange={onStatusChange}
/>
);
};
};
const _renderComponent = (Component: React.ComponentType<ComponentProps>): React.ReactNode => (
<Component
basePath={basePath}
onStatusChange={onStatusChange}
/>
);
return (
<main className='accounts--App'>
@ -68,15 +58,11 @@ function AccountsApp ({ allAccounts = {}, basePath, location, onStatusChange, t
/>
</header>
<Switch>
<Route path={`${basePath}/vanity`} render={_renderComponent(Vanity)} />
<Route render={_renderComponent(Overview)} />
<Route path={`${basePath}/vanity`}>{_renderComponent(Vanity)}</Route>
<Route>{_renderComponent(Overview)}</Route>
</Switch>
</main>
);
}
export default withMulti(
AccountsApp,
translate,
withObservable(accountObservable.subject, { propName: 'allAccounts' })
);
export default translate(AccountsApp);

View File

@ -7,13 +7,6 @@ import { ActionStatus } from '@polkadot/react-components/Status/types';
import { WithTranslation } from 'react-i18next';
export interface LocationProps {
location: any;
match: {
params: Record<string, string>;
};
}
export interface BareProps {
className?: string;
style?: Record<string, any>;
@ -21,7 +14,7 @@ export interface BareProps {
export type I18nProps = BareProps & WithTranslation;
export type ComponentProps = AppProps & LocationProps;
export type ComponentProps = AppProps;
export interface ModalProps {
onClose: () => void;

View File

@ -3,25 +3,23 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { ComponentProps } from './types';
import React, { useState } from 'react';
import { Button, CardGrid } from '@polkadot/react-components';
import addressObservable from '@polkadot/ui-keyring/observable/addresses';
import { withMulti, withObservable } from '@polkadot/react-api';
import { useAddresses } from '@polkadot/react-hooks';
import CreateModal from './modals/Create';
import Address from './Address';
import translate from './translate';
interface Props extends ComponentProps, I18nProps {
addresses?: SubjectInfo[];
}
function Overview ({ addresses, onStatusChange, t }: Props): React.ReactElement<Props> {
function Overview ({ onStatusChange, t }: Props): React.ReactElement<Props> {
const { hasAddresses, allAddresses } = useAddresses();
const [isCreateOpen, setIsCreateOpen] = useState(false);
const emptyScreen = !isCreateOpen && (!addresses || Object.keys(addresses).length === 0);
const emptyScreen = !isCreateOpen && !hasAddresses;
const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
@ -46,7 +44,7 @@ function Overview ({ addresses, onStatusChange, t }: Props): React.ReactElement<
onStatusChange={onStatusChange}
/>
)}
{addresses && Object.keys(addresses).map((address): React.ReactNode => (
{allAddresses.map((address): React.ReactNode => (
<Address
address={address}
key={address}
@ -56,8 +54,4 @@ function Overview ({ addresses, onStatusChange, t }: Props): React.ReactElement<
);
}
export default withMulti(
Overview,
translate,
withObservable(addressObservable.subject, { propName: 'addresses' })
);
export default translate(Overview);

View File

@ -4,7 +4,6 @@
import { AppProps, I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { ComponentProps } from './types';
import React from 'react';
import { Route, Switch } from 'react-router';
@ -21,16 +20,6 @@ interface Props extends AppProps, I18nProps {
}
function AddressBookApp ({ basePath, onStatusChange, t }: Props): React.ReactElement<Props> {
const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
// eslint-disable-next-line react/display-name
return (): React.ReactNode =>
<Component
basePath={basePath}
location={location}
onStatusChange={onStatusChange}
/>;
};
return (
<main className='address-book--App'>
<HelpOverlay md={basicMd} />
@ -47,7 +36,13 @@ function AddressBookApp ({ basePath, onStatusChange, t }: Props): React.ReactEle
/>
</header>
<Switch>
<Route render={_renderComponent(Overview)} />
<Route>
<Overview
basePath={basePath}
location={location}
onStatusChange={onStatusChange}
/>
</Route>
</Switch>
</main>
);

View File

@ -4,26 +4,23 @@
import { AccountId } from '@polkadot/types/interfaces';
import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import BN from 'bn.js';
import React, { useState } from 'react';
import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { withMulti, withObservable } from '@polkadot/react-api';
import { useAccounts } from '@polkadot/react-hooks';
import translate from '../translate';
interface Props extends I18nProps {
allAccounts?: SubjectInfo;
depositors: AccountId[];
proposalId: BN | number;
}
function Seconding ({ allAccounts, depositors, proposalId, t }: Props): React.ReactElement<Props> | null {
function Seconding ({ depositors, proposalId, t }: Props): React.ReactElement<Props> | null {
const { hasAccounts } = useAccounts();
const [accountId, setAccountId] = useState<string | null>(null);
const [isSecondingOpen, setIsSecondingOpen] = useState(false);
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
if (!hasAccounts) {
return null;
@ -85,8 +82,4 @@ function Seconding ({ allAccounts, depositors, proposalId, t }: Props): React.Re
);
}
export default withMulti(
Seconding,
translate,
withObservable(accountObservable.subject, { propName: 'allAccounts' })
);
export default translate(Seconding);

View File

@ -3,26 +3,23 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import BN from 'bn.js';
import React, { useState } from 'react';
import { Button, Dropdown, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { withMulti, withObservable } from '@polkadot/react-api';
import { useAccounts } from '@polkadot/react-hooks';
import translate from '../translate';
interface Props extends I18nProps {
allAccounts?: SubjectInfo;
referendumId: BN | number;
}
function Voting ({ allAccounts, referendumId, t }: Props): React.ReactElement<Props> | null {
function Voting ({ referendumId, t }: Props): React.ReactElement<Props> | null {
const { hasAccounts } = useAccounts();
const [accountId, setAccountId] = useState<string | null>(null);
const [isVotingOpen, setIsVotingOpen] = useState(false);
const [voteValue, setVoteValue] = useState(true);
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
if (!hasAccounts) {
return null;
@ -93,8 +90,4 @@ function Voting ({ allAccounts, referendumId, t }: Props): React.ReactElement<Pr
);
}
export default withMulti(
Voting,
translate,
withObservable(accountObservable.subject, { propName: 'allAccounts' })
);
export default translate(Voting);

View File

@ -7,8 +7,8 @@ import { BlockNumber } from '@polkadot/types/interfaces';
import { BareProps } from '@polkadot/react-components/types';
import React, { useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
import { withCalls, withMulti } from '@polkadot/react-api';
import { useParams } from 'react-router-dom';
import { withCalls } from '@polkadot/react-api';
import { isHex } from '@polkadot/util';
import Query from '../Query';
@ -17,15 +17,11 @@ import BlockByNumber from './ByNumber';
interface Props extends BareProps {
chain_bestNumber?: BlockNumber;
match: {
params: {
value: string;
};
};
}
function Entry ({ chain_bestNumber, match: { params: { value } } }: Props): React.ReactElement<Props> | null {
const [stateValue, setStateValue] = useState<string>(value);
function Entry ({ chain_bestNumber }: Props): React.ReactElement<Props> | null {
const { value } = useParams();
const [stateValue, setStateValue] = useState<string | undefined>(value);
useEffect((): void => {
if (value && value !== stateValue) {
@ -54,7 +50,4 @@ function Entry ({ chain_bestNumber, match: { params: { value } } }: Props): Reac
);
}
export default withMulti(
withRouter(Entry),
withCalls<Props>('derive.chain.bestNumber')
);
export default withCalls<Props>('derive.chain.bestNumber')(Entry);

View File

@ -8,7 +8,7 @@ import { AppProps, I18nProps } from '@polkadot/react-components/types';
import { Log, LogType, Snippet } from './types';
import React, { useRef, useState, useEffect } from 'react';
import { withRouter, RouteComponentProps } from 'react-router';
import { useHistory, useParams } from 'react-router-dom';
import { Transition } from 'semantic-ui-react';
import snappy from 'snappyjs';
import styled from 'styled-components';
@ -41,17 +41,7 @@ interface Injected {
window: null;
}
interface Props extends AppProps, I18nProps, RouteComponentProps<{}> {
match: {
isExact: boolean;
params: {
base64?: string;
};
path: string;
url: string;
};
// FIXME wait for proper eslint integration in tslint, then hopefully remove this
history: any;
interface Props extends AppProps, I18nProps {
}
const snippets: Snippet[] = JSON.parse(JSON.stringify(allSnippets));
@ -89,8 +79,10 @@ function decodeBase64 (base64: string): Snippet {
}
// FIXME This... ladies & gentlemen, is a mess that should be untangled
function Playground ({ className, history, match: { params: { base64 } }, t }: Props): React.ReactElement<Props> {
function Playground ({ className, t }: Props): React.ReactElement<Props> {
const { api, isDevelopment } = useApiContext();
const history = useHistory();
const { base64 } = useParams();
const injectedRef = useRef<Injected | null>(null);
const [code, setCode] = useState('');
const [isAnimated, setIsAnimated] = useState(true);
@ -334,10 +326,8 @@ function Playground ({ className, history, match: { params: { base64 } }, t }: P
);
}
const Routed = withRouter(Playground);
export default translate(
styled(Routed)`
styled(Playground)`
display: flex;
flex-direction: column;
height: 100vh;

View File

@ -2,17 +2,15 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ApiProps } from '@polkadot/react-api/types';
import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
import { AccountId, StakingLedger } from '@polkadot/types/interfaces';
import { ComponentProps } from '../types';
import React, { useState } from 'react';
import styled from 'styled-components';
import { withCalls, withMulti } from '@polkadot/react-api/with';
import { Button, CardGrid } from '@polkadot/react-components';
import { trackStream, useApiContext } from '@polkadot/react-hooks';
import { AccountName } from '@polkadot/react-query';
import { Option } from '@polkadot/types';
import createOption from '@polkadot/ui-keyring/options/item';
@ -21,12 +19,10 @@ import Account from './Account';
import StartStaking from './NewStake';
import translate from '../translate';
interface Props extends I18nProps, ComponentProps, ApiProps {
queryBonded?: Option<AccountId>[];
queryLedger?: Option<StakingLedger>[];
interface Props extends I18nProps, ComponentProps {
}
function getMyStashes (queryBonded?: Option<AccountId>[], queryLedger?: Option<StakingLedger>[], allAccounts?: SubjectInfo): string[] | null {
function getMyStashes (allAccounts: string[], queryBonded?: Option<AccountId>[], queryLedger?: Option<StakingLedger>[]): string[] | null {
const result: string[] = [];
if (!queryBonded || !queryLedger) {
@ -34,7 +30,7 @@ function getMyStashes (queryBonded?: Option<AccountId>[], queryLedger?: Option<S
}
queryBonded.forEach((value, index): void => {
value.isSome && allAccounts && result.push(Object.keys(allAccounts)[index]);
value.isSome && result.push(allAccounts[index]);
});
queryLedger.forEach((ledger): void => {
@ -48,9 +44,12 @@ function getMyStashes (queryBonded?: Option<AccountId>[], queryLedger?: Option<S
return result;
}
function Accounts ({ allAccounts, allStashes, className, queryBonded, queryLedger, recentlyOnline, t }: Props): React.ReactElement<Props> {
function Accounts ({ allAccounts, allStashes, className, recentlyOnline, t }: Props): React.ReactElement<Props> {
const { api } = useApiContext();
const queryBonded = trackStream<Option<AccountId>[]>(api.query.staking.bonded.multi as any, [allAccounts]);
const queryLedger = trackStream<Option<StakingLedger>[]>(api.query.staking.ledger.multi as any, [allAccounts]);
const [isNewStakeOpen, setIsNewStateOpen] = useState(false);
const foundStashes = getMyStashes(queryBonded, queryLedger, allAccounts);
const foundStashes = getMyStashes(allAccounts, queryBonded, queryLedger);
const stashOptions = allStashes.map((stashId): KeyringSectionOption =>
createOption(stashId, (<AccountName params={stashId} />) as any)
);
@ -91,25 +90,10 @@ function Accounts ({ allAccounts, allStashes, className, queryBonded, queryLedge
);
}
export default withMulti(
export default translate(
styled(Accounts)`
.ui--CardGrid-buttons {
text-align: right;
}
`,
translate,
withCalls<Props>(
['query.staking.bonded', {
isMulti: true,
propName: 'queryBonded',
paramPick: ({ allAccounts }: Props): undefined | string[] =>
allAccounts && Object.keys(allAccounts)
}],
['query.staking.ledger', {
isMulti: true,
propName: 'queryLedger',
paramPick: ({ allAccounts }: Props): undefined | string[] =>
allAccounts && Object.keys(allAccounts)
}]
)
`
);

View File

@ -6,21 +6,13 @@ import { I18nProps } from '@polkadot/react-components/types';
import { ComponentProps } from '../types';
import React, { useState } from 'react';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { Button, InputAddressSimple } from '@polkadot/react-components';
import translate from '../translate';
import Validator from './Validator';
interface Props extends I18nProps, ComponentProps, RouteComponentProps<{}> {
match: {
isExact: boolean;
params: {
value: string;
};
path: string;
url: string;
};
interface Props extends I18nProps, ComponentProps {
}
// const _onQuery = (): void => {
@ -46,7 +38,8 @@ interface Props extends I18nProps, ComponentProps, RouteComponentProps<{}> {
// />
// </Input>
function Query ({ className, sessionRewards, match: { params: { value } }, t }: Props): React.ReactElement<Props> {
function Query ({ className, sessionRewards, t }: Props): React.ReactElement<Props> {
const { value } = useParams();
const [validatorId, setValidatorId] = useState<string | null>(value || null);
const _onQuery = (): void => {
@ -81,4 +74,4 @@ function Query ({ className, sessionRewards, match: { params: { value } }, t }:
);
}
export default translate(withRouter(Query));
export default translate(Query);

View File

@ -4,9 +4,7 @@
import { DerivedHeartbeats, DerivedStakingOverview } from '@polkadot/api-derive/types';
import { AppProps, I18nProps } from '@polkadot/react-components/types';
import { ApiProps } from '@polkadot/react-api/types';
import { AccountId, BlockNumber } from '@polkadot/types/interfaces';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { ComponentProps } from './types';
import React from 'react';
@ -16,9 +14,7 @@ import styled from 'styled-components';
import { Option } from '@polkadot/types';
import { HelpOverlay } from '@polkadot/react-components';
import Tabs from '@polkadot/react-components/Tabs';
import { withMulti, withObservable } from '@polkadot/react-api';
import { trackStream, useApiContext, useSessionRewards } from '@polkadot/react-hooks';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { trackStream, useAccounts, useApiContext, useSessionRewards } from '@polkadot/react-hooks';
import Accounts from './Actions/Accounts';
import basicMd from './md/basic.md';
@ -27,8 +23,7 @@ import Query from './Query';
import { MAX_SESSIONS } from './constants';
import translate from './translate';
interface Props extends AppProps, ApiProps, I18nProps {
allAccounts?: SubjectInfo;
interface Props extends AppProps, I18nProps {
}
const EMPY_ACCOUNTS: string[] = [];
@ -43,8 +38,9 @@ function transformStakingControllers ([stashes, controllers]: [AccountId[], Opti
];
}
function App ({ allAccounts, basePath, className, t }: Props): React.ReactElement<Props> {
function App ({ basePath, className, t }: Props): React.ReactElement<Props> {
const { api } = useApiContext();
const { allAccounts, hasAccounts } = useAccounts();
const stakingControllers = trackStream<[string[], string[]]>(api.derive.staking.controllers, [], { transform: transformStakingControllers });
const bestNumber = trackStream<BlockNumber>(api.derive.chain.bestNumber, []);
const recentlyOnline = trackStream<DerivedHeartbeats>(api.derive.imOnline.receivedHeartbeats, []);
@ -52,32 +48,22 @@ function App ({ allAccounts, basePath, className, t }: Props): React.ReactElemen
const sessionRewards = useSessionRewards(MAX_SESSIONS);
const routeMatch = useRouteMatch({ path: basePath, strict: true });
const hasAccounts = !!allAccounts && Object.keys(allAccounts).length !== 0;
const hasQueries = hasAccounts && !!(api.query.imOnline?.authoredBlocks);
const [allStashes, allControllers] = stakingControllers || EMPTY_ALL;
const _renderComponent = (Component: React.ComponentType<ComponentProps>, className?: string): () => React.ReactNode => {
// eslint-disable-next-line react/display-name
return (): React.ReactNode => {
if (!allAccounts) {
return null;
}
return (
<Component
allAccounts={allAccounts}
allControllers={allControllers}
allStashes={allStashes}
bestNumber={bestNumber}
className={className}
hasAccounts={hasAccounts}
hasQueries={hasQueries}
recentlyOnline={recentlyOnline}
sessionRewards={sessionRewards}
stakingOverview={stakingOverview}
/>
);
};
};
const _renderComponent = (Component: React.ComponentType<ComponentProps>, className?: string): React.ReactNode => (
<Component
allAccounts={allAccounts}
allControllers={allControllers}
allStashes={allStashes}
bestNumber={bestNumber}
className={className}
hasAccounts={hasAccounts}
hasQueries={hasQueries}
recentlyOnline={recentlyOnline}
sessionRewards={sessionRewards}
stakingOverview={stakingOverview}
/>
);
return (
<main className={`staking--App ${className}`}>
@ -111,16 +97,16 @@ function App ({ allAccounts, basePath, className, t }: Props): React.ReactElemen
/>
</header>
<Switch>
<Route path={`${basePath}/actions`} render={_renderComponent(Accounts)} />
<Route path={`${basePath}/query/:value`} render={_renderComponent(Query)} />
<Route path={`${basePath}/query`} render={_renderComponent(Query)} />
<Route path={`${basePath}/actions`}>{_renderComponent(Accounts)}</Route>
<Route path={`${basePath}/query/:value`}>{_renderComponent(Query)}</Route>
<Route path={`${basePath}/query`}>{_renderComponent(Query)}</Route>
</Switch>
{_renderComponent(Overview, routeMatch?.isExact ? '' : 'staking--hidden')()}
{_renderComponent(Overview, routeMatch?.isExact ? '' : 'staking--hidden')}
</main>
);
}
export default withMulti(
export default translate(
styled(App)`
.staking--hidden {
display: none;
@ -137,7 +123,5 @@ export default withMulti(
.staking--Chart+.staking--Chart {
margin-top: 1.5rem;
}
`,
translate,
withObservable(accountObservable.subject, { propName: 'allAccounts' })
`
);

View File

@ -5,12 +5,11 @@
import { DerivedFees, DerivedBalances, DerivedHeartbeats, DerivedStakingOverview } from '@polkadot/api-derive/types';
import { BlockNumber } from '@polkadot/types/interfaces';
import { SessionRewards } from '@polkadot/react-hooks/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
export type Nominators = Record<string, string[]>;
export interface ComponentProps {
allAccounts?: SubjectInfo;
allAccounts: string[];
allControllers: string[];
allStashes: string[];
bestNumber?: BlockNumber;

View File

@ -23,10 +23,9 @@ function SetKey ({ allAccounts, className, isMine, sudoKey, t }: Props): React.R
}, [selected, sudoKey]);
const willLose = isMine &&
!!Object.keys(allAccounts).length &&
!!selected &&
selected !== sudoKey &&
!Object.keys(allAccounts).find((s): boolean => s === selected);
allAccounts.some((s): boolean => s === selected);
return (
<section>

View File

@ -4,14 +4,12 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { AppProps, I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { ComponentProps } from './types';
import React, { useEffect, useState } from 'react';
import { Route, Switch } from 'react-router';
import { Icon, Tabs } from '@polkadot/react-components';
import { withCalls, withMulti, withObservable } from '@polkadot/react-api';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { trackStream, useAccounts, useApiContext } from '@polkadot/react-hooks';
import SetKey from './SetKey';
import Sudo from './Sudo';
@ -19,17 +17,16 @@ import Sudo from './Sudo';
import translate from './translate';
interface Props extends AppProps, I18nProps {
allAccounts: SubjectInfo;
sudoKey?: string;
}
function App ({ allAccounts, basePath, sudoKey, t }: Props): React.ReactElement<Props> {
function App ({ basePath, t }: Props): React.ReactElement<Props> {
const { api } = useApiContext();
const sudoKey = trackStream<string>(api.query.sudo.key, [], { transform: (k): string => k.toString() });
const { allAccounts } = useAccounts();
const [isMine, setIsMine] = useState(false);
useEffect((): void => {
setIsMine(
!!sudoKey && !!allAccounts && Object.keys(allAccounts).some((key): boolean => key === sudoKey)
);
setIsMine(!!sudoKey && allAccounts.some((key): boolean => key === sudoKey));
}, [allAccounts, sudoKey]);
const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
@ -83,15 +80,4 @@ function App ({ allAccounts, basePath, sudoKey, t }: Props): React.ReactElement<
);
}
export default withMulti(
App,
translate,
withCalls<Props>(
['query.sudo.key', {
propName: 'sudoKey',
transform: (key): string =>
key.toString()
}]
),
withObservable(accountObservable.subject, { propName: 'allAccounts' })
);
export default translate(App);

View File

@ -2,10 +2,8 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
export interface ComponentProps {
allAccounts: SubjectInfo;
allAccounts: string[];
isMine: boolean;
sudoKey?: string;
}

View File

@ -3,13 +3,11 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { AppProps, I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import React from 'react';
import { Route, Switch } from 'react-router';
import Tabs from '@polkadot/react-components/Tabs';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { withMulti, withObservable } from '@polkadot/react-api';
import { useAccounts } from '@polkadot/react-hooks';
import Hash from './Hash';
import Rpc from './Rpc';
@ -18,11 +16,10 @@ import Verify from './Verify';
import translate from './translate';
interface Props extends AppProps, I18nProps {
allAccounts?: SubjectInfo;
}
function ToolboxApp ({ allAccounts, basePath, t }: Props): React.ReactElement<Props> {
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
function ToolboxApp ({ basePath, t }: Props): React.ReactElement<Props> {
const { hasAccounts } = useAccounts();
const tabs = [
{
isRoot: true,
@ -64,8 +61,4 @@ function ToolboxApp ({ allAccounts, basePath, t }: Props): React.ReactElement<Pr
);
}
export default withMulti(
ToolboxApp,
translate,
withObservable(accountObservable.subject, { propName: 'allAccounts' })
);
export default translate(ToolboxApp);

View File

@ -4,14 +4,13 @@
import { TreasuryProposal as TreasuryProposalType } from '@polkadot/types/interfaces';
import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import React, { useEffect } from 'react';
import styled from 'styled-components';
import { Option } from '@polkadot/types';
import { ActionItem, Icon, TreasuryProposal } from '@polkadot/react-components';
import { withCalls, withMulti, withObservable } from '@polkadot/react-api';
import keyring from '@polkadot/ui-keyring';
import { withCalls, withMulti } from '@polkadot/react-api';
import { useAccounts } from '@polkadot/react-hooks';
import translate from '../translate';
import Approve from './Approve';
@ -22,7 +21,6 @@ const Approved = styled.h3`
`;
interface Props extends I18nProps {
allAccounts?: SubjectInfo;
isApproved: boolean;
proposal?: TreasuryProposalType | null;
proposalId: string;
@ -30,7 +28,9 @@ interface Props extends I18nProps {
onRespond: () => void;
}
function ProposalDisplay ({ allAccounts, isApproved, onPopulate, onRespond, proposal, proposalId, t }: Props): React.ReactElement<Props> | null {
function ProposalDisplay ({ isApproved, onPopulate, onRespond, proposal, proposalId, t }: Props): React.ReactElement<Props> | null {
const { hasAccounts } = useAccounts();
useEffect((): void => {
onPopulate();
}, [proposal]);
@ -39,8 +39,6 @@ function ProposalDisplay ({ allAccounts, isApproved, onPopulate, onRespond, prop
return null;
}
const hasAccounts = allAccounts && Object.keys(allAccounts).length !== 0;
return (
<ActionItem
accessory={
@ -87,6 +85,5 @@ export default withMulti(
transform: (value: Option<TreasuryProposalType>): TreasuryProposalType | null =>
value.unwrapOr(null)
}]
),
withObservable(keyring.accounts.subject, { propName: 'allAccounts' })
)
);

View File

@ -8,21 +8,21 @@ import { I18nProps } from '@polkadot/react-components/types';
import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import { withCalls, withMulti } from '@polkadot/react-api';
import { Column } from '@polkadot/react-components';
import Proposal from './Proposal';
import translate from '../translate';
interface Props extends I18nProps, RouteComponentProps<{}> {
interface Props extends I18nProps {
isApprovals?: boolean;
treasury_approvals?: BN[];
treasury_proposalCount?: BN;
}
function ProposalsBase ({ history, isApprovals = false, treasury_approvals, treasury_proposalCount, t }: Props): React.ReactElement<Props> {
function ProposalsBase ({ isApprovals = false, treasury_approvals, treasury_proposalCount, t }: Props): React.ReactElement<Props> {
const history = useHistory();
const [isEmpty, setIsEmpty] = useState(true);
const [proposalIndices, setProposalIndices] = useState<BN[]>([]);
@ -71,7 +71,7 @@ function ProposalsBase ({ history, isApprovals = false, treasury_approvals, trea
}
const Proposals = withMulti(
withRouter(ProposalsBase),
ProposalsBase,
translate,
withCalls<Props>(
['query.treasury.approvals', {

View File

@ -2,10 +2,10 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/react-components/types';
import { I18nProps as Props } from '@polkadot/react-components/types';
import React, { useContext } from 'react';
import { withRouter, RouteComponentProps } from 'react-router';
import { useLocation } from 'react-router-dom';
import styled from 'styled-components';
import routing from '@polkadot/apps-routing';
import { StatusContext } from '@polkadot/react-components';
@ -15,8 +15,6 @@ import Status from './Status';
import translate from '../translate';
import NotFound from './NotFound';
interface Props extends I18nProps, RouteComponentProps {}
const unknown = {
display: {
needsApi: undefined
@ -25,7 +23,8 @@ const unknown = {
name: ''
};
function Content ({ className, location, t }: Props): React.ReactElement<Props> {
function Content ({ className, t }: Props): React.ReactElement<Props> {
const location = useLocation();
const { isApiConnected, isApiReady } = useApiContext();
const { queueAction, stqueue, txqueue } = useContext(StatusContext);
const app = location.pathname.slice(1) || '';
@ -58,26 +57,24 @@ function Content ({ className, location, t }: Props): React.ReactElement<Props>
// React-router needs to be first, otherwise we have blocked updates
export default translate(
withRouter(
styled(Content)`
background: #fafafa;
display: flex;
flex-direction: column;
flex-grow: 1;
height: 100%;
min-height: 100vh;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
padding: 0 2rem;
styled(Content)`
background: #fafafa;
display: flex;
flex-direction: column;
flex-grow: 1;
height: 100%;
min-height: 100vh;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
padding: 0 2rem;
@media(max-width: 768px) {
padding: 0 0.5rem;
}
@media(max-width: 768px) {
padding: 0 0.5rem;
}
.connecting {
padding: 1rem 0;
}
`
)
.connecting {
padding: 1rem 0;
}
`
);

View File

@ -4,7 +4,6 @@
import { ApiProps } from '@polkadot/react-api/types';
import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { Route } from '@polkadot/apps-routing/types';
import { AccountId } from '@polkadot/types/interfaces';
@ -12,9 +11,8 @@ import React, { useEffect, useState } from 'react';
import { NavLink } from 'react-router-dom';
import { ApiPromise } from '@polkadot/api';
import { Icon, Menu, Tooltip } from '@polkadot/react-components';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { withCalls, withMulti, withObservable } from '@polkadot/react-api';
import { useApiContext } from '@polkadot/react-hooks';
import { withCalls, withMulti } from '@polkadot/react-api';
import { useAccounts, useApiContext } from '@polkadot/react-hooks';
import { isFunction } from '@polkadot/util';
import translate from '../translate';
@ -22,7 +20,6 @@ import translate from '../translate';
interface Props extends I18nProps {
isCollapsed: boolean;
onClick: () => void;
allAccounts?: SubjectInfo;
route: Route;
sudoKey?: AccountId;
}
@ -77,18 +74,14 @@ function checkVisible (name: string, { api, isApiReady, isApiConnected }: ApiPro
return notFound.length === 0;
}
function Item ({ allAccounts, route: { Modal, display, i18n, icon, name }, t, isCollapsed, onClick, sudoKey }: Props): React.ReactElement<Props> | null {
function Item ({ route: { Modal, display, i18n, icon, name }, t, isCollapsed, onClick, sudoKey }: Props): React.ReactElement<Props> | null {
const { allAccounts, hasAccounts } = useAccounts();
const apiProps = useApiContext();
const [hasAccounts, setHasAccounts] = useState(false);
const [hasSudo, setHasSudo] = useState(false);
const [isVisible, setIsVisible] = useState(false);
useEffect((): void => {
setHasAccounts(Object.keys(allAccounts || {}).length !== 0);
}, [allAccounts]);
useEffect((): void => {
setHasSudo(!!sudoKey && Object.keys(allAccounts || {}).some((address): boolean => sudoKey.eq(address)));
setHasSudo(!!sudoKey && allAccounts.some((address): boolean => sudoKey.eq(address)));
}, [allAccounts, sudoKey]);
useEffect((): void => {
@ -149,6 +142,5 @@ export default withMulti(
translate,
withCalls<Props>(
['query.sudo.key', { propName: 'sudoKey' }]
),
withObservable(accountObservable.subject, { propName: 'allAccounts' })
)
);

View File

@ -2,45 +2,27 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/react-components/types';
import { ApiProps } from '@polkadot/react-api/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { I18nProps as Props } from '@polkadot/react-components/types';
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { withApi, withMulti, withObservable } from '@polkadot/react-api';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { useAccounts, useApiContext } from '@polkadot/react-hooks';
import translate from '../translate';
import BaseOverlay from './Base';
interface Props extends I18nProps, ApiProps {
allAccounts?: SubjectInfo;
}
function Accounts ({ allAccounts, isApiReady, className, t }: Props): React.ReactElement<Props> | null {
const [hasAccounts, setHasAccounts] = useState(false);
function Accounts ({ className, t }: Props): React.ReactElement<Props> | null {
const { hasAccounts } = useAccounts();
const { isApiReady } = useApiContext();
const [isHidden, setIsHidden] = useState(false);
useEffect((): void => {
if (!allAccounts) {
return;
}
const newHas = Object.keys(allAccounts).length !== 0;
if (newHas !== hasAccounts) {
setHasAccounts(newHas);
}
}, [allAccounts]);
const _onClose = (): void => setIsHidden(true);
if (!isApiReady || hasAccounts || isHidden) {
return null;
}
const _onClose = (): void => setIsHidden(true);
return (
<BaseOverlay
className={className}
@ -59,13 +41,10 @@ function Accounts ({ allAccounts, isApiReady, className, t }: Props): React.Reac
);
}
export default withMulti(
export default translate(
styled(Accounts)`
background: #fff6cb;
border-color: #e7c000;
color: #6b5900;
`,
translate,
withApi,
withObservable(accountObservable.subject, { propName: 'allAccounts' })
`
);

View File

@ -6,15 +6,13 @@ import { Proposal } from '@polkadot/types/interfaces';
import BN from 'bn.js';
import React from 'react';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import styled from 'styled-components';
import Card from './Card';
import ProposedAction, { styles as proposedActionStyles } from './ProposedAction';
import { styles as rowStyles } from './Row';
interface Props extends RouteComponentProps {
interface Props {
className?: string;
children?: React.ReactNode;
accessory?: React.ReactNode;
@ -49,4 +47,4 @@ function ActionItem ({ className, children, accessory, idNumber, proposal, expan
);
}
export default withRouter(styled(ActionItem)`${styles}`);
export default styled(ActionItem)`${styles}`;

View File

@ -3,14 +3,13 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import React, { useState } from 'react';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import { useHistory } from 'react-router-dom';
import styled from 'styled-components';
import Icon from './Icon';
import { classes } from './util';
export interface InsetProps extends RouteComponentProps {
export interface InsetProps {
className?: string;
children?: React.ReactNode;
isCollapsible?: boolean;
@ -22,7 +21,8 @@ export interface InsetProps extends RouteComponentProps {
withBottomMargin?: boolean;
}
function Inset ({ children, className, header, history, href, isCollapsible, isError, isSuccess, withBottomMargin, withTopMargin }: InsetProps): React.ReactElement<InsetProps> | null {
function Inset ({ children, className, header, href, isCollapsible, isError, isSuccess, withBottomMargin, withTopMargin }: InsetProps): React.ReactElement<InsetProps> | null {
const history = useHistory();
const [isCollapsed, setIsCollapsed] = useState(true);
if (!children) {
@ -71,84 +71,82 @@ function Inset ({ children, className, header, history, href, isCollapsible, isE
);
}
export default withRouter(
styled(Inset)`
& {
box-shadow: 0 3px 3px rgba(0,0,0,.2);
position: relative;
background: #fefefe;
padding: 1rem;
transition: all 0.2s;
display: flex;
flex-direction: column;
export default styled(Inset)`
& {
box-shadow: 0 3px 3px rgba(0,0,0,.2);
position: relative;
background: #fefefe;
padding: 1rem;
transition: all 0.2s;
display: flex;
flex-direction: column;
&.bottom-margin {
margin-bottom: 2rem;
&.bottom-margin {
margin-bottom: 2rem;
}
&.top-margin {
margin-top: 2rem;
}
&.error {
background: rgba(255, 0, 0, 0.05);
&, h1, h2, h3, h4, h5, h6, p {
color: rgba(156, 0, 0) !important;
}
}
&.success {
border: 1px solid rgb(168, 255, 136);
background: rgba(0, 255, 0, 0.05);
&, h1, h2, h3, h4, h5, h6, p {
color: rgba(34, 125, 0) !important;
}
}
.header {
cursor: pointer;
height: 2rem;
width: 100%;
h3 {
line-height: 2rem;
margin-bottom: 0;
}
&.top-margin {
margin-top: 2rem;
}
.icon {
height: 4rem;
width: 4rem;
font-size: 2rem;
color: rgba(0,0,0,0.35);
position: absolute;
right: 0;
top: 0;
line-height: 4rem;
transition: all 0.2s;
transform-origin: center center;
&.error {
background: rgba(255, 0, 0, 0.05);
&, h1, h2, h3, h4, h5, h6, p {
color: rgba(156, 0, 0) !important;
}
}
&.success {
border: 1px solid rgb(168, 255, 136);
background: rgba(0, 255, 0, 0.05);
&, h1, h2, h3, h4, h5, h6, p {
color: rgba(34, 125, 0) !important;
}
}
.header {
cursor: pointer;
height: 2rem;
width: 100%;
h3 {
line-height: 2rem;
margin-bottom: 0;
}
.icon {
height: 4rem;
width: 4rem;
font-size: 2rem;
color: rgba(0,0,0,0.35);
position: absolute;
right: 0;
top: 0;
line-height: 4rem;
transition: all 0.2s;
transform-origin: center center;
&.collapsed {
transform: rotate(180deg);
}
}
}
.children {
&.collapsed {
display: none;
}
}
&.as-link {
cursor: pointer;
&:hover {
box-shadow: 0 5px 5px rgba(0,0,0,.2);
transform: translateY(-2px);
transform: rotate(180deg);
}
}
}
`
);
.children {
&.collapsed {
display: none;
}
}
&.as-link {
cursor: pointer;
&:hover {
box-shadow: 0 5px 5px rgba(0,0,0,.2);
transform: translateY(-2px);
}
}
}
`;

View File

@ -2,7 +2,10 @@
// 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 trackStream } from './trackStream';
export { default as trackPromise } from './track/trackPromise';
export { default as trackStream } from './track/trackStream';
export { default as useAccounts } from './useAccounts';
export { default as useAddresses } from './useAddresses';
export { default as useApiContext } from './useApiContext';
export { default as useDebounce } from './useDebounce';
export { default as useFavorites } from './useFavorites';

View File

@ -0,0 +1,58 @@
// Copyright 2017-2019 @polkadot/react-hooks 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 { Options, Params } from './types';
import { useEffect, useRef, useState } from 'react';
import { extractParams, transformIdentity } from './util';
interface TrackFn <T> {
(a: any, b: any, c: any): Promise<T>;
(a: any, b: any): Promise<T>;
(a: any): Promise<T>;
(): Promise<T>;
}
// tracks a promise, typically an api.* call that
// - returns a promise with the value
// FIXME The typings here need some serious TLC
export default function trackPromise <T> (fn: TrackFn<T> | undefined, params: any, { paramMap = transformIdentity, transform = transformIdentity }: Options<T> = {}): T | undefined {
const [value, setValue] = useState<T | undefined>();
const tracker = useRef<{ serialized: string | null }>({ serialized: null });
const _subscribe = (params: Params): void => {
setImmediate((): void => {
if (fn) {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore We tried to get the typings right, close but no cigar...
fn(...params).then((value: any): void => setValue(transform(value)));
}
});
};
// initial round, subscribe once
useEffect((): void => {
const [serialized, mappedParams] = extractParams(params, paramMap);
tracker.current.serialized = serialized;
if (mappedParams) {
_subscribe(mappedParams);
}
}, []);
// on changes, re-get
useEffect((): void => {
const [serialized, mappedParams] = extractParams(params, paramMap);
if (mappedParams && serialized !== tracker.current.serialized) {
tracker.current.serialized = serialized;
_subscribe(mappedParams);
}
}, [params]);
return value;
}

View File

@ -2,15 +2,11 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Options, Params } from './types';
import { useEffect, useRef, useState } from 'react';
import { isNull, isUndefined } from '@polkadot/util';
type Params = [] | [any] | [any, any] | [any, any, any];
interface Options <T> {
paramMap?: (params: any) => any;
transform?: (value: any) => T;
}
import { dummyPromise, extractParams, transformIdentity } from './util';
interface TrackFnCallback <T> {
(value: T): void;
@ -25,41 +21,28 @@ interface TrackFn <T> {
(cb: TrackFnCallback<T>): Promise<Unsub>;
}
const dummySubscribe = Promise.resolve((): void => {
// we do nothing, this is a noop
});
function transformIdentity (value: any): any {
return value;
}
// extract the serialized and mapped params, all ready for use in our call
function extractParams (params: any[], paramMap: (params: any[]) => any): [string, Params | null] {
return [
JSON.stringify({ a: params }),
params.length === 0 || !params.some((param): boolean => isNull(param) || isUndefined(null))
? paramMap(params)
: null
];
}
// tracks a stream, typically an api.* call that
// - returns a promise with an unsubscription
// - has a callback to set the value
// FIXME The typings here need some serious TLC
export default function trackStream <T> (fn: TrackFn<any> | undefined, params: any, { paramMap = transformIdentity, transform = transformIdentity }: Options<T> = {}): T | undefined {
const [value, setValue] = useState<T | undefined>();
const tracker = useRef<{ serialized: string | null; subscriber: Promise<Unsub> }>({ serialized: null, subscriber: dummySubscribe });
const tracker = useRef<{ serialized: string | null; subscriber: Promise<Unsub> }>({ serialized: null, subscriber: dummyPromise });
const _subscribe = (params: Params): void => {
tracker.current.subscriber = fn
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore We tried to get the typings right, close but no cigar...
? fn(...params, (value: any): T => setValue(transform(value)))
: dummySubscribe;
};
const _unsubscribe = (): void => {
tracker.current.subscriber.then((fn): void => fn());
tracker.current.subscriber = dummyPromise;
};
const _subscribe = (params: Params): void => {
_unsubscribe();
setImmediate((): void => {
tracker.current.subscriber = fn
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore We tried to get the typings right, close but no cigar...
? fn(...params, (value: any): void => setValue(transform(value)))
: dummyPromise;
});
};
// initial round, subscribe once
@ -81,7 +64,7 @@ export default function trackStream <T> (fn: TrackFn<any> | undefined, params: a
if (mappedParams && serialized !== tracker.current.serialized) {
tracker.current.serialized = serialized;
_unsubscribe();
_subscribe(mappedParams);
}
}, [params]);

View File

@ -0,0 +1,10 @@
// Copyright 2017-2019 @polkadot/react-hooks 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 Params = [] | [any] | [any, any] | [any, any, any];
export interface Options <T> {
paramMap?: (params: any) => any;
transform?: (value: any) => T;
}

View File

@ -0,0 +1,25 @@
// Copyright 2017-2019 @polkadot/react-hooks 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 { Params } from './types';
import { isNull, isUndefined } from '@polkadot/util';
export const dummyPromise = Promise.resolve((): void => {
// we do nothing, this is a noop
});
export function transformIdentity (value: any): any {
return value;
}
// extract the serialized and mapped params, all ready for use in our call
export function extractParams (params: any[], paramMap: (params: any[]) => any): [string, Params | null] {
return [
JSON.stringify({ a: params }),
params.length === 0 || !params.some((param): boolean => isNull(param) || isUndefined(null))
? paramMap(params)
: null
];
}

View File

@ -0,0 +1,30 @@
// Copyright 2017-2019 @polkadot/react-hooks 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 { useEffect, useState } from 'react';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
interface UseAccounts {
allAccounts: string[];
hasAccounts: boolean;
}
export default function useAccounts (): UseAccounts {
const [state, setState] = useState<UseAccounts>({ allAccounts: [], hasAccounts: false });
useEffect((): () => void => {
const subscription = accountObservable.subject.subscribe((accounts): void => {
const allAccounts = accounts ? Object.keys(accounts) : [];
const hasAccounts = allAccounts.length !== 0;
setState({ allAccounts, hasAccounts });
});
return (): void => {
subscription.unsubscribe();
};
}, []);
return state;
}

View File

@ -0,0 +1,30 @@
// Copyright 2017-2019 @polkadot/react-hooks 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 { useEffect, useState } from 'react';
import addressObservable from '@polkadot/ui-keyring/observable/addresses';
interface UseAccounts {
allAddresses: string[];
hasAddresses: boolean;
}
export default function useAccounts (): UseAccounts {
const [state, setState] = useState<UseAccounts>({ allAddresses: [], hasAddresses: false });
useEffect((): () => void => {
const subscription = addressObservable.subject.subscribe((addresses): void => {
const allAddresses = addresses ? Object.keys(addresses) : [];
const hasAddresses = allAddresses.length !== 0;
setState({ allAddresses, hasAddresses });
});
return (): void => {
subscription.unsubscribe();
};
}, []);
return state;
}

View File

@ -113,47 +113,44 @@ async function loadSome (api: ApiPromise, fromHash: Hash, toHash: Hash): Promise
export default function useSessionRewards (maxSessions: number): SessionRewards[] {
const { api } = useApiContext();
const [getCache, setCache] = useCacheKey<Serialized[]>('hooks:sessionSlashes');
const [results, setResults] = useState<SessionRewards[]>(fromJSON(getCache() || []));
const [filtered, setFiltered] = useState<SessionRewards[]>([]);
useEffect((): void => {
let workQueue = results;
let workQueue = fromJSON(getCache() || []);
api.isReady.then(async (): Promise<void> => {
const maxSessionsStore = maxSessions + 1; // assuming first is a bust
const sessionLength = (api.consts.babe?.epochDuration as BlockNumber || new BN(500));
const count = Math.min(sessionLength.muln(maxSessionsStore).divn(10).toNumber(), 10000);
const bestHeader = await api.rpc.chain.getHeader();
let toHash = bestHeader.hash;
let toNumber = bestHeader.number.unwrap().toBn();
let fromHash = api.genesisHash;
let fromNumber = bnMax(toNumber.subn(count), new BN(1));
setImmediate((): void => {
api.isReady.then(async (): Promise<void> => {
const maxSessionsStore = maxSessions + 1; // assuming first is a bust
const sessionLength = (api.consts.babe?.epochDuration as BlockNumber || new BN(500));
const count = Math.min(sessionLength.muln(maxSessionsStore).divn(10).toNumber(), 10000);
const bestHeader = await api.rpc.chain.getHeader();
let toHash = bestHeader.hash;
let toNumber = bestHeader.number.unwrap().toBn();
let fromHash = api.genesisHash;
let fromNumber = bnMax(toNumber.subn(count), new BN(1));
while (true) {
fromHash = await api.rpc.chain.getBlockHash(fromNumber as any);
while (true) {
fromHash = await api.rpc.chain.getBlockHash(fromNumber as any);
const newQueue = await loadSome(api, fromHash, toHash);
const newQueue = await loadSome(api, fromHash, toHash);
workQueue = mergeResults(workQueue, newQueue);
toHash = fromHash;
toNumber = fromNumber;
fromNumber = bnMax(toNumber.subn(count), new BN(1));
workQueue = mergeResults(workQueue, newQueue);
toHash = fromHash;
toNumber = fromNumber;
fromNumber = bnMax(toNumber.subn(count), new BN(1));
setCache(toJSON(workQueue, maxSessionsStore));
setResults(workQueue);
setCache(toJSON(workQueue, maxSessionsStore));
setFiltered(workQueue.slice(-maxSessions));
const lastNumber = workQueue[workQueue.length - 1]?.blockNumber;
const lastNumber = workQueue[workQueue.length - 1]?.blockNumber;
if (!lastNumber || fromNumber.eqn(1) || ((workQueue.length >= maxSessionsStore) && fromNumber.lt(lastNumber))) {
break;
if (!lastNumber || fromNumber.eqn(1) || ((workQueue.length >= maxSessionsStore) && fromNumber.lt(lastNumber))) {
break;
}
}
}
});
});
}, []);
useEffect((): void => {
setFiltered(results.slice(-maxSessions));
}, [results]);
return filtered;
}