1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Edit screen fixes (#1546)

* edit refactors

* fix logic around `publisherTrustedAlgorithms`

* typing fix

* copy & typos

* conditionally add compute tab to edit screen

* more logic fixes

* fix various app crashes because of Debug component
* semi-deal with publisherTrustedAlgorithmPublishers

* more fixes, bound submit button to touched state
This commit is contained in:
Matthias Kretschmann 2022-06-27 10:15:45 +01:00 committed by GitHub
parent 5387b9a3dd
commit 02beb0f8c3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 316 additions and 321 deletions

View File

@ -1,67 +1,3 @@
{ {
"description": "Update selected metadata of this data set. Updating metadata will create an on-chain transaction you have to approve in your wallet.", "description": "Updating metadata or updating compute settings will create an on-chain transaction you have to approve in your wallet."
"form": {
"success": "🎉 Successfully updated. 🎉",
"successAction": "Close",
"error": "Updating DDO failed.",
"data": [
{
"name": "name",
"label": "New Title",
"placeholder": "e.g. Shapes of Desert Plants",
"help": "Enter a concise title.",
"required": true
},
{
"name": "description",
"label": "New Description",
"help": "Add a thorough description with as much detail as possible. You can use [Markdown](https://daringfireball.net/projects/markdown/basics).",
"type": "textarea",
"rows": 10,
"required": true
},
{
"name": "price",
"label": "New Price",
"type": "number",
"min": "1",
"placeholder": "0",
"help": "Enter a new price.",
"required": true
},
{
"name": "files",
"label": "New 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.** For a compute data set, 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.",
"prominentHelp": true,
"type": "files"
},
{
"name": "links",
"label": "New sample file",
"placeholder": "e.g. https://file.com/samplefile.json",
"help": "Please provide a URL to a sample of your data set file. This file should reveal the data structure of your data set, 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.",
"prominentHelp": true,
"type": "files"
},
{
"name": "timeout",
"label": "Timeout",
"help": "Define how long buyers should be able to download the data set again after the initial purchase.",
"type": "select",
"options": ["Forever", "1 day", "1 week", "1 month", "1 year"],
"sortOptions": false,
"required": true
},
{
"name": "author",
"label": "New Author",
"placeholder": "e.g. Mrs McJellyfish",
"help": "Give proper attribution for your data set.",
"required": false
}
]
}
} }

View File

@ -1,9 +1,8 @@
{ {
"description": "Only selected algorithms are allowed to run on this data set. Updating these settings will create an on-chain transaction you have to approve in your wallet.",
"form": { "form": {
"title": "Set allowed algorithms", "title": "Set allowed algorithms",
"success": "🎉 Successfully updated. 🎉", "description": "Only the algorithms selected here will be allowed to run on your data set. Uncheck all to remove any access to your data set.",
"successAction": "Close", "success": "🎉 Successfully updated. 🎉\n\nUpdates might not show up right away on your asset. In this case, wait some seconds and reload your asset details page in your browser.",
"error": "Updating DDO failed.", "error": "Updating DDO failed.",
"data": [ "data": [
{ {

View File

@ -0,0 +1,65 @@
{
"form": {
"success": "🎉 Successfully updated. 🎉\n\nUpdates might not show up right away on your asset. In this case, wait some seconds and reload your asset details page in your browser.",
"error": "Updating DDO failed.",
"data": [
{
"name": "name",
"label": "New Title",
"placeholder": "e.g. Shapes of Desert Plants",
"help": "Enter a concise title.",
"required": true
},
{
"name": "description",
"label": "New Description",
"help": "Add a thorough description with as much detail as possible. You can use [Markdown](https://daringfireball.net/projects/markdown/basics).",
"type": "textarea",
"rows": 10,
"required": true
},
{
"name": "price",
"label": "New Price",
"type": "number",
"min": "1",
"placeholder": "0",
"help": "Enter a new price.",
"required": true
},
{
"name": "files",
"label": "New 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.** For a compute data set, 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.",
"prominentHelp": true,
"type": "files"
},
{
"name": "links",
"label": "New sample file",
"placeholder": "e.g. https://file.com/samplefile.json",
"help": "Please provide a URL to a sample of your data set file. This file should reveal the data structure of your data set, 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.",
"prominentHelp": true,
"type": "files"
},
{
"name": "timeout",
"label": "Timeout",
"help": "Define how long buyers should be able to download the data set again after the initial purchase.",
"type": "select",
"options": ["Forever", "1 day", "1 week", "1 month", "1 year"],
"sortOptions": false,
"required": true
},
{
"name": "author",
"label": "New Author",
"placeholder": "e.g. Mrs McJellyfish",
"help": "Give proper attribution for your data set.",
"required": false
}
]
}
}

View File

@ -15,11 +15,6 @@ declare global {
name: string name: string
} }
interface ComputePrivacyForm {
allowAllPublishedAlgorithms: boolean
publisherTrustedAlgorithms: string[]
}
interface TokenOrder { interface TokenOrder {
id: string id: string
serviceIndex: number serviceIndex: number

View File

@ -25,6 +25,7 @@ import { SortTermOptions } from 'src/@types/aquarius/SearchQuery'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import { transformAssetToAssetSelection } from './assetConvertor' import { transformAssetToAssetSelection } from './assetConvertor'
import { AssetExtended } from 'src/@types/AssetExtended' import { AssetExtended } from 'src/@types/AssetExtended'
import { ComputeEditForm } from 'src/components/Asset/Edit/_types'
const getComputeOrders = gql` const getComputeOrders = gql`
query ComputeOrders($user: String!) { query ComputeOrders($user: String!) {
@ -329,6 +330,7 @@ export async function createTrustedAlgorithmList(
assetChainId: number, assetChainId: number,
cancelToken: CancelToken cancelToken: CancelToken
): Promise<PublisherTrustedAlgorithm[]> { ): Promise<PublisherTrustedAlgorithm[]> {
if (!selectedAlgorithms || selectedAlgorithms.length === 0) return []
const trustedAlgorithms: PublisherTrustedAlgorithm[] = [] const trustedAlgorithms: PublisherTrustedAlgorithm[] = []
const selectedAssets = await retrieveDDOListByDIDs( const selectedAssets = await retrieveDDOListByDIDs(
@ -337,6 +339,8 @@ export async function createTrustedAlgorithmList(
cancelToken cancelToken
) )
if (!selectedAssets || selectedAssets.length === 0) return []
for (const selectedAlgorithm of selectedAssets) { for (const selectedAlgorithm of selectedAssets) {
const sanitizedAlgorithmContainer = { const sanitizedAlgorithmContainer = {
entrypoint: selectedAlgorithm.metadata.algorithm.container.entrypoint, entrypoint: selectedAlgorithm.metadata.algorithm.container.entrypoint,
@ -357,22 +361,28 @@ export async function createTrustedAlgorithmList(
} }
export async function transformComputeFormToServiceComputeOptions( export async function transformComputeFormToServiceComputeOptions(
values: ComputePrivacyForm, values: ComputeEditForm,
currentOptions: ServiceComputeOptions, currentOptions: ServiceComputeOptions,
assetChainId: number, assetChainId: number,
cancelToken: CancelToken cancelToken: CancelToken
): Promise<ServiceComputeOptions> { ): Promise<ServiceComputeOptions> {
const publisherTrustedAlgorithms = values.allowAllPublishedAlgorithms const publisherTrustedAlgorithms = values.allowAllPublishedAlgorithms
? [] ? null
: await createTrustedAlgorithmList( : await createTrustedAlgorithmList(
values.publisherTrustedAlgorithms, values.publisherTrustedAlgorithms,
assetChainId, assetChainId,
cancelToken cancelToken
) )
// TODO: add support for selecting trusted publishers and transforming here.
// This only deals with basics so we don't accidentially allow all accounts
// to be trusted.
const publisherTrustedAlgorithmPublishers: string[] = []
const privacy: ServiceComputeOptions = { const privacy: ServiceComputeOptions = {
...currentOptions, ...currentOptions,
publisherTrustedAlgorithms publisherTrustedAlgorithms,
publisherTrustedAlgorithmPublishers
} }
return privacy return privacy

6
src/@utils/form.ts Normal file
View File

@ -0,0 +1,6 @@
export function getFieldContent(
fieldName: string,
fields: FormFieldContent[]
): FormFieldContent {
return fields.filter((field: FormFieldContent) => field.name === fieldName)[0]
}

View File

@ -1,15 +1,15 @@
import { Asset, ServiceComputeOptions } from '@oceanprotocol/lib' import { Asset, ServiceComputeOptions } from '@oceanprotocol/lib'
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
// import { transformComputeFormToServiceComputePrivacy } from '@utils/compute'
import DebugOutput from '@shared/DebugOutput' import DebugOutput from '@shared/DebugOutput'
import { useCancelToken } from '@hooks/useCancelToken' import { useCancelToken } from '@hooks/useCancelToken'
import { transformComputeFormToServiceComputeOptions } from '@utils/compute' import { transformComputeFormToServiceComputeOptions } from '@utils/compute'
import { ComputeEditForm } from './_types'
export default function DebugEditCompute({ export default function DebugEditCompute({
values, values,
asset asset
}: { }: {
values: ComputePrivacyForm values: ComputeEditForm
asset: Asset asset: Asset
}): ReactElement { }): ReactElement {
const [formTransformed, setFormTransformed] = const [formTransformed, setFormTransformed] =

View File

@ -6,9 +6,6 @@ import {
LoggerInstance, LoggerInstance,
ServiceComputeOptions, ServiceComputeOptions,
Service, Service,
ProviderInstance,
getHash,
Nft,
Asset Asset
} from '@oceanprotocol/lib' } from '@oceanprotocol/lib'
import { useUserPreferences } from '@context/UserPreferences' import { useUserPreferences } from '@context/UserPreferences'
@ -29,6 +26,7 @@ import DebugEditCompute from './DebugEditCompute'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import EditFeedback from './EditFeedback' import EditFeedback from './EditFeedback'
import { setNftMetadata } from '@utils/nft' import { setNftMetadata } from '@utils/nft'
import { ComputeEditForm } from './_types'
export default function EditComputeDataset({ export default function EditComputeDataset({
asset asset
@ -44,10 +42,7 @@ export default function EditComputeDataset({
const newCancelToken = useCancelToken() const newCancelToken = useCancelToken()
const hasFeedback = error || success const hasFeedback = error || success
async function handleSubmit( async function handleSubmit(values: ComputeEditForm, resetForm: () => void) {
values: ComputePrivacyForm,
resetForm: () => void
) {
try { try {
if (asset?.accessDetails?.type === 'free') { if (asset?.accessDetails?.type === 'free') {
const tx = await setMinterToPublisher( const tx = await setMinterToPublisher(
@ -130,18 +125,19 @@ export default function EditComputeDataset({
// move user's focus to top of screen // move user's focus to top of screen
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }) window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
// kick off editing // kick off editing
await handleSubmit(values as any, resetForm) await handleSubmit(values, resetForm)
}} }}
enableReinitialize
> >
{({ values, isSubmitting }) => {({ values, isSubmitting }) =>
isSubmitting || hasFeedback ? ( isSubmitting || hasFeedback ? (
<EditFeedback <EditFeedback
title="Updating Data Set" loading="Updating data set with new compute settings..."
error={error} error={error}
success={success} success={success}
setError={setError} setError={setError}
successAction={{ successAction={{
name: 'View Asset', name: 'Back to Asset',
onClick: async () => { onClick: async () => {
await fetchAsset() await fetchAsset()
}, },
@ -150,13 +146,7 @@ export default function EditComputeDataset({
/> />
) : ( ) : (
<> <>
<p className={styles.description}>{content.description}</p> <FormEditComputeDataset />
<article>
<FormEditComputeDataset
title={content.form.title}
data={content.form.data}
/>
</article>
<Web3Feedback <Web3Feedback
networkId={asset?.chainId} networkId={asset?.chainId}
isAssetNetwork={isAssetNetwork} isAssetNetwork={isAssetNetwork}

View File

@ -1,16 +1,10 @@
.feedback { .feedback {
width: 100%; width: 100%;
height: 100%;
min-height: 40vh; min-height: 40vh;
display: flex; display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center; align-items: center;
text-align: center; justify-content: center;
}
.box {
composes: box from '@shared/atoms/Box.module.css';
width: 100%;
} }
.feedback h3 { .feedback h3 {

View File

@ -41,17 +41,15 @@ function ActionError({ setError }: { setError: (error: string) => void }) {
} }
export default function EditFeedback({ export default function EditFeedback({
title,
error, error,
success, success,
loading, loading,
successAction, successAction,
setError setError
}: { }: {
title: string
error: string error: string
success: string success: string
loading?: string loading: string
successAction: Action successAction: Action
setError: (error: string) => void setError: (error: string) => void
}): ReactElement { }): ReactElement {
@ -64,31 +62,28 @@ export default function EditFeedback({
return ( return (
<div className={styles.feedback}> <div className={styles.feedback}>
<div className={styles.box}> {error ? (
<h3>{title}</h3> <>
{error ? ( <p>Sorry, something went wrong. Please try again.</p>
<> {moreInfo && <Alert text={error} state="error" />}
<p>Sorry, something went wrong. Please try again.</p> <Button
{moreInfo && <Alert text={error} state="error" />} style="text"
<Button size="small"
style="text" onClick={toggleMoreInfo}
size="small" className={styles.moreInfo}
onClick={toggleMoreInfo} >
className={styles.moreInfo} {moreInfo === false ? 'More Info' : 'Hide error'}
> </Button>
{moreInfo === false ? 'More Info' : 'Hide error'} <ActionError setError={setError} />
</Button> </>
<ActionError setError={setError} /> ) : success ? (
</> <SuccessConfetti
) : success ? ( success={success}
<SuccessConfetti action={<ActionSuccess action={successAction} />}
success={success} />
action={<ActionSuccess action={successAction} />} ) : (
/> <Loader message={loading} />
) : ( )}
<Loader message={loading} />
)}
</div>
</div> </div>
) )
} }

View File

@ -15,7 +15,7 @@ import Web3Feedback from '@shared/Web3Feedback'
import FormEditMetadata from './FormEditMetadata' import FormEditMetadata from './FormEditMetadata'
import { mapTimeoutStringToSeconds } from '@utils/ddo' import { mapTimeoutStringToSeconds } from '@utils/ddo'
import styles from './index.module.css' import styles from './index.module.css'
import content from '../../../../content/pages/edit.json' import content from '../../../../content/pages/editMetadata.json'
import { AssetExtended } from 'src/@types/AssetExtended' import { AssetExtended } from 'src/@types/AssetExtended'
import { useAbortController } from '@hooks/useAbortController' import { useAbortController } from '@hooks/useAbortController'
import DebugEditMetadata from './DebugEditMetadata' import DebugEditMetadata from './DebugEditMetadata'
@ -159,12 +159,12 @@ export default function Edit({
{({ isSubmitting, values }) => {({ isSubmitting, values }) =>
isSubmitting || hasFeedback ? ( isSubmitting || hasFeedback ? (
<EditFeedback <EditFeedback
title="Updating Data Set" loading="Updating asset with new metadata..."
error={error} error={error}
success={success} success={success}
setError={setError} setError={setError}
successAction={{ successAction={{
name: 'View Asset', name: 'Back to Asset',
onClick: async () => { onClick: async () => {
await fetchAsset() await fetchAsset()
}, },
@ -173,27 +173,22 @@ export default function Edit({
/> />
) : ( ) : (
<> <>
<p className={styles.description}>{content.description}</p> <FormEditMetadata
<article> data={content.form.data}
<FormEditMetadata showPrice={asset?.accessDetails?.type === 'fixed'}
data={content.form.data} isComputeDataset={isComputeType}
showPrice={asset?.accessDetails?.type === 'fixed'} />
isComputeDataset={isComputeType}
/>
<aside> <Web3Feedback
<Web3Feedback networkId={asset?.chainId}
networkId={asset?.chainId} isAssetNetwork={isAssetNetwork}
isAssetNetwork={isAssetNetwork} />
/>
</aside>
{debug === true && ( {debug === true && (
<div className={styles.grid}> <div className={styles.grid}>
<DebugEditMetadata values={values} asset={asset} /> <DebugEditMetadata values={values} asset={asset} />
</div> </div>
)} )}
</article>
</> </>
) )
} }

View File

@ -4,6 +4,7 @@ import { useAsset } from '@context/Asset'
import Button from '@shared/atoms/Button' import Button from '@shared/atoms/Button'
import styles from './FormActions.module.css' import styles from './FormActions.module.css'
import Link from 'next/link' import Link from 'next/link'
import { ComputeEditForm, MetadataEditForm } from './_types'
export default function FormActions({ export default function FormActions({
handleClick handleClick
@ -11,15 +12,17 @@ export default function FormActions({
handleClick?: () => void handleClick?: () => void
}): ReactElement { }): ReactElement {
const { isAssetNetwork, asset } = useAsset() const { isAssetNetwork, asset } = useAsset()
const { isValid }: FormikContextType<Partial<any>> = useFormikContext() const {
isValid,
touched
}: FormikContextType<MetadataEditForm | ComputeEditForm> = useFormikContext()
const isSubmitDisabled =
!isValid || !isAssetNetwork || Object.keys(touched).length === 0
return ( return (
<footer className={styles.actions}> <footer className={styles.actions}>
<Button <Button style="primary" disabled={isSubmitDisabled} onClick={handleClick}>
style="primary"
disabled={!isValid || !isAssetNetwork}
onClick={handleClick}
>
Submit Submit
</Button> </Button>
<Link href={`/asset/${asset?.id}`} key={asset?.id}> <Link href={`/asset/${asset?.id}`} key={asset?.id}>

View File

@ -1,7 +0,0 @@
.form {
composes: box from '@shared/atoms/Box.module.css';
}
.form select[multiple] {
height: 130px;
}

View File

@ -1,9 +1,14 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, {
ChangeEvent,
ReactElement,
useCallback,
useEffect,
useState
} from 'react'
import { Field, Form, FormikContextType, useFormikContext } from 'formik' import { Field, Form, FormikContextType, useFormikContext } from 'formik'
import Input, { InputProps } from '@shared/FormInput' import Input, { InputProps } from '@shared/FormInput'
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection' import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
import stylesIndex from './index.module.css' import stylesIndex from './index.module.css'
import styles from './FormEdit.module.css'
import { import {
generateBaseQuery, generateBaseQuery,
getFilterTerm, getFilterTerm,
@ -16,71 +21,100 @@ import { useCancelToken } from '@hooks/useCancelToken'
import { SortTermOptions } from '../../../@types/aquarius/SearchQuery' import { SortTermOptions } from '../../../@types/aquarius/SearchQuery'
import { getServiceByName } from '@utils/ddo' import { getServiceByName } from '@utils/ddo'
import { transformAssetToAssetSelection } from '@utils/assetConvertor' import { transformAssetToAssetSelection } from '@utils/assetConvertor'
import { useMarketMetadata } from '@context/MarketMetadata' import { ComputeEditForm } from './_types'
import content from '../../../../content/pages/editComputeDataset.json'
import { getFieldContent } from '@utils/form'
export default function FormEditComputeDataset({ export default function FormEditComputeDataset(): ReactElement {
data,
title
}: {
data: InputProps[]
title: string
}): ReactElement {
const { appConfig } = useMarketMetadata()
const { asset } = useAsset() const { asset } = useAsset()
const { values }: FormikContextType<ComputePrivacyForm> = useFormikContext() const { values }: FormikContextType<ComputeEditForm> = useFormikContext()
const [allAlgorithms, setAllAlgorithms] = useState<AssetSelectionAsset[]>()
const newCancelToken = useCancelToken() const newCancelToken = useCancelToken()
const { publisherTrustedAlgorithms } = getServiceByName(
asset,
'compute'
).compute
async function getAlgorithmList( const [allAlgorithms, setAllAlgorithms] = useState<AssetSelectionAsset[]>()
publisherTrustedAlgorithms: PublisherTrustedAlgorithm[]
): Promise<AssetSelectionAsset[]> {
const baseParams = {
chainIds: [asset.chainId],
sort: { sortBy: SortTermOptions.Created },
filters: [getFilterTerm('metadata.type', 'algorithm')]
} as BaseQueryParams
const query = generateBaseQuery(baseParams) const {
const querryResult = await queryMetadata(query, newCancelToken()) validateField,
const datasetComputeService = getServiceByName(asset, 'compute') setFieldValue,
const algorithmSelectionList = await transformAssetToAssetSelection( setFieldTouched
datasetComputeService?.serviceEndpoint, }: FormikContextType<Partial<ComputeEditForm>> = useFormikContext()
querryResult?.results,
publisherTrustedAlgorithms // Manually handle change events instead of using `handleChange` from Formik.
) // Workaround for default `validateOnChange` not kicking in unless user
return algorithmSelectionList // clicks outside of form field.
function handleFieldChange(e: ChangeEvent<HTMLInputElement>, name: string) {
validateField(name)
setFieldTouched(name, true)
setFieldValue(name, e.target.value)
} }
const getAlgorithmList = useCallback(
async (
publisherTrustedAlgorithms: PublisherTrustedAlgorithm[]
): Promise<AssetSelectionAsset[]> => {
const baseParams = {
chainIds: [asset.chainId],
sort: { sortBy: SortTermOptions.Created },
filters: [getFilterTerm('metadata.type', 'algorithm')]
} as BaseQueryParams
const query = generateBaseQuery(baseParams)
const queryResult = await queryMetadata(query, newCancelToken())
const datasetComputeService = getServiceByName(asset, 'compute')
const algorithmSelectionList = await transformAssetToAssetSelection(
datasetComputeService?.serviceEndpoint,
queryResult?.results,
publisherTrustedAlgorithms
)
return algorithmSelectionList
},
[asset, newCancelToken]
)
useEffect(() => { useEffect(() => {
if (!asset) return
const { publisherTrustedAlgorithms } = getServiceByName(
asset,
'compute'
).compute
getAlgorithmList(publisherTrustedAlgorithms).then((algorithms) => { getAlgorithmList(publisherTrustedAlgorithms).then((algorithms) => {
setAllAlgorithms(algorithms) setAllAlgorithms(algorithms)
}) })
}, [appConfig, appConfig.metadataCacheUri, publisherTrustedAlgorithms]) }, [asset, getAlgorithmList])
return ( return (
<Form className={styles.form}> <Form>
<h3 className={stylesIndex.title}>{title}</h3> <header className={stylesIndex.headerForm}>
{data.map((field: InputProps) => ( <h3 className={stylesIndex.titleForm}>{content.form.title}</h3>
<Field <p className={stylesIndex.descriptionForm}>
key={field.name} {content.form.description}
{...field} </p>
options={ </header>
field.name === 'publisherTrustedAlgorithms'
? allAlgorithms <Field
: field.options {...getFieldContent('publisherTrustedAlgorithms', content.form.data)}
} component={Input}
disabled={ name="publisherTrustedAlgorithms"
field.name === 'publisherTrustedAlgorithms' options={allAlgorithms}
? values.allowAllPublishedAlgorithms disabled={values.allowAllPublishedAlgorithms}
: false onChange={(e: ChangeEvent<HTMLInputElement>) =>
} handleFieldChange(e, 'publisherTrustedAlgorithms')
component={Input} }
/> />
))}
<Field
{...getFieldContent('allowAllPublishedAlgorithms', content.form.data)}
component={Input}
name="allowAllPublishedAlgorithms"
options={
getFieldContent('allowAllPublishedAlgorithms', content.form.data)
.options
}
onChange={(e: ChangeEvent<HTMLInputElement>) =>
handleFieldChange(e, 'allowAllPublishedAlgorithms')
}
/>
<FormActions /> <FormActions />
</Form> </Form>

View File

@ -2,7 +2,6 @@ import React, { ChangeEvent, ReactElement } from 'react'
import { Field, Form, FormikContextType, useFormikContext } from 'formik' import { Field, Form, FormikContextType, useFormikContext } from 'formik'
import Input, { InputProps } from '@shared/FormInput' import Input, { InputProps } from '@shared/FormInput'
import FormActions from './FormActions' import FormActions from './FormActions'
import styles from './FormEdit.module.css'
import { useAsset } from '@context/Asset' import { useAsset } from '@context/Asset'
import { MetadataEditForm } from './_types' import { MetadataEditForm } from './_types'
@ -28,18 +27,22 @@ export default function FormEditMetadata({
const { oceanConfig } = useAsset() const { oceanConfig } = useAsset()
const { const {
validateField, validateField,
setFieldValue setFieldValue,
setFieldTouched
}: FormikContextType<Partial<MetadataEditForm>> = useFormikContext() }: FormikContextType<Partial<MetadataEditForm>> = useFormikContext()
// Manually handle change events instead of using `handleChange` from Formik. // Manually handle change events instead of using `handleChange` from Formik.
// Workaround for default `validateOnChange` not kicking in // Workaround for default `validateOnChange` not kicking in unless user
// clicks outside of form field.
function handleFieldChange( function handleFieldChange(
e: ChangeEvent<HTMLInputElement>, e: ChangeEvent<HTMLInputElement>,
field: InputProps field: InputProps
) { ) {
validateField(field.name) validateField(field.name)
setFieldTouched(field.name, true)
setFieldValue(field.name, e.target.value) setFieldValue(field.name, e.target.value)
} }
// 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,
// so handleTimeoutCustomOption is called only once. // so handleTimeoutCustomOption is called only once.
// https://github.com/oceanprotocol/market/pull/324#discussion_r561132310 // https://github.com/oceanprotocol/market/pull/324#discussion_r561132310
@ -57,7 +60,7 @@ export default function FormEditMetadata({
} }
return ( return (
<Form className={styles.form}> <Form>
{data.map( {data.map(
(field: InputProps) => (field: InputProps) =>
(!showPrice && field.name === 'price') || ( (!showPrice && field.name === 'price') || (

View File

@ -1,7 +1,7 @@
import { FileInfo, Metadata, ServiceComputeOptions } from '@oceanprotocol/lib' import { FileInfo, Metadata, ServiceComputeOptions } from '@oceanprotocol/lib'
import { secondsToString } from '@utils/ddo' import { secondsToString } from '@utils/ddo'
import * as Yup from 'yup' import * as Yup from 'yup'
import { MetadataEditForm } from './_types' import { ComputeEditForm, MetadataEditForm } from './_types'
export const validationSchema = Yup.object().shape({ export const validationSchema = Yup.object().shape({
name: Yup.string() name: Yup.string()
@ -33,27 +33,22 @@ export function getInitialValues(
export const computeSettingsValidationSchema = Yup.object().shape({ export const computeSettingsValidationSchema = Yup.object().shape({
allowAllPublishedAlgorithms: Yup.boolean().nullable(), allowAllPublishedAlgorithms: Yup.boolean().nullable(),
publisherTrustedAlgorithms: Yup.array().nullable() publisherTrustedAlgorithms: Yup.array().nullable(),
publisherTrustedAlgorithmPublishers: Yup.array().nullable()
}) })
export function getComputeSettingsInitialValues( export function getComputeSettingsInitialValues({
compute: ServiceComputeOptions publisherTrustedAlgorithms,
): ComputePrivacyForm { publisherTrustedAlgorithmPublishers
const { publisherTrustedAlgorithmPublishers, publisherTrustedAlgorithms } = }: ServiceComputeOptions): ComputeEditForm {
compute const allowAllPublishedAlgorithms = publisherTrustedAlgorithms === null
const allowAllPublishedAlgorithms = !( const publisherTrustedAlgorithmsForForm = allowAllPublishedAlgorithms
publisherTrustedAlgorithms?.length > 0 || ? null
publisherTrustedAlgorithmPublishers?.length > 0 : publisherTrustedAlgorithms.map((algo) => algo.did)
)
const publisherTrustedAlgorithmsForForm = (
publisherTrustedAlgorithms || []
).map((algo) => algo.did)
// TODO: should we add publisherTrustedAlgorithmPublishers to the form?
return { return {
allowAllPublishedAlgorithms, allowAllPublishedAlgorithms,
publisherTrustedAlgorithms: publisherTrustedAlgorithmsForForm publisherTrustedAlgorithms: publisherTrustedAlgorithmsForForm,
publisherTrustedAlgorithmPublishers
} }
} }

View File

@ -1,5 +1,3 @@
// import { EditableMetadataLinks } from '@oceanprotocol/lib'
export interface MetadataEditForm { export interface MetadataEditForm {
name: string name: string
description: string description: string
@ -9,3 +7,9 @@ export interface MetadataEditForm {
files: string | any[] files: string | any[]
author?: string author?: string
} }
export interface ComputeEditForm {
allowAllPublishedAlgorithms: boolean
publisherTrustedAlgorithms: string[]
publisherTrustedAlgorithmPublishers: string[]
}

View File

@ -1,11 +1,7 @@
.edit ul > li[class*='Tabs_tab'] { .container {
padding: calc(var(--spacer) / 4) var(--spacer); composes: box from '@shared/atoms/Box.module.css';
} padding: 0;
position: relative;
.edit [class*='Tabs_tabContent'] {
padding-left: 0;
padding-right: 0;
padding-top: 0;
} }
.grid { .grid {
@ -19,30 +15,18 @@
overflow: hidden; overflow: hidden;
} }
.contianer {
composes: box from '@shared/atoms/Box.module.css';
position: relative;
}
@media (min-width: 60rem) { @media (min-width: 60rem) {
.grid { .grid {
grid-template-columns: 1.5fr 1fr; grid-template-columns: 1.5fr 1fr;
} }
} }
.description { .headerForm {
font-size: var(--font-size-large); margin-bottom: var(--spacer);
margin: calc(var(--spacer) * 0.3 / var(--line-height)) 0
calc(var(--spacer) / var(--line-height)) 0;
} }
.title { .titleForm {
font-size: var(--font-size-large); font-size: var(--font-size-h4);
border-bottom: 1px solid var(--border-color); margin-bottom: calc(var(--spacer) / 8);
padding-bottom: calc(var(--spacer) / 2); max-width: 50rem;
margin-top: -1rem;
margin-left: -2rem;
margin-right: -2rem;
padding-left: 2rem;
padding-right: 2rem;
} }

View File

@ -6,57 +6,58 @@ import EditMetadata from './EditMetadata'
import EditComputeDataset from './EditComputeDataset' import EditComputeDataset from './EditComputeDataset'
import Page from '@shared/Page' import Page from '@shared/Page'
import Loader from '@shared/atoms/Loader' import Loader from '@shared/atoms/Loader'
import { useWeb3 } from '@context/Web3'
import Alert from '@shared/atoms/Alert' import Alert from '@shared/atoms/Alert'
import contentPage from '../../../../content/pages/edit.json'
import Container from '@shared/atoms/Container'
export default function Edit({ uri }: { uri: string }): ReactElement { export default function Edit({ uri }: { uri: string }): ReactElement {
const { asset, error, isInPurgatory, owner, title } = useAsset() const { asset, error, isInPurgatory, title, isOwner } = useAsset()
const [isCompute, setIsCompute] = useState(false) const [isCompute, setIsCompute] = useState(false)
const [pageTitle, setPageTitle] = useState<string>('') const [pageTitle, setPageTitle] = useState<string>('')
const { accountId } = useWeb3()
useEffect(() => { useEffect(() => {
if (!asset || error || accountId !== owner) { if (!asset) return
setPageTitle('Edit action not available')
return const pageTitle = isInPurgatory
} ? ''
setPageTitle(isInPurgatory ? '' : `Edit ${title}`) : !isOwner
? 'Edit action not available'
: `Edit ${title}`
setPageTitle(pageTitle)
setIsCompute(asset?.services[0]?.type === 'compute') setIsCompute(asset?.services[0]?.type === 'compute')
}, [asset, error, isInPurgatory, title]) }, [asset, isInPurgatory, title, isOwner])
const tabs = [ const tabs = [
{ {
title: 'Edit Metadata', title: 'Edit Metadata',
content: <EditMetadata asset={asset} /> content: <EditMetadata asset={asset} />
}, },
{ ...[
title: 'Edit Compute Settings', isCompute && asset?.metadata.type !== 'algorithm'
content: <EditComputeDataset asset={asset} />, ? {
disabled: !isCompute || asset?.metadata?.type === 'algorithm' title: 'Edit Compute Settings',
} content: <EditComputeDataset asset={asset} />
}
: undefined
]
].filter((tab) => tab !== undefined) ].filter((tab) => tab !== undefined)
return asset && return (
asset?.accessDetails && <Page title={pageTitle} description={contentPage.description} uri={uri}>
accountId?.toLowerCase() === owner?.toLowerCase() ? ( {!asset?.accessDetails ? (
<Page title={pageTitle} noPageHeader uri={uri}> <Loader />
<div className={styles.container}> ) : !isOwner ? (
<Tabs items={tabs} defaultIndex={0} className={styles.edit} /> <Alert
</div> title="Edit action available only to asset owner"
</Page> text={error}
) : asset && state="error"
asset?.accessDetails && />
accountId?.toLowerCase() !== owner?.toLowerCase() ? ( ) : (
<Page title={pageTitle} noPageHeader uri={uri}> <Container className={styles.container}>
<Alert <Tabs items={tabs} defaultIndex={0} className={styles.edit} />
title="Edit action available only to asset owner" </Container>
text={error} )}
state="error"
/>
</Page>
) : (
<Page title={pageTitle} noPageHeader uri={uri}>
<Loader />
</Page> </Page>
) )
} }

View File

@ -4,13 +4,13 @@ import { Field, useFormikContext } from 'formik'
import React, { ReactElement, useEffect } from 'react' import React, { ReactElement, useEffect } from 'react'
import content from '../../../../content/publish/form.json' import content from '../../../../content/publish/form.json'
import { FormPublishData } from '../_types' import { FormPublishData } from '../_types'
import { getFieldContent } from '../_utils'
import IconDataset from '@images/dataset.svg' import IconDataset from '@images/dataset.svg'
import IconAlgorithm from '@images/algorithm.svg' import IconAlgorithm from '@images/algorithm.svg'
import styles from './index.module.css' import styles from './index.module.css'
import { algorithmContainerPresets } from '../_constants' import { algorithmContainerPresets } from '../_constants'
import Alert from '@shared/atoms/Alert' import Alert from '@shared/atoms/Alert'
import { useMarketMetadata } from '@context/MarketMetadata' import { useMarketMetadata } from '@context/MarketMetadata'
import { getFieldContent } from '@utils/form'
const assetTypeOptionsTitles = getFieldContent( const assetTypeOptionsTitles = getFieldContent(
'type', 'type',

View File

@ -1,12 +1,12 @@
import Conversion from '@shared/Price/Conversion' import Conversion from '@shared/Price/Conversion'
import { Field, useField, useFormikContext } from 'formik' import { Field, useField, useFormikContext } from 'formik'
import React, { ReactElement, useEffect } from 'react' import React, { ReactElement } from 'react'
import Input from '@shared/FormInput' import Input from '@shared/FormInput'
import Error from '@shared/FormInput/Error' import Error from '@shared/FormInput/Error'
import PriceUnit from '@shared/Price/PriceUnit' import PriceUnit from '@shared/Price/PriceUnit'
import styles from './Price.module.css' import styles from './Price.module.css'
import { FormPublishData } from '../_types' import { FormPublishData } from '../_types'
import { getFieldContent } from '../_utils' import { getFieldContent } from '@utils/form'
export default function Price({ export default function Price({
firstPrice, firstPrice,

View File

@ -4,7 +4,7 @@ import React, { ReactElement, useEffect } from 'react'
import IconDownload from '@images/download.svg' import IconDownload from '@images/download.svg'
import IconCompute from '@images/compute.svg' import IconCompute from '@images/compute.svg'
import content from '../../../../content/publish/form.json' import content from '../../../../content/publish/form.json'
import { getFieldContent } from '../_utils' import { getFieldContent } from '@utils/form'
import { FormPublishData } from '../_types' import { FormPublishData } from '../_types'
import Alert from '@shared/atoms/Alert' import Alert from '@shared/atoms/Alert'
import { useMarketMetadata } from '@context/MarketMetadata' import { useMarketMetadata } from '@context/MarketMetadata'

View File

@ -33,13 +33,6 @@ import {
} from '../../../app.config' } from '../../../app.config'
import { sanitizeUrl } from '@utils/url' import { sanitizeUrl } from '@utils/url'
export function getFieldContent(
fieldName: string,
fields: FormFieldContent[]
): FormFieldContent {
return fields.filter((field: FormFieldContent) => field.name === fieldName)[0]
}
function getUrlFileExtension(fileUrl: string): string { function getUrlFileExtension(fileUrl: string): string {
const splittedFileUrl = fileUrl.split('.') const splittedFileUrl = fileUrl.split('.')
return splittedFileUrl[splittedFileUrl.length - 1] return splittedFileUrl[splittedFileUrl.length - 1]

View File

@ -195,13 +195,13 @@ export async function addExistingParamsToUrl(
const parsed = queryString.parse(location.search) const parsed = queryString.parse(location.search)
let urlLocation = '/search?' let urlLocation = '/search?'
if (Object.keys(parsed).length > 0) { if (Object.keys(parsed).length > 0) {
for (const querryParam in parsed) { for (const queryParam in parsed) {
if (!excludedParams.includes(querryParam)) { if (!excludedParams.includes(queryParam)) {
if (querryParam === 'page' && excludedParams.includes('text')) { if (queryParam === 'page' && excludedParams.includes('text')) {
LoggerInstance.log('remove page when starting a new search') LoggerInstance.log('remove page when starting a new search')
} else { } else {
const value = parsed[querryParam] const value = parsed[queryParam]
urlLocation = `${urlLocation}${querryParam}=${value}&` urlLocation = `${urlLocation}${queryParam}=${value}&`
} }
} }
} }