Skip to content

Commit e53aa66

Browse files
authored
Merge pull request #4 from ipfs-shipyard/feat/add-react
feat: add react
2 parents 19f9e73 + df4c88a commit e53aa66

File tree

10 files changed

+343
-221
lines changed

10 files changed

+343
-221
lines changed

package-lock.json

Lines changed: 58 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@
99
"ipfs": "^0.35.0",
1010
"ipfs-css": "^0.12.0",
1111
"parcel": "^1.12.3",
12+
"react": "^16.8.6",
13+
"react-dom": "^16.8.6",
1214
"tachyons": "^4.11.1"
1315
},
1416
"devDependencies": {
17+
"babel-eslint": "^10.0.1",
1518
"standard": "^12.0.1"
1619
},
1720
"scripts": {
@@ -33,5 +36,8 @@
3336
"license": "MIT",
3437
"browserslist": [
3538
"last 2 Chrome versions"
36-
]
39+
],
40+
"standard": {
41+
"parser": "babel-eslint"
42+
}
3743
}

src/App.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/* eslint-env browser */
2+
import React, { useState, useEffect } from 'react'
3+
import Header from './Header'
4+
import Controls from './Controls'
5+
import Dag from './Dag'
6+
import { Buffer } from 'ipfs'
7+
import { ipfsAdd } from './lib/ipfs'
8+
9+
export default function App () {
10+
const [files, setFiles] = useState([])
11+
const [chunker, setChunker] = useState('size-512')
12+
const [strategy, setStrategy] = useState('balanced')
13+
const [maxChildren, setMaxChildren] = useState(11)
14+
const [layerRepeat, setLayerRepeat] = useState(4)
15+
const [rootCid, setRootCid] = useState(null)
16+
17+
useEffect(() => {
18+
if (!files.length) return
19+
ipfsAdd({ files, chunker, strategy, maxChildren, layerRepeat }).then(setRootCid)
20+
}, [files, chunker, strategy, maxChildren, layerRepeat])
21+
22+
const onFileChange = file => {
23+
const fileReader = new FileReader()
24+
fileReader.onload = e => {
25+
setFiles(files.concat({ path: file.name, content: Buffer.from(e.target.result) }))
26+
}
27+
fileReader.readAsArrayBuffer(file)
28+
}
29+
30+
return (
31+
<div className='avenir flex flex-column h-100'>
32+
<div className='flex-none'>
33+
<Header />
34+
</div>
35+
<div className='flex-none'>
36+
<Controls
37+
onFileChange={onFileChange}
38+
chunker={chunker}
39+
onChunkerChange={setChunker}
40+
strategy={strategy}
41+
onStrategyChange={setStrategy}
42+
maxChildren={maxChildren}
43+
onMaxChildrenChange={setMaxChildren}
44+
layerRepeat={layerRepeat}
45+
onLayerRepeatChange={setLayerRepeat} />
46+
</div>
47+
<div className='flex-auto'>
48+
<Dag rootCid={rootCid} />
49+
</div>
50+
</div>
51+
)
52+
}

src/Controls.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from 'react'
2+
3+
export default function Controls ({
4+
onFileChange,
5+
chunker,
6+
onChunkerChange,
7+
strategy,
8+
onStrategyChange,
9+
maxChildren,
10+
onMaxChildrenChange,
11+
layerRepeat,
12+
onLayerRepeatChange
13+
}) {
14+
return (
15+
<div className='pa3 bg-white'>
16+
<input type='file' className='input avenir' onChange={e => onFileChange(e.target.files[0])} />
17+
<select value={chunker} onChange={e => onChunkerChange(e.target.value)}>
18+
<option value='size-32'>32 byte chunks</option>
19+
<option value='size-512'>512 byte chunks</option>
20+
<option value='size-1024'>1,024 byte chunks</option>
21+
<option value='size-16384'>16,384 byte chunks</option>
22+
<option value='size-262144'>26,2144 byte chunks</option>
23+
</select>
24+
<select value={strategy} onChange={e => onStrategyChange(e.target.value)}>
25+
<option value='balanced'>Balanced DAG</option>
26+
<option value='trickle'>Trickle DAG</option>
27+
<option value='flat'>Flat DAG</option>
28+
</select>
29+
{['balanced', 'trickle'].includes(strategy) ? (
30+
<select value={maxChildren} onChange={e => onMaxChildrenChange(parseInt(e.target.value))}>
31+
<option value='11'>11 children max</option>
32+
<option value='44'>44 children max</option>
33+
<option value='174'>174 children max</option>
34+
</select>
35+
) : null}
36+
{strategy === 'trickle' ? (
37+
<select value={layerRepeat} onChange={e => onLayerRepeatChange(parseInt(e.target.value))}>
38+
<option value='1'>1 layer repeat</option>
39+
<option value='4'>4 layer repeats</option>
40+
<option value='16'>16 layer repeats</option>
41+
</select>
42+
) : null}
43+
</div>
44+
)
45+
}

