mirror of
https://github.com/oceanprotocol/market.git
synced 2024-06-30 05:41:41 +02:00
Merge pull request #933 from oceanprotocol/v4-publish
new publish form: setup & data collection & live validation
This commit is contained in:
commit
8b47cf2275
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -13,4 +13,4 @@ networks-metadata.json
|
|||
src/@types/apollo
|
||||
graphql.schema.json
|
||||
src/@types/graph.types.ts
|
||||
public
|
||||
tsconfig.tsbuildinfo
|
||||
|
|
|
@ -1,111 +0,0 @@
|
|||
{
|
||||
"title": "Publish an Algorithm",
|
||||
"data": [
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Title",
|
||||
"placeholder": "e.g. Shapes of Desert Plants",
|
||||
"help": "Enter a concise title.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"label": "Description",
|
||||
"help": "Add a thorough description with as much detail as possible. You can use [Markdown](https://daringfireball.net/projects/markdown/basics). You can change the description at any time. If you provide personal data, please note that it will remain in the transaction history. For more information on how personal data is handled within the metadata, please refer to our [privacy policy](/privacy/en).",
|
||||
"type": "textarea",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"label": "File",
|
||||
"placeholder": "e.g. https://file.com/file.json",
|
||||
"help": "Please enter the URL to your algorithm file and click \"ADD FILE\" to validate the data. This URL will be stored encrypted after publishing. Some restrictions apply:\n\n- max. running time: 1 min.\n- [Writing Algorithms for Compute to Data](https://docs.oceanprotocol.com/tutorials/compute-to-data-algorithms/)",
|
||||
"type": "files",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "dockerImage",
|
||||
"label": "Docker Image",
|
||||
"placeholder": "e.g. python3.7",
|
||||
"help": "Please select an image to run your algorithm.",
|
||||
"type": "boxSelection",
|
||||
"options": [],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"label": "Image URL",
|
||||
"placeholder": "e.g. oceanprotocol/algo_dockers or https://example.com/image_path",
|
||||
"help": "Provide the name of a public Docker image or the full url if you have it hosted in a 3rd party repo",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "containerTag",
|
||||
"label": "Docker Image Tag",
|
||||
"placeholder": "e.g. latest",
|
||||
"help": "Provide the tag for your Docker image.",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "timeout",
|
||||
"label": "Timeout",
|
||||
"help": "Define how long buyers should be able to download the algorithm again after the initial purchase.",
|
||||
"placeholder": "Forever",
|
||||
"type": "select",
|
||||
"options": ["Forever", "1 day", "1 week", "1 month", "1 year"],
|
||||
"sortOptions": false,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "dataTokenOptions",
|
||||
"label": "Datatoken Name & Symbol",
|
||||
"type": "datatoken",
|
||||
"help": "The datatoken for this algorithm will be created with this name & symbol.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "entrypoint",
|
||||
"label": "Entrypoint",
|
||||
"placeholder": "e.g. python $ALGO",
|
||||
"help": "Provide the entrypoint for your algorithm.",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "algorithmPrivacy",
|
||||
"label": "Algorithm Privacy",
|
||||
"type": "checkbox",
|
||||
"options": ["Keep my algorithm private"],
|
||||
"help": "By default, your algorithm can be downloaded for a fixed or dynamic price in addition to running in compute jobs. Enabling this option will prevent downloading, so your algorithm can only be run as part of a compute job on a data set.",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"label": "Author",
|
||||
"placeholder": "e.g. Jelly McJellyfish",
|
||||
"help": "Give proper attribution for your data set. You are welcome to use a pseudonym, and you can change your author name at any time. Please note that it will remain in the transaction history. For more information on how personal data is handled within the metadata, please refer to our [privacy policy](/privacy/en).",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"label": "Tags",
|
||||
"placeholder": "e.g. logistics, ai",
|
||||
"help": "Separate tags with comma."
|
||||
},
|
||||
{
|
||||
"name": "providerUri",
|
||||
"label": "Custom Provider URL",
|
||||
"type": "providerUri",
|
||||
"help": "Enter the URL for your custom provider or leave blank to use the default provider. [Learn more](https://github.com/oceanprotocol/provider/).",
|
||||
"placeholder": "https://provider.polygon.oceanprotocol.com/",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "termsAndConditions",
|
||||
"label": "Terms & Conditions",
|
||||
"type": "terms",
|
||||
"options": ["I agree to these Terms and Conditions"],
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"success": "Algorithm Published!"
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
{
|
||||
"title": "Publish a Data Set",
|
||||
"data": [
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Title",
|
||||
"placeholder": "e.g. Shapes of Desert Plants",
|
||||
"help": "Enter a concise title.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"label": "Description",
|
||||
"help": "Add a thorough description with as much detail as possible. You can use [Markdown](https://daringfireball.net/projects/markdown/basics). You can change the description at any time. If you provide personal data, please note that it will remain in the transaction history. For more information on how personal data is handled within the metadata, please refer to our [privacy policy](/privacy/en).",
|
||||
"type": "textarea",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"label": "File",
|
||||
"placeholder": "e.g. https://file.com/file.json",
|
||||
"help": "Please enter the URL to your data set file and click \"ADD FILE\" to validate the data. This URL will be stored encrypted after publishing. For a compute data set, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size.",
|
||||
"type": "files",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "links",
|
||||
"label": "Sample file",
|
||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
||||
"help": "Please enter the URL to a sample of your data set file and click \"ADD FILE\" to validate the data. This file should reveal the data structure of your data set, e.g. by including the header and one line of a CSV file. This file URL will be publicly available after publishing.",
|
||||
"type": "files"
|
||||
},
|
||||
{
|
||||
"name": "access",
|
||||
"label": "Access Type",
|
||||
"help": "Choose how you want your files to be accessible for the specified price.",
|
||||
"type": "boxSelection",
|
||||
"options": ["Download", "Compute"],
|
||||
"required": true,
|
||||
"disclaimer": "Please do not provide downloadable personal data without the consent of the data subjects.",
|
||||
"disclaimerValues": ["Download"]
|
||||
},
|
||||
{
|
||||
"name": "providerUri",
|
||||
"label": "Custom Provider URL",
|
||||
"type": "providerUri",
|
||||
"help": "Enter the URL for your custom provider or leave blank to use the default provider. [Learn more](https://github.com/oceanprotocol/provider/).",
|
||||
"placeholder": "https://provider.polygon.oceanprotocol.com/",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "timeout",
|
||||
"label": "Timeout",
|
||||
"help": "Define how long buyers should be able to download the data set again after the initial purchase.",
|
||||
"type": "select",
|
||||
"options": ["Forever", "1 day", "1 week", "1 month", "1 year"],
|
||||
"sortOptions": false,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "dataTokenOptions",
|
||||
"label": "Datatoken Name & Symbol",
|
||||
"type": "datatoken",
|
||||
"help": "The datatoken for this data set will be created with this name & symbol.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"label": "Author",
|
||||
"placeholder": "e.g. Jelly McJellyfish",
|
||||
"help": "Give proper attribution for your data set. You are welcome to use a pseudonym, and you can change your author name at any time. Please note that it will remain in the transaction history. For more information on how personal data is handled within the metadata, please refer to our [privacy policy](/privacy/en).",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"label": "Tags",
|
||||
"placeholder": "e.g. logistics, ai",
|
||||
"help": "Separate tags with comma."
|
||||
},
|
||||
{
|
||||
"name": "termsAndConditions",
|
||||
"label": "Terms & Conditions",
|
||||
"type": "terms",
|
||||
"options": ["I agree to these Terms and Conditions"],
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"success": "Asset Created!"
|
||||
}
|
170
content/publish/form.json
Normal file
170
content/publish/form.json
Normal file
|
@ -0,0 +1,170 @@
|
|||
{
|
||||
"metadata": {
|
||||
"title": "Metadata",
|
||||
"fields": [
|
||||
{
|
||||
"name": "nft",
|
||||
"label": "Data NFT",
|
||||
"type": "nft",
|
||||
"help": "All metadata is stored on-chain in a newly deployed ERC-721 contract representing this asset, created with this name & symbol.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"label": "Asset Type",
|
||||
"type": "boxSelection",
|
||||
"options": ["Dataset", "Algorithm"],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"label": "Title",
|
||||
"placeholder": "e.g. Shapes of Desert Plants",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "description",
|
||||
"label": "Description",
|
||||
"help": "Add a thorough description with as much detail as possible. You can use [Markdown](https://daringfireball.net/projects/markdown/basics). You can change the description at any time. If you provide personal data, please note that it will remain in the transaction history. For more information on how personal data is handled within the metadata, please refer to our [privacy policy](/privacy/en).",
|
||||
"type": "textarea",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"label": "Author",
|
||||
"placeholder": "e.g. Jelly McJellyfish",
|
||||
"help": "Give proper attribution for your data set. You are welcome to use a pseudonym, and you can change your author name at any time. Please note that it will remain in the transaction history. For more information on how personal data is handled within the metadata, please refer to our [privacy policy](/privacy/en).",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"label": "Tags",
|
||||
"placeholder": "e.g. logistics, ai",
|
||||
"help": "Separate tags with comma."
|
||||
},
|
||||
{
|
||||
"name": "dockerImage",
|
||||
"label": "Docker Image",
|
||||
"help": "Please select an image to run your algorithm.",
|
||||
"type": "boxSelection",
|
||||
"options": [
|
||||
"populated from algorithmContainerPresets in Publish/_constants"
|
||||
],
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "dockerImageCustom",
|
||||
"label": "Docker Image URL",
|
||||
"placeholder": "e.g. oceanprotocol/algo_dockers or https://example.com/image_path",
|
||||
"help": "Provide the name of a public Docker image or the full url if you have it hosted in a 3rd party repo",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "dockerImageCustomTag",
|
||||
"label": "Docker Image Tag",
|
||||
"placeholder": "e.g. latest",
|
||||
"help": "Provide the tag for your Docker image.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "dockerImageCustomEntrypoint",
|
||||
"label": "Docker Entrypoint",
|
||||
"placeholder": "e.g. python $ALGO",
|
||||
"help": "Provide the entrypoint for your algorithm.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "termsAndConditions",
|
||||
"label": "Terms & Conditions",
|
||||
"type": "checkbox",
|
||||
"options": ["I agree to the Terms and Conditions"],
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"services": {
|
||||
"title": "Access",
|
||||
"fields": [
|
||||
{
|
||||
"name": "dataTokenOptions",
|
||||
"label": "Datatoken",
|
||||
"type": "datatoken",
|
||||
"help": "The datatoken used for accessing this asset will be created with this name & symbol.",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "providerUrl",
|
||||
"label": "Provider URL",
|
||||
"type": "providerUrl",
|
||||
"help": "Enter the URL for your custom [provider](https://github.com/oceanprotocol/provider/) or leave as is to use the default one. If you change your provider URL after adding your file, please add & validate your file again.",
|
||||
"placeholder": "e.g. https://provider.oceanprotocol.com/",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "files",
|
||||
"label": "File",
|
||||
"placeholder": "e.g. https://file.com/file.json",
|
||||
"help": "Please enter the URL to your data set file and click \"ADD FILE\" to validate the data. This URL will be stored encrypted after publishing. For a compute data set, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size.",
|
||||
"type": "files",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "links",
|
||||
"label": "Sample file",
|
||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
||||
"help": "Please enter the URL to a sample of your data set file and click \"ADD FILE\" to validate the data. This file should reveal the data structure of your data set, e.g. by including the header and one line of a CSV file. This file URL will be publicly available after publishing.",
|
||||
"type": "files"
|
||||
},
|
||||
{
|
||||
"name": "algorithmPrivacy",
|
||||
"label": "Algorithm Privacy",
|
||||
"type": "checkbox",
|
||||
"options": ["Keep my algorithm private"],
|
||||
"help": "By default, your algorithm can be downloaded for a fixed or dynamic price in addition to running in compute jobs. Enabling this option will prevent downloading, so your algorithm can only be run as part of a compute job on a data set.",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"name": "access",
|
||||
"label": "Access Type",
|
||||
"help": "Choose how you want your files to be accessible for the specified price.",
|
||||
"type": "boxSelection",
|
||||
"options": ["Download", "Compute"],
|
||||
"required": true,
|
||||
"disclaimer": "Please do not provide downloadable personal data without the consent of the data subjects.",
|
||||
"disclaimerValues": ["Download"]
|
||||
},
|
||||
{
|
||||
"name": "timeout",
|
||||
"label": "Timeout",
|
||||
"help": "Define how long buyers should be able to download the data set again after the initial purchase.",
|
||||
"type": "select",
|
||||
"options": ["Forever", "1 day", "1 week", "1 month", "1 year"],
|
||||
"sortOptions": false,
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "computeOptions",
|
||||
"label": "Compute Environment",
|
||||
"type": "radio",
|
||||
"options": [
|
||||
"populated from computeEnvironmentDefaults in Publish/_constants & computeEnvironmentOptions in Publish/Services/"
|
||||
],
|
||||
"required": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"pricing": {
|
||||
"title": "Pricing",
|
||||
"fields": [
|
||||
{
|
||||
"name": "dummy content, actual content is defined under 'create' key in ../price.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"preview": {
|
||||
"title": "Preview"
|
||||
},
|
||||
"submission": {
|
||||
"title": "Submit"
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"title": "Publish",
|
||||
"description": "Highlight the important features of your data set or algorithm to make it more discoverable and catch the interest of data consumers.",
|
||||
"warning": "Given the beta status, publishing on Ropsten or Rinkeby first is strongly recommended. Please familiarize yourself with [the market](https://oceanprotocol.com/technology/marketplaces), [the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13), and the [Terms of Use](/terms).",
|
||||
"warning": "Publishing into a test network first is strongly recommended. Please familiarize yourself with [the market](https://oceanprotocol.com/technology/marketplaces), [the risks](https://blog.oceanprotocol.com/on-staking-on-data-in-ocean-market-3d8e09eb0a13), and the [Terms of Use](/terms).",
|
||||
"tooltipNetwork": "Assets are published into the network your wallet is connected to. Switch your wallet's network to publish into another one."
|
||||
}
|
2815
package-lock.json
generated
2815
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
|
@ -32,21 +32,23 @@
|
|||
"axios": "^0.24.0",
|
||||
"chart.js": "^2.9.4",
|
||||
"classnames": "^2.3.1",
|
||||
"d3": "^7.1.1",
|
||||
"date-fns": "^2.25.0",
|
||||
"decimal.js": "^10.3.1",
|
||||
"dom-confetti": "^0.2.2",
|
||||
"dotenv": "^10.0.0",
|
||||
"ethereum-address": "0.0.4",
|
||||
"ethereum-blockies": "github:MyEtherWallet/blockies",
|
||||
"filesize": "^8.0.3",
|
||||
"filesize": "^8.0.6",
|
||||
"formik": "^2.2.9",
|
||||
"gray-matter": "^4.0.3",
|
||||
"is-url-superb": "^6.0.0",
|
||||
"is-url-superb": "^6.1.0",
|
||||
"js-cookie": "^3.0.1",
|
||||
"js-sha256": "^0.9.0",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"lodash.omit": "^4.5.0",
|
||||
"next": "^12.0.1",
|
||||
"next": "^12.0.3",
|
||||
"query-string": "^7.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-chartjs-2": "^2.11.2",
|
||||
|
@ -54,16 +56,16 @@
|
|||
"react-data-table-component": "^6.11.7",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-dotdotdot": "^1.3.1",
|
||||
"react-modal": "^3.14.3",
|
||||
"react-paginate": "^7.1.3",
|
||||
"react-modal": "^3.14.4",
|
||||
"react-paginate": "^8.0.0",
|
||||
"react-spring": "^9.3.0",
|
||||
"react-tabs": "^3.2.2",
|
||||
"react-toastify": "^8.0.3",
|
||||
"react-tabs": "^3.2.3",
|
||||
"react-toastify": "^8.1.0",
|
||||
"remark": "^13.0.0",
|
||||
"remark-gfm": "^1.0.0",
|
||||
"remark-html": "^13.0.1",
|
||||
"remove-markdown": "^0.3.0",
|
||||
"slugify": "^1.6.1",
|
||||
"slugify": "^1.6.2",
|
||||
"swr": "^1.0.1",
|
||||
"urql": "^2.0.5",
|
||||
"use-dark-mode": "^2.3.1",
|
||||
|
@ -79,13 +81,14 @@
|
|||
"@graphql-codegen/typescript-react-apollo": "^3.1.6",
|
||||
"@svgr/webpack": "^5.5.0",
|
||||
"@types/chart.js": "^2.9.32",
|
||||
"@types/d3": "^7.1.0",
|
||||
"@types/js-cookie": "^3.0.0",
|
||||
"@types/loadable__component": "^5.13.1",
|
||||
"@types/lodash.debounce": "^4.0.3",
|
||||
"@types/lodash.omit": "^4.5.6",
|
||||
"@types/node": "^16.11.5",
|
||||
"@types/react": "^17.0.32",
|
||||
"@types/react-dom": "^17.0.10",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-modal": "^3.13.1",
|
||||
"@types/react-paginate": "^7.1.1",
|
||||
"@types/react-tabs": "^2.3.3",
|
||||
|
@ -98,13 +101,13 @@
|
|||
"eslint-config-oceanprotocol": "^1.5.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-react": "^7.24.0",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"eslint-plugin-react": "^7.27.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"file-loader": "^6.2.0",
|
||||
"husky": "^7.0.2",
|
||||
"prettier": "^2.4.1",
|
||||
"pretty-quick": "^3.1.1",
|
||||
"serve": "^12.0.1",
|
||||
"serve": "^13.0.2",
|
||||
"typescript": "^4.4.4"
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
@ -7,7 +7,7 @@ import React, {
|
|||
useCallback,
|
||||
ReactNode
|
||||
} from 'react'
|
||||
import { Logger, DDO, MetadataMain } from '@oceanprotocol/lib'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { PurgatoryData } from '@oceanprotocol/lib/dist/node/ddo/interfaces/PurgatoryData'
|
||||
import getAssetPurgatoryData from '@utils/purgatory'
|
||||
import { CancelToken } from 'axios'
|
||||
|
@ -20,13 +20,10 @@ import { useCancelToken } from '@hooks/useCancelToken'
|
|||
interface AssetProviderValue {
|
||||
isInPurgatory: boolean
|
||||
purgatoryData: PurgatoryData
|
||||
ddo: DDO
|
||||
did: string
|
||||
metadata: MetadataMarket
|
||||
ddo: Asset
|
||||
title: string
|
||||
owner: string
|
||||
price: BestPrice
|
||||
type: MetadataMain['type']
|
||||
error?: string
|
||||
refreshInterval: number
|
||||
isAssetNetwork: boolean
|
||||
|
@ -42,7 +39,7 @@ function AssetProvider({
|
|||
asset,
|
||||
children
|
||||
}: {
|
||||
asset: string | DDO
|
||||
asset: string | Asset
|
||||
children: ReactNode
|
||||
}): ReactElement {
|
||||
const { appConfig } = useSiteMetadata()
|
||||
|
@ -50,17 +47,17 @@ function AssetProvider({
|
|||
const { networkId } = useWeb3()
|
||||
const [isInPurgatory, setIsInPurgatory] = useState(false)
|
||||
const [purgatoryData, setPurgatoryData] = useState<PurgatoryData>()
|
||||
const [ddo, setDDO] = useState<DDO>()
|
||||
const [ddo, setDDO] = useState<Asset>()
|
||||
const [did, setDID] = useState<string>()
|
||||
const [metadata, setMetadata] = useState<MetadataMarket>()
|
||||
const [title, setTitle] = useState<string>()
|
||||
const [price, setPrice] = useState<BestPrice>()
|
||||
const [owner, setOwner] = useState<string>()
|
||||
const [error, setError] = useState<string>()
|
||||
const [type, setType] = useState<MetadataMain['type']>()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [isAssetNetwork, setIsAssetNetwork] = useState<boolean>()
|
||||
|
||||
const newCancelToken = useCancelToken()
|
||||
|
||||
const fetchDdo = async (token?: CancelToken) => {
|
||||
Logger.log('[asset] Init asset, get DDO')
|
||||
setLoading(true)
|
||||
|
@ -85,27 +82,6 @@ function AssetProvider({
|
|||
setLoading(false)
|
||||
}
|
||||
|
||||
//
|
||||
// Get and set DDO based on passed DDO or DID
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!asset || !appConfig.metadataCacheUri) return
|
||||
|
||||
let isMounted = true
|
||||
|
||||
async function init() {
|
||||
const ddo = await fetchDdo(newCancelToken())
|
||||
if (!isMounted) return
|
||||
Logger.debug('[asset] Got DDO', ddo)
|
||||
setDDO(ddo)
|
||||
setDID(asset as string)
|
||||
}
|
||||
init()
|
||||
return () => {
|
||||
isMounted = false
|
||||
}
|
||||
}, [asset, appConfig.metadataCacheUri])
|
||||
|
||||
const setPurgatory = useCallback(async (did: string): Promise<void> => {
|
||||
if (!did) return
|
||||
|
||||
|
@ -119,29 +95,41 @@ function AssetProvider({
|
|||
}
|
||||
}, [])
|
||||
|
||||
const initMetadata = useCallback(async (ddo: DDO): Promise<void> => {
|
||||
//
|
||||
// Get and set DDO based on passed DDO or DID
|
||||
//
|
||||
useEffect(() => {
|
||||
if (!asset || !appConfig.metadataCacheUri) return
|
||||
|
||||
let isMounted = true
|
||||
|
||||
async function init() {
|
||||
const ddo = await fetchDdo(newCancelToken())
|
||||
if (!isMounted || !ddo) return
|
||||
Logger.debug('[asset] Got DDO', ddo)
|
||||
setDDO(ddo)
|
||||
setDID(asset as string)
|
||||
setTitle(ddo.metadata.name)
|
||||
setOwner(ddo.nft.owner)
|
||||
setIsInPurgatory(ddo.isInPurgatory === 'true')
|
||||
await setPurgatory(ddo.id)
|
||||
}
|
||||
init()
|
||||
return () => {
|
||||
isMounted = false
|
||||
}
|
||||
}, [asset, appConfig.metadataCacheUri])
|
||||
|
||||
const initPrice = useCallback(async (ddo: Asset): Promise<void> => {
|
||||
if (!ddo) return
|
||||
setLoading(true)
|
||||
const returnedPrice = await getPrice(ddo)
|
||||
setPrice({ ...returnedPrice })
|
||||
|
||||
// Get metadata from DDO
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
setMetadata(attributes as unknown as MetadataMarket)
|
||||
setTitle(attributes?.main.name)
|
||||
setType(attributes.main.type)
|
||||
setOwner(ddo.publicKey[0].owner)
|
||||
Logger.log('[asset] Got Metadata from DDO', attributes)
|
||||
|
||||
setIsInPurgatory(ddo.isInPurgatory === 'true')
|
||||
await setPurgatory(ddo.id)
|
||||
setLoading(false)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo) return
|
||||
initMetadata(ddo)
|
||||
}, [ddo, initMetadata])
|
||||
initPrice(ddo)
|
||||
}, [ddo, initPrice])
|
||||
|
||||
// Check user network against asset network
|
||||
useEffect(() => {
|
||||
|
@ -157,11 +145,9 @@ function AssetProvider({
|
|||
{
|
||||
ddo,
|
||||
did,
|
||||
metadata,
|
||||
title,
|
||||
owner,
|
||||
price,
|
||||
type,
|
||||
error,
|
||||
isInPurgatory,
|
||||
purgatoryData,
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
} from '@utils/subgraph'
|
||||
import { useUserPreferences } from './UserPreferences'
|
||||
import { PoolShares_poolShares as PoolShare } from '../@types/apollo/PoolShares'
|
||||
import { DDO, Logger } from '@oceanprotocol/lib'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { getDownloadAssets, getPublishedAssets } from '@utils/aquarius'
|
||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||
import { accountTruncate } from '@utils/web3'
|
||||
|
@ -27,7 +27,7 @@ interface ProfileProviderValue {
|
|||
profile: Profile
|
||||
poolShares: PoolShare[]
|
||||
isPoolSharesLoading: boolean
|
||||
assets: DDO[]
|
||||
assets: Asset[]
|
||||
assetsTotal: number
|
||||
isEthAddress: boolean
|
||||
downloads: DownloadedAsset[]
|
||||
|
@ -170,7 +170,7 @@ function ProfileProvider({
|
|||
//
|
||||
// PUBLISHED ASSETS
|
||||
//
|
||||
const [assets, setAssets] = useState<DDO[]>()
|
||||
const [assets, setAssets] = useState<Asset[]>()
|
||||
const [assetsTotal, setAssetsTotal] = useState(0)
|
||||
// const [assetsWithPrices, setAssetsWithPrices] = useState<AssetListPrices[]>()
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ async function getBlockHead(config: ConfigHelperConfig) {
|
|||
// for ETH main, get block from graph fetch
|
||||
if (config.network === 'mainnet') {
|
||||
const response: any = await fetchGraph(ethGraphUrl, ethGraphQuery)
|
||||
return Number(response?.data?.blocks[0].number)
|
||||
return Number(response?.data?.blocks[0]?.number)
|
||||
}
|
||||
|
||||
// for everything else, create new web3 instance with infura
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { DDO, Logger } from '@oceanprotocol/lib'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { useState } from 'react'
|
||||
import { TransactionReceipt } from 'web3-core'
|
||||
import { Decimal } from 'decimal.js'
|
||||
|
@ -14,17 +14,17 @@ import { useOcean } from '@context/Ocean'
|
|||
import { useWeb3 } from '@context/Web3'
|
||||
|
||||
interface UsePricing {
|
||||
getDTSymbol: (ddo: DDO) => Promise<string>
|
||||
getDTName: (ddo: DDO) => Promise<string>
|
||||
getDTSymbol: (ddo: Asset) => Promise<string>
|
||||
getDTName: (ddo: Asset) => Promise<string>
|
||||
createPricing: (
|
||||
priceOptions: PriceOptions,
|
||||
ddo: DDO
|
||||
ddo: Asset
|
||||
) => Promise<TransactionReceipt | string | void>
|
||||
mint: (tokensToMint: string, ddo: DDO) => Promise<TransactionReceipt | void>
|
||||
mint: (tokensToMint: string, ddo: Asset) => Promise<TransactionReceipt | void>
|
||||
buyDT: (
|
||||
dtAmount: number | string,
|
||||
amountDataToken: number | string,
|
||||
price: BestPrice,
|
||||
ddo: DDO
|
||||
ddo: Asset
|
||||
) => Promise<TransactionReceipt | void>
|
||||
pricingStep?: number
|
||||
pricingStepText?: string
|
||||
|
@ -40,28 +40,28 @@ function usePricing(): UsePricing {
|
|||
const [pricingStepText, setPricingStepText] = useState<string>()
|
||||
const [pricingError, setPricingError] = useState<string>()
|
||||
|
||||
async function getDTSymbol(ddo: DDO): Promise<string> {
|
||||
async function getDTSymbol(ddo: Asset): Promise<string> {
|
||||
if (!ocean || !accountId) return
|
||||
|
||||
const { dataToken, dataTokenInfo } = ddo
|
||||
const { dataTokenInfo } = ddo
|
||||
return dataTokenInfo
|
||||
? dataTokenInfo.symbol
|
||||
: await ocean?.datatokens.getSymbol(dataToken)
|
||||
: await ocean?.datatokens.getSymbol(dataTokenInfo.address)
|
||||
}
|
||||
|
||||
async function getDTName(ddo: DDO): Promise<string> {
|
||||
async function getDTName(ddo: Asset): Promise<string> {
|
||||
if (!ocean || !accountId) return
|
||||
const { dataToken, dataTokenInfo } = ddo
|
||||
const { dataTokenInfo } = ddo
|
||||
return dataTokenInfo
|
||||
? dataTokenInfo.name
|
||||
: await ocean?.datatokens.getName(dataToken)
|
||||
: await ocean?.datatokens.getName(dataTokenInfo.address)
|
||||
}
|
||||
|
||||
// Helper for setting steps & feedback for all flows
|
||||
async function setStep(
|
||||
index: number,
|
||||
type: 'pool' | 'exchange' | 'free' | 'buy' | 'dispense',
|
||||
ddo: DDO
|
||||
ddo: Asset
|
||||
) {
|
||||
const dtSymbol = await getDTSymbol(ddo)
|
||||
setPricingStep(index)
|
||||
|
@ -92,18 +92,18 @@ function usePricing(): UsePricing {
|
|||
|
||||
async function mint(
|
||||
tokensToMint: string,
|
||||
ddo: DDO
|
||||
ddo: Asset
|
||||
): Promise<TransactionReceipt | void> {
|
||||
const { dataToken } = ddo
|
||||
Logger.log('mint function', dataToken, accountId)
|
||||
const { dataTokenInfo } = ddo
|
||||
Logger.log('mint function', dataTokenInfo.address, accountId)
|
||||
const balance = new Decimal(
|
||||
await ocean.datatokens.balance(dataToken, accountId)
|
||||
await ocean.datatokens.balance(dataTokenInfo.address, accountId)
|
||||
)
|
||||
const tokens = new Decimal(tokensToMint)
|
||||
if (tokens.greaterThan(balance)) {
|
||||
const mintAmount = tokens.minus(balance)
|
||||
const tx = await ocean.datatokens.mint(
|
||||
dataToken,
|
||||
dataTokenInfo.address,
|
||||
accountId,
|
||||
mintAmount.toString()
|
||||
)
|
||||
|
@ -112,9 +112,9 @@ function usePricing(): UsePricing {
|
|||
}
|
||||
|
||||
async function buyDT(
|
||||
dtAmount: number | string,
|
||||
amountDataToken: number | string,
|
||||
price: BestPrice,
|
||||
ddo: DDO
|
||||
ddo: Asset
|
||||
): Promise<TransactionReceipt | void> {
|
||||
if (!ocean || !accountId) return
|
||||
|
||||
|
@ -129,7 +129,7 @@ function usePricing(): UsePricing {
|
|||
Decimal.set({ precision: 18 })
|
||||
|
||||
switch (price?.type) {
|
||||
case 'pool': {
|
||||
case 'dynamic': {
|
||||
const oceanAmmount = new Decimal(price.value).times(1.05).toString()
|
||||
const maxPrice = new Decimal(price.value).times(2).toString()
|
||||
|
||||
|
@ -144,7 +144,7 @@ function usePricing(): UsePricing {
|
|||
tx = await ocean.pool.buyDT(
|
||||
accountId,
|
||||
price.address,
|
||||
String(dtAmount),
|
||||
String(amountDataToken),
|
||||
oceanAmmount,
|
||||
maxPrice
|
||||
)
|
||||
|
@ -152,7 +152,7 @@ function usePricing(): UsePricing {
|
|||
Logger.log('DT buy response', tx)
|
||||
break
|
||||
}
|
||||
case 'exchange': {
|
||||
case 'fixed': {
|
||||
if (!config.oceanTokenAddress) {
|
||||
Logger.error(`'oceanTokenAddress' not set in config`)
|
||||
return
|
||||
|
@ -171,7 +171,7 @@ function usePricing(): UsePricing {
|
|||
setStep(2, 'buy', ddo)
|
||||
tx = await ocean.fixedRateExchange.buyDT(
|
||||
price.address,
|
||||
`${dtAmount}`,
|
||||
`${amountDataToken}`,
|
||||
accountId
|
||||
)
|
||||
setStep(3, 'buy', ddo)
|
||||
|
@ -181,18 +181,20 @@ function usePricing(): UsePricing {
|
|||
case 'free': {
|
||||
setStep(1, 'dispense', ddo)
|
||||
const isDispensable = await ocean.OceanDispenser.isDispensable(
|
||||
ddo.dataToken,
|
||||
ddo?.services[0].datatokenAddress,
|
||||
accountId,
|
||||
'1'
|
||||
)
|
||||
|
||||
if (!isDispensable) {
|
||||
Logger.error(`Dispenser for ${ddo.dataToken} failed to dispense`)
|
||||
Logger.error(
|
||||
`Dispenser for ${ddo?.services[0].datatokenAddress} failed to dispense`
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
tx = await ocean.OceanDispenser.dispense(
|
||||
ddo.dataToken,
|
||||
ddo?.services[0].datatokenAddress,
|
||||
accountId,
|
||||
'1'
|
||||
)
|
||||
|
@ -215,17 +217,17 @@ function usePricing(): UsePricing {
|
|||
|
||||
async function createPricing(
|
||||
priceOptions: PriceOptions,
|
||||
ddo: DDO
|
||||
ddo: Asset
|
||||
): Promise<TransactionReceipt | void> {
|
||||
const { dataToken } = ddo
|
||||
const dataToken = ddo?.services[0].datatokenAddress
|
||||
const dtSymbol = await getDTSymbol(ddo)
|
||||
|
||||
if (!ocean || !accountId || !dtSymbol) return
|
||||
|
||||
const { type, oceanAmount, price, weightOnDataToken, swapFee } =
|
||||
const { type, amountOcean, price, weightOnDataToken, swapFee } =
|
||||
priceOptions
|
||||
|
||||
let { dtAmount } = priceOptions
|
||||
let { amountDataToken } = priceOptions
|
||||
const isPool = type === 'dynamic'
|
||||
|
||||
if (!isPool && !config.fixedRateExchangeAddress) {
|
||||
|
@ -244,25 +246,25 @@ function usePricing(): UsePricing {
|
|||
await ocean.OceanDispenser.activate(dataToken, '1', '1', accountId)
|
||||
} else {
|
||||
// if fixedPrice set dt to max amount
|
||||
if (!isPool) dtAmount = 1000
|
||||
await mint(`${dtAmount}`, ddo)
|
||||
if (!isPool) amountDataToken = 1000
|
||||
await mint(`${amountDataToken}`, ddo)
|
||||
}
|
||||
|
||||
// dtAmount for fixed price is set to max
|
||||
// amountDataToken for fixed price is set to max
|
||||
const tx = isPool
|
||||
? await ocean.pool
|
||||
.create(
|
||||
accountId,
|
||||
dataToken,
|
||||
`${dtAmount}`,
|
||||
`${amountDataToken}`,
|
||||
weightOnDataToken,
|
||||
`${oceanAmount}`,
|
||||
`${amountOcean}`,
|
||||
`${swapFee}`
|
||||
)
|
||||
.next((step: number) => setStep(step, 'pool', ddo))
|
||||
: type === 'fixed'
|
||||
? await ocean.fixedRateExchange
|
||||
.create(dataToken, `${price}`, accountId, `${dtAmount}`)
|
||||
.create(dataToken, `${price}`, accountId, `${amountDataToken}`)
|
||||
.next((step: number) => setStep(step, 'exchange', ddo))
|
||||
: await ocean.OceanDispenser.makeMinter(dataToken, accountId).next(
|
||||
(step: number) => setStep(step, 'free', ddo)
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
import { DDO, Logger, Metadata } from '@oceanprotocol/lib'
|
||||
import {
|
||||
Service,
|
||||
ServiceComputePrivacy,
|
||||
ServiceType
|
||||
} from '@oceanprotocol/lib/dist/node/ddo/interfaces/Service'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { sleep } from '@utils/index'
|
||||
import { publishFeedback } from '@utils/feedback'
|
||||
import { useOcean } from '@context/Ocean'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { getOceanConfig } from '@utils/ocean'
|
||||
|
||||
export interface DataTokenOptions {
|
||||
cap?: string
|
||||
name?: string
|
||||
symbol?: string
|
||||
}
|
||||
|
||||
interface UsePublish {
|
||||
publish: (
|
||||
asset: Metadata,
|
||||
serviceType: ServiceType,
|
||||
dataTokenOptions?: DataTokenOptions,
|
||||
timeout?: number,
|
||||
providerUri?: string
|
||||
) => Promise<DDO | undefined | null>
|
||||
publishStep?: number
|
||||
publishStepText?: string
|
||||
publishError?: string
|
||||
isLoading: boolean
|
||||
}
|
||||
|
||||
function usePublish(): UsePublish {
|
||||
const { networkId, web3Loading } = useWeb3()
|
||||
const { connect, ocean, account } = useOcean()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [publishStep, setPublishStep] = useState<number | undefined>()
|
||||
const [publishStepText, setPublishStepText] = useState<string | undefined>()
|
||||
const [publishError, setPublishError] = useState<string | undefined>()
|
||||
|
||||
function setStep(index?: number) {
|
||||
setPublishStep(index)
|
||||
index && setPublishStepText(publishFeedback[index])
|
||||
}
|
||||
|
||||
//
|
||||
// Initiate OceanProvider based on user wallet
|
||||
//
|
||||
useEffect(() => {
|
||||
if (web3Loading || !connect) return
|
||||
|
||||
async function initOcean() {
|
||||
const config = getOceanConfig(networkId)
|
||||
await connect(config)
|
||||
}
|
||||
initOcean()
|
||||
}, [web3Loading, networkId, connect])
|
||||
|
||||
/**
|
||||
* Publish an asset. It also creates the datatoken, mints tokens and gives the market allowance
|
||||
* @param {Metadata} asset The metadata of the asset.
|
||||
* @param {PriceOptions} priceOptions : number of tokens to mint, datatoken weight , liquidity fee, type : fixed, dynamic
|
||||
* @param {ServiceType} serviceType Desired service type of the asset access or compute
|
||||
* @param {DataTokenOptions} dataTokenOptions custom name, symbol and cap for datatoken
|
||||
* @return {Promise<DDO>} Returns the newly published ddo
|
||||
*/
|
||||
async function publish(
|
||||
asset: Metadata,
|
||||
serviceType: ServiceType,
|
||||
dataTokenOptions?: DataTokenOptions,
|
||||
timeout?: number,
|
||||
providerUri?: string
|
||||
): Promise<DDO | undefined | null> {
|
||||
if (!ocean || !account) return null
|
||||
setIsLoading(true)
|
||||
setPublishError(undefined)
|
||||
setStep(0)
|
||||
|
||||
try {
|
||||
const publishedDate =
|
||||
new Date(Date.now()).toISOString().split('.')[0] + 'Z'
|
||||
const services: Service[] = []
|
||||
const price = '1'
|
||||
asset.main.dateCreated = asset.main.datePublished = publishedDate
|
||||
|
||||
switch (serviceType) {
|
||||
case 'access': {
|
||||
if (!timeout) timeout = 0
|
||||
const accessService =
|
||||
await ocean.assets.createAccessServiceAttributes(
|
||||
account,
|
||||
price,
|
||||
publishedDate,
|
||||
timeout,
|
||||
providerUri,
|
||||
null
|
||||
)
|
||||
Logger.log('access service created', accessService)
|
||||
services.push(accessService)
|
||||
break
|
||||
}
|
||||
case 'compute': {
|
||||
if (!timeout) timeout = 3600
|
||||
const provider = {}
|
||||
const origComputePrivacy: ServiceComputePrivacy = {
|
||||
allowRawAlgorithm: false,
|
||||
allowNetworkAccess: false,
|
||||
allowAllPublishedAlgorithms: false,
|
||||
publisherTrustedAlgorithms: []
|
||||
}
|
||||
const computeService = ocean.compute.createComputeService(
|
||||
account,
|
||||
price,
|
||||
publishedDate,
|
||||
provider,
|
||||
origComputePrivacy,
|
||||
timeout,
|
||||
providerUri
|
||||
)
|
||||
services.push(computeService)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Logger.log('services created', services)
|
||||
|
||||
const ddo = await ocean.assets
|
||||
.create(
|
||||
asset,
|
||||
account,
|
||||
services,
|
||||
undefined,
|
||||
dataTokenOptions?.cap,
|
||||
dataTokenOptions?.name,
|
||||
dataTokenOptions?.symbol,
|
||||
providerUri
|
||||
)
|
||||
.next(setStep)
|
||||
Logger.log('ddo created', ddo)
|
||||
await ocean.assets.publishDdo(ddo, account.getId())
|
||||
Logger.log('ddo published')
|
||||
await sleep(20000)
|
||||
setStep(7)
|
||||
return ddo
|
||||
} catch (error) {
|
||||
setPublishError(error.message)
|
||||
Logger.error(error)
|
||||
setStep()
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
publish,
|
||||
publishStep,
|
||||
publishStepText,
|
||||
isLoading,
|
||||
publishError
|
||||
}
|
||||
}
|
||||
|
||||
export { usePublish }
|
||||
export default usePublish
|
3
src/@images/algorithm.svg
Normal file
3
src/@images/algorithm.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="15" height="9" viewBox="0 0 15 9" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M1.56946 0.51192C1.29989 0.197426 0.826414 0.161005 0.51192 0.430571C0.197426 0.700138 0.161005 1.17361 0.430571 1.48811L3.01221 4.50001L0.430571 7.51192C0.161005 7.82641 0.197426 8.29989 0.51192 8.56946C0.826414 8.83902 1.29989 8.8026 1.56946 8.48811L4.56946 4.98811C4.8102 4.70724 4.8102 4.29279 4.56946 4.01192L1.56946 0.51192ZM9.00001 4.25001C8.5858 4.25001 8.25001 4.5858 8.25001 5.00001C8.25001 5.41423 8.5858 5.75001 9.00001 5.75001H14C14.4142 5.75001 14.75 5.41423 14.75 5.00001C14.75 4.5858 14.4142 4.25001 14 4.25001H9.00001Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 678 B |
3
src/@images/dataset.svg
Normal file
3
src/@images/dataset.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="15" height="12" viewBox="0 0 15 12" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.84593 0.356445H0V3.21453H2.84593V0.356445ZM2.84593 4.64355H0V7.50164H2.84593V4.64355ZM0 9H2.84593V11.8581H0V9ZM8.5377 0.356445H5.69177V3.21453H8.5377V0.356445ZM5.69177 4.64355H8.5377V7.50164H5.69177V4.64355ZM8.5377 9H5.69177V11.8581H8.5377V9ZM11.3837 0.356445H14.2296V3.21453H11.3837V0.356445ZM14.2296 4.64355H11.3837V7.50164H14.2296V4.64355ZM11.3837 9H14.2296V11.8581H11.3837V9Z" />
|
||||
</svg>
|
After Width: | Height: | Size: 527 B |
33
src/@types/Asset.d.ts
vendored
Normal file
33
src/@types/Asset.d.ts
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
interface AssetNft {
|
||||
address: string
|
||||
name: string
|
||||
symbol: string
|
||||
owner: string
|
||||
state: 0 | 1 | 2 | 3 | 4
|
||||
}
|
||||
|
||||
interface AssetDatatoken {
|
||||
name: string
|
||||
symbol: string
|
||||
address: string
|
||||
serviceId: string
|
||||
}
|
||||
|
||||
interface AssetLastEvent {
|
||||
tx: string
|
||||
block: number
|
||||
from: string
|
||||
contract: string
|
||||
}
|
||||
|
||||
interface Asset extends DDO {
|
||||
nft: AssetNft
|
||||
datatokens: AssetDatatoken[]
|
||||
event: AssetLastEvent
|
||||
stats: { consume: number }
|
||||
isInPurgatory: string
|
||||
|
||||
// This is fake and most likely won't be used like this.
|
||||
// Just here so we can continue to have successful builds.
|
||||
dataTokenInfo: AssetDatatoken
|
||||
}
|
9
src/@types/DDO/Credentials.d.ts
vendored
Normal file
9
src/@types/DDO/Credentials.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
interface Credential {
|
||||
type: string
|
||||
values: string[]
|
||||
}
|
||||
|
||||
interface Credentials {
|
||||
allow: Credential[]
|
||||
deny: Credential[]
|
||||
}
|
28
src/@types/DDO/Metadata.d.ts
vendored
Normal file
28
src/@types/DDO/Metadata.d.ts
vendored
Normal file
|
@ -0,0 +1,28 @@
|
|||
interface MetadataAlgorithmContainer {
|
||||
entrypoint: string
|
||||
image: string
|
||||
tag: string
|
||||
checksum: string
|
||||
}
|
||||
|
||||
interface MetadataAlgorithm {
|
||||
language?: string
|
||||
version?: string
|
||||
container: MetadataAlgorithmContainer
|
||||
}
|
||||
|
||||
interface Metadata {
|
||||
created: string
|
||||
updated: string
|
||||
name: string
|
||||
description: string
|
||||
type: 'dataset' | 'algorithm' | string
|
||||
author: string
|
||||
license: string
|
||||
links?: string[]
|
||||
tags?: string[]
|
||||
copyrightHolder?: string
|
||||
contentLanguage?: string
|
||||
algorithm?: MetadataAlgorithm
|
||||
additionalInformation?: any
|
||||
}
|
29
src/@types/DDO/Services.d.ts
vendored
Normal file
29
src/@types/DDO/Services.d.ts
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
interface PublisherTrustedAlgorithm {
|
||||
did: string
|
||||
filesChecksum: string
|
||||
containerSectionChecksum: string
|
||||
}
|
||||
|
||||
interface ServiceComputeOptions {
|
||||
namespace: string
|
||||
cpu?: number
|
||||
gpu?: number
|
||||
gpuType?: string
|
||||
memory?: string
|
||||
volumeSize?: string
|
||||
allowRawAlgorithm: boolean
|
||||
allowNetworkAccess: boolean
|
||||
publisherTrustedAlgorithmPublishers: string[]
|
||||
publisherTrustedAlgorithms: PublisherTrustedAlgorithm[]
|
||||
}
|
||||
|
||||
interface Service {
|
||||
type: 'access' | 'compute' | string
|
||||
files: string
|
||||
datatokenAddress: string
|
||||
serviceEndpoint: string
|
||||
timeout: number
|
||||
name?: string
|
||||
description?: string
|
||||
compute?: ServiceComputeOptions
|
||||
}
|
10
src/@types/DDO/index.d.ts
vendored
Normal file
10
src/@types/DDO/index.d.ts
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
// DDO spec
|
||||
interface DDO {
|
||||
'@context': string[]
|
||||
id: string
|
||||
version: string
|
||||
chainId: number
|
||||
metadata: Metadata
|
||||
services: Service[]
|
||||
credentials?: Credentials
|
||||
}
|
51
src/@types/Form.d.ts
vendored
51
src/@types/Form.d.ts
vendored
|
@ -1,30 +1,23 @@
|
|||
import { AssetSelectionAsset } from '@shared/Form/FormFields/AssetSelection'
|
||||
|
||||
// declaring into global scope to be able to use this as
|
||||
// ambiant types despite the above imports
|
||||
declare global {
|
||||
interface FormFieldProps {
|
||||
label: string
|
||||
name: string
|
||||
type?: string
|
||||
options?: string[] | AssetSelectionAsset[]
|
||||
sortOptions?: boolean
|
||||
required?: boolean
|
||||
multiple?: boolean
|
||||
disabled?: boolean
|
||||
help?: string
|
||||
placeholder?: string
|
||||
pattern?: string
|
||||
min?: string
|
||||
disclaimer?: string
|
||||
disclaimerValues?: string[]
|
||||
advanced?: boolean
|
||||
}
|
||||
|
||||
interface FormContent {
|
||||
title: string
|
||||
description?: string
|
||||
success: string
|
||||
data: FormFieldProps[]
|
||||
}
|
||||
interface FormFieldContent {
|
||||
label: string
|
||||
name: string
|
||||
type?: string
|
||||
options?: string[]
|
||||
sortOptions?: boolean
|
||||
required?: boolean
|
||||
multiple?: boolean
|
||||
disabled?: boolean
|
||||
help?: string
|
||||
placeholder?: string
|
||||
pattern?: string
|
||||
min?: string
|
||||
disclaimer?: string
|
||||
disclaimerValues?: string[]
|
||||
advanced?: boolean
|
||||
}
|
||||
|
||||
interface FormStepContent {
|
||||
title: string
|
||||
description?: string
|
||||
fields: FormFieldContent[]
|
||||
}
|
||||
|
|
8
src/@types/Price.d.ts
vendored
8
src/@types/Price.d.ts
vendored
|
@ -1,5 +1,5 @@
|
|||
interface BestPrice {
|
||||
type: 'pool' | 'exchange' | 'free' | ''
|
||||
type: 'dynamic' | 'fixed' | 'free' | ''
|
||||
address: string
|
||||
value: number
|
||||
isConsumable?: 'true' | 'false' | ''
|
||||
|
@ -13,9 +13,9 @@ interface BestPrice {
|
|||
|
||||
interface PriceOptions {
|
||||
price: number
|
||||
dtAmount: number
|
||||
oceanAmount: number
|
||||
type: 'fixed' | 'dynamic' | 'free' | string
|
||||
amountDataToken: number
|
||||
amountOcean: number
|
||||
type: 'dynamic' | 'fixed' | 'free' | ''
|
||||
weightOnDataToken: string
|
||||
weightOnOcean: string
|
||||
// easier to keep this as number for Yup input validation
|
||||
|
|
16
src/@types/aquarius/DownloadedAsset.d.ts
vendored
16
src/@types/aquarius/DownloadedAsset.d.ts
vendored
|
@ -1,12 +1,6 @@
|
|||
import { DDO } from '@oceanprotocol/lib'
|
||||
|
||||
// declaring into global scope to be able to use this as
|
||||
// ambiant types despite the above imports
|
||||
declare global {
|
||||
interface DownloadedAsset {
|
||||
dtSymbol: string
|
||||
timestamp: number
|
||||
networkId: number
|
||||
ddo: DDO
|
||||
}
|
||||
interface DownloadedAsset {
|
||||
dtSymbol: string
|
||||
timestamp: number
|
||||
networkId: number
|
||||
ddo: Asset
|
||||
}
|
||||
|
|
29
src/@types/aquarius/MetaData.d.ts
vendored
29
src/@types/aquarius/MetaData.d.ts
vendored
|
@ -1,29 +0,0 @@
|
|||
import {
|
||||
Metadata,
|
||||
AdditionalInformation,
|
||||
EditableMetadataLinks
|
||||
} from '@oceanprotocol/lib'
|
||||
|
||||
// declaring into global scope to be able to use this as
|
||||
// ambiant types despite the above imports
|
||||
declare global {
|
||||
interface AdditionalInformationMarket extends AdditionalInformation {
|
||||
termsAndConditions: boolean
|
||||
}
|
||||
|
||||
interface MetadataMarket extends Metadata {
|
||||
// While required for this market, Aquarius/Plecos will keep this as optional
|
||||
// allowing external pushes of assets without `additionalInformation`.
|
||||
// Making it optional here helps safeguarding against those assets.
|
||||
additionalInformation?: AdditionalInformationMarket
|
||||
}
|
||||
|
||||
interface MetadataEditForm {
|
||||
name: string
|
||||
description: string
|
||||
timeout: string
|
||||
price?: number
|
||||
links?: string | EditableMetadataLinks[]
|
||||
author?: string
|
||||
}
|
||||
}
|
16
src/@types/aquarius/PagedAssets.d.ts
vendored
16
src/@types/aquarius/PagedAssets.d.ts
vendored
|
@ -1,12 +1,6 @@
|
|||
import { DDO } from '@oceanprotocol/lib'
|
||||
|
||||
// declaring into global scope to be able to use this as
|
||||
// ambiant types despite the above imports
|
||||
declare global {
|
||||
interface PagedAssets {
|
||||
results: DDO[]
|
||||
page: number
|
||||
totalPages: number
|
||||
totalResults: number
|
||||
}
|
||||
interface PagedAssets {
|
||||
results: Asset[]
|
||||
page: number
|
||||
totalPages: number
|
||||
totalResults: number
|
||||
}
|
||||
|
|
79
src/@types/aquarius/SearchResponse.d.ts
vendored
79
src/@types/aquarius/SearchResponse.d.ts
vendored
|
@ -1,46 +1,41 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable camelcase */
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
|
||||
// declaring into global scope to be able to use this as
|
||||
// ambiant types despite the above imports
|
||||
declare global {
|
||||
interface Explanation {
|
||||
value: number
|
||||
description: string
|
||||
details: Explanation[]
|
||||
}
|
||||
|
||||
interface ShardsResponse {
|
||||
total: number
|
||||
successful: number
|
||||
failed: number
|
||||
skipped: number
|
||||
}
|
||||
|
||||
interface SearchResponse {
|
||||
took: number
|
||||
timed_out: boolean
|
||||
_scroll_id?: string | undefined
|
||||
_shards: ShardsResponse
|
||||
hits: {
|
||||
total: number
|
||||
max_score: number
|
||||
hits: Array<{
|
||||
_index: string
|
||||
_type: string
|
||||
_id: string
|
||||
_score: number
|
||||
_source: DDO
|
||||
_version?: number | undefined
|
||||
_explanation?: Explanation | undefined
|
||||
fields?: any
|
||||
highlight?: any
|
||||
inner_hits?: any
|
||||
matched_queries?: string[] | undefined
|
||||
sort?: string[] | undefined
|
||||
}>
|
||||
}
|
||||
aggregations?: any
|
||||
}
|
||||
interface Explanation {
|
||||
value: number
|
||||
description: string
|
||||
details: Explanation[]
|
||||
}
|
||||
|
||||
interface ShardsResponse {
|
||||
total: number
|
||||
successful: number
|
||||
failed: number
|
||||
skipped: number
|
||||
}
|
||||
|
||||
interface SearchResponse {
|
||||
took: number
|
||||
timed_out: boolean
|
||||
_scroll_id?: string | undefined
|
||||
_shards: ShardsResponse
|
||||
hits: {
|
||||
total: number
|
||||
max_score: number
|
||||
hits: Array<{
|
||||
_index: string
|
||||
_type: string
|
||||
_id: string
|
||||
_score: number
|
||||
_source: Asset
|
||||
_version?: number | undefined
|
||||
_explanation?: Explanation | undefined
|
||||
fields?: any
|
||||
highlight?: any
|
||||
inner_hits?: any
|
||||
matched_queries?: string[] | undefined
|
||||
sort?: string[] | undefined
|
||||
}>
|
||||
}
|
||||
aggregations?: any
|
||||
}
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
import {
|
||||
DDO,
|
||||
DID,
|
||||
Logger,
|
||||
publisherTrustedAlgorithm as PublisherTrustedAlgorithm
|
||||
} from '@oceanprotocol/lib'
|
||||
import { AssetSelectionAsset } from '@shared/Form/FormFields/AssetSelection'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
||||
import { PriceList, getAssetsPriceList } from './subgraph'
|
||||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||
import { OrdersData_tokenOrders as OrdersData } from '../@types/apollo/OrdersData'
|
||||
|
@ -13,6 +8,7 @@ import {
|
|||
SortDirectionOptions,
|
||||
SortTermOptions
|
||||
} from '../@types/aquarius/SearchQuery'
|
||||
import { getServiceByName } from './ddo'
|
||||
|
||||
export const MAXIMUM_NUMBER_OF_PAGES_WITH_RESULTS = 476
|
||||
|
||||
|
@ -77,7 +73,7 @@ export function transformQueryResult(
|
|||
}
|
||||
|
||||
result.results = (queryResult.hits.hits || []).map(
|
||||
(hit) => new DDO(hit._source as DDO)
|
||||
(hit) => hit._source as Asset
|
||||
)
|
||||
result.totalResults = queryResult.hits.total
|
||||
result.totalPages =
|
||||
|
@ -111,18 +107,18 @@ export async function queryMetadata(
|
|||
}
|
||||
|
||||
export async function retrieveDDO(
|
||||
did: string | DID,
|
||||
did: string,
|
||||
cancelToken: CancelToken
|
||||
): Promise<DDO> {
|
||||
): Promise<Asset> {
|
||||
try {
|
||||
const response: AxiosResponse<DDO> = await axios.get(
|
||||
const response: AxiosResponse<Asset> = await axios.get(
|
||||
`${metadataCacheUri}/api/v1/aquarius/assets/ddo/${did}`,
|
||||
{ cancelToken }
|
||||
)
|
||||
if (!response || response.status !== 200 || !response.data) return
|
||||
|
||||
const data = { ...response.data }
|
||||
return new DDO(data)
|
||||
return data
|
||||
} catch (error) {
|
||||
if (axios.isCancel(error)) {
|
||||
Logger.log(error.message)
|
||||
|
@ -133,7 +129,7 @@ export async function retrieveDDO(
|
|||
}
|
||||
|
||||
export async function getAssetsNames(
|
||||
didList: string[] | DID[],
|
||||
didList: string[],
|
||||
cancelToken: CancelToken
|
||||
): Promise<Record<string, string>> {
|
||||
try {
|
||||
|
@ -179,10 +175,10 @@ export async function retrieveDDOListByDIDs(
|
|||
didList: string[],
|
||||
chainIds: number[],
|
||||
cancelToken: CancelToken
|
||||
): Promise<DDO[]> {
|
||||
): Promise<Asset[]> {
|
||||
try {
|
||||
if (didList?.length === 0 || chainIds?.length === 0) return []
|
||||
const orderedDDOListByDIDList: DDO[] = []
|
||||
const orderedDDOListByDIDList: Asset[] = []
|
||||
const baseQueryparams = {
|
||||
chainIds,
|
||||
filters: [getFilterTerm('id', didList)],
|
||||
|
@ -190,8 +186,8 @@ export async function retrieveDDOListByDIDs(
|
|||
} as BaseQueryParams
|
||||
const query = generateBaseQuery(baseQueryparams)
|
||||
const result = await queryMetadata(query, cancelToken)
|
||||
didList.forEach((did: string | DID) => {
|
||||
const ddo: DDO = result.results.find((ddo: DDO) => ddo.id === did)
|
||||
didList.forEach((did: string) => {
|
||||
const ddo = result.results.find((ddo: Asset) => ddo.id === did)
|
||||
orderedDDOListByDIDList.push(ddo)
|
||||
})
|
||||
return orderedDDOListByDIDList
|
||||
|
@ -202,7 +198,7 @@ export async function retrieveDDOListByDIDs(
|
|||
|
||||
export async function transformDDOToAssetSelection(
|
||||
datasetProviderEndpoint: string,
|
||||
ddoList: DDO[],
|
||||
ddoList: Asset[],
|
||||
selectedAlgorithms?: PublisherTrustedAlgorithm[],
|
||||
cancelToken?: CancelToken
|
||||
): Promise<AssetSelectionAsset[]> {
|
||||
|
@ -213,7 +209,7 @@ export async function transformDDOToAssetSelection(
|
|||
for (const ddo of ddoList) {
|
||||
didList.push(ddo.id)
|
||||
symbolList[ddo.id] = ddo.dataTokenInfo.symbol
|
||||
const algoComputeService = ddo.findServiceByType('compute')
|
||||
const algoComputeService = getServiceByName(ddo, 'compute')
|
||||
algoComputeService?.serviceEndpoint &&
|
||||
(didProviderEndpointMap[ddo.id] = algoComputeService?.serviceEndpoint)
|
||||
}
|
||||
|
@ -350,7 +346,8 @@ export async function getDownloadAssets(
|
|||
.map((ddo) => {
|
||||
const order = tokenOrders.find(
|
||||
({ datatokenId }) =>
|
||||
datatokenId?.address.toLowerCase() === ddo.dataToken.toLowerCase()
|
||||
datatokenId?.address.toLowerCase() ===
|
||||
ddo.services[0].datatokenAddress.toLowerCase()
|
||||
)
|
||||
|
||||
return {
|
||||
|
|
|
@ -1,21 +1,23 @@
|
|||
import { DDO, Ocean, ServiceType } from '@oceanprotocol/lib'
|
||||
import { Ocean } from '@oceanprotocol/lib'
|
||||
import { getServiceByName } from './ddo'
|
||||
|
||||
export default async function checkPreviousOrder(
|
||||
ocean: Ocean,
|
||||
accountId: string,
|
||||
ddo: DDO,
|
||||
serviceType: ServiceType
|
||||
ddo: Asset,
|
||||
serviceType: 'access' | 'compute'
|
||||
): Promise<string> {
|
||||
if (!ocean) return
|
||||
|
||||
const service = ddo.findServiceByType(serviceType)
|
||||
const service = getServiceByName(ddo, serviceType)
|
||||
// apparenlty cost and timeout are not found, even though they are there...
|
||||
const previousOrder = await ocean.datatokens.getPreviousValidOrders(
|
||||
ddo.dataToken,
|
||||
(service.attributes.main as any).cost,
|
||||
service.index,
|
||||
(service.attributes.main as any).timeout,
|
||||
accountId
|
||||
)
|
||||
return previousOrder
|
||||
// const previousOrder = await ocean.datatokens.getPreviousValidOrders(
|
||||
// ddo?.services[0].datatokenAddress,
|
||||
// service.cost,
|
||||
// service.index,
|
||||
// service.timeout,
|
||||
// accountId
|
||||
// )
|
||||
// return previousOrder
|
||||
return 'dummy'
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
ServiceComputePrivacy,
|
||||
publisherTrustedAlgorithm as PublisherTrustedAlgorithm,
|
||||
DDO,
|
||||
Service,
|
||||
Logger,
|
||||
Provider,
|
||||
|
@ -59,7 +58,7 @@ async function getAssetMetadata(
|
|||
queryDtList: string[],
|
||||
cancelToken: CancelToken,
|
||||
chainIds: number[]
|
||||
): Promise<DDO[]> {
|
||||
): Promise<Asset[]> {
|
||||
const baseQueryparams = {
|
||||
chainIds,
|
||||
filters: [
|
||||
|
@ -75,35 +74,37 @@ async function getAssetMetadata(
|
|||
return result.results
|
||||
}
|
||||
|
||||
function getServiceEndpoints(data: TokenOrder[], assets: DDO[]): string[] {
|
||||
const serviceEndpoints: string[] = []
|
||||
function getServiceEndpoints(data: TokenOrder[], assets: Asset[]): string[] {
|
||||
// const serviceEndpoints: string[] = []
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
try {
|
||||
const did = web3.utils
|
||||
.toChecksumAddress(data[i].datatokenId.address)
|
||||
.replace('0x', 'did:op:')
|
||||
const ddo = assets.filter((x) => x.id === did)[0]
|
||||
if (ddo === undefined) continue
|
||||
// for (let i = 0; i < data.length; i++) {
|
||||
// try {
|
||||
// const did = web3.utils
|
||||
// .toChecksumAddress(data[i].datatokenId.address)
|
||||
// .replace('0x', 'did:op:')
|
||||
// const ddo = assets.filter((x) => x.id === did)[0]
|
||||
// if (ddo === undefined) continue
|
||||
|
||||
const service = ddo.service.filter(
|
||||
(x: Service) => x.index === data[i].serviceId
|
||||
)[0]
|
||||
// const service = ddo.services.filter(
|
||||
// (x: Service) => x.index === data[i].serviceId
|
||||
// )[0]
|
||||
|
||||
if (!service || service.type !== 'compute') continue
|
||||
const { serviceEndpoint } = service
|
||||
// if (!service || service.type !== 'compute') continue
|
||||
// const { providerEndpoint } = service
|
||||
|
||||
const wasProviderQueried =
|
||||
serviceEndpoints?.filter((x) => x === serviceEndpoint).length > 0
|
||||
// const wasProviderQueried =
|
||||
// serviceEndpoints?.filter((x) => x === providerEndpoint).length > 0
|
||||
|
||||
if (wasProviderQueried) continue
|
||||
serviceEndpoints.push(serviceEndpoint)
|
||||
} catch (err) {
|
||||
Logger.error(err.message)
|
||||
}
|
||||
}
|
||||
// if (wasProviderQueried) continue
|
||||
// serviceEndpoints.push(providerEndpoint)
|
||||
// } catch (err) {
|
||||
// Logger.error(err.message)
|
||||
// }
|
||||
// }
|
||||
|
||||
return serviceEndpoints
|
||||
// return serviceEndpoints
|
||||
|
||||
return ['dummy']
|
||||
}
|
||||
|
||||
async function getProviders(
|
||||
|
@ -138,7 +139,7 @@ async function getProviders(
|
|||
async function getJobs(
|
||||
providers: Provider[],
|
||||
account: Account,
|
||||
assets: DDO[]
|
||||
assets: Asset[]
|
||||
): Promise<ComputeJobMetaData[]> {
|
||||
const computeJobs: ComputeJobMetaData[] = []
|
||||
|
||||
|
@ -170,13 +171,10 @@ async function getJobs(
|
|||
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,
|
||||
assetName: ddo.metadata.name,
|
||||
assetDtSymbol: ddo.dataTokenInfo.symbol,
|
||||
networkId: ddo.chainId
|
||||
}
|
||||
|
@ -205,7 +203,7 @@ export async function getComputeJobs(
|
|||
config: Config,
|
||||
ocean: Ocean,
|
||||
account: Account,
|
||||
ddo?: DDO,
|
||||
ddo?: Asset,
|
||||
token?: CancelToken
|
||||
): Promise<ComputeResults> {
|
||||
const assetDTAddress = ddo?.dataTokenInfo?.address
|
||||
|
|
38
src/@utils/datatokens/index.ts
Normal file
38
src/@utils/datatokens/index.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import wordListDefault from './words.json'
|
||||
|
||||
export interface DataTokenOptions {
|
||||
cap?: string
|
||||
name?: string
|
||||
symbol?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate new datatoken name & symbol from a word list
|
||||
* @return {<{ name: String; symbol: String }>} datatoken name & symbol. Produces e.g. "Endemic Jellyfish Token" & "ENDJEL-45"
|
||||
*/
|
||||
export function generateDatatokenName(wordList?: {
|
||||
nouns: string[]
|
||||
adjectives: string[]
|
||||
}): {
|
||||
name: string
|
||||
symbol: string
|
||||
} {
|
||||
const list = wordList || wordListDefault
|
||||
const random1 = Math.floor(Math.random() * list.adjectives.length)
|
||||
const random2 = Math.floor(Math.random() * list.nouns.length)
|
||||
const indexNumber = Math.floor(Math.random() * 100)
|
||||
|
||||
// Capitalized adjective & noun
|
||||
const adjective = list.adjectives[random1].replace(/^\w/, (c) =>
|
||||
c.toUpperCase()
|
||||
)
|
||||
const noun = list.nouns[random2].replace(/^\w/, (c) => c.toUpperCase())
|
||||
|
||||
const name = `${adjective} ${noun} Token`
|
||||
// use first 3 letters of name, uppercase it, and add random number
|
||||
const symbol = `${(
|
||||
adjective.substring(0, 3) + noun.substring(0, 3)
|
||||
).toUpperCase()}-${indexNumber}`
|
||||
|
||||
return { name, symbol }
|
||||
}
|
204
src/@utils/datatokens/words.json
Normal file
204
src/@utils/datatokens/words.json
Normal file
|
@ -0,0 +1,204 @@
|
|||
{
|
||||
"nouns": [
|
||||
"Crab",
|
||||
"Fish",
|
||||
"Seal",
|
||||
"Octopus",
|
||||
"Shark",
|
||||
"Seahorse",
|
||||
"Walrus",
|
||||
"Starfish",
|
||||
"Whale",
|
||||
"Orca",
|
||||
"Penguin",
|
||||
"Jellyfish",
|
||||
"Squid",
|
||||
"Lobster",
|
||||
"Pelican",
|
||||
"Shrimp",
|
||||
"Oyster",
|
||||
"Clam",
|
||||
"Seagull",
|
||||
"Dolphin",
|
||||
"Shell",
|
||||
"Cormorant",
|
||||
"Otter",
|
||||
"Anemone",
|
||||
"Turtle",
|
||||
"Coral",
|
||||
"Ray",
|
||||
"Barracuda",
|
||||
"Krill",
|
||||
"Anchovy",
|
||||
"Angelfish",
|
||||
"Barnacle",
|
||||
"Clownfish",
|
||||
"Cod",
|
||||
"Cuttlefish",
|
||||
"Eel",
|
||||
"Fugu",
|
||||
"Herring",
|
||||
"Haddock",
|
||||
"Ling",
|
||||
"Mackerel",
|
||||
"Manatee",
|
||||
"Narwhal",
|
||||
"Nautilus",
|
||||
"Plankton",
|
||||
"Porpoise",
|
||||
"Prawn",
|
||||
"Pufferfish",
|
||||
"Swordfish",
|
||||
"Tuna"
|
||||
],
|
||||
"adjectives": [
|
||||
"adamant",
|
||||
"adroit",
|
||||
"amatory",
|
||||
"ambitious",
|
||||
"amused",
|
||||
"animistic",
|
||||
"antic",
|
||||
"arcadian",
|
||||
"artistic",
|
||||
"astonishing",
|
||||
"astounding",
|
||||
"baleful",
|
||||
"bellicose",
|
||||
"bilious",
|
||||
"blissful",
|
||||
"boorish",
|
||||
"brave",
|
||||
"breathtaking",
|
||||
"brilliant",
|
||||
"calamitous",
|
||||
"caustic",
|
||||
"cerulean",
|
||||
"clever",
|
||||
"charming",
|
||||
"comely",
|
||||
"competent",
|
||||
"concomitant",
|
||||
"confident",
|
||||
"contumacious",
|
||||
"corpulent",
|
||||
"crapulous",
|
||||
"creative",
|
||||
"dazzling",
|
||||
"dedicated",
|
||||
"defamatory",
|
||||
"delighted",
|
||||
"delightful",
|
||||
"determined",
|
||||
"didactic",
|
||||
"dilatory",
|
||||
"dowdy",
|
||||
"efficacious",
|
||||
"effulgent",
|
||||
"egregious",
|
||||
"empowered",
|
||||
"endemic",
|
||||
"enthusiastic",
|
||||
"equanimous",
|
||||
"exceptional",
|
||||
"execrable",
|
||||
"fabulous",
|
||||
"fantastic",
|
||||
"fastidious",
|
||||
"feckless",
|
||||
"fecund",
|
||||
"friable",
|
||||
"fulsome",
|
||||
"garrulous",
|
||||
"generous",
|
||||
"gentle",
|
||||
"guileless",
|
||||
"gustatory",
|
||||
"heuristic",
|
||||
"histrionic",
|
||||
"hubristic",
|
||||
"incendiary",
|
||||
"incredible",
|
||||
"insidious",
|
||||
"insolent",
|
||||
"inspired",
|
||||
"intransigent",
|
||||
"inveterate",
|
||||
"invidious",
|
||||
"invigorated",
|
||||
"irksome",
|
||||
"jejune",
|
||||
"juicy",
|
||||
"jocular",
|
||||
"joyful",
|
||||
"judicious",
|
||||
"kind",
|
||||
"lachrymose",
|
||||
"limpid",
|
||||
"loquacious",
|
||||
"lovely",
|
||||
"luminous",
|
||||
"mannered",
|
||||
"marvelous",
|
||||
"mendacious",
|
||||
"meretricious",
|
||||
"minatory",
|
||||
"mordant",
|
||||
"motivated",
|
||||
"munificent",
|
||||
"nefarious",
|
||||
"noxious",
|
||||
"obtuse",
|
||||
"optimistic",
|
||||
"parsimonious",
|
||||
"pendulous",
|
||||
"pernicious",
|
||||
"pervasive",
|
||||
"petulant",
|
||||
"passionate",
|
||||
"phenomenal",
|
||||
"platitudinous",
|
||||
"pleasant",
|
||||
"powerful",
|
||||
"precipitate",
|
||||
"propitious",
|
||||
"puckish",
|
||||
"querulous",
|
||||
"quiescent",
|
||||
"rebarbative",
|
||||
"recalcitant",
|
||||
"redolent",
|
||||
"rhadamanthine",
|
||||
"risible",
|
||||
"ruminative",
|
||||
"sagacious",
|
||||
"salubrious",
|
||||
"sartorial",
|
||||
"sclerotic",
|
||||
"serpentine",
|
||||
"smart",
|
||||
"spasmodic",
|
||||
"strident",
|
||||
"stunning",
|
||||
"stupendous",
|
||||
"taciturn",
|
||||
"tactful",
|
||||
"tasty",
|
||||
"tenacious",
|
||||
"tremendous",
|
||||
"tremulous",
|
||||
"trenchant",
|
||||
"turbulent",
|
||||
"turgid",
|
||||
"ubiquitous",
|
||||
"uxorious",
|
||||
"verdant",
|
||||
"vibrant",
|
||||
"voluble",
|
||||
"voracious",
|
||||
"wheedling",
|
||||
"withering",
|
||||
"wonderful",
|
||||
"zealous"
|
||||
]
|
||||
}
|
58
src/@utils/ddo.ts
Normal file
58
src/@utils/ddo.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
export function getServiceByName(
|
||||
ddo: Asset | DDO,
|
||||
name: 'access' | 'compute'
|
||||
): Service {
|
||||
if (!ddo) return
|
||||
|
||||
const service = ddo.services.filter((service) => service.type === name)[0]
|
||||
return service
|
||||
}
|
||||
|
||||
export function mapTimeoutStringToSeconds(timeout: string): number {
|
||||
switch (timeout) {
|
||||
case 'Forever':
|
||||
return 0
|
||||
case '1 day':
|
||||
return 86400
|
||||
case '1 week':
|
||||
return 604800
|
||||
case '1 month':
|
||||
return 2630000
|
||||
case '1 year':
|
||||
return 31556952
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function numberEnding(number: number): string {
|
||||
return number > 1 ? 's' : ''
|
||||
}
|
||||
|
||||
export function secondsToString(numberOfSeconds: number): string {
|
||||
if (numberOfSeconds === 0) return 'Forever'
|
||||
|
||||
const years = Math.floor(numberOfSeconds / 31536000)
|
||||
const months = Math.floor((numberOfSeconds %= 31536000) / 2630000)
|
||||
const weeks = Math.floor((numberOfSeconds %= 31536000) / 604800)
|
||||
const days = Math.floor((numberOfSeconds %= 604800) / 86400)
|
||||
const hours = Math.floor((numberOfSeconds %= 86400) / 3600)
|
||||
const minutes = Math.floor((numberOfSeconds %= 3600) / 60)
|
||||
const seconds = numberOfSeconds % 60
|
||||
|
||||
return years
|
||||
? `${years} year${numberEnding(years)}`
|
||||
: months
|
||||
? `${months} month${numberEnding(months)}`
|
||||
: weeks
|
||||
? `${weeks} week${numberEnding(weeks)}`
|
||||
: days
|
||||
? `${days} day${numberEnding(days)}`
|
||||
: hours
|
||||
? `${hours} hour${numberEnding(hours)}`
|
||||
: minutes
|
||||
? `${minutes} minute${numberEnding(minutes)}`
|
||||
: seconds
|
||||
? `${seconds} second${numberEnding(seconds)}`
|
||||
: 'less than a second'
|
||||
}
|
63
src/@utils/docker.ts
Normal file
63
src/@utils/docker.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { Logger } from '@oceanprotocol/lib'
|
||||
import axios from 'axios'
|
||||
import isUrl from 'is-url-superb'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
async function isDockerHubImageValid(
|
||||
image: string,
|
||||
tag: string
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`https://dockerhub-proxy.oceanprotocol.com`,
|
||||
{ image, tag }
|
||||
)
|
||||
if (
|
||||
!response ||
|
||||
response.status !== 200 ||
|
||||
response.data.status !== 'success'
|
||||
) {
|
||||
toast.error(
|
||||
'Could not fetch docker hub image info. Please check image name and tag and try again'
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
toast.error(
|
||||
'Could not fetch docker hub image info. Please check image name and tag and try again'
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async function is3rdPartyImageValid(imageURL: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await axios.head(imageURL)
|
||||
if (!response || response.status !== 200) {
|
||||
toast.error(
|
||||
'Could not fetch docker image info. Please check URL and try again'
|
||||
)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
toast.error(
|
||||
'Could not fetch docker image info. Please check URL and try again'
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export async function validateDockerImage(
|
||||
dockerImage: string,
|
||||
tag: string
|
||||
): Promise<boolean> {
|
||||
const isValid = isUrl(dockerImage)
|
||||
? await is3rdPartyImageValid(dockerImage)
|
||||
: await isDockerHubImageValid(dockerImage, tag)
|
||||
return isValid
|
||||
}
|
|
@ -1,237 +0,0 @@
|
|||
import axios from 'axios'
|
||||
import { toast } from 'react-toastify'
|
||||
import isUrl from 'is-url-superb'
|
||||
import slugify from 'slugify'
|
||||
import { DDO, MetadataAlgorithm, Logger } from '@oceanprotocol/lib'
|
||||
import { FormPublishData } from '../components/Publish/_types'
|
||||
|
||||
export function dateToStringNoMS(date: Date): string {
|
||||
return date.toISOString().replace(/\.[0-9]{3}Z/, 'Z')
|
||||
}
|
||||
|
||||
export function transformTags(value: string): string[] {
|
||||
const originalTags = value?.split(',')
|
||||
const transformedTags = originalTags?.map((tag) => slugify(tag).toLowerCase())
|
||||
return transformedTags
|
||||
}
|
||||
|
||||
export function mapTimeoutStringToSeconds(timeout: string): number {
|
||||
switch (timeout) {
|
||||
case 'Forever':
|
||||
return 0
|
||||
case '1 day':
|
||||
return 86400
|
||||
case '1 week':
|
||||
return 604800
|
||||
case '1 month':
|
||||
return 2630000
|
||||
case '1 year':
|
||||
return 31556952
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function numberEnding(number: number): string {
|
||||
return number > 1 ? 's' : ''
|
||||
}
|
||||
|
||||
export function secondsToString(numberOfSeconds: number): string {
|
||||
if (numberOfSeconds === 0) return 'Forever'
|
||||
|
||||
const years = Math.floor(numberOfSeconds / 31536000)
|
||||
const months = Math.floor((numberOfSeconds %= 31536000) / 2630000)
|
||||
const weeks = Math.floor((numberOfSeconds %= 31536000) / 604800)
|
||||
const days = Math.floor((numberOfSeconds %= 604800) / 86400)
|
||||
const hours = Math.floor((numberOfSeconds %= 86400) / 3600)
|
||||
const minutes = Math.floor((numberOfSeconds %= 3600) / 60)
|
||||
const seconds = numberOfSeconds % 60
|
||||
|
||||
return years
|
||||
? `${years} year${numberEnding(years)}`
|
||||
: months
|
||||
? `${months} month${numberEnding(months)}`
|
||||
: weeks
|
||||
? `${weeks} week${numberEnding(weeks)}`
|
||||
: days
|
||||
? `${days} day${numberEnding(days)}`
|
||||
: hours
|
||||
? `${hours} hour${numberEnding(hours)}`
|
||||
: minutes
|
||||
? `${minutes} minute${numberEnding(minutes)}`
|
||||
: seconds
|
||||
? `${seconds} second${numberEnding(seconds)}`
|
||||
: 'less than a second'
|
||||
}
|
||||
|
||||
export function checkIfTimeoutInPredefinedValues(
|
||||
timeout: string,
|
||||
timeoutOptions: string[]
|
||||
): boolean {
|
||||
if (timeoutOptions.indexOf(timeout) > -1) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function getAlgorithmComponent(
|
||||
image: string,
|
||||
containerTag: string,
|
||||
entrypoint: string,
|
||||
algorithmLanguace: string
|
||||
): MetadataAlgorithm {
|
||||
return {
|
||||
language: algorithmLanguace,
|
||||
format: 'docker-image',
|
||||
version: '0.1',
|
||||
container: {
|
||||
entrypoint: entrypoint,
|
||||
image: image,
|
||||
tag: containerTag
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getAlgorithmFileExtension(fileUrl: string): string {
|
||||
const splitedFileUrl = fileUrl.split('.')
|
||||
return splitedFileUrl[splitedFileUrl.length - 1]
|
||||
}
|
||||
|
||||
// export function transformPublishFormToMetadata(
|
||||
// {
|
||||
// name,
|
||||
// author,
|
||||
// description,
|
||||
// tags,
|
||||
// links,
|
||||
// termsAndConditions,
|
||||
// files
|
||||
// }: Partial<FormPublishData>,
|
||||
// ddo?: DDO
|
||||
// ): MetadataMarket {
|
||||
// const currentTime = dateToStringNoMS(new Date())
|
||||
|
||||
// const metadata: MetadataMarket = {
|
||||
// main: {
|
||||
// name,
|
||||
// author,
|
||||
// dateCreated: ddo ? ddo.created : currentTime,
|
||||
// datePublished: '',
|
||||
// files: typeof files !== 'string' && files,
|
||||
// license: 'https://market.oceanprotocol.com/terms'
|
||||
// },
|
||||
// additionalInformation: {
|
||||
// description,
|
||||
// tags: transformTags(tags),
|
||||
// links: typeof links !== 'string' ? links : [],
|
||||
// termsAndConditions
|
||||
// }
|
||||
// }
|
||||
|
||||
// return metadata
|
||||
// }
|
||||
|
||||
// async function isDockerHubImageValid(
|
||||
// image: string,
|
||||
// tag: string
|
||||
// ): Promise<boolean> {
|
||||
// try {
|
||||
// const response = await axios.post(
|
||||
// `https://dockerhub-proxy.oceanprotocol.com`,
|
||||
// {
|
||||
// image,
|
||||
// tag
|
||||
// }
|
||||
// )
|
||||
// if (
|
||||
// !response ||
|
||||
// response.status !== 200 ||
|
||||
// response.data.status !== 'success'
|
||||
// ) {
|
||||
// toast.error(
|
||||
// 'Could not fetch docker hub image info. Please check image name and tag and try again'
|
||||
// )
|
||||
// return false
|
||||
// }
|
||||
|
||||
// return true
|
||||
// } catch (error) {
|
||||
// Logger.error(error.message)
|
||||
// toast.error(
|
||||
// 'Could not fetch docker hub image info. Please check image name and tag and try again'
|
||||
// )
|
||||
// return false
|
||||
// }
|
||||
// }
|
||||
|
||||
async function is3rdPartyImageValid(imageURL: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await axios.head(imageURL)
|
||||
if (!response || response.status !== 200) {
|
||||
toast.error(
|
||||
'Could not fetch docker image info. Please check URL and try again'
|
||||
)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
toast.error(
|
||||
'Could not fetch docker image info. Please check URL and try again'
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// export async function validateDockerImage(
|
||||
// dockerImage: string,
|
||||
// tag: string
|
||||
// ): Promise<boolean> {
|
||||
// const isValid = isUrl(dockerImage)
|
||||
// ? await is3rdPartyImageValid(dockerImage)
|
||||
// : await isDockerHubImageValid(dockerImage, tag)
|
||||
// return isValid
|
||||
// }
|
||||
|
||||
// export function transformPublishAlgorithmFormToMetadata(
|
||||
// {
|
||||
// name,
|
||||
// author,
|
||||
// description,
|
||||
// tags,
|
||||
// image,
|
||||
// containerTag,
|
||||
// entrypoint,
|
||||
// termsAndConditions,
|
||||
// files
|
||||
// }: Partial<FormPublishData>,
|
||||
// ddo?: DDO
|
||||
// ): MetadataMarket {
|
||||
// const currentTime = dateToStringNoMS(new Date())
|
||||
// const fileUrl = typeof files !== 'string' && files[0].url
|
||||
// const algorithmLanguage = getAlgorithmFileExtension(fileUrl)
|
||||
// const algorithm = getAlgorithmComponent(
|
||||
// image,
|
||||
// containerTag,
|
||||
// entrypoint,
|
||||
// algorithmLanguage
|
||||
// )
|
||||
// const metadata: MetadataMarket = {
|
||||
// main: {
|
||||
// name,
|
||||
// type: 'algorithm',
|
||||
// author,
|
||||
// dateCreated: ddo ? ddo.created : currentTime,
|
||||
// files: typeof files !== 'string' && files,
|
||||
// license: 'https://market.oceanprotocol.com/terms',
|
||||
// algorithm
|
||||
// },
|
||||
// additionalInformation: {
|
||||
// description,
|
||||
// tags: transformTags(tags),
|
||||
// termsAndConditions
|
||||
// }
|
||||
// }
|
||||
|
||||
// return metadata
|
||||
// }
|
62
src/@utils/nft.ts
Normal file
62
src/@utils/nft.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
import { renderStaticWaves } from './oceanWaves'
|
||||
|
||||
export interface NftOptions {
|
||||
name: string
|
||||
symbol: string
|
||||
description: string
|
||||
image: string
|
||||
}
|
||||
|
||||
function encodeSvg(svgString: string): string {
|
||||
return svgString
|
||||
.replace(
|
||||
'<svg',
|
||||
~svgString.indexOf('xmlns')
|
||||
? '<svg'
|
||||
: '<svg xmlns="http://www.w3.org/2000/svg"'
|
||||
)
|
||||
.replace(/"/g, "'")
|
||||
.replace(/%/g, '%25')
|
||||
.replace(/#/g, '%23')
|
||||
.replace(/{/g, '%7B')
|
||||
.replace(/}/g, '%7D')
|
||||
.replace(/</g, '%3C')
|
||||
.replace(/>/g, '%3E')
|
||||
.replace(/\s+/g, ' ')
|
||||
}
|
||||
|
||||
export function generateNftOptions(): NftOptions {
|
||||
// TODO: crop image properly in the end as generated SVG waves are a super-wide image,
|
||||
// and add a filled background deciding on either black or white.
|
||||
const image = renderStaticWaves()
|
||||
// const image = new XMLSerializer().serializeToString(waves)
|
||||
// const image = `<svg><path d="M0 10.4304L16.3396 10.4304L8.88727 17.6833L10.2401 19L20 9.5L10.2401 0L8.88727 1.31491L16.3396 8.56959L0 8.56959V10.4304Z" /></svg>`
|
||||
|
||||
const newNft: NftOptions = {
|
||||
name: 'Ocean Asset v4 NFT',
|
||||
symbol: 'OCEAN-V4-NFT',
|
||||
description: `This NFT represents an asset in the Ocean Protocol v4 ecosystem.`,
|
||||
// TODO: figure out if also image URI needs base64 encoding
|
||||
// generated SVG embedded as 'data:image/svg+xml' and encoded characters
|
||||
image: `data:image/svg+xml,${encodeSvg(image)}`
|
||||
// generated SVG embedded as 'data:image/svg+xml;base64'
|
||||
// image: `data:image/svg+xml;base64,${window?.btoa(image)}`
|
||||
// image: `data:image/svg+xml;base64,${Buffer.from(image).toString('base64')}`
|
||||
}
|
||||
|
||||
return newNft
|
||||
}
|
||||
|
||||
export function generateNftCreateData(nftOptions: NftOptions): any {
|
||||
const nftCreateData = {
|
||||
name: nftOptions.name,
|
||||
symbol: nftOptions.symbol,
|
||||
templateIndex: 1,
|
||||
// TODO: figure out if Buffer.from method is working in browser in final build
|
||||
// as BTOA is deprecated.
|
||||
tokenURI: window?.btoa(JSON.stringify(nftOptions))
|
||||
// tokenURI: Buffer.from(JSON.stringify(nftOptions)).toString('base64') // should end up as data:application/json;base64
|
||||
}
|
||||
|
||||
return nftCreateData
|
||||
}
|
49
src/@utils/oceanWaves.ts
Normal file
49
src/@utils/oceanWaves.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import * as d3 from 'd3'
|
||||
|
||||
/*
|
||||
* Ocean Protocol D3 waves
|
||||
* https://oceanprotocol.com/art
|
||||
* Based off of Bostock's Circle Wave
|
||||
* https://bl.ocks.org/mbostock/2d466ec3417722e3568cd83fc35338e3
|
||||
*/
|
||||
export function renderStaticWaves(): string {
|
||||
const svg = d3.create('svg')
|
||||
const width = 1000
|
||||
const height = 250
|
||||
const x = d3.scaleLinear().range([0, width])
|
||||
const angles = d3.range(Math.random(), 4 * Math.PI, Math.PI / 20)
|
||||
|
||||
const path = svg
|
||||
// .append('rect')
|
||||
// .attr('fill', '#fff')
|
||||
// .attr('width', '100%')
|
||||
// .attr('height', '100%')
|
||||
.append('g')
|
||||
.attr('transform', `translate(${width / -4}, ${height / 2})`)
|
||||
.attr('fill', 'none')
|
||||
.attr('stroke-width', 2)
|
||||
.selectAll('path')
|
||||
.data(['#FF4092', '#E000CF', '#8B98A9', '#E2E2E2'])
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('stroke', (d) => d)
|
||||
.style('mix-blend-mode', 'darken')
|
||||
.datum((d, i) => {
|
||||
return d3
|
||||
.line()
|
||||
.curve(d3.curveBasisOpen)
|
||||
.x((angles: any) => x(angles / 4))
|
||||
.y((angles: any) => {
|
||||
const t = d3.now() / 3000
|
||||
return (
|
||||
Math.cos(angles * 8 - (i * 2 * Math.PI) / 10 + t) *
|
||||
Math.pow((2 + Math.cos(angles - t)) / 2, 4) *
|
||||
15
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
path.attr('d', (d) => d(angles as any))
|
||||
|
||||
return `<svg>${svg.node().innerHTML}</svg>`
|
||||
}
|
|
@ -1,82 +1,47 @@
|
|||
import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||
import { toast } from 'react-toastify'
|
||||
import { DID, File as FileMetadata, Logger } from '@oceanprotocol/lib'
|
||||
import { DID, Logger } from '@oceanprotocol/lib'
|
||||
|
||||
export async function fileinfo(
|
||||
url: string,
|
||||
providerUri: string,
|
||||
cancelToken: CancelToken
|
||||
): Promise<FileMetadata> {
|
||||
export interface FileMetadata {
|
||||
index: number
|
||||
valid: boolean
|
||||
contentType: string
|
||||
contentLength: string
|
||||
}
|
||||
|
||||
export async function getEncryptedFileUrls(
|
||||
files: string[],
|
||||
providerUrl: string,
|
||||
did: string,
|
||||
accountId: string
|
||||
): Promise<string> {
|
||||
try {
|
||||
const response = (await axios.post(
|
||||
`${providerUri}/api/v1/services/fileinfo`,
|
||||
{
|
||||
url,
|
||||
cancelToken
|
||||
}
|
||||
)) as AxiosResponse<
|
||||
{ valid: boolean; contentLength: string; contentType: string }[]
|
||||
>
|
||||
|
||||
if (!response || response.status !== 200 || !response.data) {
|
||||
toast.error('Could not connect to File API')
|
||||
return
|
||||
}
|
||||
if (!response.data[0] || !response.data[0].valid) {
|
||||
toast.error(
|
||||
'The data file URL you entered apears to be invalid. Please check URL and try again',
|
||||
{
|
||||
autoClose: false,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined
|
||||
}
|
||||
)
|
||||
return
|
||||
} else {
|
||||
toast.dismiss() // Remove any existing error message
|
||||
toast.success('Great! That file looks good. 🐳', {
|
||||
position: 'bottom-right',
|
||||
autoClose: 5000,
|
||||
hideProgressBar: false,
|
||||
closeOnClick: true,
|
||||
pauseOnHover: true,
|
||||
draggable: true,
|
||||
progress: undefined
|
||||
// https://github.com/oceanprotocol/provider/blob/v4main/API.md#encrypt-endpoint
|
||||
const url = `${providerUrl}/api/v1/services/encrypt`
|
||||
const response: AxiosResponse<{ encryptedDocument: string }> =
|
||||
await axios.post(url, {
|
||||
documentId: did,
|
||||
signature: '', // TODO: add signature
|
||||
publisherAddress: accountId,
|
||||
document: files
|
||||
})
|
||||
}
|
||||
|
||||
const { contentLength, contentType } = response.data[0]
|
||||
|
||||
return {
|
||||
contentLength: contentLength || '',
|
||||
contentType: contentType || '', // need to do that cause lib-js File interface requires contentType
|
||||
url
|
||||
}
|
||||
return response?.data?.encryptedDocument
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
console.error('Error parsing json: ' + error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export async function getFileInfo(
|
||||
url: string | DID,
|
||||
providerUri: string,
|
||||
providerUrl: string,
|
||||
cancelToken: CancelToken
|
||||
): Promise<any> {
|
||||
): Promise<FileMetadata[]> {
|
||||
let postBody
|
||||
try {
|
||||
if (url instanceof DID)
|
||||
postBody = {
|
||||
did: url.getDid()
|
||||
}
|
||||
else
|
||||
postBody = {
|
||||
url
|
||||
}
|
||||
const response = await axios.post(
|
||||
`${providerUri}/api/v1/services/fileinfo`,
|
||||
if (url instanceof DID) postBody = { did: url.getDid() }
|
||||
else postBody = { url }
|
||||
|
||||
const response: AxiosResponse<FileMetadata[]> = await axios.post(
|
||||
`${providerUrl}/api/v1/services/fileinfo`,
|
||||
postBody,
|
||||
{ cancelToken }
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { gql, OperationResult, TypedDocumentNode, OperationContext } from 'urql'
|
||||
import { DDO, Logger } from '@oceanprotocol/lib'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { getUrqlClientInstance } from '@context/UrqlProvider'
|
||||
import { getOceanConfig } from './ocean'
|
||||
import {
|
||||
|
@ -35,7 +35,7 @@ export interface PriceList {
|
|||
}
|
||||
|
||||
export interface AssetListPrices {
|
||||
ddo: DDO
|
||||
ddo: Asset
|
||||
price: BestPrice
|
||||
}
|
||||
|
||||
|
@ -346,7 +346,7 @@ function transformPriceToBestPrice(
|
|||
) {
|
||||
if (poolPrice?.length > 0) {
|
||||
const price: BestPrice = {
|
||||
type: 'pool',
|
||||
type: 'dynamic',
|
||||
address: poolPrice[0]?.id,
|
||||
value:
|
||||
poolPrice[0]?.consumePrice === '-1'
|
||||
|
@ -363,7 +363,7 @@ function transformPriceToBestPrice(
|
|||
// TODO Hacky hack, temporary™: set isConsumable to true for fre assets.
|
||||
// isConsumable: 'true'
|
||||
const price: BestPrice = {
|
||||
type: 'exchange',
|
||||
type: 'fixed',
|
||||
value: frePrice[0]?.rate,
|
||||
address: frePrice[0]?.id,
|
||||
exchangeId: frePrice[0]?.id,
|
||||
|
@ -402,7 +402,7 @@ function transformPriceToBestPrice(
|
|||
}
|
||||
|
||||
async function getAssetsPoolsExchangesAndDatatokenMap(
|
||||
assets: DDO[]
|
||||
assets: Asset[]
|
||||
): Promise<
|
||||
[
|
||||
AssetsPoolPricePool[],
|
||||
|
@ -415,13 +415,17 @@ async function getAssetsPoolsExchangesAndDatatokenMap(
|
|||
const chainAssetLists: any = {}
|
||||
|
||||
for (const ddo of assets) {
|
||||
didDTMap[ddo?.dataToken.toLowerCase()] = ddo.id
|
||||
didDTMap[ddo?.services[0].datatokenAddress.toLowerCase()] = ddo.id
|
||||
// harcoded until we have chainId on assets
|
||||
if (chainAssetLists[ddo.chainId]) {
|
||||
chainAssetLists[ddo.chainId].push(ddo?.dataToken.toLowerCase())
|
||||
chainAssetLists[ddo.chainId].push(
|
||||
ddo?.services[0].datatokenAddress.toLowerCase()
|
||||
)
|
||||
} else {
|
||||
chainAssetLists[ddo.chainId] = []
|
||||
chainAssetLists[ddo.chainId].push(ddo?.dataToken.toLowerCase())
|
||||
chainAssetLists[ddo.chainId].push(
|
||||
ddo?.services[0].datatokenAddress.toLowerCase()
|
||||
)
|
||||
}
|
||||
}
|
||||
let poolPriceResponse: AssetsPoolPricePool[] = []
|
||||
|
@ -464,7 +468,7 @@ async function getAssetsPoolsExchangesAndDatatokenMap(
|
|||
return [poolPriceResponse, frePriceResponse, freePriceResponse, didDTMap]
|
||||
}
|
||||
|
||||
export async function getAssetsPriceList(assets: DDO[]): Promise<PriceList> {
|
||||
export async function getAssetsPriceList(assets: Asset[]): Promise<PriceList> {
|
||||
const priceList: PriceList = {}
|
||||
|
||||
const values: [
|
||||
|
@ -493,15 +497,15 @@ export async function getAssetsPriceList(assets: DDO[]): Promise<PriceList> {
|
|||
return priceList
|
||||
}
|
||||
|
||||
export async function getPrice(asset: DDO): Promise<BestPrice> {
|
||||
export async function getPrice(asset: Asset): Promise<BestPrice> {
|
||||
const freVariables = {
|
||||
datatoken: asset?.dataToken.toLowerCase()
|
||||
datatoken: asset?.services[0].datatokenAddress.toLowerCase()
|
||||
}
|
||||
const freeVariables = {
|
||||
datatoken: asset?.dataToken.toLowerCase()
|
||||
datatoken: asset?.services[0].datatokenAddress.toLowerCase()
|
||||
}
|
||||
const poolVariables = {
|
||||
datatokenAddress: asset?.dataToken.toLowerCase()
|
||||
datatokenAddress: asset?.services[0].datatokenAddress.toLowerCase()
|
||||
}
|
||||
const queryContext = getQueryContext(Number(asset.chainId))
|
||||
|
||||
|
@ -530,9 +534,9 @@ export async function getPrice(asset: DDO): Promise<BestPrice> {
|
|||
return bestPrice
|
||||
}
|
||||
|
||||
export async function getSpotPrice(asset: DDO): Promise<number> {
|
||||
export async function getSpotPrice(asset: Asset): Promise<number> {
|
||||
const poolVariables = {
|
||||
datatokenAddress: asset?.dataToken.toLowerCase()
|
||||
datatokenAddress: asset?.services[0].datatokenAddress.toLowerCase()
|
||||
}
|
||||
const queryContext = getQueryContext(Number(asset.chainId))
|
||||
|
||||
|
@ -546,7 +550,7 @@ export async function getSpotPrice(asset: DDO): Promise<number> {
|
|||
}
|
||||
|
||||
export async function getAssetsBestPrices(
|
||||
assets: DDO[]
|
||||
assets: Asset[]
|
||||
): Promise<AssetListPrices[]> {
|
||||
const assetsWithPrice: AssetListPrices[] = []
|
||||
|
||||
|
@ -561,7 +565,7 @@ export async function getAssetsBestPrices(
|
|||
const frePriceResponse = values[1]
|
||||
const freePriceResponse = values[2]
|
||||
for (const ddo of assets) {
|
||||
const dataToken = ddo.dataToken.toLowerCase()
|
||||
const dataToken = ddo.services[0].datatokenAddress.toLowerCase()
|
||||
const poolPrice: AssetsPoolPricePool[] = []
|
||||
const frePrice: AssetsFrePriceFixedRateExchange[] = []
|
||||
const freePrice: AssetFreePriceDispenser[] = []
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Logger } from '@oceanprotocol/lib'
|
|||
import { getOceanConfig } from './ocean'
|
||||
|
||||
export function accountTruncate(account: string): string {
|
||||
if (!account) return
|
||||
if (!account || account === '') return
|
||||
const middle = account.substring(6, 38)
|
||||
const truncated = account.replace(middle, '…')
|
||||
return truncated
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.display {
|
||||
composes: selection from '@shared/Form/FormFields/AssetSelection.module.css';
|
||||
composes: selection from '@shared/FormFields/AssetSelection/index.module.css';
|
||||
}
|
||||
|
||||
.display [class*='loaderWrap'] {
|
||||
|
@ -7,14 +7,14 @@
|
|||
}
|
||||
|
||||
.scroll {
|
||||
composes: scroll from '@shared/Form/FormFields/AssetSelection.module.css';
|
||||
composes: scroll from '@shared/FormFields/AssetSelection/index.module.css';
|
||||
margin-top: 0;
|
||||
border-top: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.row {
|
||||
composes: row from '@shared/Form/FormFields/AssetSelection.module.css';
|
||||
composes: row from '@shared/FormFields/AssetSelection/index.module.css';
|
||||
}
|
||||
|
||||
.row:last-child {
|
||||
|
@ -35,7 +35,7 @@
|
|||
}
|
||||
|
||||
.title {
|
||||
composes: title from '@shared/Form/FormFields/AssetSelection.module.css';
|
||||
composes: title from '@shared/FormFields/AssetSelection/index.module.css';
|
||||
}
|
||||
|
||||
.hover:hover {
|
||||
|
@ -43,7 +43,7 @@
|
|||
}
|
||||
|
||||
.price {
|
||||
composes: price from '@shared/Form/FormFields/AssetSelection.module.css';
|
||||
composes: price from '@shared/FormFields/AssetSelection/index.module.css';
|
||||
}
|
||||
|
||||
.price [class*='symbol'] {
|
||||
|
@ -51,9 +51,9 @@
|
|||
}
|
||||
|
||||
.did {
|
||||
composes: did from '@shared/Form/FormFields/AssetSelection.module.css';
|
||||
composes: did from '@shared/FormFields/AssetSelection/index.module.css';
|
||||
}
|
||||
|
||||
.empty {
|
||||
composes: empty from '@shared/Form/FormFields/AssetSelection.module.css';
|
||||
composes: empty from '@shared/FormFields/AssetSelection/index.module.css';
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import Link from 'next/link'
|
|||
import PriceUnit from '@shared/Price/PriceUnit'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import styles from './AssetComputeList.module.css'
|
||||
import { AssetSelectionAsset } from '@shared/Form/FormFields/AssetSelection'
|
||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
||||
|
||||
function Empty() {
|
||||
return <div className={styles.empty}>No assets found.</div>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { DDO } from '@oceanprotocol/lib'
|
||||
import Link from 'next/link'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import { getAssetsNames } from '@utils/aquarius'
|
||||
|
@ -11,7 +10,7 @@ export default function AssetListTitle({
|
|||
did,
|
||||
title
|
||||
}: {
|
||||
ddo?: DDO
|
||||
ddo?: Asset
|
||||
did?: string
|
||||
title?: string
|
||||
}): ReactElement {
|
||||
|
@ -21,8 +20,7 @@ export default function AssetListTitle({
|
|||
useEffect(() => {
|
||||
if (title || !appConfig.metadataCacheUri) return
|
||||
if (ddo) {
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
setAssetTitle(attributes.main.name)
|
||||
setAssetTitle(ddo.metadata.name)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import AssetTeaser from '@shared/AssetTeaser/AssetTeaser'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Pagination from '@shared/Pagination'
|
||||
import styles from './AssetList.module.css'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import styles from './index.module.css'
|
||||
import classNames from 'classnames/bind'
|
||||
import { getAssetsBestPrices, AssetListPrices } from '@utils/subgraph'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
|
@ -20,7 +19,7 @@ function LoaderArea() {
|
|||
}
|
||||
|
||||
declare type AssetListProps = {
|
||||
assets: DDO[]
|
||||
assets: Asset[]
|
||||
showPagination: boolean
|
||||
page?: number
|
||||
totalPages?: number
|
|
@ -2,15 +2,15 @@ import React from 'react'
|
|||
import Link from 'next/link'
|
||||
import Dotdotdot from 'react-dotdotdot'
|
||||
import Price from '@shared/Price'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import removeMarkdown from 'remove-markdown'
|
||||
import Publisher from '@shared/Publisher'
|
||||
import AssetType from '@shared/AssetType'
|
||||
import NetworkName from '@shared/NetworkName'
|
||||
import styles from './AssetTeaser.module.css'
|
||||
import { getServiceByName } from '@utils/ddo'
|
||||
|
||||
declare type AssetTeaserProps = {
|
||||
ddo: DDO
|
||||
ddo: Asset
|
||||
price: BestPrice
|
||||
noPublisher?: boolean
|
||||
}
|
||||
|
@ -20,12 +20,11 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
|||
price,
|
||||
noPublisher
|
||||
}: AssetTeaserProps) => {
|
||||
const { attributes } = ddo.findServiceByType('metadata')
|
||||
const { name, type } = attributes.main
|
||||
const { name, type, description } = ddo.metadata
|
||||
const { dataTokenInfo } = ddo
|
||||
const isCompute = Boolean(ddo?.findServiceByType('compute'))
|
||||
const isCompute = Boolean(getServiceByName(ddo, 'compute'))
|
||||
const accessType = isCompute ? 'compute' : 'access'
|
||||
const { owner } = ddo.publicKey[0]
|
||||
const { owner } = ddo.nft
|
||||
|
||||
return (
|
||||
<article className={`${styles.teaser} ${styles[type]}`}>
|
||||
|
@ -49,12 +48,7 @@ const AssetTeaser: React.FC<AssetTeaserProps> = ({
|
|||
|
||||
<div className={styles.content}>
|
||||
<Dotdotdot tagName="p" clamp={3}>
|
||||
{removeMarkdown(
|
||||
attributes?.additionalInformation?.description?.substring(
|
||||
0,
|
||||
300
|
||||
) || ''
|
||||
)}
|
||||
{removeMarkdown(description?.substring(0, 300) || '')}
|
||||
</Dotdotdot>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ export default function DebugOutput({
|
|||
return (
|
||||
<div style={{ marginTop: 'var(--spacer)' }}>
|
||||
<h5>{title}</h5>
|
||||
<pre>
|
||||
<pre style={{ wordWrap: 'break-word' }}>
|
||||
<code>{JSON.stringify(output, null, 2)}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import { File as FileMetadata } from '@oceanprotocol/lib'
|
||||
import filesize from 'filesize'
|
||||
import classNames from 'classnames/bind'
|
||||
import cleanupContentType from '@utils/cleanupContentType'
|
||||
import styles from './index.module.css'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import { FileMetadata } from '@utils/provider'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
.advancedBtn {
|
||||
margin-bottom: 2rem;
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
import React, { ReactElement, useState, FormEvent, ChangeEvent } from 'react'
|
||||
import Input from '@shared/Form/Input'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { Field } from 'formik'
|
||||
import styles from './AdvancedSettings.module.css'
|
||||
|
||||
export default function AdvancedSettings(prop: {
|
||||
content: FormContent
|
||||
handleFieldChange: (
|
||||
e: ChangeEvent<HTMLInputElement>,
|
||||
field: FormFieldProps
|
||||
) => void
|
||||
}): ReactElement {
|
||||
const [showAdvancedSettings, setShowAdvancedSettings] =
|
||||
useState<boolean>(false)
|
||||
|
||||
function toggleAdvancedSettings(e: FormEvent<Element>) {
|
||||
e.preventDefault()
|
||||
setShowAdvancedSettings(!!showAdvancedSettings)
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
className={styles.advancedBtn}
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={toggleAdvancedSettings}
|
||||
>
|
||||
Advanced Settings
|
||||
</Button>
|
||||
{showAdvancedSettings &&
|
||||
prop.content.data.map(
|
||||
(field: FormFieldProps) =>
|
||||
field.advanced === true && (
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={field.options}
|
||||
component={Input}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
prop.handleFieldChange(e, field)
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import { useField } from 'formik'
|
||||
import { toast } from 'react-toastify'
|
||||
import CustomInput from './URLInput/Input'
|
||||
import { useOcean } from '@context/Ocean'
|
||||
import { InputProps } from '@shared/Form/Input'
|
||||
|
||||
export default function CustomProvider(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [providerUrl, setProviderUrl] = useState<string>()
|
||||
const { ocean, config } = useOcean()
|
||||
|
||||
function loadProvider() {
|
||||
if (!providerUrl) return
|
||||
async function validateProvider() {
|
||||
let valid: boolean
|
||||
try {
|
||||
setIsLoading(true)
|
||||
valid = await ocean.provider.isValidProvider(providerUrl)
|
||||
} catch (error) {
|
||||
valid = false
|
||||
console.error(error.message)
|
||||
} finally {
|
||||
valid
|
||||
? toast.success('Perfect! That provider URL looks good 🐳')
|
||||
: toast.error(
|
||||
'Could not validate provider. Please check URL and try again'
|
||||
)
|
||||
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
validateProvider()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadProvider()
|
||||
}, [providerUrl, config?.providerUri])
|
||||
|
||||
async function handleButtonClick(e: React.SyntheticEvent, url: string) {
|
||||
helpers.setTouched(false)
|
||||
e.preventDefault()
|
||||
if (providerUrl === url) {
|
||||
loadProvider()
|
||||
}
|
||||
|
||||
setProviderUrl(url)
|
||||
}
|
||||
|
||||
return (
|
||||
<CustomInput
|
||||
submitText="Validate"
|
||||
{...props}
|
||||
{...field}
|
||||
isLoading={isLoading}
|
||||
handleButtonClick={handleButtonClick}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -1,5 +0,0 @@
|
|||
.datatoken {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: calc(var(--spacer) / 3);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { useField } from 'formik'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import { utils } from '@oceanprotocol/lib'
|
||||
import { InputProps } from '@shared/Form/Input'
|
||||
import RefreshName from './RefreshName'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export default function Datatoken(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
|
||||
async function generateName() {
|
||||
const dataTokenOptions = utils.generateDatatokenName()
|
||||
helpers.setValue({ ...dataTokenOptions })
|
||||
}
|
||||
|
||||
// Generate new DT name & symbol on first mount
|
||||
useEffect(() => {
|
||||
generateName()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={styles.datatoken}>
|
||||
<strong>{field?.value?.name}</strong> —{' '}
|
||||
<strong>{field?.value?.symbol}</strong>
|
||||
<RefreshName generateName={generateName} />
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import React, { ReactElement, useEffect } from 'react'
|
||||
import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interfaces/File'
|
||||
import { prettySize } from './utils'
|
||||
import cleanupContentType from '@utils/cleanupContentType'
|
||||
import styles from './Info.module.css'
|
||||
import { useField, useFormikContext } from 'formik'
|
||||
|
||||
export default function FileInfo({
|
||||
name,
|
||||
file
|
||||
}: {
|
||||
name: string
|
||||
file: FileMetadata
|
||||
}): ReactElement {
|
||||
const { validateField } = useFormikContext()
|
||||
const [field, meta, helpers] = useField(name)
|
||||
|
||||
// On mount, validate the field manually
|
||||
useEffect(() => {
|
||||
validateField(name)
|
||||
}, [name, validateField])
|
||||
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<h3 className={styles.url}>{file.url}</h3>
|
||||
<ul>
|
||||
<li>URL confirmed</li>
|
||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||
{file.contentType && <li>{cleanupContentType(file.contentType)}</li>}
|
||||
</ul>
|
||||
<button
|
||||
className={styles.removeButton}
|
||||
onClick={() => helpers.setValue(undefined)}
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
.terms {
|
||||
composes: content from '@shared/Page/PageMarkdown.module.css';
|
||||
|
||||
padding: calc(var(--spacer) / 2);
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--background-highlight);
|
||||
border-radius: var(--border-radius);
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
font-size: var(--font-size-small);
|
||||
|
||||
max-height: 250px;
|
||||
/* smooth overflow scrolling for pre-iOS 13 */
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.terms h1 {
|
||||
font-size: var(--font-size-base);
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.terms h2 {
|
||||
font-size: var(--font-size-small);
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import { InputProps } from '@shared/Form/Input'
|
||||
import InputElement from '@shared/Form/Input/InputElement'
|
||||
import styles from './Terms.module.css'
|
||||
|
||||
export default function Terms(props: InputProps): ReactElement {
|
||||
const termsProps: InputProps = {
|
||||
...props,
|
||||
defaultChecked: props.value.toString() === 'true'
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<InputElement {...termsProps} type="checkbox" />
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
.input {
|
||||
composes: input from '@shared/Form/Input/InputElement.module.css';
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { FieldInputProps, useField } from 'formik'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import styles from './Input.module.css'
|
||||
import InputGroup from '@shared/Form/Input/InputGroup'
|
||||
|
||||
export default function URLInput({
|
||||
submitText,
|
||||
handleButtonClick,
|
||||
isLoading,
|
||||
...props
|
||||
}: {
|
||||
submitText: string
|
||||
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
||||
isLoading: boolean
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField(props as FieldInputProps<any>)
|
||||
|
||||
return (
|
||||
<InputGroup>
|
||||
<input
|
||||
className={styles.input}
|
||||
{...props}
|
||||
type="url"
|
||||
onBlur={(e: React.SyntheticEvent) => handleButtonClick(e, field.value)}
|
||||
/>
|
||||
|
||||
<Button
|
||||
style="primary"
|
||||
size="small"
|
||||
onClick={(e: React.SyntheticEvent) => e.preventDefault()}
|
||||
disabled={!field.value}
|
||||
>
|
||||
{isLoading ? <Loader /> : submitText}
|
||||
</Button>
|
||||
</InputGroup>
|
||||
)
|
||||
}
|
|
@ -58,11 +58,11 @@
|
|||
}
|
||||
|
||||
.radio {
|
||||
composes: radio from '@shared/Form/Input/InputElement.module.css';
|
||||
composes: radio from '@shared/FormInput/InputElement.module.css';
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
composes: checkbox from '@shared/Form/Input/InputElement.module.css';
|
||||
composes: checkbox from '@shared/FormInput/InputElement.module.css';
|
||||
}
|
||||
|
||||
.title {
|
|
@ -4,9 +4,9 @@ import slugify from 'slugify'
|
|||
import classNames from 'classnames/bind'
|
||||
import PriceUnit from '@shared/Price/PriceUnit'
|
||||
import External from '@images/external.svg'
|
||||
import InputElement from '@shared/Form/Input/InputElement'
|
||||
import InputElement from '@shared/FormInput/InputElement'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import styles from './AssetSelection.module.css'
|
||||
import styles from './index.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
.boxSelectionsWrapper > div {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.boxSelection {
|
||||
|
@ -36,13 +37,14 @@
|
|||
}
|
||||
|
||||
.boxSelectionsWrapper input[type='radio']:checked + label {
|
||||
color: var(--font-color-text);
|
||||
border-color: var(--color-secondary);
|
||||
color: var(--background-body);
|
||||
border-color: var(--background-body);
|
||||
background: var(--font-color-heading);
|
||||
}
|
||||
|
||||
.boxSelection svg {
|
||||
width: var(--font-size-h4);
|
||||
height: var(--font-size-h4);
|
||||
width: var(--font-size-h5);
|
||||
height: var(--font-size-h5);
|
||||
fill: currentColor;
|
||||
margin-bottom: calc(var(--spacer) / 5);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import React, { ChangeEvent } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import styles from './BoxSelection.module.css'
|
||||
import styles from './index.module.css'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
|
@ -40,27 +40,27 @@ export default function BoxSelection({
|
|||
{!options ? (
|
||||
<Loader />
|
||||
) : (
|
||||
options.map((value: BoxSelectionOption) => (
|
||||
<div key={value.name}>
|
||||
options.map((option: BoxSelectionOption) => (
|
||||
<div key={option.name}>
|
||||
<input
|
||||
id={value.name}
|
||||
id={option.name}
|
||||
type="radio"
|
||||
className={styleClassesInput}
|
||||
defaultChecked={value.checked}
|
||||
defaultChecked={option.checked}
|
||||
onChange={(event) => handleChange(event)}
|
||||
{...props}
|
||||
disabled={disabled}
|
||||
value={value.name}
|
||||
value={option.name}
|
||||
name={name}
|
||||
/>
|
||||
<label
|
||||
className={`${styles.boxSelection} ${styles.label}`}
|
||||
htmlFor={value.name}
|
||||
title={value.name}
|
||||
htmlFor={option.name}
|
||||
title={option.name}
|
||||
>
|
||||
{value.icon}
|
||||
<span className={styles.title}>{value.title}</span>
|
||||
{value.text}
|
||||
{option.icon}
|
||||
<span className={styles.title}>{option.title}</span>
|
||||
{option.text}
|
||||
</label>
|
||||
</div>
|
||||
))
|
28
src/components/@shared/FormFields/Datatoken/index.module.css
Normal file
28
src/components/@shared/FormFields/Datatoken/index.module.css
Normal file
|
@ -0,0 +1,28 @@
|
|||
.datatoken {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: 1fr 11fr;
|
||||
margin-bottom: var(--spacer);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.token {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: calc(var(--spacer) / 3);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.image {
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
padding: var(--spacer);
|
||||
background: var(--background-body);
|
||||
border: 1px solid var(--border-color);
|
||||
fill: var(--brand-violet);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.image svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
36
src/components/@shared/FormFields/Datatoken/index.tsx
Normal file
36
src/components/@shared/FormFields/Datatoken/index.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { useField } from 'formik'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import { InputProps } from '@shared/FormInput'
|
||||
import Logo from '@images/logo.svg'
|
||||
import RefreshName from './RefreshName'
|
||||
import styles from './index.module.css'
|
||||
import { generateDatatokenName } from '@utils/datatokens'
|
||||
|
||||
export default function Datatoken(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
|
||||
async function generateName() {
|
||||
const dataTokenOptions = generateDatatokenName()
|
||||
helpers.setValue({ ...dataTokenOptions })
|
||||
}
|
||||
|
||||
// Generate new DT name & symbol on first mount
|
||||
useEffect(() => {
|
||||
if (field.value?.name !== '') return
|
||||
|
||||
generateName()
|
||||
}, [field.value?.name])
|
||||
|
||||
return (
|
||||
<div className={styles.datatoken}>
|
||||
<figure className={styles.image}>
|
||||
<Logo />
|
||||
</figure>
|
||||
<div className={styles.token}>
|
||||
<strong>{field?.value?.name}</strong> —{' '}
|
||||
<strong>{field?.value?.symbol}</strong>
|
||||
<RefreshName generateName={generateName} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -38,3 +38,7 @@
|
|||
color: var(--font-color-text);
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.info li.success {
|
||||
color: var(--brand-alert-green);
|
||||
}
|
27
src/components/@shared/FormFields/FilesInput/Info.tsx
Normal file
27
src/components/@shared/FormFields/FilesInput/Info.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import { prettySize } from './utils'
|
||||
import cleanupContentType from '@utils/cleanupContentType'
|
||||
import styles from './Info.module.css'
|
||||
import { FileMetadata } from '@utils/provider'
|
||||
|
||||
export default function FileInfo({
|
||||
file,
|
||||
handleClose
|
||||
}: {
|
||||
file: FileMetadata
|
||||
handleClose(): void
|
||||
}): ReactElement {
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
<h3 className={styles.url}>{(file as any).url}</h3>
|
||||
<ul>
|
||||
<li className={styles.success}>✓ URL confirmed</li>
|
||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||
{file.contentType && <li>{cleanupContentType(file.contentType)}</li>}
|
||||
</ul>
|
||||
<button className={styles.removeButton} onClick={handleClose}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,33 +1,33 @@
|
|||
import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import React, { ReactElement, useState } from 'react'
|
||||
import { useField } from 'formik'
|
||||
import { toast } from 'react-toastify'
|
||||
import FileInfo from './Info'
|
||||
import CustomInput from '../URLInput/Input'
|
||||
import { InputProps } from '@shared/Form/Input'
|
||||
import { fileinfo } from '@utils/provider'
|
||||
import UrlInput from '../URLInput'
|
||||
import { InputProps } from '@shared/FormInput'
|
||||
import { getFileInfo } from '@utils/provider'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { getOceanConfig } from '@utils/ocean'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { initialValues } from 'src/components/Publish/_constants'
|
||||
|
||||
export default function FilesInput(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [fileUrl, setFileUrl] = useState<string>()
|
||||
const { chainId } = useWeb3()
|
||||
const newCancelToken = useCancelToken()
|
||||
|
||||
function loadFileInfo() {
|
||||
function loadFileInfo(url: string) {
|
||||
const config = getOceanConfig(chainId || 1)
|
||||
|
||||
async function validateUrl() {
|
||||
try {
|
||||
setIsLoading(true)
|
||||
const checkedFile = await fileinfo(
|
||||
fileUrl,
|
||||
const checkedFile = await getFileInfo(
|
||||
url,
|
||||
config?.providerUri,
|
||||
newCancelToken()
|
||||
)
|
||||
checkedFile && helpers.setValue([checkedFile])
|
||||
checkedFile && helpers.setValue([{ url, ...checkedFile[0] }])
|
||||
} catch (error) {
|
||||
toast.error('Could not fetch file info. Please check URL and try again')
|
||||
console.error(error.message)
|
||||
|
@ -36,38 +36,32 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||
}
|
||||
}
|
||||
|
||||
fileUrl && validateUrl()
|
||||
validateUrl()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadFileInfo()
|
||||
}, [fileUrl])
|
||||
|
||||
async function handleButtonClick(e: React.SyntheticEvent, url: string) {
|
||||
// hack so the onBlur-triggered validation does not show,
|
||||
// like when this field is required
|
||||
helpers.setTouched(false)
|
||||
|
||||
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
||||
e.preventDefault()
|
||||
loadFileInfo(url)
|
||||
}
|
||||
|
||||
// In the case when the user re-add the same URL after it was removed (by accident or intentionally)
|
||||
if (fileUrl === url) {
|
||||
loadFileInfo()
|
||||
}
|
||||
|
||||
setFileUrl(url)
|
||||
function handleClose() {
|
||||
helpers.setValue(initialValues.services[0].files)
|
||||
helpers.setTouched(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{field?.value && field.value[0] && typeof field.value === 'object' ? (
|
||||
<FileInfo name={props.name} file={field.value[0]} />
|
||||
{field?.value?.length &&
|
||||
field.value[0].url !== '' &&
|
||||
field.value[0].valid ? (
|
||||
<FileInfo file={field.value[0]} handleClose={handleClose} />
|
||||
) : (
|
||||
<CustomInput
|
||||
submitText="Add File"
|
||||
<UrlInput
|
||||
submitText="Validate"
|
||||
{...props}
|
||||
{...field}
|
||||
name={`${field.name}[0].url`}
|
||||
hasError={Boolean(meta.touched && meta.error)}
|
||||
isLoading={isLoading}
|
||||
handleButtonClick={handleButtonClick}
|
||||
/>
|
34
src/components/@shared/FormFields/Nft/index.module.css
Normal file
34
src/components/@shared/FormFields/Nft/index.module.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
.nft {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: 1fr 11fr;
|
||||
margin-bottom: var(--spacer);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.token {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: calc(var(--spacer) / 3);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.image {
|
||||
display: block;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
border-radius: var(--border-radius);
|
||||
border: 1px solid var(--border-color);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.refresh {
|
||||
position: absolute;
|
||||
right: calc(var(--spacer) / 4);
|
||||
bottom: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.refresh svg {
|
||||
fill: var(--brand-pink);
|
||||
width: var(--font-size-mini);
|
||||
height: var(--font-size-mini);
|
||||
}
|
47
src/components/@shared/FormFields/Nft/index.tsx
Normal file
47
src/components/@shared/FormFields/Nft/index.tsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
import Button from '@shared/atoms/Button'
|
||||
import { InputProps } from '@shared/FormInput'
|
||||
import { generateNftOptions } from '@utils/nft'
|
||||
import { useField } from 'formik'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import Refresh from '@images/refresh.svg'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export default function Nft(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
|
||||
// Generate on first mount
|
||||
useEffect(() => {
|
||||
if (field.value?.name !== '') return
|
||||
|
||||
const nftOptions = generateNftOptions()
|
||||
helpers.setValue({ ...nftOptions })
|
||||
}, [field.value?.name])
|
||||
|
||||
return (
|
||||
<div className={styles.nft}>
|
||||
<figure className={styles.image}>
|
||||
<img src={field?.value?.image} width="128" height="128" />
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
className={styles.refresh}
|
||||
title="Generate new image"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
const nftOptions = generateNftOptions()
|
||||
helpers.setValue({ ...nftOptions })
|
||||
}}
|
||||
>
|
||||
<Refresh />
|
||||
</Button>
|
||||
</figure>
|
||||
|
||||
<div className={styles.token}>
|
||||
<strong>{field?.value?.name}</strong> —{' '}
|
||||
<strong>{field?.value?.symbol}</strong>
|
||||
<br />
|
||||
{field?.value?.description}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
.error {
|
||||
composes: error from '@shared/FormInput/index.module.css';
|
||||
}
|
||||
|
||||
.restore {
|
||||
font-family: var(--font-family-base);
|
||||
text-transform: none;
|
||||
font-weight: var(--font-weight-base);
|
||||
}
|
78
src/components/@shared/FormFields/Provider/index.tsx
Normal file
78
src/components/@shared/FormFields/Provider/index.tsx
Normal file
|
@ -0,0 +1,78 @@
|
|||
import React, { ReactElement, useState } from 'react'
|
||||
import { ErrorMessage, useField } from 'formik'
|
||||
import UrlInput from '../URLInput'
|
||||
import { useOcean } from '@context/Ocean'
|
||||
import { InputProps } from '@shared/FormInput'
|
||||
import FileInfo from '../FilesInput/Info'
|
||||
import styles from './index.module.css'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { initialValues } from 'src/components/Publish/_constants'
|
||||
|
||||
export default function CustomProvider(props: InputProps): ReactElement {
|
||||
const [field, meta, helpers] = useField(props.name)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const { ocean, config } = useOcean()
|
||||
|
||||
async function validateProvider(url: string) {
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
// TODO: #948 Remove ocean.provider.isValidProvider dependency.
|
||||
const isValid = await ocean.provider.isValidProvider(url)
|
||||
helpers.setValue({ url, valid: isValid })
|
||||
helpers.setError(undefined)
|
||||
} catch (error) {
|
||||
helpers.setError(
|
||||
'Could not validate provider. Please check URL and try again.'
|
||||
)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function handleValidateButtonClick(
|
||||
e: React.SyntheticEvent,
|
||||
url: string
|
||||
) {
|
||||
e.preventDefault()
|
||||
validateProvider(url)
|
||||
}
|
||||
|
||||
function handleFileInfoClose() {
|
||||
helpers.setValue({ url: '', valid: false })
|
||||
helpers.setTouched(false)
|
||||
}
|
||||
|
||||
function handleRestore(e: React.SyntheticEvent) {
|
||||
e.preventDefault()
|
||||
helpers.setValue(initialValues.services[0].providerUrl)
|
||||
}
|
||||
|
||||
return field?.value?.valid ? (
|
||||
<FileInfo file={field.value} handleClose={handleFileInfoClose} />
|
||||
) : (
|
||||
<>
|
||||
<UrlInput
|
||||
submitText="Validate"
|
||||
{...props}
|
||||
name={`${field.name}.url`}
|
||||
hasError={Boolean(meta.touched && meta.error)}
|
||||
isLoading={isLoading}
|
||||
handleButtonClick={handleValidateButtonClick}
|
||||
/>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleRestore}
|
||||
className={styles.restore}
|
||||
>
|
||||
Use Default Provider
|
||||
</Button>
|
||||
{typeof meta.error === 'string' && meta.touched && meta.error && (
|
||||
<div className={styles.error}>
|
||||
<ErrorMessage name={field.name} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
17
src/components/@shared/FormFields/URLInput/index.module.css
Normal file
17
src/components/@shared/FormFields/URLInput/index.module.css
Normal file
|
@ -0,0 +1,17 @@
|
|||
.input {
|
||||
composes: input from '@shared/FormInput/InputElement.module.css';
|
||||
}
|
||||
|
||||
.hasError {
|
||||
color: var(--brand-alert-red);
|
||||
border-color: var(--brand-alert-red);
|
||||
}
|
||||
|
||||
.error {
|
||||
composes: error from '@shared/FormInput/index.module.css';
|
||||
}
|
||||
|
||||
.success {
|
||||
background: var(--brand-alert-green);
|
||||
opacity: 1 !important;
|
||||
}
|
58
src/components/@shared/FormFields/URLInput/index.tsx
Normal file
58
src/components/@shared/FormFields/URLInput/index.tsx
Normal file
|
@ -0,0 +1,58 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { ErrorMessage, useField } from 'formik'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import styles from './index.module.css'
|
||||
import InputGroup from '@shared/FormInput/InputGroup'
|
||||
import InputElement from '@shared/FormInput/InputElement'
|
||||
|
||||
export default function URLInput({
|
||||
submitText,
|
||||
handleButtonClick,
|
||||
isLoading,
|
||||
name,
|
||||
hasError,
|
||||
...props
|
||||
}: {
|
||||
submitText: string
|
||||
handleButtonClick(e: React.SyntheticEvent, data: string): void
|
||||
isLoading: boolean
|
||||
name: string
|
||||
hasError: boolean
|
||||
}): ReactElement {
|
||||
const [field, meta] = useField(name)
|
||||
const isButtonDisabled = !field?.value || field.value === ''
|
||||
|
||||
return (
|
||||
<>
|
||||
<InputGroup>
|
||||
<InputElement
|
||||
className={`${styles.input} ${
|
||||
!isLoading && hasError ? styles.hasError : ''
|
||||
}`}
|
||||
{...props}
|
||||
{...field}
|
||||
type="url"
|
||||
/>
|
||||
|
||||
<Button
|
||||
style="primary"
|
||||
size="small"
|
||||
onClick={(e: React.SyntheticEvent) => {
|
||||
e.preventDefault()
|
||||
handleButtonClick(e, field.value)
|
||||
}}
|
||||
disabled={isButtonDisabled}
|
||||
>
|
||||
{isLoading ? <Loader /> : submitText}
|
||||
</Button>
|
||||
</InputGroup>
|
||||
|
||||
{meta.touched && meta.error && (
|
||||
<div className={styles.error}>
|
||||
<ErrorMessage name={field.name} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
|
@ -3,14 +3,14 @@ import slugify from 'slugify'
|
|||
import styles from './InputElement.module.css'
|
||||
import { InputProps } from '.'
|
||||
import FilesInput from '../FormFields/FilesInput'
|
||||
import CustomProvider from '../FormFields/CustomProvider'
|
||||
import Terms from '../FormFields/Terms'
|
||||
import CustomProvider from '../FormFields/Provider'
|
||||
import BoxSelection, { BoxSelectionOption } from '../FormFields/BoxSelection'
|
||||
import Datatoken from '../FormFields/Datatoken'
|
||||
import classNames from 'classnames/bind'
|
||||
import AssetSelection, {
|
||||
AssetSelectionAsset
|
||||
} from '../FormFields/AssetSelection'
|
||||
import Nft from '../FormFields/Nft'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
|
@ -54,7 +54,9 @@ export default function InputElement({
|
|||
const sortedOptions =
|
||||
!sortOptions && sortOptions === false
|
||||
? options
|
||||
: options.sort((a: string, b: string) => a.localeCompare(b))
|
||||
: (options as string[]).sort((a: string, b: string) =>
|
||||
a.localeCompare(b)
|
||||
)
|
||||
return (
|
||||
<select
|
||||
id={name}
|
||||
|
@ -67,7 +69,7 @@ export default function InputElement({
|
|||
<option value="">---</option>
|
||||
)}
|
||||
{sortedOptions &&
|
||||
sortedOptions.map((option: string, index: number) => (
|
||||
(sortedOptions as string[]).map((option: string, index: number) => (
|
||||
<option key={index} value={option}>
|
||||
{option} {postfix}
|
||||
</option>
|
||||
|
@ -89,7 +91,7 @@ export default function InputElement({
|
|||
return (
|
||||
<div className={styles.radioGroup}>
|
||||
{options &&
|
||||
options.map((option: string, index: number) => (
|
||||
(options as string[]).map((option: string, index: number) => (
|
||||
<div className={styles.radioWrap} key={index}>
|
||||
<input
|
||||
className={styles[type]}
|
||||
|
@ -129,12 +131,12 @@ export default function InputElement({
|
|||
)
|
||||
case 'files':
|
||||
return <FilesInput name={name} {...field} {...props} />
|
||||
case 'providerUri':
|
||||
case 'providerUrl':
|
||||
return <CustomProvider name={name} {...field} {...props} />
|
||||
case 'nft':
|
||||
return <Nft name={name} {...field} {...props} />
|
||||
case 'datatoken':
|
||||
return <Datatoken name={name} {...field} {...props} />
|
||||
case 'terms':
|
||||
return <Terms name={name} options={options} {...field} {...props} />
|
||||
case 'boxSelection':
|
||||
return (
|
||||
<BoxSelection
|
||||
|
@ -155,6 +157,7 @@ export default function InputElement({
|
|||
type={type || 'text'}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
{postfix && (
|
||||
|
@ -167,6 +170,7 @@ export default function InputElement({
|
|||
type={type || 'text'}
|
||||
size={size}
|
||||
disabled={disabled}
|
||||
{...field}
|
||||
{...props}
|
||||
/>
|
||||
)
|
|
@ -6,11 +6,3 @@
|
|||
display: block;
|
||||
margin-bottom: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.required:after {
|
||||
content: '*';
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-secondary);
|
||||
display: inline-block;
|
||||
margin-left: 0.1rem;
|
||||
}
|
|
@ -2,19 +2,13 @@ import React, { ReactElement, ReactNode } from 'react'
|
|||
import styles from './Label.module.css'
|
||||
|
||||
const Label = ({
|
||||
required,
|
||||
children,
|
||||
...props
|
||||
}: {
|
||||
required?: boolean
|
||||
children: ReactNode
|
||||
htmlFor: string
|
||||
}): ReactElement => (
|
||||
<label
|
||||
className={`${styles.label} ${required && styles.required}`}
|
||||
title={required ? 'Required' : ''}
|
||||
{...props}
|
||||
>
|
||||
<label className={styles.label} {...props}>
|
||||
{children}
|
||||
</label>
|
||||
)
|
|
@ -1,5 +1,5 @@
|
|||
.field {
|
||||
margin-bottom: var(--spacer);
|
||||
margin-bottom: calc(var(--spacer) * var(--line-height));
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,13 @@
|
|||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.required {
|
||||
font-size: var(--font-size-base);
|
||||
color: var(--color-secondary);
|
||||
display: inline-block;
|
||||
margin-left: 0.1rem;
|
||||
}
|
||||
|
||||
.error {
|
||||
display: inline-block;
|
||||
font-size: var(--font-size-mini);
|
|
@ -1,18 +1,20 @@
|
|||
import React, {
|
||||
FormEvent,
|
||||
ChangeEvent,
|
||||
FormEvent,
|
||||
KeyboardEvent,
|
||||
ReactElement,
|
||||
ReactNode,
|
||||
useEffect,
|
||||
useState
|
||||
} from 'react'
|
||||
import InputElement from './InputElement'
|
||||
import Help from './Help'
|
||||
import Label from './Label'
|
||||
import styles from './index.module.css'
|
||||
import { ErrorMessage, FieldInputProps } from 'formik'
|
||||
import classNames from 'classnames/bind'
|
||||
import Disclaimer from './Disclaimer'
|
||||
import Tooltip from '@shared/atoms/Tooltip'
|
||||
import Markdown from '@shared/Markdown'
|
||||
|
||||
const cx = classNames.bind(styles)
|
||||
|
||||
|
@ -37,10 +39,10 @@ export interface InputProps {
|
|||
): void
|
||||
onKeyPress?(
|
||||
e:
|
||||
| React.KeyboardEvent<HTMLInputElement>
|
||||
| React.KeyboardEvent<HTMLInputElement>
|
||||
| React.KeyboardEvent<HTMLSelectElement>
|
||||
| React.KeyboardEvent<HTMLTextAreaElement>
|
||||
| KeyboardEvent<HTMLInputElement>
|
||||
| KeyboardEvent<HTMLInputElement>
|
||||
| KeyboardEvent<HTMLSelectElement>
|
||||
| KeyboardEvent<HTMLTextAreaElement>
|
||||
): void
|
||||
rows?: number
|
||||
multiple?: boolean
|
||||
|
@ -68,47 +70,64 @@ export default function Input(props: Partial<InputProps>): ReactElement {
|
|||
help,
|
||||
additionalComponent,
|
||||
size,
|
||||
form,
|
||||
field,
|
||||
disclaimer,
|
||||
disclaimerValues
|
||||
} = props
|
||||
|
||||
const hasError =
|
||||
props.form?.touched[field.name] && props.form?.errors[field.name]
|
||||
const isFormikField = typeof field !== 'undefined'
|
||||
const isNestedField = field?.name?.includes('.')
|
||||
|
||||
// TODO: this feels hacky as it assumes nested `values` store. But we can't use the
|
||||
// `useField()` hook in here to get `meta.error` so we have to match against form?.errors?
|
||||
// handling flat and nested data at same time.
|
||||
const parsedFieldName =
|
||||
isFormikField && (isNestedField ? field?.name.split('.') : [field?.name])
|
||||
// const hasFormikError = !!meta?.touched && !!meta?.error
|
||||
const hasFormikError =
|
||||
form?.errors !== {} &&
|
||||
form?.touched?.[parsedFieldName[0]]?.[parsedFieldName[1]] &&
|
||||
form?.errors?.[parsedFieldName[0]]?.[parsedFieldName[1]]
|
||||
|
||||
const styleClasses = cx({
|
||||
field: true,
|
||||
hasError: hasError
|
||||
hasError: hasFormikError
|
||||
})
|
||||
|
||||
const [disclaimerVisible, setDisclaimerVisible] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isFormikField) return
|
||||
|
||||
if (disclaimer && disclaimerValues) {
|
||||
setDisclaimerVisible(
|
||||
disclaimerValues.includes(props.form?.values[field.name])
|
||||
disclaimerValues.includes(
|
||||
props.form?.values[parsedFieldName[0]]?.[parsedFieldName[1]]
|
||||
)
|
||||
)
|
||||
}
|
||||
}, [props.form?.values[field.name]])
|
||||
}, [isFormikField, props.form?.values])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styleClasses}
|
||||
data-is-submitting={props.form?.isSubmitting ? true : null}
|
||||
>
|
||||
<Label htmlFor={props.name} required={props.required}>
|
||||
<div className={styleClasses}>
|
||||
<Label htmlFor={props.name}>
|
||||
{label}
|
||||
{props.required && (
|
||||
<span title="Required" className={styles.required}>
|
||||
*
|
||||
</span>
|
||||
)}
|
||||
{help && <Tooltip content={<Markdown text={help} />} />}
|
||||
</Label>
|
||||
<InputElement size={size} {...field} {...props} />
|
||||
|
||||
{field && hasError && (
|
||||
{isFormikField && hasFormikError && (
|
||||
<div className={styles.error}>
|
||||
<ErrorMessage name={field.name} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{help && <Help>{help}</Help>}
|
||||
|
||||
{disclaimer && (
|
||||
<Disclaimer visible={disclaimerVisible}>{disclaimer}</Disclaimer>
|
||||
)}
|
|
@ -12,6 +12,7 @@ export interface PersistProps {
|
|||
isSessionStorage?: boolean
|
||||
}
|
||||
|
||||
// TODO: refactor into functional component
|
||||
class PersistImpl extends React.Component<
|
||||
PersistProps & { formik: FormikProps<any> },
|
||||
any
|
|
@ -7,6 +7,7 @@
|
|||
font-size: var(--font-size-h3);
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
@media (min-width: 40rem) {
|
||||
|
|
|
@ -10,7 +10,7 @@ export default function PageHeader({
|
|||
description,
|
||||
center
|
||||
}: {
|
||||
title: string
|
||||
title: ReactElement
|
||||
description?: string
|
||||
center?: boolean
|
||||
}): ReactElement {
|
||||
|
|
|
@ -26,7 +26,7 @@ export default function Page({
|
|||
<Container>
|
||||
{title && !noPageHeader && (
|
||||
<PageHeader
|
||||
title={title}
|
||||
title={<>{title}</>}
|
||||
description={description}
|
||||
center={headerCenter}
|
||||
/>
|
||||
|
|
|
@ -13,7 +13,7 @@ import { retrieveDDOListByDIDs } from '@utils/aquarius'
|
|||
import { CancelToken } from 'axios'
|
||||
import Title from './Title'
|
||||
import styles from './index.module.css'
|
||||
import { DDO, Logger } from '@oceanprotocol/lib'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
|
||||
const REFETCH_INTERVAL = 20000
|
||||
|
@ -77,7 +77,7 @@ export interface Datatoken {
|
|||
|
||||
export interface PoolTransaction extends TransactionHistoryPoolTransactions {
|
||||
networkId: number
|
||||
ddo: DDO
|
||||
ddo: Asset
|
||||
}
|
||||
|
||||
const columns = [
|
||||
|
|
|
@ -21,13 +21,13 @@ export default function Publisher({
|
|||
minimal?: boolean
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
// const { accountId } = useWeb3()
|
||||
const isMounted = useIsMounted()
|
||||
const [profile, setProfile] = useState<Profile>()
|
||||
const [name, setName] = useState(accountTruncate(account))
|
||||
const [accountEns, setAccountEns] = useState<string>()
|
||||
|
||||
const showAdd = account === accountId && !profile
|
||||
// const showAdd = account === accountId && !profile
|
||||
|
||||
useEffect(() => {
|
||||
if (!account) return
|
||||
|
@ -70,7 +70,7 @@ export default function Publisher({
|
|||
<Link href={`/profile/${accountEns || account}`}>
|
||||
<a title="Show profile page.">{name}</a>
|
||||
</Link>
|
||||
{showAdd && <Add />}
|
||||
{/* {showAdd && <Add />} */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -26,7 +26,9 @@ export default function TokenApproval({
|
|||
const config = getOceanConfig(ddo.chainId)
|
||||
|
||||
const tokenAddress =
|
||||
coin === 'OCEAN' ? config.oceanTokenAddress : ddo.dataTokenInfo.address
|
||||
coin === 'OCEAN'
|
||||
? config.oceanTokenAddress
|
||||
: ddo.services[0].datatokenAddress
|
||||
const spender = price.address
|
||||
|
||||
const checkTokenApproval = useCallback(async () => {
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { ReactElement } from 'react'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { addCustomNetwork } from '@utils/web3'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import styles from './index.module.css'
|
||||
import { addCustomNetwork } from '@utils/web3'
|
||||
import useNetworkMetadata, {
|
||||
getNetworkDataById,
|
||||
getNetworkDisplayName
|
||||
|
|
|
@ -34,6 +34,10 @@
|
|||
border-color: var(--font-color-heading);
|
||||
}
|
||||
|
||||
.tab[aria-disabled='true'] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.tabContent {
|
||||
padding: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
|
|
@ -2,9 +2,10 @@ import React, { ReactElement, ReactNode } from 'react'
|
|||
import { Tab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs'
|
||||
import styles from './Tabs.module.css'
|
||||
|
||||
interface TabsItem {
|
||||
export interface TabsItem {
|
||||
title: string
|
||||
content: ReactNode
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default function Tabs({
|
||||
|
@ -29,6 +30,7 @@ export default function Tabs({
|
|||
className={styles.tab}
|
||||
key={item.title}
|
||||
onClick={handleTabChange ? () => handleTabChange(item.title) : null}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.title}
|
||||
</Tab>
|
||||
|
|
|
@ -1,39 +1,41 @@
|
|||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import styles from './AlgorithmDatasetsListForCompute.module.css'
|
||||
import { getAlgorithmDatasetsForCompute } from '@utils/aquarius'
|
||||
import { AssetSelectionAsset } from '@shared/Form/FormFields/AssetSelection'
|
||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
||||
import AssetComputeList from '@shared/AssetList/AssetComputeList'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { DDO } from '@oceanprotocol/lib'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { getServiceByName } from '@utils/ddo'
|
||||
|
||||
export default function AlgorithmDatasetsListForCompute({
|
||||
algorithmDid,
|
||||
dataset
|
||||
ddo,
|
||||
algorithmDid
|
||||
}: {
|
||||
ddo: Asset
|
||||
algorithmDid: string
|
||||
dataset: DDO
|
||||
}): ReactElement {
|
||||
const { type } = useAsset()
|
||||
const [datasetsForCompute, setDatasetsForCompute] =
|
||||
useState<AssetSelectionAsset[]>()
|
||||
const newCancelToken = useCancelToken()
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo) return
|
||||
|
||||
async function getDatasetsAllowedForCompute() {
|
||||
const isCompute = Boolean(dataset?.findServiceByType('compute'))
|
||||
const datasetComputeService = dataset.findServiceByType(
|
||||
const isCompute = Boolean(getServiceByName(ddo, 'compute'))
|
||||
const datasetComputeService = getServiceByName(
|
||||
ddo,
|
||||
isCompute ? 'compute' : 'access'
|
||||
)
|
||||
const datasets = await getAlgorithmDatasetsForCompute(
|
||||
algorithmDid,
|
||||
datasetComputeService?.serviceEndpoint,
|
||||
dataset?.chainId,
|
||||
ddo?.chainId,
|
||||
newCancelToken()
|
||||
)
|
||||
setDatasetsForCompute(datasets)
|
||||
}
|
||||
type === 'algorithm' && getDatasetsAllowedForCompute()
|
||||
}, [type])
|
||||
ddo.metadata.type === 'algorithm' && getDatasetsAllowedForCompute()
|
||||
}, [ddo?.metadata?.type])
|
||||
|
||||
return (
|
||||
<div className={styles.datasetsContainer}>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user