From 3beb1dd285d89dd4b746b1c4cd287a8748376556 Mon Sep 17 00:00:00 2001 From: Li Haoyi Date: Thu, 13 Nov 2014 00:56:59 -0800 Subject: Commit after converting to React.js --- book/src/main/resources/css/side-menu.css | 10 +- book/src/main/scala/book/Book.scala | 3 +- book/src/main/scalatex/book/Index.scalatex | 56 ++++---- build.sbt | 5 +- examples/demos/Controller.scala | 214 +++++++++++------------------ examples/demos/build.sbt | 2 +- 6 files changed, 121 insertions(+), 169 deletions(-) diff --git a/book/src/main/resources/css/side-menu.css b/book/src/main/resources/css/side-menu.css index 8a9e25e..e5d014c 100755 --- a/book/src/main/resources/css/side-menu.css +++ b/book/src/main/resources/css/side-menu.css @@ -34,11 +34,11 @@ Add transition to containers so they can push in and out. } .hide, .menu-item-list > li{ - -webkit-transition: height 0.2s ease-out; - -moz-transition: height 0.2s ease-out; - -ms-transition: height 0.2s ease-out; - -o-transition: height 0.2s ease-out; - transition: height 0.2s ease-out; + -webkit-transition: all 0.2s ease-out; + -moz-transition: all 0.2s ease-out; + -ms-transition: all 0.2s ease-out; + -o-transition: all 0.2s ease-out; + transition: all 0.2s ease-out; } /* diff --git a/book/src/main/scala/book/Book.scala b/book/src/main/scala/book/Book.scala index ea57945..857bf37 100644 --- a/book/src/main/scala/book/Book.scala +++ b/book/src/main/scala/book/Book.scala @@ -24,8 +24,9 @@ object Book { "META-INF/resources/webjars/font-awesome/4.2.0/fonts/fontawesome-webfont.svg", "META-INF/resources/webjars/font-awesome/4.2.0/fonts/fontawesome-webfont.ttf", "META-INF/resources/webjars/font-awesome/4.2.0/fonts/fontawesome-webfont.woff", + "META-INF/resources/webjars/react/0.11.1/react.min.js", "css/side-menu.css", - "example-fastopt.js", + "example-opt.js", "webpage/weather.js", "favicon.svg", "favicon.png" diff --git a/book/src/main/scalatex/book/Index.scalatex b/book/src/main/scalatex/book/Index.scalatex index 59d474e..e9a8fc8 100644 --- a/book/src/main/scalatex/book/Index.scalatex +++ b/book/src/main/scalatex/book/Index.scalatex @@ -20,43 +20,43 @@ @sect("Intro to Scala.js") @Intro() -@sect("Hands On", "Writing your first Scala.js programs") - @p - This half of the book walks you through various facets of the Scala.js development experience. From making your first app, to testing and publishing modules, to writing an integrated client-server application. + @sect("Hands On", "Writing your first Scala.js programs") + @p + This half of the book walks you through various facets of the Scala.js development experience. From making your first app, to testing and publishing modules, to writing an integrated client-server application. - @sect("Getting Started") - @handson.GettingStarted() + @sect("Getting Started") + @handson.GettingStarted() - @sect("Making a Canvas App") - @handson.CanvasApp() + @sect("Making a Canvas App") + @handson.CanvasApp() - @sect("Interactive Web Pages") - @handson.WebPage() + @sect("Interactive Web Pages") + @handson.WebPage() - @sect("The Command Line") - @handson.CommandLine() + @sect("The Command Line") + @handson.CommandLine() - @sect("Cross Publishing Libraries") - @handson.PublishingModules() + @sect("Cross Publishing Libraries") + @handson.PublishingModules() - @sect("Integrating Client-Server") - @handson.ClientServer() + @sect("Integrating Client-Server") + @handson.ClientServer() -@sect("In Depth", "Exploring Scala.js") - @p - This half of the book dives into a few aspects of Scala.js much more deeply that the hands-on introduction does. It's aimed at someone who has already used Scala.js, and wants to explore the edge-cases, how things work under-the-cover, or why it has been designed in such a way. It's not a formal specification; rather, it's aim is to be a useful reference to read instead of (or in preparation for) digging into the implementation code. + @sect("In Depth", "Exploring Scala.js") + @p + This half of the book dives into a few aspects of Scala.js much more deeply that the hands-on introduction does. It's aimed at someone who has already used Scala.js, and wants to explore the edge-cases, how things work under-the-cover, or why it has been designed in such a way. It's not a formal specification; rather, it's aim is to be a useful reference to read instead of (or in preparation for) digging into the implementation code. - @sect("Javascript Interoperability") - @indepth.JavascriptInterop() + @sect("Javascript Interoperability") + @indepth.JavascriptInterop() - @sect("Deviations from Scala-JVM") - @indepth.SemanticDifferences() + @sect("Deviations from Scala-JVM") + @indepth.SemanticDifferences() - @sect("The Compilation Pipeline") - @indepth.CompilationPipeline() + @sect("The Compilation Pipeline") + @indepth.CompilationPipeline() - @sect("Scala.js' Design Space") - @indepth.DesignSpace() + @sect("Scala.js' Design Space") + @indepth.DesignSpace() - @sect("Java APIs") - @indepth.JavaAPIs() \ No newline at end of file + @sect("Java APIs") + @indepth.JavaAPIs() \ No newline at end of file diff --git a/build.sbt b/build.sbt index a2012d5..8bb2856 100644 --- a/build.sbt +++ b/build.sbt @@ -37,14 +37,15 @@ lazy val book = Project( "org.webjars" % "highlightjs" % "8.2-1", "org.webjars" % "pure" % "0.5.0", "org.webjars" % "font-awesome" % "4.2.0", + "org.webjars" % "react" % "0.11.1", "org.scala-lang" % "scala-reflect" % scalaVersion.value, "org.scala-lang" % "scala-compiler" % scalaVersion.value, "org.eclipse.jgit" % "org.eclipse.jgit" % "3.5.1.201410131835-r", "com.lihaoyi" %%% "upickle" % "0.2.5" ), (resources in Compile) += { - (fastOptJS in (demos, Compile)).value - (artifactPath in (demos, Compile, fastOptJS)).value + (fullOptJS in (demos, Compile)).value + (artifactPath in (demos, Compile, fullOptJS)).value }, (unmanagedResourceDirectories in Compile) ++= (unmanagedResourceDirectories in (demos, Compile)).value, diff --git a/examples/demos/Controller.scala b/examples/demos/Controller.scala index 0936776..e17cde9 100644 --- a/examples/demos/Controller.scala +++ b/examples/demos/Controller.scala @@ -1,4 +1,7 @@ +import japgolly.scalajs.react.React +import japgolly.scalajs.react.vdom.VDomBuilder + import scala.scalajs.js import scala.scalajs.js.annotation.JSExport import org.scalajs.dom @@ -6,15 +9,7 @@ import org.scalajs.dom.extensions._ import scala.collection.mutable import scalatags.JsDom.all._ -object Renderer{ - import japgolly.scalajs.react._ // React - import vdom.ReactVDom._ // Scalatags → React virtual DOM - import vdom.ReactVDom.all._ // Scalatags html & css (div, h1, textarea, etc.) - - val Menu = ReactComponentB[Int]("Menu").render{ p => - }.build -} case class Tree[T](name: T, children: Vector[Tree[T]]) @JSExport object Controller{ @@ -38,6 +33,14 @@ object Controller{ def main(data: scala.scalajs.js.Any) = { val structure = upickle.readJs[Tree[String]](upickle.json.readJs(data)) + var i = 0 + def recurse(t: Tree[String]): Tree[(String, String, Int)] = { + val curr = (t.name, munge(t.name), i) + i += 1 + val children = t.children.map(recurse) + Tree(curr, children) + } + val Seq(main, menu, layout, menuLink) = Seq( "main", "menu", "layout", "menuLink" @@ -47,43 +50,6 @@ object Controller{ snippets.foreach(js.Dynamic.global.hljs.highlightBlock(_)) - val contentTree = { - def rec(current: Tree[String], depth: Int): Tree[dom.HTMLElement] = { - val myCls = - "menu-item" + - (if (depth <= 1) " menu-item-divided" else "") - - val frag = - li( - paddingLeft := s"${depth * 15}px", - a( - current.name, - href:="#"+munge(current.name), - cls:=myCls - ) - ).render - Tree(frag, current.children.map(rec(_, depth + 1))) - } - rec(structure, 0) - } - val contentList = { - def rec(current: Tree[dom.HTMLElement]): Seq[dom.HTMLElement] = { - current.name +: current.children.flatMap(rec) - } - rec(contentTree).toVector - } - - val frag = div(cls:="pure-menu pure-menu-open")( - a(cls:="pure-menu-heading", href:="#")( - "Contents" - ), - ul(cls:="menu-item-list")( - contentList.drop(1) - ) - ) - menu.appendChild(frag.render) - - val headers = { def offset(el: dom.HTMLElement, parent: dom.HTMLElement): Double = { if (el == parent) 0 @@ -101,50 +67,33 @@ object Controller{ .toVector } - scrollSpy(main, headers, contentList, contentTree) + val menuBar = React.renderComponent( + Menu(recurse(structure)), + menu + ) menuLink.onclick = (e: dom.MouseEvent) => { toggleClass(layout, "active") toggleClass(menu, "active") toggleClass(menuLink, "active") } - } - - /** - * Needs to be done in a sketchy imperative fashion for performance: - * onscroll gets called quite a lot, so any additional work makes it - * noticeable jerky - */ - def scrollSpy(main: dom.HTMLElement, - headers: Vector[Double], - contentList: Vector[dom.HTMLElement], - contentTree: Tree[dom.HTMLElement]) = { - - var scrolling = false - var lastIndex = -1 - def run() = { - scrolling = false - val threshold = main.scrollTop + main.clientHeight - - var index = 0 - while(index < headers.length && index >= 0){ - index += 1 - if (headers(index) > threshold) index *= -1 - } - index = -index - - if (index != lastIndex){ - updateSideBar(lastIndex, index, contentList, contentTree) - lastIndex = index - } - } - run() main.onscroll = (e: dom.UIEvent) => { if (!scrolling){ scrolling = true - dom.requestAnimationFrame((d: Double) => run()) + dom.requestAnimationFrame{(d: Double) => + scrolling = false + val threshold = main.scrollTop + main.clientHeight + + var index = 0 + while(index < headers.length && index >= 0){ + index += 1 + if (headers(index) > threshold) index *= -1 + } + index = -index + menuBar.setState(index) + } } } } @@ -152,64 +101,65 @@ object Controller{ val rect = el.getBoundingClientRect() rect.top >= 0 && rect.bottom <= dom.innerHeight } - val lastShown = new js.Array[dom.HTMLElement](0) - val lastLined = new js.Array[dom.HTMLElement](0) - def updateSideBar(lastIndex: Int, - index: Int, - contentList: Vector[dom.HTMLElement], - contentTree: Tree[dom.HTMLElement]) = { - - println(s"MOVING $lastIndex -> $index") - if (!isElementInViewport(contentList(index))) { - contentList(index).scrollIntoView(lastIndex > index) - } - val shown = new js.Array[dom.HTMLElement](0) - val lined = new js.Array[dom.HTMLElement](0) - /** - * Makes two passes over the children list; once to determine if - * the current element is a parent of the current header, and another - * to mark all the children of the current element with the correct - * CSS classes. - */ - def rec(curr: Tree[dom.HTMLElement]): Boolean = { - - var found = false + + import japgolly.scalajs.react._ // React + import vdom.ReactVDom._ // Scalatags → React virtual DOM + import vdom.ReactVDom.all._ // Scalatags html & css (div, h1, textarea, etc.) + + val Menu = ReactComponentB[Tree[(String, String, Int)]]("Menu") + .getInitialState(_ => 0) + .render{ (structure, _, index) => + + val contentList = { var i = 0 - var j = 0 - while (j < curr.children.length){ - val x = curr.children(j) - j+= 1 - val f = rec(x) - found |= f - if (!found) i += 1 + def rec1(current: Tree[(String, String, Int)]): Tree[(String, String, Int, Boolean)] = { + val initialI = i + i += 1 + val children = current.children.map(rec1) + + val win = i >= index && initialI < index + Tree( + (current.name._1, current.name._2, current.name._3, win), + children + ) } + def rec(current: Tree[(String, String, Int, Boolean)], + depth: Int, + classes: String): Iterator[Tag] = { - if (found || curr.name == contentList(index)){ - var j = 0 - while (j < curr.children.length){ - val x = curr.children(j) - if (found && i > 0){ - lined.push(x.name) - i -= 1 - } + val winIndex = current.children.indexWhere(_.name._4) + val (before, after) = current.children.splitAt(winIndex) - j+= 1 - shown.push(x.name) - } - lined.push(curr.name) - true - }else false + val myCls = + "menu-item" + + (if (depth <= 1) " menu-item-divided" else "") + val (name, munged, currIndex, win) = current.name + val frag = + li(paddingLeft := s"${depth * 15}px")( + a( + name, + href:="#"+munged, + cls:=myCls + ), + cls:=classes + (if (win) " pure-menu-selected" else "") + ) + val afterCls = if (!win) "hide" else "" + Iterator(frag) ++ + before.flatMap(rec(_, depth+1, "lined")) ++ + after.flatMap(rec(_, depth+1, afterCls)) + } + rec(rec1(structure), 0, "") } - rec(contentTree) - for(el <- contentList){ - if (shown.indexOf(el) != -1) removeClass(el, "hide") - else addClass(el, "hide") - if (lined.indexOf(el) == -1) removeClass(el, "lined") - else addClass(el, "lined") - } - if (lastIndex != -1) removeClass(contentList(lastIndex), "pure-menu-selected") - addClass(contentList(index), "pure-menu-selected") - } -} + val frag = div(cls:="pure-menu pure-menu-open")( + a(cls:="pure-menu-heading", href:="#")( + "Contents" + ), + ul(cls:="menu-item-list")( + contentList.drop(1).toVector + ) + ) + frag + }.build +} \ No newline at end of file diff --git a/examples/demos/build.sbt b/examples/demos/build.sbt index 9be4d4d..040f9fd 100644 --- a/examples/demos/build.sbt +++ b/examples/demos/build.sbt @@ -10,7 +10,7 @@ scalaVersion := "2.11.4" libraryDependencies += "com.github.japgolly.scalajs-react" %%% "core" % "0.5.1" -jsDependencies += "org.webjars" % "react" % "0.11.1" / "react-with-addons.js" commonJSName "React" + libraryDependencies += "com.lihaoyi" %%% "upickle" % "0.2.5" -- cgit v1.2.3