diff options
author | Jakob Odersky <jodersky@gmail.com> | 2014-10-09 15:53:24 +0200 |
---|---|---|
committer | Jakob Odersky <jodersky@gmail.com> | 2014-10-09 15:54:48 +0200 |
commit | 0226a83f40e0126f8e75e155609f3f295661a3a5 (patch) | |
tree | f63aa06c616f3c7627e056b4fcde8809760a5090 | |
parent | 7f127610f7d5062eb6d6f9aa4f6eeb36f64cb51d (diff) | |
download | mavigator-0226a83f40e0126f8e75e155609f3f295661a3a5.tar.gz mavigator-0226a83f40e0126f8e75e155609f3f295661a3a5.tar.bz2 mavigator-0226a83f40e0126f8e75e155609f3f295661a3a5.zip |
tidy up frontend
19 files changed, 729 insertions, 112 deletions
diff --git a/project/Build.scala b/project/Build.scala index f4a7834..678c8d2 100644 --- a/project/Build.scala +++ b/project/Build.scala @@ -60,6 +60,7 @@ object ApplicationBuild extends Build { libraryDependencies ++= Seq( rx, dom, + tag, pickling ) ) diff --git a/project/Dependencies.scala b/project/Dependencies.scala index ae56455..2afcbb1 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -13,7 +13,8 @@ object Dependencies { val jquery = "org.webjars" % "jquery" % "2.1.1" val dom = "org.scala-lang.modules.scalajs" %%%! "scalajs-dom" % "0.6" - val rx = "com.scalarx" %%%! "scalarx" % "0.2.5" + val tag = "com.scalatags" %%%! "scalatags" % "0.4.1" + val rx = "com.scalarx" %%%! "scalarx" % "0.2.6" val pickling = "org.scalajs" %%%! "scalajs-pickling" % "0.3.1" val picklingPlay = "org.scalajs" %% "scalajs-pickling-play-json" % "0.3.1" diff --git a/project/util.scala b/project/util.scala index 595d7fe..1415ba8 100644 --- a/project/util.scala +++ b/project/util.scala @@ -25,7 +25,7 @@ package object util { } outFiles }.taskValue, - playMonitoredFiles ++= (watchSources in reference).value.map(_.getCanonicalPath) + playMonitoredFiles += (scalaSource in (reference, Compile)).value.getCanonicalPath ) } diff --git a/vfd-backend/app/controllers/Application.scala b/vfd-backend/app/controllers/Application.scala index 6bd066d..efdf1bd 100644 --- a/vfd-backend/app/controllers/Application.scala +++ b/vfd-backend/app/controllers/Application.scala @@ -14,19 +14,19 @@ import plugins.UavPlugin object Application extends Controller { + implicit val dataFrameFormat = spicklerFormat[DataFrame] + implicit val dataFrameFormatter = FrameFormatter.jsonFrame[DataFrame] + def use[A <: Plugin](implicit app: Application, m: Manifest[A]) = { app.plugin[A].getOrElse(throw new RuntimeException(m.runtimeClass.toString + " plugin should be available at this point")) } - def index() = Action { - Ok(views.html.index()) + def index = Action { implicit request => + Ok(views.html.index(routes.Application.socket.webSocketURL())) } - - implicit val dataFrameFormat = spicklerFormat[DataFrame] - implicit val dataFrameFormatter = FrameFormatter.jsonFrame[DataFrame] - def socket = WebSocket.acceptWithActor[String, DataFrame] { request => + def socket = WebSocket.acceptWithActor[String, DataFrame] { implicit request => out => use[UavPlugin].register(out) } }
\ No newline at end of file diff --git a/vfd-backend/app/views/index.scala.html b/vfd-backend/app/views/index.scala.html index 654c2a1..da7a79f 100644 --- a/vfd-backend/app/views/index.scala.html +++ b/vfd-backend/app/views/index.scala.html @@ -1,45 +1,12 @@ -@main("Main"){ - - <div class="row"> - <div class="col-lg-2"> - <div style="width: 100%; height: 450px; background-color: #000000; display: table;"> - <div style="display: table-cell; text-align: center; vertical-align: middle;">no feed</div> - </div> - </div> - <div class="col-lg-6"> - @panels.pfd() - <!-- <div style="width: 100%; height: 450px; background-color: #000000; display: table;"> - <div style="display: table-cell; text-align: center; vertical-align: middle;">no feed</div> - </div> --> - </div> - <div class="col-lg-4"> - station - <ul> - <li>server link</li> - <li>uav link</li> - </ul> - </div> - </div> - <div class="row"> - <div class="col-lg-4"> - uav - </div> - <div class="col-lg-4"> - - </div> - <div class="col-lg-4"> - @panels.eicas() - </div> - </div> - +@(socket: String) +@main("Main"){ - + <div id="app"></div> <script type="text/javascript"> - window.onload = function () { - var frontend = new Frontend('#attitude', '#heading', '#altitude') + var frontend = new Frontend('app', '@routes.Assets.at("")', '@socket') frontend.main() } </script> diff --git a/vfd-backend/app/views/main.scala.html b/vfd-backend/app/views/main.scala.html index db894d2..f65ca66 100644 --- a/vfd-backend/app/views/main.scala.html +++ b/vfd-backend/app/views/main.scala.html @@ -51,10 +51,8 @@ </div><!-- /.container-fluid --> </nav> </header> - - <div class="container-fluid"> - @content - </div> + + @content <script type="text/javascript" src="@routes.Assets.at("lib/vfd-frontend-fastopt.js")"></script> <script type="text/javascript" src="@routes.Assets.at("lib/jquery/jquery.js")"></script> diff --git a/vfd-backend/public/audio/alarm.mp3 b/vfd-backend/public/audio/alarm.mp3 Binary files differnew file mode 100644 index 0000000..97c83d5 --- /dev/null +++ b/vfd-backend/public/audio/alarm.mp3 diff --git a/vfd-backend/public/audio/alarm.ogg b/vfd-backend/public/audio/alarm.ogg Binary files differnew file mode 100644 index 0000000..19a1717 --- /dev/null +++ b/vfd-backend/public/audio/alarm.ogg diff --git a/vfd-backend/public/images/instruments/altitude.svg b/vfd-backend/public/images/instruments/altitude.svg index 24146a1..2bfb306 100644 --- a/vfd-backend/public/images/instruments/altitude.svg +++ b/vfd-backend/public/images/instruments/altitude.svg @@ -26,7 +26,7 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="0" inkscape:zoom="5.6568542" - inkscape:cx="68.511673" + inkscape:cx="68.865226" inkscape:cy="45.38396" inkscape:document-units="px" inkscape:current-layer="layer1" @@ -106,6 +106,17 @@ style="fill:#1a1a1a;fill-opacity:1;stroke:none" inkscape:connector-curvature="0" /> </g> + <text + xml:space="preserve" + style="font-size:10px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#f2f2f2;fill-opacity:1;stroke:none;font-family:Arial;-inkscape-font-specification:Arial" + x="-16.982601" + y="-14.119686" + id="altitude" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan3120" + x="-16.982601" + y="-14.119686">0000.00</tspan></text> <g id="dial"> <g diff --git a/vfd-backend/public/images/instruments/attitude.svg b/vfd-backend/public/images/instruments/attitude.svg index 7f0452c..09464d1 100644 --- a/vfd-backend/public/images/instruments/attitude.svg +++ b/vfd-backend/public/images/instruments/attitude.svg @@ -20,17 +20,17 @@ id="defs4"> <clipPath clipPathUnits="userSpaceOnUse" - id="clipPath3341"> + id="clipPath3999"> <path - sodipodi:type="arc" - style="fill:#00ff00;fill-opacity:1;stroke:none" - id="path3343" - sodipodi:cx="55" - sodipodi:cy="55" - sodipodi:rx="50" - sodipodi:ry="50" + transform="translate(-55,-55)" d="M 105,55 A 50,50 0 1 1 5,55 50,50 0 1 1 105,55 z" - transform="translate(-55,-55)" /> + sodipodi:ry="50" + sodipodi:rx="50" + sodipodi:cy="55" + sodipodi:cx="55" + id="path4002" + style="fill:#00ff00;fill-opacity:1;stroke:none" + sodipodi:type="arc" /> </clipPath> </defs> <sodipodi:namedview @@ -40,9 +40,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="5.6568542" - inkscape:cx="116.6015" - inkscape:cy="56.323079" + inkscape:zoom="2.8284271" + inkscape:cx="-34.071266" + inkscape:cy="22.322089" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" @@ -87,7 +87,7 @@ id="layer1"> <g id="roll" - clip-path="url(#clipPath3341)"> + clip-path="url(#clipPath3999)"> <g id="pitch"> <rect @@ -353,6 +353,37 @@ id="tspan3974" x="-21.544434" y="-28.301392">30</tspan></text> + <g + id="runway"> + <path + inkscape:transform-center-x="-25" + inkscape:transform-center-y="43.30127" + inkscape:connector-curvature="0" + id="path3046" + d="M 50,86.60254 0,0" + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.16695975;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> + <path + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.16695975;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + d="M 86.60254,50 0,0" + id="path3977" + inkscape:connector-curvature="0" + inkscape:transform-center-y="25" + inkscape:transform-center-x="-43.30127" /> + <path + inkscape:transform-center-x="25" + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.16695975;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + d="M -50,86.60254 1.2675457e-8,-4.7305491e-8" + id="path3979" + inkscape:connector-curvature="0" + inkscape:transform-center-y="43.30127" /> + <path + inkscape:transform-center-y="25" + inkscape:connector-curvature="0" + id="path3981" + d="M -86.60254,50 2.3652725e-8,-4.0967757e-8" + style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.16695975;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" + inkscape:transform-center-x="43.30127" /> + </g> </g> <path sodipodi:type="star" diff --git a/vfd-backend/public/images/leds/led.svg b/vfd-backend/public/images/leds/led.svg new file mode 100644 index 0000000..74cc4ff --- /dev/null +++ b/vfd-backend/public/images/leds/led.svg @@ -0,0 +1,293 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="64" + height="64" + id="svg2985" + version="1.1" + inkscape:version="0.48.5 r10040" + sodipodi:docname="led.svg" + viewBox="0 0 64 64"> + <defs + id="defs2987"> + <linearGradient + id="linearGradient3803" + inkscape:collect="always"> + <stop + id="stop3805" + offset="0" + style="stop-color:#cfcfcf;stop-opacity:0.51141554" /> + <stop + id="stop3807" + offset="1" + style="stop-color:#ffffff;stop-opacity:0.04109589" /> + </linearGradient> + <linearGradient + id="linearGradient3891"> + <stop + style="stop-color:#ffffff;stop-opacity:0.26940638" + offset="0" + id="stop3893" /> + <stop + style="stop-color:#ffffff;stop-opacity:0.15525115" + offset="1" + id="stop3895" /> + </linearGradient> + <linearGradient + id="linearGradient3883"> + <stop + style="stop-color:#000000;stop-opacity:0.02283105" + offset="0" + id="stop3885" /> + <stop + style="stop-color:#000000;stop-opacity:1" + offset="1" + id="stop3887" /> + </linearGradient> + <linearGradient + id="linearGradient3849"> + <stop + id="stop3851" + offset="0" + style="stop-color:#00ac00;stop-opacity:1" /> + <stop + id="stop3853" + offset="1" + style="stop-color:#8cff00;stop-opacity:0;" /> + </linearGradient> + <linearGradient + id="linearGradient3807"> + <stop + style="stop-color:#000000;stop-opacity:0" + offset="0" + id="stop3809" /> + <stop + style="stop-color:#000000;stop-opacity:0.5" + offset="1" + id="stop3811" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient3787"> + <stop + style="stop-color:#b3b3b3;stop-opacity:1;" + offset="0" + id="stop3789" /> + <stop + style="stop-color:#cccccc;stop-opacity:1" + offset="1" + id="stop3791" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + id="linearGradient3777"> + <stop + style="stop-color:#ffffff;stop-opacity:1" + offset="0" + id="stop3779" /> + <stop + style="stop-color:#b3b3b3;stop-opacity:1" + offset="1" + id="stop3781" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3777" + id="linearGradient3783" + x1="16.608555" + y1="18.651081" + x2="75.365089" + y2="69.463295" + gradientUnits="userSpaceOnUse" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3787" + id="radialGradient3793" + cx="48.149883" + cy="41.854801" + fx="48.149883" + fy="41.854801" + r="27.765808" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.0026007,0.01478583,-0.01619592,1.0982174,0.55265642,-4.8228038)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3891" + id="linearGradient3901" + x1="10.083722" + y1="34.833542" + x2="44.591557" + y2="14.1497" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1088977,0,0,1.1088977,-3.2150185,-2.383994)" /> + <radialGradient + inkscape:collect="always" + xlink:href="#linearGradient3807" + id="radialGradient3794" + gradientUnits="userSpaceOnUse" + cx="48.149883" + cy="41.854801" + fx="48.149883" + fy="41.854801" + r="27.765808" + gradientTransform="matrix(1.7287032,0,0,1.7287032,-35.086972,-30.499726)" /> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient3803" + id="linearGradient3801" + gradientUnits="userSpaceOnUse" + gradientTransform="matrix(1.1088977,0,0,1.1088977,-3.2150185,-2.383994)" + x1="-11.46802" + y1="62.658627" + x2="53.882328" + y2="18.582912" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="13.34375" + inkscape:cx="25.462406" + inkscape:cy="33.437936" + inkscape:current-layer="svg2985" + showgrid="true" + inkscape:document-units="px" + inkscape:grid-bbox="true" + inkscape:window-width="1920" + inkscape:window-height="1029" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" /> + <metadata + id="metadata2990"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:label="background" + inkscape:groupmode="layer" + style="display:inline" + sodipodi:insensitive="true"> + <path + sodipodi:type="arc" + style="fill:#b3b3b3;fill-opacity:1;fill-rule:nonzero;stroke:none" + id="path2993" + sodipodi:cx="48.149883" + sodipodi:cy="41.854801" + sodipodi:rx="27.765808" + sodipodi:ry="27.765808" + d="m 75.915691,41.854801 a 27.765808,27.765808 0 1 1 -55.531616,0 27.765808,27.765808 0 1 1 55.531616,0 z" + transform="matrix(-1.1268556,0,0,1.1268556,86.070613,-15.351671)" /> + <path + transform="matrix(-1.0565207,0,0,1.0565207,82.725544,-12.366264)" + d="m 75.915691,41.854801 a 27.765808,27.765808 0 1 1 -55.531616,0 27.765808,27.765808 0 1 1 55.531616,0 z" + sodipodi:ry="27.765808" + sodipodi:rx="27.765808" + sodipodi:cy="41.854801" + sodipodi:cx="48.149883" + id="path3771" + style="fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:none" + sodipodi:type="arc" /> + <path + transform="matrix(-1.1268556,0,0,1.1268556,86.070613,-15.351671)" + d="m 75.915691,41.854801 a 27.765808,27.765808 0 1 1 -55.531616,0 27.765808,27.765808 0 1 1 55.531616,0 z" + sodipodi:ry="27.765808" + sodipodi:rx="27.765808" + sodipodi:cy="41.854801" + sodipodi:cx="48.149883" + id="path3785" + style="fill:url(#radialGradient3793);fill-opacity:1;fill-rule:nonzero;stroke:none" + sodipodi:type="arc" /> + <path + sodipodi:type="arc" + style="fill:#808080;fill-opacity:1;fill-rule:nonzero;stroke:url(#linearGradient3783)" + id="path3775" + sodipodi:cx="48.149883" + sodipodi:cy="41.854801" + sodipodi:rx="27.765808" + sodipodi:ry="27.765808" + d="m 75.915691,41.854801 a 27.765808,27.765808 0 1 1 -55.531616,0 27.765808,27.765808 0 1 1 55.531616,0 z" + transform="matrix(-1.0565207,0,0,1.0565207,82.725544,-12.366264)" /> + <path + transform="matrix(-1.0445488,0,0,1.0445488,82.149098,-11.865182)" + d="m 75.915691,41.854801 a 27.765808,27.765808 0 1 1 -55.531616,0 27.765808,27.765808 0 1 1 55.531616,0 z" + sodipodi:ry="27.765808" + sodipodi:rx="27.765808" + sodipodi:cy="41.854801" + sodipodi:cx="48.149883" + id="path3795" + style="fill:#46ff00;fill-opacity:0;fill-rule:nonzero;stroke:none" + sodipodi:type="arc" /> + </g> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="color" + style="display:inline"> + <path + sodipodi:type="arc" + style="fill:#782121;fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + id="light" + sodipodi:cx="48.149883" + sodipodi:cy="41.854801" + sodipodi:rx="27.765808" + sodipodi:ry="27.765808" + d="m 75.915691,41.854801 a 27.765808,27.765808 0 1 1 -55.531616,0 27.765808,27.765808 0 1 1 55.531616,0 z" + transform="matrix(-1.0445488,0,0,1.0445488,82.149098,-11.865182)"> + <animate + attributeType="XML" + attributeName="fill" + dur="2s" + repeatCount="indefinite" + id="animate40" + values="#782121, #ff0000, #782121" /> + </path> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="reflection" + style="display:inline" + sodipodi:insensitive="true"> + <path + transform="matrix(-1.0445488,0,0,1.0445488,82.149098,-11.865182)" + d="m 75.915691,41.854801 a 27.765808,27.765808 0 1 1 -55.531616,0 27.765808,27.765808 0 1 1 55.531616,0 z" + sodipodi:ry="27.765808" + sodipodi:rx="27.765808" + sodipodi:cy="41.854801" + sodipodi:cx="48.149883" + id="path3805" + style="fill:url(#radialGradient3794);fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + sodipodi:type="arc" /> + <path + style="fill:url(#linearGradient3801);fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + d="m 13.661019,17.992002 c -2.716552,0 -5.3380492,0.375822 -7.8315903,1.074244 -1.8971123,3.856752 -2.9801626,8.198554 -2.9801626,12.786977 0,16.017772 12.9868349,29.004607 29.0046069,29.004607 2.703688,0 5.313926,-0.382194 7.796937,-1.074245 1.910078,-3.867248 3.014816,-8.21652 3.014816,-12.82163 0,-16.017772 -12.986835,-28.969953 -29.004607,-28.969953 z" + id="path3815" + inkscape:connector-curvature="0" /> + <path + style="fill:url(#linearGradient3901);fill-opacity:1;fill-rule:nonzero;stroke:none;display:inline" + d="m 31.853873,2.8486171 c -4.086863,0 -7.978121,0.8657411 -11.504814,2.3910608 2.453382,-0.6830778 5.034928,-1.0742447 7.692978,-1.0742447 16.52263,0 29.905586,14.0815598 29.905586,31.4303198 0,5.007819 -1.133611,9.729358 -3.118775,13.930528 3.772658,-4.895422 6.029632,-11.014661 6.029632,-17.673058 0,-16.017771 -12.986835,-29.0046059 -29.004607,-29.0046059 z" + id="path3857" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/vfd-backend/public/stylesheets/main.css b/vfd-backend/public/stylesheets/main.css index 815d2b9..f335da8 100644 --- a/vfd-backend/public/stylesheets/main.css +++ b/vfd-backend/public/stylesheets/main.css @@ -3,6 +3,14 @@ src: url('../fonts/ds-digi.ttf') } +html { + height: 100%; +} + +body { + min-height: 100%; +} + body { background-color: #e6e6e6; } 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())} + } + } +} diff --git a/vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala b/vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala index bf8714f..991a303 100644 --- a/vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala +++ b/vfd-uav/src/main/scala/vfd/uav/DummyConnection.scala @@ -9,17 +9,14 @@ class DummyConnection extends Actor with Connection { import context._ var time = 0.0 - val messageInterval = FiniteDuration(20, MILLISECONDS) + val messageInterval = FiniteDuration(50, MILLISECONDS) def flightData(time: Double) = { - val speed = 5.0 / 1000 - val roll = 5.0/180*math.Pi - val pitch = 10.0/180*math.Pi Connection.NewDataFrame(DataFrame( - roll, - pitch, - (roll * time * speed) % math.Pi, - (pitch * time * speed), + math.sin(time/6000) * math.Pi, + math.sin(time/5050) * math.Pi/4, + time/5000 * 2 * math.Pi, + time/1000, 22 )) } |