init network switcher component

This commit is contained in:
Max Berman 2019-12-16 11:44:14 +01:00
parent 70a3339f8b
commit 8023e30061
10 changed files with 423 additions and 374 deletions

View File

@ -1,43 +1,49 @@
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": [
"./tsconfig.json",
"./client/tsconfig.json",
"./server/tsconfig.json",
"./cypress/tsconfig.json"
]
},
"extends": [
"oceanprotocol",
"oceanprotocol/react",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier/react",
"prettier/standard",
"prettier/@typescript-eslint",
"plugin:cypress/recommended"
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": [
"./tsconfig.json",
"./client/tsconfig.json",
"./server/tsconfig.json",
"./cypress/tsconfig.json"
]
},
"extends": [
"oceanprotocol",
"oceanprotocol/react",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier/react",
"prettier/standard",
"prettier/@typescript-eslint",
"plugin:cypress/recommended"
],
"plugins": ["@typescript-eslint", "prettier", "cypress"],
"rules": {
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/member-delimiter-style": [
"error",
{ "multiline": { "delimiter": "none" } }
],
"plugins": ["@typescript-eslint", "prettier", "cypress"],
"rules": {
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/member-delimiter-style": [
"error",
{ "multiline": { "delimiter": "none" } }
],
"@typescript-eslint/no-explicit-any": "off"
},
"env": {
"es6": true,
"browser": true,
"node": true,
"jest": true,
"cypress/globals": true
},
"settings": {
"react": {
"version": "16.10"
}
"@typescript-eslint/no-explicit-any": "off",
"react/no-unescaped-entities": 0,
"react/self-closing-comp": 0,
"react/void-dom-elements-no-children": 0,
"react/jsx-indent": 0,
"react/jsx-indent-props": 0,
"react/jsx-max-props-per-line": 0
},
"env": {
"es6": true,
"browser": true,
"node": true,
"jest": true,
"cypress/globals": true
},
"settings": {
"react": {
"version": "16.10"
}
}
}

View File

@ -1,5 +1,6 @@
{
"semi": false,
"singleQuote": true,
"trailingComma": "none"
}
"trailingComma": "none",
"tabWidth": 2
}

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -5,33 +5,36 @@ import Footer from './components/organisms/Footer'
import Spinner from './components/atoms/Spinner'
import { User } from './context'
import Routes from './Routes'
import {commonsNetwork} from './components/molecules/NetworkSwitcher'
import './styles/global.scss'
import styles from './App.module.scss'
console.log(commonsNetwork)
export default class App extends Component {
public render() {
return (
<div className={styles.app}>
<Router>
<>
<Header />
public render() {
return (
<div className={styles.app}>
<Router>
<>
<Header />
<main className={styles.main}>
{this.context.isLoading ? (
<div className={styles.loader}>
<Spinner message={this.context.message} />
</div>
) : (
<Routes />
)}
</main>
<Footer />
</>
</Router>
</div>
)
}
<main className={styles.main}>
{this.context.isLoading ? (
<div className={styles.loader}>
<Spinner message={this.context.message} />
</div>
) : (
<Routes />
)}
</main>
<Footer />
</>
</Router>
</div>
)
}
}
App.contextType = User

View File

@ -0,0 +1,12 @@
import { CONNECTIONS } from '../../config'
/* TEMP NETWORK SWITCHER */
const urlParams = new URLSearchParams(window.location.search)
const network = urlParams.get('network') || 'pacific'
const idx = Object.keys(CONNECTIONS).indexOf(network)
const commonsNetwork = Object.values(CONNECTIONS)[idx]
export { commonsNetwork }

View File

