Skip to content

Commit

Permalink
Merge pull request #1441 from oasisprotocol/csillag/more-extension-po…
Browse files Browse the repository at this point in the history
…ints

More configuration options for paratimes
  • Loading branch information
csillag authored Aug 13, 2024
2 parents 3fb7588 + 04a6871 commit 089384b
Show file tree
Hide file tree
Showing 18 changed files with 152 additions and 85 deletions.
1 change: 1 addition & 0 deletions .changelog/1441.internal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add more configuration options for paratimes
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,5 @@ REACT_APP_STAGING_URLS=https://explorer.stg.oasis.io
REACT_APP_SHOW_BUILD_BANNERS=true
# REACT_APP_FIXED_NETWORK=testnet
# REACT_APP_FIXED_LAYER=sapphire
# REACT_APP_SKIP_GRAPH=true
REACT_APP_SHOW_FIAT_VALUES=true
1 change: 1 addition & 0 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ REACT_APP_STAGING_URLS=https://explorer.stg.oasis.io
REACT_APP_SHOW_BUILD_BANNERS=true
# REACT_APP_FIXED_NETWORK=testnet
# REACT_APP_FIXED_LAYER=sapphire
# REACT_APP_SKIP_GRAPH=true
REACT_APP_SHOW_FIAT_VALUES=true
2 changes: 1 addition & 1 deletion .storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import { Preview } from '@storybook/react'
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport'
import '../src/locales/i18n'
import { withDefaultTheme } from '../src/app/components/ThemeByNetwork'
import { withDefaultTheme } from '../src/app/components/ThemeByScope'
import { initialize, mswLoader } from 'msw-storybook-addon'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { handlers } from '../internals/mocks/msw-handlers'
Expand Down
6 changes: 3 additions & 3 deletions src/app/components/AnalyticsConsent/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Button from '@mui/material/Button'
import Link from '@mui/material/Link'
import { Trans, useTranslation } from 'react-i18next'
import * as matomo from './initializeMatomo'
import { ThemeByNetwork } from '../ThemeByNetwork'
import { ThemeByScope } from '../ThemeByScope'
import { Network } from '../../../types/network'
import { AnalyticsIsBlocked } from './AnalyticsIsBlocked'
import { AnalyticsDialogLayout } from './AnalyticsDialogLayout'
Expand Down Expand Up @@ -63,7 +63,7 @@ export const AnalyticsConsentProvider = (props: { children: React.ReactNode }) =
>
{props.children}
{/* Theme is needed because AnalyticsConsentProvider is outside network-themed routes */}
<ThemeByNetwork isRootTheme={false} network={Network.mainnet}>
<ThemeByScope isRootTheme={false} network={Network.mainnet}>
<AnalyticsConsentView
isOpen={hasAccepted === 'not-chosen'}
onAccept={async () => {
Expand All @@ -80,7 +80,7 @@ export const AnalyticsConsentProvider = (props: { children: React.ReactNode }) =
onReload={() => window.location.reload()}
onClose={() => setHasAccepted('timed_out_matomo_not_loaded')}
/>
</ThemeByNetwork>
</ThemeByScope>
</AnalyticsContext.Provider>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,28 @@
import { FC, ReactNode } from 'react'
import { Network } from '../../../types/network'
import { ThemeProvider } from '@mui/material/styles'
import { getThemesForNetworks } from '../../../styles/theme'
import { getThemeForScope } from '../../../styles/theme'
import CssBaseline from '@mui/material/CssBaseline'
import { fixedNetwork } from '../../utils/route-utils'
import { Layer } from '../../../oasis-nexus/api'

export const ThemeByNetwork: FC<{ network: Network; isRootTheme: boolean; children: React.ReactNode }> = ({
network,
isRootTheme,
children,
}) => (
<ThemeProvider theme={getThemesForNetworks()[network]}>
export const ThemeByScope: FC<{
network: Network
layer?: Layer
isRootTheme: boolean
children: React.ReactNode
}> = ({ network, layer, isRootTheme, children }) => (
<ThemeProvider theme={getThemeForScope(network, layer)}>
{isRootTheme && <CssBaseline />}
{children}
</ThemeProvider>
)

export const withDefaultTheme = (node: ReactNode, alwaysMainnet = false) => (
<ThemeByNetwork
<ThemeByScope
isRootTheme={true}
network={alwaysMainnet ? Network.mainnet : fixedNetwork ?? Network.mainnet}
>
{node}
</ThemeByNetwork>
</ThemeByScope>
)
6 changes: 3 additions & 3 deletions src/app/pages/HomePage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { useTranslation } from 'react-i18next'
import { ParaTimeSelectorStep } from './Graph/types'
import { BuildBanner } from '../../components/BuildBanner'
import { useSearchQueryNetworkParam } from '../../hooks/useSearchQueryNetworkParam'
import { ThemeByNetwork } from '../../components/ThemeByNetwork'
import { ThemeByScope } from '../../components/ThemeByScope'
import { NetworkOfflineBanner } from '../../components/OfflineBanner'
import { useIsApiReachable } from '../../components/OfflineBanner/hook'

Expand Down Expand Up @@ -168,7 +168,7 @@ export const HomePage: FC = () => {
</InfoScreenBtn>
)}
</SearchInputContainer>
<ThemeByNetwork isRootTheme={false} network={network}>
<ThemeByScope isRootTheme={false} network={network}>
<Box sx={{ zIndex: zIndexHomePage.paraTimeSelector }}>
<ParaTimeSelector
step={step}
Expand All @@ -179,7 +179,7 @@ export const HomePage: FC = () => {
onGraphZoomedIn={setIsGraphZoomedIn}
/>
</Box>
</ThemeByNetwork>
</ThemeByScope>
</Content>

<FooterStyled>
Expand Down
6 changes: 3 additions & 3 deletions src/app/pages/RoutingErrorPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@ import Divider from '@mui/material/Divider'
import { PageLayout } from '../../components/PageLayout'
import { ErrorDisplay } from '../../components/ErrorDisplay'
import { useRouteError } from 'react-router-dom'
import { ThemeByNetwork } from '../../components/ThemeByNetwork'
import { ThemeByScope } from '../../components/ThemeByScope'
import { useScopeParam } from '../../hooks/useScopeParam'
import { Network } from '../../../types/network'

export const RoutingErrorPage: FC = () => {
const scope = useScopeParam()
return (
<ThemeByNetwork isRootTheme={true} network={scope?.network ?? Network.mainnet}>
<ThemeByScope isRootTheme={true} network={scope?.network ?? Network.mainnet} layer={scope?.layer}>
<PageLayout>
<Divider variant="layout" />
<ErrorDisplay error={useRouteError()} />
</PageLayout>
</ThemeByNetwork>
</ThemeByScope>
)
}
5 changes: 2 additions & 3 deletions src/app/pages/SearchResultsPage/GlobalSearchResultsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
Network,
} from '../../../types/network'
import { HideMoreResults, ShowMoreResults } from './notifications'
import { getThemesForNetworks } from '../../../styles/theme'
import { getThemeForScope } from '../../../styles/theme'
import { orderByLayer } from '../../../types/layers'
import { useRedirectIfSingleResult } from './useRedirectIfSingleResult'
import { SearchParams } from '../../components/Search/search-utils'
Expand All @@ -30,7 +30,6 @@ export const GlobalSearchResultsView: FC<{
const [othersOpen, setOthersOpen] = useState(false)
useRedirectIfSingleResult(undefined, searchParams, searchResults)

const themes = getThemesForNetworks()
const networkNames = getNetworkNames(t)
const { searchTerm } = searchParams

Expand All @@ -51,7 +50,7 @@ export const GlobalSearchResultsView: FC<{
}

const otherNetworks = RouteUtils.getEnabledNetworks().filter(isNotMainnet)
const notificationTheme = themes[Network.testnet]
const notificationTheme = getThemeForScope(Network.testnet)
const mainnetResults = searchResults.filter(isOnMainnet).sort(orderByLayer)
const otherResults = searchResults.filter(isNotOnMainnet).sort(orderByLayer)

Expand Down
7 changes: 4 additions & 3 deletions src/app/pages/SearchResultsPage/ScopedSearchResultsView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
getInverseFilterForScope,
SearchScope,
} from '../../../types/searchScope'
import { getThemesForNetworks } from '../../../styles/theme'
import { getThemeForScope } from '../../../styles/theme'
import { RouteUtils } from '../../utils/route-utils'
import { SearchResults } from './hooks'
import { SearchResultsList } from './SearchResultsList'
Expand All @@ -27,12 +27,13 @@ export const ScopedSearchResultsView: FC<{
const { t } = useTranslation()
const [othersOpen, setOthersOpen] = useState(false)
const networkNames = getNetworkNames(t)
const themes = getThemesForNetworks()
const isInWantedScope = getFilterForScope(wantedScope)
const isNotInWantedScope = getInverseFilterForScope(wantedScope)
const wantedResults = searchResults.filter(isInWantedScope)
const otherResults = searchResults.filter(isNotInWantedScope)
const notificationTheme = themes[otherResults.some(isOnMainnet) ? Network.mainnet : Network.testnet]
const notificationTheme = getThemeForScope(
otherResults.some(isOnMainnet) ? Network.mainnet : Network.testnet,
)

useRedirectIfSingleResult(wantedScope, searchParams, searchResults)

Expand Down
4 changes: 2 additions & 2 deletions src/app/pages/SearchResultsPage/SearchResultsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
TokenResult,
TransactionResult,
} from './hooks'
import { getThemesForNetworks } from '../../../styles/theme'
import { getThemeForScope } from '../../../styles/theme'
import { Network } from '../../../types/network'
import { SubPageCard } from '../../components/SubPageCard'
import { AllTokenPrices } from '../../../coin-gecko/api'
Expand Down Expand Up @@ -44,7 +44,7 @@ export const SearchResultsList: FC<{
if (!numberOfResults) {
return null
}
const theme = getThemesForNetworks()[networkForTheme]
const theme = getThemeForScope(networkForTheme)

return (
<ResultListFrame theme={theme}>
Expand Down
2 changes: 1 addition & 1 deletion src/app/utils/renderWithProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { MemoryRouter } from 'react-router-dom'
import { render } from '@testing-library/react'
import { withDefaultTheme } from '../components/ThemeByNetwork'
import { withDefaultTheme } from '../components/ThemeByScope'
import React from 'react'
import { useIsApiReachable, useRuntimeFreshness } from '../components/OfflineBanner/hook'

Expand Down
108 changes: 67 additions & 41 deletions src/app/utils/route-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@ import { AppError, AppErrors } from '../../types/errors'
import { EvmTokenType, Layer } from '../../oasis-nexus/api'
import { Network } from '../../types/network'
import { SearchScope } from '../../types/searchScope'
import { isStableDeploy } from '../../config'
import { isStableDeploy, specialScopePaths } from '../../config'
import { getSearchTermFromRequest } from '../components/Search/search-utils'
import type { HasLayer } from '../../types/layers'

export const fixedNetwork = process.env.REACT_APP_FIXED_NETWORK as Network | undefined
export const fixedLayer = process.env.REACT_APP_FIXED_LAYER as Layer | undefined
export const skipGraph = !!fixedLayer || !!(process.env.REACT_APP_SKIP_GRAPH as boolean | undefined)

export type ScopeFreedom =
| 'network' // We can select only the network
Expand All @@ -37,6 +38,33 @@ export type SpecifiedPerEnabledLayer<T = any, ExcludeLayers = never> = {

export type SpecifiedPerEnabledRuntime<T = any> = SpecifiedPerEnabledLayer<T, typeof Layer.consensus>

export const specialScopeRecognition: Partial<Record<string, Partial<Record<string, SearchScope>>>> = {}

function invertSpecialScopePaths() {
const networks = Object.keys(specialScopePaths) as Network[]

networks.forEach(network => {
const networkPaths = specialScopePaths[network]!
const layers = Object.keys(networkPaths) as Layer[]
layers.forEach(layer => {
const [word1, word2] = networkPaths[layer]!
if (!specialScopeRecognition[word1]) {
specialScopeRecognition[word1] = {}
}
if (specialScopeRecognition[word1]![word2]) {
const other = specialScopeRecognition[word1]![word2]!
console.warn(
`Wrong config: conflicting special scope paths ${word1}/${word2} definitions used both for ${other.network}/${other.layer} and ${network}/${layer} `,
)
} else {
specialScopeRecognition[word1]![word2] = { network, layer }
}
})
})
}

invertSpecialScopePaths()

export const hiddenLayers: Layer[] = [Layer.pontusxdev]

export const isLayerHidden = (layer: Layer): boolean => hiddenLayers.includes(layer)
Expand Down Expand Up @@ -65,38 +93,38 @@ export abstract class RouteUtils {
},
} satisfies Record<Network, Record<Layer, boolean>>

static getDashboardRoute = ({ network, layer }: SearchScope) => {
return `/${encodeURIComponent(network)}/${encodeURIComponent(layer)}`
static getScopeRoute = ({ network, layer }: SearchScope) => {
const specialPath = specialScopePaths[network]?.[layer]
const result = specialPath
? `/${specialPath[0]}/${specialPath[1]}`
: `/${encodeURIComponent(network)}/${encodeURIComponent(layer)}`
return result
}

static getLatestTransactionsRoute = ({ network, layer }: SearchScope) => {
return `/${encodeURIComponent(network)}/${encodeURIComponent(layer)}/tx`
static getDashboardRoute = (scope: SearchScope) => this.getScopeRoute(scope)

static getLatestTransactionsRoute = (scope: SearchScope) => {
return `${this.getScopeRoute(scope)}/tx`
}

static getTopTokensRoute = ({ network, layer }: SearchScope) => {
return `/${encodeURIComponent(network)}/${encodeURIComponent(layer)}/token`
static getTopTokensRoute = (scope: SearchScope) => {
return `${this.getScopeRoute(scope)}/token`
}

static getLatestBlocksRoute = ({ network, layer }: SearchScope) => {
return `/${encodeURIComponent(network)}/${encodeURIComponent(layer)}/block`
static getLatestBlocksRoute = (scope: SearchScope) => {
return `${this.getScopeRoute(scope)}/block`
}

static getBlockRoute = ({ network, layer }: SearchScope, blockHeight: number) => {
return `/${encodeURIComponent(network)}/${encodeURIComponent(layer)}/block/${encodeURIComponent(
blockHeight,
)}`
static getBlockRoute = (scope: SearchScope, blockHeight: number) => {
return `${this.getScopeRoute(scope)}/block/${encodeURIComponent(blockHeight)}`
}

static getTransactionRoute = (scope: SearchScope, txHash: string) => {
return `/${encodeURIComponent(scope.network)}/${encodeURIComponent(scope.layer)}/tx/${encodeURIComponent(
txHash,
)}`
return `${this.getScopeRoute(scope)}/tx/${encodeURIComponent(txHash)}`
}

static getAccountRoute = ({ network, layer }: SearchScope, account: string) => {
return `/${encodeURIComponent(network)}/${encodeURIComponent(layer)}/address/${encodeURIComponent(
account,
)}`
static getAccountRoute = (scope: SearchScope, accountAddress: string) => {
return `${this.getScopeRoute(scope)}/address/${encodeURIComponent(accountAddress)}`
}

static getAccountsRoute = (network: Network) => {
Expand Down Expand Up @@ -128,28 +156,20 @@ export abstract class RouteUtils {

static getSearchRoute = (scope: SearchScope | undefined, searchTerm: string) => {
return scope
? `/${encodeURIComponent(scope.network)}/${encodeURIComponent(scope.layer)}/search?q=${encodeURIComponent(searchTerm)}`
? `${this.getScopeRoute(scope)}/search?q=${encodeURIComponent(searchTerm)}`
: `/search?q=${encodeURIComponent(searchTerm)}`
}

static getTokenRoute = ({ network, layer }: SearchScope, tokenAddress: string) => {
return `/${encodeURIComponent(network)}/${encodeURIComponent(layer)}/token/${encodeURIComponent(
tokenAddress,
)}`
static getTokenRoute = (scope: SearchScope, tokenAddress: string) => {
return `${this.getScopeRoute(scope)}/token/${encodeURIComponent(tokenAddress)}`
}

static getTokenHoldersRoute = ({ network, layer }: SearchScope, tokenAddress: string) => {
return `/${encodeURIComponent(network)}/${encodeURIComponent(layer)}/token/${encodeURIComponent(
tokenAddress,
)}/holders`
static getTokenHoldersRoute = (scope: SearchScope, tokenAddress: string) => {
return `${this.getScopeRoute(scope)}/token/${encodeURIComponent(tokenAddress)}/holders`
}

static getNFTInstanceRoute = (
{ network, layer }: SearchScope,
contractAddress: string,
instanceId: string,
): string =>
`/${encodeURIComponent(network)}/${encodeURIComponent(layer)}/token/${encodeURIComponent(
static getNFTInstanceRoute = (scope: SearchScope, contractAddress: string, instanceId: string): string =>
`${this.getScopeRoute(scope)}/token/${encodeURIComponent(
contractAddress,
)}/instance/${encodeURIComponent(instanceId)}`

Expand Down Expand Up @@ -315,19 +335,25 @@ export const runtimeTransactionParamLoader = async ({ params }: LoaderFunctionAr
return validateRuntimeTxHashParam(params.hash!)
}

export const assertEnabledScope = ({
network,
layer,
}: {
export const assertEnabledScope = (params: {
network: string | undefined
layer: string | undefined
}): SearchScope => {
if (!network || !RouteUtils.getEnabledNetworks().includes(network as Network)) {
const { network: networkLike, layer: layerLike } = params
if (!networkLike || !layerLike) {
throw new AppError(AppErrors.InvalidUrl)
}

const { network, layer } = specialScopeRecognition[networkLike]?.[layerLike] ?? {
network: networkLike as Network,
layer: layerLike as Layer,
}

if (!RouteUtils.getEnabledNetworks().includes(network as Network)) {
throw new AppError(AppErrors.InvalidUrl)
}

if (
!layer || // missing param
!RouteUtils.getAllLayersForNetwork(network as Network).enabled.includes(layer as Layer) // unsupported on network
) {
throw new AppError(AppErrors.UnsupportedLayer)
Expand Down
Loading

0 comments on commit 089384b

Please sign in to comment.