mirror of
https://github.com/oceanprotocol/market.git
synced 2024-12-02 05:57:29 +01:00
new publish preview (#947)
* refactor preview * make preview render * more preview elements, proper debug output * make more elements work * cleanup and fixes * make asset actions preview work, kinda * more fixes * reorg * make preview price display work * fix timeout * layout tweaks * fixes * another fix * make file info preview work * empty render fix
This commit is contained in:
parent
c387b27f23
commit
c484a5b40c
@ -129,7 +129,7 @@ function usePricing(): UsePricing {
|
||||
Decimal.set({ precision: 18 })
|
||||
|
||||
switch (price?.type) {
|
||||
case 'pool': {
|
||||
case 'dynamic': {
|
||||
const oceanAmmount = new Decimal(price.value).times(1.05).toString()
|
||||
const maxPrice = new Decimal(price.value).times(2).toString()
|
||||
|
||||
@ -152,7 +152,7 @@ function usePricing(): UsePricing {
|
||||
Logger.log('DT buy response', tx)
|
||||
break
|
||||
}
|
||||
case 'exchange': {
|
||||
case 'fixed': {
|
||||
if (!config.oceanTokenAddress) {
|
||||
Logger.error(`'oceanTokenAddress' not set in config`)
|
||||
return
|
||||
|
2
src/@types/DDO/Services.d.ts
vendored
2
src/@types/DDO/Services.d.ts
vendored
@ -22,7 +22,7 @@ interface Service {
|
||||
files: string
|
||||
datatokenAddress: string
|
||||
serviceEndpoint: string
|
||||
timeout: string
|
||||
timeout: number
|
||||
name?: string
|
||||
description?: string
|
||||
compute?: ServiceComputeOptions
|
||||
|
4
src/@types/Price.d.ts
vendored
4
src/@types/Price.d.ts
vendored
@ -1,5 +1,5 @@
|
||||
interface BestPrice {
|
||||
type: 'pool' | 'exchange' | 'free' | ''
|
||||
type: 'dynamic' | 'fixed' | 'free' | ''
|
||||
address: string
|
||||
value: number
|
||||
isConsumable?: 'true' | 'false' | ''
|
||||
@ -15,7 +15,7 @@ interface PriceOptions {
|
||||
price: number
|
||||
amountDataToken: number
|
||||
amountOcean: number
|
||||
type: 'fixed' | 'dynamic' | 'free' | string
|
||||
type: 'dynamic' | 'fixed' | 'free' | ''
|
||||
weightOnDataToken: string
|
||||
weightOnOcean: string
|
||||
// easier to keep this as number for Yup input validation
|
||||
|
@ -2,6 +2,8 @@ import axios, { CancelToken, AxiosResponse } from 'axios'
|
||||
import { DID, Logger } from '@oceanprotocol/lib'
|
||||
|
||||
export interface FileMetadata {
|
||||
index: number
|
||||
valid: boolean
|
||||
contentType: string
|
||||
contentLength: string
|
||||
}
|
||||
@ -35,20 +37,13 @@ export async function getFileInfo(
|
||||
): Promise<FileMetadata[]> {
|
||||
let postBody
|
||||
try {
|
||||
if (url instanceof DID)
|
||||
postBody = {
|
||||
did: url.getDid()
|
||||
}
|
||||
else
|
||||
postBody = {
|
||||
url
|
||||
}
|
||||
if (url instanceof DID) postBody = { did: url.getDid() }
|
||||
else postBody = { url }
|
||||
|
||||
const response: AxiosResponse<FileMetadata[]> = await axios.post(
|
||||
`${providerUrl}/api/v1/services/fileinfo`,
|
||||
postBody,
|
||||
{
|
||||
cancelToken
|
||||
}
|
||||
{ cancelToken }
|
||||
)
|
||||
|
||||
if (!response || response.status !== 200 || !response.data) return
|
||||
|
@ -346,7 +346,7 @@ function transformPriceToBestPrice(
|
||||
) {
|
||||
if (poolPrice?.length > 0) {
|
||||
const price: BestPrice = {
|
||||
type: 'pool',
|
||||
type: 'dynamic',
|
||||
address: poolPrice[0]?.id,
|
||||
value:
|
||||
poolPrice[0]?.consumePrice === '-1'
|
||||
@ -363,7 +363,7 @@ function transformPriceToBestPrice(
|
||||
// TODO Hacky hack, temporary™: set isConsumable to true for fre assets.
|
||||
// isConsumable: 'true'
|
||||
const price: BestPrice = {
|
||||
type: 'exchange',
|
||||
type: 'fixed',
|
||||
value: frePrice[0]?.rate,
|
||||
address: frePrice[0]?.id,
|
||||
exchangeId: frePrice[0]?.id,
|
||||
|
@ -3,7 +3,7 @@ import { Logger } from '@oceanprotocol/lib'
|
||||
import { getOceanConfig } from './ocean'
|
||||
|
||||
export function accountTruncate(account: string): string {
|
||||
if (!account) return
|
||||
if (!account || account === '') return
|
||||
const middle = account.substring(6, 38)
|
||||
const truncated = account.replace(middle, '…')
|
||||
return truncated
|
||||
|
@ -10,7 +10,7 @@ export default function DebugOutput({
|
||||
return (
|
||||
<div style={{ marginTop: 'var(--spacer)' }}>
|
||||
<h5>{title}</h5>
|
||||
<pre>
|
||||
<pre style={{ wordWrap: 'break-word' }}>
|
||||
<code>{JSON.stringify(output, null, 2)}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
@ -22,7 +22,7 @@ export default function FileInfo({
|
||||
|
||||
return (
|
||||
<div className={styles.info}>
|
||||
{/* <h3 className={styles.url}>{file}</h3> */}
|
||||
<h3 className={styles.url}>{(file as any).url}</h3>
|
||||
<ul>
|
||||
<li>URL confirmed</li>
|
||||
{file.contentLength && <li>{prettySize(+file.contentLength)}</li>}
|
||||
|
@ -27,7 +27,7 @@ export default function FilesInput(props: InputProps): ReactElement {
|
||||
config?.providerUri,
|
||||
newCancelToken()
|
||||
)
|
||||
checkedFile && helpers.setValue([checkedFile])
|
||||
checkedFile && helpers.setValue([{ url: fileUrl, ...checkedFile[0] }])
|
||||
} catch (error) {
|
||||
toast.error('Could not fetch file info. Please check URL and try again')
|
||||
console.error(error.message)
|
||||
|
@ -21,13 +21,13 @@ export default function Publisher({
|
||||
minimal?: boolean
|
||||
className?: string
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
// const { accountId } = useWeb3()
|
||||
const isMounted = useIsMounted()
|
||||
const [profile, setProfile] = useState<Profile>()
|
||||
const [name, setName] = useState(accountTruncate(account))
|
||||
const [accountEns, setAccountEns] = useState<string>()
|
||||
|
||||
const showAdd = account === accountId && !profile
|
||||
// const showAdd = account === accountId && !profile
|
||||
|
||||
useEffect(() => {
|
||||
if (!account) return
|
||||
@ -70,7 +70,7 @@ export default function Publisher({
|
||||
<Link href={`/profile/${accountEns || account}`}>
|
||||
<a title="Show profile page.">{name}</a>
|
||||
</Link>
|
||||
{showAdd && <Add />}
|
||||
{/* {showAdd && <Add />} */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
@ -34,6 +34,10 @@
|
||||
border-color: var(--font-color-heading);
|
||||
}
|
||||
|
||||
.tab[aria-disabled='true'] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.tabContent {
|
||||
padding: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
@ -2,9 +2,10 @@ import React, { ReactElement, ReactNode } from 'react'
|
||||
import { Tab, Tabs as ReactTabs, TabList, TabPanel } from 'react-tabs'
|
||||
import styles from './Tabs.module.css'
|
||||
|
||||
interface TabsItem {
|
||||
export interface TabsItem {
|
||||
title: string
|
||||
content: ReactNode
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export default function Tabs({
|
||||
@ -29,6 +30,7 @@ export default function Tabs({
|
||||
className={styles.tab}
|
||||
key={item.title}
|
||||
onClick={handleTabChange ? () => handleTabChange(item.title) : null}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.title}
|
||||
</Tab>
|
||||
|
@ -3,38 +3,39 @@ import styles from './AlgorithmDatasetsListForCompute.module.css'
|
||||
import { getAlgorithmDatasetsForCompute } from '@utils/aquarius'
|
||||
import { AssetSelectionAsset } from '@shared/FormFields/AssetSelection'
|
||||
import AssetComputeList from '@shared/AssetList/AssetComputeList'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { getServiceByName } from '@utils/ddo'
|
||||
|
||||
export default function AlgorithmDatasetsListForCompute({
|
||||
algorithmDid,
|
||||
dataset
|
||||
ddo,
|
||||
algorithmDid
|
||||
}: {
|
||||
ddo: Asset
|
||||
algorithmDid: string
|
||||
dataset: Asset
|
||||
}): ReactElement {
|
||||
const { ddo } = useAsset()
|
||||
const [datasetsForCompute, setDatasetsForCompute] =
|
||||
useState<AssetSelectionAsset[]>()
|
||||
const newCancelToken = useCancelToken()
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo) return
|
||||
|
||||
async function getDatasetsAllowedForCompute() {
|
||||
const isCompute = Boolean(getServiceByName(dataset, 'compute'))
|
||||
const isCompute = Boolean(getServiceByName(ddo, 'compute'))
|
||||
const datasetComputeService = getServiceByName(
|
||||
dataset,
|
||||
ddo,
|
||||
isCompute ? 'compute' : 'access'
|
||||
)
|
||||
const datasets = await getAlgorithmDatasetsForCompute(
|
||||
algorithmDid,
|
||||
datasetComputeService?.serviceEndpoint,
|
||||
dataset?.chainId,
|
||||
ddo?.chainId,
|
||||
newCancelToken()
|
||||
)
|
||||
setDatasetsForCompute(datasets)
|
||||
}
|
||||
ddo.metadata.type === 'algorithm' && getDatasetsAllowedForCompute()
|
||||
}, [ddo.metadata.type])
|
||||
}, [ddo?.metadata?.type])
|
||||
|
||||
return (
|
||||
<div className={styles.datasetsContainer}>
|
||||
|
@ -160,7 +160,7 @@ export default function FormStartCompute({
|
||||
}
|
||||
hasPreviousOrder={hasPreviousOrder}
|
||||
hasDatatoken={hasDatatoken}
|
||||
dtSymbol={ddo.dataTokenInfo.symbol}
|
||||
dtSymbol={ddo?.dataTokenInfo?.symbol}
|
||||
dtBalance={dtBalance}
|
||||
datasetLowPoolLiquidity={datasetLowPoolLiquidity}
|
||||
assetTimeout={assetTimeout}
|
||||
|
@ -37,12 +37,16 @@ import { SortTermOptions } from '../../../../@types/aquarius/SearchQuery'
|
||||
import { FileMetadata } from '@utils/provider'
|
||||
|
||||
export default function Compute({
|
||||
ddo,
|
||||
price,
|
||||
dtBalance,
|
||||
file,
|
||||
fileIsLoading,
|
||||
isConsumable,
|
||||
consumableFeedback
|
||||
}: {
|
||||
ddo: Asset
|
||||
price: BestPrice
|
||||
dtBalance: string
|
||||
file: FileMetadata
|
||||
fileIsLoading?: boolean
|
||||
@ -51,8 +55,7 @@ export default function Compute({
|
||||
}): ReactElement {
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean, account } = useOcean()
|
||||
const { price, ddo } = useAsset()
|
||||
const { ocean } = useOcean()
|
||||
const { buyDT, pricingError, pricingStepText } = usePricing()
|
||||
const [isJobStarting, setIsJobStarting] = useState(false)
|
||||
const [error, setError] = useState<string>()
|
||||
@ -399,10 +402,7 @@ export default function Compute({
|
||||
text="This algorithm has been set to private by the publisher and can't be downloaded. You can run it against any allowed data sets though!"
|
||||
state="info"
|
||||
/>
|
||||
<AlgorithmDatasetsListForCompute
|
||||
algorithmDid={ddo.id}
|
||||
dataset={ddo}
|
||||
/>
|
||||
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} ddo={ddo} />
|
||||
</>
|
||||
) : (
|
||||
<Formik
|
||||
@ -423,7 +423,7 @@ export default function Compute({
|
||||
hasDatatoken={hasDatatoken}
|
||||
dtBalance={dtBalance}
|
||||
datasetLowPoolLiquidity={!isConsumablePrice}
|
||||
assetType={ddo.metadata.type}
|
||||
assetType={ddo?.metadata.type}
|
||||
assetTimeout={datasetTimeout}
|
||||
hasPreviousOrderSelectedComputeAsset={hasPreviousAlgorithmOrder}
|
||||
hasDatatokenSelectedComputeAsset={hasAlgoAssetDatatoken}
|
||||
@ -448,7 +448,7 @@ export default function Compute({
|
||||
<SuccessConfetti success="Your job started successfully! Watch the progress below or on your profile." />
|
||||
)}
|
||||
</footer>
|
||||
{accountId && (
|
||||
{accountId && price?.datatoken && (
|
||||
<AssetActionHistoryTable title="Your Compute Jobs">
|
||||
<ComputeJobs minimal />
|
||||
</AssetActionHistoryTable>
|
||||
|
@ -8,7 +8,6 @@ import { gql } from 'urql'
|
||||
import { fetchData, getQueryContext } from '@utils/subgraph'
|
||||
import { OrdersData } from '../../../@types/apollo/OrdersData'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { useOcean } from '@context/Ocean'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { usePricing } from '@hooks/usePricing'
|
||||
import { useConsume } from '@hooks/useConsume'
|
||||
@ -35,6 +34,7 @@ const previousOrderQuery = gql`
|
||||
|
||||
export default function Consume({
|
||||
ddo,
|
||||
price,
|
||||
file,
|
||||
isBalanceSufficient,
|
||||
dtBalance,
|
||||
@ -43,6 +43,7 @@ export default function Consume({
|
||||
consumableFeedback
|
||||
}: {
|
||||
ddo: Asset
|
||||
price: BestPrice
|
||||
file: FileMetadata
|
||||
isBalanceSufficient: boolean
|
||||
dtBalance: string
|
||||
@ -51,11 +52,10 @@ export default function Consume({
|
||||
consumableFeedback?: string
|
||||
}): ReactElement {
|
||||
const { accountId } = useWeb3()
|
||||
const { ocean } = useOcean()
|
||||
const { appConfig } = useSiteMetadata()
|
||||
const [hasPreviousOrder, setHasPreviousOrder] = useState(false)
|
||||
const [previousOrderId, setPreviousOrderId] = useState<string>()
|
||||
const { isInPurgatory, price, isAssetNetwork } = useAsset()
|
||||
const { isInPurgatory, isAssetNetwork } = useAsset()
|
||||
const { buyDT, pricingStepText, pricingError, pricingIsLoading } =
|
||||
usePricing()
|
||||
const { consumeStepText, consume, consumeError, isLoading } = useConsume()
|
||||
@ -105,6 +105,8 @@ export default function Consume({
|
||||
}, [data, assetTimeout, accountId, isAssetNetwork])
|
||||
|
||||
useEffect(() => {
|
||||
if (!ddo) return
|
||||
|
||||
const { timeout } = ddo.services[0]
|
||||
setAssetTimeout(`${timeout}`)
|
||||
}, [ddo])
|
||||
@ -125,8 +127,7 @@ export default function Consume({
|
||||
if (!accountId) return
|
||||
setIsDisabled(
|
||||
!isConsumable ||
|
||||
((!ocean ||
|
||||
!isBalanceSufficient ||
|
||||
((!isBalanceSufficient ||
|
||||
!isAssetNetwork ||
|
||||
typeof consumeStepText !== 'undefined' ||
|
||||
pricingIsLoading ||
|
||||
@ -135,7 +136,6 @@ export default function Consume({
|
||||
!hasDatatoken)
|
||||
)
|
||||
}, [
|
||||
ocean,
|
||||
hasPreviousOrder,
|
||||
isBalanceSufficient,
|
||||
isAssetNetwork,
|
||||
@ -182,7 +182,7 @@ export default function Consume({
|
||||
datasetLowPoolLiquidity={!isConsumablePrice}
|
||||
onClick={handleConsume}
|
||||
assetTimeout={secondsToString(parseInt(assetTimeout))}
|
||||
assetType={ddo?.metadata.type}
|
||||
assetType={ddo?.metadata?.type}
|
||||
stepText={consumeStepText || pricingStepText}
|
||||
isLoading={pricingIsLoading || isLoading}
|
||||
priceType={price?.type}
|
||||
@ -203,8 +203,8 @@ export default function Consume({
|
||||
{!isInPurgatory && <PurchaseButton />}
|
||||
</div>
|
||||
</div>
|
||||
{ddo.metadata.type === 'algorithm' && (
|
||||
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} dataset={ddo} />
|
||||
{ddo?.metadata?.type === 'algorithm' && (
|
||||
<AlgorithmDatasetsListForCompute algorithmDid={ddo.id} ddo={ddo} />
|
||||
)}
|
||||
</aside>
|
||||
)
|
||||
|
@ -2,7 +2,7 @@ import React, { ReactElement, useState, useEffect } from 'react'
|
||||
import Compute from './Compute'
|
||||
import Consume from './Consume'
|
||||
import { Logger } from '@oceanprotocol/lib'
|
||||
import Tabs from '@shared/atoms/Tabs'
|
||||
import Tabs, { TabsItem } from '@shared/atoms/Tabs'
|
||||
import { compareAsBN } from '@utils/numbers'
|
||||
import Pool from './Pool'
|
||||
import Trade from './Trade'
|
||||
@ -15,11 +15,20 @@ import { getOceanConfig } from '@utils/ocean'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { useIsMounted } from '@hooks/useIsMounted'
|
||||
import styles from './index.module.css'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { FormPublishData } from 'src/components/Publish/_types'
|
||||
|
||||
export default function AssetActions(): ReactElement {
|
||||
export default function AssetActions({
|
||||
ddo,
|
||||
price
|
||||
}: {
|
||||
ddo: Asset
|
||||
price: BestPrice
|
||||
}): ReactElement {
|
||||
const { accountId, balance } = useWeb3()
|
||||
const { ocean, account } = useOcean()
|
||||
const { price, ddo, isAssetNetwork } = useAsset()
|
||||
const { isAssetNetwork } = useAsset()
|
||||
const { values } = useFormikContext<FormPublishData>()
|
||||
|
||||
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
|
||||
const [dtBalance, setDtBalance] = useState<string>()
|
||||
@ -51,25 +60,30 @@ export default function AssetActions(): ReactElement {
|
||||
// }, [accountId, isAssetNetwork, ddo, ocean])
|
||||
|
||||
useEffect(() => {
|
||||
const oceanConfig = getOceanConfig(ddo.chainId)
|
||||
const oceanConfig = getOceanConfig(ddo?.chainId)
|
||||
if (!oceanConfig) return
|
||||
|
||||
async function initFileInfo() {
|
||||
setFileIsLoading(true)
|
||||
|
||||
const asset = values?.services?.[0].files?.[0].url || ddo.id
|
||||
const providerUrl =
|
||||
values?.services[0].providerUrl || oceanConfig.providerUri
|
||||
|
||||
try {
|
||||
const fileInfoResponse = await getFileInfo(
|
||||
ddo.id,
|
||||
asset,
|
||||
oceanConfig.providerUri,
|
||||
newCancelToken()
|
||||
)
|
||||
fileInfoResponse && setFileMetadata(fileInfoResponse[0])
|
||||
isMounted() && setFileIsLoading(false)
|
||||
setFileIsLoading(false)
|
||||
} catch (error) {
|
||||
Logger.error(error.message)
|
||||
}
|
||||
}
|
||||
initFileInfo()
|
||||
}, [ddo, isMounted, newCancelToken])
|
||||
}, [ddo, isMounted, newCancelToken, values?.services])
|
||||
|
||||
// Get and set user DT balance
|
||||
useEffect(() => {
|
||||
@ -104,6 +118,8 @@ export default function AssetActions(): ReactElement {
|
||||
|
||||
const UseContent = isCompute ? (
|
||||
<Compute
|
||||
ddo={ddo}
|
||||
price={price}
|
||||
dtBalance={dtBalance}
|
||||
file={fileMetadata}
|
||||
fileIsLoading={fileIsLoading}
|
||||
@ -113,6 +129,7 @@ export default function AssetActions(): ReactElement {
|
||||
) : (
|
||||
<Consume
|
||||
ddo={ddo}
|
||||
price={price}
|
||||
dtBalance={dtBalance}
|
||||
isBalanceSufficient={isBalanceSufficient}
|
||||
file={fileMetadata}
|
||||
@ -122,22 +139,24 @@ export default function AssetActions(): ReactElement {
|
||||
/>
|
||||
)
|
||||
|
||||
const tabs = [
|
||||
const tabs: TabsItem[] = [
|
||||
{
|
||||
title: 'Use',
|
||||
content: UseContent
|
||||
}
|
||||
]
|
||||
|
||||
price?.type === 'pool' &&
|
||||
price?.type === 'dynamic' &&
|
||||
tabs.push(
|
||||
{
|
||||
title: 'Pool',
|
||||
content: <Pool />
|
||||
content: <Pool />,
|
||||
disabled: !price.datatoken
|
||||
},
|
||||
{
|
||||
title: 'Trade',
|
||||
content: <Trade />
|
||||
content: <Trade />,
|
||||
disabled: !price.datatoken
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -68,14 +68,14 @@ export default function EditHistory(): ReactElement {
|
||||
<ul className={styles.history}>
|
||||
{receipts?.map((receipt) => (
|
||||
<li key={receipt.id} className={styles.item}>
|
||||
<ExplorerLink networkId={ddo.chainId} path={`/tx/${receipt.tx}`}>
|
||||
<ExplorerLink networkId={ddo?.chainId} path={`/tx/${receipt.tx}`}>
|
||||
edited <Time date={`${receipt.timestamp}`} relative isUnix />
|
||||
</ExplorerLink>
|
||||
</li>
|
||||
))}
|
||||
<li className={styles.item}>
|
||||
<ExplorerLink networkId={ddo.chainId} path={`/tx/${creationTx}`}>
|
||||
published <Time date={ddo.metadata.created} relative />
|
||||
<ExplorerLink networkId={ddo?.chainId} path={`/tx/${creationTx}`}>
|
||||
published <Time date={ddo?.metadata?.created} relative />
|
||||
</ExplorerLink>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -4,27 +4,28 @@ import styles from './MetaFull.module.css'
|
||||
import Publisher from '@shared/Publisher'
|
||||
import { useAsset } from '@context/Asset'
|
||||
|
||||
export default function MetaFull(): ReactElement {
|
||||
const { ddo, isInPurgatory } = useAsset()
|
||||
const { type, author, algorithm } = ddo?.metadata
|
||||
export default function MetaFull({ ddo }: { ddo: Asset }): ReactElement {
|
||||
const { isInPurgatory } = useAsset()
|
||||
|
||||
function DockerImage() {
|
||||
const { image, tag } = algorithm?.container
|
||||
const { image, tag } = ddo?.metadata?.algorithm?.container
|
||||
return <span>{`${image}:${tag}`}</span>
|
||||
}
|
||||
|
||||
return (
|
||||
return ddo ? (
|
||||
<div className={styles.metaFull}>
|
||||
{!isInPurgatory && <MetaItem title="Data Author" content={author} />}
|
||||
{!isInPurgatory && (
|
||||
<MetaItem title="Data Author" content={ddo?.metadata?.author} />
|
||||
)}
|
||||
<MetaItem
|
||||
title="Owner"
|
||||
content={<Publisher account={ddo?.nft?.owner} />}
|
||||
/>
|
||||
|
||||
{type === 'algorithm' && algorithm && (
|
||||
{ddo?.metadata?.type === 'algorithm' && ddo?.metadata?.algorithm && (
|
||||
<MetaItem title="Docker Image" content={<DockerImage />} />
|
||||
)}
|
||||
<MetaItem title="DID" content={<code>{ddo?.id}</code>} />
|
||||
</div>
|
||||
)
|
||||
) : null
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ import AssetType from '@shared/AssetType'
|
||||
import styles from './MetaMain.module.css'
|
||||
import { getServiceByName } from '@utils/ddo'
|
||||
|
||||
export default function MetaMain(): ReactElement {
|
||||
const { ddo, owner, isAssetNetwork } = useAsset()
|
||||
export default function MetaMain({ ddo }: { ddo: Asset }): ReactElement {
|
||||
const { isAssetNetwork } = useAsset()
|
||||
const { web3ProviderInfo } = useWeb3()
|
||||
|
||||
const isCompute = Boolean(getServiceByName(ddo, 'compute'))
|
||||
@ -18,6 +18,9 @@ export default function MetaMain(): ReactElement {
|
||||
const blockscoutNetworks = [1287, 2021000, 2021001, 44787, 246, 1285]
|
||||
const isBlockscoutExplorer = blockscoutNetworks.includes(ddo?.chainId)
|
||||
|
||||
const dataTokenName = ddo?.dataTokenInfo?.name
|
||||
const dataTokenSymbol = ddo?.dataTokenInfo?.symbol
|
||||
|
||||
return (
|
||||
<aside className={styles.meta}>
|
||||
<header className={styles.asset}>
|
||||
@ -35,16 +38,16 @@ export default function MetaMain(): ReactElement {
|
||||
: `token/${ddo?.services[0].datatokenAddress}`
|
||||
}
|
||||
>
|
||||
{`${ddo?.dataTokenInfo.name} — ${ddo?.dataTokenInfo.symbol}`}
|
||||
{`${dataTokenName} — ${dataTokenSymbol}`}
|
||||
</ExplorerLink>
|
||||
|
||||
{web3ProviderInfo?.name === 'MetaMask' && isAssetNetwork && (
|
||||
<span className={styles.addWrap}>
|
||||
<AddToken
|
||||
address={ddo?.services[0].datatokenAddress}
|
||||
symbol={ddo?.dataTokenInfo.symbol}
|
||||
symbol={(ddo as Asset)?.dataTokenInfo?.symbol}
|
||||
logo="https://raw.githubusercontent.com/oceanprotocol/art/main/logo/datatoken.png"
|
||||
text={`Add ${ddo?.dataTokenInfo.symbol} to wallet`}
|
||||
text={`Add ${(ddo as Asset)?.dataTokenInfo?.symbol} to wallet`}
|
||||
className={styles.add}
|
||||
minimal
|
||||
/>
|
||||
@ -53,7 +56,7 @@ export default function MetaMain(): ReactElement {
|
||||
</header>
|
||||
|
||||
<div className={styles.byline}>
|
||||
Published By <Publisher account={owner} />
|
||||
Published By <Publisher account={(ddo as Asset)?.nft?.owner} />
|
||||
<p>
|
||||
<Time date={ddo?.metadata.created} relative />
|
||||
{ddo?.metadata.created !== ddo?.metadata.updated && (
|
||||
|
@ -3,7 +3,6 @@ import MetaItem from './MetaItem'
|
||||
import styles from './MetaSecondary.module.css'
|
||||
import Tags from '@shared/atoms/Tags'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import { useAsset } from '@context/Asset'
|
||||
|
||||
const SampleButton = ({ url }: { url: string }) => (
|
||||
<Button
|
||||
@ -18,9 +17,7 @@ const SampleButton = ({ url }: { url: string }) => (
|
||||
</Button>
|
||||
)
|
||||
|
||||
export default function MetaSecondary(): ReactElement {
|
||||
const { ddo } = useAsset()
|
||||
|
||||
export default function MetaSecondary({ ddo }: { ddo: Asset }): ReactElement {
|
||||
return (
|
||||
<aside className={styles.metaSecondary}>
|
||||
{ddo?.metadata.links?.length > 0 && (
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import React, { ReactElement } from 'react'
|
||||
import Markdown from '@shared/Markdown'
|
||||
import MetaFull from './MetaFull'
|
||||
import MetaSecondary from './MetaSecondary'
|
||||
@ -7,62 +7,34 @@ import { useUserPreferences } from '@context/UserPreferences'
|
||||
import Bookmark from './Bookmark'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import Alert from '@shared/atoms/Alert'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import Edit from '../AssetActions/Edit'
|
||||
import EditComputeDataset from '../AssetActions/Edit/EditComputeDataset'
|
||||
import DebugOutput from '@shared/DebugOutput'
|
||||
import MetaMain from './MetaMain'
|
||||
import EditHistory from './EditHistory'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import styles from './index.module.css'
|
||||
import NetworkName from '@shared/NetworkName'
|
||||
import content from '../../../../content/purgatory.json'
|
||||
|
||||
export default function AssetContent({ ddo }: { ddo: Asset }): ReactElement {
|
||||
export default function AssetContent({
|
||||
ddo,
|
||||
price
|
||||
}: {
|
||||
ddo: Asset
|
||||
price: BestPrice
|
||||
}): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
const { accountId } = useWeb3()
|
||||
const { price, owner, isInPurgatory, purgatoryData, isAssetNetwork } =
|
||||
useAsset()
|
||||
const [showEdit, setShowEdit] = useState<boolean>()
|
||||
const [isComputeType, setIsComputeType] = useState<boolean>(false)
|
||||
const [showEditCompute, setShowEditCompute] = useState<boolean>()
|
||||
const [isOwner, setIsOwner] = useState(false)
|
||||
const { isInPurgatory, purgatoryData } = useAsset()
|
||||
|
||||
const serviceCompute = ddo.services.filter(
|
||||
(service) => service.type === 'compute'
|
||||
)[0]
|
||||
|
||||
useEffect(() => {
|
||||
if (!accountId || !owner) return
|
||||
|
||||
const isOwner = accountId.toLowerCase() === owner.toLowerCase()
|
||||
setIsOwner(isOwner)
|
||||
setIsComputeType(Boolean(serviceCompute))
|
||||
}, [accountId, price, owner, ddo])
|
||||
|
||||
function handleEditButton() {
|
||||
setShowEdit(true)
|
||||
}
|
||||
|
||||
function handleEditComputeButton() {
|
||||
setShowEditCompute(true)
|
||||
}
|
||||
|
||||
return showEdit ? (
|
||||
<Edit setShowEdit={setShowEdit} isComputeType={isComputeType} />
|
||||
) : showEditCompute ? (
|
||||
<EditComputeDataset setShowEdit={setShowEditCompute} />
|
||||
) : (
|
||||
return (
|
||||
<>
|
||||
<div className={styles.networkWrap}>
|
||||
<NetworkName networkId={ddo.chainId} className={styles.network} />
|
||||
<NetworkName networkId={ddo?.chainId} className={styles.network} />
|
||||
</div>
|
||||
|
||||
<article className={styles.grid}>
|
||||
<div>
|
||||
<div className={styles.content}>
|
||||
<MetaMain />
|
||||
<Bookmark did={ddo.id} />
|
||||
<MetaMain ddo={ddo} />
|
||||
{price?.datatoken && <Bookmark did={ddo?.id} />}
|
||||
|
||||
{isInPurgatory ? (
|
||||
<Alert
|
||||
@ -77,43 +49,49 @@ export default function AssetContent({ ddo }: { ddo: Asset }): ReactElement {
|
||||
className={styles.description}
|
||||
text={ddo?.metadata.description || ''}
|
||||
/>
|
||||
|
||||
<MetaSecondary />
|
||||
|
||||
{isOwner && isAssetNetwork && (
|
||||
<div className={styles.ownerActions}>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleEditButton}
|
||||
>
|
||||
Edit Metadata
|
||||
</Button>
|
||||
{serviceCompute && ddo?.metadata.type === 'dataset' && (
|
||||
<>
|
||||
<span className={styles.separator}>|</span>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleEditComputeButton}
|
||||
>
|
||||
Edit Compute Settings
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<MetaSecondary ddo={ddo} />
|
||||
</>
|
||||
)}
|
||||
|
||||
<MetaFull />
|
||||
<EditHistory />
|
||||
{debug === true && <DebugOutput title="DDO" output={ddo} />}
|
||||
<MetaFull ddo={ddo} />
|
||||
{price?.datatoken && <EditHistory />}
|
||||
{price?.datatoken && debug === true && (
|
||||
<DebugOutput title="DDO" output={ddo} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<AssetActions />
|
||||
<AssetActions ddo={ddo} price={price} />
|
||||
|
||||
{/*
|
||||
TODO: restore edit actions, ideally put edit screens on new page
|
||||
with own URL instead of switching out AssetContent in place.
|
||||
Simple way would be modal usage
|
||||
*/}
|
||||
{/* {isOwner && isAssetNetwork && (
|
||||
<div className={styles.ownerActions}>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleEditButton}
|
||||
>
|
||||
Edit Metadata
|
||||
</Button>
|
||||
{serviceCompute && ddo?.metadata.type === 'dataset' && (
|
||||
<>
|
||||
<span className={styles.separator}>|</span>
|
||||
<Button
|
||||
style="text"
|
||||
size="small"
|
||||
onClick={handleEditComputeButton}
|
||||
>
|
||||
Edit Compute Settings
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)} */}
|
||||
</div>
|
||||
</article>
|
||||
</>
|
||||
|
@ -12,7 +12,7 @@ import { transformComputeFormToServiceComputePrivacy } from '@utils/compute'
|
||||
import { setMinterToDispenser, setMinterToPublisher } from '@utils/freePrice'
|
||||
import Web3Feedback from '@shared/Web3Feedback'
|
||||
import { getInitialValues, validationSchema } from './_constants'
|
||||
import content from '../../../../../content/pages/editComputeDataset.json'
|
||||
import content from '../../../../content/pages/editComputeDataset.json'
|
||||
|
||||
export default function EditComputeDataset({
|
||||
setShowEdit
|
@ -15,7 +15,7 @@ import { publisherTrustedAlgorithm as PublisherTrustedAlgorithm } from '@oceanpr
|
||||
import { useSiteMetadata } from '@hooks/useSiteMetadata'
|
||||
import FormActions from './FormActions'
|
||||
import { useCancelToken } from '@hooks/useCancelToken'
|
||||
import { SortTermOptions } from '../../../../@types/aquarius/SearchQuery'
|
||||
import { SortTermOptions } from '../../../@types/aquarius/SearchQuery'
|
||||
import { getServiceByName } from '@utils/ddo'
|
||||
|
||||
export default function FormEditComputeDataset({
|
@ -4,7 +4,7 @@ import { useOcean } from '@context/Ocean'
|
||||
import Input, { InputProps } from '@shared/FormInput'
|
||||
import FormActions from './FormActions'
|
||||
import styles from './FormEditMetadata.module.css'
|
||||
import { FormPublishData } from '../../../Publish/_types'
|
||||
import { FormPublishData } from '../../Publish/_types'
|
||||
|
||||
// function handleTimeoutCustomOption(
|
||||
// data: FormFieldContent[],
|
@ -1,4 +1,4 @@
|
||||
import { secondsToString } from '@utils/ddo'
|
||||
import { mapTimeoutStringToSeconds, secondsToString } from '@utils/ddo'
|
||||
import { EditableMetadataLinks } from '@oceanprotocol/lib'
|
||||
import * as Yup from 'yup'
|
||||
import { MetadataEditForm } from './_types'
|
||||
@ -16,7 +16,7 @@ export const validationSchema = Yup.object().shape({
|
||||
|
||||
export function getInitialValues(
|
||||
metadata: Metadata,
|
||||
timeout: string,
|
||||
timeout: number,
|
||||
price: number
|
||||
): Partial<MetadataEditForm> {
|
||||
return {
|
@ -3,7 +3,7 @@ import { EditableMetadataLinks } from '@oceanprotocol/lib'
|
||||
export interface MetadataEditForm {
|
||||
name: string
|
||||
description: string
|
||||
timeout: string
|
||||
timeout: number
|
||||
price?: number
|
||||
links?: string | EditableMetadataLinks[]
|
||||
author?: string
|
@ -12,7 +12,7 @@ import { Logger } from '@oceanprotocol/lib'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import { useOcean } from '@context/Ocean'
|
||||
import { setMinterToDispenser, setMinterToPublisher } from '@utils/freePrice'
|
||||
import content from '../../../../../content/pages/edit.json'
|
||||
import content from '../../../../content/pages/edit.json'
|
||||
import { MetadataEditForm } from './_types'
|
||||
|
||||
export default function Edit({
|
@ -6,7 +6,7 @@ import { useAsset } from '@context/Asset'
|
||||
import AssetContent from './AssetContent'
|
||||
|
||||
export default function AssetDetails({ uri }: { uri: string }): ReactElement {
|
||||
const { ddo, title, error, isInPurgatory, loading } = useAsset()
|
||||
const { ddo, title, error, isInPurgatory, loading, price } = useAsset()
|
||||
const [pageTitle, setPageTitle] = useState<string>()
|
||||
|
||||
useEffect(() => {
|
||||
@ -14,13 +14,12 @@ export default function AssetDetails({ uri }: { uri: string }): ReactElement {
|
||||
setPageTitle('Could not retrieve asset')
|
||||
return
|
||||
}
|
||||
|
||||
setPageTitle(isInPurgatory ? '' : title)
|
||||
}, [ddo, error, isInPurgatory, title])
|
||||
|
||||
return ddo && pageTitle !== undefined && !loading ? (
|
||||
<Page title={pageTitle} uri={uri}>
|
||||
<AssetContent ddo={ddo} />
|
||||
<AssetContent ddo={ddo} price={price} />
|
||||
</Page>
|
||||
) : error ? (
|
||||
<Page title={pageTitle} noPageHeader uri={uri}>
|
||||
|
@ -52,7 +52,7 @@ export default function Stats({
|
||||
const accountPoolAdresses: string[] = []
|
||||
const assetsPrices = await getAssetsBestPrices(assets)
|
||||
for (const priceInfo of assetsPrices) {
|
||||
if (priceInfo.price.type === 'pool') {
|
||||
if (priceInfo.price.type === 'dynamic') {
|
||||
accountPoolAdresses.push(priceInfo.price.address.toLowerCase())
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
import React, { FormEvent, ReactElement, Ref, RefObject } from 'react'
|
||||
import { useOcean } from '@context/Ocean'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import styles from './index.module.css'
|
||||
import { FormikContextType, useFormikContext } from 'formik'
|
||||
import { FormPublishData } from '../_types'
|
||||
import { wizardSteps } from '../_constants'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
|
||||
export default function Actions({
|
||||
scrollToRef
|
||||
}: {
|
||||
scrollToRef: RefObject<any>
|
||||
}): ReactElement {
|
||||
const { ocean, account } = useOcean()
|
||||
const { accountId } = useWeb3()
|
||||
const {
|
||||
status,
|
||||
values,
|
||||
@ -21,13 +21,13 @@ export default function Actions({
|
||||
|
||||
function handleNext(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
setFieldValue('stepCurrent', values.user.stepCurrent + 1)
|
||||
setFieldValue('user.stepCurrent', values.user.stepCurrent + 1)
|
||||
scrollToRef.current.scrollIntoView()
|
||||
}
|
||||
|
||||
function handlePrevious(e: FormEvent) {
|
||||
e.preventDefault()
|
||||
setFieldValue('stepCurrent', values.user.stepCurrent - 1)
|
||||
setFieldValue('user.stepCurrent', values.user.stepCurrent - 1)
|
||||
scrollToRef.current.scrollIntoView()
|
||||
}
|
||||
|
||||
@ -42,11 +42,7 @@ export default function Actions({
|
||||
Continue
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
style="primary"
|
||||
disabled={!ocean || !account || !isValid}
|
||||
>
|
||||
<Button type="submit" style="primary" disabled={!accountId || !isValid}>
|
||||
Submit
|
||||
</Button>
|
||||
)}
|
||||
|
@ -1,36 +0,0 @@
|
||||
import React, { ReactElement } from 'react'
|
||||
import DebugOutput from '@shared/DebugOutput'
|
||||
import styles from './index.module.css'
|
||||
// import { transformPublishFormToMetadata } from '@utils/metadata'
|
||||
import { FormPublishData } from './_types'
|
||||
import { useFormikContext } from 'formik'
|
||||
|
||||
export default function Debug(): ReactElement {
|
||||
const { values } = useFormikContext<FormPublishData>()
|
||||
const ddo = {
|
||||
'@context': 'https://w3id.org/did/v1'
|
||||
// dataTokenInfo: {
|
||||
// ...values.dataTokenOptions
|
||||
// },
|
||||
// service: [
|
||||
// {
|
||||
// index: 0,
|
||||
// type: 'metadata',
|
||||
// attributes: { ...transformPublishFormToMetadata(values) }
|
||||
// },
|
||||
// {
|
||||
// index: 1,
|
||||
// type: values.access,
|
||||
// serviceEndpoint: values.providerUri,
|
||||
// attributes: {}
|
||||
// }
|
||||
// ]
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.grid}>
|
||||
<DebugOutput title="Collected Form Values" output={values} />
|
||||
<DebugOutput title="Transformed DDO Values" output={ddo} />
|
||||
</div>
|
||||
)
|
||||
}
|
6
src/components/Publish/Debug/index.module.css
Normal file
6
src/components/Publish/Debug/index.module.css
Normal file
@ -0,0 +1,6 @@
|
||||
.debug {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: 1fr 1fr;
|
||||
word-break: break-all;
|
||||
}
|
26
src/components/Publish/Debug/index.tsx
Normal file
26
src/components/Publish/Debug/index.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import DebugOutput from '@shared/DebugOutput'
|
||||
import { FormPublishData } from '../_types'
|
||||
import { useFormikContext } from 'formik'
|
||||
import { transformPublishFormToDdo } from '../_utils'
|
||||
import styles from './index.module.css'
|
||||
|
||||
export default function Debug(): ReactElement {
|
||||
const { values } = useFormikContext<FormPublishData>()
|
||||
const [ddo, setDdo] = useState<DDO>()
|
||||
|
||||
useEffect(() => {
|
||||
async function makeDdo() {
|
||||
const ddo = await transformPublishFormToDdo(values)
|
||||
setDdo(ddo)
|
||||
}
|
||||
makeDdo()
|
||||
}, [values])
|
||||
|
||||
return (
|
||||
<div className={styles.debug}>
|
||||
<DebugOutput title="Collected Form Values" output={values} />
|
||||
<DebugOutput title="Transformed DDO Values" output={ddo} />
|
||||
</div>
|
||||
)
|
||||
}
|
@ -12,32 +12,26 @@ export default function Navigation(): ReactElement {
|
||||
setFieldValue('user.stepCurrent', step)
|
||||
}
|
||||
|
||||
console.log(errors)
|
||||
const isSuccessMetadata = errors.metadata === undefined
|
||||
const isSuccessServices = errors.services === undefined
|
||||
|
||||
return (
|
||||
<nav className={styles.navigation}>
|
||||
<ol>
|
||||
{wizardSteps.map((step) => {
|
||||
const isSuccessMetadata = errors.metadata === undefined
|
||||
const isSuccessServices = errors.services === undefined
|
||||
|
||||
return (
|
||||
<li
|
||||
key={step.title}
|
||||
onClick={() => handleStepClick(step.step)}
|
||||
// TODO: add success class for all steps
|
||||
className={`${
|
||||
values.user.stepCurrent === step.step ? styles.current : null
|
||||
} ${
|
||||
step.step === 1 && isSuccessMetadata ? styles.success : null
|
||||
} ${
|
||||
step.step === 2 && isSuccessServices ? styles.success : null
|
||||
}`}
|
||||
>
|
||||
{step.title}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
{wizardSteps.map((step) => (
|
||||
<li
|
||||
key={step.title}
|
||||
onClick={() => handleStepClick(step.step)}
|
||||
// TODO: add success class for all steps
|
||||
className={`${
|
||||
values.user.stepCurrent === step.step ? styles.current : null
|
||||
} ${step.step === 1 && isSuccessMetadata ? styles.success : null} ${
|
||||
step.step === 2 && isSuccessServices ? styles.success : null
|
||||
}`}
|
||||
>
|
||||
{step.title}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</nav>
|
||||
)
|
||||
|
@ -2,23 +2,15 @@
|
||||
font-size: var(--font-size-small);
|
||||
margin-top: calc(var(--spacer) / 2);
|
||||
margin-bottom: var(--spacer);
|
||||
margin-left: calc(var(--spacer) / -4);
|
||||
margin-right: calc(var(--spacer) / -4);
|
||||
}
|
||||
|
||||
.preview header {
|
||||
margin-bottom: var(--spacer);
|
||||
}
|
||||
|
||||
.metaFull {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
||||
}
|
||||
|
||||
.metaAlgorithm {
|
||||
display: grid;
|
||||
gap: var(--spacer);
|
||||
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
||||
margin-bottom: var(--spacer);
|
||||
@media (min-width: 60rem) {
|
||||
.preview {
|
||||
margin-left: calc(var(--spacer) * -3);
|
||||
margin-right: calc(var(--spacer) * -3);
|
||||
}
|
||||
}
|
||||
|
||||
.previewTitle {
|
||||
@ -28,33 +20,6 @@
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.datatoken {
|
||||
margin-bottom: 0;
|
||||
color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.preview [class*='MetaItem-module--metaItem'] h3 {
|
||||
margin-bottom: calc(var(--spacer) / 12);
|
||||
}
|
||||
|
||||
.description {
|
||||
position: relative;
|
||||
margin-top: calc(var(--spacer) / 4);
|
||||
}
|
||||
|
||||
.toggle {
|
||||
position: absolute;
|
||||
bottom: 0.15rem;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.asset {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 4fr;
|
||||
align-items: center;
|
||||
margin-bottom: calc(var(--spacer) / 2);
|
||||
.assetTitle {
|
||||
margin-bottom: calc(var(--spacer) / 1.5);
|
||||
}
|
||||
|
@ -1,181 +1,39 @@
|
||||
import React, { FormEvent, ReactElement, useState } from 'react'
|
||||
import Markdown from '@shared/Markdown'
|
||||
import Tags from '@shared/atoms/Tags'
|
||||
import MetaItem from '../../Asset/AssetContent/MetaItem'
|
||||
import FileIcon from '@shared/FileIcon'
|
||||
import Button from '@shared/atoms/Button'
|
||||
import NetworkName from '@shared/NetworkName'
|
||||
import { useWeb3 } from '@context/Web3'
|
||||
import React, { ReactElement, useEffect, useState } from 'react'
|
||||
import styles from './index.module.css'
|
||||
import Web3Feedback from '@shared/Web3Feedback'
|
||||
import { useAsset } from '@context/Asset'
|
||||
import { FormPublishData } from '../_types'
|
||||
import { useFormikContext } from 'formik'
|
||||
|
||||
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<FormPublishData> }) {
|
||||
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') ||
|
||||
key.includes('dockerImage') ||
|
||||
key.includes('algorithmPrivacy') ||
|
||||
value === undefined
|
||||
)
|
||||
)
|
||||
.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>
|
||||
)
|
||||
}
|
||||
import AssetContent from 'src/components/Asset/AssetContent'
|
||||
import { transformPublishFormToDdo } from '../_utils'
|
||||
|
||||
export default function Preview(): ReactElement {
|
||||
const { networkId } = useWeb3()
|
||||
const { isAssetNetwork } = useAsset()
|
||||
const [ddo, setDdo] = useState<Asset>()
|
||||
const [price, setPrice] = useState<BestPrice>()
|
||||
const { values } = useFormikContext<FormPublishData>()
|
||||
|
||||
return (
|
||||
<div className={styles.preview}>
|
||||
<h2 className={styles.previewTitle}>Preview</h2>
|
||||
{/* <header>
|
||||
{networkId && <NetworkName networkId={networkId} />}
|
||||
{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} />}
|
||||
useEffect(() => {
|
||||
async function makeDdo() {
|
||||
const ddo = await transformPublishFormToDdo(values)
|
||||
setDdo(ddo as Asset)
|
||||
|
||||
<div className={styles.asset}>
|
||||
{values.files?.length > 0 && typeof values.files !== 'string' && (
|
||||
<FileIcon
|
||||
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} />
|
||||
{isAssetNetwork === false && (
|
||||
<Web3Feedback isAssetNetwork={isAssetNetwork} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function MetadataAlgorithmPreview({
|
||||
values
|
||||
}: {
|
||||
values: Partial<FormPublishData>
|
||||
}): ReactElement {
|
||||
const { networkId } = useWeb3()
|
||||
// dummy BestPrice to trigger certain AssetActions
|
||||
const price: BestPrice = {
|
||||
type: values.pricing.type,
|
||||
address: '0x...',
|
||||
value: values.pricing.price,
|
||||
pools: [],
|
||||
oceanSymbol: 'OCEAN'
|
||||
}
|
||||
setPrice(price)
|
||||
}
|
||||
makeDdo()
|
||||
}, [values])
|
||||
|
||||
return (
|
||||
<div className={styles.preview}>
|
||||
<h2 className={styles.previewTitle}>Preview</h2>
|
||||
<header>
|
||||
{networkId && <NetworkName networkId={networkId} />}
|
||||
{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' && (
|
||||
<FileIcon
|
||||
file={values.files[0] as FileMetadata}
|
||||
className={styles.file}
|
||||
small
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{values.tags && <Tags items={transformTags(values.tags)} />}
|
||||
</header>
|
||||
<div className={styles.metaAlgorithm}>
|
||||
{values.dockerImage && (
|
||||
<MetaItem
|
||||
key="dockerImage"
|
||||
title="Docker Image"
|
||||
content={values.dockerImage}
|
||||
/>
|
||||
)}
|
||||
{values.algorithmPrivacy && (
|
||||
<MetaItem
|
||||
key="privateAlgorithm"
|
||||
title="Private Algorithm"
|
||||
content="Yes"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<MetaFull values={values} /> */}
|
||||
<h3 className={styles.assetTitle}>{values.metadata.name}</h3>
|
||||
<AssetContent ddo={ddo} price={price} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -23,13 +23,13 @@ export default function PricingFields(): ReactElement {
|
||||
function handleTabChange(tabName: string) {
|
||||
const type = tabName.toLowerCase()
|
||||
setFieldValue('pricing.type', type)
|
||||
type === 'fixed' && setFieldValue('pricing.amountDataToken', 1000)
|
||||
type === 'dynamic' && setFieldValue('pricing.amountDataToken', 1000)
|
||||
type === 'free' && price < 1 && setFieldValue('pricing.price', 1)
|
||||
}
|
||||
|
||||
// Always update everything when price value changes
|
||||
useEffect(() => {
|
||||
if (type === 'fixed' || type === 'free') return
|
||||
if (type === 'dynamic' || type === 'free') return
|
||||
|
||||
const amountDataToken =
|
||||
isValidNumber(amountOcean) &&
|
||||
@ -70,36 +70,8 @@ export default function PricingFields(): ReactElement {
|
||||
<Tabs
|
||||
items={tabs}
|
||||
handleTabChange={handleTabChange}
|
||||
defaultIndex={type === 'fixed' ? 0 : type === 'dynamic' ? 1 : 2}
|
||||
defaultIndex={type === 'dynamic' ? 1 : type === 'free' ? 2 : 0}
|
||||
className={styles.pricing}
|
||||
/>
|
||||
)
|
||||
|
||||
// async function handleCreatePricing(values: PriceOptions) {
|
||||
// try {
|
||||
// const priceOptions = {
|
||||
// ...values,
|
||||
// // swapFee is tricky: to get 0.1% you need to send 0.001 as value
|
||||
// swapFee: `${values.swapFee / 100}`
|
||||
// }
|
||||
|
||||
// const tx = await createPricing(priceOptions, ddo)
|
||||
|
||||
// // Pricing failed
|
||||
// if (!tx || pricingError) {
|
||||
// toast.error(pricingError || 'Price creation failed.')
|
||||
// Logger.error(pricingError || 'Price creation failed.')
|
||||
// return
|
||||
// }
|
||||
|
||||
// // Pricing succeeded
|
||||
// setSuccess(
|
||||
// `🎉 Successfully created a ${values.type} price. 🎉 Reload the page to get all updates.`
|
||||
// )
|
||||
// Logger.log(`Transaction: ${tx}`)
|
||||
// } catch (error) {
|
||||
// toast.error(error.message)
|
||||
// Logger.error(error.message)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -76,6 +76,8 @@ export const initialValues: FormPublishData = {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: conditional validation
|
||||
// e.g. when algo is selected, Docker image is required
|
||||
const validationMetadata = {
|
||||
type: Yup.string()
|
||||
.matches(/dataset|algorithm/g, { excludeEmptyString: true })
|
||||
|
@ -3,7 +3,12 @@ import { NftOptions } from '@utils/nft'
|
||||
import { ReactElement } from 'react'
|
||||
|
||||
export interface FormPublishService {
|
||||
files: string[]
|
||||
files: {
|
||||
url: string
|
||||
valid: boolean
|
||||
contentLength: string
|
||||
contentType: string
|
||||
}[]
|
||||
timeout: string
|
||||
dataTokenOptions: DataTokenOptions
|
||||
access: 'Download' | 'Compute' | string
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { mapTimeoutStringToSeconds } from '@utils/ddo'
|
||||
import { getEncryptedFileUrls } from '@utils/provider'
|
||||
import { sha256 } from 'js-sha256'
|
||||
import slugify from 'slugify'
|
||||
@ -27,12 +28,14 @@ function transformTags(value: string): string[] {
|
||||
|
||||
export async function transformPublishFormToDdo(
|
||||
values: FormPublishData,
|
||||
datatokenAddress: string,
|
||||
nftAddress: string
|
||||
// Those 2 are only passed during actual publishing process
|
||||
// so we can always assume if they are not passed, we are on preview.
|
||||
datatokenAddress?: string,
|
||||
nftAddress?: string
|
||||
): Promise<DDO> {
|
||||
const { metadata, services } = values
|
||||
const { chainId, accountId } = values.user
|
||||
const did = sha256(`${nftAddress}${chainId}`)
|
||||
const { metadata, services, user } = values
|
||||
const { chainId, accountId } = user
|
||||
const did = nftAddress ? `0x${sha256(`${nftAddress}${chainId}`)}` : '0x...'
|
||||
const currentTime = dateToStringNoMS(new Date())
|
||||
const {
|
||||
type,
|
||||
@ -48,12 +51,12 @@ export async function transformPublishFormToDdo(
|
||||
} = metadata
|
||||
const { access, files, providerUrl, timeout } = services[0]
|
||||
|
||||
const filesEncrypted = await getEncryptedFileUrls(
|
||||
files as string[],
|
||||
providerUrl,
|
||||
did,
|
||||
accountId
|
||||
)
|
||||
const filesTransformed = files?.length && files[0].valid && [...files[0].url]
|
||||
|
||||
const filesEncrypted =
|
||||
files?.length &&
|
||||
files[0].valid &&
|
||||
(await getEncryptedFileUrls(filesTransformed, providerUrl, did, accountId))
|
||||
|
||||
const newMetadata: Metadata = {
|
||||
created: currentTime,
|
||||
@ -70,13 +73,13 @@ export async function transformPublishFormToDdo(
|
||||
},
|
||||
...(type === 'algorithm' && {
|
||||
algorithm: {
|
||||
language: getUrlFileExtension(files[0]),
|
||||
language: files?.length ? getUrlFileExtension(filesTransformed[0]) : '',
|
||||
version: '0.1',
|
||||
container: {
|
||||
entrypoint: dockerImageCustomEntrypoint,
|
||||
image: dockerImageCustom,
|
||||
tag: dockerImageCustomTag,
|
||||
checksum: '' // how to get? Is it user input?
|
||||
checksum: '' // TODO: how to get? Is it user input?
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -87,7 +90,7 @@ export async function transformPublishFormToDdo(
|
||||
files: filesEncrypted,
|
||||
datatokenAddress,
|
||||
serviceEndpoint: providerUrl,
|
||||
timeout,
|
||||
timeout: mapTimeoutStringToSeconds(timeout),
|
||||
...(access === 'compute' && {
|
||||
compute: {
|
||||
namespace: 'ocean-compute',
|
||||
@ -110,7 +113,17 @@ export async function transformPublishFormToDdo(
|
||||
version: '4.0.0',
|
||||
chainId,
|
||||
metadata: newMetadata,
|
||||
services: [newService]
|
||||
services: [newService],
|
||||
// only added for DDO preview, reflecting Asset response
|
||||
...(!datatokenAddress && {
|
||||
dataTokenInfo: {
|
||||
name: values.services[0].dataTokenOptions.name,
|
||||
symbol: values.services[0].dataTokenOptions.symbol
|
||||
},
|
||||
nft: {
|
||||
owner: accountId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return newDdo
|
||||
|
@ -16,7 +16,9 @@ import { Steps } from './Steps'
|
||||
import { FormPublishData } from './_types'
|
||||
import { sha256 } from 'js-sha256'
|
||||
import { generateNftCreateData } from '@utils/nft'
|
||||
import { useUserPreferences } from '@context/UserPreferences'
|
||||
|
||||
// TODO: restore FormikPersist, add back clear form action
|
||||
const formName = 'ocean-publish-form'
|
||||
|
||||
export default function PublishPage({
|
||||
@ -24,6 +26,7 @@ export default function PublishPage({
|
||||
}: {
|
||||
content: { title: string; description: string; warning: string }
|
||||
}): ReactElement {
|
||||
const { debug } = useUserPreferences()
|
||||
const { accountId, chainId } = useWeb3()
|
||||
const { isInPurgatory, purgatoryData } = useAccountPurgatory(accountId)
|
||||
// const { publish, publishError, isLoading, publishStepText } = usePublish()
|
||||
@ -39,6 +42,11 @@ export default function PublishPage({
|
||||
// const nftOptions = values.metadata.nft
|
||||
// const nftCreateData = generateNftCreateData(nftOptions)
|
||||
// const ercParams = {}
|
||||
// const priceOptions = {
|
||||
// ...values,
|
||||
// // swapFee is tricky: to get 0.1% you need to send 0.001 as value
|
||||
// swapFee: `${values.swapFee / 100}`
|
||||
// }
|
||||
// const txMint = await createNftWithErc(accountId, nftCreateData)
|
||||
// const { nftAddress, datatokenAddress } = txMint.logs[0].args
|
||||
//
|
||||
@ -137,7 +145,7 @@ export default function PublishPage({
|
||||
<Steps />
|
||||
<Actions scrollToRef={scrollToRef} />
|
||||
</Form>
|
||||
<Debug />
|
||||
{debug && <Debug />}
|
||||
</>
|
||||
</Formik>
|
||||
)}
|
||||
|
Loading…
Reference in New Issue
Block a user