diff --git a/.env.example b/.env.example index a91d4aca9..ada5e4e24 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,7 @@ # Enables another asset editing button holder further advanced settings #GATSBY_ALLOW_ADVANCED_SETTINGS="true" +#GATSBY_ALLOW_ADVANCED_PUBLISH_SETTINGS="true" # Allow/Deny Lists #GATSBY_CREDENTIAL_TYPE="address" diff --git a/app.config.js b/app.config.js index a657752e1..5e508c6bf 100644 --- a/app.config.js +++ b/app.config.js @@ -60,5 +60,7 @@ module.exports = { // Used to show or hide advanced settings button in asset details page allowAdvancedSettings: process.env.GATSBY_ALLOW_ADVANCED_SETTINGS || 'false', + allowAdvancedPublishSettings: + process.env.GATSBY_ALLOW_ADVANCED_PUBLISH_SETTINGS || 'false', credentialType: process.env.GATSBY_CREDENTIAL_TYPE || 'address' } diff --git a/content/pages/publish/form-algorithm.json b/content/pages/publish/form-algorithm.json index d5839527f..8ddc85e68 100644 --- a/content/pages/publish/form-algorithm.json +++ b/content/pages/publish/form-algorithm.json @@ -91,6 +91,14 @@ "placeholder": "e.g. logistics, ai", "help": "Separate tags with comma." }, + { + "name": "providerUri", + "label": "Custom Provider URL", + "type": "providerUri", + "help": "Enter the URL for your custom provider or leave blank to use the default provider. [Learn more](https://github.com/oceanprotocol/provider/).", + "placeholder": "https://provider.polygon.oceanprotocol.com/", + "advanced": true + }, { "name": "termsAndConditions", "label": "Terms & Conditions", diff --git a/content/pages/publish/form-dataset.json b/content/pages/publish/form-dataset.json index 847ee53ef..8563a8c9b 100644 --- a/content/pages/publish/form-dataset.json +++ b/content/pages/publish/form-dataset.json @@ -38,6 +38,14 @@ "options": ["Download", "Compute"], "required": true }, + { + "name": "providerUri", + "label": "Custom Provider URL", + "type": "providerUri", + "help": "Enter the URL for your custom provider or leave blank to use the default provider. [Learn more](https://github.com/oceanprotocol/provider/).", + "placeholder": "https://provider.polygon.oceanprotocol.com/", + "advanced": true + }, { "name": "timeout", "label": "Timeout", diff --git a/src/@types/Form.d.ts b/src/@types/Form.d.ts index 929061e77..b1c234d4a 100644 --- a/src/@types/Form.d.ts +++ b/src/@types/Form.d.ts @@ -13,6 +13,7 @@ export interface FormFieldProps { placeholder?: string pattern?: string min?: string + advanced?: boolean } export interface FormContent { diff --git a/src/@types/MetaData.d.ts b/src/@types/MetaData.d.ts index 1eeddc054..6cfede111 100644 --- a/src/@types/MetaData.d.ts +++ b/src/@types/MetaData.d.ts @@ -38,6 +38,7 @@ export interface MetadataPublishFormDataset { // ---- optional fields ---- tags?: string links?: string | EditableMetadataLinks[] + providerUri?: string } export interface MetadataPublishFormAlgorithm { @@ -56,6 +57,7 @@ export interface MetadataPublishFormAlgorithm { containerTag: string entrypoint: string tags?: string + providerUri?: string } export interface MetadataEditForm { diff --git a/src/components/atoms/Input/InputElement.tsx b/src/components/atoms/Input/InputElement.tsx index 966407381..cb1d8395f 100644 --- a/src/components/atoms/Input/InputElement.tsx +++ b/src/components/atoms/Input/InputElement.tsx @@ -3,6 +3,7 @@ import slugify from '@sindresorhus/slugify' import styles from './InputElement.module.css' import { InputProps } from '.' import FilesInput from '../../molecules/FormFields/FilesInput' +import CustomProvider from '../../molecules/FormFields/CustomProvider' import Terms from '../../molecules/FormFields/Terms' import BoxSelection, { BoxSelectionOption @@ -125,6 +126,8 @@ export default function InputElement({ ) case 'files': return + case 'providerUri': + return case 'datatoken': return case 'terms': diff --git a/src/components/molecules/FormFields/AdvancedSettings.module.css b/src/components/molecules/FormFields/AdvancedSettings.module.css new file mode 100644 index 000000000..c6b65a3bc --- /dev/null +++ b/src/components/molecules/FormFields/AdvancedSettings.module.css @@ -0,0 +1,3 @@ +.advancedBtn { + margin-bottom: 2rem; +} diff --git a/src/components/molecules/FormFields/AdvancedSettings.tsx b/src/components/molecules/FormFields/AdvancedSettings.tsx new file mode 100644 index 000000000..4588ec867 --- /dev/null +++ b/src/components/molecules/FormFields/AdvancedSettings.tsx @@ -0,0 +1,53 @@ +import React, { ReactElement, useState, FormEvent, ChangeEvent } from 'react' +import { useSiteMetadata } from '../../../hooks/useSiteMetadata' +import Input from '../../atoms/Input' +import Button from '../../atoms/Button' +import { FormContent, FormFieldProps } from '../../../@types/Form' +import { Field } from 'formik' +import styles from './AdvancedSettings.module.css' + +export default function AdvancedSettings(prop: { + content: FormContent + handleFieldChange: ( + e: ChangeEvent, + field: FormFieldProps + ) => void +}): ReactElement { + const { appConfig } = useSiteMetadata() + const [advancedSettings, setAdvancedSettings] = useState(false) + function toggleAdvancedSettings(e: FormEvent) { + e.preventDefault() + advancedSettings === true + ? setAdvancedSettings(false) + : setAdvancedSettings(true) + } + return ( + <> + {appConfig.allowAdvancedPublishSettings === 'true' && ( + + )} + {prop.content.data.map( + (field: FormFieldProps) => + advancedSettings === true && + field.advanced === true && ( + ) => + prop.handleFieldChange(e, field) + } + /> + ) + )} + + ) +} diff --git a/src/components/molecules/FormFields/CustomProvider.tsx b/src/components/molecules/FormFields/CustomProvider.tsx new file mode 100644 index 000000000..3f8f9190c --- /dev/null +++ b/src/components/molecules/FormFields/CustomProvider.tsx @@ -0,0 +1,60 @@ +import React, { ReactElement, useState, useEffect } from 'react' +import { useField } from 'formik' +import { toast } from 'react-toastify' +import CustomInput from './URLInput/Input' +import { useOcean } from '../../../providers/Ocean' +import { InputProps } from '../../atoms/Input' + +export default function CustomProvider(props: InputProps): ReactElement { + const [field, meta, helpers] = useField(props.name) + const [isLoading, setIsLoading] = useState(false) + const [providerUrl, setProviderUrl] = useState() + const { ocean, config } = useOcean() + + function loadProvider() { + if (!providerUrl) return + async function validateProvider() { + let valid: boolean + try { + setIsLoading(true) + valid = await ocean.provider.isValidProvider(providerUrl) + } catch (error) { + valid = false + console.error(error.message) + } finally { + valid + ? toast.success('Perfect! That provider URL looks good 🐳') + : toast.error( + 'Could not validate provider. Please check URL and try again' + ) + + setIsLoading(false) + } + } + validateProvider() + } + + useEffect(() => { + loadProvider() + }, [providerUrl, config.providerUri]) + + async function handleButtonClick(e: React.SyntheticEvent, url: string) { + helpers.setTouched(false) + e.preventDefault() + if (providerUrl === url) { + loadProvider() + } + + setProviderUrl(url) + } + + return ( + + ) +} diff --git a/src/components/molecules/FormFields/FilesInput/index.tsx b/src/components/molecules/FormFields/FilesInput/index.tsx index 0ebdaf1ac..e07cf226a 100644 --- a/src/components/molecules/FormFields/FilesInput/index.tsx +++ b/src/components/molecules/FormFields/FilesInput/index.tsx @@ -3,7 +3,7 @@ import axios from 'axios' import { useField } from 'formik' import { toast } from 'react-toastify' import FileInfo from './Info' -import FileInput from './Input' +import CustomInput from '../URLInput/Input' import { InputProps } from '../../../atoms/Input' import { fileinfo } from '../../../../utils/provider' import { useWeb3 } from '../../../../providers/Web3' @@ -68,7 +68,8 @@ export default function FilesInput(props: InputProps): ReactElement { {field?.value && field.value[0] && typeof field.value === 'object' ? ( ) : ( - e.preventDefault()} disabled={!field.value} > - {isLoading ? : 'Add File'} + {isLoading ? : submitText} ) diff --git a/src/components/pages/Publish/Debug.tsx b/src/components/pages/Publish/Debug.tsx index 2aa2375e4..73a8ffdb6 100644 --- a/src/components/pages/Publish/Debug.tsx +++ b/src/components/pages/Publish/Debug.tsx @@ -23,6 +23,7 @@ export default function Debug({ { index: 1, type: values.access, + serviceEndpoint: values.providerUri, attributes: {} } ] diff --git a/src/components/pages/Publish/FormAlgoPublish.tsx b/src/components/pages/Publish/FormAlgoPublish.tsx index 3d60937f8..b6320b59e 100644 --- a/src/components/pages/Publish/FormAlgoPublish.tsx +++ b/src/components/pages/Publish/FormAlgoPublish.tsx @@ -11,6 +11,7 @@ import Input from '../../atoms/Input' import { FormContent, FormFieldProps } from '../../../@types/Form' import { MetadataPublishFormAlgorithm } from '../../../@types/MetaData' import { initialValues as initialValuesAlgorithm } from '../../../models/FormAlgoPublish' +import AdvancedSettings from '../../molecules/FormFields/AdvancedSettings' import FormTitle from './FormTitle' import FormActions from './FormActions' import styles from './FormPublish.module.css' @@ -33,6 +34,7 @@ const query = graphql` required sortOptions options + advanced } warning } @@ -145,6 +147,7 @@ export default function FormPublish(): ReactElement { {content.data.map( (field: FormFieldProps) => + field.advanced !== true && ((field.name !== 'entrypoint' && field.name !== 'image' && field.name !== 'containerTag') || @@ -164,7 +167,10 @@ export default function FormPublish(): ReactElement { /> ) )} - + - {content.data.map((field: FormFieldProps) => ( - ) => - handleFieldChange(e, field) - } - /> - ))} + {content.data.map( + (field: FormFieldProps) => + field.advanced !== true && ( + ) => + handleFieldChange(e, field) + } + /> + ) + )} + = { algorithmPrivacy: false, termsAndConditions: false, tags: '', - timeout: 'Forever' + timeout: 'Forever', + providerUri: '' } diff --git a/src/models/FormPublish.ts b/src/models/FormPublish.ts index 8f6550459..001a0ad60 100644 --- a/src/models/FormPublish.ts +++ b/src/models/FormPublish.ts @@ -25,10 +25,10 @@ export const validationSchema: Yup.SchemaOf = .matches(/Compute|Download/g, { excludeEmptyString: true }) .required('Required'), termsAndConditions: Yup.boolean().required('Required'), - // ---- optional fields ---- tags: Yup.string().nullable(), - links: Yup.array().nullable() + links: Yup.array().nullable(), + providerUri: Yup.string().url().nullable() }) .defined() @@ -44,5 +44,6 @@ export const initialValues: Partial = { timeout: 'Forever', access: '', termsAndConditions: false, - tags: '' + tags: '', + providerUri: '' }