161 lines
4.1 KiB
TypeScript
161 lines
4.1 KiB
TypeScript
// Copyright 2017-2024 @polkadot/react-params authors & contributors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
import type { RawParam, RawParamOnChange, RawParamOnEnter, RawParamOnEscape, Size, TypeDefExt } from '../types.js';
|
|
|
|
import React, { useCallback, useState } from 'react';
|
|
|
|
import { CopyButton, IdentityIcon, Input, styled } from '@polkadot/react-components';
|
|
import { compactAddLength, hexToU8a, isAscii, isHex, stringToU8a, u8aToHex, u8aToString, u8aToU8a } from '@polkadot/util';
|
|
import { decodeAddress } from '@polkadot/util-crypto';
|
|
|
|
import { useTranslation } from '../translate.js';
|
|
import Bare from './Bare.js';
|
|
|
|
interface Props {
|
|
asHex?: boolean;
|
|
children?: React.ReactNode;
|
|
className?: string;
|
|
defaultValue: RawParam;
|
|
isDisabled?: boolean;
|
|
isError?: boolean;
|
|
label?: React.ReactNode;
|
|
labelExtra?: React.ReactNode;
|
|
length?: number;
|
|
name?: string;
|
|
onChange?: RawParamOnChange;
|
|
onEnter?: RawParamOnEnter;
|
|
onEscape?: RawParamOnEscape;
|
|
size?: Size;
|
|
type: TypeDefExt;
|
|
validate?: (u8a: Uint8Array) => boolean;
|
|
withCopy?: boolean;
|
|
withLabel?: boolean;
|
|
withLength?: boolean;
|
|
}
|
|
|
|
interface Validity {
|
|
isAddress: boolean;
|
|
isValid: boolean;
|
|
lastValue?: Uint8Array;
|
|
}
|
|
|
|
const defaultValidate = (): boolean =>
|
|
true;
|
|
|
|
function convertInput (value: string): [boolean, boolean, Uint8Array] {
|
|
if (value === '0x') {
|
|
return [true, false, new Uint8Array([])];
|
|
} else if (value.startsWith('0x')) {
|
|
try {
|
|
return [true, false, hexToU8a(value)];
|
|
} catch {
|
|
return [false, false, new Uint8Array([])];
|
|
}
|
|
}
|
|
|
|
// maybe it is an ss58?
|
|
try {
|
|
return [true, true, decodeAddress(value)];
|
|
} catch {
|
|
// we continue
|
|
}
|
|
|
|
return isAscii(value)
|
|
? [true, false, stringToU8a(value)]
|
|
: [value === '0x', false, new Uint8Array([])];
|
|
}
|
|
|
|
function BaseBytes ({ asHex, children, className = '', defaultValue: { value }, isDisabled, isError, label, labelExtra, length = -1, onChange, onEnter, onEscape, size = 'full', validate = defaultValidate, withCopy, withLabel, withLength }: Props): React.ReactElement<Props> {
|
|
const { t } = useTranslation();
|
|
const [defaultValue] = useState(
|
|
(): string | undefined => {
|
|
if (value) {
|
|
const u8a = u8aToU8a(value as Uint8Array);
|
|
|
|
return isAscii(u8a)
|
|
? u8aToString(u8a)
|
|
: u8aToHex(u8a);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
);
|
|
const [{ isAddress, isValid, lastValue }, setValidity] = useState<Validity>(() => ({
|
|
isAddress: false,
|
|
isValid: isHex(defaultValue) || isAscii(defaultValue)
|
|
}));
|
|
|
|
const _onChange = useCallback(
|
|
(hex: string): void => {
|
|
let [isValid, isAddress, value] = convertInput(hex);
|
|
|
|
isValid = isValid && validate(value) && (
|
|
length !== -1
|
|
? value.length === length
|
|
: (value.length !== 0 || hex === '0x')
|
|
);
|
|
|
|
if (withLength && isValid) {
|
|
value = compactAddLength(value);
|
|
}
|
|
|
|
onChange && onChange({
|
|
isValid,
|
|
value: asHex
|
|
? u8aToHex(value)
|
|
: value
|
|
});
|
|
|
|
setValidity({ isAddress, isValid, lastValue: value });
|
|
},
|
|
[asHex, length, onChange, validate, withLength]
|
|
);
|
|
|
|
return (
|
|
<StyledBare className={className}>
|
|
<Input
|
|
className={size}
|
|
defaultValue={defaultValue}
|
|
isAction={!!children}
|
|
isDisabled={isDisabled}
|
|
isError={isError || !isValid}
|
|
label={label}
|
|
labelExtra={labelExtra}
|
|
onChange={_onChange}
|
|
onEnter={onEnter}
|
|
onEscape={onEscape}
|
|
placeholder={t('0x prefixed hex, e.g. 0x1234 or ascii data')}
|
|
type='text'
|
|
withEllipsis
|
|
withLabel={withLabel}
|
|
>
|
|
{children}
|
|
{withCopy && (
|
|
<CopyButton value={defaultValue} />
|
|
)}
|
|
{isAddress && (
|
|
<IdentityIcon
|
|
className='ui--InputAddressSimpleIcon'
|
|
size={32}
|
|
value={lastValue}
|
|
/>
|
|
)}
|
|
</Input>
|
|
</StyledBare>
|
|
);
|
|
}
|
|
|
|
const StyledBare = styled(Bare)`
|
|
.ui--InputAddressSimpleIcon {
|
|
background: #eee;
|
|
border: 1px solid #888;
|
|
border-radius: 50%;
|
|
left: -16px;
|
|
position: absolute;
|
|
top: 8px;
|
|
}
|
|
`;
|
|
|
|
export default React.memo(BaseBytes);
|