Skip to content

Commit

Permalink
Feat/paginate ens (#3244)
Browse files Browse the repository at this point in the history
* feat: add pagination to the ENS fetch mechanism

* feaT: paginate worlds list too

* fix: worlds section pagination

* fix: do not paginate external names

* feat: do requests in parallel

* feat: move DEFAULT_ENS_PAGE_SIZE outside of sagas
  • Loading branch information
juanmahidalgo authored Jan 29, 2025
1 parent 5508b1d commit 83eee39
Show file tree
Hide file tree
Showing 14 changed files with 138 additions and 102 deletions.
10 changes: 6 additions & 4 deletions src/components/ENSListPage/ENSListPage.container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { connect } from 'react-redux'
import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors'
import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors'
import { RootState } from 'modules/common/types'
import { getENSByWallet, getError as getENSError, getLoading } from 'modules/ens/selectors'
import { getENSByWallet, getError as getENSError, getLoading, getTotal } from 'modules/ens/selectors'
import { isLoggingIn, isLoggedIn } from 'modules/identity/selectors'
import { FETCH_ENS_LIST_REQUEST } from 'modules/ens/actions'
import { FETCH_ENS_LIST_REQUEST, fetchENSListRequest } from 'modules/ens/actions'
import { getLands, getLoading as getLandsLoading, getError as getLandsError } from 'modules/land/selectors'
import { FETCH_LANDS_REQUEST } from 'modules/land/actions'
import { getAvatar, getName } from 'modules/profile/selectors'
Expand All @@ -24,11 +24,13 @@ const mapState = (state: RootState): MapStateProps => ({
isLoadingType(getLoading(state), FETCH_ENS_LIST_REQUEST) ||
isLoggingIn(state),
isLoggedIn: isLoggedIn(state),
avatar: getAvatar(state)
avatar: getAvatar(state),
total: getTotal(state)
})

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onOpenModal: (name, metadata) => dispatch(openModal(name, metadata))
onOpenModal: (name, metadata) => dispatch(openModal(name, metadata)),
onFetchENSList: (first, skip) => dispatch(fetchENSListRequest(first, skip))
})

export default connect(mapState, mapDispatch)(ENSListPage)
63 changes: 34 additions & 29 deletions src/components/ENSListPage/ENSListPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react'
import React, { useEffect, useCallback, useMemo } from 'react'
import { Env } from '@dcl/ui-env'
import { Link, useHistory } from 'react-router-dom'
import { config } from 'config'
Expand Down Expand Up @@ -26,11 +26,35 @@ const REGISTRAR_CONTRACT_ADDRESS = config.get('REGISTRAR_CONTRACT_ADDRESS', '')
const ENS_GATEWAY = config.get('ENS_GATEWAY')

