Adjust list layouts (#8905)
* Adjust list layouts
* Generic useCache{Map, Value}
* usePoints with useCacheValue
This commit is contained in:
parent
a9aa22dbe6
commit
57fd733985
105
packages/page-staking2/src/Validators/Active/Entry.tsx
Normal file
105
packages/page-staking2/src/Validators/Active/Entry.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
// Copyright 2017-2023 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SessionInfo, Validator } from '../../types';
|
||||
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
|
||||
import { Table, Tag } from '@polkadot/react-components';
|
||||
import { useToggle } from '@polkadot/react-hooks';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import useExposure from '../useExposure';
|
||||
import useHeartbeat from '../useHeartbeat';
|
||||
import Bottom from './Row/Bottom';
|
||||
import Middle from './Row/Middle';
|
||||
import Top from './Row/Top';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
isRelay: boolean;
|
||||
points?: number;
|
||||
sessionInfo: SessionInfo;
|
||||
toggleFavorite: (stashId: string) => void;
|
||||
validator: Validator;
|
||||
}
|
||||
|
||||
interface PropsExpanded {
|
||||
className?: string;
|
||||
validator: Validator;
|
||||
}
|
||||
|
||||
function EntryExpanded ({ className = '' }: PropsExpanded): React.ReactElement<PropsExpanded> {
|
||||
return <td className={className} />;
|
||||
}
|
||||
|
||||
function Entry ({ className = '', isRelay, points, sessionInfo, toggleFavorite, validator }: Props): React.ReactElement<Props> {
|
||||
const [isExpanded, toggleExpanded] = useToggle();
|
||||
const pointsRef = useRef<{ counter: number, points: number }>({ counter: 0, points: 0 });
|
||||
const exposure = useExposure(validator, sessionInfo);
|
||||
const heartbeat = useHeartbeat(validator, sessionInfo);
|
||||
|
||||
const pointsAnimClass = useMemo(
|
||||
(): string => {
|
||||
if (!points || pointsRef.current.points === points) {
|
||||
return '';
|
||||
}
|
||||
|
||||
pointsRef.current.points = points;
|
||||
pointsRef.current.counter = (pointsRef.current.counter + 1) % 25;
|
||||
|
||||
return `greyAnim-${pointsRef.current.counter}`;
|
||||
},
|
||||
[points, pointsRef]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Top
|
||||
className={className}
|
||||
heartbeat={heartbeat}
|
||||
isExpanded={isExpanded}
|
||||
isRelay={isRelay}
|
||||
toggleExpanded={toggleExpanded}
|
||||
toggleFavorite={toggleFavorite}
|
||||
validator={validator}
|
||||
>
|
||||
{points && (
|
||||
<Tag
|
||||
className={`${pointsAnimClass} absolute`}
|
||||
color='lightgrey'
|
||||
label={formatNumber(points)}
|
||||
/>
|
||||
)}
|
||||
</Top>
|
||||
<Middle
|
||||
className={className}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<Table.Column.Balance
|
||||
className='relative'
|
||||
label={
|
||||
exposure?.waiting && (
|
||||
<>
|
||||
<span>(</span>
|
||||
<FormatBalance value={exposure.waiting.total} />
|
||||
<span>, {exposure.waiting.others.length})</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
value={exposure?.clipped?.total}
|
||||
withLoading
|
||||
/>
|
||||
</Middle>
|
||||
<Bottom
|
||||
className={className}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<EntryExpanded validator={validator} />
|
||||
</Bottom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Entry);
|
||||
@ -2,103 +2,55 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SessionInfo, Validator } from '../../types';
|
||||
import type { UsePoints } from '../types';
|
||||
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { Table, Tag } from '@polkadot/react-components';
|
||||
import { useToggle } from '@polkadot/react-hooks';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
import Legend from '@polkadot/app-staking/Legend';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useNextTick } from '@polkadot/react-hooks';
|
||||
|
||||
import Bottom from './Row/Bottom';
|
||||
import Middle from './Row/Middle';
|
||||
import Top from './Row/Top';
|
||||
import useExposure from './useExposure';
|
||||
import useHeartbeat from './useHeartbeat';
|
||||
import { useTranslation } from '../../translate';
|
||||
import Entry from './Entry';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
isRelay: boolean;
|
||||
points?: number;
|
||||
points?: UsePoints;
|
||||
sessionInfo: SessionInfo;
|
||||
toggleFavorite: (stashId: string) => void;
|
||||
validator: Validator;
|
||||
validatorsActive?: Validator[];
|
||||
}
|
||||
|
||||
interface PropsExpanded {
|
||||
className?: string;
|
||||
validator: Validator;
|
||||
}
|
||||
function Active ({ className = '', isRelay, points, sessionInfo, toggleFavorite, validatorsActive }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const isNextTick = useNextTick();
|
||||
|
||||
function ActiveExpanded ({ className = '' }: PropsExpanded): React.ReactElement<PropsExpanded> {
|
||||
return <td className={className} />;
|
||||
}
|
||||
|
||||
function Active ({ className = '', isRelay, points, sessionInfo, toggleFavorite, validator }: Props): React.ReactElement<Props> {
|
||||
const [isExpanded, toggleExpanded] = useToggle();
|
||||
const pointsRef = useRef<{ counter: number, points: number }>({ counter: 0, points: 0 });
|
||||
const exposure = useExposure(validator, sessionInfo);
|
||||
const heartbeat = useHeartbeat(validator, sessionInfo);
|
||||
|
||||
const pointsAnimClass = useMemo(
|
||||
(): string => {
|
||||
if (!points || pointsRef.current.points === points) {
|
||||
return '';
|
||||
}
|
||||
|
||||
pointsRef.current.points = points;
|
||||
pointsRef.current.counter = (pointsRef.current.counter + 1) % 25;
|
||||
|
||||
return `greyAnim-${pointsRef.current.counter}`;
|
||||
},
|
||||
[points, pointsRef]
|
||||
);
|
||||
const header = useRef<[string?, string?, number?][]>([
|
||||
// favorite, badges, details, expand
|
||||
[t<string>('validators'), 'start', 4]
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Top
|
||||
className={className}
|
||||
heartbeat={heartbeat}
|
||||
isExpanded={isExpanded}
|
||||
isRelay={isRelay}
|
||||
toggleExpanded={toggleExpanded}
|
||||
toggleFavorite={toggleFavorite}
|
||||
validator={validator}
|
||||
>
|
||||
{points && (
|
||||
<Tag
|
||||
className={`${pointsAnimClass} absolute`}
|
||||
color='lightgrey'
|
||||
label={formatNumber(points)}
|
||||
/>
|
||||
)}
|
||||
</Top>
|
||||
<Middle
|
||||
className={className}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<Table.Column.Balance
|
||||
className='relative'
|
||||
label={
|
||||
exposure?.waiting && (
|
||||
<>
|
||||
<span>(</span>
|
||||
<FormatBalance value={exposure.waiting.total} />
|
||||
<span>, {exposure.waiting.others.length})</span>
|
||||
</>
|
||||
)
|
||||
}
|
||||
value={exposure?.clipped?.total}
|
||||
withLoading
|
||||
<Table
|
||||
className={className}
|
||||
empty={isNextTick && validatorsActive && t<string>('No session validators found')}
|
||||
emptySpinner={t<string>('Retrieving session validators')}
|
||||
header={header.current}
|
||||
isSplit
|
||||
legend={<Legend isRelay={isRelay} />}
|
||||
>
|
||||
{isNextTick && validatorsActive?.map((v) => (
|
||||
<Entry
|
||||
isRelay={isRelay}
|
||||
key={v.key}
|
||||
points={points?.[v.stashId]}
|
||||
sessionInfo={sessionInfo}
|
||||
toggleFavorite={toggleFavorite}
|
||||
validator={v}
|
||||
/>
|
||||
</Middle>
|
||||
<Bottom
|
||||
className={className}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<ActiveExpanded validator={validator} />
|
||||
</Bottom>
|
||||
</>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -1,57 +0,0 @@
|
||||
// Copyright 2017-2023 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SessionInfo, Validator } from '../types';
|
||||
import type { UsePoints } from './types';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import Legend from '@polkadot/app-staking/Legend';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useNextTick } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import Active from './Active';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
isRelay: boolean;
|
||||
points?: UsePoints;
|
||||
sessionInfo: SessionInfo;
|
||||
toggleFavorite: (stashId: string) => void;
|
||||
validatorsActive?: Validator[];
|
||||
}
|
||||
|
||||
function ListActive ({ className = '', isRelay, points, sessionInfo, toggleFavorite, validatorsActive }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const isNextTick = useNextTick();
|
||||
|
||||
const header = useRef<[string?, string?, number?][]>([
|
||||
// favorite, badges, details, expand
|
||||
[t<string>('validators'), 'start', 4]
|
||||
]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={isNextTick && validatorsActive && t<string>('No session validators found')}
|
||||
emptySpinner={t<string>('Retrieving session validators')}
|
||||
header={header.current}
|
||||
isSplit
|
||||
legend={<Legend isRelay={isRelay} />}
|
||||
>
|
||||
{isNextTick && validatorsActive?.map((v) => (
|
||||
<Active
|
||||
isRelay={isRelay}
|
||||
key={v.key}
|
||||
points={points?.[v.stashId]}
|
||||
sessionInfo={sessionInfo}
|
||||
toggleFavorite={toggleFavorite}
|
||||
validator={v}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ListActive);
|
||||
@ -1,55 +0,0 @@
|
||||
// Copyright 2017-2023 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { SessionInfo, Validator } from '../types';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import Legend from '@polkadot/app-staking/Legend';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useNextTick } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import useValidatorsWaiting from '../useValidatorsWaiting';
|
||||
import Waiting from './Waiting';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
favorites: string[];
|
||||
isRelay: boolean;
|
||||
sessionInfo: SessionInfo;
|
||||
toggleFavorite: (stashId: string) => void;
|
||||
validatorsActive?: Validator[];
|
||||
}
|
||||
|
||||
function ListActive ({ className = '', favorites, isRelay, sessionInfo, toggleFavorite, validatorsActive }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const isNextTick = useNextTick();
|
||||
const validatorsWaiting = useValidatorsWaiting(favorites, sessionInfo, validatorsActive);
|
||||
|
||||
const header = useRef<[string?, string?, number?][]>([
|
||||
// favorite, badges, details, expand
|
||||
[t<string>('waiting'), 'start', 4]
|
||||
]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
className={className}
|
||||
empty={isNextTick && validatorsWaiting && t<string>('No waiting validators found')}
|
||||
emptySpinner={t<string>('Retrieving waiting validators')}
|
||||
header={header.current}
|
||||
isSplit
|
||||
legend={<Legend isRelay={isRelay} />}
|
||||
>
|
||||
{isNextTick && validatorsWaiting?.map((v) => (
|
||||
<Waiting
|
||||
key={v.key}
|
||||
toggleFavorite={toggleFavorite}
|
||||
validator={v}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ListActive);
|
||||
57
packages/page-staking2/src/Validators/Waiting/Entry.tsx
Normal file
57
packages/page-staking2/src/Validators/Waiting/Entry.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2017-2023 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Validator } from '../../types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useToggle } from '@polkadot/react-hooks';
|
||||
|
||||
import Bottom from '../Active/Row/Bottom';
|
||||
import Middle from '../Active/Row/Middle';
|
||||
import Top from '../Active/Row/Top';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
toggleFavorite: (stashId: string) => void;
|
||||
validator: Validator;
|
||||
}
|
||||
|
||||
interface PropsExpanded {
|
||||
className?: string;
|
||||
validator: Validator;
|
||||
}
|
||||
|
||||
function EntryExpanded ({ className = '' }: PropsExpanded): React.ReactElement<PropsExpanded> {
|
||||
return <td className={className} />;
|
||||
}
|
||||
|
||||
function Entry ({ className = '', toggleFavorite, validator }: Props): React.ReactElement<Props> {
|
||||
const [isExpanded, toggleExpanded] = useToggle();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Top
|
||||
className={className}
|
||||
isExpanded={isExpanded}
|
||||
toggleExpanded={toggleExpanded}
|
||||
toggleFavorite={toggleFavorite}
|
||||
validator={validator}
|
||||
/>
|
||||
<Middle
|
||||
className={className}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<td />
|
||||
</Middle>
|
||||
<Bottom
|
||||
className={className}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<EntryExpanded validator={validator} />
|
||||
</Bottom>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Entry);
|
||||
@ -1,56 +1,54 @@
|
||||
// Copyright 2017-2023 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Validator } from '../../types';
|
||||
import type { SessionInfo, Validator } from '../../types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { useToggle } from '@polkadot/react-hooks';
|
||||
import Legend from '@polkadot/app-staking/Legend';
|
||||
import { Table } from '@polkadot/react-components';
|
||||
import { useNextTick } from '@polkadot/react-hooks';
|
||||
|
||||
import Bottom from '../Active/Row/Bottom';
|
||||
import Middle from '../Active/Row/Middle';
|
||||
import Top from '../Active/Row/Top';
|
||||
import { useTranslation } from '../../translate';
|
||||
import useValidatorsWaiting from '../../useValidatorsWaiting';
|
||||
import Entry from './Entry';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
favorites: string[];
|
||||
isRelay: boolean;
|
||||
sessionInfo: SessionInfo;
|
||||
toggleFavorite: (stashId: string) => void;
|
||||
validator: Validator;
|
||||
validatorsActive?: Validator[];
|
||||
}
|
||||
|
||||
interface PropsExpanded {
|
||||
className?: string;
|
||||
validator: Validator;
|
||||
}
|
||||
function Waiting ({ className = '', favorites, isRelay, sessionInfo, toggleFavorite, validatorsActive }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const isNextTick = useNextTick();
|
||||
const validatorsWaiting = useValidatorsWaiting(favorites, sessionInfo, validatorsActive);
|
||||
|
||||
function WaitingExpanded ({ className = '' }: PropsExpanded): React.ReactElement<PropsExpanded> {
|
||||
return <td className={className} />;
|
||||
}
|
||||
|
||||
function Waiting ({ className = '', toggleFavorite, validator }: Props): React.ReactElement<Props> {
|
||||
const [isExpanded, toggleExpanded] = useToggle();
|
||||
const header = useRef<[string?, string?, number?][]>([
|
||||
// favorite, badges, details, expand
|
||||
[t<string>('waiting'), 'start', 4]
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Top
|
||||
className={className}
|
||||
isExpanded={isExpanded}
|
||||
toggleExpanded={toggleExpanded}
|
||||
toggleFavorite={toggleFavorite}
|
||||
validator={validator}
|
||||
/>
|
||||
<Middle
|
||||
className={className}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<td />
|
||||
</Middle>
|
||||
<Bottom
|
||||
className={className}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
<WaitingExpanded validator={validator} />
|
||||
</Bottom>
|
||||
</>
|
||||
<Table
|
||||
className={className}
|
||||
empty={isNextTick && validatorsWaiting && t<string>('No waiting validators found')}
|
||||
emptySpinner={t<string>('Retrieving waiting validators')}
|
||||
header={header.current}
|
||||
isSplit
|
||||
legend={<Legend isRelay={isRelay} />}
|
||||
>
|
||||
{isNextTick && validatorsWaiting?.map((v) => (
|
||||
<Entry
|
||||
key={v.key}
|
||||
toggleFavorite={toggleFavorite}
|
||||
validator={v}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -10,9 +10,9 @@ import { Button, ToggleGroup } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import useValidatorsActive from '../useValidatorsActive';
|
||||
import ListActive from './ListActive';
|
||||
import ListWaiting from './ListWaiting';
|
||||
import Active from './Active';
|
||||
import usePoints from './usePoints';
|
||||
import Waiting from './Waiting';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@ -44,7 +44,7 @@ function Validators ({ className = '', favorites, isRelay, sessionInfo, toggleFa
|
||||
</Button.Group>
|
||||
{intentIndex === 0
|
||||
? (
|
||||
<ListActive
|
||||
<Active
|
||||
isRelay={isRelay}
|
||||
points={points}
|
||||
sessionInfo={sessionInfo}
|
||||
@ -53,7 +53,7 @@ function Validators ({ className = '', favorites, isRelay, sessionInfo, toggleFa
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<ListWaiting
|
||||
<Waiting
|
||||
favorites={favorites}
|
||||
isRelay={isRelay}
|
||||
sessionInfo={sessionInfo}
|
||||
|
||||
@ -2,19 +2,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PalletStakingExposure } from '@polkadot/types/lookup';
|
||||
import type { SessionInfo, Validator } from '../../types';
|
||||
import type { UseExposure, UseExposureExposure } from '../types';
|
||||
import type { SessionInfo, Validator } from '../types';
|
||||
import type { UseExposure, UseExposureExposure } from './types';
|
||||
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { BN, objectSpread } from '@polkadot/util';
|
||||
import { BN } from '@polkadot/util';
|
||||
|
||||
interface CacheEntry extends UseExposure {
|
||||
activeEra: BN;
|
||||
}
|
||||
|
||||
type Cache = Record<string, CacheEntry>;
|
||||
import { useCacheMap } from '../useCacheMap';
|
||||
|
||||
const OPT_EXPOSURE = {
|
||||
transform: ({ others, own, total }: PalletStakingExposure): UseExposureExposure => ({
|
||||
@ -48,8 +44,6 @@ function getResult (exposure: UseExposureExposure, clipped: UseExposureExposure)
|
||||
return { clipped, exposure, waiting };
|
||||
}
|
||||
|
||||
const cache: Cache = {};
|
||||
|
||||
function useExposureImpl ({ stashId }: Validator, { activeEra }: SessionInfo): UseExposure | undefined {
|
||||
const { api } = useApi();
|
||||
|
||||
@ -66,21 +60,7 @@ function useExposureImpl ({ stashId }: Validator, { activeEra }: SessionInfo): U
|
||||
[clipExposure, fullExposure]
|
||||
);
|
||||
|
||||
// clear the cached result on era changes
|
||||
useEffect((): void => {
|
||||
if (activeEra && (!cache[stashId] || !activeEra.eq(cache[stashId].activeEra))) {
|
||||
cache[stashId] = { activeEra };
|
||||
}
|
||||
}, [activeEra, stashId]);
|
||||
|
||||
// update the cached result on exposure changes
|
||||
useEffect((): void => {
|
||||
if (result && activeEra && cache[stashId] && activeEra.eq(cache[stashId].activeEra)) {
|
||||
cache[stashId] = objectSpread<CacheEntry>({ activeEra }, result);
|
||||
}
|
||||
}, [activeEra, result, stashId]);
|
||||
|
||||
return result || cache[stashId];
|
||||
return useCacheMap('useExposure', stashId, activeEra, result);
|
||||
}
|
||||
|
||||
export default createNamedHook('useExposure', useExposureImpl);
|
||||
@ -3,20 +3,15 @@
|
||||
|
||||
import type { Option, u32 } from '@polkadot/types';
|
||||
import type { Codec } from '@polkadot/types/types';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { SessionInfo, Validator } from '../../types';
|
||||
import type { UseHeartbeat } from '../types';
|
||||
import type { SessionInfo, Validator } from '../types';
|
||||
import type { UseHeartbeat } from './types';
|
||||
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { isBoolean, isNumber, objectSpread } from '@polkadot/util';
|
||||
import { isBoolean, isNumber } from '@polkadot/util';
|
||||
|
||||
interface CacheEntry extends UseHeartbeat {
|
||||
currentSession: BN;
|
||||
}
|
||||
|
||||
type Cache = Record<string, CacheEntry>;
|
||||
import { useCacheMap } from '../useCacheMap';
|
||||
|
||||
const EMPTY: UseHeartbeat = {};
|
||||
|
||||
@ -31,8 +26,6 @@ const OPT_BEATS = {
|
||||
receivedHeartbeats.isSome
|
||||
};
|
||||
|
||||
const cache: Cache = {};
|
||||
|
||||
function useHeartbeatImpl ({ stashId, stashIndex }: Validator, { currentSession }: SessionInfo): UseHeartbeat {
|
||||
const { api } = useApi();
|
||||
|
||||
@ -57,21 +50,7 @@ function useHeartbeatImpl ({ stashId, stashIndex }: Validator, { currentSession
|
||||
[authoredBlocks, receivedHeartbeats]
|
||||
);
|
||||
|
||||
// when the session changes, empty the cache
|
||||
useEffect((): void => {
|
||||
if (currentSession && (!cache[stashId] || !currentSession.eq(cache[stashId].currentSession))) {
|
||||
cache[stashId] = { currentSession };
|
||||
}
|
||||
}, [currentSession, stashId]);
|
||||
|
||||
// set the new heartbeat info based on retrieved
|
||||
useEffect((): void => {
|
||||
if (result && currentSession && currentSession.eq(cache[stashId].currentSession)) {
|
||||
cache[stashId] = objectSpread<CacheEntry>({ currentSession }, result);
|
||||
}
|
||||
}, [currentSession, result, stashId]);
|
||||
|
||||
return result || cache[stashId] || EMPTY;
|
||||
return useCacheMap('useHeartbeat', stashId, currentSession, result) || EMPTY;
|
||||
}
|
||||
|
||||
export default createNamedHook('useHeartbeat', useHeartbeatImpl);
|
||||
@ -2,19 +2,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PalletStakingEraRewardPoints } from '@polkadot/types/lookup';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { SessionInfo } from '../types';
|
||||
import type { UsePoints } from './types';
|
||||
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { BN_ZERO } from '@polkadot/util';
|
||||
|
||||
interface Cache {
|
||||
activeEra: BN;
|
||||
points?: UsePoints;
|
||||
}
|
||||
import { useCacheValue } from '../useCacheValue';
|
||||
|
||||
const OPT_POINTS = {
|
||||
transform: ({ individual }: PalletStakingEraRewardPoints): UsePoints =>
|
||||
@ -27,8 +23,6 @@ const OPT_POINTS = {
|
||||
}, {})
|
||||
};
|
||||
|
||||
let cache: Cache | undefined;
|
||||
|
||||
function usePointsImpl ({ activeEra }: SessionInfo): UsePoints | undefined {
|
||||
const { api } = useApi();
|
||||
|
||||
@ -39,17 +33,7 @@ function usePointsImpl ({ activeEra }: SessionInfo): UsePoints | undefined {
|
||||
|
||||
const points = useCall(queryParams && api.query.staking.erasRewardPoints, queryParams, OPT_POINTS);
|
||||
|
||||
useEffect((): void => {
|
||||
if (activeEra && points) {
|
||||
cache = { activeEra, points };
|
||||
}
|
||||
}, [activeEra, points]);
|
||||
|
||||
return points || (
|
||||
cache &&
|
||||
activeEra?.eq(cache.activeEra) &&
|
||||
cache.points
|
||||
) || undefined;
|
||||
return useCacheValue('usePoints', activeEra, points);
|
||||
}
|
||||
|
||||
export default createNamedHook('usePoints', usePointsImpl);
|
||||
|
||||
@ -3,6 +3,11 @@
|
||||
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
export interface CacheValue<T> {
|
||||
check: BN;
|
||||
value?: T;
|
||||
}
|
||||
|
||||
export interface SessionInfo {
|
||||
activeEra?: BN | null;
|
||||
currentEra?: BN | null;
|
||||
|
||||
34
packages/page-staking2/src/useCacheMap.ts
Normal file
34
packages/page-staking2/src/useCacheMap.ts
Normal file
@ -0,0 +1,34 @@
|
||||
// Copyright 2017-2023 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { CacheValue } from './types';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { getResultValue } from './useCacheValue';
|
||||
|
||||
type CacheSection = `use${'Exposure' | 'Heartbeat'}`;
|
||||
|
||||
const cache: Record<CacheSection, Record<string, CacheValue<any>>> = {
|
||||
useExposure: {},
|
||||
useHeartbeat: {}
|
||||
};
|
||||
|
||||
export function useCacheMap <T> (section: CacheSection, id: string, check?: BN | null, value?: T): T | undefined {
|
||||
// ensure that the specific id has an entry
|
||||
useEffect((): void => {
|
||||
if (check && (!cache[section][id] || !cache[section][id].check.eq(check))) {
|
||||
cache[section][id] = { check };
|
||||
}
|
||||
}, [check, id, section]);
|
||||
|
||||
// update the cached result on value changes
|
||||
useEffect((): void => {
|
||||
if (value && check && cache[section][id] && cache[section][id].check.eq(check)) {
|
||||
cache[section][id] = { check, value };
|
||||
}
|
||||
}, [check, id, section, value]);
|
||||
|
||||
return getResultValue(cache[section][id] as CacheValue<T>, check, value);
|
||||
}
|
||||
45
packages/page-staking2/src/useCacheValue.ts
Normal file
45
packages/page-staking2/src/useCacheValue.ts
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2017-2023 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { CacheValue } from './types';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { BN } from '@polkadot/util';
|
||||
|
||||
type CacheSection = `use${'ElectedValidators' | 'Points' | 'ValidatorsActive' | 'ValidatorsAll' | 'ValidatorsWaiting'}`;
|
||||
|
||||
const cache: Record<CacheSection, CacheValue<any>> = {
|
||||
useElectedValidators: { check: new BN(-1) },
|
||||
usePoints: { check: new BN(-1) },
|
||||
useValidatorsActive: { check: new BN(-1) },
|
||||
useValidatorsAll: { check: new BN(-1) },
|
||||
useValidatorsWaiting: { check: new BN(-1) }
|
||||
};
|
||||
|
||||
export function getResultValue <T> (cached?: CacheValue<T>, check?: BN | null, value?: T): T | undefined {
|
||||
return value || (
|
||||
check &&
|
||||
cached &&
|
||||
cached.check.eq(check) &&
|
||||
cached.value
|
||||
) || undefined;
|
||||
}
|
||||
|
||||
export function useCacheValue <T> (section: CacheSection, check?: BN | null, value?: T): T | undefined {
|
||||
// ensure that the specific id has an entry
|
||||
useEffect((): void => {
|
||||
if (check && !cache[section].check.eq(check)) {
|
||||
cache[section] = { check };
|
||||
}
|
||||
}, [check, section]);
|
||||
|
||||
// update the cached result on value changes
|
||||
useEffect((): void => {
|
||||
if (value && check && cache[section].check.eq(check)) {
|
||||
cache[section] = { check, value };
|
||||
}
|
||||
}, [check, section, value]);
|
||||
|
||||
return getResultValue(cache[section] as CacheValue<T>, check, value);
|
||||
}
|
||||
@ -3,17 +3,13 @@
|
||||
|
||||
import type { StorageKey, u32 } from '@polkadot/types';
|
||||
import type { AccountId32 } from '@polkadot/types/interfaces';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { SessionInfo } from './types';
|
||||
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useMapKeys } from '@polkadot/react-hooks';
|
||||
|
||||
interface Cache {
|
||||
currentEra: BN;
|
||||
elected: string[];
|
||||
}
|
||||
import { useCacheValue } from './useCacheValue';
|
||||
|
||||
const OPT_ELECTED = {
|
||||
transform: (keys: StorageKey<[u32, AccountId32]>[]): string[] =>
|
||||
@ -22,33 +18,17 @@ const OPT_ELECTED = {
|
||||
)
|
||||
};
|
||||
|
||||
// since this would only change once per era for the full runtime,
|
||||
// we just store it globally to not have expensive map-key lookups
|
||||
let cache: Cache | undefined;
|
||||
|
||||
function useElectedValidatorsImpl ({ currentEra }: SessionInfo): string[] | undefined {
|
||||
const { api } = useApi();
|
||||
|
||||
const electedParams = useMemo(
|
||||
() => currentEra && (!cache || !cache.currentEra.eq(currentEra))
|
||||
? [currentEra]
|
||||
: null,
|
||||
() => currentEra && [currentEra],
|
||||
[currentEra]
|
||||
);
|
||||
|
||||
const elected = useMapKeys(electedParams && api.query.staking.erasStakers, electedParams, OPT_ELECTED);
|
||||
|
||||
useEffect((): void => {
|
||||
if (currentEra && elected) {
|
||||
cache = { currentEra, elected };
|
||||
}
|
||||
}, [currentEra, elected]);
|
||||
|
||||
return elected || (
|
||||
cache &&
|
||||
currentEra?.eq(cache.currentEra) &&
|
||||
cache.elected
|
||||
) || undefined;
|
||||
return useCacheValue('useElectedValidators', currentEra, elected);
|
||||
}
|
||||
|
||||
export default createNamedHook('useElectedValidators', useElectedValidatorsImpl);
|
||||
|
||||
@ -2,20 +2,13 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { AccountId32 } from '@polkadot/types/interfaces';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { SessionInfo, Validator } from './types';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
import { useCacheValue } from './useCacheValue';
|
||||
import useTaggedValidators from './useTaggedValidators';
|
||||
|
||||
interface Cache {
|
||||
activeEra: BN;
|
||||
tagged: Validator[];
|
||||
}
|
||||
|
||||
const OPT_VALIDATORS = {
|
||||
transform: (validators: AccountId32[]): Validator[] =>
|
||||
validators.map((a, stashIndex) => {
|
||||
@ -32,24 +25,12 @@ const OPT_VALIDATORS = {
|
||||
})
|
||||
};
|
||||
|
||||
let cache: Cache | undefined;
|
||||
|
||||
function useValidatorsActiveImpl (favorites: string[], sessionInfo: SessionInfo): Validator[] | undefined {
|
||||
const { api } = useApi();
|
||||
const sessionValidators = useCall(api.query.session.validators, undefined, OPT_VALIDATORS);
|
||||
const tagged = useTaggedValidators(favorites, sessionInfo, sessionValidators);
|
||||
|
||||
useEffect((): void => {
|
||||
if (tagged && sessionInfo.activeEra) {
|
||||
cache = { activeEra: sessionInfo.activeEra, tagged };
|
||||
}
|
||||
}, [sessionInfo, tagged]);
|
||||
|
||||
return tagged || (
|
||||
cache &&
|
||||
sessionInfo.activeEra?.eq(cache.activeEra) &&
|
||||
cache.tagged
|
||||
) || undefined;
|
||||
return useCacheValue('useValidatorsActive', sessionInfo.activeEra, tagged);
|
||||
}
|
||||
|
||||
export default createNamedHook('useValidatorsActive', useValidatorsActiveImpl);
|
||||
|
||||
@ -6,10 +6,11 @@ import type { StorageKey } from '@polkadot/types';
|
||||
import type { AccountId32, EventRecord } from '@polkadot/types/interfaces';
|
||||
import type { SessionInfo, Validator } from './types';
|
||||
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useEventChanges, useMapKeys } from '@polkadot/react-hooks';
|
||||
|
||||
import { useCacheValue } from './useCacheValue';
|
||||
import useTaggedValidators from './useTaggedValidators';
|
||||
|
||||
const EMPTY_PARAMS: unknown[] = [];
|
||||
@ -49,8 +50,6 @@ function mapValidators (validators?: AccountId32[]): Validator[] | undefined {
|
||||
});
|
||||
}
|
||||
|
||||
let cache: Validator[] | undefined;
|
||||
|
||||
function useValidatorsAllImpl (favorites: string[], sessionInfo: SessionInfo): Validator[] | undefined {
|
||||
const { api } = useApi();
|
||||
const startValue = useMapKeys(api.query.staking.validators, EMPTY_PARAMS, OPT_VALIDATORS);
|
||||
@ -66,13 +65,7 @@ function useValidatorsAllImpl (favorites: string[], sessionInfo: SessionInfo): V
|
||||
|
||||
const tagged = useTaggedValidators(favorites, sessionInfo, validatorsIndexed);
|
||||
|
||||
useEffect((): void => {
|
||||
if (tagged) {
|
||||
cache = tagged;
|
||||
}
|
||||
}, [tagged, validatorsIndexed]);
|
||||
|
||||
return tagged || cache;
|
||||
return useCacheValue('useValidatorsAll', sessionInfo.currentEra, tagged);
|
||||
}
|
||||
|
||||
export default createNamedHook('useValidatorsAll', useValidatorsAllImpl);
|
||||
|
||||
@ -1,28 +1,21 @@
|
||||
// Copyright 2017-2023 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { SessionInfo, Validator } from './types';
|
||||
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createNamedHook } from '@polkadot/react-hooks';
|
||||
|
||||
import { useCacheValue } from './useCacheValue';
|
||||
import useValidatorsAll from './useValidatorsAll';
|
||||
|
||||
interface Cache {
|
||||
activeEra: BN;
|
||||
tagged: Validator[];
|
||||
}
|
||||
|
||||
function excludeValidators (from: Validator[], exclude: Validator[]): Validator[] {
|
||||
return from.filter(({ stashId }) =>
|
||||
!exclude.some((v) => v.stashId === stashId)
|
||||
);
|
||||
}
|
||||
|
||||
let cache: Cache | undefined;
|
||||
|
||||
function useValidatorsWaitingImpl (favorites: string[], sessionInfo: SessionInfo, activeValidators?: Validator[]): Validator[] | undefined {
|
||||
const allValidators = useValidatorsAll(favorites, sessionInfo);
|
||||
|
||||
@ -33,17 +26,7 @@ function useValidatorsWaitingImpl (favorites: string[], sessionInfo: SessionInfo
|
||||
[activeValidators, allValidators]
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
if (tagged && sessionInfo.activeEra) {
|
||||
cache = { activeEra: sessionInfo.activeEra, tagged };
|
||||
}
|
||||
}, [sessionInfo, tagged]);
|
||||
|
||||
return tagged || (
|
||||
cache &&
|
||||
sessionInfo.activeEra?.eq(cache.activeEra) &&
|
||||
cache.tagged
|
||||
) || undefined;
|
||||
return useCacheValue('useValidatorsWaiting', sessionInfo.activeEra, tagged);
|
||||
}
|
||||
|
||||
export default createNamedHook('useValidatorsWaiting', useValidatorsWaitingImpl);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user