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:
2026-01-07 13:05:27 +03:00
commit d21bfb1320
5867 changed files with 329019 additions and 0 deletions
+75
View File
@@ -0,0 +1,75 @@
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@pezkuwi/api';
import type { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@pezkuwi/api/types';
import type { Call } from '@pezkuwi/types/interfaces';
import type { Props, RawParam } from '../types.js';
import React, { useState } from 'react';
import { useApi } from '@pezkuwi/react-hooks';
import { isObject, isString } from '@pezkuwi/util';
import Extrinsic from './Extrinsic.js';
function isCall (f: unknown): f is Call {
return isString((f as Call).section) && isString((f as Call).method);
}
function isSubmittable (f: unknown): f is SubmittableExtrinsic<'promise'> {
return isObject((f as SubmittableExtrinsic<'promise'>).method) && isString((f as SubmittableExtrinsic<'promise'>).method.section) && isString((f as SubmittableExtrinsic<'promise'>).method.method);
}
function mapArgs (args: unknown[]): RawParam[] {
return args.map((value) => ({
isValid: true,
value
}));
}
export function extractInitial (api: ApiPromise, initialValue: SubmittableExtrinsicFunction<'promise'>, input?: RawParam): { initialArgs?: RawParam[], initialValue: SubmittableExtrinsicFunction<'promise'> } {
try {
return input?.value
? isCall(input.value)
? {
initialArgs: mapArgs(input.value.args),
initialValue: api.tx[input.value.section][input.value.method]
}
: isSubmittable(input.value)
? {
initialArgs: mapArgs(input.value.method.args),
initialValue: api.tx[input.value.method.section][input.value.method.method]
}
: { initialValue: (input.value as SubmittableExtrinsicFunction<'promise'>) }
: { initialValue };
} catch {
return { initialValue };
}
}
function CallDisplay ({ className = '', defaultValue, isDisabled, isError, label, onChange, onEnter, onEscape, withLabel }: Props): React.ReactElement<Props> {
const { api, apiDefaultTx } = useApi();
const [{ initialArgs, initialValue }] = useState(
() => extractInitial(api, apiDefaultTx, defaultValue)
);
return (
<Extrinsic
className={className}
defaultArgs={initialArgs}
defaultValue={initialValue}
isDisabled={isDisabled}
isError={isError}
isPrivate={false}
label={label}
onChange={onChange}
onEnter={onEnter}
onEscape={onEscape}
withLabel={withLabel}
/>
);
}
export default React.memo(CallDisplay);
@@ -0,0 +1,52 @@
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@pezkuwi/api/types';
import type { RawParam, RawParamOnChange, RawParamOnEnter, RawParamOnEscape } from '../types.js';
import React, { useCallback } from 'react';
import { Extrinsic as BaseExtrinsic } from '../Named/index.js';
interface Props {
className?: string;
defaultArgs?: RawParam[];
defaultValue: SubmittableExtrinsicFunction<'promise'>;
isDisabled?: boolean;
isError?: boolean;
isPrivate: boolean;
label: React.ReactNode;
onChange?: RawParamOnChange;
onEnter?: RawParamOnEnter;
onEscape?: RawParamOnEscape;
withLabel?: boolean;
}
function ExtrinsicDisplay ({ className = '', defaultArgs, defaultValue, isDisabled, isError, isPrivate, label, onChange, onEnter, onEscape, withLabel }: Props): React.ReactElement<Props> {
const _onChange = useCallback(
(value?: SubmittableExtrinsic<'promise'>) =>
onChange && onChange({
isValid: !!value,
value
}),
[onChange]
);
return (
<BaseExtrinsic
className={className}
defaultArgs={defaultArgs}
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
isPrivate={isPrivate}
label={label}
onChange={_onChange}
onEnter={onEnter}
onEscape={onEscape}
withLabel={withLabel}
/>
);
}
export default React.memo(ExtrinsicDisplay);
@@ -0,0 +1,54 @@
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
import type { Props, RawParam } from '../types.js';
import React, { useCallback, useState } from 'react';
import { useApi } from '@pezkuwi/react-hooks';
import { extractInitial } from './Call.js';
import ExtrinsicDisplay from './Extrinsic.js';
function OpaqueCall ({ className = '', defaultValue, isDisabled, isError, label, onChange, onEnter, onEscape, withLabel }: Props): React.ReactElement<Props> {
const { api, apiDefaultTxSudo } = useApi();
const [{ initialArgs, initialValue }] = useState(
() => extractInitial(api, apiDefaultTxSudo, defaultValue)
);
const _onChange = useCallback(
({ isValid, value }: RawParam): void => {
let callData = null;
if (isValid && value) {
callData = (value as SubmittableExtrinsic<'promise'>).method.toHex();
}
onChange && onChange({
isValid,
value: callData
});
},
[onChange]
);
return (
<ExtrinsicDisplay
className={className}
defaultArgs={initialArgs}
defaultValue={initialValue}
isDisabled={isDisabled}
isError={isError}
isPrivate
label={label}
onChange={_onChange}
onEnter={onEnter}
onEscape={onEscape}
withLabel={withLabel}
/>
);
}
export default React.memo(OpaqueCall);
@@ -0,0 +1,53 @@
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Props, RawParam } from '../types.js';
import React, { useCallback, useState } from 'react';
import { useApi } from '@pezkuwi/react-hooks';
import { extractInitial } from './Call.js';
import ExtrinsicDisplay from './Extrinsic.js';
function ProposalDisplay ({ className = '', defaultValue, isDisabled, isError, label, onChange, onEnter, onEscape, withLabel }: Props): React.ReactElement<Props> {
const { api, apiDefaultTxSudo } = useApi();
const [{ initialArgs, initialValue }] = useState(
() => extractInitial(api, apiDefaultTxSudo, defaultValue)
);
const _onChange = useCallback(
({ isValid, value }: RawParam): void => {
let proposal = null;
if (isValid && value) {
proposal = api.createType('Proposal', value);
}
onChange && onChange({
isValid,
value: proposal
});
},
[api, onChange]
);
return (
<ExtrinsicDisplay
className={className}
defaultArgs={initialArgs}
defaultValue={initialValue}
isDisabled={isDisabled}
isError={isError}
isPrivate
label={label}
onChange={_onChange}
onEnter={onEnter}
onEscape={onEscape}
withLabel={withLabel}
/>
);
}
export default React.memo(ProposalDisplay);
+17
View File
@@ -0,0 +1,17 @@
// Copyright 2017-2025 @pezkuwi/app-extrinsics authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ComponentMap } from '../types.js';
import Call from './Call.js';
import OpaqueCall from './OpaqueCall.js';
import Proposal from './Proposal.js';
const components: ComponentMap = {
Call,
OpaqueCall,
Proposal,
RuntimeCall: Call
};
export default components;
+116
View File
@@ -0,0 +1,116 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { styled } from '@pezkuwi/react-components';
interface Props {
children?: React.ReactNode;
className?: string;
withBorder?: boolean;
withExpander?: boolean;
withPadding?: boolean;
}
function Holder ({ children, className = '', withBorder, withExpander, withPadding }: Props): React.ReactElement<Props> {
return (
<StyledDiv className={`${className} ui--Params ${withBorder ? 'withBorder' : ''} ${withPadding ? 'withPadding' : ''} ${withExpander ? 'withExpander' : ''}`}>
{children}
</StyledDiv>
);
}
const StyledDiv = styled.div`
&.withBorder {
padding-left: 2rem;
.ui--Params-Content {
border-left: 1px dashed var(--border-input);
.ui--Params.withBorder {
padding-left: 0;
}
}
}
&.withExpander {
padding-left: 0.25rem;
}
&.withPadding {
padding-left: 4rem;
}
.ui--Param-composite .ui--row,
.ui--Param-composite .ui--row .ui--InputAddressSimple {
& > .ui--Labelled > label {
text-transform: none !important;
}
}
.ui--row {
flex-wrap: wrap;
}
.ui--Param-Address {
}
.ui--Params-Content {
box-sizing: border-box;
padding: 0;
.ui--Params-Content {
margin-left: 2rem;
}
}
.ui--Param-text {
display: inline-block;
font-size: var(--font-size-base);
line-height: 1.714rem;
overflow: hidden;
text-overflow: ellipsis;
}
.ui--Param-text .icon {
margin-right: 0.5rem !important;
}
.ui--Param-text * {
vertical-align: middle;
}
.ui--Param-text.nowrap {
white-space: nowrap;
}
.ui--Param-text.name {
color: rgba(0, 0, 0, .6);
font-style: italic;
}
.ui--Param-text + .ui--Param-text {
margin-left: 0.5rem;
}
.ui--Param-Vector-buttons {
text-align: right;
}
.ui--Param-BTreeMap-buttons {
text-align: right;
}
.ui--Param-composite {
position: relative;
.ui--Param-overlay {
position: absolute;
top: 0.5rem;
right: 3.5rem;
}
}
`;
export default React.memo(Holder);
+184
View File
@@ -0,0 +1,184 @@
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ExtrinsicSignature } from '@pezkuwi/types/interfaces';
import type { Codec, IExtrinsic, IMethod, TypeDef } from '@pezkuwi/types/types';
import type { BN } from '@pezkuwi/util';
import type { ComponentMap } from '../types.js';
import React, { useEffect, useState } from 'react';
import { Static, styled } from '@pezkuwi/react-components';
import Params from '@pezkuwi/react-params';
import { FormatBalance } from '@pezkuwi/react-query';
import { Enum, getTypeDef } from '@pezkuwi/types';
import { balanceCalls, balanceCallsOverrides } from '../overrides.js';
import { useTranslation } from '../translate.js';
export interface Props {
callName?: string;
children?: React.ReactNode;
className?: string;
labelHash?: React.ReactNode;
labelSignature?: React.ReactNode;
mortality?: string;
onError?: () => void;
value?: IExtrinsic | IMethod | null;
withBorder?: boolean;
withExpander?: boolean;
withHash?: boolean;
withSignature?: boolean;
tip?: BN;
}
interface Param {
name: string;
type: TypeDef;
}
interface Value {
isValid: boolean;
value: Codec;
}
interface Extracted {
hash?: string | null;
overrides?: ComponentMap;
params?: Param[];
signature: string | null;
signatureType: string | null;
values?: Value[];
}
function isExtrinsic (value: unknown): value is IExtrinsic {
return !!(value as IExtrinsic).signature;
}
// This is no doubt NOT the way to do things - however there is no other option
function getRawSignature (value: IExtrinsic): ExtrinsicSignature | undefined {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
return (value as any)._raw?.signature?.multiSignature as ExtrinsicSignature;
}
function extractState (value?: IExtrinsic | IMethod | null, withHash?: boolean, withSignature?: boolean, callName?: string): Extracted {
const overrides = callName && balanceCalls.includes(callName)
? balanceCallsOverrides
: undefined;
const params = value?.meta.args.map(({ name, type }): Param => ({
name: name.toString(),
type: getTypeDef(type.toString())
}));
const values = value?.args.map((value): Value => ({
isValid: true,
value
}));
const hash = withHash
? value?.hash.toHex()
: null;
let signature: string | null = null;
let signatureType: string | null = null;
if (withSignature && isExtrinsic(value) && value.isSigned) {
const raw = getRawSignature(value);
signature = value.signature.toHex();
signatureType = raw instanceof Enum
? raw.type
: null;
}
return { hash, overrides, params, signature, signatureType, values };
}
function Call ({ callName, children, className = '', labelHash, labelSignature, mortality, onError, tip, value, withBorder, withExpander, withHash, withSignature }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [{ hash, overrides, params, signature, signatureType, values }, setExtracted] = useState<Extracted>({ hash: null, params: [], signature: null, signatureType: null, values: [] });
useEffect((): void => {
setExtracted(extractState(value, withHash, withSignature, callName));
}, [callName, value, withHash, withSignature]);
return (
<StyledDiv className={`${className} ui--Call`}>
<Params
isDisabled
onError={onError}
overrides={overrides}
params={params}
registry={value?.registry}
values={values}
withBorder={withBorder}
withExpander={withExpander}
>
{children}
<div className='ui--Call--toplevel'>
{hash && (
<Static
className='hash'
label={labelHash || t('extrinsic hash')}
value={hash}
withCopy
/>
)}
{signature && (
<Static
className='hash'
label={labelSignature || t('signature {{type}}', { replace: { type: signatureType ? `(${signatureType})` : '' } })}
value={signature}
withCopy
/>
)}
{mortality && (
<Static
className='mortality'
label={t('lifetime')}
value={mortality}
/>
)}
{tip?.gtn(0) && (
<Static
className='tip'
label={t('tip')}
value={<FormatBalance value={tip} />}
/>
)}
</div>
</Params>
</StyledDiv>
);
}
const StyledDiv = styled.div`
.ui--Labelled.hash .ui--Static {
overflow: hidden;
text-overflow: ellipsis;
word-break: unset;
word-wrap: unset;
white-space: nowrap;
}
.ui--Call--toplevel {
margin-top: 0;
.ui--Labelled {
&:last-child > .ui--Labelled-content > .ui--Static {
margin-bottom: 0;
}
> .ui--Labelled-content > .ui--Static {
background: var(--bg-static-extra);
}
+ .ui--Labelled > .ui--Labelled-content > .ui--Static {
margin-top: 0;
}
}
}
> .ui--Params {
margin-top: -0.25rem;
}
`;
export default React.memo(Call);
@@ -0,0 +1,76 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Call, Extrinsic } from '@pezkuwi/types/interfaces';
import type { BN } from '@pezkuwi/util';
import React, { useMemo } from 'react';
import { Expander } from '@pezkuwi/react-components';
import CallDisplay from './Call.js';
interface Props {
children?: React.ReactNode;
className?: string;
idString?: string;
isHeader?: boolean;
labelHash?: React.ReactNode;
labelSignature?: React.ReactNode;
mortality?: string;
onError?: () => void;
stringId?: string;
tip?: BN;
value?: Call | Extrinsic | null;
withBorder?: boolean;
withHash?: boolean;
withSignature?: boolean;
isExpanded?: boolean
}
function CallExpander ({ children, className = '', isExpanded, isHeader, labelHash, labelSignature, mortality, onError, stringId, tip, value, withBorder, withHash, withSignature }: Props): React.ReactElement<Props> | null {
const call = useMemo(
() => value?.callIndex
? value.registry.findMetaCall(value.callIndex)
: null,
[value]
);
if (!call || !value) {
return null;
}
const { meta, method, section } = call;
const callName = `${section}.${method}`;
return (
<div className={`${className} ui--CallExpander`}>
<Expander
isHeader={isHeader}
isLeft
isOpen={isExpanded}
summaryHead={
<>{stringId && `#${stringId}: `}{callName}</>
}
summaryMeta={meta}
>
<CallDisplay
callName={callName}
labelHash={labelHash}
labelSignature={labelSignature}
mortality={mortality}
onError={onError}
tip={tip}
value={value}
withBorder={withBorder}
withExpander
withHash={withHash}
withSignature={withSignature}
/>
{children}
</Expander>
</div>
);
}
export default React.memo(CallExpander);
+110
View File
@@ -0,0 +1,110 @@
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DecodedEvent } from '@pezkuwi/api-contract/types';
import type { Event, EventRecord } from '@pezkuwi/types/interfaces';
import type { Codec } from '@pezkuwi/types/types';
import React, { useMemo } from 'react';
import { Input } from '@pezkuwi/react-components';
import { getContractAbi } from '@pezkuwi/react-components/util';
import Params from '@pezkuwi/react-params';
import { balanceEvents, balanceEventsOverrides } from '../overrides.js';
import { useTranslation } from '../translate.js';
export interface Props {
children?: React.ReactNode;
className?: string;
eventName?: string;
value: Event;
withExpander?: boolean;
}
interface Value {
isValid: boolean;
value: Codec;
}
interface AbiEvent extends DecodedEvent {
values: Value[];
}
function EventDisplay ({ children, className = '', eventName, value, withExpander }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const names = value.data.names;
const params = value.typeDef.map((type, i) => ({
name: names?.[i] || undefined,
type
}));
const values = value.data.map((value) => ({ isValid: true, value }));
const overrides = useMemo(
() => eventName && balanceEvents.includes(eventName)
? balanceEventsOverrides
: undefined,
[eventName]
);
const abiEvent = useMemo(
(): AbiEvent | null => {
// for contracts, we decode the actual event
if (value.section === 'contracts' && value.method === 'ContractExecution' && value.data.length === 2) {
// see if we have info for this contract
const [accountId, encoded] = value.data;
try {
const abi = getContractAbi(accountId.toString());
if (abi) {
const decoded = abi.decodeEvent(encoded as EventRecord);
return {
...decoded,
values: decoded.args.map((value) => ({ isValid: true, value }))
};
}
} catch (error) {
// ABI mismatch?
console.error(error);
}
}
return null;
},
[value]
);
return (
<div className={`${className} ui--Event`}>
{children}
<Params
isDisabled
overrides={overrides}
params={params}
registry={value.registry}
values={values}
withExpander={withExpander}
>
{abiEvent && (
<>
<Input
isDisabled
label={t('contract event')}
value={abiEvent.event.identifier}
/>
<Params
isDisabled
params={abiEvent.event.args}
registry={value.registry}
values={abiEvent.values}
/>
</>
)}
</Params>
</div>
);
}
export default React.memo(EventDisplay);
@@ -0,0 +1,151 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubmittableExtrinsic, SubmittableExtrinsicFunction } from '@pezkuwi/api/types';
import type { TypeDef } from '@pezkuwi/types/types';
import type { ComponentMap, RawParam } from '../types.js';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { InputExtrinsic } from '@pezkuwi/react-components';
import Params from '@pezkuwi/react-params';
import { getTypeDef } from '@pezkuwi/types/create';
import { isUndefined, objectSpread } from '@pezkuwi/util';
import paramComponents from '../Extra/index.js';
import { balanceCalls, balanceCallsOverrides } from '../overrides.js';
interface Props {
className?: string;
defaultArgs?: RawParam[];
defaultValue: SubmittableExtrinsicFunction<'promise'>;
filter?: (section: string, method?: string) => boolean;
isDisabled?: boolean;
isError?: boolean;
isPrivate?: boolean;
label?: React.ReactNode;
onChange: (method?: SubmittableExtrinsic<'promise'>) => void;
onEnter?: () => void;
onError?: (error?: Error | null) => void;
onEscape?: () => void;
withLabel?: boolean;
}
interface ParamDef {
name: string;
type: TypeDef;
}
interface CallState {
extrinsic: {
fn: SubmittableExtrinsicFunction<'promise'>;
params: ParamDef[];
},
values: RawParam[];
}
const allComponents = objectSpread<ComponentMap>({}, paramComponents, balanceCallsOverrides);
function isValuesValid (params: ParamDef[], values: RawParam[]): boolean {
return values.reduce((isValid, value): boolean =>
isValid &&
!isUndefined(value) &&
!isUndefined(value.value) &&
value.isValid, params.length === values.length
);
}
function getParams ({ meta }: SubmittableExtrinsicFunction<'promise'>): ParamDef[] {
return meta.args.map(({ name, type, typeName }): { name: string; type: TypeDef } => ({
name: name.toString(),
type: {
...getTypeDef(type.toString()),
...(typeName.isSome
? { typeName: typeName.unwrap().toString() }
: {}
)
}
}));
}
function getCallState (fn: SubmittableExtrinsicFunction<'promise'>, values: RawParam[] = []): CallState {
return {
extrinsic: {
fn,
params: getParams(fn)
},
values
};
}
function ExtrinsicDisplay ({ defaultArgs, defaultValue, filter, isDisabled, isError, isPrivate, label, onChange, onEnter, onError, onEscape, withLabel }: Props): React.ReactElement<Props> {
const [{ extrinsic, values }, setDisplay] = useState<CallState>(() => getCallState(defaultValue, defaultArgs));
useEffect((): void => {
const isValid = isValuesValid(extrinsic.params, values);
let method;
if (isValid) {
try {
method = extrinsic.fn(...values.map(({ value }) => value));
} catch (error) {
onError && onError(error as Error);
}
} else {
onError && onError(null);
}
onChange(method);
}, [extrinsic, onChange, onError, values]);
const overrides = useMemo(
() => balanceCalls.includes(`${extrinsic.fn.section}.${extrinsic.fn.method}`)
? allComponents
: paramComponents,
[extrinsic]
);
const _onChangeMethod = useCallback(
(fn: SubmittableExtrinsicFunction<'promise'>) =>
setDisplay((prev): CallState =>
fn.section === prev.extrinsic.fn.section && fn.method === prev.extrinsic.fn.method
? prev
: getCallState(fn)
),
[]
);
const _setValues = useCallback(
(values: RawParam[]) =>
setDisplay(({ extrinsic }) => ({ extrinsic, values })),
[]
);
const { fn: { method, section }, params } = extrinsic;
return (
<div className='extrinsics--Extrinsic'>
<InputExtrinsic
defaultValue={defaultValue}
filter={filter}
isDisabled={isDisabled}
isError={isError}
isPrivate={isPrivate}
label={label}
onChange={_onChangeMethod}
withLabel={withLabel}
/>
<Params
key={`${section}.${method}:params` /* force re-render on change */}
onChange={_setValues}
onEnter={onEnter}
onEscape={onEscape}
overrides={overrides}
params={params}
values={values}
/>
</div>
);
}
export default React.memo(ExtrinsicDisplay);
@@ -0,0 +1,49 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Call } from '@pezkuwi/types/interfaces';
import type { BN } from '@pezkuwi/util';
import React from 'react';
import { formatNumber, isString, isUndefined } from '@pezkuwi/util';
import { useTranslation } from '../translate.js';
import CallExpander from './CallExpander.js';
interface Props {
className?: string;
proposal?: Call | null;
idNumber?: BN | number | string;
withLinks?: boolean;
expandNested?: boolean;
}
function ProposedAction ({ className = '', idNumber, proposal }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const stringId = isString(idNumber) || isUndefined(idNumber)
? idNumber
: formatNumber(idNumber);
if (!proposal) {
return (
<div className={`${className} ui--ProposedAction`}>
<div>{stringId ? `#${stringId}: ` : ''}{t('No execution details available for this proposal')}</div>
</div>
);
}
return (
<div className={`${className} ui--ProposedAction`}>
<CallExpander
isHeader
labelHash={t('preimage')}
stringId={stringId}
value={proposal}
withHash
/>
</div>
);
}
export default React.memo(ProposedAction);
+8
View File
@@ -0,0 +1,8 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
export { default as Call } from './Call.js';
export { default as CallExpander } from './CallExpander.js';
export { default as Event } from './Event.js';
export { default as Extrinsic } from './Extrinsic.js';
export { default as ProposedAction } from './ProposedAction.js';
@@ -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 };
+19
View File
@@ -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);
+41
View File
@@ -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);
+55
View File
@@ -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);
+78
View File
@@ -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);
+49
View File
@@ -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);
+74
View File
@@ -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);
+54
View File
@@ -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);
+169
View File
@@ -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);
+39
View File
@@ -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);
+19
View File
@@ -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);
+103
View File
@@ -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);
+52
View File
@@ -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 ? <>&nbsp;</> : 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);
+49
View File
@@ -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);
+58
View File
@@ -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);
+136
View File
@@ -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);
+101
View File
@@ -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;
}
+79
View File
@@ -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);
+54
View File
@@ -0,0 +1,54 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Registry, TypeDef } from '@pezkuwi/types/types';
import type { ComponentMap, RawParam, RawParamOnChangeValue, RawParams } from './types.js';
import React, { useCallback } from 'react';
import Param from './Param/index.js';
interface Props {
defaultValue: RawParam;
index: number;
isDisabled?: boolean;
isError?: boolean;
name?: string;
onChange: (index: number, value: RawParamOnChangeValue) => void;
onEnter?: () => void;
onEscape?: () => void;
overrides?: ComponentMap;
registry: Registry;
type: TypeDef;
values?: RawParams | null;
withLength?: boolean;
}
function ParamComp ({ defaultValue, index, isDisabled, isError, name, onChange, onEnter, onEscape, overrides, registry, type, withLength = true }: Props): React.ReactElement<Props> {
const _onChange = useCallback(
(value: RawParamOnChangeValue): void =>
onChange(index, value),
[index, onChange]
);
return (
<div className='ui--Param-composite'>
<Param
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
key={`input:${index}`}
name={name}
onChange={_onChange}
onEnter={onEnter}
onEscape={onEscape}
overrides={overrides}
registry={registry}
type={type}
withLength={withLength}
/>
</div>
);
}
export default React.memo(ParamComp);
+166
View File
@@ -0,0 +1,166 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { I18nProps } from '@pezkuwi/react-components/types';
import type { Registry } from '@pezkuwi/types/types';
import type { ComponentMap, ParamDef, RawParam, RawParamOnChangeValue, RawParams } from './types.js';
import React from 'react';
import { statics } from '@pezkuwi/react-api/statics';
import { ErrorBoundary } from '@pezkuwi/react-components';
import { stringify } from '@pezkuwi/util';
import Holder from './Holder.js';
import ParamComp from './ParamComp.js';
import translate from './translate.js';
import { createValue } from './values.js';
export * from './Named/index.js';
interface Props extends I18nProps {
children?: React.ReactNode;
isDisabled?: boolean;
isError?: boolean;
onChange?: (value: RawParams) => void;
onEnter?: () => void;
onError?: () => void;
onEscape?: () => void;
overrides?: ComponentMap;
params?: ParamDef[];
registry?: Registry;
values?: RawParams | null;
withBorder?: boolean;
withExpander?: boolean;
withLength?: boolean;
}
interface State {
params?: ParamDef[] | null;
values?: RawParams;
}
export { createValue, Holder, ParamComp };
class Params extends React.PureComponent<Props, State> {
public override state: State = {
params: null
};
public static getDerivedStateFromProps ({ isDisabled, params = [], registry = statics.api.registry, values }: Props, prevState: State): Pick<State, never> | null {
if (isDisabled || stringify(prevState.params) === stringify(params)) {
return null;
}
return {
params,
values: params.reduce(
(result: RawParams, param, index): RawParams => {
result.push(
values?.[index]
? values[index]
: createValue(registry, param)
);
return result;
}, []
)
};
}
// Fire the initial onChange (we did update) when the component is loaded
public override componentDidMount (): void {
this.componentDidUpdate(null, {});
}
// This is needed in the case where the item changes, i.e. the values get
// initialized and we need to alert the parent that we have new values
public override componentDidUpdate (_: Props | null, prevState: State): void {
const { isDisabled } = this.props;
const { values } = this.state;
if (!isDisabled && stringify(prevState.values) !== stringify(values)) {
this.triggerUpdate();
}
}
public override render (): React.ReactNode {
const { children, className = '', isDisabled, isError, onEnter, onEscape, overrides, params, registry = statics.api.registry, withBorder = true, withExpander, withLength = true } = this.props;
const { values = this.props.values } = this.state;
if (!values?.length) {
return null;
}
return (
<Holder
className={className}
withBorder={withBorder}
withExpander={withExpander}
>
<ErrorBoundary onError={this.onRenderError}>
<div className='ui--Params-Content'>
{values && params?.map(({ name, type }: ParamDef, index: number): React.ReactNode => (
<ParamComp
defaultValue={values[index]}
index={index}
isDisabled={isDisabled}
isError={isError}
key={`${name || ''}:${type.type.toString()}:${index}:${isDisabled ? stringify(values[index]) : ''}`}
name={name}
onChange={this.onChangeParam}
onEnter={onEnter}
onEscape={onEscape}
overrides={overrides}
registry={registry}
type={type}
withLength={withLength}
/>
))}
{children}
</div>
</ErrorBoundary>
</Holder>
);
}
private onChangeParam = (index: number, newValue: RawParamOnChangeValue): void => {
const { isDisabled } = this.props;
if (isDisabled) {
return;
}
const { isValid = false, value } = newValue;
this.setState(
(prevState: State): Pick<State, never> => ({
values: (prevState.values || []).map((prev, prevIndex): RawParam =>
prevIndex !== index
? prev
: { isValid, value }
)
}),
this.triggerUpdate
);
};
private triggerUpdate = (): void => {
const { isDisabled, onChange } = this.props;
const { values } = this.state;
if (isDisabled || !values) {
return;
}
onChange && onChange(values);
};
private onRenderError = (): void => {
const { onError } = this.props;
onError && onError();
};
}
export default translate<Props>(Params);
+152
View File
@@ -0,0 +1,152 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Registry, TypeDef } from '@pezkuwi/types/types';
import { getTypeDef } from '@pezkuwi/types';
import { TypeDefInfo } from '@pezkuwi/types/types';
import { BN_ZERO, isBn } from '@pezkuwi/util';
const warnList: string[] = [];
export default function getInitValue (registry: Registry, def: TypeDef): unknown {
if (def.info === TypeDefInfo.Vec) {
return [getInitValue(registry, def.sub as TypeDef)];
} else if (def.info === TypeDefInfo.Tuple) {
return Array.isArray(def.sub)
? def.sub.map((def) => getInitValue(registry, def))
: [];
} else if (def.info === TypeDefInfo.Struct) {
return Array.isArray(def.sub)
? def.sub.reduce((result: Record<string, unknown>, def): Record<string, unknown> => {
result[def.name || 'unknown'] = getInitValue(registry, def);
return result;
}, {})
: {};
} else if (def.info === TypeDefInfo.Enum) {
return Array.isArray(def.sub)
? { [def.sub[0].name || 'unknown']: getInitValue(registry, def.sub[0]) }
: {};
}
const type = [TypeDefInfo.Compact, TypeDefInfo.Option].includes(def.info)
? (def.sub as TypeDef).type
: def.type;
switch (type) {
case 'AccountIndex':
case 'Balance':
case 'BalanceOf':
case 'BlockNumber':
case 'Compact':
case 'Gas':
case 'Index':
case 'Nonce':
case 'ParaId':
case 'PropIndex':
case 'ProposalIndex':
case 'ReferendumIndex':
case 'i8':
case 'i16':
case 'i32':
case 'i64':
case 'i128':
case 'u8':
case 'u16':
case 'u32':
case 'u64':
case 'u128':
case 'VoteIndex':
return BN_ZERO;
case 'bool':
return false;
case 'Bytes':
return undefined;
case 'String':
case 'Text':
return '';
case 'Moment':
return BN_ZERO;
case 'Vote':
return -1;
case 'VoteThreshold':
return 0;
case 'BlockHash':
case 'CodeHash':
case 'Hash':
case 'H256':
return registry.createType('H256');
case 'H512':
return registry.createType('H512');
case 'H160':
return registry.createType('H160');
case 'Raw':
case 'Keys':
return '';
case 'AccountId':
case 'AccountId20':
case 'AccountId32':
case 'AccountIdOf':
case 'Address':
case 'Call':
case 'CandidateReceipt':
case 'Digest':
case 'Header':
case 'KeyValue':
case 'LookupSource':
case 'MisbehaviorReport':
case 'Proposal':
case 'RuntimeCall':
case 'Signature':
case 'SessionKey':
case 'StorageKey':
case 'ValidatorId':
return undefined;
case 'Extrinsic':
return registry.createType('Raw');
case 'Null':
return null;
default: {
let error: string | null = null;
try {
const instance = registry.createType(type as 'u32');
const raw = getTypeDef(instance.toRawType());
if (isBn(instance)) {
return BN_ZERO;
} else if ([TypeDefInfo.Struct].includes(raw.info)) {
return undefined;
} else if ([TypeDefInfo.Enum, TypeDefInfo.Tuple].includes(raw.info)) {
return getInitValue(registry, raw);
}
} catch (e) {
error = (e as Error).message;
}
// we only want to want once, not spam
if (!warnList.includes(type)) {
warnList.push(type);
error && console.error(`params: initValue: ${error}`);
console.info(`params: initValue: No default value for type ${type} from ${JSON.stringify(def)}, using defaults`);
}
return '0x';
}
}
}
+48
View File
@@ -0,0 +1,48 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ComponentMap } from './types.js';
import BalanceParam from './Param/Balance.js';
export const balanceCalls = [
'auctions.bid',
'balances.forceTransfer', 'balances.forceUnreserve', 'balances.setBalance', 'balances.transfer', 'balances.transferAllowDeath', 'balances.transferKeepAlive',
'bounties.proposeBounty', 'bounties.proposeCurator',
'childBounties.addChildBounty', 'childBounties.proposeCurator',
'claims.mintClaim',
'convictionVoting.delegate', 'convictionVoting.vote',
'crowdloan.contribute', 'crowdloan.create', 'crowdloan.edit',
'democracy.delegate', 'democracy.propose', 'democracy.vote',
'identity.requestJudgement', 'identity.setFee',
'nominationPools.bondExtra', 'nominationPools.create', 'nominationPools.createWithPoolId', 'nominationPools.join', 'nominationPools.unbond',
'phragmenElection.vote',
'society.bid', 'society.vouch',
'staking.bond', 'staking.bondExtra', 'staking.rebond', 'staking.unbond',
'tips.tip', 'tips.tipNew',
'treasury.proposeSpend', 'treasury.spendLocal',
'vesting.forceVestedTransfer', 'vesting.vestedTransfer'
];
// needs expansion with events from above
export const balanceEvents = [
'auctions.BidAccepted', 'auctions.ReserveConfiscated', 'auctions.Reserved', 'auctions.Unreserved',
'balances.Deposit', 'balances.DustLost', 'balances.Endowed', 'balances.Transfer', 'balances.Unreserved', 'balances.Withdraw',
'bounties.BountyClaimed', 'bounties.BountyRejected',
'claims.Claimed',
'convictionVoting.Voted',
'crowdloan.Contributed', 'crowdloan.Withdrew',
'democracy.Voted',
'nominationPools.Bonded', 'nominationPools.PaidOut', 'nominationPools.PoolSlashed', 'nominationPools.Unbonded', 'nominationPools.UnbondingPoolSlashed',
'referenda.DecisionDepositPlaced', 'referenda.DecisionDepositRefunded', 'referenda.DepositSlashed', 'referenda.SubmissionDepositRefunded',
'staking.Bonded', 'staking.Rewarded', 'staking.Unbonded', 'staking.Withdrawn',
'transactionPayment.TransactionFeePaid',
'treasury.Deposit'
];
export const balanceCallsOverrides: ComponentMap = {
'Compact<u128>': BalanceParam,
u128: BalanceParam
};
export const balanceEventsOverrides = balanceCallsOverrides;
+14
View File
@@ -0,0 +1,14 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type React from 'react';
import { useTranslation as useTranslationBase, withTranslation } from 'react-i18next';
export function useTranslation (): { t: (key: string, options?: { replace: Record<string, unknown> }) => string } {
return useTranslationBase('react-params');
}
export default function translate <T, P = Omit<T, 't'>> (C: React.ComponentClass<T>): React.ComponentType<P> {
return withTranslation(['react-params'])(C) as unknown as React.ComponentType<P>;
}
+70
View File
@@ -0,0 +1,70 @@
// 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 { HexString } from '@pezkuwi/util/types';
// FIXME Ideally, we want these as Base from api-codec - as a stop-gap, any this until we have
// params returning types extending Base (i.e. anything from api-codec)
export type RawParamValue = unknown;
export interface RawParam {
isValid: boolean;
value: RawParamValue;
}
export interface RawParamOnChangeValue {
isValid: boolean;
value: RawParamValue;
}
export type RawParamOnChange = (value: RawParamOnChangeValue) => void;
export type RawParamOnEnter = () => void;
export type RawParamOnEscape = () => void;
export type RawParams = RawParam[];
export interface Props {
className?: string;
defaultValue: RawParam;
isDisabled?: boolean;
isError?: boolean;
isInOption?: boolean;
isReadOnly?: boolean;
isOptional?: boolean;
label?: React.ReactNode;
name?: string;
onChange?: RawParamOnChange;
onEnter?: RawParamOnEnter;
onEscape?: RawParamOnEscape;
// eslint-disable-next-line no-use-before-define
overrides?: ComponentMap;
registry: Registry;
type: TypeDefExt;
withLabel?: boolean;
withLength?: boolean;
}
export type Size = 'full' | 'large' | 'medium' | 'small';
export type ComponentMap = Record<string, React.ComponentType<Props>>;
export interface ParamDef {
length?: number;
name?: string;
type: TypeDef;
}
export interface ExpandedCid {
codec: number;
hash: {
code: number;
digest: HexString;
};
version: 0 | 1;
}
export interface TypeDefExt extends TypeDef {
withOptionActive?: boolean;
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PalletAllianceCid } from '@pezkuwi/types/lookup';
import type { ExpandedCid } from './types.js';
import { CID, digest, varint } from 'multiformats';
import { u8aToHex } from '@pezkuwi/util';
export function fromIpfsCid (cid: string): ExpandedCid | null {
try {
const { code: codec, multihash: { code, digest }, version } = CID.parse(cid);
return {
codec,
hash: {
code,
digest: u8aToHex(digest)
},
version
};
} catch (error) {
console.error(`fromIpfsCid: ${(error as Error).message}::`, cid);
return null;
}
}
export function toIpfsCid (cid: PalletAllianceCid): string | null {
try {
const { codec, hash_: { code, digest: _bytes }, version } = cid;
// Since we use parse, encode into a fully-specified bytes to
// pass - <varint code> + <varint length> + bytes
const bytes = _bytes.toU8a(true);
const codeLen = varint.encodingLength(code.toNumber());
const sizeLen = varint.encodingLength(bytes.length);
const encoded = new Uint8Array(codeLen + sizeLen + bytes.length);
varint.encodeTo(code.toNumber(), encoded, 0);
varint.encodeTo(bytes.length, encoded, codeLen);
encoded.set(bytes, codeLen + sizeLen);
return CID
.create(version.index as 0, codec.toNumber(), digest.decode(encoded))
.toString();
} catch (error) {
console.error(`toIpfsCid: ${(error as Error).message}::`, cid.toHuman());
return null;
}
}
+95
View File
@@ -0,0 +1,95 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Keys, ValidatorId } from '@pezkuwi/types/interfaces';
import type { Codec } from '@pezkuwi/types/types';
import React from 'react';
import { CopyButton } from '@pezkuwi/react-components';
import { Option, Raw } from '@pezkuwi/types';
import { isFunction, isNull, isUndefined, stringify, u8aToHex } from '@pezkuwi/util';
interface DivProps {
className?: string;
key?: string;
}
function div ({ className = '', key }: DivProps, ...values: React.ReactNode[]): { cName: string, key: string | undefined, values: React.ReactNode[] } {
const cName = `${className} ui--Param-text`;
return {
cName,
key,
values
};
}
function formatKeys (keys: [ValidatorId, Keys][]): string {
return JSON.stringify(
keys.map(([validator, keys]): [string, string] => [
validator.toString(), keys.toHex()
])
);
}
function toHuman (value: Codec | Codec[]): unknown {
// eslint-disable-next-line @typescript-eslint/unbound-method
return isFunction((value as Codec).toHuman)
? (value as Codec).toHuman()
: Array.isArray(value)
? value.map((v) => toHuman(v))
: value.toString();
}
export function toHumanJson (value: unknown): string {
return stringify(value, 2)
.replace(/,\n/g, '\n')
.replace(/"/g, '')
.replace(/\\/g, '')
.replace(/\],\[/g, '],\n[');
}
export default function valueToText (type: string, value: Codec | undefined | null): React.ReactNode {
if (isNull(value) || isUndefined(value)) {
const { cName, key, values } = div({}, '<unknown>');
return (
<div
className={cName}
key={key}
>
{values}
</div>
);
}
const renderedValue = (
// eslint-disable-next-line @typescript-eslint/unbound-method
['Bytes', 'Raw', 'Option<Keys>', 'Keys'].includes(type) && isFunction(value.toU8a)
? u8aToHex(value.toU8a(true))
// HACK Handle Keys as hex-only (this should go away once the node value is
// consistently swapped to `Bytes`)
: type === 'Vec<(ValidatorId,Keys)>'
? toHumanJson(formatKeys(value as unknown as [ValidatorId, Keys][]))
: value instanceof Raw
? value.isEmpty
? '<empty>'
: value.toString()
: (value instanceof Option) && value.isNone
? '<none>'
: toHumanJson(toHuman(value))
);
const { cName, key, values } = div({}, renderedValue);
return (
<div
className={cName}
key={key}
>
{values}
<CopyButton value={renderedValue} />
</div>
);
}
+22
View File
@@ -0,0 +1,22 @@
// Copyright 2017-2025 @pezkuwi/react-params authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Registry, TypeDef } from '@pezkuwi/types/types';
import type { RawParam } from './types.js';
import { isUndefined } from '@pezkuwi/util';
import getInitValue from './initValue.js';
export function createValue (registry: Registry, param: { type: TypeDef }): RawParam {
const value = getInitValue(registry, param.type);
return {
isValid: !isUndefined(value),
value
};
}
export default function createValues (registry: Registry, params: { type: TypeDef }[]): RawParam[] {
return params.map((param) => createValue(registry, param));
}