aboutsummaryrefslogtreecommitdiff
path: root/vfd-frontend/src
diff options
context:
space:
mode:
Diffstat (limited to 'vfd-frontend/src')
-rw-r--r--vfd-frontend/src/main/scala/Frontend.scala41
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/Frontend.scala99
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala67
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/ui/Panels.scala116
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/util/Application.scala11
-rw-r--r--vfd-frontend/src/main/scala/vfd/frontend/util/Framework.scala58
6 files changed, 351 insertions, 41 deletions
diff --git a/vfd-frontend/src/main/scala/Frontend.scala b/vfd-frontend/src/main/scala/Frontend.scala
deleted file mode 100644
index dcb6a8d..0000000
--- a/vfd-frontend/src/main/scala/Frontend.scala
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-import scala.scalajs.js
-import js.annotation.JSExport
-import org.scalajs.dom
-import org.scalajs.spickling._
-import org.scalajs.spickling.jsany._
-
-@JSExport
-class Frontend {
- PicklerRegistry.register[vfd.uav.DataFrame]
-
- lazy val attitude = dom.document.getElementById("attitude").asInstanceOf[dom.HTMLObjectElement].contentDocument
- lazy val heading = dom.document.getElementById("heading").asInstanceOf[dom.HTMLObjectElement].contentDocument
- lazy val altitude = dom.document.getElementById("altitude").asInstanceOf[dom.HTMLObjectElement].contentDocument
-
- @JSExport
- def main() = {
- var roll = attitude.getElementById("roll");
- var pitch = attitude.getElementById("pitch");
- var heading = this.heading.getElementById("heading");
- var altitude = this.altitude.getElementById("hand")
-
- val connection = new dom.WebSocket("ws://localhost:9000/socket");
-
- connection.onmessage = (e: dom.MessageEvent) => {
-
- val data = PicklerRegistry.unpickle(js.JSON.parse(e.data.asInstanceOf[String]).asInstanceOf[js.Any] ).asInstanceOf[vfd.uav.DataFrame]
- //Console.println(d.roll)
-
-
-
-
- roll.setAttribute("transform", "rotate(" + data.roll.toDouble + ")");
- pitch.setAttribute("transform", "translate(0, " + data.pitch.toDouble + ")");
- heading.setAttribute("transform", "rotate(" + data.heading.toDouble + ")");
- altitude.setAttribute("transform", "rotate(" + data.altitude.toDouble * 36 + ")")
- }
- }
-
-} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/Frontend.scala b/vfd-frontend/src/main/scala/vfd/frontend/Frontend.scala
new file mode 100644
index 0000000..ffae846
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/Frontend.scala
@@ -0,0 +1,99 @@
+package vfd.frontend
+
+
+import scala.scalajs.js
+import js.annotation.JSExport
+
+import org.scalajs.dom
+import org.scalajs.spickling._
+import org.scalajs.spickling.jsany._
+
+import scalatags.JsDom.all._
+
+import rx._
+
+import vfd.frontend.ui._
+import vfd.frontend.util.Application
+import vfd.uav.DataFrame
+
+@JSExport
+class Frontend(rootId: String, assetsBase: String, socketUrl: String) {
+
+ lazy val root = dom.document.getElementById(rootId)
+ implicit lazy val app = new Application(root, assetsBase)
+
+ PicklerRegistry.register[vfd.uav.DataFrame]
+
+ @JSExport
+ def main() = {
+ val connection = new dom.WebSocket(socketUrl);
+ val input: Var[DataFrame] = Var(DataFrame(0,0,0,0,0))
+
+ connection.onmessage = (e: dom.MessageEvent) => {
+ val json = js.JSON.parse(e.data.asInstanceOf[String]).asInstanceOf[js.Any]
+ val frame = PicklerRegistry.unpickle(json).asInstanceOf[vfd.uav.DataFrame]
+ input() = frame
+ }
+
+ val element = div(`class` := "container-fluid")(
+ div(`class` := "row")(
+ div(`class` := "col-xs-12")(
+ div(`class` := "panel panel-default")(
+ div(`class` := "panel-body")(
+ Panels.autopilot
+ )
+ )
+ )
+ ),
+ div(`class` := "row")(
+ div(`class` := "col-xs-4")(
+ div(`class` := "panel panel-default")(
+ div(`class` := "panel-body")(
+ Panels.secondary(input)
+ )
+ )
+ ),
+ div(`class` := "col-xs-5")(
+ div(`class` := "panel panel-default")(
+ div(`class` := "panel-body")(
+ Panels.primary(input)
+ )
+ )
+ ),
+ div(`class` := "col-xs-3")(
+ div(`class` := "panel panel-default")(
+ div(`class` := "panel-body")(
+ Panels.eicas()
+ )
+ )
+ )
+ )
+ )
+
+ root.appendChild(element.render)
+
+ }
+
+
+
+
+
+/*
+ def alert() = {
+ val image = "/assets/images/leds/led.svg"
+ val off = "#782121"
+ val on = "#ff0000"
+ val controls = div(
+ `object`("data".attr := image, `type` := "image/svg+xml", width:= "32px")(
+ "Cannot load"
+ ),
+ audio(
+ "autoplay".attr:="true",
+ source(src:="/assets/audio/alarm.ogg", `type`:="audio/ogg")
+ )
+
+ ).render
+ controls
+ }*/
+
+} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala
new file mode 100644
index 0000000..959ca8a
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala
@@ -0,0 +1,67 @@
+package vfd.frontend.ui
+
+import rx._
+import scalatags.JsDom.all._
+import vfd.frontend.util.Application
+import org.scalajs.dom.HTMLElement
+
+object Components {
+
+ def led(color: Rx[String], size: String)(implicit app: Application) = {
+ val elem = `object`(`type` := "image/svg+xml", "data".attr := app.asset("leds/led.svg"), width := size)(
+ "Error loading image."
+ ).render
+
+ Obs(color, skipInitial = true) {
+ val svg = elem.contentDocument
+ svg.getElementById("light").setAttribute("fill", color())
+ }
+ elem
+ }
+
+ private def instrument(name: String)(implicit app: Application) = {
+ val path = app.asset("images/instruments/" + name + ".svg")
+ `object`(`type` := "image/svg+xml", "data".attr := path, width := "100%")(
+ "Error loading image " + name
+ ).render
+ }
+
+ private def frame(elem: HTMLElement, size: String) = {
+ div(style := s"width: $size; height: $size; display: inline-block;" )(
+ elem
+ )
+ }
+
+ def attitude(pitchRoll: Rx[(Double, Double)], size: String)(implicit app: Application) = {
+ val inst = instrument("attitude")
+ Obs(pitchRoll, skipInitial = true){
+ val svg = inst.contentDocument
+ val pitch = svg.getElementById("pitch")
+ val roll = svg.getElementById("roll")
+ pitch.setAttribute("transform", "translate(0, " + pitchRoll()._1 / math.Pi * 180 + ")");
+ roll.setAttribute("transform", "rotate(" + pitchRoll()._2 / math.Pi * 180 + ")");
+ }
+ frame(inst, size)
+ }
+
+ def altitude(value: Rx[Double], size: String)(implicit app: Application) = {
+ val inst = instrument("altitude")
+ Obs(value, skipInitial = true){
+ val svg = inst.contentDocument
+ // 36deg === 1m
+ svg.getElementById("hand").setAttribute("transform", "rotate(" + value() * 36 + ")");
+ }
+ frame(inst, size)
+ }
+
+ def heading(value: Rx[Double], size: String)(implicit app: Application) = {
+ val inst = instrument("heading")
+ Obs(value, skipInitial = true){
+ val svg = inst.contentDocument
+ // 1deg === 1deg
+ svg.getElementById("heading").setAttribute("transform", "rotate(" + value() / math.Pi * 180 + ")");
+ }
+ frame(inst, size)
+ }
+
+} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/ui/Panels.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/Panels.scala
new file mode 100644
index 0000000..8c5e38d
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/ui/Panels.scala
@@ -0,0 +1,116 @@
+package vfd.frontend.ui
+
+import rx._
+import rx.ops._
+import scalatags.JsDom.all._
+import vfd.uav.DataFrame
+import vfd.frontend.util.Application
+import vfd.frontend.util.Framework._
+
+
+object Panels {
+
+ def primary(input: Rx[DataFrame])(implicit app: Application) = div(
+ Components.heading(input map (_.heading), "33%"),
+ Components.attitude(input map (i => (i.pitch, i.roll)), "33%"),
+ Components.altitude(input map (_.altitude), "33%")
+ )
+
+ def secondary(input: Rx[DataFrame])(implicit app: Application) = div(
+ 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&layer=mapnik"
+ ),
+ table(`class`:="table")(
+ tr(
+ td("UAV Position"),
+ td("N13.1234 E1234.23465")
+ ),
+ tr(
+ td("Base Position"),
+ td("N13.1234 E1234.23465")
+ ),
+ tr(
+ td("Distance to UAV"),
+ td("200 m")
+ ),
+ tr(
+ td("Total flight distance"),
+ td("12.3 km")
+ ),
+ tr(
+ td("Groundspeed"),
+ td("23 km/h")
+ ),
+ tr(
+ td("---"),
+ td("")
+ ),
+ tr(
+ td("Below"),
+ td("180 cm")
+ )
+ )
+ )
+
+ def eicas()(implicit app: Application) = {
+ table(`class`:="table")(
+ tr(
+ td("Link Server"),
+ td("3"),
+ td("ms"),
+ td(img(src:="/assets/images/leds/red-off.svg",width:="16px"))
+ ),
+ tr(
+ td("Link UAV"),
+ td("-80"),
+ td("dB(m)"),
+ td(img(src:="/assets/images/leds/red-on.svg",width:="16px"))
+ ),
+ tr(
+ td("---"),
+ td(""),
+ td(""),
+ td("")
+ ),
+ tr(
+ td("Charge"),
+ td("4.800"),
+ td("Ah"),
+ td(img(src:="/assets/images/leds/red-off.svg",width:="16px"))
+ ),
+ tr(
+ td("Current"),
+ td("80"),
+ td("A"),
+ td(img(src:="/assets/images/leds/yellow-on.svg",width:="16px"))
+ ),
+ tr(
+ td("Endurance"),
+ td("14"),
+ td("min"),
+ td(img(src:="/assets/images/leds/none.svg",width:="16px"))
+ ),
+ tr(
+ td("GPS"),
+ td("5"),
+ td("satellites"),
+ td(img(src:="/assets/images/leds/none.svg",width:="16px"))
+ )
+ )
+ }
+
+ def autopilot = div(`class` := "btn-group")(
+ button(`type`:="button", `class`:="btn btn-default")("Auto"),
+ button(`type`:="button", `class`:="btn btn-default")("Position"),
+ button(`type`:="button", `class`:="btn btn-default")("Attitude"),
+ button(`type`:="button", `class`:="btn btn-default")(
+ span(`class`:="label label-default")("Manual")
+ )
+ )
+} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/util/Application.scala b/vfd-frontend/src/main/scala/vfd/frontend/util/Application.scala
new file mode 100644
index 0000000..3b4e733
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/util/Application.scala
@@ -0,0 +1,11 @@
+package vfd.frontend.util
+
+import org.scalajs.dom.HTMLElement
+
+class Application(element: HTMLElement, assetsBase: String) {
+
+ def root = element
+
+ def asset(file: String): String = assetsBase + "/" + file
+
+} \ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/util/Framework.scala b/vfd-frontend/src/main/scala/vfd/frontend/util/Framework.scala
new file mode 100644
index 0000000..76dceea
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/util/Framework.scala
@@ -0,0 +1,58 @@
+package vfd.frontend.util
+
+import scala.collection.{SortedMap, mutable}
+import scalatags.JsDom.all._
+import scala.util.{Failure, Success, Random}
+import rx._
+import rx.core.{Propagator, Obs}
+import org.scalajs.dom
+import org.scalajs.dom.{Element, DOMParser}
+import scala.scalajs.js
+import scala.language.implicitConversions
+
+/**
+ * A minimal binding between Scala.Rx and Scalatags and Scala-Js-Dom
+ * taken from https://github.com/lihaoyi/workbench-example-app/blob/todomvc/src/main/scala/example/Framework.scala, by Li Haoyi
+ */
+object Framework {
+
+ /**
+ * Wraps reactive strings in spans, so they can be referenced/replaced
+ * when the Rx changes.
+ */
+ implicit def RxStr[T](r: Rx[T])(implicit f: T => Frag): Frag = {
+ rxMod(Rx(span(r())))
+ }
+
+ /**
+ * 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 <: dom.HTMLElement](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)
+ }
+
+ implicit def RxAttrValue[T: AttrValue] = new AttrValue[Rx[T]]{
+ def apply(t: Element, a: Attr, r: Rx[T]): Unit = {
+ Obs(r){ implicitly[AttrValue[T]].apply(t, a, r())}
+ }
+ }
+
+ implicit def RxStyleValue[T: StyleValue] = new StyleValue[Rx[T]]{
+ def apply(t: Element, s: Style, r: Rx[T]): Unit = {
+ Obs(r){ implicitly[StyleValue[T]].apply(t, s, r())}
+ }
+ }
+}