From 4238db415450ddddfe4c3ebd80fb7f641dd671ec Mon Sep 17 00:00:00 2001
From: Jakob Odersky
Date: Fri, 26 Dec 2014 14:58:11 +0100
Subject: refactor ui
---
vfd-backend/app/controllers/Application.scala | 8 +-
vfd-backend/app/plugins/UavClientConnection.scala | 4 +
vfd-backend/app/plugins/UavPlugin.scala | 12 +-
vfd-backend/app/views/main.scala.html | 2 +-
vfd-backend/app/views/uav.scala.html | 15 +-
vfd-backend/conf/application.conf | 6 +-
vfd-backend/conf/routes | 8 +-
vfd-backend/public/images/instruments/balance.svg | 169 +++
vfd-backend/public/images/instruments/bar.svg | 63 +-
vfd-backend/public/images/instruments/basic.svg | 314 -----
vfd-backend/public/images/instruments/generic.svg | 1217 ++++----------------
vfd-backend/public/images/instruments/generic2.svg | 1063 +++++++++++++++++
vfd-backend/public/stylesheets/main.css | 43 +-
.../src/main/scala/vfd/frontend/Environment.scala | 14 +
.../src/main/scala/vfd/frontend/Launcher.scala | 4 +-
.../src/main/scala/vfd/frontend/Main.scala | 69 +-
.../main/scala/vfd/frontend/MavlinkSocket.scala | 23 +-
.../main/scala/vfd/frontend/ui/Components.scala | 105 --
.../src/main/scala/vfd/frontend/ui/Layout.scala | 57 +
.../vfd/frontend/ui/components/SvgInstrument.scala | 55 +
.../vfd/frontend/ui/components/instruments.scala | 124 ++
.../vfd/frontend/ui/panels/Communication.scala | 128 +-
.../scala/vfd/frontend/ui/panels/Primary.scala | 49 +-
.../main/scala/vfd/frontend/util/Environment.scala | 14 -
.../main/scala/vfd/frontend/util/Framework.scala | 70 --
.../src/main/scala/vfd/frontend/util/package.scala | 15 -
26 files changed, 1915 insertions(+), 1736 deletions(-)
create mode 100644 vfd-backend/public/images/instruments/balance.svg
delete mode 100644 vfd-backend/public/images/instruments/basic.svg
create mode 100644 vfd-backend/public/images/instruments/generic2.svg
create mode 100644 vfd-frontend/src/main/scala/vfd/frontend/Environment.scala
delete mode 100644 vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala
create mode 100644 vfd-frontend/src/main/scala/vfd/frontend/ui/Layout.scala
create mode 100644 vfd-frontend/src/main/scala/vfd/frontend/ui/components/SvgInstrument.scala
create mode 100644 vfd-frontend/src/main/scala/vfd/frontend/ui/components/instruments.scala
delete mode 100644 vfd-frontend/src/main/scala/vfd/frontend/util/Environment.scala
delete mode 100644 vfd-frontend/src/main/scala/vfd/frontend/util/Framework.scala
delete mode 100644 vfd-frontend/src/main/scala/vfd/frontend/util/package.scala
diff --git a/vfd-backend/app/controllers/Application.scala b/vfd-backend/app/controllers/Application.scala
index 09e0e0d..827950e 100644
--- a/vfd-backend/app/controllers/Application.scala
+++ b/vfd-backend/app/controllers/Application.scala
@@ -11,18 +11,18 @@ import plugins.UavPlugin
object Application extends Controller {
- private def uav = current.plugin[UavPlugin].getOrElse(throw new RuntimeException("UAV plugin is not available"))
+ 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))
}
- def uav(sysId: Int) = Action { implicit request =>
- Ok(views.html.uav(routes.Application.mavlink.webSocketURL(), sysId))
+ def uav(remoteSystemId: Int) = Action { implicit request =>
+ Ok(views.html.uav(routes.Application.mavlink.webSocketURL(), remoteSystemId.toByte, plugin.systemId, 0.toByte))
}
def mavlink = WebSocket.acceptWithActor[Array[Byte], Array[Byte]] { implicit request =>
- out => uav.register(out)
+ out => plugin.register(out)
}
}
\ No newline at end of file
diff --git a/vfd-backend/app/plugins/UavClientConnection.scala b/vfd-backend/app/plugins/UavClientConnection.scala
index b479aa5..76975e1 100644
--- a/vfd-backend/app/plugins/UavClientConnection.scala
+++ b/vfd-backend/app/plugins/UavClientConnection.scala
@@ -5,6 +5,7 @@ import akka.actor.ActorLogging
import akka.actor.ActorRef
import akka.actor.actorRef2Scala
import vfd.uav.Connection
+import akka.util.ByteString
/**
* Interfaces traffic from a websocket with a connection to a UAV.
@@ -23,6 +24,9 @@ class UavClientConnection(websocket: ActorRef, uav: ActorRef) extends Actor with
case Connection.Closed(msg) =>
log.warning(msg)
context stop self
+
+ case fromClient: Array[Byte] =>
+ uav ! Connection.Send(ByteString(fromClient))
}
diff --git a/vfd-backend/app/plugins/UavPlugin.scala b/vfd-backend/app/plugins/UavPlugin.scala
index 43e015c..9b45627 100644
--- a/vfd-backend/app/plugins/UavPlugin.scala
+++ b/vfd-backend/app/plugins/UavPlugin.scala
@@ -12,29 +12,31 @@ class UavPlugin(app: Application) extends Plugin {
private lazy val config = app.configuration.getConfig("uav")
- lazy val systemId = config.flatMap(_.getInt("system_id")).getOrElse(1)
+ lazy val systemId = config.flatMap(_.getInt("system_id")).getOrElse(1).toByte
private lazy val connection = {
val conn = config.flatMap(_.getConfig("connection"))
val tpe = conn.flatMap(_.getString("type")).getOrElse("mock")
val heartbeat = conn.flatMap(_.getInt("heartbeat")).getOrElse(2000)
- val id = conn.flatMap(_.getInt("component_id")).getOrElse(99).toByte
+ val compId = conn.flatMap(_.getInt("component_id")).getOrElse(1).toByte
val props = tpe match {
case "mock" =>
- MockConnection.apply
+ val remote = config.flatMap(_.getInt("mock.remote_system_id")).getOrElse(42).toByte
+ MockConnection(systemId, compId, remote)
case "serial" =>
val serial = config.flatMap(_.getConfig("serial"))
SerialConnection(
- id,
+ systemId,
+ compId,
heartbeat,
serial.flatMap(_.getString("port")).getOrElse("/dev/ttyUSB0"),
serial.flatMap(_.getInt("baud")).getOrElse(115200),
serial.flatMap(_.getBoolean("two_stop_bits")).getOrElse(false),
serial.flatMap(_.getInt("parity")).getOrElse(0))
- case unknown => throw new RuntimeException("Unsupported connection type '" + unknown + "'")
+ case unknown => throw new IllegalArgumentException("Unsupported connection type '" + unknown + "'")
}
Akka.system(app).actorOf(props, name = "uav-connection")
diff --git a/vfd-backend/app/views/main.scala.html b/vfd-backend/app/views/main.scala.html
index 8537db6..1be335b 100644
--- a/vfd-backend/app/views/main.scala.html
+++ b/vfd-backend/app/views/main.scala.html
@@ -28,7 +28,7 @@
- Virtual Flight Deck
+ Flight Control Panel
diff --git a/vfd-backend/app/views/uav.scala.html b/vfd-backend/app/views/uav.scala.html
index 410e3c0..0ce7927 100644
--- a/vfd-backend/app/views/uav.scala.html
+++ b/vfd-backend/app/views/uav.scala.html
@@ -1,4 +1,4 @@
-@(socket: String, remoteSystemId: Int)
+@(socket: String, remoteSystemId: Byte, systemId: Byte, componentId: Byte)
@main("Main", "Remote System " + remoteSystemId){
@@ -10,8 +10,15 @@
-
- Loading...
+
diff --git a/vfd-backend/conf/application.conf b/vfd-backend/conf/application.conf
index e069b23..c2026a3 100644
--- a/vfd-backend/conf/application.conf
+++ b/vfd-backend/conf/application.conf
@@ -68,12 +68,12 @@ logger.application=DEBUG
uav.system_id=1
# Type of connection to use
-# 'mock' for a sample connection
-uav.connection.type=serial
+# 'mock' or 'serial'
+uav.connection.type=mock
# Mavlink component id used by this connection (not the web frontend),
# in case it needs to inject messages
-uav.connection.component_id=99
+uav.connection.component_id=1
# Delay in milliseconds between heartbeat messages injected by
# the connection
diff --git a/vfd-backend/conf/routes b/vfd-backend/conf/routes
index eea8d8e..3d38889 100644
--- a/vfd-backend/conf/routes
+++ b/vfd-backend/conf/routes
@@ -3,9 +3,9 @@
# ~~~~
# Home page
-GET / controllers.Application.index
-GET /uav/:sysId controllers.Application.uav(sysId: Int)
-GET /mavlink controllers.Application.mavlink
+GET / controllers.Application.index
+GET /uav/:remoteSystemId controllers.Application.uav(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)
diff --git a/vfd-backend/public/images/instruments/balance.svg b/vfd-backend/public/images/instruments/balance.svg
new file mode 100644
index 0000000..57511e9
--- /dev/null
+++ b/vfd-backend/public/images/instruments/balance.svg
@@ -0,0 +1,169 @@
+
+
+
+
diff --git a/vfd-backend/public/images/instruments/bar.svg b/vfd-backend/public/images/instruments/bar.svg
index 03d311a..50ecebf 100644
--- a/vfd-backend/public/images/instruments/bar.svg
+++ b/vfd-backend/public/images/instruments/bar.svg
@@ -9,33 +9,26 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="100"
+ width="32"
height="100"
id="svg4387"
version="1.1"
inkscape:version="0.48.5 r10040"
- viewBox="-50 -50 100 100"
- sodipodi:docname="voltage.svg">
+ viewBox="0 0 32 100"
+ sodipodi:docname="bar.svg">
-
+ id="clipPath2997">
+ id="rect2999"
+ style="fill:#00ffff;fill-opacity:1;stroke:none" />
-
- 0
- %
-
+ x="1.5"
+ y="1.5"
+ clip-path="url(#clipPath2997)" />
+ id="fixed"
+ transform="translate(5,50)">
-
-
-
diff --git a/vfd-backend/public/images/instruments/generic.svg b/vfd-backend/public/images/instruments/generic.svg
index 02e4caa..ac04b60 100644
--- a/vfd-backend/public/images/instruments/generic.svg
+++ b/vfd-backend/public/images/instruments/generic.svg
@@ -9,43 +9,47 @@
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
- width="110"
- height="110"
- id="svg2987"
+ width="100"
+ height="100"
+ id="svg4387"
version="1.1"
inkscape:version="0.48.5 r10040"
- sodipodi:docname="generic.svg"
- viewBox="-55 -55 110 110">
+ viewBox="-50 -50 100 100"
+ sodipodi:docname="basic.svg">
+ id="defs4389">
+
+
+ inkscape:snap-grids="false">
+ id="metadata4392">
@@ -67,997 +71,244 @@
+ id="lower">
+ inkscape:transform-center-y="44.418149"
+ inkscape:transform-center-x="11.044759"
+ inkscape:connector-curvature="0"
+ id="path5010"
+ d="m -0.52403983,48.510133 0.0154511,-3.487667 C -7.2448512,44.942356 -13.911142,43.340039 -19.989534,40.326163 l -1.591395,3.126201 c 6.563792,3.254436 13.7823459,4.974145 21.05688917,5.057769 z"
+ style="fill:#00d400;fill-opacity:1;stroke:none" />
+ inkscape:transform-center-y="-24.174304"
+ inkscape:transform-center-x="39.074649"
+ inkscape:connector-curvature="0"
+ id="path5002"
+ d="m -45.973942,-15.488849 3.312195,1.092444 c 2.157808,-6.381812 5.741699,-12.226687 10.486391,-17.076243 l -2.481425,-2.479556 c -5.123476,5.236862 -8.989672,11.570695 -11.317161,18.463355 z"
+ style="fill:#00d400;fill-opacity:1;stroke:none" />
+ inkscape:transform-center-x="45.733006"
+ inkscape:transform-center-y="-3.7233588" />
+ id="path5006"
+ d="m -39.553546,28.089583 2.830664,-2.037499 c -3.894666,-5.496838 -6.516711,-11.831799 -7.651224,-18.5208328 l -3.464548,0.5500689 c 1.225206,7.2231289 4.076892,14.0738809 8.285108,20.0082629 z"
+ style="fill:#00d400;fill-opacity:1;stroke:none" />
+ style="fill:#00d400;fill-opacity:1;stroke:none"
+ d="m -22.490063,42.984936 1.597135,-3.10052 c -5.965685,-3.129577 -11.177957,-7.583685 -15.225573,-13.0286 l -2.837209,2.062987 c 4.370899,5.879623 10.021946,10.68905 16.465647,14.066133 z"
+ id="path5008"
+ inkscape:connector-curvature="0"
+ inkscape:transform-center-x="29.924319"
+ inkscape:transform-center-y="34.920377" />
+
+
+
+
+
+
-
-
-
-
-
+ inkscape:connector-curvature="0" />
+
+
+
+ inkscape:transform-center-x="21.110558"
+ transform="matrix(0.89100652,0.4539905,-0.4539905,0.89100652,0,0)"
+ style="fill:#1a1a1a;fill-opacity:1;stroke:none"
+ id="rect4961"
+ width="1"
+ height="5"
+ x="-0.5"
+ y="44"
+ inkscape:transform-center-y="41.431803" />
+
+ inkscape:transform-center-x="45.927507"
+ transform="matrix(0.15643446,0.98768834,-0.98768834,0.15643446,0,0)"
+ style="fill:#1a1a1a;fill-opacity:1;stroke:none"
+ id="rect4965"
+ width="1"
+ height="5"
+ x="-0.49999964"
+ y="44"
+ inkscape:transform-center-y="7.2742025" />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ y="44"
+ x="-0.49999928"
+ height="5"
+ width="1"
+ id="rect4967"
+ style="fill:#1a1a1a;fill-opacity:1;stroke:none"
+ transform="matrix(-0.309017,0.95105651,-0.95105651,-0.309017,0,0)"
+ inkscape:transform-center-x="44.224127" />
+ inkscape:transform-center-x="14.36929"
+ transform="matrix(-0.95105652,0.30901699,-0.30901699,-0.95105652,0,0)"
+ style="fill:#1a1a1a;fill-opacity:1;stroke:none"
+ id="rect4969"
+ width="1"
+ height="5"
+ x="-0.49999872"
+ y="44"
+ inkscape:transform-center-y="-44.224128" />
+
- 4
- 5
- 6
+ inkscape:transform-center-x="-27.332015"
+ transform="matrix(-0.80901699,-0.58778526,0.58778526,-0.80901699,0,0)"
+ style="fill:#1a1a1a;fill-opacity:1;stroke:none"
+ id="rect4973"
+ width="1"
+ height="5"
+ x="-0.49999899"
+ y="44"
+ inkscape:transform-center-y="-37.619291" />
+
+
+
7
+ id="max"
+ x="40.52644"
+ y="3.5468256">100
8
+ id="text5102"
+ y="39.272289"
+ x="0.0016270902"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#4d4d4d;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ xml:space="preserve">0
0
- 1
2
+ id="text5106"
+ y="38.902164"
+ x="25.957726"
+ style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:125%;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#4d4d4d;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans"
+ xml:space="preserve">%
3
-
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;text-anchor:start;font-family:Sans;-inkscape-font-specification:Sans">50
+
+
+ d="m 48,50 c 0,13.34999 2,42 2,42 0,0 2,-28.65001 2,-42 0,-3 -4,-3 -4,0 z"
+ id="path5012"
+ inkscape:connector-curvature="0"
+ transform="translate(-50,-50)"
+ sodipodi:nodetypes="scss" />
diff --git a/vfd-backend/public/images/instruments/generic2.svg b/vfd-backend/public/images/instruments/generic2.svg
new file mode 100644
index 0000000..02e4caa
--- /dev/null
+++ b/vfd-backend/public/images/instruments/generic2.svg
@@ -0,0 +1,1063 @@
+
+
+
+
diff --git a/vfd-backend/public/stylesheets/main.css b/vfd-backend/public/stylesheets/main.css
index 295bc03..e92cd24 100644
--- a/vfd-backend/public/stylesheets/main.css
+++ b/vfd-backend/public/stylesheets/main.css
@@ -8,59 +8,48 @@ html {
}
body {
- min-height: 100%;
+ height: 100%;
}
body {
background-color: #e6e6e6;
}
-nav.side {
- position: absolute;
- left: 0;
- width: 15%;
- height: 100%;
- background: #FFFFCC;
- border: 1px solid #969696;
- box-sizing: border-box;
- -moz-box-sizing:border-box;
+.loader {
+ width: 100%;
+ font-size: 50px;
+ text-align: center;
}
-.control-table {
- border-collapse: collapse;
- background: #e5e5e5;
- border: 1px solid #969696;
- margin-right: 10px;
+.table-instrument {
+ table-layout: fixed;
+ width: 100%;
}
-.control-table td {
- padding: 10px;
+.table-instrument td {
+ width: 100%;
}
-.heartbeat{
+.heartbeat {
color: rgba(165, 25, 25, 1);
- animation: heartbeat 1s linear;
- opacity: 0;
+ animation: heartbeat 2s linear infinite;
}
@keyframes heartbeat {
0% {
transform: scale(1);
- opacity: 1;
}
- 25% {
+ 7% {
transform: scale(1.3);
}
- 50% {
+ 14% {
transform: scale(1);
}
- 75% {
+ 21% {
transform: scale(1.3);
- opacity: 1;
}
- 100% {
+ 28% {
transform: scale(1);
- opacity: 0;
}
}
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/Environment.scala b/vfd-frontend/src/main/scala/vfd/frontend/Environment.scala
new file mode 100644
index 0000000..53571f8
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/Environment.scala
@@ -0,0 +1,14 @@
+package vfd.frontend
+
+import org.scalajs.dom.HTMLElement
+
+/** Represents an application's environment */
+trait Environment {
+
+ /** The application's root element. */
+ def root: HTMLElement
+
+ /** Retrieve an asset's URL based on its file location. */
+ def asset(file: String): String
+
+}
\ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/Launcher.scala b/vfd-frontend/src/main/scala/vfd/frontend/Launcher.scala
index e1715d7..cf7ce94 100644
--- a/vfd-frontend/src/main/scala/vfd/frontend/Launcher.scala
+++ b/vfd-frontend/src/main/scala/vfd/frontend/Launcher.scala
@@ -4,9 +4,7 @@ import scala.scalajs.js.annotation.JSExport
import org.scalajs.dom
-import vfd.frontend.util.Environment
-
-@JSExport
+@JSExport("Launcher")
class Launcher(rootId: String, assetsBase: String) {
lazy val env = new Environment {
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/Main.scala b/vfd-frontend/src/main/scala/vfd/frontend/Main.scala
index 7b7990e..f4ef84d 100644
--- a/vfd-frontend/src/main/scala/vfd/frontend/Main.scala
+++ b/vfd-frontend/src/main/scala/vfd/frontend/Main.scala
@@ -1,74 +1,13 @@
package vfd.frontend
-import org.mavlink.messages.Message
-import rx.ops.RxOps
-import scalatags.JsDom.all.ExtendedString
-import scalatags.JsDom.all.button
-import scalatags.JsDom.all.`class`
-import scalatags.JsDom.all.div
-import scalatags.JsDom.all.height
-import scalatags.JsDom.all.iframe
-import scalatags.JsDom.all.img
-import scalatags.JsDom.all.src
-import scalatags.JsDom.all.stringAttr
-import scalatags.JsDom.all.stringFrag
-import scalatags.JsDom.all.stringStyle
-import scalatags.JsDom.all.width
-import vfd.frontend.ui.panels
-import vfd.frontend.util.Environment
-import rx.core.Obs
+import vfd.frontend.ui.Layout
object Main {
def main(args: Map[String, String])(implicit env: Environment) = {
- val socketUrl = args("socketurl")
- val remoteSystemId = args("remotesystemid").toInt
+ val socket = new MavlinkSocket(args("socketurl"), args("remotesystemid").toInt)
+ val layout = new Layout(socket)
- val socket = new MavlinkSocket(socketUrl, remoteSystemId)
-
- val message = socket.packet.map { p =>
- Message.unpack(socket.packet().messageId, socket.packet().payload)
- }
-
- Obs(message) {
- println(message().toString())
- }
-
- val element = div(`class` := "container-fluid")(
- div(`class` := "row")(
- div(`class` := "col-xs-12")(
- div(`class` := "panel panel-default")(
- div(`class` := "panel-body")(
- button(`class` := "btn")("LOCKED"),
- button(`class` := "btn")("MANUAL"),
- button(`class` := "btn")("GUIDED"),
- button(`class` := "btn")("AUTO"))))),
- div(`class` := "row")(
- div(`class` := "col-xs-4")(
- div(`class` := "panel panel-default")(
- div(`class` := "panel-body")(
- 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")))),
- div(`class` := "col-xs-5")(
- div(`class` := "panel panel-default")(
- div(`class` := "panel-body")(
- panels.Primary(message)))),
- div(`class` := "col-xs-3")(
- div(`class` := "panel panel-default")(
- div(`class` := "panel-body")(
- panels.Communication(
- socket.stats.packets,
- socket.stats.crcErrors,
- socket.stats.overflows,
- socket.stats.wrongIds,
- message))))))
-
- env.root.appendChild(element.render)
+ env.root.appendChild(layout.element)
}
}
\ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala b/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala
index 6dcc5fd..903020f 100644
--- a/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala
+++ b/vfd-frontend/src/main/scala/vfd/frontend/MavlinkSocket.scala
@@ -5,19 +5,26 @@ import scala.scalajs.js.Any.fromFunction1
import org.mavlink.Packet
import org.mavlink.Parser
+import org.mavlink.messages.Message
import org.scalajs.dom
+import rx.core.Rx
import rx.core.Var
+import rx.ops.RxOps
class MavlinkSocket(url: String, remoteSystemId: Int) {
- val packet: Var[Packet] = Var(Packet.Empty)
+ lazy val packet: Var[Packet] = Var(Packet.Empty)
+ lazy val message: Rx[Message] = packet.map{p =>
+ Message.unpack(p.messageId, p.payload)
+ }
object stats {
val crcErrors = Var(0)
val overflows = Var(0)
val wrongIds = Var(0)
val packets = Var(0)
+ val open = Var(false)
}
private val parser = new Parser(
@@ -26,7 +33,8 @@ class MavlinkSocket(url: String, remoteSystemId: Int) {
case Packet(seq, `remoteSystemId`, compId, msgId, payload) =>
packet() = pckt
stats.packets() += 1
- case _ => stats.wrongIds() += 1
+ case _ =>
+ stats.wrongIds() += 1
}
},
err => {
@@ -39,16 +47,19 @@ class MavlinkSocket(url: String, remoteSystemId: Int) {
private val connection = new dom.WebSocket(url)
connection.binaryType = "arraybuffer";
+ connection.onopen = (e: dom.Event) => {
+ stats.open() = true
+ }
connection.onmessage = (e: dom.MessageEvent) => {
val buffer = e.data.asInstanceOf[js.typedarray.ArrayBuffer]
- val dv = new js.typedarray.DataView(buffer)
+ val view = new js.typedarray.DataView(buffer)
- for (i <- 0 until dv.byteLength) {
- parser.push(dv.getInt8(i))
+ for (i <- 0 until view.byteLength) {
+ parser.push(view.getInt8(i))
}
}
connection.onclose = (e: dom.CloseEvent) => {
- dom.alert("closed")
+ stats.open() = false
}
}
\ 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
deleted file mode 100644
index f911865..0000000
--- a/vfd-frontend/src/main/scala/vfd/frontend/ui/Components.scala
+++ /dev/null
@@ -1,105 +0,0 @@
-package vfd.frontend.ui
-
-import org.scalajs.dom.HTMLElement
-import rx.Obs
-import rx.Rx
-import rx.Rx
-import scalatags.JsDom.all.ExtendedString
-import scalatags.JsDom.all.bindNode
-import scalatags.JsDom.all.div
-import scalatags.JsDom.all.`object`
-import scalatags.JsDom.all.stringAttr
-import scalatags.JsDom.all.stringFrag
-import scalatags.JsDom.all.stringStyle
-import scalatags.JsDom.all.style
-import scalatags.JsDom.all.`type`
-import scalatags.JsDom.all.width
-import vfd.frontend.util.Environment
-
-object Components {
-
- private def instrument(name: String)(implicit app: Environment) = {
- 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 led(color: Rx[String], size: String)(implicit app: Environment) = {
- 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
- }
-
- def horizon(pitchRoll: Rx[(Double, Double)], size: String)(implicit app: Environment) = {
- val inst = instrument("horizon")
- Obs(pitchRoll, skipInitial = true) {
- val svg = inst.contentDocument
- val pitch = svg.getElementById("pitch")
- val roll = svg.getElementById("roll")
- pitch.style.transition = "transform 250ms ease-out"
- roll.style.transition = "transform 250ms ease-out"
- pitch.style.transform = "translate(0px, " + pitchRoll()._1 + "px)"
- roll.style.transform = "rotate(" + pitchRoll()._2 + "deg)"
- }
- frame(inst, size)
- }
-
- def altimeter(value: Rx[Double], size: String)(implicit app: Environment) = {
- val inst = instrument("altimeter")
- Obs(value, skipInitial = true) {
- val svg = inst.contentDocument
- // 36deg === 1m
- svg.getElementById("hand").setAttribute("transform", "rotate(" + value() * 36 + ")");
- }
- frame(inst, size)
- }
-
- def compass(value: Rx[Double], size: String)(implicit app: Environment) = {
- val inst = instrument("compass")
- Obs(value, skipInitial = true) {
- val svg = inst.contentDocument
- val heading = svg.getElementById("heading")
- heading.style.transition = "transform 250ms ease-out"
- heading.style.transform = "rotate(" + value() + "deg)"
- }
- frame(inst, size)
- }
-
- def basic(value: Rx[Double], size: String)(implicit app: Environment) = {
- val inst = instrument("basic")
- Obs(value, skipInitial = true) {
- val svg = inst.contentDocument
- val hand = svg.getElementById("hand")
- hand.style.transform = "rotate(" + value() * 270 / 100 + "deg)";
- hand.style.transition = "transform 250ms ease-out"
- svg.getElementById("unit").textContent = "%"
- svg.getElementById("value").textContent = value().toString
- }
- frame(inst, size)
- }
-
- def bar(value: Rx[Double], size: String)(implicit app: Environment) = {
- val inst = instrument("bar")
- Obs(value, skipInitial = true) {
- val svg = inst.contentDocument
- val level = svg.getElementById("level")
- level.style.transform = "translate(0px, " + 97 * (1 - value() / 100) + "px)";
- level.style.transition = "transform 250ms ease-out"
- svg.getElementById("unit").textContent = "%"
- svg.getElementById("value").textContent = value().toString
- }
- frame(inst, size)
- }
-
-}
-
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/ui/Layout.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/Layout.scala
new file mode 100644
index 0000000..5892402
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/ui/Layout.scala
@@ -0,0 +1,57 @@
+package vfd.frontend.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.frontend.Environment
+import vfd.frontend.MavlinkSocket
+import vfd.frontend.ui.panels.Communication
+import vfd.frontend.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&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-frontend/src/main/scala/vfd/frontend/ui/components/SvgInstrument.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/components/SvgInstrument.scala
new file mode 100644
index 0000000..c22daa0
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/ui/components/SvgInstrument.scala
@@ -0,0 +1,55 @@
+package vfd.frontend.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.frontend.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-frontend/src/main/scala/vfd/frontend/ui/components/instruments.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/components/instruments.scala
new file mode 100644
index 0000000..08e164a
--- /dev/null
+++ b/vfd-frontend/src/main/scala/vfd/frontend/ui/components/instruments.scala
@@ -0,0 +1,124 @@
+package vfd.frontend.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.frontend.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-frontend/src/main/scala/vfd/frontend/ui/panels/Communication.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Communication.scala
index 813b695..7fec52a 100644
--- a/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Communication.scala
+++ b/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Communication.scala
@@ -1,74 +1,106 @@
package vfd.frontend.ui.panels
-import rx.Rx
-import rx.Rx
+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.i
-import scalatags.JsDom.all.intFrag
import scalatags.JsDom.all.stringAttr
-import scalatags.JsDom.all.stringFrag
-import scalatags.JsDom.all.style
import scalatags.JsDom.all.table
import scalatags.JsDom.all.tbody
import scalatags.JsDom.all.td
-import scalatags.JsDom.all.tr
-import vfd.frontend.util.Environment
-import vfd.frontend.util.Framework.RxStr
-import vfd.frontend.ui.Components
-import rx.core.Var
-import rx.core.Obs
-import org.mavlink.messages._
+import scalatags.JsDom.all._
+import vfd.frontend.Environment
+import vfd.frontend.MavlinkSocket
+import vfd.frontend.ui.components.Generic
+import vfd.frontend.ui.components.Balance
+import vfd.frontend.ui.components.Bar
+import vfd.frontend.ui.components.Led
object Communication {
- def apply(packets: Rx[Int], crcs: Rx[Int], overflows: Rx[Int], wrongIds: Rx[Int], message: Rx[Message])(implicit app: Environment) = {
-
- val m0 = Var(0.0)
- val m1 = Var(0.0)
- val m2 = Var(0.0)
- val m3 = Var(0.0)
- val battery = Var(0.0)
+ def apply(socket: MavlinkSocket)(implicit app: Environment): HTMLElement = {
+
+ val hb = i(`class` := "fa fa-heart heartbeat").render
- Obs(message) {
- message() match {
- case Motor(_m0, _m1, _m2, _m3) =>
- m0() = _m0
- m1() = _m1
- m2() = _m2
- m3() = _m3
- case Power(mV) => battery() = mV / 120
- case _ => ()
+ 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(
- "Link Status",
- table(`class` := "table table-condensed")(
+ table(`class` := "table")(
+ thead("Communication"),
tbody(
tr(
- td("Uplink"),
- td("-20 dBm"),
+ td("Conn"),
+ div(width := "20px")(td((new Led()).element)),
td("Server"),
td("5 ms")),
tr(
+ td("Uplink"),
+ td("-20 dBm"),
td("Heartbeat"),
- td(i(`class` := "fa fa-heart", style := "color: #ff0000;"))))),
- "Packet Statistics",
- table(`class` := "table table-condensed")(
+ td(hb)))),
+ table(`class` := "table-instrument", style := "height: 100px")(
tbody(
tr(
- td("OK"),
- td(packets),
- td(`class` := "danger")("CRC"),
- td(crcs),
- td("OFLW"),
- td(overflows),
- td("BID"),
- td(wrongIds)))),
- div(Components.bar(battery, "25%")),
- div(
- Components.basic(m0, "25%"),Components.basic(m1, "25%"),Components.basic(m2, "25%"),Components.basic(m3, "25%")))
+ 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-frontend/src/main/scala/vfd/frontend/ui/panels/Primary.scala b/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Primary.scala
index 4517ea0..f5f260c 100644
--- a/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Primary.scala
+++ b/vfd-frontend/src/main/scala/vfd/frontend/ui/panels/Primary.scala
@@ -1,33 +1,46 @@
package vfd.frontend.ui.panels
-import org.mavlink.messages._
+import org.mavlink.messages.Attitude
+import org.scalajs.dom.HTMLElement
+
import rx.core.Obs
-import rx.core.Rx
-import rx.core.Var
-import scalatags.JsDom.all.div
-import vfd.frontend.ui.Components
-import vfd.frontend.util.Environment
+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.frontend.Environment
+import vfd.frontend.MavlinkSocket
+import vfd.frontend.ui.components.Altimeter
+import vfd.frontend.ui.components.Compass
+import vfd.frontend.ui.components.Horizon
object Primary {
- def apply(message: Rx[Message])(implicit env: Environment) = {
- val pitchRoll = Var((0.0, 0.0)) // Rx{(attitude().pitch.toDouble, attitude().roll.toDouble)}
- val heading = Var(0.0) //Rx{attitude().heading.toDouble}
- val altitude = Var(0.0) //Rx{pressure().pressure.toDouble}
+ def apply(socket: MavlinkSocket)(implicit env: Environment): HTMLElement = {
+
+ val compass = new Compass
+ val horizon = new Horizon
+ val altimeter = new Altimeter
- Obs(message) {
- message() match {
+ Obs(socket.message, skipInitial = true) {
+ socket.message() match {
case Attitude(roll, pitch, yaw) =>
- pitchRoll() = (roll, pitch)
- heading() = yaw
+ horizon.update(pitch, roll)
+ compass.update(yaw)
case _ => ()
}
}
- div(
- Components.compass(heading, "33%"),
- Components.horizon(pitchRoll, "33%"),
- Components.altimeter(altitude, "33%"))
+ table(`class` := "table-instrument")(
+ tbody(
+ tr(
+ td(compass.element),
+ td(horizon.element),
+ td(altimeter.element)))).render
+
}
}
\ No newline at end of file
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/util/Environment.scala b/vfd-frontend/src/main/scala/vfd/frontend/util/Environment.scala
deleted file mode 100644
index fdd908a..0000000
--- a/vfd-frontend/src/main/scala/vfd/frontend/util/Environment.scala
+++ /dev/null
@@ -1,14 +0,0 @@
-package vfd.frontend.util
-
-import org.scalajs.dom.HTMLElement
-
-/** Represents an application's environment */
-trait Environment {
-
- /** The application's root element. */
- def root: HTMLElement
-
- /** Retrieve an asset's URL based on its file location. */
- def asset(file: String): String
-
-}
\ 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
deleted file mode 100644
index 066478e..0000000
--- a/vfd-frontend/src/main/scala/vfd/frontend/util/Framework.scala
+++ /dev/null
@@ -1,70 +0,0 @@
-package vfd.frontend.util
-
-import scala.language.implicitConversions
-import scala.util.Failure
-import scala.util.Success
-
-import org.scalajs.dom
-import org.scalajs.dom.Element
-
-import rx.Rx
-import rx.Rx
-import rx.core.Obs
-import scalatags.JsDom.all.Attr
-import scalatags.JsDom.all.AttrValue
-import scalatags.JsDom.all.Frag
-import scalatags.JsDom.all.HtmlTag
-import scalatags.JsDom.all.Style
-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
-import scalatags.JsDom.all.StyleValue
-
-/**
- * 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()) }
- }
- }
-}
diff --git a/vfd-frontend/src/main/scala/vfd/frontend/util/package.scala b/vfd-frontend/src/main/scala/vfd/frontend/util/package.scala
deleted file mode 100644
index 2d98e2f..0000000
--- a/vfd-frontend/src/main/scala/vfd/frontend/util/package.scala
+++ /dev/null
@@ -1,15 +0,0 @@
-package vfd.frontend
-
-import scala.reflect.ClassTag
-
-import rx.Rx
-import rx.Rx
-import rx.ops.RxOps
-
-package object util {
-
- implicit class richRx[A](val input: Rx[A]) extends AnyVal {
- def only[B <: A](implicit ct: ClassTag[B]): Rx[B] = input filter (_.isInstanceOf[B]) map (_.asInstanceOf[B])
- }
-
-}
\ No newline at end of file
--
cgit v1.2.3