package reading import org.scalajs.dom.{ window, MouseEvent } import org.scalajs.dom.raw.PopStateEvent import scala.scalajs.js.URIUtils import rx.Var import reading.models.{ Filter, FilterKind, Book, Books => BooksModel } sealed trait Route object Route { case class Books(filters: Set[Filter] = Set(), detail: Option[Book] = None) extends Route val current: Var[Route] = Var(parse(window.location.hash)) window.onpopstate = (e: PopStateEvent) => { current() = parse(window.location.hash) } // Prevent page changes when clicking with the mouse left button window.onclick = { (event: MouseEvent) => if (event.button == 0) event.preventDefault() } def parse(hash: String): Route = pathAndParams(hash) match { case ("books" :: Nil, params) => { val filters = params.flatMap { case Param(key, value) => for { kind <- FilterKind.withNameOption(key) filter <- Filter(kind, value) } yield filter case _ => None }.toSet val detail = params.collectFirst { case Param("detail", title) => BooksModel().find(_.title == title) }.flatten Books(filters, detail) } case _ => Books() } def pathAndParams(hash: String): (List[String], List[String]) = { def splitPath(path: String) = path.split("/").drop(1).toList URIUtils.decodeURI(hash.drop(1)).split('?') match { case Array(path) => (splitPath(path), Nil) case Array(path, params) => (splitPath(path), params.split("&").toList) } } def url(route: Route): String = { val hash = route match { case Books(filters, detail) => { val filterParams = filters.toSeq.map(filter => (filter.kind.toString, filter.nonFormattedName)) val detailParams = detail.map(book => ("detail", book.title)) s"/books${Param.format(filterParams ++ detailParams)}" } case _ => "/books" } window.location.origin + window.location.pathname + "#" + URIUtils.encodeURI(hash) } def goTo(route: Route): Unit = { push(route) current() = route } def push(route: Route): Unit = window.history.pushState(null, "", url(route)); } object Param { def apply(key: String, value: String): (String, String) = (key, value) def unapply(x: Any): Option[(String, String)] = x match { case str: String => str.split("=") match { case Array(key, value) => Some((key, value)) case _ => None } case _ => None } def format(params: Seq[(String, String)]): String = if (params.isEmpty) "" else params .map { case (key, value) => s"$key=$value" } .mkString("?", "&", "") }