Skip to content

Commit

Permalink
feat: Open JoinPool canvas immediately, preloader, prioritise low m…
Browse files Browse the repository at this point in the history
…ember pools. (#2059)

Co-authored-by: Ting A Lin <linshaoting6@gmail.com>
  • Loading branch information
Ross Bulat and TingALin authored Apr 5, 2024
1 parent 965d3e1 commit 5360eaa
Show file tree
Hide file tree
Showing 15 changed files with 344 additions and 131 deletions.
101 changes: 101 additions & 0 deletions src/canvas/JoinPool/Preloader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Copyright 2024 @paritytech/polkadot-staking-dashboard authors & contributors
// SPDX-License-Identifier: GPL-3.0-only

import { faArrowsRotate, faTimes } from '@fortawesome/free-solid-svg-icons';
import { ButtonPrimary } from 'kits/Buttons/ButtonPrimary';
import { ButtonPrimaryInvert } from 'kits/Buttons/ButtonPrimaryInvert';
import { useOverlay } from 'kits/Overlay/Provider';
import { useTranslation } from 'react-i18next';
import {
JoinFormWrapper,
JoinPoolInterfaceWrapper,
PreloaderWrapper,
TitleWrapper,
} from './Wrappers';
import { PoolSync } from 'library/PoolSync';
import { CallToActionLoader } from 'library/Loader/CallToAction';
import { LoaderWrapper } from 'library/Loader/Wrappers';
import { PageTitleTabs } from 'kits/Structure/PageTitleTabs';

export const Preloader = () => {
const { t } = useTranslation();
const { closeCanvas } = useOverlay().canvas;

return (
<>
<div className="head">
<ButtonPrimaryInvert
text={t('chooseAnotherPool', { ns: 'library' })}
iconLeft={faArrowsRotate}
disabled
lg
/>
<ButtonPrimary
text={t('cancel', { ns: 'library' })}
lg
onClick={() => closeCanvas()}
iconLeft={faTimes}
style={{ marginLeft: '1.1rem' }}
/>
</div>
<TitleWrapper>
<div className="inner">
<div className="empty"></div>
<div className="standalone">
<div className="title">
<h1>{t('syncingPoolData', { ns: 'library' })}...</h1>
</div>
<div className="labels">
<h3>
{t('analyzingPoolPerformance', { ns: 'library' })}
<PoolSync label={t('complete', { ns: 'library' })} />
</h3>
</div>
</div>
</div>
<PageTitleTabs
sticky={false}
tabs={[
{
title: t('pools.overview', { ns: 'pages' }),
active: true,
onClick: () => {
/* Do nothing */
},
disabled: true,
asPreloader: true,
},
{
title: t('nominate.nominations', { ns: 'pages' }),
active: true,
onClick: () => {
/* Do nothing */
},
disabled: true,
asPreloader: true,
},
]}
tabClassName="canvas"
inline={true}
/>
</TitleWrapper>

<JoinPoolInterfaceWrapper>
<div className="content">
<div className="main">
<PreloaderWrapper>
<CallToActionLoader />
</PreloaderWrapper>
</div>
<div className="side">
<div>
<JoinFormWrapper className="preload">
<LoaderWrapper style={{ width: '100%', height: '30rem' }} />
</JoinFormWrapper>
</div>
</div>
</div>
</JoinPoolInterfaceWrapper>
</>
);
};
21 changes: 21 additions & 0 deletions src/canvas/JoinPool/Wrappers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ export const JoinPoolInterfaceWrapper = styled.div`
}
`;

export const PreloaderWrapper = styled.div`
background-color: var(--background-floating-card);
width: 100%;
height: 3.75rem;
border-radius: 2rem;
opacity: 0.4;
`;

export const TitleWrapper = styled.div`
border-bottom: 1px solid var(--border-secondary-color);
flex: 1;
Expand All @@ -73,12 +81,20 @@ export const TitleWrapper = styled.div`
&:nth-child(1) {
max-width: 4rem;
&.empty {
max-width: 0px;
}
}
&:nth-child(2) {
padding-left: 1rem;
flex-direction: column;
&.standalone {
padding-left: 0;
}
> .title {
position: relative;
padding-top: 2rem;
Expand Down Expand Up @@ -146,6 +162,11 @@ export const JoinFormWrapper = styled.div`
margin-top: 1rem;
}
&.preload {
padding: 0;
opacity: 0.5;
}
h4 {
display: flex;
align-items: center;
Expand Down
76 changes: 43 additions & 33 deletions src/canvas/JoinPool/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,22 @@ import { usePoolPerformance } from 'contexts/Pools/PoolPerformance';
import { MaxEraRewardPointsEras } from 'consts';
import { useStaking } from 'contexts/Staking';
import { useJoinPools } from 'contexts/Pools/JoinPools';
import { Preloader } from './Preloader';

export const JoinPool = () => {
const {
closeCanvas,
config: { options },
} = useOverlay().canvas;
const { eraStakers } = useStaking();
const { poolsForJoin } = useJoinPools();
const { getPoolRewardPoints } = usePoolPerformance();
const { poolsMetaData, bondedPools } = useBondedPools();
const { getPoolRewardPoints, getPoolPerformanceTask } = usePoolPerformance();

// Get the pool performance task to determine if performance data is ready.
const poolJoinPerformanceTask = getPoolPerformanceTask('pool_join');
const performanceDataReady = poolJoinPerformanceTask.status === 'synced';

// Get performance data: Assumed to be fetched now.
const poolRewardPoints = getPoolRewardPoints('pool_join');

// The active canvas tab.
Expand Down Expand Up @@ -53,14 +59,14 @@ export const JoinPool = () => {
staker.others.find(({ who }) => who !== pool.addresses.stash)
)
),
[poolsForJoin, poolRewardPoints]
[poolsForJoin, poolRewardPoints, performanceDataReady]
);

const initialSelectedPoolId = useMemo(
() =>
options?.poolId ||
filteredBondedPools[(filteredBondedPools.length * Math.random()) << 0]
.id ||
?.id ||
0,
[]
);
Expand All @@ -77,41 +83,45 @@ export const JoinPool = () => {
[selectedPoolId]
);

// Close canvas if no pool id is selected.
// If syncing completes within the canvas, assign a selected pool.
useEffect(() => {
if (selectedPoolId === 0) {
closeCanvas();
if (performanceDataReady && selectedPoolId === 0) {
setSelectedPoolId(
filteredBondedPools[(filteredBondedPools.length * Math.random()) << 0]
?.id || 0
);
}
}, [selectedPoolId]);

// Ensure bonded pool exists before rendering. Canvas should close if this is the case.
if (!bondedPool) {
return null;
}
}, [performanceDataReady]);

return (
<CanvasFullScreenWrapper>
<Header
activeTab={activeTab}
setActiveTab={setActiveTab}
setSelectedPoolId={setSelectedPoolId}
bondedPool={bondedPool}
metadata={poolsMetaData[selectedPoolId]}
autoSelected={options?.poolId === undefined}
filteredBondedPools={filteredBondedPools}
/>
{poolJoinPerformanceTask.status !== 'synced' || !bondedPool ? (
<Preloader />
) : (
<>
<Header
activeTab={activeTab}
setActiveTab={setActiveTab}
setSelectedPoolId={setSelectedPoolId}
bondedPool={bondedPool}
metadata={poolsMetaData[selectedPoolId]}
autoSelected={options?.poolId === undefined}
filteredBondedPools={filteredBondedPools}
/>

<JoinPoolInterfaceWrapper>
<div className="content">
{activeTab === 0 && <Overview bondedPool={bondedPool} />}
{activeTab === 1 && (
<Nominations
poolId={bondedPool.id}
stash={bondedPool.addresses.stash}
/>
)}
</div>
</JoinPoolInterfaceWrapper>
<JoinPoolInterfaceWrapper>
<div className="content">
{activeTab === 0 && <Overview bondedPool={bondedPool} />}
{activeTab === 1 && (
<Nominations
poolId={bondedPool.id}
stash={bondedPool.addresses.stash}
/>
)}
</div>
</JoinPoolInterfaceWrapper>
</>
)}
</CanvasFullScreenWrapper>
);
};
45 changes: 38 additions & 7 deletions src/contexts/Pools/JoinPools/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { useApi } from 'contexts/Api';
import { useValidators } from 'contexts/Validators/ValidatorEntries';
import { usePoolPerformance } from '../PoolPerformance';
import type { BondedPool } from '../BondedPools/types';
import { shuffle } from '@w3ux/utils';
import { rmCommas, shuffle } from '@w3ux/utils';
import BigNumber from 'bignumber.js';

export const JoinPoolsContext = createContext<JoinPoolsContextInterface>(
defaultJoinPoolsContext
Expand All @@ -20,7 +21,11 @@ export const JoinPoolsContext = createContext<JoinPoolsContextInterface>(
export const useJoinPools = () => useContext(JoinPoolsContext);

export const JoinPoolsProvider = ({ children }: { children: ReactNode }) => {
const { api, activeEra } = useApi();
const {
api,
activeEra,
networkMetrics: { minimumActiveStake },
} = useApi();
const { bondedPools } = useBondedPools();
const { erasRewardPointsFetched } = useValidators();
const { getPoolPerformanceTask, startPoolRewardPointsFetch } =
Expand All @@ -46,12 +51,38 @@ export const JoinPoolsProvider = ({ children }: { children: ReactNode }) => {
erasRewardPointsFetched === 'synced' &&
getPoolPerformanceTask('pool_join')?.status === 'unsynced'
) {
// Generate a subset of pools to fetch performance data for. TODO: Send pools to JoinPool
// canvas and only select those. Move this logic to a separate context.
const poolJoinSelection = shuffle(
bondedPools.filter(({ state }) => state === 'Open')
).slice(0, MaxPoolsForJoin);
// Generate a subset of pools to fetch performance data for. Start by only considering active pools.
const activeBondedPools = bondedPools.filter(
({ state }) => state === 'Open'
);

// Filter pools that do not have at least double the minimum active stake in points. NOTE:
// assumes that points are a 1:1 ratio between balance and points.
const rewardBondedPools = activeBondedPools.filter(({ points }) => {
const pointsBn = new BigNumber(rmCommas(points));
const threshold = minimumActiveStake.multipliedBy(2);
return pointsBn.isGreaterThanOrEqualTo(threshold);
});

// Order active bonded pools by member count.
const sortedBondedPools = rewardBondedPools.sort(
(a, b) =>
Number(rmCommas(a.memberCounter)) - Number(rmCommas(b.memberCounter))
);

// Take lower third of sorted bonded pools to join.
const lowerThirdBondedPools = sortedBondedPools.slice(
0,
Math.floor(sortedBondedPools.length / 3)
);

// Shuffle the lower third of bonded pools to join, and select a random subset of them.
const poolJoinSelection = shuffle(lowerThirdBondedPools).slice(
0,
MaxPoolsForJoin
);

// Commit final pool selection to state.
setPoolsToJoin(poolJoinSelection);
}
}, [
Expand Down
11 changes: 8 additions & 3 deletions src/kits/Buttons/ButtonTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export type ButtonTabProps = ComponentBaseWithClassName &
title: string;
// a badge value can represent the main content of the tab page
badge?: string | number;
// whether this tab is acting as a preloader.
asPreloader?: boolean;
};

/**
Expand All @@ -31,17 +33,20 @@ export const ButtonTab = ({
onMouseOver,
onMouseMove,
onMouseOut,
asPreloader,
}: ButtonTabProps) => (
<button
className={`btn-tab${appendOrEmpty(active, 'active')}${
className ? ` ${className}` : ''
}`}
}${asPreloader ? ` preload` : ``}`}
style={style}
type="button"
disabled={disabled}
{...onMouseHandlers({ onClick, onMouseOver, onMouseMove, onMouseOut })}
>
{title}
{badge ? <span className="badge">{badge}</span> : null}
<span className={`${asPreloader ? `preload` : ``}`}>
{title}
{badge ? <span className="badge">{badge}</span> : null}
</span>
</button>
);
Loading

0 comments on commit 5360eaa

Please sign in to comment.