aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoris2017-02-25 22:25:03 +0100
committerJoris2017-02-25 22:25:03 +0100
commitbbe5788cdcfbb26358566bfc74426ec38029cc73 (patch)
tree27607f6b5a667c264f11aeb10708d6d1dadb0fde
parentf1de0dd7632eb29a40ea1f5cf136ab43ee945926 (diff)
downloadreading-bbe5788cdcfbb26358566bfc74426ec38029cc73.tar.gz
reading-bbe5788cdcfbb26358566bfc74426ec38029cc73.tar.bz2
reading-bbe5788cdcfbb26358566bfc74426ec38029cc73.zip
Add detailed book page instead of a modal.
-rw-r--r--README.md7
-rw-r--r--public/cover/Ariol.jpgbin27340 -> 41131 bytes
-rw-r--r--public/cover/Lou !.jpg (renamed from public/cover/Lou !.jpg)bin28249 -> 28249 bytes
-rw-r--r--public/cover/L’Apprenti d’Araluen.jpgbin109912 -> 38523 bytes
-rw-r--r--src/main/scala/reading/Main.scala2
-rw-r--r--src/main/scala/reading/Route.scala55
-rw-r--r--src/main/scala/reading/component/Index.scala11
-rw-r--r--src/main/scala/reading/component/index/BookDetail.scala81
-rw-r--r--src/main/scala/reading/component/index/Books.scala94
-rw-r--r--src/main/scala/reading/component/index/FilterUtils.scala6
-rw-r--r--src/main/scala/reading/component/index/Filters.scala65
-rw-r--r--src/main/scala/reading/component/index/Header.scala45
-rw-r--r--src/main/scala/reading/component/index/Menu.scala104
-rw-r--r--src/main/scala/reading/component/index/style/BookDetail.scala65
-rw-r--r--src/main/scala/reading/component/index/style/Books.scala15
-rw-r--r--src/main/scala/reading/component/index/style/Filters.scala69
-rw-r--r--src/main/scala/reading/component/index/style/Header.scala61
-rw-r--r--src/main/scala/reading/component/index/style/Menu.scala52
-rw-r--r--src/main/scala/reading/component/style/Global.scala3
-rw-r--r--src/main/scala/reading/component/style/Index.scala4
-rw-r--r--src/main/scala/reading/component/widget/Animate.scala17
-rw-r--r--src/main/scala/reading/component/widget/AnimateMethod.scala29
-rw-r--r--src/main/scala/reading/component/widget/Input.scala23
-rw-r--r--src/main/scala/reading/component/widget/Modal.scala59
-rw-r--r--src/main/scala/reading/component/widget/style/Input.scala15
-rw-r--r--src/main/scala/reading/component/widget/style/Modal.scala65
-rw-r--r--src/main/scala/reading/models/Book.scala3
-rw-r--r--src/main/scala/reading/models/Books.scala209
-rw-r--r--src/main/scala/reading/models/FilterFactory.scala2
-rw-r--r--src/main/scala/reading/models/Genre.scala4
-rw-r--r--src/main/scala/reading/models/Grade.scala2
-rw-r--r--src/main/scala/reading/models/GroupedTheme.scala5
-rw-r--r--src/main/scala/reading/models/Level.scala2
-rw-r--r--src/main/scala/reading/models/Period.scala14
-rw-r--r--src/main/scala/reading/models/Theme.scala4
35 files changed, 736 insertions, 456 deletions
diff --git a/README.md b/README.md
index 736eaf8..a19499d 100644
--- a/README.md
+++ b/README.md
@@ -41,3 +41,10 @@ Dev environment:
``` sh
./dev
```
+
+TODO
+----
+
+- Resumes for every book
+- Click on filter on a book detail must do something
+- Links attached to filters and book details
diff --git a/public/cover/Ariol.jpg b/public/cover/Ariol.jpg
index 4a95c79..9e21ac9 100644
--- a/public/cover/Ariol.jpg
+++ b/public/cover/Ariol.jpg
Binary files differ
diff --git a/public/cover/Lou !.jpg b/public/cover/Lou !.jpg
index e681d20..e681d20 100644
--- a/public/cover/Lou !.jpg
+++ b/public/cover/Lou !.jpg
Binary files differ
diff --git a/public/cover/L’Apprenti d’Araluen.jpg b/public/cover/L’Apprenti d’Araluen.jpg
index 77752f3..51cf3e5 100644
--- a/public/cover/L’Apprenti d’Araluen.jpg
+++ b/public/cover/L’Apprenti d’Araluen.jpg
Binary files differ
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 {