summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2014-09-05 14:17:29 -0700
committerLi Haoyi <haoyi@dropbox.com>2014-09-05 14:17:29 -0700
commitb9624be0c341ad070869497d2d10db0bb6beb3ed (patch)
treeee6e5ee7b7c07248acbf9b6f97911d6541c72572
parentfa3b7505ee26c4db0abbe72a4139ea4dafaaad60 (diff)
downloadworkbench-b9624be0c341ad070869497d2d10db0bb6beb3ed.tar.gz
workbench-b9624be0c341ad070869497d2d10db0bb6beb3ed.tar.bz2
workbench-b9624be0c341ad070869497d2d10db0bb6beb3ed.zip
0.2.0
-rw-r--r--build.sbt2
-rw-r--r--client/src/main/scala/workbench/WorkbenchClient.scala28
-rw-r--r--example/src/main/scala/example/ScalaJSExample.scala2
-rw-r--r--readme.md15
-rw-r--r--shared/main/scala/workbench/Shared.scala30
-rw-r--r--src/main/scala/workbench/Server.scala38
6 files changed, 87 insertions, 28 deletions
diff --git a/build.sbt b/build.sbt
index 6bb8ec3..74522bf 100644
--- a/build.sbt
+++ b/build.sbt
@@ -9,7 +9,7 @@ val defaultSettings = Seq(
lazy val root = project.in(file(".")).settings(defaultSettings:_*).settings(
name := "workbench",
- version := "0.1.6",
+ version := "0.2.0",
organization := "com.lihaoyi",
scalaVersion := "2.10.4",
sbtPlugin := true,
diff --git a/client/src/main/scala/workbench/WorkbenchClient.scala b/client/src/main/scala/workbench/WorkbenchClient.scala
index 662e797..9fc5b89 100644
--- a/client/src/main/scala/workbench/WorkbenchClient.scala
+++ b/client/src/main/scala/workbench/WorkbenchClient.scala
@@ -6,24 +6,24 @@ import upickle.{Reader, Writer, Js}
import scala.scalajs.js
import scala.scalajs.js.annotation.JSExport
import scalajs.concurrent.JSExecutionContext.Implicits.runNow
-object WireServer extends autowire.Server[Js.Value, upickle.Reader, upickle.Writer]{
- def write[Result: Writer](r: Result) = upickle.writeJs(r)
- def read[Result: Reader](p: Js.Value) = upickle.readJs[Result](p)
+/**
+ * The connection from workbench server to the client
+ */
+object Wire extends autowire.Server[Js.Value, upickle.Reader, upickle.Writer] with ReadWrite{
def wire(parsed: Js.Arr): Unit = {
val Js.Arr(path, args: Js.Obj) = parsed
-
- val req = new Request(
- upickle.readJs[Seq[String]](path),
- args.value.toMap
- )
- WireServer.route[Api](WorkbenchClient).apply(req)
+ val req = new Request(upickle.readJs[Seq[String]](path), args.value.toMap)
+ Wire.route[Api](WorkbenchClient).apply(req)
}
}
@JSExport
object WorkbenchClient extends Api{
+ @JSExport
val shadowBody = dom.document.body.cloneNode(deep = true)
+ @JSExport
var interval = 1000
+ @JSExport
var success = false
@JSExport
def main(bootSnippet: String, host: String, port: Int): Unit = {
@@ -35,7 +35,7 @@ object WorkbenchClient extends Api{
json.read(data.responseText)
.asInstanceOf[Js.Arr]
.value
- .foreach(v => WireServer.wire(v.asInstanceOf[Js.Arr]))
+ .foreach(v => Wire.wire(v.asInstanceOf[Js.Arr]))
main(bootSnippet, host, port)
case util.Failure(e) =>
if (!success) println("Workbench disconnected " + e)
@@ -44,7 +44,7 @@ object WorkbenchClient extends Api{
dom.setTimeout(() => main(bootSnippet, host, port), interval)
}
}
-
+ @JSExport
override def clear(): Unit = {
dom.document.asInstanceOf[js.Dynamic].body = shadowBody.cloneNode(true)
for(i <- 0 until 100000){
@@ -52,12 +52,12 @@ object WorkbenchClient extends Api{
dom.clearInterval(i)
}
}
-
+ @JSExport
override def reload(): Unit = {
dom.console.log("Reloading page...")
dom.location.reload()
}
-
+ @JSExport
override def run(path: String, bootSnippet: Option[String]): Unit = {
val tag = dom.document.createElement("script")
var loaded = false
@@ -75,7 +75,7 @@ object WorkbenchClient extends Api{
}
dom.document.head.appendChild(tag)
}
-
+ @JSExport
override def print(level: String, msg: String): Unit = {
level match {
case "error" => dom.console.error(msg)
diff --git a/example/src/main/scala/example/ScalaJSExample.scala b/example/src/main/scala/example/ScalaJSExample.scala
index bab193b..7a41c44 100644
--- a/example/src/main/scala/example/ScalaJSExample.scala
+++ b/example/src/main/scala/example/ScalaJSExample.scala
@@ -22,7 +22,7 @@ object ScalaJSExample {
val corners = Seq(Point(255, 255), Point(0, 255), Point(128, 0))
def clear() = {
- ctx.fillStyle = "black"
+ ctx.fillStyle = "white"
ctx.fillRect(0, 0, 255, 255)
}
diff --git a/readme.md b/readme.md
index 9616a7a..08ae7c6 100644
--- a/readme.md
+++ b/readme.md
@@ -20,7 +20,7 @@ resolvers += "spray repo" at "http://repo.spray.io"
resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/"
-addSbtPlugin("com.lihaoyi" % "workbench" % "0.1.5")
+addSbtPlugin("com.lihaoyi" % "workbench" % "0.2.0")
```
- Add to your `build.sbt`
```scala
@@ -83,6 +83,19 @@ You can force the clean-up-and-reboot to happen from the browser via the shortcu
With this done, you should be receiving the SBT logspam (compilation, warnings, errors) in your browse console, and the page should be automatically refreshing/updating when the application gets recompiled. If you have problems setting this up, try starting from the [example app](https://github.com/lihaoyi/workbench-example-app) and working from there.
+
+# Development
+
+To develop, go into `example/` and run `sbt ~fastOptJS`. Then you can go to
+
+```
+http://localhost:12345/target/scala-2.11/classes/index-dev.html
+```
+
+and see a small sierpinski-triangle application. Editing the code within `example/` should cause the SBT log-spam to appear in the browser console, and changes (e.g. changing the color of the background fill) should cause a recompile and updating of the browser animation.
+
+To make changes to workbench, modify the workbench source code and stop/re-run `sbt ~fastOptJS`. When workbench finishes re-compiling, SBT re-starts and the page becomes accessible, your changes to workbench will take effect. You can replace `fullOptJs` in the `built.sbt` file with `fastOptJS`, and swapping the reference to `client-opt.js` to `client-fastopt.js`, if you want to speed up the development cycle.
+
Pull requests welcome!
License
diff --git a/shared/main/scala/workbench/Shared.scala b/shared/main/scala/workbench/Shared.scala
index da202b4..f9a1434 100644
--- a/shared/main/scala/workbench/Shared.scala
+++ b/shared/main/scala/workbench/Shared.scala
@@ -1,8 +1,38 @@
package com.lihaoyi.workbench
+import upickle.{Js, Reader, Writer}
+
+/**
+ * A standard way to read and write `Js.Value`s with autowire/upickle
+ */
+trait ReadWrite{
+ def write[Result: Writer](r: Result) = upickle.writeJs(r)
+ def read[Result: Reader](p: Js.Value) = upickle.readJs[Result](p)
+}
+
+/**
+ * Shared API between the workbench server and the workbench client,
+ * comprising methods the server can call on the client to make it do
+ * things
+ */
trait Api{
+ /**
+ * Reset the HTML page to its initial state
+ */
def clear(): Unit
+ /**
+ * Reload the entire webpage
+ */
def reload(): Unit
+
+ /**
+ * Print a `msg` with the given logging `level`
+ */
def print(level: String, msg: String): Unit
+
+ /**
+ * Execute the javascript file available at the given `path`. Optionally,
+ * run a `bootSnippet` after the file has been executed.
+ */
def run(path: String, bootSnippet: Option[String]): Unit
} \ No newline at end of file
diff --git a/src/main/scala/workbench/Server.scala b/src/main/scala/workbench/Server.scala
index bec88b6..4d37073 100644
--- a/src/main/scala/workbench/Server.scala
+++ b/src/main/scala/workbench/Server.scala
@@ -18,17 +18,28 @@ class Server(url: String, port: Int, bootSnippet: String) extends SimpleRoutingA
config = ConfigFactory.load(ActorSystem.getClass.getClassLoader),
classLoader = ActorSystem.getClass.getClassLoader
)
- object Wire extends autowire.Client[Js.Value, upickle.Reader, upickle.Writer]{
+
+ /**
+ * The connection from workbench server to the client
+ */
+ object Wire extends autowire.Client[Js.Value, upickle.Reader, upickle.Writer] with ReadWrite{
def doCall(req: Request): Future[Js.Value] = {
- pubSub ! Js.Arr(upickle.writeJs(req.path), Js.Obj(req.args.toSeq:_*))
+ longPoll ! Js.Arr(upickle.writeJs(req.path), Js.Obj(req.args.toSeq:_*))
Future.successful(Js.Null)
}
- def write[Result: Writer](r: Result) = upickle.writeJs(r)
- def read[Result: Reader](p: Js.Value) = upickle.readJs[Result](p)
}
- private val pubSub = actor(new Actor{
+
+ /**
+ * Actor meant to handle long polling, buffering messages or waiting actors
+ */
+ private val longPoll = actor(new Actor{
var waitingActor: Option[ActorRef] = None
var queuedMessages = List[Js.Value]()
+
+ /**
+ * Flushes returns nothing to any waiting actor every so often,
+ * to prevent the connection from living too long.
+ */
case object Clear
import system.dispatcher
@@ -53,12 +64,19 @@ class Server(url: String, port: Int, bootSnippet: String) extends SimpleRoutingA
case (msg: Js.Arr, Some(a), Nil) =>
respond(a, upickle.json.write(Js.Arr(msg)))
waitingActor = None
- case (Clear, Some(a), Nil) =>
- respond(a, upickle.json.write(Js.Arr()))
+ case (Clear, waiting, Nil) =>
+ waiting.foreach(respond(_, upickle.json.write(Js.Arr())))
waitingActor = None
}
})
+ /**
+ * Simple spray server:
+ *
+ * - /workbench.js is hardcoded to be the workbench javascript client
+ * - Any other GET request just pulls from the local filesystem
+ * - POSTs to /notifications get routed to the longPoll actor
+ */
startServer(url, port) {
get {
path("workbench.js") {
@@ -79,11 +97,9 @@ class Server(url: String, port: Int, bootSnippet: String) extends SimpleRoutingA
} ~
post {
path("notifications") { ctx =>
- pubSub ! ctx.responder
+ longPoll ! ctx.responder
}
}
}
- def kill() = {
- system.shutdown()
- }
+ def kill() = system.shutdown()
} \ No newline at end of file