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:
parent
5387b9a3dd
commit
02beb0f8c3
@ -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.",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
"description": "Updating metadata or updating compute settings will create an on-chain transaction you have to approve in your wallet."
|
||||
}
|
||||
|
@ -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": {
|
||||
"title": "Set allowed algorithms",
|
||||
"success": "🎉 Successfully updated. 🎉",
|
||||
"successAction": "Close",
|
||||
"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.",
|
||||
"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": [
|
||||
{
|
||||
|
65
content/pages/editMetadata.json
Normal file
65
content/pages/editMetadata.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
5
src/@types/Compute.d.ts
vendored
5
src/@types/Compute.d.ts
vendored
@ -15,11 +15,6 @@ declare global {
|
||||
name: string
|
||||
}
|
||||
|
||||
interface ComputePrivacyForm {
|
||||
allowAllPublishedAlgorithms: boolean
|
||||
publisherTrustedAlgorithms: string[]
|
||||
}
|
||||
|
||||
interface TokenOrder {
|
||||
id: string
|
||||
serviceIndex: number
|
||||
|
@ -25,6 +25,7 @@ import { SortTermOptions } from 'src/@types/aquarius/SearchQuery'
|
||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
||||
import { transformAssetToAssetSelection } from './assetConvertor'
|
||||
import { AssetExtended } from 'src/@types/AssetExtended'
|
||||
import { ComputeEditForm } from 'src/components/Asset/Edit/_types'
|
||||
|
||||
const getComputeOrders = gql`
|
||||
query ComputeOrders($user: String!) {
|
||||
@ -329,6 +330,7 @@ export async function createTrustedAlgorithmList(
|
||||
assetChainId: number,
|
||||
cancelToken: CancelToken
|
||||
): Promise<PublisherTrustedAlgorithm[]> {
|
||||
if (!selectedAlgorithms || selectedAlgorithms.length === 0) return []
|
||||
const trustedAlgorithms: PublisherTrustedAlgorithm[] = []
|
||||
|
||||
const selectedAssets = await retrieveDDOListByDIDs(
|
||||
@ -337,6 +339,8 @@ export async function createTrustedAlgorithmList(
|
||||
cancelToken
|
||||
)
|
||||
|
||||
if (!selectedAssets || selectedAssets.length === 0) return []
|
||||
|
||||
for (const selectedAlgorithm of selectedAssets) {
|
||||
const sanitizedAlgorithmContainer = {
|
||||
entrypoint: selectedAlgorithm.metadata.algorithm.container.entrypoint,
|
||||
@ -357,22 +361,28 @@ export async function createTrustedAlgorithmList(
|
||||
}
|
||||
|
||||
export async function transformComputeFormToServiceComputeOptions(
|
||||
values: ComputePrivacyForm,
|
||||
values: ComputeEditForm,
|
||||
currentOptions: ServiceComputeOptions,
|
||||
assetChainId: number,
|
||||
cancelToken: CancelToken
|
||||
): Promise<ServiceComputeOptions> {
|
||||
const publisherTrustedAlgorithms = values.allowAllPublishedAlgorithms
|
||||
? []
|
||||
? null
|
||||
: await createTrustedAlgorithmList(
|
||||
values.publisherTrustedAlgorithms,
|
||||
assetChainId,
|
||||
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 = {
|
||||
...currentOptions,
|
||||
publisherTrustedAlgorithms
|
||||
publisherTrustedAlgorithms,
|
||||
publisherTrustedAlgorithmPublishers
|
||||
}
|
||||
|
||||
return privacy
|
||||
|
6
src/@utils/form.ts
Normal file
6
src/@utils/form.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export function getFieldContent(
|
||||
fieldName: string,
|
||||
fields: FormFieldContent[]
|
||||
): FormFieldContent {
|
||||
return fields.filter((field: FormFieldContent) => field.name === fieldName)[0]
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
import { Asset, ServiceComputeOptions } from '@oceanprotocol/lib'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
// import { transformComputeFormToServiceComputePrivacy } from '@utils/compute'
|
||||
import DebugOutput from '@shared/DebugOutput'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { transformComputeFormToServiceComputeOptions } from '@utils/compute'
|
||||
import { ComputeEditForm } from './_types'
|
||||
|
||||
export default function DebugEditCompute({
|
||||
values,
|
||||
asset
|
||||
}: {
|
||||
values: ComputePrivacyForm
|
||||
values: ComputeEditForm
|
||||
asset: Asset
|
||||
}): ReactElement {
|
||||
const [formTransformed, setFormTransformed] =
|
||||
|
@ -6,9 +6,6 @@ import {
|
||||
LoggerInstance,
|
||||
ServiceComputeOptions,
|
||||
Service,
|
||||
ProviderInstance,
|
||||
getHash,
|
||||
Nft,
|
||||
Asset
|
||||
} from '@oceanprotocol/lib'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
@ -29,6 +26,7 @@ import DebugEditCompute from './DebugEditCompute'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import EditFeedback from './EditFeedback'
|
||||
import { setNftMetadata } from '@utils/nft'
|
||||
import { ComputeEditForm } from './_types'
|
||||
|
||||
export default function EditComputeDataset({
|
||||
asset
|
||||
@ -44,10 +42,7 @@ export default function EditComputeDataset({
|
||||
const newCancelToken = useCancelToken()
|
||||
const hasFeedback = error || success
|
||||
|
||||
async function handleSubmit(
|
||||
values: ComputePrivacyForm,
|
||||
resetForm: () => void
|
||||
) {
|
||||
async function handleSubmit(values: ComputeEditForm, resetForm: () => void) {
|
||||
try {
|
||||
if (asset?.accessDetails?.type === 'free') {
|
||||
const tx = await setMinterToPublisher(
|
||||
@ -130,18 +125,19 @@ export default function EditComputeDataset({
|
||||
// move user's focus to top of screen
|
||||
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
|
||||
// kick off editing
|
||||
await handleSubmit(values as any, resetForm)
|
||||
await handleSubmit(values, resetForm)
|
||||
}}
|
||||
enableReinitialize
|
||||
>
|
||||
{({ values, isSubmitting }) =>
|
||||
isSubmitting || hasFeedback ? (
|
||||
<EditFeedback
|
||||
title="Updating Data Set"
|
||||
loading="Updating data set with new compute settings..."
|
||||
error={error}
|
||||
success={success}
|
||||
setError={setError}
|
||||
successAction={{
|
||||
name: 'View Asset',
|
||||
name: 'Back to Asset',
|
||||
onClick: async () => {
|
||||
await fetchAsset()
|
||||
},
|
||||
@ -150,13 +146,7 @@ export default function EditComputeDataset({
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<p className={styles.description}>{content.description}</p>
|
||||
<article>
|
||||
<FormEditComputeDataset
|
||||
title={content.form.title}
|
||||
data={content.form.data}
|
||||
/>
|
||||
</article>
|
||||
<FormEditComputeDataset />
|
||||
<Web3Feedback
|
||||
networkId={asset?.chainId}
|
||||
isAssetNetwork={isAssetNetwork}
|
||||
|
@ -1,16 +1,10 @@
|
||||
.feedback {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 40vh;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.box {
|
||||
composes: box from '@shared/atoms/Box.module.css';
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.feedback h3 {
|
||||
|
@ -41,17 +41,15 @@ function ActionError({ setError }: { setError: (error: string) => void }) {
|
||||
}
|
||||
|
||||
export default function EditFeedback({
|
||||
title,
|
||||
error,
|
||||
success,
|
||||
loading,
|
||||
successAction,
|
||||
setError
|
||||
}: {
|
||||
title: string
|
||||
error: string
|
||||
success: string
|
||||
loading?: string
|
||||
loading: string
|
||||
successAction: Action
|
||||
setError: (error: string) => void
|
||||
}): ReactElement {
|
||||
@ -64,31 +62,28 @@ export default function EditFeedback({
|
||||
|
||||
return (
|
||||
<div className={styles.feedback}>
|
||||
<div className={styles.box}>
|
||||
<h3>{title}</h3>
|
||||
{error ? (
|
||||
<>
|
||||
<p>Sorry, something went wrong. Please try again.</p>
|
||||
{moreInfo && <Alert text={error} state="error" />}
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={toggleMoreInfo}
|
||||
className={styles.moreInfo}
|
||||
>
|
||||
{moreInfo === false ? 'More Info' : 'Hide error'}
|
||||
</Button>
|
||||
<ActionError setError={setError} />
|
||||
</>
|
||||
) : success ? (
|
||||
<SuccessConfetti
|
||||
success={success}
|
||||
action={<ActionSuccess action={successAction} />}
|
||||
/>
|
||||
) : (
|
||||
<Loader message={loading} />
|
||||
)}
|
||||
</div>
|
||||
{error ? (
|
||||
<>
|
||||
<p>Sorry, something went wrong. Please try again.</p>
|
||||
{moreInfo && <Alert text={error} state="error" />}
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={toggleMoreInfo}
|
||||
className={styles.moreInfo}
|
||||
>
|
||||
{moreInfo === false ? 'More Info' : 'Hide error'}
|
||||
</Button>
|
||||
<ActionError setError={setError} />
|
||||
</>
|
||||
) : success ? (
|
||||
<SuccessConfetti
|
||||
success={success}
|
||||
action={<ActionSuccess action={successAction} />}
|
||||
/>
|
||||
) : (
|
||||
<Loader message={loading} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import Web3Feedback from '@shared/Web3Feedback'
|
||||
import FormEditMetadata from './FormEditMetadata'
|
||||
import { mapTimeoutStringToSeconds } from '@utils/ddo'
|
||||
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 { useAbortController } from '@hooks/useAbortController'
|
||||
import DebugEditMetadata from './DebugEditMetadata'
|
||||
@ -159,12 +159,12 @@ export default function Edit({
|
||||
{({ isSubmitting, values }) =>
|
||||
isSubmitting || hasFeedback ? (
|
||||
<EditFeedback
|
||||
title="Updating Data Set"
|
||||
loading="Updating asset with new metadata..."
|
||||
error={error}
|
||||
success={success}
|
||||
setError={setError}
|
||||
successAction={{
|
||||
name: 'View Asset',
|
||||
name: 'Back to Asset',
|
||||
onClick: async () => {
|
||||
await fetchAsset()
|
||||
},
|
||||
@ -173,27 +173,22 @@ export default function Edit({
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<p className={styles.description}>{content.description}</p>
|
||||
<article>
|
||||
<FormEditMetadata
|
||||
data={content.form.data}
|
||||
showPrice={asset?.accessDetails?.type === 'fixed'}
|
||||
isComputeDataset={isComputeType}
|
||||
/>
|
||||
<FormEditMetadata
|
||||
data={content.form.data}
|
||||
showPrice={asset?.accessDetails?.type === 'fixed'}
|
||||
isComputeDataset={isComputeType}
|
||||
/>
|
||||
|
||||
<aside>
|
||||
<Web3Feedback
|
||||
networkId={asset?.chainId}
|
||||
isAssetNetwork={isAssetNetwork}
|
||||
/>
|
||||
</aside>
|
||||
<Web3Feedback
|
||||
networkId={asset?.chainId}
|
||||
isAssetNetwork={isAssetNetwork}
|
||||
/>
|
||||
|
||||
{debug === true && (
|
||||
<div className={styles.grid}>
|
||||
<DebugEditMetadata values={values} asset={asset} />
|
||||
</div>
|
||||
)}
|
||||
</article>
|
||||
{debug === true && (
|
||||
<div className={styles.grid}>
|
||||
<DebugEditMetadata values={values} asset={asset} />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { useAsset } from '@context/Asset'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import styles from './FormActions.module.css'
|
||||
import Link from 'next/link'
|
||||
import { ComputeEditForm, MetadataEditForm } from './_types'
|
||||
|
||||
export default function FormActions({
|
||||
handleClick
|
||||
@ -11,15 +12,17 @@ export default function FormActions({
|
||||
handleClick?: () => void
|
||||
}): ReactElement {
|
||||
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 (
|
||||
<footer className={styles.actions}>
|
||||
<Button
|
||||
style="primary"
|
||||
disabled={!isValid || !isAssetNetwork}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Button style="primary" disabled={isSubmitDisabled} onClick={handleClick}>
|
||||
Submit
|
||||
</Button>
|
||||
<Link href={`/asset/${asset?.id}`} key={asset?.id}>
|
||||
|
@ -1,7 +0,0 @@
|
||||
.form {
|
||||
composes: box from '@shared/atoms/Box.module.css';
|
||||
}
|
||||
|
||||
.form select[multiple] {
|
||||
height: 130px;
|
||||
}
|
@ -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 Input, { InputProps } from '@shared/FormInput'
|
||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
||||
import stylesIndex from './index.module.css'
|
||||
import styles from './FormEdit.module.css'
|
||||
import {
|
||||
generateBaseQuery,
|
||||
getFilterTerm,
|
||||
@ -16,71 +21,100 @@ import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { SortTermOptions } from '../../../@types/aquarius/SearchQuery'
|
||||
import { getServiceByName } from '@utils/ddo'
|
||||
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({
|
||||
data,
|
||||
title
|
||||
}: {
|
||||
data: InputProps[]
|
||||
title: string
|
||||
}): ReactElement {
|
||||
const { appConfig } = useMarketMetadata()
|
||||
export default function FormEditComputeDataset(): ReactElement {
|
||||
const { asset } = useAsset()
|
||||
const { values }: FormikContextType<ComputePrivacyForm> = useFormikContext()
|
||||
const [allAlgorithms, setAllAlgorithms] = useState<AssetSelectionAsset[]>()
|
||||
const { values }: FormikContextType<ComputeEditForm> = useFormikContext()
|
||||
const newCancelToken = useCancelToken()
|
||||
const { publisherTrustedAlgorithms } = getServiceByName(
|
||||
asset,
|
||||
'compute'
|
||||
).compute
|
||||
|
||||
async function getAlgorithmList(
|
||||
publisherTrustedAlgorithms: PublisherTrustedAlgorithm[]
|
||||
): Promise<AssetSelectionAsset[]> {
|
||||
const baseParams = {
|
||||
chainIds: [asset.chainId],
|
||||
sort: { sortBy: SortTermOptions.Created },
|
||||
filters: [getFilterTerm('metadata.type', 'algorithm')]
|
||||
} as BaseQueryParams
|
||||
const [allAlgorithms, setAllAlgorithms] = useState<AssetSelectionAsset[]>()
|
||||
|
||||
const query = generateBaseQuery(baseParams)
|
||||
const querryResult = await queryMetadata(query, newCancelToken())
|
||||
const datasetComputeService = getServiceByName(asset, 'compute')
|
||||
const algorithmSelectionList = await transformAssetToAssetSelection(
|
||||
datasetComputeService?.serviceEndpoint,
|
||||
querryResult?.results,
|
||||
publisherTrustedAlgorithms
|
||||
)
|
||||
return algorithmSelectionList
|
||||
const {
|
||||
validateField,
|
||||
setFieldValue,
|
||||
setFieldTouched
|
||||
}: FormikContextType<Partial<ComputeEditForm>> = useFormikContext()
|
||||
|
||||
// Manually handle change events instead of using `handleChange` from Formik.
|
||||
// Workaround for default `validateOnChange` not kicking in unless user
|
||||
// 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(() => {
|
||||
if (!asset) return
|
||||
|
||||
const { publisherTrustedAlgorithms } = getServiceByName(
|
||||
asset,
|
||||
'compute'
|
||||
).compute
|
||||
|
||||
getAlgorithmList(publisherTrustedAlgorithms).then((algorithms) => {
|
||||
setAllAlgorithms(algorithms)
|
||||
})
|
||||
}, [appConfig, appConfig.metadataCacheUri, publisherTrustedAlgorithms])
|
||||
}, [asset, getAlgorithmList])
|
||||
|
||||
return (
|
||||
<Form className={styles.form}>
|
||||
<h3 className={stylesIndex.title}>{title}</h3>
|
||||
{data.map((field: InputProps) => (
|
||||
<Field
|
||||
key={field.name}
|
||||
{...field}
|
||||
options={
|
||||
field.name === 'publisherTrustedAlgorithms'
|
||||
? allAlgorithms
|
||||
: field.options
|
||||
}
|
||||
disabled={
|
||||
field.name === 'publisherTrustedAlgorithms'
|
||||
? values.allowAllPublishedAlgorithms
|
||||
: false
|
||||
}
|
||||
component={Input}
|
||||
/>
|
||||
))}
|
||||
<Form>
|
||||
<header className={stylesIndex.headerForm}>
|
||||
<h3 className={stylesIndex.titleForm}>{content.form.title}</h3>
|
||||
<p className={stylesIndex.descriptionForm}>
|
||||
{content.form.description}
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<Field
|
||||
{...getFieldContent('publisherTrustedAlgorithms', content.form.data)}
|
||||
component={Input}
|
||||
name="publisherTrustedAlgorithms"
|
||||
options={allAlgorithms}
|
||||
disabled={values.allowAllPublishedAlgorithms}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
handleFieldChange(e, 'publisherTrustedAlgorithms')
|
||||
}
|
||||
/>
|
||||
|
||||
<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 />
|
||||
</Form>
|
||||
|
@ -2,7 +2,6 @@ import React, { ChangeEvent, ReactElement } from 'react'
|
||||
import { Field, Form, FormikContextType, useFormikContext } from 'formik'
|
||||
import Input, { InputProps } from '@shared/FormInput'
|
||||
import FormActions from './FormActions'
|
||||
import styles from './FormEdit.module.css'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { MetadataEditForm } from './_types'
|
||||
|
||||
@ -28,18 +27,22 @@ export default function FormEditMetadata({
|
||||
const { oceanConfig } = useAsset()
|
||||
const {
|
||||
validateField,
|
||||
setFieldValue
|
||||
setFieldValue,
|
||||
setFieldTouched
|
||||
}: FormikContextType<Partial<MetadataEditForm>> = useFormikContext()
|
||||
|
||||
// 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(
|
||||
e: ChangeEvent<HTMLInputElement>,
|
||||
field: InputProps
|
||||
) {
|
||||
validateField(field.name)
|
||||
setFieldTouched(field.name, true)
|
||||
setFieldValue(field.name, e.target.value)
|
||||
}
|
||||
|
||||
// This component is handled by Formik so it's not rendered like a "normal" react component,
|
||||
// so handleTimeoutCustomOption is called only once.
|
||||
// https://github.com/oceanprotocol/market/pull/324#discussion_r561132310
|
||||
@ -57,7 +60,7 @@ export default function FormEditMetadata({
|
||||
}
|
||||
|
||||
return (
|
||||
<Form className={styles.form}>
|
||||
<Form>
|
||||
{data.map(
|
||||
(field: InputProps) =>
|
||||
(!showPrice && field.name === 'price') || (
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FileInfo, Metadata, ServiceComputeOptions } from '@oceanprotocol/lib'
|
||||
import { secondsToString } from '@utils/ddo'
|
||||
import * as Yup from 'yup'
|
||||
import { MetadataEditForm } from './_types'
|
||||
import { ComputeEditForm, MetadataEditForm } from './_types'
|
||||
|
||||
export const validationSchema = Yup.object().shape({
|
||||
name: Yup.string()
|
||||
@ -33,27 +33,22 @@ export function getInitialValues(
|
||||
|
||||
export const computeSettingsValidationSchema = Yup.object().shape({
|
||||
allowAllPublishedAlgorithms: Yup.boolean().nullable(),
|
||||
publisherTrustedAlgorithms: Yup.array().nullable()
|
||||
publisherTrustedAlgorithms: Yup.array().nullable(),
|
||||
publisherTrustedAlgorithmPublishers: Yup.array().nullable()
|
||||
})
|
||||
|
||||
export function getComputeSettingsInitialValues(
|
||||
compute: ServiceComputeOptions
|
||||
): ComputePrivacyForm {
|
||||
const { publisherTrustedAlgorithmPublishers, publisherTrustedAlgorithms } =
|
||||
compute
|
||||
const allowAllPublishedAlgorithms = !(
|
||||
publisherTrustedAlgorithms?.length > 0 ||
|
||||
publisherTrustedAlgorithmPublishers?.length > 0
|
||||
)
|
||||
|
||||
const publisherTrustedAlgorithmsForForm = (
|
||||
publisherTrustedAlgorithms || []
|
||||
).map((algo) => algo.did)
|
||||
|
||||
// TODO: should we add publisherTrustedAlgorithmPublishers to the form?
|
||||
export function getComputeSettingsInitialValues({
|
||||
publisherTrustedAlgorithms,
|
||||
publisherTrustedAlgorithmPublishers
|
||||
}: ServiceComputeOptions): ComputeEditForm {
|
||||
const allowAllPublishedAlgorithms = publisherTrustedAlgorithms === null
|
||||
const publisherTrustedAlgorithmsForForm = allowAllPublishedAlgorithms
|
||||
? null
|
||||
: publisherTrustedAlgorithms.map((algo) => algo.did)
|
||||
|
||||
return {
|
||||
allowAllPublishedAlgorithms,
|
||||
publisherTrustedAlgorithms: publisherTrustedAlgorithmsForForm
|
||||
publisherTrustedAlgorithms: publisherTrustedAlgorithmsForForm,
|
||||
publisherTrustedAlgorithmPublishers
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
// import { EditableMetadataLinks } from '@oceanprotocol/lib'
|
||||
|
||||
export interface MetadataEditForm {
|
||||
name: string
|
||||
description: string
|
||||
@ -9,3 +7,9 @@ export interface MetadataEditForm {
|
||||
files: string | any[]
|
||||
author?: string
|
||||
}
|
||||
|
||||
export interface ComputeEditForm {
|
||||
allowAllPublishedAlgorithms: boolean
|
||||
publisherTrustedAlgorithms: string[]
|
||||
publisherTrustedAlgorithmPublishers: string[]
|
||||
}
|
||||
|
@ -1,11 +1,7 @@
|
||||
.edit ul > li[class*='Tabs_tab'] {
|
||||
padding: calc(var(--spacer) / 4) var(--spacer);
|
||||
}
|
||||
|
||||
.edit [class*='Tabs_tabContent'] {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
padding-top: 0;
|
||||
.container {
|
||||
composes: box from '@shared/atoms/Box.module.css';
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.grid {
|
||||
@ -19,30 +15,18 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.contianer {
|
||||
composes: box from '@shared/atoms/Box.module.css';
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (min-width: 60rem) {
|
||||
.grid {
|
||||
grid-template-columns: 1.5fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: var(--font-size-large);
|
||||
margin: calc(var(--spacer) * 0.3 / var(--line-height)) 0
|
||||
calc(var(--spacer) / var(--line-height)) 0;
|
||||
.headerForm {
|
||||
margin-bottom: var(--spacer);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-size-large);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: calc(var(--spacer) / 2);
|
||||
margin-top: -1rem;
|
||||
margin-left: -2rem;
|
||||
margin-right: -2rem;
|
||||
padding-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
.titleForm {
|
||||
font-size: var(--font-size-h4);
|
||||
margin-bottom: calc(var(--spacer) / 8);
|
||||
max-width: 50rem;
|
||||
}
|
||||
|
@ -6,57 +6,58 @@ import EditMetadata from './EditMetadata'
|
||||
import EditComputeDataset from './EditComputeDataset'
|
||||
import Page from '@shared/Page'
|
||||
import Loader from '@shared/atoms/Loader'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
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 {
|
||||
const { asset, error, isInPurgatory, owner, title } = useAsset()
|
||||
const { asset, error, isInPurgatory, title, isOwner } = useAsset()
|
||||
const [isCompute, setIsCompute] = useState(false)
|
||||
const [pageTitle, setPageTitle] = useState<string>('')
|
||||
const { accountId } = useWeb3()
|
||||
|
||||
useEffect(() => {
|
||||
if (!asset || error || accountId !== owner) {
|
||||
setPageTitle('Edit action not available')
|
||||
return
|
||||
}
|
||||
setPageTitle(isInPurgatory ? '' : `Edit ${title}`)
|
||||
if (!asset) return
|
||||
|
||||
const pageTitle = isInPurgatory
|
||||
? ''
|
||||
: !isOwner
|
||||
? 'Edit action not available'
|
||||
: `Edit ${title}`
|
||||
|
||||
setPageTitle(pageTitle)
|
||||
setIsCompute(asset?.services[0]?.type === 'compute')
|
||||
}, [asset, error, isInPurgatory, title])
|
||||
}, [asset, isInPurgatory, title, isOwner])
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: 'Edit Metadata',
|
||||
content: <EditMetadata asset={asset} />
|
||||
},
|
||||
{
|
||||
title: 'Edit Compute Settings',
|
||||
content: <EditComputeDataset asset={asset} />,
|
||||
disabled: !isCompute || asset?.metadata?.type === 'algorithm'
|
||||
}
|
||||
...[
|
||||
isCompute && asset?.metadata.type !== 'algorithm'
|
||||
? {
|
||||
title: 'Edit Compute Settings',
|
||||
content: <EditComputeDataset asset={asset} />
|
||||
}
|
||||
: undefined
|
||||
]
|
||||
].filter((tab) => tab !== undefined)
|
||||
|
||||
return asset &&
|
||||
asset?.accessDetails &&
|
||||
accountId?.toLowerCase() === owner?.toLowerCase() ? (
|
||||
<Page title={pageTitle} noPageHeader uri={uri}>
|
||||
<div className={styles.container}>
|
||||
<Tabs items={tabs} defaultIndex={0} className={styles.edit} />
|
||||
</div>
|
||||
</Page>
|
||||
) : asset &&
|
||||
asset?.accessDetails &&
|
||||
accountId?.toLowerCase() !== owner?.toLowerCase() ? (
|
||||
<Page title={pageTitle} noPageHeader uri={uri}>
|
||||
<Alert
|
||||
title="Edit action available only to asset owner"
|
||||
text={error}
|
||||
state="error"
|
||||
/>
|
||||
</Page>
|
||||
) : (
|
||||
<Page title={pageTitle} noPageHeader uri={uri}>
|
||||
<Loader />
|
||||
return (
|
||||
<Page title={pageTitle} description={contentPage.description} uri={uri}>
|
||||
{!asset?.accessDetails ? (
|
||||
<Loader />
|
||||
) : !isOwner ? (
|
||||
<Alert
|
||||
title="Edit action available only to asset owner"
|
||||
text={error}
|
||||
state="error"
|
||||
/>
|
||||
) : (
|
||||
<Container className={styles.container}>
|
||||
<Tabs items={tabs} defaultIndex={0} className={styles.edit} />
|
||||
</Container>
|
||||
)}
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
|
@ -4,13 +4,13 @@ import { Field, useFormikContext } from 'formik'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import content from '../../../../content/publish/form.json'
|
||||
import { FormPublishData } from '../_types'
|
||||
import { getFieldContent } from '../_utils'
|
||||
import IconDataset from '@images/dataset.svg'
|
||||
import IconAlgorithm from '@images/algorithm.svg'
|
||||
import styles from './index.module.css'
|
||||
import { algorithmContainerPresets } from '../_constants'
|
||||
import Alert from '@shared/atoms/Alert'
|
||||
import { useMarketMetadata } from '@context/MarketMetadata'
|
||||
import { getFieldContent } from '@utils/form'
|
||||
|
||||
const assetTypeOptionsTitles = getFieldContent(
|
||||
'type',
|
||||
|
@ -1,12 +1,12 @@
|
||||
import Conversion from '@shared/Price/Conversion'
|
||||
import { Field, useField, useFormikContext } from 'formik'
|
||||
import React, { ReactElement, useEffect } from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import Input from '@shared/FormInput'
|
||||
import Error from '@shared/FormInput/Error'
|
||||
import PriceUnit from '@shared/Price/PriceUnit'
|
||||
import styles from './Price.module.css'
|
||||
import { FormPublishData } from '../_types'
|
||||
import { getFieldContent } from '../_utils'
|
||||
import { getFieldContent } from '@utils/form'
|
||||
|
||||
export default function Price({
|
||||
firstPrice,
|
||||
|
@ -4,7 +4,7 @@ import React, { ReactElement, useEffect } from 'react'
|
||||
import IconDownload from '@images/download.svg'
|
||||
import IconCompute from '@images/compute.svg'
|
||||
import content from '../../../../content/publish/form.json'
|
||||
import { getFieldContent } from '../_utils'
|
||||
import { getFieldContent } from '@utils/form'
|
||||
import { FormPublishData } from '../_types'
|
||||
import Alert from '@shared/atoms/Alert'
|
||||
import { useMarketMetadata } from '@context/MarketMetadata'
|
||||
|
@ -33,13 +33,6 @@ import {
|
||||
} from '../../../app.config'
|
||||
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 {
|
||||
const splittedFileUrl = fileUrl.split('.')
|
||||
return splittedFileUrl[splittedFileUrl.length - 1]
|
||||
|
@ -195,13 +195,13 @@ export async function addExistingParamsToUrl(
|
||||
const parsed = queryString.parse(location.search)
|
||||
let urlLocation = '/search?'
|
||||
if (Object.keys(parsed).length > 0) {
|
||||
for (const querryParam in parsed) {
|
||||
if (!excludedParams.includes(querryParam)) {
|
||||
if (querryParam === 'page' && excludedParams.includes('text')) {
|
||||
for (const queryParam in parsed) {
|
||||
if (!excludedParams.includes(queryParam)) {
|
||||
if (queryParam === 'page' && excludedParams.includes('text')) {
|
||||
LoggerInstance.log('remove page when starting a new search')
|
||||
} else {
|
||||
const value = parsed[querryParam]
|
||||
urlLocation = `${urlLocation}${querryParam}=${value}&`
|
||||
const value = parsed[queryParam]
|
||||
urlLocation = `${urlLocation}${queryParam}=${value}&`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user