diff options
author | Jakob Odersky <jakob@inpher.io> | 2019-11-26 00:03:43 -0500 |
---|---|---|
committer | Jakob Odersky <jakob@inpher.io> | 2019-11-26 13:40:36 -0500 |
commit | f38dd59d93f56213a8400841c7ebb0a7202144a7 (patch) | |
tree | a697acc765cf6203eb0a499601185d1303da8939 | |
parent | 2f298c65846b2f62b9b40cd23f11971b301829f3 (diff) | |
download | scala-tutorial-f38dd59d93f56213a8400841c7ebb0a7202144a7.tar.gz scala-tutorial-f38dd59d93f56213a8400841c7ebb0a7202144a7.tar.bz2 scala-tutorial-f38dd59d93f56213a8400841c7ebb0a7202144a7.zip |
Share model across fe and be and add websocket support
-rw-r--r-- | build.sc | 22 | ||||
-rw-r--r-- | server/src/Main.scala | 28 | ||||
-rw-r--r-- | shared/Message.scala (renamed from server/src/Message.scala) | 0 | ||||
-rw-r--r-- | shared/Templates.scala (renamed from server/src/Templates.scala) | 8 | ||||
-rw-r--r-- | webapp/src/Main.scala | 25 |
5 files changed, 72 insertions, 11 deletions
@@ -1,11 +1,23 @@ import mill._, scalalib._, scalajslib._ -object server extends ScalaModule { - def scalaVersion = "2.13.1" +trait Shared extends ScalaModule { + def sharedSources = T.sources(build.millSourcePath / "shared") + + def sources = T.sources( + super.sources() ++ sharedSources() + ) def ivyDeps = Agg( + ivy"com.lihaoyi::scalatags::0.7.0", // html rendering DSL http://www.lihaoyi.com/scalatags/ + ivy"com.lihaoyi::upickle::0.8.0" // json serializatio, is also included by cask http://www.lihaoyi.com/upickle/ + ) +} + +object server extends ScalaModule with Shared { + def scalaVersion = "2.13.1" + + def ivyDeps = super.ivyDeps() ++ Agg( ivy"com.lihaoyi::cask:0.3.6", // web framework, http://www.lihaoyi.com/cask/ - ivy"com.lihaoyi::scalatags:0.7.0", // html rendering DSL http://www.lihaoyi.com/scalatags/ ivy"io.getquill::quill-jdbc:3.4.10", // language integrated queries https://getquill.io/#docs ivy"org.xerial:sqlite-jdbc:3.28.0" // actual database driver, note the single ':' ) @@ -18,11 +30,11 @@ object server extends ScalaModule { } -object webapp extends ScalaJSModule { +object webapp extends ScalaJSModule with Shared { def scalaVersion = "2.13.1" def scalaJSVersion = "0.6.31" // https://www.scala-js.org/ - def ivyDeps = Agg( + def ivyDeps = super.ivyDeps() ++ Agg( ivy"org.scala-js::scalajs-dom::0.9.7" // http://scala-js.github.io/scala-js-dom/ ) diff --git a/server/src/Main.scala b/server/src/Main.scala index 9d17fa4..0230d04 100644 --- a/server/src/Main.scala +++ b/server/src/Main.scala @@ -3,6 +3,8 @@ import io.getquill.{SnakeCase, SqliteJdbcContext} import scala.collection.mutable import com.typesafe.config.ConfigFactory import java.nio.file.Files +import cask.endpoints.WsChannelActor +import cask.util.Ws object Main extends cask.MainRoutes { @@ -13,6 +15,8 @@ object Main extends cask.MainRoutes { )) import ctx._ // this import allows us to construct SQL queries in a nice DSL + object Templates extends Templates(scalatags.Text) + // tables aren't typically create from within a program, but we do it here' // for demonstration purposes ctx.executeAction( @@ -34,6 +38,30 @@ object Main extends cask.MainRoutes { @cask.postJson("/") def post(message: Message) = { run(query[Message].insert(lift(message))) + LiveMessages.broadcast(message) + } + + object LiveMessages { + val channels = mutable.WeakHashMap.empty[WsChannelActor, Unit] + + // This actor receives incoming messages from websockets. Since this + // example project is only interested in broadcasting, we ignore + // all messages received. + val incoming = cask.WsActor { + case _ => + } + + def broadcast(message: Message): Unit = channels.keySet.foreach(channel => + channel.send(Ws.Text(upickle.default.write(message))) + ) + } + + @cask.websocket("/feed") + def messageFeed(): cask.WebsocketResult = { + cask.WsHandler{ channel => + LiveMessages.channels += channel -> () + LiveMessages.incoming + } } @cask.staticResources("/assets") diff --git a/server/src/Message.scala b/shared/Message.scala index 55b4602..55b4602 100644 --- a/server/src/Message.scala +++ b/shared/Message.scala diff --git a/server/src/Templates.scala b/shared/Templates.scala index 3abb09e..9c3c292 100644 --- a/server/src/Templates.scala +++ b/shared/Templates.scala @@ -1,7 +1,7 @@ +import scalatags.generic.Bundle -import scalatags.Text.all._ - -object Templates { +class Templates[Builder, Output <: FragT, FragT](val backend: Bundle[Builder, Output, FragT]) { + import backend.all._ def message(msg: Message): Tag = { div(cls := "col-xs-12 col-sm-6 col-md-3 col-lg-2")( @@ -59,7 +59,7 @@ object Templates { raw( """|document.addEventListener("DOMContentLoaded", function(event) { | console.info("Starting ScalaJS application...") - | Main.func() // run ScalaJS application + | Main.main() // run ScalaJS application |}) |""".stripMargin ) diff --git a/webapp/src/Main.scala b/webapp/src/Main.scala index 5aa2f2b..7aa1322 100644 --- a/webapp/src/Main.scala +++ b/webapp/src/Main.scala @@ -5,9 +5,30 @@ import scalajs.js.annotation @annotation.JSExportTopLevel("Main") object Main { + object Templates extends Templates(scalatags.JsDom) + @annotation.JSExport - def func() = { - dom.console.log("hello from ScalaJS!") + def main() = { + + val container = dom.document.getElementById("conversation") + val address = { + val location = dom.window.location + val protocol = if (location.protocol == "https:") { + "wss" + } else { + "ws" + } + s"$protocol://${location.host}/feed" + } + val ws = new dom.WebSocket(address) + + ws.onmessage = event => { + val msg = upickle.default.read[Message]( + event.data.asInstanceOf[String]) + + container.appendChild(Templates.message(msg).render) + dom.window.scrollTo(0, dom.document.body.scrollHeight) + } } } |