lotus/lotuspond/front/src/ChainExplorer.js
2019-08-19 23:31:25 +02:00

138 lines
4.1 KiB
JavaScript

import React from 'react';
import {Cristal} from "react-cristal";
import {BlockLinks} from "./BlockLink";
const rows = 32
class ChainExplorer extends React.Component {
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.ChainGetBlockMessages', [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, cache, msgcache) {
const cids = cache[h + 1].Blocks.map(b => b.Parents).reduce((acc, val) => acc.concat(val), [])
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)
}
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) => [...await prev, await this.fetch(next, cache, msgcache)], Promise.resolve([]))
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>
if(this.state.cache[row]) {
const ts = this.state.cache[row]
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]['/']].SecpkMessages.length + this.state.messages[ts.Cids[0]['/']].BlsMessages.length
}
if(msgc > 0) {
msgc = <b>{msgc}</b>
}
info = <span>
<BlockLinks cids={ts.Cids} blocks={ts.Blocks} conn={this.props.client} mountWindow={this.props.mountWindow} /> Msgs: {msgc}
</span>
}
return <div key={row} className={className}>@{row} {info}</div>
})}</div>
return (<Cristal onClose={this.props.onClose} title={`Chain Explorer ${this.state.follow ? '(Following)' : ''}`}>
{content}
</Cristal>)
}
}
export default ChainExplorer