2018-09-06 15:15:55 +03:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2018-09-11 19:02:40 +03:00
|
|
|
"fmt"
|
|
|
|
|
2018-09-06 15:15:55 +03:00
|
|
|
"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
|
|
|
|
|
2018-09-17 22:11:04 +03:00
|
|
|
webgl *WebGLScene
|
2018-09-06 15:15:55 +03:00
|
|
|
|
2018-10-19 19:00:11 +02:00
|
|
|
loaded bool
|
|
|
|
isSimulating bool
|
2018-09-06 15:15:55 +03:00
|
|
|
|
2018-10-19 16:01:49 +02:00
|
|
|
loader *widgets.Loader
|
|
|
|
forceEditor *widgets.ForceEditor
|
|
|
|
network *NetworkSelector
|
|
|
|
simulationWidget *widgets.Simulation
|
|
|
|
statsWidget *widgets.Stats
|
|
|
|
|
|
|
|
simulation *Simulation
|
2018-09-06 15:15:55 +03:00
|
|
|
}
|
|
|
|
|
2018-09-11 18:33:28 +03:00
|
|
|
// NewPage creates and inits new app page.
|
2018-09-17 22:11:04 +03:00
|
|
|
func NewPage() *Page {
|
2018-09-06 18:37:46 +03:00
|
|
|
page := &Page{
|
2018-10-19 22:10:56 +02:00
|
|
|
loader: widgets.NewLoader(),
|
2018-09-06 18:37:46 +03:00
|
|
|
}
|
2018-10-19 22:10:56 +02:00
|
|
|
page.forceEditor = widgets.NewForceEditor(page.onForcesApply)
|
2018-09-18 19:01:45 +03:00
|
|
|
page.network = NewNetworkSelector(page.onNetworkChange)
|
2018-09-17 22:11:04 +03:00
|
|
|
page.webgl = NewWebGLScene()
|
2018-10-19 22:10:56 +02:00
|
|
|
page.simulationWidget = widgets.NewSimulation("http://localhost:8084", page.startSimulation, page.replaySimulation)
|
2018-10-19 14:13:41 +02:00
|
|
|
page.statsWidget = widgets.NewStats()
|
2018-09-06 18:37:46 +03:00
|
|
|
return page
|
|
|
|
}
|
|
|
|
|
2018-09-06 15:15:55 +03:00
|
|
|
// Render implements the vecty.Component interface.
|
|
|
|
func (p *Page) Render() vecty.ComponentOrHTML {
|
|
|
|
return elem.Body(
|
|
|
|
elem.Div(
|
|
|
|
vecty.Markup(
|
2018-10-19 22:10:56 +02:00
|
|
|
vecty.Class("columns"),
|
2018-09-06 15:15:55 +03:00
|
|
|
),
|
|
|
|
elem.Div(
|
2018-10-19 22:10:56 +02:00
|
|
|
vecty.Markup(
|
|
|
|
vecty.Class("column", "is-narrow"),
|
|
|
|
vecty.Style("width", "300px"),
|
|
|
|
),
|
|
|
|
p.header(),
|
2018-10-19 19:00:11 +02:00
|
|
|
elem.Div(
|
|
|
|
vecty.Markup(
|
|
|
|
vecty.MarkupIf(p.isSimulating,
|
2018-10-20 19:38:44 +02:00
|
|
|
vecty.Attribute("disabled", "true"),
|
2018-10-19 19:00:11 +02:00
|
|
|
),
|
|
|
|
),
|
|
|
|
p.network,
|
2018-10-19 19:01:53 +02:00
|
|
|
p.forceEditor,
|
2018-10-19 19:00:11 +02:00
|
|
|
),
|
2018-09-06 17:43:42 +03:00
|
|
|
elem.Div(
|
|
|
|
vecty.Markup(
|
|
|
|
vecty.MarkupIf(!p.loaded, vecty.Style("visibility", "hidden")),
|
|
|
|
),
|
2018-10-19 16:01:49 +02:00
|
|
|
p.simulationWidget,
|
2018-10-19 19:00:11 +02:00
|
|
|
elem.Div(
|
|
|
|
vecty.Markup(
|
|
|
|
vecty.MarkupIf(p.isSimulating,
|
2018-10-20 19:38:44 +02:00
|
|
|
vecty.Attribute("disabled", "true"),
|
2018-10-19 19:00:11 +02:00
|
|
|
),
|
|
|
|
),
|
|
|
|
p.statsWidget,
|
|
|
|
),
|
2018-09-06 17:43:42 +03:00
|
|
|
),
|
2018-09-06 15:15:55 +03:00
|
|
|
),
|
|
|
|
elem.Div(
|
2018-09-17 22:11:04 +03:00
|
|
|
vecty.Markup(
|
2018-10-19 22:10:56 +02:00
|
|
|
vecty.Class("column"),
|
2018-09-17 22:11:04 +03:00
|
|
|
/*
|
|
|
|
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,
|
2018-10-20 19:38:44 +02:00
|
|
|
vecty.Class("is-invisible"),
|
2018-09-17 22:11:04 +03:00
|
|
|
vecty.Style("height", "0px"),
|
|
|
|
vecty.Style("width", "0px"),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
p.webgl,
|
|
|
|
),
|
|
|
|
elem.Div(
|
|
|
|
vecty.Markup(
|
2018-10-20 19:38:44 +02:00
|
|
|
vecty.Class("column", "is-full", "has-text-centered"),
|
2018-09-06 15:15:55 +03:00
|
|
|
),
|
|
|
|
vecty.If(!p.loaded, p.loader),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
vecty.Markup(
|
|
|
|
event.KeyDown(p.KeyListener),
|
2018-10-22 14:02:59 +02:00
|
|
|
event.MouseMove(p.MouseMoveListener),
|
2018-09-17 22:56:16 +03:00
|
|
|
event.VisibilityChange(p.VisibilityListener),
|
2018-09-06 15:15:55 +03:00
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2018-10-19 22:10:56 +02:00
|
|
|
// onForcesApply executes when Force Editor click is fired.
|
|
|
|
func (p *Page) onForcesApply() {
|
2018-09-06 19:11:51 +03:00
|
|
|
if !p.loaded {
|
|
|
|
return
|
|
|
|
}
|
2018-10-19 22:10:56 +02:00
|
|
|
p.UpdateGraph()
|
2018-09-06 18:37:46 +03:00
|
|
|
}
|
2018-09-11 19:02:40 +03:00
|
|
|
|
2018-09-18 19:01:45 +03:00
|
|
|
func (p *Page) onNetworkChange(network *Network) {
|
2018-09-19 12:45:54 +03:00
|
|
|
fmt.Println("Network changed:", network)
|
2018-09-18 19:01:45 +03:00
|
|
|
config := p.forceEditor.Config()
|
2018-10-22 16:39:11 +02:00
|
|
|
p.layout = layout.New(network.Data, config.Config)
|
2018-09-19 16:00:53 +03:00
|
|
|
go p.UpdateGraph()
|
2018-09-18 19:01:45 +03:00
|
|
|
}
|
2018-09-19 21:06:30 +03:00
|
|
|
|
2018-10-19 16:01:49 +02:00
|
|
|
// startSimulation is called on the end of each simulation round.
|
2018-10-19 16:41:16 +02:00
|
|
|
func (p *Page) startSimulation() error {
|
2018-10-19 19:00:11 +02:00
|
|
|
p.isSimulating = true
|
|
|
|
vecty.Rerender(p)
|
|
|
|
|
|
|
|
defer func() {
|
|
|
|
p.isSimulating = false
|
|
|
|
vecty.Rerender(p)
|
|
|
|
}()
|
|
|
|
|
2018-10-19 16:01:49 +02:00
|
|
|
backend := p.simulationWidget.Address()
|
|
|
|
sim, err := p.runSimulation(backend)
|
2018-09-19 21:06:30 +03:00
|
|
|
if err != nil {
|
2018-10-19 16:41:16 +02:00
|
|
|
return err
|
2018-09-19 21:06:30 +03:00
|
|
|
}
|
2018-10-19 14:13:41 +02:00
|
|
|
|
2018-10-19 16:01:49 +02:00
|
|
|
// calculate stats and update stats widget
|
2018-10-19 18:37:58 +02:00
|
|
|
stats := p.RecalculateStats(sim.plog)
|
|
|
|
p.statsWidget.Update(stats)
|
2018-10-19 16:01:49 +02:00
|
|
|
|
2018-10-19 18:37:58 +02:00
|
|
|
sim.stats = stats
|
2018-10-19 16:01:49 +02:00
|
|
|
p.simulation = sim
|
2018-10-19 14:13:41 +02:00
|
|
|
|
2018-10-19 16:01:49 +02:00
|
|
|
p.replaySimulation()
|
2018-10-19 16:41:16 +02:00
|
|
|
return nil
|
2018-10-19 16:01:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// replaySimulation animates last simulation.
|
|
|
|
func (p *Page) replaySimulation() {
|
|
|
|
if p.simulation == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
p.webgl.AnimatePropagation(p.simulation.plog)
|
2018-10-19 14:13:41 +02:00
|
|
|
}
|
2018-10-19 22:10:56 +02:00
|
|
|
|
|
|
|
func (p *Page) header() *vecty.HTML {
|
|
|
|
return elem.Section(
|
|
|
|
elem.Heading2(
|
|
|
|
vecty.Markup(
|
|
|
|
vecty.Class("title", "has-text-weight-light"),
|
|
|
|
),
|
|
|
|
vecty.Text("Whisper Message Propagation"),
|
|
|
|
),
|
|
|
|
elem.Heading5(
|
|
|
|
vecty.Markup(
|
|
|
|
vecty.Class("subtitle", "has-text-weight-light"),
|
|
|
|
),
|
|
|
|
vecty.Text("This visualization represents message propagation in the p2p network."),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|