1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Merge branch 'feature/compute' into publish-algo

This commit is contained in:
Matthias Kretschmann 2021-02-23 11:43:49 +01:00
commit 70d3e26bab
Signed by: m
GPG Key ID: 606EEEF3C479A91F
29 changed files with 441 additions and 206 deletions

View File

@ -75,6 +75,8 @@ npm start
To use the app together with MetaMask, importing one of the accounts auto-generated by the Ganache container is the easiest way to have test ETH available. All of them have 100 ETH by default. Upon start, the `ocean_ganache_1` container will print out the private keys of multiple accounts in its logs. Pick one of them and import into MetaMask. To use the app together with MetaMask, importing one of the accounts auto-generated by the Ganache container is the easiest way to have test ETH available. All of them have 100 ETH by default. Upon start, the `ocean_ganache_1` container will print out the private keys of multiple accounts in its logs. Pick one of them and import into MetaMask.
To fully test all [The Graph](https://thegraph.com) integrations, you have to run your own local Graph node with our [`ocean-subgraph`](https://github.com/oceanprotocol/ocean-subgraph) deployed to it. Barge does not include a local subgraph so by default, the `config.subgraphUri` is hardcoded to the Rinkeby subgraph in our [`NetworkMonitor` component](https://github.com/oceanprotocol/market/blob/main/src/helpers/NetworkMonitor.tsx).
> Cleaning all Docker images so they are fetched freshly is often a good idea to make sure no issues are caused by old or stale images: `docker system prune --all --volumes` > Cleaning all Docker images so they are fetched freshly is often a good idea to make sure no issues are caused by old or stale images: `docker system prune --all --volumes`
## 🦑 Environment variables ## 🦑 Environment variables

View File

@ -1,4 +1,4 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement } from 'react'
import Footer from './organisms/Footer' import Footer from './organisms/Footer'
import Header from './organisms/Header' import Header from './organisms/Header'
import Styles from '../global/Styles' import Styles from '../global/Styles'
@ -7,15 +7,7 @@ import { useSiteMetadata } from '../hooks/useSiteMetadata'
import { useOcean } from '@oceanprotocol/react' import { useOcean } from '@oceanprotocol/react'
import Alert from './atoms/Alert' import Alert from './atoms/Alert'
import { graphql, PageProps, useStaticQuery } from 'gatsby' import { graphql, PageProps, useStaticQuery } from 'gatsby'
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
NormalizedCacheObject
} from '@apollo/client'
import fetch from 'cross-fetch'
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
const contentQuery = graphql` const contentQuery = graphql`
query AppQuery { query AppQuery {
purgatory: allFile(filter: { relativePath: { eq: "purgatory.json" } }) { purgatory: allFile(filter: { relativePath: { eq: "purgatory.json" } }) {
@ -45,24 +37,9 @@ export default function App({
const { warning } = useSiteMetadata() const { warning } = useSiteMetadata()
const { const {
isInPurgatory: isAccountInPurgatory, isInPurgatory: isAccountInPurgatory,
purgatoryData: accountPurgatory, purgatoryData: accountPurgatory
config
} = useOcean() } = useOcean()
const [client, setClient] = useState<ApolloClient<NormalizedCacheObject>>()
useEffect(() => {
const newClient = new ApolloClient({
link: new HttpLink({
uri: `${
(config as ConfigHelperConfig).subgraphUri
}/subgraphs/name/oceanprotocol/ocean-subgraph`,
fetch
}),
cache: new InMemoryCache()
})
setClient(newClient)
}, [config])
return ( return (
<Styles> <Styles>
<div className={styles.app}> <div className={styles.app}>
@ -78,13 +55,7 @@ export default function App({
state="error" state="error"
/> />
)} )}
{client ? ( <main className={styles.main}>{children}</main>
<ApolloProvider client={client}>
<main className={styles.main}>{children}</main>
</ApolloProvider>
) : (
<></>
)}
<Footer /> <Footer />
</div> </div>
</Styles> </Styles>

View File

@ -1,6 +1,6 @@
.box { .box {
display: block; display: block;
background: var(--background-body); background: var(--background-content);
border-radius: var(--border-radius); border-radius: var(--border-radius);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
box-shadow: 0 6px 17px 0 var(--box-shadow-color); box-shadow: 0 6px 17px 0 var(--box-shadow-color);

View File

@ -23,15 +23,17 @@
color: var(--color-secondary); color: var(--color-secondary);
} }
.table [role='row']:not(:last-of-type) { .table [role='row'] {
border-color: var(--border-color); border-bottom: 1px solid var(--border-color) !important;
} }
.table + div [class*='rdt_Pagination'] { .table + div [class*='rdt_Pagination'] {
font-size: var(--font-size-small); font-size: var(--font-size-small);
font-weight: var(--font-weight-bold);
color: var(--color-secondary); color: var(--color-secondary);
background: none; background: none;
min-height: 0; min-height: 0;
padding-top: calc(var(--spacer) / 2);
} }
.table + div [class*='rdt_Pagination'] svg { .table + div [class*='rdt_Pagination'] svg {
@ -50,6 +52,11 @@
fill: var(--brand-pink); fill: var(--brand-pink);
} }
.table + div [class*='rdt_Pagination'] button[aria-label='First Page'],
.table + div [class*='rdt_Pagination'] button[aria-label='Last Page'] {
display: none;
}
.empty { .empty {
width: 100%; width: 100%;
text-align: left; text-align: left;
@ -57,3 +64,11 @@
font-size: var(--font-size-small); font-size: var(--font-size-small);
font-style: italic; font-style: italic;
} }
.arrow {
composes: arrow from '../molecules/Pagination.module.css';
}
.previous {
composes: previous from '../molecules/Pagination.module.css';
}

View File

@ -1,6 +1,7 @@
import React, { ReactElement } from 'react' import React, { ReactElement } from 'react'
import DataTable, { IDataTableProps } from 'react-data-table-component' import DataTable, { IDataTableProps } from 'react-data-table-component'
import Loader from './Loader' import Loader from './Loader'
import Pagination from '../molecules/Pagination'
import styles from './Table.module.css' import styles from './Table.module.css'
interface TableProps extends IDataTableProps { interface TableProps extends IDataTableProps {
@ -35,10 +36,10 @@ export default function Table({
noHeader noHeader
pagination={pagination || data?.length >= 9} pagination={pagination || data?.length >= 9}
paginationPerPage={paginationPerPage || 10} paginationPerPage={paginationPerPage || 10}
paginationComponentOptions={{ noRowsPerPage: true }}
noDataComponent={<Empty message={emptyMessage} />} noDataComponent={<Empty message={emptyMessage} />}
progressPending={isLoading} progressPending={isLoading}
progressComponent={<Loader />} progressComponent={<Loader />}
paginationComponent={Pagination}
defaultSortField={sortField} defaultSortField={sortField}
defaultSortAsc={sortAsc} defaultSortAsc={sortAsc}
{...props} {...props}

View File

@ -5,7 +5,6 @@
.content { .content {
composes: box from './Box.module.css'; composes: box from './Box.module.css';
padding: calc(var(--spacer) / 4); padding: calc(var(--spacer) / 4);
width: calc(100% - var(--spacer) / 3);
max-width: 25rem; max-width: 25rem;
font-size: var(--font-size-small); font-size: var(--font-size-small);
} }

View File

@ -28,7 +28,9 @@ export default function Tooltip({
trigger, trigger,
disabled, disabled,
className, className,
placement placement,
link,
reference
}: { }: {
content: ReactNode content: ReactNode
children?: ReactNode children?: ReactNode
@ -36,6 +38,8 @@ export default function Tooltip({
disabled?: boolean disabled?: boolean
className?: string className?: string
placement?: Placement placement?: Placement
link?: string
reference?: string
}): ReactElement { }): ReactElement {
const [props, setSpring] = useSpring(() => animation.from) const [props, setSpring] = useSpring(() => animation.from)
@ -72,6 +76,7 @@ export default function Tooltip({
<animated.div style={props}> <animated.div style={props}>
<div className={styles.content} {...attrs}> <div className={styles.content} {...attrs}>
{content} {content}
{link && <a href={link}>{reference}</a>}
<div className={styles.arrow} data-popper-arrow /> <div className={styles.arrow} data-popper-arrow />
</div> </div>
</animated.div> </animated.div>

View File

@ -14,6 +14,10 @@
flex-direction: column; flex-direction: column;
} }
.algorithm .link {
background-color: var(--background-body);
}
.content { .content {
margin-top: calc(var(--spacer) / 2); margin-top: calc(var(--spacer) / 2);
overflow-wrap: break-word; overflow-wrap: break-word;
@ -49,18 +53,6 @@
margin: 0; margin: 0;
} }
.accessLabel {
font-size: var(--font-size-mini);
width: auto;
position: absolute;
top: 0;
right: 0;
color: var(--brand-black);
background: var(--brand-grey-lighter);
padding: 0.2rem 0.5rem;
border-bottom-left-radius: var(--border-radius);
}
.symbol { .symbol {
display: block; display: block;
} }
@ -69,3 +61,28 @@
font-size: var(--font-size-mini); font-size: var(--font-size-mini);
margin-top: calc(var(--spacer) / 2); margin-top: calc(var(--spacer) / 2);
} }
.typeDetails {
position: absolute;
top: calc(var(--spacer) / 3);
right: calc(var(--spacer) / 3);
width: auto;
font-size: var(--font-size-mini);
}
.icon {
fill: var(--brand-grey-light);
width: 1.1em;
height: 1.1em;
vertical-align: baseline;
margin-bottom: -0.2em;
display: inline-block;
}
.typeLabel {
display: inline-block;
text-transform: uppercase;
border-right: 1px solid var(--border-color);
padding-right: calc(var(--spacer) / 3.5);
margin-right: calc(var(--spacer) / 4);
}

View File

@ -5,10 +5,11 @@ import Price from '../atoms/Price'
import styles from './AssetTeaser.module.css' import styles from './AssetTeaser.module.css'
import { DDO } from '@oceanprotocol/lib' import { DDO } from '@oceanprotocol/lib'
import removeMarkdown from 'remove-markdown' import removeMarkdown from 'remove-markdown'
import Tooltip from '../atoms/Tooltip'
import Publisher from '../atoms/Publisher' import Publisher from '../atoms/Publisher'
import { useMetadata } from '@oceanprotocol/react' import { useMetadata } from '@oceanprotocol/react'
import Time from '../atoms/Time' import Time from '../atoms/Time'
import { ReactComponent as Compute } from '../../images/compute.svg'
import { ReactComponent as Download } from '../../images/download.svg'
declare type AssetTeaserProps = { declare type AssetTeaserProps = {
ddo: DDO ddo: DDO
@ -17,27 +18,35 @@ declare type AssetTeaserProps = {
const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => { const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
const { owner } = useMetadata(ddo) const { owner } = useMetadata(ddo)
const { attributes } = ddo.findServiceByType('metadata') const { attributes } = ddo.findServiceByType('metadata')
const { name } = attributes.main const { name, type } = attributes.main
const { dataTokenInfo } = ddo const { dataTokenInfo } = ddo
const isCompute = Boolean(ddo.findServiceByType('compute')) const accessType = ddo.service[1].type
return ( return (
<article className={styles.teaser}> <article className={`${styles.teaser} ${styles[type]}`}>
<Link to={`/asset/${ddo.id}`} className={styles.link}> <Link to={`/asset/${ddo.id}`} className={styles.link}>
<header className={styles.header}> <header className={styles.header}>
<Tooltip <div className={styles.symbol}>{dataTokenInfo?.symbol}</div>
placement="left"
content={dataTokenInfo?.name}
className={styles.symbol}
>
{dataTokenInfo?.symbol}
</Tooltip>
<Dotdotdot clamp={3}> <Dotdotdot clamp={3}>
<h1 className={styles.title}>{name}</h1> <h1 className={styles.title}>{name}</h1>
</Dotdotdot> </Dotdotdot>
<Publisher account={owner} minimal className={styles.publisher} /> <Publisher account={owner} minimal className={styles.publisher} />
</header> </header>
{isCompute && <div className={styles.accessLabel}>Compute</div>}
<aside className={styles.typeDetails}>
<div className={styles.typeLabel}>
{type === 'dataset' ? 'data set' : 'algorithm'}
</div>
{accessType === 'access' ? (
<Download
role="img"
aria-label="Download"
className={styles.icon}
/>
) : (
<Compute role="img" aria-label="Compute" className={styles.icon} />
)}
</aside>
<div className={styles.content}> <div className={styles.content}>
<Dotdotdot tagName="p" clamp={3}> <Dotdotdot tagName="p" clamp={3}>

View File

@ -12,3 +12,7 @@
color: var(--color-secondary) !important; color: var(--color-secondary) !important;
font-size: var(--font-size-small) !important; font-size: var(--font-size-small) !important;
} }
.info {
width: 0.85rem;
}

View File

@ -1,83 +1,47 @@
import { Logger } from '@oceanprotocol/lib'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import PriceUnit from '../atoms/Price/PriceUnit'
import axios from 'axios'
import styles from './MarketStats.module.css' import styles from './MarketStats.module.css'
import { useInView } from 'react-intersection-observer' import { gql, useQuery } from '@apollo/client'
import Conversion from '../atoms/Price/Conversion'
import PriceUnit from '../atoms/Price/PriceUnit'
import Tooltip from '../atoms/Tooltip'
interface MarketStatsResponse { const getTotalPoolsValues = gql`
datasets: { query PoolsData {
pools: number poolFactories {
exchanges: number totalValueLocked
none: number totalOceanLiquidity
total: number finalizedPoolCount
}
} }
owners: number `
ocean: number
datatoken: number
}
const refreshInterval = 60000 // 60 sec.
export default function MarketStats(): ReactElement { export default function MarketStats(): ReactElement {
const [ref, inView] = useInView() const [totalValueLocked, setTotalValueLocked] = useState<string>()
const [stats, setStats] = useState<MarketStatsResponse>() const [totalOceanLiquidity, setTotalOceanLiquidity] = useState<string>()
const [poolCount, setPoolCount] = useState<number>()
const { data } = useQuery(getTotalPoolsValues)
useEffect(() => { useEffect(() => {
const source = axios.CancelToken.source() if (!data) return
async function getStats() { setTotalValueLocked(data.poolFactories[0].totalValueLocked)
try { setTotalOceanLiquidity(data.poolFactories[0].totalOceanLiquidity)
const response = await axios('https://market-stats.oceanprotocol.com', { setPoolCount(data.poolFactories[0].finalizedPoolCount)
cancelToken: source.token }, [data])
})
if (!response || response.status !== 200) return
setStats(response.data)
} catch (error) {
if (axios.isCancel(error)) {
Logger.log(error.message)
} else {
Logger.error(error.message)
}
}
}
// Update periodically when in viewport
const interval = setInterval(getStats, refreshInterval)
if (!inView) {
clearInterval(interval)
}
getStats()
return () => {
clearInterval(interval)
source.cancel()
}
}, [inView])
return ( return (
<div className={styles.stats} ref={ref}> <div className={styles.stats}>
Total of <strong>{stats?.datasets.total}</strong> data sets & unique <Conversion price={`${totalValueLocked}`} hideApproximateSymbol />{' '}
datatokens published by <strong>{stats?.owners}</strong> accounts. <abbr title="Total Value Locked">TVL</abbr> across{' '}
<br /> <strong>{poolCount}</strong> data set pools that contain{' '}
<PriceUnit <PriceUnit price={totalOceanLiquidity} small className={styles.total} />,
price={`${stats?.ocean}`} plus datatokens for each pool.
small <Tooltip
className={styles.total} className={styles.info}
conversion content="Counted on-chain from our pool factory. Does not filter out data sets in "
/>{' '} reference="list-purgatory"
and{' '} link="https://github.com/oceanprotocol/list-purgatory"
<PriceUnit />
price={`${stats?.datatoken}`}
symbol="datatokens"
small
className={styles.total}
/>{' '}
in <strong>{stats?.datasets.pools}</strong> data set pools.
<br />
<strong>{stats?.datasets.none}</strong> data sets have no price set yet.
</div> </div>
) )
} }

View File

@ -2,21 +2,25 @@
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
margin-top: calc(var(--spacer) * 2); margin-top: var(--spacer);
margin-bottom: var(--spacer); margin-bottom: var(--spacer);
padding-left: 0; padding-left: 0;
font-size: var(--font-size-small);
} }
.number { .number {
text-align: center;
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2); padding: calc(var(--spacer) / 4) calc(var(--spacer) / 2);
margin-left: -1px; margin-left: -1px;
margin-top: -1px; margin-top: -1px;
display: inline-block; display: block;
cursor: pointer; cursor: pointer;
border: 1px solid var(--border-color); min-width: 3rem;
min-width: 3.5rem; height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: var(--color-secondary);
} }
li:first-child .number, li:first-child .number,
@ -39,8 +43,7 @@ li:last-child .number {
} }
.number:hover { .number:hover {
background-color: var(--color-primary); color: var(--brand-pink);
color: var(--brand-white);
} }
.current, .current,
@ -59,7 +62,7 @@ li:last-child .number {
.current:hover, .current:hover,
.current:focus, .current:focus,
.current:active { .current:active {
color: var(--brand-grey); color: var(--text-color);
} }
.next { .next {
@ -69,3 +72,13 @@ li:last-child .number {
.prevNextDisabled { .prevNextDisabled {
opacity: 0; opacity: 0;
} }
.arrow {
width: 1rem;
height: 1rem;
fill: currentColor;
}
.arrow.previous {
transform: rotate(180deg);
}

View File

@ -1,24 +1,49 @@
import React, { useState, useEffect, ReactElement } from 'react' import React, { useState, useEffect, ReactElement } from 'react'
import ReactPaginate from 'react-paginate' import ReactPaginate from 'react-paginate'
import styles from './Pagination.module.css' import styles from './Pagination.module.css'
import { ReactComponent as Arrow } from '../../images/arrow.svg'
interface PaginationProps { interface PaginationProps {
totalPages: number totalPages?: number
currentPage: number currentPage?: number
onPageChange(selected: number): void onChangePage?(selected: number): void
rowsPerPage?: number
rowCount?: number
} }
export default function Pagination({ export default function Pagination({
totalPages, totalPages,
currentPage, currentPage,
onPageChange rowsPerPage,
rowCount,
onChangePage
}: PaginationProps): ReactElement { }: PaginationProps): ReactElement {
const [smallViewport, setSmallViewport] = useState(true) const [smallViewport, setSmallViewport] = useState(true)
const [totalPageNumbers, setTotalPageNumbers] = useState<number>()
function getTotalPages() {
if (totalPages) return setTotalPageNumbers(totalPages)
const doublePageNumber = rowCount / rowsPerPage
const roundedPageNumber = Math.round(doublePageNumber)
const total =
roundedPageNumber < doublePageNumber
? roundedPageNumber + 1
: roundedPageNumber
setTotalPageNumbers(total)
}
function onPageChange(page: number) {
totalPages ? onChangePage(page) : onChangePage(page + 1)
}
function viewportChange(mq: { matches: boolean }) { function viewportChange(mq: { matches: boolean }) {
setSmallViewport(!mq.matches) setSmallViewport(!mq.matches)
} }
useEffect(() => {
getTotalPages()
}, [totalPages, rowCount])
useEffect(() => { useEffect(() => {
const mq = window.matchMedia('(min-width: 600px)') const mq = window.matchMedia('(min-width: 600px)')
viewportChange(mq) viewportChange(mq)
@ -29,18 +54,18 @@ export default function Pagination({
} }
}, []) }, [])
return totalPages && totalPages > 1 ? ( return totalPageNumbers && totalPageNumbers > 1 ? (
<ReactPaginate <ReactPaginate
pageCount={totalPages} pageCount={totalPageNumbers}
// react-pagination starts counting at 0, we start at 1 // react-pagination starts counting at 0, we start at 1
initialPage={currentPage - 1} initialPage={currentPage ? currentPage - 1 : 0}
// adapt based on media query match // adapt based on media query match
marginPagesDisplayed={smallViewport ? 0 : 1} marginPagesDisplayed={smallViewport ? 0 : 1}
pageRangeDisplayed={smallViewport ? 3 : 6} pageRangeDisplayed={smallViewport ? 3 : 6}
onPageChange={(data) => onPageChange(data.selected)} onPageChange={(data) => onPageChange(data.selected)}
disableInitialCallback disableInitialCallback
previousLabel="←" previousLabel={<Arrow className={`${styles.arrow} ${styles.previous}`} />}
nextLabel="→" nextLabel={<Arrow className={styles.arrow} />}
breakLabel="..." breakLabel="..."
containerClassName={styles.pagination} containerClassName={styles.pagination}
pageLinkClassName={styles.number} pageLinkClassName={styles.number}

View File

@ -11,3 +11,7 @@
.form label { .form label {
display: none; display: none;
} }
.form input {
background-color: var(--background-content);
}

View File

@ -48,7 +48,7 @@ const AssetList: React.FC<AssetListProps> = ({
<Pagination <Pagination
totalPages={totalPages} totalPages={totalPages}
currentPage={page} currentPage={page}
onPageChange={handlePageChange} onChangePage={handlePageChange}
/> />
)} )}
</> </>

View File

@ -4,20 +4,39 @@ import { useOcean } from '@oceanprotocol/react'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import Loader from '../../atoms/Loader' import Loader from '../../atoms/Loader'
import AssetList from '../../organisms/AssetList' import AssetList from '../../organisms/AssetList'
import axios from 'axios'
import { queryMetadata } from '../../../utils/aquarius'
export default function PublishedList(): ReactElement { export default function PublishedList(): ReactElement {
const { ocean, status, accountId } = useOcean() const { accountId } = useOcean()
const [queryResult, setQueryResult] = useState<QueryResult>() const [queryResult, setQueryResult] = useState<QueryResult>()
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const { config } = useOcean()
const source = axios.CancelToken.source()
const [page, setPage] = useState<number>(1)
useEffect(() => { useEffect(() => {
async function getPublished() { async function getPublished() {
if (!accountId || !ocean) return if (!accountId) return
const queryPublishedAssets = {
page: page,
offset: 9,
query: {
nativeSearch: 1,
query_string: {
query: `(publicKey.owner:${accountId})`
}
},
sort: { 'price.ocean': -1 }
}
try { try {
setIsLoading(true) queryResult || setIsLoading(true)
const queryResult = await ocean.assets.ownerAssets(accountId) const result = await queryMetadata(
setQueryResult(queryResult) queryPublishedAssets as any,
config.metadataCacheUri,
source.token
)
setQueryResult(result)
} catch (error) { } catch (error) {
Logger.error(error.message) Logger.error(error.message)
} finally { } finally {
@ -25,12 +44,20 @@ export default function PublishedList(): ReactElement {
} }
} }
getPublished() getPublished()
}, [ocean, status, accountId]) }, [accountId, page, config.metadataCacheUri])
return isLoading ? ( return isLoading ? (
<Loader /> <Loader />
) : accountId && ocean && queryResult ? ( ) : accountId && queryResult ? (
<AssetList assets={queryResult.results} showPagination={false} /> <AssetList
assets={queryResult.results}
showPagination
page={queryResult.page}
totalPages={queryResult.totalPages}
onPageChange={(newPage) => {
setPage(newPage)
}}
/>
) : ( ) : (
<div>Connect your wallet to see your published data sets.</div> <div>Connect your wallet to see your published data sets.</div>
) )

View File

@ -104,16 +104,24 @@ export default function HomePage(): ReactElement {
</section> </section>
<SectionQueryResult <SectionQueryResult
title="Highest Liquidity Pools" title="Highest Liquidity"
query={queryHighest} query={queryHighest}
action={
<Button
style="text"
to="/search?priceType=pool&sort=liquidity&sortOrder=desc"
>
Data sets and algorithms with pool
</Button>
}
/> />
<SectionQueryResult <SectionQueryResult
title="New Data Sets" title="Recently Published"
query={queryLatest} query={queryLatest}
action={ action={
<Button style="text" to="/search?sort=created&sortOrder=desc"> <Button style="text" to="/search?sort=created&sortOrder=desc">
All data sets All data sets and algorithms
</Button> </Button>
} }
/> />

View File

@ -31,3 +31,7 @@ button.filter,
background: var(--font-color-text); background: var(--font-color-text);
border-color: var(--background-body); border-color: var(--background-body);
} }
.filterList:first-of-type {
margin-bottom: calc(var(--spacer) / 6);
}

View File

@ -2,23 +2,37 @@ import React, { ReactElement } from 'react'
import { useNavigate } from '@reach/router' import { useNavigate } from '@reach/router'
import styles from './filterPrice.module.css' import styles from './filterPrice.module.css'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import { addExistingParamsToUrl, FilterByPriceOptions } from './utils' import {
addExistingParamsToUrl,
FilterByPriceOptions,
FilterByTypeOptions
} from './utils'
import Button from '../../atoms/Button' import Button from '../../atoms/Button'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
const filterItems = [ const filterItemsPrice = [
{ display: 'all', value: undefined }, { display: 'all', value: undefined },
{ display: 'fixed price', value: FilterByPriceOptions.Fixed }, { display: 'fixed price', value: FilterByPriceOptions.Fixed },
{ display: 'dynamic price', value: FilterByPriceOptions.Dynamic } { display: 'dynamic price', value: FilterByPriceOptions.Dynamic }
] ]
const filterItemsType = [
{ display: 'all', value: undefined },
{ display: 'algorithms', value: FilterByTypeOptions.Algorithm },
{ display: 'data sets', value: FilterByTypeOptions.Data }
]
export default function FilterPrice({ export default function FilterPrice({
priceType, priceType,
setPriceType setPriceType,
serviceType,
setServiceType
}: { }: {
priceType: string priceType: string
setPriceType: React.Dispatch<React.SetStateAction<string>> setPriceType: React.Dispatch<React.SetStateAction<string>>
serviceType: string
setServiceType: React.Dispatch<React.SetStateAction<string>>
}): ReactElement { }): ReactElement {
const navigate = useNavigate() const navigate = useNavigate()
@ -31,27 +45,59 @@ export default function FilterPrice({
navigate(urlLocation) navigate(urlLocation)
} }
async function applyTypeFilter(filterBy: string) {
let urlLocation = await addExistingParamsToUrl(location, 'serviceType')
if (filterBy) {
urlLocation = `${urlLocation}&serviceType=${filterBy}`
}
setServiceType(filterBy)
navigate(urlLocation)
}
return ( return (
<div> <div>
{filterItems.map((e, index) => { <div className={styles.filterList}>
const filter = cx({ {filterItemsType.map((e, index) => {
[styles.selected]: e.value === priceType, const filter = cx({
[styles.filter]: true [styles.selected]: e.value === serviceType,
}) [styles.filter]: true
return ( })
<Button return (
size="small" <Button
style="text" size="small"
key={index} style="text"
className={filter} key={index}
onClick={async () => { className={filter}
await applyFilter(e.value) onClick={async () => {
}} await applyTypeFilter(e.value)
> }}
{e.display} >
</Button> {e.display}
) </Button>
})} )
})}
</div>
<div className={styles.filterList}>
{filterItemsPrice.map((e, index) => {
const filter = cx({
[styles.selected]: e.value === priceType,
[styles.filter]: true
})
return (
<Button
size="small"
style="text"
key={index}
className={filter}
onClick={async () => {
await applyFilter(e.value)
}}
>
{e.display}
</Button>
)
})}
</div>
</div> </div>
) )
} }

View File

@ -21,10 +21,20 @@ export default function SearchPage({
}): ReactElement { }): ReactElement {
const { config } = useOcean() const { config } = useOcean()
const parsed = queryString.parse(location.search) const parsed = queryString.parse(location.search)
const { text, owner, tags, page, sort, sortOrder, priceType } = parsed const {
text,
owner,
tags,
page,
sort,
sortOrder,
priceType,
serviceType
} = parsed
const [queryResult, setQueryResult] = useState<QueryResult>() const [queryResult, setQueryResult] = useState<QueryResult>()
const [loading, setLoading] = useState<boolean>() const [loading, setLoading] = useState<boolean>()
const [price, setPriceType] = useState<string>(priceType as string) const [price, setPriceType] = useState<string>(priceType as string)
const [type, setType] = useState<string>(serviceType as string)
const [sortType, setSortType] = useState<string>(sort as string) const [sortType, setSortType] = useState<string>(sort as string)
const [sortDirection, setSortDirection] = useState<string>( const [sortDirection, setSortDirection] = useState<string>(
sortOrder as string sortOrder as string
@ -49,6 +59,7 @@ export default function SearchPage({
sort, sort,
page, page,
priceType, priceType,
serviceType,
sortOrder, sortOrder,
config.metadataCacheUri config.metadataCacheUri
]) ])
@ -69,7 +80,12 @@ export default function SearchPage({
<SearchBar initialValue={(text || owner) as string} /> <SearchBar initialValue={(text || owner) as string} />
)} )}
<div className={styles.row}> <div className={styles.row}>
<PriceFilter priceType={price} setPriceType={setPriceType} /> <PriceFilter
priceType={price}
setPriceType={setPriceType}
serviceType={type}
setServiceType={setType}
/>
<Sort <Sort
sortType={sortType} sortType={sortType}
sortDirection={sortDirection} sortDirection={sortDirection}

View File

@ -1,4 +1,6 @@
.sortList { .sortList {
align-self: flex-end;
padding: calc(var(--spacer) / 10);
display: flex; display: flex;
align-items: center; align-items: center;
border-radius: var(--border-radius); border-radius: var(--border-radius);

View File

@ -4,7 +4,8 @@ import {
addExistingParamsToUrl, addExistingParamsToUrl,
SortTermOptions, SortTermOptions,
SortValueOptions, SortValueOptions,
FilterByPriceOptions FilterByPriceOptions,
FilterByTypeOptions
} from './utils' } from './utils'
import Button from '../../atoms/Button' import Button from '../../atoms/Button'
import styles from './sort.module.css' import styles from './sort.module.css'

View File

@ -4,6 +4,7 @@ import {
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import { MetadataCache, Logger } from '@oceanprotocol/lib' import { MetadataCache, Logger } from '@oceanprotocol/lib'
import queryString from 'query-string' import queryString from 'query-string'
import { TypeOf } from 'yup'
export const SortTermOptions = { export const SortTermOptions = {
Liquidity: 'liquidity', Liquidity: 'liquidity',
@ -31,6 +32,12 @@ export const FilterByPriceOptions = {
} as const } as const
type FilterByPriceOptions = typeof FilterByPriceOptions[keyof typeof FilterByPriceOptions] type FilterByPriceOptions = typeof FilterByPriceOptions[keyof typeof FilterByPriceOptions]
export const FilterByTypeOptions = {
Data: 'dataset',
Algorithm: 'algorithm'
} as const
type FilterByTypeOptions = typeof FilterByTypeOptions[keyof typeof FilterByTypeOptions]
function addPriceFilterToQuerry(sortTerm: string, priceFilter: string): string { function addPriceFilterToQuerry(sortTerm: string, priceFilter: string): string {
sortTerm = priceFilter sortTerm = priceFilter
? sortTerm === '' ? sortTerm === ''
@ -40,6 +47,15 @@ function addPriceFilterToQuerry(sortTerm: string, priceFilter: string): string {
return sortTerm return sortTerm
} }
function addTypeFilterToQuery(sortTerm: string, typeFilter: string): string {
sortTerm = typeFilter
? sortTerm === ''
? `service.attributes.main.type:${typeFilter}`
: `${sortTerm} AND service.attributes.main.type:${typeFilter}`
: sortTerm
return sortTerm
}
function getSortType(sortParam: string): string { function getSortType(sortParam: string): string {
const sortTerm = const sortTerm =
sortParam === SortTermOptions.Liquidity sortParam === SortTermOptions.Liquidity
@ -59,7 +75,8 @@ export function getSearchQuery(
offset?: string, offset?: string,
sort?: string, sort?: string,
sortOrder?: string, sortOrder?: string,
priceType?: string priceType?: string,
serviceType?: string
): SearchQuery { ): SearchQuery {
const sortTerm = getSortType(sort) const sortTerm = getSortType(sort)
const sortValue = sortOrder === SortValueOptions.Ascending ? 1 : -1 const sortValue = sortOrder === SortValueOptions.Ascending ? 1 : -1
@ -73,7 +90,8 @@ export function getSearchQuery(
`(service.attributes.additionalInformation.categories:\"${categories}\")` `(service.attributes.additionalInformation.categories:\"${categories}\")`
: text || '' : text || ''
searchTerm = addPriceFilterToQuerry(searchTerm, priceType) searchTerm = addPriceFilterToQuerry(searchTerm, priceType)
searchTerm = addTypeFilterToQuery(searchTerm, serviceType)
console.log('search', searchTerm, serviceType)
return { return {
page: Number(page) || 1, page: Number(page) || 1,
offset: Number(offset) || 21, offset: Number(offset) || 21,
@ -112,6 +130,7 @@ export async function getResults(
sort?: string sort?: string
sortOrder?: string sortOrder?: string
priceType?: string priceType?: string
serviceType?: string
}, },
metadataCacheUri: string metadataCacheUri: string
): Promise<QueryResult> { ): Promise<QueryResult> {
@ -124,7 +143,8 @@ export async function getResults(
categories, categories,
sort, sort,
sortOrder, sortOrder,
priceType priceType,
serviceType
} = params } = params
const metadataCache = new MetadataCache(metadataCacheUri, Logger) const metadataCache = new MetadataCache(metadataCacheUri, Logger)
const searchQuery = getSearchQuery( const searchQuery = getSearchQuery(
@ -136,7 +156,8 @@ export async function getResults(
offset, offset,
sort, sort,
sortOrder, sortOrder,
priceType priceType,
serviceType
) )
const queryResult = await metadataCache.queryMetadata(searchQuery) const queryResult = await metadataCache.queryMetadata(searchQuery)

View File

@ -25,8 +25,9 @@
/* Only use these vars for most color referencing for easy light/dark mode */ /* Only use these vars for most color referencing for easy light/dark mode */
--font-color-text: #41474e; --font-color-text: #41474e;
--font-color-heading: #141414; --font-color-heading: #141414;
--background-body: #fff; --background-body: #fafafa;
--background-body-transparent: rgba(255, 255, 255, 0.8); --background-body-transparent: rgba(255, 255, 255, 0.8);
--background-content: #fff;
--background-highlight: #f7f7f7; --background-highlight: #f7f7f7;
--border-color: #e2e2e2; --border-color: #e2e2e2;
--box-shadow-color: rgba(0, 0, 0, 0.05); --box-shadow-color: rgba(0, 0, 0, 0.05);
@ -70,8 +71,9 @@
.dark { .dark {
--font-color-text: #e2e2e2; --font-color-text: #e2e2e2;
--font-color-heading: #f7f7f7; --font-color-heading: #f7f7f7;
--background-body: #141414; --background-body: rgb(10, 10, 10);
--background-body-transparent: rgba(20, 20, 20, 0.9); --background-body-transparent: rgba(10, 10, 10, 0.9);
--background-content: #141414;
--background-highlight: #201f1f; --background-highlight: #201f1f;
--border-color: #303030; --border-color: #303030;
--box-shadow-color: rgba(0, 0, 0, 0.2); --box-shadow-color: rgba(0, 0, 0, 0.2);

View File

@ -7,6 +7,18 @@ import contractAddresses from '@oceanprotocol/contracts/artifacts/address.json'
const refreshInterval = 5000 // 5 sec. const refreshInterval = 5000 // 5 sec.
export function getDevelopmentConfig(): Partial<ConfigHelperConfig> {
return {
factoryAddress: contractAddresses.development?.DTFactory,
poolFactoryAddress: contractAddresses.development?.BFactory,
fixedRateExchangeAddress: contractAddresses.development?.FixedRateExchange,
metadataContractAddress: contractAddresses.development?.Metadata,
oceanTokenAddress: contractAddresses.development?.Ocean,
// There is no subgraph in barge so we hardcode the Rinkeby one for now
subgraphUri: 'https://subgraph.rinkeby.oceanprotocol.com'
}
}
export function NetworkMonitor(): ReactElement { export function NetworkMonitor(): ReactElement {
const { const {
connect, connect,
@ -28,12 +40,7 @@ export function NetworkMonitor(): ReactElement {
// add local dev values // add local dev values
...(chainId === '8996' && { ...(chainId === '8996' && {
factoryAddress: contractAddresses.development?.DTFactory, ...getDevelopmentConfig()
poolFactoryAddress: contractAddresses.development?.BFactory,
fixedRateExchangeAddress:
contractAddresses.development?.FixedRateExchange,
metadataContractAddress: contractAddresses.development?.Metadata,
oceanTokenAddress: contractAddresses.development?.Ocean
}) })
} }

View File

@ -2,7 +2,7 @@ import React, { ReactElement } from 'react'
import { OceanProvider } from '@oceanprotocol/react' import { OceanProvider } from '@oceanprotocol/react'
import { ConfigHelper, Config } from '@oceanprotocol/lib' import { ConfigHelper, Config } from '@oceanprotocol/lib'
import { web3ModalOpts } from '../utils/wallet' import { web3ModalOpts } from '../utils/wallet'
import { NetworkMonitor } from './NetworkMonitor' import { getDevelopmentConfig, NetworkMonitor } from './NetworkMonitor'
import appConfig from '../../app.config' import appConfig from '../../app.config'
import { import {
ConfigHelperNetworkName, ConfigHelperNetworkName,
@ -10,6 +10,7 @@ import {
} from '@oceanprotocol/lib/dist/node/utils/ConfigHelper' } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
import { UserPreferencesProvider } from '../providers/UserPreferences' import { UserPreferencesProvider } from '../providers/UserPreferences'
import PricesProvider from '../providers/Prices' import PricesProvider from '../providers/Prices'
import ApolloClientProvider from '../providers/ApolloClientProvider'
export function getOceanConfig( export function getOceanConfig(
network: ConfigHelperNetworkName | ConfigHelperNetworkId network: ConfigHelperNetworkName | ConfigHelperNetworkId
@ -26,16 +27,26 @@ export default function wrapRootElement({
element: ReactElement element: ReactElement
}): ReactElement { }): ReactElement {
const { network } = appConfig const { network } = appConfig
const oceanInitialConfig = getOceanConfig(network) const oceanInitialConfig = {
...getOceanConfig(network),
// add local dev values
...(network === 'development' && {
...getDevelopmentConfig()
})
}
return ( return (
<OceanProvider <OceanProvider
initialConfig={oceanInitialConfig} initialConfig={oceanInitialConfig}
web3ModalOpts={web3ModalOpts} web3ModalOpts={web3ModalOpts}
> >
<UserPreferencesProvider> <ApolloClientProvider>
<NetworkMonitor /> <UserPreferencesProvider>
<PricesProvider>{element}</PricesProvider> <NetworkMonitor />
</UserPreferencesProvider> <PricesProvider>{element}</PricesProvider>
</UserPreferencesProvider>
</ApolloClientProvider>
</OceanProvider> </OceanProvider>
) )
} }

5
src/images/compute.svg Normal file
View File

@ -0,0 +1,5 @@
<svg width="19" height="19" viewBox="0 0 19 19" xmlns="http://www.w3.org/2000/svg">
<path d="M14 0H5V9H14V0ZM7 2H12V7H7V2Z" />
<path d="M9 10V19H0V10H9ZM7 12H2V17H7V12Z" />
<path d="M19 10V19H10V10H19ZM17 12H12V17H17V12Z" />
</svg>

After

Width:  |  Height:  |  Size: 232 B

3
src/images/download.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="18" height="20" viewBox="0 0 18 20" xmlns="http://www.w3.org/2000/svg">
<path d="M8.11978 0.980591L8.11977 16.4369L1.25897 9.38744L0.0134277 10.6671L8.99989 19.8995L17.9864 10.6671L16.7425 9.38744L9.88001 16.4369L9.88001 0.980591L8.11978 0.980591Z" />
</svg>

After

Width:  |  Height:  |  Size: 271 B

View File

@ -0,0 +1,53 @@
import {
ApolloClient,
ApolloProvider,
HttpLink,
InMemoryCache,
NormalizedCacheObject
} from '@apollo/client'
import { Logger } from '@oceanprotocol/lib'
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
import { useOcean } from '@oceanprotocol/react'
import fetch from 'cross-fetch'
import React, { useState, useEffect, ReactNode, ReactElement } from 'react'
function createClient(subgraphUri: string) {
const client = new ApolloClient({
link: new HttpLink({
uri: `${subgraphUri}/subgraphs/name/oceanprotocol/ocean-subgraph`,
fetch
}),
cache: new InMemoryCache()
})
return client
}
export default function ApolloClientProvider({
children
}: {
children: ReactNode
}): ReactElement {
const { config } = useOcean()
const [client, setClient] = useState<ApolloClient<NormalizedCacheObject>>()
useEffect(() => {
if (!(config as ConfigHelperConfig)?.subgraphUri) {
Logger.error(
'No subgraphUri defined, preventing ApolloProvider from initialization.'
)
return
}
const newClient = createClient((config as ConfigHelperConfig).subgraphUri)
setClient(newClient)
}, [config])
return client ? (
<ApolloProvider client={client}>{children}</ApolloProvider>
) : (
<></>
)
}
export { ApolloClientProvider }