From 7c30d974ad4b655139acb640a28d13bb028acd5d Mon Sep 17 00:00:00 2001 From: Norbi Date: Thu, 18 Feb 2021 12:02:12 +0200 Subject: [PATCH] WIP --- .../organisms/AlgorithmContent/index.tsx | 112 ++++++++++ .../templates/PageAlgorithmDetails.tsx | 43 ++++ src/pages/algorithm/index.tsx | 19 ++ src/providers/Algorithm.tsx | 205 ++++++++++++++++++ 4 files changed, 379 insertions(+) create mode 100644 src/components/organisms/AlgorithmContent/index.tsx create mode 100644 src/components/templates/PageAlgorithmDetails.tsx create mode 100644 src/pages/algorithm/index.tsx create mode 100644 src/providers/Algorithm.tsx diff --git a/src/components/organisms/AlgorithmContent/index.tsx b/src/components/organisms/AlgorithmContent/index.tsx new file mode 100644 index 000000000..8891f7131 --- /dev/null +++ b/src/components/organisms/AlgorithmContent/index.tsx @@ -0,0 +1,112 @@ +import React, { ReactElement, useEffect, useState } from 'react' +import { graphql, useStaticQuery } from 'gatsby' +import Markdown from '../../atoms/Markdown' +import MetaFull from '../AssetContent/MetaFull' +import MetaSecondary from '../AssetContent/MetaSecondary' +import styles from './index.module.css' +import AssetActions from '../AssetActions' +import { useUserPreferences } from '../../../providers/UserPreferences' +import Pricing from '../AssetContent/Pricing' +import { useOcean } from '@oceanprotocol/react' +import Bookmark from '../AssetContent/Bookmark' +import { useAsset } from '../../../providers/Asset' +import Alert from '../../atoms/Alert' +import Button from '../../atoms/Button' +import Edit from '../AssetActions/Edit' +import DebugOutput from '../../atoms/DebugOutput' +import MetaMain from '../AssetContent/MetaMain' +// import EditHistory from './EditHistory' + +export interface AlgorithmContentProps { + path?: string +} + +const contentQuery = graphql` + query ContentQuery { + purgatory: allFile(filter: { relativePath: { eq: "purgatory.json" } }) { + edges { + node { + childContentJson { + asset { + title + description + } + } + } + } + } + } +` + +export default function AlgorithmContent( + props: AlgorithmContentProps +): ReactElement { + const data = useStaticQuery(contentQuery) + const content = data.purgatory.edges[0].node.childContentJson.asset + const { debug } = useUserPreferences() + const { accountId } = useOcean() + const { owner, isInPurgatory, purgatoryData } = useAsset() + const [showPricing, setShowPricing] = useState(false) + const [showEdit, setShowEdit] = useState() + const { ddo, price, metadata } = useAsset() + const isOwner = accountId === owner + + useEffect(() => { + if (!price) return + setShowPricing(isOwner && price.address === '') + }, [isOwner, price]) + + function handleEditButton() { + // move user's focus to top of screen + window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }) + setShowEdit(true) + } + + return showEdit ? ( + + ) : ( +
+
+ {showPricing && } +
+ + + + {isInPurgatory ? ( + + ) : ( + <> + + + + + {isOwner && ( +
+ +
+ )} + + )} + + + {/* */} + {debug === true && } +
+
+ +
+ +
+
+ ) +} diff --git a/src/components/templates/PageAlgorithmDetails.tsx b/src/components/templates/PageAlgorithmDetails.tsx new file mode 100644 index 000000000..8151ff8db --- /dev/null +++ b/src/components/templates/PageAlgorithmDetails.tsx @@ -0,0 +1,43 @@ +import React, { useState, useEffect, ReactElement } from 'react' +import { Router } from '@reach/router' +import AssetContent from '../organisms/AssetContent' +import Page from './Page' +import Alert from '../atoms/Alert' +import Loader from '../atoms/Loader' +import { useAlgorithm } from '../../providers/Algorithm' + +export default function PageTemplateAlgorithmDetails({ + uri +}: { + uri: string +}): ReactElement { + const { ddo, title, error, isInPurgatory } = useAlgorithm() + const [pageTitle, setPageTitle] = useState() + + useEffect(() => { + if (!ddo || error) { + setPageTitle('Could not retrieve asset') + return + } + + setPageTitle(isInPurgatory ? '' : title) + }, [ddo, error, isInPurgatory, title]) + + return ddo ? ( + <> + + + + + + + ) : error ? ( + + + + ) : ( + + + + ) +} diff --git a/src/pages/algorithm/index.tsx b/src/pages/algorithm/index.tsx new file mode 100644 index 000000000..99225b603 --- /dev/null +++ b/src/pages/algorithm/index.tsx @@ -0,0 +1,19 @@ +import React, { ReactElement, useEffect, useState } from 'react' +import { PageProps } from 'gatsby' +import PageTemplateAlgorithmDetails from '../../components/templates/PageAlgorithmDetails' +import AlgorithmProvider from '../../providers/Algorithm' + +export default function PageGatsbyAssetDetails(props: PageProps): ReactElement { + const [did, setDid] = useState() + + useEffect(() => { + console.log('did', props.location.pathname.split('/')[2]) + setDid(props.location.pathname.split('/')[2]) + }, [props.location.pathname]) + + return ( + + + + ) +} diff --git a/src/providers/Algorithm.tsx b/src/providers/Algorithm.tsx new file mode 100644 index 000000000..5ceb00069 --- /dev/null +++ b/src/providers/Algorithm.tsx @@ -0,0 +1,205 @@ +import React, { + useContext, + useState, + useEffect, + createContext, + ReactElement, + useCallback, + ReactNode +} from 'react' +import { Logger, DDO, BestPrice } from '@oceanprotocol/lib' +import { PurgatoryData } from '@oceanprotocol/lib/dist/node/ddo/interfaces/PurgatoryData' +import { getDataTokenPrice, useOcean } from '@oceanprotocol/react' +import getAssetPurgatoryData from '../utils/purgatory' +import { ConfigHelperConfig } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper' +import axios, { CancelToken } from 'axios' +import { retrieveDDO } from '../utils/aquarius' +import { MetadataMarket } from '../@types/MetaData' + +interface AlgorithmProviderValue { + isInPurgatory: boolean + purgatoryData: PurgatoryData + ddo: DDO | undefined + did: string | undefined + metadata: MetadataMarket | undefined + title: string | undefined + owner: string | undefined + price: BestPrice | undefined + error?: string + refreshInterval: number + refreshDdo: (token?: CancelToken) => Promise + refreshPrice: () => Promise +} + +const AssetContext = createContext({} as AlgorithmProviderValue) + +const refreshInterval = 10000 // 10 sec. + +function AlgorithmProvider({ + asset, + children +}: { + asset: string | DDO + children: ReactNode +}): ReactElement { + const { ocean, status, config, networkId } = useOcean() + const [isInPurgatory, setIsInPurgatory] = useState(false) + const [purgatoryData, setPurgatoryData] = useState() + const [ddo, setDDO] = useState() + const [did, setDID] = useState() + const [metadata, setMetadata] = useState() + const [title, setTitle] = useState() + const [price, setPrice] = useState() + const [owner, setOwner] = useState() + const [error, setError] = useState() + + const refreshPrice = useCallback(async () => { + if ( + !ddo || + status !== 1 || + networkId !== (config as ConfigHelperConfig).networkId + ) + return + + const newPrice = await getDataTokenPrice( + ocean, + ddo.dataToken, + ddo?.price?.type, + ddo.price.address + ) + setPrice(newPrice) + Logger.log(`Refreshed asset price: ${newPrice?.value}`, newPrice) + }, [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 + // + useEffect(() => { + if (!asset || !config?.metadataCacheUri) return + + const source = axios.CancelToken.source() + let isMounted = true + Logger.log('Init asset, get ddo') + + async function init() { + const ddo = await fetchDdo(source.token) + if (!isMounted) return + Logger.debug('DDO', ddo) + setDDO(ddo) + setDID(asset as string) + } + init() + return () => { + isMounted = false + source.cancel() + } + }, [asset, config?.metadataCacheUri]) + + useEffect(() => { + // Re-fetch price periodically, triggering re-calculation of everything + let isMounted = true + + const interval = setInterval(() => { + if (!isMounted) return + refreshPrice() + }, refreshInterval) + + return () => { + clearInterval(interval) + isMounted = false + } + }, [ddo, networkId, refreshPrice]) + + const setPurgatory = useCallback(async (did: string): Promise => { + if (!did) return + try { + const result = await getAssetPurgatoryData(did) + + if (result?.did !== undefined) { + setIsInPurgatory(true) + setPurgatoryData(result) + return + } + + setIsInPurgatory(false) + } catch (error) { + Logger.error(error) + } + }, []) + + const initMetadata = useCallback( + async (ddo: DDO): Promise => { + if (!ddo) return + + Logger.log('Init metadata') + // Set price & metadata from DDO first + setPrice(ddo.price) + const { attributes } = ddo.findServiceByType('metadata') + setMetadata((attributes as unknown) as MetadataMarket) + setTitle(attributes?.main.name) + setOwner(ddo.publicKey[0].owner) + setIsInPurgatory(ddo.isInPurgatory === 'true') + + await setPurgatory(ddo.id) + await refreshPrice() + }, + [refreshPrice, setPurgatory] + ) + + useEffect(() => { + if (!ddo) return + initMetadata(ddo) + }, [ddo, initMetadata]) + + return ( + + {children} + + ) +} + +// Helper hook to access the provider values +const useAlgorithm = (): AlgorithmProviderValue => useContext(AssetContext) + +export { AlgorithmProvider, useAlgorithm, AlgorithmProviderValue, AssetContext } +export default AlgorithmProvider