mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-06-13 19:51:07 +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,125 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
import type { HashState } from './types.js';
|
||||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
import { InputBalance, Modal, Static } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
import { Extrinsic } from '@pezkuwi/react-params';
|
||||
import { BN_ZERO } from '@pezkuwi/util';
|
||||
import { blake2AsHex } from '@pezkuwi/util-crypto';
|
||||
|
||||
import { useTranslation } from '../../translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
onChange: (state: HashState) => void;
|
||||
}
|
||||
|
||||
const EMPTY_HASH = blake2AsHex('');
|
||||
|
||||
export const EMPTY_PROPOSAL: HashState = {
|
||||
encodedHash: EMPTY_HASH,
|
||||
encodedLength: 0,
|
||||
encodedProposal: null,
|
||||
notePreimageTx: null,
|
||||
storageFee: BN_ZERO
|
||||
};
|
||||
|
||||
function getState (api: ApiPromise, proposal?: SubmittableExtrinsic<'promise'>): HashState {
|
||||
let encodedHash = EMPTY_HASH;
|
||||
let encodedProposal: HexString | null = null;
|
||||
let encodedLength = 0;
|
||||
let notePreimageTx: SubmittableExtrinsic<'promise'> | null = null;
|
||||
let storageFee = BN_ZERO;
|
||||
|
||||
if (proposal) {
|
||||
encodedProposal = proposal.method.toHex();
|
||||
encodedLength = Math.ceil((encodedProposal.length - 2) / 2);
|
||||
encodedHash = blake2AsHex(encodedProposal);
|
||||
notePreimageTx = api.tx.preimage.notePreimage(encodedProposal);
|
||||
|
||||
// we currently don't have a constant exposed, however match to Bizinikiwi
|
||||
storageFee = ((api.consts.preimage?.baseDeposit || BN_ZERO) as unknown as BN).add(
|
||||
((api.consts.preimage?.byteDeposit || BN_ZERO) as unknown as BN).muln(encodedLength)
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
encodedHash,
|
||||
encodedLength,
|
||||
encodedProposal,
|
||||
notePreimageTx,
|
||||
storageFee
|
||||
};
|
||||
}
|
||||
|
||||
function Partial ({ className, onChange }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api, apiDefaultTxSudo } = useApi();
|
||||
const [{ encodedHash, encodedLength, storageFee }, setState] = useState<HashState>(EMPTY_PROPOSAL);
|
||||
|
||||
const changeState = useCallback(
|
||||
(state: HashState): void => {
|
||||
setState(state);
|
||||
onChange(state);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
const setProposal = useCallback(
|
||||
(proposal?: SubmittableExtrinsic<'promise'>) =>
|
||||
changeState(getState(api, proposal)),
|
||||
[api, changeState]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Modal.Columns
|
||||
className={className}
|
||||
hint={
|
||||
<>
|
||||
<p>{t('The image (proposal) will be stored on-chain against the hash of the contents.')}</p>
|
||||
<p>{t('When submitting a proposal the hash needs to be known. Proposals can be submitted with hash-only, but upon dispatch the preimage needs to be available.')}</p>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<Extrinsic
|
||||
defaultValue={apiDefaultTxSudo}
|
||||
label={t('propose')}
|
||||
onChange={setProposal}
|
||||
/>
|
||||
<Static
|
||||
label={t('preimage hash')}
|
||||
value={encodedHash}
|
||||
withCopy
|
||||
/>
|
||||
<Static
|
||||
label={t('preimage length')}
|
||||
value={encodedLength || '0'}
|
||||
withCopy
|
||||
/>
|
||||
</Modal.Columns>
|
||||
{!storageFee.isZero() && (
|
||||
<Modal.Columns
|
||||
className={className}
|
||||
hint={t('The calculated storage costs based on the base and the per-bytes fee.')}
|
||||
>
|
||||
<InputBalance
|
||||
defaultValue={storageFee}
|
||||
isDisabled
|
||||
label={t('calculated storage fee')}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Partial);
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HashState } from './types.js';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { Button, InputAddress, Modal, TxButton } from '@pezkuwi/react-components';
|
||||
import { useToggle } from '@pezkuwi/react-hooks';
|
||||
import { Available } from '@pezkuwi/react-query';
|
||||
|
||||
import { useTranslation } from '../../translate.js';
|
||||
import Proposal, { EMPTY_PROPOSAL } from './Partial.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
imageHash?: string;
|
||||
}
|
||||
|
||||
function Add ({ className, imageHash }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [isAddOpen, toggleAdd] = useToggle();
|
||||
const [accountId, setAccountId] = useState<string | null>(null);
|
||||
const [{ encodedHash, notePreimageTx }, setProposal] = useState<HashState>(EMPTY_PROPOSAL);
|
||||
|
||||
const isMatched = !imageHash || imageHash === encodedHash;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isAddOpen && (
|
||||
<Modal
|
||||
className={className}
|
||||
header={t('Submit preimage')}
|
||||
onClose={toggleAdd}
|
||||
size='large'
|
||||
>
|
||||
<Modal.Content>
|
||||
<Modal.Columns hint={t('This account will pay the fees for the preimage, based on the size thereof.')}>
|
||||
<InputAddress
|
||||
label={t('send from account')}
|
||||
labelExtra={
|
||||
<Available
|
||||
label={<span className='label'>{t('transferable')}</span>}
|
||||
params={accountId}
|
||||
/>
|
||||
}
|
||||
onChange={setAccountId}
|
||||
type='account'
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Proposal onChange={setProposal} />
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
extrinsic={notePreimageTx}
|
||||
icon='plus'
|
||||
isDisabled={!accountId || !isMatched || !notePreimageTx}
|
||||
label={t('Submit preimage')}
|
||||
onStart={toggleAdd}
|
||||
/>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)}
|
||||
<Button
|
||||
icon='plus'
|
||||
label={t('Add preimage')}
|
||||
onClick={toggleAdd}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Add);
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SubmittableExtrinsic } from '@pezkuwi/api/types';
|
||||
import type { BN } from '@pezkuwi/util';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
export interface HashState {
|
||||
encodedHash: HexString;
|
||||
encodedLength: number;
|
||||
encodedProposal?: HexString | null;
|
||||
notePreimageTx?: SubmittableExtrinsic<'promise'> | null;
|
||||
storageFee: BN;
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Preimage } from '@pezkuwi/react-hooks/types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { AddressMini, CopyButton, MarkError, MarkWarning, styled } from '@pezkuwi/react-components';
|
||||
import { ZERO_ACCOUNT } from '@pezkuwi/react-hooks/useWeight';
|
||||
import { CallExpander } from '@pezkuwi/react-params';
|
||||
import { Null } from '@pezkuwi/types-codec';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
noTmp?: boolean;
|
||||
value?: Preimage;
|
||||
}
|
||||
|
||||
function PreimageCall ({ className = '', value }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const link = useMemo(
|
||||
() =>
|
||||
value?.proposal
|
||||
? `#/extrinsics/decode/${value?.proposal?.toHex()}`
|
||||
: null,
|
||||
[value]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<td className={`${className} all`}>
|
||||
{value && value.isCompleted
|
||||
? (
|
||||
<>
|
||||
{value.proposal && (
|
||||
<CallExpander
|
||||
labelHash={t('call')}
|
||||
value={value.proposal}
|
||||
/>
|
||||
)}
|
||||
{link && (
|
||||
<StyledDiv>
|
||||
<a
|
||||
className='isDecoded'
|
||||
href={link}
|
||||
rel='noreferrer'
|
||||
>{link.slice(0, 30)}...</a>
|
||||
<CopyButton value={value.proposal?.toHex()} />
|
||||
</StyledDiv>
|
||||
)}
|
||||
{value.proposalError
|
||||
? <MarkError content={value.proposalError} />
|
||||
: value.proposalWarning
|
||||
? <MarkWarning content={value.proposalWarning} />
|
||||
: null
|
||||
}
|
||||
</>
|
||||
)
|
||||
: <div className='--tmp'>balances.transfer</div>
|
||||
}
|
||||
</td>
|
||||
<td className='address media--1300'>
|
||||
{value && value.isCompleted
|
||||
? value.deposit
|
||||
? (
|
||||
<AddressMini
|
||||
// HACK: In the rare case that the value is passed down as a Null Codec type as seen with Tangle
|
||||
// We ensure to handle that case. ref: https://github.com/pezkuwi-js/apps/issues/10793
|
||||
balance={!(value.deposit.amount instanceof Null) ? value.deposit.amount : undefined}
|
||||
value={value.deposit.who}
|
||||
withBalance
|
||||
/>
|
||||
)
|
||||
: null
|
||||
: (
|
||||
<AddressMini
|
||||
className='--tmp'
|
||||
value={ZERO_ACCOUNT}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: -0.4rem 0rem -0.4rem 1rem;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
export default React.memo(PreimageCall);
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Preimage } from '@pezkuwi/react-hooks/types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { TxButton } from '@pezkuwi/react-components';
|
||||
import { useAccounts, useApi } from '@pezkuwi/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
value: Preimage;
|
||||
}
|
||||
|
||||
function Free ({ className, value: { count, deposit, proposalHash, status } }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { allAccounts } = useAccounts();
|
||||
const isAvailable = useMemo(
|
||||
() => count === 0 && status && status.isUnrequested && deposit && allAccounts.includes(deposit.who),
|
||||
[allAccounts, count, deposit, status]
|
||||
);
|
||||
|
||||
if (!isAvailable || !deposit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<TxButton
|
||||
accountId={deposit.who}
|
||||
className={className}
|
||||
icon='minus'
|
||||
label={t('Unnote')}
|
||||
params={[proposalHash]}
|
||||
tx={api.tx.preimage.unnotePreimage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Free);
|
||||
@@ -0,0 +1,39 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { CopyButton, styled } from '@pezkuwi/react-components';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
value: HexString;
|
||||
}
|
||||
|
||||
function Hash ({ className = '', value }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledTd className={`${className} hash`}>
|
||||
<div className='shortHash'>{value}</div>
|
||||
<CopyButton value={value} />
|
||||
</StyledTd>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledTd = styled.td`
|
||||
white-space: nowrap;
|
||||
|
||||
> div {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.shortHash {
|
||||
+ div {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Hash);
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Preimage as TPreimage } from '@pezkuwi/react-hooks/types';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { usePreimage } from '@pezkuwi/react-hooks';
|
||||
import { formatNumber } from '@pezkuwi/util';
|
||||
|
||||
import Call from './Call.js';
|
||||
import Free from './Free.js';
|
||||
import Hash from './Hash.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
value: HexString;
|
||||
cb?: (info: TPreimage) => void;
|
||||
}
|
||||
|
||||
function Preimage ({ cb, className, value }: Props): React.ReactElement<Props> {
|
||||
const info = usePreimage(value);
|
||||
|
||||
useEffect(() => {
|
||||
info && cb?.(info);
|
||||
}, [cb, info]);
|
||||
|
||||
return (
|
||||
<tr className={className}>
|
||||
<Hash value={value} />
|
||||
<Call value={info} />
|
||||
<td className='number media--1000'>
|
||||
{info?.proposalLength
|
||||
? formatNumber(info.proposalLength)
|
||||
: <span className='--tmp'>999,999</span>}
|
||||
</td>
|
||||
<td className='preimageStatus together media--1200'>
|
||||
{info
|
||||
? (
|
||||
<>
|
||||
{info.status && (<div>{info.status?.type}{info.count !== 0 && <> / {formatNumber(info.count)}</>}</div>)}
|
||||
<Free value={info} />
|
||||
</>
|
||||
)
|
||||
: <span className='--tmp'>Unrequested</span>}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Preimage);
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { CardSummary, SummaryBox } from '@pezkuwi/react-components';
|
||||
import { formatNumber } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
hashes?: string[];
|
||||
}
|
||||
|
||||
function Summary ({ className, hashes }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<SummaryBox className={className}>
|
||||
<CardSummary label={t('images')}>
|
||||
{hashes === undefined
|
||||
? <span className='--tmp'>99</span>
|
||||
: formatNumber(hashes.length)
|
||||
}
|
||||
</CardSummary>
|
||||
</SummaryBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Summary);
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SubmittableExtrinsicFunction } from '@pezkuwi/api/types';
|
||||
import type { Preimage as TPreimage } from '@pezkuwi/react-hooks/types';
|
||||
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { Button, styled, Table } from '@pezkuwi/react-components';
|
||||
import { useAccounts } from '@pezkuwi/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import usePreimages from '../usePreimages.js';
|
||||
import Add from './Add/index.js';
|
||||
import UserPreimages from './userPreimages/index.js';
|
||||
import Preimage from './Preimage.js';
|
||||
import Summary from './Summary.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
defaultPropose?: SubmittableExtrinsicFunction<'promise'>;
|
||||
filter?: (section: string, method?: string) => boolean;
|
||||
}
|
||||
|
||||
function Hashes ({ className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { allAccounts } = useAccounts();
|
||||
const [allPreImagesInfo, setAllPreImagesInfo] = useState<TPreimage[]>([]);
|
||||
const hashes = usePreimages();
|
||||
|
||||
// HACK to concat all preimages info without creating a new hook, just for multiple hashes
|
||||
const onSetAllPreImagesInfo = useCallback((info: TPreimage) => {
|
||||
setAllPreImagesInfo((preimages) => ([
|
||||
...preimages.filter((e) => e.proposalHash !== info.proposalHash),
|
||||
info
|
||||
]));
|
||||
}, []);
|
||||
|
||||
const groupedUserPreimages = useMemo(() => {
|
||||
return allPreImagesInfo.reduce((result: Record<string, TPreimage[]>, current) => {
|
||||
if (current.deposit?.who && allAccounts.includes(current.deposit?.who)) {
|
||||
const newItems = [...(result[current.deposit?.who] || []), current];
|
||||
|
||||
result[current.deposit?.who] = newItems;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {} as Record<string, TPreimage[]>);
|
||||
}, [allAccounts, allPreImagesInfo]);
|
||||
|
||||
const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([
|
||||
[t('preimages'), 'start', 2],
|
||||
[undefined, 'media--1300'],
|
||||
[t('length'), 'media--1000'],
|
||||
[t('status'), 'start media--1200']
|
||||
]);
|
||||
|
||||
return (
|
||||
<StyledDiv className={className}>
|
||||
<Summary hashes={hashes} />
|
||||
<Button.Group>
|
||||
<Add />
|
||||
</Button.Group>
|
||||
<UserPreimages userPreimages={groupedUserPreimages} />
|
||||
<Table
|
||||
className={className}
|
||||
empty={hashes && t('No hashes found')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{hashes?.map((h) => (
|
||||
<Preimage
|
||||
cb={onSetAllPreImagesInfo}
|
||||
key={h}
|
||||
value={h}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
td.preimageStatus {
|
||||
div+.ui--Button {
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Hashes);
|
||||
@@ -0,0 +1,151 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Preimage as TPreimage } from '@pezkuwi/react-hooks/types';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { AddressMini, Checkbox, styled, TxButton } from '@pezkuwi/react-components';
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
import { formatNumber } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../../translate.js';
|
||||
import Call from '../Call.js';
|
||||
import Hash from '../Hash.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
depositor: string,
|
||||
preimageInfos: TPreimage[];
|
||||
}
|
||||
|
||||
interface SelectPreimageProps {
|
||||
proposalHash: TPreimage['proposalHash'],
|
||||
onSelectPreimage: React.Dispatch<React.SetStateAction<TPreimage['proposalHash'][]>>
|
||||
}
|
||||
|
||||
const SelectPreimage = ({ onSelectPreimage, proposalHash }: SelectPreimageProps) => {
|
||||
const [checked, setChecked] = useState(false);
|
||||
|
||||
const onChange = React.useCallback((value: boolean) => {
|
||||
setChecked(value);
|
||||
onSelectPreimage((prevHashes) =>
|
||||
// Add preimage hash if checked else filter it out
|
||||
value ? [...prevHashes, proposalHash] : prevHashes.filter((i) => i !== proposalHash)
|
||||
);
|
||||
}, [onSelectPreimage, proposalHash]);
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
onChange={onChange}
|
||||
value={checked}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const Preimage = ({ className, depositor, preimageInfos }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
|
||||
const [selectedPreimages, onSelectPreimage] = useState<TPreimage['proposalHash'][]>([]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{preimageInfos.map((info, index) => {
|
||||
return (
|
||||
<StyledTr
|
||||
className={`isExpanded ${className}`}
|
||||
isFirstItem={index === 0}
|
||||
isLastItem={false}
|
||||
key={info.proposalHash}
|
||||
>
|
||||
<td
|
||||
className='address all'
|
||||
style={{ paddingTop: 15, verticalAlign: 'top' }}
|
||||
>
|
||||
{index === 0 && <AddressMini value={depositor} />}
|
||||
</td>
|
||||
<Call value={info} />
|
||||
<td style={{ alignItems: 'center', display: 'flex' }}>
|
||||
<SelectPreimage
|
||||
onSelectPreimage={onSelectPreimage}
|
||||
proposalHash={info.proposalHash}
|
||||
/>
|
||||
<Hash value={info.proposalHash} />
|
||||
</td>
|
||||
<td className='number media--1000'>
|
||||
{info?.proposalLength
|
||||
? formatNumber(info.proposalLength)
|
||||
: <span className='--tmp'>999,999</span>}
|
||||
</td>
|
||||
<td className='preimageStatus start media--1100 together'>
|
||||
{info
|
||||
? (
|
||||
<>
|
||||
{info.status && (<div>{info.status?.type}{info.count !== 0 && <> / {formatNumber(info.count)}</>}</div>)}
|
||||
</>
|
||||
)
|
||||
: <span className='--tmp'>Unrequested</span>}
|
||||
</td>
|
||||
</StyledTr>
|
||||
);
|
||||
})}
|
||||
<StyledTr
|
||||
className={`isExpanded ${className}`}
|
||||
isFirstItem={false}
|
||||
isLastItem
|
||||
>
|
||||
<td className='all' />
|
||||
<td className='all' />
|
||||
<td className='media--1300' />
|
||||
<td>
|
||||
<TxButton
|
||||
accountId={depositor}
|
||||
className={className}
|
||||
icon='minus'
|
||||
isToplevel
|
||||
label={t('Unnote')}
|
||||
params={[selectedPreimages.map((i) => api.tx.preimage.unnotePreimage(i))]}
|
||||
tx={api.tx.utility.batchAll}
|
||||
/>
|
||||
</td>
|
||||
<td className='media--1000' />
|
||||
<td className='media--1100' />
|
||||
</StyledTr>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const BASE_BORDER = 0.125;
|
||||
const BORDER_TOP = `${BASE_BORDER * 3}rem solid var(--bg-page)`;
|
||||
const BORDER_RADIUS = `${BASE_BORDER * 4}rem`;
|
||||
|
||||
const StyledTr = styled.tr<{isFirstItem: boolean; isLastItem: boolean}>`
|
||||
background: var(--bg-table);
|
||||
|
||||
.ui--Icon {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
td {
|
||||
border-top: ${(props) => props.isFirstItem && BORDER_TOP};
|
||||
border-radius: 0rem !important;
|
||||
|
||||
&:first-child {
|
||||
padding-block: 1rem !important;
|
||||
border-top-left-radius: ${(props) => props.isFirstItem ? BORDER_RADIUS : '0rem'}!important;
|
||||
border-bottom-left-radius: ${(props) => props.isLastItem ? BORDER_RADIUS : '0rem'}!important;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: ${(props) => props.isFirstItem ? BORDER_RADIUS : '0rem'}!important;
|
||||
border-bottom-right-radius: ${(props) => props.isLastItem ? BORDER_RADIUS : '0rem'}!important;
|
||||
}
|
||||
|
||||
td {
|
||||
border: none !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Preimage);
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Preimage as TPreimage } from '@pezkuwi/react-hooks/types';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { Table } from '@pezkuwi/react-components';
|
||||
|
||||
import { useTranslation } from '../../translate.js';
|
||||
import Preimage from './Preimage.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
userPreimages: Record<string, TPreimage[]>
|
||||
}
|
||||
|
||||
const UserPreimages = ({ className, userPreimages }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([
|
||||
[t('my preimages'), 'start', 2],
|
||||
[undefined, 'media--1300'],
|
||||
[t('hash'), 'start'],
|
||||
[t('length'), 'media--1000'],
|
||||
[t('status'), 'start media--1100']
|
||||
]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={Object.values(userPreimages) && t('No hashes found')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{Object.keys(userPreimages)?.map((depositor) => (
|
||||
<Preimage
|
||||
depositor={depositor}
|
||||
key={depositor}
|
||||
preimageInfos={userPreimages[depositor]}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
export default React.memo(UserPreimages);
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { Tabs } from '@pezkuwi/react-components';
|
||||
|
||||
import Preimages from './Preimages/index.js';
|
||||
import { useTranslation } from './translate.js';
|
||||
|
||||
interface Props {
|
||||
basePath: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function App ({ basePath, className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const tabsRef = useRef([
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'overview',
|
||||
text: t('Overview')
|
||||
}
|
||||
]);
|
||||
|
||||
return (
|
||||
<main className={className}>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
items={tabsRef.current}
|
||||
/>
|
||||
<Preimages />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(App);
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useTranslation as useTranslationBase } from 'react-i18next';
|
||||
|
||||
export function useTranslation (): { t: (key: string, options?: { replace: Record<string, unknown> }) => string } {
|
||||
return useTranslationBase('app-preimages');
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-preimages authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Changes } from '@pezkuwi/react-hooks/useEventChanges';
|
||||
import type { StorageKey } from '@pezkuwi/types';
|
||||
import type { EventRecord, Hash } from '@pezkuwi/types/interfaces';
|
||||
import type { HexString } from '@pezkuwi/util/types';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useEventChanges, useMapKeys } from '@pezkuwi/react-hooks';
|
||||
|
||||
const EMPTY_PARAMS: unknown[] = [];
|
||||
|
||||
const OPT_HASH = {
|
||||
transform: (keys: StorageKey<[Hash]>[]): Hash[] =>
|
||||
keys.map(({ args: [hash] }) => hash)
|
||||
};
|
||||
|
||||
function filter (records: EventRecord[]): Changes<Hash> {
|
||||
const added: Hash[] = [];
|
||||
const removed: Hash[] = [];
|
||||
|
||||
records.forEach(({ event: { data: [hash], method } }): void => {
|
||||
if (method === 'Noted') {
|
||||
added.push(hash as Hash);
|
||||
} else {
|
||||
removed.push(hash as Hash);
|
||||
}
|
||||
});
|
||||
|
||||
return { added, removed };
|
||||
}
|
||||
|
||||
function usePreimagesImpl (): HexString[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValueStatusFor = useMapKeys(api.query.preimage.statusFor, EMPTY_PARAMS, OPT_HASH);
|
||||
const startvalueRequstStatusFor = useMapKeys(api.query.preimage.requestStatusFor, EMPTY_PARAMS, OPT_HASH);
|
||||
const hashes = useEventChanges([
|
||||
api.events.preimage.Cleared,
|
||||
api.events.preimage.Noted
|
||||
], filter, startValueStatusFor?.concat(startvalueRequstStatusFor || []));
|
||||
|
||||
return useMemo(
|
||||
() => hashes?.map((h) => h.toHex()),
|
||||
[hashes]
|
||||
);
|
||||
}
|
||||
|
||||
export default createNamedHook('usePreimages', usePreimagesImpl);
|
||||
Reference in New Issue
Block a user