aboutsummaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
Diffstat (limited to 'src/main')
-rw-r--r--src/main/scala/reading/Main.scala8
-rw-r--r--src/main/scala/reading/component/Index.scala24
-rw-r--r--src/main/scala/reading/component/index/BookDetail.scala11
-rw-r--r--src/main/scala/reading/component/index/Books.scala11
-rw-r--r--src/main/scala/reading/component/index/FilterUtils.scala39
-rw-r--r--src/main/scala/reading/component/index/Header.scala48
-rw-r--r--src/main/scala/reading/component/index/Menu.scala160
-rw-r--r--src/main/scala/reading/component/index/style/BookDetail.scala4
-rw-r--r--src/main/scala/reading/component/index/style/Header.scala12
-rw-r--r--src/main/scala/reading/component/index/style/Menu.scala26
-rw-r--r--src/main/scala/reading/component/style/Index.scala18
-rw-r--r--src/main/scala/reading/component/widget/Cross.scala2
-rw-r--r--src/main/scala/reading/component/widget/Input.scala44
-rw-r--r--src/main/scala/reading/component/widget/Modal.scala20
-rw-r--r--src/main/scala/reading/component/widget/style/Input.scala16
-rw-r--r--src/main/scala/reading/component/widget/style/Modal.scala9
-rw-r--r--src/main/scala/reading/models/Book.scala7
-rw-r--r--src/main/scala/reading/models/Books.scala (renamed from src/main/scala/reading/Books.scala)5
-rw-r--r--src/main/scala/reading/models/Filter.scala38
-rw-r--r--src/main/scala/reading/models/Search.scala16
-rw-r--r--src/main/scala/reading/utils/Rx.scala91
21 files changed, 422 insertions, 187 deletions
diff --git a/src/main/scala/reading/Main.scala b/src/main/scala/reading/Main.scala
index 6104891..a9fc3e2 100644
--- a/src/main/scala/reading/Main.scala
+++ b/src/main/scala/reading/Main.scala
@@ -1,13 +1,13 @@
package reading
import scala.scalajs.js.JSApp
-
+import rx._
+import rx.Ctx.Owner.Unsafe._
import org.scalajs.dom
-
import scalacss.Defaults._
import reading.component.style.{ Global => GlobalStyle }
-import reading.utils.RxTag
+import reading.utils.RxUtils._
object Main extends JSApp {
def main(): Unit = {
@@ -16,7 +16,7 @@ object Main extends JSApp {
dom.document.head.appendChild(style)
val _ = dom.document.body.appendChild {
- RxTag { implicit context =>
+ Rx {
Route.current() match {
case Route.Books(filters) => component.Index(filters)
}
diff --git a/src/main/scala/reading/component/Index.scala b/src/main/scala/reading/component/Index.scala
index 57f5b4b..78890de 100644
--- a/src/main/scala/reading/component/Index.scala
+++ b/src/main/scala/reading/component/Index.scala
@@ -1,34 +1,30 @@
package reading.component
import rx._
-import Ctx.Owner.Unsafe._
-
-import scalatags.JsDom.all._
import scalacss.Defaults._
import scalacss.ScalatagsCss._
+import scalatags.JsDom.all._
-import reading.Books
-import reading.component.style.{ Index => IndexStyle }
import reading.component.index.{ Menu, Header, Books => BooksComponent }
-import reading.models.{ Book, Filter }
+import reading.component.style.{ Index => IndexStyle }
+import reading.models.{ Book, Books, Filter }
object Index {
- def apply(initialFilters: Seq[Filter]): HtmlTag = {
+ def apply(initialFilters: Seq[Filter])(implicit ctx: Ctx.Owner): Frag = {
val filters: Var[Seq[Filter]] = Var(initialFilters)
- val books: Rx[Seq[Book]] = Rx {
- if (filters().isEmpty) Books() else Book.filter(Books(), filters())
- }
- val count: Rx[Int] = Rx(books().length)
+ 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()))
div(
IndexStyle.render,
IndexStyle.page,
- Menu(books, filters, showFiltersMenu),
+ Menu(books, filters, search, showFiltersMenu),
div(
IndexStyle.main,
- Header(filters, showFiltersMenu, count),
- BooksComponent(books)
+ Header(searchedBooks, filters, search, showFiltersMenu),
+ BooksComponent(searchedBooks)
)
)
}
diff --git a/src/main/scala/reading/component/index/BookDetail.scala b/src/main/scala/reading/component/index/BookDetail.scala
index 7df771b..c42029f 100644
--- a/src/main/scala/reading/component/index/BookDetail.scala
+++ b/src/main/scala/reading/component/index/BookDetail.scala
@@ -8,7 +8,7 @@ import reading.component.index.style.{ BookDetail => BookStyle }
import reading.models.{ Program, Book }
object BookDetail {
- def apply(book: Book): HtmlTag =
+ def apply(book: Book): Frag =
div(
BookStyle.render,
BookStyle.detail,
@@ -20,6 +20,8 @@ object BookDetail {
),
div(
+ BookStyle.items,
+
if (book.programs.nonEmpty) {
item("classe", book.programs.map(Program.grade(_).prettyPrint).distinct.sorted)
},
@@ -33,11 +35,14 @@ object BookDetail {
item("genre", book.genres.sorted.map(_.prettyPrint))
},
book.period.map { period =>
- item("période", Seq(period.prettyPrint))
- }
+ item("période", period.prettyPrint)
+ },
+ item("niveau", book.level.prettyPrint)
)
)
+ private def item(key: String, value: String): Frag = item(key, Seq(value))
+
private def item(key: String, values: Seq[String]): Frag =
div(
BookStyle.item,
diff --git a/src/main/scala/reading/component/index/Books.scala b/src/main/scala/reading/component/index/Books.scala
index 20b308d..c22639f 100644
--- a/src/main/scala/reading/component/index/Books.scala
+++ b/src/main/scala/reading/component/index/Books.scala
@@ -1,7 +1,6 @@
package reading.component.index
import rx._
-import Ctx.Owner.Unsafe._
import scalatags.JsDom.all._
import scalacss.Defaults._
@@ -10,16 +9,16 @@ import scalacss.ScalatagsCss._
import reading.component.index.style.{ Books => BooksStyle }
import reading.component.widget.Modal
import reading.models.{ Book }
-import reading.utils.{ RxTag, RxAttr }
+import reading.utils.RxUtils._
object Books {
- def apply(books: Rx[Seq[Book]]): Frag = {
+ def apply(books: Rx[Seq[Book]])(implicit ctx: Ctx.Owner): Frag = {
val focus: Var[Option[Book]] = Var(None)
div(
BooksStyle.render,
- RxTag { implicit context =>
+ Rx {
div(
div(
BooksStyle.books,
@@ -31,13 +30,13 @@ object Books {
BooksStyle.cover,
src := s"cover/${book.title}.jpg",
alt := s"${book.title}, ${book.author}",
- RxAttr(onclick, Rx(() => focus() = Some(book)))
+ onclick := (() => focus() = Some(book))
)
)
}
),
- RxTag { implicit context =>
+ Rx {
focus() match {
case Some(book) => Modal(onClose = focus() = None)(BookDetail(book))
case None => span("")
diff --git a/src/main/scala/reading/component/index/FilterUtils.scala b/src/main/scala/reading/component/index/FilterUtils.scala
new file mode 100644
index 0000000..d4b24e4
--- /dev/null
+++ b/src/main/scala/reading/component/index/FilterUtils.scala
@@ -0,0 +1,39 @@
+package reading.component.index
+
+import rx._
+
+import reading.models._
+import reading.Route
+
+object FilterUtils {
+ def remove(
+ filters: Var[Seq[Filter]],
+ search: Var[String],
+ filter: Filter
+ ): Unit = {
+ val newFilters = Filter.remove(filters.now, filter)
+ filters() = newFilters
+ search() = ""
+ Route.push(Route.Books(newFilters))
+ }
+
+ def removeAll(
+ filters: Var[Seq[Filter]],
+ search: Var[String]
+ ): Unit = {
+ filters() = Nil
+ search() = ""
+ Route.push(Route.Books(Nil))
+ }
+
+ def add(
+ filters: Var[Seq[Filter]],
+ search: Var[String],
+ filter: Filter
+ ): Unit = {
+ val newFilters = filter +: filters.now
+ filters() = newFilters
+ search() = ""
+ Route.push(Route.Books(newFilters))
+ }
+}
diff --git a/src/main/scala/reading/component/index/Header.scala b/src/main/scala/reading/component/index/Header.scala
index cf078ad..50d520e 100644
--- a/src/main/scala/reading/component/index/Header.scala
+++ b/src/main/scala/reading/component/index/Header.scala
@@ -1,32 +1,39 @@
package reading.component.index
import rx._
-import Ctx.Owner.Unsafe._
import scalatags.JsDom.all._
import scalacss.Defaults._
import scalacss.ScalatagsCss._
import reading.component.index.style.{ Header => HeaderStyle }
-import reading.component.widget.Cross
+import reading.component.widget.{ Cross, Input }
import reading.component.style.{ Color => C }
-import reading.models.Filter
-import reading.Route
-import reading.utils.{ RxTag, RxAttr }
+import reading.models.{ Book, Filter }
+import reading.utils.RxUtils._
object Header {
- def apply(filters: Var[Seq[Filter]], showFiltersMenu: Var[Boolean], booksCount: Rx[Int]): Frag = {
+ def apply(
+ books: Rx[Seq[Book]],
+ filters: Var[Seq[Filter]],
+ 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,
- RxTag { implicit context =>
+ Rx {
div(
div(
HeaderStyle.showFiltersMenu,
- RxAttr(onclick, Rx(() => showFiltersMenu() = true)),
+ onclick := (() => showFiltersMenu() = true),
"Filtrer",
if (filtersCount() > 0) span(HeaderStyle.filtersCount, filtersCount()) else span("")
),
@@ -39,14 +46,14 @@ object Header {
div(
HeaderStyle.clear,
- RxAttr(onclick, Rx(() => updateFilters(filters, Nil))),
+ onclick := (() => FilterUtils.removeAll(filters, search)),
"Effacer les filtres"
),
filters().sortBy(_.name).map { filter =>
div(
HeaderStyle.filter,
- RxAttr(onclick, Rx(() => updateFilters(filters, Filter.remove(filters(), filter)))),
+ onclick := (() => FilterUtils.remove(filters, search, filter)),
span(HeaderStyle.name, filter.name.capitalize),
Cross(15.px, C.black.value)
)
@@ -55,17 +62,16 @@ object Header {
)
},
- RxTag { implicit context =>
- div(
- HeaderStyle.booksCount,
- span(s"${booksCount()} livre${if (booksCount() > 1) "s" else ""}")
- )
- }
+ div(
+ HeaderStyle.searchAndCount,
+ Input(HeaderStyle.search, search, "Rechercher"),
+ Rx {
+ div(
+ HeaderStyle.booksCount,
+ span(s"${booksCount()} livre${if (booksCount() > 1) "s" else ""}")
+ )
+ }
+ )
)
}
-
- private def updateFilters(filters: Var[Seq[Filter]], newFilters: Seq[Filter]): Unit = {
- filters() = newFilters
- Route.push(Route.Books(newFilters))
- }
}
diff --git a/src/main/scala/reading/component/index/Menu.scala b/src/main/scala/reading/component/index/Menu.scala
index a0aabd1..4c118bd 100644
--- a/src/main/scala/reading/component/index/Menu.scala
+++ b/src/main/scala/reading/component/index/Menu.scala
@@ -1,7 +1,6 @@
package reading.component.index
import rx._
-import Ctx.Owner.Unsafe._
import scalatags.JsDom.all._
import scalacss.Defaults._
@@ -9,45 +8,60 @@ import scalacss.ScalatagsCss._
import reading.component.index.style.{ Menu => MenuStyle }
import reading.models._
-import reading.utils.{ RxTag, RxAttr }
-import reading.Route
+import reading.utils.RxUtils._
object Menu {
- def apply(books: Rx[Seq[Book]], filters: Var[Seq[Filter]], showFiltersMenu: Var[Boolean]): Frag =
- RxTag { implicit context =>
+ def apply(
+ books: Rx[Seq[Book]],
+ filters: Var[Seq[Filter]],
+ search: Var[String],
+ showFiltersMenu: Var[Boolean]
+ )(
+ implicit
+ ctx: Ctx.Owner
+ ): Frag =
+ div(
+ MenuStyle.render,
+ Rx(if (showFiltersMenu()) MenuStyle.show else MenuStyle.empty),
+ MenuStyle.menu,
+
+ div(MenuStyle.background),
+
div(
- MenuStyle.render,
- if (showFiltersMenu()) MenuStyle.show else "",
- MenuStyle.menu,
+ MenuStyle.content,
- header(showFiltersMenu, filters().length),
+ Rx(header(showFiltersMenu, filters().length)),
div(
MenuStyle.groups,
- filters().find(_.kind == FilterKind.Grade) match {
- case Some(grade) => {
- val programs = Program.values.filter(p => Program.grade(p).toString() == grade.nonFormattedName)
- group(books, filters, grade.name, programs.map(Filter.apply(_)), Some(grade))
+ 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(_)))
}
- case None => group(books, filters, "Classe", Grade.values.map(Filter.apply(_)))
},
- filters().find(_.kind == FilterKind.GroupedTheme) match {
- case Some(groupedTheme) => {
- val themes = Theme.values.filter(t => Theme.groupedTheme(t).toString() == groupedTheme.nonFormattedName)
- group(books, filters, groupedTheme.name, themes.map(Filter.apply(_)), Some(groupedTheme))
+ 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(_)))
}
- case None => group(books, filters, "Theme", GroupedTheme.values.map(Filter.apply(_)))
},
- group(books, filters, "Genre", Genre.values.sorted.map(Filter.apply(_))),
- group(books, filters, "Niveau", Level.values.map(Filter.apply(_))),
- group(books, filters, "Période", Period.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(_)))
),
- footer(Rx(books().length), filters, showFiltersMenu)
+ footer(books, filters, search, showFiltersMenu)
)
- }
+ )
- def header(showFiltersMenu: Var[Boolean], count: Int): HtmlTag =
+ def header(showFiltersMenu: Var[Boolean], count: Int): Frag =
div(
MenuStyle.header,
"Filtrer",
@@ -57,78 +71,84 @@ object Menu {
def group(
books: Rx[Seq[Book]],
filters: Var[Seq[Filter]],
+ search: Var[String],
name: String,
groupFilters: Seq[Filter],
parentFilter: Option[Filter] = None
)(
implicit
- context: Ctx.Data
+ ctx: Ctx.Owner
): Frag = {
val filtersWithCount = Rx {
groupFilters
- .map(filter => (filter, Book.filter(books(), Seq(filter)).length))
+ .map(filter => (filter, Filter.add(books(), filter).length))
.filter(_._2 > 0)
}
- if (filtersWithCount().isEmpty)
- span("")
- else
- div(
- MenuStyle.filterGroup,
+ Rx {
+ if (filtersWithCount().isEmpty)
+ span("")
+ else
div(
- MenuStyle.filterTitle,
- parentFilter.map { filter =>
- RxAttr(onclick, Rx(() => updateFilters(filters, Filter.remove(filters(), filter))))
- }.getOrElse(""),
- if (parentFilter.isDefined) MenuStyle.activeFilter else "",
- name,
- RxTag { implicit context =>
- val count = filters().filter(f => groupFilters.exists(Filter.equals(f, _))).length
- if (count > 0) span(MenuStyle.filterTitleCount, count) else span("")
- }
- ),
- div(
- filtersWithCount().map {
- case (filter, count) => {
- val isActive = Filter.contains(filters(), filter)
+ div(
+ MenuStyle.filterTitle,
+ parentFilter.map { filter =>
+ onclick := (() => FilterUtils.remove(filters, search, filter))
+ }.getOrElse(""),
+ if (parentFilter.isDefined) MenuStyle.activeFilter else "",
+ name,
+ Rx {
+ val count = filters().filter(f => groupFilters.exists(f == _)).length
+ if (count > 0) span(MenuStyle.filterTitleCount, count) else span("")
+ }
+ ),
+ div(
+ filtersWithCount().map {
+ case (filter, count) => {
+ val isActive = Filter.contains(filters(), filter)
- button(
- MenuStyle.filter,
- if (isActive) MenuStyle.activeFilter else "",
- RxAttr(onclick, Rx(() => updateFilters(
- filters,
- if (isActive) Filter.remove(filters(), filter) else filter +: filters()
- ))),
- span(
- span(filter.name.capitalize),
- span(MenuStyle.filterCount, count)
+ button(
+ MenuStyle.filter,
+ if (isActive) MenuStyle.activeFilter else "",
+ onclick := (() =>
+ if (isActive)
+ FilterUtils.remove(filters, search, filter)
+ else
+ FilterUtils.add(filters, search, filter)),
+ span(
+ span(filter.name.capitalize),
+ span(MenuStyle.filterCount, count)
+ )
)
- )
+ }
}
- }
+ )
)
- )
- }
-
- private def updateFilters(filters: Var[Seq[Filter]], newFilters: Seq[Filter]): Unit = {
- filters() = newFilters
- Route.push(Route.Books(newFilters))
+ }
}
- def footer(bookCount: Rx[Int], filters: Var[Seq[Filter]], showFiltersMenu: Var[Boolean]): HtmlTag =
+ def footer(
+ books: Rx[Seq[Book]],
+ filters: Var[Seq[Filter]],
+ search: Var[String],
+ showFiltersMenu: Var[Boolean]
+ )(
+ implicit
+ ctx: Ctx.Owner
+ ): Frag =
div(
MenuStyle.footer,
div(
MenuStyle.clear,
- RxAttr(onclick, Rx(() => filters() = Nil)),
+ onclick := (() => FilterUtils.removeAll(filters, search)),
"Effacer"
),
div(
MenuStyle.returnToBooks,
- RxAttr(onclick, Rx(() => showFiltersMenu() = false)),
+ onclick := (() => showFiltersMenu() = false),
"Afficher",
- RxTag { implicit context =>
- span(MenuStyle.bookCount, bookCount())
+ Rx {
+ 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 e8f970e..f432fda 100644
--- a/src/main/scala/reading/component/index/style/BookDetail.scala
+++ b/src/main/scala/reading/component/index/style/BookDetail.scala
@@ -18,6 +18,10 @@ object BookDetail extends StyleSheet.Inline {
marginBottom(30.px)
)
+ val items = style(
+ marginBottom(25.px)
+ )
+
val item = style(
lineHeight(25.px),
margin(0.px, 15.px, 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 2260c91..2eb6eb2 100644
--- a/src/main/scala/reading/component/index/style/Header.scala
+++ b/src/main/scala/reading/component/index/style/Header.scala
@@ -66,6 +66,18 @@ object Header extends StyleSheet.Inline {
height(15.px)
)
+ val searchAndCount = style(
+ display.flex,
+ flexWrap.wrap,
+ alignItems.center,
+ Media.mobile(justifyContent.center)
+ )
+
+ val search = style(
+ Media.mobile(display.none),
+ Media.desktop(marginRight(30.px))
+ )
+
val booksCount = style(
fontSize(20.px),
color(C.gray.value),
diff --git a/src/main/scala/reading/component/index/style/Menu.scala b/src/main/scala/reading/component/index/style/Menu.scala
index 12b0646..dd74039 100644
--- a/src/main/scala/reading/component/index/style/Menu.scala
+++ b/src/main/scala/reading/component/index/style/Menu.scala
@@ -10,9 +10,27 @@ object Menu extends StyleSheet.Inline {
val menu = style(
Media.mobile(display.none),
- color(C.white.value),
+ Media.desktop(
+ color(C.white.value),
+ position.relative,
+ width(280.px)
+ )
+ )
+
+ val background = style(
+ Media.desktop(
+ position.fixed,
+ width(280.px),
+ height(100.%%),
+ backgroundColor(C.englishWalnut.value),
+ boxShadow := "4px 0px 6px -1px rgba(0, 0, 0, 0.2)"
+ )
+ )
+
+ val content = style(
position.relative,
- width(280.px)
+ width(100.%%),
+ height(100.%%)
)
val header = style(
@@ -50,6 +68,8 @@ object Menu extends StyleSheet.Inline {
)
)
+ val empty = style()
+
val groups = style(
Media.mobile(
height :=! "calc(100% - 120px)",
@@ -57,8 +77,6 @@ object Menu extends StyleSheet.Inline {
)
)
- val filterGroup = style()
-
val filterTitle = style(
Commons.filter(),
minHeight(50.px),
diff --git a/src/main/scala/reading/component/style/Index.scala b/src/main/scala/reading/component/style/Index.scala
index 99e4746..e02ebd9 100644
--- a/src/main/scala/reading/component/style/Index.scala
+++ b/src/main/scala/reading/component/style/Index.scala
@@ -3,7 +3,6 @@ package reading.component.style
import scalacss.Defaults._
import reading.Media
-import reading.component.style.{ Color => C }
object Index extends StyleSheet.Inline {
import dsl._
@@ -11,22 +10,11 @@ object Index extends StyleSheet.Inline {
val page = style(
display.flex,
overflowY.scroll,
- height(100.%%),
-
- Media.desktop(
- &.before(
- content := "\"\"",
- display.block,
- position.fixed,
- width(280.px),
- height(100.%%),
- backgroundColor(C.englishWalnut.value),
- boxShadow := "4px 0px 6px -1px rgba(0, 0, 0, 0.2)"
- )
- )
+ height(100.%%)
)
val main = style(
- width(100.%%)
+ Media.desktop(width :=! "calc(100% - 280px)"),
+ Media.mobile(width(100.%%))
)
}
diff --git a/src/main/scala/reading/component/widget/Cross.scala b/src/main/scala/reading/component/widget/Cross.scala
index c9e3054..40087a1 100644
--- a/src/main/scala/reading/component/widget/Cross.scala
+++ b/src/main/scala/reading/component/widget/Cross.scala
@@ -8,7 +8,7 @@ import scalacss.internal.ValueT, ValueT.Color
import reading.component.widget.style.{ Cross => CrossStyle }
object Cross {
- def apply(size: String, color: ValueT[Color]): HtmlTag =
+ def apply(size: String, color: ValueT[Color]): Frag =
div(
CrossStyle.render,
CrossStyle.cross,
diff --git a/src/main/scala/reading/component/widget/Input.scala b/src/main/scala/reading/component/widget/Input.scala
new file mode 100644
index 0000000..7dac47a
--- /dev/null
+++ b/src/main/scala/reading/component/widget/Input.scala
@@ -0,0 +1,44 @@
+package reading.component.widget
+
+import scalatags.JsDom.all._
+
+import org.scalajs.dom.KeyboardEvent
+import org.scalajs.dom.html.Input
+
+import scalacss.Defaults._
+import scalacss.ScalatagsCss._
+
+import rx._
+
+import reading.component.widget.style.{ Input => InputStyle }
+
+object Input {
+ def apply(
+ style: StyleA,
+ query: Var[String],
+ label: String = "",
+ onEnter: => Unit = ()
+ )(
+ 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
+ }
+ ).render
+
+ query.trigger {
+ inputBox.value = query.now
+ }
+
+ inputBox
+ }
+}
diff --git a/src/main/scala/reading/component/widget/Modal.scala b/src/main/scala/reading/component/widget/Modal.scala
index 81d0c78..db1f7e6 100644
--- a/src/main/scala/reading/component/widget/Modal.scala
+++ b/src/main/scala/reading/component/widget/Modal.scala
@@ -3,29 +3,27 @@ package reading.component.widget
import scala.util.Random
import org.scalajs.dom.raw.HTMLElement
-import rx._
-import rx.Ctx.Owner.Unsafe._
import scalacss.Defaults._
import scalacss.ScalatagsCss._
import scalatags.JsDom.all._
import reading.component.widget.style.{ Modal => ModalStyle }
-import reading.utils.{ RxAttr }
object Modal {
- def apply(onClose: => Unit)(content: HtmlTag): HtmlTag = {
+ 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})"
+ 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(
@@ -35,7 +33,7 @@ object Modal {
div(
ModalStyle.curtain,
- RxAttr(onclick, Rx(() => close(modalId, onClose)))
+ onclick := (() => close(modalId, onClose))
),
div(
@@ -43,7 +41,7 @@ object Modal {
content,
button(
ModalStyle.close,
- RxAttr(onclick, Rx(() => close(modalId, onClose))),
+ onclick := (() => close(modalId, onClose)),
"Fermer"
)
)
diff --git a/src/main/scala/reading/component/widget/style/Input.scala b/src/main/scala/reading/component/widget/style/Input.scala
new file mode 100644
index 0000000..967393b
--- /dev/null
+++ b/src/main/scala/reading/component/widget/style/Input.scala
@@ -0,0 +1,16 @@
+package reading.component.widget.style
+
+import scalacss.Defaults._
+
+import reading.component.style.{ Color => C }
+
+object Input extends StyleSheet.Inline {
+ import dsl._
+
+ val input = style(
+ border(1.px, solid, C.mickado.value),
+ borderRadius(2.px),
+ padding(10.px),
+ &.hover(borderColor(C.gray.value))
+ )
+}
diff --git a/src/main/scala/reading/component/widget/style/Modal.scala b/src/main/scala/reading/component/widget/style/Modal.scala
index 1872344..faf325d 100644
--- a/src/main/scala/reading/component/widget/style/Modal.scala
+++ b/src/main/scala/reading/component/widget/style/Modal.scala
@@ -55,8 +55,11 @@ object Modal extends StyleSheet.Inline {
)
val close = style(
- Button.simple,
- marginTop(20.px),
- marginBottom(30.px)
+ 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 7d72f23..6f4d8dd 100644
--- a/src/main/scala/reading/models/Book.scala
+++ b/src/main/scala/reading/models/Book.scala
@@ -16,6 +16,9 @@ case class Book(
}
object Book {
- def filter(books: Seq[Book], filters: Seq[Filter]): Seq[Book] =
- books.filter(b => filters.forall(_.filter(b)))
+ def filter(books: Seq[Book], search: String = ""): Seq[Book] =
+ books.filter { book =>
+ (Search(book.title, search)
+ || Search(book.author, search))
+ }
}
diff --git a/src/main/scala/reading/Books.scala b/src/main/scala/reading/models/Books.scala
index eb4722a..43ed2b8 100644
--- a/src/main/scala/reading/Books.scala
+++ b/src/main/scala/reading/models/Books.scala
@@ -1,6 +1,5 @@
-package reading
+package reading.models
-import reading.models.{ Book, Period, Theme, Genre, Program, Level }
import Period._
import Theme._
import Genre._
@@ -1090,7 +1089,7 @@ object Books {
),
Book(
- title = "Caïus",
+ title = "L’affaire Caïus",
author = "Henry WINTERFELD",
year = "2014",
parts = 2,
diff --git a/src/main/scala/reading/models/Filter.scala b/src/main/scala/reading/models/Filter.scala
index d14ca63..7ec6340 100644
--- a/src/main/scala/reading/models/Filter.scala
+++ b/src/main/scala/reading/models/Filter.scala
@@ -5,6 +5,16 @@ trait Filter {
def kind: FilterKind
def nonFormattedName: String
def name: String
+
+ override def equals(that: Any): Boolean =
+ that match {
+ case that: Filter =>
+ this.kind == that.kind && this.name == that.name
+ case _ =>
+ false
+ }
+
+ override def hashCode: Int = this.kind.hashCode + this.nonFormattedName.hashCode
}
object Filter {
@@ -23,15 +33,33 @@ object Filter {
}
def contains(filters: Seq[Filter], filter: Filter): Boolean =
- filters.find(equals(_, filter)).nonEmpty
-
- def equals(f1: Filter, f2: Filter): Boolean =
- f1.kind == f2.kind && f1.name == f2.name
+ filters.find(_ == filter).nonEmpty
def remove(fs: Seq[Filter], rf: Filter): Seq[Filter] =
fs.filterNot { f =>
- (equals(f, rf)
+ (f == rf
|| rf.kind == FilterKind.Grade && f.kind == FilterKind.Program
|| rf.kind == FilterKind.GroupedTheme && f.kind == FilterKind.Theme)
}
+
+ val onBooks: Map[Filter, Seq[Book]] =
+ Seq(
+ Grade.values.map(Filter.apply(_)),
+ Program.values.map(Filter.apply(_)),
+ Theme.values.map(Filter.apply(_)),
+ GroupedTheme.values.map(Filter.apply(_)),
+ Genre.values.map(Filter.apply(_)),
+ Level.values.map(Filter.apply(_)),
+ Period.values.map(Filter.apply(_))
+ )
+ .flatten
+ .map(f => (f, Books().filter(f.filter)))
+ .toMap
+
+ def add(books: Seq[Book], filters: Seq[Filter]): Seq[Book] =
+ filters.foldLeft(books)(add)
+
+ def add(books: Seq[Book], filter: Filter): Seq[Book] =
+ books.intersect(onBooks.getOrElse(filter, Nil))
+
}
diff --git a/src/main/scala/reading/models/Search.scala b/src/main/scala/reading/models/Search.scala
new file mode 100644
index 0000000..5ef97c1
--- /dev/null
+++ b/src/main/scala/reading/models/Search.scala
@@ -0,0 +1,16 @@
+package reading.models
+
+object Search {
+ def apply(text: String, search: String): Boolean =
+ format(text).contains(format(search))
+
+ private def format(str: String): String =
+ str
+ .toLowerCase
+ .replace('’', '\'')
+ .replaceAll("[èéêë]", "e")
+ .replaceAll("[ûù]", "u")
+ .replaceAll("[ïî]", "i")
+ .replaceAll("[àâ]", "a")
+ .replaceAll("ô", "o")
+}
diff --git a/src/main/scala/reading/utils/Rx.scala b/src/main/scala/reading/utils/Rx.scala
index 76d05eb..a5b56ee 100644
--- a/src/main/scala/reading/utils/Rx.scala
+++ b/src/main/scala/reading/utils/Rx.scala
@@ -1,44 +1,85 @@
package reading.utils
+import java.util.concurrent.atomic.AtomicReference
+
+import scala.annotation.tailrec
+import scala.language.implicitConversions
import scala.util.{ Failure, Success }
import org.scalajs.dom.Element
-
-import scalatags.JsDom.all._
import rx._
+import scalacss.Defaults.StyleA
+import scalatags.JsDom.all._
-import Ctx.Owner.Unsafe._
+object RxUtils {
-object RxTag {
- def apply(r: Ctx.Data => HtmlTag): HtmlTag =
- rxMod(Rx(r(implicitly[Ctx.Data])))
+ implicit def rxFrag[T](n: Rx[T])(implicit f: T => Frag, ctx: Ctx.Owner): Frag = {
- private def rxMod(r: Rx[HtmlTag]): HtmlTag = {
- def rSafe = r.toTry match {
- case Success(v) => v.render
- case Failure(e) => span(e.toString, backgroundColor := "red").render
+ @tailrec def clearChildren(node: org.scalajs.dom.Node): Unit = {
+ if (node.firstChild != null) {
+ node.removeChild(node.firstChild)
+ clearChildren(node)
+ }
}
- var last = rSafe
- r.trigger {
- val newLast = rSafe
- Option(last.parentElement).foreach {
- _.replaceChild(newLast, last)
+
+ def fSafe: Frag = n match {
+ case r: Rx.Dynamic[T] => r.toTry match {
+ case Success(v) => v.render
+ case Failure(e) => span(e.getMessage, backgroundColor := "red").render
}
+ case v: Var[T] => v.now.render
+ }
+
+ var last = fSafe.render
+
+ val container = span(last).render
+
+ n.triggerLater {
+ val newLast = fSafe.render
+ //Rx[Seq[T]] can generate multiple children per propagate, so use clearChildren instead of replaceChild
+ clearChildren(container)
+ container.appendChild(newLast)
last = newLast
}
- span(
- bindNode(last)
- )
+ bindNode(container)
}
-}
-object RxAttr {
- def apply[Builder, T: AttrValue](attr: scalatags.generic.Attr, v: Rx[T]) = {
- val attrValue = new AttrValue[Rx[T]] {
- def apply(t: Element, a: Attr, r: Rx[T]): Unit = {
- val _ = r.trigger { implicitly[AttrValue[T]].apply(t, a, r.now) }
+ implicit def RxAttrValue[T: AttrValue](implicit ctx: Ctx.Owner) = new AttrValue[Rx.Dynamic[T]] {
+ def apply(t: Element, a: Attr, r: Rx.Dynamic[T]): Unit = {
+ r.trigger { implicitly[AttrValue[T]].apply(t, a, r.now) }
+ ()
+ }
+ }
+
+ implicit def RxStyleValue[T: StyleValue](implicit ctx: Ctx.Owner) = new StyleValue[Rx.Dynamic[T]] {
+ def apply(t: Element, s: Style, r: Rx.Dynamic[T]): Unit = {
+ r.trigger { implicitly[StyleValue[T]].apply(t, s, r.now) }
+ ()
+ }
+ }
+
+ implicit class bindRxStyle(rx: Rx[StyleA])(implicit ctx: Ctx.Owner) extends Modifier {
+ def applyTo(container: Element) = {
+ val atomicReference = new AtomicReference(rx.now)
+ applyStyle(container, atomicReference.get())
+ rx.triggerLater {
+ val current = rx.now
+ val previous = atomicReference.getAndSet(current)
+ removeStyle(container, previous)
+ applyStyle(container, current)
+ ()
}
+ ()
}
- scalatags.generic.AttrPair(attr, v, attrValue)
+
+ private def removeStyle(container: Element, style: StyleA): Unit =
+ style.classNameIterator.foreach { className =>
+ container.classList.remove(className.value)
+ }
+
+ private def applyStyle(container: Element, style: StyleA): Unit =
+ style.classNameIterator.foreach { className =>
+ container.classList.add(className.value)
+ }
}
}