Skip to content

Commit 00da1b0

Browse files
author
Alan Shaw
committed
feat: add react
License: MIT Signed-off-by: Alan Shaw <[email protected]>
1 parent 57a1d70 commit 00da1b0

File tree

8 files changed

+280
-230
lines changed

8 files changed

+280
-230
lines changed

src/App.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 })
20+
.then(cid => setRootCid(cid))
21+
}, [files, chunker, strategy, maxChildren, layerRepeat])
22+
23+
const onFileChange = file => {
24+
const fileReader = new FileReader()
25+
fileReader.onload = async e => {
26+
const content = Buffer.from(e.target.result)
27+
setFiles(files.concat({ path: file.name, content }))
28+
}
29+
fileReader.readAsArrayBuffer(file)
30+
}
31+
32+
return (
33+
<div className='avenir flex flex-column h-100'>
34+
<div className='flex-none'>
35+
<Header />
36+
</div>
37+
<div className='flex-none'>
38+
<Controls
39+
onFileChange={onFileChange}
40+
chunker={chunker}
41+
onChunkerChange={setChunker}
42+
strategy={strategy}
43+
onStrategyChange={setStrategy}
44+
maxChildren={maxChildren}
45+
onMaxChildrenChange={setMaxChildren}
46+
layerRepeat={layerRepeat}
47+
onLayerRepeatChange={setLayerRepeat} />
48+
</div>
49+
<div className='flex-auto'>
50+
<Dag rootCid={rootCid} />
51+
</div>
52+
</div>
53+
)
54+
}

src/Controls.js

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

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+
}

src/index.html

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,11 @@
22
<html lang="en" dir="ltr" class="h-100">
33
<head>
44
<meta charset="utf-8">
5-
<title>Add files to IPFS</title>
5+
<title>DAG builder visualization</title>
66
<link rel="stylesheet" href="index.css">
77
</head>
8-
<body class="avenir flex flex-column h-100">
9-
<header class="flex-none flex items-center pa3 bg-navy">
10-
<a href="https://ipfs.io" title="home" class="w-50">
11-
<img src="https://ipfs.io/images/ipfs-logo.svg" style="height: 50px" />
12-
</a>
13-
<h1 class="w-50 ma0 tr f3 fw2 montserrat aqua">DAG builder</h1>
14-
</header>
15-
<main></main>
16-
<!--section class="flex-none pa3 bg-white">
17-
<input type="file" id="file" style="visibility:hidden" class="input avenir" />
18-
<select id="chunker">
19-
<option value="size-32">32 byte chunks</option>
20-
<option value="size-512" selected>512 byte chunks</option>
21-
<option value="size-1024">1,024 byte chunks</option>
22-
<option value="size-16384">16,384 byte chunks</option>
23-
<option value="size-262144">26,2144 byte chunks</option>
24-
</select>
25-
<select id="strategy">
26-
<option value="balanced" selected>Balanced DAG</option>
27-
<option value="trickle">Trickle DAG</option>
28-
<option value="flat">Flat DAG</option>
29-
</select>
30-
<select id="max-children">
31-
<option value="11" selected>11 children max</option>
32-
<option value="44">44 children max</option>
33-
<option value="174">174 children max</option>
34-
</select>
35-
<select id="layer-repeat" style="visibility:hidden">
36-
<option value="1">1 layer repeat</option>
37-
<option value="4" selected>4 layer repeats</option>
38-
<option value="16">16 layer repeats</option>
39-
</select>
40-
</section>
41-
<div class="flex-auto" id="root" style="background: pink;"></div-->
8+
<body class="h-100">
9+
<div id="root" class="h-100"></div>
4210
<script src="./index.js" ></script>
4311
</body>
4412
</html>

0 commit comments

Comments
 (0)