Asset create & list display (#4911)
* Asset create & list display * metadata WIP * ... inputs * Steps * min balance * with balance * Expand display
This commit is contained in:
parent
32fc8b0257
commit
3afc055b3b
@ -10,12 +10,11 @@ export default function create (t: TFunction): Route {
|
||||
return {
|
||||
Component,
|
||||
display: {
|
||||
isHidden: true,
|
||||
needsApi: [
|
||||
'tx.assets.create'
|
||||
'tx.assets.setMetadata'
|
||||
]
|
||||
},
|
||||
group: 'accounts',
|
||||
group: 'network',
|
||||
icon: 'shopping-basket',
|
||||
name: 'assets',
|
||||
text: t('nav.assets', 'Assets', { ns: 'apps-routing' })
|
||||
|
||||
@ -6,6 +6,7 @@ import type { Routes } from './types';
|
||||
|
||||
import accounts from './accounts';
|
||||
import addresses from './addresses';
|
||||
import assets from './assets';
|
||||
import bounties from './bounties';
|
||||
import calendar from './calendar';
|
||||
import claims from './claims';
|
||||
@ -45,6 +46,7 @@ export default function create (t: TFunction): Routes {
|
||||
techcomm(t),
|
||||
parachains(t),
|
||||
gilt(t),
|
||||
assets(t),
|
||||
society(t),
|
||||
calendar(t),
|
||||
contracts(t),
|
||||
|
||||
31
packages/page-assets/src/Overview/Asset.tsx
Normal file
31
packages/page-assets/src/Overview/Asset.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AssetInfo } from './types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { AddressSmall } from '@polkadot/react-components';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
value: AssetInfo;
|
||||
}
|
||||
|
||||
function Asset ({ className, value: { details, id, metadata } }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<tr className={className}>
|
||||
<td className='number'><h1>{formatNumber(id)}</h1></td>
|
||||
<td className='together'>{metadata?.name.toUtf8()}</td>
|
||||
<td className='address'>{details && <AddressSmall value={details.admin} />}</td>
|
||||
<td className='address'>{details && <AddressSmall value={details.issuer} />}</td>
|
||||
<td className='address'>{details && <AddressSmall value={details.freezer} />}</td>
|
||||
<td className='number all'>{formatNumber(metadata?.decimals)}</td>
|
||||
<td className='upper'>{metadata?.symbol.toUtf8()}</td>
|
||||
<td className='button' />
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Asset);
|
||||
49
packages/page-assets/src/Overview/Assets.tsx
Normal file
49
packages/page-assets/src/Overview/Assets.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AssetId } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { Table } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import Asset from './Asset';
|
||||
import useAssetInfos from './useAssetInfos';
|
||||
|
||||
interface Props {
|
||||
assetIds?: AssetId[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Assets ({ assetIds, className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const assetInfos = useAssetInfos(assetIds);
|
||||
|
||||
const headerRef = useRef([
|
||||
[t('assets'), 'start', 2],
|
||||
[t('admin'), 'start'],
|
||||
[t('issuer'), 'start'],
|
||||
[t('freezer'), 'start'],
|
||||
[t('decimals')],
|
||||
[t('symbol'), 'start'],
|
||||
[]
|
||||
]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={assetInfos && t<string>('No assets found')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{assetInfos?.map((info) => (
|
||||
<Asset
|
||||
key={info.key}
|
||||
value={info}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Assets);
|
||||
75
packages/page-assets/src/Overview/Create/Info.tsx
Normal file
75
packages/page-assets/src/Overview/Create/Info.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type BN from 'bn.js';
|
||||
import type { AssetId } from '@polkadot/types/interfaces';
|
||||
import type { InfoState } from './types';
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { InputAddress, InputNumber, Modal } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
import { BN_ZERO } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../../translate';
|
||||
|
||||
interface Props {
|
||||
assetIds: AssetId[];
|
||||
className?: string;
|
||||
onChange: (info: InfoState | null) => void;
|
||||
}
|
||||
|
||||
function Info ({ assetIds, className = '', onChange }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [accountId, setAccountId] = useState<string | null>(null);
|
||||
const [assetId, setAssetId] = useState<BN | null>(null);
|
||||
const [minBalance, setMinBalance] = useState<BN | null>(null);
|
||||
|
||||
const isIdValid = useMemo(
|
||||
() => !!assetId && assetId.gt(BN_ZERO) && !assetIds.some((a) => a.eq(assetId)),
|
||||
[assetId, assetIds]
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
onChange(
|
||||
assetId && accountId && minBalance && isIdValid && !minBalance.isZero()
|
||||
? {
|
||||
accountId,
|
||||
assetId,
|
||||
createTx: api.tx.assets.create(assetId, accountId, minBalance)
|
||||
}
|
||||
: null
|
||||
);
|
||||
}, [api, accountId, assetId, assetIds, isIdValid, minBalance, onChange]);
|
||||
|
||||
return (
|
||||
<Modal.Content className={className}>
|
||||
<Modal.Columns hint={t<string>('The account that is to be used to create this asset and setup the initial metadata.')}>
|
||||
<InputAddress
|
||||
label={t<string>('creator account')}
|
||||
onChange={setAccountId}
|
||||
type='account'
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t<string>('The selected id for the asset. Should not match an already-existing id.')}>
|
||||
<InputNumber
|
||||
autoFocus
|
||||
isError={!isIdValid}
|
||||
isZeroable={false}
|
||||
label={t<string>('asset id')}
|
||||
onChange={setAssetId}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t<string>('The minimum balance for the asset.')}>
|
||||
<InputNumber
|
||||
isZeroable={false}
|
||||
label={t<string>('minimum balance')}
|
||||
onChange={setMinBalance}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Info);
|
||||
78
packages/page-assets/src/Overview/Create/Metadata.tsx
Normal file
78
packages/page-assets/src/Overview/Create/Metadata.tsx
Normal file
@ -0,0 +1,78 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type BN from 'bn.js';
|
||||
import type { MetadataState } from './types';
|
||||
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { Input, InputNumber, Modal } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../../translate';
|
||||
|
||||
interface Props {
|
||||
assetId?: BN | null;
|
||||
className?: string;
|
||||
onChange: (info: MetadataState | null) => void;
|
||||
}
|
||||
|
||||
function Metadata ({ assetId, className = '', onChange }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [assetDecimals, setAssetDecimals] = useState<BN | null>(null);
|
||||
const [assetName, setAssetName] = useState<string | null>(null);
|
||||
const [assetSymbol, setAssetSymbol] = useState<string | null>(null);
|
||||
|
||||
const isValidDecimals = useMemo(
|
||||
() => !!assetDecimals && assetDecimals.lten(20),
|
||||
[assetDecimals]
|
||||
);
|
||||
|
||||
const isValidName = useMemo(
|
||||
() => !!assetName && assetName.length >= 3 && assetName.length <= 32,
|
||||
[assetName]
|
||||
);
|
||||
|
||||
const isValidSymbol = useMemo(
|
||||
() => !!assetSymbol && assetSymbol.length >= 3,
|
||||
[assetSymbol]
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
onChange(
|
||||
assetId && assetName && assetSymbol && assetDecimals && isValidName && isValidSymbol && isValidDecimals
|
||||
? { metadataTx: api.tx.assets.setMetadata(assetId, assetName, assetSymbol, assetDecimals) }
|
||||
: null
|
||||
);
|
||||
}, [api, assetDecimals, assetId, assetName, assetSymbol, isValidName, isValidSymbol, isValidDecimals, onChange]);
|
||||
|
||||
return (
|
||||
<Modal.Content className={className}>
|
||||
<Modal.Columns hint={t<string>('The descriptive name for this asset.')}>
|
||||
<Input
|
||||
autoFocus
|
||||
isError={!isValidName}
|
||||
label={t<string>('asset name')}
|
||||
onChange={setAssetName}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t<string>('The symbol that will represent this asset.')}>
|
||||
<Input
|
||||
isError={!isValidSymbol}
|
||||
label={t<string>('asset symbol')}
|
||||
onChange={setAssetSymbol}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t<string>('The number of decimals for this token. Max allowed via the UI is set to 20.')}>
|
||||
<InputNumber
|
||||
isError={!isValidDecimals}
|
||||
label={t<string>('asset decimals')}
|
||||
onChange={setAssetDecimals}
|
||||
/>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Metadata);
|
||||
68
packages/page-assets/src/Overview/Create/Team.tsx
Normal file
68
packages/page-assets/src/Overview/Create/Team.tsx
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type BN from 'bn.js';
|
||||
import type { TeamState } from './types';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { InputAddress, Modal } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../../translate';
|
||||
|
||||
interface Props {
|
||||
accountId?: string;
|
||||
assetId?: BN;
|
||||
className?: string;
|
||||
onChange: (info: TeamState | null) => void;
|
||||
}
|
||||
|
||||
function Team ({ accountId, assetId, className = '', onChange }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [adminId, setAdminId] = useState<string | null>(null);
|
||||
const [freezerId, setFreezerId] = useState<string | null>(null);
|
||||
const [issuerId, setIssuerId] = useState<string | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
onChange(
|
||||
assetId && accountId && adminId && freezerId && issuerId
|
||||
? adminId !== accountId || freezerId !== accountId || issuerId !== accountId
|
||||
? { teamTx: api.tx.assets.setTeam(assetId, issuerId, adminId, freezerId) }
|
||||
: { teamTx: null }
|
||||
: null
|
||||
);
|
||||
}, [api, assetId, accountId, adminId, freezerId, issuerId, onChange]);
|
||||
|
||||
return (
|
||||
<Modal.Content className={className}>
|
||||
<Modal.Columns hint={t<string>('The account that is to be used for ongoing admin on the token.')}>
|
||||
<InputAddress
|
||||
defaultValue={accountId}
|
||||
label={t<string>('admin account')}
|
||||
onChange={setAdminId}
|
||||
type='account'
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t<string>('The account that is to be used for issuing for this token.')}>
|
||||
<InputAddress
|
||||
defaultValue={accountId}
|
||||
label={t<string>('issuer account')}
|
||||
onChange={setIssuerId}
|
||||
type='account'
|
||||
/>
|
||||
</Modal.Columns>
|
||||
<Modal.Columns hint={t<string>('The account that is to be used for performing freezing.')}>
|
||||
<InputAddress
|
||||
defaultValue={accountId}
|
||||
label={t<string>('freezer account')}
|
||||
onChange={setFreezerId}
|
||||
type='account'
|
||||
/>
|
||||
</Modal.Columns>
|
||||
</Modal.Content>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Team);
|
||||
130
packages/page-assets/src/Overview/Create/index.tsx
Normal file
130
packages/page-assets/src/Overview/Create/index.tsx
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SubmittableExtrinsic } from '@polkadot/api/types';
|
||||
import type { AssetId } from '@polkadot/types/interfaces';
|
||||
import type { InfoState, MetadataState, TeamState } from './types';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { Button, Modal, TxButton } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useStepper, useToggle } from '@polkadot/react-hooks';
|
||||
import { isFunction } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../../translate';
|
||||
import Info from './Info';
|
||||
import Metadata from './Metadata';
|
||||
import Team from './Team';
|
||||
|
||||
interface Props {
|
||||
assetIds?: AssetId[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Create ({ assetIds, className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { hasAccounts } = useAccounts();
|
||||
const [step, nextStep, prevStep] = useStepper();
|
||||
const [isOpen, toggleOpen] = useToggle();
|
||||
const [asset, setAsset] = useState<InfoState | null>(null);
|
||||
const [metadata, setMetadata] = useState<MetadataState | null>(null);
|
||||
const [team, setTeam] = useState<TeamState | null>(null);
|
||||
const [extrinsic, setExtrinsic] = useState<SubmittableExtrinsic<'promise'> | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
setExtrinsic(
|
||||
() => asset && metadata && team
|
||||
? team.teamTx
|
||||
? api.tx.utility.batchAll([asset.createTx, metadata.metadataTx, team.teamTx])
|
||||
: api.tx.utility.batchAll([asset.createTx, metadata.metadataTx])
|
||||
: null
|
||||
);
|
||||
}, [api, asset, metadata, team]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon='plus'
|
||||
isDisabled={!assetIds || !hasAccounts || !isFunction(api.tx.utility.batchAll)}
|
||||
label={t<string>('Create')}
|
||||
onClick={toggleOpen}
|
||||
/>
|
||||
{isOpen && assetIds && (
|
||||
<Modal
|
||||
className={className}
|
||||
header={t<string>('create asset {{step}}/{{steps}}', { replace: { step, steps: 3 } })}
|
||||
size='large'
|
||||
>
|
||||
{step === 1 && (
|
||||
<Info
|
||||
assetIds={assetIds}
|
||||
onChange={setAsset}
|
||||
/>
|
||||
)}
|
||||
{step === 2 && (
|
||||
<Metadata
|
||||
assetId={asset?.assetId}
|
||||
onChange={setMetadata}
|
||||
/>
|
||||
)}
|
||||
{step === 3 && (
|
||||
<Team
|
||||
accountId={asset?.accountId}
|
||||
assetId={asset?.assetId}
|
||||
onChange={setTeam}
|
||||
/>
|
||||
)}
|
||||
<Modal.Actions onCancel={toggleOpen}>
|
||||
{step === 1 &&
|
||||
<Button
|
||||
icon='step-forward'
|
||||
isDisabled={!asset}
|
||||
label={t<string>('Next')}
|
||||
onClick={nextStep}
|
||||
/>
|
||||
}
|
||||
{step === 2 && (
|
||||
<>
|
||||
<Button
|
||||
icon='step-backward'
|
||||
label={t<string>('Prev')}
|
||||
onClick={prevStep}
|
||||
/>
|
||||
<Button
|
||||
icon='step-forward'
|
||||
isDisabled={!metadata}
|
||||
label={t<string>('Next')}
|
||||
onClick={nextStep}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{step === 3 && (
|
||||
<>
|
||||
<Button
|
||||
icon='step-backward'
|
||||
label={t<string>('Prev')}
|
||||
onClick={prevStep}
|
||||
/>
|
||||
<TxButton
|
||||
accountId={asset?.accountId}
|
||||
extrinsic={extrinsic}
|
||||
icon='plus'
|
||||
label={t<string>('Create')}
|
||||
onStart={toggleOpen}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Create)`
|
||||
.stepHidden.content {
|
||||
display: none !important;
|
||||
}
|
||||
`);
|
||||
19
packages/page-assets/src/Overview/Create/types.ts
Normal file
19
packages/page-assets/src/Overview/Create/types.ts
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type BN from 'bn.js';
|
||||
import type { SubmittableExtrinsic } from '@polkadot/api/types';
|
||||
|
||||
export interface InfoState {
|
||||
accountId: string;
|
||||
assetId: BN;
|
||||
createTx: SubmittableExtrinsic<'promise'>;
|
||||
}
|
||||
|
||||
export interface MetadataState {
|
||||
metadataTx: SubmittableExtrinsic<'promise'>;
|
||||
}
|
||||
|
||||
export interface TeamState {
|
||||
teamTx: SubmittableExtrinsic<'promise'> | null;
|
||||
}
|
||||
28
packages/page-assets/src/Overview/Summary.tsx
Normal file
28
packages/page-assets/src/Overview/Summary.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { CardSummary, SummaryBox } from '@polkadot/react-components';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
numAssets?: number;
|
||||
}
|
||||
|
||||
function Summary ({ className, numAssets }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<SummaryBox className={className}>
|
||||
<CardSummary label={t<string>('assets')}>
|
||||
{formatNumber(numAssets)}
|
||||
</CardSummary>
|
||||
</SummaryBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Summary);
|
||||
@ -3,14 +3,27 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Button } from '@polkadot/react-components';
|
||||
|
||||
import Assets from './Assets';
|
||||
import Create from './Create';
|
||||
import Summary from './Summary';
|
||||
import useAssetIds from './useAssetIds';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Overview ({ className }: Props): React.ReactElement<Props> {
|
||||
const assetIds = useAssetIds();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
TODO
|
||||
<Summary numAssets={assetIds?.length} />
|
||||
<Button.Group>
|
||||
<Create assetIds={assetIds} />
|
||||
</Button.Group>
|
||||
<Assets assetIds={assetIds} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
15
packages/page-assets/src/Overview/types.ts
Normal file
15
packages/page-assets/src/Overview/types.ts
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AssetDetails, AssetId, AssetMetadata } from '@polkadot/types/interfaces';
|
||||
|
||||
export interface AssetInfo {
|
||||
details: AssetDetails | null;
|
||||
id: AssetId;
|
||||
isAdmin: boolean;
|
||||
isOwner: boolean;
|
||||
isIssuer: boolean;
|
||||
isFreezer: boolean;
|
||||
key: string;
|
||||
metadata: AssetMetadata | null;
|
||||
}
|
||||
28
packages/page-assets/src/Overview/useAssetIds.ts
Normal file
28
packages/page-assets/src/Overview/useAssetIds.ts
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AssetId } from '@polkadot/types/interfaces';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { useApi, useEventTrigger } from '@polkadot/react-hooks';
|
||||
|
||||
export default function useAssetIds (): AssetId[] | undefined {
|
||||
const { api } = useApi();
|
||||
const trigger = useEventTrigger([api.events.assets.Created, api.events.assets.Destroyed]);
|
||||
const [state, setState] = useState<AssetId[] | undefined>();
|
||||
|
||||
useEffect((): void => {
|
||||
trigger &&
|
||||
api.query.assets.asset
|
||||
.keys()
|
||||
.then((keys) => setState(
|
||||
keys
|
||||
.map(({ args: [assetId] }) => assetId)
|
||||
.sort((a, b) => a.cmp(b))
|
||||
))
|
||||
.catch(console.error);
|
||||
}, [api, trigger]);
|
||||
|
||||
return state;
|
||||
}
|
||||
47
packages/page-assets/src/Overview/useAssetInfos.ts
Normal file
47
packages/page-assets/src/Overview/useAssetInfos.ts
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2017-2021 @polkadot/app-assets authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { AccountId, AssetDetails, AssetId, AssetMetadata } from '@polkadot/types/interfaces';
|
||||
import type { AssetInfo } from './types';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useAccounts, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
function isAccount (allAccounts: string[], accountId: AccountId): boolean {
|
||||
const address = accountId.toString();
|
||||
|
||||
return allAccounts.some((a) => a === address);
|
||||
}
|
||||
|
||||
function extractInfo (allAccounts: string[], id: AssetId, optDetails: Option<AssetDetails>, metadata: AssetMetadata): AssetInfo {
|
||||
const details = optDetails.unwrapOr(null);
|
||||
|
||||
return {
|
||||
details,
|
||||
id,
|
||||
isAdmin: !!details && isAccount(allAccounts, details.admin),
|
||||
isFreezer: !!details && isAccount(allAccounts, details.freezer),
|
||||
isIssuer: !!details && isAccount(allAccounts, details.issuer),
|
||||
isOwner: !!details && isAccount(allAccounts, details.owner),
|
||||
key: id.toString(),
|
||||
metadata: metadata.isEmpty
|
||||
? null
|
||||
: metadata
|
||||
};
|
||||
}
|
||||
|
||||
export default function useAssetInfos (ids?: AssetId[]): AssetInfo[] | undefined {
|
||||
const { api } = useApi();
|
||||
const { allAccounts } = useAccounts();
|
||||
const allMetadata = useCall<AssetMetadata[]>(api.query.assets.metadata.multi, [ids]);
|
||||
const allDetails = useCall<Option<AssetDetails>[]>(api.query.assets.asset.multi, [ids]);
|
||||
|
||||
return useMemo(
|
||||
() => allDetails && allMetadata && ids && (allDetails.length === ids.length) && (allMetadata.length === ids.length)
|
||||
? ids.map((id, index) => extractInfo(allAccounts, id, allDetails[index], allMetadata[index]))
|
||||
: undefined,
|
||||
[allAccounts, allDetails, allMetadata, ids]
|
||||
);
|
||||
}
|
||||
@ -166,6 +166,10 @@ export default React.memo(styled(Body)`
|
||||
}
|
||||
}
|
||||
|
||||
&.upper {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
&.favorite .ui--Icon.isSelected {
|
||||
color: darkorange;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user