aboutsummaryrefslogtreecommitdiff
path: root/vfd-dashboard/src/main/scala/vfd/dashboard/ui
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2015-01-20 17:37:10 +0100
committerJakob Odersky <jodersky@gmail.com>2015-01-20 17:37:10 +0100
commit13eae49f4c48c03b2a7a9b40a08ca68063044c6d (patch)
tree6ad530458c1600dabdf2f6cdcf3482282268ebca /vfd-dashboard/src/main/scala/vfd/dashboard/ui
parentdb266a316a2d5a22cd11503094e10b327a8e1cd6 (diff)
downloadmavigator-13eae49f4c48c03b2a7a9b40a08ca68063044c6d.tar.gz
mavigator-13eae49f4c48c03b2a7a9b40a08ca68063044c6d.tar.bz2
mavigator-13eae49f4c48c03b2a7a9b40a08ca68063044c6d.zip
rename subprojects
Diffstat (limited to 'vfd-dashboard/src/main/scala/vfd/dashboard/ui')
-rw-r--r--vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala57
-rw-r--r--vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/SvgInstrument.scala55
-rw-r--r--vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/instruments.scala124
-rw-r--r--vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Communication.scala106
-rw-r--r--vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Primary.scala46
5 files changed, 388 insertions, 0 deletions
diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala
new file mode 100644
index 0000000..29ca4ca
--- /dev/null
+++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/Layout.scala
@@ -0,0 +1,57 @@
+package vfd.dashboard.ui
+
+import org.scalajs.dom.HTMLElement
+
+import scalatags.JsDom.all.ExtendedString
+import scalatags.JsDom.all.bindNode
+import scalatags.JsDom.all.`class`
+import scalatags.JsDom.all.div
+import scalatags.JsDom.all.height
+import scalatags.JsDom.all.iframe
+import scalatags.JsDom.all.p
+import scalatags.JsDom.all.src
+import scalatags.JsDom.all.stringAttr
+import scalatags.JsDom.all.stringFrag
+import scalatags.JsDom.all.stringStyle
+import scalatags.JsDom.all.style
+import scalatags.JsDom.all.width
+import vfd.dashboard.Environment
+import vfd.dashboard.MavlinkSocket
+import vfd.dashboard.ui.panels.Communication
+import vfd.dashboard.ui.panels.Primary
+
+class Layout(socket: MavlinkSocket) {
+
+ val map = iframe(
+ width := "100%",
+ height := "350px",
+ "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&amp;layer=mapnik")
+
+ val feed = div(style := "width: 100%; height: 460px; color: #ffffff; background-color: #c2c2c2; text-align: center;")(
+ p(style := "padding-top: 220px")("video feed"))
+
+ def element(implicit env: Environment): HTMLElement = div(`class` := "container-fluid")(
+ div(`class` := "row")(
+ div(`class` := "col-xs-12")(
+ div(`class` := "panel panel-default")(
+ div(`class` := "panel-body")()))),
+ div(`class` := "row")(
+ div(`class` := "col-xs-4")(
+ div(`class` := "panel panel-default")(
+ div(`class` := "panel-body")(
+ map))),
+ div(`class` := "col-xs-5")(
+ div(`class` := "panel panel-default")(
+ div(`class` := "panel-body")(
+ feed)),
+ div(`class` := "panel panel-default")(
+ div(`class` := "panel-body")(Primary(socket)))),
+ div(`class` := "col-xs-3")(
+ div(`class` := "panel panel-default")(
+ div(`class` := "panel-body")(Communication(socket)))))).render
+
+} \ No newline at end of file
diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/SvgInstrument.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/SvgInstrument.scala
new file mode 100644
index 0000000..8ddba1a
--- /dev/null
+++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/SvgInstrument.scala
@@ -0,0 +1,55 @@
+package vfd.dashboard.ui.components
+
+import scala.scalajs.js.Any.fromFunction1
+
+import org.scalajs.dom
+
+import scalatags.JsDom.all.ExtendedString
+import scalatags.JsDom.all.`object`
+import scalatags.JsDom.all.stringAttr
+import scalatags.JsDom.all.stringFrag
+import scalatags.JsDom.all.stringStyle
+import scalatags.JsDom.all.`type`
+import scalatags.JsDom.all.width
+import vfd.dashboard.Environment
+
+trait SvgInstrument[A] {
+
+ /** SVG object element that contains the rendered instrument */
+ def element: dom.HTMLObjectElement
+
+ /** Actual svg document */
+ protected def content: dom.Document = element.contentDocument
+
+ /** Moveable parts of the instrument */
+ protected def moveable: Seq[dom.HTMLElement]
+
+ /** Updates the instrument to show a new value */
+ def update(value: A): Unit
+
+ protected def load(event: dom.Event): Unit = {
+ for (part <- moveable) {
+ part.style.transition = "transform 250ms ease-out"
+ }
+ }
+
+ element.addEventListener("load", (e: dom.Event) => load(e))
+}
+
+object SvgInstrument {
+
+ def svg(name: String)(implicit app: Environment): dom.HTMLObjectElement = {
+ val path = app.asset("images/instruments/" + name + ".svg")
+ `object`(`type` := "image/svg+xml", "data".attr := path, width := "100%")(
+ "Error loading instrument " + name).render
+ }
+
+ def translate(elem: dom.HTMLElement, x: Int, y: Int): Unit = {
+ elem.style.transform = "translate(" + x + "px, " + y + "px)";
+ }
+
+ def rotate(elem: dom.HTMLElement, deg: Int): Unit = {
+ elem.style.transform = "rotateZ(" + deg + "deg)";
+ }
+
+} \ No newline at end of file
diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/instruments.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/instruments.scala
new file mode 100644
index 0000000..1cde83c
--- /dev/null
+++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/components/instruments.scala
@@ -0,0 +1,124 @@
+package vfd.dashboard.ui.components
+
+import org.scalajs.dom
+
+import scalatags.JsDom.all.ExtendedString
+import scalatags.JsDom.all.`object`
+import scalatags.JsDom.all.stringAttr
+import scalatags.JsDom.all.stringFrag
+import scalatags.JsDom.all.stringStyle
+import scalatags.JsDom.all.`type`
+import scalatags.JsDom.all.width
+import vfd.dashboard.Environment
+
+class Led(implicit env: Environment) extends SvgInstrument[String] {
+ lazy val element = `object`(`type` := "image/svg+xml", "data".attr := env.asset("images/leds/led.svg"), width := "100%")(
+ "Error loading led.").render
+
+ def update(color: String) = {
+ content.getElementById("light").setAttribute("fill", color)
+ }
+
+ protected def moveable = Seq()
+
+}
+
+class Horizon(implicit env: Environment) extends SvgInstrument[(Double, Double)] {
+ lazy val element = SvgInstrument.svg("horizon")
+
+ def pitch = content.getElementById("pitch")
+ def roll = content.getElementById("roll")
+ protected def moveable = Seq(pitch, roll)
+
+ def update(pitchRoll: (Double, Double)) = {
+ SvgInstrument.translate(pitch, 0, pitchRoll._1.toInt)
+ SvgInstrument.rotate(roll, pitchRoll._2.toInt)
+ }
+}
+
+class Altimeter(implicit env: Environment) extends SvgInstrument[Double] {
+ lazy val element = SvgInstrument.svg("altimeter")
+
+ def hand = content.getElementById("hand")
+ protected def moveable = Seq(hand)
+
+ // 36deg === 1m
+ def update(altitude: Double) = {
+ SvgInstrument.rotate(hand, (altitude * 36).toInt)
+ }
+}
+
+class Compass(implicit env: Environment) extends SvgInstrument[Double] {
+ lazy val element = SvgInstrument.svg("compass")
+
+ def plate = content.getElementById("heading")
+ protected def moveable = Seq(plate)
+
+ def update(heading: Double) = {
+ SvgInstrument.rotate(plate, heading.toInt)
+ }
+}
+
+class Generic(
+ min: Double,
+ med: Double,
+ max: Double,
+ unit: String)(implicit env: Environment) extends SvgInstrument[Double] {
+
+ lazy val element = SvgInstrument.svg("generic")
+
+ def handElement = content.getElementById("hand")
+ def unitElement = content.getElementById("unit")
+ def valueElement = content.getElementById("value")
+ def minElement = content.getElementById("min")
+ def medElement = content.getElementById("med")
+ def maxElement = content.getElementById("max")
+ protected def moveable = Seq(handElement)
+
+ override protected def load(e: dom.Event) = {
+ unitElement.textContent = unit
+ minElement.textContent = min.toString
+ medElement.textContent = med.toString
+ maxElement.textContent = max.toString
+ update(min)
+ super.load(e)
+ }
+
+ def update(value: Double) = {
+ SvgInstrument.rotate(handElement, (value * 270 / (max - min)).toInt)
+ valueElement.textContent = value.toString
+ }
+}
+
+class Bar(implicit env: Environment) extends SvgInstrument[Double] {
+
+ lazy val element = SvgInstrument.svg("bar")
+
+ def level = content.getElementById("level")
+ protected def moveable = Seq(level)
+
+ def update(value: Double) = {
+ SvgInstrument.translate(level, 0, (97 * (1 - value / 100)).toInt)
+ }
+
+}
+
+class Balance(implicit env: Environment) extends SvgInstrument[(Double, Double, Double, Double)] {
+ lazy val element = SvgInstrument.svg("balance")
+
+ def position = content.getElementById("position")
+ protected def moveable = Seq(position)
+
+ def update(value: (Double, Double, Double, Double)) = {
+ val m0 = value._1
+ val m1 = value._2
+ val m2 = value._3
+ val m3 = value._4
+ val s = m0 + m1 + m2 + m3
+ val i = (m0 - m2) / s
+ val j = (m1 - m3) / s
+ val x = 0.5 * (i - j)
+ val y = 0.5 * (-i - j)
+ SvgInstrument.translate(position, (x * 50).toInt, (y * 50).toInt)
+ }
+} \ No newline at end of file
diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Communication.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Communication.scala
new file mode 100644
index 0000000..dd43ab4
--- /dev/null
+++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Communication.scala
@@ -0,0 +1,106 @@
+package vfd.dashboard.ui.panels
+
+import org.mavlink.messages.Heartbeat
+import org.mavlink.messages.Motor
+import org.mavlink.messages.Power
+import org.scalajs.dom.HTMLElement
+import rx.core.Obs
+import scalatags.JsDom.all.bindNode
+import scalatags.JsDom.all.`class`
+import scalatags.JsDom.all.div
+import scalatags.JsDom.all.stringAttr
+import scalatags.JsDom.all.table
+import scalatags.JsDom.all.tbody
+import scalatags.JsDom.all.td
+import scalatags.JsDom.all._
+import vfd.dashboard.Environment
+import vfd.dashboard.MavlinkSocket
+import vfd.dashboard.ui.components.Generic
+import vfd.dashboard.ui.components.Balance
+import vfd.dashboard.ui.components.Bar
+import vfd.dashboard.ui.components.Led
+
+object Communication {
+
+ def apply(socket: MavlinkSocket)(implicit app: Environment): HTMLElement = {
+
+ val hb = i(`class` := "fa fa-heart heartbeat").render
+
+ def foo() = {
+ hb.textContent = ""
+ }
+
+ val motor0 = new Generic(0, 50, 100, "%")
+ val motor1 = new Generic(0, 50, 100, "%")
+ val motor2 = new Generic(0, 50, 100, "%")
+ val motor3 = new Generic(0, 50, 100, "%")
+ val powerDistribution = new Balance()
+ val batteryLevel = new Bar()
+
+ Obs(socket.message, skipInitial = true) {
+ socket.message() match {
+ case Motor(m0, m1, m2, m3) =>
+ motor0.update(m0)
+ motor1.update(m1)
+ motor2.update(m2)
+ motor3.update(m3)
+ powerDistribution.update(m0, m1, m2, m3)
+
+ case Power(mV) =>
+ batteryLevel.update(100 * (mV - 9600) / 12600)
+ case Heartbeat(_) => {
+ hb.style.visibility = "hidden"
+ hb.style.visibility = "visible"
+ //hb.classList.remove("heartbeat")
+ //hb.offsetHeight
+ //hb.classList.add("heartbeat")
+ }
+ case _ =>
+ }
+ }
+
+ div(
+ table(`class` := "table")(
+ thead("Communication"),
+ tbody(
+ tr(
+ td("Conn"),
+ div(width := "20px")(td((new Led()).element)),
+ td("Server"),
+ td("5 ms")),
+ tr(
+ td("Uplink"),
+ td("-20 dBm"),
+ td("Heartbeat"),
+ td(hb)))),
+ table(`class` := "table-instrument", style := "height: 100px")(
+ tbody(
+ tr(
+ td(),
+ td(),
+ td(),
+ td(),
+ td(),
+ td(),
+ td(),
+ td(),
+ td(),
+ td(batteryLevel.element)))),
+ table (`class` := "table-instrument")(
+ thead("Motors"),
+ tbody(
+ tr(
+ td(motor0.element),
+ td(),
+ td(motor1.element)),
+ tr(
+ td(),
+ td(powerDistribution.element),
+ td()),
+ tr(
+ td(motor2.element),
+ td(),
+ td(motor3.element))))).render
+ }
+
+} \ No newline at end of file
diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Primary.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Primary.scala
new file mode 100644
index 0000000..14b26f6
--- /dev/null
+++ b/vfd-dashboard/src/main/scala/vfd/dashboard/ui/panels/Primary.scala
@@ -0,0 +1,46 @@
+package vfd.dashboard.ui.panels
+
+import org.mavlink.messages.Attitude
+import org.scalajs.dom.HTMLElement
+
+import rx.core.Obs
+import scalatags.JsDom.all.bindNode
+import scalatags.JsDom.all.`class`
+import scalatags.JsDom.all.stringAttr
+import scalatags.JsDom.all.table
+import scalatags.JsDom.all.tbody
+import scalatags.JsDom.all.td
+import scalatags.JsDom.all.tr
+import vfd.dashboard.Environment
+import vfd.dashboard.MavlinkSocket
+import vfd.dashboard.ui.components.Altimeter
+import vfd.dashboard.ui.components.Compass
+import vfd.dashboard.ui.components.Horizon
+
+object Primary {
+
+ def apply(socket: MavlinkSocket)(implicit env: Environment): HTMLElement = {
+
+ val compass = new Compass
+ val horizon = new Horizon
+ val altimeter = new Altimeter
+
+ Obs(socket.message, skipInitial = true) {
+ socket.message() match {
+ case Attitude(roll, pitch, yaw) =>
+ horizon.update(pitch, roll)
+ compass.update(yaw)
+ case _ => ()
+ }
+ }
+
+ table(`class` := "table-instrument")(
+ tbody(
+ tr(
+ td(compass.element),
+ td(horizon.element),
+ td(altimeter.element)))).render
+
+ }
+
+} \ No newline at end of file