diff options
44 files changed, 1414 insertions, 1342 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index a95c3bb..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,7 +0,0 @@ -Thanks for taking the time to help out! - -# Pull Requests -Please follow these basic guidelines when submitting pull requests: - - Follow the Scala style guide - - Keep commit history clean by squashing non-relevant commits and rebasing onto master prior to a pull request - - Use the present tense in commit messages ("Add feature" not "Added feature")
\ No newline at end of file diff --git a/mavigator-cockpit/src/main/scala/mavigator/cockpit/Cockpit.scala b/mavigator-cockpit/src/main/scala/mavigator/cockpit/Cockpit.scala new file mode 100644 index 0000000..f8aba76 --- /dev/null +++ b/mavigator-cockpit/src/main/scala/mavigator/cockpit/Cockpit.scala @@ -0,0 +1,26 @@ +package mavigator +package cockpit + +import scala.scalajs.js.annotation.JSExport + +import mavigator.util.{Environment, Application} +import scalatags.JsDom.all._ +import util._ + +class Cockpit + extends Page + with Layout + with MavlinkWebSockets + with Instruments + +@JSExport("mavigator_cockpit_Main") +object Cockpit extends Application { + + override def main(args: Map[String, String])(implicit env: Environment): Unit = { + val app = new Cockpit + app.attach(env) + app.connect(args("socketUrl"), args("remoteSystemId").toInt) + } + +} + diff --git a/mavigator-cockpit/src/main/scala/mavigator/cockpit/Instruments.scala b/mavigator-cockpit/src/main/scala/mavigator/cockpit/Instruments.scala new file mode 100644 index 0000000..7c4bf92 --- /dev/null +++ b/mavigator-cockpit/src/main/scala/mavigator/cockpit/Instruments.scala @@ -0,0 +1,173 @@ +package mavigator +package cockpit + +import org.scalajs.dom +import org.scalajs.dom.html + +import scalatags.JsDom.all._ + +import util.Page + +trait Instruments { page: Page => + + trait Instrument[A] { + def element: html.Element + def update(newValue: A): Unit + } + + /** Common behaviour for svg-based instruments. */ + abstract class SvgInstrument[A]( + path: String + ) extends Instrument[A] { + + override def element: html.Element = objectElement + + /** SVG object element that contains the rendered instrument */ + lazy val objectElement: html.Object = SvgInstrument.svgObject(path) + + /** Retrieves an element of the underlying SVG document by ID. */ + protected def part(id: String): html.Element = + objectElement.contentDocument.getElementById(id).asInstanceOf[html.Element] + + /** Movable parts of the instrument */ + protected def moveable: Seq[html.Element] + + /** Called when element has been loaded. */ + private def load(event: dom.Event): Unit = { + for (part <- moveable) { + part.style.transition = "transform 20ms ease-out" + } + } + + element.addEventListener("load", (e: dom.Event) => load(e)) + } + + /** Contains helpers for SVG instruments. */ + object SvgInstrument { + + /** Retrieves an SVG object element by its instrument's name. */ + def svgObject(path: String): html.Object = { + val fullPath = page.asset("images/" + path) + `object`(`type` := "image/svg+xml", "data".attr := fullPath, width := 100.pct)( + "Error loading instrument " + fullPath).render + } + + /** Applies translation styling to an element. */ + def translate(elem: html.Element, x: Int, y: Int): Unit = { + elem.style.transform = "translate(" + x + "px, " + y + "px)"; + } + + /** Applies rotation styling to an element. */ + def rotate(elem: html.Element, rad: Double): Unit = { + elem.style.transform = "rotateZ(" + rad + "rad)"; + } + + } + + lazy val attitudeOverlay = new SvgInstrument[(Float, Float)]("hud/attitude.svg") { + override def element = div(`class`:="hud-overlay")(objectElement).render + private lazy val pitchPart = part("pitch") + private lazy val rollPart = part("roll") + override lazy val moveable = Seq(pitchPart, rollPart) + + override def update(pitchRoll: (Float, Float)) = { + import SvgInstrument._ + val (pitch, roll) = pitchRoll + translate(pitchPart, 0, (pitch * 180 / math.Pi * 10).toInt) // 1deg === 10px + rotate(rollPart, roll) + } + } + + lazy val horizonOverlay = new SvgInstrument[(Float, Float)]("hud/horizon.svg") { + import SvgInstrument._ + + override def element = div(`class`:="hud-overlay")(objectElement).render + lazy val horizon = part("horizon") + lazy override val moveable = Seq(horizon) + + override def update(pitchRoll: (Float, Float)) = { + val (pitch, roll) = pitchRoll + + val t = (pitch * 180 / math.Pi * 10).toInt // 1deg === 10px + + horizon.style.transform = s"rotateZ(${roll}rad)translate(0px, ${t}px) " + } + } + + + val overlayStyle = """ + |.hud-overlay { + | position: absolute; + | left: 0; + | right: 0; + | top: 0; + | bottom: 0; + | + | display: flex; + | flex-direction: row; + | justify-content: center; + | align-items: center; + |} + |.hud-overlay > * { + | flex: 1 1 0; + | width: 100%; + | height: 100%; + | max-width: 100%; + | max-height: 100%; + |}""".stripMargin + + + def mode(name: String, kind: String, on: Boolean = false) = { + div(`class` := s"mode $kind ${if (!on) "off"}")(name) + } + + //TODO make these into real instruments and lazy vals + def modes = div(style := "float: right;")( + mode("LINK", "danger", true), + mode("BAT", "warning", true), + mode("GPS", "warning", true), + mode("STABILIZED", "info", true) + ) + + val modeStyle = """ +.mode { + display: inline-block; + box-sizing: border-box; + text-decoration: normal; + margin-right: 5px; + padding: 5px; +} + +.mode.danger { + color: #d9534f; + text-shadow: 0 0 5px #d9534f; + animation: danger-blink 0.5s linear infinite; + -webkit-animation: danger-blink 0.5s linear infinite; +} + +.mode.warning { + color: #f0ad4e; + text-shadow: 0 0 5px #f0ad4e; +} + +.mode.info { + color: #5bc0de; + text-shadow: 0 0 5px #5bc0de; +} + +.mode.success { + color: #5cb85c; + text-shadow: 0 0 5px #5cb85c; +} + +.mode.off { + color: #e6e6e6; + text-shadow: none; + animation: none; + -webkit-animation: none; +} +""" + + def instrumentStyles: Seq[String] = Seq(overlayStyle, modeStyle) + +} diff --git a/mavigator-cockpit/src/main/scala/mavigator/cockpit/Layout.scala b/mavigator-cockpit/src/main/scala/mavigator/cockpit/Layout.scala new file mode 100644 index 0000000..887c4d6 --- /dev/null +++ b/mavigator-cockpit/src/main/scala/mavigator/cockpit/Layout.scala @@ -0,0 +1,54 @@ +package mavigator +package cockpit + +import org.scalajs.dom.html +import scalatags.JsDom.all._ + +import util._ + +/** Provides main cockpit layout. */ +trait Layout { self: Page with Instruments => + + /** Elements to display in the mode control pannel (top panel). */ + def mcp = div(id := "mcp")( + img(src := asset("images/logo-invert.svg"), style:="height: 20px; margin: 5px;"), + span(`class`:="mode warning")("Demo System"), + modes + ) + + /** Element to deisplay on heads-up display (main area). */ + def hud = div(id :="hud")( + attitudeOverlay.element + ) + + val layoutStyle = """ + |#cockpit { + | width: 100%; + | height: 100%; + | display: flex; + | flex-direction: column; + | justify-content: flex-start; + | align-items: stretch; + | + | background-color: pink; + |} + | + |#mcp { + | flex: 0 1 0; + | background-color: #222222; + |} + | + |#hud { + | flex: 1 1 auto; + | position: relative; + | background-color: lightblue; + |}""".stripMargin + + override def styles = Seq(layoutStyle) ++ instrumentStyles + + override def elements: Seq[html.Element] = Seq(div(id := "cockpit")( + mcp, + hud + ).render) + +} diff --git a/mavigator-cockpit/src/main/scala/mavigator/cockpit/MavlinkWebSockets.scala b/mavigator-cockpit/src/main/scala/mavigator/cockpit/MavlinkWebSockets.scala new file mode 100644 index 0000000..3797005 --- /dev/null +++ b/mavigator-cockpit/src/main/scala/mavigator/cockpit/MavlinkWebSockets.scala @@ -0,0 +1,60 @@ +package mavigator +package cockpit + +import org.mavlink._ +import org.mavlink.Parser.Errors._ +import org.mavlink.messages._ + +import org.scalajs.dom +import scalajs.js + +trait MavlinkWebSockets { self: Instruments => + + private class MavlinkWebSocket(url: String, remoteSystemId: Int) { + + private var _open = false + def open: Boolean = _open + + private val parser = new Parser( + { + case pckt@Packet(seq, `remoteSystemId`, compId, msgId, payload) => + val msg = Message.unpack(pckt.messageId, pckt.payload) + onMessage(msg) + //_packets() = _packets.now + 1 + case _ => + //_wrongIds() = _wrongIds.now + 1 + }, + { + case CrcError => //_crcErrors() = _crcErrors.now + 1 + case OverflowError => //_overflows() = _overflows.now + 1 + } + ) + + private val connection = new dom.WebSocket(url) + + connection.binaryType = "arraybuffer" + + connection.onopen = (e: dom.Event) => { + _open = true + } + 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)) + } + } + connection.onclose = (e: dom.CloseEvent) => { + _open = false + } + + } + + private def onMessage(msg: Message): Unit = msg match { + case a: Attitude => attitudeOverlay.update((a.pitch.toFloat, a.roll.toFloat)) + case _ => () + } + + def connect(url: String, remoteSystemId: Int): Unit = new MavlinkWebSocket(url, remoteSystemId) +} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/Main.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/Main.scala deleted file mode 100644 index f3a111a..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/Main.scala +++ /dev/null @@ -1,23 +0,0 @@ -package mavigator.dashboard - -import scala.scalajs.js -import scala.scalajs.js.annotation.JSExport - -import org.scalajs.dom.html - -import mavigator.dashboard.ui.Layout -import mavigator.util.Environment -import mavigator.util.Application - -import scalatags.JsDom.all._ - -@JSExport("mavigator_dashboard_Main") -object Main extends Application { - - override def main(args: Map[String, String])(implicit env: Environment): Unit = { - val socket = new MavlinkSocket(args("socketUrl"), args("remoteSystemId").toInt) - val layout = new Layout(socket) - - env.root.appendChild(layout.element) - } -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/MavlinkSocket.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/MavlinkSocket.scala deleted file mode 100644 index 7f4ffdc..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/MavlinkSocket.scala +++ /dev/null @@ -1,70 +0,0 @@ -package mavigator.dashboard - -import scala.scalajs.js -import scala.scalajs.js.Any.fromFunction1 -import org.mavlink.Packet -import org.mavlink.Parser -import org.mavlink.Parser.Errors._ -import org.mavlink.messages.Message -import org.scalajs.dom -import scala.concurrent.duration._ -import rx._ -import rx.ops._ -import scala.concurrent.ExecutionContext.Implicits.global - -class MavlinkSocket(url: String, val remoteSystemId: Int) { - implicit val scheduler = new DomScheduler - - lazy val packet: Var[Packet] = Var(Packet.empty) - lazy val message: Rx[Message] = packet.map{p => - Message.unpack(p.messageId, p.payload) - } - - object stats { - private val DebounceTime = 1.seconds - - private[MavlinkSocket] val _crcErrors = Var(0) - private[MavlinkSocket] val _overflows = Var(0) - private[MavlinkSocket] val _wrongIds = Var(0) - private[MavlinkSocket] val _packets = Var(0) - - val crcErrors = _crcErrors.debounce(DebounceTime) - val overflows = _overflows.debounce(DebounceTime) - val wrongIds = _wrongIds.debounce(DebounceTime) - val packets = _packets.debounce(DebounceTime) - val open = Var(false) - } - - private val parser = new Parser( - { - case pckt@Packet(seq, `remoteSystemId`, compId, msgId, payload) => - packet() = pckt - stats._packets() += 1 - case _ => - stats._wrongIds() += 1 - }, - { - case CrcError => stats._crcErrors() += 1 - case OverflowError => stats._overflows() += 1 - }) - - 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 view = new js.typedarray.DataView(buffer) - - for (i <- 0 until view.byteLength) { - parser.push(view.getInt8(i)) - } - } - connection.onclose = (e: dom.CloseEvent) => { - stats.open() = false - } - -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/RxUtil.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/RxUtil.scala deleted file mode 100644 index 2637ddf..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/RxUtil.scala +++ /dev/null @@ -1,70 +0,0 @@ -package mavigator.dashboard - -import scala.language.implicitConversions -import scala.util.Failure -import scala.util.Success - -import org.scalajs.dom.html - -import rx.Obs -import rx.Rx -import rx.Rx -import rx.Var -import rx.Var -import scalatags.JsDom.all.Frag -import scalatags.JsDom.all.HtmlTag -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 - -package object rxutil { - - /** Rx, implicitly enhanced with additional methods. */ - implicit class RichRx(val rx: Rx[_]) extends AnyVal { - - /** - * Builds a new Rx by applying a partial function to all values of - * this Rx on which the function is defined. - * @param initial initial value of the returned Rx - * @param pf the partial function which filters and maps this Rx - * @return a new Rx resulting from applying the given partial - * function pf to each value on which it is defined and collecting - * the result - */ - def collect[B](initial: B)(pf: PartialFunction[Any, B]): Rx[B] = { - val result: Var[B] = Var(initial) - Obs(rx, skipInitial = true) { - if (pf.isDefinedAt(rx())) { - result() = pf(rx()) - } - } - result - } - - } - - /** - * 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) - } - -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/Hud.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/Hud.scala deleted file mode 100644 index 18c5c27..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/Hud.scala +++ /dev/null @@ -1,47 +0,0 @@ -package mavigator.dashboard.ui - -import mavigator.util.Environment -import mavigator.dashboard.ui.instruments._ - -import scalatags.JsDom.all._ -import rx._ - -class Hud(attitude: Rx[(Double, Double)])(implicit env: Environment) { - - private def overlay(name: String, z: Int, thinnerThanWide: Boolean = false) = { - val direction = if (thinnerThanWide) "row" else "column" - div( - style:= - "position: absolute; left: 0; right: 0; top: 0; bottom: 0;" + - s"display: flex; align-content: center; align-items: stretch; flex-direction: $direction;"+ - s"z-index: $z;" - )( - `object`(`type`:="image/svg+xml", "data".attr:=env.asset("images/hud/" + name + ".svg"), style := "flex: 1 1 100%;") - ) - } - - object Horizon extends SvgInstrument[(Double, Double)] { - import SvgInstrument._ - - val value = attitude - - lazy val element = `object`(`type`:="image/svg+xml", "data".attr:=env.asset("images/hud/horizon.svg"), style:="flex: 1 1 100%;").render - lazy val horizon = part("horizon") - lazy val moveable = Seq(horizon) - - protected def update(pitchRoll: (Double, Double)) = { - rotate(horizon, pitchRoll._2) - //translate(horizon, 0, (pitchRoll._1 * 180 / math.Pi).toInt) // 1deg === 1px - } - } - - val element = div( - style:= - "position: absolute; left: 0; right: 0; top: 0; bottom: 0;" + - "display: flex; align-content: stretch; align-items: stretch; flex-direction: row;"+ - "z-index: 0;" - )( - Horizon.element - ) -} - diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/Layout.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/Layout.scala deleted file mode 100644 index 902cb9c..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/Layout.scala +++ /dev/null @@ -1,266 +0,0 @@ -package mavigator.dashboard.ui - -import rx._ -import scalatags.JsDom.all._ -import mavigator.util.Environment -import mavigator.dashboard.MavlinkSocket -import mavigator.dashboard.ui.instruments._ -import org.mavlink.messages._ -import mavigator.dashboard.rxutil._ - -class Layout(socket: MavlinkSocket)(implicit env: Environment) { - - private def panel(contents: Frag*) = div(`class` := "d-panel")(contents: _*) - - private def mode(name: String, kind: String, on: Boolean = false) = div(`class` := s"mode $kind ${if (!on) "off"}")(name) - - val modes = div( - mode("MANUAL", "warning", true), - mode("STABILIZED", "info", true), - mode("GUIDED", "success", true), - mode("AUTO", "success", true)) - - val infos = div( - mode("BAY", "info"), - mode("RECOVERY", "danger"), - mode("NOGPS", "warning", true), - mode("OVERLOAD", "danger", true), - mode("BATTERY", "danger", false), - mode("LINK", "danger", true), - mode("SOCKET", "danger", true), - div(style := "float: right")(mode("CRITICAL", "danger", true))) - - val map = iframe( - "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: 100%; color: #ffffff; background-color: #c2c2c2; text-align: center;") - - val altimeter = new Altimeter( - Var(0.0) - ) - val horizon = new Horizon(socket.message.collect((0.0, 0.0)) { - case att: Attitude => (att.pitch, att.roll) - }) - val compass = new Compass(socket.message.collect(0.0) { - case att: Attitude => att.yaw - }) - val motor0 = new Generic(0, 50, 100, "%", socket.message.collect(0.0) { - case s: ServoOutputRaw => 100 * (s.servo1Raw - 1000) / 1000 - }) - val motor1 = new Generic(0, 50, 100, "%", socket.message.collect(0.0) { - case s: ServoOutputRaw => 100 * (s.servo2Raw - 1000) / 1000 - }) - val motor2 = new Generic(0, 50, 100, "%", socket.message.collect(0.0) { - case s: ServoOutputRaw => 100 * (s.servo3Raw - 1000) / 1000 - }) - val motor3 = new Generic(0, 50, 100, "%", socket.message.collect(0.0) { - case s: ServoOutputRaw => 100 * (s.servo4Raw - 1000) / 1000 - }) - val powerDistribution = new Distribution( - socket.message.collect((0.0, 0.0, 0.0, 0.0)) { - case s: ServoOutputRaw => - ( - 1.0 * (s.servo1Raw - 1000) / 1000, - 1.0 * (s.servo2Raw - 1000) / 1000, - 1.0 * (s.servo3Raw - 1000) / 1000, - 1.0 * (s.servo4Raw - 1000) / 1000 - ) - } - ) - val batteryLevel = new Bar( - Var(0.0) - ) - - val top = header( - div("Flight Control Panel"), - div((new Clock).element), - div("UAV " + socket.remoteSystemId) - ) - - val left = div( - panel( - table(`class` := "table-instrument")( - thead("Communication"), - tbody( - tr( - td("Uplink RSSI"), - td("89"), - td("Socket"), - td("5ms") - ), - tr( - td("Something else"), - td("unknown"), - td("Heartbeat"), - td(i(`class` := "fa fa-heart heartbeat")) - ) - ) - ), - table(`class` := "table-instrument")( - thead("Packets"), - tbody( - tr( - td("OK"), - Rx { td(socket.stats.packets()) }, - td("CRC"), - Rx { td(socket.stats.crcErrors()) }, - td("OFLW"), - Rx { td(socket.stats.overflows()) }, - td("BID"), - Rx { td(socket.stats.wrongIds()) } - ), - tr( - td("Ratio"), - Rx { - import socket.stats._ - val sum = packets() + crcErrors() + overflows() + wrongIds() - td(1.0 * packets() / sum formatted "%.2f") - }, - td(), - td(), - td(), - td(), - td(), - td() - ) - ) - ) - ), - panel( - table(`class` := "table-instrument")( - tbody( - tr( - td(compass.element), - td(horizon.element), - td(altimeter.element), - td(altimeter.element) - ) - ) - ) - ), - panel( - div(style := "width: 50%; display: inline-block;")( - table(`class` := "table-instrument")( - tbody( - tr( - td(motor1.element), - td(), - td(motor0.element) - ), - tr( - td(), - td(powerDistribution.element), - td() - ), - tr( - td(motor2.element), - td(), - td(motor3.element) - ) - ) - ) - ), - div(style := "width: 50%; display: inline-block;")( - table(`class` := "table-instrument")( - thead("Power"), - tbody( - tr( - td("VHIGH"), - td("12.6V"), - td("VLOW"), - td("9V") - ), - tr( - td("Voltage"), - td("11.2V"), - td("Remaining"), - td("80%") - ), - tr( - td("Flight"), - td("05:00"), - td("Endurance"), - td("12:00") - ) - ) - ), - table(`class` := "table-instrument")( - thead("Navigation"), - tbody( - tr( - td("Satellites"), - td("5"), - td("Precision"), - td("10cm") - ), - tr( - td("LON"), - td(""), - td("LAT"), - td("") - ), - tr( - td("GSpeed"), - td("3 m/s"), - td(), - td() - ), - tr( - td("Travelled"), - td("5000m"), - td("Home"), - td("1200m") - ) - ) - ) - ) - ) - ) - - - - val hud = { - def overlay(name: String, z: Int, thinnerThanWide: Boolean = false) = { - val direction = if (thinnerThanWide) "row" else "column" - div( - style:= - "position: absolute; left: 0; right: 0; top: 0; bottom: 0;" + - s"display: flex; align-content: center; align-items: stretch; flex-direction: $direction;"+ - s"z-index: $z;" - )( - `object`(`type`:="image/svg+xml", "data".attr:=env.asset("images/hud/" + name + ".svg"), style := "flex: 1 1 100%;") - ) - - } - - Seq( - overlay("horizon", 0), - overlay("roll", 1) - ) - } - - - - val element = div(`class` := "d-container d-column", style:="width: 100%; height: 100%;")( - div(`class` := "d-above")( - top - ), - div(`class` := "d-above d-container d-row")( - panel(modes), - panel(infos) - ), - div(`class` := "d-container d-row")( - div(`class` := "d-container d-left")( - left - ), - div(`class` := "d-main", style:="position: relative;")( - (new Hud(horizon.value)).element - ) - ) - ).render - -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Altimeter.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Altimeter.scala deleted file mode 100644 index 9919d33..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Altimeter.scala +++ /dev/null @@ -1,19 +0,0 @@ -package mavigator.dashboard.ui.instruments - -import mavigator.util.Environment - -import org.scalajs.dom.html -import rx._ - -class Altimeter(val value: Rx[Double])(implicit env: Environment) extends SvgInstrument[Double] { - import SvgInstrument._ - - lazy val element = svgObject("altimeter") - lazy val hand = part("hand") - lazy val moveable = Seq(hand) - - // 1m === 36deg = 2Pi / 10 ~= 0.62832 - protected def update(altitude: Double) = { - rotate(hand, (altitude * 0.62832).toInt) - } -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Bar.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Bar.scala deleted file mode 100644 index 0950eab..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Bar.scala +++ /dev/null @@ -1,18 +0,0 @@ -package mavigator.dashboard.ui.instruments - -import mavigator.util.Environment -import org.scalajs.dom.html -import rx._ - -class Bar(val value: Rx[Double])(implicit env: Environment) extends SvgInstrument[Double] { - import SvgInstrument._ - - lazy val element = svgObject("bar") - lazy val level = part("level") - lazy val moveable = Seq(level) - - protected def update(value: Double) = { - translate(level, 0, (97 * (1 - value / 100)).toInt) - } - -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Clock.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Clock.scala deleted file mode 100644 index 160fd2d..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Clock.scala +++ /dev/null @@ -1,23 +0,0 @@ -package mavigator.dashboard.ui.instruments - -import org.scalajs.dom -import rx._ -import scala.scalajs.js.Date -import scalatags.JsDom.all._ - -class Clock extends Instrument[Date] { - - def format(date: Date) = date.toLocaleTimeString() - - val value = Var(new Date) - - val element = span(format(value())).render - - protected def update(value: Date) = { - element.innerHTML = format(value) - } - - dom.setInterval(() => {value() = new Date}, 1000) - ready() - -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Compass.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Compass.scala deleted file mode 100644 index 117c7a3..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Compass.scala +++ /dev/null @@ -1,17 +0,0 @@ -package mavigator.dashboard.ui.instruments - -import org.scalajs.dom.html -import rx._ -import mavigator.util.Environment - -class Compass(val value: Rx[Double])(implicit env: Environment) extends SvgInstrument[Double] { - import SvgInstrument._ - - lazy val element = svgObject("compass") - lazy val plate = part("heading") - lazy val moveable = Seq(plate) - - protected def update(heading: Double) = { - rotate(plate, heading) - } -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Distribution.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Distribution.scala deleted file mode 100644 index aadafe0..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Distribution.scala +++ /dev/null @@ -1,25 +0,0 @@ -package mavigator.dashboard.ui.instruments - -import org.scalajs.dom.html -import rx._ -import mavigator.util.Environment - -class Distribution(val value: Rx[(Double, Double, Double, Double)])(implicit env: Environment) extends SvgInstrument[(Double, Double, Double, Double)] { - import SvgInstrument._ - - lazy val element = svgObject("distribution") - lazy val position = part("position") - lazy val moveable = Seq(position) - - private final val Radius = 50 //px - - protected def update(value: (Double, Double, Double, Double)) = { - val sum = value._1 + value._2 + value._3 + value._4 - val i = (value._1 - value._3) / sum - val j = (value._2 - value._4) / sum - val x = math.sqrt(2) / 2 * (i - j) - val y = math.sqrt(2) / 2 * (-i - j) - translate(position, (x * Radius).toInt, (y * Radius).toInt) - } - -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Generic.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Generic.scala deleted file mode 100644 index 14f7de3..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Generic.scala +++ /dev/null @@ -1,40 +0,0 @@ -package mavigator.dashboard.ui.instruments - -import org.scalajs.dom -import org.scalajs.dom.html -import rx._ -import mavigator.util.Environment - -class Generic( - min: Double, - med: Double, - max: Double, - unit: String, - val value: Rx[Double]) - (implicit env: Environment) - extends SvgInstrument[Double] { - - import SvgInstrument._ - - lazy val element = svgObject("generic") - lazy val handElement = part("hand") - lazy val unitElement = element.contentDocument.getElementById("unit") - lazy val valueElement = element.contentDocument.getElementById("value") - lazy val minElement = element.contentDocument.getElementById("min") - lazy val medElement = element.contentDocument.getElementById("med") - lazy val maxElement = element.contentDocument.getElementById("max") - lazy val 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 - super.load(e) - } - - protected def update(value: Double) = { - rotate(handElement, value / (max - min) * math.Pi * 3 / 2) - valueElement.textContent = value.toString - } -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Horizon.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Horizon.scala deleted file mode 100644 index 249f892..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Horizon.scala +++ /dev/null @@ -1,19 +0,0 @@ -package mavigator.dashboard.ui.instruments - -import org.scalajs.dom.html -import rx._ -import mavigator.util.Environment - -class Horizon(val value: Rx[(Double, Double)])(implicit env: Environment) extends SvgInstrument[(Double, Double)] { - import SvgInstrument._ - - lazy val element = svgObject("horizon") - lazy val pitch = part("pitch") - lazy val roll = part("roll") - lazy val moveable = Seq(pitch, roll) - - protected def update(pitchRoll: (Double, Double)) = { - translate(pitch, 0, (pitchRoll._1 * 180 / math.Pi).toInt) // 1deg === 1px - rotate(roll, pitchRoll._2) - } -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Instrument.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Instrument.scala deleted file mode 100644 index 61f240c..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Instrument.scala +++ /dev/null @@ -1,25 +0,0 @@ -package mavigator.dashboard.ui.instruments - -import rx._ -import org.scalajs.dom.html - -/** Common trait to all flight instruments. */ -trait Instrument[A] { - - /** Current value that is displayed in the instrument. */ - val value: Rx[A] - - /** HTML element that contains the rendered instrument */ - val element: html.Element - - /** Performs the actual UI update of this instrument. */ - protected def update(newValue: A): Unit - - /** Call when instrument has finished setting up its UI. */ - protected def ready() = { - Obs(value, skipInitial = true) { - update(value()) - } - } - -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Led.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Led.scala deleted file mode 100644 index c6f7d45..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/Led.scala +++ /dev/null @@ -1,17 +0,0 @@ -package mavigator.dashboard.ui.instruments - -import rx._ -import scalatags.JsDom.all._ -import mavigator.util.Environment - -class Led(val value: Rx[String])(implicit env: Environment) extends SvgInstrument[String] { - - lazy val element = `object`(`type` := "image/svg+xml", "data".attr := env.asset("images/leds/led.svg"), width := 100.pct)( - "Error loading led.").render - protected def moveable = Seq() - - protected def update(color: String) = { - part("light").setAttribute("fill", color) - } - -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/SvgInstrument.scala b/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/SvgInstrument.scala deleted file mode 100644 index 28c8d65..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/dashboard/ui/instruments/SvgInstrument.scala +++ /dev/null @@ -1,54 +0,0 @@ -package mavigator.dashboard.ui.instruments - - -import org.scalajs.dom -import org.scalajs.dom.html - -import scalatags.JsDom.all._ - -import mavigator.util.Environment - -/** An instrument backed by an SVG image. */ -trait SvgInstrument[A] extends Instrument[A] { - - /** SVG object element that contains the rendered instrument */ - val element: html.Object - - /** Retrieves an element of the underlying SVG document by ID. */ - protected def part(id: String) = element.contentDocument.getElementById(id).asInstanceOf[html.Element] - - /** Movable parts of the instrument */ - protected def moveable: Seq[html.Element] - - /** Called when element has been loaded. */ - protected def load(event: dom.Event): Unit = { - for (part <- moveable) { - part.style.transition = "transform 50ms ease-out" - } - ready() - } - - element.addEventListener("load", (e: dom.Event) => load(e)) -} - -/** Contains helpers for SVG instruments. */ -object SvgInstrument { - - /** Retrieves an SVG object element by its instrument's name. */ - def svgObject(name: String)(implicit app: Environment): html.Object = { - val path = app.asset("images/instruments/" + name + ".svg") - `object`(`type` := "image/svg+xml", "data".attr := path, width := 100.pct)( - "Error loading instrument " + name).render - } - - /** Applies translation styling to an element. */ - def translate(elem: html.Element, x: Int, y: Int): Unit = { - elem.style.transform = "translate(" + x + "px, " + y + "px)"; - } - - /** Applies rotation styling to an element. */ - def rotate(elem: html.Element, rad: Double): Unit = { - elem.style.transform = "rotateZ(" + rad + "rad)"; - } - -} diff --git a/mavigator-cockpit/src/main/scala/mavigator/index/ActiveVehicle.scala b/mavigator-cockpit/src/main/scala/mavigator/index/ActiveVehicle.scala index 870d3be..7e59ed8 100644 --- a/mavigator-cockpit/src/main/scala/mavigator/index/ActiveVehicle.scala +++ b/mavigator-cockpit/src/main/scala/mavigator/index/ActiveVehicle.scala @@ -13,7 +13,8 @@ object ActiveVehicle { id, vehicleType(hb.`type`), autopilot(hb.autopilot), - state(hb.systemStatus)) + state(hb.systemStatus) + ) def vehicleType(tpe: Int) = tpe match { case MavType.MavTypeGeneric => "Generic" diff --git a/mavigator-cockpit/src/main/scala/mavigator/index/Main.scala b/mavigator-cockpit/src/main/scala/mavigator/index/Main.scala index 7724f89..452d517 100644 --- a/mavigator-cockpit/src/main/scala/mavigator/index/Main.scala +++ b/mavigator-cockpit/src/main/scala/mavigator/index/Main.scala @@ -5,7 +5,6 @@ import scala.scalajs.js.annotation.JSExport import org.scalajs.dom.html -import mavigator.dashboard.ui.Layout import mavigator.util.Environment import mavigator.util.Application @@ -29,16 +28,17 @@ object Main extends Application { val parser = new Parser( packet => { val m: Message = Message.unpack(packet.messageId, packet.payload) - println(m) m match { case hb: Heartbeat => - active() += ActiveVehicle.fromHeartbeat(packet.systemId, hb) + active() = active.now + ActiveVehicle.fromHeartbeat(packet.systemId, hb) case _ => () } } ) override def main(args: Map[String, String])(implicit env: Environment): Unit = { + import rx.Ctx.Owner.Unsafe._ + val root = env.root val connection = new dom.WebSocket(args("socketUrl")) @@ -64,34 +64,45 @@ object Main extends Application { } - root.appendChild(div( - table(`class` := "table table-hover")( - thead( - tr( - th("System ID"), - th("Type"), - th("Autopilot"), - th("State"), - th("") - ) - ), - Rx { - tbody( - for (vehicle <- active().toSeq) yield { - tr( - td(vehicle.systemId), - td(vehicle.vehicleType), - td(vehicle.autopilot), - td(vehicle.state), - td(a(href := "/dashboard/" + vehicle.systemId, `class` := "btn btn-default")("Pilot")) - ) - } + val elem = + div(`class` := "container")( + 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")("Available vehicles") + ), + div(`class` := "panel-body")( + table(`class` := "table table-hover")( + thead( + tr( + th("System ID"), + th("Type"), + th("Autopilot"), + th("State"), + th("") + ) + ), + Rx { + tbody( + for (vehicle <- active().toSeq) yield { + tr( + td(vehicle.systemId), + td(vehicle.vehicleType), + td(vehicle.autopilot), + td(vehicle.state), + td(a(href := "/cockpit/" + vehicle.systemId, `class` := "btn btn-default")("Pilot")) + ) + } + ) + } + ), + p(i(`class`:="fa fa-spinner fa-spin")(), " Listening for heartbeats...") + ) ) - } - ), - i(`class`:="fa fa-spinner fa-spin")(), - " Listening for heartbeats..." - ).render) + ) + ).render + + root.appendChild(elem) } } diff --git a/mavigator-cockpit/src/main/scala/mavigator/index/Util.scala b/mavigator-cockpit/src/main/scala/mavigator/index/Util.scala index 16f3d5d..2671f2f 100644 --- a/mavigator-cockpit/src/main/scala/mavigator/index/Util.scala +++ b/mavigator-cockpit/src/main/scala/mavigator/index/Util.scala @@ -13,7 +13,7 @@ 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 * @@ -22,13 +22,13 @@ object Util { * 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 = { + implicit def rxMod(r: Rx[HtmlTag])(implicit owner: Ctx.Owner): 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) { + r.triggerLater{ val newLast = rSafe last.parentElement.replaceChild(newLast, last) last = newLast diff --git a/mavigator-cockpit/src/main/scala/mavigator/util/Application.scala b/mavigator-cockpit/src/main/scala/mavigator/util/Application.scala index 7cb6f27..68a39b1 100644 --- a/mavigator-cockpit/src/main/scala/mavigator/util/Application.scala +++ b/mavigator-cockpit/src/main/scala/mavigator/util/Application.scala @@ -4,6 +4,7 @@ package util import scala.scalajs.js.annotation.JSExport import scala.scalajs.js +import org.scalajs.dom.console import org.scalajs.dom.html trait Application { @@ -13,13 +14,18 @@ trait Application { @JSExport final def start(settings: js.Dynamic): Unit = { - val env = new StaticEnvironment( + console.info("Initializing environment,,,") + val env = new Environment( root = settings.root.asInstanceOf[html.Element], - assetsBase = settings.assetsBase.asInstanceOf[String] + styleRoot = settings.styleRoot.asInstanceOf[html.Element], + baseUrl = settings.baseUrl.asInstanceOf[String] ) - val args = settings.args.asInstanceOf[js.Dictionary[Any]].mapValues(_.toString).toMap + console.info("Reading arguments...") + val args: Map[String, String] = + settings.args.asInstanceOf[js.Dictionary[Any]].mapValues(_.toString).toMap + console.info("Entering main...") main(args)(env) } diff --git a/mavigator-cockpit/src/main/scala/mavigator/util/Environment.scala b/mavigator-cockpit/src/main/scala/mavigator/util/Environment.scala new file mode 100644 index 0000000..b1076fc --- /dev/null +++ b/mavigator-cockpit/src/main/scala/mavigator/util/Environment.scala @@ -0,0 +1,15 @@ +package mavigator +package util + +import org.scalajs.dom +import org.scalajs.dom.html + +/** Represents an application's environment + * @param root The application's root element. + * @param styleRoot An html 'style' tag to which app-specific styles are appended. + * @param baseUrl Base URL. */ +case class Environment( + root: html.Element, + styleRoot: html.Element, + baseUrl: String +) diff --git a/mavigator-cockpit/src/main/scala/mavigator/util/Page.scala b/mavigator-cockpit/src/main/scala/mavigator/util/Page.scala new file mode 100644 index 0000000..f8f0dee --- /dev/null +++ b/mavigator-cockpit/src/main/scala/mavigator/util/Page.scala @@ -0,0 +1,38 @@ +package mavigator +package util + +import org.scalajs.dom +import org.scalajs.dom.html + +/** Contents of a single-page app, consisting of styles and a main element. */ +trait Page { + + private var isAttached = false + private def attached[A](action: => A) = if (!isAttached) { + sys.error("Page has not been attached to an environment yet.") + } else { + action + } + + private var baseUrl: String = null + + def asset(path: String): String = attached { baseUrl + "/" + path } + + def styles: Seq[String] + + def elements: Seq[html.Element] + + /** Attach this page to a website. */ + def attach(env: Environment): Unit = { + baseUrl = env.baseUrl + isAttached = true + + val styleText = dom.document.createTextNode(styles.reduce(_ + _)) + env.styleRoot.appendChild(styleText) + + for (elem <- elements) { + env.root.appendChild(elem) + } + } + +} diff --git a/mavigator-cockpit/src/main/scala/mavigator/util/environment.scala b/mavigator-cockpit/src/main/scala/mavigator/util/environment.scala deleted file mode 100644 index 3d58c5a..0000000 --- a/mavigator-cockpit/src/main/scala/mavigator/util/environment.scala +++ /dev/null @@ -1,24 +0,0 @@ -package mavigator -package util - -import org.scalajs.dom.html - -/** Represents an application's environment */ -trait Environment { - - /** The application's root element. */ - def root: html.Element - - /** Retrieve an asset's URL based on its file location. */ - def asset(file: String): String - -} - -class StaticEnvironment( - override val root: html.Element, - assetsBase: String -) extends Environment { - - override def asset(file: String): String = assetsBase + "/" + file - -} diff --git a/mavigator-server/src/main/resources/assets/images/hud/attitude.svg b/mavigator-server/src/main/resources/assets/images/hud/attitude.svg new file mode 100644 index 0000000..eb7dd70 --- /dev/null +++ b/mavigator-server/src/main/resources/assets/images/hud/attitude.svg @@ -0,0 +1,815 @@ +<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="1000" + height="1000" + viewBox="-500 -500 1000 1000" + id="svg2" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="attitude.svg"> + <defs + id="defs4"> + <clipPath + clipPathUnits="userSpaceOnUse" + id="clipPath4472"> + <circle + id="circle4474" + cx="0" + cy="0" + r="380" /> + </clipPath> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="415.60712" + inkscape:cy="547.77206" + inkscape:document-units="px" + inkscape:current-layer="svg2" + showgrid="true" + units="px" + inkscape:snap-grids="true" + inkscape:snap-bbox="true" + inkscape:bbox-paths="true" + inkscape:snap-bbox-midpoints="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:snap-center="true" + inkscape:snap-intersection-paths="true" + inkscape:object-nodes="true" + inkscape:snap-smooth-nodes="true" + inkscape:object-paths="true" + inkscape:snap-midpoints="true" + inkscape:snap-object-midpoints="true" + inkscape:snap-text-baseline="true" + inkscape:window-width="1920" + inkscape:window-height="1034" + inkscape:window-x="0" + inkscape:window-y="27" + inkscape:window-maximized="1" + showguides="false"> + <inkscape:grid + type="xygrid" + id="grid4686" /> + </sodipodi:namedview> + <metadata + id="metadata7"> + <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="roll" + style="stroke:#1a1a1a" + clip-path="none"> + <g + id="g4476" + clip-path="url(#clipPath4472)"> + <g + id="pitch"> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -50,-100 100,0" + id="path5000" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -30,-50 60,0" + id="path5002" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path5028" + d="m -30,-150 60,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -30,-250 60,0" + id="path5030" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path5032" + d="m -30,-350 60,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path5034" + d="m -50,-200 100,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -50,-300 100,0" + id="path5036" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path5038" + d="m -30,50 60,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -30,150 60,0" + id="path5040" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path5042" + d="m -30,250 60,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -30,350 60,0" + id="path5044" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path5046" + d="m -50,100 100,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -50,200 100,0" + id="path5048" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path5050" + d="m -50,300 100,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="-95.449829" + id="text5054" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5056" + x="50" + y="-95.449829">10</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="-195.44983" + id="text5058" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5060" + x="50" + y="-195.44983">20</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="-295.44983" + id="text5062" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5064" + x="50" + y="-295.44983">30</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="104.55017" + id="text5070" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5072" + x="50" + y="104.55017">10</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="204.55017" + id="text5074" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5076" + x="50" + y="204.55017">20</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="304.55017" + id="text5078" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5080" + x="50" + y="304.55017">30</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="-295.44983" + id="text5082" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5084" + x="-50" + y="-295.44983">30</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="-195.44983" + id="text5086" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5088" + x="-50" + y="-195.44983">20</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="-95.449829" + id="text5090" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5092" + x="-50" + y="-95.449829">10</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="104.55017" + id="text5094" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5096" + x="-50" + y="104.55017">10</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="204.55017" + id="text5098" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5100" + x="-50" + y="204.55017">20</tspan></text> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="304.55017" + id="text5102" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + x="-50" + y="304.55017" + id="tspan5106">30</tspan></text> + <path + inkscape:connector-curvature="0" + id="path5110" + d="M -50,0 50,0" + style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="-50" + y="4.5501709" + id="text5116" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5118" + x="-50" + y="4.5501709">0</tspan></text> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-70 20,0" + id="path5307" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path5309" + d="m -10,-80 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path5305" + d="m -10,-60 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <text + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:start;letter-spacing:0px;word-spacing:0px;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="50" + y="4.5501709" + id="text5120" + sodipodi:linespacing="125%"><tspan + sodipodi:role="line" + id="tspan5122" + x="50" + y="4.5501709">0</tspan></text> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-40 20,0" + id="path4231" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4233" + d="m -10,-30 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-20 20,0" + id="path4235" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4237" + d="m -10,-10 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-90 20,0" + id="path4239" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4241" + d="m -10,-270 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-280 20,0" + id="path4243" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-260 20,0" + id="path4245" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4247" + d="m -10,-240 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-230 20,0" + id="path4249" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4251" + d="m -10,-220 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-210 20,0" + id="path4253" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4255" + d="m -10,-290 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-340 20,0" + id="path4263" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4265" + d="m -10,-330 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-320 20,0" + id="path4267" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4269" + d="m -10,-310 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4273" + d="m -10,-170 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-180 20,0" + id="path4275" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-160 20,0" + id="path4277" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4279" + d="m -10,-140 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-130 20,0" + id="path4281" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4283" + d="m -10,-120 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,-110 20,0" + id="path4285" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4287" + d="m -10,-190 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,30 20,0" + id="path4289" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4291" + d="m -10,20 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4293" + d="m -10,40 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,60 20,0" + id="path4295" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4297" + d="m -10,70 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,80 20,0" + id="path4299" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4301" + d="m -10,90 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,10 20,0" + id="path4303" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4305" + d="m -10,130 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,120 20,0" + id="path4307" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,140 20,0" + id="path4309" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4311" + d="m -10,160 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,170 20,0" + id="path4313" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4315" + d="m -10,180 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,190 20,0" + id="path4317" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4319" + d="m -10,110 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,230 20,0" + id="path4321" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4323" + d="m -10,220 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:connector-curvature="0" + id="path4325" + d="m -10,240 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,260 20,0" + id="path4327" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4329" + d="m -10,270 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,280 20,0" + id="path4331" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4333" + d="m -10,290 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,210 20,0" + id="path4335" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4337" + d="m -10,330 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,320 20,0" + id="path4339" + inkscape:connector-curvature="0" /> + <path + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -10,340 20,0" + id="path4341" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4351" + d="m -10,310 20,0" + style="fill:none;fill-rule:evenodd;stroke:#999999;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + </g> + <text + inkscape:transform-center-x="-387.965" + transform="matrix(0.5,0.8660254,-0.8660254,0.5,0,0)" + sodipodi:linespacing="125%" + id="text5215" + y="-443.43323" + x="7.9833984" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve" + inkscape:transform-center-y="-223.9917"><tspan + y="-443.43323" + x="7.9833984" + id="tspan5217" + sodipodi:role="line">60</tspan></text> + <text + inkscape:transform-center-y="-387.965" + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="7.9833989" + y="-443.43323" + id="text5211" + sodipodi:linespacing="125%" + transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,0)" + inkscape:transform-center-x="-223.9917"><tspan + sodipodi:role="line" + id="tspan5213" + x="7.9833989" + y="-443.43323">30</tspan></text> + <text + sodipodi:linespacing="125%" + id="text5207" + y="-443.43323" + x="0.85449171" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:center;letter-spacing:0px;word-spacing:0px;text-anchor:middle;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve" + inkscape:transform-center-y="-447.9834"><tspan + y="-443.43323" + x="0.85449171" + id="tspan5209" + sodipodi:role="line">0</tspan></text> + <text + inkscape:transform-center-y="-387.965" + xml:space="preserve" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + x="7.9833989" + y="-443.43323" + id="text5203" + sodipodi:linespacing="125%" + transform="matrix(0.8660254,-0.5,0.5,0.8660254,0,0)" + inkscape:transform-center-x="223.9917"><tspan + sodipodi:role="line" + id="tspan5205" + x="7.9833989" + y="-443.43323">30</tspan></text> + <text + inkscape:transform-center-x="387.965" + transform="matrix(0.5,-0.8660254,0.8660254,0.5,0,0)" + sodipodi:linespacing="125%" + id="text5199" + y="-443.43323" + x="7.9833994" + style="font-style:normal;font-weight:normal;font-size:12.5px;line-height:125%;font-family:sans-serif;text-align:end;letter-spacing:0px;word-spacing:0px;text-anchor:end;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + xml:space="preserve" + inkscape:transform-center-y="-223.9917"><tspan + y="-443.43323" + x="7.9833994" + id="tspan5201" + sodipodi:role="line">60</tspan></text> + <path + sodipodi:open="true" + d="M -346.65485,-199.57559 A 400,400 0 0 1 -0.24561214,-399.99992 400,400 0 0 1 346.4095,-200.00115" + sodipodi:end="5.7595832" + sodipodi:start="3.6639667" + sodipodi:ry="400" + sodipodi:rx="400" + sodipodi:cy="0" + sodipodi:cx="0" + sodipodi:type="arc" + id="path4684" + style="fill:none;stroke:#1a1a1a;stroke-width:1;stroke-linecap:butt;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> + <path + inkscape:transform-center-x="420" + inkscape:connector-curvature="0" + id="path4698" + d="m -400,0 -40,0" + style="fill:none;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:transform-center-y="-210" + style="fill:none;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -346.41016,-200 -34.64102,-20" + id="path4700" + inkscape:connector-curvature="0" + inkscape:transform-center-x="363.73067" /> + <path + inkscape:transform-center-x="210" + inkscape:connector-curvature="0" + id="path4702" + d="m -200,-346.41016 -20,-34.64102" + style="fill:none;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + inkscape:transform-center-y="-363.73067" /> + <path + inkscape:transform-center-x="-210" + inkscape:connector-curvature="0" + id="path4706" + d="m 200,-346.41016 20,-34.64102" + style="fill:none;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + inkscape:transform-center-y="-363.73067" /> + <path + inkscape:transform-center-y="-210" + style="fill:none;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 346.41016,-200 34.64102,-20" + id="path4708" + inkscape:connector-curvature="0" + inkscape:transform-center-x="-363.73067" /> + <path + inkscape:transform-center-x="-420" + inkscape:connector-curvature="0" + id="path4710" + d="M 400,-7.5688777e-7 440,1.1674235e-6" + style="fill:none;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + inkscape:transform-center-y="-265.14989" + style="fill:#ffff00;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -306.41778,-257.11504 -19.15111,-16.06969" + id="path4714" + inkscape:connector-curvature="0" + inkscape:transform-center-x="315.99334" /> + <path + inkscape:transform-center-x="265.1499" + inkscape:connector-curvature="0" + id="path4725" + d="m -257.11505,-306.41777 -16.06969,-19.15112" + style="fill:#ffff00;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + inkscape:transform-center-y="-315.99334" /> + <path + inkscape:transform-center-y="-387.62322" + style="fill:#ffff00;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m -136.80807,-375.87705 -8.55049,-23.49232" + id="path4727" + inkscape:connector-curvature="0" + inkscape:transform-center-x="141.08332" /> + <path + inkscape:transform-center-x="71.629882" + inkscape:connector-curvature="0" + id="path4729" + d="m -69.459281,-393.92311 -4.341191,-24.62019" + style="fill:#ffff00;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + inkscape:transform-center-y="-406.23322" /> + <path + inkscape:transform-center-x="-71.629877" + inkscape:transform-center-y="-406.23322" + style="fill:#ffff00;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 69.459269,-393.92311 4.341217,-24.62019" + id="path4731" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4733" + d="m 136.80806,-375.87706 8.55051,-23.49231" + style="fill:#ffff00;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + inkscape:transform-center-y="-387.62323" + inkscape:transform-center-x="-141.08331" /> + <path + inkscape:transform-center-x="-265.1499" + inkscape:transform-center-y="-315.99336" + style="fill:#ffff00;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 257.11506,-306.41779 16.06969,-19.1511" + id="path4735" + inkscape:connector-curvature="0" /> + <path + inkscape:connector-curvature="0" + id="path4737" + d="m 306.4178,-257.11505 19.15111,-16.06968" + style="fill:#ffff00;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + inkscape:transform-center-y="-265.14991" + inkscape:transform-center-x="-315.99335" /> + <path + inkscape:connector-curvature="0" + id="path4739" + d="m 0,-400 -12,-20 24,0 z" + style="fill:none;fill-rule:evenodd;stroke:#1a1a1a;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + </g> + <g + id="static"> + <path + inkscape:transform-center-y="-390" + inkscape:transform-center-x="-0.0686585" + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + id="path4696" + d="M 0,-400 -11.862683,-380 12,-380 Z" + style="fill:#ffdd55;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + style="fill:#fbff00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + d="m 103.52762,-386.37033 -16.634855,16.24823 23.049585,6.17611 z" + id="path4741" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccc" + inkscape:transform-center-x="-98.417558" + inkscape:transform-center-y="-375.15816" /> + <path + inkscape:transform-center-y="-375.17593" + inkscape:transform-center-x="98.284918" + sodipodi:nodetypes="cccc" + inkscape:connector-curvature="0" + id="path4743" + d="m -103.52762,-386.37033 -6.28209,22.3888 23.049582,-6.17612 z" + style="fill:#fbff00;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> + <path + d="M 30,0 150,0 M -30,0 -150,0 M 0,0 15,25 30,0 15,25 0,0 -15,25 -15,25 -30,0" + style="fill:none;fill-rule:evenodd;stroke:#ffdd55;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="path4991" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/mavigator-server/src/main/resources/assets/images/hud/hud.svg b/mavigator-server/src/main/resources/assets/images/hud/hud.svg deleted file mode 100644 index 943d7a4..0000000 --- a/mavigator-server/src/main/resources/assets/images/hud/hud.svg +++ /dev/null @@ -1,182 +0,0 @@ -<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="1200" - height="1200" - id="svg2" - version="1.1" - inkscape:version="0.48.5 r10040" - sodipodi:docname="drawing.svg" - viewBox="-600 600 1200 1200"> - <defs - id="defs4" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.25013902" - inkscape:cx="1001.4796" - inkscape:cy="-231.89709" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="true" - showborder="true" - inkscape:window-width="1920" - inkscape:window-height="1033" - inkscape:window-x="0" - inkscape:window-y="27" - inkscape:window-maximized="1"> - <inkscape:grid - type="xygrid" - id="grid2987" - empspacing="5" - visible="true" - enabled="true" - snapvisiblegridlinesonly="true" /> - </sodipodi:namedview> - <metadata - id="metadata7"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1"> - <rect - style="opacity:0.6;fill:#808000;fill-opacity:0.48235294;stroke:none" - id="rect2985" - width="1200" - height="1200" - x="-600" - y="600" /> - <path - sodipodi:type="arc" - style="opacity:0.6;fill:#808000;fill-opacity:0.48235294;stroke:none" - id="path2989" - sodipodi:cx="0" - sodipodi:cy="1200" - sodipodi:rx="440" - sodipodi:ry="440" - d="m 440,1200 a 440,440 0 1 1 -880,0 440,440 0 1 1 880,0 z" /> - <path - inkscape:connector-curvature="0" - id="path3782" - d="m 380,1200 60,0" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> - <path - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - d="m -440,1200 60,0" - id="path3012" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - d="M 0,760.06247 0,815" - id="path3786" - inkscape:connector-curvature="0" - inkscape:transform-center-y="-412.46877" /> - <path - inkscape:transform-center-y="-357.20843" - inkscape:connector-curvature="0" - id="path3794" - d="M -219.96877,819.00292 -192.5,866.58022" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="206.23439" /> - <path - inkscape:transform-center-y="-291.65947" - inkscape:connector-curvature="0" - id="path3796" - d="m -311.08281,888.91719 38.8467,38.8467" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="291.65946" /> - <path - inkscape:transform-center-y="-291.65947" - inkscape:connector-curvature="0" - id="path3798" - d="m 311.08281,888.91719 -38.8467,38.8467" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="-291.65946" /> - <path - inkscape:transform-center-y="-357.20843" - inkscape:connector-curvature="0" - id="path3800" - d="M 219.96877,819.00292 192.5,866.58022" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="-206.23439" /> - <path - inkscape:transform-center-y="-406.20244" - inkscape:connector-curvature="0" - id="path3802" - d="m 76.394345,766.74611 -9.539802,54.1029" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="-71.624444" /> - <path - inkscape:transform-center-x="-141.07262" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - d="m 150.46749,786.59395 -18.78974,51.62438" - id="path3804" - inkscape:connector-curvature="0" - inkscape:transform-center-y="-387.59386" /> - <path - inkscape:transform-center-y="-406.20244" - inkscape:connector-curvature="0" - id="path3806" - d="m -76.394345,766.74611 9.539802,54.1029" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="71.624444" /> - <path - inkscape:transform-center-x="141.07262" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - d="m -150.46749,786.59395 18.78974,51.62438" - id="path3808" - inkscape:connector-curvature="0" - inkscape:transform-center-y="-387.59386" /> - <path - style="fill:#ffb380;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - d="m -10,760 20,0 -10,40 -10,-40" - id="path3810" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cccc" /> - <path - d="m 440,1200 a 440,440 0 1 1 -880,0 440,440 0 1 1 880,0 z" - sodipodi:ry="440" - sodipodi:rx="440" - sodipodi:cy="1200" - sodipodi:cx="0" - id="path3812" - style="opacity:0.6;fill:#808000;fill-opacity:0.48235294;stroke:none" - sodipodi:type="arc" - transform="translate(0,910)" /> - <rect - style="opacity:0.6;fill:#ffb380;fill-opacity:0.48235294;stroke:#ffffff;stroke-width:5;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" - id="rect3814" - width="125" - height="895" - x="-597.5" - y="752.5" /> - <rect - y="752.5" - x="472.5" - height="895" - width="125" - id="rect3816" - style="opacity:0.6;fill:#ffb380;fill-opacity:0.48235294;stroke:#ffffff;stroke-width:5;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0" /> - </g> -</svg> diff --git a/mavigator-server/src/main/resources/assets/images/hud/overlay.html b/mavigator-server/src/main/resources/assets/images/hud/overlay.html deleted file mode 100644 index 58b0ef7..0000000 --- a/mavigator-server/src/main/resources/assets/images/hud/overlay.html +++ /dev/null @@ -1,29 +0,0 @@ -<!DOCTYPE html> - -<html> - <head> - <style> - html,body { - margin: 0; - width: 100%; - height: 100%; - } - </style> - </head> - <body> - - <div style="width: 100%; height: 100%; display: flex; align-content: flex-start; align-items: stretch; flex-direction: row"> - <div style="flex: 1; background-color: yellow;">side menu</div> - - <div style="flex: 1 1 70%; background-color: red; position: relative;"> - <div style="background-color: green; z-index: 0; display: flex; align-content: center; align-items: stretch; flex-direction: column; position: absolute; left: 0; right: 0; top: 0; bottom: 0;"> - <object style="flex: 1 1 100%;" type="image/svg+xml" data="horizon.svg">level0</object> - </div> - - <div style="z-index: 1; display: flex; align-content: center; align-items: stretch; flex-direction: column; position: absolute; left: 0; right: 0; top: 0; bottom: 0;"> - <object style="flex: 1 1 100%;" type="image/svg+xml" data="roll.svg">level0</object> - </div> - </div> - </div> - </body> -</html> diff --git a/mavigator-server/src/main/resources/assets/images/hud/roll.svg b/mavigator-server/src/main/resources/assets/images/hud/roll.svg deleted file mode 100644 index 93a9f90..0000000 --- a/mavigator-server/src/main/resources/assets/images/hud/roll.svg +++ /dev/null @@ -1,143 +0,0 @@ -<?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:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="1200" - height="1200" - id="svg2" - version="1.1" - inkscape:version="0.48.5 r10040" - sodipodi:docname="hud.svg" - viewBox="-600 600 1200 1200"> - <defs - id="defs4" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="0.70749999" - inkscape:cx="1028.6796" - inkscape:cy="816.93521" - inkscape:document-units="px" - inkscape:current-layer="layer1" - showgrid="true" - showborder="true" - inkscape:window-width="1920" - inkscape:window-height="1033" - inkscape:window-x="0" - inkscape:window-y="27" - inkscape:window-maximized="1"> - <inkscape:grid - type="xygrid" - id="grid2987" - empspacing="5" - visible="true" - enabled="true" - snapvisiblegridlinesonly="true" /> - </sodipodi:namedview> - <metadata - id="metadata7"> - <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></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1"> - <path - inkscape:connector-curvature="0" - id="path3782" - d="m 380,1200 60,0" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" /> - <path - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - d="m -440,1200 60,0" - id="path3012" - inkscape:connector-curvature="0" /> - <path - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - d="M 0,760.06247 0,815" - id="path3786" - inkscape:connector-curvature="0" - inkscape:transform-center-y="-412.46877" /> - <path - inkscape:transform-center-y="-357.20843" - inkscape:connector-curvature="0" - id="path3794" - d="M -219.96877,819.00292 -192.5,866.58022" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="206.23439" /> - <path - inkscape:transform-center-y="-291.65947" - inkscape:connector-curvature="0" - id="path3796" - d="m -311.08281,888.91719 38.8467,38.8467" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="291.65946" /> - <path - inkscape:transform-center-y="-291.65947" - inkscape:connector-curvature="0" - id="path3798" - d="m 311.08281,888.91719 -38.8467,38.8467" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="-291.65946" /> - <path - inkscape:transform-center-y="-357.20843" - inkscape:connector-curvature="0" - id="path3800" - d="M 219.96877,819.00292 192.5,866.58022" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="-206.23439" /> - <path - inkscape:transform-center-y="-406.20244" - inkscape:connector-curvature="0" - id="path3802" - d="m 76.394345,766.74611 -9.539802,54.1029" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="-71.624444" /> - <path - inkscape:transform-center-x="-141.07262" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - d="m 150.46749,786.59395 -18.78974,51.62438" - id="path3804" - inkscape:connector-curvature="0" - inkscape:transform-center-y="-387.59386" /> - <path - inkscape:transform-center-y="-406.20244" - inkscape:connector-curvature="0" - id="path3806" - d="m -76.394345,766.74611 9.539802,54.1029" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - inkscape:transform-center-x="71.624444" /> - <path - inkscape:transform-center-x="141.07262" - style="fill:none;stroke:#ffffff;stroke-width:5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" - d="m -150.46749,786.59395 18.78974,51.62438" - id="path3808" - inkscape:connector-curvature="0" - inkscape:transform-center-y="-387.59386" /> - <path - style="fill:#ffb380;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - d="m -10,760 20,0 -10,40 -10,-40" - id="path3810" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cccc" /> - </g> -</svg> diff --git a/mavigator-server/src/main/resources/assets/stylesheets/main.css b/mavigator-server/src/main/resources/assets/stylesheets/main.css index f254449..6529940 100644 --- a/mavigator-server/src/main/resources/assets/stylesheets/main.css +++ b/mavigator-server/src/main/resources/assets/stylesheets/main.css @@ -1,203 +1,146 @@ -html, body { +html, body, .app { width: 100%; height: 100%; } body { - background-color: #e6e6e6; + background-color: #e6e6e6; } .loader { - width: 100%; - font-size: 50px; - text-align: center; -} - -#vfd-dashboard { - width: 100%; - height: 100%; -} - -#vfd-dashboard header { - color: #eeeeee; - background-color: #222222; - padding-left: 8px; - padding-right: 8px; - padding-top: 3px; - padding-bottom: 3px; - margin-bottom: 3px; - display: flex; -} - -#vfd-dashboard header > * { - margin: 5px; - flex: 1; -} - -#vfd-dashboard header > :nth-child(1) { - text-align: left; -} - -#vfd-dashboard header > :nth-child(2) { - text-align: center; -} - -#vfd-dashboard header > :nth-child(3) { - text-align: right; -} - -/* dashboard layout */ -.d-container { - display: flex; - align-content: flex-start; - align-items: stretch; -} - -.d-container > * { - flex: 1; -} - -.d-column { - flex-direction: column; -} - -.d-row { - flex-direction: row; -} - -.d-above { - flex: none; -} - -.d-left { - flex: 1 1 30%; -} - -.d-main { - flex: 1 1 70%; -} - -.d-panel { - margin: 3px; - padding: 10px; - background-color: white; - border-radius: 3px; -} - + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; -/* Mode styles */ -.mode { - display: inline-block; - box-sizing: border-box; - text-decoration: normal; - margin-right: 5px; + font-size: 50px; + text-align: center; } -.mode.danger { +@keyframes danger-blink { + 0% { + color: #222222; + text-shadow: none; + } + 50% { color: #d9534f; text-shadow: 0 0 5px #d9534f; - animation: danger-blink 1s linear infinite; - -webkit-animation: danger-blink 1s linear infinite; -} - -.mode.warning { - color: #f0ad4e; - text-shadow: 0 0 5px #f0ad4e; + } + 100% { + color: #222222; + text-shadow: none; + } } -.mode.info { - color: #5bc0de; - text-shadow: 0 0 5px #5bc0de; +@-webkit-keyframes danger-blink { + 0% { + color: #eeeeee; + text-shadow: none; + } + 50% { + color: #d9534f; + text-shadow: 0 0 5px #d9534f; + } + 100% { + color: #eeeeee; + text-shadow: none; + } } -.mode.success { - color: #5cb85c; - text-shadow: 0 0 5px #5cb85c; +#map { + position: absolute; + top: auto; + left: auto; + right: 10px; + bottom: 10px; } -.mode.off { - color: #eeeeee; - text-shadow: none; - animation: none; - -webkit-animation: none; +#motors { + position: absolute; + top: auto; + bottom: 10px; + left: 10px; + right: auto; } -/* TODO: Rules below are maybe obsolete and need to be reviewed */ .table-instrument { - table-layout: fixed; - width: 100%; + table-layout: fixed; + width: 100%; } .table-instrument td { - width: 100%; + width: 100%; } .heartbeat { - color: rgba(165, 25, 25, 1); - animation: heartbeat 2s linear infinite; - -webkit-animation: heartbeat 2s linear infinite; + color: rgba(165, 25, 25, 1); + animation: heartbeat 2s linear infinite; + -webkit-animation: heartbeat 2s linear infinite; } @keyframes heartbeat { - 0% { - transform: scale(1); - } - 7% { - transform: scale(1.3); - } - 14% { - transform: scale(1); - } - 21% { - transform: scale(1.3); - } - 28% { - transform: scale(1); - } + 0% { + transform: scale(1); + } + 7% { + transform: scale(1.3); + } + 14% { + transform: scale(1); + } + 21% { + transform: scale(1.3); + } + 28% { + transform: scale(1); + } } @-webkit-keyframes heartbeat { - 0% { - transform: scale(1); - } - 7% { - transform: scale(1.3); - } - 14% { - transform: scale(1); - } - 21% { - transform: scale(1.3); - } - 28% { - transform: scale(1); - } + 0% { + transform: scale(1); + } + 7% { + transform: scale(1.3); + } + 14% { + transform: scale(1); + } + 21% { + transform: scale(1.3); + } + 28% { + transform: scale(1); + } } @keyframes danger-blink { - 0% { - color: #eeeeee; - text-shadow: none; - } - 50% { - color: #d9534f; - text-shadow: 0 0 5px #d9534f; - } - 100% { - color: #eeeeee; - text-shadow: none; - } + 0% { + color: #eeeeee; + text-shadow: none; + } + 50% { + color: #d9534f; + text-shadow: 0 0 5px #d9534f; + } + 100% { + color: #eeeeee; + text-shadow: none; + } } @-webkit-keyframes danger-blink { - 0% { - color: #eeeeee; - text-shadow: none; - } - 50% { - color: #d9534f; - text-shadow: 0 0 5px #d9534f; - } - 100% { - color: #eeeeee; - text-shadow: none; - } -}
\ No newline at end of file + 0% { + color: #eeeeee; + text-shadow: none; + } + 50% { + color: #d9534f; + text-shadow: 0 0 5px #d9534f; + } + 100% { + color: #eeeeee; + text-shadow: none; + } +} +*/ diff --git a/mavigator-server/src/main/resources/assets/stylesheets/reset.css b/mavigator-server/src/main/resources/assets/stylesheets/reset.css new file mode 100644 index 0000000..af94440 --- /dev/null +++ b/mavigator-server/src/main/resources/assets/stylesheets/reset.css @@ -0,0 +1,48 @@ +/* http://meyerweb.com/eric/tools/css/reset/ + v2.0 | 20110126 + License: none (public domain) +*/ + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +}
\ No newline at end of file diff --git a/mavigator-server/src/main/scala/mavigator/Router.scala b/mavigator-server/src/main/scala/mavigator/Router.scala index 07e40b0..389cb4d 100644 --- a/mavigator-server/src/main/scala/mavigator/Router.scala +++ b/mavigator-server/src/main/scala/mavigator/Router.scala @@ -22,11 +22,11 @@ object Router { val socketUrl = "ws://localhost:8080/mavlink" def route(implicit system: ActorSystem): Route = ( - path("dashboard" / IntNumber) { id => + path("cockpit" / IntNumber) { id => get { val html = mavigator.views.html.app( "Mavigator", - "mavigator_dashboard_Main", + "mavigator_cockpit_Main", Map( "socketUrl" -> socketUrl, "remoteSystemId" -> "0", diff --git a/mavigator-server/src/main/twirl/mavigator/views/app.scala.html b/mavigator-server/src/main/twirl/mavigator/views/app.scala.html index 34d1fde..34359f8 100644 --- a/mavigator-server/src/main/twirl/mavigator/views/app.scala.html +++ b/mavigator-server/src/main/twirl/mavigator/views/app.scala.html @@ -13,9 +13,9 @@ <p>The error was: "<span id="scalajs-error-message"></span>"</p> </div> -<div id="@appId"> +<div id="@appId" class="app"> <div class="loader"> - <i class="fa fa-spinner fa-spin"></i> + <i class="fa fa-cog fa-spin"></i> </div> </div> @@ -30,10 +30,12 @@ root0.removeChild(root0.firstChild); } - //run ScalaJS application + //run ScalaJS application + console.info("[@appId] Starting ScalaJS application...") @{appId}().start({ root: root0, - assetsBase: "/assets", + styleRoot: document.getElementById("app-styles"), + baseUrl: "/assets", args: {@args.map{ case (key, value) => @key: "@value", }} @@ -48,6 +50,6 @@ }); </script> -<script type="text/javascript" src="/assets/js/mavigator-dashboard-opt.js"></script> +<script type="text/javascript" src="/assets/js/mavigator-cockpit-fastopt.js"></script> } diff --git a/mavigator-server/src/main/twirl/mavigator/views/index.scala.html b/mavigator-server/src/main/twirl/mavigator/views/index.scala.html deleted file mode 100644 index 1bf892b..0000000 --- a/mavigator-server/src/main/twirl/mavigator/views/index.scala.html +++ /dev/null @@ -1,5 +0,0 @@ -@() - -@main("Index"){ -<h1>Hello world!</h1> -} diff --git a/mavigator-server/src/main/twirl/mavigator/views/main.scala.html b/mavigator-server/src/main/twirl/mavigator/views/main.scala.html index e3e83b8..1e21b1b 100644 --- a/mavigator-server/src/main/twirl/mavigator/views/main.scala.html +++ b/mavigator-server/src/main/twirl/mavigator/views/main.scala.html @@ -5,15 +5,18 @@ <html lang="en"> <head> <meta charset="utf-8"> - <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> + + <!-- Become fullscreen on mobile --> <meta name="mobile-web-app-capable" content="yes"> + <title>Mavigator - @title</title> <link rel="shortcut icon" href="/assets/images/logo.svg"> <link rel="stylesheet" media="screen" href="/assets/lib/bootstrap-3.3.6-dist/css/bootstrap.min.css"> <link rel="stylesheet" media="screen" href="/assets/lib/font-awesome-4.5.0/css/font-awesome.min.css"> <link rel="stylesheet" media="screen" href="/assets/stylesheets/main.css"> + <style id="app-styles"></style> </head> <body> diff --git a/mavigator-uav/src/main/scala/mavigator/uav/MavlinkUtil.scala b/mavigator-uav/src/main/scala/mavigator/uav/MavlinkUtil.scala.disabled index 0fb56b0..0fb56b0 100644 --- a/mavigator-uav/src/main/scala/mavigator/uav/MavlinkUtil.scala +++ b/mavigator-uav/src/main/scala/mavigator/uav/MavlinkUtil.scala.disabled diff --git a/mavigator-uav/src/main/scala/mavigator/uav/Multiplexer.scala b/mavigator-uav/src/main/scala/mavigator/uav/Multiplexer.scala.disabled index 5e48ea1..5e48ea1 100644 --- a/mavigator-uav/src/main/scala/mavigator/uav/Multiplexer.scala +++ b/mavigator-uav/src/main/scala/mavigator/uav/Multiplexer.scala.disabled diff --git a/mavigator-uav/src/main/scala/mavigator/uav/Uav.scala b/mavigator-uav/src/main/scala/mavigator/uav/Uav.scala index 06a8f00..8f8c083 100644 --- a/mavigator-uav/src/main/scala/mavigator/uav/Uav.scala +++ b/mavigator-uav/src/main/scala/mavigator/uav/Uav.scala @@ -32,7 +32,7 @@ class Uav(system: ExtendedActorSystem) extends Extension { def connect(): Flow[ByteString, ByteString, NotUsed] = { Flow.fromSinkAndSource( Sink.ignore, - (new MockConnection(0,0,1)).data + (new MockConnection(0,0,1)).data //TODO: use source instead of hardcoded value ) } diff --git a/mavigator-uav/src/main/scala/mavigator/uav/mock/RandomFlightPlan.scala b/mavigator-uav/src/main/scala/mavigator/uav/mock/RandomFlightPlan.scala index 89e5a75..680de1e 100644 --- a/mavigator-uav/src/main/scala/mavigator/uav/mock/RandomFlightPlan.scala +++ b/mavigator-uav/src/main/scala/mavigator/uav/mock/RandomFlightPlan.scala @@ -54,9 +54,10 @@ class RandomFlightPlan { 0 ) + def attitude = Attitude( millis, - (2 * math.Pi * time / 6).toFloat, + (math.sin(2 * math.Pi * time ) * math.Pi / 6).toFloat, (math.sin(2 * math.Pi * time / 5) * math.Pi / 6).toFloat, (2 * math.Pi * time / 4).toFloat, 0, diff --git a/project/Dependencies.scala b/project/Dependencies.scala index be6cbfe..ba39c94 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -19,8 +19,8 @@ object Dependencies { val flowNative = "com.github.jodersky" % "flow-native" % FlowVersion % Runtime val flowStream = "com.github.jodersky" % "flow-stream" % FlowVersion - val jsDom = Def.setting{"org.scala-js" %%% "scalajs-dom" % "0.8.2"} + val jsDom = Def.setting{"org.scala-js" %%% "scalajs-dom" % "0.9.0"} val scalatags = Def.setting{"com.lihaoyi" %%% "scalatags" % "0.5.4"} - val scalarx = Def.setting{"com.lihaoyi" %%% "scalarx" % "0.2.8"} + val scalarx = Def.setting{"com.lihaoyi" %%% "scalarx" % "0.3.1"} } diff --git a/project/Js.scala b/project/Js.scala index d665eab..9f85f3b 100644 --- a/project/Js.scala +++ b/project/Js.scala @@ -8,7 +8,7 @@ object Js { def dependsOnJs(proj: Project): Seq[Setting[_]] = Seq( resourceGenerators in Compile += Def.task{ - val js: File = (fullOptJS in (proj, Compile)).value.data + val js: File = (fastOptJS in (proj, Compile)).value.data val map = js.getParentFile / (js.name + ".map") val out = (resourceManaged in Compile).value / "assets" / "js" diff --git a/project/plugins.sbt b/project/plugins.sbt index 798db0c..301572f 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -2,19 +2,18 @@ * Additional resolvers */ -resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/" +//resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/" -resolvers += "jgit-repo" at "http://download.eclipse.org/jgit/maven" +//resolvers += "jgit-repo" at "http://download.eclipse.org/jgit/maven" resolvers += Resolver.jcenterRepo - /* * Main plugins */ // add support for scalajs -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.6") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.8") // twirl html templating addSbtPlugin("com.typesafe.sbt" % "sbt-twirl" % "1.1.1") |