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.",
"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."
}

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": {
"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": [
{

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
}
interface ComputePrivacyForm {
allowAllPublishedAlgorithms: boolean
publisherTrustedAlgorithms: string[]
}
interface TokenOrder {
id: string
serviceIndex: number

View File

@ -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
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 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] =

View File

@ -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}

View File

@ -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 {

View File

@ -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>
)
}

View File

@ -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>
)}
</>
)
}

View File

@ -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}>

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 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>

View File

@ -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') || (

View File

@ -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
}
}

View File

@ -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[]
}

View File

@ -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;
}

View File

@ -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>
)
}

View File

@ -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',

View File

@ -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,

View File

@ -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'

View File

@ -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]

View File

@ -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}&`
}
}
}