Params support for non-native AccountId{20, 32} (#8752)

This commit is contained in:
Jaco 2023-01-10 19:54:52 +02:00 committed by GitHub
parent e239e8e1df
commit b2e8b6444a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 143 additions and 16 deletions

View File

@ -18,6 +18,7 @@ import RoboHash from './RoboHash';
interface Props {
className?: string;
forceIconType?: 'ethereum' | 'substrate';
prefix?: IdentityProps['prefix'];
size?: number;
theme?: IdentityProps['theme'] | 'robohash';
@ -32,11 +33,12 @@ function isCodec (value?: AccountId | AccountIndex | Address | string | Uint8Arr
return !!(value && (value as AccountId).toHuman);
}
function IdentityIcon ({ className = '', prefix, size = 24, theme, value }: Props): React.ReactElement<Props> {
function IdentityIcon ({ className = '', forceIconType, prefix, size = 24, theme, value }: Props): React.ReactElement<Props> {
const { isEthereum, specName, systemName } = useApi();
const { t } = useTranslation();
const { queueAction } = useQueue();
const thisTheme = theme || getIdentityTheme(systemName, specName);
const Custom = thisTheme === 'robohash'
? RoboHash
: undefined;
@ -58,7 +60,7 @@ function IdentityIcon ({ className = '', prefix, size = 24, theme, value }: Prop
onCopy={_onCopy}
prefix={prefix}
size={size}
theme={isEthereum ? 'ethereum' : thisTheme as 'substrate'}
theme={forceIconType || (isEthereum ? 'ethereum' : thisTheme as 'substrate')}
value={isCodec(value) ? value.toString() : value}
/>
);

View File

@ -13,6 +13,7 @@ interface Props {
children?: React.ReactNode;
className?: string;
defaultValue?: string | null;
forceIconType?: 'ethereum' | 'substrate';
help?: React.ReactNode;
isDisabled?: boolean;
isError?: boolean;
@ -24,7 +25,7 @@ interface Props {
onEscape?: () => void;
}
function InputAddressSimple ({ autoFocus, children, className = '', defaultValue, help, isDisabled, isError, isFull, label, noConvert, onChange, onEnter, onEscape }: Props): React.ReactElement<Props> {
function InputAddressSimple ({ autoFocus, children, className = '', defaultValue, forceIconType, help, isDisabled, isError, isFull, label, noConvert, onChange, onEnter, onEscape }: Props): React.ReactElement<Props> {
const [address, setAddress] = useState<string | null>(defaultValue || null);
const _onChange = useCallback(
@ -43,7 +44,7 @@ function InputAddressSimple ({ autoFocus, children, className = '', defaultValue
);
return (
<div className={className}>
<div className={`${className} ui--InputAddressSimple`}>
<Input
autoFocus={autoFocus}
defaultValue={defaultValue}
@ -60,6 +61,7 @@ function InputAddressSimple ({ autoFocus, children, className = '', defaultValue
</Input>
<IdentityIcon
className='ui--InputAddressSimpleIcon'
forceIconType={forceIconType}
size={32}
value={address}
/>

View File

@ -9,9 +9,7 @@ import { KeyringCtx } from './ctx/Keyring';
import { createNamedHook } from './createNamedHook';
function useAccountsImpl (): Accounts {
const { accounts } = useContext(KeyringCtx);
return accounts;
return useContext(KeyringCtx).accounts;
}
export const useAccounts = createNamedHook('useAccounts', useAccountsImpl);

View File

@ -9,9 +9,7 @@ import { KeyringCtx } from './ctx/Keyring';
import { createNamedHook } from './createNamedHook';
function useAddressesImpl (): Addresses {
const { addresses } = useContext(KeyringCtx);
return addresses;
return useContext(KeyringCtx).addresses;
}
export const useAddresses = createNamedHook('useAddresses', useAddressesImpl);

View File

@ -41,10 +41,13 @@ export default React.memo(styled(Holder)`
padding-left: 4rem;
}
.ui--Param-composite .ui--row > .ui--Labelled > label {
text-transform: none !important;
font: var(--font-mono);
font-size: var(--font-size-label);
.ui--Param-composite .ui--row,
.ui--Param-composite .ui--row .ui--InputAddressSimple {
& > .ui--Labelled > label {
text-transform: none !important;
font: var(--font-mono);
font-size: var(--font-size-label);
}
}
.ui--row {

View File

@ -0,0 +1,19 @@
// Copyright 2017-2023 @polkadot/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Props } from '../types';
import React from 'react';
import BasicAccountIdBase from './BasicAccountIdBase';
function BasicAccountId20 (props: Props): React.ReactElement<Props> {
return (
<BasicAccountIdBase
{...props}
bytesLength={20}
/>
);
}
export default React.memo(BasicAccountId20);

View File

@ -0,0 +1,19 @@
// Copyright 2017-2023 @polkadot/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Props } from '../types';
import React from 'react';
import BasicAccountIdBase from './BasicAccountIdBase';
function BasicAccountId32 (props: Props): React.ReactElement<Props> {
return (
<BasicAccountIdBase
{...props}
bytesLength={32}
/>
);
}
export default React.memo(BasicAccountId32);

View File

@ -0,0 +1,70 @@
// Copyright 2017-2023 @polkadot/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Props as BaseProps } from '../types';
import React, { useCallback, useState } from 'react';
import { InputAddressSimple } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { isEthereumAddress, validateAddress } from '@polkadot/util-crypto';
import Bare from './Bare';
interface Props extends BaseProps {
bytesLength: 20 | 32;
}
function isValidAddress (value: string | null | undefined, isEthereum: boolean): boolean {
if (value) {
try {
if (isEthereum) {
return isEthereumAddress(value);
} else {
return validateAddress(value);
}
return true;
} catch (err) {
console.error(err);
}
}
return false;
}
function BasicAccountIdBase (props: Props): React.ReactElement<Props> {
const { isEthereum } = useApi();
const { bytesLength, className = '', defaultValue: { value }, isDisabled, isError, isInOption, label, onChange, withLabel } = props;
const [defaultValue] = useState(() => (value as string)?.toString());
const _onChange = useCallback(
(value?: string | null) =>
onChange && onChange({
isValid: isValidAddress(value, bytesLength === 20),
value
}),
[bytesLength, onChange]
);
return (
<Bare className={className}>
<InputAddressSimple
className='full'
defaultValue={defaultValue}
forceIconType={bytesLength === 20 ? 'ethereum' : 'substrate'}
hideAddress={isInOption}
isDisabled={isDisabled}
isError={isError}
isInput
label={label}
noConvert
onChange={_onChange}
placeholder={isEthereum ? '5...' : '0x1...'}
withLabel={withLabel}
/>
</Bare>
);
}
export default React.memo(BasicAccountIdBase);

View File

@ -11,6 +11,8 @@ import { isBn } from '@polkadot/util';
import Account from './Account';
import Amount from './Amount';
import Balance from './Balance';
import AccountId20 from './BasicAccountId20';
import AccountId32 from './BasicAccountId32';
import Bool from './Bool';
import Bytes from './Bytes';
import Call from './Call';
@ -43,12 +45,12 @@ interface TypeToComponent {
t: string[];
}
const SPECIAL_TYPES = ['AccountId', 'AccountId32', 'AccountIndex', 'Address', 'Balance', 'BalanceOf', 'Vec<KeyValue>'];
const SPECIAL_TYPES = ['AccountId', 'AccountId20', 'AccountId32', 'AccountIndex', 'Address', 'Balance', 'BalanceOf', 'Vec<KeyValue>'];
const DISPATCH_ERROR = ['DispatchError', 'SpRuntimeDispatchError'];
const componentDef: TypeToComponent[] = [
{ c: Account, t: ['AccountId', 'AccountId32', 'Address', 'LookupSource', 'MultiAddress'] },
{ c: Account, t: ['AccountId', 'Address', 'LookupSource', 'MultiAddress'] },
{ c: Amount, t: ['AccountIndex', 'i8', 'i16', 'i32', 'i64', 'i128', 'u8', 'u16', 'u32', 'u64', 'u128', 'u256'] },
{ c: Balance, t: ['Amount', 'Balance', 'BalanceOf'] },
{ c: Bool, t: ['bool'] },
@ -142,6 +144,20 @@ function fromDef ({ displayName, info, lookupName, sub, type }: TypeDef): string
}
export default function findComponent (registry: Registry, def: TypeDef, overrides: ComponentMap = {}): React.ComponentType<Props> {
// Explicit/special handling for Account20/32 types where they don't match
// the actual chain we are connected to
if (['AccountId20', 'AccountId32'].includes(def.type)) {
const defType = `AccountId${registry.createType('AccountId').length}`;
if (def.type !== defType) {
if (def.type === 'AccountId20') {
return AccountId20;
} else {
return AccountId32;
}
}
}
const findOne = (type?: string): React.ComponentType<Props> | null =>
type
? overrides[type] || components[type]