mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
Update metadata, the proper way (#292)
* prototype view switching * refactor, more UI * formik form setup & data flow * debug output, fixes, refactor * description preview refactor * publish/update date changes * output created & updated date at top of asset * use ddo.created & ddo.updated everywhere * stop pushing metadata.main.datePublished * owner check for edit link * all the feedback states and switching between them: loading, error, success * refactor feedback, one component for publish & edit * action & date output fixes * move all content, iterate form fields from it * UI updates * styling tweaks * ddo dataflow refactor, more useAsset usage * more useAsset usage * form actions styling * prepare edit history component * metadata output tweaks * copy * safeguard against profile urls without protocol defined * refetch ddo after edit Signed-off-by: mihaisc <mihai.scarlat@smartcontrol.ro> * switch author for dataTokenOptions in metadata preview * refactor * copy * showPricing fix * validation: minimum characters for title & description * disable submit button when validation fails * form validation fixes * manually trigger onChange validation in publish & edit forms Co-authored-by: mihaisc <mihai.scarlat@smartcontrol.ro>
This commit is contained in:
parent
c57731cd0b
commit
960c5b3234
25
content/pages/edit.json
Normal file
25
content/pages/edit.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
18
src/components/atoms/DebugOutput.tsx
Normal file
18
src/components/atoms/DebugOutput.tsx
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
|
||||||
|
export default function DebugOutput({
|
||||||
|
title,
|
||||||
|
output
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
output: any
|
||||||
|
}): ReactElement {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h5>{title}</h5>
|
||||||
|
<pre>
|
||||||
|
<code>{JSON.stringify(output, null, 2)}</code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -44,15 +44,7 @@ export interface InputProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Input(props: Partial<InputProps>): ReactElement {
|
export default function Input(props: Partial<InputProps>): ReactElement {
|
||||||
const {
|
const { label, help, additionalComponent, size, field } = props
|
||||||
required,
|
|
||||||
name,
|
|
||||||
label,
|
|
||||||
help,
|
|
||||||
additionalComponent,
|
|
||||||
size,
|
|
||||||
field
|
|
||||||
} = props
|
|
||||||
|
|
||||||
const hasError =
|
const hasError =
|
||||||
props.form?.touched[field.name] && props.form?.errors[field.name]
|
props.form?.touched[field.name] && props.form?.errors[field.name]
|
||||||
@ -67,7 +59,7 @@ export default function Input(props: Partial<InputProps>): ReactElement {
|
|||||||
className={styleClasses}
|
className={styleClasses}
|
||||||
data-is-submitting={props.form?.isSubmitting ? true : null}
|
data-is-submitting={props.form?.isSubmitting ? true : null}
|
||||||
>
|
>
|
||||||
<Label htmlFor={name} required={required}>
|
<Label htmlFor={props.name} required={props.required}>
|
||||||
{label}
|
{label}
|
||||||
</Label>
|
</Label>
|
||||||
<InputElement size={size} {...field} {...props} />
|
<InputElement size={size} {...field} {...props} />
|
||||||
|
@ -5,6 +5,14 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.markdown h1,
|
||||||
|
.markdown h2,
|
||||||
|
.markdown h3,
|
||||||
|
.markdown h4,
|
||||||
|
.markdown h5 {
|
||||||
|
margin-bottom: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
.markdown h1 {
|
.markdown h1 {
|
||||||
font-size: var(--font-size-h3);
|
font-size: var(--font-size-h3);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,9 @@ export default function PublisherLinks({
|
|||||||
? `https://twitter.com/${link.value}`
|
? `https://twitter.com/${link.value}`
|
||||||
: link.name === 'GitHub'
|
: link.name === 'GitHub'
|
||||||
? `https://github.com/${link.value}`
|
? `https://github.com/${link.value}`
|
||||||
: link.value
|
: link.value.includes('http') // safeguard against urls without protocol defined
|
||||||
|
? link.value
|
||||||
|
: `//${link.value}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href={href} key={link.name} target="_blank" rel="noreferrer">
|
<a href={href} key={link.name} target="_blank" rel="noreferrer">
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import { format, formatDistance } from 'date-fns'
|
import { format, formatDistance } from 'date-fns'
|
||||||
import { setDate } from 'date-fns/esm'
|
|
||||||
|
|
||||||
export default function Time({
|
export default function Time({
|
||||||
date,
|
date,
|
||||||
@ -15,24 +14,26 @@ export default function Time({
|
|||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const [dateIso, setDateIso] = useState<string>()
|
const [dateIso, setDateIso] = useState<string>()
|
||||||
const [dateNew, setDateNew] = useState<Date>()
|
const [dateNew, setDateNew] = useState<Date>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!date) return
|
if (!date) return
|
||||||
|
|
||||||
const dateNew = isUnix ? new Date(Number(date) * 1000) : new Date(date)
|
const dateNew = isUnix ? new Date(Number(date) * 1000) : new Date(date)
|
||||||
setDateIso(dateNew.toISOString())
|
setDateIso(dateNew.toISOString())
|
||||||
setDateNew(dateNew)
|
setDateNew(dateNew)
|
||||||
}, [date])
|
}, [date, isUnix])
|
||||||
|
|
||||||
return !dateIso || !dateNew ? (
|
return !dateIso || !dateNew ? (
|
||||||
<></>
|
<></>
|
||||||
) : (
|
) : (
|
||||||
<time
|
<time
|
||||||
title={relative ? format(dateNew, 'MMMM d, yyyy') : undefined}
|
title={format(dateNew, 'PPppp')}
|
||||||
dateTime={dateIso}
|
dateTime={dateIso}
|
||||||
className={className || undefined}
|
className={className || undefined}
|
||||||
>
|
>
|
||||||
{relative
|
{relative
|
||||||
? formatDistance(dateNew, Date.now(), { addSuffix: true })
|
? formatDistance(dateNew, Date.now(), { addSuffix: true })
|
||||||
: format(dateNew, 'MMMM d, yyyy')}
|
: format(dateNew, 'PP')}
|
||||||
</time>
|
</time>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement, useEffect } from 'react'
|
||||||
import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interfaces/File'
|
import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interfaces/File'
|
||||||
import { prettySize } from '../../../../utils'
|
import { prettySize } from '../../../../utils'
|
||||||
import cleanupContentType from '../../../../utils/cleanupContentType'
|
import cleanupContentType from '../../../../utils/cleanupContentType'
|
||||||
import styles from './Info.module.css'
|
import styles from './Info.module.css'
|
||||||
|
import { useField, useFormikContext } from 'formik'
|
||||||
|
|
||||||
export default function FileInfo({
|
export default function FileInfo({
|
||||||
file,
|
name,
|
||||||
removeItem
|
file
|
||||||
}: {
|
}: {
|
||||||
|
name: string
|
||||||
file: FileMetadata
|
file: FileMetadata
|
||||||
removeItem?(): void
|
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
|
const { validateField } = useFormikContext()
|
||||||
|
const [field, meta, helpers] = useField(name)
|
||||||
|
|
||||||
|
// On mount, validate the field manually
|
||||||
|
useEffect(() => {
|
||||||
|
validateField(name)
|
||||||
|
}, [name, validateField])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.info}>
|
<div className={styles.info}>
|
||||||
<h3 className={styles.url}>{file.url}</h3>
|
<h3 className={styles.url}>{file.url}</h3>
|
||||||
@ -19,11 +28,12 @@ export default function FileInfo({
|
|||||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||||
{file.contentType && <li>{cleanupContentType(file.contentType)}</li>}
|
{file.contentType && <li>{cleanupContentType(file.contentType)}</li>}
|
||||||
</ul>
|
</ul>
|
||||||
{removeItem && (
|
<button
|
||||||
<button className={styles.removeButton} onClick={() => removeItem()}>
|
className={styles.removeButton}
|
||||||
|
onClick={() => helpers.setValue(undefined)}
|
||||||
|
>
|
||||||
×
|
×
|
||||||
</button>
|
</button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,10 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
async function handleButtonClick(e: React.SyntheticEvent, url: string) {
|
async function handleButtonClick(e: React.SyntheticEvent, url: string) {
|
||||||
|
// hack so the onBlur-triggered validation does not show,
|
||||||
|
// like when this field is required
|
||||||
|
helpers.setTouched(false)
|
||||||
|
|
||||||
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
// File example 'https://oceanprotocol.com/tech-whitepaper.pdf'
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
@ -26,14 +30,10 @@ export default function FilesInput(props: InputProps): ReactElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeItem() {
|
|
||||||
helpers.setValue(undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{field?.value && field.value[0] && typeof field.value === 'object' ? (
|
{field?.value && field.value[0] && typeof field.value === 'object' ? (
|
||||||
<FileInfo file={field.value[0]} removeItem={removeItem} />
|
<FileInfo name={props.name} file={field.value[0]} />
|
||||||
) : (
|
) : (
|
||||||
<FileInput
|
<FileInput
|
||||||
{...props}
|
{...props}
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.box {
|
.box {
|
||||||
composes: box from '../../atoms/Box.module.css';
|
composes: box from '../atoms/Box.module.css';
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
78
src/components/molecules/MetadataFeedback.tsx
Normal file
78
src/components/molecules/MetadataFeedback.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import Alert from '../atoms/Alert'
|
||||||
|
import Button from '../atoms/Button'
|
||||||
|
import Loader from '../atoms/Loader'
|
||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import styles from './MetadataFeedback.module.css'
|
||||||
|
import SuccessConfetti from '../atoms/SuccessConfetti'
|
||||||
|
|
||||||
|
interface Action {
|
||||||
|
name: string
|
||||||
|
onClick?: () => void
|
||||||
|
to?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function ActionSuccess({ action }: { action: Action }) {
|
||||||
|
const { name, onClick, to } = action
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
style="primary"
|
||||||
|
size="small"
|
||||||
|
onClick={onClick || null}
|
||||||
|
to={to || null}
|
||||||
|
className={styles.action}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ActionError({ setError }: { setError: (error: string) => void }) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
style="primary"
|
||||||
|
size="small"
|
||||||
|
className={styles.action}
|
||||||
|
onClick={() => setError(undefined)}
|
||||||
|
>
|
||||||
|
Try Again
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MetadataFeedback({
|
||||||
|
title,
|
||||||
|
error,
|
||||||
|
success,
|
||||||
|
loading,
|
||||||
|
successAction,
|
||||||
|
setError
|
||||||
|
}: {
|
||||||
|
title: string
|
||||||
|
error: string
|
||||||
|
success: string
|
||||||
|
loading?: string
|
||||||
|
successAction: Action
|
||||||
|
setError: (error: string) => void
|
||||||
|
}): ReactElement {
|
||||||
|
return (
|
||||||
|
<div className={styles.feedback}>
|
||||||
|
<div className={styles.box}>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
{error ? (
|
||||||
|
<>
|
||||||
|
<Alert text={error} state="error" />
|
||||||
|
<ActionError setError={setError} />
|
||||||
|
</>
|
||||||
|
) : success ? (
|
||||||
|
<SuccessConfetti
|
||||||
|
success={success}
|
||||||
|
action={<ActionSuccess action={successAction} />}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Loader message={loading} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -24,11 +24,10 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.author {
|
.datatoken {
|
||||||
margin-top: calc(var(--spacer) / 8);
|
margin-top: calc(var(--spacer) / 8);
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
color: var(--color-secondary);
|
color: var(--color-secondary);
|
||||||
font-weight: var(--font-weight-bold);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview [class*='MetaItem-module--metaItem'] h3 {
|
.preview [class*='MetaItem-module--metaItem'] h3 {
|
121
src/components/molecules/MetadataPreview.tsx
Normal file
121
src/components/molecules/MetadataPreview.tsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import React, { FormEvent, ReactElement, useState } from 'react'
|
||||||
|
import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interfaces/File'
|
||||||
|
import Markdown from '../atoms/Markdown'
|
||||||
|
import Tags from '../atoms/Tags'
|
||||||
|
import MetaItem from '../organisms/AssetContent/MetaItem'
|
||||||
|
import styles from './MetadataPreview.module.css'
|
||||||
|
import File from '../atoms/File'
|
||||||
|
import { MetadataPublishForm } from '../../@types/MetaData'
|
||||||
|
import Button from '../atoms/Button'
|
||||||
|
import { transformTags } from '../../utils/metadata'
|
||||||
|
|
||||||
|
function Description({ description }: { description: string }) {
|
||||||
|
const [fullDescription, setFullDescription] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const textLimit = 500 // string.length
|
||||||
|
const descriptionDisplay =
|
||||||
|
fullDescription === true
|
||||||
|
? description
|
||||||
|
: `${description.substring(0, textLimit)}${
|
||||||
|
description.length > textLimit ? `...` : ''
|
||||||
|
}`
|
||||||
|
|
||||||
|
function handleDescriptionToggle(e: FormEvent<HTMLButtonElement>) {
|
||||||
|
e.preventDefault()
|
||||||
|
setFullDescription(!fullDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.description}>
|
||||||
|
<Markdown text={descriptionDisplay} />
|
||||||
|
{description.length > textLimit && (
|
||||||
|
<Button
|
||||||
|
style="text"
|
||||||
|
size="small"
|
||||||
|
onClick={handleDescriptionToggle}
|
||||||
|
className={styles.toggle}
|
||||||
|
>
|
||||||
|
{fullDescription === true ? 'Close' : 'Expand'}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function MetaFull({ values }: { values: Partial<MetadataPublishForm> }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.metaFull}>
|
||||||
|
{Object.entries(values)
|
||||||
|
.filter(
|
||||||
|
([key, value]) =>
|
||||||
|
!(
|
||||||
|
key.includes('name') ||
|
||||||
|
key.includes('description') ||
|
||||||
|
key.includes('tags') ||
|
||||||
|
key.includes('files') ||
|
||||||
|
key.includes('links') ||
|
||||||
|
key.includes('termsAndConditions') ||
|
||||||
|
key.includes('dataTokenOptions') ||
|
||||||
|
value === undefined ||
|
||||||
|
value === ''
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map(([key, value]) => (
|
||||||
|
<MetaItem key={key} title={key} content={value} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Sample({ url }: { url: string }) {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
download
|
||||||
|
style="text"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
Download Sample
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function MetadataPreview({
|
||||||
|
values
|
||||||
|
}: {
|
||||||
|
values: Partial<MetadataPublishForm>
|
||||||
|
}): ReactElement {
|
||||||
|
return (
|
||||||
|
<div className={styles.preview}>
|
||||||
|
<h2 className={styles.previewTitle}>Preview</h2>
|
||||||
|
<header>
|
||||||
|
{values.name && <h3 className={styles.title}>{values.name}</h3>}
|
||||||
|
{values.dataTokenOptions?.name && (
|
||||||
|
<p
|
||||||
|
className={styles.datatoken}
|
||||||
|
>{`${values.dataTokenOptions.name} — ${values.dataTokenOptions.symbol}`}</p>
|
||||||
|
)}
|
||||||
|
{values.description && <Description description={values.description} />}
|
||||||
|
|
||||||
|
<div className={styles.asset}>
|
||||||
|
{values.files?.length > 0 && typeof values.files !== 'string' && (
|
||||||
|
<File
|
||||||
|
file={values.files[0] as FileMetadata}
|
||||||
|
className={styles.file}
|
||||||
|
small
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{typeof values.links !== 'string' && values.links?.length && (
|
||||||
|
<Sample url={(values.links[0] as FileMetadata).url} />
|
||||||
|
)}
|
||||||
|
{values.tags && <Tags items={transformTags(values.tags)} />}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<MetaFull values={values} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -13,7 +13,6 @@ import {
|
|||||||
usePricing
|
usePricing
|
||||||
} from '@oceanprotocol/react'
|
} from '@oceanprotocol/react'
|
||||||
import styles from './Compute.module.css'
|
import styles from './Compute.module.css'
|
||||||
import Button from '../../atoms/Button'
|
|
||||||
import Input from '../../atoms/Input'
|
import Input from '../../atoms/Input'
|
||||||
import Alert from '../../atoms/Alert'
|
import Alert from '../../atoms/Alert'
|
||||||
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
|
||||||
|
@ -28,19 +28,17 @@ export default function Consume({
|
|||||||
const [hasPreviousOrder, setHasPreviousOrder] = useState(false)
|
const [hasPreviousOrder, setHasPreviousOrder] = useState(false)
|
||||||
const [previousOrderId, setPreviousOrderId] = useState<string>()
|
const [previousOrderId, setPreviousOrderId] = useState<string>()
|
||||||
const { isInPurgatory, price } = useAsset()
|
const { isInPurgatory, price } = useAsset()
|
||||||
const {
|
const { buyDT, pricingStepText, pricingError, pricingIsLoading } = usePricing(
|
||||||
dtSymbol,
|
ddo
|
||||||
buyDT,
|
)
|
||||||
pricingStepText,
|
|
||||||
pricingError,
|
|
||||||
pricingIsLoading
|
|
||||||
} = usePricing(ddo)
|
|
||||||
const { consumeStepText, consume, consumeError } = useConsume()
|
const { consumeStepText, consume, consumeError } = useConsume()
|
||||||
const [isDisabled, setIsDisabled] = useState(true)
|
const [isDisabled, setIsDisabled] = useState(true)
|
||||||
const [hasDatatoken, setHasDatatoken] = useState(false)
|
const [hasDatatoken, setHasDatatoken] = useState(false)
|
||||||
const [isConsumable, setIsConsumable] = useState(true)
|
const [isConsumable, setIsConsumable] = useState(true)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!price) return
|
||||||
|
|
||||||
setIsConsumable(
|
setIsConsumable(
|
||||||
price.isConsumable !== undefined ? price.isConsumable === 'true' : true
|
price.isConsumable !== undefined ? price.isConsumable === 'true' : true
|
||||||
)
|
)
|
||||||
@ -110,14 +108,14 @@ export default function Consume({
|
|||||||
</Button>
|
</Button>
|
||||||
{hasDatatoken && (
|
{hasDatatoken && (
|
||||||
<div className={styles.help}>
|
<div className={styles.help}>
|
||||||
You own {dtBalance} {dtSymbol} allowing you to use this data set
|
You own {dtBalance} {ddo.dataTokenInfo.symbol} allowing you to use
|
||||||
without paying again.
|
this data set without paying again.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{(!hasDatatoken || !hasPreviousOrder) && (
|
{(!hasDatatoken || !hasPreviousOrder) && (
|
||||||
<div className={styles.help}>
|
<div className={styles.help}>
|
||||||
For using this data set, you will buy 1 {dtSymbol} and immediately
|
For using this data set, you will buy 1 {ddo.dataTokenInfo.symbol}{' '}
|
||||||
spend it back to the publisher and pool.
|
and immediately spend it back to the publisher and pool.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
31
src/components/organisms/AssetActions/Edit/Debug.tsx
Normal file
31
src/components/organisms/AssetActions/Edit/Debug.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { DDO } from '@oceanprotocol/lib'
|
||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import { MetadataPublishForm } from '../../../../@types/MetaData'
|
||||||
|
import { transformPublishFormToMetadata } from '../../../../utils/metadata'
|
||||||
|
import DebugOutput from '../../../atoms/DebugOutput'
|
||||||
|
|
||||||
|
export default function Debug({
|
||||||
|
values,
|
||||||
|
ddo
|
||||||
|
}: {
|
||||||
|
values: Partial<MetadataPublishForm>
|
||||||
|
ddo: DDO
|
||||||
|
}): ReactElement {
|
||||||
|
const newDdo = {
|
||||||
|
'@context': 'https://w3id.org/did/v1',
|
||||||
|
service: [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
type: 'metadata',
|
||||||
|
attributes: { ...transformPublishFormToMetadata(values, ddo) }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DebugOutput title="Collected Form Values" output={values} />
|
||||||
|
<DebugOutput title="Transformed DDO Values" output={newDdo} />
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
.form {
|
||||||
|
composes: box from '../../../atoms/Box.module.css';
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin-left: -2rem;
|
||||||
|
margin-right: -2rem;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
padding: calc(var(--spacer) / 2) var(--spacer) 0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 40rem) {
|
||||||
|
.actions {
|
||||||
|
padding-top: var(--spacer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions a,
|
||||||
|
.actions button {
|
||||||
|
margin-left: calc(var(--spacer) / 2);
|
||||||
|
margin-right: calc(var(--spacer) / 2);
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
import React, { ChangeEvent, ReactElement } from 'react'
|
||||||
|
import styles from './FormEditMetadata.module.css'
|
||||||
|
import { Field, Form, FormikContextType, useFormikContext } from 'formik'
|
||||||
|
import Button from '../../../atoms/Button'
|
||||||
|
import Input from '../../../atoms/Input'
|
||||||
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
|
import { FormFieldProps } from '../../../../@types/Form'
|
||||||
|
import { MetadataPublishForm } from '../../../../@types/MetaData'
|
||||||
|
|
||||||
|
export default function FormEditMetadata({
|
||||||
|
data,
|
||||||
|
setShowEdit
|
||||||
|
}: {
|
||||||
|
data: FormFieldProps[]
|
||||||
|
setShowEdit: (show: boolean) => void
|
||||||
|
}): ReactElement {
|
||||||
|
const { ocean, accountId } = useOcean()
|
||||||
|
const {
|
||||||
|
isValid,
|
||||||
|
validateField,
|
||||||
|
setFieldValue
|
||||||
|
}: FormikContextType<Partial<MetadataPublishForm>> = useFormikContext()
|
||||||
|
|
||||||
|
// Manually handle change events instead of using `handleChange` from Formik.
|
||||||
|
// Workaround for default `validateOnChange` not kicking in
|
||||||
|
function handleFieldChange(
|
||||||
|
e: ChangeEvent<HTMLInputElement>,
|
||||||
|
field: FormFieldProps
|
||||||
|
) {
|
||||||
|
validateField(field.name)
|
||||||
|
setFieldValue(field.name, e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form className={styles.form}>
|
||||||
|
{data.map((field: FormFieldProps) => (
|
||||||
|
<Field
|
||||||
|
key={field.name}
|
||||||
|
{...field}
|
||||||
|
component={Input}
|
||||||
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
handleFieldChange(e, field)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<footer className={styles.actions}>
|
||||||
|
<Button style="primary" disabled={!ocean || !accountId || !isValid}>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
<Button style="text" onClick={() => setShowEdit(false)}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</footer>
|
||||||
|
</Form>
|
||||||
|
)
|
||||||
|
}
|
10
src/components/organisms/AssetActions/Edit/index.module.css
Normal file
10
src/components/organisms/AssetActions/Edit/index.module.css
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
.grid {
|
||||||
|
composes: grid from '../../AssetContent/index.module.css';
|
||||||
|
margin-top: var(--spacer);
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: var(--font-size-large);
|
||||||
|
margin-top: -1.5rem;
|
||||||
|
max-width: 50rem;
|
||||||
|
}
|
139
src/components/organisms/AssetActions/Edit/index.tsx
Normal file
139
src/components/organisms/AssetActions/Edit/index.tsx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
|
import { Formik } from 'formik'
|
||||||
|
import React, { ReactElement, useState } from 'react'
|
||||||
|
import { MetadataPublishForm } from '../../../../@types/MetaData'
|
||||||
|
import {
|
||||||
|
validationSchema,
|
||||||
|
getInitialValues
|
||||||
|
} from '../../../../models/FormEditMetadata'
|
||||||
|
import { useAsset } from '../../../../providers/Asset'
|
||||||
|
import { useUserPreferences } from '../../../../providers/UserPreferences'
|
||||||
|
import MetadataPreview from '../../../molecules/MetadataPreview'
|
||||||
|
import Debug from './Debug'
|
||||||
|
import Web3Feedback from '../../../molecules/Wallet/Feedback'
|
||||||
|
import FormEditMetadata from './FormEditMetadata'
|
||||||
|
import styles from './index.module.css'
|
||||||
|
import { Logger } from '@oceanprotocol/lib'
|
||||||
|
import MetadataFeedback from '../../../molecules/MetadataFeedback'
|
||||||
|
import { graphql, useStaticQuery } from 'gatsby'
|
||||||
|
|
||||||
|
const contentQuery = graphql`
|
||||||
|
query EditMetadataQuery {
|
||||||
|
content: allFile(filter: { relativePath: { eq: "pages/edit.json" } }) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
childPagesJson {
|
||||||
|
description
|
||||||
|
form {
|
||||||
|
success
|
||||||
|
successAction
|
||||||
|
error
|
||||||
|
data {
|
||||||
|
name
|
||||||
|
placeholder
|
||||||
|
label
|
||||||
|
help
|
||||||
|
type
|
||||||
|
required
|
||||||
|
options
|
||||||
|
rows
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
export default function Edit({
|
||||||
|
setShowEdit
|
||||||
|
}: {
|
||||||
|
setShowEdit: (show: boolean) => void
|
||||||
|
}): ReactElement {
|
||||||
|
const data = useStaticQuery(contentQuery)
|
||||||
|
const content = data.content.edges[0].node.childPagesJson
|
||||||
|
|
||||||
|
const { debug } = useUserPreferences()
|
||||||
|
const { ocean, account } = useOcean()
|
||||||
|
const { did, metadata, ddo, refreshDdo } = useAsset()
|
||||||
|
const [success, setSuccess] = useState<string>()
|
||||||
|
const [error, setError] = useState<string>()
|
||||||
|
|
||||||
|
const hasFeedback = error || success
|
||||||
|
|
||||||
|
async function handleSubmit(
|
||||||
|
values: Partial<MetadataPublishForm>,
|
||||||
|
resetForm: () => void
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const ddo = await ocean.assets.editMetadata(
|
||||||
|
did,
|
||||||
|
{ title: values.name, description: values.description },
|
||||||
|
account
|
||||||
|
)
|
||||||
|
|
||||||
|
// Edit failed
|
||||||
|
if (!ddo) {
|
||||||
|
setError(content.form.error)
|
||||||
|
Logger.error(content.form.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit succeeded
|
||||||
|
setSuccess(content.form.success)
|
||||||
|
resetForm()
|
||||||
|
} catch (error) {
|
||||||
|
Logger.error(error.message)
|
||||||
|
setError(error.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Formik
|
||||||
|
initialValues={getInitialValues(metadata)}
|
||||||
|
validationSchema={validationSchema}
|
||||||
|
onSubmit={async (values, { resetForm }) => {
|
||||||
|
// move user's focus to top of screen
|
||||||
|
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
|
||||||
|
// kick off editing
|
||||||
|
await handleSubmit(values, resetForm)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{({ isSubmitting, values }) =>
|
||||||
|
isSubmitting || hasFeedback ? (
|
||||||
|
<MetadataFeedback
|
||||||
|
title="Updating Data Set"
|
||||||
|
error={error}
|
||||||
|
success={success}
|
||||||
|
setError={setError}
|
||||||
|
successAction={{
|
||||||
|
name: content.form.successAction,
|
||||||
|
onClick: async () => {
|
||||||
|
await refreshDdo()
|
||||||
|
setShowEdit(false)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<p className={styles.description}>{content.description}</p>
|
||||||
|
<article className={styles.grid}>
|
||||||
|
<FormEditMetadata
|
||||||
|
data={content.form.data}
|
||||||
|
setShowEdit={setShowEdit}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<MetadataPreview values={values} />
|
||||||
|
<Web3Feedback />
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
{debug === true && <Debug values={values} ddo={ddo} />}
|
||||||
|
</article>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Formik>
|
||||||
|
)
|
||||||
|
}
|
@ -12,6 +12,7 @@ import Alert from '../../../../atoms/Alert'
|
|||||||
import TokenBalance from '../../../../../@types/TokenBalance'
|
import TokenBalance from '../../../../../@types/TokenBalance'
|
||||||
import { useUserPreferences } from '../../../../../providers/UserPreferences'
|
import { useUserPreferences } from '../../../../../providers/UserPreferences'
|
||||||
import Output from './Output'
|
import Output from './Output'
|
||||||
|
import DebugOutput from '../../../../atoms/DebugOutput'
|
||||||
|
|
||||||
const contentQuery = graphql`
|
const contentQuery = graphql`
|
||||||
query PoolAddQuery {
|
query PoolAddQuery {
|
||||||
@ -201,11 +202,7 @@ export default function Add({
|
|||||||
action={submitForm}
|
action={submitForm}
|
||||||
txId={txId}
|
txId={txId}
|
||||||
/>
|
/>
|
||||||
{debug && (
|
{debug && <DebugOutput title="Collected values" output={values} />}
|
||||||
<pre>
|
|
||||||
<code>{JSON.stringify(values, null, 2)}</code>
|
|
||||||
</pre>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import { useOcean, useMetadata, usePricing } from '@oceanprotocol/react'
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
import { DDO, Logger } from '@oceanprotocol/lib'
|
import { Logger } from '@oceanprotocol/lib'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import stylesActions from './Actions.module.css'
|
import stylesActions from './Actions.module.css'
|
||||||
import PriceUnit from '../../../atoms/Price/PriceUnit'
|
import PriceUnit from '../../../atoms/Price/PriceUnit'
|
||||||
@ -37,15 +37,20 @@ const contentQuery = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
|
export default function Pool(): ReactElement {
|
||||||
const data = useStaticQuery(contentQuery)
|
const data = useStaticQuery(contentQuery)
|
||||||
const content = data.content.edges[0].node.childContentJson.pool
|
const content = data.content.edges[0].node.childContentJson.pool
|
||||||
|
|
||||||
const { ocean, accountId, networkId, config } = useOcean()
|
const { ocean, accountId, networkId, config } = useOcean()
|
||||||
const { owner } = useMetadata(ddo)
|
const {
|
||||||
|
isInPurgatory,
|
||||||
const { dtSymbol } = usePricing(ddo)
|
ddo,
|
||||||
const { isInPurgatory, price, refreshInterval, refreshPrice } = useAsset()
|
owner,
|
||||||
|
price,
|
||||||
|
refreshInterval,
|
||||||
|
refreshPrice
|
||||||
|
} = useAsset()
|
||||||
|
const dtSymbol = ddo?.dataTokenInfo.symbol
|
||||||
|
|
||||||
const [poolTokens, setPoolTokens] = useState<string>()
|
const [poolTokens, setPoolTokens] = useState<string>()
|
||||||
const [totalPoolTokens, setTotalPoolTokens] = useState<string>()
|
const [totalPoolTokens, setTotalPoolTokens] = useState<string>()
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import { useOcean } from '@oceanprotocol/react'
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
import { DDO } from '@oceanprotocol/lib'
|
|
||||||
import FormTrade from './FormTrade'
|
import FormTrade from './FormTrade'
|
||||||
import TokenBalance from '../../../../@types/TokenBalance'
|
import TokenBalance from '../../../../@types/TokenBalance'
|
||||||
import { useAsset } from '../../../../providers/Asset'
|
import { useAsset } from '../../../../providers/Asset'
|
||||||
|
|
||||||
export default function Trade({ ddo }: { ddo: DDO }): ReactElement {
|
export default function Trade(): ReactElement {
|
||||||
const { ocean, balance, accountId } = useOcean()
|
const { ocean, balance, accountId } = useOcean()
|
||||||
const [tokenBalance, setTokenBalance] = useState<TokenBalance>()
|
const [tokenBalance, setTokenBalance] = useState<TokenBalance>()
|
||||||
const { price } = useAsset()
|
const { price, ddo } = useAsset()
|
||||||
const [maxDt, setMaxDt] = useState(0)
|
const [maxDt, setMaxDt] = useState(0)
|
||||||
const [maxOcean, setMaxOcean] = useState(0)
|
const [maxOcean, setMaxOcean] = useState(0)
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import React, { ReactElement, useState, useEffect } from 'react'
|
|||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import Compute from './Compute'
|
import Compute from './Compute'
|
||||||
import Consume from './Consume'
|
import Consume from './Consume'
|
||||||
import { DDO, Logger } from '@oceanprotocol/lib'
|
import { Logger } from '@oceanprotocol/lib'
|
||||||
import Tabs from '../../atoms/Tabs'
|
import Tabs from '../../atoms/Tabs'
|
||||||
import { useOcean } from '@oceanprotocol/react'
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
import compareAsBN from '../../../utils/compareAsBN'
|
import compareAsBN from '../../../utils/compareAsBN'
|
||||||
@ -10,14 +10,13 @@ import Pool from './Pool'
|
|||||||
import Trade from './Trade'
|
import Trade from './Trade'
|
||||||
import { useAsset } from '../../../providers/Asset'
|
import { useAsset } from '../../../providers/Asset'
|
||||||
|
|
||||||
export default function AssetActions({ ddo }: { ddo: DDO }): ReactElement {
|
export default function AssetActions(): ReactElement {
|
||||||
const { ocean, balance, accountId } = useOcean()
|
const { ocean, balance, accountId } = useOcean()
|
||||||
const { price } = useAsset()
|
const { price, ddo, metadata } = useAsset()
|
||||||
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
|
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
|
||||||
const [dtBalance, setDtBalance] = useState<string>()
|
const [dtBalance, setDtBalance] = useState<string>()
|
||||||
|
|
||||||
const isCompute = Boolean(ddo.findServiceByType('compute'))
|
const isCompute = Boolean(ddo?.findServiceByType('compute'))
|
||||||
const { attributes } = ddo.findServiceByType('metadata')
|
|
||||||
|
|
||||||
// Get and set user DT balance
|
// Get and set user DT balance
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -60,7 +59,7 @@ export default function AssetActions({ ddo }: { ddo: DDO }): ReactElement {
|
|||||||
ddo={ddo}
|
ddo={ddo}
|
||||||
dtBalance={dtBalance}
|
dtBalance={dtBalance}
|
||||||
isBalanceSufficient={isBalanceSufficient}
|
isBalanceSufficient={isBalanceSufficient}
|
||||||
file={attributes.main.files[0]}
|
file={metadata?.main.files[0]}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -72,17 +71,17 @@ export default function AssetActions({ ddo }: { ddo: DDO }): ReactElement {
|
|||||||
]
|
]
|
||||||
|
|
||||||
// Check from metadata, cause that is available earlier
|
// Check from metadata, cause that is available earlier
|
||||||
const hasPool = ddo.price?.type === 'pool'
|
const hasPool = ddo?.price?.type === 'pool'
|
||||||
|
|
||||||
hasPool &&
|
hasPool &&
|
||||||
tabs.push(
|
tabs.push(
|
||||||
{
|
{
|
||||||
title: 'Pool',
|
title: 'Pool',
|
||||||
content: <Pool ddo={ddo} />
|
content: <Pool />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Trade',
|
title: 'Trade',
|
||||||
content: <Trade ddo={ddo} />
|
content: <Trade />
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
41
src/components/organisms/AssetContent/EditHistory.module.css
Normal file
41
src/components/organisms/AssetContent/EditHistory.module.css
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
.title {
|
||||||
|
composes: title from './MetaItem.module.css';
|
||||||
|
margin-top: var(--spacer);
|
||||||
|
margin-bottom: calc(var(--spacer) / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.history {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
padding-left: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: calc(var(--spacer) / 4);
|
||||||
|
color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item::before {
|
||||||
|
content: '▪';
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
left: -1.25rem;
|
||||||
|
color: var(--color-secondary);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -1rem;
|
||||||
|
top: 62%;
|
||||||
|
display: block;
|
||||||
|
width: 1px;
|
||||||
|
height: 110%;
|
||||||
|
background-color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.item:last-child::after {
|
||||||
|
display: none;
|
||||||
|
}
|
59
src/components/organisms/AssetContent/EditHistory.tsx
Normal file
59
src/components/organisms/AssetContent/EditHistory.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
|
import { useAsset } from '../../../providers/Asset'
|
||||||
|
import EtherscanLink from '../../atoms/EtherscanLink'
|
||||||
|
import Time from '../../atoms/Time'
|
||||||
|
import styles from './EditHistory.module.css'
|
||||||
|
|
||||||
|
interface Receipt {
|
||||||
|
hash: string
|
||||||
|
timestamp: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fetch for real
|
||||||
|
const fakeReceipts = [
|
||||||
|
{
|
||||||
|
hash: '0xxxxxxxxx',
|
||||||
|
timestamp: '1607460269'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hash: '0xxxxxxxxx',
|
||||||
|
timestamp: '1606460159'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hash: '0xxxxxxxxx',
|
||||||
|
timestamp: '1506460159'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export default function EditHistory(): ReactElement {
|
||||||
|
const { networkId } = useOcean()
|
||||||
|
const { ddo } = useAsset()
|
||||||
|
|
||||||
|
const [receipts, setReceipts] = useState<Receipt[]>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setReceipts(fakeReceipts)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<h3 className={styles.title}>Metadata History</h3>
|
||||||
|
<ul className={styles.history}>
|
||||||
|
{receipts?.map((receipt) => (
|
||||||
|
<li key={receipt.hash} className={styles.item}>
|
||||||
|
<EtherscanLink networkId={networkId} path={`/tx/${receipt.hash}`}>
|
||||||
|
edited <Time date={receipt.timestamp} relative isUnix />
|
||||||
|
</EtherscanLink>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
<li className={styles.item}>
|
||||||
|
{/* TODO: get this initial metadata creation tx somehow */}
|
||||||
|
<EtherscanLink networkId={networkId} path="/tx/xxx">
|
||||||
|
published <Time date={ddo.created} relative />
|
||||||
|
</EtherscanLink>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
.metaFull {
|
.metaFull {
|
||||||
margin-top: var(--spacer);
|
margin-top: var(--spacer);
|
||||||
font-size: var(--font-size-small);
|
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: var(--spacer);
|
gap: var(--spacer);
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
@ -14,3 +13,8 @@
|
|||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* debug output */
|
||||||
|
.metaFull + div {
|
||||||
|
margin-top: var(--spacer);
|
||||||
|
}
|
||||||
|
@ -2,21 +2,11 @@ import React, { ReactElement } from 'react'
|
|||||||
import Time from '../../atoms/Time'
|
import Time from '../../atoms/Time'
|
||||||
import MetaItem from './MetaItem'
|
import MetaItem from './MetaItem'
|
||||||
import styles from './MetaFull.module.css'
|
import styles from './MetaFull.module.css'
|
||||||
import { MetadataMarket } from '../../../@types/MetaData'
|
|
||||||
import { DDO } from '@oceanprotocol/lib'
|
|
||||||
import Publisher from '../../atoms/Publisher'
|
import Publisher from '../../atoms/Publisher'
|
||||||
|
import { useAsset } from '../../../providers/Asset'
|
||||||
|
|
||||||
export default function MetaFull({
|
export default function MetaFull(): ReactElement {
|
||||||
ddo,
|
const { ddo, metadata, isInPurgatory } = useAsset()
|
||||||
metadata,
|
|
||||||
isInPurgatory
|
|
||||||
}: {
|
|
||||||
ddo: DDO
|
|
||||||
metadata: MetadataMarket
|
|
||||||
isInPurgatory: boolean
|
|
||||||
}): ReactElement {
|
|
||||||
const { id, publicKey } = ddo
|
|
||||||
const { dateCreated, datePublished } = metadata.main
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.metaFull}>
|
<div className={styles.metaFull}>
|
||||||
@ -25,19 +15,19 @@ export default function MetaFull({
|
|||||||
)}
|
)}
|
||||||
<MetaItem
|
<MetaItem
|
||||||
title="Owner"
|
title="Owner"
|
||||||
content={<Publisher account={publicKey[0].owner} />}
|
content={<Publisher account={ddo?.publicKey[0].owner} />}
|
||||||
/>
|
/>
|
||||||
|
{/* <MetaItem
|
||||||
|
title="Data Created"
|
||||||
|
content={<Time date={metadata?.main.dateCreated} />}
|
||||||
|
/> */}
|
||||||
|
|
||||||
{metadata?.additionalInformation?.categories && (
|
{/* TODO: remove those 2 date items here when EditHistory component is ready */}
|
||||||
<MetaItem
|
<MetaItem title="Published" content={<Time date={ddo?.created} />} />
|
||||||
title="Category"
|
{ddo?.created !== ddo?.updated && (
|
||||||
content={metadata?.additionalInformation?.categories[0]}
|
<MetaItem title="Updated" content={<Time date={ddo?.updated} />} />
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
|
<MetaItem title="DID" content={<code>{ddo?.id}</code>} />
|
||||||
<MetaItem title="Data Created" content={<Time date={dateCreated} />} />
|
|
||||||
<MetaItem title="Published" content={<Time date={datePublished} />} />
|
|
||||||
<MetaItem title="DID" content={<code>{id}</code>} />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
font-family: var(--font-family-base);
|
||||||
|
font-weight: var(--font-weight-base);
|
||||||
font-size: var(--font-size-small);
|
font-size: var(--font-size-small);
|
||||||
margin-bottom: calc(var(--spacer) / 4);
|
margin-bottom: calc(var(--spacer) / 4);
|
||||||
|
color: var(--color-secondary);
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
13
src/components/organisms/AssetContent/MetaMain.module.css
Normal file
13
src/components/organisms/AssetContent/MetaMain.module.css
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.meta {
|
||||||
|
margin-bottom: calc(var(--spacer) / 1.5);
|
||||||
|
color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.meta p {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
margin-top: calc(var(--spacer) / 2);
|
||||||
|
}
|
34
src/components/organisms/AssetContent/MetaMain.tsx
Normal file
34
src/components/organisms/AssetContent/MetaMain.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
|
import React, { ReactElement } from 'react'
|
||||||
|
import { useAsset } from '../../../providers/Asset'
|
||||||
|
import EtherscanLink from '../../atoms/EtherscanLink'
|
||||||
|
import Publisher from '../../atoms/Publisher'
|
||||||
|
import Time from '../../atoms/Time'
|
||||||
|
import styles from './MetaMain.module.css'
|
||||||
|
|
||||||
|
export default function MetaMain(): ReactElement {
|
||||||
|
const { ddo, owner } = useAsset()
|
||||||
|
const { networkId } = useOcean()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className={styles.meta}>
|
||||||
|
<p>
|
||||||
|
<EtherscanLink networkId={networkId} path={`token/${ddo?.dataToken}`}>
|
||||||
|
{`${ddo?.dataTokenInfo.name} — ${ddo?.dataTokenInfo.symbol}`}
|
||||||
|
</EtherscanLink>
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
Published By <Publisher account={owner} />
|
||||||
|
</div>
|
||||||
|
<p className={styles.date}>
|
||||||
|
<Time date={ddo?.created} relative />
|
||||||
|
{ddo?.created !== ddo?.updated && (
|
||||||
|
<>
|
||||||
|
{' — '}
|
||||||
|
updated <Time date={ddo?.updated} relative />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</aside>
|
||||||
|
)
|
||||||
|
}
|
@ -14,7 +14,3 @@
|
|||||||
.samples {
|
.samples {
|
||||||
margin-top: var(--spacer);
|
margin-top: var(--spacer);
|
||||||
}
|
}
|
||||||
|
|
||||||
.date {
|
|
||||||
color: var(--color-secondary);
|
|
||||||
}
|
|
||||||
|
@ -1,25 +1,13 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import MetaItem from './MetaItem'
|
import MetaItem from './MetaItem'
|
||||||
import styles from './MetaSecondary.module.css'
|
import styles from './MetaSecondary.module.css'
|
||||||
import { MetadataMarket } from '../../../@types/MetaData'
|
|
||||||
import Tags from '../../atoms/Tags'
|
import Tags from '../../atoms/Tags'
|
||||||
import Button from '../../atoms/Button'
|
import Button from '../../atoms/Button'
|
||||||
import Time from '../../atoms/Time'
|
import { useAsset } from '../../../providers/Asset'
|
||||||
|
|
||||||
export default function MetaSecondary({
|
const SampleButton = ({ url }: { url: string }) => (
|
||||||
metadata
|
|
||||||
}: {
|
|
||||||
metadata: MetadataMarket
|
|
||||||
}): ReactElement {
|
|
||||||
return (
|
|
||||||
<aside className={styles.metaSecondary}>
|
|
||||||
{metadata?.additionalInformation?.links?.length > 0 && (
|
|
||||||
<div className={styles.samples}>
|
|
||||||
<MetaItem
|
|
||||||
title="Sample Data"
|
|
||||||
content={
|
|
||||||
<Button
|
<Button
|
||||||
href={metadata?.additionalInformation?.links[0].url}
|
href={url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
download
|
download
|
||||||
@ -28,6 +16,21 @@ export default function MetaSecondary({
|
|||||||
>
|
>
|
||||||
Download Sample
|
Download Sample
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default function MetaSecondary(): ReactElement {
|
||||||
|
const { metadata } = useAsset()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<aside className={styles.metaSecondary}>
|
||||||
|
{metadata?.additionalInformation?.links?.length > 0 && (
|
||||||
|
<div className={styles.samples}>
|
||||||
|
<MetaItem
|
||||||
|
title="Sample Data"
|
||||||
|
content={
|
||||||
|
<SampleButton
|
||||||
|
url={metadata?.additionalInformation?.links[0].url}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -36,10 +39,6 @@ export default function MetaSecondary({
|
|||||||
{metadata?.additionalInformation?.tags?.length > 0 && (
|
{metadata?.additionalInformation?.tags?.length > 0 && (
|
||||||
<Tags items={metadata?.additionalInformation?.tags} />
|
<Tags items={metadata?.additionalInformation?.tags} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className={styles.date}>
|
|
||||||
Published <Time date={metadata?.main.datePublished} relative />
|
|
||||||
</p>
|
|
||||||
</aside>
|
</aside>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -25,8 +25,6 @@ export default function Dynamic({
|
|||||||
content: any
|
content: any
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { account, balance, networkId, refreshBalance } = useOcean()
|
const { account, balance, networkId, refreshBalance } = useOcean()
|
||||||
const { dtSymbol, dtName } = usePricing(ddo)
|
|
||||||
|
|
||||||
const [firstPrice, setFirstPrice] = useState<string>()
|
const [firstPrice, setFirstPrice] = useState<string>()
|
||||||
|
|
||||||
// Connect with form
|
// Connect with form
|
||||||
@ -117,7 +115,10 @@ export default function Dynamic({
|
|||||||
/>
|
/>
|
||||||
<Coin
|
<Coin
|
||||||
name="dtAmount"
|
name="dtAmount"
|
||||||
datatokenOptions={{ symbol: dtSymbol, name: dtName }}
|
datatokenOptions={{
|
||||||
|
symbol: ddo.dataTokenInfo.symbol,
|
||||||
|
name: ddo.dataTokenInfo.name
|
||||||
|
}}
|
||||||
weight={`${Number(weightOnDataToken) * 10}%`}
|
weight={`${Number(weightOnDataToken) * 10}%`}
|
||||||
readOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
|
@ -25,20 +25,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.meta {
|
.ownerActions {
|
||||||
margin-bottom: var(--spacer);
|
text-align: center;
|
||||||
color: var(--color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.meta p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datatoken a {
|
|
||||||
color: var(--color-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttonGroup {
|
|
||||||
margin-top: var(--spacer);
|
margin-top: var(--spacer);
|
||||||
margin-bottom: var(--spacer);
|
margin-bottom: calc(var(--spacer) * 1.5);
|
||||||
|
margin-left: -2rem;
|
||||||
|
margin-right: -2rem;
|
||||||
|
padding: calc(var(--spacer) / 4) var(--spacer);
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ownerActions a,
|
||||||
|
.ownerActions button {
|
||||||
|
color: var(--color-secondary);
|
||||||
|
margin-left: calc(var(--spacer) / 4);
|
||||||
|
margin-right: calc(var(--spacer) / 4);
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
import { MetadataMarket } from '../../../@types/MetaData'
|
|
||||||
import React, { ReactElement, useEffect, useState } from 'react'
|
import React, { ReactElement, useEffect, useState } from 'react'
|
||||||
import { graphql, Link, useStaticQuery } from 'gatsby'
|
import { graphql, useStaticQuery } from 'gatsby'
|
||||||
import Markdown from '../../atoms/Markdown'
|
import Markdown from '../../atoms/Markdown'
|
||||||
import MetaFull from './MetaFull'
|
import MetaFull from './MetaFull'
|
||||||
import MetaSecondary from './MetaSecondary'
|
import MetaSecondary from './MetaSecondary'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import AssetActions from '../AssetActions'
|
import AssetActions from '../AssetActions'
|
||||||
import { DDO } from '@oceanprotocol/lib'
|
|
||||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||||
import Pricing from './Pricing'
|
import Pricing from './Pricing'
|
||||||
import { useOcean, usePricing } from '@oceanprotocol/react'
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
import EtherscanLink from '../../atoms/EtherscanLink'
|
|
||||||
import Bookmark from './Bookmark'
|
import Bookmark from './Bookmark'
|
||||||
import Publisher from '../../atoms/Publisher'
|
|
||||||
import { useAsset } from '../../../providers/Asset'
|
import { useAsset } from '../../../providers/Asset'
|
||||||
import Alert from '../../atoms/Alert'
|
import Alert from '../../atoms/Alert'
|
||||||
|
import Button from '../../atoms/Button'
|
||||||
|
import Edit from '../AssetActions/Edit'
|
||||||
|
import DebugOutput from '../../atoms/DebugOutput'
|
||||||
|
import MetaMain from './MetaMain'
|
||||||
|
// import EditHistory from './EditHistory'
|
||||||
|
|
||||||
export interface AssetContentProps {
|
export interface AssetContentProps {
|
||||||
metadata: MetadataMarket
|
|
||||||
ddo: DDO
|
|
||||||
path?: string
|
path?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,54 +38,37 @@ const contentQuery = graphql`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export default function AssetContent({
|
export default function AssetContent(props: AssetContentProps): ReactElement {
|
||||||
metadata,
|
|
||||||
ddo
|
|
||||||
}: AssetContentProps): ReactElement {
|
|
||||||
const data = useStaticQuery(contentQuery)
|
const data = useStaticQuery(contentQuery)
|
||||||
const content = data.purgatory.edges[0].node.childContentJson.asset
|
const content = data.purgatory.edges[0].node.childContentJson.asset
|
||||||
|
|
||||||
const { debug } = useUserPreferences()
|
const { debug } = useUserPreferences()
|
||||||
const { accountId, networkId } = useOcean()
|
const { accountId } = useOcean()
|
||||||
const { owner, isInPurgatory, purgatoryData } = useAsset()
|
const { owner, isInPurgatory, purgatoryData } = useAsset()
|
||||||
const { dtSymbol, dtName } = usePricing(ddo)
|
|
||||||
const [showPricing, setShowPricing] = useState(false)
|
const [showPricing, setShowPricing] = useState(false)
|
||||||
const { price } = useAsset()
|
const [showEdit, setShowEdit] = useState<boolean>()
|
||||||
|
const { ddo, price, metadata } = useAsset()
|
||||||
|
const isOwner = accountId === owner
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowPricing(accountId === owner && price.isConsumable === '')
|
if (!price) return
|
||||||
}, [accountId, owner, price])
|
setShowPricing(isOwner && price.address === '')
|
||||||
|
}, [isOwner, price])
|
||||||
|
|
||||||
return (
|
function handleEditButton() {
|
||||||
|
// move user's focus to top of screen
|
||||||
|
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
|
||||||
|
setShowEdit(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return showEdit ? (
|
||||||
|
<Edit setShowEdit={setShowEdit} />
|
||||||
|
) : (
|
||||||
<article className={styles.grid}>
|
<article className={styles.grid}>
|
||||||
<div>
|
<div>
|
||||||
{showPricing && <Pricing ddo={ddo} />}
|
{showPricing && <Pricing ddo={ddo} />}
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{metadata?.additionalInformation?.categories?.length && (
|
<MetaMain />
|
||||||
<p>
|
<Bookmark did={ddo.id} />
|
||||||
<Link
|
|
||||||
to={`/search?categories=${metadata?.additionalInformation?.categories[0]}`}
|
|
||||||
>
|
|
||||||
{metadata?.additionalInformation?.categories[0]}
|
|
||||||
</Link>
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<aside className={styles.meta}>
|
|
||||||
<p className={styles.datatoken}>
|
|
||||||
<EtherscanLink
|
|
||||||
networkId={networkId}
|
|
||||||
path={`token/${ddo.dataToken}`}
|
|
||||||
>
|
|
||||||
{dtName ? (
|
|
||||||
`${dtName} — ${dtSymbol}`
|
|
||||||
) : (
|
|
||||||
<code>{ddo.dataToken}</code>
|
|
||||||
)}
|
|
||||||
</EtherscanLink>
|
|
||||||
</p>
|
|
||||||
Published By <Publisher account={owner} />
|
|
||||||
</aside>
|
|
||||||
|
|
||||||
{isInPurgatory ? (
|
{isInPurgatory ? (
|
||||||
<Alert
|
<Alert
|
||||||
@ -102,28 +84,26 @@ export default function AssetContent({
|
|||||||
text={metadata?.additionalInformation?.description || ''}
|
text={metadata?.additionalInformation?.description || ''}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<MetaSecondary metadata={metadata} />
|
<MetaSecondary />
|
||||||
|
|
||||||
|
{isOwner && (
|
||||||
|
<div className={styles.ownerActions}>
|
||||||
|
<Button style="text" size="small" onClick={handleEditButton}>
|
||||||
|
Edit Metadata
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<MetaFull
|
<MetaFull />
|
||||||
ddo={ddo}
|
{/* <EditHistory /> */}
|
||||||
metadata={metadata}
|
{debug === true && <DebugOutput title="DDO" output={ddo} />}
|
||||||
isInPurgatory={isInPurgatory}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{debug === true && (
|
|
||||||
<pre>
|
|
||||||
<code>{JSON.stringify(ddo, null, 2)}</code>
|
|
||||||
</pre>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Bookmark did={ddo.id} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.actions}>
|
<div className={styles.actions}>
|
||||||
<AssetActions ddo={ddo} />
|
<AssetActions />
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
)
|
)
|
||||||
|
@ -1,16 +1,8 @@
|
|||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { MetadataPublishForm } from '../../../@types/MetaData'
|
import { MetadataPublishForm } from '../../../@types/MetaData'
|
||||||
|
import DebugOutput from '../../atoms/DebugOutput'
|
||||||
import styles from './index.module.css'
|
import styles from './index.module.css'
|
||||||
import { transformPublishFormToMetadata } from './utils'
|
import { transformPublishFormToMetadata } from '../../../utils/metadata'
|
||||||
|
|
||||||
const Output = ({ title, output }: { title: string; output: any }) => (
|
|
||||||
<div>
|
|
||||||
<h5>{title}</h5>
|
|
||||||
<pre>
|
|
||||||
<code>{JSON.stringify(output, null, 2)}</code>
|
|
||||||
</pre>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
export default function Debug({
|
export default function Debug({
|
||||||
values
|
values
|
||||||
@ -38,8 +30,8 @@ export default function Debug({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
<Output title="Collected Form Values" output={values} />
|
<DebugOutput title="Collected Form Values" output={values} />
|
||||||
<Output title="Transformed DDO Values" output={ddo} />
|
<DebugOutput title="Transformed DDO Values" output={ddo} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
import Alert from '../../atoms/Alert'
|
|
||||||
import Button from '../../atoms/Button'
|
|
||||||
import Loader from '../../atoms/Loader'
|
|
||||||
import React, { ReactElement } from 'react'
|
|
||||||
import styles from './Feedback.module.css'
|
|
||||||
import SuccessConfetti from '../../atoms/SuccessConfetti'
|
|
||||||
import { DDO } from '@oceanprotocol/lib'
|
|
||||||
|
|
||||||
export default function Feedback({
|
|
||||||
error,
|
|
||||||
success,
|
|
||||||
ddo,
|
|
||||||
publishStepText,
|
|
||||||
setError
|
|
||||||
}: {
|
|
||||||
error: string
|
|
||||||
success: string
|
|
||||||
ddo: DDO
|
|
||||||
publishStepText: string
|
|
||||||
setError: (error: string) => void
|
|
||||||
}): ReactElement {
|
|
||||||
const SuccessAction = () => (
|
|
||||||
<Button
|
|
||||||
style="primary"
|
|
||||||
size="small"
|
|
||||||
to={`/asset/${ddo?.id}`}
|
|
||||||
className={styles.action}
|
|
||||||
>
|
|
||||||
Go to data set →
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.feedback}>
|
|
||||||
<div className={styles.box}>
|
|
||||||
<h3>Publishing Data Set</h3>
|
|
||||||
{error ? (
|
|
||||||
<>
|
|
||||||
<Alert text={error} state="error" />
|
|
||||||
<Button
|
|
||||||
style="primary"
|
|
||||||
size="small"
|
|
||||||
className={styles.action}
|
|
||||||
onClick={() => setError(undefined)}
|
|
||||||
>
|
|
||||||
Try Again
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
) : success ? (
|
|
||||||
<SuccessConfetti success={success} action={<SuccessAction />} />
|
|
||||||
) : (
|
|
||||||
<Loader message={publishStepText} />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,10 +1,11 @@
|
|||||||
import React, { ReactElement, useEffect, FormEvent } from 'react'
|
import React, { ReactElement, useEffect, FormEvent, ChangeEvent } from 'react'
|
||||||
import styles from './FormPublish.module.css'
|
import styles from './FormPublish.module.css'
|
||||||
import { useOcean } from '@oceanprotocol/react'
|
import { useOcean } from '@oceanprotocol/react'
|
||||||
import { useFormikContext, Field, Form } from 'formik'
|
import { useFormikContext, Field, Form, FormikContextType } from 'formik'
|
||||||
import Input from '../../atoms/Input'
|
import Input from '../../atoms/Input'
|
||||||
import Button from '../../atoms/Button'
|
import Button from '../../atoms/Button'
|
||||||
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
import { FormContent, FormFieldProps } from '../../../@types/Form'
|
||||||
|
import { MetadataPublishForm } from '../../../@types/MetaData'
|
||||||
|
|
||||||
export default function FormPublish({
|
export default function FormPublish({
|
||||||
content
|
content
|
||||||
@ -19,8 +20,10 @@ export default function FormPublish({
|
|||||||
setErrors,
|
setErrors,
|
||||||
setTouched,
|
setTouched,
|
||||||
resetForm,
|
resetForm,
|
||||||
initialValues
|
initialValues,
|
||||||
} = useFormikContext()
|
validateField,
|
||||||
|
setFieldValue
|
||||||
|
}: FormikContextType<MetadataPublishForm> = useFormikContext()
|
||||||
|
|
||||||
// reset form validation on every mount
|
// reset form validation on every mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -30,6 +33,16 @@ export default function FormPublish({
|
|||||||
// setSubmitting(false)
|
// setSubmitting(false)
|
||||||
}, [setErrors, setTouched])
|
}, [setErrors, setTouched])
|
||||||
|
|
||||||
|
// Manually handle change events instead of using `handleChange` from Formik.
|
||||||
|
// Workaround for default `validateOnChange` not kicking in
|
||||||
|
function handleFieldChange(
|
||||||
|
e: ChangeEvent<HTMLInputElement>,
|
||||||
|
field: FormFieldProps
|
||||||
|
) {
|
||||||
|
validateField(field.name)
|
||||||
|
setFieldValue(field.name, e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
const resetFormAndClearStorage = (e: FormEvent<Element>) => {
|
const resetFormAndClearStorage = (e: FormEvent<Element>) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
resetForm({ values: initialValues, status: 'empty' })
|
resetForm({ values: initialValues, status: 'empty' })
|
||||||
@ -43,7 +56,14 @@ export default function FormPublish({
|
|||||||
onChange={() => status === 'empty' && setStatus(null)}
|
onChange={() => status === 'empty' && setStatus(null)}
|
||||||
>
|
>
|
||||||
{content.data.map((field: FormFieldProps) => (
|
{content.data.map((field: FormFieldProps) => (
|
||||||
<Field key={field.name} {...field} component={Input} />
|
<Field
|
||||||
|
key={field.name}
|
||||||
|
{...field}
|
||||||
|
component={Input}
|
||||||
|
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
handleFieldChange(e, field)
|
||||||
|
}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<footer className={styles.actions}>
|
<footer className={styles.actions}>
|
||||||
|
@ -1,103 +0,0 @@
|
|||||||
import React, { FormEvent, ReactElement, useState } from 'react'
|
|
||||||
import { File as FileMetadata } from '@oceanprotocol/lib/dist/node/ddo/interfaces/File'
|
|
||||||
import Markdown from '../../atoms/Markdown'
|
|
||||||
import Tags from '../../atoms/Tags'
|
|
||||||
import MetaItem from '../../organisms/AssetContent/MetaItem'
|
|
||||||
import styles from './Preview.module.css'
|
|
||||||
import File from '../../atoms/File'
|
|
||||||
import { MetadataPublishForm } from '../../../@types/MetaData'
|
|
||||||
import Button from '../../atoms/Button'
|
|
||||||
import { transformTags } from './utils'
|
|
||||||
|
|
||||||
export default function Preview({
|
|
||||||
values
|
|
||||||
}: {
|
|
||||||
values: Partial<MetadataPublishForm>
|
|
||||||
}): ReactElement {
|
|
||||||
const [fullDescription, setFullDescription] = useState<boolean>(false)
|
|
||||||
|
|
||||||
const textLimit = 500 // string.length
|
|
||||||
const description =
|
|
||||||
fullDescription === true
|
|
||||||
? values.description
|
|
||||||
: `${values.description.substring(0, textLimit)}${
|
|
||||||
values.description.length > textLimit ? `...` : ''
|
|
||||||
}`
|
|
||||||
|
|
||||||
function handleDescriptionToggle(e: FormEvent<HTMLButtonElement>) {
|
|
||||||
e.preventDefault()
|
|
||||||
setFullDescription(!fullDescription)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.preview}>
|
|
||||||
<h2 className={styles.previewTitle}>Preview</h2>
|
|
||||||
<header>
|
|
||||||
{values.name && <h3 className={styles.title}>{values.name}</h3>}
|
|
||||||
{values.author && <p className={styles.author}>{values.author}</p>}
|
|
||||||
|
|
||||||
{values.description && (
|
|
||||||
<div className={styles.description}>
|
|
||||||
<Markdown text={description} />
|
|
||||||
{values.description.length > textLimit && (
|
|
||||||
<Button
|
|
||||||
style="text"
|
|
||||||
size="small"
|
|
||||||
onClick={handleDescriptionToggle}
|
|
||||||
className={styles.toggle}
|
|
||||||
>
|
|
||||||
{fullDescription === true ? 'Close' : 'Expand'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={styles.asset}>
|
|
||||||
{values.files?.length > 0 && typeof values.files !== 'string' && (
|
|
||||||
<File
|
|
||||||
file={values.files[0] as FileMetadata}
|
|
||||||
className={styles.file}
|
|
||||||
small
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{typeof values.links !== 'string' && values.links?.length && (
|
|
||||||
<Button
|
|
||||||
href={(values.links[0] as FileMetadata).url}
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
download
|
|
||||||
style="text"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
Download Sample
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{values.tags && <Tags items={transformTags(values.tags)} />}
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<div className={styles.metaFull}>
|
|
||||||
{Object.entries(values)
|
|
||||||
.filter(
|
|
||||||
([key, value]) =>
|
|
||||||
!(
|
|
||||||
key.includes('author') ||
|
|
||||||
key.includes('name') ||
|
|
||||||
key.includes('description') ||
|
|
||||||
key.includes('tags') ||
|
|
||||||
key.includes('files') ||
|
|
||||||
key.includes('links') ||
|
|
||||||
key.includes('termsAndConditions') ||
|
|
||||||
key.includes('dataTokenOptions') ||
|
|
||||||
value === undefined ||
|
|
||||||
value === ''
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.map(([key, value]) => (
|
|
||||||
<MetaItem key={key} title={key} content={value} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
@ -6,15 +6,15 @@ import FormPublish from './FormPublish'
|
|||||||
import Web3Feedback from '../../molecules/Wallet/Feedback'
|
import Web3Feedback from '../../molecules/Wallet/Feedback'
|
||||||
import { FormContent } from '../../../@types/Form'
|
import { FormContent } from '../../../@types/Form'
|
||||||
import { initialValues, validationSchema } from '../../../models/FormPublish'
|
import { initialValues, validationSchema } from '../../../models/FormPublish'
|
||||||
import { transformPublishFormToMetadata } from './utils'
|
import { transformPublishFormToMetadata } from '../../../utils/metadata'
|
||||||
import Preview from './Preview'
|
import MetadataPreview from '../../molecules/MetadataPreview'
|
||||||
import { MetadataPublishForm } from '../../../@types/MetaData'
|
import { MetadataPublishForm } from '../../../@types/MetaData'
|
||||||
import { useUserPreferences } from '../../../providers/UserPreferences'
|
import { useUserPreferences } from '../../../providers/UserPreferences'
|
||||||
import { DDO, Logger, Metadata } from '@oceanprotocol/lib'
|
import { Logger, Metadata } from '@oceanprotocol/lib'
|
||||||
import { Persist } from '../../atoms/FormikPersist'
|
import { Persist } from '../../atoms/FormikPersist'
|
||||||
import Debug from './Debug'
|
import Debug from './Debug'
|
||||||
import Feedback from './Feedback'
|
|
||||||
import Alert from '../../atoms/Alert'
|
import Alert from '../../atoms/Alert'
|
||||||
|
import MetadataFeedback from '../../molecules/MetadataFeedback'
|
||||||
|
|
||||||
const formName = 'ocean-publish-form'
|
const formName = 'ocean-publish-form'
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ export default function PublishPage({
|
|||||||
const { isInPurgatory, purgatoryData } = useOcean()
|
const { isInPurgatory, purgatoryData } = useOcean()
|
||||||
const [success, setSuccess] = useState<string>()
|
const [success, setSuccess] = useState<string>()
|
||||||
const [error, setError] = useState<string>()
|
const [error, setError] = useState<string>()
|
||||||
const [ddo, setDdo] = useState<DDO>()
|
const [did, setDid] = useState<string>()
|
||||||
|
|
||||||
const hasFeedback = isLoading || error || success
|
const hasFeedback = isLoading || error || success
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ export default function PublishPage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Publish succeeded
|
// Publish succeeded
|
||||||
setDdo(ddo)
|
setDid(ddo.id)
|
||||||
setSuccess(
|
setSuccess(
|
||||||
'🎉 Successfully published. 🎉 Now create a price on your data set.'
|
'🎉 Successfully published. 🎉 Now create a price on your data set.'
|
||||||
)
|
)
|
||||||
@ -77,12 +77,11 @@ export default function PublishPage({
|
|||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
initialStatus="empty"
|
initialStatus="empty"
|
||||||
validationSchema={validationSchema}
|
validationSchema={validationSchema}
|
||||||
onSubmit={async (values, { setSubmitting, resetForm }) => {
|
onSubmit={async (values, { resetForm }) => {
|
||||||
// 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 publishing
|
// kick off publishing
|
||||||
await handleSubmit(values, resetForm)
|
await handleSubmit(values, resetForm)
|
||||||
setSubmitting(false)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{({ values }) => (
|
{({ values }) => (
|
||||||
@ -90,12 +89,16 @@ export default function PublishPage({
|
|||||||
<Persist name={formName} ignoreFields={['isSubmitting']} />
|
<Persist name={formName} ignoreFields={['isSubmitting']} />
|
||||||
|
|
||||||
{hasFeedback ? (
|
{hasFeedback ? (
|
||||||
<Feedback
|
<MetadataFeedback
|
||||||
|
title="Publishing Data Set"
|
||||||
error={error}
|
error={error}
|
||||||
success={success}
|
success={success}
|
||||||
publishStepText={publishStepText}
|
loading={publishStepText}
|
||||||
ddo={ddo}
|
|
||||||
setError={setError}
|
setError={setError}
|
||||||
|
successAction={{
|
||||||
|
name: 'Go to data set →',
|
||||||
|
to: `/asset/${did}`
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -109,7 +112,7 @@ export default function PublishPage({
|
|||||||
|
|
||||||
<aside>
|
<aside>
|
||||||
<div className={styles.sticky}>
|
<div className={styles.sticky}>
|
||||||
<Preview values={values} />
|
<MetadataPreview values={values} />
|
||||||
<Web3Feedback />
|
<Web3Feedback />
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
@ -2,7 +2,6 @@ import React, { useState, useEffect, ReactElement } from 'react'
|
|||||||
import { Router } from '@reach/router'
|
import { Router } from '@reach/router'
|
||||||
import AssetContent from '../organisms/AssetContent'
|
import AssetContent from '../organisms/AssetContent'
|
||||||
import Page from './Page'
|
import Page from './Page'
|
||||||
import { MetadataMarket } from '../../@types/MetaData'
|
|
||||||
import Alert from '../atoms/Alert'
|
import Alert from '../atoms/Alert'
|
||||||
import Loader from '../atoms/Loader'
|
import Loader from '../atoms/Loader'
|
||||||
import { useAsset } from '../../providers/Asset'
|
import { useAsset } from '../../providers/Asset'
|
||||||
@ -12,37 +11,29 @@ export default function PageTemplateAssetDetails({
|
|||||||
}: {
|
}: {
|
||||||
uri: string
|
uri: string
|
||||||
}): ReactElement {
|
}): ReactElement {
|
||||||
const { isInPurgatory } = useAsset()
|
const { ddo, title, error, isInPurgatory } = useAsset()
|
||||||
const [metadata, setMetadata] = useState<MetadataMarket>()
|
const [pageTitle, setPageTitle] = useState<string>()
|
||||||
const [title, setTitle] = useState<string>()
|
|
||||||
const { ddo, error } = useAsset()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ddo || error) {
|
if (!ddo || error) {
|
||||||
setTitle('Could not retrieve asset')
|
setPageTitle('Could not retrieve asset')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const { attributes } = ddo.findServiceByType('metadata')
|
setPageTitle(isInPurgatory ? '' : title)
|
||||||
setTitle(isInPurgatory ? '' : attributes.main.name)
|
}, [ddo, error, isInPurgatory, title])
|
||||||
setMetadata((attributes as unknown) as MetadataMarket)
|
|
||||||
}, [ddo, error, isInPurgatory])
|
|
||||||
|
|
||||||
return ddo && metadata ? (
|
return ddo ? (
|
||||||
<>
|
<>
|
||||||
<Page title={title} uri={uri}>
|
<Page title={pageTitle} uri={uri}>
|
||||||
<Router basepath="/asset">
|
<Router basepath="/asset">
|
||||||
<AssetContent
|
<AssetContent path=":did" />
|
||||||
ddo={ddo}
|
|
||||||
metadata={metadata as MetadataMarket}
|
|
||||||
path=":did"
|
|
||||||
/>
|
|
||||||
</Router>
|
</Router>
|
||||||
</Page>
|
</Page>
|
||||||
</>
|
</>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
<Page title={title} noPageHeader uri={uri}>
|
<Page title={pageTitle} noPageHeader uri={uri}>
|
||||||
<Alert title={title} text={error} state="error" />
|
<Alert title={pageTitle} text={error} state="error" />
|
||||||
</Page>
|
</Page>
|
||||||
) : (
|
) : (
|
||||||
<Page title={undefined} uri={uri}>
|
<Page title={undefined} uri={uri}>
|
||||||
|
20
src/models/FormEditMetadata.ts
Normal file
20
src/models/FormEditMetadata.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { MetadataMarket, MetadataPublishForm } from '../@types/MetaData'
|
||||||
|
import * as Yup from 'yup'
|
||||||
|
|
||||||
|
export const validationSchema = Yup.object().shape<
|
||||||
|
Partial<MetadataPublishForm>
|
||||||
|
>({
|
||||||
|
name: Yup.string()
|
||||||
|
.min(4, (param) => `Title must be at least ${param.min} characters`)
|
||||||
|
.required('Required'),
|
||||||
|
description: Yup.string().required('Required').min(10)
|
||||||
|
})
|
||||||
|
|
||||||
|
export function getInitialValues(
|
||||||
|
metadata: MetadataMarket
|
||||||
|
): Partial<MetadataPublishForm> {
|
||||||
|
return {
|
||||||
|
name: metadata.main.name,
|
||||||
|
description: metadata.additionalInformation.description
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,9 @@ import * as Yup from 'yup'
|
|||||||
|
|
||||||
export const validationSchema = Yup.object().shape<MetadataPublishForm>({
|
export const validationSchema = Yup.object().shape<MetadataPublishForm>({
|
||||||
// ---- required fields ----
|
// ---- required fields ----
|
||||||
name: Yup.string().required('Required'),
|
name: Yup.string()
|
||||||
|
.min(4, (param) => `Title must be at least ${param.min} characters`)
|
||||||
|
.required('Required'),
|
||||||
author: Yup.string().required('Required'),
|
author: Yup.string().required('Required'),
|
||||||
dataTokenOptions: Yup.object()
|
dataTokenOptions: Yup.object()
|
||||||
.shape({
|
.shape({
|
||||||
@ -13,7 +15,7 @@ export const validationSchema = Yup.object().shape<MetadataPublishForm>({
|
|||||||
})
|
})
|
||||||
.required('Required'),
|
.required('Required'),
|
||||||
files: Yup.array<FileMetadata>().required('Required').nullable(),
|
files: Yup.array<FileMetadata>().required('Required').nullable(),
|
||||||
description: Yup.string().required('Required'),
|
description: Yup.string().min(10).required('Required'),
|
||||||
access: Yup.string()
|
access: Yup.string()
|
||||||
.matches(/Compute|Download/g)
|
.matches(/Compute|Download/g)
|
||||||
.required('Required'),
|
.required('Required'),
|
||||||
|
@ -7,25 +7,27 @@ import React, {
|
|||||||
useCallback,
|
useCallback,
|
||||||
ReactNode
|
ReactNode
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { Logger, DDO, Metadata, BestPrice } from '@oceanprotocol/lib'
|
import { Logger, DDO, BestPrice } from '@oceanprotocol/lib'
|
||||||
import { PurgatoryData } from '@oceanprotocol/lib/dist/node/ddo/interfaces/PurgatoryData'
|
import { PurgatoryData } from '@oceanprotocol/lib/dist/node/ddo/interfaces/PurgatoryData'
|
||||||
import { getDataTokenPrice, useOcean } from '@oceanprotocol/react'
|
import { getDataTokenPrice, useOcean } from '@oceanprotocol/react'
|
||||||
import getAssetPurgatoryData from '../utils/purgatory'
|
import getAssetPurgatoryData from '../utils/purgatory'
|
||||||
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
|
import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
|
||||||
import axios from 'axios'
|
import axios, { CancelToken } from 'axios'
|
||||||
import { retrieveDDO } from '../utils/aquarius'
|
import { retrieveDDO } from '../utils/aquarius'
|
||||||
|
import { MetadataMarket } from '../@types/MetaData'
|
||||||
|
|
||||||
interface AssetProviderValue {
|
interface AssetProviderValue {
|
||||||
isInPurgatory: boolean
|
isInPurgatory: boolean
|
||||||
purgatoryData: PurgatoryData
|
purgatoryData: PurgatoryData
|
||||||
ddo: DDO | undefined
|
ddo: DDO | undefined
|
||||||
did: string | undefined
|
did: string | undefined
|
||||||
metadata: Metadata | undefined
|
metadata: MetadataMarket | undefined
|
||||||
title: string | undefined
|
title: string | undefined
|
||||||
owner: string | undefined
|
owner: string | undefined
|
||||||
price: BestPrice | undefined
|
price: BestPrice | undefined
|
||||||
error?: string
|
error?: string
|
||||||
refreshInterval: number
|
refreshInterval: number
|
||||||
|
refreshDdo: (token?: CancelToken) => Promise<void>
|
||||||
refreshPrice: () => Promise<void>
|
refreshPrice: () => Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +47,7 @@ function AssetProvider({
|
|||||||
const [purgatoryData, setPurgatoryData] = useState<PurgatoryData>()
|
const [purgatoryData, setPurgatoryData] = useState<PurgatoryData>()
|
||||||
const [ddo, setDDO] = useState<DDO>()
|
const [ddo, setDDO] = useState<DDO>()
|
||||||
const [did, setDID] = useState<string>()
|
const [did, setDID] = useState<string>()
|
||||||
const [metadata, setMetadata] = useState<Metadata>()
|
const [metadata, setMetadata] = useState<MetadataMarket>()
|
||||||
const [title, setTitle] = useState<string>()
|
const [title, setTitle] = useState<string>()
|
||||||
const [price, setPrice] = useState<BestPrice>()
|
const [price, setPrice] = useState<BestPrice>()
|
||||||
const [owner, setOwner] = useState<string>()
|
const [owner, setOwner] = useState<string>()
|
||||||
@ -69,6 +71,29 @@ function AssetProvider({
|
|||||||
Logger.log(`Refreshed asset price: ${newPrice?.value}`)
|
Logger.log(`Refreshed asset price: ${newPrice?.value}`)
|
||||||
}, [ocean, config, ddo, networkId, status])
|
}, [ocean, config, ddo, networkId, status])
|
||||||
|
|
||||||
|
const fetchDdo = async (token?: CancelToken) => {
|
||||||
|
Logger.log('Init asset, get ddo')
|
||||||
|
const ddo = await retrieveDDO(
|
||||||
|
asset as string,
|
||||||
|
config.metadataCacheUri,
|
||||||
|
token
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!ddo) {
|
||||||
|
setError(
|
||||||
|
`The DDO for ${asset} was not found in MetadataCache. If you just published a new data set, wait some seconds and refresh this page.`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setError(undefined)
|
||||||
|
}
|
||||||
|
return ddo
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshDdo = async (token?: CancelToken) => {
|
||||||
|
const ddo = await fetchDdo(token)
|
||||||
|
Logger.debug('DDO', ddo)
|
||||||
|
setDDO(ddo)
|
||||||
|
}
|
||||||
//
|
//
|
||||||
// Get and set DDO based on passed DDO or DID
|
// Get and set DDO based on passed DDO or DID
|
||||||
//
|
//
|
||||||
@ -79,28 +104,14 @@ function AssetProvider({
|
|||||||
let isMounted = true
|
let isMounted = true
|
||||||
Logger.log('Init asset, get ddo')
|
Logger.log('Init asset, get ddo')
|
||||||
|
|
||||||
async function init(): Promise<void> {
|
async function init() {
|
||||||
const ddo = await retrieveDDO(
|
const ddo = await fetchDdo(source.token)
|
||||||
asset as string,
|
|
||||||
config.metadataCacheUri,
|
|
||||||
source.token
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!ddo) {
|
|
||||||
setError(
|
|
||||||
`The DDO for ${asset} was not found in MetadataCache. If you just published a new data set, wait some seconds and refresh this page.`
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
setError(undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isMounted) return
|
if (!isMounted) return
|
||||||
Logger.debug('DDO', ddo)
|
Logger.debug('DDO', ddo)
|
||||||
setDDO(ddo)
|
setDDO(ddo)
|
||||||
setDID(asset as string)
|
setDID(asset as string)
|
||||||
}
|
}
|
||||||
init()
|
init()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
isMounted = false
|
isMounted = false
|
||||||
source.cancel()
|
source.cancel()
|
||||||
@ -130,10 +141,10 @@ function AssetProvider({
|
|||||||
if (result?.did !== undefined) {
|
if (result?.did !== undefined) {
|
||||||
setIsInPurgatory(true)
|
setIsInPurgatory(true)
|
||||||
setPurgatoryData(result)
|
setPurgatoryData(result)
|
||||||
} else {
|
return
|
||||||
setIsInPurgatory(false)
|
|
||||||
}
|
}
|
||||||
setPurgatoryData(result)
|
|
||||||
|
setIsInPurgatory(false)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
Logger.error(error)
|
Logger.error(error)
|
||||||
}
|
}
|
||||||
@ -147,7 +158,7 @@ function AssetProvider({
|
|||||||
// Set price & metadata from DDO first
|
// Set price & metadata from DDO first
|
||||||
setPrice(ddo.price)
|
setPrice(ddo.price)
|
||||||
const { attributes } = ddo.findServiceByType('metadata')
|
const { attributes } = ddo.findServiceByType('metadata')
|
||||||
setMetadata(attributes)
|
setMetadata((attributes as unknown) as MetadataMarket)
|
||||||
setTitle(attributes?.main.name)
|
setTitle(attributes?.main.name)
|
||||||
setOwner(ddo.publicKey[0].owner)
|
setOwner(ddo.publicKey[0].owner)
|
||||||
setIsInPurgatory(ddo.isInPurgatory === 'true')
|
setIsInPurgatory(ddo.isInPurgatory === 'true')
|
||||||
@ -177,6 +188,7 @@ function AssetProvider({
|
|||||||
isInPurgatory,
|
isInPurgatory,
|
||||||
purgatoryData,
|
purgatoryData,
|
||||||
refreshInterval,
|
refreshInterval,
|
||||||
|
refreshDdo,
|
||||||
refreshPrice
|
refreshPrice
|
||||||
} as AssetProviderValue
|
} as AssetProviderValue
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { MetadataMarket, MetadataPublishForm } from '../../../@types/MetaData'
|
import { MetadataMarket, MetadataPublishForm } from '../@types/MetaData'
|
||||||
import { toStringNoMS } from '../../../utils'
|
import { toStringNoMS } from '.'
|
||||||
import AssetModel from '../../../models/Asset'
|
import AssetModel from '../models/Asset'
|
||||||
import slugify from '@sindresorhus/slugify'
|
import slugify from '@sindresorhus/slugify'
|
||||||
|
import { DDO } from '@oceanprotocol/lib'
|
||||||
|
|
||||||
export function transformTags(value: string): string[] {
|
export function transformTags(value: string): string[] {
|
||||||
const originalTags = value?.split(',')
|
const originalTags = value?.split(',')
|
||||||
@ -10,11 +11,7 @@ export function transformTags(value: string): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function transformPublishFormToMetadata(
|
export function transformPublishFormToMetadata(
|
||||||
data: Partial<MetadataPublishForm>
|
{
|
||||||
): MetadataMarket {
|
|
||||||
const currentTime = toStringNoMS(new Date())
|
|
||||||
|
|
||||||
const {
|
|
||||||
name,
|
name,
|
||||||
author,
|
author,
|
||||||
description,
|
description,
|
||||||
@ -22,15 +19,17 @@ export function transformPublishFormToMetadata(
|
|||||||
links,
|
links,
|
||||||
termsAndConditions,
|
termsAndConditions,
|
||||||
files
|
files
|
||||||
} = data
|
}: Partial<MetadataPublishForm>,
|
||||||
|
ddo?: DDO
|
||||||
|
): MetadataMarket {
|
||||||
|
const currentTime = toStringNoMS(new Date())
|
||||||
|
|
||||||
const metadata: MetadataMarket = {
|
const metadata: MetadataMarket = {
|
||||||
main: {
|
main: {
|
||||||
...AssetModel.main,
|
...AssetModel.main,
|
||||||
name,
|
name,
|
||||||
author,
|
author,
|
||||||
dateCreated: currentTime,
|
dateCreated: ddo ? ddo.created : currentTime,
|
||||||
datePublished: currentTime,
|
|
||||||
files: typeof files !== 'string' && files,
|
files: typeof files !== 'string' && files,
|
||||||
license: 'https://market.oceanprotocol.com/terms'
|
license: 'https://market.oceanprotocol.com/terms'
|
||||||
},
|
},
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import { transformPublishFormToMetadata } from '../../../src/components/pages/Publish/utils'
|
import { transformPublishFormToMetadata } from '../../../src/utils/metadata'
|
||||||
import {
|
import {
|
||||||
MetadataMarket,
|
MetadataMarket,
|
||||||
MetadataPublishForm
|
MetadataPublishForm
|
||||||
|
Loading…
Reference in New Issue
Block a user