packages/page-contracts/src/shared/Messages.tsx
// Copyright 2017-2024 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Abi, ContractPromise } from '@polkadot/api-contract';
import type { AbiMessage, ContractCallOutcome } from '@polkadot/api-contract/types';
import type { Option } from '@polkadot/types';
import type { ContractInfo } from '@polkadot/types/interfaces';
import React, { useCallback, useEffect, useState } from 'react';
import { Expander, styled } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/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);