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