mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-04-25 01:07:55 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { MultiAddress } from '@pezkuwi/types/interfaces';
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { InputAddress } from '@pezkuwi/react-components';
|
||||
import { keyring } from '@pezkuwi/ui-keyring';
|
||||
|
||||
import Bare from './Bare.js';
|
||||
import Enum from './Enum.js';
|
||||
|
||||
function isValidAddress (value?: string | null): boolean {
|
||||
if (value) {
|
||||
try {
|
||||
keyring.decodeAddress(value);
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function Account (props: Props): React.ReactElement<Props> {
|
||||
const { className = '', defaultValue: { value }, isDisabled, isError, label, onChange, type, withLabel } = props;
|
||||
const [defaultValue] = useState(() => (value as string)?.toString());
|
||||
|
||||
const _onChange = useCallback(
|
||||
(value?: string | null) =>
|
||||
onChange && onChange({
|
||||
isValid: isValidAddress(value),
|
||||
value
|
||||
}),
|
||||
[onChange]
|
||||
);
|
||||
|
||||
// special handling for MultiAddress
|
||||
if (type.type === 'MultiAddress') {
|
||||
if (!isDisabled || !value || (value as MultiAddress).type !== 'Id') {
|
||||
return <Enum {...props} />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<InputAddress
|
||||
className='full'
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
isInput
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
placeholder='5GLFK...'
|
||||
type='allPlus'
|
||||
withEllipsis
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Account);
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BitLength } from '@pezkuwi/react-components/types';
|
||||
import type { Registry, TypeDef } from '@pezkuwi/types/types';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { Input, InputNumber } from '@pezkuwi/react-components';
|
||||
import { bnToBn, formatNumber, isUndefined } from '@pezkuwi/util';
|
||||
|
||||
import Bare from './Bare.js';
|
||||
|
||||
function getBitLength (registry: Registry, { type }: TypeDef): BitLength {
|
||||
try {
|
||||
return registry.createType(type as 'u32').bitLength() as BitLength;
|
||||
} catch {
|
||||
return 32;
|
||||
}
|
||||
}
|
||||
|
||||
function Amount ({ className = '', defaultValue: { value }, isDisabled, isError, label, onChange, onEnter, registry, type, withLabel }: Props): React.ReactElement<Props> {
|
||||
const isSigned = useMemo(
|
||||
// Allow signed inputs for i{8, 16, 32, 64, 128, ...} types
|
||||
() => /^i\d*$/.test(type.type),
|
||||
[type]
|
||||
);
|
||||
|
||||
const defaultValue = useMemo(
|
||||
() => isDisabled
|
||||
? value instanceof registry.createClass('AccountIndex')
|
||||
? value.toString()
|
||||
: formatNumber(value as number)
|
||||
: bnToBn((value as number) || 0).toString(),
|
||||
[isDisabled, registry, value]
|
||||
);
|
||||
|
||||
const bitLength = useMemo(
|
||||
() => getBitLength(registry, type),
|
||||
[registry, type]
|
||||
);
|
||||
|
||||
const _onChange = useCallback(
|
||||
(value?: BN) =>
|
||||
onChange && onChange({
|
||||
isValid: !isUndefined(value),
|
||||
value
|
||||
}),
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
{isDisabled
|
||||
? (
|
||||
<Input
|
||||
className='full'
|
||||
defaultValue={defaultValue}
|
||||
isDisabled
|
||||
label={label}
|
||||
withEllipsis
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<InputNumber
|
||||
bitLength={bitLength}
|
||||
className='full'
|
||||
defaultValue={defaultValue}
|
||||
isError={isError}
|
||||
isSigned={isSigned}
|
||||
isZeroable
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
onEnter={onEnter}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Amount);
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ParamDef, Props, RawParam } from '../types.js';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Button } from '@pezkuwi/react-components';
|
||||
import { BTreeMap } from '@pezkuwi/types';
|
||||
import { isCodec, isUndefined } from '@pezkuwi/util';
|
||||
|
||||
import Params from '../index.js';
|
||||
import getInitValue from '../initValue.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Base from './Base.js';
|
||||
import useParamDefs from './useParamDefs.js';
|
||||
|
||||
function getParamType ([key, value]: ParamDef[]): ParamDef[] {
|
||||
return [{
|
||||
name: '(Key, Value)',
|
||||
type: {
|
||||
info: 17,
|
||||
sub: [key.type, value.type],
|
||||
type: `(${key.type.type}, ${value.type.type})`
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
function getParam ([{ name, type }]: ParamDef[], index: number): ParamDef {
|
||||
return {
|
||||
name: `${index}: ${name || type.type}`,
|
||||
type
|
||||
};
|
||||
}
|
||||
|
||||
export function getParams (keyValueParam: ParamDef[], prev: ParamDef[], max: number): ParamDef[] {
|
||||
if (prev.length === max) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
const params: ParamDef[] = [];
|
||||
|
||||
for (let index = 0; index < max; index++) {
|
||||
params.push(getParam(keyValueParam, index));
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export function getValues ({ isValid, value }: RawParam): RawParam[] {
|
||||
return (isValid && isCodec(value) && value instanceof BTreeMap)
|
||||
? [...value.entries()].map(([key, value]: RawParam[]) => {
|
||||
return {
|
||||
isValid: true,
|
||||
value: [{ isValid: true, value: key }, { isValid: true, value }]
|
||||
};
|
||||
})
|
||||
: [];
|
||||
}
|
||||
|
||||
function BTreeMapParam ({ className = '', defaultValue, isDisabled = false, label, onChange, overrides, registry, type, withLabel }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const keyValueParam = getParamType(useParamDefs(registry, type));
|
||||
const [values, setValues] = useState<RawParam[]>(() => getValues(defaultValue));
|
||||
const [count, setCount] = useState(() => values.length);
|
||||
const [params, setParams] = useState<ParamDef[]>(() => getParams(keyValueParam, [], count));
|
||||
|
||||
// build up the list of parameters we are using
|
||||
useEffect((): void => {
|
||||
keyValueParam.length &&
|
||||
setParams((prev) =>
|
||||
getParams(keyValueParam, prev, isDisabled ? values.length : count)
|
||||
);
|
||||
}, [count, values, isDisabled, keyValueParam]);
|
||||
|
||||
// when !isDisable, generating an input list based on count
|
||||
useEffect((): void => {
|
||||
!isDisabled && keyValueParam.length &&
|
||||
setValues((values): RawParam[] => {
|
||||
if (values.length === count) {
|
||||
return values;
|
||||
}
|
||||
|
||||
while (values.length < count) {
|
||||
const value = getInitValue(registry, keyValueParam[0].type);
|
||||
|
||||
values.push({ isValid: !isUndefined(value), value });
|
||||
}
|
||||
|
||||
return values.slice(0, count);
|
||||
});
|
||||
}, [count, defaultValue, keyValueParam, isDisabled, registry]);
|
||||
|
||||
// when our values has changed, alert upstream
|
||||
useEffect((): void => {
|
||||
const output = new Map();
|
||||
let isValid = true;
|
||||
|
||||
for (const entry of values) {
|
||||
const [key, value] = entry.value as RawParam[];
|
||||
|
||||
if (output.has(key)) {
|
||||
isValid = false;
|
||||
console.error('BTreeMap: Duplicate key ', key);
|
||||
}
|
||||
|
||||
output.set(key, value);
|
||||
isValid = isValid && entry.isValid;
|
||||
}
|
||||
|
||||
onChange && onChange({
|
||||
isValid,
|
||||
value: output
|
||||
});
|
||||
}, [values, onChange]);
|
||||
|
||||
const _rowAdd = useCallback(
|
||||
(): void => setCount((count) => count + 1),
|
||||
[]
|
||||
);
|
||||
const _rowRemove = useCallback(
|
||||
(): void => setCount((count) => count - 1),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Base
|
||||
className={className}
|
||||
isOuter
|
||||
label={label}
|
||||
withLabel={withLabel}
|
||||
>
|
||||
{!isDisabled && (
|
||||
<div className='ui--Param-BTreeMap-buttons'>
|
||||
<Button
|
||||
icon='plus'
|
||||
label={t('Add item')}
|
||||
onClick={_rowAdd}
|
||||
/>
|
||||
<Button
|
||||
icon='minus'
|
||||
isDisabled={values.length === 0}
|
||||
label={t('Remove item')}
|
||||
onClick={_rowRemove}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Params
|
||||
isDisabled={isDisabled}
|
||||
onChange={setValues}
|
||||
overrides={overrides}
|
||||
params={params}
|
||||
registry={registry}
|
||||
values={values}
|
||||
/>
|
||||
</Base>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(BTreeMapParam);
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
// circular dep :(
|
||||
import InputBalance from '@pezkuwi/react-components/InputBalance';
|
||||
import { BN } from '@pezkuwi/util';
|
||||
|
||||
import Bare from './Bare.js';
|
||||
|
||||
function Balance ({ className = '', defaultValue: { value }, isDisabled, isError, label, onChange, onEnter, onEscape, withLabel }: Props): React.ReactElement<Props> {
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const [defaultValue] = useState(() => new BN((value as BN || '0').toString()).toString(10));
|
||||
|
||||
const _onChange = useCallback(
|
||||
(value?: BN): void => {
|
||||
const isValid = !isError && !!value;
|
||||
|
||||
onChange && onChange({
|
||||
isValid,
|
||||
value
|
||||
});
|
||||
setIsValid(isValid);
|
||||
},
|
||||
[isError, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<InputBalance
|
||||
className='full'
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError || !isValid}
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
withEllipsis
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Balance);
|
||||
|
||||
export { Balance };
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Bare ({ children, className = '' }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<div className={`${className} ui--row --relative`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Bare);
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Size } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Labelled } from '@pezkuwi/react-components';
|
||||
|
||||
import Bare from './Bare.js';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
isDisabled?: boolean;
|
||||
isOuter?: boolean;
|
||||
label?: React.ReactNode;
|
||||
labelExtra?: React.ReactNode;
|
||||
size?: Size;
|
||||
withLabel?: boolean;
|
||||
}
|
||||
|
||||
function Base ({ children, className = '', isOuter, label, labelExtra, size = 'full', withLabel }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<Labelled
|
||||
className={size}
|
||||
isOuter
|
||||
label={label}
|
||||
labelExtra={labelExtra}
|
||||
withEllipsis
|
||||
withLabel={withLabel}
|
||||
>
|
||||
{!isOuter && children}
|
||||
</Labelled>
|
||||
{isOuter && children}
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Base);
|
||||
@@ -0,0 +1,160 @@
|
||||
// Copyright 2017-2025 @pezkuwi/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 '@pezkuwi/react-components';
|
||||
import { compactAddLength, hexToU8a, isAscii, isHex, stringToU8a, u8aToHex, u8aToString, u8aToU8a } from '@pezkuwi/util';
|
||||
import { decodeAddress } from '@pezkuwi/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, isHex(value) ? hexToU8a(value) : stringToU8a(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);
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import BasicAccountIdBase from './BasicAccountIdBase.js';
|
||||
|
||||
function BasicAccountId20 (props: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<BasicAccountIdBase
|
||||
{...props}
|
||||
bytesLength={20}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(BasicAccountId20);
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import BasicAccountIdBase from './BasicAccountIdBase.js';
|
||||
|
||||
function BasicAccountId32 (props: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<BasicAccountIdBase
|
||||
{...props}
|
||||
bytesLength={32}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(BasicAccountId32);
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props as BaseProps } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { InputAddressSimple } from '@pezkuwi/react-components';
|
||||
import { isEthereumAddress, validateAddress } from '@pezkuwi/util-crypto';
|
||||
|
||||
import Bare from './Bare.js';
|
||||
|
||||
interface Props extends BaseProps {
|
||||
bytesLength: 20 | 32;
|
||||
}
|
||||
|
||||
function isValidAddress (value: string | null | undefined, isEthereum: boolean): boolean {
|
||||
if (value) {
|
||||
try {
|
||||
return isEthereum
|
||||
? isEthereumAddress(value)
|
||||
: validateAddress(value);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function BasicAccountIdBase (props: Props): React.ReactElement<Props> {
|
||||
const { bytesLength, className = '', defaultValue: { value }, isDisabled, isError, label, onChange } = 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
|
||||
bytesLength={bytesLength}
|
||||
className='full'
|
||||
defaultValue={defaultValue}
|
||||
forceIconType={bytesLength === 20 ? 'ethereum' : 'bizinikiwi'}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
noConvert
|
||||
onChange={_onChange}
|
||||
placeholder={bytesLength === 20 ? '0x1...' : '5...'}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(BasicAccountIdBase);
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useRef, useState } from 'react';
|
||||
|
||||
import { Dropdown } from '@pezkuwi/react-components';
|
||||
import { isBoolean } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Bare from './Bare.js';
|
||||
|
||||
function BoolParam ({ className = '', defaultValue: { value }, isDisabled, isError, label, onChange, withLabel }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [defaultValue] = useState(
|
||||
value instanceof Boolean
|
||||
? value.valueOf()
|
||||
: isBoolean(value)
|
||||
? value
|
||||
: false
|
||||
);
|
||||
|
||||
const options = useRef([
|
||||
{ text: t('No'), value: false },
|
||||
{ text: t('Yes'), value: true }
|
||||
]);
|
||||
|
||||
const _onChange = useCallback(
|
||||
(value: boolean) =>
|
||||
onChange && onChange({
|
||||
isValid: true,
|
||||
value
|
||||
}),
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<Dropdown
|
||||
className='full'
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
options={options.current}
|
||||
withEllipsis
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(BoolParam);
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Toggle } from '@pezkuwi/react-components';
|
||||
import { compactAddLength } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import BaseBytes from './BaseBytes.js';
|
||||
import File from './File.js';
|
||||
|
||||
function Bytes ({ className = '', defaultValue, isDisabled, isError, label, name, onChange, onEnter, onEscape, type, withLabel, withLength = true }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const [isFileDrop, setFileInput] = useState(false);
|
||||
|
||||
const _onChangeFile = useCallback(
|
||||
(value: Uint8Array): void => {
|
||||
const isValid = value.length !== 0;
|
||||
|
||||
onChange && onChange({
|
||||
isValid,
|
||||
value: compactAddLength(value)
|
||||
});
|
||||
|
||||
setIsValid(isValid);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const toggleLabel = !isDisabled && (
|
||||
<Toggle
|
||||
label={t('file upload')}
|
||||
onChange={setFileInput}
|
||||
value={isFileDrop}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`${className} --relative`}>
|
||||
{!isDisabled && isFileDrop
|
||||
? (
|
||||
<File
|
||||
isDisabled={isDisabled}
|
||||
isError={isError || !isValid}
|
||||
label={label}
|
||||
labelExtra={toggleLabel}
|
||||
onChange={_onChangeFile}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<BaseBytes
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
labelExtra={toggleLabel}
|
||||
length={-1}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
type={type}
|
||||
withLabel={withLabel}
|
||||
withLength={withLength}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Bytes);
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Extrinsic } from '@pezkuwi/types/interfaces';
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Static } from '@pezkuwi/react-components';
|
||||
|
||||
import { Call } from '../Named/index.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Bare from './Bare.js';
|
||||
import Unknown from './Unknown.js';
|
||||
|
||||
function CallDisplay (props: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { className = '', defaultValue: { value }, isDisabled, label, withLabel } = props;
|
||||
|
||||
if (!isDisabled) {
|
||||
return (
|
||||
<Unknown {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
const call = value as Extrinsic;
|
||||
const { method, section } = call.registry.findMetaCall(call.callIndex);
|
||||
const callName = `${section}.${method}`;
|
||||
|
||||
return (
|
||||
<Bare>
|
||||
<Static
|
||||
className={`${className} full`}
|
||||
label={label}
|
||||
withLabel={withLabel}
|
||||
>
|
||||
{callName}
|
||||
</Static>
|
||||
<Call
|
||||
callName={callName}
|
||||
labelHash={t('call hash / {{section}}.{{method}}', { replace: { method, section } })}
|
||||
value={call}
|
||||
withHash
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(CallDisplay);
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PalletAllianceCid } from '@pezkuwi/types/lookup';
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Input } from '@pezkuwi/react-components';
|
||||
import { isCodec } from '@pezkuwi/util';
|
||||
|
||||
import { fromIpfsCid, toIpfsCid } from '../util.js';
|
||||
import Bare from './Bare.js';
|
||||
import Static from './Static.js';
|
||||
import Struct from './Struct.js';
|
||||
|
||||
function Cid (props: Props): React.ReactElement<Props> {
|
||||
const { className = '', defaultValue, isDisabled, isError, label, onChange, withLabel } = props;
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
const [ipfsCid] = useState<string | null>(() =>
|
||||
isDisabled && defaultValue && isCodec(defaultValue.value)
|
||||
? toIpfsCid(defaultValue.value as PalletAllianceCid)
|
||||
: null
|
||||
);
|
||||
const [isStruct] = useState<boolean>(() => isDisabled || !defaultValue || isCodec(defaultValue.value));
|
||||
|
||||
const _onChange = useCallback(
|
||||
(_value: string): void => {
|
||||
const value = fromIpfsCid(_value);
|
||||
const isValid = !!value;
|
||||
|
||||
onChange && onChange({
|
||||
isValid,
|
||||
value
|
||||
});
|
||||
setIsValid(isValid);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
if (ipfsCid) {
|
||||
return (
|
||||
<Static {...props}>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label='ipfs'
|
||||
type='text'
|
||||
value={ipfsCid}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Static>
|
||||
);
|
||||
} else if (isStruct) {
|
||||
return <Struct {...props} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled={isDisabled}
|
||||
isError={isError || !isValid}
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
placeholder='IPFS compatible CID'
|
||||
type='text'
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Cid);
|
||||
@@ -0,0 +1,54 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { isWasm } from '@pezkuwi/util';
|
||||
|
||||
import Bytes from './Bytes.js';
|
||||
import BytesFile from './File.js';
|
||||
|
||||
function Code ({ className = '', defaultValue, isDisabled, isError, label, onChange, onEnter, onEscape, registry, type, withLabel }: Props): React.ReactElement<Props> {
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
|
||||
const _onChange = useCallback(
|
||||
(value: Uint8Array): void => {
|
||||
const isValid = isWasm(value);
|
||||
|
||||
onChange && onChange({ isValid, value });
|
||||
setIsValid(isValid);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
if (isDisabled) {
|
||||
return (
|
||||
<Bytes
|
||||
className={className}
|
||||
defaultValue={defaultValue}
|
||||
isError={isError || !isValid}
|
||||
label={label}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
registry={registry}
|
||||
type={type}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<BytesFile
|
||||
className={className}
|
||||
defaultValue={defaultValue}
|
||||
isError={isError || !isValid}
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Code);
|
||||
@@ -0,0 +1,87 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DispatchError } from '@pezkuwi/types/interfaces';
|
||||
import type { Props as BaseProps } from '../types.js';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { Input } from '@pezkuwi/react-components';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Static from './Static.js';
|
||||
import Unknown from './Unknown.js';
|
||||
|
||||
interface Details {
|
||||
details?: string | null;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
interface Props extends BaseProps {
|
||||
childrenPre?: React.ReactNode;
|
||||
}
|
||||
|
||||
function isDispatchError (value?: unknown): value is DispatchError {
|
||||
return !!(value && (
|
||||
(value as DispatchError).isModule ||
|
||||
(value as DispatchError).isToken
|
||||
));
|
||||
}
|
||||
|
||||
function ErrorDisplay (props: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [{ details, type }, setDetails] = useState<Details>({});
|
||||
|
||||
useEffect((): void => {
|
||||
const { value } = props.defaultValue || {};
|
||||
|
||||
if (isDispatchError(value)) {
|
||||
if (value.isModule) {
|
||||
try {
|
||||
const mod = value.asModule;
|
||||
const { docs, name, section } = mod.registry.findMetaError(mod);
|
||||
|
||||
return setDetails({
|
||||
details: docs.join(', '),
|
||||
type: `${section}.${name}`
|
||||
});
|
||||
} catch (error) {
|
||||
// Errors may not actually be exposed, in this case, just return the default representation
|
||||
console.error(error);
|
||||
}
|
||||
} else if (value.isToken) {
|
||||
return setDetails({
|
||||
details: value.asToken.type,
|
||||
type: value.type
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setDetails({ details: null });
|
||||
}, [props.defaultValue]);
|
||||
|
||||
if (!props.isDisabled || !details) {
|
||||
return <Unknown {...props} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Static {...props}>
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('type')}
|
||||
value={type}
|
||||
/>
|
||||
{details && (
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={t('details')}
|
||||
value={details}
|
||||
/>
|
||||
)}
|
||||
</Static>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ErrorDisplay);
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DispatchResult } from '@pezkuwi/types/interfaces';
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { Input } from '@pezkuwi/react-components';
|
||||
|
||||
import DispatchError from './DispatchError.js';
|
||||
import Static from './Static.js';
|
||||
import Unknown from './Unknown.js';
|
||||
|
||||
function isDispatchResultErr (value?: unknown): value is DispatchResult {
|
||||
return !!(value && (value as DispatchResult).isErr);
|
||||
}
|
||||
|
||||
function DispatchResultDisplay (props: Props): React.ReactElement<Props> {
|
||||
const { defaultValue, isDisabled, label } = props;
|
||||
const dispatchError = useMemo(
|
||||
() => defaultValue && isDispatchResultErr(defaultValue.value)
|
||||
? { isValid: true, value: defaultValue.value.asErr }
|
||||
: null,
|
||||
[defaultValue]
|
||||
);
|
||||
|
||||
if (!isDisabled) {
|
||||
return <Unknown {...props} />;
|
||||
} else if (!dispatchError) {
|
||||
return (
|
||||
<Static
|
||||
{...props}
|
||||
defaultValue={{ isValid: true, value: 'Ok' }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DispatchError
|
||||
{...props}
|
||||
childrenPre={
|
||||
<Input
|
||||
className='full'
|
||||
isDisabled
|
||||
label={label}
|
||||
value='Err'
|
||||
/>
|
||||
}
|
||||
defaultValue={dispatchError}
|
||||
label='DispatchError'
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(DispatchResultDisplay);
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Registry, TypeDef } from '@pezkuwi/types/types';
|
||||
import type { ParamDef, Props, RawParam } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Dropdown } from '@pezkuwi/react-components';
|
||||
import { Enum, getTypeDef } from '@pezkuwi/types';
|
||||
import { isObject } from '@pezkuwi/util';
|
||||
|
||||
import Params from '../index.js';
|
||||
import Bare from './Bare.js';
|
||||
|
||||
interface Option {
|
||||
text?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
interface Options {
|
||||
options: Option[];
|
||||
subTypes: TypeDef[];
|
||||
}
|
||||
|
||||
interface Initial {
|
||||
initialEnum: string | undefined | null;
|
||||
initialParams: RawParam[] | undefined | null;
|
||||
}
|
||||
|
||||
function getSubTypes (registry: Registry, type: TypeDef): TypeDef[] {
|
||||
return getTypeDef(
|
||||
registry.createType(type.type as '(u32, u32)').toRawType()
|
||||
).sub as TypeDef[];
|
||||
}
|
||||
|
||||
function getOptions (registry: Registry, type: TypeDef): Options {
|
||||
const subTypes = getSubTypes(registry, type).filter(({ name }) =>
|
||||
!!name &&
|
||||
!name.startsWith('__Unused')
|
||||
);
|
||||
|
||||
return {
|
||||
options: subTypes.map(({ name }): Option => ({
|
||||
text: name,
|
||||
value: name
|
||||
})),
|
||||
subTypes
|
||||
};
|
||||
}
|
||||
|
||||
function getInitial (defaultValue: RawParam, options: Option[]): Initial {
|
||||
if (defaultValue?.value) {
|
||||
if (defaultValue.value instanceof Enum) {
|
||||
return {
|
||||
initialEnum: defaultValue.value.type,
|
||||
initialParams: [{
|
||||
isValid: true,
|
||||
value: defaultValue.value.inner
|
||||
}]
|
||||
};
|
||||
} else if (isObject<Record<string, unknown>>(defaultValue.value)) {
|
||||
const [initialEnum, value] = Object.entries(defaultValue.value)[0];
|
||||
|
||||
// Ensure that the defaultValue is actually in our enum, e.g. it
|
||||
// may start with __Unused<x> values, in which case it would be
|
||||
// invalid
|
||||
if (options.some(({ value }) => value === initialEnum)) {
|
||||
return {
|
||||
initialEnum,
|
||||
initialParams: [{
|
||||
isValid: true,
|
||||
value
|
||||
}]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
initialEnum: options[0]?.value,
|
||||
initialParams: undefined
|
||||
};
|
||||
}
|
||||
|
||||
function getCurrent (registry: Registry, type: TypeDef, defaultValue: RawParam, subTypes: TypeDef[]): ParamDef[] | null {
|
||||
const subs = getSubTypes(registry, type);
|
||||
|
||||
return defaultValue.value instanceof Enum
|
||||
? [{ name: defaultValue.value.type, type: subs[defaultValue.value.index] }]
|
||||
: [{ name: subTypes[0].name, type: subTypes[0] }];
|
||||
}
|
||||
|
||||
function EnumParam (props: Props): React.ReactElement<Props> {
|
||||
const { className = '', defaultValue, isDisabled, isError, label, onChange, overrides, registry, type, withLabel } = props;
|
||||
const [{ options, subTypes }] = useState<Options>(() => getOptions(registry, type));
|
||||
const [current, setCurrent] = useState<ParamDef[] | null>(() => getCurrent(registry, type, defaultValue, subTypes));
|
||||
const [{ initialEnum, initialParams }, setInitial] = useState<Initial>(() => getInitial(defaultValue, options));
|
||||
|
||||
const _onChange = useCallback(
|
||||
(value: string): void => {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newType = subTypes.find(({ name }) => name === value) || null;
|
||||
|
||||
setCurrent(
|
||||
newType
|
||||
? [{ name: newType.name, type: newType }]
|
||||
: null
|
||||
);
|
||||
|
||||
if (newType) {
|
||||
// if the enum changes, we want to discard the original initParams,
|
||||
// these are not applicable anymore, rather use empty defaults
|
||||
setInitial((prev) =>
|
||||
newType.name === prev.initialEnum
|
||||
? prev
|
||||
: { initialEnum: prev.initialEnum, initialParams: null }
|
||||
);
|
||||
}
|
||||
},
|
||||
[isDisabled, subTypes]
|
||||
);
|
||||
|
||||
const _onChangeParam = useCallback(
|
||||
([{ isValid, value }]: RawParam[]): void => {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
current && onChange && onChange({
|
||||
isValid,
|
||||
value: { [current[0].name || 'unknown']: value }
|
||||
});
|
||||
},
|
||||
[current, isDisabled, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<Dropdown
|
||||
className='full'
|
||||
defaultValue={initialEnum}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
options={options}
|
||||
withEllipsis
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
{current && (
|
||||
<Params
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
onChange={_onChangeParam}
|
||||
overrides={overrides}
|
||||
params={current}
|
||||
registry={registry}
|
||||
values={initialParams}
|
||||
/>
|
||||
)}
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(EnumParam);
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { InputFile } from '@pezkuwi/react-components';
|
||||
|
||||
import Bare from './Bare.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
defaultValue?: unknown;
|
||||
isDisabled?: boolean;
|
||||
isError?: boolean;
|
||||
label?: React.ReactNode;
|
||||
labelExtra?: React.ReactNode;
|
||||
onChange?: (contents: Uint8Array) => void;
|
||||
placeholder?: string;
|
||||
withLabel?: boolean;
|
||||
}
|
||||
|
||||
function File ({ className = '', isDisabled, isError = false, label, labelExtra, onChange, placeholder, withLabel }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<InputFile
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
labelExtra={labelExtra}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
withEllipsis
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(File);
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import BaseBytes from './BaseBytes.js';
|
||||
|
||||
function Hash160 ({ className = '', defaultValue, isDisabled, isError, label, name, onChange, onEnter, onEscape, type, withLabel }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<BaseBytes
|
||||
asHex
|
||||
className={className}
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
length={20}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
type={type}
|
||||
withCopy={isDisabled}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Hash160);
|
||||
@@ -0,0 +1,86 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Toggle } from '@pezkuwi/react-components';
|
||||
import { u8aToHex } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import BaseBytes from './BaseBytes.js';
|
||||
import File from './File.js';
|
||||
|
||||
function Hash256 ({ className = '', defaultValue, isDisabled, isError, label, name, onChange, onEnter, onEscape, registry, type, withLabel }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [isFileDrop, setFileInput] = useState(false);
|
||||
const [placeholder, setPlaceholder] = useState<string | null>(null);
|
||||
|
||||
const _onChangeFile = useCallback(
|
||||
(u8a: Uint8Array): void => {
|
||||
const value = registry.hash(u8a);
|
||||
|
||||
setPlaceholder(u8aToHex(value));
|
||||
onChange && onChange({
|
||||
isValid: true,
|
||||
value
|
||||
});
|
||||
},
|
||||
[onChange, registry]
|
||||
);
|
||||
|
||||
const _setFileInput = useCallback(
|
||||
(value: boolean): void => {
|
||||
setPlaceholder(null);
|
||||
setFileInput(value);
|
||||
},
|
||||
[setFileInput, setPlaceholder]
|
||||
);
|
||||
|
||||
const toggleLabel = !isDisabled && (
|
||||
<Toggle
|
||||
label={t('hash a file')}
|
||||
onChange={_setFileInput}
|
||||
value={isFileDrop}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{!isDisabled && isFileDrop
|
||||
? (
|
||||
<File
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
labelExtra={toggleLabel}
|
||||
onChange={_onChangeFile}
|
||||
placeholder={placeholder || undefined}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<BaseBytes
|
||||
asHex
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
labelExtra={toggleLabel}
|
||||
length={32}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
type={type}
|
||||
withCopy={isDisabled}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Hash256);
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import BaseBytes from './BaseBytes.js';
|
||||
|
||||
function Hash512 ({ className = '', defaultValue, isDisabled, isError, label, name, onChange, onEnter, onEscape, type, withLabel }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<BaseBytes
|
||||
asHex
|
||||
className={className}
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
length={64}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
type={type}
|
||||
withCopy={isDisabled}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Hash512);
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Input } from '@pezkuwi/react-components';
|
||||
import { compactAddLength, hexToU8a, u8aConcat } from '@pezkuwi/util';
|
||||
|
||||
import Bare from './Bare.js';
|
||||
|
||||
interface StateParam {
|
||||
isValid: boolean;
|
||||
u8a: Uint8Array;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export function createParam (hex: string | String, ignoreLength = false): StateParam {
|
||||
let u8a;
|
||||
let isValid = false;
|
||||
|
||||
try {
|
||||
u8a = hexToU8a(hex.toString());
|
||||
|
||||
isValid = ignoreLength || u8a.length !== 0;
|
||||
} catch {
|
||||
u8a = new Uint8Array([]);
|
||||
}
|
||||
|
||||
return {
|
||||
isValid,
|
||||
u8a: compactAddLength(u8a)
|
||||
};
|
||||
}
|
||||
|
||||
function KeyValue ({ className = '', isDisabled, label, onChange, onEnter, withLabel }: Props): React.ReactElement<Props> {
|
||||
const [, setIsValid] = useState(false);
|
||||
const [key, setKey] = useState<StateParam>(() => ({ isValid: false, u8a: new Uint8Array([]) }));
|
||||
const [value, setValue] = useState<StateParam>(() => ({ isValid: false, u8a: new Uint8Array([]) }));
|
||||
|
||||
useEffect((): void => {
|
||||
const isValid = key.isValid && value.isValid;
|
||||
|
||||
onChange && onChange({
|
||||
isValid,
|
||||
value: u8aConcat(
|
||||
key.u8a,
|
||||
value.u8a
|
||||
)
|
||||
});
|
||||
setIsValid(isValid);
|
||||
}, [key, onChange, value]);
|
||||
|
||||
const _onChangeKey = useCallback(
|
||||
(key: string): void => setKey(createParam(key)),
|
||||
[]
|
||||
);
|
||||
const _onChangeValue = useCallback(
|
||||
(value: string): void => setValue(createParam(value, true)),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<Input
|
||||
className='medium'
|
||||
isDisabled={isDisabled}
|
||||
isError={!key.isValid}
|
||||
label={label}
|
||||
onChange={_onChangeKey}
|
||||
placeholder='0x...'
|
||||
type='text'
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
<Input
|
||||
className='medium'
|
||||
isDisabled={isDisabled}
|
||||
isError={!value.isValid}
|
||||
onChange={_onChangeValue}
|
||||
onEnter={onEnter}
|
||||
placeholder='0x...'
|
||||
type='text'
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(KeyValue);
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Vec } from '@pezkuwi/types';
|
||||
import type { KeyValue as Pair } from '@pezkuwi/types/interfaces';
|
||||
import type { Props, RawParam } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { assert, isHex, u8aToHex, u8aToString } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Base from './Base.js';
|
||||
import Bytes from './Bytes.js';
|
||||
import File from './File.js';
|
||||
import { createParam } from './KeyValue.js';
|
||||
|
||||
interface Parsed {
|
||||
isValid: boolean;
|
||||
value: [Uint8Array, Uint8Array][];
|
||||
}
|
||||
|
||||
const BYTES_TYPE = {
|
||||
info: 0,
|
||||
type: 'Bytes'
|
||||
};
|
||||
|
||||
function parseFile (raw: Uint8Array): Parsed {
|
||||
const json = JSON.parse(u8aToString(raw)) as Record<string, string>;
|
||||
const keys = Object.keys(json);
|
||||
let isValid = keys.length !== 0;
|
||||
const value = keys.map((key): [Uint8Array, Uint8Array] => {
|
||||
const value = json[key];
|
||||
|
||||
assert(isHex(key) && isHex(value), `Non-hex key/value pair found in ${key.toString()} => ${value.toString()}`);
|
||||
|
||||
const encKey = createParam(key);
|
||||
const encValue = createParam(value, true);
|
||||
|
||||
isValid = isValid && encKey.isValid && encValue.isValid;
|
||||
|
||||
return [encKey.u8a, encValue.u8a];
|
||||
});
|
||||
|
||||
return {
|
||||
isValid,
|
||||
value
|
||||
};
|
||||
}
|
||||
|
||||
function KeyValueArray ({ className = '', defaultValue, isDisabled, isError, label, onChange, onEnter, onEscape, registry, withLabel }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [placeholder, setPlaceholder] = useState<string>(t('click to select or drag and drop JSON key/value (hex-encoded) file'));
|
||||
|
||||
const _onChange = useCallback(
|
||||
(raw: Uint8Array): void => {
|
||||
let encoded: Parsed = { isValid: false, value: [] };
|
||||
|
||||
try {
|
||||
encoded = parseFile(raw);
|
||||
|
||||
setPlaceholder(t('{{count}} key/value pairs encoded for submission', {
|
||||
replace: {
|
||||
count: encoded.value.length
|
||||
}
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error converting json k/v', error);
|
||||
|
||||
setPlaceholder(t('click to select or drag and drop JSON key/value (hex-encoded) file'));
|
||||
}
|
||||
|
||||
onChange && onChange(encoded);
|
||||
},
|
||||
[onChange, t]
|
||||
);
|
||||
|
||||
if (isDisabled) {
|
||||
const pairs = defaultValue.value as Vec<Pair>;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Base
|
||||
className={className}
|
||||
label={label}
|
||||
>
|
||||
<div />
|
||||
</Base>
|
||||
<div className='ui--Params'>
|
||||
{pairs.map(([key, value]): React.ReactNode => {
|
||||
const keyHex = u8aToHex(key.toU8a(true));
|
||||
|
||||
return (
|
||||
<Bytes
|
||||
defaultValue={{ value } as unknown as RawParam}
|
||||
isDisabled
|
||||
key={keyHex}
|
||||
label={keyHex}
|
||||
name={keyHex}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
registry={registry}
|
||||
type={BYTES_TYPE}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<File
|
||||
className={className}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
placeholder={placeholder}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(KeyValueArray);
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props, RawParamOnChangeValue } from '../types.js';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Static } from '@pezkuwi/react-components';
|
||||
|
||||
import Amount from './Amount.js';
|
||||
|
||||
function Moment ({ className = '', defaultValue, isDisabled, isError, label, onChange, onEnter, onEscape, registry, type, withLabel }: Props): React.ReactElement<Props> {
|
||||
const _onChange = useCallback(
|
||||
(value: RawParamOnChangeValue) =>
|
||||
onChange && onChange(value),
|
||||
[onChange]
|
||||
);
|
||||
|
||||
if (isDisabled) {
|
||||
return (
|
||||
<Static
|
||||
className={className}
|
||||
defaultValue={
|
||||
(defaultValue?.value)
|
||||
? (defaultValue.value as string).toString()
|
||||
: ''
|
||||
}
|
||||
isError={isError}
|
||||
label={label}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Amount
|
||||
className={className}
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
registry={registry}
|
||||
type={type}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Moment);
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
function Null ({ onChange }: Props): React.ReactElement<Props> | null {
|
||||
useEffect((): void => {
|
||||
onChange && onChange({
|
||||
isValid: true,
|
||||
value: null
|
||||
});
|
||||
}, [onChange]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default React.memo(Null);
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Bytes } from '@pezkuwi/types';
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import CallDisplay from './Call.js';
|
||||
import Unknown from './Unknown.js';
|
||||
|
||||
function OpaqueCall (props: Props): React.ReactElement<Props> {
|
||||
if (!props.isDisabled) {
|
||||
return (
|
||||
<Unknown {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
const value = props.registry.createType('Call', (props.defaultValue.value as Bytes).toHex());
|
||||
|
||||
return (
|
||||
<CallDisplay
|
||||
{...props}
|
||||
defaultValue={{ isValid: true, value }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(OpaqueCall);
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Codec, TypeDef } from '@pezkuwi/types/types';
|
||||
import type { ParamDef, Props, RawParamOnChangeValue } from '../types.js';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Toggle } from '@pezkuwi/react-components';
|
||||
import { Option } from '@pezkuwi/types';
|
||||
import { isU8a, u8aConcat } from '@pezkuwi/util';
|
||||
|
||||
import Holder from '../Holder.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Base from './Base.js';
|
||||
import Param from './index.js';
|
||||
import Static from './Static.js';
|
||||
import useParamDefs from './useParamDefs.js';
|
||||
import { getParams } from './Vector.js';
|
||||
|
||||
const DEF_VALUE = { isValid: true, value: undefined };
|
||||
const OPT_PREFIX = new Uint8Array([1]);
|
||||
|
||||
function OptionDisplay ({ className = '', defaultValue: _defaultValue, isDisabled, label, onChange, onEnter, onEscape, registry, type, withLabel }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const inputParams = useParamDefs(registry, type);
|
||||
const [params] = useState<ParamDef[]>(() => getParams(inputParams, [], (inputParams[0].length || 1)));
|
||||
|
||||
const { sub, withOptionActive } = type;
|
||||
|
||||
const [isActive, setIsActive] = useState(() => withOptionActive || !!(_defaultValue && _defaultValue.value instanceof Option && _defaultValue.value.isSome) || false);
|
||||
|
||||
const [defaultValue] = useState(
|
||||
() => isActive || isDisabled
|
||||
? _defaultValue && (
|
||||
_defaultValue.value instanceof Option && _defaultValue.value.isSome
|
||||
? { isValid: _defaultValue.isValid, value: (_defaultValue.value as Option<Codec>).unwrap() }
|
||||
: DEF_VALUE
|
||||
)
|
||||
: DEF_VALUE
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
!isActive && onChange && onChange({
|
||||
isValid: true,
|
||||
value: null
|
||||
});
|
||||
}, [isActive, onChange]);
|
||||
|
||||
const _onChange = useCallback(
|
||||
(value: RawParamOnChangeValue) =>
|
||||
onChange && onChange(
|
||||
value.isValid && isU8a(value.value) && !withOptionActive && isActive
|
||||
? { isValid: true, value: u8aConcat(OPT_PREFIX, value.value) }
|
||||
: value
|
||||
),
|
||||
[isActive, onChange, withOptionActive]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={`${className} --relative`}>
|
||||
<Base
|
||||
className='--relative'
|
||||
label={label}
|
||||
labelExtra={!isDisabled && (
|
||||
<Toggle
|
||||
label={t('include option')}
|
||||
onChange={setIsActive}
|
||||
value={isActive}
|
||||
/>
|
||||
)}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
<Holder>
|
||||
<div className='ui--Params-Content'>
|
||||
{isActive
|
||||
? (
|
||||
<Param
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled || !isActive}
|
||||
isOptional={!isActive && !isDisabled}
|
||||
onChange={_onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
registry={registry}
|
||||
type={(sub ?? params.at(0)?.type) as TypeDef}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Static
|
||||
defaultValue={DEF_VALUE}
|
||||
isOptional
|
||||
label='None'
|
||||
/>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</Holder>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(OptionDisplay);
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Codec } from '@pezkuwi/types/types';
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Input } from '@pezkuwi/react-components';
|
||||
|
||||
import Bare from './Bare.js';
|
||||
|
||||
function Raw ({ className = '', defaultValue: { value }, isDisabled, isError, label, onChange, onEnter, onEscape, withLabel }: Props): React.ReactElement<Props> {
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
|
||||
const _onChange = useCallback(
|
||||
(value: string): void => {
|
||||
const isValid = value.length !== 0;
|
||||
|
||||
onChange && onChange({
|
||||
isValid,
|
||||
value
|
||||
});
|
||||
setIsValid(isValid);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const defaultValue = value
|
||||
? ((value as { toHex?: () => unknown }).toHex ? (value as Codec).toHex() : value)
|
||||
: '';
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<Input
|
||||
className='full'
|
||||
defaultValue={defaultValue as string}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError || !isValid}
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
placeholder='Hex data'
|
||||
type='text'
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Raw);
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Codec } from '@pezkuwi/types/types';
|
||||
import type { RawParam } from '../types.js';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { Static, styled } from '@pezkuwi/react-components';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import { toHumanJson } from '../valueToText.js';
|
||||
import Bare from './Bare.js';
|
||||
|
||||
interface Props {
|
||||
asHex?: boolean;
|
||||
children?: React.ReactNode;
|
||||
childrenPre?: React.ReactNode;
|
||||
className?: string;
|
||||
defaultValue: RawParam;
|
||||
isOptional?: boolean;
|
||||
label?: React.ReactNode;
|
||||
withLabel?: boolean;
|
||||
}
|
||||
|
||||
function StaticParam ({ asHex, children, childrenPre, className = '', defaultValue, isOptional, label }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const value = useMemo(
|
||||
() => !!defaultValue?.value && (
|
||||
asHex
|
||||
? (defaultValue.value as Codec).toHex()
|
||||
: toHumanJson(
|
||||
(defaultValue.value as Codec).toHuman
|
||||
? (defaultValue.value as Codec).toHuman()
|
||||
: defaultValue.value
|
||||
)
|
||||
),
|
||||
[asHex, defaultValue]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledBare className={className}>
|
||||
{childrenPre}
|
||||
<Static
|
||||
className='full'
|
||||
label={label}
|
||||
value={<pre>{value || (isOptional ? <> </> : t('<empty>'))}</pre>}
|
||||
/>
|
||||
{children}
|
||||
</StyledBare>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledBare = styled(Bare)`
|
||||
pre {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ui--Static {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(StaticParam);
|
||||
@@ -0,0 +1,63 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props, RawParam } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Struct } from '@pezkuwi/types';
|
||||
import { isCodec } from '@pezkuwi/util';
|
||||
|
||||
import Params from '../index.js';
|
||||
import Base from './Base.js';
|
||||
import useParamDefs from './useParamDefs.js';
|
||||
|
||||
function extractValues ({ isValid, value }: RawParam): RawParam[] | undefined {
|
||||
return (isValid && isCodec(value) && value instanceof Struct)
|
||||
? value.toArray().map((value) => ({ isValid: true, value }))
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function StructParam (props: Props): React.ReactElement<Props> {
|
||||
const params = useParamDefs(props.registry, props.type);
|
||||
const { className = '', defaultValue, isDisabled, label, onChange, overrides, withLabel } = props;
|
||||
const [values] = useState(() => extractValues(defaultValue));
|
||||
|
||||
const _onChangeParams = useCallback(
|
||||
(values: RawParam[]): void => {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange && onChange({
|
||||
isValid: values.reduce((result: boolean, { isValid }) => result && isValid, true),
|
||||
value: params.reduce((value: Record<string, unknown>, { name }, index): Record<string, unknown> => {
|
||||
value[name || 'unknown'] = values[index].value;
|
||||
|
||||
return value;
|
||||
}, {})
|
||||
});
|
||||
},
|
||||
[isDisabled, params, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='ui--Params-Struct'>
|
||||
<Base
|
||||
className={className}
|
||||
label={label}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
<Params
|
||||
isDisabled={isDisabled}
|
||||
onChange={_onChangeParams}
|
||||
overrides={overrides}
|
||||
params={params}
|
||||
registry={props.registry}
|
||||
values={values}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(StructParam);
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Input } from '@pezkuwi/react-components';
|
||||
|
||||
import Bare from './Bare.js';
|
||||
|
||||
function Text ({ className = '', defaultValue: { value }, isDisabled, isError, label, onChange, onEnter, onEscape, withLabel }: Props): React.ReactElement<Props> {
|
||||
const [isValid, setIsValid] = useState(false);
|
||||
|
||||
const _onChange = useCallback(
|
||||
(value: string): void => {
|
||||
const isValid = value.length !== 0;
|
||||
|
||||
onChange && onChange({
|
||||
isValid,
|
||||
value
|
||||
});
|
||||
setIsValid(isValid);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const defaultValue = (value as string || '').toString();
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<Input
|
||||
className='full'
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError || !isValid}
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
placeholder='<any string>'
|
||||
type='text'
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Text);
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props, RawParam } from '../types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { Tuple } from '@pezkuwi/types';
|
||||
|
||||
import Params from '../index.js';
|
||||
import Base from './Base.js';
|
||||
import useParamDefs from './useParamDefs.js';
|
||||
|
||||
function getInitialValues ({ value }: RawParam): RawParam[] {
|
||||
return value instanceof Tuple
|
||||
? value.map((value: unknown) => ({ isValid: true, value }))
|
||||
: value as RawParam[];
|
||||
}
|
||||
|
||||
function TupleDisplay (props: Props): React.ReactElement<Props> {
|
||||
const { className = '', defaultValue, isDisabled, label, onChange, overrides, registry, type, withLabel } = props;
|
||||
const params = useParamDefs(registry, type);
|
||||
const [values] = useState<RawParam[]>(() => getInitialValues(defaultValue));
|
||||
|
||||
const _onChangeParams = useCallback(
|
||||
(values: RawParam[]): void => {
|
||||
if (isDisabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange && onChange({
|
||||
isValid: values.reduce<boolean>((result, { isValid }) => result && isValid, true),
|
||||
value: values.map(({ value }) => value)
|
||||
});
|
||||
},
|
||||
[isDisabled, onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='ui--Params-Tuple'>
|
||||
<Base
|
||||
className={className}
|
||||
label={label}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
<Params
|
||||
isDisabled={isDisabled}
|
||||
onChange={_onChangeParams}
|
||||
overrides={overrides}
|
||||
params={params}
|
||||
registry={registry}
|
||||
values={values}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(TupleDisplay);
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props as BaseProps } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import BaseBytes from './BaseBytes.js';
|
||||
import Static from './Static.js';
|
||||
|
||||
interface Props extends BaseProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
function Unknown (props: Props): React.ReactElement<Props> {
|
||||
const { className = '', defaultValue, isDisabled, isError, label, name, onChange, onEnter, onEscape, type } = props;
|
||||
|
||||
if (isDisabled) {
|
||||
return <Static {...props} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseBytes
|
||||
asHex
|
||||
className={className}
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
length={-1}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
type={type}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Unknown);
|
||||
@@ -0,0 +1,136 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ParamDef, Props, RawParam } from '../types.js';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Button } from '@pezkuwi/react-components';
|
||||
import { isUndefined } from '@pezkuwi/util';
|
||||
|
||||
import Params from '../index.js';
|
||||
import getInitValue from '../initValue.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Base from './Base.js';
|
||||
import useParamDefs from './useParamDefs.js';
|
||||
|
||||
function getParam ([{ name, type }]: ParamDef[], index: number): ParamDef {
|
||||
return {
|
||||
name: `${index}: ${name || type.type}`,
|
||||
type
|
||||
};
|
||||
}
|
||||
|
||||
export function getParams (inputParams: ParamDef[], prev: ParamDef[], max: number): ParamDef[] {
|
||||
if (prev.length === max) {
|
||||
return prev;
|
||||
}
|
||||
|
||||
const params: ParamDef[] = [];
|
||||
|
||||
for (let index = 0; index < max; index++) {
|
||||
params.push(getParam(inputParams, index));
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
export function getValues ({ value }: RawParam): RawParam[] {
|
||||
if (value instanceof Set) {
|
||||
value = [...value.values()];
|
||||
}
|
||||
|
||||
return Array.isArray(value)
|
||||
? value.map((value: RawParam) =>
|
||||
isUndefined(value) || isUndefined(value.isValid)
|
||||
? { isValid: !isUndefined(value), value }
|
||||
: value
|
||||
)
|
||||
: [];
|
||||
}
|
||||
|
||||
function Vector ({ className = '', defaultValue, isDisabled = false, label, onChange, overrides, registry, type, withLabel }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const inputParams = useParamDefs(registry, type);
|
||||
const [values, setValues] = useState<RawParam[]>(() => getValues(defaultValue));
|
||||
const [count, setCount] = useState(() => values.length);
|
||||
const [params, setParams] = useState<ParamDef[]>(() => getParams(inputParams, [], count));
|
||||
|
||||
// build up the list of parameters we are using
|
||||
useEffect((): void => {
|
||||
inputParams.length &&
|
||||
setParams((prev) =>
|
||||
getParams(inputParams, prev, isDisabled ? values.length : count)
|
||||
);
|
||||
}, [count, values, isDisabled, inputParams]);
|
||||
|
||||
// when !isDisable, generating an input list based on count
|
||||
useEffect((): void => {
|
||||
!isDisabled && inputParams.length &&
|
||||
setValues((values): RawParam[] => {
|
||||
if (values.length === count) {
|
||||
return values;
|
||||
}
|
||||
|
||||
while (values.length < count) {
|
||||
const value = getInitValue(registry, inputParams[0].type);
|
||||
|
||||
values.push({ isValid: !isUndefined(value), value });
|
||||
}
|
||||
|
||||
return values.slice(0, count);
|
||||
});
|
||||
}, [count, defaultValue, inputParams, isDisabled, registry]);
|
||||
|
||||
// when our values has changed, alert upstream
|
||||
useEffect((): void => {
|
||||
onChange && onChange({
|
||||
isValid: values.reduce<boolean>((result, { isValid }) => result && isValid, true),
|
||||
value: values.map(({ value }) => value)
|
||||
});
|
||||
}, [values, onChange]);
|
||||
|
||||
const _rowAdd = useCallback(
|
||||
(): void => setCount((count) => count + 1),
|
||||
[]
|
||||
);
|
||||
const _rowRemove = useCallback(
|
||||
(): void => setCount((count) => count - 1),
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Base
|
||||
className={className}
|
||||
isOuter
|
||||
label={label}
|
||||
withLabel={withLabel}
|
||||
>
|
||||
{!isDisabled && (
|
||||
<div className='ui--Param-Vector-buttons'>
|
||||
<Button
|
||||
icon='plus'
|
||||
label={t('Add item')}
|
||||
onClick={_rowAdd}
|
||||
/>
|
||||
<Button
|
||||
icon='minus'
|
||||
isDisabled={values.length === 0}
|
||||
label={t('Remove item')}
|
||||
onClick={_rowRemove}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Params
|
||||
isDisabled={isDisabled}
|
||||
onChange={setValues}
|
||||
overrides={overrides}
|
||||
params={params}
|
||||
registry={registry}
|
||||
values={values}
|
||||
/>
|
||||
</Base>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Vector);
|
||||
@@ -0,0 +1,75 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ParamDef, Props, RawParam } from '../types.js';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { VecFixed } from '@pezkuwi/types';
|
||||
import { isUndefined } from '@pezkuwi/util';
|
||||
|
||||
import Params from '../index.js';
|
||||
import getInitValue from '../initValue.js';
|
||||
import Base from './Base.js';
|
||||
import useParamDefs from './useParamDefs.js';
|
||||
import { getParams, getValues } from './Vector.js';
|
||||
|
||||
function getInitialValues (defaultValue: RawParam): RawParam[] {
|
||||
return defaultValue.value instanceof VecFixed
|
||||
? defaultValue.value.map((value) => ({ isValid: true, value: value as unknown }))
|
||||
: getValues(defaultValue);
|
||||
}
|
||||
|
||||
function VectorFixed ({ className = '', defaultValue, isDisabled = false, label, onChange, overrides, registry, type, withLabel }: Props): React.ReactElement<Props> | null {
|
||||
const inputParams = useParamDefs(registry, type);
|
||||
const [params] = useState<ParamDef[]>(() => getParams(inputParams, [], (inputParams[0].length || 1)));
|
||||
const [values, setValues] = useState<RawParam[]>(() => getInitialValues(defaultValue));
|
||||
|
||||
// when !isDisable, generating an input list based on count
|
||||
useEffect((): void => {
|
||||
!isDisabled && inputParams.length &&
|
||||
setValues((values): RawParam[] => {
|
||||
const count = (inputParams[0].length || 1);
|
||||
|
||||
if (values.length === count) {
|
||||
return values;
|
||||
}
|
||||
|
||||
while (values.length < count) {
|
||||
const value = getInitValue(registry, inputParams[0].type);
|
||||
|
||||
values.push({ isValid: !isUndefined(value), value });
|
||||
}
|
||||
|
||||
return values.slice(0, count);
|
||||
});
|
||||
}, [inputParams, isDisabled, registry]);
|
||||
|
||||
// when our values has changed, alert upstream
|
||||
useEffect((): void => {
|
||||
onChange && onChange({
|
||||
isValid: values.reduce((result: boolean, { isValid }) => result && isValid, true),
|
||||
value: values.map(({ value }) => value)
|
||||
});
|
||||
}, [values, onChange]);
|
||||
|
||||
return (
|
||||
<Base
|
||||
className={className}
|
||||
isOuter
|
||||
label={label}
|
||||
withLabel={withLabel}
|
||||
>
|
||||
<Params
|
||||
isDisabled={isDisabled}
|
||||
onChange={setValues}
|
||||
overrides={overrides}
|
||||
params={params}
|
||||
registry={registry}
|
||||
values={values}
|
||||
/>
|
||||
</Base>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(VectorFixed);
|
||||
@@ -0,0 +1,101 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useMemo, useRef } from 'react';
|
||||
|
||||
import { Dropdown } from '@pezkuwi/react-components';
|
||||
import { GenericVote } from '@pezkuwi/types';
|
||||
import { isBn, isNumber } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Bare from './Bare.js';
|
||||
|
||||
const AYE_MASK = 0b10000000;
|
||||
|
||||
// In this approach, it will avoid using additional local states and instead rely directly on the parent-provided values and methods.
|
||||
function Vote ({ className = '', defaultValue: { value }, isDisabled, isError, onChange, withLabel }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// Derive aye and conviction from the value prop
|
||||
const { aye, conviction } = useMemo(() => {
|
||||
// Logic sucks here, but to be honest, can not find other way to do it
|
||||
const resolvedAye = value instanceof GenericVote
|
||||
? value.isAye
|
||||
: isBn(value)
|
||||
? !!(value.toNumber() & AYE_MASK)
|
||||
: isNumber(value)
|
||||
? !!(value & AYE_MASK)
|
||||
: typeof value === 'object' && value !== null && 'aye' in value
|
||||
? !!value.aye
|
||||
: true;
|
||||
|
||||
const resolvedConviction = value instanceof GenericVote
|
||||
? value.conviction.index
|
||||
: typeof value === 'object' && value !== null && 'conviction' in value
|
||||
? Number(value.conviction)
|
||||
: 0;
|
||||
|
||||
return {
|
||||
aye: resolvedAye,
|
||||
conviction: resolvedConviction
|
||||
};
|
||||
}, [value]);
|
||||
|
||||
const onChangeVote = useCallback(
|
||||
(newAye: boolean) => {
|
||||
onChange?.({ isValid: true, value: { aye: newAye, conviction } });
|
||||
},
|
||||
[conviction, onChange]
|
||||
);
|
||||
|
||||
const onChangeConviction = useCallback(
|
||||
(newConviction: number) => {
|
||||
onChange?.({ isValid: true, value: { aye, conviction: newConviction } });
|
||||
},
|
||||
[aye, onChange]
|
||||
);
|
||||
|
||||
const optAyeRef = useRef([
|
||||
{ text: t('Nay'), value: false },
|
||||
{ text: t('Aye'), value: true }
|
||||
]);
|
||||
|
||||
const optConvRef = useRef([
|
||||
{ text: t('None'), value: 0 },
|
||||
{ text: t('Locked1x'), value: 1 },
|
||||
{ text: t('Locked2x'), value: 2 },
|
||||
{ text: t('Locked3x'), value: 3 },
|
||||
{ text: t('Locked4x'), value: 4 },
|
||||
{ text: t('Locked5x'), value: 5 },
|
||||
{ text: t('Locked6x'), value: 6 }
|
||||
]);
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<Dropdown
|
||||
className='full'
|
||||
defaultValue={aye}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={t('aye: bool')}
|
||||
onChange={onChangeVote}
|
||||
options={optAyeRef.current}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
<Dropdown
|
||||
className='full'
|
||||
defaultValue={conviction}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={t('conviction: Conviction')}
|
||||
onChange={onChangeConviction}
|
||||
options={optConvRef.current}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Vote);
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { Dropdown } from '@pezkuwi/react-components';
|
||||
import { bnToBn, isFunction } from '@pezkuwi/util';
|
||||
|
||||
import Bare from './Bare.js';
|
||||
|
||||
type TextMap = Record<number, string>;
|
||||
|
||||
const options = [
|
||||
{ text: 'Super majority approval', value: 0 },
|
||||
{ text: 'Super majority rejection', value: 1 },
|
||||
{ text: 'Simple majority', value: 2 }
|
||||
];
|
||||
|
||||
export const textMap = options.reduce((textMap, { text, value }): TextMap => {
|
||||
textMap[value] = text;
|
||||
|
||||
return textMap;
|
||||
}, {} as unknown as TextMap);
|
||||
|
||||
function VoteThresholdParam ({ className = '', defaultValue: { value }, isDisabled, isError, label, onChange, withLabel }: Props): React.ReactElement<Props> {
|
||||
const _onChange = useCallback(
|
||||
(value: number) =>
|
||||
onChange && onChange({
|
||||
isValid: true,
|
||||
value
|
||||
}),
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const defaultValue = useMemo(
|
||||
// eslint-disable-next-line @typescript-eslint/unbound-method
|
||||
() => isFunction((value as BN).toNumber)
|
||||
? (value as BN).toNumber()
|
||||
: bnToBn(value as number).toNumber(),
|
||||
[value]
|
||||
);
|
||||
|
||||
return (
|
||||
<Bare className={className}>
|
||||
<Dropdown
|
||||
className='full'
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
label={label}
|
||||
onChange={_onChange}
|
||||
options={options}
|
||||
withLabel={withLabel}
|
||||
/>
|
||||
</Bare>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(VoteThresholdParam);
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type React from 'react';
|
||||
import type { Registry, TypeDef } from '@pezkuwi/types/types';
|
||||
import type { ComponentMap, Props } from '../types.js';
|
||||
|
||||
import { getTypeDef } from '@pezkuwi/types';
|
||||
import { TypeDefInfo } from '@pezkuwi/types/types';
|
||||
import { isBn } from '@pezkuwi/util';
|
||||
|
||||
import Account from './Account.js';
|
||||
import Amount from './Amount.js';
|
||||
import Balance from './Balance.js';
|
||||
import AccountId20 from './BasicAccountId20.js';
|
||||
import AccountId32 from './BasicAccountId32.js';
|
||||
import Bool from './Bool.js';
|
||||
import BTreeMap from './BTreeMap.js';
|
||||
import Bytes from './Bytes.js';
|
||||
import Call from './Call.js';
|
||||
import Cid from './Cid.js';
|
||||
import Code from './Code.js';
|
||||
import DispatchError from './DispatchError.js';
|
||||
import DispatchResult from './DispatchResult.js';
|
||||
import Enum from './Enum.js';
|
||||
import Hash160 from './Hash160.js';
|
||||
import Hash256 from './Hash256.js';
|
||||
import Hash512 from './Hash512.js';
|
||||
import KeyValue from './KeyValue.js';
|
||||
import KeyValueArray from './KeyValueArray.js';
|
||||
import Moment from './Moment.js';
|
||||
import Null from './Null.js';
|
||||
import OpaqueCall from './OpaqueCall.js';
|
||||
import Option from './Option.js';
|
||||
import Raw from './Raw.js';
|
||||
import Struct from './Struct.js';
|
||||
import Text from './Text.js';
|
||||
import Tuple from './Tuple.js';
|
||||
import Unknown from './Unknown.js';
|
||||
import Vector from './Vector.js';
|
||||
import VectorFixed from './VectorFixed.js';
|
||||
import Vote from './Vote.js';
|
||||
import VoteThreshold from './VoteThreshold.js';
|
||||
|
||||
interface TypeToComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
c: React.ComponentType<any>;
|
||||
t: string[];
|
||||
}
|
||||
|
||||
const SPECIAL_TYPES = ['AccountId', 'AccountId20', 'AccountId32', 'AccountIndex', 'Address', 'Balance', 'BalanceOf', 'Vec<KeyValue>'];
|
||||
|
||||
const DISPATCH_ERROR = ['DispatchError', 'SpRuntimeDispatchError'];
|
||||
|
||||
const componentDef: TypeToComponent[] = [
|
||||
{ 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'] },
|
||||
{ c: Bytes, t: ['Bytes', 'Vec<u8>'] },
|
||||
{ c: Call, t: ['Call', 'Proposal', 'RuntimeCall'] },
|
||||
{ c: Cid, t: ['PalletAllianceCid'] },
|
||||
{ c: Code, t: ['Code'] },
|
||||
{ c: DispatchError, t: DISPATCH_ERROR },
|
||||
{ c: DispatchResult, t: ['DispatchResult', 'Result<Null, SpRuntimeDispatchError>'] },
|
||||
{ c: Raw, t: ['Raw', 'RuntimeSessionKeys', 'Keys'] },
|
||||
{ c: Enum, t: ['Enum'] },
|
||||
{ c: Hash256, t: ['Hash', 'H256'] },
|
||||
{ c: Hash160, t: ['H160'] },
|
||||
{ c: Hash512, t: ['H512'] },
|
||||
{ c: KeyValue, t: ['KeyValue'] },
|
||||
{ c: KeyValueArray, t: ['Vec<KeyValue>'] },
|
||||
{ c: Moment, t: ['Moment', 'MomentOf'] },
|
||||
{ c: Null, t: ['Null'] },
|
||||
{ c: OpaqueCall, t: ['OpaqueCall'] },
|
||||
{ c: Option, t: ['Option'] },
|
||||
{ c: Text, t: ['String', 'Text'] },
|
||||
{ c: Struct, t: ['Struct'] },
|
||||
{ c: Tuple, t: ['Tuple'] },
|
||||
{ c: BTreeMap, t: ['BTreeMap'] },
|
||||
{ c: Vector, t: ['Vec', 'BTreeSet'] },
|
||||
{ c: VectorFixed, t: ['VecFixed'] },
|
||||
{ c: Vote, t: ['Vote'] },
|
||||
{ c: VoteThreshold, t: ['VoteThreshold'] },
|
||||
{ c: Unknown, t: ['Unknown'] }
|
||||
];
|
||||
|
||||
const components: ComponentMap = componentDef.reduce((components, { c, t }): ComponentMap => {
|
||||
t.forEach((type): void => {
|
||||
components[type] = c;
|
||||
});
|
||||
|
||||
return components;
|
||||
}, {} as unknown as ComponentMap);
|
||||
|
||||
const warnList: string[] = [];
|
||||
|
||||
function fromDef ({ displayName, info, lookupName, sub, type }: TypeDef): string {
|
||||
if (displayName && SPECIAL_TYPES.includes(displayName)) {
|
||||
return displayName;
|
||||
} else if (type.endsWith('RuntimeSessionKeys')) {
|
||||
return 'RuntimeSessionKeys';
|
||||
}
|
||||
|
||||
const typeValue = lookupName || type;
|
||||
|
||||
switch (info) {
|
||||
case TypeDefInfo.Compact:
|
||||
return (sub as TypeDef).type;
|
||||
|
||||
case TypeDefInfo.Option:
|
||||
return 'Option';
|
||||
|
||||
case TypeDefInfo.Enum:
|
||||
return 'Enum';
|
||||
|
||||
case TypeDefInfo.Result: {
|
||||
const [, errSub] = (sub as TypeDef[]);
|
||||
|
||||
return DISPATCH_ERROR.includes(errSub.lookupName || errSub.type)
|
||||
? 'DispatchResult'
|
||||
: typeValue;
|
||||
}
|
||||
|
||||
case TypeDefInfo.Struct:
|
||||
return 'Struct';
|
||||
|
||||
case TypeDefInfo.BTreeSet:
|
||||
return 'BTreeSet';
|
||||
|
||||
case TypeDefInfo.BTreeMap:
|
||||
return 'BTreeMap';
|
||||
|
||||
case TypeDefInfo.Tuple:
|
||||
return components[type] === Account
|
||||
? type
|
||||
: 'Tuple';
|
||||
|
||||
case TypeDefInfo.Vec:
|
||||
return type === 'Vec<u8>'
|
||||
? 'Bytes'
|
||||
: ['Vec<KeyValue>'].includes(type)
|
||||
? 'Vec<KeyValue>'
|
||||
: 'Vec';
|
||||
|
||||
case TypeDefInfo.VecFixed:
|
||||
return (sub as TypeDef).type === 'u8'
|
||||
? type
|
||||
: 'VecFixed';
|
||||
|
||||
default:
|
||||
return typeValue;
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
: null;
|
||||
|
||||
const type = fromDef(def);
|
||||
let Component = findOne(def.lookupName) || findOne(def.type) || findOne(type);
|
||||
|
||||
if (!Component) {
|
||||
try {
|
||||
const instance = registry.createType(type as 'u32');
|
||||
const raw = getTypeDef(instance.toRawType());
|
||||
|
||||
Component = findOne(raw.lookupName || raw.type) || findOne(fromDef(raw));
|
||||
|
||||
if (Component) {
|
||||
return Component;
|
||||
} else if (isBn(instance)) {
|
||||
return Amount;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`params: findComponent: ${(e as Error).message}`);
|
||||
}
|
||||
|
||||
// we only want to want once, not spam
|
||||
if (!warnList.includes(type)) {
|
||||
warnList.push(type);
|
||||
console.info(`params: findComponent: No pre-defined component for type ${type} from ${TypeDefInfo[def.info]}: ${JSON.stringify(def)}`);
|
||||
}
|
||||
}
|
||||
|
||||
return Component || Unknown;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Props } from '../types.js';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { getTypeDef } from '@pezkuwi/types';
|
||||
import { encodeTypeDef } from '@pezkuwi/types/create';
|
||||
import { isUndefined } from '@pezkuwi/util';
|
||||
|
||||
import findComponent from './findComponent.js';
|
||||
import Static from './Static.js';
|
||||
|
||||
function formatJSON (input: string): string {
|
||||
return input
|
||||
.replace(/"/g, '')
|
||||
.replace(/\\/g, '')
|
||||
.replace(/:Null/g, '')
|
||||
.replace(/:/g, ': ')
|
||||
.replace(/,/g, ', ')
|
||||
.replace(/^{_alias: {.*}, /, '{');
|
||||
}
|
||||
|
||||
function Param ({ className = '', defaultValue, isDisabled, isError, isOptional, name, onChange, onEnter, onEscape, overrides, registry, type, withLength = true }: Props): React.ReactElement<Props> | null {
|
||||
const Component = useMemo(
|
||||
() => findComponent(registry, type, overrides),
|
||||
[registry, type, overrides]
|
||||
);
|
||||
|
||||
const label = useMemo(
|
||||
(): string => {
|
||||
const inner = encodeTypeDef(
|
||||
registry,
|
||||
// if our type is a Lookup, try and unwrap again
|
||||
registry.isLookupType(type.lookupName || type.type)
|
||||
? getTypeDef(registry.createType(type.type).toRawType())
|
||||
: type
|
||||
);
|
||||
const fmtType = formatJSON(inner);
|
||||
|
||||
return `${isUndefined(name) ? '' : `${name}: `}${fmtType}${type.typeName && !fmtType.includes(type.typeName) ? ` (${type.typeName})` : ''}`;
|
||||
},
|
||||
[name, registry, type]
|
||||
);
|
||||
|
||||
if (!Component) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return isOptional
|
||||
? (
|
||||
<Static
|
||||
defaultValue={defaultValue}
|
||||
isOptional
|
||||
label='None'
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Component
|
||||
className={`${className} ui--Param`}
|
||||
defaultValue={defaultValue}
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
key={`${name || 'unknown'}:${label}`}
|
||||
label={label}
|
||||
name={name}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
overrides={overrides}
|
||||
registry={registry}
|
||||
type={type}
|
||||
withLength={withLength}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Param);
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Registry, TypeDef } from '@pezkuwi/types/types';
|
||||
import type { ParamDef } from '../types.js';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook } from '@pezkuwi/react-hooks';
|
||||
import { getTypeDef } from '@pezkuwi/types/create';
|
||||
|
||||
function expandDef (registry: Registry, td: TypeDef): TypeDef {
|
||||
try {
|
||||
return getTypeDef(
|
||||
registry.createType(td.type as 'u32').toRawType()
|
||||
);
|
||||
} catch {
|
||||
return td;
|
||||
}
|
||||
}
|
||||
|
||||
function getDefs (registry: Registry, type: TypeDef): ParamDef[] {
|
||||
const typeDef = expandDef(registry, type);
|
||||
|
||||
return typeDef.sub
|
||||
? (Array.isArray(typeDef.sub) ? typeDef.sub : [typeDef.sub]).map((td): ParamDef => ({
|
||||
length: typeDef.length,
|
||||
name: td.name,
|
||||
type: td
|
||||
}))
|
||||
: [];
|
||||
}
|
||||
|
||||
function useParamDefsImpl (registry: Registry, type: TypeDef): ParamDef[] {
|
||||
return useMemo(
|
||||
() => getDefs(registry, type),
|
||||
[registry, type]
|
||||
);
|
||||
}
|
||||
|
||||
export default createNamedHook('useParamDefs', useParamDefsImpl);
|
||||
Reference in New Issue
Block a user