Skip to content

Commit 448b61d

Browse files
committedJun 19, 2022
Forked from nft-browser-v2
0 parents  commit 448b61d

22 files changed

+32633
-0
lines changed
 

‎.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules/
2+
build/
3+
4+

‎.on-save.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[
2+
{
3+
"srcDir": "",
4+
"destDir": "",
5+
"files": "**/*.js",
6+
"command": "npm run lint"
7+
}
8+
]

‎LICENSE.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Copyright 2022 Chris Troutner
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4+
5+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software
6+
7+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

‎README.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# nft-browser-v2
2+
3+
This is a single page app (SPA) based on [react-bootstrap](https://www.npmjs.com/package/react-bootstrap). This app leverages the [Cash Stack](https://cashstack.info) web3 architecture to retrieve NFT information from the Bitcoin Cash (BCH) blockchain and display them.
4+
5+
## License
6+
[MIT](./LICENSE.md)

‎dev-docs/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Developer Docs
2+
3+
This file contains notes taken during software development. These notes may eventually be edited into informaiton that goes into the top-level README, or other documentation.
4+
5+
## Main Features of this App
6+
7+
- [react-bootstrap](https://react-bootstrap.github.io/) is used for general style and layout control.
8+
- An easily customizable waiting modal component can be invoked while waiting for network calls to complete.
9+
- [minimal-slp-wallet](https://www.npmjs.com/package/minimal-slp-wallet) is used to access tokens and BCH on the Bitcoin Cash blockchain.
10+
- A 'server selection' dropdown allows the user to select from an array of redundent back end servers.
11+
- This site is statically compiled, uploaded to Filecoin, and served over IPFS for censorship resistance and version control.

‎package-lock.json

+31,749
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "nft-browser-v2",
3+
"version": "1.0.0",
4+
"dependencies": {
5+
"axios": "0.27.2",
6+
"bootstrap": "5.1.3",
7+
"query-string": "7.1.1",
8+
"react": "17.0.2",
9+
"react-bootstrap": "2.0.0",
10+
"react-dom": "17.0.2",
11+
"react-scripts": "5.0.1",
12+
"use-query-params": "1.2.3"
13+
},
14+
"scripts": {
15+
"start": "react-scripts start",
16+
"build": "react-scripts build",
17+
"test": "react-scripts test",
18+
"eject": "react-scripts eject",
19+
"lint": "standard --env mocha --fix"
20+
},
21+
"eslintConfig": {
22+
"extends": "react-app"
23+
},
24+
"browserslist": {
25+
"production": [
26+
">0.2%",
27+
"not dead",
28+
"not op_mini all"
29+
],
30+
"development": [
31+
"last 1 chrome version",
32+
"last 1 firefox version",
33+
"last 1 safari version"
34+
]
35+
},
36+
"devDependencies": {
37+
"standard": "17.0.0",
38+
"web3.storage": "4.2.0"
39+
}
40+
}

‎public/index.html

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>React-Bootstrap CodeSandbox Starter</title>
7+
</head>
8+
<body>
9+
<noscript>You need to enable JavaScript to run this app.</noscript>
10+
<div id="root"></div>
11+
</body>
12+
</html>

‎publish.js

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
Run this script with `npm run pub` after running `npm run build`
3+
4+
This script will upload the compiled site to Filecoin, then publish the new
5+
hash to the BCH blockchain. The page at troutsblog.com will automatically
6+
redirect users to the new site.
7+
*/
8+
9+
const FILE_PATH = './build'
10+
11+
const { Web3Storage, getFilesFromPath } = require('web3.storage')
12+
// const BCHJS = require('@psf/bch-js')
13+
// const BchWallet = require('minimal-slp-wallet/index')
14+
// const BchMessageLib = require('bch-message-lib/index')
15+
const fs = require('fs')
16+
17+
async function publish () {
18+
try {
19+
// Get the Filecoin token from the environment variable.
20+
const filecoinToken = process.env.FILECOIN_TOKEN
21+
if (!filecoinToken) {
22+
throw new Error(
23+
'Filecoin token not detected. Get a token from https://web3.storage and save it to the FILECOIN_TOKEN environment variable.'
24+
)
25+
}
26+
27+
// Get the WIF for updating Trout's blog from the environment variable.
28+
// const troutWif = process.env.TROUT_BLOG_WIF
29+
// if (!troutWif) {
30+
// throw new Error(
31+
// 'WIF for troutsblog.com not detect. Add it to the TROUT_BLOG_WIF environment variable.'
32+
// )
33+
// }
34+
35+
// Get a list of all the files to be uploaded.
36+
const fileAry = await getFileList()
37+
// console.log(`fileAry: ${JSON.stringify(fileAry, null, 2)}`)
38+
39+
// Upload the files to Filecoin.
40+
const cid = await uploadToFilecoin(fileAry, filecoinToken)
41+
// const cid = 'bafybeibgekzxiqr7irgs26g5pw5sv6xtvfiihccyagpmn7anzl6ffb2xc4'
42+
console.log('Content added with CID:', cid)
43+
console.log(`https://${cid}.ipfs.dweb.link/`)
44+
45+
// Initialize libraries for working with BCH blockchain.
46+
// const bchjs = new BCHJS()
47+
// const wallet = new BchWallet(troutWif, {
48+
// interface: 'consumer-api',
49+
// })
50+
// await wallet.walletInfoPromise
51+
// const bchMsg = new BchMessageLib({ wallet })
52+
//
53+
// // Publish the CID to the BCH blockchain.
54+
// const hex = await bchMsg.memo.memoPush(cid, 'IPFS UPDATE')
55+
//
56+
// // const txid = await bchjs.RawTransactions.sendRawTransaction(hex)
57+
// // Broadcast the transaction to the network.
58+
// const txid = await wallet.ar.sendTx(hex)
59+
// console.log(`BCH blockchain updated with new CID. TXID: ${txid}`)
60+
// console.log(`https://blockchair.com/bitcoin-cash/transaction/${txid}`)
61+
} catch (err) {
62+
console.error(err)
63+
}
64+
}
65+
publish()
66+
67+
function getFileList () {
68+
const fileAry = []
69+
70+
return new Promise((resolve, reject) => {
71+
fs.readdir(FILE_PATH, (err, files) => {
72+
if (err) return reject(err)
73+
74+
files.forEach(file => {
75+
// console.log(file)
76+
fileAry.push(`${FILE_PATH}/${file}`)
77+
})
78+
79+
return resolve(fileAry)
80+
})
81+
})
82+
}
83+
84+
async function uploadToFilecoin (fileAry, token) {
85+
const storage = new Web3Storage({ token })
86+
87+
const files = []
88+
for (let i = 0; i < fileAry.length; i++) {
89+
const thisPath = fileAry[i]
90+
// console.log('thisPath: ', thisPath)
91+
92+
const pathFiles = await getFilesFromPath(thisPath)
93+
// console.log('pathFiles: ', pathFiles)
94+
95+
files.push(...pathFiles)
96+
}
97+
98+
console.log(`Uploading ${files.length} files. Please wait...`)
99+
const cid = await storage.put(files)
100+
101+
return cid
102+
}

‎src/App.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.header {
2+
text-align: center;
3+
}

‎src/App.js

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
This is an SPA that displays information about NFTs on the BCH blockchain.
3+
*/
4+
5+
// Global npm libraries
6+
import React from 'react'
7+
import { Container, Row, Col } from 'react-bootstrap'
8+
import { useQueryParam, StringParam } from 'use-query-params'
9+
10+
// Local libraries
11+
import './App.css'
12+
import LoadScripts from './components/load-scripts'
13+
import NFTs from './components/nfts'
14+
import WaitingModal from './components/waiting-modal'
15+
import AsyncLoad from './services/async-load'
16+
import ServerSelect from './components/servers'
17+
import Footer from './components/footer'
18+
19+
// Token ID for Trout's NFTs
20+
const groupTokenId = '030563ddd65772d8e9b79b825529ed53c7d27037507b57c528788612b4911107'
21+
22+
// Default restURL for a back-end server.
23+
let serverURL = 'https://free-bch.fullstack.cash'
24+
25+
class App extends React.Component {
26+
constructor (props) {
27+
super(props)
28+
29+
// Encasulate dependencies
30+
this.asyncLoad = new AsyncLoad()
31+
32+
// Working array for storing modal output.
33+
this.modalBody = []
34+
this.tokenData = {}
35+
36+
this.state = {
37+
walletInitialized: false,
38+
wallet: false,
39+
modalBody: this.modalBody,
40+
hideSpinner: false
41+
}
42+
43+
this.cnt = 0
44+
}
45+
46+
async componentDidMount () {
47+
try {
48+
this.addToModal('Loading minimal-slp-wallet')
49+
50+
await this.asyncLoad.loadWalletLib()
51+
52+
this.addToModal('Initializing wallet')
53+
54+
const wallet = await this.asyncLoad.initWallet(serverURL)
55+
56+
this.addToModal('Getting Group Token Information')
57+
58+
// Get Group Token info
59+
const groupData = await this.asyncLoad.getGroupData(groupTokenId)
60+
// console.log(`groupData: ${JSON.stringify(groupData, null, 2)}`)
61+
62+
this.addToModal('Getting NFT Information')
63+
64+
/// Get NFT child info
65+
const nftData = []
66+
for (let i = 0; i < groupData.nfts.length; i++) {
67+
const tokenData = await this.asyncLoad.getTokenData(groupData.nfts[i])
68+
nftData.push(tokenData)
69+
}
70+
// console.log(`nft data: ${JSON.stringify(nftData, null, 2)}`)
71+
72+
this.tokenData = {
73+
groupData,
74+
nftData
75+
}
76+
77+
this.setState({
78+
wallet,
79+
walletInitialized: true
80+
})
81+
} catch (err) {
82+
this.modalBody = [
83+
`Error: ${err.message}`,
84+
`Try selecting a different back end server using the drop-down menu at the bottom of the app.`
85+
]
86+
87+
this.setState({
88+
modalBody: this.modalBody,
89+
hideSpinner: true
90+
})
91+
}
92+
}
93+
94+
render () {
95+
// console.log('App component rendered. this.state.wallet: ', this.state.wallet)
96+
97+
return (
98+
<>
99+
<GetRestUrl />
100+
<LoadScripts />
101+
{this.state.walletInitialized ? <InitializedView wallet={this.state.wallet} tokens={this.tokenData} /> : <UninitializedView modalBody={this.state.modalBody} hideSpinner={this.state.hideSpinner}/>}
102+
<ServerSelect />
103+
<Footer />
104+
</>
105+
)
106+
}
107+
108+
// Add a new line to the waiting modal.
109+
addToModal (inStr) {
110+
this.modalBody.push(inStr)
111+
112+
this.setState({
113+
modalBody: this.modalBody
114+
})
115+
}
116+
}
117+
118+
// This is rendered *before* the BCH wallet is initialized.
119+
function UninitializedView (props) {
120+
// console.log('UninitializedView props: ', props)
121+
122+
const heading = 'Loading Blockchain Data...'
123+
124+
return (
125+
<Container style={{ backgroundColor: '#ddd' }}>
126+
<Row style={{ padding: '25px' }}>
127+
<Col>
128+
<h1 className='header'>NFT Explorer</h1>
129+
130+
<WaitingModal heading={heading} body={props.modalBody} hideSpinner={props.hideSpinner} />
131+
</Col>
132+
</Row>
133+
</Container>
134+
)
135+
}
136+
137+
// This is rendered *after* the BCH wallet is initialized.
138+
function InitializedView (props) {
139+
return (
140+
<>
141+
<Container style={{ backgroundColor: '#ddd' }}>
142+
<Row style={{ padding: '25px' }}>
143+
<Col>
144+
<h1 className='header'>NFT Explorer</h1>
145+
</Col>
146+
</Row>
147+
</Container>
148+
<NFTs wallet={props.wallet} tokens={props.tokens} />
149+
</>
150+
)
151+
}
152+
153+
function GetRestUrl (props) {
154+
const [restURL] = useQueryParam('restURL', StringParam)
155+
// console.log('restURL: ', restURL)
156+
157+
serverURL = restURL
158+
159+
return (<></>)
160+
}
161+
162+
// function sleep (ms) {
163+
// return new Promise(resolve => setTimeout(resolve, ms))
164+
// }
165+
166+
export default App

‎src/App.test.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react'
2+
import ReactDOM from 'react-dom'
3+
import App from './App'
4+
5+
it('renders without crashing', () => {
6+
const div = document.createElement('div')
7+
ReactDOM.render(<App />, div)
8+
ReactDOM.unmountComponentAtNode(div)
9+
})

‎src/components/footer.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
A footer section for the SPA
3+
*/
4+
5+
// Global npm libraries
6+
import React from 'react'
7+
import { Container, Row, Col } from 'react-bootstrap'
8+
9+
const IPFS_CID = 'bafybeiff7sjpdizarbtjgc3ftefyzndggsx3uxqs7l42ruzcwdkbuld6eq'
10+
11+
class Footer extends React.Component {
12+
render () {
13+
return (
14+
<Container style={{ backgroundColor: '#ddd' }}>
15+
<Row style={{ padding: '25px' }}>
16+
<Col>
17+
<h6>Site Mirrors</h6>
18+
<ul>
19+
<li><a href='https://troutnfts.com' target='_blank' rel='noreferrer'>troutnfts.com</a></li>
20+
<li><a href={`https://${IPFS_CID}.ipfs.dweb.link/`} target='_blank' rel='noreferrer'>Filecoin</a></li>
21+
</ul>
22+
</Col>
23+
24+
<Col />
25+
26+
<Col>
27+
<h6>Source Code</h6>
28+
<ul>
29+
<li><a href='https://github.com/christroutner/nft-browser-v2' target='_blank' rel='noreferrer'>GitHub</a></li>
30+
</ul>
31+
</Col>
32+
</Row>
33+
</Container>
34+
)
35+
}
36+
}
37+
38+
export default Footer

‎src/components/load-scripts.js

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
Load <script> libraries
3+
*/
4+
5+
import useScript from '../hooks/use-script'
6+
7+
function LoadScripts () {
8+
useScript('https://unpkg.com/minimal-slp-wallet')
9+
10+
return true
11+
}
12+
13+
export default LoadScripts

‎src/components/nfts/index.js

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
This is a top level component for displaying the NFTs.
3+
*/
4+
5+
// Global npm libraries
6+
import React from 'react'
7+
import { Container, Row, Col } from 'react-bootstrap'
8+
9+
// Local libraries
10+
import NFTCard from './nft-card'
11+
12+
class NFTs extends React.Component {
13+
constructor (props) {
14+
super(props)
15+
16+
console.log('props passed to NFT component: ', props)
17+
18+
this.groupData = this.props.tokens.groupData
19+
this.nftData = this.props.tokens.nftData
20+
21+
this.state = {
22+
nfts: []
23+
}
24+
}
25+
26+
async componentDidMount () {
27+
// console.log('NFT component didMount(). nftData: ', this.nftData)
28+
29+
const nfts = []
30+
for (let i = 0; i < this.nftData.length; i++) {
31+
nfts.push(<NFTCard key={`nft${i}`} nftData={this.nftData[i]} />)
32+
}
33+
34+
this.setState({
35+
nfts
36+
})
37+
}
38+
39+
render () {
40+
// Load spinner at startup while the wallet is being initialized.
41+
return (
42+
<>
43+
<Container>
44+
<Row>
45+
<Col className="text-break" style={{ textAlign: 'center' }}>
46+
<p>Loading NFTs associated with Group token{' '}
47+
<a
48+
href={`https://token.fullstack.cash/?tokenid=${this.groupData.tokenId}`}
49+
target='_blank'
50+
rel='noopener noreferrer'
51+
>
52+
{this.groupData.tokenId}
53+
</a>
54+
</p>
55+
</Col>
56+
</Row>
57+
<Row><hr /></Row>
58+
{this.state.nfts}
59+
</Container>
60+
</>
61+
)
62+
}
63+
}
64+
65+
export default NFTs

‎src/components/nfts/nft-card.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
This component controls the display of each NFT.
3+
*/
4+
/* eslint-disable */
5+
6+
// Global npm libraries
7+
import React from 'react'
8+
import axios from 'axios'
9+
import { Container, Row, Col, Image } from 'react-bootstrap'
10+
11+
12+
class NFTCard extends React.Component {
13+
constructor (props) {
14+
super(props)
15+
16+
// console.log('NFTCard props: ', props)
17+
18+
this.tokenData = props.nftData
19+
20+
this.state = {
21+
mutableData: {},
22+
immutableData: {}
23+
}
24+
}
25+
26+
render () {
27+
// console.log('Rendering NFT card with this token data: ', this.tokenData)
28+
29+
return (
30+
<Row>
31+
<Col style={{ textAlign: 'center' }}>
32+
<Image className="d-md-none" src={this.tokenData.mutableData.tokenIcon} style={{border: 'black solid 5px', maxWidth: '300px'}} />
33+
<Image className="d-none d-md-block" src={this.tokenData.mutableData.tokenIcon} style={{border: 'black solid 5px'}} />
34+
</Col>
35+
<Col>
36+
<Container>
37+
<Row>
38+
<Col>
39+
<br />
40+
<h3>{this.tokenData.genesisData.name} ({this.tokenData.genesisData.ticker})</h3>
41+
</Col>
42+
</Row>
43+
<Row style={{ textAlign: 'left' }}>
44+
<Col className="text-break">
45+
<b>Token ID:</b> <a href={`https://token.fullstack.cash/?tokenid=${this.tokenData.genesisData.tokenId}`} target="_blank">{this.tokenData.genesisData.tokenId}</a><br />
46+
<b>Description: </b> {this.tokenData.mutableData.description}<br />
47+
<b>Content: </b>
48+
<ul>
49+
<li><a href={this.tokenData.mutableData.content.youtube} target="_blank">YouTube</a></li>
50+
<li><a href={this.tokenData.mutableData.content.rumble} target="_blank">Rumble</a></li>
51+
<li><a href={this.tokenData.mutableData.content.odysee} target="_blank">Odysee</a></li>
52+
<li><a href={this.tokenData.mutableData.content.filecoin} target="_blank">Filecoin</a> (download)</li>
53+
</ul>
54+
</Col>
55+
</Row>
56+
</Container>
57+
</Col>
58+
</Row>
59+
)
60+
}
61+
}
62+
63+
export default NFTCard

‎src/components/servers/index.js

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
This component contains a drop-down form that lets the user select from
3+
a range of Global Back End servers.
4+
*/
5+
6+
// Global npm libraries
7+
import React from 'react'
8+
// import Select from 'react-dropdown-select'
9+
import { Container, Row, Col, Form } from 'react-bootstrap'
10+
11+
// Local libraries
12+
import GistServers from '../../services/gist-servers'
13+
14+
const defaultOptions = [
15+
{ value: 'https://free-bch.fullstack.cash', label: 'https://free-bch.fullstack.cash' },
16+
{ value: 'https://bc01-ca-bch-consumer.fullstackcash.nl', label: 'https://bc01-ca-bch-consumer.fullstackcash.nl' },
17+
{ value: 'https://pdx01-usa-bch-consumer.fullstackcash.nl', label: 'https://pdx01-usa-bch-consumer.fullstackcash.nl' },
18+
{ value: 'https://wa-usa-bch-consumer.fullstackcash.nl', label: 'https://wa-usa-bch-consumer.fullstackcash.nl' }
19+
]
20+
21+
// const defaultOptions = [
22+
// 'https://free-bch.fullstack.cash',
23+
// 'https://bc01-ca-bch-consumer.fullstackcash.nl',
24+
// 'https://pdx01-usa-bch-consumer.fullstackcash.nl',
25+
// 'https://wa-usa-bch-consumer.fullstackcash.nl'
26+
// ]
27+
28+
class ServerSelect extends React.Component {
29+
constructor (props) {
30+
super(props)
31+
32+
// this.wallet = props.wallet
33+
34+
this.state = {
35+
options: defaultOptions
36+
}
37+
}
38+
39+
// async componentDidMount () {
40+
// const servers = await this.getServers()
41+
// // console.log('Server list retrieved from GitHub Gist: ', servers)
42+
//
43+
// this.setState({
44+
// options: servers
45+
// })
46+
// }
47+
48+
// This is called when the a new drop-down item is selected.
49+
selectServer (event) {
50+
console.log('event.target.value: ', event.target.value)
51+
52+
const value = event.target.value
53+
54+
if (!value) return
55+
56+
window.location.href = `/?restURL=${value}`
57+
}
58+
59+
// render() {
60+
// return (
61+
// <div>
62+
// <Select options={this.state.options} onChange={(values) => this.selectServer(values)} />
63+
// <p style={{textAlign: 'center'}}>Having trouble loading the NFTs? If NFTs don't load after a minute, then
64+
// try selecting a different back-end server.</p>
65+
// </div>
66+
// )
67+
// }
68+
69+
render () {
70+
const items = []
71+
for (let i = 0; i < this.state.options.length; i++) {
72+
const thisServer = this.state.options[i]
73+
74+
items.push(<option key={`server-${i}`} value={thisServer.value}>{thisServer.label}</option>)
75+
}
76+
77+
return (
78+
<Container>
79+
<hr />
80+
<Row>
81+
<Col>
82+
<br />
83+
<h5 style={{ textAlign: 'center' }}>
84+
Having trouble loading the NFTs? If NFTs don't load after a minute,
85+
then try selecting a different back-end server.
86+
</h5>
87+
<Form.Select onChange={(values) => this.selectServer(values)}>
88+
<option>Choose a back-end server</option>
89+
{items}
90+
</Form.Select>
91+
<br />
92+
</Col>
93+
</Row>
94+
</Container>
95+
)
96+
}
97+
98+
// Retrieve an array of server URLs from the GitHub Gist.
99+
async getServers () {
100+
const gistLib = new GistServers()
101+
const gistServers = await gistLib.getServerList()
102+
103+
const serversAry = []
104+
105+
for (let i = 0; i < gistServers.length; i++) {
106+
serversAry.push({ value: gistServers[i].url, label: gistServers[i].url })
107+
}
108+
109+
return serversAry
110+
}
111+
}
112+
113+
export default ServerSelect

‎src/components/waiting-modal/index.js

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
This 'Waiting Modal' component displays a spinner animation and a status log.
3+
It's used to inform the user that the app is waiting for something, and to
4+
display progress.
5+
*/
6+
7+
// Global npm libraries
8+
import React, { useState } from 'react'
9+
import { Container, Row, Col, Modal, Spinner } from 'react-bootstrap'
10+
11+
function ModalTemplate (props) {
12+
const [show, setShow] = useState(true)
13+
14+
const handleClose = () => setShow(false)
15+
// const handleShow = () => setShow(true)
16+
17+
return (
18+
<Modal show={show} onHide={handleClose}>
19+
<Modal.Header closeButton>
20+
<Modal.Title>{props.heading}</Modal.Title>
21+
</Modal.Header>
22+
<Modal.Body>
23+
<Container>
24+
<Row>
25+
<Col style={{ textAlign: 'center' }}>
26+
<BodyList body={props.body} />
27+
{props.hideSpinner ? null : <Spinner animation='border' />}
28+
</Col>
29+
</Row>
30+
</Container>
31+
</Modal.Body>
32+
<Modal.Footer />
33+
</Modal>
34+
)
35+
}
36+
37+
function BodyList (props) {
38+
const items = props.body
39+
40+
const listItems = []
41+
42+
// List items
43+
// for(let i=0; i < items.length; i++) {
44+
// listItems.push(<li key={items[i]}>{items[i]}</li>)
45+
// }
46+
// return (
47+
// <ul>
48+
// {listItems}
49+
// </ul>
50+
// )
51+
52+
// Paragraphs
53+
for (let i = 0; i < items.length; i++) {
54+
listItems.push(<p key={items[i]}><code>{items[i]}</code></p>)
55+
}
56+
57+
return (
58+
listItems
59+
)
60+
}
61+
62+
// export default WaitingModal
63+
export default ModalTemplate

‎src/hooks/use-script.js

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { useEffect } from 'react'
2+
3+
const useScript = url => {
4+
useEffect(() => {
5+
const script = document.createElement('script')
6+
7+
script.src = url
8+
script.async = true
9+
10+
document.body.appendChild(script)
11+
12+
return () => {
13+
document.body.removeChild(script)
14+
}
15+
}, [url])
16+
}
17+
18+
export default useScript

‎src/index.js

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react'
2+
import ReactDOM from 'react-dom'
3+
import App from './App'
4+
import { QueryParamProvider } from 'use-query-params'
5+
6+
// Importing the Bootstrap CSS
7+
import 'bootstrap/dist/css/bootstrap.min.css'
8+
9+
ReactDOM.render(
10+
<>
11+
<QueryParamProvider>
12+
<App />
13+
</QueryParamProvider>
14+
</>
15+
, document.getElementById('root'))

‎src/services/async-load.js

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
This library gets data that requires an async wait.
3+
*/
4+
5+
// Global npm libraries
6+
import axios from 'axios'
7+
8+
class AsyncLoad {
9+
constructor () {
10+
this.BchWallet = false
11+
}
12+
13+
// Load the minimal-slp-wallet which comes in as a <script> file and is
14+
// attached to the global 'window' object.
15+
async loadWalletLib () {
16+
do {
17+
if (typeof window !== 'undefined' && window.SlpWallet) {
18+
this.BchWallet = window.SlpWallet
19+
20+
return this.BchWallet
21+
} else {
22+
console.log('Waiting for wallet library to load...')
23+
}
24+
25+
await sleep(1000)
26+
} while (!this.BchWallet)
27+
}
28+
29+
// Initialize the BCH wallet
30+
async initWallet (restURL) {
31+
const options = {
32+
interface: 'consumer-api',
33+
restURL,
34+
noUpdate: true
35+
}
36+
37+
const wallet = new this.BchWallet(null, options)
38+
39+
await wallet.walletInfoPromise
40+
console.log(`mnemonic: ${wallet.walletInfo.mnemonic}`)
41+
42+
this.wallet = wallet
43+
44+
return wallet
45+
}
46+
47+
// Get token data for a given Token ID
48+
async getTokenData (tokenId) {
49+
const tokenData = await this.wallet.getTokenData(tokenId)
50+
51+
// Convert the IPFS CIDs into actual data.
52+
tokenData.immutableData = await this.getIpfsData(tokenData.immutableData)
53+
tokenData.mutableData = await this.getIpfsData(tokenData.mutableData)
54+
55+
return tokenData
56+
}
57+
58+
// Get data about a Group token
59+
async getGroupData (tokenId) {
60+
const tokenData = await this.getTokenData(tokenId)
61+
62+
const groupData = {
63+
immutableData: tokenData.immutableData,
64+
mutableData: tokenData.mutableData,
65+
nfts: tokenData.genesisData.nfts,
66+
tokenId: tokenData.genesisData.tokenId
67+
}
68+
69+
return groupData
70+
}
71+
72+
// Given an IPFS URI, this will download and parse the JSON data.
73+
async getIpfsData (ipfsUri) {
74+
const cid = ipfsUri.slice(7)
75+
76+
const downloadUrl = `https://${cid}.ipfs.dweb.link/data.json`
77+
78+
const response = await axios.get(downloadUrl)
79+
const data = response.data
80+
81+
return data
82+
}
83+
}
84+
85+
function sleep (ms) {
86+
return new Promise(resolve => setTimeout(resolve, ms))
87+
}
88+
89+
export default AsyncLoad

‎src/services/gist-servers.js

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
This library downloads a dynamic list of back-end servers from a GitHub Gist.
3+
*/
4+
5+
const axios = require('axios')
6+
7+
class GistServers {
8+
constructor () {
9+
this.axios = axios
10+
}
11+
12+
// Retrieve a JSON file from a GitHub Gist
13+
async getServerList () {
14+
try {
15+
// https://gist.github.com/christroutner/e818ecdaed6c35075bfc0751bf222258
16+
const gistUrl =
17+
'https://api.github.com/gists/63c5513782181f8b8ea3eb89f7cadeb6'
18+
19+
// Retrieve the gist from github.com.
20+
const result = await this.axios.get(gistUrl)
21+
// console.log('result.data: ', result.data)
22+
23+
// Get the current content of the gist.
24+
const content = result.data.files['psf-consumer-apis.json'].content
25+
// console.log('content: ', content)
26+
27+
// Parse the JSON string into an Object.
28+
const object = JSON.parse(content)
29+
// console.log('object: ', object)
30+
31+
return object.consumerApis
32+
} catch (err) {
33+
console.error('Error in getCRList()')
34+
throw err
35+
}
36+
}
37+
}
38+
39+
export default GistServers

0 commit comments

Comments
 (0)
Please sign in to comment.