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 import org.scalajs.dom.extensions._ import scala.collection.mutable import scalatags.JsDom.all._ case class Tree[T](name: T, children: Vector[Tree[T]]) @JSExport object Controller{ def munge(name: String) = { name.replace(" ", "") } def addClass(el: dom.HTMLElement, cls: String) = { removeClass(el, cls) el.className = el.className + " " + cls } def removeClass(el: dom.HTMLElement, cls: String) = { el.className = el.className.split(' ').filter(_ != cls).mkString(" ") } def toggleClass(el: dom.HTMLElement, cls: String) = { val frags = el.className.split(' ') if (!frags.contains(cls)) addClass(el, cls) else removeClass(el, cls) } @JSExport 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" ).map(dom.document.getElementById) val snippets = dom.document.getElementsByClassName("highlight-me") snippets.foreach(js.Dynamic.global.hljs.highlightBlock(_)) val headers = { def offset(el: dom.HTMLElement, parent: dom.HTMLElement): Double = { if (el == parent) 0 else el.offsetTop + offset(el.offsetParent.asInstanceOf[dom.HTMLElement], parent) } val menuItems = { def rec(current: Tree[String]): Seq[String] = { current.name +: current.children.flatMap(rec) } rec(structure).tail } menuItems.map(munge) .map(dom.document.getElementById) .map(offset(_, main)) .toVector } val menuBar = React.renderComponent( Menu(recurse(structure)), menu ) menuLink.onclick = (e: dom.MouseEvent) => { toggleClass(layout, "active") toggleClass(menu, "active") toggleClass(menuLink, "active") } var scrolling = false main.onscroll = (e: dom.UIEvent) => { if (!scrolling){ scrolling = true 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) } } } } def isElementInViewport(el: dom.HTMLElement) = { val rect = el.getBoundingClientRect() rect.top >= 0 && rect.bottom <= dom.innerHeight } 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 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] = { val winIndex = current.children.indexWhere(_.name._4) val (before, after) = current.children.splitAt(winIndex) 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, "") } 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 }