Skip to content

Commit

Permalink
Enhance Preimages Page with Improved Usability (#11342)
Browse files Browse the repository at this point in the history
* enhance: Add user preimages component and update preimage handling

* refactor: user preimages handling and improve UI components

* style: enhance table row styling in Preimage component

* feat: unnote multiple preimages at once

* chore: check if id is not undefined
  • Loading branch information
ap211unitech authored Mar 5, 2025
1 parent 79fe7b0 commit 331fc4b
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 4 deletions.
10 changes: 8 additions & 2 deletions packages/page-preimages/src/Preimages/Preimage.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// Copyright 2017-2025 @polkadot/app-preimages authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { Preimage as TPreimage } from '@polkadot/react-hooks/types';
import type { HexString } from '@polkadot/util/types';

import React from 'react';
import React, { useEffect } from 'react';

import { usePreimage } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/util';
Expand All @@ -15,11 +16,16 @@ import Hash from './Hash.js';
interface Props {
className?: string;
value: HexString;
cb?: (info: TPreimage) => void;
}

function Preimage ({ className, value }: Props): React.ReactElement<Props> {
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} />
Expand Down
29 changes: 28 additions & 1 deletion packages/page-preimages/src/Preimages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
// SPDX-License-Identifier: Apache-2.0

import type { SubmittableExtrinsicFunction } from '@polkadot/api/types';
import type { Preimage as TPreimage } from '@polkadot/react-hooks/types';

import React, { useRef } from 'react';
import React, { useCallback, useMemo, useRef, useState } from 'react';

import { Button, styled, Table } from '@polkadot/react-components';
import { useAccounts } from '@polkadot/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';

Expand All @@ -21,8 +24,30 @@ interface Props {

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'],
Expand All @@ -36,13 +61,15 @@ function Hashes ({ className }: Props): React.ReactElement<Props> {
<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}
/>
Expand Down
149 changes: 149 additions & 0 deletions packages/page-preimages/src/Preimages/userPreimages/Preimage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// Copyright 2017-2025 @polkadot/app-preimages authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { Preimage as TPreimage } from '@polkadot/react-hooks/types';

import React, { useState } from 'react';

import { AddressMini, Checkbox, styled, TxButton } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/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 && <>&nbsp;/&nbsp;{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}>`
.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);
46 changes: 46 additions & 0 deletions packages/page-preimages/src/Preimages/userPreimages/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2017-2025 @polkadot/app-preimages authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { Preimage as TPreimage } from '@polkadot/react-hooks/types';

import React, { useRef } from 'react';

import { Table } from '@polkadot/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);
2 changes: 1 addition & 1 deletion packages/react-hooks/src/useAssetIds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const EMPTY_PARAMS: unknown[] = [];

const OPT_KEY = {
transform: (keys: StorageKey<[u32]>[]): u32[] =>
keys.map(({ args: [id] }) => id)
keys.map(({ args: [id] }) => id).filter((id) => id !== undefined)
};

function filter (records: EventRecord[]): Changes<u32> {
Expand Down

0 comments on commit 331fc4b

Please sign in to comment.