Skip to content

Commit

Permalink
feat: Display dcl and ens worlds in worlds view (#2933)
Browse files Browse the repository at this point in the history
* feat: Create hook to get currently selected tab

* fix: Move types to another file

* feat: Provide classnames to worlds storage

* fix: Format

* feat: Render dcl names or ens names view according to tab

* fix: Rename functions

* feat: Render size

* feat: Fetch external names on render

* fix: Tests

* fix: Update tests

* fix: Add tests for new util

* feat: Fetch data on connect

* fix: Add tests

* fix: Format

* fix: Fetch world deployments for external names when they are fetched successfuly

* fix: Test

* fix: Only show / 25 for ens names

* feat: Saga returns ENS instead of just strings

* feat: Fetch deployments and world status when external names are fetched

* fix: Add test

* fix: Remove unused function

* fix: Make owner mandatory

* fix: Make address mandatory

* fix: Update src/modules/ens/utils.spec.ts

Co-authored-by: Lautaro Petaccio <1120791+LautaroPetaccio@users.noreply.github.com>
Signed-off-by: Fernando Zavalia <24811313+fzavalia@users.noreply.github.com>

* fix: Remove unused function

* fix: Use Mb instead of mb

* fix: Add test

---------

Signed-off-by: Fernando Zavalia <24811313+fzavalia@users.noreply.github.com>
Co-authored-by: Lautaro Petaccio <1120791+LautaroPetaccio@users.noreply.github.com>
  • Loading branch information
fzavalia and LautaroPetaccio committed Oct 4, 2023
1 parent 1473bc9 commit 5aa57f9
Show file tree
Hide file tree
Showing 31 changed files with 467 additions and 392 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { push, replace } from 'connected-react-router'
import { RootState } from 'modules/common/types'
import { getCurrentProject } from 'modules/project/selectors'
import { getENSByWallet, getExternalNamesForConnectedWallet } from 'modules/ens/selectors'
import { fetchExternalNamesRequest } from 'modules/ens/actions'
import { deployToWorldRequest } from 'modules/deployment/actions'
import { getCurrentMetrics } from 'modules/scene/selectors'
import { recordMediaRequest } from 'modules/media/actions'
Expand Down Expand Up @@ -32,8 +31,7 @@ const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
},
onRecord: () => dispatch(recordMediaRequest()),
onNavigate: path => dispatch(push(path)),
onReplace: (path, locationState) => dispatch(replace(path, locationState)),
onFetchExternalNames: () => dispatch(fetchExternalNamesRequest())
onReplace: (path, locationState) => dispatch(replace(path, locationState))
})

export default connect(mapState, mapDispatch)(DeployToWorld)
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ export default function DeployToWorld({
onNavigate,
onReplace,
onClose,
onBack,
onFetchExternalNames
onBack
}: Props) {
const analytics = getAnalytics()

Expand Down Expand Up @@ -86,10 +85,6 @@ export default function DeployToWorld({
}
}, [claimedName, analytics])

useEffect(() => {
onFetchExternalNames()
}, [onFetchExternalNames])

