aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jodersky@gmail.com>2015-03-23 20:18:43 +0100
committerJakob Odersky <jodersky@gmail.com>2015-03-23 22:57:58 +0100
commit402d5c80d54f16d41a4544131a7c09ff26f7aa84 (patch)
tree30633d632424f14fcb177ce539f52f26e0a4a5c9
parent26225ca2275fb20149efecbd287564fbed2c1e23 (diff)
downloadmavigator-402d5c80d54f16d41a4544131a7c09ff26f7aa84.tar.gz
mavigator-402d5c80d54f16d41a4544131a7c09ff26f7aa84.tar.bz2
mavigator-402d5c80d54f16d41a4544131a7c09ff26f7aa84.zip
implement index landing page
-rw-r--r--project/Build.scala34
-rw-r--r--vfd-dashboard/src/main/scala/vfd/dashboard/Launcher.scala35
-rw-r--r--vfd-dashboard/src/main/scala/vfd/dashboard/Main.scala16
-rw-r--r--vfd-index/.gitignore29
-rw-r--r--vfd-index/src/main/scala/vfd/index/Main.scala62
-rw-r--r--vfd-index/src/main/scala/vfd/index/Util.scala39
-rw-r--r--vfd-main/app/controllers/Application.scala6
-rw-r--r--vfd-main/app/views/app.scala.html50
-rw-r--r--vfd-main/app/views/dashboard.scala.html40
-rw-r--r--vfd-main/app/views/index.scala.html36
-rw-r--r--vfd-main/app/views/main.scala.html36
-rw-r--r--vfd-main/app/views/uav.scala.html37
-rw-r--r--vfd-main/conf/routes8
13 files changed, 302 insertions, 126 deletions
diff --git a/project/Build.scala b/project/Build.scala
index 60a3edc..0a18e7c 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -46,7 +46,7 @@ object ApplicationBuild extends Build {
settings(common: _*)
settings(
resolvers += Resolver.url("scala-js-releases", url("http://dl.bintray.com/content/scala-js/scala-js-releases"))(Resolver.ivyStylePatterns),
- scalaJSProjects := Seq(dashboard),
+ scalaJSProjects := Seq(dashboard, index),
pipelineStages := Seq(scalaJSProd),
libraryDependencies ++= Seq(
"org.webjars" % "bootstrap" % "3.3.1",
@@ -55,7 +55,10 @@ object ApplicationBuild extends Build {
)
)
dependsOn(uav)
- aggregate(projectToRef(dashboard))
+ aggregate(
+ projectToRef(dashboard),
+ projectToRef(index)
+ )
)
//communication backend
@@ -72,20 +75,31 @@ object ApplicationBuild extends Build {
)
)
- //web frontend
+ //web frontends
+ val scalajs = Seq(
+ libraryDependencies ++= Seq(
+ "org.scala-js" %%% "scalajs-dom" % "0.8.0",
+ "com.lihaoyi" %%% "scalatags" % "0.4.6",
+ "com.lihaoyi" %%% "scalarx" % "0.2.8"
+ )
+ )
+
lazy val dashboard = (
Project("vfd-dashboard", file("vfd-dashboard"))
enablePlugins(ScalaJSPlugin)
enablePlugins(ScalaJSPlay)
enablePlugins(SbtMavlink)
settings(common: _*)
- settings(
- libraryDependencies ++= Seq(
- "org.scala-js" %%% "scalajs-dom" % "0.8.0",
- "com.lihaoyi" %%% "scalatags" % "0.4.6",
- "com.lihaoyi" %%% "scalarx" % "0.2.8"
- )
- )
+ settings(scalajs: _*)
+ )
+
+ lazy val index = (
+ Project("vfd-index", file("vfd-index"))
+ enablePlugins(ScalaJSPlugin)
+ enablePlugins(ScalaJSPlay)
+ enablePlugins(SbtMavlink)
+ settings(common: _*)
+ settings(scalajs: _*)
)
} \ No newline at end of file
diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/Launcher.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/Launcher.scala
deleted file mode 100644
index d091911..0000000
--- a/vfd-dashboard/src/main/scala/vfd/dashboard/Launcher.scala
+++ /dev/null
@@ -1,35 +0,0 @@
-package vfd.dashboard
-
-import scala.scalajs.js.annotation.JSExport
-
-import org.scalajs.dom
-import org.scalajs.dom.html
-
-@JSExport("Launcher")
-class Launcher(rootId: String, assetsBase: String) {
-
- lazy val env = new Environment {
- val root = dom.document.getElementById(rootId).asInstanceOf[html.Element]
- def asset(file: String) = assetsBase + "/" + file
- }
-
- @JSExport
- def main() = {
- import env._
-
- val args: Seq[(String, String)] = for (
- i <- 0 until root.attributes.length;
- attr = root.attributes.item(i);
- if attr.name.startsWith("data-")
- ) yield {
- attr.name.drop(5) -> attr.value
- }
-
- while (env.root.hasChildNodes) {
- env.root.removeChild(env.root.firstChild)
- }
-
- Main.main(args.toMap)(env)
- }
-
-} \ No newline at end of file
diff --git a/vfd-dashboard/src/main/scala/vfd/dashboard/Main.scala b/vfd-dashboard/src/main/scala/vfd/dashboard/Main.scala
index 36ca2db..71c5378 100644
--- a/vfd-dashboard/src/main/scala/vfd/dashboard/Main.scala
+++ b/vfd-dashboard/src/main/scala/vfd/dashboard/Main.scala
@@ -1,11 +1,23 @@
package vfd.dashboard
+import scala.scalajs.js
+import scala.scalajs.js.annotation.JSExport
+
+import org.scalajs.dom.html
+
import vfd.dashboard.ui.Layout
+@JSExport("Main")
object Main {
- def main(args: Map[String, String])(implicit env: Environment) = {
- val socket = new MavlinkSocket(args("socketurl"), args("remotesystemid").toInt)
+ @JSExport
+ def main(rootElement: html.Element, assetsBase: String, args: js.Dictionary[String]) = {
+ implicit val env = new Environment {
+ def root = rootElement
+ def asset(file: String) = assetsBase + "/" + file
+ }
+
+ val socket = new MavlinkSocket(args("socketUrl"), args("remoteSystemId").toInt)
val layout = new Layout(socket)
env.root.appendChild(layout.element)
diff --git a/vfd-index/.gitignore b/vfd-index/.gitignore
new file mode 100644
index 0000000..e38fe35
--- /dev/null
+++ b/vfd-index/.gitignore
@@ -0,0 +1,29 @@
+# sbt
+.cache
+.history/
+.lib/
+dist/*
+target/
+lib_managed/
+src_managed/
+project/boot/
+project/plugins/project/
+
+# scala-ide specific
+/.settings
+/.scala_dependencies
+/.project
+/.classpath
+/.cache
+/.history
+
+# ensime
+.ensime
+
+# general files
+/*.jar
+*.swp
+*.class
+*.log
+*~
+
diff --git a/vfd-index/src/main/scala/vfd/index/Main.scala b/vfd-index/src/main/scala/vfd/index/Main.scala
new file mode 100644
index 0000000..f2f9f0d
--- /dev/null
+++ b/vfd-index/src/main/scala/vfd/index/Main.scala
@@ -0,0 +1,62 @@
+package vfd.index
+
+import org.mavlink.Parser
+import org.mavlink.messages.Message
+import org.mavlink.messages.Heartbeat
+
+import org.scalajs.dom
+import org.scalajs.dom.html
+
+import rx._
+
+import scala.scalajs.js
+import scala.scalajs.js.annotation.JSExport
+
+import scalatags.JsDom.all._
+
+@JSExport("Main")
+object Main {
+ import Util._
+
+ case class ActiveVehicle(id: Int)
+
+ val active = Var(Set.empty[ActiveVehicle])
+
+ val parser = new Parser(
+ packet => Message.unpack(packet.messageId, packet.payload) match {
+ case hb: Heartbeat =>
+ active() += ActiveVehicle(packet.systemId)
+ case _ => ()
+ })
+
+ @JSExport
+ def main(root: html.Element, baseAssets: String, args: js.Dictionary[String]): Unit = {
+
+ val connection = new dom.WebSocket(args("socketUrl"))
+
+ connection.binaryType = "arraybuffer";
+ connection.onmessage = (e: dom.MessageEvent) => {
+ val buffer = e.data.asInstanceOf[js.typedarray.ArrayBuffer]
+ val view = new js.typedarray.DataView(buffer)
+
+ for (i <- 0 until view.byteLength) {
+ parser.push(view.getInt8(i))
+ }
+ }
+
+ root.appendChild(table(`class` := "table table-hover")(
+ thead(
+ tr(
+ th("System ID"),
+ th(""))),
+ Rx {
+ tbody(
+ for (vehicle <- active().toSeq) yield {
+ tr(
+ td(vehicle.id),
+ td(a(href := "/dashboard/" + vehicle.id, `class` := "btn btn-default")("Pilot")))
+ })
+ }).render)
+ }
+
+} \ No newline at end of file
diff --git a/vfd-index/src/main/scala/vfd/index/Util.scala b/vfd-index/src/main/scala/vfd/index/Util.scala
new file mode 100644
index 0000000..09793df
--- /dev/null
+++ b/vfd-index/src/main/scala/vfd/index/Util.scala
@@ -0,0 +1,39 @@
+package vfd.index
+
+import scala.language.implicitConversions
+
+import org.scalajs.dom.html
+
+import rx._
+
+import scala.util.Try
+import scala.util.Success
+import scala.util.Failure
+
+import scalatags.JsDom.all._
+
+object Util {
+
+ /**
+ * 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-main/app/controllers/Application.scala b/vfd-main/app/controllers/Application.scala
index 827950e..149ac1c 100644
--- a/vfd-main/app/controllers/Application.scala
+++ b/vfd-main/app/controllers/Application.scala
@@ -14,11 +14,11 @@ object Application extends Controller {
private def plugin = current.plugin[UavPlugin].getOrElse(throw new RuntimeException("UAV plugin is not available"))
def index = Action { implicit request =>
- Redirect(routes.Application.uav(0))
+ Ok(views.html.index(routes.Application.mavlink.webSocketURL()))
}
- def uav(remoteSystemId: Int) = Action { implicit request =>
- Ok(views.html.uav(routes.Application.mavlink.webSocketURL(), remoteSystemId.toByte, plugin.systemId, 0.toByte))
+ def dashboard(remoteSystemId: Int) = Action { implicit request =>
+ Ok(views.html.dashboard(routes.Application.mavlink.webSocketURL(), remoteSystemId.toByte, plugin.systemId, 0.toByte))
}
def mavlink = WebSocket.acceptWithActor[Array[Byte], Array[Byte]] { implicit request =>
diff --git a/vfd-main/app/views/app.scala.html b/vfd-main/app/views/app.scala.html
new file mode 100644
index 0000000..ea738f0
--- /dev/null
+++ b/vfd-main/app/views/app.scala.html
@@ -0,0 +1,50 @@
+@(app: String)(args: (String, String)*)
+
+@import play.api.Play
+
+<div id="scalajsError" class="alert alert-danger" style="display: none;">
+ <p><strong><i class="fa fa-bug"></i> Error! </strong> An uncaught exception occurred in the browser application,
+ any information displayed on this website may be corrupt. This is NOT an error that should occur under normal
+ operation, it is an indication of a bug in the software.</p>
+ <p>The error was: "<span id="scalajsErrorMessage"></span>"
+ </p>
+</div>
+
+<div id="@app">
+ <div class="loader">
+ <i class="fa fa-spinner fa-spin"></i>
+ </div>
+</div>
+
+<script type="text/javascript">
+ document.addEventListener('DOMContentLoaded', function() {
+ try {
+ var root = document.getElementById('@app')
+ var args = {
+ @args.map{ case (key, value) =>
+ @key: '@value',
+ }
+ }
+
+ while (root.firstChild) {
+ root.removeChild(root.firstChild);
+ }
+
+ Main().main(
+ root,
+ '@routes.Assets.at("")',
+ args
+ )
+ } catch(err) {
+ document.getElementById("scalajsError").style.display = "block";
+ document.getElementById("scalajsErrorMessage").innerHTML = err;
+ console.error(err)
+ }
+ }, false);
+</script>
+
+@if(Play.isProd(Play.current)) {
+ <script type="text/javascript" src="@routes.Assets.at(app + "-opt.js")"></script>
+} else {
+ <script type="text/javascript" src="@routes.Assets.at(app + "-fastopt.js")"></script>
+} \ No newline at end of file
diff --git a/vfd-main/app/views/dashboard.scala.html b/vfd-main/app/views/dashboard.scala.html
new file mode 100644
index 0000000..02a79f7
--- /dev/null
+++ b/vfd-main/app/views/dashboard.scala.html
@@ -0,0 +1,40 @@
+@(socket: String, remoteSystemId: Byte, systemId: Byte, componentId: Byte)
+
+@main("Main"){
+
+ <header>
+ <nav class="navbar navbar-inverse navbar-static-top" role="navigation">
+ <div class="container-fluid">
+ <!-- Brand and toggle get grouped for better mobile display -->
+ <div class="navbar-header">
+ <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-main-collapse">
+ <span class="sr-only">Toggle navigation</span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ <span class="icon-bar"></span>
+ </button>
+ <a class="navbar-brand" href="@routes.Application.index">
+ <!-- <img style="max-height: 100%;" src="@routes.Assets.at("images/logo-invert.svg")" alt="logo"> -->
+ Flight Control Panel
+ </a>
+ </div>
+
+ <!-- Collect the nav links, forms, and other content for toggling -->
+ <div class="collapse navbar-collapse" id="navbar-main-collapse">
+ <div class="nav navbar-nav navbar-right">
+ <p class="navbar-text">Remote System @remoteSystemId</p>
+ </div>
+ </div><!-- /.navbar-collapse -->
+ </div><!-- /.container-fluid -->
+ </nav>
+ </header>
+
+ @app("vfd-dashboard")(
+ "socketUrl" -> socket,
+ "remoteSystemId" -> remoteSystemId.toString,
+ "systemId" -> systemId.toString,
+ "componentId" -> componentId.toString
+ )
+
+}
+
diff --git a/vfd-main/app/views/index.scala.html b/vfd-main/app/views/index.scala.html
new file mode 100644
index 0000000..49b6a71
--- /dev/null
+++ b/vfd-main/app/views/index.scala.html
@@ -0,0 +1,36 @@
+@(socket: String)
+
+@import play.api.Play
+
+@main("Home") {
+
+ <div class="container" style="margin-top: 200px">
+ <div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3">
+
+ <div class="panel panel-default">
+ <div class="panel-heading">
+ <h3 class="panel-title">
+ <strong>Available vehicles </strong>
+ </h3>
+ </div>
+ <div class="panel-body">
+ @app("vfd-index")(
+ "socketUrl" -> socket
+ )
+ <i class="fa fa-spinner fa-spin"></i> Listening for heartbeats...
+ </div>
+ </div>
+ @if(!Play.isProd(Play.current)) {
+ <div class="alert alert-warning">
+ <strong><i class="fa fa-exclamation-triangle"></i> Warning</strong> running in development mode
+ </div>
+ }
+ <div class="text-center">
+ <a href="#"><small></small></a>
+ <a href="#"><small>Help</small></a>
+ </div>
+ </div>
+ </div>
+
+
+} \ No newline at end of file
diff --git a/vfd-main/app/views/main.scala.html b/vfd-main/app/views/main.scala.html
index 4c680fd..6300e1f 100644
--- a/vfd-main/app/views/main.scala.html
+++ b/vfd-main/app/views/main.scala.html
@@ -1,6 +1,4 @@
-@(title: String, info: String)(content: Html)
-
-@import play.api.Play
+@(title: String)(content: Html)
<!DOCTYPE html>
@@ -17,43 +15,11 @@
<link rel="stylesheet" media="screen" href="@routes.Assets.at("stylesheets/main.css")">
</head>
<body>
- <header>
- <nav class="navbar navbar-inverse navbar-static-top" role="navigation">
- <div class="container-fluid">
- <!-- Brand and toggle get grouped for better mobile display -->
- <div class="navbar-header">
- <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navbar-main-collapse">
- <span class="sr-only">Toggle navigation</span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- </button>
- <a class="navbar-brand" href="@routes.Application.index">
- <!-- <img style="max-height: 100%;" src="@routes.Assets.at("images/logo-invert.svg")" alt="logo"> -->
- Flight Control Panel
- </a>
- </div>
-
- <!-- Collect the nav links, forms, and other content for toggling -->
- <div class="collapse navbar-collapse" id="navbar-main-collapse">
- <div class="nav navbar-nav navbar-right">
- <p class="navbar-text">@info</p>
- </div>
- </div><!-- /.navbar-collapse -->
- </div><!-- /.container-fluid -->
- </nav>
- </header>
@content
<script type="text/javascript" src="@routes.Assets.at("lib/jquery/jquery.js")"></script>
<script type="text/javascript" src="@routes.Assets.at("lib/bootstrap/js/bootstrap.min.js")"></script>
-
- @if(Play.isProd(Play.current)) {
- <script type="text/javascript" src="@routes.Assets.at("vfd-dashboard-opt.js")"></script>
- } else {
- <script type="text/javascript" src="@routes.Assets.at("vfd-dashboard-fastopt.js")"></script>
- }
</body>
</html> \ No newline at end of file
diff --git a/vfd-main/app/views/uav.scala.html b/vfd-main/app/views/uav.scala.html
deleted file mode 100644
index 0ce7927..0000000
--- a/vfd-main/app/views/uav.scala.html
+++ /dev/null
@@ -1,37 +0,0 @@
-@(socket: String, remoteSystemId: Byte, systemId: Byte, componentId: Byte)
-
-@main("Main", "Remote System " + remoteSystemId){
-
- <div id="scalajsError" class="alert alert-danger" style="display: none;">
- <p><strong><i class="fa fa-bug"></i> Error! </strong> An uncaught exception occurred in the browser application,
- any information displayed on this website may be corrupt. This is NOT an error that should occur under normal
- operation, it is an indication of a bug in the software.</p>
- <p>The error was: "<span id="scalajsErrorMessage"></span>"
- </p>
- </div>
-
- <div
- id="app"
- data-socketUrl="@socket"
- data-remoteSystemId="@remoteSystemId.toString"
- data-systemId="@systemId.toString"
- data-componentId="@componentId.toString">
- <div class="loader">
- <i class="fa fa-spinner fa-spin"></i>
- </div>
- </div>
-
- <script type="text/javascript">
- window.onload = function () {
- try {
- var launcher = new Launcher('app', '@routes.Assets.at("")');
- launcher.main();
- } catch(err) {
- document.getElementById("scalajsError").style.display = "block";
- document.getElementById("scalajsErrorMessage").innerHTML = err;
- console.error(err)
- }
- }
- </script>
-}
-
diff --git a/vfd-main/conf/routes b/vfd-main/conf/routes
index 3d38889..a89add4 100644
--- a/vfd-main/conf/routes
+++ b/vfd-main/conf/routes
@@ -3,9 +3,9 @@
# ~~~~
# Home page
-GET / controllers.Application.index
-GET /uav/:remoteSystemId controllers.Application.uav(remoteSystemId: Int)
-GET /mavlink controllers.Application.mavlink
+GET / controllers.Application.index
+GET /dashboard/:remoteSystemId controllers.Application.dashboard(remoteSystemId: Int)
+GET /mavlink controllers.Application.mavlink
# Map static resources from the /public folder to the /assets URL path
-GET /assets/*file controllers.Assets.at(path="/public", file)
+GET /assets/*file controllers.Assets.at(path="/public", file)