2018-09-06 12:15:55 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2018-09-19 18:06:30 +00:00
|
|
|
"bytes"
|
2018-09-11 16:02:40 +00:00
|
|
|
"fmt"
|
|
|
|
|
2018-09-19 18:06:30 +00:00
|
|
|
"github.com/divan/graphx/formats"
|
2018-09-06 12:15:55 +00:00
|
|
|
"github.com/divan/graphx/layout"
|
|
|
|
"github.com/gopherjs/vecty"
|
|
|
|
"github.com/gopherjs/vecty/elem"
|
|
|
|
"github.com/gopherjs/vecty/event"
|
2018-10-19 12:13:41 +00:00
|
|
|
"github.com/status-im/simulation/propagation"
|
2018-09-06 12:15:55 +00:00
|
|
|
"github.com/status-im/whispervis/widgets"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Page is our main page component.
|
|
|
|
type Page struct {
|
|
|
|
vecty.Core
|
|
|
|
|
|
|
|
layout *layout.Layout
|
|
|
|
|
2018-09-17 19:11:04 +00:00
|
|
|
webgl *WebGLScene
|
2018-09-06 12:15:55 +00:00
|
|
|
|
2018-09-17 19:11:04 +00:00
|
|
|
loaded bool
|
2018-09-06 12:15:55 +00:00
|
|
|
|
2018-09-19 13:00:53 +00:00
|
|
|
loader *widgets.Loader
|
|
|
|
forceEditor *widgets.ForceEditor
|
|
|
|
network *NetworkSelector
|
|
|
|
simulationConf *widgets.Simulation
|
2018-10-19 12:13:41 +00:00
|
|
|
statsWidget *widgets.Stats
|
2018-09-06 12:15:55 +00:00
|
|
|
}
|
|
|
|
|
2018-09-11 15:33:28 +00:00
|
|
|
// NewPage creates and inits new app page.
|
2018-09-17 19:11:04 +00:00
|
|
|
func NewPage() *Page {
|
2018-09-06 15:37:46 +00:00
|
|
|
page := &Page{
|
2018-09-19 18:06:30 +00:00
|
|
|
loader: widgets.NewLoader(),
|
|
|
|
forceEditor: widgets.NewForceEditor(),
|
2018-09-06 15:37:46 +00:00
|
|
|
}
|
2018-09-18 16:01:45 +00:00
|
|
|
page.network = NewNetworkSelector(page.onNetworkChange)
|
2018-09-17 19:11:04 +00:00
|
|
|
page.webgl = NewWebGLScene()
|
2018-10-19 12:13:41 +00:00
|
|
|
page.simulationConf = widgets.NewSimulation("localhost:8084", page.CurrentNetwork, page.onSimulationFinish)
|
|
|
|
page.statsWidget = widgets.NewStats()
|
2018-09-06 15:37:46 +00:00
|
|
|
return page
|
|
|
|
}
|
|
|
|
|
2018-09-06 12:15:55 +00:00
|
|
|
// 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.")),
|
2018-09-18 14:01:13 +00:00
|
|
|
p.network,
|
2018-09-19 13:00:53 +00:00
|
|
|
elem.HorizontalRule(),
|
2018-09-06 14:43:42 +00:00
|
|
|
elem.Div(
|
|
|
|
vecty.Markup(
|
|
|
|
vecty.MarkupIf(!p.loaded, vecty.Style("visibility", "hidden")),
|
|
|
|
),
|
2018-09-19 13:00:53 +00:00
|
|
|
p.simulationConf,
|
|
|
|
elem.HorizontalRule(),
|
2018-09-06 14:43:42 +00:00
|
|
|
p.forceEditor,
|
2018-09-06 15:37:46 +00:00
|
|
|
p.updateButton(),
|
2018-10-19 12:13:41 +00:00
|
|
|
elem.HorizontalRule(),
|
|
|
|
p.statsWidget,
|
2018-09-06 14:43:42 +00:00
|
|
|
),
|
2018-09-06 12:15:55 +00:00
|
|
|
),
|
|
|
|
elem.Div(
|
2018-09-17 19:11:04 +00:00
|
|
|
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"),
|
2018-09-06 12:15:55 +00:00
|
|
|
),
|
|
|
|
vecty.If(!p.loaded, p.loader),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
vecty.Markup(
|
|
|
|
event.KeyDown(p.KeyListener),
|
2018-09-17 19:56:16 +00:00
|
|
|
event.VisibilityChange(p.VisibilityListener),
|
2018-09-06 12:15:55 +00:00
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-09-06 15:37:46 +00:00
|
|
|
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) {
|
2018-09-06 16:11:51 +00:00
|
|
|
if !p.loaded {
|
|
|
|
return
|
|
|
|
}
|
2018-09-19 13:00:53 +00:00
|
|
|
go p.UpdateGraph()
|
2018-09-06 15:37:46 +00:00
|
|
|
}
|
2018-09-11 16:02:40 +00:00
|
|
|
|
2018-09-18 16:01:45 +00:00
|
|
|
func (p *Page) onNetworkChange(network *Network) {
|
2018-09-19 09:45:54 +00:00
|
|
|
fmt.Println("Network changed:", network)
|
2018-09-18 16:01:45 +00:00
|
|
|
config := p.forceEditor.Config()
|
2018-09-19 18:06:30 +00:00
|
|
|
p.layout = layout.NewFromConfig(network.Data, config.Config)
|
2018-09-19 13:00:53 +00:00
|
|
|
go p.UpdateGraph()
|
2018-09-18 16:01:45 +00:00
|
|
|
}
|
2018-09-19 18:06:30 +00:00
|
|
|
|
|
|
|
// CurrentNetwork returns JSON encoded description of the current graph/network.
|
|
|
|
func (p *Page) CurrentNetwork() []byte {
|
|
|
|
net := p.network.current.Data
|
|
|
|
var buf bytes.Buffer
|
|
|
|
err := formats.NewD3JSON(&buf, true).ExportGraph(net)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println("[ERROR] Can't export graph:", err)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return buf.Bytes()
|
|
|
|
}
|
2018-10-19 12:13:41 +00:00
|
|
|
|
|
|
|
// onSimulationFinish is called on the end of each simulation round.
|
|
|
|
func (p *Page) onSimulationFinish(plog *propagation.Log) {
|
|
|
|
net := p.network.current
|
|
|
|
nodesCount := len(net.Data.Nodes())
|
|
|
|
linksCount := len(net.Data.Links())
|
|
|
|
p.statsWidget.Update(plog, nodesCount, linksCount)
|
|
|
|
|
|
|
|
p.webgl.AnimatePropagation(plog)
|
|
|
|
}
|