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,8 +64,73 @@
"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": 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,
"required": true

View File

@ -138,8 +138,73 @@
"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,
"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,
"required": true

409
package-lock.json generated
View File

@ -10,6 +10,8 @@
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.3.1",
"@coingecko/cryptoformat": "^0.5.4",
"@loadable/component": "^5.15.2",
"@oceanprotocol/art": "^3.2.0",
@ -17,6 +19,7 @@
"@oceanprotocol/typographies": "^0.1.0",
"@oceanprotocol/use-dark-mode": "^2.4.3",
"@tippyjs/react": "^4.2.6",
"@uiw/react-codemirror": "^4.19.5",
"@urql/exchange-refocus": "^1.0.0",
"@walletconnect/web3-provider": "^1.8.0",
"axios": "^1.2.0",
@ -58,7 +61,7 @@
"yup": "^0.32.11"
},
"devDependencies": {
"@storybook/addon-essentials": "^6.5.15",
"@storybook/addon-essentials": "^6.5.13",
"@storybook/builder-webpack5": "^6.5.13",
"@storybook/manager-webpack5": "^6.5.13",
"@storybook/react": "^6.5.13",
@ -76,6 +79,7 @@
"@types/remove-markdown": "^0.3.1",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0",
"@uiw/codemirror-themes": "^4.19.1",
"apollo": "^2.34.0",
"cross-env": "^7.0.3",
"eslint": "^8.35.0",
@ -2039,6 +2043,102 @@
"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": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@coingecko/cryptoformat/-/cryptoformat-0.5.4.tgz",
@ -4112,6 +4212,36 @@
"@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": {
"version": "5.15.2",
"resolved": "https://registry.npmjs.org/@loadable/component/-/component-5.15.2.tgz",
@ -17736,6 +17866,67 @@
"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": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@urql/core/-/core-3.0.3.tgz",
@ -22190,6 +22381,20 @@
"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": {
"version": "1.0.6",
"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==",
"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": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@ -43370,6 +43580,11 @@
"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": {
"version": "0.3.0",
"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"
}
},
"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": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz",
@ -48351,6 +48571,96 @@
"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": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/@coingecko/cryptoformat/-/cryptoformat-0.5.4.tgz",
@ -49918,6 +50228,36 @@
"@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": {
"version": "5.15.2",
"resolved": "https://registry.npmjs.org/@loadable/component/-/component-5.15.2.tgz",
@ -60444,6 +60784,44 @@
"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": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@urql/core/-/core-3.0.3.tgz",
@ -64050,6 +64428,20 @@
"integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==",
"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": {
"version": "1.0.6",
"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==",
"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": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@ -80572,6 +80969,11 @@
"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": {
"version": "0.3.0",
"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"
}
},
"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": {
"version": "3.0.0",
"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"
},
"dependencies": {
"@codemirror/lang-json": "^6.0.1",
"@codemirror/language": "^6.3.1",
"@coingecko/cryptoformat": "^0.5.4",
"@loadable/component": "^5.15.2",
"@oceanprotocol/art": "^3.2.0",
@ -30,6 +32,7 @@
"@oceanprotocol/typographies": "^0.1.0",
"@oceanprotocol/use-dark-mode": "^2.4.3",
"@tippyjs/react": "^4.2.6",
"@uiw/react-codemirror": "^4.19.5",
"@urql/exchange-refocus": "^1.0.0",
"@walletconnect/web3-provider": "^1.8.0",
"axios": "^1.2.0",
@ -71,7 +74,7 @@
"yup": "^0.32.11"
},
"devDependencies": {
"@storybook/addon-essentials": "^6.5.15",
"@storybook/addon-essentials": "^6.5.13",
"@storybook/builder-webpack5": "^6.5.13",
"@storybook/manager-webpack5": "^6.5.13",
"@storybook/react": "^6.5.13",
@ -89,6 +92,7 @@
"@types/remove-markdown": "^0.3.1",
"@typescript-eslint/eslint-plugin": "^5.43.0",
"@typescript-eslint/parser": "^5.43.0",
"@uiw/codemirror-themes": "^4.19.1",
"apollo": "^2.34.0",
"cross-env": "^7.0.3",
"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 {
const regex = /did:op:[A-Za-z0-9]{64}/
@ -70,3 +86,105 @@ export function secondsToString(numberOfSeconds: number): string {
? `${seconds} second${numberEnding(seconds)}`
: '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
} from '../../app.config'
import { toast } from 'react-toastify'
import { getEncryptedFiles, getFileInfo } from './provider'
async function initializeProvider(
asset: AssetExtended,
@ -63,6 +64,11 @@ export async function order(
const datatoken = new Datatoken(web3)
const config = getOceanConfig(asset.chainId)
const filesEncrypted = await getEncryptedFiles(
asset.services[0].files,
asset.services[0].serviceEndpoint
)
const initializeData = await initializeProvider(
asset,
accountId,

View File

@ -1,5 +1,7 @@
import {
Arweave,
GraphqlQuery,
Smartcontract,
ComputeAlgorithm,
ComputeAsset,
ComputeEnvironment,
@ -11,9 +13,24 @@ import {
ProviderInstance,
UrlFile
} from '@oceanprotocol/lib'
import { QueryHeader } from '@shared/FormInput/InputElement/Headers'
import Web3 from 'web3'
import { AbiItem } from 'web3-utils/types'
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(
dataset: AssetExtended,
algorithm: AssetExtended,
@ -38,6 +55,11 @@ export async function initializeProviderForCompute(
)
try {
const filesEncrypted = await getEncryptedFiles(
dataset.services[0].files,
dataset.services[0].serviceEndpoint
)
return await ProviderInstance.initializeCompute(
[computeAsset],
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(
did: string,
serviceId: string,
@ -88,36 +96,73 @@ export async function getFileDidInfo(
export async function getFileInfo(
file: string,
providerUrl: string,
storageType: string
storageType: string,
query?: string,
headers?: QueryHeader[],
abi?: string,
chainId?: number,
method?: string
): Promise<FileInfo[]> {
try {
let response
const headersProvider = {}
if (headers?.length > 0) {
headers.map((el) => {
headersProvider[el.key] = el.value
return el
})
}
switch (storageType) {
case 'ipfs': {
const fileIPFS: Ipfs = {
type: 'ipfs',
type: storageType,
hash: file
}
response = await ProviderInstance.getFileInfo(fileIPFS, providerUrl)
break
}
case 'arweave': {
const fileArweave: Arweave = {
type: 'arweave',
type: storageType,
transactionId: file
}
response = await ProviderInstance.getFileInfo(fileArweave, providerUrl)
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: {
const fileUrl: UrlFile = {
type: 'url',
index: 0,
url: file,
method: 'get'
headers: headersProvider,
method
}
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
export const isGoogleUrl = (url: string): boolean => {
if (!url || !isUrl(url)) return
const googleUrl = new URL(url)
return googleUrl.hostname.endsWith('google.com')
}

View File

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

View File

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

View File

@ -20,7 +20,7 @@ const mockMeta = {
value: ''
}
const mockField = {
const mockFieldUrl = {
value: 'https://hello.com',
checked: false,
onChange: jest.fn(),
@ -28,6 +28,39 @@ const mockField = {
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 = {
setValue: jest.fn(),
setTouched: jest.fn()
@ -35,7 +68,13 @@ const mockHelpers = {
const mockForm = {
values: {
services: [{ providerUrl: 'https://provider.url' }]
services: [
{
providerUrl: {
url: 'https://v4.provider.mainnet.oceanprotocol.com'
}
}
]
},
errors: {},
touched: {},
@ -46,8 +85,12 @@ const mockForm = {
}
describe('@shared/FormInput/InputElement/FilesInput', () => {
it('renders without crashing', async () => {
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers])
it('renders URL without crashing', async () => {
;(useField as jest.Mock).mockReturnValue([
mockFieldUrl,
mockMeta,
mockHelpers
])
;(checkValidProvider as jest.Mock).mockReturnValue(true)
;(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()
fireEvent.click(screen.getByText('Validate'))
@ -85,7 +128,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
mockMeta,
mockHelpers
])
render(<FilesInput {...props} field={mockField} />)
render(<FilesInput {...props} field={mockFieldUrl} />)
expect(screen.getByText('https://hello.com')).toBeInTheDocument()
})
@ -106,7 +149,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
mockMeta,
mockHelpers
])
render(<FilesInput {...props} field={mockField} />)
render(<FilesInput {...props} field={mockFieldIpfs} />)
expect(screen.getByText('✓ File confirmed')).toBeInTheDocument()
})
@ -127,28 +170,10 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
mockMeta,
mockHelpers
])
render(<FilesInput {...props} field={mockField} />)
render(<FilesInput {...props} field={mockFieldArwave} />)
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', () => {
;(useField as jest.Mock).mockReturnValue([
{
@ -163,9 +188,60 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
mockMeta,
mockHelpers
])
render(<FilesInput {...props} field={mockField} />)
render(<FilesInput {...props} field={mockFieldUrl} />)
expect(
screen.getByText('https://oceanprotocol/placeholder')
).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 { useField } from 'formik'
import FileInfo from './Info'
import { Field, useField } from 'formik'
import FileInfoDetails from './Info'
import UrlInput from '../URLInput'
import { InputProps } from '@shared/FormInput'
import Input, { InputProps } from '@shared/FormInput'
import { getFileInfo, checkValidProvider } from '@utils/provider'
import { LoggerInstance } from '@oceanprotocol/lib'
import { LoggerInstance, FileInfo } from '@oceanprotocol/lib'
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 isUrl from 'is-url-superb'
import MethodInput from '../MethodInput'
export default function FilesInput(props: InputProps): ReactElement {
const [field, meta, helpers] = useField(props.name)
const [isLoading, setIsLoading] = useState(false)
const [disabledButton, setDisabledButton] = useState(true)
const { asset } = useAsset()
const { chainId } = useWeb3()
const providerUrl = props.form?.values?.services
? props.form?.values?.services[0].providerUrl.url
: asset.services[0].serviceEndpoint
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) {
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
@ -26,8 +40,7 @@ export default function FilesInput(props: InputProps): ReactElement {
try {
setIsLoading(true)
// TODO: handled on provider
if (isGoogleUrl(url)) {
if (isUrl(url) && isGoogleUrl(url)) {
throw Error(
'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.'
)
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
if (!checkedFile)
throw Error('Could not fetch file info. Is your network down?')
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
helpers.setValue([{ url, type: storageType, ...checkedFile[0] }])
helpers.setValue([
{
url,
providerUrl,
type: storageType,
query,
headers,
abi,
chainId,
...checkedFile[0]
}
])
} catch (error) {
props.form.setFieldError(`${field.name}[0].url`, 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() {
helpers.setTouched(false)
helpers.setValue([
@ -66,22 +105,96 @@ 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 (
<>
{field?.value?.[0]?.valid === true ||
field?.value?.[0]?.type === 'hidden' ? (
<FileInfo file={field.value[0]} handleClose={handleClose} />
<FileInfoDetails file={field.value[0]} handleClose={handleClose} />
) : (
<>
{props.methods && storageType === 'url' ? (
<MethodInput
{...props}
name={`${field.name}[0].url`}
isLoading={isLoading}
checkUrl={true}
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 isUrl from 'is-url-superb'
import { isCID } from '@utils/ipfs'
import web3 from 'web3'
export interface URLInputProps {
submitText: string
handleButtonClick(e: React.SyntheticEvent, data: string): void
@ -15,6 +15,7 @@ export interface URLInputProps {
name: string
checkUrl?: boolean
storageType?: string
hideButton?: boolean
}
export default function URLInput({
@ -24,11 +25,12 @@ export default function URLInput({
name,
checkUrl,
storageType,
hideButton,
...props
}: URLInputProps): ReactElement {
const [field, meta] = useField(name)
const [isButtonDisabled, setIsButtonDisabled] = useState(true)
const inputValues = (props as any)?.value
useEffect(() => {
if (!field?.value) return
@ -37,10 +39,15 @@ export default function URLInput({
field.value === '' ||
(checkUrl && storageType === 'url' && !isUrl(field.value)) ||
(checkUrl && storageType === 'ipfs' && !isCID(field.value)) ||
(checkUrl &&
storageType === 'graphql' &&
!isCID(field.value) &&
!inputValues[0]?.query) ||
field.value.includes('javascript:') ||
(storageType === 'smartcontract' && !inputValues[0]?.abi) ||
meta?.error
)
}, [field?.value, meta?.error])
}, [field?.value, meta?.error, inputValues])
return (
<>
@ -56,6 +63,7 @@ export default function URLInput({
type="url"
/>
{!hideButton && (
<Button
style="primary"
size="small"
@ -67,6 +75,7 @@ export default function URLInput({
>
{isLoading ? <Loader /> : submitText}
</Button>
)}
</InputGroup>
{meta.touched && meta.error && (

View File

@ -47,7 +47,7 @@
display: none;
}
.textarea {
.codemirror, .textarea {
composes: input;
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 { InputProps } from '..'
import FilesInput from './FilesInput'
@ -12,6 +13,9 @@ import InputRadio from './Radio'
import ContainerInput from '@shared/FormInput/InputElement/ContainerInput'
import TagsAutoComplete from './TagsAutoComplete'
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)
@ -56,6 +60,7 @@ export default function InputElement({
...props
}: InputProps): ReactElement {
const styleClasses = cx({ select: true, [size]: size })
const darkMode = useDarkMode(false, appConfig?.darkModeConfig)
switch (props.type) {
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':
return <textarea id={props.name} className={styles.textarea} {...props} />

View File

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

View File

@ -65,13 +65,22 @@
border-top: 0;
}
.tabLabel {
.tabPanel {
color: var(--font-color-text);
font-size: var(--font-size-small);
font-size: var(--font-size-base);
font-family: var(--font-family-heading);
line-height: 1.2;
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) {

View File

@ -25,7 +25,18 @@ export default function TabsFile({
className
}: TabsProps): ReactElement {
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
const isHidden = items[tabIndex].props.value[0].type === 'hidden'
@ -76,7 +87,10 @@ export default function TabsFile({
{items.map((item, index) => {
return (
<>
<TabPanel key={`tabpanel_${items[tabIndex].props.name}_${index}`}>
<TabPanel
key={`tabpanel_${items[tabIndex].props.name}_${index}`}
className={styles.tabPanel}
>
{!isHidden && (
<label className={styles.tabLabel}>
{item.field.label}

View File

@ -22,7 +22,7 @@ export default function AssetActions({
}: {
asset: AssetExtended
}): ReactElement {
const { accountId, balance, web3 } = useWeb3()
const { accountId, balance, web3, chainId } = useWeb3()
const { isAssetNetwork } = useAsset()
const newCancelToken = useCancelToken()
const isMounted = useIsMounted()
@ -56,13 +56,25 @@ export default function AssetActions({
? formikState?.values?.services[0].files[0].type
: 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 {
const fileInfoResponse = formikState?.values?.services?.[0].files?.[0]
.url
? await getFileInfo(
formikState?.values?.services?.[0].files?.[0].url,
providerUrl,
storageType
storageType,
query,
headers,
abi,
chainId,
method
)
: 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 { transformComputeFormToServiceComputeOptions } from '@utils/compute'
import { ComputeEditForm } from './_types'
import { previewDebugPatch } from '@utils/ddo'
export default function DebugEditCompute({
values,
@ -12,6 +13,7 @@ export default function DebugEditCompute({
values: ComputeEditForm
asset: Asset
}): ReactElement {
const [valuePreview, setValuePreview] = useState({})
const [formTransformed, setFormTransformed] =
useState<ServiceComputeOptions>()
const newCancelToken = useCancelToken()
@ -27,11 +29,12 @@ export default function DebugEditCompute({
setFormTransformed(privacy)
}
transformValues()
setValuePreview(previewDebugPatch(values, asset.chainId))
}, [values, asset])
return (
<>
<DebugOutput title="Collected Form Values" output={values} />
<DebugOutput title="Collected Form Values" output={valuePreview} />
<DebugOutput title="Transformed Form Values" output={formTransformed} />
</>
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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