export default function ENSListPage(props: Props) {
const { ensList, alias, hasProfileCreated, avatar, isLoading, error, onOpenModal } = props
const { ensList, alias, hasProfileCreated, avatar, isLoading, error, onOpenModal, total, onFetchENSList } = props
const [sortBy, setSortBy] = React.useState(SortBy.ASC)
const [page, setPage] = React.useState(1)
const history = useHistory()

useEffect(() => {
onFetchENSList(PAGE_SIZE, (page - 1) * PAGE_SIZE)
}, [onFetchENSList, page])

const handlePageChange = useCallback((newPage: number) => {
setPage(newPage)
}, [])

const sortedEnsList = useMemo(() => {
return [...ensList].sort((a: ENS, b: ENS) => {
switch (sortBy) {
case SortBy.ASC: {
return a.subdomain.toLowerCase() < b.subdomain.toLowerCase() ? -1 : 1
}
case SortBy.DESC: {
return a.subdomain.toLowerCase() > b.subdomain.toLowerCase() ? -1 : 1
}
default: {
return 0
}
}
})
}, [ensList, sortBy])

const handleAssignENS = useCallback((ens: ENS) => history.push(locations.ensSelectLand(ens.subdomain)), [history])
const buildENSLink = useCallback(
(ens: ENS) => {
Expand Down Expand Up @@ -67,7 +91,7 @@ export default function ENSListPage(props: Props) {
[onOpenModal]
)

const renderSortDropdown = () => {
const renderSortDropdown = useCallback(() => {
return (
<Dropdown
direction="left"
Expand All @@ -79,25 +103,7 @@ export default function ENSListPage(props: Props) {
onChange={(_event, { value }) => setSortBy(value as SortBy)}
/>
)
}

const paginate = useCallback(() => {
return ensList
.sort((a: ENS, b: ENS) => {
switch (sortBy) {
case SortBy.ASC: {
return a.subdomain.toLowerCase() < b.subdomain.toLowerCase() ? 1 : -1
}
case SortBy.DESC: {
return a.subdomain.toLowerCase() > b.subdomain.toLowerCase() ? 1 : -1
}
default: {
return 0
}
}
})
.slice((page - 1) * PAGE_SIZE, page * PAGE_SIZE)
}, [ensList, sortBy, page])
}, [sortBy])

const isAlias = useCallback(
(ens: ENS) => {
Expand Down Expand Up @@ -268,16 +274,14 @@ export default function ENSListPage(props: Props) {
}, [])

const renderEnsList = useCallback(() => {
const total = ensList.length
const totalPages = Math.ceil(total / PAGE_SIZE)
const paginatedItems = paginate()

return (
<div className="ens-page-content">
<div className="ens-page-header">
<div className="ens-page-title">
<h1>{t('ens_list_page.title')}</h1>
{t('ens_list_page.result', { count: ensList.length })}
{t('ens_list_page.result', { count: total })}
</div>
<div className="ens-page-actions">
{ensList.length > 1 ? (
Expand All @@ -293,13 +297,14 @@ export default function ENSListPage(props: Props) {
<TableContainer
children={
<TableContent
data={formatToTable(paginatedItems)}
data={formatToTable(sortedEnsList)}
isLoading={isLoading}
activePage={page}
setPage={page => setPage(page)}
setPage={handlePageChange}
totalPages={totalPages}
empty={renderEmptyEnsList}
total={PAGE_SIZE}
total={total}
rowsPerPage={PAGE_SIZE}
hasHeaders
customHeaders={{
name: <span className="ens-list-page-table-headers">{t('ens_list_page.table.name')}</span>,
Expand Down Expand Up @@ -329,7 +334,7 @@ export default function ENSListPage(props: Props) {
/>
</div>
)
}, [ensList, page, isLoading, renderSortDropdown, renderEmptyEnsList, formatToTable])
}, [sortedEnsList, isLoading, page, total, handlePageChange, formatToTable, renderEmptyEnsList, renderSortDropdown, ensList.length])

return (
<LoggedInDetailPage
Expand Down
7 changes: 5 additions & 2 deletions src/components/ENSListPage/ENSListPage.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Dispatch } from 'redux'
import { ENS } from 'modules/ens/types'
import { Land } from 'modules/land/types'
import { openModal } from 'decentraland-dapps/dist/modules/modal/actions'
import { fetchENSListRequest } from 'modules/ens/actions'
import { Avatar } from '@dcl/schemas'

export enum SortBy {
Expand All @@ -19,7 +20,9 @@ export type Props = {
isLoggedIn: boolean
isLoading: boolean
avatar: Avatar | null
total: number
onOpenModal: typeof openModal
onFetchENSList: typeof fetchENSListRequest
}

export type State = {
Expand All @@ -29,7 +32,7 @@ export type State = {

export type MapStateProps = Pick<
Props,
'address' | 'alias' | 'ensList' | 'lands' | 'hasProfileCreated' | 'isLoading' | 'error' | 'isLoggedIn' | 'avatar'
'address' | 'alias' | 'ensList' | 'lands' | 'hasProfileCreated' | 'isLoading' | 'error' | 'isLoggedIn' | 'avatar' | 'total'
>
export type MapDispatchProps = Pick<Props, 'onOpenModal'>
export type MapDispatchProps = Pick<Props, 'onOpenModal' | 'onFetchENSList'>
export type MapDispatch = Dispatch
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Project } from 'modules/project/types'
import { MapDispatch, MapDispatchProps, MapStateProps } from './DeployToWorld.types'

import DeployToWorld from './DeployToWorld'
import { getWallet } from 'modules/wallet/selectors'

const mapState = (state: RootState): MapStateProps => {
return {
Expand All @@ -28,6 +29,7 @@ const mapState = (state: RootState): MapStateProps => {
metrics: getCurrentMetrics(state),
deployments: getDeploymentsByWorlds(state),
deploymentProgress: getUploadProgress(state),
wallet: getWallet(state),
error: getError(state),
isLoading: isLoading(state) || isLoadingENS(state),
isWorldContributorEnabled: getIsWorldContributorEnabled(state)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,29 @@ import styles from './DeployToWorld.module.css'
import { ModelMetrics } from 'modules/models/types'
import { ENS } from 'modules/ens/types'
import Profile from 'components/Profile'
import { marketplace } from 'lib/api/marketplace'

const EXPLORER_URL = config.get('EXPLORER_URL', '')
const WORLDS_CONTENT_SERVER_URL = config.get('WORLDS_CONTENT_SERVER', '')
const ENS_DOMAINS_URL = config.get('ENS_DOMAINS_URL', '')
const MARKETPLACE_WEB_URL = config.get('MARKETPLACE_WEB_URL', '')
const CLAIM_NAME_OPTION = 'claim_name_option'

const ENS_LIST_PAGE_SIZE = 2000 // TODO: see how to paginate this in the future

export default function DeployToWorld({
name,
project,
scene,
metrics,
ensList,
externalNames,
deployments,
isLoading,
error,
claimedName,
contributableNames,
isWorldContributorEnabled,
wallet,
onPublish,
onRecord,
onClose,
Expand All @@ -49,6 +52,16 @@ export default function DeployToWorld({
}: Props) {
const analytics = getAnalytics()

const [ensList, setEnsList] = useState<ENS[]>([])

useEffect(() => {
const fetchENSList = async () => {
const ensList = await marketplace.fetchENSList(wallet?.address ?? '', ENS_LIST_PAGE_SIZE, 0)
setEnsList(ensList.map(ens => ({ subdomain: ens, name: ens } as ENS)))
}
void fetchENSList()
}, [])

const [view, setView] = useState<string>('')
const [world, setWorld] = useState<string>(claimedName ?? '')
const [nameType, setNameType] = useState<NameType>(NameType.DCL)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Dispatch } from 'redux'
import { SceneMetrics } from '@dcl/inspector/dist/redux/scene-metrics/types'
import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types'
import { deployToWorldRequest, DeployToWorldRequestAction } from 'modules/deployment/actions'
import { recordMediaRequest, RecordMediaRequestAction } from 'modules/media/actions'
import { ENS } from 'modules/ens/types'
Expand All @@ -25,6 +26,7 @@ export type Props = {
isLoading: boolean
claimedName: string | null
isWorldContributorEnabled: boolean
wallet: Wallet | null
onClose: () => void
onBack: () => void
onPublish: typeof deployToWorldRequest
Expand All @@ -45,6 +47,7 @@ export type MapStateProps = Pick<
| 'scene'
| 'contributableNames'
| 'isWorldContributorEnabled'
| 'wallet'
>
export type MapDispatchProps = Pick<Props, 'onPublish' | 'onRecord' | 'onFetchContributableNames'>
export type MapDispatch = Dispatch<
Expand Down
11 changes: 7 additions & 4 deletions src/components/WorldListPage/WorldListPage.container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { openModal } from 'decentraland-dapps/dist/modules/modal/actions'
import { isConnected } from 'decentraland-dapps/dist/modules/wallet'
import { RootState } from 'modules/common/types'
import { getIsWorldContributorEnabled } from 'modules/features/selectors'
import { FETCH_ENS_LIST_REQUEST, FETCH_EXTERNAL_NAMES_REQUEST, fetchContributableNamesRequest } from 'modules/ens/actions'
import { FETCH_ENS_LIST_REQUEST, FETCH_EXTERNAL_NAMES_REQUEST, fetchContributableNamesRequest, fetchENSListRequest } from 'modules/ens/actions'
import {
getENSByWallet,
getError as getENSError,
getExternalNamesForConnectedWallet,
getLoading as getLoadingENS
getLoading as getLoadingENS,
getTotal as getTotalENS
} from 'modules/ens/selectors'
import { FETCH_WORLD_DEPLOYMENTS_REQUEST, clearDeploymentRequest } from 'modules/deployment/actions'
import { getDeploymentsByWorlds, getError as getDeploymentsError, getLoading as getDeploymentsLoading } from 'modules/deployment/selectors'
Expand Down Expand Up @@ -38,7 +39,8 @@ const mapState = (state: RootState): MapStateProps => ({
isLoggedIn: isLoggedIn(state),
worldsWalletStats: getConnectedWalletStats(state),
isConnected: isConnected(state),
isWorldContributorEnabled: getIsWorldContributorEnabled(state)
isWorldContributorEnabled: getIsWorldContributorEnabled(state),
ensTotal: getTotalENS(state)
})

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
Expand All @@ -47,7 +49,8 @@ const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
dispatch(openModal('WorldPermissionsModal', { worldName: name, isCollaboratorsTabShown })),
onOpenWorldsForENSOwnersAnnouncementModal: () => dispatch(openModal('WorldsForENSOwnersAnnouncementModal')),
onUnpublishWorld: deploymentId => dispatch(clearDeploymentRequest(deploymentId)),
onFetchContributableNames: () => dispatch(fetchContributableNamesRequest())
onFetchContributableNames: () => dispatch(fetchContributableNamesRequest()),
onFetchENSList: (first, skip) => dispatch(fetchENSListRequest(first, skip))
})

export default connect(mapState, mapDispatch)(WorldListPage)
Loading

0 comments on commit 83eee39

Please sign in to comment.