aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jakob@inpher.io>2019-11-26 00:03:43 -0500
committerJakob Odersky <jakob@inpher.io>2019-11-26 13:40:36 -0500
commitf38dd59d93f56213a8400841c7ebb0a7202144a7 (patch)
treea697acc765cf6203eb0a499601185d1303da8939
parent2f298c65846b2f62b9b40cd23f11971b301829f3 (diff)
downloadscala-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.sc22
-rw-r--r--server/src/Main.scala28
-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.scala25
5 files changed, 72 insertions, 11 deletions
diff --git a/build.sc b/build.sc
index 10354e9..3799ef2 100644
--- a/build.sc
+++ b/build.sc
@@ -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)
+ }
}
}