mirror of
https://github.com/fullstack-cash/fullstack-gatsby-theme-bch-wallet.git
synced 2026-09-21 16:52:06 -07:00
feat(IPFS): Added IPFS tab to web wallet
This commit is contained in:
Generated
+22893
-35232
File diff suppressed because it is too large
Load Diff
+3
-1
@@ -38,7 +38,9 @@
|
|||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-jdenticon": "0.0.9",
|
"react-jdenticon": "0.0.9",
|
||||||
"react-qr-reader": "^2.2.1",
|
"react-qr-reader": "^2.2.1",
|
||||||
"react-redux": "^7.2.4"
|
"react-redux": "^7.2.4",
|
||||||
|
"@chris.troutner/ipfs": "2.0.2",
|
||||||
|
"ipfs-coord": "^6.6.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "^7.18.0",
|
"eslint": "^7.18.0",
|
||||||
|
|||||||
@@ -0,0 +1,165 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { Content, Row, Col, Inputs, Box } from 'adminlte-2-react'
|
||||||
|
|
||||||
|
import IPFSTabs from './ipfs-tabs'
|
||||||
|
|
||||||
|
import IpfsControl from '../lib/ipfs-control'
|
||||||
|
|
||||||
|
// const BchWallet = typeof window !== 'undefined' ? window.SlpWallet : null
|
||||||
|
|
||||||
|
const { Checkbox } = Inputs
|
||||||
|
|
||||||
|
let _this
|
||||||
|
class IPFS extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {
|
||||||
|
ipfsConnection: false
|
||||||
|
}
|
||||||
|
_this = this
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { ipfsConnection } = _this.state
|
||||||
|
return (
|
||||||
|
<Content>
|
||||||
|
<Row>
|
||||||
|
<Col sm={2} />
|
||||||
|
<Col sm={8}>
|
||||||
|
<Box className='text-center'>
|
||||||
|
<Checkbox
|
||||||
|
value={ipfsConnection} // mark as checked
|
||||||
|
text='Connect to wallet services over IPFS'
|
||||||
|
labelPosition='none'
|
||||||
|
labelXs={0}
|
||||||
|
name='ipfsConnection'
|
||||||
|
onChange={this.handleIpfs}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Col>
|
||||||
|
<Col sm={2} />
|
||||||
|
</Row>
|
||||||
|
{ipfsConnection && <IPFSTabs />}
|
||||||
|
</Content>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleIpfs () {
|
||||||
|
_this.setState(prevState => ({
|
||||||
|
ipfsConnection: !_this.state.ipfsConnection
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
initIPFSControl (bchWallet) {
|
||||||
|
try {
|
||||||
|
const ipfsConfig = {
|
||||||
|
statusLog: _this.onStatusLog,
|
||||||
|
// handleChatLog: _this.onCommandLog
|
||||||
|
handleChatLog: _this.incommingChat,
|
||||||
|
bchWallet: bchWallet || _this.props.bchWallet, // bch wallet instance
|
||||||
|
privateLog: _this.privLogChat
|
||||||
|
}
|
||||||
|
// Retrieve last ipfs control
|
||||||
|
const { data } = _this.props.menuNavigation
|
||||||
|
if (data && data.chatInfo.ipfsControl) {
|
||||||
|
this.ipfsControl = data.chatInfo.ipfsControl
|
||||||
|
} else {
|
||||||
|
// Instantiate a new ipfs control
|
||||||
|
this.ipfsControl = new IpfsControl(ipfsConfig)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a line to the Command terminal
|
||||||
|
onCommandLog (msg) {
|
||||||
|
try {
|
||||||
|
let commandOutput
|
||||||
|
if (!msg) {
|
||||||
|
commandOutput = ''
|
||||||
|
} else {
|
||||||
|
commandOutput = _this.state.commandOutput + ' ' + msg + '\n'
|
||||||
|
}
|
||||||
|
_this.setState({
|
||||||
|
commandOutput
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adds a line to the Status terminal
|
||||||
|
onStatusLog (str) {
|
||||||
|
try {
|
||||||
|
// Update the Status terminal
|
||||||
|
_this.setState({
|
||||||
|
statusOutput: _this.state.statusOutput + ' ' + str + '\n'
|
||||||
|
})
|
||||||
|
|
||||||
|
// If a new peer is found, trigger handleNewPeer()
|
||||||
|
if (str.includes('New peer found:')) {
|
||||||
|
const ipfsId = str.substring(24)
|
||||||
|
_this.handleNewPeer(ipfsId)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle decrypted, private messages and send them to the right terminal.
|
||||||
|
privLogChat (str, from) {
|
||||||
|
try {
|
||||||
|
// console.log(`privLogChat str: ${str}`)
|
||||||
|
// console.log(`privLogChat from: ${from}`)
|
||||||
|
|
||||||
|
const { chatOutputs } = _this.state
|
||||||
|
|
||||||
|
const terminalOut = `peer: ${str}`
|
||||||
|
|
||||||
|
// Asigns the output to the corresponding peer
|
||||||
|
chatOutputs[from].output = chatOutputs[from].output + terminalOut + '\n'
|
||||||
|
|
||||||
|
_this.setState({
|
||||||
|
chatOutputs
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Error in privLogChat():', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle chat messages coming in from the IPFS network.
|
||||||
|
incommingChat (str) {
|
||||||
|
try {
|
||||||
|
const { chatOutputs, connectedPeer } = _this.state
|
||||||
|
// console.log(`connectedPeer: ${JSON.stringify(connectedPeer, null, 2)}`)
|
||||||
|
// console.log(`incommingChat str: ${JSON.stringify(str, null, 2)}`)
|
||||||
|
|
||||||
|
const msg = str.data.data.message
|
||||||
|
const handle = str.data.data.handle
|
||||||
|
const terminalOut = `${handle}: ${msg}`
|
||||||
|
|
||||||
|
if (str.data && str.data.apiName && str.data.apiName.includes('chat')) {
|
||||||
|
// If the message is marked as 'chat' data, then post it to the public
|
||||||
|
// chat terminal.
|
||||||
|
chatOutputs.All.output = chatOutputs.All.output + terminalOut + '\n'
|
||||||
|
} else {
|
||||||
|
// Asigns the output to the corresponding peer
|
||||||
|
chatOutputs[connectedPeer].output =
|
||||||
|
chatOutputs[connectedPeer].output + terminalOut + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
_this.setState({
|
||||||
|
chatOutputs
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err)
|
||||||
|
// Don't throw an error as this is a top-level handler.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IPFS.propTypes = {}
|
||||||
|
|
||||||
|
export default IPFS
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { Tabs, Row, Col, TabContent } from 'adminlte-2-react'
|
||||||
|
|
||||||
|
// let _this
|
||||||
|
class IPFSTabs extends React.Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
this.state = {}
|
||||||
|
// _this = this
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col md={12}>
|
||||||
|
<Tabs defaultActiveKey='tab_1' activeKey='tab_2'>
|
||||||
|
<TabContent title='Tab 1' eventKey='tab_1'>
|
||||||
|
<b>How to use:</b>
|
||||||
|
<p>
|
||||||
|
Exactly like the original bootstrap tabs except you should use
|
||||||
|
the custom wrapper <code>.nav-tabs-custom</code> to achieve this
|
||||||
|
style.
|
||||||
|
</p>
|
||||||
|
A wonderful serenity has taken possession of my entire soul, like
|
||||||
|
these sweet mornings of spring which I enjoy with my whole heart.
|
||||||
|
I am alone, and feel the charm of existence in this spot, which
|
||||||
|
was created for the bliss of souls like mine. I am so happy, my
|
||||||
|
dear friend, so absorbed in the exquisite sense of mere tranquil
|
||||||
|
existence, that I neglect my talents. I should be incapable of
|
||||||
|
drawing a single stroke at the present moment; and yet I feel that
|
||||||
|
I never was a greater artist than now.
|
||||||
|
</TabContent>
|
||||||
|
<TabContent title='Tab 2' eventKey='tab_2'>
|
||||||
|
The European languages are members of the same family. Their
|
||||||
|
separate existence is a myth. For science, music, sport, etc,
|
||||||
|
Europe uses the same vocabulary. The languages only differ in
|
||||||
|
their grammar, their pronunciation and their most common words.
|
||||||
|
Everyone realizes why a new common language would be desirable:
|
||||||
|
one could refuse to pay expensive translators. To achieve this, it
|
||||||
|
would be necessary to have uniform grammar, pronunciation and more
|
||||||
|
common words. If several languages coalesce, the grammar of the
|
||||||
|
resulting language is more simple and regular than that of the
|
||||||
|
individual languages.
|
||||||
|
</TabContent>
|
||||||
|
<TabContent title='Tab 3' eventKey='tab_3'>
|
||||||
|
Lorem Ipsum is simply dummy text of the printing and typesetting
|
||||||
|
industry. Lorem Ipsum has been the industry"s standard dummy
|
||||||
|
text ever since the 1500s, when an unknown printer took a galley
|
||||||
|
of type and scrambled it to make a type specimen book. It has
|
||||||
|
survived not only five centuries, but also the leap into
|
||||||
|
electronic typesetting, remaining essentially unchanged. It was
|
||||||
|
popularised in the 1960s with the release of Letraset sheets
|
||||||
|
containing Lorem Ipsum passages, and more recently with desktop
|
||||||
|
publishing software like Aldus PageMaker including versions of
|
||||||
|
Lorem Ipsum.
|
||||||
|
</TabContent>
|
||||||
|
</Tabs>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IPFSTabs.propTypes = {}
|
||||||
|
|
||||||
|
export default IPFSTabs
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
Handles commands from command terminal.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let _this
|
||||||
|
|
||||||
|
class CommandRouter {
|
||||||
|
constructor (cmdConfig) {
|
||||||
|
if (cmdConfig && cmdConfig.ipfsControl) {
|
||||||
|
this.ipfsControl = cmdConfig.ipfsControl
|
||||||
|
}
|
||||||
|
|
||||||
|
_this = this
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse and route a command to the proper handler.
|
||||||
|
async route (command, appIpfs) {
|
||||||
|
try {
|
||||||
|
// console.log(`command: ${command}`)
|
||||||
|
|
||||||
|
// Split the command into an array of words separated by a space
|
||||||
|
const words = command.toString().split(' ')
|
||||||
|
// console.log(`words: ${JSON.stringify(words, null, 2)}`)
|
||||||
|
|
||||||
|
switch (words[0]) {
|
||||||
|
case 'help':
|
||||||
|
return this.help()
|
||||||
|
case 'list':
|
||||||
|
return await this.list(command, appIpfs)
|
||||||
|
case 'clear':
|
||||||
|
return 'clear'
|
||||||
|
case 'pubsub':
|
||||||
|
return await this.pubsub(command, appIpfs)
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in commandRouter()')
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display the help
|
||||||
|
help () {
|
||||||
|
const msg = `
|
||||||
|
Available commands:
|
||||||
|
- help - this help.
|
||||||
|
- clear - clear the command terminal.
|
||||||
|
- list peers - list all known ipfs-coord peers.
|
||||||
|
- list relays - list all known circuit relays and their state.
|
||||||
|
- pubsub list - list all subscribed pubsub channels.
|
||||||
|
`
|
||||||
|
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
async list (command, appIpfs) {
|
||||||
|
const words = command.toString().split(' ')
|
||||||
|
|
||||||
|
switch (words[1]) {
|
||||||
|
case 'relays':
|
||||||
|
return this.listRelays(_this.ipfsControl)
|
||||||
|
case 'peers':
|
||||||
|
return this.listPeers(_this.ipfsControl)
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async pubsub (command, appIpfs) {
|
||||||
|
const words = command.toString().split(' ')
|
||||||
|
|
||||||
|
switch (words[1]) {
|
||||||
|
case 'list':
|
||||||
|
return this.listPubsubChannels(appIpfs)
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List known ipfs-coord peers.
|
||||||
|
async listPeers (appIpfs) {
|
||||||
|
try {
|
||||||
|
// console.log('appIpfs: ', appIpfs)
|
||||||
|
|
||||||
|
const relays = `Known ipfs-coord peers:\n${JSON.stringify(
|
||||||
|
appIpfs.ipfsCoord.ipfs.peers.state.peers,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
return relays
|
||||||
|
|
||||||
|
// return "test"
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in listPeers(): ', err)
|
||||||
|
return 'Error in listPeers()'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List the relays connected to this IPFS node.
|
||||||
|
async listRelays (appIpfs) {
|
||||||
|
try {
|
||||||
|
// console.log('appIpfs: ', appIpfs)
|
||||||
|
|
||||||
|
const relays = `Known Circuit Relays:\n${JSON.stringify(
|
||||||
|
appIpfs.ipfsCoord.ipfs.cr.state.relays,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
return relays
|
||||||
|
|
||||||
|
// return "test"
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in listRelays(): ', err)
|
||||||
|
return 'Error in listRelays()'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async listPubsubChannels (appIpfs) {
|
||||||
|
try {
|
||||||
|
// console.log('appIpfs: ', appIpfs)
|
||||||
|
|
||||||
|
const channels = await appIpfs.ipfs.pubsub.ls()
|
||||||
|
|
||||||
|
const outStr = `Pubsub Subscriptions:\n${JSON.stringify(
|
||||||
|
channels,
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)}`
|
||||||
|
|
||||||
|
return outStr
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in listPubsubChannels(): ', err)
|
||||||
|
return 'Error in listPubsubChannels()'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CommandRouter
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
/*
|
||||||
|
This library controls the IPFS interface for the app.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
This library contains the logic around the browser-based IPFS full node.
|
||||||
|
*/
|
||||||
|
import IPFS from '@chris.troutner/ipfs'
|
||||||
|
import IpfsCoord from 'ipfs-coord'
|
||||||
|
|
||||||
|
// CHANGE THESE VARIABLES
|
||||||
|
const CHAT_ROOM_NAME = 'psf-ipfs-chat-001'
|
||||||
|
|
||||||
|
// JSON-LD schema used in announcement.
|
||||||
|
// Customize this data for your own app.
|
||||||
|
const name = 'Browser Chat ' + Math.floor(Math.random() * 1000)
|
||||||
|
const announceJsonLd = {
|
||||||
|
'@context': 'https://schema.org/',
|
||||||
|
'@type': 'WebAPI',
|
||||||
|
name: name,
|
||||||
|
description: 'This is a browser-based IPFS node.',
|
||||||
|
documentation: '',
|
||||||
|
provider: {
|
||||||
|
'@type': 'Organization',
|
||||||
|
name: 'Permissionless Software Foundation',
|
||||||
|
url: 'https://PSFoundation.cash'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let _this
|
||||||
|
|
||||||
|
class IpfsControl {
|
||||||
|
constructor (ipfsConfig) {
|
||||||
|
this.statusLog = ipfsConfig.statusLog
|
||||||
|
this.handleChatLog = ipfsConfig.handleChatLog
|
||||||
|
this.wallet = ipfsConfig.bchWallet
|
||||||
|
this.privateLog = ipfsConfig.privateLog
|
||||||
|
|
||||||
|
_this = this
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top level function for controlling the IPFS node. This funciton is called
|
||||||
|
// by the componentDidMount() function of the page.
|
||||||
|
async startIpfs () {
|
||||||
|
try {
|
||||||
|
console.log('Setting up instance of IPFS...')
|
||||||
|
this.statusLog('Setting up instance of IPFS...')
|
||||||
|
|
||||||
|
// Use DHT routing and ipfs.io delegates.
|
||||||
|
const ipfsOptions = {
|
||||||
|
config: {
|
||||||
|
Bootstrap: [],
|
||||||
|
Swarm: {
|
||||||
|
ConnMgr: {
|
||||||
|
HighWater: 30,
|
||||||
|
LowWater: 10
|
||||||
|
},
|
||||||
|
AddrFilters: []
|
||||||
|
},
|
||||||
|
Routing: {
|
||||||
|
Type: 'dhtclient'
|
||||||
|
},
|
||||||
|
preload: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
offline: true
|
||||||
|
},
|
||||||
|
libp2p: {
|
||||||
|
config: {
|
||||||
|
dht: {
|
||||||
|
enabled: true,
|
||||||
|
clientMode: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const ipfsOptions = {
|
||||||
|
// Bootstrap: [],
|
||||||
|
// Swarm: {
|
||||||
|
// ConnMgr: {
|
||||||
|
// HighWater: 30,
|
||||||
|
// LowWater: 10
|
||||||
|
// },
|
||||||
|
// AddrFilters: []
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
this.ipfs = await IPFS.create(ipfsOptions)
|
||||||
|
this.statusLog('IPFS node created.')
|
||||||
|
|
||||||
|
// Set a 'low-power' profile for the IPFS node.
|
||||||
|
await this.ipfs.config.profiles.apply('lowpower')
|
||||||
|
|
||||||
|
// Generate a new wallet.
|
||||||
|
// this.wallet = new BchWallet()
|
||||||
|
// console.log("this.wallet: ", this.wallet);
|
||||||
|
|
||||||
|
if (!this.wallet) {
|
||||||
|
throw new Error('Wallet Not Found.! . Create or import a wallet')
|
||||||
|
}
|
||||||
|
// Wait for the wallet to initialize.
|
||||||
|
await this.wallet.walletInfoPromise
|
||||||
|
|
||||||
|
// Instantiate the IPFS Coordination library.
|
||||||
|
this.ipfsCoord = new IpfsCoord({
|
||||||
|
ipfs: this.ipfs,
|
||||||
|
type: 'browser',
|
||||||
|
statusLog: this.statusLog, // Status log
|
||||||
|
bchjs: this.wallet.bchjs,
|
||||||
|
mnemonic: this.wallet.walletInfo.mnemonic,
|
||||||
|
privateLog: this.privateLog,
|
||||||
|
announceJsonLd
|
||||||
|
})
|
||||||
|
this.statusLog('ipfs-coord library instantiated.')
|
||||||
|
|
||||||
|
// Wait for the coordination stuff to be setup.
|
||||||
|
await this.ipfsCoord.start()
|
||||||
|
|
||||||
|
const nodeConfig = await this.ipfs.config.getAll()
|
||||||
|
console.log(
|
||||||
|
`IPFS node configuration: ${JSON.stringify(nodeConfig, null, 2)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// subscribe to the 'chat' chatroom.
|
||||||
|
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
|
||||||
|
CHAT_ROOM_NAME,
|
||||||
|
this.handleChatLog,
|
||||||
|
this.ipfsCoord.thisNode
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pass the IPFS instance to the window object. Makes it easy to debug IPFS
|
||||||
|
// issues in the browser console.
|
||||||
|
if (typeof window !== 'undefined') window.ipfs = this.ipfs
|
||||||
|
|
||||||
|
// Get this nodes IPFS ID
|
||||||
|
const id = await this.ipfs.id()
|
||||||
|
this.ipfsId = id.id
|
||||||
|
this.statusLog(`This IPFS node ID: ${this.ipfsId}`)
|
||||||
|
|
||||||
|
console.log('IPFS node setup complete.')
|
||||||
|
this.statusLog('IPFS node setup complete.')
|
||||||
|
_this.statusLog(' ')
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in startIpfs(): ', err)
|
||||||
|
this.statusLog(
|
||||||
|
'Error trying to initialize IPFS node! Have you created a wallet?'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This funciton handles incoming chat messages.
|
||||||
|
handleChatMsg (msg) {
|
||||||
|
try {
|
||||||
|
console.log('handleChatMsg msg: ', msg)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in handleChatMsg(): ', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeInfo () {
|
||||||
|
return {
|
||||||
|
ipfsId: this.ipfsId,
|
||||||
|
announceJsonLd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// module.exports = AppIpfs
|
||||||
|
export default IpfsControl
|
||||||
@@ -3,11 +3,15 @@
|
|||||||
// export default MenuComponents
|
// export default MenuComponents
|
||||||
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import Ipfs from './ipfs-tab'
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
key: '',
|
key: 'IPFS',
|
||||||
icon: 'fas-message',
|
icon: 'fas-message',
|
||||||
component: <><p>test</p></>
|
component: (
|
||||||
|
<>
|
||||||
|
<Ipfs />
|
||||||
|
</>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user