From a3ba670da8f7de94b5e5c6e307fda65b8dd5d791 Mon Sep 17 00:00:00 2001 From: Jakob Odersky Date: Wed, 15 Apr 2015 20:12:53 +0200 Subject: layout: slimer panels and more instruments --- .../src/main/scala/vfd/dashboard/RxUtil.scala | 43 ++++- .../src/main/scala/vfd/dashboard/ui/Layout.scala | 194 +++++++++++++++------ vfd-main/app/views/main.scala.html | 1 + vfd-main/public/stylesheets/main.css | 58 +++--- 4 files changed, 210 insertions(+), 86 deletions(-) diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/RxUtil.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/RxUtil.scala index 51e2fcd..a73c3ba 100644 --- a/vfd-dashboard/src/main/scala/vfd/dashboard/RxUtil.scala +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/RxUtil.scala @@ -1,6 +1,23 @@ package vfd.dashboard -import rx._ +import scala.language.implicitConversions +import scala.util.Failure +import scala.util.Success + +import org.scalajs.dom.html + +import rx.Obs +import rx.Rx +import rx.Rx +import rx.Var +import rx.Var +import scalatags.JsDom.all.Frag +import scalatags.JsDom.all.HtmlTag +import scalatags.JsDom.all.backgroundColor +import scalatags.JsDom.all.bindNode +import scalatags.JsDom.all.span +import scalatags.JsDom.all.stringFrag +import scalatags.JsDom.all.stringStyle package object rxutil { @@ -14,7 +31,7 @@ package object rxutil { * @param pf the partial function which filters and maps this Rx * @return a new Rx resulting from applying the given partial * function pf to each value on which it is defined and collecting - * the result + * the result */ def collect[B](initial: B)(pf: PartialFunction[Any, B]): Rx[B] = { val result: Var[B] = Var(initial) @@ -28,4 +45,26 @@ package object rxutil { } + /** + * Copied from https://github.com/lihaoyi/workbench-example-app/blob/todomvc/src/main/scala/example/Framework.scala + * + * Sticks some Rx into a Scalatags fragment, which means hooking up an Obs + * to propagate changes into the DOM via the element's ID. Monkey-patches + * the Obs onto the element itself so we have a reference to kill it when + * the element leaves the DOM (e.g. it gets deleted). + */ + implicit def rxMod[T <: html.Element](r: Rx[HtmlTag]): Frag = { + def rSafe = r.toTry match { + case Success(v) => v.render + case Failure(e) => span(e.toString, backgroundColor := "red").render + } + var last = rSafe + Obs(r, skipInitial = true) { + val newLast = rSafe + last.parentElement.replaceChild(newLast, last) + last = newLast + } + bindNode(last) + } + } \ No newline at end of file diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala index 1d6090d..b3c9387 100644 --- a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala +++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala @@ -31,15 +31,13 @@ class Layout(socket: MavlinkSocket)(implicit env: Environment) { div(style := "float: right")(mode("CRITICAL", "danger", true))) val map = iframe( - width := 100.pct, - height := 350.px, "frameborder".attr := "0", "scrolling".attr := "no", "marginheight".attr := "0", "marginwidth".attr := "0", src := "http://www.openstreetmap.org/export/embed.html?bbox=6.5611016750335684%2C46.51718501017836%2C6.570038795471191%2C46.520577350893525&layer=mapnik") - val feed = div(style := "width: 100%; height: 460px; color: #ffffff; background-color: #c2c2c2; text-align: center;")( + val feed = div(style := "width: 100%; color: #ffffff; background-color: #c2c2c2; text-align: center;")( p(style := "padding-top: 220px")("video feed")) val altimeter = new Altimeter( @@ -78,69 +76,163 @@ class Layout(socket: MavlinkSocket)(implicit env: Environment) { div("UAV " + socket.remoteSystemId) ) - val left = panel( - map, - table(`class` := "table-instrument")( - thead("Motors"), - tbody( - tr( - td(motor1.element), - td(), - td(motor0.element), - td() - ), - tr( - td(), - td(powerDistribution.element), - td(), - td() - ), - tr( - td(motor2.element), - td(), - td(motor3.element), - td() + val left = div( + panel( + table(`class` := "table-instrument")( + thead("Communication"), + tbody( + tr( + td("Uplink RSSI"), + td("89"), + td("Socket"), + td("5ms") + ), + tr( + td("Something else"), + td("unknown"), + td("Heartbeat"), + td(i(`class` := "fa fa-heart heartbeat")) + ) + ) + ), + table(`class` := "table-instrument")( + thead("Packets"), + tbody( + tr( + td("OK"), + Rx{td(socket.stats.packets())}, + td("CRC"), + Rx{td(socket.stats.crcErrors())}, + td("OFLW"), + Rx{td(socket.stats.overflows())}, + td("BID"), + Rx{td(socket.stats.wrongIds())} + ), + tr( + td("Ratio"), + Rx{ + import socket.stats._ + val sum = packets() + crcErrors() + overflows() + wrongIds() + td(1.0 * packets() / sum formatted "%.2f") + }, + td(), + td(), + td(), + td(), + td(), + td() + ) ) ) - ) - ) - - val center = panel(feed) - - val below = panel( - table(`class` := "table-instrument")( - tbody( - tr( - td(compass.element), - td(horizon.element), - td(altimeter.element) + ), + panel( + table(`class` := "table-instrument")( + tbody( + tr( + td(compass.element), + td(horizon.element), + td(altimeter.element), + td(altimeter.element) + ) + ) + ) + ), + panel( + div(style := "width: 50%; display: inline-block;")( + table(`class` := "table-instrument")( + tbody( + tr( + td(motor1.element), + td(), + td(motor0.element) + ), + tr( + td(), + td(powerDistribution.element), + td() + ), + tr( + td(motor2.element), + td(), + td(motor3.element) + ) + ) + ) + ), + div(style := "width: 50%; display: inline-block;")( + table(`class` := "table-instrument")( + thead("Power"), + tbody( + tr( + td("VHIGH"), + td("12.6V"), + td("VLOW"), + td("9V") + ), + tr( + td("Voltage"), + td("11.2V"), + td("Remaining"), + td("80%") + ), + tr( + td("Flight"), + td("05:00"), + td("Endurance"), + td("12:00") + ) + ) + ), + table(`class` := "table-instrument")( + thead("Navigation"), + tbody( + tr( + td("Satellites"), + td("5"), + td("Precision"), + td("10cm") + ), + tr( + td("LON"), + td(""), + td("LAT"), + td("") + ), + tr( + td("GSpeed"), + td("3 m/s"), + td(), + td() + ), + tr( + td("Travelled"), + td("5000m"), + td("Home"), + td("1200m") + ) + ) ) ) ) ) - val right = panel() - val element = div(`class` := "d-container d-column")( div(`class` := "d-above")( - top), + top + ), div(`class` := "d-above d-container d-row")( panel(modes), - panel(infos)), + panel(infos) + ), div(`class` := "d-container d-row")( - div(`class` := "d-container d-details")( - panel("foo")), div(`class` := "d-container d-left")( - left), + left + ), div(`class` := "d-container d-column d-middle")( - div(`class` := "d-container d-center")( - center), - div(`class` := "d-container d-below")( - below) + panel(feed), + panel(map) ), - div(`class` := "d-container d-right")( - right - ) + div(`class` := "d-container d-right")() ) ).render diff --git a/vfd-main/app/views/main.scala.html b/vfd-main/app/views/main.scala.html index 6300e1f..79a9f2b 100644 --- a/vfd-main/app/views/main.scala.html +++ b/vfd-main/app/views/main.scala.html @@ -7,6 +7,7 @@ + VFD - @title diff --git a/vfd-main/public/stylesheets/main.css b/vfd-main/public/stylesheets/main.css index 00d3a31..5430334 100644 --- a/vfd-main/public/stylesheets/main.css +++ b/vfd-main/public/stylesheets/main.css @@ -20,11 +20,11 @@ body { #vfd-dashboard header { color: #eeeeee; background-color: #222222; - padding-left: 15px; - padding-right: 15px; - padding-top: 5px; - padding-bottom: 5px; - margin-bottom: 5px; + padding-left: 8px; + padding-right: 8px; + padding-top: 3px; + padding-bottom: 3px; + margin-bottom: 3px; display: flex; } @@ -82,35 +82,9 @@ body { flex: 1 1 25%; } -.d-center { - flex: none; -} - -.d-below { - flex: 1; -} - -.d-details { - display: none; -} - -.d-detailed .d-left, .d-detailed .d-right { - display: none; -} - -.d-detailed .d-middle { - flex: 1 1 25%; -} - -.d-detailed .d-details { - display: flex; - flex: 1 1 75%; - overerflow-y: scroll; -} - .d-panel { - margin: 5px; - padding: 15px; + margin: 3px; + padding: 10px; background-color: white; border-radius: 3px; } @@ -166,6 +140,7 @@ body { .heartbeat { color: rgba(165, 25, 25, 1); animation: heartbeat 2s linear infinite; + -webkit-animation: heartbeat 2s linear infinite; } @keyframes heartbeat { @@ -185,6 +160,23 @@ body { transform: scale(1); } } +@-webkit-keyframes heartbeat { + 0% { + transform: scale(1); + } + 7% { + transform: scale(1.3); + } + 14% { + transform: scale(1); + } + 21% { + transform: scale(1.3); + } + 28% { + transform: scale(1); + } +} @keyframes danger-blink { 0% { -- cgit v1.2.3