From 331fc4b9a2cb8255733d6e7d374532b6d7681414 Mon Sep 17 00:00:00 2001 From: Arjun Porwal <65214523+ap211unitech@users.noreply.github.com> Date: Wed, 5 Mar 2025 22:52:59 +0530 Subject: [PATCH] Enhance Preimages Page with Improved Usability (#11342) * 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 --- .../page-preimages/src/Preimages/Preimage.tsx | 10 +- .../page-preimages/src/Preimages/index.tsx | 29 +++- .../src/Preimages/userPreimages/Preimage.tsx | 149 ++++++++++++++++++ .../src/Preimages/userPreimages/index.tsx | 46 ++++++ packages/react-hooks/src/useAssetIds.ts | 2 +- 5 files changed, 232 insertions(+), 4 deletions(-) create mode 100644 packages/page-preimages/src/Preimages/userPreimages/Preimage.tsx create mode 100644 packages/page-preimages/src/Preimages/userPreimages/index.tsx diff --git a/packages/page-preimages/src/Preimages/Preimage.tsx b/packages/page-preimages/src/Preimages/Preimage.tsx index 4144228adc..fb9d0bea61 100644 --- a/packages/page-preimages/src/Preimages/Preimage.tsx +++ b/packages/page-preimages/src/Preimages/Preimage.tsx @@ -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'; @@ -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 { +function Preimage ({ cb, className, value }: Props): React.ReactElement { const info = usePreimage(value); + useEffect(() => { + info && cb?.(info); + }, [cb, info]); + return ( diff --git a/packages/page-preimages/src/Preimages/index.tsx b/packages/page-preimages/src/Preimages/index.tsx index 2eacaba67d..c9ed6def42 100644 --- a/packages/page-preimages/src/Preimages/index.tsx +++ b/packages/page-preimages/src/Preimages/index.tsx @@ -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'; @@ -21,8 +24,30 @@ interface Props { function Hashes ({ className }: Props): React.ReactElement { const { t } = useTranslation(); + const { allAccounts } = useAccounts(); + const [allPreImagesInfo, setAllPreImagesInfo] = useState([]); 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, 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); + }, [allAccounts, allPreImagesInfo]); + const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([ [t('preimages'), 'start', 2], [undefined, 'media--1300'], @@ -36,6 +61,7 @@ function Hashes ({ className }: Props): React.ReactElement { + { > {hashes?.map((h) => ( diff --git a/packages/page-preimages/src/Preimages/userPreimages/Preimage.tsx b/packages/page-preimages/src/Preimages/userPreimages/Preimage.tsx new file mode 100644 index 0000000000..ada4f96492 --- /dev/null +++ b/packages/page-preimages/src/Preimages/userPreimages/Preimage.tsx @@ -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> +} + +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 ( + + ); +}; + +const Preimage = ({ className, depositor, preimageInfos }: Props) => { + const { t } = useTranslation(); + const { api } = useApi(); + + const [selectedPreimages, onSelectPreimage] = useState([]); + + return ( + <> + {preimageInfos.map((info, index) => { + return ( + + + + + + + + ); + })} + + +
+ {index === 0 && } + + + + + {info?.proposalLength + ? formatNumber(info.proposalLength) + : 999,999} + + {info + ? ( + <> + {info.status && (
{info.status?.type}{info.count !== 0 && <> / {formatNumber(info.count)}}
)} + + ) + : Unrequested} +
+ + + + api.tx.preimage.unnotePreimage(i))]} + tx={api.tx.utility.batchAll} + /> + + + + + ); +}; + +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); diff --git a/packages/page-preimages/src/Preimages/userPreimages/index.tsx b/packages/page-preimages/src/Preimages/userPreimages/index.tsx new file mode 100644 index 0000000000..3470987263 --- /dev/null +++ b/packages/page-preimages/src/Preimages/userPreimages/index.tsx @@ -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 +} + +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 ( + + {Object.keys(userPreimages)?.map((depositor) => ( + + ))} +
+ ); +}; + +export default React.memo(UserPreimages); diff --git a/packages/react-hooks/src/useAssetIds.ts b/packages/react-hooks/src/useAssetIds.ts index 312a9d0f7a..45f5dc1908 100644 --- a/packages/react-hooks/src/useAssetIds.ts +++ b/packages/react-hooks/src/useAssetIds.ts @@ -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 {