diff options
Diffstat (limited to 'src/main/scala')
| -rw-r--r-- | src/main/scala/reading/Main.scala | 9 | ||||
| -rw-r--r-- | src/main/scala/reading/Route.scala | 64 | ||||
| -rw-r--r-- | src/main/scala/reading/component/Index.scala | 4 | ||||
| -rw-r--r-- | src/main/scala/reading/component/index/Books.scala | 6 | ||||
| -rw-r--r-- | src/main/scala/reading/component/index/Filters.scala | 7 | ||||
| -rw-r--r-- | src/main/scala/reading/component/index/FiltersMenu.scala | 11 | ||||
| -rw-r--r-- | src/main/scala/reading/component/index/style/Books.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/reading/models/Filter.scala | 83 | ||||
| -rw-r--r-- | src/main/scala/reading/models/FilterFactory.scala | 67 | ||||
| -rw-r--r-- | src/main/scala/reading/models/FilterKind.scala | 16 | ||||
| -rw-r--r-- | src/main/scala/reading/models/Genre.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/reading/models/Grade.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/reading/models/Level.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/reading/models/Period.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/reading/models/Program.scala | 2 | ||||
| -rw-r--r-- | src/main/scala/reading/models/Theme.scala | 2 | 
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" | 
