aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2017-01-16 22:23:38 +0100
committerJoris2017-01-16 22:23:38 +0100
commit36fc53409c9119ac94788d8296d6b171eb0a6500 (patch)
treed75a3f3232ed73e2c9859200f5d88995d379dc08
parente9e235a10cabc20ceaf110420d4618e83e425631 (diff)
downloadreading-36fc53409c9119ac94788d8296d6b171eb0a6500.tar.gz
reading-36fc53409c9119ac94788d8296d6b171eb0a6500.tar.bz2
reading-36fc53409c9119ac94788d8296d6b171eb0a6500.zip
Add navigation history with urls
-rw-r--r--src/main/scala/reading/Main.scala9
-rw-r--r--src/main/scala/reading/Route.scala64
-rw-r--r--src/main/scala/reading/component/Index.scala4
-rw-r--r--src/main/scala/reading/component/index/Books.scala6
-rw-r--r--src/main/scala/reading/component/index/Filters.scala7
-rw-r--r--src/main/scala/reading/component/index/FiltersMenu.scala11
-rw-r--r--src/main/scala/reading/component/index/style/Books.scala2
-rw-r--r--src/main/scala/reading/models/Filter.scala83
-rw-r--r--src/main/scala/reading/models/FilterFactory.scala67
-rw-r--r--src/main/scala/reading/models/FilterKind.scala16
-rw-r--r--src/main/scala/reading/models/Genre.scala2
-rw-r--r--src/main/scala/reading/models/Grade.scala2
-rw-r--r--src/main/scala/reading/models/Level.scala2
-rw-r--r--src/main/scala/reading/models/Period.scala2
-rw-r--r--src/main/scala/reading/models/Program.scala2
-rw-r--r--src/main/scala/reading/models/Theme.scala2
16 files changed, 194 insertions, 87 deletions
diff --git a/src/main/scala/reading/Main.scala b/src/main/scala/reading/Main.scala
index 09bd76c..41057fd 100644
--- a/src/main/scala/reading/Main.scala
+++ b/src/main/scala/reading/Main.scala
@@ -7,6 +7,7 @@ import org.scalajs.dom
import scalacss.Defaults._
import reading.component.style.{Global => GlobalStyle}
+import reading.utils.RxTag
object Main extends JSApp {
def main(): Unit = {
@@ -14,6 +15,12 @@ object Main extends JSApp {
style.appendChild(dom.document.createTextNode(GlobalStyle.render))
dom.document.head.appendChild(style)
- val _ = dom.document.body.appendChild(component.Index().render)
+ val _ = dom.document.body.appendChild(
+ RxTag { implicit context =>
+ Route.current() match {
+ case Route.Books(filters) => component.Index(filters)
+ }
+ }.render
+ )
}
}
diff --git a/src/main/scala/reading/Route.scala b/src/main/scala/reading/Route.scala
new file mode 100644
index 0000000..85d1d6b
--- /dev/null
+++ b/src/main/scala/reading/Route.scala
@@ -0,0 +1,64 @@
+package reading
+
+import org.scalajs.dom
+import scala.scalajs.js.URIUtils
+
+import rx.Var
+
+import reading.models.{Filter, FilterKind}
+
+sealed trait Route
+
+object Route {
+ case class Books(filters: Seq[Filter]) extends Route
+
+ val current: Var[Route] = Var(parse(dom.window.location.hash))
+
+ dom.window.onpopstate = (e: dom.raw.PopStateEvent) => {
+ current() = parse(dom.window.location.hash)
+ }
+
+ def parse(hash: String): Route =
+ pathAndParams(hash) match {
+ case ("books" :: Nil, params) => {
+ val filters = params.flatMap { param =>
+ param.split("=") match {
+ case Array(kind, nonFormattedName) =>
+ for {
+ kind <- FilterKind.withNameOption(kind)
+ filter <- Filter(kind, nonFormattedName)
+ } yield filter
+ case _ => None
+ }
+ }
+ Books(filters)
+ }
+ case _ =>
+ Books(Nil)
+ }
+
+ 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) => "/books" ++ (if(filters.nonEmpty) filters.map(filter => s"${filter.kind}=${filter.nonFormattedName}").mkString("?", "&", "") else "")
+ case _ => "/books"
+ }
+ dom.window.location.origin + dom.window.location.pathname + "#" + URIUtils.encodeURI(hash)
+ }
+
+ def goTo(route: Route): Unit = {
+ push(route)
+ current() = route
+ }
+
+ def push(route: Route): Unit = {
+ dom.window.history.pushState(null, "", url(route));
+ }
+}
diff --git a/src/main/scala/reading/component/Index.scala b/src/main/scala/reading/component/Index.scala
index ef8ae51..41139cc 100644
--- a/src/main/scala/reading/component/Index.scala
+++ b/src/main/scala/reading/component/Index.scala
@@ -14,8 +14,8 @@ import reading.models.{Book, Filter}
import reading.utils.RxAttr
object Index {
- def apply(): Frag = {
- val filters: Var[Seq[Filter]] = Var(Nil)
+ def apply(initialFilters: Seq[Filter]): HtmlTag = {
+ val filters: Var[Seq[Filter]] = Var(initialFilters)
val books: Rx[Seq[Book]] = Rx {
if(filters().isEmpty) Books() else Book.filter(Books(), filters())
}
diff --git a/src/main/scala/reading/component/index/Books.scala b/src/main/scala/reading/component/index/Books.scala
index 435bf50..3faa130 100644
--- a/src/main/scala/reading/component/index/Books.scala
+++ b/src/main/scala/reading/component/index/Books.scala
@@ -32,10 +32,12 @@ object Books {
),
div(
div(BooksStyle.item, s"classe : ${book.programs.map(Program.grade).distinct.sorted.mkString(", ")}"),
- div(BooksStyle.item, s"programme : ${book.programs.sorted.mkString(", ")}"),
+ div(BooksStyle.item, s"programme : ${book.programs.map(p => "« " ++ p.toString ++ " »").sorted.mkString(", ")}"),
div(BooksStyle.item, s"thème : ${book.themes.sorted.mkString(", ")}"),
div(BooksStyle.item, s"genre : ${book.genres.sorted.mkString(", ")}"),
- div(BooksStyle.item, s"période : ${book.period}")
+ book.period.map { period =>
+ div(BooksStyle.item, s"période : $period")
+ }
)
)
)
diff --git a/src/main/scala/reading/component/index/Filters.scala b/src/main/scala/reading/component/index/Filters.scala
index a53e46d..a5ea3f6 100644
--- a/src/main/scala/reading/component/index/Filters.scala
+++ b/src/main/scala/reading/component/index/Filters.scala
@@ -11,6 +11,7 @@ import reading.component.index.style.{Filters => FiltersStyle}
import reading.component.widget.Cross
import reading.component.style.Col
import reading.models.Filter
+import reading.Route
import reading.utils.{RxTag, RxAttr}
object Filters {
@@ -26,7 +27,11 @@ object Filters {
filters().sortBy(_.name).map { filter =>
div(
FiltersStyle.filter,
- RxAttr(onclick, Rx(() => filters() = Filter.remove(filters(), filter))),
+ RxAttr(onclick, Rx(() => {
+ val newFilters = Filter.remove(filters(), filter)
+ filters() = newFilters
+ Route.push(Route.Books(newFilters))
+ })),
span(FiltersStyle.name, filter.name),
Cross(15.px, Col.white)
)
diff --git a/src/main/scala/reading/component/index/FiltersMenu.scala b/src/main/scala/reading/component/index/FiltersMenu.scala
index febc52c..1015df1 100644
--- a/src/main/scala/reading/component/index/FiltersMenu.scala
+++ b/src/main/scala/reading/component/index/FiltersMenu.scala
@@ -10,6 +10,7 @@ import scalacss.ScalatagsCss._
import reading.component.index.style.{FiltersMenu => FiltersMenuStyle}
import reading.models._
import reading.utils.{RxTag, RxAttr}
+import reading.Route
object FiltersMenu {
def apply(books: Rx[Seq[Book]], filters: Var[Seq[Filter]]): Frag =
@@ -17,7 +18,7 @@ object FiltersMenu {
div(
FiltersMenuStyle.render,
FiltersMenuStyle.groups,
- filters().find(_.kind == GradeKind) match {
+ filters().find(_.kind == FilterKind.Grade) match {
case None =>
group(books, filters, "Classe", Grade.values.map(Filter.apply(_)))
case Some(grade) =>
@@ -41,7 +42,7 @@ object FiltersMenu {
val filtersWithCount = Rx {
groupFilters
.filter(filter => !Filter.contains(filters(), filter))
- .map(filter => (filter, Book.filter(books(), filter +: filters()).length))
+ .map(filter => (filter, Book.filter(books(), Seq(filter)).length))
.filter(_._2 > 0)
}
@@ -55,7 +56,11 @@ object FiltersMenu {
filtersWithCount().map { case (filter, count) =>
button(
FiltersMenuStyle.filter,
- RxAttr(onclick, Rx(() => filters() = filter +: filters())),
+ RxAttr(onclick, Rx(() => {
+ val newFilters = filter +: filters()
+ filters() = newFilters
+ Route.push(Route.Books(newFilters))
+ })),
span(s"${filter.name.capitalize} ($count)")
)
}
diff --git a/src/main/scala/reading/component/index/style/Books.scala b/src/main/scala/reading/component/index/style/Books.scala
index fc3a18f..ad7375d 100644
--- a/src/main/scala/reading/component/index/style/Books.scala
+++ b/src/main/scala/reading/component/index/style/Books.scala
@@ -8,8 +8,6 @@ object Books extends StyleSheet.Inline {
import dsl._
val books = style(
- display.flex,
- flexWrap.wrap
)
val book = style(
diff --git a/src/main/scala/reading/models/Filter.scala b/src/main/scala/reading/models/Filter.scala
index 5aabcc6..c3d81c6 100644
--- a/src/main/scala/reading/models/Filter.scala
+++ b/src/main/scala/reading/models/Filter.scala
@@ -3,89 +3,32 @@ package reading.models
trait Filter {
def filter(book: Book): Boolean
def kind: FilterKind
+ def nonFormattedName: String
def name: String
}
-sealed trait FilterKind
-case object PeriodKind extends FilterKind
-case object ThemeKind extends FilterKind
-case object GenreKind extends FilterKind
-case object LevelKind extends FilterKind
-case object ProgramKind extends FilterKind
-case object GradeKind extends FilterKind
-
object Filter {
def apply[T](in: T)(implicit filterFactory: FilterFactory[T]): Filter =
filterFactory.create(in)
+ def apply(kind: FilterKind, nonFormattedName: String): Option[Filter] =
+ kind match {
+ case FilterKind.Period => Period.withNameOption(nonFormattedName).map(apply[Period])
+ case FilterKind.Theme => Theme.withNameOption(nonFormattedName).map(apply[Theme])
+ case FilterKind.Genre => Genre.withNameOption(nonFormattedName).map(apply[Genre])
+ case FilterKind.Level => Level.withNameOption(nonFormattedName).map(apply[Level])
+ case FilterKind.Program => Program.withNameOption(nonFormattedName).map(apply[Program])
+ case FilterKind.Grade => Grade.withNameOption(nonFormattedName).map(apply[Grade])
+ }
+
def contains(filters: Seq[Filter], filter: Filter): Boolean =
- filters.find(f => f.kind == filter.kind && f.name == filter.name).nonEmpty
+ filters.find(equals(_, filter)).nonEmpty
def equals(f1: Filter, f2: Filter): Boolean =
f1.kind == f2.kind && f1.name == f2.name
def remove(fs: Seq[Filter], rf: Filter): Seq[Filter] =
fs.filterNot { f =>
- equals(f, rf) || rf.kind == GradeKind && f.kind == ProgramKind
+ equals(f, rf) || rf.kind == FilterKind.Grade && f.kind == FilterKind.Program
}
}
-
-trait FilterFactory[T] {
- def create(in: T): Filter
-}
-
-object FilterFactory {
- implicit object PeriodFilter extends FilterFactory[Period] {
- def create(period: Period): Filter =
- new Filter {
- def filter(book: Book): Boolean = book.period == Some(period)
- val kind: FilterKind = PeriodKind
- val name: String = period.toString()
- }
- }
-
- implicit object ThemeFilter extends FilterFactory[Theme] {
- def create(theme: Theme): Filter =
- new Filter {
- def filter(book: Book): Boolean = book.themes.contains(theme)
- val kind: FilterKind = ThemeKind
- val name: String = theme.toString()
- }
- }
-
- implicit object GenreFilter extends FilterFactory[Genre] {
- def create(genre: Genre): Filter =
- new Filter {
- def filter(book: Book): Boolean = book.genres.contains(genre)
- val kind: FilterKind = GenreKind
- val name: String = genre.toString()
- }
- }
-
- implicit object ProgramFilter extends FilterFactory[Program] {
- def create(program: Program): Filter =
- new Filter {
- def filter(book: Book): Boolean = book.programs.contains(program)
- val kind: FilterKind = ProgramKind
- val name: String = program.toString()
- }
- }
-
- implicit object GradeFilter extends FilterFactory[Grade] {
- def create(grade: Grade): Filter =
- new Filter {
- def filter(book: Book): Boolean = book.programs.map(Program.grade).contains(grade)
- val kind: FilterKind = GradeKind
- val name: String = grade.toString()
- }
- }
-
- implicit object LevelFilter extends FilterFactory[Level] {
- def create(level: Level): Filter =
- new Filter {
- def filter(book: Book): Boolean = book.level == level
- val kind: FilterKind = LevelKind
- val name: String = level.toString()
- }
- }
-}
diff --git a/src/main/scala/reading/models/FilterFactory.scala b/src/main/scala/reading/models/FilterFactory.scala
new file mode 100644
index 0000000..269af82
--- /dev/null
+++ b/src/main/scala/reading/models/FilterFactory.scala
@@ -0,0 +1,67 @@
+package reading.models
+
+trait FilterFactory[T] {
+ def create(in: T): Filter
+}
+
+object FilterFactory {
+ implicit object PeriodFilter extends FilterFactory[Period] {
+ def create(period: Period): Filter =
+ new Filter {
+ def filter(book: Book): Boolean = book.period == Some(period)
+ val kind: FilterKind = FilterKind.Period
+ val nonFormattedName: String = period.toString()
+ val name: String = period.prettyPrint()
+ }
+ }
+
+ implicit object ThemeFilter extends FilterFactory[Theme] {
+ def create(theme: Theme): Filter =
+ new Filter {
+ def filter(book: Book): Boolean = book.themes.contains(theme)
+ val kind: FilterKind = FilterKind.Theme
+ val nonFormattedName: String = theme.toString()
+ val name: String = theme.prettyPrint()
+ }
+ }
+
+ implicit object GenreFilter extends FilterFactory[Genre] {
+ def create(genre: Genre): Filter =
+ new Filter {
+ def filter(book: Book): Boolean = book.genres.contains(genre)
+ val kind: FilterKind = FilterKind.Genre
+ val nonFormattedName: String = genre.toString()
+ val name: String = genre.prettyPrint()
+ }
+ }
+
+ implicit object ProgramFilter extends FilterFactory[Program] {
+ def create(program: Program): Filter =
+ new Filter {
+ def filter(book: Book): Boolean = book.programs.contains(program)
+ val kind: FilterKind = FilterKind.Program
+ val nonFormattedName: String = program.toString()
+ val name: String = program.prettyPrint()
+ }
+ }
+
+ implicit object GradeFilter extends FilterFactory[Grade] {
+ def create(grade: Grade): Filter =
+ new Filter {
+ def filter(book: Book): Boolean = book.programs.map(Program.grade).contains(grade)
+ val kind: FilterKind = FilterKind.Grade
+ val nonFormattedName: String = grade.toString()
+ val name: String = grade.prettyPrint()
+ }
+ }
+
+ implicit object LevelFilter extends FilterFactory[Level] {
+ def create(level: Level): Filter =
+ new Filter {
+ def filter(book: Book): Boolean = book.level == level
+ val kind: FilterKind = FilterKind.Level
+ val nonFormattedName: String = level.toString()
+ val name: String = level.prettyPrint()
+ }
+ }
+}
diff --git a/src/main/scala/reading/models/FilterKind.scala b/src/main/scala/reading/models/FilterKind.scala
new file mode 100644
index 0000000..ba63f15
--- /dev/null
+++ b/src/main/scala/reading/models/FilterKind.scala
@@ -0,0 +1,16 @@
+package reading.models
+
+import enumeratum._
+
+sealed trait FilterKind extends EnumEntry
+
+object FilterKind extends Enum[FilterKind] {
+ val values = findValues
+
+ case object Period extends FilterKind
+ case object Theme extends FilterKind
+ case object Genre extends FilterKind
+ case object Level extends FilterKind
+ case object Program extends FilterKind
+ case object Grade extends FilterKind
+}
diff --git a/src/main/scala/reading/models/Genre.scala b/src/main/scala/reading/models/Genre.scala
index 3fa13bc..51d2394 100644
--- a/src/main/scala/reading/models/Genre.scala
+++ b/src/main/scala/reading/models/Genre.scala
@@ -9,7 +9,7 @@ sealed trait Genre extends EnumEntry with Ordered[Genre] {
values.indexOf(that) - values.indexOf(this)
}
- override def toString(): String = this match {
+ def prettyPrint(): String = this match {
case JournalIntime => "journal intime"
case RomanHistorique => "roman historique"
case Policier => "policier"
diff --git a/src/main/scala/reading/models/Grade.scala b/src/main/scala/reading/models/Grade.scala
index e41d1a8..c48234b 100644
--- a/src/main/scala/reading/models/Grade.scala
+++ b/src/main/scala/reading/models/Grade.scala
@@ -9,7 +9,7 @@ sealed trait Grade extends EnumEntry with Ordered[Grade] {
values.indexOf(that) - values.indexOf(this)
}
- override def toString(): String = this match {
+ def prettyPrint(): String = this match {
case Sixieme => "6ème"
case Cinquieme => "5ème"
case Quatrieme => "4ème"
diff --git a/src/main/scala/reading/models/Level.scala b/src/main/scala/reading/models/Level.scala
index ebec020..c06776e 100644
--- a/src/main/scala/reading/models/Level.scala
+++ b/src/main/scala/reading/models/Level.scala
@@ -9,7 +9,7 @@ sealed trait Level extends EnumEntry with Ordered[Level] {
values.indexOf(that) - values.indexOf(this)
}
- override def toString(): String = this match {
+ def prettyPrint(): String = this match {
case Facile => "facile"
case Moyen => "moyen"
case Difficile => "difficile"
diff --git a/src/main/scala/reading/models/Period.scala b/src/main/scala/reading/models/Period.scala
index 8500591..f16bde3 100644
--- a/src/main/scala/reading/models/Period.scala
+++ b/src/main/scala/reading/models/Period.scala
@@ -5,7 +5,7 @@ import enumeratum._
sealed trait Period extends EnumEntry {
import Period._
- override def toString(): String = this match {
+ def prettyPrint(): String = this match {
case Louis14 => "Louis XIV"
case Siecle19 => "19ème siècle"
case Siecle20 => "20ème siècle"
diff --git a/src/main/scala/reading/models/Program.scala b/src/main/scala/reading/models/Program.scala
index 00e2b3b..da07653 100644
--- a/src/main/scala/reading/models/Program.scala
+++ b/src/main/scala/reading/models/Program.scala
@@ -9,7 +9,7 @@ sealed trait Program extends EnumEntry with Ordered[Program] {
values.indexOf(that) - values.indexOf(this)
}
- override def toString(): String = this match {
+ def prettyPrint(): String = this match {
case Monstre => "Le monstre, aux limites de l'humain"
case RecitAventure => "Récits d'aventures"
case CreationPoetique => "Récit de création, création poétique"
diff --git a/src/main/scala/reading/models/Theme.scala b/src/main/scala/reading/models/Theme.scala
index be8e5a9..5905d49 100644
--- a/src/main/scala/reading/models/Theme.scala
+++ b/src/main/scala/reading/models/Theme.scala
@@ -9,7 +9,7 @@ sealed trait Theme extends EnumEntry with Ordered[Theme] {
values.indexOf(that) - values.indexOf(this)
}
- override def toString(): String = this match {
+ def prettyPrint(): String = this match {
case Amitie => "amitié"
case Aventure => "aventure"
case Americain => "américain"