const handlePublish = useCallback(() => {
if (world) {
onPublish(project.id, world)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,13 @@ export type Props = {
onRecord: typeof recordMediaRequest
onNavigate: (path: string) => void
onReplace: (path: string, locationState?: DeployToWorldLocationStateProps) => void
onFetchExternalNames: () => void
}

export type MapStateProps = Pick<
Props,
'ensList' | 'externalNames' | 'project' | 'metrics' | 'deployments' | 'deploymentProgress' | 'error' | 'isLoading'
>
export type MapDispatchProps = Pick<Props, 'onPublish' | 'onNavigate' | 'onRecord' | 'onReplace' | 'onFetchExternalNames'>
export type MapDispatchProps = Pick<Props, 'onPublish' | 'onNavigate' | 'onRecord' | 'onReplace'>
export type MapDispatch = Dispatch<
DeployToWorldRequestAction | CallHistoryMethodAction | RecordMediaRequestAction | FetchExternalNamesRequestAction
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,42 @@ import { connect } from 'react-redux'
import { push } from 'connected-react-router'
import { isLoadingType } from 'decentraland-dapps/dist/modules/loading/selectors'
import { RootState } from 'modules/common/types'
import { FETCH_ENS_LIST_REQUEST } from 'modules/ens/actions'
import { getENSByWallet, getError as getENSError, getLoading } from 'modules/ens/selectors'
import { FETCH_ENS_LIST_REQUEST, FETCH_EXTERNAL_NAMES_REQUEST } from 'modules/ens/actions'
import {
getENSByWallet,
getError as getENSError,
getExternalNamesForConnectedWallet,
getLoading as getLoadingENS
} from 'modules/ens/selectors'
import { FETCH_WORLD_DEPLOYMENTS_REQUEST } from 'modules/deployment/actions'
import { getDeploymentsByWorlds, getError as getDeploymentsError, getLoading as getDeploymentsLoading } from 'modules/deployment/selectors'
import { FETCH_LANDS_REQUEST } from 'modules/land/actions'
import { getLoading as getLandsLoading } from 'modules/land/selectors'
import { isLoggingIn, isLoggedIn } from 'modules/identity/selectors'
import { getProjects } from 'modules/ui/dashboard/selectors'
import { fetchWorldsWalletStatsRequest } from 'modules/worlds/actions'
import { getConnectedWalletStats, getLoading as getLoadingWorlds } from 'modules/worlds/selectors'
import { MapStateProps, MapDispatchProps, MapDispatch } from './WorldListPage.types'
import WorldListPage from './WorldListPage'

const mapState = (state: RootState): MapStateProps => ({
ensList: getENSByWallet(state),
externalNames: getExternalNamesForConnectedWallet(state),
deploymentsByWorlds: getDeploymentsByWorlds(state),
projects: getProjects(state),
error: getENSError(state)?.message ?? getDeploymentsError(state) ?? undefined,
isLoading:
isLoadingType(getLoading(state), FETCH_ENS_LIST_REQUEST) ||
isLoadingType(getLoadingENS(state), FETCH_ENS_LIST_REQUEST) ||
isLoadingType(getDeploymentsLoading(state), FETCH_WORLD_DEPLOYMENTS_REQUEST) ||
isLoadingType(getLandsLoading(state), FETCH_LANDS_REQUEST) ||
isLoadingType(getLoadingWorlds(state), FETCH_WORLD_DEPLOYMENTS_REQUEST) ||
isLoadingType(getLoadingENS(state), FETCH_EXTERNAL_NAMES_REQUEST) ||
isLoggingIn(state),
isLoggedIn: isLoggedIn(state),
worldsWalletStats: getConnectedWalletStats(state)
})

const mapDispatch = (dispatch: MapDispatch): MapDispatchProps => ({
onNavigate: path => dispatch(push(path)),
onFetchWorldsWalletStats: () => dispatch(fetchWorldsWalletStatsRequest())
onNavigate: path => dispatch(push(path))
})

export default connect(mapState, mapDispatch)(WorldListPage)
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,7 @@
.WorldListPage .ui.dropdown {
margin-right: 20px;
}

.WorldListPage .worlds-storage {
margin-bottom: 24px;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from 'decentraland-ui'
import { config } from 'config'
import { isDevelopment } from 'lib/environment'
import { WorldsWalletStats } from 'lib/api/worlds'
import { ENS } from 'modules/ens/types'
import { locations } from 'routing/locations'
import CopyToClipboard from 'components/CopyToClipboard/CopyToClipboard'
Expand All @@ -25,16 +26,19 @@ import { NavigationTab } from 'components/Navigation/Navigation.types'
import { Props, SortBy } from './WorldListPage.types'
import NameTabs from './NameTabs'
import WorldsStorage from './WorldsStorage'
import { TabType, useCurrentlySelectedTab } from './hooks'
import { fromBytesToMegabytes } from './utils'
import './WorldListPage.css'

const EXPLORER_URL = config.get('EXPLORER_URL', '')
const WORLDS_CONTENT_SERVER_URL = config.get('WORLDS_CONTENT_SERVER', '')
const PAGE_SIZE = 12

const WorldListPage: React.FC<Props> = props => {
const { ensList, error, deploymentsByWorlds, isLoading, projects, worldsWalletStats, onNavigate, onFetchWorldsWalletStats } = props
const { ensList, externalNames, error, deploymentsByWorlds, isLoading, projects, worldsWalletStats, onNavigate } = props
const [sortBy, setSortBy] = useState(SortBy.ASC)
const [page, setPage] = useState(1)
const { tab } = useCurrentlySelectedTab()

const isWorldDeployed = (ens: ENS) => {
if (ens.worldStatus?.healthy) {
Expand Down Expand Up @@ -81,7 +85,9 @@ const WorldListPage: React.FC<Props> = props => {
}

const paginate = () => {
return ensList
const list = tab === TabType.DCL ? ensList : externalNames

return list
.sort((a: ENS, b: ENS) => {
switch (sortBy) {
case SortBy.ASC: {
Expand Down Expand Up @@ -144,8 +150,16 @@ const WorldListPage: React.FC<Props> = props => {
)
}

const renderEnsList = () => {
const total = ensList.length
const renderWorldSize = (ens: ENS, stats?: WorldsWalletStats) => {
const names = tab === TabType.DCL ? stats?.dclNames : stats?.ensNames
const bytes = names?.find(dclName => dclName.name === ens.subdomain)?.size
const suffix = tab === TabType.ENS ? ' / 25' : ''

return !bytes ? '-' : fromBytesToMegabytes(Number(bytes)).toFixed(2) + suffix
}

const renderList = () => {
const total = tab === TabType.DCL ? ensList.length : externalNames.length
const totalPages = Math.ceil(total / PAGE_SIZE)
const paginatedItems = paginate()

Expand Down Expand Up @@ -184,6 +198,9 @@ const WorldListPage: React.FC<Props> = props => {
<Table.HeaderCell width="2">{t('worlds_list_page.table.name')}</Table.HeaderCell>
<Table.HeaderCell width="2">{t('worlds_list_page.table.url')}</Table.HeaderCell>
<Table.HeaderCell width="1">{t('worlds_list_page.table.published_scene')}</Table.HeaderCell>
<Table.HeaderCell width="1" textAlign="center">
{t('worlds_list_page.table.size')}
</Table.HeaderCell>
<Table.HeaderCell width="1" textAlign="center">
{t('worlds_list_page.table.status')}
</Table.HeaderCell>
Expand All @@ -196,6 +213,9 @@ const WorldListPage: React.FC<Props> = props => {
<Table.Cell width={2}>{ens.name}</Table.Cell>
<Table.Cell width={2}>{renderWorldUrl(ens)}</Table.Cell>
<Table.Cell width={1}>{renderPublishSceneButton(ens)}</Table.Cell>
<Table.Cell width={1} textAlign="center">
{renderWorldSize(ens, worldsWalletStats)}
</Table.Cell>
<Table.Cell width={1} textAlign="center">
{renderWorldStatus(ens)}
</Table.Cell>
Expand Down Expand Up @@ -233,9 +253,30 @@ const WorldListPage: React.FC<Props> = props => {
)
}

const renderDCLNamesView = () => {
return (
<div>
{worldsWalletStats ? (
<WorldsStorage
maxBytes={Number(worldsWalletStats.maxAllowedSpace)}
currentBytes={Number(worldsWalletStats.usedSpace)}
className="worlds-storage"
/>
) : null}
{ensList.length > 0 ? renderList() : renderEmptyPage()}
</div>
)
}

const renderENSNamesView = () => {
return <div>{externalNames.length > 0 ? renderList() : renderEmptyPage()}</div>
}

// Reset values when changing tab.
useEffect(() => {
onFetchWorldsWalletStats()
}, [onFetchWorldsWalletStats])
setSortBy(SortBy.ASC)
setPage(1)
}, [tab])

return (
<LoggedInDetailPage
Expand All @@ -245,22 +286,11 @@ const WorldListPage: React.FC<Props> = props => {
isLoading={isLoading}
isPageFullscreen={true}
>
{/** The following elements are just for show until the feature is complete, disregard the layout, just preview the components */}
<Container>
<h1>Worlds</h1>
<NameTabs />
<div
style={{
marginBottom: '1rem'
}}
>
{worldsWalletStats ? (
<WorldsStorage maxBytes={Number(worldsWalletStats.maxAllowedSpace)} currentBytes={Number(worldsWalletStats.usedSpace)} />
) : null}
</div>
{tab === TabType.DCL ? renderDCLNamesView() : renderENSNamesView()}
</Container>
{/** Old ens list which will be removed or replaced with the new worlds for ens owners feature */}
{ensList.length > 0 ? renderEnsList() : renderEmptyPage()}
</LoggedInDetailPage>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ export enum SortBy {
export type Props = {
error?: string
ensList: ENS[]
externalNames: ENS[]
deploymentsByWorlds: Record<string, Deployment>
projects: Project[]
isLoggedIn: boolean
isLoading: boolean
worldsWalletStats?: WorldsWalletStats
onNavigate: (path: string) => void
onFetchWorldsWalletStats: () => void
}

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

export type MapStateProps = Pick<
Props,
'ensList' | 'deploymentsByWorlds' | 'isLoading' | 'error' | 'projects' | 'isLoggedIn' | 'worldsWalletStats'
'ensList' | 'externalNames' | 'deploymentsByWorlds' | 'isLoading' | 'error' | 'projects' | 'isLoggedIn' | 'worldsWalletStats'
>
export type MapDispatchProps = Pick<Props, 'onNavigate' | 'onFetchWorldsWalletStats'>
export type MapDispatchProps = Pick<Props, 'onNavigate'>
export type MapDispatch = Dispatch
Original file line number Diff line number Diff line change
@@ -1,18 +1,50 @@
import { render, screen } from '@testing-library/react'
import WorldsStorage, { CURRENT_MBS_TEST_ID, PROGRESS_TEST_ID } from './WorldsStorage'
import WorldsStorage, { CURRENT_MBS_TEST_ID, PROGRESS_TEST_ID, WORLDS_STORAGE_TEST_ID } from './WorldsStorage'

describe('when rendering the worlds storage component', () => {
let currentBytes: number
let maxBytes: number
let className: string

beforeEach(() => {
currentBytes = 100
maxBytes = 100
})

describe('when the provided current bytes is 50550000 and the max bytes is 100000000', () => {
beforeEach(() => {
render(<WorldsStorage currentBytes={50550000} maxBytes={100000000} />)
currentBytes = 50550000
maxBytes = 100000000

render(<WorldsStorage currentBytes={currentBytes} maxBytes={maxBytes} />)
})

it('should render 50.55/100.00mb', () => {
expect(screen.getByTestId(CURRENT_MBS_TEST_ID).textContent).toEqual('50.55 / 100.00 mb')
expect(screen.getByTestId(CURRENT_MBS_TEST_ID).textContent).toEqual('50.55 / 100.00 Mb')
})

it('should render the storage front bar with 50%', () => {
expect(screen.getByTestId(PROGRESS_TEST_ID).children[0].getAttribute('style')).toEqual('width: 50%;')
})
})

describe('when providing a classname as prop', () => {
beforeEach(() => {
className = 'some-class'
render(<WorldsStorage currentBytes={currentBytes} maxBytes={maxBytes} className={className} />)
})
it('should set the classname to the root element', () => {
expect(screen.getByTestId(WORLDS_STORAGE_TEST_ID).className).toEqual(`worldsStorage ${className}`)
})
})

describe('when a classname is not provided', () => {
beforeEach(() => {
render(<WorldsStorage currentBytes={currentBytes} maxBytes={maxBytes} />)
})

it('should set the classname to the root element', () => {
expect(screen.getByTestId(WORLDS_STORAGE_TEST_ID).className).toEqual('worldsStorage')
})
})
})
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import React from 'react'
import classnames from 'classnames'
import { Progress } from 'decentraland-ui'
import { t } from 'decentraland-dapps/dist/modules/translation/utils'
import { Props } from './WorldsStorage.types'
import styles from './WorldsStorage.module.css'
import { fromBytesToMegabytes } from '../utils'

export const CURRENT_MBS_TEST_ID = 'worlds-storage-current-mbs'
export const PROGRESS_TEST_ID = 'worlds-storage-bar-front'
export const WORLDS_STORAGE_TEST_ID = 'worlds-storage'

const WorldsStorage = ({ maxBytes, currentBytes }: Props) => {
const maxMbs = maxBytes / 1000000
const currentMbs = currentBytes / 1000000
const WorldsStorage = ({ maxBytes, currentBytes, className }: Props) => {
const maxMbs = fromBytesToMegabytes(maxBytes)
const currentMbs = fromBytesToMegabytes(currentBytes)
const usedPercentage = (currentMbs * 100) / maxMbs

return (
<div className={styles.worldsStorage}>
<div data-testid={WORLDS_STORAGE_TEST_ID} className={classnames(styles.worldsStorage, className)}>
<div className={styles.spaceContainer}>
<span>{t('worlds_list_page.worlds_storage.space_used')}</span>
<div data-testid={CURRENT_MBS_TEST_ID}>
<span className={styles.currentMbs}>{currentMbs.toFixed(2)}</span> / {maxMbs.toFixed(2)} mb
<span className={styles.currentMbs}>{currentMbs.toFixed(2)}</span> / {maxMbs.toFixed(2)} Mb
</div>
</div>
<Progress data-testid={PROGRESS_TEST_ID} percent={Math.trunc(usedPercentage)} className={styles.bar} size="small" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type Props = {
maxBytes: number
currentBytes: number
className?: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const fromBytesToMegabytes = (bytes: number) => {
return bytes / 1000000
}
2 changes: 1 addition & 1 deletion src/modules/deployment/sagas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { CatalystClient, createCatalystClient, createContentClient } from 'dcl-c
import { expectSaga } from 'redux-saga-test-plan'
import * as matchers from 'redux-saga-test-plan/matchers'
import { buildEntity } from 'dcl-catalyst-client/dist/client/utils/DeploymentBuilder'
import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors'
import { BuilderAPI } from 'lib/api/builder'
import { getCatalystContentUrl } from 'lib/api/peer'
import { isLoggedIn } from 'modules/identity/selectors'
Expand All @@ -16,7 +17,6 @@ import { deployToWorldRequest, fetchWorldDeploymentsRequest, fetchWorldDeploymen
import { deploymentSaga } from './sagas'
import { makeContentFiles } from './contentUtils'
import { Deployment } from './types'
import { getAddress } from 'decentraland-dapps/dist/modules/wallet/selectors'

let builderAPI: BuilderAPI
let catalystClient: CatalystClient
Expand Down
Loading

1 comment on commit 5aa57f9

@vercel
Copy link

@vercel vercel bot commented on 5aa57f9 Oct 4, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

builder – ./

builder-git-master-decentraland1.vercel.app
builder-decentraland1.vercel.app

Please sign in to comment.