mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-07-31 20:35:43 +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,78 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Abi } from '@pezkuwi/api-contract';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { IconLink, InputFile, Labelled } from '@pezkuwi/react-components';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Messages from './Messages.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
contractAbi?: Abi | null;
|
||||
errorText?: string | null;
|
||||
isDisabled?: boolean;
|
||||
isError?: boolean;
|
||||
isFull?: boolean;
|
||||
isRequired?: boolean;
|
||||
isValid?: boolean;
|
||||
isSupplied?: boolean;
|
||||
label?: React.ReactNode;
|
||||
onChange: (u8a: Uint8Array, name: string) => void;
|
||||
onRemove?: () => void;
|
||||
onRemoved?: () => void;
|
||||
onSelect?: () => void;
|
||||
onSelectConstructor?: (index?: number) => void;
|
||||
withConstructors?: boolean;
|
||||
withLabel?: boolean;
|
||||
withMessages?: boolean;
|
||||
withWasm?: boolean;
|
||||
}
|
||||
|
||||
const NOOP = (): void => undefined;
|
||||
|
||||
function ABI ({ className, contractAbi, errorText, isDisabled, isError, isFull, isValid, label, onChange, onRemove = NOOP, onSelectConstructor, withConstructors = true, withLabel = true, withMessages = true, withWasm }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (contractAbi && isValid)
|
||||
? (
|
||||
<Labelled
|
||||
className={className}
|
||||
label={label || t('contract ABI')}
|
||||
labelExtra={onRemove && (
|
||||
<IconLink
|
||||
icon='trash'
|
||||
label={t('Remove ABI')}
|
||||
onClick={onRemove}
|
||||
/>
|
||||
)}
|
||||
withLabel={withLabel}
|
||||
>
|
||||
<Messages
|
||||
contractAbi={contractAbi}
|
||||
isLabelled={withLabel}
|
||||
onSelectConstructor={onSelectConstructor}
|
||||
withConstructors={withConstructors}
|
||||
withMessages={withMessages}
|
||||
withWasm={withWasm}
|
||||
/>
|
||||
</Labelled>
|
||||
)
|
||||
: (
|
||||
<div className={className}>
|
||||
<InputFile
|
||||
isDisabled={isDisabled}
|
||||
isError={isError}
|
||||
isFull={isFull}
|
||||
label={label || t('contract ABI')}
|
||||
onChange={onChange}
|
||||
placeholder={errorText || t('click to select or drag and drop a JSON file')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ABI);
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { CodeStored } from '../types.js';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Icon, styled } from '@pezkuwi/react-components';
|
||||
import Row from '@pezkuwi/react-components/Row';
|
||||
|
||||
import contracts from '../store.js';
|
||||
|
||||
interface Props {
|
||||
buttons?: React.ReactNode;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
code: CodeStored;
|
||||
isInline?: boolean;
|
||||
withTags?: boolean;
|
||||
}
|
||||
|
||||
const DEFAULT_HASH = '0x';
|
||||
const DEFAULT_NAME = '<unknown>';
|
||||
|
||||
function CodeRow ({ buttons, children, className, code: { json }, isInline, withTags }: Props): React.ReactElement<Props> {
|
||||
const [name, setName] = useState(json.name || DEFAULT_NAME);
|
||||
const [tags, setTags] = useState(json.tags || []);
|
||||
const [codeHash, setCodeHash] = useState(json.codeHash || DEFAULT_HASH);
|
||||
|
||||
useEffect((): void => {
|
||||
setName(json.name || DEFAULT_NAME);
|
||||
setTags(json.tags || []);
|
||||
setCodeHash(json.codeHash || DEFAULT_HASH);
|
||||
}, [json]);
|
||||
|
||||
const _onSaveName = useCallback(
|
||||
(): void => {
|
||||
const trimmedName = name.trim();
|
||||
|
||||
if (trimmedName && codeHash) {
|
||||
contracts.saveCode(codeHash, { name });
|
||||
}
|
||||
},
|
||||
[codeHash, name]
|
||||
);
|
||||
|
||||
const _onSaveTags = useCallback(
|
||||
(): void => {
|
||||
codeHash && contracts.saveCode(codeHash, { tags });
|
||||
},
|
||||
[codeHash, tags]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledRow
|
||||
buttons={buttons}
|
||||
className={className}
|
||||
icon={
|
||||
<div className='ui--CodeRow-icon'>
|
||||
<Icon icon='code' />
|
||||
</div>
|
||||
}
|
||||
isInline={isInline}
|
||||
name={name}
|
||||
onChangeName={setName}
|
||||
onChangeTags={setTags}
|
||||
onSaveName={_onSaveName}
|
||||
onSaveTags={_onSaveTags}
|
||||
tags={withTags ? tags : undefined}
|
||||
>
|
||||
{children}
|
||||
</StyledRow>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledRow = styled(Row)`
|
||||
.ui--CodeRow-icon {
|
||||
margin-right: -0.5em;
|
||||
background: #eee;
|
||||
border-radius: 50%;
|
||||
color: #666;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(CodeRow);
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { WeightV2 } from '@pezkuwi/types/interfaces';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { UseWeight } from '../types.js';
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { InputNumber, Toggle } from '@pezkuwi/react-components';
|
||||
import { BN_MILLION, BN_ONE, BN_ZERO } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
estimatedWeight?: BN | null;
|
||||
estimatedWeightV2?: WeightV2 | null;
|
||||
isCall?: boolean;
|
||||
weight: UseWeight;
|
||||
}
|
||||
|
||||
function InputMegaGas ({ className,
|
||||
estimatedWeight,
|
||||
estimatedWeightV2,
|
||||
isCall,
|
||||
weight: { executionTime,
|
||||
isValid,
|
||||
isWeightV2,
|
||||
megaGas,
|
||||
megaRefTime,
|
||||
percentage,
|
||||
proofSize,
|
||||
setIsEmpty,
|
||||
setMegaGas,
|
||||
setMegaRefTime,
|
||||
setProofSize } }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [withEstimate, setWithEstimate] = useState(true);
|
||||
|
||||
const estimatedMg = useMemo(
|
||||
() => estimatedWeight
|
||||
? estimatedWeight.div(BN_MILLION).iadd(BN_ONE)
|
||||
: null,
|
||||
[estimatedWeight]
|
||||
);
|
||||
|
||||
const estimatedMgRefTime = useMemo(
|
||||
() => estimatedWeightV2
|
||||
? estimatedWeightV2.refTime.toBn().div(BN_MILLION).iadd(BN_ONE)
|
||||
: null,
|
||||
[estimatedWeightV2]
|
||||
);
|
||||
|
||||
const estimatedProofSize = useMemo(
|
||||
() => estimatedWeightV2
|
||||
? estimatedWeightV2.proofSize.toBn()
|
||||
: null,
|
||||
[estimatedWeightV2]
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
withEstimate && estimatedMg && setMegaGas(estimatedMg);
|
||||
}, [estimatedMg, setMegaGas, withEstimate]);
|
||||
|
||||
useEffect((): void => {
|
||||
withEstimate && estimatedMgRefTime && setMegaRefTime(estimatedMgRefTime);
|
||||
}, [estimatedMgRefTime, setMegaRefTime, withEstimate]);
|
||||
|
||||
useEffect((): void => {
|
||||
withEstimate && estimatedProofSize && setProofSize(estimatedProofSize);
|
||||
}, [estimatedProofSize, setProofSize, withEstimate]);
|
||||
|
||||
useEffect((): void => {
|
||||
setIsEmpty(withEstimate && !!isCall);
|
||||
}, [isCall, setIsEmpty, withEstimate]);
|
||||
|
||||
const isDisabled = !!estimatedMg && withEstimate;
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{isWeightV2
|
||||
? <>
|
||||
<InputNumber
|
||||
defaultValue={estimatedMgRefTime && isDisabled ? estimatedMgRefTime.toString() : undefined}
|
||||
isDisabled={isDisabled}
|
||||
isError={!isValid}
|
||||
isZeroable={isCall}
|
||||
label={
|
||||
estimatedMgRefTime && (isCall ? !withEstimate : true)
|
||||
? t('max RefTime allowed (M, {{estimatedRefTime}} estimated)', { replace: { estimatedMgRefTime: estimatedMgRefTime.toString() } })
|
||||
: t('max RefTime allowed (M)')
|
||||
}
|
||||
onChange={isDisabled ? undefined : setMegaRefTime}
|
||||
value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : megaRefTime)}
|
||||
>
|
||||
{(estimatedWeightV2 || isCall) && (
|
||||
<Toggle
|
||||
label={
|
||||
isCall
|
||||
? t('max read gas')
|
||||
: t('use estimated gas')
|
||||
}
|
||||
onChange={setWithEstimate}
|
||||
value={withEstimate}
|
||||
/>
|
||||
)}
|
||||
</InputNumber>
|
||||
<InputNumber
|
||||
defaultValue={estimatedProofSize && isDisabled ? estimatedProofSize.toString() : undefined}
|
||||
isDisabled={isDisabled}
|
||||
isError={!isValid}
|
||||
isZeroable={isCall}
|
||||
label={
|
||||
estimatedProofSize && (isCall ? !withEstimate : true)
|
||||
? t('max ProofSize allowed ({{estimatedProofSize}} estimated)', { replace: { estimatedProofSize: estimatedProofSize.toString() } })
|
||||
: t('max ProofSize allowed')
|
||||
}
|
||||
onChange={isDisabled ? undefined : setProofSize}
|
||||
value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : proofSize)}
|
||||
/>
|
||||
<div className='contracts--Input-meter'>
|
||||
{t('{{executionTime}}s execution time', { replace: { executionTime: executionTime.toFixed(3) } })}{', '}
|
||||
{t('{{percentage}}% of block weight', { replace: { percentage: percentage.toFixed(2) } })}
|
||||
</div>
|
||||
</>
|
||||
: <>
|
||||
<InputNumber
|
||||
defaultValue={estimatedMg && isDisabled ? estimatedMg.toString() : undefined}
|
||||
isDisabled={isDisabled}
|
||||
isError={!isValid}
|
||||
isZeroable={isCall}
|
||||
label={
|
||||
estimatedMg && (isCall ? !withEstimate : true)
|
||||
? t('max gas allowed (M, {{estimatedMg}} estimated)', { replace: { estimatedMg: estimatedMg.toString() } })
|
||||
: t('max gas allowed (M)')
|
||||
}
|
||||
onChange={isDisabled ? undefined : setMegaGas}
|
||||
value={isDisabled ? undefined : ((isCall && withEstimate) ? BN_ZERO : megaGas)}
|
||||
>
|
||||
{(estimatedWeight || isCall) && (
|
||||
<Toggle
|
||||
label={
|
||||
isCall
|
||||
? t('max read gas')
|
||||
: t('use estimated gas')
|
||||
}
|
||||
onChange={setWithEstimate}
|
||||
value={withEstimate}
|
||||
/>
|
||||
)}
|
||||
</InputNumber>
|
||||
<div className='contracts--Input-meter'>
|
||||
{t('{{executionTime}}s execution time', { replace: { executionTime: executionTime.toFixed(3) } })}{', '}
|
||||
{t('{{percentage}}% of block weight', { replace: { percentage: percentage.toFixed(2) } })}
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(InputMegaGas);
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Input } from '@pezkuwi/react-components';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
isBusy?: boolean;
|
||||
isContract?: boolean;
|
||||
isError?: boolean;
|
||||
isDisabled?: boolean;
|
||||
onChange: (_: string) => void;
|
||||
onEnter?: () => void;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
function InputName ({ className, isBusy, isContract, isError, onChange, onEnter, value = '' }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Input
|
||||
className={className}
|
||||
isDisabled={isBusy}
|
||||
isError={isError}
|
||||
label={t(
|
||||
isContract
|
||||
? 'contract name'
|
||||
: 'code bundle name'
|
||||
)}
|
||||
onChange={onChange}
|
||||
onEnter={onEnter}
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(InputName);
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AbiConstructor, ContractCallOutcome } from '@pezkuwi/api-contract/types';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { Button, Output, styled } from '@pezkuwi/react-components';
|
||||
import valueToText from '@pezkuwi/react-params/valueToText';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import MessageSignature from './MessageSignature.js';
|
||||
|
||||
export interface Props {
|
||||
className?: string;
|
||||
index: number;
|
||||
lastResult?: ContractCallOutcome;
|
||||
message: AbiConstructor;
|
||||
onSelect?: (index: number) => void;
|
||||
}
|
||||
|
||||
function filterDocs (docs: string[]): string[] {
|
||||
let skip = false;
|
||||
|
||||
return docs
|
||||
.map((line) => line.trim())
|
||||
.filter((line) => line)
|
||||
.filter((line, index): boolean => {
|
||||
if (skip) {
|
||||
return false;
|
||||
} else if (index || line.startsWith('#')) {
|
||||
skip = true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function Message ({ className = '', index, lastResult, message, onSelect }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const _onSelect = useCallback(
|
||||
() => onSelect && onSelect(index),
|
||||
[index, onSelect]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledDiv
|
||||
className={`${className} ${!onSelect ? 'exempt-hover' : ''} ${message.isConstructor ? 'constructor' : ''}`}
|
||||
key={`${message.identifier}-${index}`}
|
||||
>
|
||||
{onSelect && (
|
||||
message.isConstructor
|
||||
? (
|
||||
<Button
|
||||
className='accessory'
|
||||
icon='upload'
|
||||
label={t('deploy')}
|
||||
onClick={_onSelect}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Button
|
||||
className='accessory'
|
||||
icon='play'
|
||||
isDisabled={message.isMutating ? false : (!message.args.length && lastResult?.result.isOk)}
|
||||
label={message.isMutating ? t('exec') : t('read')}
|
||||
onClick={_onSelect}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
<div className='info'>
|
||||
<MessageSignature
|
||||
asConstructor={message.isConstructor}
|
||||
message={message}
|
||||
withTooltip
|
||||
/>
|
||||
<div className='docs'>
|
||||
{message.docs.length
|
||||
? filterDocs(message.docs).map((line, index) => ((
|
||||
<div key={`${message.identifier}-docs-${index}`}>{line}</div>
|
||||
)))
|
||||
: <i> {t('No documentation provided')} </i>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{lastResult && lastResult.result.isOk && lastResult.output && (
|
||||
<Output
|
||||
className='result'
|
||||
isFull
|
||||
label={t('current value')}
|
||||
>
|
||||
{valueToText('Text', lastResult.output)}
|
||||
</Output>
|
||||
)}
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
align-items: center;
|
||||
border-radius: 0.25rem;
|
||||
display: flex;
|
||||
padding: 0.25rem 0.75rem 0.25rem 0;
|
||||
|
||||
&.disabled {
|
||||
opacity: 1 !important;
|
||||
background: #eee !important;
|
||||
color: #555 !important;
|
||||
}
|
||||
|
||||
.info {
|
||||
flex: 1 1;
|
||||
margin-left: 1.5rem;
|
||||
|
||||
.docs {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.result {
|
||||
min-width: 15rem;
|
||||
}
|
||||
|
||||
&+& {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Message);
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AbiMessage } from '@pezkuwi/api-contract/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Icon, styled, Tooltip } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
import { encodeTypeDef } from '@pezkuwi/types/create';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
const MAX_PARAM_LENGTH = 20;
|
||||
|
||||
export interface Props {
|
||||
asConstructor?: boolean;
|
||||
className?: string;
|
||||
message: AbiMessage;
|
||||
params?: unknown[];
|
||||
withTooltip?: boolean;
|
||||
}
|
||||
|
||||
function truncate (param: string): string {
|
||||
return param.length > MAX_PARAM_LENGTH
|
||||
? `${param.substring(0, MAX_PARAM_LENGTH / 2)}…${param.substring(param.length - MAX_PARAM_LENGTH / 2)}`
|
||||
: param;
|
||||
}
|
||||
|
||||
function MessageSignature ({ className, message: { args, isConstructor, isMutating, method, returnType }, params = [], withTooltip = false }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
|
||||
return (
|
||||
<StyledDiv className={className}>
|
||||
<span className='ui--MessageSignature-name'>{method}</span>
|
||||
{' '}({args.map(({ name, type }, index): React.ReactNode => {
|
||||
return (
|
||||
<React.Fragment key={`${name}-args-${index}`}>
|
||||
{name}:
|
||||
{' '}
|
||||
<span className='ui--MessageSignature-type'>
|
||||
{params?.[index]
|
||||
? <b>{truncate((params as string[])[index].toString())}</b>
|
||||
: encodeTypeDef(api.registry, type)
|
||||
}
|
||||
</span>
|
||||
{index < (args.length) - 1 && ', '}
|
||||
</React.Fragment>
|
||||
);
|
||||
})})
|
||||
{(!isConstructor && returnType) && (
|
||||
<>
|
||||
:
|
||||
{' '}
|
||||
<span className='ui--MessageSignature-returnType'>
|
||||
{encodeTypeDef(api.registry, returnType)}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
{isMutating && (
|
||||
<>
|
||||
<Icon
|
||||
className='ui--MessageSignature-mutates'
|
||||
icon='database'
|
||||
tooltip={`mutates-${method}`}
|
||||
/>
|
||||
{withTooltip && (
|
||||
<Tooltip
|
||||
text={t('Mutates contract state')}
|
||||
trigger={`mutates-${method}`}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
font: var(--font-mono);
|
||||
font-weight: var(--font-weight-normal);
|
||||
flex-grow: 1;
|
||||
|
||||
.ui--MessageSignature-mutates {
|
||||
color: #ff8600;
|
||||
margin-left: 0.5rem;
|
||||
opacity: var(--opacity-light);
|
||||
}
|
||||
|
||||
.ui--MessageSignature-name {
|
||||
color: #2f8ddb;
|
||||
font-weight: var(--font-weight-normal);
|
||||
}
|
||||
|
||||
.ui--MessageSignature-type {
|
||||
color: #21a2b2;
|
||||
}
|
||||
|
||||
.ui--MessageSignature-returnType {
|
||||
color: #ff8600;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(MessageSignature);
|
||||
@@ -0,0 +1,146 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Abi, ContractPromise } from '@pezkuwi/api-contract';
|
||||
import type { AbiMessage, ContractCallOutcome } from '@pezkuwi/api-contract/types';
|
||||
import type { Option } from '@pezkuwi/types';
|
||||
import type { ContractInfo } from '@pezkuwi/types/interfaces';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Expander, styled } from '@pezkuwi/react-components';
|
||||
import { useApi, useCall } from '@pezkuwi/react-hooks';
|
||||
import { formatNumber } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Message from './Message.js';
|
||||
|
||||
export interface Props {
|
||||
className?: string;
|
||||
contract?: ContractPromise;
|
||||
contractAbi: Abi;
|
||||
isLabelled?: boolean;
|
||||
isWatching?: boolean;
|
||||
trigger?: number;
|
||||
onSelect?: (messageIndex: number, resultCb: (messageIndex: number, result?: ContractCallOutcome) => void) => void;
|
||||
onSelectConstructor?: (constructorIndex: number) => void;
|
||||
withConstructors?: boolean;
|
||||
withMessages?: boolean;
|
||||
withWasm?: boolean;
|
||||
}
|
||||
|
||||
const READ_ADDR = '0x'.padEnd(66, '0');
|
||||
|
||||
function sortMessages (messages: AbiMessage[]): [AbiMessage, number][] {
|
||||
return messages
|
||||
.map((m, index): [AbiMessage, number] => [m, index])
|
||||
.sort((a, b) => a[0].identifier.localeCompare(b[0].identifier))
|
||||
.sort((a, b) => a[0].isMutating === b[0].isMutating
|
||||
? 0
|
||||
: a[0].isMutating
|
||||
? -1
|
||||
: 1
|
||||
);
|
||||
}
|
||||
|
||||
function Messages ({ className = '', contract, contractAbi: { constructors, info: { source }, messages }, isLabelled, isWatching, onSelect, onSelectConstructor, trigger, withConstructors, withMessages, withWasm }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const optInfo = useCall<Option<ContractInfo>>(contract && api.query.contracts.contractInfoOf, [contract?.address]);
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [lastResults, setLastResults] = useState<(ContractCallOutcome | undefined)[]>([]);
|
||||
|
||||
const _onExpander = useCallback(
|
||||
(isOpen: boolean): void => {
|
||||
isWatching && setIsUpdating(isOpen);
|
||||
},
|
||||
[isWatching]
|
||||
);
|
||||
|
||||
const _onRefresh = useCallback(
|
||||
(): void => {
|
||||
optInfo && contract &&
|
||||
Promise
|
||||
.all(
|
||||
messages.map((m) =>
|
||||
m.isMutating || m.args.length !== 0
|
||||
? Promise.resolve(undefined)
|
||||
: contract
|
||||
.query[m.method](READ_ADDR, { gasLimit: -1, value: 0 })
|
||||
.catch((e: Error) => console.error(`contract.query.${m.method}:: ${e.message}`))
|
||||
.then(() => undefined)
|
||||
)
|
||||
)
|
||||
.then(setLastResults)
|
||||
.catch(console.error);
|
||||
},
|
||||
[contract, messages, optInfo]
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
(isUpdating || trigger) && optInfo && contract && _onRefresh();
|
||||
}, [_onRefresh, contract, isUpdating, optInfo, trigger]);
|
||||
|
||||
const _setMessageResult = useCallback(
|
||||
(_messageIndex: number, _result?: ContractCallOutcome): void => {
|
||||
// ignore
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const _onSelect = useCallback(
|
||||
(index: number) => onSelect && onSelect(index, _setMessageResult),
|
||||
[_setMessageResult, onSelect]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Messages ${isLabelled ? 'isLabelled' : ''}`}>
|
||||
{withConstructors && (
|
||||
<Expander summary={t('Constructors ({{count}})', { replace: { count: constructors.length } })}>
|
||||
{sortMessages(constructors).map(([message, index]) => (
|
||||
<Message
|
||||
index={index}
|
||||
key={index}
|
||||
message={message}
|
||||
onSelect={onSelectConstructor}
|
||||
/>
|
||||
))}
|
||||
</Expander>
|
||||
)}
|
||||
{withMessages && (
|
||||
<Expander
|
||||
onClick={_onExpander}
|
||||
summary={t('Messages ({{count}})', { replace: { count: messages.length } })}
|
||||
>
|
||||
{sortMessages(messages).map(([message, index]) => (
|
||||
<Message
|
||||
index={index}
|
||||
key={index}
|
||||
lastResult={lastResults[index]}
|
||||
message={message}
|
||||
onSelect={_onSelect}
|
||||
/>
|
||||
))}
|
||||
</Expander>
|
||||
)}
|
||||
{withWasm && source.wasm.length !== 0 && (
|
||||
<div>{t('{{size}} WASM bytes', { replace: { size: formatNumber(source.wasm.length) } })}</div>
|
||||
)}
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
padding-bottom: 0.75rem !important;
|
||||
|
||||
&.isLabelled {
|
||||
background: var(--bg-input);
|
||||
box-sizing: border-box;
|
||||
border: 1px solid var(--border-input);
|
||||
border-radius: .28571429rem;
|
||||
padding: 1rem 1rem 0.5rem;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Messages);
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-contracts authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { RawParams } from '@pezkuwi/react-params/types';
|
||||
import type { Registry, TypeDef } from '@pezkuwi/types/types';
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import UIParams from '@pezkuwi/react-params';
|
||||
|
||||
interface Props {
|
||||
isDisabled?: boolean;
|
||||
params?: ParamDef[] | null | '';
|
||||
onChange: (values: unknown[]) => void;
|
||||
onEnter?: () => void;
|
||||
registry: Registry;
|
||||
}
|
||||
|
||||
interface ParamDef {
|
||||
name: string;
|
||||
type: TypeDef;
|
||||
}
|
||||
|
||||
function Params ({ isDisabled, onChange, onEnter, params: propParams, registry }: Props): React.ReactElement<Props> | null {
|
||||
const [params, setParams] = useState<ParamDef[]>([]);
|
||||
|
||||
useEffect((): void => {
|
||||
propParams && setParams(propParams);
|
||||
}, [propParams]);
|
||||
|
||||
const _onChange = useCallback(
|
||||
(values: RawParams) => onChange(values.map(({ value }) => value)),
|
||||
[onChange]
|
||||
);
|
||||
|
||||
if (!params.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<UIParams
|
||||
isDisabled={isDisabled}
|
||||
onChange={_onChange}
|
||||
onEnter={onEnter}
|
||||
params={params}
|
||||
registry={registry}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Params);
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2017-2025 @pezkuwi/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export { default as ABI } from './ABI.js';
|
||||
export { default as CodeRow } from './CodeRow.js';
|
||||
export { default as InputMegaGas } from './InputMegaGas.js';
|
||||
export { default as InputName } from './InputName.js';
|
||||
export { default as Messages } from './Messages.js';
|
||||
export { default as MessageSignature } from './MessageSignature.js';
|
||||
export { default as Params } from './Params.js';
|
||||
Reference in New Issue
Block a user