Support multiple explorers (#2316)
* Support multiple explorers * LinkExplorer -> LinkExternal * Add externals to council, treasury & democracy
This commit is contained in:
parent
a2c51c70f7
commit
a65aa97b5f
@ -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'
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
152
packages/react-components/src/LinkExternal.tsx
Normal file
152
packages/react-components/src/LinkExternal.tsx
Normal 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;
|
||||
}
|
||||
}
|
||||
`;
|
||||
@ -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;
|
||||
`;
|
||||
@ -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';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user