diff options
| author | Joris | 2017-02-25 22:25:03 +0100 | 
|---|---|---|
| committer | Joris | 2017-02-25 22:25:03 +0100 | 
| commit | bbe5788cdcfbb26358566bfc74426ec38029cc73 (patch) | |
| tree | 27607f6b5a667c264f11aeb10708d6d1dadb0fde /src/main | |
| parent | f1de0dd7632eb29a40ea1f5cf136ab43ee945926 (diff) | |
Add detailed book page instead of a modal.
Diffstat (limited to 'src/main')
31 files changed, 729 insertions, 456 deletions
| diff --git a/src/main/scala/reading/Main.scala b/src/main/scala/reading/Main.scala index a9fc3e2..b97abd2 100644 --- a/src/main/scala/reading/Main.scala +++ b/src/main/scala/reading/Main.scala @@ -18,7 +18,7 @@ object Main extends JSApp {      val _ = dom.document.body.appendChild {        Rx {          Route.current() match { -          case Route.Books(filters) => component.Index(filters) +          case Route.Books(filters, detail) => component.Index(filters, detail)          }        }.render      } diff --git a/src/main/scala/reading/Route.scala b/src/main/scala/reading/Route.scala index 9295d49..76a9431 100644 --- a/src/main/scala/reading/Route.scala +++ b/src/main/scala/reading/Route.scala @@ -6,12 +6,12 @@ import scala.scalajs.js.URIUtils  import rx.Var -import reading.models.{ Filter, FilterKind } +import reading.models.{ Filter, FilterKind, Book, Books => BooksModel }  sealed trait Route  object Route { -  case class Books(filters: Seq[Filter]) extends Route +  case class Books(filters: Seq[Filter] = Nil, detail: Option[Book] = None) extends Route    val current: Var[Route] = Var(parse(window.location.hash)) @@ -22,20 +22,22 @@ object Route {    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 -          } +        val filters = params.flatMap { +          case Param(key, value) => +            for { +              kind <- FilterKind.withNameOption(key) +              filter <- Filter(kind, value) +            } yield filter +          case _ => +            None          } -        Books(filters) +        val detail = params.collectFirst { +          case Param("detail", title) => BooksModel().find(_.title == title) +        }.flatten +        Books(filters, detail)        }        case _ => -        Books(Nil) +        Books()      }    def pathAndParams(hash: String): (List[String], List[String]) = { @@ -48,7 +50,11 @@ object Route {    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(filters, detail) => { +        val filterParams = filters.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) @@ -63,3 +69,24 @@ object Route {      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("?", "&", "") +} diff --git a/src/main/scala/reading/component/Index.scala b/src/main/scala/reading/component/Index.scala index 78890de..0105150 100644 --- a/src/main/scala/reading/component/Index.scala +++ b/src/main/scala/reading/component/Index.scala @@ -5,26 +5,25 @@ import scalacss.Defaults._  import scalacss.ScalatagsCss._  import scalatags.JsDom.all._ -import reading.component.index.{ Menu, Header, Books => BooksComponent } +import reading.component.index.{ Menu, Books => BooksComponent }  import reading.component.style.{ Index => IndexStyle }  import reading.models.{ Book, Books, Filter }  object Index { -  def apply(initialFilters: Seq[Filter])(implicit ctx: Ctx.Owner): Frag = { +  def apply(initialFilters: Seq[Filter], initialDetail: Option[Book])(implicit ctx: Ctx.Owner): Frag = {      val filters: Var[Seq[Filter]] = Var(initialFilters)      val books: Rx[Seq[Book]] = Rx(Filter.add(Books(), filters()))      val search: Var[String] = Var("")      val showFiltersMenu: Var[Boolean] = Var(false) -    val searchedBooks: Rx[Seq[Book]] = Rx(Book.filter(books(), search())) +    val detail: Var[Option[Book]] = Var(initialDetail)      div(        IndexStyle.render,        IndexStyle.page, -      Menu(books, filters, search, showFiltersMenu), +      Menu(books, filters, detail, search, showFiltersMenu),        div(          IndexStyle.main, -        Header(searchedBooks, filters, search, showFiltersMenu), -        BooksComponent(searchedBooks) +        BooksComponent(books, filters, detail, search, showFiltersMenu)        )      )    } diff --git a/src/main/scala/reading/component/index/BookDetail.scala b/src/main/scala/reading/component/index/BookDetail.scala index c42029f..ed91211 100644 --- a/src/main/scala/reading/component/index/BookDetail.scala +++ b/src/main/scala/reading/component/index/BookDetail.scala @@ -1,17 +1,28 @@  package reading.component.index -import scalatags.JsDom.all._ +import scala.util.Random +  import scalacss.Defaults._  import scalacss.ScalatagsCss._ +import scalatags.JsDom.all._  import reading.component.index.style.{ BookDetail => BookStyle } -import reading.models.{ Program, Book } +import reading.component.widget.AnimateMethod +import reading.models.{ Book, Program }  object BookDetail { -  def apply(book: Book): Frag = +  val componentId = s"books${Random.nextInt}" + +  def apply(book: Book, parentId: String, onClose: => Unit): Frag = { +    val titleParts = if (book.parts > 1) s", ${book.parts} volumes" else "" +    val grades = book.programs.map(Program.grade(_)).distinct.sorted + +    AnimateMethod.fadeIn(componentId) +      div(        BookStyle.render,        BookStyle.detail, +      id := componentId,        img(          BookStyle.cover, @@ -20,36 +31,48 @@ object BookDetail {        ),        div( -        BookStyle.items, +        BookStyle.presentation, +        div(BookStyle.title, s"${book.title}$titleParts"), +        div(BookStyle.author, book.author), -        if (book.programs.nonEmpty) { -          item("classe", book.programs.map(Program.grade(_).prettyPrint).distinct.sorted) -        }, -        if (book.programs.nonEmpty) { -          item("programme", book.programs.map(p => "« " ++ p.prettyPrint ++ " »").sorted) -        }, -        if (book.themes.nonEmpty) { -          item("thème", book.themes.sorted.map(_.prettyPrint)) +        book.resume match { +          case Some(resume) => +            p(BookStyle.resume, raw(resume)) +          case _ => +            span("")          }, -        if (book.genres.nonEmpty) { -          item("genre", book.genres.sorted.map(_.prettyPrint)) -        }, -        book.period.map { period => -          item("période", period.prettyPrint) -        }, -        item("niveau", book.level.prettyPrint) -      ) -    ) -  private def item(key: String, value: String): Frag = item(key, Seq(value)) +        dl( +          BookStyle.definitions, -  private def item(key: String, values: Seq[String]): Frag = -    div( -      BookStyle.item, -      div(BookStyle.itemName, key), -      ul( -        BookStyle.itemValues, -        values.map(value => li(BookStyle.itemValue, value.capitalize)) +          grades.map { grade => +            val programs = book.programs.filter(p => Program.grade(p) == grade).sorted +            val pp = grade.prettyPrint +            definition(pp, pp, programs.map(p => s"« ${p.prettyPrint} »")) +          }, +          if (book.themes.nonEmpty) { +            definition("thème", "thèmes", book.themes.sorted.map(_.prettyPrint)) +          }, +          if (book.genres.nonEmpty) { +            definition("genre", "genres", book.genres.sorted.map(_.prettyPrint)) +          }, +          definition("niveau", "niveaux", Seq(book.level.prettyPrint)) +        ), + +        button( +          BookStyle.close, +          onclick := (() => onClose), +          "Fermer" +        )        )      ) +  } + +  private def definition(key: String, pluralKey: String, values: Seq[String]): Seq[Frag] = { +    val term = if (values.length > 1) pluralKey else key +    Seq( +      dt(BookStyle.definitionTerm, s"${term.capitalize} :"), +      dd(BookStyle.definitionDescription, values.mkString(", ")) +    ) +  }  } diff --git a/src/main/scala/reading/component/index/Books.scala b/src/main/scala/reading/component/index/Books.scala index c22639f..b5e172b 100644 --- a/src/main/scala/reading/component/index/Books.scala +++ b/src/main/scala/reading/component/index/Books.scala @@ -1,49 +1,89 @@  package reading.component.index -import rx._ +import scala.util.Random -import scalatags.JsDom.all._ +import rx._  import scalacss.Defaults._  import scalacss.ScalatagsCss._ +import scalatags.JsDom.all._  import reading.component.index.style.{ Books => BooksStyle } -import reading.component.widget.Modal -import reading.models.{ Book } +import reading.component.widget.AnimateMethod +import reading.models.{ Book, Filter } +import reading.Route  import reading.utils.RxUtils._  object Books { -  def apply(books: Rx[Seq[Book]])(implicit ctx: Ctx.Owner): Frag = { -    val focus: Var[Option[Book]] = Var(None) +  val componentId = s"books${Random.nextInt}" + +  def apply( +    books: Rx[Seq[Book]], +    filters: Var[Seq[Filter]], +    detail: Var[Option[Book]], +    search: Var[String], +    showFiltersMenu: Var[Boolean] +  )( +    implicit +    ctx: Ctx.Owner +  ): Frag = { +    val searchedBooks: Rx[Seq[Book]] = Rx(Book.filter(books(), search())) + +    if (detail.now.isEmpty) AnimateMethod.fadeIn(id = componentId)      div( -      BooksStyle.render, +      BooksStyle.booksParent, + +      div( +        id := componentId, +        BooksStyle.render, +        BooksStyle.books, + +        Header(searchedBooks, filters, detail, search, showFiltersMenu), -      Rx {          div( -          div( -            BooksStyle.books, - -            books().sorted.map { book => -              div( -                BooksStyle.book, -                img( -                  BooksStyle.cover, -                  src := s"cover/${book.title}.jpg", -                  alt := s"${book.title}, ${book.author}", -                  onclick := (() => focus() = Some(book)) -                ) -              ) -            } -          ), +          BooksStyle.listParent,            Rx { -            focus() match { -              case Some(book) => Modal(onClose = focus() = None)(BookDetail(book)) -              case None => span("") -            } +            div( +              BooksStyle.list, + +              searchedBooks().sorted.map { book => +                div( +                  BooksStyle.book, +                  img( +                    BooksStyle.cover, +                    src := s"cover/${book.title}.jpg", +                    alt := s"${book.title}, ${book.author}", +                    onclick := (() => { +                      Route.push(Route.Books(filters.now, Some(book))) +                      AnimateMethod.fadeOut( +                        id = componentId, +                        onEnd = detail() = Some(book) +                      ) +                    }) +                  ) +                ) +              } +            )            }          ) +      ), + +      Rx { +        detail() match { +          case Some(book) => +            BookDetail(book, componentId, onClose = closeDetail(filters, detail)) +          case None => +            span("") +        }        }      )    } + +  def closeDetail(filters: Var[Seq[Filter]], detail: Var[Option[Book]]): Unit = +    AnimateMethod.fadeOut(BookDetail.componentId, onEnd = { +      detail() = None +      Route.push(Route.Books(filters.now, None)) +      AnimateMethod.fadeIn(componentId) +    })  } diff --git a/src/main/scala/reading/component/index/FilterUtils.scala b/src/main/scala/reading/component/index/FilterUtils.scala index d4b24e4..89f993a 100644 --- a/src/main/scala/reading/component/index/FilterUtils.scala +++ b/src/main/scala/reading/component/index/FilterUtils.scala @@ -8,31 +8,37 @@ import reading.Route  object FilterUtils {    def remove(      filters: Var[Seq[Filter]], +    detail: Var[Option[Book]],      search: Var[String],      filter: Filter    ): Unit = {      val newFilters = Filter.remove(filters.now, filter)      filters() = newFilters +    if (detail.now.nonEmpty) Books.closeDetail(filters, detail)      search() = ""      Route.push(Route.Books(newFilters))    }    def removeAll(      filters: Var[Seq[Filter]], +    detail: Var[Option[Book]],      search: Var[String]    ): Unit = {      filters() = Nil +    if (detail.now.nonEmpty) Books.closeDetail(filters, detail)      search() = ""      Route.push(Route.Books(Nil))    }    def add(      filters: Var[Seq[Filter]], +    detail: Var[Option[Book]],      search: Var[String],      filter: Filter    ): Unit = {      val newFilters = filter +: filters.now      filters() = newFilters +    if (detail.now.nonEmpty) Books.closeDetail(filters, detail)      search() = ""      Route.push(Route.Books(newFilters))    } diff --git a/src/main/scala/reading/component/index/Filters.scala b/src/main/scala/reading/component/index/Filters.scala new file mode 100644 index 0000000..3aa26e8 --- /dev/null +++ b/src/main/scala/reading/component/index/Filters.scala @@ -0,0 +1,65 @@ +package reading.component.index + +import rx._ + +import scalatags.JsDom.all._ +import scalacss.Defaults._ +import scalacss.ScalatagsCss._ + +import reading.component.index.style.{ Filters => FiltersStyle } +import reading.component.widget.Cross +import reading.component.style.{ Color => C } +import reading.models.{ Filter, Book } +import reading.utils.RxUtils._ + +object Filters { +  def apply( +    filters: Var[Seq[Filter]], +    detail: Var[Option[Book]], +    search: Var[String], +    showFiltersMenu: Var[Boolean] +  )( +    implicit +    ctx: Ctx.Owner +  ): Frag = { +    val filtersCount: Rx[Int] = Rx(filters().length) + +    div( +      FiltersStyle.render, +      FiltersStyle.filters, + +      Rx { +        div( +          div( +            FiltersStyle.showFiltersMenu, +            onclick := (() => showFiltersMenu() = true), +            "Filtrer", +            if (filtersCount() > 0) span(FiltersStyle.filtersCount, filtersCount()) else span("") +          ), + +          if (filters().isEmpty) +            span("") +          else +            div( +              FiltersStyle.values, + +              div( +                FiltersStyle.clear, +                onclick := (() => FilterUtils.removeAll(filters, detail, search)), +                "Effacer les filtres" +              ), + +              filters().sortBy(_.name).map { filter => +                div( +                  FiltersStyle.filter, +                  onclick := (() => FilterUtils.remove(filters, detail, search, filter)), +                  span(FiltersStyle.name, filter.name.capitalize), +                  Cross(15.px, C.gray.value) +                ) +              } +            ) +        ) +      } +    ) +  } +} diff --git a/src/main/scala/reading/component/index/Header.scala b/src/main/scala/reading/component/index/Header.scala index 50d520e..0809b0c 100644 --- a/src/main/scala/reading/component/index/Header.scala +++ b/src/main/scala/reading/component/index/Header.scala @@ -1,14 +1,12 @@  package reading.component.index  import rx._ - -import scalatags.JsDom.all._  import scalacss.Defaults._  import scalacss.ScalatagsCss._ +import scalatags.JsDom.all._  import reading.component.index.style.{ Header => HeaderStyle } -import reading.component.widget.{ Cross, Input } -import reading.component.style.{ Color => C } +import reading.component.widget.Input  import reading.models.{ Book, Filter }  import reading.utils.RxUtils._ @@ -16,58 +14,27 @@ object Header {    def apply(      books: Rx[Seq[Book]],      filters: Var[Seq[Filter]], +    detail: Var[Option[Book]],      search: Var[String],      showFiltersMenu: Var[Boolean]    )(      implicit      ctx: Ctx.Owner    ): Frag = { -    val filtersCount: Rx[Int] = Rx(filters().length)      val booksCount: Rx[Int] = books.map(_.length)      div(        HeaderStyle.render,        HeaderStyle.header, -      Rx { -        div( -          div( -            HeaderStyle.showFiltersMenu, -            onclick := (() => showFiltersMenu() = true), -            "Filtrer", -            if (filtersCount() > 0) span(HeaderStyle.filtersCount, filtersCount()) else span("") -          ), - -          if (filters().isEmpty) -            span("") -          else -            div( -              HeaderStyle.filters, - -              div( -                HeaderStyle.clear, -                onclick := (() => FilterUtils.removeAll(filters, search)), -                "Effacer les filtres" -              ), - -              filters().sortBy(_.name).map { filter => -                div( -                  HeaderStyle.filter, -                  onclick := (() => FilterUtils.remove(filters, search, filter)), -                  span(HeaderStyle.name, filter.name.capitalize), -                  Cross(15.px, C.black.value) -                ) -              } -            ) -        ) -      }, +      Filters(filters, detail, search, showFiltersMenu),        div(          HeaderStyle.searchAndCount, -        Input(HeaderStyle.search, search, "Rechercher"), +        Input(HeaderStyle.search, search, "Rechercher", maxLength = Some(25)),          Rx {            div( -            HeaderStyle.booksCount, +            HeaderStyle.count,              span(s"${booksCount()} livre${if (booksCount() > 1) "s" else ""}")            )          } diff --git a/src/main/scala/reading/component/index/Menu.scala b/src/main/scala/reading/component/index/Menu.scala index 4c118bd..cfeb6d4 100644 --- a/src/main/scala/reading/component/index/Menu.scala +++ b/src/main/scala/reading/component/index/Menu.scala @@ -2,9 +2,9 @@ package reading.component.index  import rx._ -import scalatags.JsDom.all._  import scalacss.Defaults._  import scalacss.ScalatagsCss._ +import scalatags.JsDom.all._  import reading.component.index.style.{ Menu => MenuStyle }  import reading.models._ @@ -14,6 +14,7 @@ object Menu {    def apply(      books: Rx[Seq[Book]],      filters: Var[Seq[Filter]], +    detail: Var[Option[Book]],      search: Var[String],      showFiltersMenu: Var[Boolean]    )( @@ -25,40 +26,34 @@ object Menu {        Rx(if (showFiltersMenu()) MenuStyle.show else MenuStyle.empty),        MenuStyle.menu, -      div(MenuStyle.background), +      Rx(header(showFiltersMenu, filters().length)),        div( -        MenuStyle.content, - -        Rx(header(showFiltersMenu, filters().length)), - -        div( -          MenuStyle.groups, -          Rx { -            filters().find(_.kind == FilterKind.Grade) match { -              case Some(grade) => -                val programs = Program.values.filter(p => Program.grade(p).toString() == grade.nonFormattedName) -                group(books, filters, search, grade.name, programs.map(Filter.apply(_)), Some(grade)) -              case None => -                group(books, filters, search, "Classe", Grade.values.map(Filter.apply(_))) -            } -          }, -          Rx { -            filters().find(_.kind == FilterKind.GroupedTheme) match { -              case Some(groupedTheme) => -                val themes = Theme.values.filter(t => Theme.groupedTheme(t).toString() == groupedTheme.nonFormattedName) -                group(books, filters, search, groupedTheme.name, themes.map(Filter.apply(_)), Some(groupedTheme)) -              case None => -                group(books, filters, search, "Theme", GroupedTheme.values.map(Filter.apply(_))) -            } -          }, -          group(books, filters, search, "Genre", Genre.values.sorted.map(Filter.apply(_))), -          group(books, filters, search, "Niveau", Level.values.map(Filter.apply(_))), -          group(books, filters, search, "Période", Period.values.map(Filter.apply(_))) -        ), +        MenuStyle.groups, +        Rx { +          filters().find(_.kind == FilterKind.Grade) match { +            case Some(grade) => +              val programs = Program.values.filter(p => Program.grade(p).toString() == grade.nonFormattedName).sorted +              group(books, filters, detail, search, grade.name, grade.name, programs.map(Filter.apply(_)), Some(grade)) +            case None => +              group(books, filters, detail, search, "classe", "classes", Grade.values.sorted.map(Filter.apply(_))) +          } +        }, +        Rx { +          filters().find(_.kind == FilterKind.GroupedTheme) match { +            case Some(groupedTheme) => +              val themes = Theme.values.filter(t => Theme.grouped(t).toString() == groupedTheme.nonFormattedName).sorted +              group(books, filters, detail, search, groupedTheme.name, groupedTheme.name, themes.map(Filter.apply(_)), Some(groupedTheme)) +            case None => +              group(books, filters, detail, search, "thème", "thèmes", GroupedTheme.values.sorted.map(Filter.apply(_))) +          } +        }, +        group(books, filters, detail, search, "genre", "genres", Genre.values.sorted.map(Filter.apply(_))), +        group(books, filters, detail, search, "niveau", "niveaux", Level.values.sorted.map(Filter.apply(_))), +        group(books, filters, detail, search, "période", "périodes", Period.values.sorted.map(Filter.apply(_))) +      ), -        footer(books, filters, search, showFiltersMenu) -      ) +      footer(books, filters, detail, search, showFiltersMenu)      )    def header(showFiltersMenu: Var[Boolean], count: Int): Frag = @@ -71,8 +66,10 @@ object Menu {    def group(      books: Rx[Seq[Book]],      filters: Var[Seq[Filter]], +    detail: Var[Option[Book]],      search: Var[String],      name: String, +    pluralName: String,      groupFilters: Seq[Filter],      parentFilter: Option[Filter] = None    )( @@ -84,19 +81,20 @@ object Menu {          .map(filter => (filter, Filter.add(books(), filter).length))          .filter(_._2 > 0)      } +    val filtersCount = filtersWithCount.map(_.length)      Rx { -      if (filtersWithCount().isEmpty) +      if (filtersCount() == 0)          span("")        else          div(            div(              MenuStyle.filterTitle,              parentFilter.map { filter => -              onclick := (() => FilterUtils.remove(filters, search, filter)) +              onclick := (() => FilterUtils.remove(filters, detail, search, filter))              }.getOrElse(""),              if (parentFilter.isDefined) MenuStyle.activeFilter else "", -            name, +            if (filtersCount() > 1) pluralName else name,              Rx {                val count = filters().filter(f => groupFilters.exists(f == _)).length                if (count > 0) span(MenuStyle.filterTitleCount, count) else span("") @@ -112,9 +110,9 @@ object Menu {                    if (isActive) MenuStyle.activeFilter else "",                    onclick := (() =>                      if (isActive) -                      FilterUtils.remove(filters, search, filter) +                      FilterUtils.remove(filters, detail, search, filter)                      else -                      FilterUtils.add(filters, search, filter)), +                      FilterUtils.add(filters, detail, search, filter)),                    span(                      span(filter.name.capitalize),                      span(MenuStyle.filterCount, count) @@ -130,26 +128,32 @@ object Menu {    def footer(      books: Rx[Seq[Book]],      filters: Var[Seq[Filter]], +    detail: Var[Option[Book]],      search: Var[String],      showFiltersMenu: Var[Boolean]    )(      implicit      ctx: Ctx.Owner    ): Frag = -    div( -      MenuStyle.footer, -      div( -        MenuStyle.clear, -        onclick := (() => FilterUtils.removeAll(filters, search)), -        "Effacer" -      ), +    Rx {        div( -        MenuStyle.returnToBooks, -        onclick := (() => showFiltersMenu() = false), -        "Afficher", -        Rx { +        MenuStyle.footer, + +        if (filters().nonEmpty) +          div( +          MenuStyle.clear, +          onclick := (() => if (filters.now.nonEmpty) FilterUtils.removeAll(filters, detail, search)), +          "Effacer" +        ) +        else +          span(""), + +        div( +          MenuStyle.returnToBooks, +          onclick := (() => showFiltersMenu() = false), +          "Afficher",            span(MenuStyle.bookCount, books().length) -        } +        )        ) -    ) +    }  } diff --git a/src/main/scala/reading/component/index/style/BookDetail.scala b/src/main/scala/reading/component/index/style/BookDetail.scala index f432fda..2488a8f 100644 --- a/src/main/scala/reading/component/index/style/BookDetail.scala +++ b/src/main/scala/reading/component/index/style/BookDetail.scala @@ -2,15 +2,25 @@ package reading.component.index.style  import scalacss.Defaults._ -import reading.component.style.{ Color => C } +import reading.component.style.{ Color => C, Button } +import reading.Media  object BookDetail extends StyleSheet.Inline {    import dsl._    val detail = style( +    position.fixed, +    height(100.%%), +    top(0.px), +    right(0.px), +    padding(30.px, 30.px, 0.px, 30.px), +    overflowY.scroll,      display.flex,      flexWrap.wrap, -    justifyContent.spaceAround +    justifyContent.spaceAround, +    Media.desktop(width :=! "calc(100% - 280px)"), +    Media.mobile(width(100.%%)), +    opacity(0)    )    val cover = style( @@ -18,32 +28,47 @@ object BookDetail extends StyleSheet.Inline {      marginBottom(30.px)    ) -  val items = style( -    marginBottom(25.px) +  val presentation = style( +    Media.desktop(width(50.%%))    ) -  val item = style( -    lineHeight(25.px), -    margin(0.px, 15.px, 15.px), -    &.lastChild(marginBottom(0.px)) +  val title = style( +    color(C.congoBrown.value), +    fontSize(26.px), +    fontWeight.bold, +    marginBottom(1.em), +    lineHeight(1.4.em) +  ) + +  val author = style( +    fontSize(20.px), +    marginBottom(1.em) +  ) + +  val resume = style( +    textAlign.justify, +    lineHeight(1.4.em), +    marginBottom(2.em) +  ) + +  val definitions = style( +    marginBottom(2.5.em)    ) -  val itemName = style( +  val definitionTerm = style(      fontWeight.bold, -    textTransform.uppercase, -    marginBottom(10.px) +    float.left, +    marginRight(5.px), +    lineHeight(1.4.em)    ) -  val itemValues = style( -    marginLeft(15.px) +  val definitionDescription = style( +    marginBottom(1.em), +    lineHeight(1.4.em)    ) -  val itemValue = style( -    marginBottom(5.px), -    &.before( -      content := "\"•\"", -      color(C.stiletto.value), -      marginRight(10.px) -    ) +  val close = style( +    Button.simple, +    marginBottom(1.em)    )  } diff --git a/src/main/scala/reading/component/index/style/Books.scala b/src/main/scala/reading/component/index/style/Books.scala index ca52328..f7c1ffc 100644 --- a/src/main/scala/reading/component/index/style/Books.scala +++ b/src/main/scala/reading/component/index/style/Books.scala @@ -9,8 +9,23 @@ import reading.component.style.{ Color => C }  object Books extends StyleSheet.Inline {    import dsl._ +  val booksParent = style( +    height(100.%%) +  ) +    val books = style(      display.flex, +    flexDirection.column, +    height(100.%%), +    opacity(0) +  ) + +  val listParent = style( +    overflowY.scroll +  ) + +  val list = style( +    display.flex,      flexWrap.wrap,      justifyContent.spaceAround    ) diff --git a/src/main/scala/reading/component/index/style/Filters.scala b/src/main/scala/reading/component/index/style/Filters.scala new file mode 100644 index 0000000..e4346a2 --- /dev/null +++ b/src/main/scala/reading/component/index/style/Filters.scala @@ -0,0 +1,69 @@ +package reading.component.index.style + +import scalacss.Defaults._ + +import reading.Media +import reading.component.style.{ Color => C, Button, Count } + +object Filters extends StyleSheet.Inline { +  import dsl._ + +  val filters = style( +    Media.mobile(margin(0.px, 30.px, 10.px)) +  ) + +  val showFiltersMenu = style( +    Media.desktop(display.none), +    Media.mobile( +      Button.simple, +      marginBottom(20.px) +    ) +  ) + +  val filtersCount = style( +    Count.major, +    marginLeft(20.px) +  ) + +  val values = style( +    display.flex, +    flexWrap.wrap, +    alignItems.center, +    Media.desktop(margin(0.px, 40.px, 10.px)), +    Media.mobile(display.none) +  ) + +  private val box = style( +    display.flex, +    alignItems.center, +    height(50.px), +    marginRight(20.px), +    marginBottom(10.px), +    padding(15.px), +    borderRadius(2.px), +    border(1.px, solid, C.gray.lighten(60).value), +    fontSize(18.px), +    &.hover(cursor.pointer) +  ) + +  val clear = style( +    box, +    backgroundColor(C.mickado.value), +    color(C.white.value), +    &.hover(backgroundColor(C.mickado.lighten(30).value)) +  ) + +  val filter = style( +    box, +    &.hover(borderColor(C.gray.lighten(80).value)) +  ) + +  val name = style( +    marginRight(10.px) +  ) + +  val cross = style( +    width(15.px), +    height(15.px) +  ) +} diff --git a/src/main/scala/reading/component/index/style/Header.scala b/src/main/scala/reading/component/index/style/Header.scala index 2eb6eb2..10ce059 100644 --- a/src/main/scala/reading/component/index/style/Header.scala +++ b/src/main/scala/reading/component/index/style/Header.scala @@ -3,73 +3,20 @@ package reading.component.index.style  import scalacss.Defaults._  import reading.Media -import reading.component.style.{ Color => C, Button, Count } +import reading.component.style.{ Color => C }  object Header extends StyleSheet.Inline {    import dsl._    val header = style( -    Media.desktop(margin(40.px)), -    Media.mobile(margin(30.px)) -  ) - -  val showFiltersMenu = style( -    Media.desktop(display.none), -    Media.mobile( -      Button.simple, -      marginBottom(20.px) -    ) -  ) - -  val filtersCount = style( -    Count.major, -    marginLeft(20.px) -  ) - -  val filters = style( -    display.flex, -    flexWrap.wrap, -    marginBottom(15.px), -    Media.mobile(display.none) -  ) - -  private val box = style( -    display.flex, -    alignItems.center, -    padding(15.px), -    marginRight(20.px), -    marginBottom(15.px), -    borderRadius(2.px), -    border(1.px, solid, C.gray.lighten(60).value), -    fontSize(18.px), -    &.hover(cursor.pointer) -  ) - -  val clear = style( -    box, -    backgroundColor(C.mickado.value), -    color(C.white.value), -    &.hover(backgroundColor(C.mickado.lighten(30).value)) -  ) - -  val filter = style( -    box, -    &.hover(borderColor(C.gray.lighten(80).value)) -  ) - -  val name = style( -    marginRight(10.px) -  ) - -  val cross = style( -    width(15.px), -    height(15.px) +    margin(30.px, 0.px)    )    val searchAndCount = style(      display.flex,      flexWrap.wrap,      alignItems.center, +    Media.desktop(paddingLeft(40.px)),      Media.mobile(justifyContent.center)    ) @@ -78,7 +25,7 @@ object Header extends StyleSheet.Inline {      Media.desktop(marginRight(30.px))    ) -  val booksCount = style( +  val count = style(      fontSize(20.px),      color(C.gray.value),      Media.mobile(textAlign.center) diff --git a/src/main/scala/reading/component/index/style/Menu.scala b/src/main/scala/reading/component/index/style/Menu.scala index dd74039..09f529c 100644 --- a/src/main/scala/reading/component/index/style/Menu.scala +++ b/src/main/scala/reading/component/index/style/Menu.scala @@ -9,28 +9,25 @@ object Menu extends StyleSheet.Inline {    import dsl._    val menu = style( -    Media.mobile(display.none), +    height(100.%%), +    zIndex(1),      Media.desktop( +      backgroundColor(C.englishWalnut.value),        color(C.white.value),        position.relative,        width(280.px) -    ) -  ) - -  val background = style( -    Media.desktop( +    ), +    Media.mobile( +      backgroundColor(C.white.value), +      color(C.black.value),        position.fixed, -      width(280.px), -      height(100.%%), -      backgroundColor(C.englishWalnut.value), -      boxShadow := "4px 0px 6px -1px rgba(0, 0, 0, 0.2)" +      display.none, +      width(100.%%)      )    ) -  val content = style( -    position.relative, -    width(100.%%), -    height(100.%%) +  val show = style( +    Media.mobile(display.block)    )    val header = style( @@ -44,7 +41,6 @@ object Menu extends StyleSheet.Inline {      textTransform.uppercase,      fontWeight.bold,      letterSpacing(1.px), -    Media.desktop(marginBottom(20.px)),      Media.mobile(boxShadow := "0px 3px 5px -1px rgba(0, 0, 0, 0.2)")    ) @@ -53,28 +49,13 @@ object Menu extends StyleSheet.Inline {      marginLeft(20.px)    ) -  val show = style( -    Media.mobile( -      display.block, -      position.fixed, -      top(0.px), -      left(0.px), -      width(100.%%), -      height(100.%%), -      zIndex(1), -      overflowY.scroll, -      backgroundColor(C.white.value), -      color(C.black.value) -    ) -  ) -    val empty = style()    val groups = style( -    Media.mobile( -      height :=! "calc(100% - 120px)", -      overflowY.auto -    ) +    overflowY.scroll, +    paddingTop(20.px), +    Media.desktop(height :=! "calc(100% - 60px)"), +    Media.mobile(height :=! "calc(100% - 120px)")    )    val filterTitle = style( @@ -155,9 +136,10 @@ object Commons extends StyleSheet.Inline {    val footerButton = style(      display.flex, -    width(50.%%), +    flexGrow(1),      justifyContent.center,      alignItems.center, +    height(50.px),      margin(5.px),      textTransform.uppercase,      fontSize(14.px), diff --git a/src/main/scala/reading/component/style/Global.scala b/src/main/scala/reading/component/style/Global.scala index 7501ec3..b7bf38c 100644 --- a/src/main/scala/reading/component/style/Global.scala +++ b/src/main/scala/reading/component/style/Global.scala @@ -14,8 +14,7 @@ object Global extends StyleSheet.Standalone {    "body" - (      position.absolute,      width(100.%%), -    height(100.%%), -    overflowY.hidden +    height(100.%%)    )    "a" - ( diff --git a/src/main/scala/reading/component/style/Index.scala b/src/main/scala/reading/component/style/Index.scala index e02ebd9..a2be884 100644 --- a/src/main/scala/reading/component/style/Index.scala +++ b/src/main/scala/reading/component/style/Index.scala @@ -9,12 +9,12 @@ object Index extends StyleSheet.Inline {    val page = style(      display.flex, -    overflowY.scroll,      height(100.%%)    )    val main = style(      Media.desktop(width :=! "calc(100% - 280px)"), -    Media.mobile(width(100.%%)) +    Media.mobile(width(100.%%)), +    overflow.hidden    )  } diff --git a/src/main/scala/reading/component/widget/Animate.scala b/src/main/scala/reading/component/widget/Animate.scala index 0e848aa..6328177 100644 --- a/src/main/scala/reading/component/widget/Animate.scala +++ b/src/main/scala/reading/component/widget/Animate.scala @@ -14,16 +14,16 @@ object Animate {      transition: (Double, Double) => Double,      animate: (Double, HTMLElement) => Unit,      onEnd: => Unit = () -  ): Unit = { +  ): Unit =      animationFrames.get(id) match { -      case Some(animationFrame) => window.cancelAnimationFrame(animationFrame) -      case None => () +      case Some(animationFrame) => +        () +      case None => +        val animationFrame = window.requestAnimationFrame(ts => +          frame(id, ts, duration, transition, animate, onEnd)(ts)) +        animationFrames.put(id, animationFrame) +        ()      } -    val animationFrame = window.requestAnimationFrame(ts => -      frame(id, ts, duration, transition, animate, onEnd)(ts)) -    animationFrames.put(id, animationFrame) -    () -  }    private def frame(      id: String, @@ -44,6 +44,7 @@ object Animate {            animationFrames.put(id, animationFrame)          } else {            animate(1, element) +          animationFrames.remove(id)            onEnd          }        case _ => diff --git a/src/main/scala/reading/component/widget/AnimateMethod.scala b/src/main/scala/reading/component/widget/AnimateMethod.scala new file mode 100644 index 0000000..dfe3e46 --- /dev/null +++ b/src/main/scala/reading/component/widget/AnimateMethod.scala @@ -0,0 +1,29 @@ +package reading.component.widget + +object AnimateMethod { +  def fadeOut(id: String, onEnd: => Unit = ()): Unit = +    Animate( +      id = id, +      duration = 100, +      transition = Transition.linear, +      animate = +      (progress, element) => { +        element.style.opacity = s"${1 - progress}" +        element.style.transform = s"translateX(${20 * progress}px)" +      }, +      onEnd = onEnd +    ) + +  def fadeIn(id: String, onEnd: => Unit = ()): Unit = +    Animate( +      id = id, +      duration = 100, +      transition = Transition.easeIn, +      animate = +      (progress, element) => { +        element.style.opacity = s"${progress}" +        element.style.transform = s"translateX(${20 * (1 - progress)}px)" +      }, +      onEnd = onEnd +    ) +} diff --git a/src/main/scala/reading/component/widget/Input.scala b/src/main/scala/reading/component/widget/Input.scala index 7dac47a..1a1157e 100644 --- a/src/main/scala/reading/component/widget/Input.scala +++ b/src/main/scala/reading/component/widget/Input.scala @@ -2,14 +2,15 @@ package reading.component.widget  import scalatags.JsDom.all._ -import org.scalajs.dom.KeyboardEvent  import org.scalajs.dom.html.Input +import org.scalajs.dom.KeyboardEvent  import scalacss.Defaults._  import scalacss.ScalatagsCss._  import rx._ +import reading.component.style.{ Color => C }  import reading.component.widget.style.{ Input => InputStyle }  object Input { @@ -17,28 +18,38 @@ object Input {      style: StyleA,      query: Var[String],      label: String = "", -    onEnter: => Unit = () +    onEnter: => Unit = (), +    maxLength: Option[Int] = None    )(      implicit      ctx: Ctx.Owner    ): Frag = {      val inputBox = input( -      InputStyle.render,        InputStyle.input, -      style,        placeholder := label,        onkeyup := { (e: KeyboardEvent) =>          val input = e.target.asInstanceOf[Input]          query() = input.value          input.value = input.value          if (e.keyCode == 13) onEnter -      } +      }, +      maxlength := maxLength.map(_.toString).getOrElse("")      ).render      query.trigger {        inputBox.value = query.now      } -    inputBox +    div( +      InputStyle.render, +      InputStyle.parent, +      style, +      inputBox, +      span( +        InputStyle.clear, +        onclick := (() => query() = ""), +        Cross(15.px, C.gray.value) +      ) +    )    }  } diff --git a/src/main/scala/reading/component/widget/Modal.scala b/src/main/scala/reading/component/widget/Modal.scala deleted file mode 100644 index db1f7e6..0000000 --- a/src/main/scala/reading/component/widget/Modal.scala +++ /dev/null @@ -1,59 +0,0 @@ -package reading.component.widget - -import scala.util.Random - -import org.scalajs.dom.raw.HTMLElement -import scalacss.Defaults._ -import scalacss.ScalatagsCss._ -import scalatags.JsDom.all._ - -import reading.component.widget.style.{ Modal => ModalStyle } - -object Modal { -  def apply(onClose: => Unit)(content: Frag): Frag = { -    val modalId = s"modal${Random.nextInt}" - -    Animate( -      id = modalId, -      duration = 250, -      transition = Transition.easeOut, -      animate = -      (progress, element) => { -        element.style.opacity = s"$progress" -        element.childNodes(2) match { -          case e: HTMLElement => e.style.transform = s"scale(${0.85 + 0.15 * progress})" -        } -      } -    ) - -    div( -      ModalStyle.render, -      ModalStyle.modal, -      id := modalId, - -      div( -        ModalStyle.curtain, -        onclick := (() => close(modalId, onClose)) -      ), - -      div( -        ModalStyle.content, -        content, -        button( -          ModalStyle.close, -          onclick := (() => close(modalId, onClose)), -          "Fermer" -        ) -      ) -    ) -  } - -  private def close(modalId: String, onClose: => Unit): Unit = -    Animate( -      id = modalId, -      duration = 300, -      transition = Transition.linear, -      onEnd = onClose, -      animate = (progress, element) => element.style.opacity = s"${1 - progress}" -    ) -} diff --git a/src/main/scala/reading/component/widget/style/Input.scala b/src/main/scala/reading/component/widget/style/Input.scala index 967393b..9453640 100644 --- a/src/main/scala/reading/component/widget/style/Input.scala +++ b/src/main/scala/reading/component/widget/style/Input.scala @@ -7,10 +7,25 @@ import reading.component.style.{ Color => C }  object Input extends StyleSheet.Inline {    import dsl._ +  val parent = style( +    position.relative +  ) +    val input = style( +    height(45.px),      border(1.px, solid, C.mickado.value),      borderRadius(2.px),      padding(10.px),      &.hover(borderColor(C.gray.value))    ) + +  val clear = style( +    position.absolute, +    top(0.px), +    right(10.px), +    display.flex, +    height(100.%%), +    alignItems.center, +    cursor.pointer +  )  } diff --git a/src/main/scala/reading/component/widget/style/Modal.scala b/src/main/scala/reading/component/widget/style/Modal.scala deleted file mode 100644 index faf325d..0000000 --- a/src/main/scala/reading/component/widget/style/Modal.scala +++ /dev/null @@ -1,65 +0,0 @@ -package reading.component.widget.style - -import scalacss.Defaults._ - -import reading.Media -import reading.component.style.{ Color => C, Button } - -object Modal extends StyleSheet.Inline { -  import dsl._ - -  val modal = style( -    display.flex, -    justifyContent.center, -    position.fixed, -    width(100.%%), -    height(100.%%), -    top(0.px), -    right(0.px), -    bottom(0.px), -    left(0.px), -    opacity(0), -    overflowY.scroll -  ) - -  val curtain = style( -    Media.desktop( -      width(100.%%), -      height(100.%%), -      position.fixed, -      top(0.px), -      left(0.px), -      backgroundColor(C.black.value), -      opacity(0.7), -      cursor.pointer -    ), -    Media.mobile( -      display.none -    ) -  ) - -  val content = style( -    position.relative, -    backgroundColor(C.white.value), -    margin.auto, -    Media.desktop( -      width(50.%%), -      borderRadius(5.px) -    ), -    Media.mobile( -      width(100.%%), -      height(100.%%), -      overflowY.auto -    ), -    padding(30.px, 30.px, 0.px, 30.px) -  ) - -  val close = style( -    Media.desktop(display.none), -    Media.mobile( -      Button.simple, -      marginTop(20.px), -      marginBottom(30.px) -    ) -  ) -} diff --git a/src/main/scala/reading/models/Book.scala b/src/main/scala/reading/models/Book.scala index 6f4d8dd..923f2ed 100644 --- a/src/main/scala/reading/models/Book.scala +++ b/src/main/scala/reading/models/Book.scala @@ -9,7 +9,8 @@ case class Book(      genres: Seq[Genre],      themes: Seq[Theme],      programs: Seq[Program], -    level: Level +    level: Level, +    resume: Option[String] = None  ) extends Ordered[Book] {    def compare(that: Book) =      Compare.format(this.title).compare(Compare.format(that.title)) diff --git a/src/main/scala/reading/models/Books.scala b/src/main/scala/reading/models/Books.scala index 43ed2b8..69f9ee6 100644 --- a/src/main/scala/reading/models/Books.scala +++ b/src/main/scala/reading/models/Books.scala @@ -89,7 +89,7 @@ object Books {      ),      Book( -      title = "Lou !", +      title = "Lou !",        author = "Julien NEEL",        year = "2004-2016",        parts = 7, @@ -176,7 +176,34 @@ object Books {        genres = Seq(RomanAventure),        themes = Seq(Amitie, Aventure),        programs = Seq(VoyageEtAventure), -      level = Moyen +      level = Moyen, +      resume = Some(""" +        Pour Tom Sawyer, il y a des choses vraiment plus importantes que +        l’école! Aller à la pêche, par exemple, se battre avec les nouveaux +        venus au village ou, plus important encore, retrouver son grand ami +        Huckleberry, qui mène une vie de bohème à l’image de son vagabond de +        père… Mais à force de se prendre pour des bandits et de faire des +        expériences de sorcellerie à la nuit tombée, Tom et Huck vont être +        mêlés à un véritable crime, avec de vrais assassins et un authentique +        trésor… Un chef-d’œuvre de la littérature américaine.<br> +        <br> +        Mark Twain nous avertit : « La plupart des aventures relatées dans ce +        livre sont vécues. » En effet, Tom Sawyer, chenapan de stature +        internationale, lui fut inspiré par deux ou trois de ses camarades et +        Huckleberry Finn est "décrit d’après nature". Plus intéressés par +        l’aventure que par l’école, les deux garçons jouent aux brigands et aux +        sorciers. Jusqu’au jour où ils se retrouvent embarqués dans une +        véritable affaire criminelle…<br> +        <br> +        Tom Sawyer est un garçon chez qui l’amour du jeu l’emporte sur tout +        autre sentiment. Sa famille éplorée le cherche-t-elle partout ? Il joue +        au pirate et il ne rentrera que pour assister à son propre service +        funèbre. Le punit-on ? Il transformera la corvée en jeu et la fera +        exécuter par ses camarades. Son cerveau infatigable s’envole hors de la +        réalité, entraînant le lecteur vers toutes sortes d’aventures +        inattendues.<br> +        Source : Bibliothèque verte, Hachette +      """)      ),      Book( @@ -323,7 +350,18 @@ object Books {        genres = Seq(Comique),        themes = Seq(Humour, Amitie, Aventure),        programs = Seq(Monstre, Autrui, Heros), -      level = Moyen +      level = Moyen, +      resume = Some(""" +        Sophie ne rêve pas, cette nuit-là, quand elle aperçoit de la fenêtre de +        l’orphelinat une silhouette immense vêtue d’une longue cape et munie +        d’une curieuse trompette. Une main énorme s’approche et saisit la +        petite fille terrifiée pour l’emmener au pays des géants. Mais +        heureusement, Sophie est tombée entre les mains d’un géant peu +        ordinaire : le Bon Gros Géant, qui se nourrit de légumes et souffle des +        rêves dans les chambres des enfants… Avec la jeune Sophie, devenez +        l’ami du géant au grand cœur et apprenez son langage pour le moins +        loufoque ! Un chef-d’œuvre d’imagination signé Roald Dahl ! +      """)      ),      Book( @@ -539,7 +577,19 @@ object Books {        genres = Seq(Fantasy),        themes = Seq(Aventure),        programs = Seq(), -      level = Moyen +      level = Moyen, +      resume = Some(""" +        Bilbo, comme tous les hobbits, est un petit être paisible et sans +        histoire. Son quotidien est bouleversé un beau jour, lorsque Grandalf +        le magicien et treize nains barbus l’entraînent dans un voyage +        périlleux. C’est le début d’une grande aventure, d’une fantastique +        quête au trésor semée d’embûches et d’épreuves, qui mènera Bilbo +        jusqu’à la Montagne Solitaire gardée par le dragon Smaug…<br> +        <br> +        Prélude au Seigneur des anneaux, Bilbo le Hobbit a été vendu à des +        millions d’exemplaires depuis sa publication en 1937, s’imposant comme +        l’un des livres les plus aimés et les plus influents du XXᵉ siècle. +      """)      ),      Book( @@ -974,7 +1024,16 @@ object Books {        genres = Seq(Fantasy),        themes = Seq(Conflit, Initiation),        programs = Seq(), -      level = Moyen +      level = Moyen, +      resume = Some(""" +        Will rêve de devenir chevalier, comme son père, mort en héros au +        combat. Mais c’est un tout autre destin qui lui est réservé ! Il sera +        l’apprenti du sombre Halt, un Rôdeur aux pouvoirs troublants, défenseur +        secret du royaume d’Araluen. Pour maintenir la paix du domaine, Will +        doit apprendre la magie de la dissimulation et devenir une ombre parmi +        les ombres. Mais il lui faut faire vite, car le seigneur Morgarath +        menace de reprendre le pouvoir par le feu et le sang. +      """)      ),      Book( @@ -1059,11 +1118,26 @@ object Books {        title = "Bichon",        author = "David GILSON",        year = "2013-2015", +      parts = 2,        period = None,        genres = Seq(BD),        themes = Seq(Humour, Ecole, Amitie, Famille, Amour, Homosexualite),        programs = Seq(), -      level = Facile +      level = Facile, +      resume = Some(""" +        Se déguiser en princesse pour un goûter d’anniversaire, jouer à +        l’élastique, entamer une chorégraphie en pleine cour de récré… Un peu +        compliqué quand on aime faire toutes ces choses et qu’on est un petit +        garçon de 8 ans. Mais pas pour Bichon : il transgresse les règles de la +        société sans même s’en rendre compte ! Heureusement, sa famille et ses +        amis l’aiment tel qu’il est. Même que parfois Jean-Marc, le beau garçon +        du CM2, prend sa défense quand on se moque de lui… David Gilson réussit +        l’ambitieux pari de raconter avec tendresse et humour la vie +        quotidienne d’un petit garçon « pas comme les autres ». Bichon ne fait +        qu’être lui-même et se soucie peu du regard des autres, et cette +        personnalité déjà si affirmée et si naturelle est un joyeux exemple +        pour les petits et grands lecteurs ! +      """)      ),      Book( @@ -1097,7 +1171,16 @@ object Books {        genres = Seq(Roman),        themes = Seq(Enquete, Enfants),        programs = Seq(), -      level = Moyen +      level = Moyen, +      resume = Some(""" +        Caïus est un âne.<br> +        La phrase inscrite par Rufus sur sa tablette remporte un grand succès +        en classe. mais Caïus rougit de colère. Comment Rufus ose-t-il +        l’insulter, lui, le fils d’un richissime sénateur ? Pourtant, le +        lendemain, plus personne n’a envie de rire. La même phrase est tracée +        en lettres rouges sur la façade du temple de Minerve. Or, dans la Rome +        impériale, le sacrilège est terrible. +      """)      ),      Book( @@ -1253,7 +1336,22 @@ object Books {        genres = Seq(BD, Fantasy, RomanAventure),        themes = Seq(Combat, Initiation),        programs = Seq(Heros), -      level = Facile +      level = Facile, +      resume = Some(""" +        La neige est méchante en cet hiver 1065, elle a décidé de s’en prendre +        aux hommes. Elle envoie ses légions de flocons de la taille d’un roc +        sur le Fizzland, avec pour mission d’engloutir les villages vikings et +        tous leurs habitants. Afin d’échapper à la Démone blanche, Bjorn et sa +        famille se claquemurent dans la salle commune de la maison de son père, +        Erik, le colosse sans peur. Tous se préparent à supporter un siège qui +        risque de durer de longs mois. Lors de cette épreuve exceptionnelle, +        chacun va dévoiler son cœur et son courage. À l’exception de Bjorn. Lui +        ne se révèle pas, il se métamorphose. Ce jeune garçon timide et +        craintif, dont le nez coule comme une source, maigre comme un oisillon +        et pas très doué pour les armes va brusquement se transformer en un +        combattant redoutable. Par quel miracle ? Bjorn serait-il un morphir ? +        Lui-même en doute. +      """)      ),      Book( @@ -1364,7 +1462,18 @@ object Books {        genres = Seq(Roman),        themes = Seq(Adolescence, Deuil, Famille),        programs = Seq(Autrui), -      level = Facile +      level = Facile, +      resume = Some(""" +        Il fait beau, ce jour-là, à la terrasse de l’hôtel. La famille est +        attablée. On discute d’un temple à visiter. Mais avec cette mer +        turquoise… Maxime n’a aucune envie de bouger. Il va rester ici, +        tranquille, à profiter de la plage avec Jade, sa sœur jumelle. Quelques +        minutes plus tard, une vague apparaît. Une vague qui n’en finit pas de +        grossir. Une vague qui engloutit tout. Dans leur course folle, Jade +        lâche la main de son frère. Pour Max, il n’ y a plus de mots. Plus de +        larmes. Plus de présent. Plus d’avenir. Pourra-t-il survivre à ce +        drame ? +      """)      ),      Book( @@ -1551,7 +1660,23 @@ object Books {        genres = Seq(Fantastique),        themes = Seq(Adolescence, Deuil),        programs = Seq(Valeurs), -      level = Moyen +      level = Moyen, +      resume = Some(""" +        En Louisiane, tout le monde croit aux esprits. Lanesha, elle, a le don +        de les voir. « Tu es comme moi, ma chérie, tu as un don de +        double-vue », lui a expliqué Mama Ya-Ya, la sage-femme qui l’a +        recueillie à sa naissance. Mama Ya-Ya, savait qu’un ouragan approchait, +        bien avant que la radio et la télévision n’en parlent. Les dégâts +        seront incommensurables, répète le présentateur. Tous les habitants de +        la Nouvelle-Orléans doivent quitter la ville. Mama Ya-Ya est très âgée, +        et ne possède pas de voiture, alors Lanesha a fait des provisions d’eau +        et de nourriture, et a cloué des planches sur les fenêtres. Elle ne +        sait pas ce qui l’attend, mais elle se prépare de toutes ses forces à +        survivre. Avec TaShlon, le fils des voisins, avec le chien Spot qu’ils +        viennent d’adopter ensemble. Avec le fantôme silencieux de sa mère, qui +        est venu pour l’aider. Avec l’amour de Mama Ya-Ya, qui est +        incommensurable. +      """)      ),      Book( @@ -1573,7 +1698,23 @@ object Books {        genres = Seq(Fantastique),        themes = Seq(Adolescence, Viol, Danse),        programs = Seq(SeRaconter), -      level = Facile +      level = Facile, +      resume = Some(""" +        Lucie a été trouvée, bébé, au pied d’un arbre dans la forêt. Recueillie +        et adoptée par des parents aimants, elle grandit comme tous les autres +        enfants. Passionnée de danse, elle rêve d’incarner une sylphide, ces +        esprits de l’air, à mi-chemin entre les anges et les elfes. Inscrite au +        conservatoire de Lyon, elle remporte le rôle pour un spectacle et se +        lance à corps perdu dans les répétitions, ignorant les conseils de son +        professeur qui lui demande de prendre soin d’elle, refusant de voir ces +        bosses qui jaillissent de temps en temps dans son dos.<br> +        <br> +        Fruit des amours d’un humain et d’une sylphide, elle est un être à +        part. Au cours d’une promenade dans les bois, elle va faire la +        connaissance de ses soeurs. Elle n’a pas encore leur légèreté, mais au +        contact de la nature, elle parvient à déployer ses ailes. Bien sûr, +        elle doit garder secrète sa métamorphose… +      """)      ),      Book( @@ -1585,7 +1726,20 @@ object Books {        genres = Seq(Manga, SF),        themes = Seq(Adolescence, Immortalite, Conflit, Mutant),        programs = Seq(Reel), -      level = Moyen +      level = Moyen, +      resume = Some(""" +        Renversé par un camion en rentrant de l’école, le jeune Kei meurt sur +        le coup. Mais quelques instant plus tard, il ressuscite +        mystérieusement. Dès lors, sa vie de lycéen bascule. Une étrange +        organisation gouvernementale tente par tous les moyens de le capturer +        afin de mener des expériences scientifiques sur lui. Rapidement, il +        apprend qu’il n’est pas le seul être dans cette situation périlleuse, +        et qu’il semble être ce que certains nomment un Ajin. Personne ne sait +        exactement comment ils sont apparus ni pourquoi ils existent. Mais les +        services spéciaux du gouvernement sont prêts à user de tous les moyens +        pour le découvrir, car rien à leurs yeux n’est plus dangereux pour +        l’humanité… qu’un être immortel ! +      """)      ),      Book( @@ -1609,7 +1763,13 @@ object Books {        genres = Seq(BD),        themes = Seq(Amitie, Famille, Humour, Ecole),        programs = Seq(Resister, Autrui), -      level = Facile +      level = Facile, +      resume = Some(""" +        Ariol est un petit âne qui n’aime pas se lever le matin pour aller à +        l’école, surtout l’hiver. Dehors, il fait nuit et froid, comme dans le +        frigo quand la petite lumière est en panne. Mais bon, à l’école, il y a +        la jolie Pétula ! Alors, Ariol se lève et il y va. +      """)      ),      Book( @@ -1669,7 +1829,12 @@ object Books {        genres = Seq(Nouvelle, Fantastique, Thriller),        themes = Seq(Vieillesse),        programs = Seq(SeRaconter), -      level = Difficile +      level = Difficile, +      resume = Some(""" +        Un recueil de nouvelles auscultant les paradoxes de l’Amérique et +        abordant des thèmes tels que les souffrances individuelles et +        collectives, la vieillesse et la mort, la culpabilité, etc. +      """)      ),      Book( @@ -1686,7 +1851,7 @@ object Books {      Book(        title = "Le Monde Secret de Sombreterre", -      author = "Cassandra O'DONNELL", +      author = "Cassandra O’DONNELL",        year = "2016",        parts = 2,        period = None, @@ -1698,7 +1863,7 @@ object Books {      Book(        title = "Malenfer", -      author = "Cassandra O'DONNELL", +      author = "Cassandra O’DONNELL",        year = "2014-2015",        parts = 3,        period = None, @@ -1956,7 +2121,15 @@ object Books {        genres = Seq(Roman),        themes = Seq(Adolescence, Famille, Homosexualite, Apprentissage),        programs = Seq(), -      level = Moyen +      level = Moyen, +      resume = Some(""" +        Dante attend les résultats de ses examens. Le courrier qui lui ouvrira +        les portes de l’université. De sa future vie. Celle dont il a toujours +        rêvé. Mais quand on sonne enfin à la porte, ce n’est pas le facteur, +        c’est Mélanie. Son ex-copine, dont il n’a plus entendu parler depuis +        des mois. Avec un bébé. Le sien. Le leur. Etre père à 17 ans ? Il y a +        de quoi pleurer. Mais les garçons ne pleurent jamais… +      """)      ),      Book( @@ -2183,7 +2356,7 @@ object Books {      Book(        title = "Emmett TILL, derniers jours d’une courte vie", -      author = "Arnaud FLOC'H", +      author = "Arnaud FLOC’H",        year = "2015",        period = Some(Siecle20),        genres = Seq(BD), diff --git a/src/main/scala/reading/models/FilterFactory.scala b/src/main/scala/reading/models/FilterFactory.scala index c7b9fbf..d900af5 100644 --- a/src/main/scala/reading/models/FilterFactory.scala +++ b/src/main/scala/reading/models/FilterFactory.scala @@ -18,7 +18,7 @@ object FilterFactory {    implicit object GroupedTheme extends FilterFactory[GroupedTheme] {      def create(groupedTheme: GroupedTheme): Filter =        new Filter { -        def filter(book: Book): Boolean = book.themes.map(Theme.groupedTheme).contains(groupedTheme) +        def filter(book: Book): Boolean = book.themes.map(Theme.grouped).contains(groupedTheme)          val kind: FilterKind = FilterKind.GroupedTheme          val nonFormattedName: String = groupedTheme.toString()          val name: String = groupedTheme.prettyPrint() diff --git a/src/main/scala/reading/models/Genre.scala b/src/main/scala/reading/models/Genre.scala index 2d55e60..a3a3165 100644 --- a/src/main/scala/reading/models/Genre.scala +++ b/src/main/scala/reading/models/Genre.scala @@ -25,8 +25,8 @@ sealed trait Genre extends EnumEntry with Ordered[Genre] {      case Uchronie => "uchronie"      case Manga => "manga"      case Thriller => "thriller" -    case Epistolaire => "Epistolaire" -    case Nouvelle => "Nouvelle" +    case Epistolaire => "epistolaire" +    case Nouvelle => "nouvelle"    }  } diff --git a/src/main/scala/reading/models/Grade.scala b/src/main/scala/reading/models/Grade.scala index 32c5b72..f54211d 100644 --- a/src/main/scala/reading/models/Grade.scala +++ b/src/main/scala/reading/models/Grade.scala @@ -6,7 +6,7 @@ sealed trait Grade extends EnumEntry with Ordered[Grade] {    import Grade._    def compare(that: Grade): Int = { -    values.indexOf(that) - values.indexOf(this) +    values.indexOf(this) - values.indexOf(that)    }    def prettyPrint(): String = this match { diff --git a/src/main/scala/reading/models/GroupedTheme.scala b/src/main/scala/reading/models/GroupedTheme.scala index 9ece7db..61a5281 100644 --- a/src/main/scala/reading/models/GroupedTheme.scala +++ b/src/main/scala/reading/models/GroupedTheme.scala @@ -5,9 +5,8 @@ import enumeratum._  sealed trait GroupedTheme extends EnumEntry with Ordered[GroupedTheme] {    import GroupedTheme._ -  def compare(that: GroupedTheme): Int = { -    values.indexOf(that) - values.indexOf(this) -  } +  def compare(that: GroupedTheme): Int = +    Compare.format(this.prettyPrint).compare(Compare.format(that.prettyPrint))    def prettyPrint(): String = this match {      case Culture => "culture" diff --git a/src/main/scala/reading/models/Level.scala b/src/main/scala/reading/models/Level.scala index c06776e..9f25165 100644 --- a/src/main/scala/reading/models/Level.scala +++ b/src/main/scala/reading/models/Level.scala @@ -6,7 +6,7 @@ sealed trait Level extends EnumEntry with Ordered[Level] {    import Level._    def compare(that: Level): Int = { -    values.indexOf(that) - values.indexOf(this) +    values.indexOf(this) - values.indexOf(that)    }    def prettyPrint(): String = this match { diff --git a/src/main/scala/reading/models/Period.scala b/src/main/scala/reading/models/Period.scala index f148b4c..c0b663e 100644 --- a/src/main/scala/reading/models/Period.scala +++ b/src/main/scala/reading/models/Period.scala @@ -6,20 +6,20 @@ sealed trait Period extends EnumEntry with Ordered[Period] {    import Period._    def compare(that: Period): Int = -    values.indexOf(that) - values.indexOf(this) +    values.indexOf(this) - values.indexOf(that)    def prettyPrint(): String = this match { -    case Antiquite => "Antiquité" -    case MoyenAge => "Moyen âge" -    case Renaissance => "Renaissance" -    case Lumieres => "Lumières" -    case Louis14 => "Louis XIV" +    case Antiquite => "antiquité" +    case MoyenAge => "moyen âge" +    case Renaissance => "renaissance" +    case Lumieres => "lumières" +    case Louis14 => "louis XIV"      case Siecle18 => "18ème siècle"      case Siecle19 => "19ème siècle"      case Siecle20 => "20ème siècle"      case Annees50 => "années 50"      case Contemporain => "contemporain" -    case Futur => "Futur" +    case Futur => "futur"    }  } diff --git a/src/main/scala/reading/models/Theme.scala b/src/main/scala/reading/models/Theme.scala index 6ab4b9f..66870a4 100644 --- a/src/main/scala/reading/models/Theme.scala +++ b/src/main/scala/reading/models/Theme.scala @@ -45,7 +45,7 @@ sealed trait Theme extends EnumEntry with Ordered[Theme] {      case Guerre => "guerre"      case Handicap => "handicap"      case Harcelement => "harcelement" -    case Homosexualite => "Homosexualité" +    case Homosexualite => "homosexualité"      case Humour => "humour"      case Immortalite => "immortalité"      case Initiation => "initiation" @@ -174,7 +174,7 @@ object Theme extends Enum[Theme] {    case object Viol extends Theme    case object Voyage extends Theme -  def groupedTheme(theme: Theme): GroupedTheme = { +  def grouped(theme: Theme): GroupedTheme = {      import GroupedTheme._      theme match { | 
