From d66fd4a85cd13006acad380dd36634277fdf4988 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Sun, 25 Jan 2015 18:12:14 -0800 Subject: WIP --- book/src/main/scala/book/BookData.scala | 30 +++--- book/src/main/scala/book/Main.scala | 39 ++++---- book/src/main/scalatex/book/Index.scalatex | 2 +- .../main/scalatex/book/handson/CanvasApp.scalatex | 19 ++-- .../scalatex/book/handson/ClientServer.scalatex | 42 ++++---- .../scalatex/book/handson/GettingStarted.scalatex | 20 ++-- .../book/handson/PublishingModules.scalatex | 111 ++------------------- .../main/scalatex/book/handson/WebPage.scalatex | 36 +++---- .../book/indepth/AdvancedTechniques.scalatex | 34 ++++--- .../main/scalatex/book/indepth/JavaAPIs.scalatex | 2 +- build.sbt | 4 +- project/build.sbt | 2 +- 12 files changed, 128 insertions(+), 213 deletions(-) diff --git a/book/src/main/scala/book/BookData.scala b/book/src/main/scala/book/BookData.scala index 8484f10..b21f660 100644 --- a/book/src/main/scala/book/BookData.scala +++ b/book/src/main/scala/book/BookData.scala @@ -3,33 +3,31 @@ package book import java.io.File import acyclic.file +import ammonite.all._ import scalatags.Text.TypedTag import scalatags.Text.all._ object BookData { - val cloneRoot = System.getProperty("clone.root") + "/" + val wd = processWorkingDir + val cloneRoot = root/System.getProperty("clone.root").split('/') lazy val javaAPIs = { import java.io.File - def recursiveListFiles(f: File): Array[File] = { - val these = f.listFiles - these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles) - } + val roots = Seq( - "scala-js/javalanglib/src/main/scala", - "scala-js/javalib/src/main/scala" + "scala-js"/'javalanglib/'src/'main/'scala, + "scala-js"/'javalib/'src/'main/'scala ) for{ root <- roots - file <- recursiveListFiles(new File(cloneRoot + root)) - if file != null - if file.isFile + file <- ls.rec! cloneRoot/root + if file.ext == "scala" } yield{ - val path = file.getPath - .drop(cloneRoot.length + root.length + 1) - .dropRight(".scala".length) + + val path = (file - cloneRoot).toString.stripSuffix(".scala") val filename = path.replace('/', '.') + val docpath = s"https://docs.oracle.com/javase/7/docs/api/$path.html" filename -> docpath } @@ -52,9 +50,9 @@ object BookData { val hl = new scalatex.site.Highlighter { override val pathMappings = Seq( - s"$cloneRoot/scala-js" -> "https://github.com/scala-js/scala-js", - s"$cloneRoot/workbench-example-app" -> "https://github.com/lihaoyi/workbench-example-app", - "" -> "https://github.com/lihaoyi/hands-on-scala-js" + cloneRoot/"scala-js" -> "https://github.com/scala-js/scala-js/blob/master", + cloneRoot/"workbench-example-app" -> "https://github.com/lihaoyi/workbench-example-app/blob/master", + wd -> "https://github.com/lihaoyi/hands-on-scala-js/blob/master" ) override val suffixMappings = Map( "scala" -> "scala", diff --git a/book/src/main/scala/book/Main.scala b/book/src/main/scala/book/Main.scala index 90b0738..9696874 100644 --- a/book/src/main/scala/book/Main.scala +++ b/book/src/main/scala/book/Main.scala @@ -3,13 +3,16 @@ import acyclic.file import java.io.InputStream import java.nio.file.{Paths, Files} +import ammonite.ops.Path + import scalatags.Text.{attrs, tags2, all} import scalatags.Text.all._ import scalatex.site.Section.Tree import scalatex.site.Site - +import ammonite.all.{rel => _, _} object Main { + val wd = processWorkingDir def main(args: Array[String]): Unit = { val googleAnalytics = """ @@ -27,22 +30,23 @@ object Main { val s = new Site { def content = Map("index.html" -> Index()) - override def autoResources = super.autoResources | Set( - "META-INF/resources/webjars/pure/0.5.0/grids-responsive-min.css", - "css/side-menu.css", - "example-opt.js", - "webpage/weather.js", - "favicon.svg", - "favicon.png" + override def autoResources = super.autoResources ++ Seq( + wd/"META-INF"/'resources/'webjars/'pure/"0.5.0"/"grids-responsive-min.css", + wd/'css/"side-menu.css", + wd/"example-opt.js", + wd/'webpage/"weather.js", + wd/"favicon.svg", + wd/"favicon.png" ) - override def manualResources = super.manualResources | Set( - "images/javascript-the-good-parts-the-definitive-guide.jpg", - "images/Hello World.png", - "images/Hello World White.png", - "images/Hello World Console.png", - "images/IntelliJ Hello.png", - "images/Dropdown.png", - "images/Scalatags Downloads.png" + + override def manualResources = super.manualResources ++ Seq( + wd/'images/"javascript-the-good-parts-the-definitive-guide.jpg", + wd/'images/"Hello World.png", + wd/'images/"Hello World White.png", + wd/'images/"Hello World Console.png", + wd/'images/"IntelliJ Hello.png", + wd/'images/"Dropdown.png", + wd/'images/"Scalatags Downloads.png" ) override def headFrags = super.headFrags ++ Seq( meta(charset:="utf-8"), @@ -72,10 +76,9 @@ object Main { ), onload:=s"Controller().main($data)" ) - } - s.renderTo(System.getProperty("output.root") + "/") + s.renderTo(Path(System.getProperty("output.root"))) val allNames = { diff --git a/book/src/main/scalatex/book/Index.scalatex b/book/src/main/scalatex/book/Index.scalatex index bf33d19..35c2fb5 100644 --- a/book/src/main/scalatex/book/Index.scalatex +++ b/book/src/main/scalatex/book/Index.scalatex @@ -9,7 +9,7 @@ is a set of detailed expositions on various parts of the Scala.js platform. Noth @sect("Hands-on Scala.js", "Writing client-side web applications in Scala") @split @more - @hl.ref("examples/demos/src/main/scala/Splash.scala", "var x") + @hl.ref(wd/'examples/'demos/'src/'main/'scala/"Splash.scala", "var x") @less @BookData.example(canvas, "Splash().main") diff --git a/book/src/main/scalatex/book/handson/CanvasApp.scalatex b/book/src/main/scalatex/book/handson/CanvasApp.scalatex index 230ae67..b49f1e5 100644 --- a/book/src/main/scalatex/book/handson/CanvasApp.scalatex +++ b/book/src/main/scalatex/book/handson/CanvasApp.scalatex @@ -1,4 +1,5 @@ @import BookData._ +@val canvasapp = wd/'examples/'demos/'src/'main/'scala/'canvasapp @p By this point, you've already cloned and got your hands dirty fiddling around with the toy @lnk("workbench-example-app", "https://github.com/lihaoyi/workbench-example-app"). You have your editor set up, SBT installed, and have published the example application in a way you can host online for other people to see. Maybe you've even made some changes to the application to see what happens. Hopefully you're curious, and want to learn more. @@ -21,7 +22,7 @@ @p To begin with, lets remove all the existing stuff in our @code{.scala} file and leave only the @hl.scala{object} and the @hl.scala{main} method. Let's start off with some necessary boilerplate: - @hl.ref("examples/demos/src/main/scala/canvasapp/ScratchPad.scala", "/*setup*/", end = "/*code*/") + @hl.ref(canvasapp/"ScratchPad.scala", "/*setup*/", end = "/*code*/") @p As described earlier, this code uses the @lnk.dom.getElementById} function to fish out the @code{canvas} element that we interested in from the DOM. It then gets a rendering context from that @code{canvas}, and sets the height and width of the canvas to completely fill its containing element. Lastly, it fills out the canvas light-gray, so that we can see it on the page. @@ -31,7 +32,7 @@ @split @more - @hl.ref("examples/demos/src/main/scala/canvasapp/ScratchPad.scala", "/*code*/") + @hl.ref(canvasapp/"ScratchPad.scala", "/*code*/") @less @BookData.example(canvas, "ScratchPad().main") @@ -55,7 +56,7 @@ @p Again, we need roughly the same boilerplate as just now to set up the canvas: - @hl.ref("examples/demos/src/main/scala/canvasapp/Clock.scala", "/*setup*/", "/*code*/") + @hl.ref(canvasapp/"Clock.scala", "/*setup*/", "/*code*/") @p The only thing unusual here is that I'm going to create a @hl.scala{linearGradient} in order to make the stopwatch look pretty. This is by no means necessary, and you could simply make the @hl.scala{fillStyle} @hl.scala{"black"} if you want to keep things simple. @@ -65,7 +66,7 @@ @split @more - @hl.ref("examples/demos/src/main/scala/canvasapp/Clock.scala", "/*code*/") + @hl.ref(canvasapp/"Clock.scala", "/*code*/") @less @BookData.example(canvas, "Clock().main") @@ -97,7 +98,7 @@ It's a relatively simple game, but there should be enough "business logic" in here that we won't be simply gluing together APIs. Let's start! @sect{Setting Up the Canvas} - @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "/*setup*/", end="/*variables*/") + @hl.ref(canvasapp/"FlappyLine.scala", "/*setup*/", end="/*variables*/") @p This section of the code is peripherally necessary, but not core to the implementation or logic of Flappy Box. We see the same @hl.scala{canvas}/@hl.scala{renderer} logic we've seen in all our examples, along with some logic to make the canvas a reasonable size, and some configuration of how we will render text to the canvas. @@ -106,7 +107,7 @@ In general, code like this will usually end up being necessary in a Scala.js program: the Javascript APIs that the browser provides to do things often ends up being somewhat roundabout and verbose. It's somewhat annoying to have to do for a small program such as this one, but in a larger application, the cost is both spread out over thousands of lines of code and also typically hidden away in helper functions, so the verbosity and non-idiomatic-scala-ness doesn't bother you much. @sect{Defining our State} - @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "/*variables*/", end="def runLive") + @hl.ref(canvasapp/"FlappyLine.scala", "/*variables*/", end="def runLive") @p This is where we start defining things that are relevant to Flappy Box. There are roughly two groups of values here: immutable constants in the top group, and mutable variables in the bottom. The rough meaning of each variable is documented in the comments, and we'll see exactly how we use them later. @@ -115,7 +116,7 @@ One notable thing is that we're using a @lnk("collection.mutable.Queue", "http://docs.scala-lang.org/overviews/collections/concrete-mutable-collection-classes.html") to store the list of obstacles. This is defined in the Scala standard library; in general, all the collections in the Scala standard library can be used without issue in Scala.js. @sect{Game Logic} - @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "def runLive", "def runDead") + @hl.ref(canvasapp/"FlappyLine.scala", "def runLive", "def runDead") @p The @hl.scala{runLive} function is the meat of Flappy Box. In it, we @@ -135,13 +136,13 @@ @p This function basically contains all the game logic, from motion, to collision-detection, to rendering, so it's pretty large. Not that large though! And entirely understandable, even if it takes a few moments to read through. - @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "def runDead", "def run()") + @hl.ref(canvasapp/"FlappyLine.scala", "def runDead", "def run()") @p This is the function that handles what happens when you're dead. Essentially, we reset all the mutable variables to their initial state, and just count down the @hl.scala{dead} counter until it reaches zero and we're considered alive again. @sect{A Working Product} - @hl.ref("examples/demos/src/main/scala/canvasapp/FlappyLine.scala", "def run()") + @hl.ref(canvasapp/"FlappyLine.scala", "def run()") @p And finally, this is the code that kicks everything off: we define the @hl.scala{run} function to swap between @hl.scala{runLive} and @hl.scala{runDead}, register an @lnk.dom.onclick handler to make the player jump by tweaking his velocity, and we call @lnk.dom.setInterval to run the @hl.scala{run} function every 20 milliseconds. diff --git a/book/src/main/scalatex/book/handson/ClientServer.scalatex b/book/src/main/scalatex/book/handson/ClientServer.scalatex index 3ddb88b..8cbf9cf 100644 --- a/book/src/main/scalatex/book/handson/ClientServer.scalatex +++ b/book/src/main/scalatex/book/handson/ClientServer.scalatex @@ -21,6 +21,9 @@ @p Scala.js lets you share code between client and server relatively straightforwardly. As we saw in the previous chapter, where we made a shared module. Let's work to turn that shared module into a working client-server application! +@val server = wd/'examples/'crossBuilds/'clientserver/'server/'src/'main/'scala/'simple +@val client = wd/'examples/'crossBuilds/'clientserver/'client/'src/'main/'scala/'simple + @sect{A Client-Server Setup} @p Getting started with client-server integration, let's go with the simplest configuration possible: a Spray server and a Scala.js client. Most of the other web-frameworks (@lnk.misc.Play, @lnk.misc.Scalatra, etc.) will have more complex configurations, but the basic mechanism of wiring up Scala.js to your web framework will be the same. Our project will look like this: @@ -43,7 +46,7 @@ @p First, let's do the wiring in @code{build.sbt}: - @hl.ref("examples/crossBuilds/clientserver/build.sbt") + @hl.ref(wd/'examples/'crossBuilds/'clientserver/"build.sbt") @p We have two projects: @code{client} and @code{server}, one of which is a Scala.js project (indicated by the presence of @hl.scala{scalaJSSettings}). Both projects share a number of settings: the presence of the @code{shared/} folder, which shared code can live in (similar to what we saw in @sect.ref{Cross Publishing Libraries}) and the settings to add @lnk.github.Scalatags and @lnk.github.uPickle to the build. Note that those two dependencies use the triple @code{%%%} instead of the double @code{%%} to declare: this means that for each dependency, we will pull in the Scala-JVM or Scala.js version depending on whether it's being used in a Scala.js project. Note also the @hl.scala{packageArchetype.java_application} setting, which isn't strictly necessary depending on what you want to do with the application, but this example needs it as part of the deployment to Heroku. @@ -54,7 +57,8 @@ @p Next, let's kick off the Spray server in our Scala-JVM main method: - @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala") + + @hl.ref(server/"Server.scala") @p This is a not-very-interesting @lnk("spray-routing", "http://spray.io/documentation/1.2.2/spray-routing/") application: we set up a server on @code{localhost:8080}, have the root URL serve the main page on GET, and have other GET URLs serve resources. This includes the @code{js-fastopt.js} file that is now in our resources because of our @code{build.sbt} config earlier! We also add a POST route to allow the client ask the server to list files various directories. @@ -62,14 +66,14 @@ @p The HTML template @hl.scala{Page.skeleton} is not shown above; I put it in a separate file for neatness: - @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala") + @hl.ref(server/"Page.scala") @p This is a typical @lnk.github.Scalatags HTML snippet. Note that since we're serving it directly from the server in Scala code, we do not need to leave a @code{.html} file somewhere on the filesystem! We can declare all HTML, including the skeleton of the page, in Scalatags. Otherwise it's the same as what we saw in earlier chapters: A simple HTML page which includes a script tag to run our Scala.js application. @p Lastly, we'll set up the Scala.js main method, which we are calling in the @hl.html{