From b780f6f660cc5b2ff2b1ca3884871002823256a1 Mon Sep 17 00:00:00 2001
From: Joris
Date: Sun, 27 Nov 2016 18:38:58 +0100
Subject: Init
---
src/main/resources/index-dev.html | 13 ++++++
src/main/resources/index-prod.html | 13 ++++++
src/main/resources/reset.css | 51 ++++++++++++++++++++++
src/main/scala/reading/Main.scala | 19 ++++++++
src/main/scala/reading/component/Index.scala | 42 ++++++++++++++++++
src/main/scala/reading/component/index/Books.scala | 33 ++++++++++++++
.../scala/reading/component/index/Filters.scala | 33 ++++++++++++++
.../reading/component/index/FiltersMenu.scala | 48 ++++++++++++++++++++
.../reading/component/index/style/Books.scala | 31 +++++++++++++
.../reading/component/index/style/Filters.scala | 21 +++++++++
.../component/index/style/FiltersMenu.scala | 24 ++++++++++
src/main/scala/reading/component/style/Color.scala | 16 +++++++
.../scala/reading/component/style/Global.scala | 28 ++++++++++++
src/main/scala/reading/component/style/Index.scala | 26 +++++++++++
src/main/scala/reading/models/Book.scala | 21 +++++++++
src/main/scala/reading/models/Filter.scala | 46 +++++++++++++++++++
src/main/scala/reading/models/Genre.scala | 21 +++++++++
src/main/scala/reading/models/Theme.scala | 21 +++++++++
src/main/scala/reading/utils/Rx.scala | 44 +++++++++++++++++++
19 files changed, 551 insertions(+)
create mode 100644 src/main/resources/index-dev.html
create mode 100644 src/main/resources/index-prod.html
create mode 100644 src/main/resources/reset.css
create mode 100644 src/main/scala/reading/Main.scala
create mode 100644 src/main/scala/reading/component/Index.scala
create mode 100644 src/main/scala/reading/component/index/Books.scala
create mode 100644 src/main/scala/reading/component/index/Filters.scala
create mode 100644 src/main/scala/reading/component/index/FiltersMenu.scala
create mode 100644 src/main/scala/reading/component/index/style/Books.scala
create mode 100644 src/main/scala/reading/component/index/style/Filters.scala
create mode 100644 src/main/scala/reading/component/index/style/FiltersMenu.scala
create mode 100644 src/main/scala/reading/component/style/Color.scala
create mode 100644 src/main/scala/reading/component/style/Global.scala
create mode 100644 src/main/scala/reading/component/style/Index.scala
create mode 100644 src/main/scala/reading/models/Book.scala
create mode 100644 src/main/scala/reading/models/Filter.scala
create mode 100644 src/main/scala/reading/models/Genre.scala
create mode 100644 src/main/scala/reading/models/Theme.scala
create mode 100644 src/main/scala/reading/utils/Rx.scala
(limited to 'src')
diff --git a/src/main/resources/index-dev.html b/src/main/resources/index-dev.html
new file mode 100644
index 0000000..f9b17e7
--- /dev/null
+++ b/src/main/resources/index-dev.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ reading
+
+
+
+
+
+
+
diff --git a/src/main/resources/index-prod.html b/src/main/resources/index-prod.html
new file mode 100644
index 0000000..93ae2f0
--- /dev/null
+++ b/src/main/resources/index-prod.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+ reading
+
+
+
+
+
+
+
diff --git a/src/main/resources/reset.css b/src/main/resources/reset.css
new file mode 100644
index 0000000..636e49f
--- /dev/null
+++ b/src/main/resources/reset.css
@@ -0,0 +1,51 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ * v2.0 | 20110126
+ * License: none (public domain)
+ */
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+a {
+ text-decoration: none;
+}
diff --git a/src/main/scala/reading/Main.scala b/src/main/scala/reading/Main.scala
new file mode 100644
index 0000000..09bd76c
--- /dev/null
+++ b/src/main/scala/reading/Main.scala
@@ -0,0 +1,19 @@
+package reading
+
+import scala.scalajs.js.JSApp
+
+import org.scalajs.dom
+
+import scalacss.Defaults._
+
+import reading.component.style.{Global => GlobalStyle}
+
+object Main extends JSApp {
+ def main(): Unit = {
+ val style = dom.document.createElement("style")
+ style.appendChild(dom.document.createTextNode(GlobalStyle.render))
+ dom.document.head.appendChild(style)
+
+ val _ = dom.document.body.appendChild(component.Index().render)
+ }
+}
diff --git a/src/main/scala/reading/component/Index.scala b/src/main/scala/reading/component/Index.scala
new file mode 100644
index 0000000..28d9081
--- /dev/null
+++ b/src/main/scala/reading/component/Index.scala
@@ -0,0 +1,42 @@
+package reading.component
+
+import rx._
+import Ctx.Owner.Unsafe._
+
+import scalatags.JsDom.all._
+import scalacss.Defaults._
+import scalacss.ScalatagsCss._
+
+import reading.component.style.{Index => IndexStyle}
+import reading.component.index.{FiltersMenu, Filters, Books}
+import reading.models.{Book, Filter}
+import reading.utils.RxAttr
+
+object Index {
+ def apply(): Frag = {
+ val filters: Var[Seq[Filter]] = Var(Nil)
+ val books: Rx[Seq[Book]] = Rx {
+ if(filters().isEmpty) Book.all else Book.filter(Book.all, filters())
+ }
+
+ div(
+ IndexStyle.render,
+
+ button(
+ IndexStyle.header,
+ RxAttr(onclick, Rx(() => filters() = Nil)),
+ "Conseils de lecture"
+ ),
+
+ div(
+ IndexStyle.page,
+ FiltersMenu(books, filters),
+ div(
+ IndexStyle.main,
+ Filters(filters),
+ Books(books)
+ )
+ )
+ )
+ }
+}
diff --git a/src/main/scala/reading/component/index/Books.scala b/src/main/scala/reading/component/index/Books.scala
new file mode 100644
index 0000000..6ce1b2b
--- /dev/null
+++ b/src/main/scala/reading/component/index/Books.scala
@@ -0,0 +1,33 @@
+package reading.component.index
+
+import rx._
+
+import scalatags.JsDom.all._
+import scalacss.Defaults._
+import scalacss.ScalatagsCss._
+
+import reading.component.index.style.{Books => BooksStyle}
+import reading.models.Book
+import reading.utils.RxTag
+
+object Books {
+ def apply(books: Rx[Seq[Book]]): Frag =
+ div(
+ BooksStyle.render,
+ BooksStyle.books,
+
+ RxTag { implicit context =>
+ div(
+ books().sortBy(_.title).map { book =>
+ div(
+ BooksStyle.book,
+ div(BooksStyle.title, book.title),
+ div(BooksStyle.author, book.author),
+ div(BooksStyle.genres, book.genres.mkString(", ")),
+ div(BooksStyle.themes, book.themes.mkString(", "))
+ )
+ }
+ )
+ }
+ )
+}
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..1d9cc93
--- /dev/null
+++ b/src/main/scala/reading/component/index/Filters.scala
@@ -0,0 +1,33 @@
+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.{Filters => FiltersStyle}
+import reading.models.Filter
+import reading.utils.{RxTag, RxAttr}
+
+object Filters {
+ def apply(filters: Var[Seq[Filter]]): Frag =
+ RxTag { implicit context =>
+ if(filters().isEmpty)
+ span("")
+ else
+ div(
+ FiltersStyle.render,
+ FiltersStyle.filters,
+
+ filters().sortBy(_.name).map { filter =>
+ button(
+ FiltersStyle.filter,
+ RxAttr(onclick, Rx(() => filters() = filters().filter(!Filter.equals(_, filter)))),
+ filter.name
+ )
+ }
+ )
+ }
+}
diff --git a/src/main/scala/reading/component/index/FiltersMenu.scala b/src/main/scala/reading/component/index/FiltersMenu.scala
new file mode 100644
index 0000000..880c3e7
--- /dev/null
+++ b/src/main/scala/reading/component/index/FiltersMenu.scala
@@ -0,0 +1,48 @@
+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.{FiltersMenu => FiltersMenuStyle}
+import reading.models.{Book, Filter, Genre, Theme, FilterFactory}
+import reading.utils.{RxTag, RxAttr}
+
+object FiltersMenu {
+ def apply(books: Rx[Seq[Book]], filters: Var[Seq[Filter]]): Frag =
+ div(
+ FiltersMenuStyle.render,
+ group(books, filters, "Genre", Genre.values),
+ group(books, filters, "Theme", Theme.values)
+ )
+
+ def group[T: FilterFactory](books: Rx[Seq[Book]], filters: Var[Seq[Filter]], name: String, groupFilters: Seq[T]): Frag = {
+ val filtersWithCount = Rx {
+ groupFilters
+ .filter(filter => !Filter.contains(filters(), Filter(filter)))
+ .map(filter => (filter, Book.filter(books(), Filter(filter) +: filters()).length))
+ .filter(_._2 > 0)
+ }
+
+ div(
+ FiltersMenuStyle.group,
+
+ div(FiltersMenuStyle.groupTitle, name),
+
+ RxTag { implicit context =>
+ div(
+ filtersWithCount().map { case (filter, count) =>
+ button(
+ FiltersMenuStyle.filter,
+ RxAttr(onclick, Rx(() => filters() = Filter(filter) +: filters())),
+ span(s"${filter.toString} ($count)")
+ )
+ }
+ )
+ }
+ )
+ }
+}
diff --git a/src/main/scala/reading/component/index/style/Books.scala b/src/main/scala/reading/component/index/style/Books.scala
new file mode 100644
index 0000000..2c0dfc0
--- /dev/null
+++ b/src/main/scala/reading/component/index/style/Books.scala
@@ -0,0 +1,31 @@
+package reading.component.index.style
+
+import scalacss.Defaults._
+
+import reading.component.style.Col
+
+object Books extends StyleSheet.Inline {
+ import dsl._
+
+ val books = style(
+ )
+
+ val book = style(
+ marginBottom(30.px)
+ )
+
+ val title = style(
+ fontWeight.bold,
+ marginBottom(10.px),
+ color(Col.congoBrown)
+ )
+
+ val author = style(
+ )
+
+ val genres = style(
+ )
+
+ val themes = style(
+ )
+}
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..c2d0aaf
--- /dev/null
+++ b/src/main/scala/reading/component/index/style/Filters.scala
@@ -0,0 +1,21 @@
+package reading.component.index.style
+
+import scalacss.Defaults._
+
+import reading.component.style.Col
+
+object Filters extends StyleSheet.Inline {
+ import dsl._
+
+ val filters = style(
+ marginBottom(30.px),
+ display.flex
+ )
+
+ val filter = style(
+ backgroundColor(Col.gray),
+ color(Col.white),
+ padding(5.px, 10.px),
+ marginRight(10.px)
+ )
+}
diff --git a/src/main/scala/reading/component/index/style/FiltersMenu.scala b/src/main/scala/reading/component/index/style/FiltersMenu.scala
new file mode 100644
index 0000000..9fd50f0
--- /dev/null
+++ b/src/main/scala/reading/component/index/style/FiltersMenu.scala
@@ -0,0 +1,24 @@
+package reading.component.index.style
+
+import scalacss.Defaults._
+
+import reading.component.style.Col
+
+object FiltersMenu extends StyleSheet.Inline {
+ import dsl._
+
+ val group = style(
+ marginBottom(30.px)
+ )
+
+ val groupTitle = style(
+ color(Col.congoBrown),
+ fontWeight.bold,
+ textTransform.uppercase,
+ padding(10.px, 30.px, 15.px)
+ )
+
+ val filter = style(
+ padding(10.px, 30.px)
+ )
+}
diff --git a/src/main/scala/reading/component/style/Color.scala b/src/main/scala/reading/component/style/Color.scala
new file mode 100644
index 0000000..b9f9cf4
--- /dev/null
+++ b/src/main/scala/reading/component/style/Color.scala
@@ -0,0 +1,16 @@
+package reading.component.style
+
+import scalacss.Defaults._
+
+// http://chir.ag/projects/name-that-color
+object Col extends StyleSheet.Inline {
+ import dsl._
+
+ val black = c"#000000"
+ val white = c"#FFFFFF"
+ val gray = c"#7E7E7E"
+ val eastBay = c"#505080"
+ val tawnyPort = c"#7F2447"
+ val cosmic = c"#683649"
+ val congoBrown = c"#57363E"
+}
diff --git a/src/main/scala/reading/component/style/Global.scala b/src/main/scala/reading/component/style/Global.scala
new file mode 100644
index 0000000..276a30d
--- /dev/null
+++ b/src/main/scala/reading/component/style/Global.scala
@@ -0,0 +1,28 @@
+package reading.component.style
+
+import scalacss.Defaults._
+
+object Global extends StyleSheet.Standalone {
+ import dsl._
+
+ "html" -
+ boxSizing.borderBox
+
+ "a" - (
+ color(Col.eastBay),
+ &.hover (
+ textDecoration := "underline"
+ )
+ )
+
+ "*, *:before, *:after" -
+ boxSizing.inherit
+
+ "button" - (
+ cursor.pointer,
+ display.flex,
+ backgroundColor(initial),
+ color(Col.black),
+ border.none
+ )
+}
diff --git a/src/main/scala/reading/component/style/Index.scala b/src/main/scala/reading/component/style/Index.scala
new file mode 100644
index 0000000..78e0630
--- /dev/null
+++ b/src/main/scala/reading/component/style/Index.scala
@@ -0,0 +1,26 @@
+package reading.component.style
+
+import scalacss.Defaults._
+
+object Index extends StyleSheet.Inline {
+ import dsl._
+
+ val header = style(
+ fontSize(40.px),
+ color(Col.congoBrown),
+ textAlign.center,
+ margin(10.px, auto),
+ padding(20.px),
+ &.hover (
+ textDecoration := "none"
+ )
+ )
+
+ val page = style(
+ display.flex
+ )
+
+ val main = style(
+ marginLeft(20.px)
+ )
+}
diff --git a/src/main/scala/reading/models/Book.scala b/src/main/scala/reading/models/Book.scala
new file mode 100644
index 0000000..1e4b81a
--- /dev/null
+++ b/src/main/scala/reading/models/Book.scala
@@ -0,0 +1,21 @@
+package reading.models
+
+case class Book(
+ title: String,
+ author: String,
+ genres: Seq[Genre],
+ themes: Seq[Theme]
+)
+
+object Book {
+ def all: Seq[Book] = Seq(
+ Book("Les dix petits nègres", "Agatha Christie", Seq(Genre.Detective), Seq(Theme.Fear)),
+ Book("Le joueur", "Fiódor Dostoyevski", Seq(Genre.Adventure), Seq(Theme.Fear)),
+ Book("Voyage au bout de la nuit", "Céline", Seq(Genre.Adventure), Seq(Theme.Fear)),
+ Book("Le petit prince", "Antoine de Saint Exupéry", Seq(Genre.Adventure), Seq(Theme.Friendship)),
+ Book("Les frères Karamazov", "Fiódor Dostoyevski", Seq(Genre.Adventure), Seq(Theme.Family))
+ )
+
+ def filter(books: Seq[Book], filters: Seq[Filter]): Seq[Book] =
+ books.filter(b => filters.forall(_.filter(b)))
+}
diff --git a/src/main/scala/reading/models/Filter.scala b/src/main/scala/reading/models/Filter.scala
new file mode 100644
index 0000000..c4836bb
--- /dev/null
+++ b/src/main/scala/reading/models/Filter.scala
@@ -0,0 +1,46 @@
+package reading.models
+
+trait Filter {
+ def filter(book: Book): Boolean
+ def kind: FilterKind
+ def name: String
+}
+
+sealed trait FilterKind
+case object ThemeKind extends FilterKind
+case object GenreKind extends FilterKind
+
+object Filter {
+ def apply[T](in: T)(implicit filterFactory: FilterFactory[T]): Filter =
+ filterFactory.create(in)
+
+ def contains(filters: Seq[Filter], filter: Filter): Boolean =
+ filters.find(f => f.kind == filter.kind && f.name == filter.name).nonEmpty
+
+ def equals(f1: Filter, f2: Filter): Boolean =
+ f1.kind == f2.kind && f1.name == f2.name
+}
+
+trait FilterFactory[T] {
+ def create(in: T): Filter
+}
+
+object FilterFactory {
+ implicit object ThemeFilter extends FilterFactory[Theme] {
+ def create(theme: Theme): Filter =
+ new Filter {
+ def filter(book: Book): Boolean = book.themes.contains(theme)
+ val kind: FilterKind = ThemeKind
+ val name: String = theme.toString()
+ }
+ }
+
+ implicit object GenreFilter extends FilterFactory[Genre] {
+ def create(genre: Genre): Filter =
+ new Filter {
+ def filter(book: Book): Boolean = book.genres.contains(genre)
+ val kind: FilterKind = GenreKind
+ val name: String = genre.toString()
+ }
+ }
+}
diff --git a/src/main/scala/reading/models/Genre.scala b/src/main/scala/reading/models/Genre.scala
new file mode 100644
index 0000000..44da79d
--- /dev/null
+++ b/src/main/scala/reading/models/Genre.scala
@@ -0,0 +1,21 @@
+package reading.models
+
+import enumeratum._
+
+sealed trait Genre extends EnumEntry {
+ override def toString(): String = this match {
+ case Genre.Adventure => "aventure"
+ case Genre.Fantastic => "fantastique"
+ case Genre.Detective => "policier"
+ case Genre.Marvellous => "merveilleux"
+ }
+}
+
+object Genre extends Enum[Genre] {
+ val values = findValues
+
+ case object Adventure extends Genre
+ case object Fantastic extends Genre
+ case object Detective extends Genre
+ case object Marvellous extends Genre
+}
diff --git a/src/main/scala/reading/models/Theme.scala b/src/main/scala/reading/models/Theme.scala
new file mode 100644
index 0000000..ed7ee0b
--- /dev/null
+++ b/src/main/scala/reading/models/Theme.scala
@@ -0,0 +1,21 @@
+package reading.models
+
+import enumeratum._
+
+sealed trait Theme extends EnumEntry {
+ override def toString(): String = this match {
+ case Theme.Love => "amour"
+ case Theme.Friendship => "amitié"
+ case Theme.Family => "famille"
+ case Theme.Fear => "peur"
+ }
+}
+
+object Theme extends Enum[Theme] {
+ val values = findValues
+
+ case object Love extends Theme
+ case object Friendship extends Theme
+ case object Family extends Theme
+ case object Fear extends Theme
+}
diff --git a/src/main/scala/reading/utils/Rx.scala b/src/main/scala/reading/utils/Rx.scala
new file mode 100644
index 0000000..83de617
--- /dev/null
+++ b/src/main/scala/reading/utils/Rx.scala
@@ -0,0 +1,44 @@
+package reading.utils
+
+import scala.util.{Failure, Success}
+
+import org.scalajs.dom.Element
+
+import scalatags.JsDom.all._
+import rx._
+
+import Ctx.Owner.Unsafe._
+
+object RxTag {
+ def apply(r: Ctx.Data => HtmlTag): HtmlTag =
+ rxMod(Rx(r(implicitly[Ctx.Data])))
+
+ 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
+ }
+ var last = rSafe
+ r.trigger {
+ val newLast = rSafe
+ Option(last.parentElement).foreach {
+ _.replaceChild(newLast, last)
+ }
+ last = newLast
+ }
+ span(
+ bindNode(last)
+ )
+ }
+}
+
+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) }
+ }
+ }
+ scalatags.generic.AttrPair(attr, v, attrValue)
+ }
+}
--
cgit v1.2.3