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:
parent
223c8bf302
commit
b6f3bcbe68
@ -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
|
||||
|
@ -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
409
package-lock.json
generated
@ -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",
|
||||
|
@ -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
36
src/@utils/codemirror.ts
Normal 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()]
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -0,0 +1,3 @@
|
||||
.textblock {
|
||||
margin-top: var(--spacer);
|
||||
}
|
@ -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} />)
|
||||
})
|
||||
})
|
||||
|
@ -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>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
135
src/components/@shared/FormInput/InputElement/Headers/index.tsx
Normal file
135
src/components/@shared/FormInput/InputElement/Headers/index.tsx
Normal 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>
|
||||
)
|
||||
}
|
@ -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';
|
||||
}
|
@ -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'))
|
||||
})
|
||||
})
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -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 && (
|
||||
|
@ -47,7 +47,7 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
.codemirror, .textarea {
|
||||
composes: input;
|
||||
height: auto;
|
||||
}
|
||||
|
@ -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} />
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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}
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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} />
|
||||
</>
|
||||
)
|
||||
|
@ -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} />
|
||||
</>
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -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 =
|
||||
|
@ -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.')
|
||||
})
|
||||
)
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user