Support multiple explorers (#2316)

* Support multiple explorers

* LinkExplorer -> LinkExternal

* Add externals to council, treasury & democracy
This commit is contained in:
Jaco Greeff 2020-03-01 13:29:58 +01:00 committed by GitHub
parent a2c51c70f7
commit a65aa97b5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 187 additions and 79 deletions

View File

@ -9,7 +9,7 @@ import { RecoveryConfig } from '@polkadot/types/interfaces';
import React, { useState, useEffect } from 'react';
import { Label } from 'semantic-ui-react';
import styled from 'styled-components';
import { AddressInfo, AddressSmall, Badge, Button, ChainLock, Forget, Icon, IdentityIcon, InputTags, LinkPolkascan, Menu, Popup, Input } from '@polkadot/react-components';
import { AddressInfo, AddressSmall, Badge, Button, ChainLock, Forget, Icon, IdentityIcon, InputTags, LinkExternal, Menu, Popup, Input } from '@polkadot/react-components';
import { useApi, useCall, useToggle } from '@polkadot/react-hooks';
import { Option } from '@polkadot/types';
import keyring from '@polkadot/ui-keyring';
@ -414,7 +414,7 @@ function Account ({ address, className, filter, isFavorite, toggleFavorite }: Pr
</Popup>
</td>
<td className='mini top'>
<LinkPolkascan
<LinkExternal
className='ui--AddressCard-exporer-link'
data={address}
type='address'

View File

@ -9,7 +9,7 @@ import { ActionStatus } from '@polkadot/react-components/Status/types';
import React, { useEffect, useState } from 'react';
import { Label } from 'semantic-ui-react';
import styled from 'styled-components';
import { AddressSmall, AddressInfo, Button, ChainLock, Icon, InputTags, Input, LinkPolkascan, Forget, Menu, Popup } from '@polkadot/react-components';
import { AddressSmall, AddressInfo, Button, ChainLock, Icon, InputTags, Input, LinkExternal, Forget, Menu, Popup } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import keyring from '@polkadot/ui-keyring';
import Transfer from '@polkadot/app-accounts/modals/Transfer';
@ -295,7 +295,7 @@ function Address ({ address, className, filter, isFavorite, toggleFavorite }: Pr
</Popup>
</td>
<td className='mini top'>
<LinkPolkascan
<LinkExternal
className='ui--AddressCard-exporer-link'
data={address}
type='address'

View File

@ -5,7 +5,7 @@
import { DerivedCollectiveProposal } from '@polkadot/api-derive/types';
import React from 'react';
import { AddressMini } from '@polkadot/react-components';
import { AddressMini, LinkExternal } from '@polkadot/react-components';
import ProposalCell from '@polkadot/app-democracy/Overview/ProposalCell';
import { formatNumber } from '@polkadot/util';
@ -68,6 +68,10 @@ export default function Motion ({ className, isMember, members, motion: { hash,
members={members}
proposal={proposal}
/>
<LinkExternal
data={index}
type='council'
/>
</td>
</tr>
);

View File

@ -6,6 +6,7 @@ import { AccountId, Balance, BlockNumber, Hash, Proposal, ReferendumIndex } from
import { ITuple } from '@polkadot/types/types';
import React, { useEffect, useState } from 'react';
import { LinkExternal } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { Bytes, Option } from '@polkadot/types';
import { formatNumber } from '@polkadot/util';
@ -54,6 +55,10 @@ export default function DispatchEntry ({ blockNumber, hash, referendumIndex }: P
isImminent
proposal={proposal}
/>
<LinkExternal
data={referendumIndex}
type='referendum'
/>
</td>
</tr>
);

View File

@ -6,7 +6,7 @@ import { DeriveProposal } from '@polkadot/api-derive/types';
import React from 'react';
import styled from 'styled-components';
import { AddressMini, AddressSmall, Button } from '@polkadot/react-components';
import { AddressMini, AddressSmall, Button, LinkExternal } from '@polkadot/react-components';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
@ -68,6 +68,10 @@ function Proposal ({ className, value: { balance, hash, index, proposal, propose
proposal={proposal}
/>
</Button.Group>
<LinkExternal
data={index}
type='proposal'
/>
</td>
</tr>
);

View File

@ -9,7 +9,7 @@ import { BlockNumber } from '@polkadot/types/interfaces';
import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { Button } from '@polkadot/react-components';
import { Button, LinkExternal } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
@ -126,6 +126,10 @@ function Referendum ({ className, idNumber, value }: Props): React.ReactElement<
proposal={value.proposal}
/>
</Button.Group>
<LinkExternal
data={value.index}
type='referendum'
/>
</td>
</tr>
);

View File

@ -8,7 +8,7 @@ import React from 'react';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { HeaderExtended } from '@polkadot/api-derive';
import { AddressMini, LinkPolkascan } from '@polkadot/react-components';
import { AddressMini, LinkExternal } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';
interface Props extends BareProps {
@ -75,7 +75,7 @@ function BlockHeader ({ className, isSummary, value, withExplorer, withLink }: P
}
{
withExplorer
? <LinkPolkascan data={hashHex} type='block' />
? <LinkExternal data={hashHex} type='block' />
: undefined
}
</article>

View File

@ -8,7 +8,7 @@ import { I18nProps } from '@polkadot/react-components/types';
import React from 'react';
import styled from 'styled-components';
import { registry } from '@polkadot/react-api';
import { AddressMini, Call, Column, LinkPolkascan } from '@polkadot/react-components';
import { AddressMini, Call, Column, LinkExternal } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';
import translate from '../translate';
@ -73,7 +73,7 @@ function renderExtrinsic (props: Props, extrinsic: Extrinsic, index: number): Re
</details>
{
extrinsic.isSigned
? <LinkPolkascan data={extrinsic.hash.toHex()} type='extrinsic' />
? <LinkExternal data={extrinsic.hash.toHex()} type='extrinsic' />
: null
}
</article>

View File

@ -5,7 +5,7 @@
import { DerivedTreasuryProposal } from '@polkadot/api-derive/types';
import React from 'react';
import { AddressMini, AddressSmall } from '@polkadot/react-components';
import { AddressMini, AddressSmall, LinkExternal } from '@polkadot/react-components';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
@ -29,7 +29,7 @@ export default function ProposalDisplay ({ className, isMember, proposal: { coun
<td className='number top'>
<h1>{formatNumber(id)}</h1>
</td>
<td>
<td className='top'>
<AddressSmall value={proposal.proposer} />
</td>
<td className='top'>
@ -64,6 +64,10 @@ export default function ProposalDisplay ({ className, isMember, proposal: { coun
/>
</>
)}
<LinkExternal
data={id}
type='treasury'
/>
</td>
</tr>
);

View File

@ -0,0 +1,152 @@
// Copyright 2017-2020 @polkadot/react-components authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import BN from 'bn.js';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { useApi } from '@polkadot/react-hooks';
import { useTranslation } from './translate';
import Icon from './Icon';
import Tooltip from './Tooltip';
export type LinkTypes = 'address' | 'block' | 'council' | 'extrinsic' | 'proposal' | 'referendum' | 'treasury';
interface Props {
className?: string;
data: BN | number | string;
type: LinkTypes;
withShort?: boolean;
}
interface Explorer {
isActive: boolean;
chains: Record<string, string>;
paths: Partial<Record<LinkTypes, string>>;
create: (chain: string, path: string, data: BN | number | string) => string;
}
const EXPLORERS: Record<string, Explorer> = {
Commonwealth: {
isActive: true,
chains: {
Edgeware: 'edgeware',
Kusama: 'kusama',
'Kusama CC3': 'kusama'
},
paths: {
council: 'proposal/councilmotion',
proposal: 'proposal/democracyproposal',
referendum: 'proposal/referendum',
treasury: 'proposal/treasuryproposal'
},
create: (chain: string, path: string, data: BN | number | string): string =>
`https://commonwealth.im/${chain}/${path}/${data.toString()}`
},
Polkascan: {
isActive: true,
chains: {
Edgeware: 'edgeware',
Kulupu: 'kulupu',
Kusama: 'kusama',
'Kusama CC3': 'kusama',
Westend: 'westend'
},
paths: {
address: 'module/account',
block: 'system/block',
council: 'council/motion',
extrinsic: 'system/extrinsic',
proposal: 'democracy/proposal',
referendum: 'democracy/referendum',
treasury: 'treasury/proposal'
},
create: (chain: string, path: string, data: BN | number | string): string =>
`https://polkascan.io/pre/${chain}/${path}/${data.toString()}`
},
Subscan: {
isActive: false,
chains: {
Kusama: 'kusama',
'Kusama CC3': 'kusama'
},
paths: {
address: 'account',
block: 'block',
extrinsic: 'extrinsic'
},
create: (chain: string, path: string, data: BN | number | string): string =>
`https://${chain}.subscan.io/${path}/${data.toString()}`
}
};
function genLinks (systemChain: string, { data, type, withShort }: Props): React.ReactNode[] {
return Object
.entries(EXPLORERS)
.map(([name, { isActive, chains, paths, create }]): React.ReactNode | null => {
const extChain = chains[systemChain];
const extPath = paths[type];
if (!isActive || !extChain || !extPath) {
return null;
}
const trigger = `${name}-${type}-${data}`;
const link = create(extChain, extPath, data);
return (
<a
data-for={trigger}
data-tip={true}
href={link}
key={name}
rel='noopener noreferrer'
target='_blank'
>
{withShort
? <Icon name='external' />
: name
}
<Tooltip
place='top'
text={name}
trigger={trigger}
/>
</a>
);
})
.filter((node): node is React.ReactNode => !!node);
}
function LinkExternal ({ className, data, type, withShort }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { systemChain } = useApi();
const links = useMemo((): React.ReactNode[] => {
return genLinks(systemChain, { data, type, withShort });
}, [systemChain, data, type, withShort]);
if (!links.length) {
return null;
}
return (
<div className={`${className} ${withShort ? 'withShort' : ''}`}>
{!withShort && <div>{t('View this externally')}</div>}<div>{links.map((link, index) => <span key={index}>{link}</span>)}</div>
</div>
);
}
export default styled(LinkExternal)`
margin-top: 0.75rem;
text-align: right;
> div {
display: block;
whitespace: nowrap;
> span+span {
margin-left: 0.3rem;
}
}
`;

View File

@ -1,65 +0,0 @@
// Copyright 2017-2020 @polkadot/react-components authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import styled from 'styled-components';
import { useApi } from '@polkadot/react-hooks';
import { useTranslation } from './translate';
import Icon from './Icon';
export type LinkTypes = 'address' | 'block' | 'extrinsic';
interface Props {
className?: string;
data: string;
type: LinkTypes;
withShort?: boolean;
}
const BASE = 'https://polkascan.io/pre/';
const CHAINS: Record<string, string> = {
Kulupu: 'kulupu',
Kusama: 'kusama',
'Kusama CC3': 'kusama',
Westend: 'westend'
};
const TYPES: Record<string, string> = {
address: '/module/account/',
block: '/system/block/',
extrinsic: '/system/extrinsic/'
};
function LinkPolkascan ({ className, data, type, withShort }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { systemChain } = useApi();
const extChain = CHAINS[systemChain];
const extType = TYPES[type];
if (!extChain || !extType) {
return null;
}
return (
<div className={`${className} ${withShort ? 'withShort' : ''}`}>
<a
href={`${BASE}${extChain}${extType}${data}`}
rel='noopener noreferrer'
target='_blank'
>
{withShort
? <Icon name='external' />
: t('View this {{type}} on Polkascan.io', { replace: { type } })
}
</a>
</div>
);
}
export default styled(LinkPolkascan)`
margin-top: 1rem;
text-align: right;
`;

View File

@ -60,7 +60,7 @@ export { default as Inset } from './Inset';
export { default as Label } from './Label';
export { default as LabelHelp } from './LabelHelp';
export { default as Labelled } from './Labelled';
export { default as LinkPolkascan } from './LinkPolkascan';
export { default as LinkExternal } from './LinkExternal';
export { default as LockedVote } from './LockedVote';
export { default as Menu } from './Menu';
export { default as Messages } from './Messages';