From bbe5788cdcfbb26358566bfc74426ec38029cc73 Mon Sep 17 00:00:00 2001 From: Joris Date: Sat, 25 Feb 2017 22:25:03 +0100 Subject: Add detailed book page instead of a modal. --- src/main/scala/reading/Main.scala | 2 +- src/main/scala/reading/Route.scala | 55 ++++-- src/main/scala/reading/component/Index.scala | 11 +- .../scala/reading/component/index/BookDetail.scala | 81 +++++--- src/main/scala/reading/component/index/Books.scala | 94 ++++++--- .../reading/component/index/FilterUtils.scala | 6 + .../scala/reading/component/index/Filters.scala | 65 +++++++ .../scala/reading/component/index/Header.scala | 45 +---- src/main/scala/reading/component/index/Menu.scala | 104 +++++----- .../reading/component/index/style/BookDetail.scala | 65 +++++-- .../reading/component/index/style/Books.scala | 15 ++ .../reading/component/index/style/Filters.scala | 69 +++++++ .../reading/component/index/style/Header.scala | 61 +----- .../scala/reading/component/index/style/Menu.scala | 52 ++--- .../scala/reading/component/style/Global.scala | 3 +- src/main/scala/reading/component/style/Index.scala | 4 +- .../scala/reading/component/widget/Animate.scala | 17 +- .../reading/component/widget/AnimateMethod.scala | 29 +++ .../scala/reading/component/widget/Input.scala | 23 ++- .../scala/reading/component/widget/Modal.scala | 59 ------ .../reading/component/widget/style/Input.scala | 15 ++ .../reading/component/widget/style/Modal.scala | 65 ------- src/main/scala/reading/models/Book.scala | 3 +- src/main/scala/reading/models/Books.scala | 209 +++++++++++++++++++-- src/main/scala/reading/models/FilterFactory.scala | 2 +- src/main/scala/reading/models/Genre.scala | 4 +- src/main/scala/reading/models/Grade.scala | 2 +- src/main/scala/reading/models/GroupedTheme.scala | 5 +- src/main/scala/reading/models/Level.scala | 2 +- src/main/scala/reading/models/Period.scala | 14 +- src/main/scala/reading/models/Theme.scala | 4 +- 31 files changed, 729 insertions(+), 456 deletions(-) create mode 100644 src/main/scala/reading/component/index/Filters.scala create mode 100644 src/main/scala/reading/component/index/style/Filters.scala create mode 100644 src/main/scala/reading/component/widget/AnimateMethod.scala delete mode 100644 src/main/scala/reading/component/widget/Modal.scala delete mode 100644 src/main/scala/reading/component/widget/style/Modal.scala (limited to 'src/main') 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,7 +9,22 @@ 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.
+
+ 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…
+
+ 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.
+ 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…
+
+ 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.
+ 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.
+
+ 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 { -- cgit v1.2.3