whispervis/page.go

293 lines
6.5 KiB
Go
Raw Normal View History

2018-09-06 15:15:55 +03:00
package main
import (
"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"
2018-10-22 21:11:20 +02:00
"github.com/gopherjs/vecty/prop"
2018-10-23 06:42:56 +02:00
"github.com/status-im/whispervis/network"
2018-09-06 15:15:55 +03:00
"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
loaded bool
isSimulating bool
2018-09-06 15:15:55 +03:00
2018-10-19 16:01:49 +02:00
loader *widgets.Loader
2018-10-23 06:42:56 +02:00
network *widgets.NetworkSelector
2018-10-24 20:43:31 +02:00
forceEditor *widgets.ForceEditor
graphics *widgets.Graphics
2018-10-19 16:01:49 +02:00
simulationWidget *widgets.Simulation
statsWidget *widgets.Stats
2018-10-23 17:19:07 +02:00
statsPage *StatsPage
2018-10-23 17:20:53 +02:00
faqPage *FAQPage
2018-10-23 17:19:07 +02:00
2018-10-19 16:01:49 +02:00
simulation *Simulation
2018-10-23 17:04:47 +02:00
activeView string
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-10-24 22:48:53 +02:00
page := &Page{}
// preload defaults
page.activeView = View3D
// init widgets
page.loader = widgets.NewLoader()
page.webgl = NewWebGLScene(page.onWebGLReady)
2018-10-23 06:42:56 +02:00
page.network = widgets.NewNetworkSelector(page.onNetworkChange)
2018-10-24 20:43:31 +02:00
page.forceEditor = widgets.NewForceEditor(page.onForcesApply)
2018-10-24 22:48:53 +02:00
page.graphics = widgets.NewGraphics(page.webgl)
2018-10-27 09:45:12 +02:00
page.simulationWidget = widgets.NewSimulation("http://localhost:8084", page.startSimulation, page.replaySimulation, page.stepSimulation)
2018-10-19 14:13:41 +02:00
page.statsWidget = widgets.NewStats()
2018-10-23 17:19:07 +02:00
page.statsPage = NewStatsPage()
2018-10-23 17:20:53 +02:00
page.faqPage = NewFAQPage()
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
),
2018-10-23 17:04:47 +02:00
// Left sidebar
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(),
elem.Div(
vecty.Markup(
vecty.MarkupIf(p.isSimulating,
2018-10-20 19:38:44 +02:00
vecty.Attribute("disabled", "true"),
),
),
p.network,
2018-10-19 19:01:53 +02:00
p.forceEditor,
2018-10-24 20:43:31 +02:00
p.graphics,
),
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,
elem.Div(
vecty.Markup(
vecty.MarkupIf(p.isSimulating,
2018-10-20 19:38:44 +02:00
vecty.Attribute("disabled", "true"),
),
),
p.statsWidget,
),
2018-09-06 17:43:42 +03:00
),
2018-09-06 15:15:55 +03:00
),
2018-10-23 17:04:47 +02:00
// Right page section
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-10-23 17:04:47 +02:00
),
p.renderTabs(),
elem.Div(
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.
*/
2018-10-23 17:04:47 +02:00
vecty.Markup(
vecty.MarkupIf(!p.loaded || p.activeView != View3D,
vecty.Class("is-invisible"),
vecty.Style("height", "0px"),
),
2018-09-17 22:11:04 +03:00
),
2018-10-23 17:04:47 +02:00
p.webgl,
2018-09-17 22:11:04 +03:00
),
2018-10-23 17:04:47 +02:00
vecty.If(p.activeView == ViewStats,
2018-10-23 17:19:07 +02:00
p.statsPage,
2018-10-23 17:04:47 +02:00
),
2018-10-23 17:20:53 +02:00
vecty.If(p.activeView == ViewFAQ,
p.faqPage,
),
2018-10-23 17:04:47 +02:00
vecty.If(!p.loaded,
elem.Div(
vecty.Markup(
vecty.Class("has-text-centered"),
),
p.loader,
),
2018-09-06 15:15:55 +03:00
),
),
),
vecty.Markup(
event.KeyDown(p.KeyListener),
event.MouseMove(p.MouseMoveListener),
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() {
if !p.loaded {
return
}
2018-10-19 22:10:56 +02:00
p.UpdateGraph()
2018-09-06 18:37:46 +03:00
}
2018-10-23 06:42:56 +02:00
func (p *Page) onNetworkChange(network *network.Network) {
2018-09-19 12:45:54 +03:00
fmt.Println("Network changed:", network)
2018-10-24 12:54:48 +02:00
// reset view on network switch
2018-10-24 22:48:53 +02:00
p.switchView(View3D)
2018-10-24 12:54:48 +02:00
// reset simulation and stats
p.simulation = nil
p.simulationWidget.Reset()
p.statsWidget.Reset()
2018-09-18 19:01:45 +03:00
config := p.forceEditor.Config()
p.layout = layout.New(network.Data, config.Config)
2018-10-23 07:28:41 +02:00
// set forced positions if found in network
if network.Positions != nil {
p.layout.SetPositions(network.Positions)
go p.RecreateObjects()
return
}
// else, recalculate positions
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-27 09:45:12 +02:00
// Returns numnber of timesteps for the simulation.
// TODO(divan): maybe sim widget need to have access to whole simulation?
func (p *Page) startSimulation() (int, error) {
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()
2018-10-24 19:37:31 +02:00
ttl := p.simulationWidget.TTL()
sim, err := p.runSimulation(backend, ttl)
2018-09-19 21:06:30 +03:00
if err != nil {
2018-10-27 09:45:12 +02:00
return 0, 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-24 12:54:48 +02:00
p.statsPage.UpdateStats(p.network.Current().Data, p.simulation.plog)
2018-10-19 14:13:41 +02:00
2018-10-19 16:01:49 +02:00
p.replaySimulation()
2018-10-27 09:45:12 +02:00
return len(sim.plog.Timestamps), 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(
2018-10-22 21:11:20 +02:00
elem.Image(
vecty.Markup(
vecty.Style("padding-top", "5px"),
prop.Src("images/status.png"),
),
),
2018-10-19 22:10:56 +02:00
elem.Heading2(
vecty.Markup(
vecty.Class("title", "has-text-weight-light"),
),
2018-10-22 21:11:20 +02:00
vecty.Text("Whisper Simulator"),
2018-10-19 22:10:56 +02:00
),
2018-10-22 21:11:20 +02:00
elem.Heading6(
2018-10-19 22:10:56 +02:00
vecty.Markup(
vecty.Class("subtitle", "has-text-weight-light"),
),
2018-10-22 21:11:20 +02:00
vecty.Text("This simulator shows message propagation in the Whisper network."),
2018-10-19 22:10:56 +02:00
),
)
}
2018-10-23 08:28:14 +02:00
// onWebGLReady is executed when WebGL context is up and ready to render scene.
func (p *Page) onWebGLReady() {
p.onNetworkChange(p.network.Current())
}
2018-10-23 17:04:47 +02:00
func (p *Page) renderTabs() *vecty.HTML {
return elem.Div(
vecty.Markup(
vecty.Class("tabs", "is-marginless", "is-boxed", "is-fullwidth"),
),
elem.UnorderedList(
elem.ListItem(
vecty.Markup(
vecty.MarkupIf(p.activeView == View3D,
vecty.Class("is-active"),
),
event.Click(p.onTabSwitch(View3D)),
),
elem.Anchor(
vecty.Text("3D view"),
),
),
elem.ListItem(
vecty.Markup(
vecty.MarkupIf(p.activeView == ViewStats,
vecty.Class("is-active"),
),
event.Click(p.onTabSwitch(ViewStats)),
),
elem.Anchor(
vecty.Text("Stats view"),
),
),
elem.ListItem(
vecty.Markup(
vecty.MarkupIf(p.activeView == ViewFAQ,
vecty.Class("is-active"),
),
event.Click(p.onTabSwitch(ViewFAQ)),
),
elem.Anchor(
vecty.Text("FAQ"),
),
),
),
)
}
2018-10-27 09:45:12 +02:00
// stepSimulation animates a single step from the last simulation.
func (p *Page) stepSimulation(step int) {
if p.simulation == nil {
return
}
p.webgl.AnimateOneStep(p.simulation.plog, step)
}