@ -3,110 +3,109 @@ import { VersionNumbersState } from '.'
import VersionTableRow from './VersionTableRow'
import styles from './VersionTable.module.scss'
import VersionNumber from './VersionNumber'
import { useParams } from 'react-router-dom'
import {
serviceUri,
nodeUri,
aquariusUri,
brizoUri,
brizoAddress,
secretStoreUri,
faucetUri
serviceUri,
nodeUri,
aquariusUri,
brizoUri,
brizoAddress,
secretStoreUri,
faucetUri,
CONNECTIONS
} from '../../../config'
const commonsConfig = {
serviceUri,
nodeUri,
aquariusUri,
brizoUri,
brizoAddress,
secretStoreUri,
faucetUri
serviceUri,
nodeUri,
aquariusUri,
brizoUri,
brizoAddress,
secretStoreUri,
faucetUri
}
export const VersionTableContracts = ({
contracts,
network,
keeperVersion
contracts,
network,
keeperVersion
}: {
contracts: {
[contractName: string]: string
}
network: string
keeperVersion?: string
contracts: {
[contractName: string]: string
}
network: string
keeperVersion?: string
}) => (
<table>
<tbody>
<tr>
<td>
<strong>Keeper Contracts</strong>
</td>
<td>
<VersionNumber
name="Keeper Contracts"
version={keeperVersion}
/>
</td>
</tr>
{contracts &&
Object.keys(contracts)
// sort alphabetically
.sort((a, b) => a.localeCompare(b))
.map(key => {
const submarineLink = `https://submarine.${
network === 'pacific'
? 'oceanprotocol'
: `${network}.dev-ocean`
}.com/address/${contracts[key]}`
<table>
<tbody>
<tr>
<td>
<strong>Keeper Contracts</strong>
</td>
<td>
<VersionNumber name="Keeper Contracts" version={keeperVersion} />
</td>
</tr>
{contracts &&
Object.keys(contracts)
// sort alphabetically
.sort((a, b) => a.localeCompare(b))
.map(key => {
const submarineLink = `https://submarine.${
network === 'pacific' ? 'oceanprotocol' : `${network}.dev-ocean`
}.com/address/${contracts[key]}`
return (
<tr key={key}>
<td>
<code className={styles.label}>{key}</code>
</td>
<td>
<a href={submarineLink}>
<code>{contracts[key]}</code>
</a>
</td>
</tr>
)
})}
</tbody>
</table>
return (
<tr key={key}>
<td>
<code className={styles.label}>{key}</code>
</td>
<td>
<a href={submarineLink}>
<code>{contracts[key]}</code>
</a>
</td>
</tr>
)
})}
</tbody>
</table>
)
export const VersionTableCommons = () => (
<table>
<tbody>
{Object.entries(commonsConfig).map(([key, value]) => (
<tr key={key}>
<td>
<code className={styles.label}>{key}</code>
</td>
<td>
<code>{value}</code>
</td>
</tr>
))}
</tbody>
</table>
<table>
<tbody>
{Object.entries(commonsConfig).map(([key, value]) => (
<tr key={key}>
<td>
<code className={styles.label}>{key}</code>
</td>
<td>
<code>{value}</code>
</td>
</tr>
))}
</tbody>
</table>
)
const VersionTable = ({ data }: { data: VersionNumbersState }) => {
return (
<div className={styles.tableWrap}>
<table className={styles.table}>
<tbody>
{Object.entries(data)
.filter(([key]) => key !== 'status')
.map(([key, value]) => (
<VersionTableRow key={key} value={value} />
))}
</tbody>
</table>
</div>
)
return (
<div className={styles.tableWrap}>
<table className={styles.table}>
<tbody>
{Object.entries(data)
.filter(([key]) => key !== 'status')
.map(([key, value]) => (
<VersionTableRow key={key} value={value} />
))}
</tbody>
</table>
</div>
)
}
export default VersionTable

View File

