mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
pull from origin main
This commit is contained in:
commit
20361578ca
13
README.md
13
README.md
@ -25,6 +25,7 @@
|
||||
- [🛳 Production](#-production)
|
||||
- [⬆️ Deployment](#️-deployment)
|
||||
- [💖 Contributing](#-contributing)
|
||||
- [🍴 Forking](#-forking)
|
||||
- [🏛 License](#-license)
|
||||
|
||||
## 🏄 Get Started
|
||||
@ -358,6 +359,18 @@ We welcome contributions in form of bug reports, feature requests, code changes,
|
||||
- [Code of Conduct →](https://docs.oceanprotocol.com/concepts/code-of-conduct/)
|
||||
- [Reporting Vulnerabilities →](https://docs.oceanprotocol.com/concepts/vulnerabilities/)
|
||||
|
||||
## 🍴 Forking
|
||||
|
||||
We encourage you to fork this repository and create your own data marketplace. When you publish your forked version of this market there are a few elements that you are required to change for copyright reasons:
|
||||
|
||||
- The typeface is copyright protected and needs to be changed unless you purchase a license for it.
|
||||
- The Ocean Protocol logo is a trademark of the Ocean Protocol Foundation and must be removed from forked versions of the market.
|
||||
- The name "Ocean Market" is also copyright protected and should be changed to the name of your market.
|
||||
|
||||
Additionally, we would also advise that your retain the text saying "Powered by Ocean Protocol" on your forked version of the marketplace in order to give credit for the development work done by the Ocean Protocol team.
|
||||
|
||||
Everything else is made open according to the apache2 license. We look forward to seeing your data marketplace!
|
||||
|
||||
## 🏛 License
|
||||
|
||||
```text
|
||||
|
@ -28,8 +28,8 @@
|
||||
"label": "Docker Image",
|
||||
"placeholder": "e.g. python3.7",
|
||||
"help": "Please select an image to run your algorithm.",
|
||||
"type": "select",
|
||||
"options": ["node:latest", "python:latest", "custom image"],
|
||||
"type": "boxSelection",
|
||||
"options": [],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
|
@ -34,7 +34,7 @@
|
||||
"name": "access",
|
||||
"label": "Access Type",
|
||||
"help": "Choose how you want your files to be accessible for the specified price.",
|
||||
"type": "select",
|
||||
"type": "boxSelection",
|
||||
"options": ["Download", "Compute"],
|
||||
"required": true
|
||||
},
|
||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -18101,7 +18101,7 @@
|
||||
}
|
||||
},
|
||||
"ethereumjs-abi": {
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#1a27c59c15ab1e95ee8e5c4ed6ad814c49cc439e",
|
||||
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#1ce6a1d64235fabe2aaf827fd606def55693508f",
|
||||
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
|
||||
"requires": {
|
||||
"bn.js": "^4.11.8",
|
||||
|
71
src/components/atoms/AddToken.module.css
Normal file
71
src/components/atoms/AddToken.module.css
Normal file
@ -0,0 +1,71 @@
|
||||
.button {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.button:hover,
|
||||
.button:focus {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.logoWrap {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.logoWrap::before {
|
||||
content: '+';
|
||||
color: var(--color-secondary);
|
||||
font-family: var(--font-family-base);
|
||||
font-weight: var(--font-weight-base);
|
||||
font-size: 1.25em;
|
||||
position: absolute;
|
||||
right: 0.05em;
|
||||
top: 0.05em;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 1.6em;
|
||||
height: 1.6em;
|
||||
display: inline-block;
|
||||
margin-bottom: -0.35em;
|
||||
border-radius: 50%;
|
||||
border: 0.065rem solid var(--color-secondary);
|
||||
margin-right: calc(var(--spacer) / 10);
|
||||
transition: 0.2s ease-out;
|
||||
}
|
||||
|
||||
.button:hover .logo,
|
||||
.button:focus .logo {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.button:hover .logoWrap::before,
|
||||
.button:focus .logoWrap::before {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.text {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.minimal .text {
|
||||
opacity: 0;
|
||||
transform: translate3d(-1rem, 0, 0);
|
||||
transition: 0.2s ease-out;
|
||||
z-index: 0;
|
||||
white-space: pre;
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: 0.15rem;
|
||||
}
|
||||
|
||||
.minimal:hover .text,
|
||||
.minimal:focus .text {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
53
src/components/atoms/AddToken.tsx
Normal file
53
src/components/atoms/AddToken.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import { addTokenToWallet } from '../../utils/web3'
|
||||
import { useWeb3 } from '../../providers/Web3'
|
||||
import Button from './Button'
|
||||
import styles from './AddToken.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export default function AddToken({
|
||||
address,
|
||||
symbol,
|
||||
logo,
|
||||
text,
|
||||
className,
|
||||
minimal
|
||||
}: {
|
||||
address: string
|
||||
symbol: string
|
||||
logo: string // needs to be a remote image
|
||||
text?: string
|
||||
className?: string
|
||||
minimal?: boolean
|
||||
}): ReactElement {
|
||||
const { web3Provider } = useWeb3()
|
||||
|
||||
const styleClasses = cx({
|
||||
button: true,
|
||||
minimal: minimal,
|
||||
[className]: className
|
||||
})
|
||||
|
||||
async function handleAddToken() {
|
||||
if (!web3Provider) return
|
||||
|
||||
await addTokenToWallet(web3Provider, address, symbol, logo)
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
className={styleClasses}
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleAddToken}
|
||||
>
|
||||
<span className={styles.logoWrap}>
|
||||
<img src={logo} className={styles.logo} width="16" height="16" />
|
||||
</span>
|
||||
|
||||
<span className={styles.text}>{text || `Add ${symbol}`}</span>
|
||||
</Button>
|
||||
)
|
||||
}
|
@ -1,20 +1,30 @@
|
||||
import React, { ReactElement, ReactNode, useEffect, useState } from 'react'
|
||||
import { ReactComponent as External } from '../../images/external.svg'
|
||||
import styles from './ExplorerLink.module.css'
|
||||
import classNames from 'classnames/bind'
|
||||
import { ConfigHelperConfig } from '@oceanprotocol/lib'
|
||||
import { useOcean } from '../../providers/Ocean'
|
||||
import styles from './ExplorerLink.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export default function ExplorerLink({
|
||||
path,
|
||||
children
|
||||
children,
|
||||
className
|
||||
}: {
|
||||
networkId: number
|
||||
path: string
|
||||
children: ReactNode
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const [url, setUrl] = useState<string>()
|
||||
|
||||
const styleClasses = cx({
|
||||
link: true,
|
||||
[className]: className
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setUrl((config as ConfigHelperConfig).explorerUri)
|
||||
}, [config])
|
||||
@ -25,7 +35,7 @@ export default function ExplorerLink({
|
||||
title={`View on ${(config as ConfigHelperConfig).explorerUri}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
className={styles.link}
|
||||
className={styleClasses}
|
||||
>
|
||||
{children} <External />
|
||||
</a>
|
||||
|
@ -4,6 +4,9 @@ import styles from './InputElement.module.css'
|
||||
import { InputProps } from '.'
|
||||
import FilesInput from '../../molecules/FormFields/FilesInput'
|
||||
import Terms from '../../molecules/FormFields/Terms'
|
||||
import BoxSelection, {
|
||||
BoxSelectionOption
|
||||
} from '../../molecules/FormFields/BoxSelection'
|
||||
import Datatoken from '../../molecules/FormFields/Datatoken'
|
||||
import classNames from 'classnames/bind'
|
||||
import AssetSelection, {
|
||||
@ -91,7 +94,7 @@ export default function InputElement({
|
||||
id={slugify(option)}
|
||||
type={type}
|
||||
name={name}
|
||||
checked={props.defaultChecked}
|
||||
defaultChecked={props.defaultChecked}
|
||||
{...props}
|
||||
/>
|
||||
<label className={styles.radioLabel} htmlFor={slugify(option)}>
|
||||
@ -125,6 +128,15 @@ export default function InputElement({
|
||||
return <Datatoken name={name} {...field} {...props} />
|
||||
case 'terms':
|
||||
return <Terms name={name} options={options} {...field} {...props} />
|
||||
case 'boxSelection':
|
||||
return (
|
||||
<BoxSelection
|
||||
name={name}
|
||||
options={(options as unknown) as BoxSelectionOption[]}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return prefix || postfix ? (
|
||||
<div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}>
|
||||
|
@ -68,7 +68,6 @@ export default function Publisher({
|
||||
>
|
||||
{name}
|
||||
</Link>
|
||||
|
||||
<div className={styles.links}>
|
||||
{' — '}
|
||||
{profile && (
|
||||
|
@ -2,9 +2,10 @@ import AssetTeaser from '../molecules/AssetTeaser'
|
||||
import * as React from 'react'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import ddo from '../../../tests/unit/__fixtures__/ddo'
|
||||
import { AssetListPrices } from '../../utils/subgraph'
|
||||
|
||||
export default {
|
||||
title: 'Molecules/Asset Teaser'
|
||||
}
|
||||
|
||||
export const Default = () => <AssetTeaser ddo={ddo as DDO} />
|
||||
export const Default = () => <AssetTeaser ddo={ddo as DDO} price={undefined} />
|
||||
|
@ -3,7 +3,7 @@ import { Link } from 'gatsby'
|
||||
import Dotdotdot from 'react-dotdotdot'
|
||||
import Price from '../atoms/Price'
|
||||
import styles from './AssetTeaser.module.css'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import { DDO, BestPrice } from '@oceanprotocol/lib'
|
||||
import removeMarkdown from 'remove-markdown'
|
||||
import Publisher from '../atoms/Publisher'
|
||||
import Time from '../atoms/Time'
|
||||
@ -11,9 +11,13 @@ import AssetType from '../atoms/AssetType'
|
||||
|
||||
declare type AssetTeaserProps = {
|
||||
ddo: DDO
|
||||
price: BestPrice
|
||||
}
|
||||
|
||||
const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
|
||||
const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
||||
ddo,
|
||||
price
|
||||
}: AssetTeaserProps) => {
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
const { name, type } = attributes.main
|
||||
const { dataTokenInfo } = ddo
|
||||
@ -47,7 +51,7 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({ ddo }: AssetTeaserProps) => {
|
||||
</div>
|
||||
|
||||
<footer className={styles.foot}>
|
||||
<Price price={ddo.price} small />
|
||||
<Price price={price} small />
|
||||
<p className={styles.date}>
|
||||
<Time date={ddo?.created} relative />
|
||||
</p>
|
||||
|
57
src/components/molecules/FormFields/BoxSelection.module.css
Normal file
57
src/components/molecules/FormFields/BoxSelection.module.css
Normal file
@ -0,0 +1,57 @@
|
||||
.boxSelectionsWrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0 calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.boxSelectionsWrapper > div {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.boxSelection {
|
||||
display: block;
|
||||
flex: 1 1 0px;
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 2)
|
||||
calc(var(--spacer) / 4) calc(var(--spacer) / 2) !important;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--color-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: var(--font-weight-bold);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.boxSelectionsWrapper input[type='radio'] {
|
||||
position: fixed;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input[type='radio']:checked + label {
|
||||
color: var(--font-color-text);
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.boxSelection svg {
|
||||
width: var(--font-size-h4);
|
||||
height: var(--font-size-h4);
|
||||
fill: currentColor;
|
||||
margin-bottom: calc(var(--spacer) / 5);
|
||||
}
|
||||
|
||||
.boxSelectionsWrapper label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
color: var(--color-secondary);
|
||||
font-weight: normal;
|
||||
}
|
70
src/components/molecules/FormFields/BoxSelection.tsx
Normal file
70
src/components/molecules/FormFields/BoxSelection.tsx
Normal file
@ -0,0 +1,70 @@
|
||||
import React, { ChangeEvent } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import Loader from '../../atoms/Loader'
|
||||
import styles from './BoxSelection.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
export interface BoxSelectionOption {
|
||||
name: string
|
||||
checked: boolean
|
||||
title: JSX.Element | string
|
||||
icon?: JSX.Element
|
||||
text?: JSX.Element | string
|
||||
}
|
||||
|
||||
export default function BoxSelection({
|
||||
name,
|
||||
options,
|
||||
disabled,
|
||||
handleChange,
|
||||
...props
|
||||
}: {
|
||||
name: string
|
||||
options: BoxSelectionOption[]
|
||||
disabled?: boolean
|
||||
handleChange?: (event: ChangeEvent<HTMLInputElement>) => void
|
||||
}): JSX.Element {
|
||||
const styleClassesWrapper = cx({
|
||||
boxSelectionsWrapper: true,
|
||||
[styles.disabled]: disabled
|
||||
})
|
||||
|
||||
const styleClassesInput = cx({
|
||||
input: true,
|
||||
radio: true
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={styleClassesWrapper}>
|
||||
{!options ? (
|
||||
<Loader />
|
||||
) : (
|
||||
options.map((value: BoxSelectionOption) => (
|
||||
<div key={value.name}>
|
||||
<input
|
||||
id={value.name}
|
||||
type="radio"
|
||||
className={styleClassesInput}
|
||||
defaultChecked={value.checked}
|
||||
onChange={(event) => handleChange(event)}
|
||||
{...props}
|
||||
disabled={disabled}
|
||||
value={value.name}
|
||||
name={name}
|
||||
/>
|
||||
<label
|
||||
className={`${styles.boxSelection} ${styles.label}`}
|
||||
htmlFor={value.name}
|
||||
title={value.name}
|
||||
>
|
||||
{value.icon}
|
||||
<span className={styles.title}>{value.title}</span>
|
||||
{value.text}
|
||||
</label>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -2,7 +2,6 @@ import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useWeb3 } from '../../providers/Web3'
|
||||
import { addCustomNetwork, NetworkObject } from '../../utils/web3'
|
||||
import { getOceanConfig } from '../../utils/ocean'
|
||||
import { getProviderInfo } from 'web3modal'
|
||||
import { useOcean } from '../../providers/Ocean'
|
||||
import { useSiteMetadata } from '../../hooks/useSiteMetadata'
|
||||
import AnnouncementBanner, {
|
||||
@ -19,7 +18,7 @@ const networkMatic: NetworkObject = {
|
||||
}
|
||||
|
||||
export default function NetworkBanner(): ReactElement {
|
||||
const { web3Provider } = useWeb3()
|
||||
const { web3Provider, web3ProviderInfo } = useWeb3()
|
||||
const { config, connect } = useOcean()
|
||||
const { announcement } = useSiteMetadata()
|
||||
|
||||
@ -51,10 +50,9 @@ export default function NetworkBanner(): ReactElement {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!web3Provider && !config) return
|
||||
if (!web3ProviderInfo || (!web3Provider && !config)) return
|
||||
|
||||
const providerInfo = getProviderInfo(web3Provider)
|
||||
switch (providerInfo?.name) {
|
||||
switch (web3ProviderInfo.name) {
|
||||
case 'Web3':
|
||||
if (config.networkId !== 137) {
|
||||
setText(announcement.main)
|
||||
@ -80,7 +78,7 @@ export default function NetworkBanner(): ReactElement {
|
||||
setAction(undefined)
|
||||
}
|
||||
}
|
||||
}, [web3Provider, config, announcement])
|
||||
}, [web3Provider, web3ProviderInfo, config, announcement])
|
||||
|
||||
return <AnnouncementBanner text={text} action={action} />
|
||||
}
|
||||
|
@ -30,3 +30,7 @@
|
||||
color: var(--font-color-text);
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.appearances div[class*='boxSelectionsWrapper'] {
|
||||
padding-bottom: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
@ -1,42 +1,44 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, ChangeEvent } from 'react'
|
||||
import { DarkMode } from 'use-dark-mode'
|
||||
import Button from '../../atoms/Button'
|
||||
import FormHelp from '../../atoms/Input/Help'
|
||||
import Label from '../../atoms/Input/Label'
|
||||
import styles from './Appearance.module.css'
|
||||
import { ReactComponent as Moon } from '../../../images/moon.svg'
|
||||
import { ReactComponent as Sun } from '../../../images/sun.svg'
|
||||
|
||||
const buttons = ['Light', 'Dark']
|
||||
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
|
||||
import styles from './Appearance.module.css'
|
||||
|
||||
export default function Appearance({
|
||||
darkMode
|
||||
}: {
|
||||
darkMode: DarkMode
|
||||
}): ReactElement {
|
||||
return (
|
||||
<li>
|
||||
<Label htmlFor="">Appearance</Label>
|
||||
<div className={styles.buttons}>
|
||||
{buttons.map((button) => {
|
||||
const isDark = button === 'Dark'
|
||||
const selected =
|
||||
(isDark && darkMode.value) || (!isDark && !darkMode.value)
|
||||
const options: BoxSelectionOption[] = [
|
||||
{
|
||||
name: 'Light',
|
||||
checked: !darkMode.value,
|
||||
title: 'Light',
|
||||
icon: <Sun />
|
||||
},
|
||||
{
|
||||
name: 'Dark',
|
||||
checked: darkMode.value,
|
||||
title: 'Dark',
|
||||
icon: <Moon />
|
||||
}
|
||||
]
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={button}
|
||||
className={`${styles.button} ${selected ? styles.selected : ''}`}
|
||||
size="small"
|
||||
style="text"
|
||||
onClick={() => (isDark ? darkMode.enable() : darkMode.disable())}
|
||||
>
|
||||
{isDark ? <Moon /> : <Sun />}
|
||||
{button}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
function handleChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
event.target.value === 'Dark' ? darkMode.enable() : darkMode.disable()
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={styles.appearances}>
|
||||
<Label htmlFor="">Appearance</Label>
|
||||
<BoxSelection
|
||||
options={options}
|
||||
name="appearanceMode"
|
||||
handleChange={handleChange}
|
||||
/>
|
||||
<FormHelp>Defaults to your OS setting, select to override.</FormHelp>
|
||||
</li>
|
||||
)
|
||||
|
@ -17,3 +17,14 @@
|
||||
.selected {
|
||||
composes: selected from './Appearance.module.css';
|
||||
}
|
||||
|
||||
.chains div[class*='boxSelectionsWrapper'] {
|
||||
display: grid;
|
||||
gap: calc(var(--spacer) / 4);
|
||||
grid-template-columns: repeat(auto-fit, minmax(6rem, 1fr));
|
||||
padding-bottom: calc(var(--spacer) / 8);
|
||||
}
|
||||
|
||||
.chains label[class*='boxSelection'] {
|
||||
padding: calc(var(--spacer) / 3) calc(var(--spacer) / 4) !important;
|
||||
}
|
||||
|
@ -1,53 +1,58 @@
|
||||
import { ConfigHelperConfig } from '@oceanprotocol/lib'
|
||||
import React, { ReactElement } from 'react'
|
||||
import React, { ReactElement, ChangeEvent } from 'react'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import { getOceanConfig } from '../../../utils/ocean'
|
||||
import Button from '../../atoms/Button'
|
||||
import FormHelp from '../../atoms/Input/Help'
|
||||
import Label from '../../atoms/Input/Label'
|
||||
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
|
||||
import styles from './Chain.module.css'
|
||||
|
||||
export default function Chain(): ReactElement {
|
||||
const { web3 } = useWeb3()
|
||||
const { config, connect } = useOcean()
|
||||
|
||||
async function connectOcean(networkName: string) {
|
||||
const config = getOceanConfig(networkName)
|
||||
async function connectOcean(event: ChangeEvent<HTMLInputElement>) {
|
||||
const config = getOceanConfig(event.target.value)
|
||||
await connect(config)
|
||||
}
|
||||
|
||||
const chains = [
|
||||
{ name: 'ETH', oceanConfig: 'mainnet', label: 'Mainnet' },
|
||||
{ name: 'Polygon/Matic', oceanConfig: 'polygon', label: 'Mainnet' },
|
||||
{ name: 'Moonbase Alpha', oceanConfig: 'moonbeamalpha', label: 'Testnet' }
|
||||
function isNetworkSelected(oceanConfig: string) {
|
||||
return (config as ConfigHelperConfig).network === oceanConfig
|
||||
}
|
||||
|
||||
const options: BoxSelectionOption[] = [
|
||||
{
|
||||
name: 'mainnet',
|
||||
checked: isNetworkSelected('mainnet'),
|
||||
title: 'ETH',
|
||||
text: 'Mainnet'
|
||||
},
|
||||
{
|
||||
name: 'polygon',
|
||||
checked: isNetworkSelected('polygon'),
|
||||
title: 'Polygon/Matic',
|
||||
text: 'Mainnet'
|
||||
},
|
||||
{
|
||||
name: 'moonbeamalpha',
|
||||
checked: isNetworkSelected('moonbeamalpha'),
|
||||
title: 'Moonbase Alpha',
|
||||
text: 'Testnet'
|
||||
}
|
||||
]
|
||||
|
||||
// TODO: to fully solve https://github.com/oceanprotocol/market/issues/432
|
||||
// there are more considerations for users with a wallet connected (wallet network vs. setting network).
|
||||
// For now, only show the setting for non-wallet users.
|
||||
return !web3 ? (
|
||||
<li>
|
||||
<li className={styles.chains}>
|
||||
<Label htmlFor="">Chain</Label>
|
||||
<div className={styles.buttons}>
|
||||
{chains.map((button) => {
|
||||
const selected =
|
||||
(config as ConfigHelperConfig).network === button.oceanConfig
|
||||
|
||||
return (
|
||||
<Button
|
||||
key={button.name}
|
||||
className={`${styles.button} ${selected ? styles.selected : ''}`}
|
||||
size="small"
|
||||
style="text"
|
||||
onClick={() => connectOcean(button.oceanConfig)}
|
||||
>
|
||||
{button.name}
|
||||
<span>{button.label}</span>
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<BoxSelection
|
||||
options={options}
|
||||
name="chain"
|
||||
handleChange={connectOcean}
|
||||
/>
|
||||
<FormHelp>Switch the data source for the interface.</FormHelp>
|
||||
</li>
|
||||
) : null
|
||||
|
@ -42,7 +42,7 @@
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.actions span {
|
||||
.walletLogoWrap {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@ -84,3 +84,7 @@
|
||||
.walletInfo button {
|
||||
margin-top: calc(var(--spacer) / 5) !important;
|
||||
}
|
||||
|
||||
.addToken {
|
||||
margin-left: 0.3rem;
|
||||
}
|
||||
|
@ -1,33 +1,29 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Button from '../../atoms/Button'
|
||||
import styles from './Details.module.css'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import Web3Feedback from './Feedback'
|
||||
import { getProviderInfo, IProviderInfo } from 'web3modal'
|
||||
import Conversion from '../../atoms/Price/Conversion'
|
||||
import { formatCurrency } from '@coingecko/cryptoformat'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||
import Button from '../../atoms/Button'
|
||||
import AddToken from '../../atoms/AddToken'
|
||||
import Conversion from '../../atoms/Price/Conversion'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import { addOceanToWallet } from '../../../utils/web3'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
|
||||
import Web3Feedback from './Feedback'
|
||||
import styles from './Details.module.css'
|
||||
|
||||
export default function Details(): ReactElement {
|
||||
const { web3Provider, connect, logout, networkData } = useWeb3()
|
||||
const {
|
||||
web3Provider,
|
||||
web3ProviderInfo,
|
||||
connect,
|
||||
logout,
|
||||
networkData
|
||||
} = useWeb3()
|
||||
const { balance, config } = useOcean()
|
||||
const { locale } = useUserPreferences()
|
||||
|
||||
const [providerInfo, setProviderInfo] = useState<IProviderInfo>()
|
||||
const [mainCurrency, setMainCurrency] = useState<string>()
|
||||
// const [portisNetwork, setPortisNetwork] = useState<string>()
|
||||
|
||||
// Workaround cause getInjectedProviderName() always returns `MetaMask`
|
||||
// https://github.com/oceanprotocol/market/issues/332
|
||||
useEffect(() => {
|
||||
if (!web3Provider) return
|
||||
const providerInfo = getProviderInfo(web3Provider)
|
||||
setProviderInfo(providerInfo)
|
||||
}, [web3Provider])
|
||||
|
||||
useEffect(() => {
|
||||
if (!networkData) return
|
||||
|
||||
@ -61,11 +57,11 @@ export default function Details(): ReactElement {
|
||||
|
||||
<li className={styles.actions}>
|
||||
<div title="Connected provider" className={styles.walletInfo}>
|
||||
<span>
|
||||
<img className={styles.walletLogo} src={providerInfo?.logo} />
|
||||
{providerInfo?.name}
|
||||
<span className={styles.walletLogoWrap}>
|
||||
<img className={styles.walletLogo} src={web3ProviderInfo?.logo} />
|
||||
{web3ProviderInfo?.name}
|
||||
</span>
|
||||
{/* {providerInfo?.name === 'Portis' && (
|
||||
{/* {web3ProviderInfo?.name === 'Portis' && (
|
||||
<InputElement
|
||||
name="network"
|
||||
type="select"
|
||||
@ -75,20 +71,17 @@ export default function Details(): ReactElement {
|
||||
onChange={handlePortisNetworkChange}
|
||||
/>
|
||||
)} */}
|
||||
{providerInfo?.name === 'MetaMask' && (
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
addOceanToWallet(config, web3Provider)
|
||||
}}
|
||||
>
|
||||
{`Add ${config.oceanTokenSymbol}`}
|
||||
</Button>
|
||||
{web3ProviderInfo?.name === 'MetaMask' && (
|
||||
<AddToken
|
||||
address={config.oceanTokenAddress}
|
||||
symbol={config.oceanTokenSymbol}
|
||||
logo="https://raw.githubusercontent.com/oceanprotocol/art/main/logo/token.png"
|
||||
className={styles.addToken}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<p>
|
||||
{providerInfo?.name === 'Portis' && (
|
||||
{web3ProviderInfo?.name === 'Portis' && (
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
|
@ -3,7 +3,6 @@ import {
|
||||
DDO,
|
||||
File as FileMetadata,
|
||||
Logger,
|
||||
ServiceType,
|
||||
publisherTrustedAlgorithm,
|
||||
BestPrice
|
||||
} from '@oceanprotocol/lib'
|
||||
@ -37,11 +36,8 @@ import FormStartComputeDataset from './FormComputeDataset'
|
||||
import styles from './index.module.css'
|
||||
import SuccessConfetti from '../../../atoms/SuccessConfetti'
|
||||
import Button from '../../../atoms/Button'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { FrePrice } from '../../../../@types/apollo/FrePrice'
|
||||
import { PoolPrice } from '../../../../@types/apollo/PoolPrice'
|
||||
import { secondsToString } from '../../../../utils/metadata'
|
||||
import { getPreviousOrders } from '../../../../utils/subgraph'
|
||||
import { getPreviousOrders, getPrice } from '../../../../utils/subgraph'
|
||||
|
||||
const SuccessAction = () => (
|
||||
<Button style="text" to="/history" size="small">
|
||||
@ -49,23 +45,6 @@ const SuccessAction = () => (
|
||||
</Button>
|
||||
)
|
||||
|
||||
const freQuery = gql`
|
||||
query AlgorithmFrePrice($datatoken: String) {
|
||||
fixedRateExchanges(orderBy: id, where: { datatoken: $datatoken }) {
|
||||
rate
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
const poolQuery = gql`
|
||||
query AlgorithmPoolPrice($datatoken: String) {
|
||||
pools(where: { datatokenAddress: $datatoken }) {
|
||||
spotPrice
|
||||
consumePrice
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function Compute({
|
||||
isBalanceSufficient,
|
||||
dtBalance,
|
||||
@ -95,7 +74,6 @@ export default function Compute({
|
||||
)
|
||||
const [algorithmDTBalance, setalgorithmDTBalance] = useState<string>()
|
||||
const [algorithmPrice, setAlgorithmPrice] = useState<BestPrice>()
|
||||
const [variables, setVariables] = useState({})
|
||||
const [
|
||||
previousAlgorithmOrderId,
|
||||
setPreviousAlgorithmOrderId
|
||||
@ -103,25 +81,6 @@ export default function Compute({
|
||||
const [datasetTimeout, setDatasetTimeout] = useState<string>()
|
||||
const [algorithmTimeout, setAlgorithmTimeout] = useState<string>()
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
const {
|
||||
refetch: refetchFre,
|
||||
startPolling: startPollingFre,
|
||||
data: frePrice
|
||||
} = useQuery<FrePrice>(freQuery, {
|
||||
variables,
|
||||
skip: false
|
||||
})
|
||||
const {
|
||||
refetch: refetchPool,
|
||||
startPolling: startPollingPool,
|
||||
data: poolPrice
|
||||
} = useQuery<PoolPrice>(poolQuery, {
|
||||
variables,
|
||||
skip: false
|
||||
})
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
|
||||
const isComputeButtonDisabled =
|
||||
isJobStarting === true || file === null || !ocean || !isBalanceSufficient
|
||||
const hasDatatoken = Number(dtBalance) >= 1
|
||||
@ -167,7 +126,7 @@ export default function Compute({
|
||||
const algorithmQuery =
|
||||
trustedAlgorithmList.length > 0 ? `(${algoQuerry}) AND` : ``
|
||||
const query = {
|
||||
page: 1,
|
||||
offset: 500,
|
||||
query: {
|
||||
query_string: {
|
||||
query: `${algorithmQuery} service.attributes.main.type:algorithm -isInPurgatory:true`
|
||||
@ -215,40 +174,10 @@ export default function Compute({
|
||||
setDatasetTimeout(secondsToString(timeout))
|
||||
}, [ddo])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!frePrice ||
|
||||
frePrice.fixedRateExchanges.length === 0 ||
|
||||
algorithmPrice.type !== 'exchange'
|
||||
)
|
||||
return
|
||||
setAlgorithmPrice((prevState) => ({
|
||||
...prevState,
|
||||
value: frePrice.fixedRateExchanges[0].rate,
|
||||
address: frePrice.fixedRateExchanges[0].id
|
||||
}))
|
||||
}, [frePrice])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!poolPrice ||
|
||||
poolPrice.pools.length === 0 ||
|
||||
algorithmPrice.type !== 'pool'
|
||||
)
|
||||
return
|
||||
setAlgorithmPrice((prevState) => ({
|
||||
...prevState,
|
||||
value:
|
||||
poolPrice.pools[0].consumePrice === '-1'
|
||||
? poolPrice.pools[0].spotPrice
|
||||
: poolPrice.pools[0].consumePrice
|
||||
}))
|
||||
}, [poolPrice])
|
||||
|
||||
const initMetadata = useCallback(async (ddo: DDO): Promise<void> => {
|
||||
if (!ddo) return
|
||||
setAlgorithmPrice(ddo.price)
|
||||
setVariables({ datatoken: ddo?.dataToken.toLowerCase() })
|
||||
const price = await getPrice(ddo)
|
||||
setAlgorithmPrice(price)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
@ -430,7 +359,8 @@ export default function Compute({
|
||||
|
||||
Logger.log('[compute] Starting compute job response: ', response)
|
||||
|
||||
setHasPreviousDatasetOrder(true)
|
||||
await checkPreviousOrders(selectedAlgorithmAsset)
|
||||
await checkPreviousOrders(ddo)
|
||||
setIsPublished(true)
|
||||
} catch (error) {
|
||||
setError('Failed to start job!')
|
||||
|
@ -44,7 +44,7 @@ export default function FormEditComputeDataset({
|
||||
): Promise<AssetSelectionAsset[]> {
|
||||
const source = axios.CancelToken.source()
|
||||
const query = {
|
||||
page: 1,
|
||||
offset: 500,
|
||||
query: {
|
||||
query_string: {
|
||||
query: `service.attributes.main.type:algorithm -isInPurgatory:true`
|
||||
|
@ -81,6 +81,8 @@ export default function Remove({
|
||||
const [minOceanAmount, setMinOceanAmount] = useState<string>('0')
|
||||
const [minDatatokenAmount, setMinDatatokenAmount] = useState<string>('0')
|
||||
|
||||
Decimal.set({ toExpNeg: -18, precision: 18, rounding: 1 })
|
||||
|
||||
async function handleRemoveLiquidity() {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
@ -157,12 +159,18 @@ export default function Remove({
|
||||
])
|
||||
|
||||
useEffect(() => {
|
||||
const minOceanAmount =
|
||||
(Number(amountOcean) * (100 - Number(slippage))) / 100
|
||||
const minDatatokenAmount =
|
||||
(Number(amountDatatoken) * (100 - Number(slippage))) / 100
|
||||
setMinOceanAmount(`${minOceanAmount}`)
|
||||
setMinDatatokenAmount(`${minDatatokenAmount}`)
|
||||
const minOceanAmount = new Decimal(amountOcean)
|
||||
.mul(new Decimal(100).minus(new Decimal(slippage)))
|
||||
.dividedBy(100)
|
||||
.toString()
|
||||
|
||||
const minDatatokenAmount = new Decimal(amountDatatoken)
|
||||
.mul(new Decimal(100).minus(new Decimal(slippage)))
|
||||
.dividedBy(100)
|
||||
.toString()
|
||||
|
||||
setMinOceanAmount(minOceanAmount.slice(0, 18))
|
||||
setMinDatatokenAmount(minDatatokenAmount.slice(0, 18))
|
||||
}, [slippage, amountOcean, amountDatatoken, isAdvanced])
|
||||
|
||||
// Set amountPoolShares based on set slider value
|
||||
@ -173,9 +181,9 @@ export default function Remove({
|
||||
const amountPoolShares = new Decimal(e.target.value)
|
||||
.dividedBy(100)
|
||||
.mul(new Decimal(poolTokens))
|
||||
.toPrecision(18) // in some cases the returned value contain more than 18 digits which break conversion to wei
|
||||
.toString()
|
||||
|
||||
setAmountPoolShares(`${amountPoolShares}`)
|
||||
setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
|
||||
}
|
||||
|
||||
function handleMaxButton(e: ChangeEvent<HTMLInputElement>) {
|
||||
@ -185,9 +193,9 @@ export default function Remove({
|
||||
const amountPoolShares = new Decimal(amountMaxPercent)
|
||||
.dividedBy(100)
|
||||
.mul(new Decimal(poolTokens))
|
||||
.toPrecision(18)
|
||||
.toString()
|
||||
|
||||
setAmountPoolShares(`${amountPoolShares}`)
|
||||
setAmountPoolShares(`${amountPoolShares.slice(0, 18)}`)
|
||||
}
|
||||
|
||||
function handleAdvancedButton(e: FormEvent<HTMLButtonElement>) {
|
||||
|
@ -7,7 +7,7 @@ import { useAsset } from '../../../../providers/Asset'
|
||||
|
||||
export default function Transactions(): ReactElement {
|
||||
const [open, setOpen] = useState(false)
|
||||
const { ddo } = useAsset()
|
||||
const { price } = useAsset()
|
||||
function handleClick() {
|
||||
setOpen(!open)
|
||||
}
|
||||
@ -29,7 +29,7 @@ export default function Transactions(): ReactElement {
|
||||
</Button>
|
||||
</h3>
|
||||
{open === true && (
|
||||
<PoolTransactions poolAddress={ddo.price?.address} minimal />
|
||||
<PoolTransactions poolAddress={price?.address} minimal />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
@ -95,8 +95,8 @@ export default function Pool(): ReactElement {
|
||||
const [refreshPool, setRefreshPool] = useState(false)
|
||||
const { data: dataLiquidity } = useQuery<PoolLiquidity>(poolLiquidityQuery, {
|
||||
variables: {
|
||||
id: ddo.price.address.toLowerCase(),
|
||||
shareId: `${ddo.price.address.toLowerCase()}-${ddo.publicKey[0].owner.toLowerCase()}`
|
||||
id: price.address.toLowerCase(),
|
||||
shareId: `${price.address.toLowerCase()}-${ddo.publicKey[0].owner.toLowerCase()}`
|
||||
},
|
||||
pollInterval: 5000
|
||||
})
|
||||
@ -170,7 +170,8 @@ export default function Pool(): ReactElement {
|
||||
const totalUserLiquidityInOcean =
|
||||
userLiquidity?.ocean + userLiquidity?.datatoken * price?.value
|
||||
setTotalUserLiquidityInOcean(totalUserLiquidityInOcean)
|
||||
const totalLiquidityInOcean = price?.ocean + price?.datatoken * price?.value
|
||||
const totalLiquidityInOcean =
|
||||
Number(price?.ocean) + Number(price?.datatoken) * Number(price?.value)
|
||||
setTotalLiquidityInOcean(totalLiquidityInOcean)
|
||||
}, [userLiquidity, price, poolTokens, totalPoolTokens])
|
||||
|
||||
|
@ -19,7 +19,6 @@ export default function AssetActions(): ReactElement {
|
||||
|
||||
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
|
||||
const [dtBalance, setDtBalance] = useState<string>()
|
||||
|
||||
const isCompute = Boolean(ddo?.findServiceByType('compute'))
|
||||
|
||||
// Get and set user DT balance
|
||||
@ -75,10 +74,7 @@ export default function AssetActions(): ReactElement {
|
||||
}
|
||||
]
|
||||
|
||||
// Check from metadata, cause that is available earlier
|
||||
const hasPool = ddo?.price?.type === 'pool'
|
||||
|
||||
hasPool &&
|
||||
price?.type === 'pool' &&
|
||||
tabs.push(
|
||||
{
|
||||
title: 'Pool',
|
||||
|
@ -27,6 +27,11 @@
|
||||
margin-right: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.datatoken {
|
||||
white-space: pre;
|
||||
margin-right: calc(var(--spacer) / 3);
|
||||
}
|
||||
|
||||
.byline {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
@ -34,3 +39,13 @@
|
||||
.updated {
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
|
||||
.addWrap {
|
||||
padding-left: calc(var(--spacer) / 5);
|
||||
border-left: 1px solid var(--border-color);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.add {
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
|
@ -3,13 +3,14 @@ import { useAsset } from '../../../providers/Asset'
|
||||
import { useWeb3 } from '../../../providers/Web3'
|
||||
import ExplorerLink from '../../atoms/ExplorerLink'
|
||||
import Publisher from '../../atoms/Publisher'
|
||||
import AddToken from '../../atoms/AddToken'
|
||||
import Time from '../../atoms/Time'
|
||||
import styles from './MetaMain.module.css'
|
||||
import AssetType from '../../atoms/AssetType'
|
||||
import styles from './MetaMain.module.css'
|
||||
|
||||
export default function MetaMain(): ReactElement {
|
||||
const { ddo, owner, type } = useAsset()
|
||||
const { networkId } = useWeb3()
|
||||
const { networkId, web3ProviderInfo } = useWeb3()
|
||||
const isCompute = Boolean(ddo?.findServiceByType('compute'))
|
||||
const accessType = isCompute ? 'compute' : 'access'
|
||||
|
||||
@ -22,6 +23,7 @@ export default function MetaMain(): ReactElement {
|
||||
className={styles.assetType}
|
||||
/>
|
||||
<ExplorerLink
|
||||
className={styles.datatoken}
|
||||
networkId={networkId}
|
||||
path={
|
||||
networkId === 137 || networkId === 1287
|
||||
@ -31,6 +33,19 @@ export default function MetaMain(): ReactElement {
|
||||
>
|
||||
{`${ddo?.dataTokenInfo.name} — ${ddo?.dataTokenInfo.symbol}`}
|
||||
</ExplorerLink>
|
||||
|
||||
{web3ProviderInfo?.name === 'MetaMask' && (
|
||||
<span className={styles.addWrap}>
|
||||
<AddToken
|
||||
address={ddo?.dataTokenInfo.address}
|
||||
symbol={ddo?.dataTokenInfo.symbol}
|
||||
logo="https://raw.githubusercontent.com/oceanprotocol/art/main/logo/datatoken.png"
|
||||
text={`Add ${ddo?.dataTokenInfo.symbol} to wallet`}
|
||||
className={styles.add}
|
||||
minimal
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<div className={styles.byline}>
|
||||
|
@ -16,3 +16,9 @@
|
||||
font-size: var(--font-size-small);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.loaderWrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -1,17 +1,28 @@
|
||||
import AssetTeaser from '../molecules/AssetTeaser'
|
||||
import React from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Pagination from '../molecules/Pagination'
|
||||
import styles from './AssetList.module.css'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import classNames from 'classnames/bind'
|
||||
import { getAssetsBestPrices, AssetListPrices } from '../../utils/subgraph'
|
||||
import Loader from '../atoms/Loader'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
function LoaderArea() {
|
||||
return (
|
||||
<div className={styles.loaderWrap}>
|
||||
<Loader />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
declare type AssetListProps = {
|
||||
assets: DDO[]
|
||||
showPagination: boolean
|
||||
page?: number
|
||||
totalPages?: number
|
||||
isLoading?: boolean
|
||||
onPageChange?: React.Dispatch<React.SetStateAction<number>>
|
||||
className?: string
|
||||
}
|
||||
@ -21,9 +32,22 @@ const AssetList: React.FC<AssetListProps> = ({
|
||||
showPagination,
|
||||
page,
|
||||
totalPages,
|
||||
isLoading,
|
||||
onPageChange,
|
||||
className
|
||||
}) => {
|
||||
const [assetsWithPrices, setAssetWithPrices] = useState<AssetListPrices[]>()
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (!assets) return
|
||||
isLoading && setLoading(true)
|
||||
getAssetsBestPrices(assets).then((asset) => {
|
||||
setAssetWithPrices(asset)
|
||||
setLoading(false)
|
||||
})
|
||||
}, [assets])
|
||||
|
||||
// // This changes the page field inside the query
|
||||
function handlePageChange(selected: number) {
|
||||
onPageChange(selected + 1)
|
||||
@ -34,11 +58,19 @@ const AssetList: React.FC<AssetListProps> = ({
|
||||
[className]: className
|
||||
})
|
||||
|
||||
return (
|
||||
return assetsWithPrices &&
|
||||
!loading &&
|
||||
(isLoading === undefined || isLoading === false) ? (
|
||||
<>
|
||||
<div className={styleClasses}>
|
||||
{assets.length > 0 ? (
|
||||
assets.map((ddo) => <AssetTeaser ddo={ddo} key={ddo.id} />)
|
||||
{assetsWithPrices.length > 0 ? (
|
||||
assetsWithPrices.map((assetWithPrice) => (
|
||||
<AssetTeaser
|
||||
ddo={assetWithPrice.ddo}
|
||||
price={assetWithPrice.price}
|
||||
key={assetWithPrice.ddo.id}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<div className={styles.empty}>No results found.</div>
|
||||
)}
|
||||
@ -52,6 +84,8 @@ const AssetList: React.FC<AssetListProps> = ({
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<LoaderArea />
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -2,3 +2,25 @@
|
||||
text-transform: uppercase;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.computeTableContainer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.refresh,
|
||||
.refresh:first-child {
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.refresh svg {
|
||||
display: inline-block;
|
||||
fill: currentColor;
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
margin-right: calc(var(--spacer) / 6);
|
||||
margin-bottom: -0.1rem;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { DDO, Logger, Service, Provider } from '@oceanprotocol/lib'
|
||||
import { ComputeJobMetaData } from '../../../../@types/ComputeJobMetaData'
|
||||
import Dotdotdot from 'react-dotdotdot'
|
||||
import Table from '../../../atoms/Table'
|
||||
import Button from '../../../atoms/Button'
|
||||
import { useOcean } from '../../../../providers/Ocean'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { useWeb3 } from '../../../../providers/Web3'
|
||||
@ -13,8 +14,9 @@ import { queryMetadata } from '../../../../utils/aquarius'
|
||||
import axios, { CancelToken } from 'axios'
|
||||
import { ComputeOrders } from '../../../../@types/apollo/ComputeOrders'
|
||||
import Details from './Details'
|
||||
import styles from './index.module.css'
|
||||
import { ComputeJob } from '@oceanprotocol/lib/dist/node/ocean/interfaces/Compute'
|
||||
import { ReactComponent as Refresh } from '../../../../images/refresh.svg'
|
||||
import styles from './index.module.css'
|
||||
|
||||
const getComputeOrders = gql`
|
||||
query ComputeOrders($user: String!) {
|
||||
@ -99,7 +101,7 @@ async function getAssetMetadata(
|
||||
export default function ComputeJobs(): ReactElement {
|
||||
const { ocean, account, config } = useOcean()
|
||||
const { accountId } = useWeb3()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [jobs, setJobs] = useState<ComputeJobMetaData[]>([])
|
||||
const { data } = useQuery<ComputeOrders>(getComputeOrders, {
|
||||
variables: {
|
||||
@ -107,139 +109,158 @@ export default function ComputeJobs(): ReactElement {
|
||||
}
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (data === undefined || !config?.metadataCacheUri) return
|
||||
async function getJobs() {
|
||||
if (!ocean || !account) return
|
||||
|
||||
async function getJobs() {
|
||||
if (!ocean || !account) return
|
||||
setIsLoading(true)
|
||||
|
||||
setIsLoading(true)
|
||||
const dtList = []
|
||||
const computeJobs: ComputeJobMetaData[] = []
|
||||
for (let i = 0; i < data.tokenOrders.length; i++) {
|
||||
dtList.push(data.tokenOrders[i].datatokenId.address)
|
||||
}
|
||||
const queryDtList = JSON.stringify(dtList)
|
||||
.replace(/,/g, ' ')
|
||||
.replace(/"/g, '')
|
||||
.replace(/(\[|\])/g, '')
|
||||
|
||||
const dtList = []
|
||||
const computeJobs: ComputeJobMetaData[] = []
|
||||
try {
|
||||
const source = axios.CancelToken.source()
|
||||
const assets = await getAssetMetadata(
|
||||
queryDtList,
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
const providers: Provider[] = []
|
||||
const serviceEndpoints: string[] = []
|
||||
for (let i = 0; i < data.tokenOrders.length; i++) {
|
||||
dtList.push(data.tokenOrders[i].datatokenId.address)
|
||||
try {
|
||||
const did = web3.utils
|
||||
.toChecksumAddress(data.tokenOrders[i].datatokenId.address)
|
||||
.replace('0x', 'did:op:')
|
||||
|
||||
const ddo = assets.filter((x) => x.id === did)[0]
|
||||
|
||||
if (!ddo) continue
|
||||
|
||||
const service = ddo.service.filter(
|
||||
(x: Service) => x.index === data.tokenOrders[i].serviceId
|
||||
)[0]
|
||||
|
||||
if (!service || service.type !== 'compute') continue
|
||||
const { serviceEndpoint } = service
|
||||
|
||||
const wasProviderQueried =
|
||||
serviceEndpoints.filter((x) => x === serviceEndpoint).length > 0
|
||||
|
||||
if (wasProviderQueried) continue
|
||||
serviceEndpoints.push(serviceEndpoint)
|
||||
} catch (err) {
|
||||
Logger.error(err)
|
||||
}
|
||||
}
|
||||
const queryDtList = JSON.stringify(dtList)
|
||||
.replace(/,/g, ' ')
|
||||
.replace(/"/g, '')
|
||||
.replace(/(\[|\])/g, '')
|
||||
|
||||
try {
|
||||
const source = axios.CancelToken.source()
|
||||
const assets = await getAssetMetadata(
|
||||
queryDtList,
|
||||
config.metadataCacheUri,
|
||||
source.token
|
||||
)
|
||||
const providers: Provider[] = []
|
||||
const serviceEndpoints: string[] = []
|
||||
for (let i = 0; i < data.tokenOrders.length; i++) {
|
||||
try {
|
||||
const did = web3.utils
|
||||
.toChecksumAddress(data.tokenOrders[i].datatokenId.address)
|
||||
.replace('0x', 'did:op:')
|
||||
for (let i = 0; i < serviceEndpoints.length; i++) {
|
||||
const instanceConfig = {
|
||||
config,
|
||||
web3: config.web3Provider,
|
||||
logger: Logger,
|
||||
ocean: ocean
|
||||
}
|
||||
const provider = await Provider.getInstance(instanceConfig)
|
||||
await provider.setBaseUrl(serviceEndpoints[i])
|
||||
const hasSameCompute =
|
||||
providers.filter(
|
||||
(x) => x.computeAddress === provider.computeAddress
|
||||
).length > 0
|
||||
if (!hasSameCompute) providers.push(provider)
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(err)
|
||||
}
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
try {
|
||||
const providerComputeJobs = (await providers[i].computeStatus(
|
||||
'',
|
||||
account,
|
||||
undefined,
|
||||
undefined,
|
||||
false
|
||||
)) as ComputeJob[]
|
||||
|
||||
// means the provider uri is not good, so we ignore it and move on
|
||||
if (!providerComputeJobs) continue
|
||||
providerComputeJobs.sort((a, b) => {
|
||||
if (a.dateCreated > b.dateCreated) {
|
||||
return -1
|
||||
}
|
||||
if (a.dateCreated < b.dateCreated) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
for (let j = 0; j < providerComputeJobs.length; j++) {
|
||||
const job = providerComputeJobs[j]
|
||||
const did = job.inputDID[0]
|
||||
const ddo = assets.filter((x) => x.id === did)[0]
|
||||
|
||||
if (!ddo) continue
|
||||
|
||||
const service = ddo.service.filter(
|
||||
(x: Service) => x.index === data.tokenOrders[i].serviceId
|
||||
const serviceMetadata = ddo.service.filter(
|
||||
(x: Service) => x.type === 'metadata'
|
||||
)[0]
|
||||
|
||||
if (!service || service.type !== 'compute') continue
|
||||
const { serviceEndpoint } = service
|
||||
|
||||
const wasProviderQueried =
|
||||
serviceEndpoints.filter((x) => x === serviceEndpoint).length > 0
|
||||
|
||||
if (wasProviderQueried) continue
|
||||
serviceEndpoints.push(serviceEndpoint)
|
||||
} catch (err) {
|
||||
Logger.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
for (let i = 0; i < serviceEndpoints.length; i++) {
|
||||
const instanceConfig = {
|
||||
config,
|
||||
web3: config.web3Provider,
|
||||
logger: Logger,
|
||||
ocean: ocean
|
||||
const compJob: ComputeJobMetaData = {
|
||||
...job,
|
||||
assetName: serviceMetadata.attributes.main.name,
|
||||
assetDtSymbol: ddo.dataTokenInfo.symbol
|
||||
}
|
||||
const provider = await Provider.getInstance(instanceConfig)
|
||||
await provider.setBaseUrl(serviceEndpoints[i])
|
||||
const hasSameCompute =
|
||||
providers.filter(
|
||||
(x) => x.computeAddress === provider.computeAddress
|
||||
).length > 0
|
||||
if (!hasSameCompute) providers.push(provider)
|
||||
computeJobs.push(compJob)
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(err)
|
||||
}
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
try {
|
||||
const providerComputeJobs = (await providers[i].computeStatus(
|
||||
'',
|
||||
account,
|
||||
undefined,
|
||||
undefined,
|
||||
false
|
||||
)) as ComputeJob[]
|
||||
|
||||
// means the provider uri is not good, so we ignore it and move on
|
||||
if (!providerComputeJobs) continue
|
||||
providerComputeJobs.sort((a, b) => {
|
||||
if (a.dateCreated > b.dateCreated) {
|
||||
return -1
|
||||
}
|
||||
if (a.dateCreated < b.dateCreated) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
for (let j = 0; j < providerComputeJobs.length; j++) {
|
||||
const job = providerComputeJobs[j]
|
||||
const did = job.inputDID[0]
|
||||
const ddo = assets.filter((x) => x.id === did)[0]
|
||||
|
||||
if (!ddo) continue
|
||||
const serviceMetadata = ddo.service.filter(
|
||||
(x: Service) => x.type === 'metadata'
|
||||
)[0]
|
||||
|
||||
const compJob: ComputeJobMetaData = {
|
||||
...job,
|
||||
assetName: serviceMetadata.attributes.main.name,
|
||||
assetDtSymbol: ddo.dataTokenInfo.symbol
|
||||
}
|
||||
computeJobs.push(compJob)
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(err)
|
||||
}
|
||||
}
|
||||
setJobs(computeJobs)
|
||||
} catch (error) {
|
||||
Logger.log(error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
setJobs(computeJobs)
|
||||
} catch (error) {
|
||||
Logger.log(error.message)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (data === undefined || !config?.metadataCacheUri) {
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
getJobs()
|
||||
}, [ocean, account, data, config?.metadataCacheUri])
|
||||
|
||||
return (
|
||||
<Table
|
||||
columns={columns}
|
||||
data={jobs}
|
||||
isLoading={isLoading}
|
||||
defaultSortField="row.dateCreated"
|
||||
defaultSortAsc={false}
|
||||
/>
|
||||
<>
|
||||
{jobs.length > 0 && (
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
title="Refresh compute jobs"
|
||||
onClick={() => getJobs()}
|
||||
disabled={isLoading}
|
||||
className={styles.refresh}
|
||||
>
|
||||
<Refresh />
|
||||
Refresh
|
||||
</Button>
|
||||
)}
|
||||
<Table
|
||||
columns={columns}
|
||||
data={jobs}
|
||||
isLoading={isLoading}
|
||||
defaultSortField="row.dateCreated"
|
||||
defaultSortAsc={false}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import Loader from '../../atoms/Loader'
|
||||
import AssetList from '../../organisms/AssetList'
|
||||
import axios from 'axios'
|
||||
import { queryMetadata } from '../../../utils/aquarius'
|
||||
@ -48,14 +47,13 @@ export default function PublishedList(): ReactElement {
|
||||
getPublished()
|
||||
}, [accountId, page, config.metadataCacheUri])
|
||||
|
||||
return isLoading ? (
|
||||
<Loader />
|
||||
) : accountId && queryResult ? (
|
||||
return accountId ? (
|
||||
<AssetList
|
||||
assets={queryResult.results}
|
||||
assets={queryResult?.results}
|
||||
isLoading={isLoading}
|
||||
showPagination
|
||||
page={queryResult.page}
|
||||
totalPages={queryResult.totalPages}
|
||||
page={queryResult?.page}
|
||||
totalPages={queryResult?.totalPages}
|
||||
onPageChange={(newPage) => {
|
||||
setPage(newPage)
|
||||
}}
|
||||
|
@ -16,9 +16,3 @@
|
||||
.section [class*='button'] {
|
||||
margin-top: var(--spacer);
|
||||
}
|
||||
|
||||
.loaderWrap {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
@ -7,13 +7,13 @@ import {
|
||||
SearchQuery
|
||||
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import Container from '../atoms/Container'
|
||||
import Loader from '../atoms/Loader'
|
||||
import { useOcean } from '../../providers/Ocean'
|
||||
import Button from '../atoms/Button'
|
||||
import Bookmarks from '../molecules/Bookmarks'
|
||||
import axios from 'axios'
|
||||
import { queryMetadata } from '../../utils/aquarius'
|
||||
import Permission from '../organisms/Permission'
|
||||
import { useWeb3 } from '../../providers/Web3'
|
||||
|
||||
const queryHighest = {
|
||||
page: 1,
|
||||
@ -37,14 +37,6 @@ const queryLatest = {
|
||||
sort: { created: -1 }
|
||||
}
|
||||
|
||||
function LoaderArea() {
|
||||
return (
|
||||
<div className={styles.loaderWrap}>
|
||||
<Loader />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function SectionQueryResult({
|
||||
title,
|
||||
query,
|
||||
@ -56,11 +48,10 @@ function SectionQueryResult({
|
||||
}) {
|
||||
const { config } = useOcean()
|
||||
const [result, setResult] = useState<QueryResult>()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const { web3Loading } = useWeb3()
|
||||
|
||||
useEffect(() => {
|
||||
if (!config?.metadataCacheUri) return
|
||||
|
||||
if (!config?.metadataCacheUri || web3Loading) return
|
||||
const source = axios.CancelToken.source()
|
||||
|
||||
async function init() {
|
||||
@ -70,23 +61,18 @@ function SectionQueryResult({
|
||||
source.token
|
||||
)
|
||||
setResult(result)
|
||||
setLoading(false)
|
||||
}
|
||||
init()
|
||||
|
||||
return () => {
|
||||
source.cancel()
|
||||
}
|
||||
}, [config?.metadataCacheUri, query])
|
||||
}, [config?.metadataCacheUri, query, web3Loading])
|
||||
|
||||
return (
|
||||
<section className={styles.section}>
|
||||
<h3>{title}</h3>
|
||||
{loading ? (
|
||||
<LoaderArea />
|
||||
) : (
|
||||
result && <AssetList assets={result.results} showPagination={false} />
|
||||
)}
|
||||
<AssetList assets={result?.results} showPagination={false} />
|
||||
{action && action}
|
||||
</section>
|
||||
)
|
||||
|
@ -14,7 +14,6 @@ import Button from '../../atoms/Button'
|
||||
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||
import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData'
|
||||
import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish'
|
||||
|
||||
import stylesIndex from './index.module.css'
|
||||
|
||||
const query = graphql`
|
||||
@ -63,6 +62,24 @@ export default function FormPublish(): ReactElement {
|
||||
initialValues.dockerImage
|
||||
)
|
||||
|
||||
const dockerImageOptions = [
|
||||
{
|
||||
name: 'node:latest',
|
||||
title: 'node:latest',
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
name: 'python:latest',
|
||||
title: 'python:latest',
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
name: 'custom image',
|
||||
title: 'custom image',
|
||||
checked: false
|
||||
}
|
||||
]
|
||||
|
||||
// reset form validation on every mount
|
||||
useEffect(() => {
|
||||
setErrors({})
|
||||
@ -135,6 +152,11 @@ export default function FormPublish(): ReactElement {
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.type === 'boxSelection'
|
||||
? dockerImageOptions
|
||||
: field.options
|
||||
}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleFieldChange(e, field)
|
||||
|
@ -7,6 +7,8 @@ import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||
import { MetadataPublishFormDataset } from '../../../@types/MetaData'
|
||||
import { initialValues as initialValuesDataset } from '../../../models/FormAlgoPublish'
|
||||
import { useOcean } from '../../../providers/Ocean'
|
||||
import { ReactComponent as Download } from '../../../images/download.svg'
|
||||
import { ReactComponent as Compute } from '../../../images/compute.svg'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './FormPublish.module.css'
|
||||
|
||||
@ -61,6 +63,19 @@ export default function FormPublish(): ReactElement {
|
||||
// setSubmitting(false)
|
||||
}, [setErrors, setTouched])
|
||||
|
||||
const accessTypeOptions = [
|
||||
{
|
||||
name: 'Download',
|
||||
title: 'Download',
|
||||
icon: <Download />
|
||||
},
|
||||
{
|
||||
name: 'Compute',
|
||||
title: 'Compute',
|
||||
icon: <Compute />
|
||||
}
|
||||
]
|
||||
|
||||
// Manually handle change events instead of using `handleChange` from Formik.
|
||||
// Workaround for default `validateOnChange` not kicking in
|
||||
function handleFieldChange(
|
||||
@ -94,6 +109,9 @@ export default function FormPublish(): ReactElement {
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.type === 'boxSelection' ? accessTypeOptions : field.options
|
||||
}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleFieldChange(e, field)
|
||||
|
@ -23,7 +23,7 @@ export default function PageTemplateAssetDetails({
|
||||
setPageTitle(isInPurgatory ? '' : title)
|
||||
}, [ddo, error, isInPurgatory, title])
|
||||
|
||||
return ddo ? (
|
||||
return ddo && pageTitle ? (
|
||||
<>
|
||||
<Page title={pageTitle} uri={uri}>
|
||||
<Router basepath="/asset">
|
||||
|
@ -1,188 +0,0 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { useNavigate } from '@reach/router'
|
||||
import styles from './filterPrice.module.css'
|
||||
import classNames from 'classnames/bind'
|
||||
import {
|
||||
addExistingParamsToUrl,
|
||||
FilterByPriceOptions,
|
||||
FilterByTypeOptions
|
||||
} from './utils'
|
||||
import Button from '../../atoms/Button'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
const clearFilters = [{ display: 'Clear', value: '' }]
|
||||
|
||||
const priceFilterItems = [
|
||||
{ display: 'fixed price', value: FilterByPriceOptions.Fixed },
|
||||
{ display: 'dynamic price', value: FilterByPriceOptions.Dynamic }
|
||||
]
|
||||
|
||||
const serviceFilterItems = [
|
||||
{ display: 'data sets', value: FilterByTypeOptions.Data },
|
||||
{ display: 'algorithms', value: FilterByTypeOptions.Algorithm }
|
||||
]
|
||||
|
||||
export default function FilterPrice({
|
||||
priceType,
|
||||
serviceType,
|
||||
setPriceType,
|
||||
setServiceType
|
||||
}: {
|
||||
priceType: string
|
||||
setPriceType: React.Dispatch<React.SetStateAction<string>>
|
||||
serviceType: string
|
||||
setServiceType: React.Dispatch<React.SetStateAction<string>>
|
||||
}): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
|
||||
const [priceSelections, setPriceSelections] = useState<string[]>([])
|
||||
const [serviceSelections, setServiceSelections] = useState<string[]>([])
|
||||
|
||||
async function applyPriceFilter(filterBy: string) {
|
||||
let urlLocation = await addExistingParamsToUrl(location, 'priceType')
|
||||
if (filterBy) {
|
||||
urlLocation = `${urlLocation}&priceType=${filterBy}`
|
||||
}
|
||||
setPriceType(filterBy)
|
||||
navigate(urlLocation)
|
||||
}
|
||||
|
||||
async function applyServiceFilter(filterBy: string) {
|
||||
let urlLocation = await addExistingParamsToUrl(location, 'serviceType')
|
||||
if (filterBy && location.search.indexOf('&serviceType') === -1) {
|
||||
urlLocation = `${urlLocation}&serviceType=${filterBy}`
|
||||
}
|
||||
setServiceType(filterBy)
|
||||
navigate(urlLocation)
|
||||
}
|
||||
|
||||
async function handleSelectedFilter(isSelected: boolean, value: string) {
|
||||
if (
|
||||
value === FilterByPriceOptions.Fixed ||
|
||||
value === FilterByPriceOptions.Dynamic
|
||||
) {
|
||||
if (isSelected) {
|
||||
if (priceSelections.length > 1) {
|
||||
// both selected -> select the other one
|
||||
const otherValue = priceFilterItems.find((p) => p.value !== value)
|
||||
.value
|
||||
await applyPriceFilter(otherValue)
|
||||
} else {
|
||||
// only the current one selected -> deselect it
|
||||
await applyPriceFilter(undefined)
|
||||
}
|
||||
} else {
|
||||
if (priceSelections.length > 0) {
|
||||
// one already selected -> both selected
|
||||
await applyPriceFilter(FilterByPriceOptions.All)
|
||||
setPriceSelections(priceFilterItems.map((p) => p.value))
|
||||
} else {
|
||||
// none selected -> select
|
||||
await applyPriceFilter(value)
|
||||
setPriceSelections([value])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isSelected) {
|
||||
if (serviceSelections.length > 1) {
|
||||
const otherValue = serviceFilterItems.find((p) => p.value !== value)
|
||||
.value
|
||||
await applyServiceFilter(otherValue)
|
||||
setServiceSelections([otherValue])
|
||||
} else {
|
||||
await applyServiceFilter(undefined)
|
||||
}
|
||||
} else {
|
||||
if (serviceSelections.length) {
|
||||
await applyServiceFilter(undefined)
|
||||
setServiceSelections(serviceFilterItems.map((p) => p.value))
|
||||
} else {
|
||||
await applyServiceFilter(value)
|
||||
setServiceSelections([value])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function applyClearFilter() {
|
||||
let urlLocation = await addExistingParamsToUrl(
|
||||
location,
|
||||
'priceType',
|
||||
'serviceType'
|
||||
)
|
||||
|
||||
urlLocation = `${urlLocation}`
|
||||
|
||||
setServiceSelections([])
|
||||
setPriceSelections([])
|
||||
|
||||
setPriceType(undefined)
|
||||
setServiceType(undefined)
|
||||
navigate(urlLocation)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.filterList}>
|
||||
{priceFilterItems.map((e, index) => {
|
||||
const isPriceSelected =
|
||||
e.value === priceType || priceSelections.includes(e.value)
|
||||
const selectFilter = cx({
|
||||
[styles.selected]: isPriceSelected,
|
||||
[styles.filter]: true
|
||||
})
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
style="text"
|
||||
key={index}
|
||||
className={selectFilter}
|
||||
onClick={async () => {
|
||||
handleSelectedFilter(isPriceSelected, e.value)
|
||||
}}
|
||||
>
|
||||
{e.display}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
{serviceFilterItems.map((e, index) => {
|
||||
const isServiceSelected =
|
||||
e.value === serviceType || serviceSelections.includes(e.value)
|
||||
const selectFilter = cx({
|
||||
[styles.selected]: isServiceSelected,
|
||||
[styles.filter]: true
|
||||
})
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
style="text"
|
||||
key={index}
|
||||
className={selectFilter}
|
||||
onClick={async () => {
|
||||
handleSelectedFilter(isServiceSelected, e.value)
|
||||
}}
|
||||
>
|
||||
{e.display}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
{clearFilters.map((e, index) => {
|
||||
const showClear =
|
||||
priceSelections.length > 0 || serviceSelections.length > 0
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
style="text"
|
||||
key={index}
|
||||
className={showClear ? styles.showClear : styles.hideClear}
|
||||
onClick={async () => {
|
||||
applyClearFilter()
|
||||
}}
|
||||
>
|
||||
{e.display}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -47,6 +47,10 @@ button.filter,
|
||||
margin-bottom: calc(var(--spacer) / 6);
|
||||
}
|
||||
|
||||
.showClear:hover {
|
||||
display: inline-flex;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
.showClear {
|
||||
display: inline-flex;
|
||||
text-transform: capitalize;
|
111
src/components/templates/Search/filterService.tsx
Normal file
111
src/components/templates/Search/filterService.tsx
Normal file
@ -0,0 +1,111 @@
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import { useNavigate } from '@reach/router'
|
||||
import styles from './filterService.module.css'
|
||||
import classNames from 'classnames/bind'
|
||||
import { addExistingParamsToUrl, FilterByTypeOptions } from './utils'
|
||||
import Button from '../../atoms/Button'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
const clearFilters = [{ display: 'Clear', value: '' }]
|
||||
|
||||
const serviceFilterItems = [
|
||||
{ display: 'data sets', value: FilterByTypeOptions.Data },
|
||||
{ display: 'algorithms', value: FilterByTypeOptions.Algorithm }
|
||||
]
|
||||
|
||||
export default function FilterPrice({
|
||||
serviceType,
|
||||
setServiceType
|
||||
}: {
|
||||
serviceType: string
|
||||
setServiceType: React.Dispatch<React.SetStateAction<string>>
|
||||
}): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
const [serviceSelections, setServiceSelections] = useState<string[]>([])
|
||||
|
||||
async function applyServiceFilter(filterBy: string) {
|
||||
let urlLocation = await addExistingParamsToUrl(location, 'serviceType')
|
||||
if (filterBy && location.search.indexOf('&serviceType') === -1) {
|
||||
urlLocation = `${urlLocation}&serviceType=${filterBy}`
|
||||
}
|
||||
setServiceType(filterBy)
|
||||
navigate(urlLocation)
|
||||
}
|
||||
|
||||
async function handleSelectedFilter(isSelected: boolean, value: string) {
|
||||
if (isSelected) {
|
||||
if (serviceSelections.length > 1) {
|
||||
const otherValue = serviceFilterItems.find((p) => p.value !== value)
|
||||
.value
|
||||
await applyServiceFilter(otherValue)
|
||||
setServiceSelections([otherValue])
|
||||
} else {
|
||||
await applyServiceFilter(undefined)
|
||||
if (serviceSelections.includes(value)) {
|
||||
serviceSelections.pop()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (serviceSelections.length) {
|
||||
await applyServiceFilter(undefined)
|
||||
setServiceSelections(serviceFilterItems.map((p) => p.value))
|
||||
} else {
|
||||
await applyServiceFilter(value)
|
||||
setServiceSelections([value])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function applyClearFilter() {
|
||||
let urlLocation = await addExistingParamsToUrl(location, 'serviceType')
|
||||
|
||||
urlLocation = `${urlLocation}`
|
||||
|
||||
setServiceSelections([])
|
||||
setServiceType(undefined)
|
||||
navigate(urlLocation)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.filterList}>
|
||||
{serviceFilterItems.map((e, index) => {
|
||||
const isServiceSelected =
|
||||
e.value === serviceType || serviceSelections.includes(e.value)
|
||||
const selectFilter = cx({
|
||||
[styles.selected]: isServiceSelected,
|
||||
[styles.filter]: true
|
||||
})
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
style="text"
|
||||
key={index}
|
||||
className={selectFilter}
|
||||
onClick={async () => {
|
||||
handleSelectedFilter(isServiceSelected, e.value)
|
||||
}}
|
||||
>
|
||||
{e.display}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
{clearFilters.map((e, index) => {
|
||||
const showClear = serviceSelections.length > 0
|
||||
return (
|
||||
<Button
|
||||
size="small"
|
||||
style="text"
|
||||
key={index}
|
||||
className={showClear ? styles.showClear : styles.hideClear}
|
||||
onClick={async () => {
|
||||
applyClearFilter()
|
||||
}}
|
||||
>
|
||||
{e.display}
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
@ -5,7 +5,7 @@ import SearchBar from '../../molecules/SearchBar'
|
||||
import AssetList from '../../organisms/AssetList'
|
||||
import styles from './index.module.css'
|
||||
import queryString from 'query-string'
|
||||
import PriceFilter from './filterPrice'
|
||||
import ServiceFilter from './filterService'
|
||||
import Sort from './sort'
|
||||
import { getResults } from './utils'
|
||||
import { navigate } from 'gatsby'
|
||||
@ -22,19 +22,9 @@ export default function SearchPage({
|
||||
}): ReactElement {
|
||||
const { config } = useOcean()
|
||||
const parsed = queryString.parse(location.search)
|
||||
const {
|
||||
text,
|
||||
owner,
|
||||
tags,
|
||||
page,
|
||||
sort,
|
||||
sortOrder,
|
||||
priceType,
|
||||
serviceType
|
||||
} = parsed
|
||||
const { text, owner, tags, page, sort, sortOrder, serviceType } = parsed
|
||||
const [queryResult, setQueryResult] = useState<QueryResult>()
|
||||
const [loading, setLoading] = useState<boolean>()
|
||||
const [price, setPriceType] = useState<string>(priceType as string)
|
||||
const [service, setServiceType] = useState<string>(serviceType as string)
|
||||
const [sortType, setSortType] = useState<string>(sort as string)
|
||||
const [sortDirection, setSortDirection] = useState<string>(
|
||||
@ -59,7 +49,6 @@ export default function SearchPage({
|
||||
tags,
|
||||
sort,
|
||||
page,
|
||||
priceType,
|
||||
serviceType,
|
||||
sortOrder,
|
||||
config.metadataCacheUri
|
||||
@ -82,10 +71,8 @@ export default function SearchPage({
|
||||
<SearchBar initialValue={(text || owner) as string} />
|
||||
)}
|
||||
<div className={styles.row}>
|
||||
<PriceFilter
|
||||
priceType={price}
|
||||
<ServiceFilter
|
||||
serviceType={service}
|
||||
setPriceType={setPriceType}
|
||||
setServiceType={setServiceType}
|
||||
/>
|
||||
<Sort
|
||||
@ -93,25 +80,18 @@ export default function SearchPage({
|
||||
sortDirection={sortDirection}
|
||||
setSortType={setSortType}
|
||||
setSortDirection={setSortDirection}
|
||||
setPriceType={setPriceType}
|
||||
setServiceType={setServiceType}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.results}>
|
||||
{loading ? (
|
||||
<Loader />
|
||||
) : queryResult ? (
|
||||
<AssetList
|
||||
assets={queryResult.results}
|
||||
showPagination
|
||||
page={queryResult.page}
|
||||
totalPages={queryResult.totalPages}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<AssetList
|
||||
assets={queryResult?.results}
|
||||
showPagination
|
||||
isLoading={loading}
|
||||
page={queryResult?.page}
|
||||
totalPages={queryResult?.totalPages}
|
||||
onPageChange={setPage}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</Permission>
|
||||
|
@ -3,9 +3,7 @@ import { useNavigate } from '@reach/router'
|
||||
import {
|
||||
addExistingParamsToUrl,
|
||||
SortTermOptions,
|
||||
SortValueOptions,
|
||||
FilterByPriceOptions,
|
||||
FilterByTypeOptions
|
||||
SortValueOptions
|
||||
} from './utils'
|
||||
import Button from '../../atoms/Button'
|
||||
import styles from './sort.module.css'
|
||||
@ -13,26 +11,18 @@ import classNames from 'classnames/bind'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
const sortItems = [
|
||||
{ display: 'Published', value: SortTermOptions.Created },
|
||||
{ display: 'Liquidity', value: SortTermOptions.Liquidity },
|
||||
{ display: 'Price', value: SortTermOptions.Price }
|
||||
]
|
||||
const sortItems = [{ display: 'Published', value: SortTermOptions.Created }]
|
||||
|
||||
export default function Sort({
|
||||
sortType,
|
||||
setSortType,
|
||||
sortDirection,
|
||||
setSortDirection,
|
||||
setPriceType,
|
||||
setServiceType
|
||||
setSortDirection
|
||||
}: {
|
||||
sortType: string
|
||||
setSortType: React.Dispatch<React.SetStateAction<string>>
|
||||
sortDirection: string
|
||||
setSortDirection: React.Dispatch<React.SetStateAction<string>>
|
||||
setPriceType: React.Dispatch<React.SetStateAction<string>>
|
||||
setServiceType: React.Dispatch<React.SetStateAction<string>>
|
||||
}): ReactElement {
|
||||
const navigate = useNavigate()
|
||||
const directionArrow = String.fromCharCode(
|
||||
@ -41,12 +31,7 @@ export default function Sort({
|
||||
async function sortResults(sortBy?: string, direction?: string) {
|
||||
let urlLocation: string
|
||||
if (sortBy) {
|
||||
urlLocation = await addExistingParamsToUrl(location, 'sort', 'priceType')
|
||||
urlLocation = `${urlLocation}&sort=${sortBy}`
|
||||
if (sortBy === SortTermOptions.Liquidity) {
|
||||
urlLocation = `${urlLocation}&priceType=${FilterByPriceOptions.Dynamic}`
|
||||
setPriceType(FilterByPriceOptions.Dynamic)
|
||||
}
|
||||
setSortType(sortBy)
|
||||
} else if (direction) {
|
||||
urlLocation = await addExistingParamsToUrl(location, 'sortOrder')
|
||||
|
@ -6,8 +6,6 @@ import { MetadataCache, Logger } from '@oceanprotocol/lib'
|
||||
import queryString from 'query-string'
|
||||
|
||||
export const SortTermOptions = {
|
||||
Liquidity: 'liquidity',
|
||||
Price: 'price',
|
||||
Created: 'created'
|
||||
} as const
|
||||
type SortTermOptions = typeof SortTermOptions[keyof typeof SortTermOptions]
|
||||
@ -25,36 +23,12 @@ export const SortValueOptions = {
|
||||
} as const
|
||||
type SortValueOptions = typeof SortValueOptions[keyof typeof SortValueOptions]
|
||||
|
||||
export const FilterByPriceOptions = {
|
||||
Fixed: 'exchange',
|
||||
Dynamic: 'pool',
|
||||
All: 'all'
|
||||
} as const
|
||||
type FilterByPriceOptions = typeof FilterByPriceOptions[keyof typeof FilterByPriceOptions]
|
||||
|
||||
export const FilterByTypeOptions = {
|
||||
Data: 'dataset',
|
||||
Algorithm: 'algorithm'
|
||||
} as const
|
||||
type FilterByTypeOptions = typeof FilterByTypeOptions[keyof typeof FilterByTypeOptions]
|
||||
|
||||
function addPriceFilterToQuery(sortTerm: string, priceFilter: string): string {
|
||||
if (priceFilter === FilterByPriceOptions.All) {
|
||||
sortTerm = priceFilter
|
||||
? sortTerm === ''
|
||||
? `(price.type:${FilterByPriceOptions.Fixed} OR price.type:${FilterByPriceOptions.Dynamic})`
|
||||
: `${sortTerm} AND (price.type:${FilterByPriceOptions.Dynamic} OR price.type:${FilterByPriceOptions.Fixed})`
|
||||
: sortTerm
|
||||
} else {
|
||||
sortTerm = priceFilter
|
||||
? sortTerm === ''
|
||||
? `price.type:${priceFilter}`
|
||||
: `${sortTerm} AND price.type:${priceFilter}`
|
||||
: sortTerm
|
||||
}
|
||||
return sortTerm
|
||||
}
|
||||
|
||||
function addTypeFilterToQuery(sortTerm: string, typeFilter: string): string {
|
||||
sortTerm = typeFilter
|
||||
? sortTerm === ''
|
||||
@ -64,13 +38,8 @@ function addTypeFilterToQuery(sortTerm: string, typeFilter: string): string {
|
||||
return sortTerm
|
||||
}
|
||||
|
||||
function getSortType(sortParam: string): string {
|
||||
const sortTerm =
|
||||
sortParam === SortTermOptions.Liquidity
|
||||
? SortElasticTerm.Liquidity
|
||||
: sortParam === SortTermOptions.Price
|
||||
? SortElasticTerm.Price
|
||||
: SortTermOptions.Created
|
||||
function getSortType(): string {
|
||||
const sortTerm = SortTermOptions.Created
|
||||
return sortTerm
|
||||
}
|
||||
|
||||
@ -83,10 +52,9 @@ export function getSearchQuery(
|
||||
offset?: string,
|
||||
sort?: string,
|
||||
sortOrder?: string,
|
||||
priceType?: string,
|
||||
serviceType?: string
|
||||
): SearchQuery {
|
||||
const sortTerm = getSortType(sort)
|
||||
const sortTerm = getSortType()
|
||||
const sortValue = sortOrder === SortValueOptions.Ascending ? 1 : -1
|
||||
let searchTerm = owner
|
||||
? `(publicKey.owner:${owner})`
|
||||
@ -97,15 +65,25 @@ export function getSearchQuery(
|
||||
? // eslint-disable-next-line no-useless-escape
|
||||
`(service.attributes.additionalInformation.categories:\"${categories}\")`
|
||||
: text || ''
|
||||
|
||||
// HACK: resolves the case sensitivity related to dataTokenInfo.symbol
|
||||
searchTerm = '*' + searchTerm.toUpperCase() + '*'
|
||||
searchTerm = addTypeFilterToQuery(searchTerm, serviceType)
|
||||
searchTerm = addPriceFilterToQuery(searchTerm, priceType)
|
||||
|
||||
return {
|
||||
page: Number(page) || 1,
|
||||
offset: Number(offset) || 21,
|
||||
query: {
|
||||
query_string: {
|
||||
query: `${searchTerm} -isInPurgatory:true`
|
||||
query: `${searchTerm} -isInPurgatory:true`,
|
||||
fields: [
|
||||
'dataTokenInfo.name',
|
||||
'dataTokenInfo.symbol',
|
||||
'service.attributes.main.name',
|
||||
'service.attributes.main.author',
|
||||
'service.attributes.additionalInformation.description'
|
||||
],
|
||||
default_operator: 'AND'
|
||||
}
|
||||
// ...(owner && { 'publicKey.owner': [owner] }),
|
||||
// ...(tags && { tags: [tags] }),
|
||||
@ -136,7 +114,6 @@ export async function getResults(
|
||||
offset?: string
|
||||
sort?: string
|
||||
sortOrder?: string
|
||||
priceType?: string
|
||||
serviceType?: string
|
||||
},
|
||||
metadataCacheUri: string
|
||||
@ -150,10 +127,10 @@ export async function getResults(
|
||||
categories,
|
||||
sort,
|
||||
sortOrder,
|
||||
priceType,
|
||||
serviceType
|
||||
} = params
|
||||
const metadataCache = new MetadataCache(metadataCacheUri, Logger)
|
||||
|
||||
const searchQuery = getSearchQuery(
|
||||
text,
|
||||
owner,
|
||||
@ -163,11 +140,10 @@ export async function getResults(
|
||||
offset,
|
||||
sort,
|
||||
sortOrder,
|
||||
priceType,
|
||||
serviceType
|
||||
)
|
||||
const queryResult = await metadataCache.queryMetadata(searchQuery)
|
||||
|
||||
const queryResult = await metadataCache.queryMetadata(searchQuery)
|
||||
return queryResult
|
||||
}
|
||||
|
||||
|
@ -12,11 +12,9 @@ import { PurgatoryData } from '@oceanprotocol/lib/dist/node/ddo/interfaces/Purga
|
||||
import getAssetPurgatoryData from '../utils/purgatory'
|
||||
import axios, { CancelToken } from 'axios'
|
||||
import { retrieveDDO } from '../utils/aquarius'
|
||||
import { getPrice } from '../utils/subgraph'
|
||||
import { MetadataMarket } from '../@types/MetaData'
|
||||
import { useOcean } from './Ocean'
|
||||
import { gql, useQuery } from '@apollo/client'
|
||||
import { PoolPrice } from '../@types/apollo/PoolPrice'
|
||||
import { FrePrice } from '../@types/apollo/FrePrice'
|
||||
|
||||
interface AssetProviderValue {
|
||||
isInPurgatory: boolean
|
||||
@ -33,26 +31,6 @@ interface AssetProviderValue {
|
||||
refreshDdo: (token?: CancelToken) => Promise<void>
|
||||
}
|
||||
|
||||
const poolQuery = gql`
|
||||
query PoolPrice($datatoken: String) {
|
||||
pools(where: { datatokenAddress: $datatoken }) {
|
||||
spotPrice
|
||||
consumePrice
|
||||
datatokenReserve
|
||||
oceanReserve
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const freQuery = gql`
|
||||
query FrePrice($datatoken: String) {
|
||||
fixedRateExchanges(orderBy: id, where: { datatoken: $datatoken }) {
|
||||
rate
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const AssetContext = createContext({} as AssetProviderValue)
|
||||
|
||||
const refreshInterval = 10000 // 10 sec.
|
||||
@ -75,68 +53,6 @@ function AssetProvider({
|
||||
const [owner, setOwner] = useState<string>()
|
||||
const [error, setError] = useState<string>()
|
||||
const [type, setType] = useState<MetadataMain['type']>()
|
||||
const [variables, setVariables] = useState({})
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
const {
|
||||
refetch: refetchFre,
|
||||
startPolling: startPollingFre,
|
||||
data: frePrice
|
||||
} = useQuery<FrePrice>(freQuery, {
|
||||
variables,
|
||||
skip: false
|
||||
})
|
||||
const {
|
||||
refetch: refetchPool,
|
||||
startPolling: startPollingPool,
|
||||
data: poolPrice
|
||||
} = useQuery<PoolPrice>(poolQuery, {
|
||||
variables,
|
||||
skip: false
|
||||
})
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
|
||||
// this is not working as expected, thus we need to fetch both pool and fre
|
||||
// useEffect(() => {
|
||||
// if (!ddo || !variables || variables === '') return
|
||||
|
||||
// if (ddo.price.type === 'exchange') {
|
||||
// refetchFre(variables)
|
||||
// startPollingFre(refreshInterval)
|
||||
// } else {
|
||||
// refetchPool(variables)
|
||||
// startPollingPool(refreshInterval)
|
||||
// }
|
||||
// }, [ddo, variables])
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!frePrice ||
|
||||
frePrice.fixedRateExchanges.length === 0 ||
|
||||
price.type !== 'exchange'
|
||||
)
|
||||
return
|
||||
setPrice((prevState) => ({
|
||||
...prevState,
|
||||
value: frePrice.fixedRateExchanges[0].rate,
|
||||
address: frePrice.fixedRateExchanges[0].id
|
||||
}))
|
||||
}, [frePrice])
|
||||
|
||||
useEffect(() => {
|
||||
if (!poolPrice || poolPrice.pools.length === 0 || price.type !== 'pool')
|
||||
return
|
||||
setPrice((prevState) => ({
|
||||
...prevState,
|
||||
value:
|
||||
poolPrice.pools[0].consumePrice === '-1'
|
||||
? poolPrice.pools[0].spotPrice
|
||||
: poolPrice.pools[0].consumePrice,
|
||||
ocean: poolPrice.pools[0].oceanReserve,
|
||||
datatoken: poolPrice.pools[0].datatokenReserve,
|
||||
isConsumable: poolPrice.pools[0].consumePrice === '-1' ? 'false' : 'true'
|
||||
}))
|
||||
}, [poolPrice])
|
||||
|
||||
const fetchDdo = async (token?: CancelToken) => {
|
||||
Logger.log('[asset] Init asset, get DDO')
|
||||
@ -201,10 +117,8 @@ function AssetProvider({
|
||||
const initMetadata = useCallback(async (ddo: DDO): Promise<void> => {
|
||||
if (!ddo) return
|
||||
|
||||
// Set price & metadata from DDO first
|
||||
// TODO Hacky hack, temporary™: set isConsumable to true by default since Aquarius can't be trusted.
|
||||
setPrice({ ...ddo.price, isConsumable: 'true' })
|
||||
setVariables({ datatoken: ddo?.dataToken.toLowerCase() })
|
||||
const returnedPrice = await getPrice(ddo)
|
||||
setPrice({ ...returnedPrice })
|
||||
|
||||
// Get metadata from DDO
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
|
@ -8,7 +8,7 @@ import React, {
|
||||
useCallback
|
||||
} from 'react'
|
||||
import Web3 from 'web3'
|
||||
import Web3Modal from 'web3modal'
|
||||
import Web3Modal, { getProviderInfo, IProviderInfo } from 'web3modal'
|
||||
import { infuraProjectId as infuraId, portisId } from '../../app.config'
|
||||
import WalletConnectProvider from '@walletconnect/web3-provider'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
@ -24,6 +24,7 @@ interface Web3ProviderValue {
|
||||
web3: Web3
|
||||
web3Provider: any
|
||||
web3Modal: Web3Modal
|
||||
web3ProviderInfo: IProviderInfo
|
||||
accountId: string
|
||||
networkId: number
|
||||
networkDisplayName: string
|
||||
@ -106,6 +107,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
|
||||
const [web3, setWeb3] = useState<Web3>()
|
||||
const [web3Provider, setWeb3Provider] = useState<any>()
|
||||
const [web3Modal, setWeb3Modal] = useState<Web3Modal>()
|
||||
const [web3ProviderInfo, setWeb3ProviderInfo] = useState<IProviderInfo>()
|
||||
const [networkId, setNetworkId] = useState<number>()
|
||||
const [networkDisplayName, setNetworkDisplayName] = useState<string>()
|
||||
const [networkData, setNetworkData] = useState<EthereumListsChain>()
|
||||
@ -148,7 +150,10 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
|
||||
// Create initial Web3Modal instance
|
||||
// -----------------------------------
|
||||
useEffect(() => {
|
||||
if (web3Modal) return
|
||||
if (web3Modal) {
|
||||
setWeb3Loading(false)
|
||||
return
|
||||
}
|
||||
|
||||
async function init() {
|
||||
// note: needs artificial await here so the log message is reached and output
|
||||
@ -209,6 +214,18 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
|
||||
getBlock()
|
||||
}, [web3, networkId])
|
||||
|
||||
// -----------------------------------
|
||||
// Get and set web3 provider info
|
||||
// -----------------------------------
|
||||
// Workaround cause getInjectedProviderName() always returns `MetaMask`
|
||||
// https://github.com/oceanprotocol/market/issues/332
|
||||
useEffect(() => {
|
||||
if (!web3Provider) return
|
||||
|
||||
const providerInfo = getProviderInfo(web3Provider)
|
||||
setWeb3ProviderInfo(providerInfo)
|
||||
}, [web3Provider])
|
||||
|
||||
// -----------------------------------
|
||||
// Logout helper
|
||||
// -----------------------------------
|
||||
@ -255,6 +272,7 @@ function Web3Provider({ children }: { children: ReactNode }): ReactElement {
|
||||
web3,
|
||||
web3Provider,
|
||||
web3Modal,
|
||||
web3ProviderInfo,
|
||||
accountId,
|
||||
networkId,
|
||||
networkDisplayName,
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
SearchQuery
|
||||
} from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
|
||||
import { AssetSelectionAsset } from '../components/molecules/FormFields/AssetSelection'
|
||||
import { PriceList, getAssetPrices } from './subgraph'
|
||||
import { PriceList, getAssetsPriceList } from './subgraph'
|
||||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||
|
||||
// TODO: import directly from ocean.js somehow.
|
||||
@ -113,7 +113,7 @@ export async function transformDDOToAssetSelection(
|
||||
): Promise<AssetSelectionAsset[]> {
|
||||
const source = axios.CancelToken.source()
|
||||
const didList: string[] = []
|
||||
const priceList: PriceList = await getAssetPrices(ddoList)
|
||||
const priceList: PriceList = await getAssetsPriceList(ddoList)
|
||||
const symbolList: any = {}
|
||||
for (const ddo of ddoList) {
|
||||
didList.push(ddo.id)
|
||||
|
@ -1,14 +1,32 @@
|
||||
import { gql, DocumentNode, ApolloQueryResult } from '@apollo/client'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import { DDO, BestPrice } from '@oceanprotocol/lib'
|
||||
import { getApolloClientInstance } from '../providers/ApolloClientProvider'
|
||||
import {
|
||||
AssetsPoolPrice,
|
||||
AssetsPoolPrice_pools as AssetsPoolPricePools
|
||||
} from '../@types/apollo/AssetsPoolPrice'
|
||||
import {
|
||||
AssetsFrePrice,
|
||||
AssetsFrePrice_fixedRateExchanges as AssetsFrePriceFixedRateExchanges
|
||||
} from '../@types/apollo/AssetsFrePrice'
|
||||
import { AssetPreviousOrder } from '../@types/apollo/AssetPreviousOrder'
|
||||
import BigNumber from 'bignumber.js'
|
||||
|
||||
export interface PriceList {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
const freQuery = gql`
|
||||
query AssetFrePrice($datatoken_in: [String!]) {
|
||||
export interface AssetListPrices {
|
||||
ddo: DDO
|
||||
price: BestPrice
|
||||
}
|
||||
|
||||
interface DidAndDatatokenMap {
|
||||
[name: string]: string
|
||||
}
|
||||
|
||||
const FreQuery = gql`
|
||||
query AssetsFrePrice($datatoken_in: [String!]) {
|
||||
fixedRateExchanges(orderBy: id, where: { datatoken_in: $datatoken_in }) {
|
||||
rate
|
||||
id
|
||||
@ -20,18 +38,42 @@ const freQuery = gql`
|
||||
}
|
||||
`
|
||||
|
||||
const poolQuery = gql`
|
||||
query AssetPoolPrice($datatokenAddress_in: [String!]) {
|
||||
pools(where: { datatokenAddress_in: $datatokenAddress_in }) {
|
||||
spotPrice
|
||||
consumePrice
|
||||
const AssetFreQuery = gql`
|
||||
query AssetFrePrice($datatoken: String) {
|
||||
fixedRateExchanges(orderBy: id, where: { datatoken: $datatoken }) {
|
||||
rate
|
||||
id
|
||||
datatokenAddress
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const previousOrderQuery = gql`
|
||||
const PoolQuery = gql`
|
||||
query AssetsPoolPrice($datatokenAddress_in: [String!]) {
|
||||
pools(where: { datatokenAddress_in: $datatokenAddress_in }) {
|
||||
id
|
||||
spotPrice
|
||||
consumePrice
|
||||
datatokenAddress
|
||||
datatokenReserve
|
||||
oceanReserve
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const AssetPoolPriceQuerry = gql`
|
||||
query AssetPoolPrice($datatokenAddress: String) {
|
||||
pools(where: { datatokenAddress: $datatokenAddress }) {
|
||||
id
|
||||
spotPrice
|
||||
consumePrice
|
||||
datatokenAddress
|
||||
datatokenReserve
|
||||
oceanReserve
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const PreviousOrderQuery = gql`
|
||||
query AssetPreviousOrder($id: String!, $account: String!) {
|
||||
tokenOrders(
|
||||
first: 1
|
||||
@ -53,7 +95,8 @@ async function fetchData(
|
||||
const client = getApolloClientInstance()
|
||||
const response = await client.query({
|
||||
query: query,
|
||||
variables: variables
|
||||
variables: variables,
|
||||
fetchPolicy: 'no-cache'
|
||||
})
|
||||
return response
|
||||
} catch (error) {
|
||||
@ -70,19 +113,18 @@ export async function getPreviousOrders(
|
||||
id: id,
|
||||
account: account
|
||||
}
|
||||
const fetchedPreviousOrders: any = await fetchData(
|
||||
previousOrderQuery,
|
||||
const fetchedPreviousOrders: ApolloQueryResult<AssetPreviousOrder> = await fetchData(
|
||||
PreviousOrderQuery,
|
||||
variables
|
||||
)
|
||||
if (fetchedPreviousOrders.data?.tokenOrders?.length === 0) return null
|
||||
if (assetTimeout === '0') {
|
||||
return fetchedPreviousOrders?.data?.tokenOrders[0]?.tx
|
||||
} else {
|
||||
const expiry = new BigNumber(
|
||||
fetchedPreviousOrders?.data?.tokenOrders[0]?.timestamp
|
||||
).plus(assetTimeout)
|
||||
const unixTime = new BigNumber(Math.floor(Date.now() / 1000))
|
||||
if (unixTime.isLessThan(expiry)) {
|
||||
const expiry =
|
||||
fetchedPreviousOrders?.data?.tokenOrders[0]?.timestamp * 1000 +
|
||||
Number(assetTimeout) * 1000
|
||||
if (Date.now() <= expiry) {
|
||||
return fetchedPreviousOrders?.data?.tokenOrders[0]?.tx
|
||||
} else {
|
||||
return null
|
||||
@ -90,9 +132,63 @@ export async function getPreviousOrders(
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAssetPrices(assets: DDO[]): Promise<PriceList> {
|
||||
const priceList: PriceList = {}
|
||||
const didDTMap: any = {}
|
||||
function transformPriceToBestPrice(
|
||||
frePrice: AssetsFrePriceFixedRateExchanges[],
|
||||
poolPrice: AssetsPoolPricePools[]
|
||||
) {
|
||||
if (poolPrice?.length > 0) {
|
||||
const price: BestPrice = {
|
||||
type: 'pool',
|
||||
address: poolPrice[0]?.id,
|
||||
value:
|
||||
poolPrice[0]?.consumePrice === '-1'
|
||||
? poolPrice[0]?.spotPrice
|
||||
: poolPrice[0]?.consumePrice,
|
||||
ocean: poolPrice[0]?.oceanReserve,
|
||||
datatoken: poolPrice[0]?.datatokenReserve,
|
||||
pools: [poolPrice[0]?.id],
|
||||
isConsumable: poolPrice[0]?.consumePrice === '-1' ? 'false' : 'true'
|
||||
}
|
||||
return price
|
||||
} else if (frePrice?.length > 0) {
|
||||
// TODO Hacky hack, temporary™: set isConsumable to true for fre assets.
|
||||
// isConsumable: 'true'
|
||||
const price: BestPrice = {
|
||||
type: 'exchange',
|
||||
value: frePrice[0]?.rate,
|
||||
address: frePrice[0]?.id,
|
||||
exchange_id: frePrice[0]?.id,
|
||||
ocean: 0,
|
||||
datatoken: 0,
|
||||
pools: [],
|
||||
isConsumable: 'true'
|
||||
}
|
||||
return price
|
||||
} else {
|
||||
const price: BestPrice = {
|
||||
type: '',
|
||||
value: 0,
|
||||
address: '',
|
||||
exchange_id: '',
|
||||
ocean: 0,
|
||||
datatoken: 0,
|
||||
pools: [],
|
||||
isConsumable: 'false'
|
||||
}
|
||||
return price
|
||||
}
|
||||
}
|
||||
|
||||
async function getAssetsPoolsExchangesAndDatatokenMap(
|
||||
assets: DDO[]
|
||||
): Promise<
|
||||
[
|
||||
ApolloQueryResult<AssetsPoolPrice>,
|
||||
ApolloQueryResult<AssetsFrePrice>,
|
||||
DidAndDatatokenMap
|
||||
]
|
||||
> {
|
||||
const didDTMap: DidAndDatatokenMap = {}
|
||||
const dataTokenList: string[] = []
|
||||
|
||||
for (const ddo of assets) {
|
||||
@ -105,16 +201,100 @@ export async function getAssetPrices(assets: DDO[]): Promise<PriceList> {
|
||||
const poolVariables = {
|
||||
datatokenAddress_in: dataTokenList
|
||||
}
|
||||
const poolPriceResponse: any = await fetchData(poolQuery, poolVariables)
|
||||
|
||||
const poolPriceResponse: ApolloQueryResult<AssetsPoolPrice> = await fetchData(
|
||||
PoolQuery,
|
||||
poolVariables
|
||||
)
|
||||
const frePriceResponse: ApolloQueryResult<AssetsFrePrice> = await fetchData(
|
||||
FreQuery,
|
||||
freVariables
|
||||
)
|
||||
|
||||
return [poolPriceResponse, frePriceResponse, didDTMap]
|
||||
}
|
||||
|
||||
export async function getAssetsPriceList(assets: DDO[]): Promise<PriceList> {
|
||||
const priceList: PriceList = {}
|
||||
|
||||
const values: [
|
||||
ApolloQueryResult<AssetsPoolPrice>,
|
||||
ApolloQueryResult<AssetsFrePrice>,
|
||||
DidAndDatatokenMap
|
||||
] = await getAssetsPoolsExchangesAndDatatokenMap(assets)
|
||||
const poolPriceResponse = values[0]
|
||||
const frePriceResponse = values[1]
|
||||
const didDTMap: DidAndDatatokenMap = values[2]
|
||||
|
||||
for (const poolPrice of poolPriceResponse.data?.pools) {
|
||||
priceList[didDTMap[poolPrice.datatokenAddress]] =
|
||||
poolPrice.consumePrice === '-1'
|
||||
? poolPrice.spotPrice
|
||||
: poolPrice.consumePrice
|
||||
}
|
||||
const frePriceResponse: any = await fetchData(freQuery, freVariables)
|
||||
for (const frePrice of frePriceResponse.data?.fixedRateExchanges) {
|
||||
priceList[didDTMap[frePrice.datatoken?.address]] = frePrice.rate
|
||||
}
|
||||
return priceList
|
||||
}
|
||||
|
||||
export async function getPrice(asset: DDO): Promise<BestPrice> {
|
||||
const freVariables = {
|
||||
datatoken: asset?.dataToken.toLowerCase()
|
||||
}
|
||||
|
||||
const poolVariables = {
|
||||
datatokenAddress: asset?.dataToken.toLowerCase()
|
||||
}
|
||||
|
||||
const poolPriceResponse: ApolloQueryResult<AssetsPoolPrice> = await fetchData(
|
||||
AssetPoolPriceQuerry,
|
||||
poolVariables
|
||||
)
|
||||
const frePriceResponse: ApolloQueryResult<AssetsFrePrice> = await fetchData(
|
||||
AssetFreQuery,
|
||||
freVariables
|
||||
)
|
||||
|
||||
const bestPrice: BestPrice = transformPriceToBestPrice(
|
||||
frePriceResponse.data.fixedRateExchanges,
|
||||
poolPriceResponse.data.pools
|
||||
)
|
||||
|
||||
return bestPrice
|
||||
}
|
||||
|
||||
export async function getAssetsBestPrices(
|
||||
assets: DDO[]
|
||||
): Promise<AssetListPrices[]> {
|
||||
const assetsWithPrice: AssetListPrices[] = []
|
||||
|
||||
const values: [
|
||||
ApolloQueryResult<AssetsPoolPrice>,
|
||||
ApolloQueryResult<AssetsFrePrice>,
|
||||
DidAndDatatokenMap
|
||||
] = await getAssetsPoolsExchangesAndDatatokenMap(assets)
|
||||
const poolPriceResponse = values[0]
|
||||
const frePriceResponse = values[1]
|
||||
|
||||
for (const ddo of assets) {
|
||||
const dataToken = ddo.dataToken.toLowerCase()
|
||||
const poolPrice: AssetsPoolPricePools[] = []
|
||||
const frePrice: AssetsFrePriceFixedRateExchanges[] = []
|
||||
const pool = poolPriceResponse.data?.pools.find(
|
||||
(pool: any) => pool.datatokenAddress === dataToken
|
||||
)
|
||||
pool && poolPrice.push(pool)
|
||||
const fre = frePriceResponse.data?.fixedRateExchanges.find(
|
||||
(fre: any) => fre.datatoken.address === dataToken
|
||||
)
|
||||
fre && frePrice.push(fre)
|
||||
const bestPrice = transformPriceToBestPrice(frePrice, poolPrice)
|
||||
assetsWithPrice.push({
|
||||
ddo: ddo,
|
||||
price: bestPrice
|
||||
})
|
||||
}
|
||||
|
||||
return assetsWithPrice
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Logger, ConfigHelperConfig } from '@oceanprotocol/lib'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
|
||||
export interface EthereumListsChain {
|
||||
name: string
|
||||
@ -79,32 +79,33 @@ export function addCustomNetwork(
|
||||
)
|
||||
}
|
||||
|
||||
export function addOceanToWallet(
|
||||
config: ConfigHelperConfig,
|
||||
web3Provider: any
|
||||
): void {
|
||||
export async function addTokenToWallet(
|
||||
web3Provider: any,
|
||||
address: string,
|
||||
symbol: string,
|
||||
logo?: string
|
||||
): Promise<void> {
|
||||
const image =
|
||||
logo ||
|
||||
'https://raw.githubusercontent.com/oceanprotocol/art/main/logo/token.png'
|
||||
|
||||
const tokenMetadata = {
|
||||
type: 'ERC20',
|
||||
options: {
|
||||
address: config.oceanTokenAddress,
|
||||
symbol: config.oceanTokenSymbol,
|
||||
decimals: 18,
|
||||
image:
|
||||
'https://raw.githubusercontent.com/oceanprotocol/art/main/logo/token.png'
|
||||
}
|
||||
options: { address, symbol, image, decimals: 18 }
|
||||
}
|
||||
|
||||
web3Provider.sendAsync(
|
||||
{
|
||||
method: 'wallet_watchAsset',
|
||||
params: tokenMetadata,
|
||||
id: Math.round(Math.random() * 100000)
|
||||
},
|
||||
(err: string, added: any) => {
|
||||
(err: { code: number; message: string }, added: any) => {
|
||||
if (err || 'error' in added) {
|
||||
Logger.error(
|
||||
`Couldn't add ${tokenMetadata.options.symbol} (${
|
||||
tokenMetadata.options.address
|
||||
}) to MetaMask, error: ${err || added.error}`
|
||||
}) to MetaMask, error: ${err.message || added.error}`
|
||||
)
|
||||
} else {
|
||||
Logger.log(
|
||||
|
Loading…
Reference in New Issue
Block a user