diff --git a/content/pages/editMetadata.json b/content/pages/editMetadata.json index 2b3c0d871..b70bd3737 100644 --- a/content/pages/editMetadata.json +++ b/content/pages/editMetadata.json @@ -59,6 +59,13 @@ "placeholder": "e.g. Mrs McJellyfish", "help": "Give proper attribution for your dataset.", "required": false + }, + { + "name": "tags", + "label": "New Tags", + "type": "tags", + "placeholder": "e.g. logistics", + "required": false } ] } diff --git a/content/publish/form.json b/content/publish/form.json index fef5b8d7b..e625624ac 100644 --- a/content/publish/form.json +++ b/content/publish/form.json @@ -39,8 +39,8 @@ { "name": "tags", "label": "Tags", - "placeholder": "e.g. logistics, ai", - "help": "Separate tags with comma." + "type": "tags", + "placeholder": "e.g. logistics" }, { "name": "dockerImage", @@ -54,21 +54,22 @@ }, { "name": "dockerImageCustom", - "label": "Docker Image URL", - "placeholder": "e.g. oceanprotocol/algo_dockers or https://example.com/image_path", - "help": "Provide the name of a public Docker image or the full url if you have it hosted in a 3rd party repo", + "label": "Custom Docker Image", + "placeholder": "e.g. oceanprotocol/algo_dockers:node-vibrant or quay.io/startx/mariadb", + "help": "Provide the name and the tag of a public Docker hub image or the custom image if you have it hosted in a 3rd party repository", + "type": "container", "required": true }, { - "name": "dockerImageCustomTag", - "label": "Docker Image Tag", - "placeholder": "e.g. latest", - "help": "Provide the tag for your Docker image.", + "name": "dockerImageChecksum", + "label": "Docker Image Checksum", + "placeholder": "e.g. sha256:xiXqb7Vet0FbN9q0GFMgUdi5C22wjJT0i2G6lYKC2jl6QxkKzVz7KaPDgqfTMjNF", + "help": "Provide the checksum(DIGEST) of your docker image.", "required": true }, { "name": "dockerImageCustomEntrypoint", - "label": "Docker Entrypoint", + "label": "Docker Image Entrypoint", "placeholder": "e.g. python $ALGO", "help": "Provide the entrypoint for your algorithm.", "required": true diff --git a/package-lock.json b/package-lock.json index fe7e72105..11fcc6297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@coingecko/cryptoformat": "^0.5.4", "@loadable/component": "^5.15.2", "@oceanprotocol/art": "^3.2.0", - "@oceanprotocol/lib": "^2.1.1", + "@oceanprotocol/lib": "^2.2.1", "@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/use-dark-mode": "^2.4.3", "@tippyjs/react": "^4.2.6", @@ -32,6 +32,7 @@ "js-cookie": "^3.0.1", "lodash.debounce": "^4.0.8", "lodash.omit": "^4.5.0", + "match-sorter": "^6.3.1", "myetherwallet-blockies": "^0.1.1", "next": "12.3.1", "query-string": "^7.1.1", @@ -42,6 +43,7 @@ "react-dotdotdot": "^1.3.1", "react-modal": "^3.15.1", "react-paginate": "^8.1.3", + "react-select": "^5.4.0", "react-spring": "^9.5.2", "react-tabs": "^5.1.0", "react-toastify": "^9.0.4", @@ -2061,6 +2063,64 @@ "node": ">=10.0.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.10.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz", + "integrity": "sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/plugin-syntax-jsx": "^7.17.12", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/serialize": "^1.1.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.10.3", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.3.tgz", + "integrity": "sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==", + "dependencies": { + "@emotion/memoize": "^0.8.0", + "@emotion/sheet": "^1.2.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "stylis": "4.0.13" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", + "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" + }, "node_modules/@emotion/is-prop-valid": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", @@ -2074,6 +2134,55 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" }, + "node_modules/@emotion/react": { + "version": "11.10.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.4.tgz", + "integrity": "sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.0", + "@emotion/cache": "^11.10.0", + "@emotion/serialize": "^1.1.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.0.tgz", + "integrity": "sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA==", + "dependencies": { + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/unitless": "^0.8.0", + "@emotion/utils": "^1.2.0", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/serialize/node_modules/@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + }, + "node_modules/@emotion/sheet": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.0.tgz", + "integrity": "sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w==" + }, "node_modules/@emotion/stylis": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", @@ -2084,6 +2193,24 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", + "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", + "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", + "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + }, "node_modules/@endemolshinegroup/cosmiconfig-typescript-loader": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", @@ -4412,9 +4539,9 @@ "integrity": "sha512-Oe+oBRiu1dlco9PQ7eUYcTYi2Nua69S3TiSw62H46AIpwnFK8ORuO0Ny20No++KisBA9F+84b5lDn6kQy5Lt/Q==" }, "node_modules/@oceanprotocol/lib": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.1.1.tgz", - "integrity": "sha512-N7NKnwVujJDn2X9MwxFu15x8VvTVEDqWuIZFY4s3NG0NbwXGEHptewKlAVhOkvm6jOGuCN3NXWqRUTvMFOWGbQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.2.1.tgz", + "integrity": "sha512-HNmT3DJJeyvFRwCbmgJucGpte90epIhgSy+68PSc83TLKRW2CF4N1mioMkoGxMwnK3rJzj6tEy4R9NKKLbdT5w==", "dependencies": { "@oceanprotocol/contracts": "^1.1.7", "bignumber.js": "^9.1.0", @@ -13002,8 +13129,7 @@ "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "node_modules/@types/parse5": { "version": "5.0.3", @@ -13088,6 +13214,14 @@ "@types/react": "*" } }, + "node_modules/@types/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/remove-markdown": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@types/remove-markdown/-/remove-markdown-0.3.1.tgz", @@ -15856,7 +15990,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -16975,7 +17108,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "engines": { "node": ">=6" } @@ -18384,7 +18516,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -19575,6 +19706,15 @@ "utila": "~0.4" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -22350,6 +22490,11 @@ "node": ">=6" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -24277,7 +24422,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -24293,7 +24437,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, "engines": { "node": ">=4" } @@ -28101,8 +28244,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json-rpc-engine": { "version": "6.1.0", @@ -28456,8 +28598,7 @@ "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "node_modules/listr": { "version": "0.14.3", @@ -29250,6 +29391,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, "node_modules/matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -29542,8 +29692,7 @@ "node_modules/memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "peer": true + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, "node_modules/memoizerific": { "version": "1.11.3", @@ -32949,7 +33098,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -32995,7 +33143,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -33248,7 +33395,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, "engines": { "node": ">=8" } @@ -34611,6 +34757,24 @@ "node": ">=0.10.0" } }, + "node_modules/react-select": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.4.0.tgz", + "integrity": "sha512-CjE9RFLUvChd5SdlfG4vqxZd55AZJRrLrHzkQyTYeHlpOztqcgnyftYAolJ0SGsBev6zAs6qFrjm6KU3eo2hzg==", + "dependencies": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^5.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-shallow-renderer": { "version": "16.15.0", "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", @@ -34848,6 +35012,21 @@ "react-dom": ">=16" } }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, "node_modules/react-use-measure": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", @@ -35551,6 +35730,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "node_modules/remove-markdown": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.5.0.tgz", @@ -37985,6 +38169,11 @@ } } }, + "node_modules/stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "node_modules/sudo-prompt": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", @@ -41344,7 +41533,6 @@ "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, "engines": { "node": ">= 6" } @@ -42804,6 +42992,54 @@ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", "dev": true }, + "@emotion/babel-plugin": { + "version": "11.10.2", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.10.2.tgz", + "integrity": "sha512-xNQ57njWTFVfPAc3cjfuaPdsgLp5QOSuRsj9MA6ndEhH/AzuZM86qIQzt6rq+aGBwj3n5/TkLmU5lhAfdRmogA==", + "requires": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/plugin-syntax-jsx": "^7.17.12", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/serialize": "^1.1.0", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.0.13" + }, + "dependencies": { + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + } + } + }, + "@emotion/cache": { + "version": "11.10.3", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.10.3.tgz", + "integrity": "sha512-Psmp/7ovAa8appWh3g51goxu/z3iVms7JXOreq136D8Bbn6dYraPnmL6mdM8GThEx9vwSn92Fz+mGSjBzN8UPQ==", + "requires": { + "@emotion/memoize": "^0.8.0", + "@emotion/sheet": "^1.2.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "stylis": "4.0.13" + } + }, + "@emotion/hash": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.0.tgz", + "integrity": "sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==" + }, "@emotion/is-prop-valid": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", @@ -42817,6 +43053,45 @@ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" }, + "@emotion/react": { + "version": "11.10.4", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.10.4.tgz", + "integrity": "sha512-j0AkMpr6BL8gldJZ6XQsQ8DnS9TxEQu1R+OGmDZiWjBAJtCcbt0tS3I/YffoqHXxH6MjgI7KdMbYKw3MEiU9eA==", + "requires": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.10.0", + "@emotion/cache": "^11.10.0", + "@emotion/serialize": "^1.1.0", + "@emotion/use-insertion-effect-with-fallbacks": "^1.0.0", + "@emotion/utils": "^1.2.0", + "@emotion/weak-memoize": "^0.3.0", + "hoist-non-react-statics": "^3.3.1" + } + }, + "@emotion/serialize": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.1.0.tgz", + "integrity": "sha512-F1ZZZW51T/fx+wKbVlwsfchr5q97iW8brAnXmsskz4d0hVB4O3M/SiA3SaeH06x02lSNzkkQv+n3AX3kCXKSFA==", + "requires": { + "@emotion/hash": "^0.9.0", + "@emotion/memoize": "^0.8.0", + "@emotion/unitless": "^0.8.0", + "@emotion/utils": "^1.2.0", + "csstype": "^3.0.2" + }, + "dependencies": { + "@emotion/unitless": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", + "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" + } + } + }, + "@emotion/sheet": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.2.0.tgz", + "integrity": "sha512-OiTkRgpxescko+M51tZsMq7Puu/KP55wMT8BgpcXVG2hqXc0Vo0mfymJ/Uj24Hp0i083ji/o0aLddh08UEjq8w==" + }, "@emotion/stylis": { "version": "0.8.5", "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", @@ -42827,6 +43102,22 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" }, + "@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.0.tgz", + "integrity": "sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==", + "requires": {} + }, + "@emotion/utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.2.0.tgz", + "integrity": "sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==" + }, + "@emotion/weak-memoize": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz", + "integrity": "sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==" + }, "@endemolshinegroup/cosmiconfig-typescript-loader": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz", @@ -44512,9 +44803,9 @@ "integrity": "sha512-Oe+oBRiu1dlco9PQ7eUYcTYi2Nua69S3TiSw62H46AIpwnFK8ORuO0Ny20No++KisBA9F+84b5lDn6kQy5Lt/Q==" }, "@oceanprotocol/lib": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.1.1.tgz", - "integrity": "sha512-N7NKnwVujJDn2X9MwxFu15x8VvTVEDqWuIZFY4s3NG0NbwXGEHptewKlAVhOkvm6jOGuCN3NXWqRUTvMFOWGbQ==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@oceanprotocol/lib/-/lib-2.2.1.tgz", + "integrity": "sha512-HNmT3DJJeyvFRwCbmgJucGpte90epIhgSy+68PSc83TLKRW2CF4N1mioMkoGxMwnK3rJzj6tEy4R9NKKLbdT5w==", "requires": { "@oceanprotocol/contracts": "^1.1.7", "bignumber.js": "^9.1.0", @@ -51164,8 +51455,7 @@ "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", - "dev": true + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==" }, "@types/parse5": { "version": "5.0.3", @@ -51250,6 +51540,14 @@ "@types/react": "*" } }, + "@types/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==", + "requires": { + "@types/react": "*" + } + }, "@types/remove-markdown": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@types/remove-markdown/-/remove-markdown-0.3.1.tgz", @@ -53514,7 +53812,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "dev": true, "requires": { "@babel/runtime": "^7.12.5", "cosmiconfig": "^7.0.0", @@ -54406,8 +54703,7 @@ "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "camel-case": { "version": "4.1.2", @@ -55511,7 +55807,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.0.1.tgz", "integrity": "sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ==", - "dev": true, "requires": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", @@ -56433,6 +56728,15 @@ "utila": "~0.4" } }, + "dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "requires": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -58696,6 +59000,11 @@ } } }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -60161,7 +60470,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -60170,8 +60478,7 @@ "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" } } }, @@ -63103,8 +63410,7 @@ "json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-rpc-engine": { "version": "6.1.0", @@ -63412,8 +63718,7 @@ "lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, "listr": { "version": "0.14.3", @@ -64039,6 +64344,15 @@ "repeat-string": "^1.0.0" } }, + "match-sorter": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", + "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", + "requires": { + "@babel/runtime": "^7.12.5", + "remove-accents": "0.4.2" + } + }, "matcher": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", @@ -64261,8 +64575,7 @@ "memoize-one": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "peer": true + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" }, "memoizerific": { "version": "1.11.3", @@ -67055,7 +67368,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, "requires": { "callsites": "^3.0.0" } @@ -67094,7 +67406,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -67304,8 +67615,7 @@ "path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" }, "pbkdf2": { "version": "3.1.2", @@ -68332,6 +68642,20 @@ "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==", "dev": true }, + "react-select": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/react-select/-/react-select-5.4.0.tgz", + "integrity": "sha512-CjE9RFLUvChd5SdlfG4vqxZd55AZJRrLrHzkQyTYeHlpOztqcgnyftYAolJ0SGsBev6zAs6qFrjm6KU3eo2hzg==", + "requires": { + "@babel/runtime": "^7.12.0", + "@emotion/cache": "^11.4.0", + "@emotion/react": "^11.8.1", + "@types/react-transition-group": "^4.4.0", + "memoize-one": "^5.0.0", + "prop-types": "^15.6.0", + "react-transition-group": "^4.3.0" + } + }, "react-shallow-renderer": { "version": "16.15.0", "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", @@ -68523,6 +68847,17 @@ "clsx": "^1.1.1" } }, + "react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "requires": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + } + }, "react-use-measure": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.1.tgz", @@ -69074,6 +69409,11 @@ "mdast-util-to-markdown": "^0.6.0" } }, + "remove-accents": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", + "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" + }, "remove-markdown": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/remove-markdown/-/remove-markdown-0.5.0.tgz", @@ -70972,6 +71312,11 @@ "integrity": "sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==", "requires": {} }, + "stylis": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz", + "integrity": "sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==" + }, "sudo-prompt": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", @@ -73681,8 +74026,7 @@ "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" }, "yargs": { "version": "17.5.1", diff --git a/package.json b/package.json index db31739bd..d9dc37b96 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "@coingecko/cryptoformat": "^0.5.4", "@loadable/component": "^5.15.2", "@oceanprotocol/art": "^3.2.0", - "@oceanprotocol/lib": "^2.1.1", + "@oceanprotocol/lib": "^2.2.1", "@oceanprotocol/typographies": "^0.1.0", "@oceanprotocol/use-dark-mode": "^2.4.3", "@tippyjs/react": "^4.2.6", @@ -45,6 +45,7 @@ "js-cookie": "^3.0.1", "lodash.debounce": "^4.0.8", "lodash.omit": "^4.5.0", + "match-sorter": "^6.3.1", "myetherwallet-blockies": "^0.1.1", "next": "12.3.1", "query-string": "^7.1.1", @@ -55,6 +56,7 @@ "react-dotdotdot": "^1.3.1", "react-modal": "^3.15.1", "react-paginate": "^8.1.3", + "react-select": "^5.4.0", "react-spring": "^9.5.2", "react-tabs": "^5.1.0", "react-toastify": "^9.0.4", diff --git a/src/@hooks/useNetworkMetadata/utils.ts b/src/@hooks/useNetworkMetadata/utils.ts index f3ec518f1..c97dcdc2e 100644 --- a/src/@hooks/useNetworkMetadata/utils.ts +++ b/src/@hooks/useNetworkMetadata/utils.ts @@ -32,22 +32,25 @@ export function getNetworkDisplayName( displayName = 'Polygon' break case 1287: - displayName = 'Moonbase Alpha' + displayName = 'Moonbase' break case 1285: displayName = 'Moonriver' break case 80001: - displayName = 'Polygon Mumbai' + displayName = 'Mumbai' break case 8996: displayName = 'Development' break case 3: - displayName = 'ETH Ropsten' + displayName = 'Ropsten' + break + case 5: + displayName = 'Görli' break case 2021000: - displayName = 'GAIA-X Testnet' + displayName = 'GAIA-X' break default: displayName = data diff --git a/src/@types/aquarius/SearchQuery.ts b/src/@types/aquarius/SearchQuery.ts index c4e0dea5c..3e4543c39 100644 --- a/src/@types/aquarius/SearchQuery.ts +++ b/src/@types/aquarius/SearchQuery.ts @@ -6,7 +6,8 @@ export enum SortDirectionOptions { export enum SortTermOptions { Created = 'nft.created', Relevance = '_score', - Stats = 'stats.orders' + Orders = 'stats.orders', + Allocated = 'stats.allocated' } // Note: could not figure out how to get `enum` to be ambiant diff --git a/src/@types/aquarius/TagsList.d.ts b/src/@types/aquarius/TagsList.d.ts new file mode 100644 index 000000000..58bc34e30 --- /dev/null +++ b/src/@types/aquarius/TagsList.d.ts @@ -0,0 +1,4 @@ +interface AggregatedTag { + doc_count: number + key: string +} diff --git a/src/@utils/aquarius.ts b/src/@utils/aquarius.ts index 0210e1991..851219ab9 100644 --- a/src/@utils/aquarius.ts +++ b/src/@utils/aquarius.ts @@ -45,13 +45,18 @@ export function generateBaseQuery( ): SearchQuery { const generatedQuery = { from: baseQueryParams.esPaginationOptions?.from || 0, - size: baseQueryParams.esPaginationOptions?.size || 1000, + size: + baseQueryParams.esPaginationOptions?.size >= 0 + ? baseQueryParams.esPaginationOptions?.size + : 1000, query: { bool: { ...baseQueryParams.nestedQuery, filter: [ ...(baseQueryParams.filters || []), - getFilterTerm('chainId', baseQueryParams.chainIds), + baseQueryParams.chainIds + ? getFilterTerm('chainId', baseQueryParams.chainIds) + : [], getFilterTerm('_index', 'aquarius'), ...(baseQueryParams.ignorePurgatory ? [] @@ -289,7 +294,7 @@ export async function getAlgorithmDatasetsForCompute( const query = generateBaseQuery(baseQueryParams) const computeDatasets = await queryMetadata(query, cancelToken) - if (computeDatasets.totalResults === 0) return [] + if (computeDatasets?.totalResults === 0) return [] const datasets = await transformAssetToAssetSelection( datasetProviderUri, @@ -326,7 +331,7 @@ export async function getPublishedAssets( aggs: { totalOrders: { sum: { - field: SortTermOptions.Stats + field: SortTermOptions.Orders } } }, @@ -380,7 +385,7 @@ export async function getTopPublishers( aggs: { totalSales: { sum: { - field: SortTermOptions.Stats + field: SortTermOptions.Orders } } } @@ -482,3 +487,47 @@ export async function getDownloadAssets( } } } + +export async function getTagsList( + chainIds: number[], + cancelToken: CancelToken +): Promise { + const baseQueryParams = { + chainIds, + esPaginationOptions: { from: 0, size: 0 } + } as BaseQueryParams + const query = { + ...generateBaseQuery(baseQueryParams), + aggs: { + tags: { + terms: { + field: 'metadata.tags.keyword', + size: 1000 + } + } + } + } + + try { + const response: AxiosResponse = await axios.post( + `${metadataCacheUri}/api/aquarius/assets/query`, + { ...query }, + { cancelToken } + ) + if (response?.status !== 200 || !response?.data) return + const { buckets }: { buckets: AggregatedTag[] } = + response.data.aggregations.tags + + const tagsList = buckets + .filter((tag) => tag.key !== '') + .map((tag) => tag.key) + + return tagsList.sort() + } catch (error) { + if (axios.isCancel(error)) { + LoggerInstance.log(error.message) + } else { + LoggerInstance.error(error.message) + } + } +} diff --git a/src/@utils/docker.ts b/src/@utils/docker.ts index 9453f5447..ee917828d 100644 --- a/src/@utils/docker.ts +++ b/src/@utils/docker.ts @@ -1,16 +1,27 @@ import { LoggerInstance } from '@oceanprotocol/lib' import axios from 'axios' -import isUrl from 'is-url-superb' import { toast } from 'react-toastify' -async function isDockerHubImageValid( +export interface dockerContainerInfo { + exists: boolean + checksum: string +} + +export async function getContainerChecksum( image: string, tag: string -): Promise { +): Promise { + const containerInfo: dockerContainerInfo = { + exists: false, + checksum: null + } try { const response = await axios.post( `https://dockerhub-proxy.oceanprotocol.com`, - { image, tag } + { + image, + tag + } ) if ( !response || @@ -18,46 +29,18 @@ async function isDockerHubImageValid( response.data.status !== 'success' ) { toast.error( - 'Could not fetch docker hub image info. Please check image name and tag and try again' + 'Could not fetch docker hub image informations. If you have it hosted in a 3rd party repository please fill in the container checksum manually.' ) - return false + return containerInfo } - - return true + containerInfo.exists = true + containerInfo.checksum = response.data.result.checksum + return containerInfo } catch (error) { LoggerInstance.error(error.message) toast.error( - 'Could not fetch docker hub image info. Please check image name and tag and try again' + 'Could not fetch docker hub image informations. If you have it hosted in a 3rd party repository please fill in the container checksum manually.' ) - return false + return containerInfo } } - -async function is3rdPartyImageValid(imageURL: string): Promise { - try { - const response = await axios.head(imageURL) - if (!response || response.status !== 200) { - toast.error( - 'Could not fetch docker image info. Please check URL and try again' - ) - return false - } - return true - } catch (error) { - LoggerInstance.error(error.message) - toast.error( - 'Could not fetch docker image info. Please check URL and try again' - ) - return false - } -} - -export async function validateDockerImage( - dockerImage: string, - tag: string -): Promise { - const isValid = isUrl(dockerImage) - ? await is3rdPartyImageValid(dockerImage) - : await isDockerHubImageValid(dockerImage, tag) - return isValid -} diff --git a/src/@utils/veAllocation.ts b/src/@utils/veAllocation.ts new file mode 100644 index 000000000..351d5e25f --- /dev/null +++ b/src/@utils/veAllocation.ts @@ -0,0 +1,45 @@ +import { AllLocked } from 'src/@types/subgraph/AllLocked' +import { gql, OperationResult } from 'urql' +import { fetchData, getQueryContext } from './subgraph' +import axios from 'axios' + +const AllLocked = gql` + query AllLocked { + veOCEANs(first: 1000) { + lockedAmount + } + } +` + +interface TotalVe { + totalLocked: number + totalAllocated: number +} + +export async function getTotalAllocatedAndLocked(): Promise { + const totals = { + totalLocked: 0, + totalAllocated: 0 + } + + const queryContext = getQueryContext(1) + + const response = await axios.post(`https://df-sql.oceandao.org/nftinfo`) + totals.totalAllocated = response.data?.reduce( + (previousValue: number, currentValue: { ve_allocated: any }) => + previousValue + Number(currentValue.ve_allocated), + 0 + ) + + const fetchedLocked: OperationResult = await fetchData( + AllLocked, + null, + queryContext + ) + totals.totalLocked = fetchedLocked.data?.veOCEANs.reduce( + (previousValue, currentValue) => + previousValue + Number(currentValue.lockedAmount), + 0 + ) + return totals +} diff --git a/src/components/@shared/AssetList/index.tsx b/src/components/@shared/AssetList/index.tsx index 6fa0cc51e..7de0b25b8 100644 --- a/src/components/@shared/AssetList/index.tsx +++ b/src/components/@shared/AssetList/index.tsx @@ -1,4 +1,4 @@ -import AssetTeaser from '@shared/AssetTeaser/AssetTeaser' +import AssetTeaser from '@shared/AssetTeaser' import React, { ReactElement, useEffect, useState } from 'react' import Pagination from '@shared/Pagination' import styles from './index.module.css' diff --git a/src/components/@shared/AssetTeaser/AssetTeaser.tsx b/src/components/@shared/AssetTeaser/AssetTeaser.tsx deleted file mode 100644 index d1dbeea34..000000000 --- a/src/components/@shared/AssetTeaser/AssetTeaser.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React, { ReactElement } from 'react' -import Link from 'next/link' -import Dotdotdot from 'react-dotdotdot' -import Price from '@shared/Price' -import removeMarkdown from 'remove-markdown' -import Publisher from '@shared/Publisher' -import AssetType from '@shared/AssetType' -import NetworkName from '@shared/NetworkName' -import styles from './AssetTeaser.module.css' -import { getServiceByName } from '@utils/ddo' - -declare type AssetTeaserProps = { - asset: AssetExtended - noPublisher?: boolean -} - -export default function AssetTeaser({ - asset, - noPublisher -}: AssetTeaserProps): ReactElement { - const { name, type, description } = asset.metadata - const { datatokens } = asset - const isCompute = Boolean(getServiceByName(asset, 'compute')) - const accessType = isCompute ? 'compute' : 'access' - const { owner } = asset.nft - const { orders } = asset.stats - return ( - - ) -} diff --git a/src/components/@shared/AssetTeaser/AssetTeaser.module.css b/src/components/@shared/AssetTeaser/index.module.css similarity index 58% rename from src/components/@shared/AssetTeaser/AssetTeaser.module.css rename to src/components/@shared/AssetTeaser/index.module.css index daa73e675..ca468094f 100644 --- a/src/components/@shared/AssetTeaser/AssetTeaser.module.css +++ b/src/components/@shared/AssetTeaser/index.module.css @@ -9,6 +9,8 @@ height: 100%; color: var(--color-secondary); position: relative; + padding-top: calc(var(--spacer) / 2); + padding-bottom: calc(var(--spacer) / 2); /* for sticking footer to bottom */ display: flex; flex-direction: column; @@ -18,8 +20,12 @@ background-color: var(--background-body); } +.detailLine { + margin-bottom: calc(var(--spacer) / 2); +} + .content { - margin-top: calc(var(--spacer) / 2); + margin-top: calc(var(--spacer) / 3); overflow-wrap: break-word; hyphens: auto; /* for sticking footer to bottom */ @@ -27,7 +33,7 @@ } .content p { - margin-bottom: calc(var(--spacer) / 4); + margin-bottom: calc(var(--spacer) / 3); } .title { @@ -37,36 +43,20 @@ overflow-wrap: break-word; } -.publisher { - display: block; -} - -.foot { +.footer { margin-top: calc(var(--spacer) / 4); - display: flex; - justify-content: space-between; - align-items: flex-end; } -.foot p { - margin: 0; -} - -.symbol { - display: block; -} - -.typeDetails { - position: absolute; - top: calc(var(--spacer) / 3); - right: calc(var(--spacer) / 3); - width: auto; +.typeLabel { font-size: var(--font-size-mini); + display: inline-block; + border-left: 1px solid var(--border-color); + padding-left: calc(var(--spacer) / 4); + margin-left: calc(var(--spacer) / 4); } -.network { - font-size: var(--font-size-mini); - position: absolute; - right: calc(var(--spacer) / 3); - bottom: calc(var(--spacer) / 3); +.typeLabel:first-child { + border-left: none; + padding-left: 0; + margin-left: 0; } diff --git a/src/components/@shared/AssetTeaser/index.tsx b/src/components/@shared/AssetTeaser/index.tsx new file mode 100644 index 000000000..f0db24936 --- /dev/null +++ b/src/components/@shared/AssetTeaser/index.tsx @@ -0,0 +1,86 @@ +import React, { ReactElement } from 'react' +import Link from 'next/link' +import Dotdotdot from 'react-dotdotdot' +import Price from '@shared/Price' +import removeMarkdown from 'remove-markdown' +import Publisher from '@shared/Publisher' +import AssetType from '@shared/AssetType' +import NetworkName from '@shared/NetworkName' +import styles from './index.module.css' +import { getServiceByName } from '@utils/ddo' +import { formatPrice } from '@shared/Price/PriceUnit' +import { useUserPreferences } from '@context/UserPreferences' + +declare type AssetTeaserProps = { + asset: AssetExtended + noPublisher?: boolean +} + +export default function AssetTeaser({ + asset, + noPublisher +}: AssetTeaserProps): ReactElement { + const { name, type, description } = asset.metadata + const { datatokens } = asset + const isCompute = Boolean(getServiceByName(asset, 'compute')) + const accessType = isCompute ? 'compute' : 'access' + const { owner } = asset.nft + const { orders, allocated } = asset.stats + const isUnsupportedPricing = asset?.accessDetails?.type === 'NOT_SUPPORTED' + const { locale } = useUserPreferences() + + return ( +
+ + + +
+ + {name.slice(0, 200)} + + {!noPublisher && } +
+
+ + {removeMarkdown(description?.substring(0, 300) || '')} + +
+ {isUnsupportedPricing ? ( + No pricing schema available + ) : ( + + )} +
+ {allocated && allocated > 0 ? ( + + {allocated < 0 + ? '' + : `${formatPrice(allocated, locale)} veOCEAN`} + + ) : null} + {orders && orders > 0 ? ( + + {orders < 0 + ? 'N/A' + : `${orders} ${orders === 1 ? 'sale' : 'sales'}`} + + ) : null} +
+
+ +
+ ) +} diff --git a/src/components/@shared/AssetType/index.tsx b/src/components/@shared/AssetType/index.tsx index 7d3384c02..14be396dc 100644 --- a/src/components/@shared/AssetType/index.tsx +++ b/src/components/@shared/AssetType/index.tsx @@ -7,13 +7,11 @@ import Lock from '@images/lock.svg' export default function AssetType({ type, accessType, - className, - totalSales + className }: { type: string accessType: string className?: string - totalSales?: number }): ReactElement { return (
@@ -28,14 +26,6 @@ export default function AssetType({
{type === 'dataset' ? 'dataset' : 'algorithm'}
- - {(totalSales || totalSales === 0) && ( -
- {totalSales < 0 - ? 'N/A' - : `${totalSales} ${totalSales === 1 ? 'sale' : 'sales'}`} -
- )}
) } diff --git a/src/components/@shared/FormFields/ContainerInput/Info.module.css b/src/components/@shared/FormFields/ContainerInput/Info.module.css new file mode 100644 index 000000000..ed25b655f --- /dev/null +++ b/src/components/@shared/FormFields/ContainerInput/Info.module.css @@ -0,0 +1,48 @@ +.info { + border-radius: var(--border-radius); + padding: calc(var(--spacer) / 2); + border: 1px solid var(--border-color); + background-color: var(--background-highlight); + position: relative; +} + +.info ul { + margin: 0; +} + +.info li { + display: inline-block; + font-size: var(--font-size-small); + margin-right: calc(var(--spacer) / 2); + color: var(--color-secondary); +} + +.info li.success { + color: var(--brand-alert-green); +} + +.info li.error { + color: var(--brand-alert-red); +} + +.contianer { + margin: 0; + font-size: var(--font-size-base); + line-height: var(--line-height); + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-all; + padding-right: calc(var(--spacer) / 2); +} + +.removeButton { + cursor: pointer; + border: none; + position: absolute; + top: -0.2rem; + right: 0; + font-size: var(--font-size-h3); + cursor: pointer; + color: var(--font-color-text); + background-color: transparent; +} diff --git a/src/components/@shared/FormFields/ContainerInput/Info.tsx b/src/components/@shared/FormFields/ContainerInput/Info.tsx new file mode 100644 index 000000000..a24332273 --- /dev/null +++ b/src/components/@shared/FormFields/ContainerInput/Info.tsx @@ -0,0 +1,29 @@ +import React, { ReactElement } from 'react' +import styles from './Info.module.css' + +export default function ImageInfo({ + image, + tag, + valid, + handleClose +}: { + image: string + tag: string + valid: boolean + handleClose(): void +}): ReactElement { + const displayText = valid + ? '✓ Image found, container checksum automatically added!' + : 'x Container checksum could not be fetched automatically, please add it manually' + return ( +
+

{`Image: ${image} Tag: ${tag}`}

+
    +
  • {displayText}
  • +
+ +
+ ) +} diff --git a/src/components/@shared/FormFields/ContainerInput/index.tsx b/src/components/@shared/FormFields/ContainerInput/index.tsx new file mode 100644 index 000000000..400088ae1 --- /dev/null +++ b/src/components/@shared/FormFields/ContainerInput/index.tsx @@ -0,0 +1,83 @@ +import React, { ReactElement, useState } from 'react' +import { useField, useFormikContext } from 'formik' +import UrlInput from '../URLInput' +import { InputProps } from '@shared/FormInput' +import { FormPublishData } from 'src/components/Publish/_types' +import { LoggerInstance } from '@oceanprotocol/lib' +import ImageInfo from './Info' +import { getContainerChecksum } from '@utils/docker' + +export default function ContainerInput(props: InputProps): ReactElement { + const [field] = useField(props.name) + const [fieldChecksum, metaChecksum, helpersChecksum] = useField( + 'metadata.dockerImageCustomChecksum' + ) + + const { values, setFieldError, setFieldValue } = + useFormikContext() + const [isLoading, setIsLoading] = useState(false) + const [isValid, setIsValid] = useState(false) + const [checked, setChecked] = useState(false) + + async function handleValidation(e: React.SyntheticEvent, container: string) { + e.preventDefault() + try { + setIsLoading(true) + const parsedContainerValue = container?.split(':') + const imageName = + parsedContainerValue?.length > 1 + ? parsedContainerValue?.slice(0, -1).join(':') + : parsedContainerValue[0] + const tag = + parsedContainerValue?.length > 1 ? parsedContainerValue?.at(-1) : '' + const containerInfo = await getContainerChecksum(imageName, tag) + setFieldValue('metadata.dockerImageCustom', imageName) + setFieldValue('metadata.dockerImageCustomTag', tag) + setChecked(true) + if (containerInfo.checksum) { + setFieldValue( + 'metadata.dockerImageCustomChecksum', + containerInfo.checksum + ) + helpersChecksum.setTouched(false) + setIsValid(true) + } + } catch (error) { + setFieldError(`${field.name}[0].url`, error.message) + LoggerInstance.error(error.message) + } finally { + setIsLoading(false) + } + } + + function handleClose() { + setFieldValue('metadata.dockerImageCustom', '') + setFieldValue('metadata.dockerImageCustomTag', '') + setFieldValue('metadata.dockerImageCustomChecksum', '') + setChecked(false) + setIsValid(false) + helpersChecksum.setTouched(true) + } + + return ( + <> + {checked ? ( + + ) : ( + + )} + + ) +} diff --git a/src/components/@shared/FormFields/URLInput/index.tsx b/src/components/@shared/FormFields/URLInput/index.tsx index 3890032c9..af7d0fdf3 100644 --- a/src/components/@shared/FormFields/URLInput/index.tsx +++ b/src/components/@shared/FormFields/URLInput/index.tsx @@ -12,12 +12,14 @@ export default function URLInput({ handleButtonClick, isLoading, name, + checkUrl, ...props }: { submitText: string handleButtonClick(e: React.SyntheticEvent, data: string): void isLoading: boolean name: string + checkUrl?: boolean }): ReactElement { const [field, meta] = useField(name) const [isButtonDisabled, setIsButtonDisabled] = useState(true) @@ -28,7 +30,7 @@ export default function URLInput({ setIsButtonDisabled( !field?.value || field.value === '' || - !isUrl(field.value) || + (checkUrl && !isUrl(field.value)) || field.value.includes('javascript:') || meta?.error ) diff --git a/src/components/@shared/FormInput/InputElement.tsx b/src/components/@shared/FormInput/InputElement.tsx index 343889238..79e846f54 100644 --- a/src/components/@shared/FormInput/InputElement.tsx +++ b/src/components/@shared/FormInput/InputElement.tsx @@ -11,6 +11,8 @@ import AssetSelection, { } from '../FormFields/AssetSelection' import Nft from '../FormFields/Nft' import InputRadio from './InputRadio' +import ContainerInput from '@shared/FormFields/ContainerInput' +import TagsAutoComplete from './TagsAutoComplete' const cx = classNames.bind(styles) @@ -107,6 +109,8 @@ export default function InputElement({ ) case 'files': return + case 'container': + return case 'providerUrl': return case 'nft': @@ -121,6 +125,8 @@ export default function InputElement({ {...props} /> ) + case 'tags': + return default: return prefix || postfix ? (
diff --git a/src/components/@shared/FormInput/TagsAutoComplete.module.css b/src/components/@shared/FormInput/TagsAutoComplete.module.css new file mode 100644 index 000000000..cb872cab4 --- /dev/null +++ b/src/components/@shared/FormInput/TagsAutoComplete.module.css @@ -0,0 +1,101 @@ +.select [class$='control'] { + border-color: var(--border-color); + border-radius: var(--border-radius); + box-shadow: none; + background-color: var(--background-content); + font-size: var(--font-size-base); + font-family: var(--font-family-base); + font-weight: var(--font-weight-bold); + cursor: text; + min-height: 43px; +} + +.select [class$='control']:hover { + border-color: var(--border-color); +} + +.select [class$='control']:focus-within { + border-color: var(--font-color-text); +} + +.select [class$='ValueContainer'] { + padding: calc(var(--spacer) / 4) calc(var(--spacer) / 3); +} + +.select [class$='Input'] { + margin: 0; + padding-bottom: 0; + padding-top: 0; + font-size: var(--font-size-base); + font-family: var(--font-family-base); + font-weight: var(--font-weight-bold); +} + +.select input { + color: var(--font-color-heading) !important; +} + +.select [class$='menu'] { + background-color: var(--background-highlight); + border-radius: var(--border-radius); +} + +.select [class$='option'] { + color: var(--font-color-heading); + font-size: var(--font-size-base); + font-family: var(--font-family-base); + font-weight: var(--font-weight-bold); +} + +.select [class$='option']:active { + background-color: var(--color-secondary); +} + +.select [class$='multiValue'], +.select [class$='multiValue'] > *, +.select [class$='multiValue']:hover > * { + border-radius: var(--border-radius); + background-color: var(--background-highlight); + color: var(--font-color-text); + font-size: var(--font-size-base); + font-family: var(--font-family-base); + font-weight: var(--font-weight-bold); + padding-top: 0; + padding-bottom: 0; +} + +.select [class$='multiValue'] > div[role$='button'], +.select [class$='indicatorContainer'] svg { + cursor: pointer; +} + +.select [class$='placeholder'] { + margin-left: 0; + margin-right: 0; + color: var(--color-secondary); + font-weight: var(--font-weight-base); + transition: 0.2s ease-out; + opacity: 0.7; +} + +.select [class$='menu'] { + background-color: var(--background-content); + margin-top: -2px; + border: 1px solid var(--font-color-text); + border-top-color: var(--border-color); + border-top-left-radius: 0; + border-top-right-radius: 0; + box-shadow: none; +} + +.select [class$='menu'] [class$='option']:hover, +.select [class$='menu'] [class$='option']:focus-within { + background-color: var(--font-color-heading); + color: var(--background-content); +} + +.select [class$='NoOptionsMessage'] { + font-size: var(--font-size-small); + color: var(--color-secondary); + text-align: left; +} diff --git a/src/components/@shared/FormInput/TagsAutoComplete.tsx b/src/components/@shared/FormInput/TagsAutoComplete.tsx new file mode 100644 index 000000000..32cee4961 --- /dev/null +++ b/src/components/@shared/FormInput/TagsAutoComplete.tsx @@ -0,0 +1,92 @@ +import React, { ReactElement, useEffect, useState } from 'react' +import CreatableSelect from 'react-select/creatable' +import { OnChangeValue } from 'react-select' +import { useField } from 'formik' +import { InputProps } from '.' +import { getTagsList } from '@utils/aquarius' +import { chainIds } from 'app.config' +import { useCancelToken } from '@hooks/useCancelToken' +import styles from './TagsAutoComplete.module.css' +import { matchSorter } from 'match-sorter' + +interface AutoCompleteOption { + readonly value: string + readonly label: string +} + +export default function TagsAutoComplete({ + ...props +}: InputProps): ReactElement { + const { name, placeholder } = props + const [tagsList, setTagsList] = useState() + const [matchedTagsList, setMatchedTagsList] = useState( + [] + ) + const [field, meta, helpers] = useField(name) + const [input, setInput] = useState() + + const newCancelToken = useCancelToken() + + const generateAutocompleteOptions = ( + options: string[] + ): AutoCompleteOption[] => { + return options?.map((tag) => ({ + value: tag, + label: tag + })) + } + + const defaultTags = !field.value + ? undefined + : generateAutocompleteOptions(field.value) + + useEffect(() => { + const generateTagsList = async () => { + const tags = await getTagsList(chainIds, newCancelToken()) + const autocompleteOptions = generateAutocompleteOptions(tags) + setTagsList(autocompleteOptions) + } + generateTagsList() + }, [newCancelToken]) + + const handleChange = (userInput: OnChangeValue) => { + const normalizedInput = userInput.map((input) => input.value) + helpers.setValue(normalizedInput) + helpers.setTouched(true) + } + + const handleOptionsFilter = ( + options: AutoCompleteOption[], + input: string + ): void => { + setInput(input) + const matchedTagsList = matchSorter(options, input, { keys: ['value'] }) + setMatchedTagsList(matchedTagsList) + } + + return ( + null, + IndicatorSeparator: () => null + }} + className={styles.select} + defaultValue={defaultTags} + hideSelectedOptions + isMulti + isClearable={false} + noOptionsMessage={() => + 'Start typing to get suggestions based on tags from all published assets.' + } + onChange={(value: AutoCompleteOption[]) => handleChange(value)} + onInputChange={(value) => handleOptionsFilter(tagsList, value)} + openMenuOnClick + options={!input || input?.length < 1 ? [] : matchedTagsList} + placeholder={placeholder} + theme={(theme) => ({ + ...theme, + colors: { ...theme.colors, primary25: 'var(--border-color)' } + })} + /> + ) +} diff --git a/src/components/@shared/NetworkName/NetworkIcon.tsx b/src/components/@shared/NetworkName/NetworkIcon.tsx index 5af785ca9..55dc360e1 100644 --- a/src/components/@shared/NetworkName/NetworkIcon.tsx +++ b/src/components/@shared/NetworkName/NetworkIcon.tsx @@ -9,7 +9,7 @@ import styles from './index.module.css' export function NetworkIcon({ name }: { name: string }): ReactElement { const IconMapped = name.includes('ETH') ? EthIcon - : name.includes('Polygon') + : name.includes('Polygon') || name.includes('Mumbai') ? PolygonIcon : name.includes('Moon') ? MoonbeamIcon diff --git a/src/components/Asset/AssetActions/AssetStats/index.module.css b/src/components/Asset/AssetActions/AssetStats/index.module.css index 0519db863..a8d2ae270 100644 --- a/src/components/Asset/AssetActions/AssetStats/index.module.css +++ b/src/components/Asset/AssetActions/AssetStats/index.module.css @@ -20,6 +20,18 @@ } } +.stat { + border-left: 1px solid var(--border-color); + padding-left: calc(var(--spacer) / 3.5); + margin-left: calc(var(--spacer) / 4); +} + +.stat:first-child { + border-left: none; + padding-left: 0; + margin-left: 0; +} + .number { font-weight: var(--font-weight-bold); color: var(--font-color-heading); diff --git a/src/components/Asset/AssetActions/AssetStats/index.tsx b/src/components/Asset/AssetActions/AssetStats/index.tsx index 4ee046802..dd6e79991 100644 --- a/src/components/Asset/AssetActions/AssetStats/index.tsx +++ b/src/components/Asset/AssetActions/AssetStats/index.tsx @@ -1,21 +1,43 @@ import { useAsset } from '@context/Asset' -import React from 'react' +import { useUserPreferences } from '@context/UserPreferences' +import { formatPrice } from '@shared/Price/PriceUnit' +import React, { useEffect, useState } from 'react' import styles from './index.module.css' export default function AssetStats() { + const { locale } = useUserPreferences() const { asset } = useAsset() + const [orders, setOrders] = useState(0) + const [allocated, setAllocated] = useState(0) + + useEffect(() => { + if (!asset) return + + const { orders, allocated } = asset.stats + + setOrders(orders) + setAllocated(allocated) + }, [asset]) return (
- {!asset || !asset?.stats || asset?.stats?.orders < 0 ? ( + {allocated && allocated > 0 ? ( + + + {formatPrice(allocated, locale)} + + veOCEAN + + ) : null} + {!asset || !asset?.stats || orders < 0 ? ( 'N/A' - ) : asset?.stats?.orders === 0 ? ( + ) : orders === 0 ? ( 'No sales yet' ) : ( - <> - {asset.stats.orders} sale - {asset.stats.orders === 1 ? '' : 's'} - + + {orders} sale + {orders === 1 ? '' : 's'} + )}
) diff --git a/src/components/Asset/AssetActions/Compute/History.tsx b/src/components/Asset/AssetActions/Compute/History.tsx index 9fa285476..805ba8585 100644 --- a/src/components/Asset/AssetActions/Compute/History.tsx +++ b/src/components/Asset/AssetActions/Compute/History.tsx @@ -5,14 +5,17 @@ import Caret from '@images/caret.svg' export default function ComputeHistory({ title, - children + children, + refetchJobs }: { title: string children: ReactNode + refetchJobs?: any }): ReactElement { const [open, setOpen] = useState(false) - function handleClick() { + async function handleClick() { + await refetchJobs(true) setOpen(!open) } diff --git a/src/components/Asset/AssetActions/Compute/index.tsx b/src/components/Asset/AssetActions/Compute/index.tsx index 34804bfe8..cab80cfdb 100644 --- a/src/components/Asset/AssetActions/Compute/index.tsx +++ b/src/components/Asset/AssetActions/Compute/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, ReactElement, useEffect } from 'react' +import React, { useState, ReactElement, useEffect, useCallback } from 'react' import { Asset, DDO, @@ -29,7 +29,8 @@ import { isOrderable, getAlgorithmAssetSelectionList, getAlgorithmsForAsset, - getComputeEnviroment + getComputeEnviroment, + getComputeJobs } from '@utils/compute' import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import AlgorithmDatasetsListForCompute from './AlgorithmDatasetsListForCompute' @@ -43,7 +44,9 @@ import { handleComputeOrder } from '@utils/order' import { getComputeFeedback } from '@utils/feedback' import { getDummyWeb3 } from '@utils/web3' import { initializeProviderForCompute } from '@utils/provider' +import { useUserPreferences } from '@context/UserPreferences' +const refreshInterval = 10000 // 10 sec. export default function Compute({ asset, dtBalance, @@ -58,6 +61,7 @@ export default function Compute({ consumableFeedback?: string }): ReactElement { const { accountId, web3 } = useWeb3() + const { chainIds } = useUserPreferences() const newAbortController = useAbortController() const newCancelToken = useCancelToken() @@ -91,6 +95,8 @@ export default function Compute({ const [isRequestingAlgoOrderPrice, setIsRequestingAlgoOrderPrice] = useState(false) const [refetchJobs, setRefetchJobs] = useState(false) + const [isLoadingJobs, setIsLoadingJobs] = useState(false) + const [jobs, setJobs] = useState([]) const hasDatatoken = Number(dtBalance) >= 1 const isComputeButtonDisabled = @@ -243,6 +249,44 @@ export default function Compute({ }) }, [asset, isUnsupportedPricing]) + const fetchJobs = useCallback( + async (type: string) => { + if (!chainIds || chainIds.length === 0 || !accountId) { + return + } + + try { + type === 'init' && setIsLoadingJobs(true) + const computeJobs = await getComputeJobs( + [asset?.chainId] || chainIds, + accountId, + asset, + newCancelToken() + ) + setJobs(computeJobs.computeJobs) + setIsLoadingJobs(!computeJobs.isLoaded) + } catch (error) { + LoggerInstance.error(error.message) + setIsLoadingJobs(false) + } + }, + [accountId, asset, chainIds, isLoadingJobs, newCancelToken] + ) + + useEffect(() => { + fetchJobs('init') + + // init periodic refresh for jobs + const balanceInterval = setInterval( + () => fetchJobs('repeat'), + refreshInterval + ) + + return () => { + clearInterval(balanceInterval) + } + }, [refetchJobs]) + // Output errors in toast UI useEffect(() => { const newError = error @@ -443,11 +487,15 @@ export default function Compute({ )} {accountId && asset?.accessDetails?.datatoken && ( - + setRefetchJobs(!refetchJobs)} + > setRefetchJobs(!refetchJobs)} /> )} diff --git a/src/components/Asset/Edit/EditMetadata.tsx b/src/components/Asset/Edit/EditMetadata.tsx index 8c471dc84..a03918c16 100644 --- a/src/components/Asset/Edit/EditMetadata.tsx +++ b/src/components/Asset/Edit/EditMetadata.tsx @@ -73,7 +73,8 @@ export default function Edit({ name: values.name, description: values.description, links: linksTransformed, - author: values.author + author: values.author, + tags: values.tags } asset?.accessDetails?.type === 'fixed' && diff --git a/src/components/Asset/Edit/_constants.ts b/src/components/Asset/Edit/_constants.ts index 6027ecb4c..aefafa657 100644 --- a/src/components/Asset/Edit/_constants.ts +++ b/src/components/Asset/Edit/_constants.ts @@ -14,7 +14,8 @@ export function getInitialValues( links: metadata?.links as any, files: [{ url: '', type: '' }], timeout: secondsToString(timeout), - author: metadata?.author + author: metadata?.author, + tags: metadata?.tags } } diff --git a/src/components/Asset/Edit/_types.ts b/src/components/Asset/Edit/_types.ts index c36b968b6..f5a972cb6 100644 --- a/src/components/Asset/Edit/_types.ts +++ b/src/components/Asset/Edit/_types.ts @@ -7,6 +7,7 @@ export interface MetadataEditForm { files: FileInfo[] links?: FileInfo[] author?: string + tags?: string[] } export interface ComputeEditForm { diff --git a/src/components/Footer/MarketStats/Total.tsx b/src/components/Footer/MarketStats/Total.tsx index e23a426c1..01b883ab5 100644 --- a/src/components/Footer/MarketStats/Total.tsx +++ b/src/components/Footer/MarketStats/Total.tsx @@ -1,3 +1,4 @@ +import PriceUnit from '@shared/Price/PriceUnit' import React, { ReactElement } from 'react' import { StatsTotal } from './_types' @@ -8,9 +9,12 @@ export default function MarketStatsTotal({ }): ReactElement { return ( <> - {total.orders} orders across{' '} - {total.nfts} assets with{' '} - {total.datatokens} different datatokens. + orders across{' '} + assets with{' '} + different datatokens.{' '} + {' '} + allocated.{' '} + locked. ) } diff --git a/src/components/Footer/MarketStats/_types.ts b/src/components/Footer/MarketStats/_types.ts index 7f3d89939..5d2cc2295 100644 --- a/src/components/Footer/MarketStats/_types.ts +++ b/src/components/Footer/MarketStats/_types.ts @@ -6,4 +6,6 @@ export interface StatsTotal { nfts: number datatokens: number orders: number + veAllocated: number + veLocked: number } diff --git a/src/components/Footer/MarketStats/index.tsx b/src/components/Footer/MarketStats/index.tsx index 665be6cd1..563be5839 100644 --- a/src/components/Footer/MarketStats/index.tsx +++ b/src/components/Footer/MarketStats/index.tsx @@ -14,11 +14,14 @@ import { useMarketMetadata } from '@context/MarketMetadata' import Tooltip from '@shared/atoms/Tooltip' import Markdown from '@shared/Markdown' import content from '../../../../content/footer.json' +import { getTotalAllocatedAndLocked } from '@utils/veAllocation' const initialTotal: StatsTotal = { nfts: 0, datatokens: 0, - orders: 0 + orders: 0, + veAllocated: 0, + veLocked: 0 } export default function MarketStats(): ReactElement { @@ -34,15 +37,15 @@ export default function MarketStats(): ReactElement { // Set the main chain ids we want to display stats for // useEffect(() => { - if (!networksList || !appConfig || !appConfig?.chainIdsSupported) return + if (!networksList || !appConfig || !appConfig?.chainIds) return const mainChainIdsList = filterNetworksByType( 'mainnet', - appConfig.chainIdsSupported, + appConfig.chainIds, networksList ) setMainChainIds(mainChainIdsList) - }, [appConfig, appConfig?.chainIdsSupported, networksList]) + }, [appConfig, appConfig?.chainIds, networksList]) // // Helper: fetch data from subgraph @@ -68,6 +71,12 @@ export default function MarketStats(): ReactElement { LoggerInstance.error('Error fetching global stats: ', error.message) } } + + const veTotals = await getTotalAllocatedAndLocked() + total.veAllocated = veTotals.totalAllocated + total.veLocked = veTotals.totalLocked + setTotal(total) + setData(newData) }, [mainChainIds]) @@ -83,9 +92,7 @@ export default function MarketStats(): ReactElement { // useEffect(() => { if (!data || !mainChainIds?.length) return - const newTotal: StatsTotal = { - ...initialTotal // always start calculating beginning from initial 0 values - } + const newTotal: StatsTotal = total for (const chainId of mainChainIds) { try { diff --git a/src/components/Header/SearchBar.module.css b/src/components/Header/SearchBar.module.css index a2f582104..2192c23a5 100644 --- a/src/components/Header/SearchBar.module.css +++ b/src/components/Header/SearchBar.module.css @@ -58,9 +58,6 @@ width: auto; left: auto; background: none; - } - - .input:focus + .button { z-index: 3; } } diff --git a/src/components/Home/SectionQueryResult.tsx b/src/components/Home/SectionQueryResult.tsx new file mode 100644 index 000000000..7efc796f1 --- /dev/null +++ b/src/components/Home/SectionQueryResult.tsx @@ -0,0 +1,83 @@ +import { useUserPreferences } from '@context/UserPreferences' +import { useCancelToken } from '@hooks/useCancelToken' +import { useIsMounted } from '@hooks/useIsMounted' +import { Asset, LoggerInstance } from '@oceanprotocol/lib' +import AssetList from '@shared/AssetList' +import { queryMetadata } from '@utils/aquarius' +import React, { ReactElement, useState, useEffect } from 'react' +import styles from './index.module.css' + +function sortElements(items: Asset[], sorted: string[]) { + items.sort(function (a, b) { + return sorted.indexOf(a.nftAddress) - sorted.indexOf(b.nftAddress) + }) + return items +} + +export default function SectionQueryResult({ + title, + query, + action, + queryData +}: { + title: ReactElement | string + query: SearchQuery + action?: ReactElement + queryData?: string[] +}): ReactElement { + const { chainIds } = useUserPreferences() + const [result, setResult] = useState() + const [loading, setLoading] = useState() + const isMounted = useIsMounted() + const newCancelToken = useCancelToken() + + useEffect(() => { + if (!query) return + + async function init() { + if (chainIds.length === 0) { + const result: PagedAssets = { + results: [], + page: 0, + totalPages: 0, + totalResults: 0, + aggregations: undefined + } + setResult(result) + setLoading(false) + } else { + try { + setLoading(true) + + const result = await queryMetadata(query, newCancelToken()) + if (!isMounted()) return + if (queryData && result?.totalResults > 0) { + const sortedAssets = sortElements(result.results, queryData) + const overflow = sortedAssets.length - 6 + sortedAssets.splice(sortedAssets.length - overflow, overflow) + result.results = sortedAssets + } + setResult(result) + setLoading(false) + } catch (error) { + LoggerInstance.error(error.message) + } + } + } + init() + }, [chainIds.length, isMounted, newCancelToken, query, queryData]) + + return ( +
+

{title}

+ + + + {action && action} +
+ ) +} diff --git a/src/components/Home/TopTags/_utils.ts b/src/components/Home/TopTags/_utils.ts new file mode 100644 index 000000000..dfcb48884 --- /dev/null +++ b/src/components/Home/TopTags/_utils.ts @@ -0,0 +1,45 @@ +import { LoggerInstance } from '@oceanprotocol/lib' +import { generateBaseQuery, queryMetadata } from '@utils/aquarius' +import axios, { CancelToken } from 'axios' +import { SortTermOptions } from 'src/@types/aquarius/SearchQuery' + +export async function getTopTags( + chainIds: number[], + cancelToken: CancelToken +): Promise { + const baseQueryParams = { + chainIds, + aggs: { + topTags: { + terms: { + field: 'metadata.tags.keyword', + size: 20, + order: { totalSales: 'desc' } + }, + aggs: { + totalSales: { + sum: { + field: SortTermOptions.Orders + } + } + } + } + }, + esPaginationOptions: { from: 0, size: 0 } + } as BaseQueryParams + + const query = generateBaseQuery(baseQueryParams) + try { + const result = await queryMetadata(query, cancelToken) + const tagsList = result?.aggregations?.topTags?.buckets.map( + (x: { key: any }) => x.key + ) + return tagsList + } catch (error) { + if (axios.isCancel(error)) { + LoggerInstance.log(error.message) + } else { + LoggerInstance.error(error.message) + } + } +} diff --git a/src/components/Home/TopTags/index.module.css b/src/components/Home/TopTags/index.module.css new file mode 100644 index 000000000..d176386d7 --- /dev/null +++ b/src/components/Home/TopTags/index.module.css @@ -0,0 +1,3 @@ +.section { + composes: section from '../index.module.css'; +} diff --git a/src/components/Home/TopTags/index.tsx b/src/components/Home/TopTags/index.tsx new file mode 100644 index 000000000..f20232868 --- /dev/null +++ b/src/components/Home/TopTags/index.tsx @@ -0,0 +1,51 @@ +import { useUserPreferences } from '@context/UserPreferences' +import React, { ReactElement, useEffect, useState } from 'react' +import styles from './index.module.css' +import Tags from '@shared/atoms/Tags' +import { getTopTags } from './_utils' +import { useCancelToken } from '@hooks/useCancelToken' +import { LoggerInstance } from '@oceanprotocol/lib' +import Loader from '@shared/atoms/Loader' + +export default function TopTags({ + title, + action +}: { + title: ReactElement | string + action?: ReactElement +}): ReactElement { + const { chainIds } = useUserPreferences() + const [result, setResult] = useState([]) + const [loading, setLoading] = useState() + const newCancelToken = useCancelToken() + useEffect(() => { + async function init() { + setLoading(true) + if (chainIds.length === 0) { + const result: string[] = [] + setResult(result) + setLoading(false) + } else { + try { + const tags = await getTopTags(chainIds, newCancelToken()) + setResult(tags) + setLoading(false) + } catch (error) { + LoggerInstance.error(error.message) + setLoading(false) + } + } + } + + init() + }, [chainIds]) + + return ( +
+

{title}

+ {loading ? : } + + {action && action} +
+ ) +} diff --git a/src/components/Home/index.tsx b/src/components/Home/index.tsx index e256ee102..1cd7cc4c5 100644 --- a/src/components/Home/index.tsx +++ b/src/components/Home/index.tsx @@ -1,103 +1,25 @@ import React, { ReactElement, useEffect, useState } from 'react' -import AssetList from '@shared/AssetList' import Button from '@shared/atoms/Button' import Bookmarks from './Bookmarks' -import { generateBaseQuery, queryMetadata } from '@utils/aquarius' -import { Asset, LoggerInstance } from '@oceanprotocol/lib' +import { generateBaseQuery } from '@utils/aquarius' import { useUserPreferences } from '@context/UserPreferences' -import { useIsMounted } from '@hooks/useIsMounted' -import { useCancelToken } from '@hooks/useCancelToken' import { SortTermOptions } from '../../@types/aquarius/SearchQuery' import TopSales from './TopSales' +import TopTags from './TopTags' +import SectionQueryResult from './SectionQueryResult' import styles from './index.module.css' -function sortElements(items: Asset[], sorted: string[]) { - items.sort(function (a, b) { - return ( - sorted.indexOf(a.services[0].datatokenAddress.toLowerCase()) - - sorted.indexOf(b.services[0].datatokenAddress.toLowerCase()) - ) - }) - return items -} - -function SectionQueryResult({ - title, - query, - action, - queryData -}: { - title: ReactElement | string - query: SearchQuery - action?: ReactElement - queryData?: string[] -}) { - const { chainIds } = useUserPreferences() - const [result, setResult] = useState() - const [loading, setLoading] = useState() - const isMounted = useIsMounted() - const newCancelToken = useCancelToken() - - useEffect(() => { - if (!query) return - - async function init() { - if (chainIds.length === 0) { - const result: PagedAssets = { - results: [], - page: 0, - totalPages: 0, - totalResults: 0, - aggregations: undefined - } - setResult(result) - setLoading(false) - } else { - try { - setLoading(true) - const result = await queryMetadata(query, newCancelToken()) - if (!isMounted()) return - if (queryData && result?.totalResults > 0) { - const sortedAssets = sortElements(result.results, queryData) - const overflow = sortedAssets.length - 9 - sortedAssets.splice(sortedAssets.length - overflow, overflow) - result.results = sortedAssets - } - setResult(result) - setLoading(false) - } catch (error) { - LoggerInstance.error(error.message) - } - } - } - init() - }, [chainIds.length, isMounted, newCancelToken, query, queryData]) - - return ( -
-

{title}

- - - - {action && action} -
- ) -} - export default function HomePage(): ReactElement { const [queryLatest, setQueryLatest] = useState() const [queryMostSales, setQueryMostSales] = useState() + const [queryMostAllocation, setQueryMostAllocation] = useState() const { chainIds } = useUserPreferences() useEffect(() => { const baseParams = { chainIds, esPaginationOptions: { - size: 9 + size: 6 }, sortOptions: { sortBy: SortTermOptions.Created @@ -108,13 +30,23 @@ export default function HomePage(): ReactElement { const baseParamsSales = { chainIds, esPaginationOptions: { - size: 9 + size: 6 }, sortOptions: { - sortBy: SortTermOptions.Stats + sortBy: SortTermOptions.Orders } as SortOptions } as BaseQueryParams setQueryMostSales(generateBaseQuery(baseParamsSales)) + const baseParamsAllocation = { + chainIds, + esPaginationOptions: { + size: 6 + }, + sortOptions: { + sortBy: SortTermOptions.Allocated + } as SortOptions + } as BaseQueryParams + setQueryMostAllocation(generateBaseQuery(baseParamsAllocation)) }, [chainIds]) return ( @@ -124,8 +56,16 @@ export default function HomePage(): ReactElement { + + + + + } /> - - ) } diff --git a/src/components/Profile/History/ComputeJobs/index.tsx b/src/components/Profile/History/ComputeJobs/index.tsx index 405b50809..a1fd24cc7 100644 --- a/src/components/Profile/History/ComputeJobs/index.tsx +++ b/src/components/Profile/History/ComputeJobs/index.tsx @@ -1,6 +1,5 @@ -import React, { ReactElement, useEffect, useState, useCallback } from 'react' +import React, { ReactElement, useState } from 'react' import Time from '@shared/atoms/Time' -import { LoggerInstance } from '@oceanprotocol/lib' import Table, { TableOceanColumn } from '@shared/atoms/Table' import Button from '@shared/atoms/Button' import { useWeb3 } from '@context/Web3' @@ -8,11 +7,7 @@ import Details from './Details' import Refresh from '@images/refresh.svg' import { useUserPreferences } from '@context/UserPreferences' import NetworkName from '@shared/NetworkName' -import { getComputeJobs } from '@utils/compute' import styles from './index.module.css' -import { useAsset } from '@context/Asset' -import { useIsMounted } from '@hooks/useIsMounted' -import { useCancelToken } from '@hooks/useCancelToken' import AssetListTitle from '@shared/AssetList/AssetListTitle' export function Status({ children }: { children: string }): ReactElement { @@ -51,48 +46,19 @@ const columns: TableOceanColumn[] = [ export default function ComputeJobs({ minimal, - assetChainIds, + jobs, + isLoading, refetchJobs }: { minimal?: boolean - assetChainIds?: number[] - refetchJobs?: boolean + jobs?: ComputeJobMetaData[] + isLoading?: boolean + refetchJobs?: any }): ReactElement { const { accountId } = useWeb3() - const { asset } = useAsset() const { chainIds } = useUserPreferences() - const isMounted = useIsMounted() - const newCancelToken = useCancelToken() - - const [isLoading, setIsLoading] = useState(false) - const [jobs, setJobs] = useState([]) const [columnsMinimal] = useState([columns[4], columns[5], columns[3]]) - const fetchJobs = useCallback(async () => { - if (!chainIds || chainIds.length === 0 || !accountId) { - setJobs([]) - setIsLoading(false) - return - } - try { - setIsLoading(true) - const jobs = await getComputeJobs( - assetChainIds || chainIds, - accountId, - asset, - newCancelToken() - ) - isMounted() && setJobs(jobs.computeJobs) - setIsLoading(!jobs.isLoaded) - } catch (error) { - LoggerInstance.error(error.message) - } - }, [chainIds, accountId, asset, isMounted, assetChainIds, newCancelToken]) - - useEffect(() => { - fetchJobs() - }, [fetchJobs, refetchJobs]) - return accountId ? ( <> {jobs?.length >= 0 && !minimal && ( @@ -100,7 +66,7 @@ export default function ComputeJobs({ style="text" size="small" title="Refresh compute jobs" - onClick={async () => await fetchJobs()} + onClick={async () => await refetchJobs(true)} disabled={isLoading} className={styles.refresh} > diff --git a/src/components/Profile/History/index.tsx b/src/components/Profile/History/index.tsx index 10af47f11..8be94572a 100644 --- a/src/components/Profile/History/index.tsx +++ b/src/components/Profile/History/index.tsx @@ -1,17 +1,30 @@ -import React, { ReactElement } from 'react' +import React, { ReactElement, useCallback, useEffect, useState } from 'react' import Tabs from '@shared/atoms/Tabs' import PublishedList from './PublishedList' import Downloads from './Downloads' import ComputeJobs from './ComputeJobs' import styles from './index.module.css' import { useWeb3 } from '@context/Web3' +import { chainIds } from 'app.config' +import { getComputeJobs } from '@utils/compute' +import { useUserPreferences } from '@context/UserPreferences' +import { useCancelToken } from '@hooks/useCancelToken' +import { LoggerInstance } from '@oceanprotocol/lib' interface HistoryTab { title: string content: JSX.Element } -function getTabs(accountId: string, userAccountId: string): HistoryTab[] { +const refreshInterval = 10000 // 10 sec. +function getTabs( + accountId: string, + userAccountId: string, + jobs: ComputeJobMetaData[], + isLoadingJobs: boolean, + refetchJobs: boolean, + setRefetchJobs: any +): HistoryTab[] { const defaultTabs: HistoryTab[] = [ { title: 'Published', @@ -24,7 +37,13 @@ function getTabs(accountId: string, userAccountId: string): HistoryTab[] { ] const computeTab: HistoryTab = { title: 'Compute Jobs', - content: + content: ( + setRefetchJobs(!refetchJobs)} + /> + ) } if (accountId === userAccountId) { defaultTabs.push(computeTab) @@ -38,10 +57,62 @@ export default function HistoryPage({ accountIdentifier: string }): ReactElement { const { accountId } = useWeb3() + const { chainIds } = useUserPreferences() + const newCancelToken = useCancelToken() const url = new URL(location.href) const defaultTab = url.searchParams.get('defaultTab') - const tabs = getTabs(accountIdentifier, accountId) + + const [refetchJobs, setRefetchJobs] = useState(false) + const [isLoadingJobs, setIsLoadingJobs] = useState(false) + const [jobs, setJobs] = useState([]) + + const fetchJobs = useCallback( + async (type: string) => { + if (!chainIds || chainIds.length === 0 || !accountId) { + return + } + + try { + type === 'init' && setIsLoadingJobs(true) + const computeJobs = await getComputeJobs( + chainIds, + accountId, + null, + newCancelToken() + ) + setJobs(computeJobs.computeJobs) + setIsLoadingJobs(!computeJobs.isLoaded) + } catch (error) { + LoggerInstance.error(error.message) + setIsLoadingJobs(false) + } + }, + [accountId, chainIds, isLoadingJobs, newCancelToken] + ) + + useEffect(() => { + fetchJobs('init') + + // init periodic refresh for jobs + const balanceInterval = setInterval( + () => fetchJobs('repeat'), + refreshInterval + ) + + return () => { + clearInterval(balanceInterval) + } + }, [refetchJobs]) + + const tabs = getTabs( + accountIdentifier, + accountId, + jobs, + isLoadingJobs, + refetchJobs, + setRefetchJobs + ) let defaultTabIndex = 0 defaultTab === 'ComputeJobs' ? (defaultTabIndex = 4) : (defaultTabIndex = 0) diff --git a/src/components/Publish/AvailableNetworks/index.tsx b/src/components/Publish/AvailableNetworks/index.tsx index d6bc5e97b..8e74dad5d 100644 --- a/src/components/Publish/AvailableNetworks/index.tsx +++ b/src/components/Publish/AvailableNetworks/index.tsx @@ -26,7 +26,6 @@ export default function AvailableNetworks(): ReactElement { { title: 'Main', data: networksMain }, { title: 'Test', data: networksTest } ] - const networkList = (networks: number[]) => networks.map((chainId) => (
  • diff --git a/src/components/Publish/Metadata/index.tsx b/src/components/Publish/Metadata/index.tsx index 2be4852bd..775246ab9 100644 --- a/src/components/Publish/Metadata/index.tsx +++ b/src/components/Publish/Metadata/index.tsx @@ -1,6 +1,6 @@ import { BoxSelectionOption } from '@shared/FormFields/BoxSelection' import Input from '@shared/FormInput' -import { Field, useFormikContext } from 'formik' +import { Field, useField, useFormikContext } from 'formik' import React, { ReactElement, useEffect } from 'react' import content from '../../../../content/publish/form.json' import { FormPublishData } from '../_types' @@ -23,6 +23,8 @@ export default function MetadataFields(): ReactElement { // connect with Form state, use for conditional field rendering const { values, setFieldValue } = useFormikContext() + const [field, meta] = useField('metadata.dockerImageCustomChecksum') + // BoxSelection component is not a Formik component // so we need to handle checked state manually. const assetTypeOptions: BoxSelectionOption[] = [ @@ -124,11 +126,14 @@ export default function MetadataFields(): ReactElement { /> { if (dockerImage === '') return const preset = algorithmContainerPresets.find( (preset) => `${preset.image}:${preset.tag}` === dockerImage ) + preset.checksum = await ( + await getContainerChecksum(preset.image, preset.tag) + ).checksum return preset } @@ -47,8 +51,7 @@ function dateToStringNoMS(date: Date): string { return date.toISOString().replace(/\.[0-9]{3}Z/, 'Z') } -function transformTags(value: string): string[] { - const originalTags = value?.split(',') +function transformTags(originalTags: string[]): string[] { const transformedTags = originalTags?.map((tag) => slugify(tag).toLowerCase()) return transformedTags } @@ -81,6 +84,11 @@ export async function transformPublishFormToDdo( const currentTime = dateToStringNoMS(new Date()) const isPreview = !datatokenAddress && !nftAddress + const algorithmContainerPresets = + type === 'algorithm' && dockerImage !== '' && dockerImage !== 'custom' + ? await getAlgorithmContainerPreset(dockerImage) + : null + // Transform from files[0].url to string[] assuming only 1 file const filesTransformed = files?.length && files[0].valid && [sanitizeUrl(files[0].url)] @@ -111,20 +119,19 @@ export async function transformPublishFormToDdo( entrypoint: dockerImage === 'custom' ? dockerImageCustomEntrypoint - : getAlgorithmContainerPreset(dockerImage).entrypoint, + : algorithmContainerPresets.entrypoint, image: dockerImage === 'custom' ? dockerImageCustom - : getAlgorithmContainerPreset(dockerImage).image, + : algorithmContainerPresets.image, tag: dockerImage === 'custom' ? dockerImageCustomTag - : getAlgorithmContainerPreset(dockerImage).tag, + : algorithmContainerPresets.tag, checksum: dockerImage === 'custom' - ? // ? dockerImageCustomChecksum - '' - : getAlgorithmContainerPreset(dockerImage).checksum + ? dockerImageCustomChecksum + : algorithmContainerPresets.checksum } } })