@ -2,26 +2,26 @@
// commons-server connection
//
export const serviceUri =
process.env.REACT_APP_SERVICE_URI || 'http://localhost:4000'
process.env.REACT_APP_SERVICE_URI || 'http://localhost:4000'
//
// OCEAN REMOTE CONNECTIONS
//
export const nodeUri =
process.env.REACT_APP_NODE_URI || 'https://pacific.oceanprotocol.com'
process.env.REACT_APP_NODE_URI || 'https://pacific.oceanprotocol.com'
export const aquariusUri =
process.env.REACT_APP_AQUARIUS_URI ||
'https://aquarius.commons.oceanprotocol.com'
process.env.REACT_APP_AQUARIUS_URI ||
'https://aquarius.commons.oceanprotocol.com'
export const brizoUri =
process.env.REACT_APP_BRIZO_URI || 'https://brizo.commons.oceanprotocol.com'
process.env.REACT_APP_BRIZO_URI || 'https://brizo.commons.oceanprotocol.com'
export const brizoAddress =
process.env.REACT_APP_BRIZO_ADDRESS ||
'0x008c25ed3594e094db4592f4115d5fa74c4f41ea'
process.env.REACT_APP_BRIZO_ADDRESS ||
'0x008c25ed3594e094db4592f4115d5fa74c4f41ea'
export const secretStoreUri =
process.env.REACT_APP_SECRET_STORE_URI ||
'https://secret-store.oceanprotocol.com'
process.env.REACT_APP_SECRET_STORE_URI ||
'https://secret-store.oceanprotocol.com'
export const faucetUri =
process.env.REACT_APP_FAUCET_URI || 'https://faucet.oceanprotocol.com'
process.env.REACT_APP_FAUCET_URI || 'https://faucet.oceanprotocol.com'
//
// APP CONFIG
@ -30,13 +30,40 @@ export const verbose = true
export const analyticsId = 'UA-60614729-11'
export const showChannels =
process.env.REACT_APP_SHOW_CHANNELS === 'true' || false
process.env.REACT_APP_SHOW_CHANNELS === 'true' || false
export const allowPricing =
process.env.REACT_APP_ALLOW_PRICING === 'true' || false
process.env.REACT_APP_ALLOW_PRICING === 'true' || false
export const showRequestTokens =
process.env.REACT_APP_SHOW_REQUEST_TOKENS_BUTTON === 'true' || false
process.env.REACT_APP_SHOW_REQUEST_TOKENS_BUTTON === 'true' || false
// https://ipfs.github.io/public-gateway-checker/
export const ipfsGatewayUri =
process.env.REACT_APP_IPFS_GATEWAY_URI || 'https://gateway.ipfs.io'
process.env.REACT_APP_IPFS_GATEWAY_URI || 'https://gateway.ipfs.io'
export const ipfsNodeUri =
process.env.REACT_APP_IPFS_NODE_URI || 'https://ipfs.infura.io:5001'
process.env.REACT_APP_IPFS_NODE_URI || 'https://ipfs.infura.io:5001'
export const CONNECTIONS = {
pacific: {
nodeUri: 'https://pacific.oceanprotocol.com',
aquariusUri: 'https://aquarius.commons.oceanprotocol.com',
brizoUri: 'https://brizo.commons.oceanprotocol.com',
brizoAddress: '0x008c25ed3594e094db4592f4115d5fa74c4f41ea',
secretStoreUri: 'https://secret-store.oceanprotocol.com',
faucetUri: 'https://faucet.oceanprotocol.com'
},
nile: {
nodeUri: 'https://nile.dev-ocean.com',
aquariusUri: 'https://aquarius.nile.dev-ocean.com',
brizoUri: 'https://brizo.nile.dev-ocean.com',
brizoAddress: '0x4aaab179035dc57b35e2ce066919048686f82972',
secretStoreUri: 'https://secret-store.nile.dev-ocean.com',
faucetUri: 'https://faucet.nile.dev-ocean.com'
},
duero: {
nodeUri: 'https://duero.dev-ocean.com',
aquariusUri: 'https://aquarius.duero.dev-ocean.com',
brizoUri: 'https://brizo.duero.dev-ocean.com',
brizoAddress: '0x9d4ed58293f71122ad6a733c1603927a150735d0',
secretStoreUri: 'https://secret-store.duero.dev-ocean.com',
faucetUri: 'https://faucet.duero.dev-ocean.com'
}
}

View File

@ -21,6 +21,7 @@ export { renderToDOM }
renderToDOM()
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA

View File

@ -1,55 +1,58 @@
import { Ocean, Logger } from '@oceanprotocol/squid'
import Web3 from 'web3'
import { User } from './context'
import {
aquariusUri,
brizoUri,
brizoAddress,
faucetUri,
nodeUri,
secretStoreUri,
verbose
} from './config'
export async function provideOcean(web3Provider: Web3) {
const config = {
web3Provider,
nodeUri,
aquariusUri,
brizoUri,
brizoAddress,
faucetUri,
nodeUri,
secretStoreUri,
verbose
} from './config'
export async function provideOcean(web3Provider: Web3) {
const config = {
web3Provider,
nodeUri,
aquariusUri,
brizoUri,
brizoAddress,
secretStoreUri,
verbose
}
const ocean: any = await Ocean.getInstance(config)
return { ocean }
}
const ocean: any = await Ocean.getInstance(config)
return { ocean }
}
//
// Faucet
//
export interface FaucetResponse {
success: boolean
message: string
trxHash?: string
success: boolean
message: string
trxHash?: string
}
export async function requestFromFaucet(account: string) {
try {
const url = `${faucetUri}/faucet`
const response = await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
address: account,
agent: 'commons'
})
})
return response.json()
} catch (error) {
Logger.error('requestFromFaucet', error.message)
}
try {
const url = `${faucetUri}/faucet`
const response = await fetch(url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
address: account,
agent: 'commons'
})
})
return response.json()
} catch (error) {
Logger.error('requestFromFaucet', error.message)
}
}

