mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
adding ipfs / arweave support (#1765)
* 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 * cleanup logic * update @oceanprotocol/lib * Update package-lock.json * fix test error * fix test assetsWithAccessDetails Co-authored-by: Matthias Kretschmann <m@kretschmann.io>
This commit is contained in:
parent
071948e0e6
commit
f6d11e5e6f
@ -57,12 +57,12 @@ export const assetAquarius: Asset = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 22
|
orders: 22,
|
||||||
// price: {
|
price: {
|
||||||
// value: 3231343254,
|
value: 3231343254,
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// tokenSymbol: 'OCEAN'
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
purgatory: {
|
purgatory: {
|
||||||
state: false,
|
state: false,
|
||||||
|
@ -68,8 +68,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 0
|
orders: 0,
|
||||||
// price: {}
|
price: {
|
||||||
|
value: 3231343254,
|
||||||
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -151,12 +155,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 45554.69921875,
|
allocated: 45554.69921875,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0x282d8efCe846A88B159800bd4130ad77443Fa1A1',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'mOCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 100
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -245,12 +249,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 7
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -339,12 +343,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 50.2051887512,
|
allocated: 50.2051887512,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 10
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -439,12 +443,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 6
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -569,12 +573,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 5
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0'
|
version: '4.1.0'
|
||||||
},
|
},
|
||||||
@ -657,12 +661,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 5
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -751,12 +755,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 5
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -844,10 +848,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 114.1658859253,
|
allocated: 114.1658859253,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// value: 0
|
value: 3231343254,
|
||||||
// }
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -927,12 +933,12 @@ export const assets: AssetExtended[] = [
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 2
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -1018,10 +1024,12 @@ export const assets: AssetExtended[] = [
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// value: 0
|
value: 3231343254,
|
||||||
// }
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -1103,10 +1111,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 11159.279296875,
|
allocated: 11159.279296875,
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// value: 0
|
value: 3231343254,
|
||||||
// }
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -1186,10 +1196,12 @@ export const assets: AssetExtended[] = [
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 0
|
orders: 0,
|
||||||
// price: {
|
price: {
|
||||||
// value: 0
|
value: 3231343254,
|
||||||
// }
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -1270,8 +1282,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 0,
|
allocated: 0,
|
||||||
orders: 0
|
orders: 0,
|
||||||
// price: {}
|
price: {
|
||||||
|
value: 3231343254,
|
||||||
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -1339,12 +1355,12 @@ export const assets: AssetExtended[] = [
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 1
|
orders: 1,
|
||||||
// price: {
|
price: {
|
||||||
// tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
value: 3231343254,
|
||||||
// tokenSymbol: 'OCEAN',
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
// value: 1
|
tokenSymbol: 'OCEAN'
|
||||||
// }
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -1430,10 +1446,12 @@ export const assets: AssetExtended[] = [
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
orders: 0
|
orders: 0,
|
||||||
// price: {
|
price: {
|
||||||
// value: 0
|
value: 3231343254,
|
||||||
// }
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
@ -1525,7 +1543,12 @@ export const assets: AssetExtended[] = [
|
|||||||
],
|
],
|
||||||
stats: {
|
stats: {
|
||||||
allocated: 422.9883117676,
|
allocated: 422.9883117676,
|
||||||
orders: 0
|
orders: 0,
|
||||||
|
price: {
|
||||||
|
value: 3231343254,
|
||||||
|
tokenAddress: '0xCfDdA22C9837aE76E0faA845354f33C62E03653a',
|
||||||
|
tokenSymbol: 'OCEAN'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
version: '4.1.0',
|
version: '4.1.0',
|
||||||
accessDetails: {
|
accessDetails: {
|
||||||
|
1
.jest/__mocks__/is-ipfs/index.ts
Normal file
1
.jest/__mocks__/is-ipfs/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export default true
|
@ -1 +1,3 @@
|
|||||||
export default true
|
export default function isUrl() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
@ -29,19 +29,62 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "files",
|
"name": "files",
|
||||||
"label": "New file",
|
"label": "File",
|
||||||
"placeholder": "e.g. https://file.com/file.json",
|
"prominentHelp": false,
|
||||||
"help": "This URL 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.** For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. Leaving this field empty will not remove the current value.",
|
"type": "tabs",
|
||||||
|
"fields": [{
|
||||||
|
"value": "ipfs",
|
||||||
|
"title": "IPFS",
|
||||||
|
"label": "CID",
|
||||||
|
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
|
||||||
|
"help": "This CID 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,
|
"prominentHelp": true,
|
||||||
"type": "files"
|
"type": "files",
|
||||||
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "links",
|
"value": "arweave",
|
||||||
"label": "New sample file",
|
"title": "Arweave",
|
||||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
"label": "Transaction ID",
|
||||||
"help": "Please provide a URL to a sample of your dataset file. This file should reveal the data structure of your dataset, e.g. by including the header and one line of a CSV file. This file URL will be publicly available after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.** Leaving this field empty will not remove the current value.",
|
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
|
||||||
|
"help": "This Transaction ID 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,
|
"prominentHelp": true,
|
||||||
"type": "files"
|
"type": "files",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL 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
|
||||||
|
}],
|
||||||
|
"sortOptions": false,
|
||||||
|
"required": true
|
||||||
|
},{
|
||||||
|
"name": "links",
|
||||||
|
"label": "Sample file",
|
||||||
|
"prominentHelp": false,
|
||||||
|
"type": "tabs",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL 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": false
|
||||||
|
}],
|
||||||
|
"required": false
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -104,19 +104,61 @@
|
|||||||
{
|
{
|
||||||
"name": "files",
|
"name": "files",
|
||||||
"label": "File",
|
"label": "File",
|
||||||
"placeholder": "e.g. https://file.com/file.json",
|
"prominentHelp": false,
|
||||||
"help": "This URL 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.** For a compute dataset, your file should match the file type required by the algorithm, and should not exceed 1 GB in file size. ",
|
"type": "tabs",
|
||||||
|
"fields": [{
|
||||||
|
"value": "ipfs",
|
||||||
|
"title": "IPFS",
|
||||||
|
"label": "CID",
|
||||||
|
"placeholder": "e.g. bafkreidgvpkjawlxz6sffxzwgooowe5yt7i6wsyg236mfoks77nywkptdq",
|
||||||
|
"help": "This CID 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,
|
"prominentHelp": true,
|
||||||
"type": "files",
|
"type": "files",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"value": "arweave",
|
||||||
|
"title": "Arweave",
|
||||||
|
"label": "Transaction ID",
|
||||||
|
"placeholder": "e.g. DBRCL94j3QqdPaUtt4VWRen8rZfJZBb7Ey40iMpXfhtd",
|
||||||
|
"help": "This Transaction ID 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
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL 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
|
||||||
|
}],
|
||||||
|
"sortOptions": false,
|
||||||
|
"required": true
|
||||||
|
},{
|
||||||
"name": "links",
|
"name": "links",
|
||||||
"label": "Sample file",
|
"label": "Sample file",
|
||||||
"placeholder": "e.g. https://file.com/samplefile.json",
|
"prominentHelp": false,
|
||||||
"help": "This file should reveal the data structure of your dataset, e.g. by including the header and one line of a CSV file. This file URL will be publicly available after publishing. **Please make sure that the endpoint is accessible over the internet and is not protected by a firewall or by credentials.**",
|
"type": "tabs",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"value": "url",
|
||||||
|
"title": "URL",
|
||||||
|
"label": "File",
|
||||||
|
"placeholder": "e.g. https://file.com/file.json",
|
||||||
|
"help": "This URL 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,
|
"prominentHelp": true,
|
||||||
"type": "files"
|
"type": "files",
|
||||||
|
"required": false
|
||||||
|
}],
|
||||||
|
"required": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "algorithmPrivacy",
|
"name": "algorithmPrivacy",
|
||||||
|
45915
package-lock.json
generated
45915
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -26,7 +26,7 @@
|
|||||||
"@coingecko/cryptoformat": "^0.5.4",
|
"@coingecko/cryptoformat": "^0.5.4",
|
||||||
"@loadable/component": "^5.15.2",
|
"@loadable/component": "^5.15.2",
|
||||||
"@oceanprotocol/art": "^3.2.0",
|
"@oceanprotocol/art": "^3.2.0",
|
||||||
"@oceanprotocol/lib": "^2.4.0",
|
"@oceanprotocol/lib": "^2.5.2",
|
||||||
"@oceanprotocol/typographies": "^0.1.0",
|
"@oceanprotocol/typographies": "^0.1.0",
|
||||||
"@oceanprotocol/use-dark-mode": "^2.4.3",
|
"@oceanprotocol/use-dark-mode": "^2.4.3",
|
||||||
"@tippyjs/react": "^4.2.6",
|
"@tippyjs/react": "^4.2.6",
|
||||||
@ -41,6 +41,7 @@
|
|||||||
"filesize": "^10.0.5",
|
"filesize": "^10.0.5",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
|
"is-ipfs": "^7.0.3",
|
||||||
"is-url-superb": "^6.1.0",
|
"is-url-superb": "^6.1.0",
|
||||||
"js-cookie": "^3.0.1",
|
"js-cookie": "^3.0.1",
|
||||||
"match-sorter": "^6.3.1",
|
"match-sorter": "^6.3.1",
|
||||||
|
5
src/@utils/ipfs.ts
Normal file
5
src/@utils/ipfs.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import * as isIPFS from 'is-ipfs'
|
||||||
|
|
||||||
|
export function isCID(value: string) {
|
||||||
|
return isIPFS.cid(value)
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
import {
|
import {
|
||||||
|
Arweave,
|
||||||
ComputeAlgorithm,
|
ComputeAlgorithm,
|
||||||
ComputeAsset,
|
ComputeAsset,
|
||||||
ComputeEnvironment,
|
ComputeEnvironment,
|
||||||
downloadFileBrowser,
|
downloadFileBrowser,
|
||||||
FileInfo,
|
FileInfo,
|
||||||
|
Ipfs,
|
||||||
LoggerInstance,
|
LoggerInstance,
|
||||||
ProviderComputeInitializeResults,
|
ProviderComputeInitializeResults,
|
||||||
ProviderInstance,
|
ProviderInstance,
|
||||||
@ -83,18 +85,45 @@ export async function getFileDidInfo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getFileUrlInfo(
|
export async function getFileInfo(
|
||||||
url: string,
|
file: string,
|
||||||
providerUrl: string
|
providerUrl: string,
|
||||||
|
storageType: string
|
||||||
): Promise<FileInfo[]> {
|
): Promise<FileInfo[]> {
|
||||||
try {
|
try {
|
||||||
|
let response
|
||||||
|
switch (storageType) {
|
||||||
|
case 'ipfs': {
|
||||||
|
const fileIPFS: Ipfs = {
|
||||||
|
type: 'ipfs',
|
||||||
|
hash: file
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await ProviderInstance.getFileInfo(fileIPFS, providerUrl)
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 'arweave': {
|
||||||
|
const fileArweave: Arweave = {
|
||||||
|
type: 'arweave',
|
||||||
|
transactionId: file
|
||||||
|
}
|
||||||
|
|
||||||
|
response = await ProviderInstance.getFileInfo(fileArweave, providerUrl)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default: {
|
||||||
const fileUrl: UrlFile = {
|
const fileUrl: UrlFile = {
|
||||||
type: 'url',
|
type: 'url',
|
||||||
index: 0,
|
index: 0,
|
||||||
url,
|
url: file,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
}
|
}
|
||||||
const response = await ProviderInstance.getFileInfo(fileUrl, providerUrl)
|
|
||||||
|
response = await ProviderInstance.getFileInfo(fileUrl, providerUrl)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
return response
|
return response
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
LoggerInstance.error(error.message)
|
LoggerInstance.error(error.message)
|
||||||
|
44
src/@utils/yup.ts
Normal file
44
src/@utils/yup.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { isCID } from '@utils/ipfs'
|
||||||
|
import isUrl from 'is-url-superb'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
|
export function testLinks() {
|
||||||
|
return Yup.string().test((value, context) => {
|
||||||
|
const { type } = context.parent
|
||||||
|
let validField
|
||||||
|
let errorMessage
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'url':
|
||||||
|
validField = isUrl(value?.toString() || '')
|
||||||
|
if (!validField) {
|
||||||
|
errorMessage = 'Must be a valid url.'
|
||||||
|
} else {
|
||||||
|
if (value?.toString().includes('drive.google')) {
|
||||||
|
validField = false
|
||||||
|
errorMessage =
|
||||||
|
'Google Drive is not a supported hosting service. Please use an alternative.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'ipfs':
|
||||||
|
validField = isCID(value?.toString())
|
||||||
|
errorMessage = !value?.toString() ? 'CID required.' : 'CID not valid.'
|
||||||
|
break
|
||||||
|
case 'arweave':
|
||||||
|
validField = !value?.toString().includes('http')
|
||||||
|
errorMessage = !value?.toString()
|
||||||
|
? 'Transaction ID required.'
|
||||||
|
: 'Transaction ID not valid.'
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validField) {
|
||||||
|
return context.createError({
|
||||||
|
message: errorMessage
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
@ -75,6 +75,7 @@ export default function ContainerInput(props: InputProps): ReactElement {
|
|||||||
name={`${field.name}[0].url`}
|
name={`${field.name}[0].url`}
|
||||||
checkUrl={false}
|
checkUrl={false}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
storageType={'url'}
|
||||||
handleButtonClick={handleValidation}
|
handleButtonClick={handleValidation}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -23,7 +23,7 @@ export default function FileInfo({
|
|||||||
{hideUrl ? 'https://oceanprotocol/placeholder' : file.url}
|
{hideUrl ? 'https://oceanprotocol/placeholder' : file.url}
|
||||||
</h3>
|
</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li className={styles.success}>✓ URL confirmed</li>
|
<li className={styles.success}>✓ File confirmed</li>
|
||||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||||
{contentTypeCleaned && <li>{contentTypeCleaned}</li>}
|
{contentTypeCleaned && <li>{contentTypeCleaned}</li>}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -2,7 +2,7 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import FilesInput from './index'
|
import FilesInput from './index'
|
||||||
import { useField } from 'formik'
|
import { useField } from 'formik'
|
||||||
import { getFileUrlInfo } from '@utils/provider'
|
import { getFileInfo } from '@utils/provider'
|
||||||
|
|
||||||
jest.mock('formik')
|
jest.mock('formik')
|
||||||
jest.mock('@utils/provider')
|
jest.mock('@utils/provider')
|
||||||
@ -48,16 +48,17 @@ const mockForm = {
|
|||||||
describe('@shared/FormInput/InputElement/FilesInput', () => {
|
describe('@shared/FormInput/InputElement/FilesInput', () => {
|
||||||
it('renders without crashing', async () => {
|
it('renders without crashing', async () => {
|
||||||
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers])
|
;(useField as jest.Mock).mockReturnValue([mockField, mockMeta, mockHelpers])
|
||||||
;(getFileUrlInfo as jest.Mock).mockReturnValue([
|
;(getFileInfo as jest.Mock).mockReturnValue([
|
||||||
{
|
{
|
||||||
valid: true,
|
valid: true,
|
||||||
url: 'https://hello.com',
|
url: 'https://hello.com',
|
||||||
|
type: 'url',
|
||||||
contentType: 'text/html',
|
contentType: 'text/html',
|
||||||
contentLength: 100
|
contentLength: 100
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
|
|
||||||
render(<FilesInput form={mockForm} {...props} />)
|
render(<FilesInput form={mockForm} field={mockField} {...props} />)
|
||||||
expect(screen.getByText('Validate')).toBeInTheDocument()
|
expect(screen.getByText('Validate')).toBeInTheDocument()
|
||||||
fireEvent.click(screen.getByText('Validate'))
|
fireEvent.click(screen.getByText('Validate'))
|
||||||
|
|
||||||
@ -67,13 +68,14 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
|||||||
expect(mockHelpers.setValue).toHaveBeenCalled()
|
expect(mockHelpers.setValue).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders fileinfo when file is valid', () => {
|
it('renders fileinfo when file url is valid', () => {
|
||||||
;(useField as jest.Mock).mockReturnValue([
|
;(useField as jest.Mock).mockReturnValue([
|
||||||
{
|
{
|
||||||
value: [
|
value: [
|
||||||
{
|
{
|
||||||
valid: true,
|
valid: true,
|
||||||
url: 'https://hello.com',
|
url: 'https://hello.com',
|
||||||
|
type: 'url',
|
||||||
contentType: 'text/html',
|
contentType: 'text/html',
|
||||||
contentLength: 100
|
contentLength: 100
|
||||||
}
|
}
|
||||||
@ -82,10 +84,52 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
|||||||
mockMeta,
|
mockMeta,
|
||||||
mockHelpers
|
mockHelpers
|
||||||
])
|
])
|
||||||
render(<FilesInput {...props} />)
|
render(<FilesInput {...props} field={mockField} />)
|
||||||
expect(screen.getByText('https://hello.com')).toBeInTheDocument()
|
expect(screen.getByText('https://hello.com')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('renders fileinfo when ipfs is valid', () => {
|
||||||
|
;(useField as jest.Mock).mockReturnValue([
|
||||||
|
{
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
valid: true,
|
||||||
|
hash: 'bafkreicxccbk4blsx5qtovqfgsuutxjxom47dvyzyz3asi2ggjg5ipwlc4',
|
||||||
|
type: 'ipfs',
|
||||||
|
contentLength: '40492',
|
||||||
|
contentType: 'text/csv',
|
||||||
|
index: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
mockMeta,
|
||||||
|
mockHelpers
|
||||||
|
])
|
||||||
|
render(<FilesInput {...props} field={mockField} />)
|
||||||
|
expect(screen.getByText('✓ File confirmed')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders fileinfo when arweave is valid', () => {
|
||||||
|
;(useField as jest.Mock).mockReturnValue([
|
||||||
|
{
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
valid: true,
|
||||||
|
transactionId: 'T6NL8Zc0LCbT3bF9HacAGQC4W0_hW7b3tXbm8OtWtlA',
|
||||||
|
type: 'arweave',
|
||||||
|
contentLength: '57043',
|
||||||
|
contentType: 'image/jpeg',
|
||||||
|
index: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
mockMeta,
|
||||||
|
mockHelpers
|
||||||
|
])
|
||||||
|
render(<FilesInput {...props} field={mockField} />)
|
||||||
|
expect(screen.getByText('✓ File confirmed')).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
it('renders fileinfo without contentType', () => {
|
it('renders fileinfo without contentType', () => {
|
||||||
;(useField as jest.Mock).mockReturnValue([
|
;(useField as jest.Mock).mockReturnValue([
|
||||||
{
|
{
|
||||||
@ -93,6 +137,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
|||||||
{
|
{
|
||||||
valid: true,
|
valid: true,
|
||||||
url: 'https://hello.com',
|
url: 'https://hello.com',
|
||||||
|
type: 'url',
|
||||||
contentLength: 100
|
contentLength: 100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -100,7 +145,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
|||||||
mockMeta,
|
mockMeta,
|
||||||
mockHelpers
|
mockHelpers
|
||||||
])
|
])
|
||||||
render(<FilesInput {...props} />)
|
render(<FilesInput {...props} field={mockField} />)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders fileinfo placeholder when hideUrl is passed', () => {
|
it('renders fileinfo placeholder when hideUrl is passed', () => {
|
||||||
@ -117,7 +162,7 @@ describe('@shared/FormInput/InputElement/FilesInput', () => {
|
|||||||
mockMeta,
|
mockMeta,
|
||||||
mockHelpers
|
mockHelpers
|
||||||
])
|
])
|
||||||
render(<FilesInput {...props} />)
|
render(<FilesInput {...props} field={mockField} />)
|
||||||
expect(
|
expect(
|
||||||
screen.getByText('https://oceanprotocol/placeholder')
|
screen.getByText('https://oceanprotocol/placeholder')
|
||||||
).toBeInTheDocument()
|
).toBeInTheDocument()
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import React, { ReactElement, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import { useField } from 'formik'
|
import { useField } from 'formik'
|
||||||
import FileInfo from './Info'
|
import FileInfo from './Info'
|
||||||
import UrlInput from '../URLInput'
|
import UrlInput from '../URLInput'
|
||||||
import { InputProps } from '@shared/FormInput'
|
import { InputProps } from '@shared/FormInput'
|
||||||
import { getFileUrlInfo } from '@utils/provider'
|
import { getFileInfo } from '@utils/provider'
|
||||||
import { LoggerInstance } from '@oceanprotocol/lib'
|
import { LoggerInstance } from '@oceanprotocol/lib'
|
||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
|
|
||||||
@ -12,14 +12,17 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const { asset } = useAsset()
|
const { asset } = useAsset()
|
||||||
|
|
||||||
|
const providerUrl = props.form?.values?.services
|
||||||
|
? props.form?.values?.services[0].providerUrl.url
|
||||||
|
: asset.services[0].serviceEndpoint
|
||||||
|
|
||||||
|
const storageType = field.value[0].type
|
||||||
|
|
||||||
async function handleValidation(e: React.SyntheticEvent, url: string) {
|
async function handleValidation(e: React.SyntheticEvent, url: string) {
|
||||||
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
||||||
e?.preventDefault()
|
e?.preventDefault()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const providerUrl = props.form?.values?.services
|
|
||||||
? props.form?.values?.services[0].providerUrl.url
|
|
||||||
: asset.services[0].serviceEndpoint
|
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
|
|
||||||
// TODO: handled on provider
|
// TODO: handled on provider
|
||||||
@ -29,7 +32,7 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const checkedFile = await getFileUrlInfo(url, providerUrl)
|
const checkedFile = await getFileInfo(url, providerUrl, storageType)
|
||||||
|
|
||||||
// error if something's not right from response
|
// error if something's not right from response
|
||||||
if (!checkedFile)
|
if (!checkedFile)
|
||||||
@ -39,7 +42,7 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
throw Error('✗ No valid file detected. Check your URL and try again.')
|
throw Error('✗ No valid file detected. Check your URL and try again.')
|
||||||
|
|
||||||
// if all good, add file to formik state
|
// if all good, add file to formik state
|
||||||
helpers.setValue([{ url, ...checkedFile[0] }])
|
helpers.setValue([{ url, type: storageType, ...checkedFile[0] }])
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
props.form.setFieldError(`${field.name}[0].url`, error.message)
|
props.form.setFieldError(`${field.name}[0].url`, error.message)
|
||||||
LoggerInstance.error(error.message)
|
LoggerInstance.error(error.message)
|
||||||
@ -50,7 +53,9 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
helpers.setTouched(false)
|
helpers.setTouched(false)
|
||||||
helpers.setValue(meta.initialValue)
|
helpers.setValue([
|
||||||
|
{ url: '', type: storageType === 'hidden' ? 'ipfs' : storageType }
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -64,7 +69,9 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
{...props}
|
{...props}
|
||||||
name={`${field.name}[0].url`}
|
name={`${field.name}[0].url`}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
checkUrl={true}
|
||||||
handleButtonClick={handleValidation}
|
handleButtonClick={handleValidation}
|
||||||
|
storageType={storageType}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -6,6 +6,7 @@ import styles from './index.module.css'
|
|||||||
import InputGroup from '@shared/FormInput/InputGroup'
|
import InputGroup from '@shared/FormInput/InputGroup'
|
||||||
import InputElement from '@shared/FormInput/InputElement'
|
import InputElement from '@shared/FormInput/InputElement'
|
||||||
import isUrl from 'is-url-superb'
|
import isUrl from 'is-url-superb'
|
||||||
|
import { isCID } from '@utils/ipfs'
|
||||||
|
|
||||||
export interface URLInputProps {
|
export interface URLInputProps {
|
||||||
submitText: string
|
submitText: string
|
||||||
@ -13,6 +14,7 @@ export interface URLInputProps {
|
|||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
name: string
|
name: string
|
||||||
checkUrl?: boolean
|
checkUrl?: boolean
|
||||||
|
storageType?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function URLInput({
|
export default function URLInput({
|
||||||
@ -21,6 +23,7 @@ export default function URLInput({
|
|||||||
isLoading,
|
isLoading,
|
||||||
name,
|
name,
|
||||||
checkUrl,
|
checkUrl,
|
||||||
|
storageType,
|
||||||
...props
|
...props
|
||||||
}: URLInputProps): ReactElement {
|
}: URLInputProps): ReactElement {
|
||||||
const [field, meta] = useField(name)
|
const [field, meta] = useField(name)
|
||||||
@ -32,7 +35,8 @@ export default function URLInput({
|
|||||||
setIsButtonDisabled(
|
setIsButtonDisabled(
|
||||||
!field?.value ||
|
!field?.value ||
|
||||||
field.value === '' ||
|
field.value === '' ||
|
||||||
(checkUrl && !isUrl(field.value)) ||
|
(checkUrl && storageType === 'url' && !isUrl(field.value)) ||
|
||||||
|
(checkUrl && storageType === 'ipfs' && !isCID(field.value)) ||
|
||||||
field.value.includes('javascript:') ||
|
field.value.includes('javascript:') ||
|
||||||
meta?.error
|
meta?.error
|
||||||
)
|
)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement, useCallback, useState } from 'react'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { InputProps } from '..'
|
import { InputProps } from '..'
|
||||||
import FilesInput from './FilesInput'
|
import FilesInput from './FilesInput'
|
||||||
@ -11,6 +11,7 @@ import Nft from './Nft'
|
|||||||
import InputRadio from './Radio'
|
import InputRadio from './Radio'
|
||||||
import ContainerInput from '@shared/FormInput/InputElement/ContainerInput'
|
import ContainerInput from '@shared/FormInput/InputElement/ContainerInput'
|
||||||
import TagsAutoComplete from './TagsAutoComplete'
|
import TagsAutoComplete from './TagsAutoComplete'
|
||||||
|
import TabsFile from '@shared/atoms/TabsFile'
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
const cx = classNames.bind(styles)
|
||||||
|
|
||||||
@ -55,6 +56,7 @@ export default function InputElement({
|
|||||||
...props
|
...props
|
||||||
}: InputProps): ReactElement {
|
}: InputProps): ReactElement {
|
||||||
const styleClasses = cx({ select: true, [size]: size })
|
const styleClasses = cx({ select: true, [size]: size })
|
||||||
|
|
||||||
switch (props.type) {
|
switch (props.type) {
|
||||||
case 'select': {
|
case 'select': {
|
||||||
const sortedOptions =
|
const sortedOptions =
|
||||||
@ -80,6 +82,26 @@ export default function InputElement({
|
|||||||
</select>
|
</select>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case 'tabs': {
|
||||||
|
const tabs: any = []
|
||||||
|
props.fields.map((field: any, i) => {
|
||||||
|
return tabs.push({
|
||||||
|
title: field.title,
|
||||||
|
field,
|
||||||
|
props,
|
||||||
|
content: (
|
||||||
|
<FilesInput
|
||||||
|
key={`fileInput_${i}`}
|
||||||
|
{...field}
|
||||||
|
form={form}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return <TabsFile items={tabs} className={styles.pricing} />
|
||||||
|
}
|
||||||
case 'textarea':
|
case 'textarea':
|
||||||
return <textarea id={props.name} className={styles.textarea} {...props} />
|
return <textarea id={props.name} className={styles.textarea} {...props} />
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ export interface InputProps {
|
|||||||
type?: string
|
type?: string
|
||||||
options?: string[] | AssetSelectionAsset[] | BoxSelectionOption[]
|
options?: string[] | AssetSelectionAsset[] | BoxSelectionOption[]
|
||||||
sortOptions?: boolean
|
sortOptions?: boolean
|
||||||
|
fields?: FieldInputProps<any>[]
|
||||||
additionalComponent?: ReactElement
|
additionalComponent?: ReactElement
|
||||||
value?: string | number
|
value?: string | number
|
||||||
onChange?(
|
onChange?(
|
||||||
|
87
src/components/@shared/atoms/TabsFile/index.module.css
Normal file
87
src/components/@shared/atoms/TabsFile/index.module.css
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
.tabListContainer {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabList {
|
||||||
|
text-align: center;
|
||||||
|
padding: calc(var(--spacer) / 2) 0 0 0;
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
display: inline-block;
|
||||||
|
padding: calc(var(--spacer) / 8) calc(var(--spacer) / 2);
|
||||||
|
font-weight: var(--font-weight-bold);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--color-secondary);
|
||||||
|
margin-right: -1px;
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab,
|
||||||
|
.tab label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab[aria-selected='true'] {
|
||||||
|
background-color: inherit;
|
||||||
|
color: inherit;
|
||||||
|
list-style: none;
|
||||||
|
background-color: var(--background-body);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-top-left-radius: var(--border-radius);
|
||||||
|
border-top-right-radius: var(--border-radius);
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab[aria-disabled='true'] {
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab > div {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabContent {
|
||||||
|
padding: calc(var(--spacer) / 2);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-bottom-left-radius: var(--border-radius);
|
||||||
|
border-bottom-right-radius: var(--border-radius);
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabLabel{
|
||||||
|
color: var(--font-color-text);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
font-family: var(--font-family-heading);
|
||||||
|
line-height: 1.2;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 40rem) {
|
||||||
|
.tabContent {
|
||||||
|
padding: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 60rem) {
|
||||||
|
.tab {
|
||||||
|
padding: calc(var(--spacer) / 8) var(--spacer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio {
|
||||||
|
composes: radio from '../../FormInput/InputElement/Radio/index.module.css';
|
||||||
|
}
|
52
src/components/@shared/atoms/TabsFile/index.stories.tsx
Normal file
52
src/components/@shared/atoms/TabsFile/index.stories.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { ComponentStory, ComponentMeta } from '@storybook/react'
|
||||||
|
|
||||||
|
import Tabs, { TabsProps } from '@shared/atoms/Tabs'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Component/@shared/atoms/Tabs',
|
||||||
|
component: Tabs
|
||||||
|
} as ComponentMeta<typeof Tabs>
|
||||||
|
|
||||||
|
const Template: ComponentStory<typeof Tabs> = (args) => <Tabs {...args} />
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
args: TabsProps
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
title: 'First tab',
|
||||||
|
content: 'this is the content for the first tab'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Second tab',
|
||||||
|
content: 'this is the content for the second tab'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Third tab',
|
||||||
|
content: 'this is the content for the third tab'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const Default = Template.bind({})
|
||||||
|
Default.args = {
|
||||||
|
items
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithRadio: Props = Template.bind({})
|
||||||
|
WithRadio.args = {
|
||||||
|
items,
|
||||||
|
showRadio: true
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithDefaultIndex: Props = Template.bind({})
|
||||||
|
WithDefaultIndex.args = {
|
||||||
|
items,
|
||||||
|
defaultIndex: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LotsOfTabs: Props = Template.bind({})
|
||||||
|
LotsOfTabs.args = {
|
||||||
|
items: items.flatMap((i) => [i, i, i])
|
||||||
|
}
|
25
src/components/@shared/atoms/TabsFile/index.test.tsx
Normal file
25
src/components/@shared/atoms/TabsFile/index.test.tsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { render, screen, fireEvent } from '@testing-library/react'
|
||||||
|
import { Default, WithRadio } from './index.stories'
|
||||||
|
|
||||||
|
describe('Tabs', () => {
|
||||||
|
test('should be able to change', async () => {
|
||||||
|
render(<Default {...Default.args} />)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Second tab'))
|
||||||
|
const secondTab = await screen.findByText(/content for the second tab/i)
|
||||||
|
expect(secondTab).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should fire custom change handler', async () => {
|
||||||
|
const handler = jest.fn()
|
||||||
|
render(<Default {...Default.args} handleTabChange={handler} />)
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByText('Second tab'))
|
||||||
|
expect(handler).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('renders WithRadio', () => {
|
||||||
|
render(<Default {...WithRadio.args} />)
|
||||||
|
})
|
||||||
|
})
|
109
src/components/@shared/atoms/TabsFile/index.tsx
Normal file
109
src/components/@shared/atoms/TabsFile/index.tsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import Label from '@shared/FormInput/Label'
|
||||||
|
import Markdown from '@shared/Markdown'
|
||||||
|
import { useFormikContext } from 'formik'
|
||||||
|
import React, { ReactElement, ReactNode, useEffect, useState } from 'react'
|
||||||
|
import { Tab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs'
|
||||||
|
import { FormPublishData } from 'src/components/Publish/_types'
|
||||||
|
import Tooltip from '../Tooltip'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
|
||||||
|
export interface TabsItem {
|
||||||
|
field: any
|
||||||
|
title: string
|
||||||
|
content: ReactNode
|
||||||
|
disabled?: boolean
|
||||||
|
props: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TabsProps {
|
||||||
|
items: TabsItem[]
|
||||||
|
className?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TabsFile({
|
||||||
|
items,
|
||||||
|
className
|
||||||
|
}: TabsProps): ReactElement {
|
||||||
|
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
||||||
|
const [tabIndex, setTabIndex] = useState(0)
|
||||||
|
// hide tabs if are hidden
|
||||||
|
const isHidden = items[tabIndex].props.value[0].type === 'hidden'
|
||||||
|
|
||||||
|
const setIndex = (tabName: string) => {
|
||||||
|
const index = items.findIndex((tab: any) => {
|
||||||
|
if (tab.title !== tabName) return false
|
||||||
|
return tab
|
||||||
|
})
|
||||||
|
setTabIndex(index)
|
||||||
|
setFieldValue(`${items[index].props.name}[0]`, {
|
||||||
|
url: '',
|
||||||
|
type: items[index].field.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTabChange = (tabName: string) => {
|
||||||
|
setIndex(tabName)
|
||||||
|
}
|
||||||
|
|
||||||
|
let textToolTip = false
|
||||||
|
if (values?.services) {
|
||||||
|
textToolTip = values.services[0].access === 'compute'
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactTabs className={`${className || ''}`} defaultIndex={tabIndex}>
|
||||||
|
<div className={styles.tabListContainer}>
|
||||||
|
<TabList className={styles.tabList}>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
if (isHidden) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tab
|
||||||
|
className={styles.tab}
|
||||||
|
key={`tab_${items[tabIndex].props.name}_${index}`}
|
||||||
|
onClick={
|
||||||
|
handleTabChange ? () => handleTabChange(item.title) : null
|
||||||
|
}
|
||||||
|
disabled={item.disabled}
|
||||||
|
>
|
||||||
|
{item.title}
|
||||||
|
</Tab>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TabList>
|
||||||
|
</div>
|
||||||
|
<div className={styles.tabContent}>
|
||||||
|
{items.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TabPanel key={`tabpanel_${items[tabIndex].props.name}_${index}`}>
|
||||||
|
{!isHidden && (
|
||||||
|
<label className={styles.tabLabel}>
|
||||||
|
{item.field.label}
|
||||||
|
{item.field.required && (
|
||||||
|
<span title="Required" className={styles.required}>
|
||||||
|
*
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{item.field.help && item.field.prominentHelp && (
|
||||||
|
<Tooltip
|
||||||
|
content={
|
||||||
|
<Markdown
|
||||||
|
text={`${item.field.help} ${
|
||||||
|
textToolTip ? item.field.computeHelp : ''
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
{item.content}
|
||||||
|
</TabPanel>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</ReactTabs>
|
||||||
|
)
|
||||||
|
}
|
@ -7,7 +7,7 @@ import { compareAsBN } from '@utils/numbers'
|
|||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import Web3Feedback from '@shared/Web3Feedback'
|
import Web3Feedback from '@shared/Web3Feedback'
|
||||||
import { getFileDidInfo, getFileUrlInfo } from '@utils/provider'
|
import { getFileDidInfo, getFileInfo } from '@utils/provider'
|
||||||
import { getOceanConfig } from '@utils/ocean'
|
import { getOceanConfig } from '@utils/ocean'
|
||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
import { useIsMounted } from '@hooks/useIsMounted'
|
import { useIsMounted } from '@hooks/useIsMounted'
|
||||||
@ -52,14 +52,20 @@ export default function AssetActions({
|
|||||||
formikState?.values?.services[0].providerUrl.url ||
|
formikState?.values?.services[0].providerUrl.url ||
|
||||||
asset?.services[0]?.serviceEndpoint
|
asset?.services[0]?.serviceEndpoint
|
||||||
|
|
||||||
|
const storageType = formikState?.values?.services
|
||||||
|
? formikState?.values?.services[0].files[0].type
|
||||||
|
: null
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const fileInfoResponse = formikState?.values?.services?.[0].files?.[0]
|
const fileInfoResponse = formikState?.values?.services?.[0].files?.[0]
|
||||||
.url
|
.url
|
||||||
? await getFileUrlInfo(
|
? await getFileInfo(
|
||||||
formikState?.values?.services?.[0].files?.[0].url,
|
formikState?.values?.services?.[0].files?.[0].url,
|
||||||
providerUrl
|
providerUrl,
|
||||||
|
storageType
|
||||||
)
|
)
|
||||||
: await getFileDidInfo(asset?.id, asset?.services[0]?.id, providerUrl)
|
: await getFileDidInfo(asset?.id, asset?.services[0]?.id, providerUrl)
|
||||||
|
|
||||||
fileInfoResponse && setFileMetadata(fileInfoResponse[0])
|
fileInfoResponse && setFileMetadata(fileInfoResponse[0])
|
||||||
|
|
||||||
// set the content type in the Dataset Schema
|
// set the content type in the Dataset Schema
|
||||||
|
@ -115,7 +115,7 @@ export default function Edit({
|
|||||||
datatokenAddress: asset.services[0].datatokenAddress,
|
datatokenAddress: asset.services[0].datatokenAddress,
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
type: 'url',
|
type: values.files[0].type,
|
||||||
index: 0,
|
index: 0,
|
||||||
url: values.files[0].url,
|
url: values.files[0].url,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import React, { ReactElement, useEffect } from 'react'
|
import React, { ReactElement, useEffect } from 'react'
|
||||||
import { Field, Form, useFormikContext } from 'formik'
|
import { Field, Form, useFormikContext } from 'formik'
|
||||||
import Input, { InputProps } from '@shared/FormInput'
|
import Input from '@shared/FormInput'
|
||||||
import FormActions from './FormActions'
|
import FormActions from './FormActions'
|
||||||
import { useAsset } from '@context/Asset'
|
import { useAsset } from '@context/Asset'
|
||||||
import { FormPublishData } from 'src/components/Publish/_types'
|
import { FormPublishData } from 'src/components/Publish/_types'
|
||||||
import { getFileUrlInfo } from '@utils/provider'
|
import { getFileInfo } from '@utils/provider'
|
||||||
|
import { getFieldContent } from '@utils/form'
|
||||||
|
|
||||||
export function checkIfTimeoutInPredefinedValues(
|
export function checkIfTimeoutInPredefinedValues(
|
||||||
timeout: string,
|
timeout: string,
|
||||||
@ -21,11 +22,11 @@ export default function FormEditMetadata({
|
|||||||
showPrice,
|
showPrice,
|
||||||
isComputeDataset
|
isComputeDataset
|
||||||
}: {
|
}: {
|
||||||
data: InputProps[]
|
data: FormFieldContent[]
|
||||||
showPrice: boolean
|
showPrice: boolean
|
||||||
isComputeDataset: boolean
|
isComputeDataset: boolean
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { oceanConfig, asset } = useAsset()
|
const { asset } = useAsset()
|
||||||
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
const { values, setFieldValue } = useFormikContext<FormPublishData>()
|
||||||
|
|
||||||
// This component is handled by Formik so it's not rendered like a "normal" react component,
|
// This component is handled by Formik so it's not rendered like a "normal" react component,
|
||||||
@ -56,9 +57,10 @@ export default function FormEditMetadata({
|
|||||||
const providerUrl = values?.services
|
const providerUrl = values?.services
|
||||||
? values?.services[0].providerUrl.url
|
? values?.services[0].providerUrl.url
|
||||||
: asset.services[0].serviceEndpoint
|
: asset.services[0].serviceEndpoint
|
||||||
|
|
||||||
// if we have a sample file, we need to get the files' info before setting defaults links value
|
// if we have a sample file, we need to get the files' info before setting defaults links value
|
||||||
asset?.metadata?.links?.[0] &&
|
asset?.metadata?.links?.[0] &&
|
||||||
getFileUrlInfo(asset.metadata.links[0], providerUrl).then(
|
getFileInfo(asset.metadata.links[0], providerUrl, 'url').then(
|
||||||
(checkedFile) => {
|
(checkedFile) => {
|
||||||
// set valid false if url is using google drive
|
// set valid false if url is using google drive
|
||||||
if (asset.metadata.links[0].includes('drive.google')) {
|
if (asset.metadata.links[0].includes('drive.google')) {
|
||||||
@ -74,6 +76,7 @@ export default function FormEditMetadata({
|
|||||||
setFieldValue('links', [
|
setFieldValue('links', [
|
||||||
{
|
{
|
||||||
url: asset.metadata.links[0],
|
url: asset.metadata.links[0],
|
||||||
|
type: 'url',
|
||||||
...checkedFile[0]
|
...checkedFile[0]
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
@ -83,23 +86,48 @@ export default function FormEditMetadata({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
{data.map(
|
<Field {...getFieldContent('name', data)} component={Input} name="name" />
|
||||||
(field: InputProps) =>
|
|
||||||
(!showPrice && field.name === 'price') || (
|
|
||||||
<Field
|
<Field
|
||||||
key={field.name}
|
{...getFieldContent('description', data)}
|
||||||
options={
|
|
||||||
field.name === 'timeout' && isComputeDataset === true
|
|
||||||
? timeoutOptionsArray
|
|
||||||
: field.options
|
|
||||||
}
|
|
||||||
{...field}
|
|
||||||
component={Input}
|
component={Input}
|
||||||
prefix={field.name === 'price' && oceanConfig?.oceanTokenSymbol}
|
name="description"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{showPrice && (
|
||||||
|
<Field
|
||||||
|
{...getFieldContent('price', data)}
|
||||||
|
component={Input}
|
||||||
|
name="price"
|
||||||
/>
|
/>
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Field
|
||||||
|
{...getFieldContent('files', data)}
|
||||||
|
component={Input}
|
||||||
|
name="files"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
{...getFieldContent('links', data)}
|
||||||
|
component={Input}
|
||||||
|
name="links"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
{...getFieldContent('timeout', data)}
|
||||||
|
component={Input}
|
||||||
|
name="timeout"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field
|
||||||
|
{...getFieldContent('author', data)}
|
||||||
|
component={Input}
|
||||||
|
name="author"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Field {...getFieldContent('tags', data)} component={Input} name="tags" />
|
||||||
|
|
||||||
<FormActions />
|
<FormActions />
|
||||||
</Form>
|
</Form>
|
||||||
)
|
)
|
||||||
|
@ -12,8 +12,8 @@ export function getInitialValues(
|
|||||||
name: metadata?.name,
|
name: metadata?.name,
|
||||||
description: metadata?.description,
|
description: metadata?.description,
|
||||||
price,
|
price,
|
||||||
links: [{ url: '', type: '' }],
|
links: [{ url: '', type: 'url' }],
|
||||||
files: [{ url: '', type: '' }],
|
files: [{ url: '', type: 'ipfs' }],
|
||||||
timeout: secondsToString(timeout),
|
timeout: secondsToString(timeout),
|
||||||
author: metadata?.author,
|
author: metadata?.author,
|
||||||
tags: metadata?.tags,
|
tags: metadata?.tags,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { FileInfo } from '@oceanprotocol/lib'
|
import { FileInfo } from '@oceanprotocol/lib'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import web3 from 'web3'
|
import web3 from 'web3'
|
||||||
|
import { testLinks } from '../../../@utils/yup'
|
||||||
|
|
||||||
export const validationSchema = Yup.object().shape({
|
export const validationSchema = Yup.object().shape({
|
||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
@ -11,35 +12,32 @@ export const validationSchema = Yup.object().shape({
|
|||||||
files: Yup.array<FileInfo[]>()
|
files: Yup.array<FileInfo[]>()
|
||||||
.of(
|
.of(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
url: Yup.string()
|
url: testLinks(),
|
||||||
.url('Must be a valid URL.')
|
valid: Yup.boolean().test((value, context) => {
|
||||||
.test(
|
const { type } = context.parent
|
||||||
'GoogleNotSupported',
|
|
||||||
'Google Drive is not a supported hosting service. Please use an alternative.',
|
// allow user to submit if the value type is hidden
|
||||||
(value) => {
|
if (type === 'hidden') return true
|
||||||
return !value?.toString().includes('drive.google')
|
|
||||||
}
|
return value || false
|
||||||
),
|
})
|
||||||
valid: Yup.boolean().isTrue()
|
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.nullable(),
|
.nullable(),
|
||||||
links: Yup.array<FileInfo[]>()
|
links: Yup.array<FileInfo[]>().of(
|
||||||
.of(
|
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
url: Yup.string()
|
url: testLinks(),
|
||||||
.url('Must be a valid URL.')
|
valid: Yup.boolean().test((value, context) => {
|
||||||
.test(
|
// allow user to submit if the value is null
|
||||||
'GoogleNotSupported',
|
const { valid, url } = context.parent
|
||||||
'Google Drive is not a supported hosting service. Please use an alternative.',
|
|
||||||
(value) => {
|
// allow user to continue if the url is empty
|
||||||
return !value?.toString().includes('drive.google')
|
if (!url) return true
|
||||||
}
|
|
||||||
),
|
return valid
|
||||||
valid: Yup.boolean().isTrue()
|
|
||||||
})
|
})
|
||||||
)
|
})
|
||||||
.nullable(),
|
),
|
||||||
timeout: Yup.string().required('Required'),
|
timeout: Yup.string().required('Required'),
|
||||||
author: Yup.string().nullable(),
|
author: Yup.string().nullable(),
|
||||||
tags: Yup.array<string[]>().nullable(),
|
tags: Yup.array<string[]>().nullable(),
|
||||||
|
@ -5,7 +5,6 @@ import Downloads from './Downloads'
|
|||||||
import ComputeJobs from './ComputeJobs'
|
import ComputeJobs from './ComputeJobs'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { useWeb3 } from '@context/Web3'
|
import { useWeb3 } from '@context/Web3'
|
||||||
import { chainIds } from 'app.config'
|
|
||||||
import { getComputeJobs } from '@utils/compute'
|
import { getComputeJobs } from '@utils/compute'
|
||||||
import { useUserPreferences } from '@context/UserPreferences'
|
import { useUserPreferences } from '@context/UserPreferences'
|
||||||
import { useCancelToken } from '@hooks/useCancelToken'
|
import { useCancelToken } from '@hooks/useCancelToken'
|
||||||
|
@ -72,8 +72,8 @@ export const initialValues: FormPublishData = {
|
|||||||
},
|
},
|
||||||
services: [
|
services: [
|
||||||
{
|
{
|
||||||
files: [{ url: '', type: '' }],
|
files: [{ url: '', type: 'ipfs' }],
|
||||||
links: [{ url: '', type: '' }],
|
links: [{ url: '', type: 'url' }],
|
||||||
dataTokenOptions: { name: '', symbol: '' },
|
dataTokenOptions: { name: '', symbol: '' },
|
||||||
timeout: '',
|
timeout: '',
|
||||||
access: 'access',
|
access: 'access',
|
||||||
|
@ -139,18 +139,24 @@ export async function transformPublishFormToDdo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// this is the default format hardcoded
|
// this is the default format hardcoded
|
||||||
|
|
||||||
const file = {
|
const file = {
|
||||||
nftAddress,
|
nftAddress,
|
||||||
datatokenAddress,
|
datatokenAddress,
|
||||||
files: [
|
files: [
|
||||||
{
|
{
|
||||||
type: 'url',
|
type: files[0].type,
|
||||||
index: 0,
|
index: 0,
|
||||||
url: files[0].url,
|
[files[0].type === 'ipfs'
|
||||||
|
? 'hash'
|
||||||
|
: files[0].type === 'arweave'
|
||||||
|
? 'transactionId'
|
||||||
|
: 'url']: files[0].url,
|
||||||
method: 'GET'
|
method: 'GET'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const filesEncrypted =
|
const filesEncrypted =
|
||||||
!isPreview &&
|
!isPreview &&
|
||||||
files?.length &&
|
files?.length &&
|
||||||
|
@ -2,6 +2,7 @@ import { MAX_DECIMALS } from '@utils/constants'
|
|||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { getMaxDecimalsValidation } from '@utils/numbers'
|
import { getMaxDecimalsValidation } from '@utils/numbers'
|
||||||
import { FileInfo } from '@oceanprotocol/lib'
|
import { FileInfo } from '@oceanprotocol/lib'
|
||||||
|
import { testLinks } from '../../@utils/yup'
|
||||||
|
|
||||||
// TODO: conditional validation
|
// TODO: conditional validation
|
||||||
// e.g. when algo is selected, Docker image is required
|
// e.g. when algo is selected, Docker image is required
|
||||||
@ -32,16 +33,7 @@ const validationService = {
|
|||||||
files: Yup.array<FileInfo[]>()
|
files: Yup.array<FileInfo[]>()
|
||||||
.of(
|
.of(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
url: Yup.string()
|
url: testLinks().required('Required'),
|
||||||
.test(
|
|
||||||
'GoogleNotSupported',
|
|
||||||
'Google Drive is not a supported hosting service. Please use an alternative.',
|
|
||||||
(value) => {
|
|
||||||
return !value?.toString().includes('drive.google')
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.url('Must be a valid URL.')
|
|
||||||
.required('Required'),
|
|
||||||
|
|
||||||
valid: Yup.boolean().isTrue().required('File must be valid.')
|
valid: Yup.boolean().isTrue().required('File must be valid.')
|
||||||
})
|
})
|
||||||
@ -51,16 +43,7 @@ const validationService = {
|
|||||||
links: Yup.array<FileInfo[]>()
|
links: Yup.array<FileInfo[]>()
|
||||||
.of(
|
.of(
|
||||||
Yup.object().shape({
|
Yup.object().shape({
|
||||||
url: Yup.string()
|
url: testLinks(),
|
||||||
.url('Must be a valid URL.')
|
|
||||||
.test(
|
|
||||||
'GoogleNotSupported',
|
|
||||||
'Google Drive is not a supported hosting service. Please use an alternative.',
|
|
||||||
(value) => {
|
|
||||||
return !value?.toString().includes('drive.google')
|
|
||||||
}
|
|
||||||
),
|
|
||||||
// TODO: require valid file only when URL is given
|
|
||||||
valid: Yup.boolean()
|
valid: Yup.boolean()
|
||||||
// valid: Yup.boolean().isTrue('File must be valid.')
|
// valid: Yup.boolean().isTrue('File must be valid.')
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user