src/Dag.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React, { Component, createRef } from 'react'
2+
import cytoscape from 'cytoscape'
3+
import dagre from 'cytoscape-dagre'
4+
import UnixFs from 'ipfs-unixfs'
5+
import { getIpfs } from './lib/ipfs'
6+
import DagGraphOptions from './DagGraphOptions'
7+
8+
cytoscape.use(dagre)
9+
10+
export default class Dag extends Component {
11+
constructor () {
12+
super()
13+
this._graphRoot = createRef()
14+
}
15+
16+
componentDidMount () {
17+
this._updateGraph()
18+
}
19+
20+
componentDidUpdate () {
21+
this._updateGraph()
22+
}
23+
24+
async _updateGraph () {
25+
if (this._graph) this._graph.destroy()
26+
27+
const { rootCid } = this.props
28+
if (!rootCid) return
29+
30+
const nodeMap = await this._getGraphNodes(rootCid)
31+
32+
// ...could have taken a while, did we get a new root node?
33+
if (rootCid !== this.props.rootCid) return
34+
35+
const container = this._graphRoot.current
36+
const elements = Array.from(nodeMap.values())
37+
38+
this._graph = cytoscape({ elements, container, ...DagGraphOptions })
39+
this._graph.layout(DagGraphOptions.layout).run()
40+
}
41+
42+
async _getGraphNodes (cid, nodeMap = new Map()) {
43+
if (nodeMap.get(cid)) return
44+
45+
const ipfs = await getIpfs()
46+
const { value: source } = await ipfs.dag.get(cid)
47+
48+
let nodeData = {}
49+
50+
try {
51+
// it's a unix system?
52+
nodeData = UnixFs.unmarshal(source.data)
53+
} catch (err) {
54+
// dag-pb but not a unixfs.
55+
console.log(err)
56+
}
57+
58+
for (let i = 0; i < source.links.length; i++) {
59+
await this._getGraphNodes(source.links[i].cid.toString(), nodeMap)
60+
}
61+
62+
nodeMap.set(cid, {
63+
group: 'nodes',
64+
data: { id: cid, ...nodeData },
65+
classes: source.links.length ? [] : ['leaf']
66+
})
67+
68+
source.links.forEach(link => {
69+
nodeMap.set(cid + '->' + link.cid, {
70+
group: 'edges',
71+
data: { source: cid, target: link.cid.toString() }
72+
})
73+
})
74+
75+
return nodeMap
76+
}
77+
78+
render () {
79+
return <div ref={this._graphRoot} style={{ background: 'pink' }} className='h-100' />
80+
}
81+
}

src/DagGraphOptions.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
export default {
2+
layout: {
3+
name: 'dagre',
4+
rankSep: 80,
5+
nodeSep: 1
6+
},
7+
style: [
8+
{
9+
selector: 'node',
10+
style: {
11+
shape: 'ellipse',
12+
width: '14px',
13+
height: '14px',
14+
'background-color': '#244e66'
15+
}
16+
},
17+
{
18+
selector: '.leaf',
19+
style: {
20+
shape: 'square',
21+
width: '14px',
22+
height: '14px',
23+
'background-color': '#28CA9F'
24+
}
25+
},
26+
{
27+
selector: 'edge',
28+
style: {
29+
'source-distance-from-node': 3,
30+
'target-distance-from-node': 4,
31+
'curve-style': 'bezier',
32+
'control-point-weight': 0.5,
33+
'width': 1,
34+
'line-color': '#979797',
35+
'line-style': 'dotted',
36+
// 'target-label': 'data(index)',
37+
'font-family': 'Consolas, monaco, monospace',
38+
'font-size': '8px',
39+
'target-text-margin-x': '-5px',
40+
'color': '#ccc',
41+
'target-text-margin-y': '-2px',
42+
'text-halign': 'center',
43+
'text-valign': 'bottom'
44+
}
45+
}
46+
]
47+
}

src/Header.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import React from 'react'
2+
3+
export default function Header () {
4+
return (
5+
<header className='flex items-center pa3 bg-navy'>
6+
<a href='https://ipfs.io' title='home' className='w-50'>
7+
<img src='https://ipfs.io/images/ipfs-logo.svg' style={{ height: 50 }} />
8+
</a>
9+
<h1 className='w-50 ma0 tr f3 fw2 montserrat aqua'>DAG builder</h1>
10+
</header>
11+
)
12+
}

0 commit comments

Comments
 (0)