View File

@ -1,9 +1,9 @@
import React, {
lazy,
Suspense,
FormEvent,
PureComponent,
ChangeEvent
lazy,
Suspense,
FormEvent,
PureComponent,
ChangeEvent
} from 'react'
import axios from 'axios'
import { Logger, File } from '@oceanprotocol/squid'
@ -21,202 +21,196 @@ import Spinner from '../../../components/atoms/Spinner'
const Ipfs = lazy(() => import('./Ipfs'))
export interface FilePublish extends File {
found: boolean // non-standard
found: boolean // non-standard
}
interface FilesProps {
files: File[]
placeholder: string
help?: string
name: string
onChange(
event:
| ChangeEvent<HTMLInputElement>
| FormEvent<HTMLInputElement>
| ChangeEvent<HTMLSelectElement>
| ChangeEvent<HTMLTextAreaElement>
): void
files: File[]
placeholder: string
help?: string
name: string
onChange(
event:
| ChangeEvent<HTMLInputElement>
| FormEvent<HTMLInputElement>
| ChangeEvent<HTMLSelectElement>
| ChangeEvent<HTMLTextAreaElement>
): void
}
interface FilesStates {
isFormShown: boolean
isIpfsFormShown: boolean
isFormShown: boolean
isIpfsFormShown: boolean
}
const buttons = [
{
id: 'url',
title: '+ From URL',
titleActive: '- Cancel'
},
{
id: 'ipfs',
title: '+ Add to IPFS',
titleActive: '- Cancel'
}
{
id: 'url',
title: '+ From URL',
titleActive: '- Cancel'
},
{
id: 'ipfs',
title: '+ Add to IPFS',
titleActive: '- Cancel'
}
]
export default class Files extends PureComponent<FilesProps, FilesStates> {
public state: FilesStates = {
public state: FilesStates = {
isFormShown: false,
isIpfsFormShown: false
}
// for canceling axios requests
public signal = axios.CancelToken.source()
public componentWillUnmount() {
this.signal.cancel()
}
private toggleForm = (e: Event, form: string) => {
e.preventDefault()
this.setState({
isFormShown: form === 'url' ? !this.state.isFormShown : false,
isIpfsFormShown: form === 'ipfs' ? !this.state.isIpfsFormShown : false
})
}
private async getFile(url: string) {
const file: FilePublish = {
url,
contentType: '',
found: false // non-standard
}
try {
const response = await axios({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
url: `${serviceUri}/api/v1/urlcheck`,
data: { url },
cancelToken: this.signal.token
})
const { contentLength, contentType, found } = response.data.result
if (contentLength) file.contentLength = contentLength
if (contentType) {
file.contentType = contentType
file.compression = cleanupContentType(contentType)
}
file.found = found
return file
} catch (error) {
!axios.isCancel(error) && Logger.error(error.message)
}
}
private addFile = async (url: string) => {
// check for duplicate urls
const duplicateFiles = this.props.files.filter(props =>
url.includes(props.url)
)
if (duplicateFiles.length > 0) {
return this.setState({
isFormShown: false,
isIpfsFormShown: false
})
}
// for canceling axios requests
public signal = axios.CancelToken.source()
const file: FilePublish | undefined = await this.getFile(url)
file && this.props.files.push(file)
public componentWillUnmount() {
this.signal.cancel()
const event = {
currentTarget: {
name: 'files',
value: this.props.files
}
}
this.props.onChange(event as any)
private toggleForm = (e: Event, form: string) => {
e.preventDefault()
this.setState({
isFormShown: false,
isIpfsFormShown: false
})
this.setState({
isFormShown: form === 'url' ? !this.state.isFormShown : false,
isIpfsFormShown:
form === 'ipfs' ? !this.state.isIpfsFormShown : false
})
this.forceUpdate()
}
private removeFile = (index: number) => {
this.props.files.splice(index, 1)
const event = {
currentTarget: {
name: 'files',
value: this.props.files
}
}
this.props.onChange(event as any)
this.forceUpdate()
}
private async getFile(url: string) {
const file: FilePublish = {
url,
contentType: '',
found: false // non-standard
}
public render() {
const { files, help, placeholder, name, onChange } = this.props
const { isFormShown, isIpfsFormShown } = this.state
try {
const response = await axios({
method: 'POST',
headers: { 'Content-Type': 'application/json' },
url: `${serviceUri}/api/v1/urlcheck`,
data: { url },
cancelToken: this.signal.token
})
return (
<>
{help && <Help>{help}</Help>}
const { contentLength, contentType, found } = response.data.result
{/* Use hidden input to collect files */}
<input
type="hidden"
name={name}
value={JSON.stringify(files)}
onChange={onChange}
data-testid="files"
/>
if (contentLength) file.contentLength = contentLength
if (contentType) {
file.contentType = contentType
file.compression = cleanupContentType(contentType)
}
file.found = found
return file
} catch (error) {
!axios.isCancel(error) && Logger.error(error.message)
}
}
private addFile = async (url: string) => {
// check for duplicate urls
const duplicateFiles = this.props.files.filter(props =>
url.includes(props.url)
)
if (duplicateFiles.length > 0) {
return this.setState({
isFormShown: false,
isIpfsFormShown: false
})
}
const file: FilePublish | undefined = await this.getFile(url)
file && this.props.files.push(file)
const event = {
currentTarget: {
name: 'files',
value: this.props.files
}
}
this.props.onChange(event as any)
this.setState({
isFormShown: false,
isIpfsFormShown: false
})
this.forceUpdate()
}
private removeFile = (index: number) => {
this.props.files.splice(index, 1)
const event = {
currentTarget: {
name: 'files',
value: this.props.files
}
}
this.props.onChange(event as any)
this.forceUpdate()
}
public render() {
const { files, help, placeholder, name, onChange } = this.props
const { isFormShown, isIpfsFormShown } = this.state
return (
<>
{help && <Help>{help}</Help>}
{/* Use hidden input to collect files */}
<input
type="hidden"
name={name}
value={JSON.stringify(files)}
onChange={onChange}
data-testid="files"
<div className={styles.newItems}>
{files.length > 0 && (
<ul className={styles.itemsList}>
{files.map((item: any, index: number) => (
<Item
key={shortid.generate()}
item={item}
removeFile={() => this.removeFile(index)}
/>
))}
</ul>
)}
<div className={styles.newItems}>
{files.length > 0 && (
<ul className={styles.itemsList}>
{files.map((item: any, index: number) => (
<Item
key={shortid.generate()}
item={item}
removeFile={() => this.removeFile(index)}
/>
))}
</ul>
)}
{buttons.map(button => {
const isActive =
(button.id === 'url' && isFormShown) ||
(button.id === 'ipfs' && isIpfsFormShown)
{buttons.map(button => {
const isActive =
(button.id === 'url' && isFormShown) ||
(button.id === 'ipfs' && isIpfsFormShown)
return (
<Button
key={shortid.generate()}
link
onClick={(e: Event) => this.toggleForm(e, button.id)}
>
{isActive ? button.titleActive : button.title}
</Button>
)
})}
return (
<Button
key={shortid.generate()}
link
onClick={(e: Event) =>
this.toggleForm(e, button.id)
}
>
{isActive ? button.titleActive : button.title}
</Button>
)
})}
{isFormShown && (
<ItemForm placeholder={placeholder} addFile={this.addFile} />
)}
{isFormShown && (
<ItemForm
placeholder={placeholder}
addFile={this.addFile}
/>
)}
{isIpfsFormShown && (
<Suspense fallback={<Spinner message="Loading..." />}>
<Ipfs addFile={this.addFile} />
</Suspense>
)}
</div>
</>
)
}
{isIpfsFormShown && (
<Suspense fallback={<Spinner message="Loading..." />}>
<Ipfs addFile={this.addFile} />
</Suspense>
)}
</div>
</>
)
}
}