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

graphql and onchain storage support (#1794)

* support storage type publish

* adding storageType to edit form

* add IPFS type

* fix testst and rollback ipfs typing

* update oceanjs lib

* fix Ipfs type

* Update package-lock.json

* added graphql and smartcontract options UI (WIP)

* update package.json

* removed graphql and smartcontracts

* make various fixes for edit and publish with IPFS (missing Arweave)

* removed no-case-declarations lines

* moved ipfs utils

* renamed getFileUrlInfo to getFileInfo

* added is-ipfs to jest mock

* Update package-lock.json

* fix things

* npm is fun

* rename url to file in getFileInfo

* refactor publish form (storage type field)

* fix tab value when changing tabs

* refactoring edit form

* more refactor edit form

* fix edit validation

* fix validation when input is empty on edit form

* fix validation when loading asset in edit form

* change URL to file confirmed

* change messages based on service type

* Update form.json

* fix FileInput tests and added ipfs / arweave tests

* removed unnecessary comment

* Update index.tsx

* adding graphql / onchain to fieldInput

* merged main and fix storages (WIP)

* restore graphql / smart contract

* added headers to graphql and connect fields

* fix tests temp

* fix publish preview file object

* added codemirror component and change layout

* added codemirror to edit and normalizing ddo debug preview

* added codemirror to edit form

* fix typing issues

* style and logic changes to codemirror component

* fix test (temp fix)

* Avoiding getInitialPaymentCollector failure (#1816)

* early return is no web3 or ddo

* Creating test for MetaFull

* adding test: src/components/Asset/AssetContent/MetaSecondary.test.tsx

* Adding test for bookmarks

* Adding test for displaying payment collector

* Removing comments

* Renaming assetAquarius

* Renaming assetWithAccessDetails

* Ensuring that the payment collector is shown even without a wallet connected

* Removing broken test

* Using getDummyWeb3 for fetching the payment collector address

* WIP

* update ocean lib

* parse abi obj on publish

* fix previewDebugPatch function

* Update package-lock.json

* remove logs

* fix build error

* fix stupid typo

* upgrade codemirror

* simplify checkJson function

* fix preview json display

* validate json on sm storage

* cleanup

* fix edit validation

* fix initial state tabs storage type

* remove logs

* fix text headers

* google validation (#1835)

* Updating validation to exclude any google link

* Updating Yup validation

* Checking if domain includes google.com

* Updating isGoogleUrl function

* Moving isGoogleUrl into @utils/url/index file

* isGoogleUrl function

* Updating tests

* Adding additional tests for other google domains

* Updating tests

* Updating isGoogleUrl file path

* upgrade ocean lib

* cleanup

* edit mode fixes

* added tests for graphql and sm

* Updating pricing message (#1842)

* Bump @storybook/addon-essentials from 6.5.13 to 6.5.15 (#1841)

Bumps [@storybook/addon-essentials](https://github.com/storybookjs/storybook/tree/HEAD/addons/essentials) from 6.5.13 to 6.5.15.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/v6.5.15/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v6.5.15/addons/essentials)

---
updated-dependencies:
- dependency-name: "@storybook/addon-essentials"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump @types/jest from 29.2.3 to 29.2.5 (#1840)

Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 29.2.3 to 29.2.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump prettier from 2.8.0 to 2.8.1 (#1837)

Bumps [prettier](https://github.com/prettier/prettier) from 2.8.0 to 2.8.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.0...2.8.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump react-select from 5.6.1 to 5.7.0 (#1839)

Bumps [react-select](https://github.com/JedWatson/react-select) from 5.6.1 to 5.7.0.
- [Release notes](https://github.com/JedWatson/react-select/releases)
- [Changelog](https://github.com/JedWatson/react-select/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/JedWatson/react-select/compare/react-select@5.6.1...react-select@5.7.0)

---
updated-dependencies:
- dependency-name: react-select
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump react-tabs from 5.1.0 to 6.0.0 (#1838)

Bumps [react-tabs](https://github.com/reactjs/react-tabs) from 5.1.0 to 6.0.0.
- [Release notes](https://github.com/reactjs/react-tabs/releases)
- [Commits](https://github.com/reactjs/react-tabs/compare/v5.1.0...v6.0.0)

---
updated-dependencies:
- dependency-name: react-tabs
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix asset route (#1836)

* updating the buy button message for free assets

* Updating pricing text for compute and algorithms

* Updating tests

* Adding a seperate sentence about paying gas fees for network charges with free assets

* Fixing tests

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* Showing hosting type in File Info (#1846)

* Bump @storybook/addon-essentials from 6.5.13 to 6.5.15 (#1841)

Bumps [@storybook/addon-essentials](https://github.com/storybookjs/storybook/tree/HEAD/addons/essentials) from 6.5.13 to 6.5.15.
- [Release notes](https://github.com/storybookjs/storybook/releases)
- [Changelog](https://github.com/storybookjs/storybook/blob/v6.5.15/CHANGELOG.md)
- [Commits](https://github.com/storybookjs/storybook/commits/v6.5.15/addons/essentials)

---
updated-dependencies:
- dependency-name: "@storybook/addon-essentials"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump @types/jest from 29.2.3 to 29.2.5 (#1840)

Bumps [@types/jest](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jest) from 29.2.3 to 29.2.5.
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jest)

---
updated-dependencies:
- dependency-name: "@types/jest"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump prettier from 2.8.0 to 2.8.1 (#1837)

Bumps [prettier](https://github.com/prettier/prettier) from 2.8.0 to 2.8.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.0...2.8.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump react-select from 5.6.1 to 5.7.0 (#1839)

Bumps [react-select](https://github.com/JedWatson/react-select) from 5.6.1 to 5.7.0.
- [Release notes](https://github.com/JedWatson/react-select/releases)
- [Changelog](https://github.com/JedWatson/react-select/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/JedWatson/react-select/compare/react-select@5.6.1...react-select@5.7.0)

---
updated-dependencies:
- dependency-name: react-select
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump react-tabs from 5.1.0 to 6.0.0 (#1838)

Bumps [react-tabs](https://github.com/reactjs/react-tabs) from 5.1.0 to 6.0.0.
- [Release notes](https://github.com/reactjs/react-tabs/releases)
- [Commits](https://github.com/reactjs/react-tabs/compare/v5.1.0...v6.0.0)

---
updated-dependencies:
- dependency-name: react-tabs
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* fix asset route (#1836)

* Adding hosting type to the file info component

* Writting smart contract hosting type across two lines

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mihaisc <mihai.scarlat@smartcontrol.ro>

* add initial price value for not supported price assets (#1851)

* Fix compute basetoken issue (#1829)

* fix lint

* add dynamic provider fees

* fixes

* cleanup and more fixes

* bump oceanlib to 2.6.0

* fix 404 styling (#1850)

* cleanup and remove logs

* fix smart contract check

* restore headers

* removed unnecessary message

* fix error SM with headers

* added headers as object to normalize file

* fix input error in url file

* adding methods to url storage type

* test pipeline (now it's stuck)

* cleanup

* fix MethodInput test

* fix arweave fileObj

* Update index.tsx

* fix provider url in form test

* fixing test errors

* fix test

* fix help labels

* cleanup leftovers

* cleanup dependencies codemirror

* remove any from normalizeFile

* edit comment

* remove any from oceanTheme

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
Co-authored-by: Jamie Hewitt <jamie@oceanprotocol.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: mihaisc <mihai.scarlat@smartcontrol.ro>
Co-authored-by: Jamie Hewitt <jamie.hewitt15@gmail.com>
Co-authored-by: Bogdan Fazakas <bogdan.fazakas@gmail.com>
This commit is contained in:
EnzoVezzaro 2023-03-06 06:32:32 -04:00 committed by GitHub
parent 223c8bf302
commit b6f3bcbe68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 1476 additions and 139 deletions

View File

@ -64,7 +64,72 @@
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ", "computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true, "prominentHelp": true,
"type": "files", "type": "files",
"required": true "required": true,
"innerFields": [
{
"value": "headers",
"title": "Headers",
"label": "Headers",
"placeholder_value": "Authorization",
"help": "This HEADERS will be stored encrypted after publishing.",
"type": "headers",
"required": true
}
]
},
{
"value": "graphql",
"title": "Graphql",
"label": "URL",
"placeholder": "e.g. http://172.15.0.15:8000/subgraphs/name/oceanprotocol/ocean-subgraph",
"help": "This URL will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true,
"headers": true,
"innerFields": [
{
"value": "headers",
"title": "Headers",
"label": "Headers",
"placeholder_value": "Authorization",
"help": "This HEADERS will be stored encrypted after publishing.",
"type": "headers",
"required": true
},
{
"value": "query",
"title": "Query",
"label": "Query",
"placeholder": "query{\n nfts(\n orderBy: createdTimestamp,\n orderDirection:desc\n ){\n id\n symbol\n createdTimestamp\n }\n}",
"help": "This QUERY will be stored encrypted after publishing.",
"type": "codeeditor",
"required": true
}
]
},
{
"value": "smartcontract",
"title": "Smart Contract",
"label": "Address",
"placeholder": "e.g. 0x8149276f275EEFAc110D74AFE8AFECEaeC7d1593",
"help": "This ADDRESS will be stored encrypted after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true,
"innerFields": [
{
"value": "abi",
"title": "ABI",
"label": "ABI",
"placeholder": "{\n 'inputs': [],\n 'name': 'swapOceanFee',\n 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}],\n 'stateMutability': 'view',\n 'type': 'function'\n}",
"help": "This ABI will be stored encrypted after publishing.",
"type": "codeeditor",
"required": true
}
]
} }
], ],
"sortOptions": false, "sortOptions": false,

View File

@ -138,7 +138,72 @@
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ", "computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true, "prominentHelp": true,
"type": "files", "type": "files",
"required": true "required": true,
"methods": true,
"innerFields": [
{
"value": "headers",
"title": "Headers",
"label": "Headers",
"placeholder_value": "Authorization",
"help": "This HEADERS will be stored encrypted after publishing.",
"type": "headers",
"required": false
}
]
},
{
"value": "graphql",
"title": "Graphql",
"label": "URL",
"placeholder": "e.g. http://172.15.0.15:8000/subgraphs/name/oceanprotocol/ocean-subgraph",
"help": "This URL will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true,
"innerFields": [
{
"value": "headers",
"title": "Headers",
"label": "Headers",
"placeholder_value": "Authorization",
"help": "This HEADERS will be stored encrypted after publishing.",
"type": "headers",
"required": false
},
{
"value": "query",
"title": "Query",
"label": "Query",
"placeholder": "query{\n nfts(\n orderBy: createdTimestamp,\n orderDirection:desc\n ){\n id\n symbol\n createdTimestamp\n }\n}",
"help": "This QUERY will be stored encrypted after publishing.",
"type": "codeeditor",
"required": true
}
]
},
{
"value": "smartcontract",
"title": "Smartcontract",
"label": "Address",
"placeholder": "e.g. 0x8149276f275EEFAc110D74AFE8AFECEaeC7d1593",
"help": "This ADDRESS will be stored encrypted after publishing.",
"computeHelp": "For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
"prominentHelp": true,
"type": "files",
"required": true,
"innerFields": [
{
"value": "abi",
"title": "ABI",
"label": "ABI",
"placeholder": "{\n 'inputs': [],\n 'name': 'swapOceanFee',\n 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}],\n 'stateMutability': 'view',\n 'type': 'function'\n}",
"help": "This ABI will be stored encrypted after publishing.",
"type": "codeeditor",
"required": true
}
]
} }
], ],
"sortOptions": false, "sortOptions": false,

409
package-lock.json generated
View File

@ -10,6 +10,8 @@
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.3.1",
"@coingecko/cryptoformat": "^0.5.4", "@coingecko/cryptoformat": "^0.5.4",
"@loadable/component": "^5.15.2", "@loadable/component": "^5.15.2",
"@oceanprotocol/art": "^3.2.0", "@oceanprotocol/art": "^3.2.0",
@ -17,6 +19,7 @@
"@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/typographies": "^0.1.0",
"@oceanprotocol/use-dark-mode": "^2.4.3", "@oceanprotocol/use-dark-mode": "^2.4.3",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"@uiw/react-codemirror": "^4.19.5",
"@urql/exchange-refocus": "^1.0.0", "@urql/exchange-refocus": "^1.0.0",
"@walletconnect/web3-provider": "^1.8.0", "@walletconnect/web3-provider": "^1.8.0",
"axios": "^1.2.0", "axios": "^1.2.0",
@ -58,7 +61,7 @@
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-essentials": "^6.5.15", "@storybook/addon-essentials": "^6.5.13",
"@storybook/builder-webpack5": "^6.5.13", "@storybook/builder-webpack5": "^6.5.13",
"@storybook/manager-webpack5": "^6.5.13", "@storybook/manager-webpack5": "^6.5.13",
"@storybook/react": "^6.5.13", "@storybook/react": "^6.5.13",
@ -76,6 +79,7 @@
"@types/remove-markdown": "^0.3.1", "@types/remove-markdown": "^0.3.1",
"@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0", "@typescript-eslint/parser": "^5.43.0",
"@uiw/codemirror-themes": "^4.19.1",
"apollo": "^2.34.0", "apollo": "^2.34.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.35.0", "eslint": "^8.35.0",
@ -2039,6 +2043,102 @@
"node": ">=0.1.95" "node": ">=0.1.95"
} }
}, },
"node_modules/@codemirror/autocomplete": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.0.tgz",
"integrity": "sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.6.0",
"@lezer/common": "^1.0.0"
},
"peerDependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/commands": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.3.tgz",
"integrity": "sha512-wUw1+vb34Ultv0Q9m/OVB7yizGXgtoDbkI5f5ErM8bebwLyUYjicdhJTKhTvPTpgkv8dq/BK0lQ3K5pRf2DAJw==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0"
}
},
"node_modules/@codemirror/lang-json": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@lezer/json": "^1.0.0"
}
},
"node_modules/@codemirror/language": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.3.2.tgz",
"integrity": "sha512-g42uHhOcEMAXjmozGG+rdom5UsbyfMxQFh7AbkeoaNImddL6Xt4cQDL0+JxmG7+as18rUAvZaqzP/TjsciVIrA==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"node_modules/@codemirror/lint": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.0.tgz",
"integrity": "sha512-mdvDQrjRmYPvQ3WrzF6Ewaao+NWERYtpthJvoQ3tK3t/44Ynhk8ZGjTSL9jMEv8CgSMogmt75X8ceOZRDSXHtQ==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/search": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.2.3.tgz",
"integrity": "sha512-V9n9233lopQhB1dyjsBK2Wc1i+8hcCqxl1wQ46c5HWWLePoe4FluV3TGHoZ04rBRlGjNyz9DTmpJErig8UE4jw==",
"dependencies": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"node_modules/@codemirror/state": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz",
"integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA=="
},
"node_modules/@codemirror/theme-one-dark": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.0.tgz",
"integrity": "sha512-AiTHtFRu8+vWT9wWUWDM+cog6ZwgivJogB1Tm/g40NIpLwph7AnmxrSzWfvJN5fBVufsuwBxecQCNmdcR5D7Aw==",
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/highlight": "^1.0.0"
}
},
"node_modules/@codemirror/view": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.2.tgz",
"integrity": "sha512-HeK2GyycxceaQVyvYVYXmn1vUKYYBsHCcfGRSsFO+3fRRtwXx2STK0YiFBmiWx2vtU9gUAJgIUXUN8a0osI8Ng==",
"dependencies": {
"@codemirror/state": "^6.1.4",
"style-mod": "^4.0.0",
"w3c-keyname": "^2.2.4"
}
},
"node_modules/@coingecko/cryptoformat": { "node_modules/@coingecko/cryptoformat": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/@coingecko/cryptoformat/-/cryptoformat-0.5.4.tgz", "resolved": "https://registry.npmjs.org/@coingecko/cryptoformat/-/cryptoformat-0.5.4.tgz",
@ -4112,6 +4212,36 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/sourcemap-codec": "^1.4.10"
} }
}, },
"node_modules/@lezer/common": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz",
"integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng=="
},
"node_modules/@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@lezer/json": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.0.tgz",
"integrity": "sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==",
"dependencies": {
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"node_modules/@lezer/lr": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz",
"integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==",
"dependencies": {
"@lezer/common": "^1.0.0"
}
},
"node_modules/@loadable/component": { "node_modules/@loadable/component": {
"version": "5.15.2", "version": "5.15.2",
"resolved": "https://registry.npmjs.org/@loadable/component/-/component-5.15.2.tgz", "resolved": "https://registry.npmjs.org/@loadable/component/-/component-5.15.2.tgz",
@ -17736,6 +17866,67 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
} }
}, },
"node_modules/@uiw/codemirror-extensions-basic-setup": {
"version": "4.19.5",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.19.5.tgz",
"integrity": "sha512-1zt7ZPJ01xKkSW/KDy0FZNga0bngN1fC594wCVG7FBi60ehfcAucpooQ+JSPScKXopxcb+ugPKZvVLzr9/OfzA==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
},
"peerDependencies": {
"@codemirror/autocomplete": ">=6.0.0",
"@codemirror/commands": ">=6.0.0",
"@codemirror/language": ">=6.0.0",
"@codemirror/lint": ">=6.0.0",
"@codemirror/search": ">=6.0.0",
"@codemirror/state": ">=6.0.0",
"@codemirror/view": ">=6.0.0"
}
},
"node_modules/@uiw/codemirror-themes": {
"version": "4.19.5",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.19.5.tgz",
"integrity": "sha512-BWCTwQJaGiOc+nYqPLQDjmCtIojaCEKx2aO1bOTyGw0fisKwGw9Csll+bi9ujqA+vk6qYJmXI0P5K7kVs8fbdA==",
"dev": true,
"dependencies": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
},
"peerDependencies": {
"@codemirror/language": ">=6.0.0",
"@codemirror/state": ">=6.0.0",
"@codemirror/view": ">=6.0.0"
}
},
"node_modules/@uiw/react-codemirror": {
"version": "4.19.5",
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.19.5.tgz",
"integrity": "sha512-ZCHh8d7beXbF8/t7F1+yHht6A9Y6CdKeOkZq4A09lxJEnyTQrj1FMf2zvfaqc7K23KNjkTCtSlbqKKbVDgrWaw==",
"dependencies": {
"@babel/runtime": "^7.18.6",
"@codemirror/commands": "^6.1.0",
"@codemirror/state": "^6.1.1",
"@codemirror/theme-one-dark": "^6.0.0",
"@uiw/codemirror-extensions-basic-setup": "4.19.5",
"codemirror": "^6.0.0"
},
"peerDependencies": {
"@babel/runtime": ">=7.11.0",
"@codemirror/state": ">=6.0.0",
"@codemirror/theme-one-dark": ">=6.0.0",
"@codemirror/view": ">=6.0.0",
"codemirror": ">=6.0.0",
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
},
"node_modules/@urql/core": { "node_modules/@urql/core": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@urql/core/-/core-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@urql/core/-/core-3.0.3.tgz",
@ -22190,6 +22381,20 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
"dependencies": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"node_modules/collapse-white-space": { "node_modules/collapse-white-space": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz",
@ -23070,6 +23275,11 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true "dev": true
}, },
"node_modules/crelt": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz",
"integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA=="
},
"node_modules/cross-env": { "node_modules/cross-env": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@ -43370,6 +43580,11 @@
"webpack": "^4.0.0 || ^5.0.0" "webpack": "^4.0.0 || ^5.0.0"
} }
}, },
"node_modules/style-mod": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz",
"integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw=="
},
"node_modules/style-to-object": { "node_modules/style-to-object": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",
@ -45470,6 +45685,11 @@
"browser-process-hrtime": "^1.0.0" "browser-process-hrtime": "^1.0.0"
} }
}, },
"node_modules/w3c-keyname": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
"integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg=="
},
"node_modules/w3c-xmlserializer": { "node_modules/w3c-xmlserializer": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz",
@ -48351,6 +48571,96 @@
"minimist": "^1.2.0" "minimist": "^1.2.0"
} }
}, },
"@codemirror/autocomplete": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.4.0.tgz",
"integrity": "sha512-HLF2PnZAm1s4kGs30EiqKMgD7XsYaQ0XJnMR0rofEWQ5t5D60SfqpDIkIh1ze5tiEbyUWm8+VJ6W1/erVvBMIA==",
"requires": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.6.0",
"@lezer/common": "^1.0.0"
}
},
"@codemirror/commands": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.3.tgz",
"integrity": "sha512-wUw1+vb34Ultv0Q9m/OVB7yizGXgtoDbkI5f5ErM8bebwLyUYjicdhJTKhTvPTpgkv8dq/BK0lQ3K5pRf2DAJw==",
"requires": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.2.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0"
}
},
"@codemirror/lang-json": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz",
"integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==",
"requires": {
"@codemirror/language": "^6.0.0",
"@lezer/json": "^1.0.0"
}
},
"@codemirror/language": {
"version": "6.3.2",
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.3.2.tgz",
"integrity": "sha512-g42uHhOcEMAXjmozGG+rdom5UsbyfMxQFh7AbkeoaNImddL6Xt4cQDL0+JxmG7+as18rUAvZaqzP/TjsciVIrA==",
"requires": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/common": "^1.0.0",
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0",
"style-mod": "^4.0.0"
}
},
"@codemirror/lint": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.1.0.tgz",
"integrity": "sha512-mdvDQrjRmYPvQ3WrzF6Ewaao+NWERYtpthJvoQ3tK3t/44Ynhk8ZGjTSL9jMEv8CgSMogmt75X8ceOZRDSXHtQ==",
"requires": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"@codemirror/search": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.2.3.tgz",
"integrity": "sha512-V9n9233lopQhB1dyjsBK2Wc1i+8hcCqxl1wQ46c5HWWLePoe4FluV3TGHoZ04rBRlGjNyz9DTmpJErig8UE4jw==",
"requires": {
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"crelt": "^1.0.5"
}
},
"@codemirror/state": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz",
"integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA=="
},
"@codemirror/theme-one-dark": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.0.tgz",
"integrity": "sha512-AiTHtFRu8+vWT9wWUWDM+cog6ZwgivJogB1Tm/g40NIpLwph7AnmxrSzWfvJN5fBVufsuwBxecQCNmdcR5D7Aw==",
"requires": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0",
"@lezer/highlight": "^1.0.0"
}
},
"@codemirror/view": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.2.tgz",
"integrity": "sha512-HeK2GyycxceaQVyvYVYXmn1vUKYYBsHCcfGRSsFO+3fRRtwXx2STK0YiFBmiWx2vtU9gUAJgIUXUN8a0osI8Ng==",
"requires": {
"@codemirror/state": "^6.1.4",
"style-mod": "^4.0.0",
"w3c-keyname": "^2.2.4"
}
},
"@coingecko/cryptoformat": { "@coingecko/cryptoformat": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/@coingecko/cryptoformat/-/cryptoformat-0.5.4.tgz", "resolved": "https://registry.npmjs.org/@coingecko/cryptoformat/-/cryptoformat-0.5.4.tgz",
@ -49918,6 +50228,36 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/sourcemap-codec": "^1.4.10"
} }
}, },
"@lezer/common": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz",
"integrity": "sha512-SVgiGtMnMnW3ActR8SXgsDhw7a0w0ChHSYAyAUxxrOiJ1OqYWEKk/xJd84tTSPo1mo6DXLObAJALNnd0Hrv7Ng=="
},
"@lezer/highlight": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.1.3.tgz",
"integrity": "sha512-3vLKLPThO4td43lYRBygmMY18JN3CPh9w+XS2j8WC30vR4yZeFG4z1iFe4jXE43NtGqe//zHW5q8ENLlHvz9gw==",
"requires": {
"@lezer/common": "^1.0.0"
}
},
"@lezer/json": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.0.tgz",
"integrity": "sha512-zbAuUY09RBzCoCA3lJ1+ypKw5WSNvLqGMtasdW6HvVOqZoCpPr8eWrsGnOVWGKGn8Rh21FnrKRVlJXrGAVUqRw==",
"requires": {
"@lezer/highlight": "^1.0.0",
"@lezer/lr": "^1.0.0"
}
},
"@lezer/lr": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.2.5.tgz",
"integrity": "sha512-f9319YG1A/3ysgUE3bqCHEd7g+3ZZ71MWlwEc42mpnLVYXgfJJgtu1XAyBB4Kz8FmqmnFe9caopDqKeMMMAU6g==",
"requires": {
"@lezer/common": "^1.0.0"
}
},
"@loadable/component": { "@loadable/component": {
"version": "5.15.2", "version": "5.15.2",
"resolved": "https://registry.npmjs.org/@loadable/component/-/component-5.15.2.tgz", "resolved": "https://registry.npmjs.org/@loadable/component/-/component-5.15.2.tgz",
@ -60444,6 +60784,44 @@
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.3.0"
} }
}, },
"@uiw/codemirror-extensions-basic-setup": {
"version": "4.19.5",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-extensions-basic-setup/-/codemirror-extensions-basic-setup-4.19.5.tgz",
"integrity": "sha512-1zt7ZPJ01xKkSW/KDy0FZNga0bngN1fC594wCVG7FBi60ehfcAucpooQ+JSPScKXopxcb+ugPKZvVLzr9/OfzA==",
"requires": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"@uiw/codemirror-themes": {
"version": "4.19.5",
"resolved": "https://registry.npmjs.org/@uiw/codemirror-themes/-/codemirror-themes-4.19.5.tgz",
"integrity": "sha512-BWCTwQJaGiOc+nYqPLQDjmCtIojaCEKx2aO1bOTyGw0fisKwGw9Csll+bi9ujqA+vk6qYJmXI0P5K7kVs8fbdA==",
"dev": true,
"requires": {
"@codemirror/language": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"@uiw/react-codemirror": {
"version": "4.19.5",
"resolved": "https://registry.npmjs.org/@uiw/react-codemirror/-/react-codemirror-4.19.5.tgz",
"integrity": "sha512-ZCHh8d7beXbF8/t7F1+yHht6A9Y6CdKeOkZq4A09lxJEnyTQrj1FMf2zvfaqc7K23KNjkTCtSlbqKKbVDgrWaw==",
"requires": {
"@babel/runtime": "^7.18.6",
"@codemirror/commands": "^6.1.0",
"@codemirror/state": "^6.1.1",
"@codemirror/theme-one-dark": "^6.0.0",
"@uiw/codemirror-extensions-basic-setup": "4.19.5",
"codemirror": "^6.0.0"
}
},
"@urql/core": { "@urql/core": {
"version": "3.0.3", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/@urql/core/-/core-3.0.3.tgz", "resolved": "https://registry.npmjs.org/@urql/core/-/core-3.0.3.tgz",
@ -64050,6 +64428,20 @@
"integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==",
"dev": true "dev": true
}, },
"codemirror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz",
"integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==",
"requires": {
"@codemirror/autocomplete": "^6.0.0",
"@codemirror/commands": "^6.0.0",
"@codemirror/language": "^6.0.0",
"@codemirror/lint": "^6.0.0",
"@codemirror/search": "^6.0.0",
"@codemirror/state": "^6.0.0",
"@codemirror/view": "^6.0.0"
}
},
"collapse-white-space": { "collapse-white-space": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz",
@ -64778,6 +65170,11 @@
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true "dev": true
}, },
"crelt": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz",
"integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA=="
},
"cross-env": { "cross-env": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@ -80572,6 +80969,11 @@
"schema-utils": "^3.0.0" "schema-utils": "^3.0.0"
} }
}, },
"style-mod": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz",
"integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw=="
},
"style-to-object": { "style-to-object": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-0.3.0.tgz",
@ -82187,6 +82589,11 @@
"browser-process-hrtime": "^1.0.0" "browser-process-hrtime": "^1.0.0"
} }
}, },
"w3c-keyname": {
"version": "2.2.6",
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
"integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg=="
},
"w3c-xmlserializer": { "w3c-xmlserializer": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz",

View File

@ -23,6 +23,8 @@
"storybook:build": "cross-env NODE_ENV=test build-storybook" "storybook:build": "cross-env NODE_ENV=test build-storybook"
}, },
"dependencies": { "dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.3.1",
"@coingecko/cryptoformat": "^0.5.4", "@coingecko/cryptoformat": "^0.5.4",
"@loadable/component": "^5.15.2", "@loadable/component": "^5.15.2",
"@oceanprotocol/art": "^3.2.0", "@oceanprotocol/art": "^3.2.0",
@ -30,6 +32,7 @@
"@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/typographies": "^0.1.0",
"@oceanprotocol/use-dark-mode": "^2.4.3", "@oceanprotocol/use-dark-mode": "^2.4.3",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"@uiw/react-codemirror": "^4.19.5",
"@urql/exchange-refocus": "^1.0.0", "@urql/exchange-refocus": "^1.0.0",
"@walletconnect/web3-provider": "^1.8.0", "@walletconnect/web3-provider": "^1.8.0",
"axios": "^1.2.0", "axios": "^1.2.0",
@ -71,7 +74,7 @@
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-essentials": "^6.5.15", "@storybook/addon-essentials": "^6.5.13",
"@storybook/builder-webpack5": "^6.5.13", "@storybook/builder-webpack5": "^6.5.13",
"@storybook/manager-webpack5": "^6.5.13", "@storybook/manager-webpack5": "^6.5.13",
"@storybook/react": "^6.5.13", "@storybook/react": "^6.5.13",
@ -89,6 +92,7 @@
"@types/remove-markdown": "^0.3.1", "@types/remove-markdown": "^0.3.1",
"@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0", "@typescript-eslint/parser": "^5.43.0",
"@uiw/codemirror-themes": "^4.19.1",
"apollo": "^2.34.0", "apollo": "^2.34.0",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint": "^8.35.0", "eslint": "^8.35.0",

36
src/@utils/codemirror.ts Normal file
View File

@ -0,0 +1,36 @@
import { createTheme } from '@uiw/codemirror-themes'
import { json } from '@codemirror/lang-json'
export function checkJson(text: string) {
try {
JSON.parse(text)
return true
} catch (error) {
return false
}
}
declare type Theme = 'light' | 'dark'
export const oceanTheme = (marketTheme: Theme, field) => {
let textColor = 'var(--font-color-text)'
if (
(field.name === 'files[0].abi' ||
field.name === 'services[0].files[0].abi') &&
!checkJson(field.value)
) {
textColor = 'var(--brand-alert-red)'
}
return createTheme({
theme: marketTheme,
settings: {
background: 'var(--background-content)',
foreground: textColor,
selection: 'var(--background-highlight)',
lineHighlight: 'transparent',
gutterBackground: 'var(--background-body)',
gutterForeground: 'var(--font-color-text)'
},
styles: []
})
}
export const extensions = [json()]

View File

@ -1,4 +1,20 @@
import { Asset, DDO, Service } from '@oceanprotocol/lib' import {
ComputeEditForm,
MetadataEditForm
} from '@components/Asset/Edit/_types'
import { FormPublishData } from '@components/Publish/_types'
import {
Arweave,
Asset,
DDO,
FileInfo,
GraphqlQuery,
Ipfs,
Service,
Smartcontract,
UrlFile
} from '@oceanprotocol/lib'
import { checkJson } from './codemirror'
export function isValidDid(did: string): boolean { export function isValidDid(did: string): boolean {
const regex = /did:op:[A-Za-z0-9]{64}/ const regex = /did:op:[A-Za-z0-9]{64}/
@ -70,3 +86,105 @@ export function secondsToString(numberOfSeconds: number): string {
? `${seconds} second${numberEnding(seconds)}` ? `${seconds} second${numberEnding(seconds)}`
: 'less than a second' : 'less than a second'
} }
// this is required to make it work properly for preview/publish/edit/debug.
// TODO: find a way to only have FileInfo interface instead of FileExtended
interface FileExtended extends FileInfo {
url?: string
query?: string
transactionId?: string
address?: string
abi?: string
headers?: { key: string; value: string }[]
}
export function normalizeFile(
storageType: string,
file: FileExtended,
chainId: number
) {
let fileObj
const headersProvider = {}
const headers = file[0]?.headers || file?.headers
if (headers && headers.length > 0) {
headers.map((el) => {
headersProvider[el.key] = el.value
return el
})
}
switch (storageType) {
case 'ipfs': {
fileObj = {
type: storageType,
hash: file[0]?.url || file?.url
} as Ipfs
break
}
case 'arweave': {
fileObj = {
type: storageType,
transactionId:
file[0]?.url ||
file?.url ||
file[0]?.transactionId ||
file?.transactionId
} as Arweave
break
}
case 'graphql': {
fileObj = {
type: storageType,
url: file[0]?.url || file?.url,
query: file[0]?.query || file?.query,
headers: headersProvider
} as GraphqlQuery
break
}
case 'smartcontract': {
// clean obj
fileObj = {
chainId,
type: storageType,
address: file[0]?.address || file?.address || file[0]?.url || file?.url,
abi: checkJson(file[0]?.abi || file?.abi)
? JSON.parse(file[0]?.abi || file?.abi)
: file[0]?.abi || file?.abi
} as Smartcontract
break
}
default: {
fileObj = {
type: 'url',
index: 0,
url: file ? file[0]?.url || file?.url : null,
headers: headersProvider,
method: file.method
} as UrlFile
break
}
}
return fileObj
}
export function previewDebugPatch(
values: FormPublishData | Partial<MetadataEditForm> | ComputeEditForm,
chainId: number
) {
// handle file's object property dynamically
// without braking Yup and type validation
const buildValuesPreview = JSON.parse(JSON.stringify(values))
// fallback for edit mode under "edit compute settings"
if (!buildValuesPreview.services) return buildValuesPreview
const valuesService = buildValuesPreview.services
? buildValuesPreview.services[0]
: buildValuesPreview
valuesService.files[0] = normalizeFile(
valuesService.files[0].type,
valuesService.files[0],
chainId
)
return buildValuesPreview
}

View File

@ -22,6 +22,7 @@ import {
consumeMarketFixedSwapFee consumeMarketFixedSwapFee
} from '../../app.config' } from '../../app.config'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { getEncryptedFiles, getFileInfo } from './provider'
async function initializeProvider( async function initializeProvider(
asset: AssetExtended, asset: AssetExtended,
@ -63,6 +64,11 @@ export async function order(
const datatoken = new Datatoken(web3) const datatoken = new Datatoken(web3)
const config = getOceanConfig(asset.chainId) const config = getOceanConfig(asset.chainId)
const filesEncrypted = await getEncryptedFiles(
asset.services[0].files,
asset.services[0].serviceEndpoint
)
const initializeData = await initializeProvider( const initializeData = await initializeProvider(
asset, asset,
accountId, accountId,

View File

@ -1,5 +1,7 @@
import { import {
Arweave, Arweave,
GraphqlQuery,
Smartcontract,
ComputeAlgorithm, ComputeAlgorithm,
ComputeAsset, ComputeAsset,
ComputeEnvironment, ComputeEnvironment,
@ -11,9 +13,24 @@ import {
ProviderInstance, ProviderInstance,
UrlFile UrlFile
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { QueryHeader } from '@shared/FormInput/InputElement/Headers'
import Web3 from 'web3' import Web3 from 'web3'
import { AbiItem } from 'web3-utils/types'
import { getValidUntilTime } from './compute' import { getValidUntilTime } from './compute'
export async function getEncryptedFiles(
files: any,
providerUrl: string
): Promise<string> {
try {
// https://github.com/oceanprotocol/provider/blob/v4main/API.md#encrypt-endpoint
const response = await ProviderInstance.encrypt(files, providerUrl)
return response
} catch (error) {
console.error('Error parsing json: ' + error.message)
}
}
export async function initializeProviderForCompute( export async function initializeProviderForCompute(
dataset: AssetExtended, dataset: AssetExtended,
algorithm: AssetExtended, algorithm: AssetExtended,
@ -38,6 +55,11 @@ export async function initializeProviderForCompute(
) )
try { try {
const filesEncrypted = await getEncryptedFiles(
dataset.services[0].files,
dataset.services[0].serviceEndpoint
)
return await ProviderInstance.initializeCompute( return await ProviderInstance.initializeCompute(
[computeAsset], [computeAsset],
computeAlgo, computeAlgo,
@ -52,20 +74,6 @@ export async function initializeProviderForCompute(
} }
} }
// TODO: Why do we have these one line functions ?!?!?!
export async function getEncryptedFiles(
files: any,
providerUrl: string
): Promise<string> {
try {
// https://github.com/oceanprotocol/provider/blob/v4main/API.md#encrypt-endpoint
const response = await ProviderInstance.encrypt(files, providerUrl)
return response
} catch (error) {
console.error('Error parsing json: ' + error.message)
}
}
export async function getFileDidInfo( export async function getFileDidInfo(
did: string, did: string,
serviceId: string, serviceId: string,
@ -88,36 +96,73 @@ export async function getFileDidInfo(
export async function getFileInfo( export async function getFileInfo(
file: string, file: string,
providerUrl: string, providerUrl: string,
storageType: string storageType: string,
query?: string,
headers?: QueryHeader[],
abi?: string,
chainId?: number,
method?: string
): Promise<FileInfo[]> { ): Promise<FileInfo[]> {
try { try {
let response let response
const headersProvider = {}
if (headers?.length > 0) {
headers.map((el) => {
headersProvider[el.key] = el.value
return el
})
}
switch (storageType) { switch (storageType) {
case 'ipfs': { case 'ipfs': {
const fileIPFS: Ipfs = { const fileIPFS: Ipfs = {
type: 'ipfs', type: storageType,
hash: file hash: file
} }
response = await ProviderInstance.getFileInfo(fileIPFS, providerUrl) response = await ProviderInstance.getFileInfo(fileIPFS, providerUrl)
break break
} }
case 'arweave': { case 'arweave': {
const fileArweave: Arweave = { const fileArweave: Arweave = {
type: 'arweave', type: storageType,
transactionId: file transactionId: file
} }
response = await ProviderInstance.getFileInfo(fileArweave, providerUrl) response = await ProviderInstance.getFileInfo(fileArweave, providerUrl)
break break
} }
case 'graphql': {
const fileGraphql: GraphqlQuery = {
type: storageType,
url: file,
headers: headersProvider,
query
}
response = await ProviderInstance.getFileInfo(fileGraphql, providerUrl)
break
}
case 'smartcontract': {
// clean obj
const fileSmartContract: Smartcontract = {
chainId,
type: storageType,
address: file,
abi: JSON.parse(abi) as AbiItem
}
response = await ProviderInstance.getFileInfo(
fileSmartContract,
providerUrl
)
break
}
default: { default: {
const fileUrl: UrlFile = { const fileUrl: UrlFile = {
type: 'url', type: 'url',
index: 0, index: 0,
url: file, url: file,
method: 'get' headers: headersProvider,
method
} }
response = await ProviderInstance.getFileInfo(fileUrl, providerUrl) response = await ProviderInstance.getFileInfo(fileUrl, providerUrl)

View File

@ -9,6 +9,7 @@ export function sanitizeUrl(url: string) {
// check if the url is a google domain // check if the url is a google domain
export const isGoogleUrl = (url: string): boolean => { export const isGoogleUrl = (url: string): boolean => {
if (!url || !isUrl(url)) return if (!url || !isUrl(url)) return
const googleUrl = new URL(url) const googleUrl = new URL(url)
return googleUrl.hostname.endsWith('google.com') return googleUrl.hostname.endsWith('google.com')
} }

View File

@ -1,6 +1,7 @@
import { isCID } from '@utils/ipfs' import { isCID } from '@utils/ipfs'
import isUrl from 'is-url-superb' import isUrl from 'is-url-superb'
import * as Yup from 'yup' import * as Yup from 'yup'
import web3 from 'web3'
import { isGoogleUrl } from './url/index' import { isGoogleUrl } from './url/index'
export function testLinks(isEdit?: boolean) { export function testLinks(isEdit?: boolean) {
@ -15,16 +16,18 @@ export function testLinks(isEdit?: boolean) {
validField = true validField = true
break break
case 'url': case 'url':
case 'graphql':
validField = isUrl(value?.toString() || '') validField = isUrl(value?.toString() || '')
// if we're in publish, the field must be valid // if we're in publish, the field must be valid
if (!validField) { if (!validField) {
validField = false validField = false
errorMessage = 'Must be a valid url.' errorMessage = 'Must be a valid url.'
} }
// we allow submit if we're in the edit page and the field is empty // we allow submit on empty sample field
if ( if (
(!value?.toString() && isEdit) || !value?.toString() &&
(!value?.toString() && context.path === 'services[0].links[0].url') (context.path === 'links[0].url' ||
context.path === 'services[0].links[0].url')
) { ) {
validField = true validField = true
} }
@ -40,11 +43,17 @@ export function testLinks(isEdit?: boolean) {
errorMessage = !value?.toString() ? 'CID required.' : 'CID not valid.' errorMessage = !value?.toString() ? 'CID required.' : 'CID not valid.'
break break
case 'arweave': case 'arweave':
validField = !value?.toString().includes('http') validField = value && !value?.toString().includes('http')
errorMessage = !value?.toString() errorMessage = !value?.toString()
? 'Transaction ID required.' ? 'Transaction ID required.'
: 'Transaction ID not valid.' : 'Transaction ID not valid.'
break break
case 'smartcontract':
validField = web3.utils.isAddress(value?.toString())
errorMessage = !value?.toString()
? 'Address required.'
: 'Address not valid.'
break
} }
if (!validField) { if (!validField) {

View File

@ -0,0 +1,3 @@
.textblock {
margin-top: var(--spacer);
}

View File

@ -20,7 +20,7 @@ const mockMeta = {
value: '' value: ''
} }
const mockField = { const mockFieldUrl = {
value: 'https://hello.com', value: 'https://hello.com',
checked: false, checked: false,
onChange: jest.fn(), onChange: jest.fn(),
@ -28,6 +28,39 @@ const mockField = {
name: 'url' name: 'url'
} }
const mockFieldIpfs = {
value: 'bafkreicxccbk4blsx5qtovqfgsuutxjxom47dvyzyz3asi2ggjg5ipwlc4',
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'ipfs'
}
const mockFieldArwave = {
value: 'T6NL8Zc0LCbT3bF9HacAGQC4W0_hW7b3tXbm8OtWtlA',
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'arweave'
}
const mockFieldGraphQL = {
value:
'https://v4.subgraph.mumbai.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph',
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'graphql'
}
const mockFieldSM = {
value: '0x564955E9d25B49afE5Abd66966Ab4Bc9Ad55Fedb',
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'smartcontract'
}
const mockHelpers = { const mockHelpers = {
setValue: jest.fn(), setValue: jest.fn(),
setTouched: jest.fn() setTouched: jest.fn()
@ -35,7 +68,13 @@ const mockHelpers = {
const mockForm = { const mockForm = {
values: { values: {
services: [{ providerUrl: 'https://provider.url' }] services: [
{
providerUrl: {
url: 'https://v4.provider.mainnet.oceanprotocol.com'
}
}
]
}, },
errors: {}, errors: {},
touched: {}, touched: {},
@ -46,8 +85,12 @@ const mockForm = {
} }
describe('@shared/FormInput/InputElement/FilesInput', () => { describe('@shared/FormInput/InputElement/FilesInput', () => {
it('renders without crashing', async () => { it('renders URL without crashing', async () => {
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers]) ;(useField as jest.Mock).mockReturnValue([
mockFieldUrl,
mockMeta,
mockHelpers
])
;(checkValidProvider as jest.Mock).mockReturnValue(true) ;(checkValidProvider as jest.Mock).mockReturnValue(true)
;(getFileInfo as jest.Mock).mockReturnValue([ ;(getFileInfo as jest.Mock).mockReturnValue([
{ {
@ -59,7 +102,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
} }
]) ])
render(<FilesInput form={mockForm} field={mockField} {...props} />) render(<FilesInput form={mockForm} field={mockFieldUrl} {...props} />)
expect(screen.getByText('Validate')).toBeInTheDocument() expect(screen.getByText('Validate')).toBeInTheDocument()
fireEvent.click(screen.getByText('Validate')) fireEvent.click(screen.getByText('Validate'))
@ -85,7 +128,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
mockMeta, mockMeta,
mockHelpers mockHelpers
]) ])
render(<FilesInput {...props} field={mockField} />) render(<FilesInput {...props} field={mockFieldUrl} />)
expect(screen.getByText('https://hello.com')).toBeInTheDocument() expect(screen.getByText('https://hello.com')).toBeInTheDocument()
}) })
@ -106,7 +149,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
mockMeta, mockMeta,
mockHelpers mockHelpers
]) ])
render(<FilesInput {...props} field={mockField} />) render(<FilesInput {...props} field={mockFieldIpfs} />)
expect(screen.getByText('✓ File confirmed')).toBeInTheDocument() expect(screen.getByText('✓ File confirmed')).toBeInTheDocument()
}) })
@ -127,28 +170,10 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
mockMeta, mockMeta,
mockHelpers mockHelpers
]) ])
render(<FilesInput {...props} field={mockField} />) render(<FilesInput {...props} field={mockFieldArwave} />)
expect(screen.getByText('✓ File confirmed')).toBeInTheDocument() expect(screen.getByText('✓ File confirmed')).toBeInTheDocument()
}) })
it('renders fileinfo without contentType', () => {
;(useField as jest.Mock).mockReturnValue([
{
value: [
{
valid: true,
url: 'https://hello.com',
type: 'url',
contentLength: 100
}
]
},
mockMeta,
mockHelpers
])
render(<FilesInput {...props} field={mockField} />)
})
it('renders fileinfo placeholder when hideUrl is passed', () => { it('renders fileinfo placeholder when hideUrl is passed', () => {
;(useField as jest.Mock).mockReturnValue([ ;(useField as jest.Mock).mockReturnValue([
{ {
@ -163,9 +188,60 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
mockMeta, mockMeta,
mockHelpers mockHelpers
]) ])
render(<FilesInput {...props} field={mockField} />) render(<FilesInput {...props} field={mockFieldUrl} />)
expect( expect(
screen.getByText('https://oceanprotocol/placeholder') screen.getByText('https://oceanprotocol/placeholder')
).toBeInTheDocument() ).toBeInTheDocument()
}) })
it('renders fileinfo when graphql is valid', () => {
;(useField as jest.Mock).mockReturnValue([
{
value: [
{
type: 'graphql',
valid: true,
url: 'https://v4.subgraph.mumbai.oceanprotocol.com/subgraphs/name/oceanprotocol/ocean-subgraph',
query:
'query{\n nfts(orderBy: createdTimestamp,orderDirection:desc){\n id\n symbol\n createdTimestamp\n }\n }',
checksum: false
}
]
},
mockMeta,
mockHelpers
])
render(<FilesInput {...props} field={mockFieldGraphQL} />)
})
it('renders fileinfo when smart contract is valid', () => {
;(useField as jest.Mock).mockReturnValue([
{
value: [
{
chainId: 80001,
type: 'smartcontract',
address: '0x564955E9d25B49afE5Abd66966Ab4Bc9Ad55Fedb',
abi: {
inputs: [],
name: 'swapOceanFee',
outputs: [
{
internalType: 'uint256',
name: '',
type: 'uint256'
}
],
stateMutability: 'view',
type: 'function'
},
valid: true
}
]
},
mockMeta,
mockHelpers
])
render(<FilesInput {...props} field={mockFieldSM} />)
})
}) })

View File

@ -1,23 +1,37 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import { useField } from 'formik' import { Field, useField } from 'formik'
import FileInfo from './Info' import FileInfoDetails from './Info'
import UrlInput from '../URLInput' import UrlInput from '../URLInput'
import { InputProps } from '@shared/FormInput' import Input, { InputProps } from '@shared/FormInput'
import { getFileInfo, checkValidProvider } from '@utils/provider' import { getFileInfo, checkValidProvider } from '@utils/provider'
import { LoggerInstance } from '@oceanprotocol/lib' import { LoggerInstance, FileInfo } from '@oceanprotocol/lib'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import styles from './index.module.css'
import { useWeb3 } from '@context/Web3'
import InputHeaders from '../Headers'
import Button from '@shared/atoms/Button'
import Loader from '@shared/atoms/Loader'
import { checkJson } from '@utils/codemirror'
import { isGoogleUrl } from '@utils/url/index' import { isGoogleUrl } from '@utils/url/index'
import isUrl from 'is-url-superb'
import MethodInput from '../MethodInput'
export default function FilesInput(props: InputProps): ReactElement { export default function FilesInput(props: InputProps): ReactElement {
const [field, meta, helpers] = useField(props.name) const [field, meta, helpers] = useField(props.name)
const [isLoading, setIsLoading] = useState(false) const [isLoading, setIsLoading] = useState(false)
const [disabledButton, setDisabledButton] = useState(true)
const { asset } = useAsset() const { asset } = useAsset()
const { chainId } = useWeb3()
const providerUrl = props.form?.values?.services const providerUrl = props.form?.values?.services
? props.form?.values?.services[0].providerUrl.url ? props.form?.values?.services[0].providerUrl.url
: asset.services[0].serviceEndpoint : asset.services[0].serviceEndpoint
const storageType = field.value[0].type const storageType = field.value[0].type
const query = field.value[0].query || undefined
const abi = field.value[0].abi || undefined
const headers = field.value[0].headers || undefined
const method = field.value[0].method || undefined
async function handleValidation(e: React.SyntheticEvent, url: string) { async function handleValidation(e: React.SyntheticEvent, url: string) {
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf' // File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
@ -26,8 +40,7 @@ export default function FilesInput(props: InputProps): ReactElement {
try { try {
setIsLoading(true) setIsLoading(true)
// TODO: handled on provider if (isUrl(url) && isGoogleUrl(url)) {
if (isGoogleUrl(url)) {
throw Error( throw Error(
'Google Drive is not a supported hosting service. Please use an alternative.' 'Google Drive is not a supported hosting service. Please use an alternative.'
) )
@ -40,17 +53,39 @@ export default function FilesInput(props: InputProps): ReactElement {
'✗ Provider cannot be reached, please check status.oceanprotocol.com and try again later.' '✗ Provider cannot be reached, please check status.oceanprotocol.com and try again later.'
) )
const checkedFile = await getFileInfo(url, providerUrl, storageType) const checkedFile = await getFileInfo(
url,
providerUrl,
storageType,
query,
headers,
abi,
chainId,
method
)
// error if something's not right from response // error if something's not right from response
if (!checkedFile) if (!checkedFile)
throw Error('Could not fetch file info. Is your network down?') throw Error('Could not fetch file info. Is your network down?')
if (checkedFile[0].valid === false) if (checkedFile[0].valid === false)
throw Error('✗ No valid file detected. Check your URL and try again.') throw Error(
`✗ No valid file detected. Check your ${props.label} and details, and try again.`
)
// if all good, add file to formik state // if all good, add file to formik state
helpers.setValue([{ url, type: storageType, ...checkedFile[0] }]) helpers.setValue([
{
url,
providerUrl,
type: storageType,
query,
headers,
abi,
chainId,
...checkedFile[0]
}
])
} catch (error) { } catch (error) {
props.form.setFieldError(`${field.name}[0].url`, error.message) props.form.setFieldError(`${field.name}[0].url`, error.message)
LoggerInstance.error(error.message) LoggerInstance.error(error.message)
@ -59,6 +94,10 @@ export default function FilesInput(props: InputProps): ReactElement {
} }
} }
async function handleMethod(method: string) {
helpers.setValue([{ ...props.value[0], method }])
}
function handleClose() { function handleClose() {
helpers.setTouched(false) helpers.setTouched(false)
helpers.setValue([ helpers.setValue([
@ -66,21 +105,95 @@ export default function FilesInput(props: InputProps): ReactElement {
]) ])
} }
useEffect(() => {
storageType === 'graphql' && setDisabledButton(!providerUrl || !query)
storageType === 'smartcontract' &&
setDisabledButton(!providerUrl || !abi || !checkJson(abi))
storageType === 'url' && setDisabledButton(!providerUrl)
if (meta.error?.length > 0) {
const { url } = meta.error[0] as unknown as FileInfo
url && setDisabledButton(true)
}
}, [storageType, providerUrl, headers, query, abi, meta])
return ( return (
<> <>
{field?.value?.[0]?.valid === true || {field?.value?.[0]?.valid === true ||
field?.value?.[0]?.type === 'hidden' ? ( field?.value?.[0]?.type === 'hidden' ? (
<FileInfo file={field.value[0]} handleClose={handleClose} /> <FileInfoDetails file={field.value[0]} handleClose={handleClose} />
) : ( ) : (
<UrlInput <>
submitText="Validate" {props.methods && storageType === 'url' ? (
{...props} <MethodInput
name={`${field.name}[0].url`} {...props}
isLoading={isLoading} name={`${field.name}[0].url`}
checkUrl={true} isLoading={isLoading}
handleButtonClick={handleValidation} checkUrl={true}
storageType={storageType} handleButtonClick={handleMethod}
/> storageType={storageType}
/>
) : (
<UrlInput
submitText="Validate"
{...props}
name={`${field.name}[0].url`}
isLoading={isLoading}
hideButton={
storageType === 'graphql' || storageType === 'smartcontract'
}
checkUrl={true}
handleButtonClick={handleValidation}
storageType={storageType}
/>
)}
{props.innerFields && (
<>
<div className={`${styles.textblock}`}>
{props.innerFields &&
props.innerFields.map((innerField: any, i: number) => {
return (
<>
<Field
key={i}
component={
innerField.type === 'headers' ? InputHeaders : Input
}
{...innerField}
name={`${field.name}[0].${innerField.value}`}
value={field.value[0][innerField.value]}
/>
</>
)
})}
</div>
<Button
style="primary"
onClick={(e: React.SyntheticEvent) => {
e.preventDefault()
handleValidation(e, field.value[0].url)
}}
disabled={disabledButton}
>
{isLoading ? (
<Loader />
) : (
`submit ${
storageType === 'graphql'
? 'query'
: storageType === 'smartcontract'
? 'abi'
: 'url'
}`
)}
</Button>
</>
)}
</>
)} )}
</> </>
) )

View File

@ -0,0 +1,13 @@
.headersContainer {
display: flex;
justify-content: space-between;
gap: 0 calc(var(--spacer) / 4);
margin-bottom: var(--spacer);
}
.headersAddedContainer {
display: flex;
justify-content: space-between;
gap: 0 calc(var(--spacer) / 4);
margin-bottom: var(--spacer);
}

View File

@ -0,0 +1,135 @@
import React, { ChangeEvent, ReactElement, useEffect, useState } from 'react'
import InputElement from '../../InputElement'
import Label from '../../Label'
import styles from './index.module.css'
import Tooltip from '@shared/atoms/Tooltip'
import Markdown from '@shared/Markdown'
import Button from '@shared/atoms/Button'
import { InputProps } from '@shared/FormInput'
export interface QueryHeader {
key: string
value: string
}
export default function InputHeaders(props: InputProps): ReactElement {
const { label, help, prominentHelp, form, field } = props
const [currentKey, setCurrentKey] = useState('')
const [currentValue, setCurrentValue] = useState('')
const [disabledButton, setDisabledButton] = useState(true)
const [headers, setHeaders] = useState([] as QueryHeader[])
const addHeader = () => {
setHeaders((prev) => [
...prev,
{
key: currentKey,
value: currentValue
}
])
setCurrentKey('')
setCurrentValue('')
}
const removeHeader = (i: number) => {
const newHeaders = headers.filter((header, index) => index !== i)
setHeaders(newHeaders)
setCurrentKey('')
setCurrentValue('')
}
function handleChange(e: ChangeEvent<HTMLInputElement>) {
const checkType = e.target.name.search('key')
checkType > 0
? setCurrentKey(e.target.value)
: setCurrentValue(e.target.value)
return e
}
useEffect(() => {
form.setFieldValue(`${field.name}`, headers)
}, [headers])
useEffect(() => {
setDisabledButton(!currentKey || !currentValue)
}, [currentKey, currentValue])
return (
<div>
<Label htmlFor={props.name}>
{label}
{props.required && (
<span title="Required" className={styles.required}>
*
</span>
)}
{help && !prominentHelp && (
<Tooltip content={<Markdown text={help} />} />
)}
</Label>
<div className={styles.headersContainer}>
<InputElement
name={`${field.name}.key`}
placeholder={'key'}
value={`${currentKey}`}
onChange={handleChange}
/>
<InputElement
className={`${styles.input}`}
name={`${field.name}.value`}
placeholder={'value'}
value={`${currentValue}`}
onChange={handleChange}
/>
<Button
style="primary"
size="small"
onClick={(e: React.SyntheticEvent) => {
e.preventDefault()
addHeader()
}}
disabled={disabledButton}
>
add
</Button>
</div>
{headers.length > 0 &&
headers.map((header, i) => {
return (
<div className={styles.headersAddedContainer} key={`header_${i}`}>
<InputElement
name={`header[${i}].key`}
value={`${header.key}`}
disabled
/>
<InputElement
name={`header[${i}].key`}
value={`${header.value}`}
disabled
/>
<Button
style="primary"
size="small"
onClick={(e: React.SyntheticEvent) => {
e.preventDefault()
removeHeader(i)
}}
disabled={false}
>
remove
</Button>
</div>
)
})}
</div>
)
}

View File

@ -0,0 +1,44 @@
.input {
composes: input from '@shared/FormInput/InputElement/index.module.css';
}
.inputMethod {
composes: input from '@shared/FormInput/InputElement/index.module.css';
cursor: pointer;
outline: 0;
margin: 0;
display: inline-block;
min-width: 7rem;
padding: calc(var(--spacer) / 3) var(--spacer);
font-size: var(--font-size-base);
font-family: var(--font-family-base);
font-weight: var(--font-weight-bold);
text-transform: uppercase;
border-radius: var(--border-radius);
transition: 0.2s ease-out;
color: var(--brand-white);
box-shadow: 0 9px 18px 0 rgb(0 0 0 / 10%);
-webkit-user-select: none;
user-select: none;
text-align: center;
border-top-right-radius: var(--border-radius);
border-top-left-radius: 0;
border-bottom-left-radius: 0;
margin-top: 0;
margin-left: -1px;
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
white-space: nowrap;
background: var(--brand-gradient);
border: 0;
}
.hasError {
color: var(--brand-alert-red);
border-color: var(--brand-alert-red);
}
.error {
composes: error from '@shared/FormInput/index.module.css';
}

View File

@ -0,0 +1,58 @@
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
import MethodInput, { MethodInputProps } from './index'
import { useField } from 'formik'
jest.mock('formik')
const props: MethodInputProps = {
handleButtonClick: jest.fn(),
isLoading: false,
name: 'Hello Name'
}
const mockMeta = {
touched: false,
error: '',
initialError: '',
initialTouched: false,
initialValue: '',
value: ''
}
describe('@shared/FormInput/InputElement/MethodInput', () => {
it('renders without crashing', () => {
const mockField = {
value: '',
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'url',
method: 'get'
}
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta])
render(<MethodInput {...props} />)
expect(screen.getByRole('textbox')).toBeInTheDocument()
fireEvent.change(screen.getByRole('textbox'), {
target: { value: 'https://google.com' }
})
})
it('renders button enabled with value', () => {
const mockField = {
value: 'https://google.com',
checked: false,
onChange: jest.fn(),
onBlur: jest.fn(),
name: 'url',
method: 'get'
}
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta])
render(<MethodInput {...props} />)
expect(screen.getByRole('textbox')).toBeInTheDocument()
fireEvent.click(screen.getByRole('textbox'))
})
})

View File

@ -0,0 +1,69 @@
import React, { ReactElement, useEffect, useState } 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'
import isUrl from 'is-url-superb'
import { isCID } from '@utils/ipfs'
export interface MethodInputProps {
handleButtonClick(method: string): void
isLoading: boolean
name: string
checkUrl?: boolean
storageType?: string
hideButton?: boolean
}
export default function MethodInput({
handleButtonClick,
isLoading,
name,
checkUrl,
storageType,
...props
}: MethodInputProps): ReactElement {
const [field, meta] = useField(name)
const [methodSelected, setMethod] = useState(field?.value[0]?.method || 'get')
return (
<>
<InputGroup>
<InputElement
className={`${styles.input} ${
!isLoading && meta.error !== undefined && meta.touched
? styles.hasError
: ''
}`}
{...props}
{...field}
type="url"
/>
<InputElement
className={`${styles.inputMethod} ${
!isLoading && meta.error !== undefined && meta.touched
? styles.hasError
: ''
}`}
name={`${field.name}[0].method`}
value={methodSelected}
onChange={(e) => {
setMethod(e.currentTarget.value)
handleButtonClick(e.currentTarget.value)
}}
type="select"
options={['get', 'post']}
/>
</InputGroup>
{meta.touched && meta.error && (
<div className={styles.error}>
<ErrorMessage name={field.name} />
</div>
)}
</>
)
}

View File

@ -7,7 +7,7 @@ import InputGroup from '@shared/FormInput/InputGroup'
import InputElement from '@shared/FormInput/InputElement' import InputElement from '@shared/FormInput/InputElement'
import isUrl from 'is-url-superb' import isUrl from 'is-url-superb'
import { isCID } from '@utils/ipfs' import { isCID } from '@utils/ipfs'
import web3 from 'web3'
export interface URLInputProps { export interface URLInputProps {
submitText: string submitText: string
handleButtonClick(e: React.SyntheticEvent, data: string): void handleButtonClick(e: React.SyntheticEvent, data: string): void
@ -15,6 +15,7 @@ export interface URLInputProps {
name: string name: string
checkUrl?: boolean checkUrl?: boolean
storageType?: string storageType?: string
hideButton?: boolean
} }
export default function URLInput({ export default function URLInput({
@ -24,11 +25,12 @@ export default function URLInput({
name, name,
checkUrl, checkUrl,
storageType, storageType,
hideButton,
...props ...props
}: URLInputProps): ReactElement { }: URLInputProps): ReactElement {
const [field, meta] = useField(name) const [field, meta] = useField(name)
const [isButtonDisabled, setIsButtonDisabled] = useState(true) const [isButtonDisabled, setIsButtonDisabled] = useState(true)
const inputValues = (props as any)?.value
useEffect(() => { useEffect(() => {
if (!field?.value) return if (!field?.value) return
@ -37,10 +39,15 @@ export default function URLInput({
field.value === '' || field.value === '' ||
(checkUrl && storageType === 'url' && !isUrl(field.value)) || (checkUrl && storageType === 'url' && !isUrl(field.value)) ||
(checkUrl && storageType === 'ipfs' && !isCID(field.value)) || (checkUrl && storageType === 'ipfs' && !isCID(field.value)) ||
(checkUrl &&
storageType === 'graphql' &&
!isCID(field.value) &&
!inputValues[0]?.query) ||
field.value.includes('javascript:') || field.value.includes('javascript:') ||
(storageType === 'smartcontract' && !inputValues[0]?.abi) ||
meta?.error meta?.error
) )
}, [field?.value, meta?.error]) }, [field?.value, meta?.error, inputValues])
return ( return (
<> <>
@ -56,17 +63,19 @@ export default function URLInput({
type="url" type="url"
/> />
<Button {!hideButton && (
style="primary" <Button
size="small" style="primary"
onClick={(e: React.SyntheticEvent) => { size="small"
e.preventDefault() onClick={(e: React.SyntheticEvent) => {
handleButtonClick(e, field.value) e.preventDefault()
}} handleButtonClick(e, field.value)
disabled={isButtonDisabled} }}
> disabled={isButtonDisabled}
{isLoading ? <Loader /> : submitText} >
</Button> {isLoading ? <Loader /> : submitText}
</Button>
)}
</InputGroup> </InputGroup>
{meta.touched && meta.error && ( {meta.touched && meta.error && (

View File

@ -47,7 +47,7 @@
display: none; display: none;
} }
.textarea { .codemirror, .textarea {
composes: input; composes: input;
height: auto; height: auto;
} }

View File

@ -1,4 +1,5 @@
import React, { ReactElement, useCallback, useState } from 'react' import React, { ReactElement } from 'react'
import CodeMirror from '@uiw/react-codemirror'
import styles from './index.module.css' import styles from './index.module.css'
import { InputProps } from '..' import { InputProps } from '..'
import FilesInput from './FilesInput' import FilesInput from './FilesInput'
@ -12,6 +13,9 @@ import InputRadio from './Radio'
import ContainerInput from '@shared/FormInput/InputElement/ContainerInput' import ContainerInput from '@shared/FormInput/InputElement/ContainerInput'
import TagsAutoComplete from './TagsAutoComplete' import TagsAutoComplete from './TagsAutoComplete'
import TabsFile from '@shared/atoms/TabsFile' import TabsFile from '@shared/atoms/TabsFile'
import useDarkMode from '@oceanprotocol/use-dark-mode'
import appConfig from '../../../../../app.config'
import { extensions, oceanTheme } from '@utils/codemirror'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
@ -56,6 +60,7 @@ export default function InputElement({
...props ...props
}: InputProps): ReactElement { }: InputProps): ReactElement {
const styleClasses = cx({ select: true, [size]: size }) const styleClasses = cx({ select: true, [size]: size })
const darkMode = useDarkMode(false, appConfig?.darkModeConfig)
switch (props.type) { switch (props.type) {
case 'select': { case 'select': {
@ -100,8 +105,31 @@ export default function InputElement({
}) })
}) })
return <TabsFile items={tabs} className={styles.pricing} /> return (
<TabsFile
items={tabs}
key={`tabFile_${props.name}`}
className={styles.pricing}
/>
)
} }
case 'codeeditor':
return (
<CodeMirror
id={props.name}
className={styles.codemirror}
value={`${props.value ? props.value : ''}`}
height="200px"
placeholder={props.placeholder}
theme={oceanTheme(darkMode ? 'dark' : 'light', props)}
extensions={[extensions]}
onChange={(value) => {
form.setFieldValue(`${props.name}`, value)
}}
/>
)
case 'textarea': case 'textarea':
return <textarea id={props.name} className={styles.textarea} {...props} /> return <textarea id={props.name} className={styles.textarea} {...props} />

View File

@ -33,6 +33,8 @@ export interface InputProps {
options?: string[] | AssetSelectionAsset[] | BoxSelectionOption[] options?: string[] | AssetSelectionAsset[] | BoxSelectionOption[]
sortOptions?: boolean sortOptions?: boolean
fields?: FieldInputProps<any>[] fields?: FieldInputProps<any>[]
methods?: boolean
innerFields?: any
additionalComponent?: ReactElement additionalComponent?: ReactElement
value?: string | number value?: string | number
onChange?( onChange?(
@ -77,8 +79,9 @@ function checkError(
if ( if (
(form?.touched?.[parsedFieldName[0]]?.[parsedFieldName[1]] && (form?.touched?.[parsedFieldName[0]]?.[parsedFieldName[1]] &&
form?.errors?.[parsedFieldName[0]]?.[parsedFieldName[1]]) || form?.errors?.[parsedFieldName[0]]?.[parsedFieldName[1]]) ||
(form?.touched[field.name] && (form?.touched[field?.name] &&
form?.errors[field.name] && form?.errors[field?.name] &&
field.name !== 'files' &&
field.name !== 'links') field.name !== 'links')
) { ) {
return true return true

View File

@ -65,13 +65,22 @@
border-top: 0; border-top: 0;
} }
.tabLabel { .tabPanel {
color: var(--font-color-text); color: var(--font-color-text);
font-size: var(--font-size-small); font-size: var(--font-size-base);
font-family: var(--font-family-heading); font-family: var(--font-family-heading);
line-height: 1.2; line-height: 1.2;
display: block; display: block;
margin-bottom: calc(var(--spacer) / 2); margin-bottom: 0;
}
.tabPanel label {
font-size: var(--font-size-small) !important;
}
.tabLabel {
display: block;
margin-bottom: calc(var(--spacer) / 4);
} }
@media (min-width: 40rem) { @media (min-width: 40rem) {

View File

@ -25,7 +25,18 @@ export default function TabsFile({
className className
}: TabsProps): ReactElement { }: TabsProps): ReactElement {
const { values, setFieldValue } = useFormikContext<FormPublishData>() const { values, setFieldValue } = useFormikContext<FormPublishData>()
const [tabIndex, setTabIndex] = useState(0) const initialState = () => {
const index = items.findIndex((tab: any) => {
// fallback for edit mode (starts at index 0 with hidden element)
if (!values?.services) return 0
return tab.field.value === values.services[0].files[0].type
})
return index < 0 ? 0 : index
}
const [tabIndex, setTabIndex] = useState(initialState)
// hide tabs if are hidden // hide tabs if are hidden
const isHidden = items[tabIndex].props.value[0].type === 'hidden' const isHidden = items[tabIndex].props.value[0].type === 'hidden'
@ -76,7 +87,10 @@ export default function TabsFile({
{items.map((item, index) => { {items.map((item, index) => {
return ( return (
<> <>
<TabPanel key={`tabpanel_${items[tabIndex].props.name}_${index}`}> <TabPanel
key={`tabpanel_${items[tabIndex].props.name}_${index}`}
className={styles.tabPanel}
>
{!isHidden && ( {!isHidden && (
<label className={styles.tabLabel}> <label className={styles.tabLabel}>
{item.field.label} {item.field.label}

View File

@ -22,7 +22,7 @@ export default function AssetActions({
}: { }: {
asset: AssetExtended asset: AssetExtended
}): ReactElement { }): ReactElement {
const { accountId, balance, web3 } = useWeb3() const { accountId, balance, web3, chainId } = useWeb3()
const { isAssetNetwork } = useAsset() const { isAssetNetwork } = useAsset()
const newCancelToken = useCancelToken() const newCancelToken = useCancelToken()
const isMounted = useIsMounted() const isMounted = useIsMounted()
@ -56,13 +56,25 @@ export default function AssetActions({
? formikState?.values?.services[0].files[0].type ? formikState?.values?.services[0].files[0].type
: null : null
// TODO: replace 'any' with correct typing
const file = formikState?.values?.services[0].files[0] as any
const query = file?.query || undefined
const abi = file?.abi || undefined
const headers = file?.headers || undefined
const method = file?.method || undefined
try { try {
const fileInfoResponse = formikState?.values?.services?.[0].files?.[0] const fileInfoResponse = formikState?.values?.services?.[0].files?.[0]
.url .url
? await getFileInfo( ? await getFileInfo(
formikState?.values?.services?.[0].files?.[0].url, formikState?.values?.services?.[0].files?.[0].url,
providerUrl, providerUrl,
storageType storageType,
query,
headers,
abi,
chainId,
method
) )
: await getFileDidInfo(asset?.id, asset?.services[0]?.id, providerUrl) : await getFileDidInfo(asset?.id, asset?.services[0]?.id, providerUrl)

View File

@ -4,6 +4,7 @@ import DebugOutput from '@shared/DebugOutput'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import { transformComputeFormToServiceComputeOptions } from '@utils/compute' import { transformComputeFormToServiceComputeOptions } from '@utils/compute'
import { ComputeEditForm } from './_types' import { ComputeEditForm } from './_types'
import { previewDebugPatch } from '@utils/ddo'
export default function DebugEditCompute({ export default function DebugEditCompute({
values, values,
@ -12,6 +13,7 @@ export default function DebugEditCompute({
values: ComputeEditForm values: ComputeEditForm
asset: Asset asset: Asset
}): ReactElement { }): ReactElement {
const [valuePreview, setValuePreview] = useState({})
const [formTransformed, setFormTransformed] = const [formTransformed, setFormTransformed] =
useState<ServiceComputeOptions>() useState<ServiceComputeOptions>()
const newCancelToken = useCancelToken() const newCancelToken = useCancelToken()
@ -27,11 +29,12 @@ export default function DebugEditCompute({
setFormTransformed(privacy) setFormTransformed(privacy)
} }
transformValues() transformValues()
setValuePreview(previewDebugPatch(values, asset.chainId))
}, [values, asset]) }, [values, asset])
return ( return (
<> <>
<DebugOutput title="Collected Form Values" output={values} /> <DebugOutput title="Collected Form Values" output={valuePreview} />
<DebugOutput title="Transformed Form Values" output={formTransformed} /> <DebugOutput title="Transformed Form Values" output={formTransformed} />
</> </>
) )

View File

@ -1,8 +1,8 @@
import { Asset, Metadata, Service } from '@oceanprotocol/lib' import { Asset, Metadata, Service } from '@oceanprotocol/lib'
import React, { ReactElement } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import DebugOutput from '@shared/DebugOutput' import DebugOutput from '@shared/DebugOutput'
import { MetadataEditForm } from './_types' import { MetadataEditForm } from './_types'
import { mapTimeoutStringToSeconds } from '@utils/ddo' import { mapTimeoutStringToSeconds, previewDebugPatch } from '@utils/ddo'
import { sanitizeUrl } from '@utils/url' import { sanitizeUrl } from '@utils/url'
export default function DebugEditMetadata({ export default function DebugEditMetadata({
@ -12,6 +12,7 @@ export default function DebugEditMetadata({
values: Partial<MetadataEditForm> values: Partial<MetadataEditForm>
asset: Asset asset: Asset
}): ReactElement { }): ReactElement {
const [valuePreview, setValuePreview] = useState({})
const linksTransformed = values.links?.length && const linksTransformed = values.links?.length &&
values.links[0].valid && [sanitizeUrl(values.links[0].url)] values.links[0].valid && [sanitizeUrl(values.links[0].url)]
@ -32,9 +33,13 @@ export default function DebugEditMetadata({
services: [updatedService] services: [updatedService]
} }
useEffect(() => {
setValuePreview(previewDebugPatch(values, asset.chainId))
}, [asset.chainId, values])
return ( return (
<> <>
<DebugOutput title="Collected Form Values" output={values} /> <DebugOutput title="Collected Form Values" output={valuePreview} />
<DebugOutput title="Transformed Asset Values" output={updatedAsset} /> <DebugOutput title="Transformed Asset Values" output={updatedAsset} />
</> </>
) )

View File

@ -16,7 +16,7 @@ import { useWeb3 } from '@context/Web3'
import { useUserPreferences } from '@context/UserPreferences' import { useUserPreferences } from '@context/UserPreferences'
import Web3Feedback from '@shared/Web3Feedback' import Web3Feedback from '@shared/Web3Feedback'
import FormEditMetadata from './FormEditMetadata' import FormEditMetadata from './FormEditMetadata'
import { mapTimeoutStringToSeconds } from '@utils/ddo' import { mapTimeoutStringToSeconds, normalizeFile } from '@utils/ddo'
import styles from './index.module.css' import styles from './index.module.css'
import content from '../../../../content/pages/editMetadata.json' import content from '../../../../content/pages/editMetadata.json'
import { useAbortController } from '@hooks/useAbortController' import { useAbortController } from '@hooks/useAbortController'
@ -36,7 +36,7 @@ export default function Edit({
}): ReactElement { }): ReactElement {
const { debug } = useUserPreferences() const { debug } = useUserPreferences()
const { fetchAsset, isAssetNetwork, assetState } = useAsset() const { fetchAsset, isAssetNetwork, assetState } = useAsset()
const { accountId, web3 } = useWeb3() const { accountId, web3, chainId } = useWeb3()
const newAbortController = useAbortController() const newAbortController = useAbortController()
const [success, setSuccess] = useState<string>() const [success, setSuccess] = useState<string>()
const [paymentCollector, setPaymentCollector] = useState<string>() const [paymentCollector, setPaymentCollector] = useState<string>()
@ -115,19 +115,9 @@ export default function Edit({
const file = { const file = {
nftAddress: asset.nftAddress, nftAddress: asset.nftAddress,
datatokenAddress: asset.services[0].datatokenAddress, datatokenAddress: asset.services[0].datatokenAddress,
files: [ files: [normalizeFile(values.files[0].type, values.files[0], chainId)]
{
type: values.files[0].type,
index: 0,
[values.files[0].type === 'ipfs'
? 'hash'
: values.files[0].type === 'arweave'
? 'transactionId'
: 'url']: values.files[0].url,
method: 'GET'
}
]
} }
const filesEncrypted = await getEncryptedFiles( const filesEncrypted = await getEncryptedFiles(
file, file,
asset.services[0].serviceEndpoint asset.services[0].serviceEndpoint

View File

@ -5,14 +5,19 @@ import { useFormikContext } from 'formik'
import { transformPublishFormToDdo } from '../_utils' import { transformPublishFormToDdo } from '../_utils'
import styles from './index.module.css' import styles from './index.module.css'
import { DDO } from '@oceanprotocol/lib' import { DDO } from '@oceanprotocol/lib'
import { previewDebugPatch } from '@utils/ddo'
import { useWeb3 } from '@context/Web3'
export default function Debug(): ReactElement { export default function Debug(): ReactElement {
const { values } = useFormikContext<FormPublishData>() const { values } = useFormikContext<FormPublishData>()
const [valuePreview, setValuePreview] = useState({})
const [ddo, setDdo] = useState<DDO>() const [ddo, setDdo] = useState<DDO>()
const { chainId } = useWeb3()
useEffect(() => { useEffect(() => {
async function makeDdo() { async function makeDdo() {
const ddo = await transformPublishFormToDdo(values) const ddo = await transformPublishFormToDdo(values)
setValuePreview(previewDebugPatch(values, chainId))
setDdo(ddo) setDdo(ddo)
} }
makeDdo() makeDdo()
@ -20,7 +25,7 @@ export default function Debug(): ReactElement {
return ( return (
<div className={styles.debug}> <div className={styles.debug}>
<DebugOutput title="Collected Form Values" output={values} /> <DebugOutput title="Collected Form Values" output={valuePreview} />
<DebugOutput title="Transformed DDO Values" output={ddo} /> <DebugOutput title="Transformed DDO Values" output={ddo} />
</div> </div>
) )

View File

@ -13,7 +13,7 @@ import {
Service, Service,
ZERO_ADDRESS ZERO_ADDRESS
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { mapTimeoutStringToSeconds } from '@utils/ddo' import { mapTimeoutStringToSeconds, normalizeFile } from '@utils/ddo'
import { generateNftCreateData } from '@utils/nft' import { generateNftCreateData } from '@utils/nft'
import { getEncryptedFiles } from '@utils/provider' import { getEncryptedFiles } from '@utils/provider'
import slugify from 'slugify' import slugify from 'slugify'
@ -138,23 +138,10 @@ export async function transformPublishFormToDdo(
}) })
} }
// this is the default format hardcoded
const file = { const file = {
nftAddress, nftAddress,
datatokenAddress, datatokenAddress,
files: [ files: [normalizeFile(files[0].type, files[0], chainId)]
{
type: files[0].type,
index: 0,
[files[0].type === 'ipfs'
? 'hash'
: files[0].type === 'arweave'
? 'transactionId'
: 'url']: files[0].url,
method: 'GET'
}
]
} }
const filesEncrypted = const filesEncrypted =

View File

@ -33,8 +33,7 @@ const validationService = {
files: Yup.array<FileInfo[]>() files: Yup.array<FileInfo[]>()
.of( .of(
Yup.object().shape({ Yup.object().shape({
url: testLinks().required('Required'), url: testLinks(),
valid: Yup.boolean().isTrue().required('File must be valid.') valid: Yup.boolean().isTrue().required('File must be valid.')
}) })
) )

View File

@ -126,6 +126,12 @@ ul li {
color: var(--background-body); color: var(--background-body);
} }
/* fix required for codemirror */
.cm-scroller ::selection {
color: var(--font-color-heading);
}
form, form,
fieldset { fieldset {
border: 0; border: 0;

View File

@ -1,7 +1,7 @@
{ {
"compilerOptions": { "compilerOptions": {
"baseUrl": ".", // This must be specified if "paths" is. "baseUrl": ".", // This must be specified if "paths" is.
"target": "es5", "target": "es2015",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,