Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: auto loading referenda list #1599

Merged
merged 7 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,11 @@ function InternetConnectivity (): React.ReactElement {

useEffect(() => {
checkInternetAccess().then((_isOnline) => {
console.log('internet check result:', _isOnline);

setIsOnline(_isOnline);
}).catch(console.error);

const intervalId = setInterval(() => {
checkInternetAccess().then((_isOnline) => {
console.log('internet check result:', _isOnline);

setIsOnline(_isOnline);
}).catch(console.error);
}, CHECK_INTERNET_CONNECTIVITY_PERIOD);
Expand Down
80 changes: 66 additions & 14 deletions packages/extension-polkagate/src/fullscreen/governance/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0

// @ts-nocheck

/* eslint-disable react/jsx-max-props-per-line */

import type { u32 } from '@polkadot/types-codec';
import type { LatestReferenda } from './utils/types';

import { Container, Grid, Typography, useTheme } from '@mui/material';
// @ts-ignore
import { CubeGrid } from 'better-react-spinkit';
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useParams } from 'react-router';
Expand All @@ -34,7 +34,7 @@ export type Fellowship = [string, number];
export default function Governance (): React.ReactElement {
useFullscreen();
const { t } = useTranslation();
const { state } = useLocation();
const { state } = useLocation() as unknown as {state: {selectedSubMenu?: string}};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve type assertion for useLocation

Casting useLocation() to unknown and then asserting a specific type may lead to runtime errors if the structure doesn't match. Instead, define a proper interface for the expected state.

Define an interface for the location state:

interface LocationState {
  selectedSubMenu?: string;
}

const { state } = useLocation() as LocationState;

const theme = useTheme();
const { address, topMenu } = useParams<{ address: string, topMenu: 'referenda' | 'fellowship' }>();
const api = useApi(address);
Expand All @@ -48,7 +48,7 @@ export default function Governance (): React.ReactElement {

const pageTrackRef = useRef({ listFinished: false, page: 1, subMenu: 'All', topMenu });
const [menuOpen, setMenuOpen] = useState(false);
const [selectedSubMenu, setSelectedSubMenu] = useState<string>(state?.selectedSubMenu as string || 'All');
const [selectedSubMenu, setSelectedSubMenu] = useState<string>(state?.selectedSubMenu || 'All');
const [referendumCount, setReferendumCount] = useState<{ referenda: number | undefined, fellowship: number | undefined }>({ fellowship: undefined, referenda: undefined });
const [referenda, setReferenda] = useState<LatestReferenda[] | null>();
const [filteredReferenda, setFilteredReferenda] = useState<LatestReferenda[] | null>();
Expand Down Expand Up @@ -77,7 +77,7 @@ export default function Governance (): React.ReactElement {
fetchJson();
}, []);

const referendaTrackId = tracks?.find((t) => String(t[1].name) === selectedSubMenu.toLowerCase().replace(' ', '_'))?.[0]?.toNumber() as number;
const referendaTrackId = tracks?.find((t) => String(t[1].name) === selectedSubMenu.toLowerCase().replace(' ', '_'))?.[0]?.toNumber()!;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const currentTrack = useMemo(() => {
if (!tracks && !fellowshipTracks) {
Expand Down Expand Up @@ -131,7 +131,7 @@ export default function Governance (): React.ReactElement {
return;
}

if (!api.consts.referenda || !api.query.referenda) {
if (!api.consts['referenda'] || !api.query['referenda']) {
console.log('OpenGov is not supported on this chain');
setNotSupportedChain(true);
// to reset refs on non supported chain, or when chain has changed
Expand All @@ -143,19 +143,23 @@ export default function Governance (): React.ReactElement {

setNotSupportedChain(false);

api.query.referenda.referendumCount().then((count) => {
referendumCount.referenda = count?.toNumber();
api.query['referenda']['referendumCount']().then((count) => {
referendumCount.referenda = (count as u32)?.toNumber();
setReferendumCount({ ...referendumCount });
}).catch(console.error);

api.query.fellowshipReferenda && api.query.fellowshipReferenda.referendumCount().then((count) => {
referendumCount.fellowship = count?.toNumber();
api.query['fellowshipReferenda']?.['referendumCount']().then((count) => {
referendumCount.fellowship = (count as u32)?.toNumber();
setReferendumCount({ ...referendumCount });
}).catch(console.error);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [api, chainName]);

const addFellowshipOriginsFromSb = useCallback(async (resPA: LatestReferenda[]): Promise<LatestReferenda[] | undefined> => {
if (!chainName) {
return;
}

const resSb = await getReferendumsListSb(chainName, topMenu, pageTrackRef.current.page * LATEST_REFERENDA_LIMIT_TO_LOAD_PER_REQUEST);

if (resSb) {
Expand Down Expand Up @@ -297,8 +301,9 @@ export default function Governance (): React.ReactElement {
return;
}

api.query.fellowshipCollective && api.query.fellowshipCollective.members.entries().then((keys) => {
api.query['fellowshipCollective']?.['members'].entries().then((keys) => {
const fellowships = keys.map(([{ args: [id] }, option]) => {
//@ts-ignore
Comment on lines +304 to +306
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid suppressing TypeScript errors with @ts-ignore

The //@ts-ignore comment suppresses TypeScript errors, which might hide underlying issues. Instead, properly type the variables or fix the type error.

Define the correct types for id and option:

api.query.fellowshipCollective?.members.entries().then((keys) => {
  const fellowships = keys.map(([{ args: [id] }, option]) => {
    return [id.toString(), (option as any)?.value?.rank?.toNumber()] as Fellowship;
  });
  // rest of the code...
});

return [id.toString(), option?.value?.rank?.toNumber()] as Fellowship;
});

Expand All @@ -312,6 +317,47 @@ export default function Governance (): React.ReactElement {
setGetMore(pageTrackRef.current.page);
}, [pageTrackRef]);

const observerInstance = useRef<IntersectionObserver>();
const target = document.getElementById('observerObj');

useEffect(() => {
const observerCallback = (entries: IntersectionObserverEntry[]): void => {
const [entry] = entries;

if (!entry.isIntersecting) {
return; // If the observer object is not in view, do nothing
}

if (isLoadingMore) {
return; // If already fetching, do nothing
}

if (pageTrackRef.current.listFinished) {
observerInstance.current?.disconnect();

return;
}

getMoreReferenda();
};

const options = {
root: document.getElementById('scrollArea'),
rootMargin: '0px',
threshold: 1.0 // Trigger when 100% of the target (observerObj) is visible
};

observerInstance.current = new IntersectionObserver(observerCallback, options);

if (target) {
observerInstance.current.observe(target); // Start observing the target
}

return () => {
observerInstance.current?.disconnect();
};
}, [chainName, getMoreReferenda, isLoadingMore, target]);

return (
<>
<FullScreenHeader page='governance' />
Expand All @@ -335,16 +381,19 @@ export default function Governance (): React.ReactElement {
decidingCounts={decidingCounts}
menuOpen={menuOpen}
setMenuOpen={setMenuOpen}
// @ts-ignore
setSelectedSubMenu={setSelectedSubMenu}
/>
<Container disableGutters sx={{ maxWidth: 'inherit' }}>
<Bread
address={address}
// @ts-ignore
setSelectedSubMenu={setSelectedSubMenu}
subMenu={selectedSubMenu}
// @ts-ignore
topMenu={topMenu}
/>
<Container disableGutters sx={{ maxHeight: parent.innerHeight - 170, maxWidth: 'inherit', opacity: menuOpen ? 0.3 : 1, overflowY: 'scroll', px: '10px' }}>
<Container disableGutters id='scrollArea' sx={{ maxHeight: parent.innerHeight - 170, maxWidth: 'inherit', opacity: menuOpen ? 0.3 : 1, overflowY: 'scroll', px: '10px' }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect use of parent.innerHeight

The use of parent.innerHeight is incorrect and may lead to runtime errors because parent is not defined in this context. To obtain the viewport height, you should use window.innerHeight.

Apply this diff to fix the issue:

- <Container disableGutters id='scrollArea' sx={{ maxHeight: parent.innerHeight - 170, maxWidth: 'inherit', opacity: menuOpen ? 0.3 : 1, overflowY: 'scroll', px: '10px' }}>
+ <Container disableGutters id='scrollArea' sx={{ maxHeight: window.innerHeight - 170, maxWidth: 'inherit', opacity: menuOpen ? 0.3 : 1, overflowY: 'scroll', px: '10px' }}>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Container disableGutters id='scrollArea' sx={{ maxHeight: parent.innerHeight - 170, maxWidth: 'inherit', opacity: menuOpen ? 0.3 : 1, overflowY: 'scroll', px: '10px' }}>
<Container disableGutters id='scrollArea' sx={{ maxHeight: window.innerHeight - 170, maxWidth: 'inherit', opacity: menuOpen ? 0.3 : 1, overflowY: 'scroll', px: '10px' }}>

{selectedSubMenu === 'All'
? <AllReferendaStats
address={address}
Expand Down Expand Up @@ -389,12 +438,12 @@ export default function Governance (): React.ReactElement {
!isLoadingMore
? <Grid container item justifyContent='center' sx={{ '&:hover': { cursor: 'pointer' }, pb: '15px' }}>
{notSupportedChain
? <Typography color='secondary.contrastText' fontSize='18px' fontWeight={600} onClick={getMoreReferenda} pt='50px'>
? <Typography color='secondary.contrastText' fontSize='18px' fontWeight={600} pt='50px'>
{t('Open Governance is not supported on the {{chainName}}', { replace: { chainName } })}
</Typography>
: !!referenda?.length && referendumCount[topMenu] && referenda.length < (referendumCount[topMenu] || 0)
? <Typography color='secondary.contrastText' fontSize='18px' fontWeight={600} onClick={getMoreReferenda}>
{t('Loaded {{count}} out of {{referendumCount}} referenda. Click here to load more', { replace: { count: referenda?.length || 0, referendumCount: referendumCount[topMenu] } })}
<div id='observerObj' style={{ height: '1px' }} />
</Typography>
: <Typography color='text.disabled' fontSize='15px'>
{t('No more referenda to load.')}
Expand All @@ -404,6 +453,9 @@ export default function Governance (): React.ReactElement {
: isLoadingMore &&
<Grid container justifyContent='center'>
<HorizontalWaiting color={theme.palette.primary.main} />
<Typography color='secondary.contrastText' fontSize='13px' display='block' width="100%" textAlign="center">
{t('Loaded {{count}} out of {{referendumCount}} referenda ...', { replace: { count: referenda?.length || 0, referendumCount: referendumCount[topMenu] } })}
</Typography>
</Grid>
}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck

/* eslint-disable react/jsx-max-props-per-line */

import type { Count } from '../../../hooks/useDecidingCount';

import { AdminPanelSettings as AdminsIcon, BorderAll as All, Groups3 as Groups3Icon, List as ListIcon } from '@mui/icons-material/';
import { Container, Grid, Typography, useTheme } from '@mui/material';
import React, { useCallback } from 'react';
import { useHistory } from 'react-router-dom';

import { Count } from '../../../hooks/useDecidingCount';
import { MAX_WIDTH } from '../utils/consts';
import { findItemDecidingCount } from './ReferendaMenu';

Expand All @@ -21,15 +21,15 @@ interface Props {

}

export default function FellowshipMenu({ address, decidingCounts, setMenuOpen, setSelectedSubMenu }: Props): React.ReactElement<Props> {
export default function FellowshipMenu ({ address, decidingCounts, setMenuOpen, setSelectedSubMenu }: Props): React.ReactElement<Props> {
const history = useHistory();
const theme = useTheme();

const onMouseLeave = useCallback(() => {
setMenuOpen(false);
}, [setMenuOpen]);

function MenuItem({ borderWidth = '2px', clickable = true, fontWeight, icon, item, top = false, width = '18%' }: { item: string, icon?: React.ReactElement, top?: boolean, width?: string, borderWidth?: string, fontWeight?: number, clickable?: boolean }): React.ReactElement {
function MenuItem ({ borderWidth = '2px', clickable = true, fontWeight, icon, item, top = false, width = '18%' }: { item: string, icon?: React.ReactElement, top?: boolean, width?: string, borderWidth?: string, fontWeight?: number, clickable?: boolean }): React.ReactElement {
const decidingCount = findItemDecidingCount(item, decidingCounts);

const onSubMenuClick = useCallback(() => {
Expand All @@ -44,7 +44,9 @@ export default function FellowshipMenu({ address, decidingCounts, setMenuOpen, s
return (
<Grid alignItems='center' container item
sx={{
borderBottom: top && `${borderWidth} solid`,
'&:hover': clickable ? { fontWeight: 700, textDecoration: 'underline' } : undefined,
borderBottom: top ? `${borderWidth} solid` : undefined,
borderColor: 'primary.main',
color: clickable
? (theme.palette.mode === 'light'
? 'secondary.main'
Expand All @@ -53,7 +55,11 @@ export default function FellowshipMenu({ address, decidingCounts, setMenuOpen, s
? 'text.primary'
: 'action.focus'
),
cursor: clickable && 'pointer', fontSize: '18px', width, borderColor: 'primary.main', mr: '20px', py: '5px', '&:hover': clickable && { fontWeight: 700, textDecoration: 'underline' }
cursor: clickable ? 'pointer' : undefined,
fontSize: '18px',
mr: '20px',
py: '5px',
width
}}>
{icon}
<Typography onClick={onSubMenuClick} sx={{ display: 'inline-block', fontWeight: fontWeight || 'inherit' }}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck

/* eslint-disable react/jsx-max-props-per-line */

import type { Count } from '../../../hooks/useDecidingCount';

import { AccountBalance as TreasuryIcon, AdminPanelSettings as AdminsIcon, BorderAll as All, Cancel, Hub as Root } from '@mui/icons-material/';
import { Container, Grid, Typography, useTheme } from '@mui/material';
import React, { useCallback } from 'react';
import { useHistory } from 'react-router-dom';

import { Count } from '../../../hooks/useDecidingCount';
import { MAX_WIDTH } from '../utils/consts';

interface Props {
Expand All @@ -30,7 +30,7 @@ export const findItemDecidingCount = (item: string, decidingCounts: Count[] | un
return filtered?.[1];
};

export default function ReferendaMenu({ address, decidingCounts, setMenuOpen, setSelectedSubMenu }: Props): React.ReactElement<Props> {
export default function ReferendaMenu ({ address, decidingCounts, setMenuOpen, setSelectedSubMenu }: Props): React.ReactElement<Props> {
const history = useHistory();
const theme = useTheme();
const onMouseLeave = useCallback(() => {
Expand All @@ -51,7 +51,9 @@ export default function ReferendaMenu({ address, decidingCounts, setMenuOpen, se
return (
<Grid alignItems='center' container item
sx={{
borderBottom: top && `${borderWidth} solid`,
'&:hover': clickable ? { fontWeight: 700, textDecoration: 'underline' } : undefined,
borderBottom: top ? `${borderWidth} solid` : undefined,
borderColor: 'primary.main',
color: clickable
? (theme.palette.mode === 'light'
? 'secondary.main'
Expand All @@ -60,7 +62,11 @@ export default function ReferendaMenu({ address, decidingCounts, setMenuOpen, se
? 'text.primary'
: 'action.focus'
),
cursor: clickable && 'pointer', fontSize: '18px', width, borderColor: 'primary.main', mr: '20px', py: '5px', '&:hover': clickable && { fontWeight: 700, textDecoration: 'underline' }
cursor: clickable ? 'pointer' : 'default',
fontSize: '18px',
mr: '20px',
py: '5px',
width
}}>
{icon}
<Typography onClick={onSubMenuClick} sx={{ display: 'inline-block', fontWeight: fontWeight || 'inherit' }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,13 +395,13 @@ interface RefListSb {
}[];
}

export async function getReferendumsListSb (chainName: string, type: TopMenu, listingLimit = 30): Promise<RefListSb | null> {
export async function getReferendumsListSb (chainName: string, type: 'referenda' | 'fellowship', listingLimit = 30): Promise<RefListSb | null> {
console.log('Getting ref list from sb ...');

return new Promise((resolve) => {
try {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
postData('https://' + chainName + `.api.subscan.io/api/scan/${type.toLocaleLowerCase()}/referendums`,
postData('https://' + chainName + `.api.subscan.io/api/scan/${type.toLowerCase()}/referendums`,
{
// page:1,
row: listingLimit
Expand Down
Loading