whispervis/page.go

155 lines
3.5 KiB
Go

package main
import (
"fmt"
"github.com/divan/graphx/layout"
"github.com/gopherjs/vecty"
"github.com/gopherjs/vecty/elem"
"github.com/gopherjs/vecty/event"
"github.com/status-im/whispervis/widgets"
)
// Page is our main page component.
type Page struct {
vecty.Core
layout *layout.Layout
webgl *WebGLScene
loaded bool
loader *widgets.Loader
forceEditor *widgets.ForceEditor
network *NetworkSelector
simulationWidget *widgets.Simulation
statsWidget *widgets.Stats
simulation *Simulation
}
// NewPage creates and inits new app page.
func NewPage() *Page {
page := &Page{
loader: widgets.NewLoader(),
forceEditor: widgets.NewForceEditor(),
}
page.network = NewNetworkSelector(page.onNetworkChange)
page.webgl = NewWebGLScene()
page.simulationWidget = widgets.NewSimulation("localhost:8084", page.startSimulation, page.replaySimulation)
page.statsWidget = widgets.NewStats()
return page
}
// Render implements the vecty.Component interface.
func (p *Page) Render() vecty.ComponentOrHTML {
return elem.Body(
elem.Div(
vecty.Markup(
vecty.Class("pure-g"),
vecty.Style("height", "100%"),
),
elem.Div(
vecty.Markup(vecty.Class("pure-u-1-5")),
elem.Heading1(vecty.Text("Whisper Message Propagation")),
elem.Paragraph(vecty.Text("This visualization represents message propagation in the p2p network.")),
p.network,
elem.HorizontalRule(),
elem.Div(
vecty.Markup(
vecty.MarkupIf(!p.loaded, vecty.Style("visibility", "hidden")),
),
p.simulationWidget,
elem.HorizontalRule(),
p.forceEditor,
p.updateButton(),
elem.HorizontalRule(),
p.statsWidget,
),
),
elem.Div(
vecty.Markup(
vecty.Class("pure-u-4-5"),
/*
we use display:none property to hide WebGL instead of mounting/unmounting,
because we want to create only one WebGL context and reuse it. Plus,
WebGL takes time to initialize, so it can do it being hidden.
*/
vecty.MarkupIf(!p.loaded,
vecty.Style("visibility", "hidden"),
vecty.Style("height", "0px"),
vecty.Style("width", "0px"),
),
),
p.webgl,
),
elem.Div(
vecty.Markup(
vecty.Class("pure-u-4-5"),
),
vecty.If(!p.loaded, p.loader),
),
),
vecty.Markup(
event.KeyDown(p.KeyListener),
event.VisibilityChange(p.VisibilityListener),
),
)
}
func (p *Page) updateButton() *vecty.HTML {
return elem.Div(
elem.Button(
vecty.Markup(
vecty.Class("pure-button"),
vecty.Style("background", "rgb(28, 184, 65)"),
vecty.Style("color", "white"),
vecty.Style("border-radius", "4px"),
event.Click(p.onUpdateClick),
),
vecty.Text("Update"),
),
)
}
func (p *Page) onUpdateClick(e *vecty.Event) {
if !p.loaded {
return
}
go p.UpdateGraph()
}
func (p *Page) onNetworkChange(network *Network) {
fmt.Println("Network changed:", network)
config := p.forceEditor.Config()
p.layout = layout.NewFromConfig(network.Data, config.Config)
go p.UpdateGraph()
}
// startSimulation is called on the end of each simulation round.
func (p *Page) startSimulation() error {
backend := p.simulationWidget.Address()
sim, err := p.runSimulation(backend)
if err != nil {
return err
}
// calculate stats and update stats widget
p.RecalculateStats()
p.statsWidget.Update(sim.stats)
p.simulation = sim
p.replaySimulation()
return nil
}
// replaySimulation animates last simulation.
func (p *Page) replaySimulation() {
if p.simulation == nil {
return
}
p.webgl.AnimatePropagation(p.simulation.plog)
}