lotus/lotuspond/front/src/ChainExplorer.js
2019-10-09 09:56:00 +02:00

175 lines
4.8 KiB
JavaScript

import React from 'react';
import {BlockLinks} from "./BlockLink";
import Window from "./Window";
const rows = 32
class ChainExplorer extends React.Component {
fetching = []
constructor(props) {
super(props)
this.update = this.update.bind(this)
this.scroll = this.scroll.bind(this)
this.state = {
follow: true,
at: props.ts.Height,
highest: props.ts,
cache: {[props.ts.Height]: props.ts},
messages: {},
}
}
viewport() {
const base = this.state.at - this.state.at % rows
return Array(rows).fill(0)
.map((_, k) => k + base)
.map(k => k > this.state.at ? k - rows : k)
}
async componentDidMount() {
let msgcache = {}
await this.updateMessages(this.props.ts.Cids, msgcache)
this.setState(prev => ({messages: {...prev.messages, ...msgcache}}))
setInterval(this.update, 1000)
}
async updateMessages(cids, msgcache) {
const msgs = await Promise.all(cids.map(async cid => [cid['/'], await this.props.client.call('Filecoin.ChainGetParentMessages', [cid])]))
msgs.forEach(([cid, msg]) => msgcache[cid] = msg)
}
async update() {
const tipset = await this.props.client.call("Filecoin.ChainHead", [])
if(tipset.Height > this.state.highest.Height) {
let msgcache = {}
await this.updateMessages(tipset.Cids, msgcache)
this.setState(prev => ({highest: tipset, messages: {...prev.messages, ...msgcache}, cache: {...prev.cache, [tipset.Height]: tipset}}))
if(this.state.follow) {
this.setState({at: tipset.Height})
}
}
await this.fetchVisible()
}
async fetch(h, base, cache, msgcache) {
console.log(h, base, cache)
if (this.fetching[h]) {
return
}
this.fetching[h] = true
if (h < 0) {
return
}
if(!base.Blocks) {
console.log("base for H is nll blk", h, base)
return
}
let cids = base.Blocks.map(b => b.Parents)
.reduce((acc, val) => {
let out = {...acc}
val.forEach(c => out[c['/']] = 8)
return out
}, {})
cids = Object.keys(cids).map(k => ({'/': k}))
console.log("parents", cids)
const blocks = await Promise.all(cids.map(cid => this.props.client.call('Filecoin.ChainGetBlock', [cid])))
cache[h] = {
Height: h,
Cids: cids,
Blocks: blocks,
}
await this.updateMessages(cids, msgcache)
return cache[h]
}
async fetchVisible() {
await this.fetchN(this.state.at)
}
async fetchN(top) {
if(!this.state.cache[top]) {
if(top === this.state.highest.Height) {
throw "fetchN broke (tipset not fetched)"
}
let h = top + rows > this.state.highest.Height ? this.state.highest.Height : top + rows
await this.fetchN(h)
}
const tofetch = Array(rows).fill(0).map((_, i) => top - i)
.filter(v => !this.state.cache[v])
let cache = {...this.state.cache}
let msgcache = {...this.state.messages}
await tofetch.reduce(async (prev, next) => {
let prevts = await prev
let newts = await this.fetch(next, prevts, cache, msgcache)
return newts ? newts : prevts
}, Promise.resolve(cache[top]))
this.setState({cache: cache, messages: msgcache})
}
scroll(event) {
if(event.deltaY < 0 && this.state.at > 0) {
this.setState(prev => ({at: prev.at - 1, follow: false}))
}
if(event.deltaY > 0 && this.state.at < this.state.highest.Height) {
this.setState(prev => ({at: prev.at + 1, follow: prev.at + 1 === this.state.highest.Height}))
}
}
render() {
const view = this.viewport()
const content = <div className="ChainExplorer" onWheel={this.scroll}>{view.map(row => {
const base = this.state.at - this.state.at % rows
const className = row === this.state.at ? 'ChainExplorer-at' : (row < base ? 'ChainExplorer-after' : 'ChainExplorer-before')
let info = <span>(fetching)</span>
let h = <i>{row}</i>
if(this.state.cache[row]) {
const ts = this.state.cache[row]
h = ts.Height
let msgc = -1
if(ts.Cids[0] && this.state.messages[ts.Cids[0]['/']]) { // TODO: get from all blks
msgc = this.state.messages[ts.Cids[0]['/']].length
}
if(msgc > 0) {
msgc = <b>{msgc}</b>
}
let time = '?'
if(this.state.cache[row - 1]){
time = <span>{ts.Blocks[0].Timestamp - this.state.cache[row - 1].Blocks[0].Timestamp}s</span>
}
info = <span>
<BlockLinks cids={ts.Cids} blocks={ts.Blocks} conn={this.props.client} mountWindow={this.props.mountWindow} /> Msgs: {msgc} ΔT:{time}
</span>
}
return <div key={row} className={className}>@{h} {info}</div>
})}</div>
return (<Window onClose={this.props.onClose} title={`Chain Explorer ${this.state.follow ? '(Following)' : ''}`}>
{content}
</Window>)
}
}
export default ChainExplorer