diff options
-rw-r--r-- | project/Build.scala | 34 | ||||
-rw-r--r-- | vfd-dashboard/src/main/scala/vfd/dashboard/Launcher.scala | 35 | ||||
-rw-r--r-- | vfd-dashboard/src/main/scala/vfd/dashboard/Main.scala | 16 | ||||
-rw-r--r-- | vfd-index/.gitignore | 29 | ||||
-rw-r--r-- | vfd-index/src/main/scala/vfd/index/Main.scala | 62 | ||||
-rw-r--r-- | vfd-index/src/main/scala/vfd/index/Util.scala | 39 | ||||
-rw-r--r-- | vfd-main/app/controllers/Application.scala | 6 | ||||
-rw-r--r-- | vfd-main/app/views/app.scala.html | 50 | ||||
-rw-r--r-- | vfd-main/app/views/dashboard.scala.html | 40 | ||||
-rw-r--r-- | vfd-main/app/views/index.scala.html | 36 | ||||
-rw-r--r-- | vfd-main/app/views/main.scala.html | 36 | ||||
-rw-r--r-- | vfd-main/app/views/uav.scala.html | 37 | ||||
-rw-r--r-- | vfd-main/conf/routes | 8 |
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) |