From 154b398c144b51c7609c9141b8dc9aaa80fae04f Mon Sep 17 00:00:00 2001 From: Joris Date: Mon, 27 Feb 2017 15:22:35 +0100 Subject: Filter when the user click on a theme / genre / program on book detail --- README.md | 1 - src/main/scala/reading/Route.scala | 9 ++- src/main/scala/reading/component/Index.scala | 4 +- .../scala/reading/component/index/BookDetail.scala | 84 +++++++++++++++++----- src/main/scala/reading/component/index/Books.scala | 10 +-- .../reading/component/index/FilterUtils.scala | 15 ++-- .../scala/reading/component/index/Filters.scala | 6 +- .../scala/reading/component/index/Header.scala | 2 +- src/main/scala/reading/component/index/Menu.scala | 18 ++--- .../reading/component/index/style/BookDetail.scala | 9 +++ .../reading/component/index/style/Books.scala | 3 +- .../reading/component/index/style/Header.scala | 3 +- src/main/scala/reading/models/Filter.scala | 6 +- src/main/scala/reading/models/FilterFactory.scala | 6 +- src/main/scala/reading/models/Grade.scala | 11 +++ src/main/scala/reading/models/GroupedTheme.scala | 19 +++++ src/main/scala/reading/models/Program.scala | 11 --- src/main/scala/reading/models/Theme.scala | 19 ----- 18 files changed, 149 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index 2e62924..76e1177 100644 --- a/README.md +++ b/README.md @@ -46,4 +46,3 @@ TODO ---- - Add a resume to every book. -- Filter when the user click on a theme / genre / program on book detail. diff --git a/src/main/scala/reading/Route.scala b/src/main/scala/reading/Route.scala index f098468..89c34cf 100644 --- a/src/main/scala/reading/Route.scala +++ b/src/main/scala/reading/Route.scala @@ -11,7 +11,7 @@ import reading.models.{ Filter, FilterKind, Book, Books => BooksModel } sealed trait Route object Route { - case class Books(filters: Seq[Filter] = Nil, detail: Option[Book] = None) extends Route + case class Books(filters: Set[Filter] = Set(), detail: Option[Book] = None) extends Route val current: Var[Route] = Var(parse(window.location.hash)) @@ -35,7 +35,7 @@ object Route { } yield filter case _ => None - } + }.toSet val detail = params.collectFirst { case Param("detail", title) => BooksModel().find(_.title == title) }.flatten @@ -56,7 +56,7 @@ object Route { def url(route: Route): String = { val hash = route match { case Books(filters, detail) => { - val filterParams = filters.map(filter => (filter.kind.toString, filter.nonFormattedName)) + val filterParams = filters.toSeq.map(filter => (filter.kind.toString, filter.nonFormattedName)) val detailParams = detail.map(book => ("detail", book.title)) s"/books${Param.format(filterParams ++ detailParams)}" } @@ -70,9 +70,8 @@ object Route { current() = route } - def push(route: Route): Unit = { + def push(route: Route): Unit = window.history.pushState(null, "", url(route)); - } } object Param { diff --git a/src/main/scala/reading/component/Index.scala b/src/main/scala/reading/component/Index.scala index 0105150..d6d1b6f 100644 --- a/src/main/scala/reading/component/Index.scala +++ b/src/main/scala/reading/component/Index.scala @@ -10,8 +10,8 @@ import reading.component.style.{ Index => IndexStyle } import reading.models.{ Book, Books, Filter } object Index { - def apply(initialFilters: Seq[Filter], initialDetail: Option[Book])(implicit ctx: Ctx.Owner): Frag = { - val filters: Var[Seq[Filter]] = Var(initialFilters) + def apply(initialFilters: Set[Filter], initialDetail: Option[Book])(implicit ctx: Ctx.Owner): Frag = { + val filters: Var[Set[Filter]] = Var(initialFilters) val books: Rx[Seq[Book]] = Rx(Filter.add(Books(), filters())) val search: Var[String] = Var("") val showFiltersMenu: Var[Boolean] = Var(false) diff --git a/src/main/scala/reading/component/index/BookDetail.scala b/src/main/scala/reading/component/index/BookDetail.scala index f532c02..e97a154 100644 --- a/src/main/scala/reading/component/index/BookDetail.scala +++ b/src/main/scala/reading/component/index/BookDetail.scala @@ -1,27 +1,32 @@ package reading.component.index -import scala.util.Random - +import rx._ import scalacss.Defaults._ import scalacss.ScalatagsCss._ import scalatags.JsDom.all._ import reading.component.index.style.{ BookDetail => BookStyle } import reading.component.widget.AnimateMethod -import reading.models.{ Book, Program, Filter } +import reading.models.{ Filter, Book, Program, Grade, Theme, GroupedTheme } import reading.Route +import reading.utils.RxUtils._ object BookDetail { - val componentId = s"books${Random.nextInt}" + val componentId = s"books-detail" def apply( - filters: Seq[Filter], + filters: Var[Set[Filter]], + detail: Var[Option[Book]], + search: Var[String], book: Book, parentId: String, onClose: => Unit + )( + implicit + ctx: Ctx.Owner ): Frag = { val titleParts = if (book.parts > 1) s", ${book.parts} volumes" else "" - val grades = book.programs.map(Program.grade(_)).distinct.sorted + val grades = book.programs.map(Grade.from(_)).distinct.sorted AnimateMethod.fadeIn(componentId) @@ -55,23 +60,33 @@ object BookDetail { BookStyle.definitions, grades.map { grade => - val programs = book.programs.filter(p => Program.grade(p) == grade).sorted + val programs = book.programs.filter(p => Grade.from(p) == grade).sorted val pp = grade.prettyPrint - definition(pp, pp, programs.map(p => s"« ${p.prettyPrint} »")) + val programFilters = programs.map { program => + (Filter(program), filters.map(addProgram(_, program))) + } + definition(filters, detail, search, pp, pp, programFilters, p => s"« $p »") }, if (book.themes.nonEmpty) { - definition("thème", "thèmes", book.themes.sorted.map(_.prettyPrint)) + val themeFilters = book.themes.sorted.map { theme => + (Filter(theme), filters.map(addTheme(_, theme))) + } + definition(filters, detail, search, "thème", "thèmes", themeFilters) }, if (book.genres.nonEmpty) { - definition("genre", "genres", book.genres.sorted.map(_.prettyPrint)) + val bookFilters = book.genres.sorted.map(Filter(_)).map(b => (b, filters.map(_ + b))) + definition(filters, detail, search, "genre", "genres", bookFilters) }, - definition("niveau", "niveaux", Seq(book.level.prettyPrint)) + { + val levelFilters = Seq(Filter(book.level)).map(b => (b, filters.map(_ + b))) + definition(filters, detail, search, "niveau", "niveaux", levelFilters) + } ), a( BookStyle.close, onclick := (() => onClose), - href := Route.url(Route.Books(filters)), + href := Rx(Route.url(Route.Books(filters()))), "Fermer" ) ) @@ -79,11 +94,48 @@ object BookDetail { ) } - private def definition(key: String, pluralKey: String, values: Seq[String]): Seq[Frag] = { - val term = if (values.length > 1) pluralKey else key + private def definition( + filters: Var[Set[Filter]], + detail: Var[Option[Book]], + search: Var[String], + term: String, + pluralTerm: String, + definitionFilters: Seq[(Filter, Rx[Set[Filter]])], + format: String => String = _.capitalize + )( + implicit + ctx: Ctx.Owner + ): Seq[Frag] = { + val sTerm = if (definitionFilters.length > 1) pluralTerm else term + Seq( - dt(BookStyle.definitionTerm, s"${term.capitalize} :"), - dd(BookStyle.definitionDescription, values.mkString(", ")) + dt(BookStyle.definitionTerm, s"${sTerm.capitalize} :"), + dd( + BookStyle.definitionDescription, + definitionFilters.map { + case (filter, newFilters) => + a( + BookStyle.definitionFilter, + href := Rx(Route.url(Route.Books(newFilters()))), + onclick := (() => FilterUtils.set(filters, detail, search, newFilters.now)), + format(filter.name) + ) + } + ) ) } + + private def addProgram(filters: Set[Filter], program: Program): Set[Filter] = { + val grade = Grade.from(program) + val otherGrades = Grade.values.filter(_ != grade).map(Filter(_)) + val otherPrograms = Program.values.filter(Grade.from(_) != grade).map(Filter(_)) + filters -- otherGrades -- otherPrograms + Filter(grade) + Filter(program) + } + + private def addTheme(filters: Set[Filter], theme: Theme): Set[Filter] = { + val groupedTheme = GroupedTheme.from(theme) + val otherGroupedThemes = GroupedTheme.values.filter(_ != groupedTheme).map(Filter(_)) + val otherThemes = Theme.values.filter(GroupedTheme.from(_) != groupedTheme).map(Filter(_)) + filters -- otherGroupedThemes -- otherThemes + Filter(groupedTheme) + Filter(theme) + } } diff --git a/src/main/scala/reading/component/index/Books.scala b/src/main/scala/reading/component/index/Books.scala index f15e2dc..14aac51 100644 --- a/src/main/scala/reading/component/index/Books.scala +++ b/src/main/scala/reading/component/index/Books.scala @@ -18,7 +18,7 @@ object Books { def apply( books: Rx[Seq[Book]], - filters: Var[Seq[Filter]], + filters: Var[Set[Filter]], detail: Var[Option[Book]], search: Var[String], showFiltersMenu: Var[Boolean] @@ -75,7 +75,10 @@ object Books { Rx { detail() match { case Some(book) => - BookDetail(filters.now, book, componentId, onClose = closeDetail(filters, detail)) + BookDetail(filters, detail, search, book, componentId, onClose = { + closeDetail(filters, detail) + Route.push(Route.Books(filters.now, None)) + }) case None => span("") } @@ -83,10 +86,9 @@ object Books { ) } - def closeDetail(filters: Var[Seq[Filter]], detail: Var[Option[Book]]): Unit = + def closeDetail(filters: Var[Set[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 89f993a..c019513 100644 --- a/src/main/scala/reading/component/index/FilterUtils.scala +++ b/src/main/scala/reading/component/index/FilterUtils.scala @@ -7,7 +7,7 @@ import reading.Route object FilterUtils { def remove( - filters: Var[Seq[Filter]], + filters: Var[Set[Filter]], detail: Var[Option[Book]], search: Var[String], filter: Filter @@ -20,23 +20,22 @@ object FilterUtils { } def removeAll( - filters: Var[Seq[Filter]], + filters: Var[Set[Filter]], detail: Var[Option[Book]], search: Var[String] ): Unit = { - filters() = Nil + filters() = Set() if (detail.now.nonEmpty) Books.closeDetail(filters, detail) search() = "" - Route.push(Route.Books(Nil)) + Route.push(Route.Books()) } - def add( - filters: Var[Seq[Filter]], + def set( + filters: Var[Set[Filter]], detail: Var[Option[Book]], search: Var[String], - filter: Filter + newFilters: Set[Filter] ): Unit = { - val newFilters = filter +: filters.now filters() = newFilters if (detail.now.nonEmpty) Books.closeDetail(filters, detail) search() = "" diff --git a/src/main/scala/reading/component/index/Filters.scala b/src/main/scala/reading/component/index/Filters.scala index 935e501..c300190 100644 --- a/src/main/scala/reading/component/index/Filters.scala +++ b/src/main/scala/reading/component/index/Filters.scala @@ -15,7 +15,7 @@ import reading.utils.RxUtils._ object Filters { def apply( - filters: Var[Seq[Filter]], + filters: Var[Set[Filter]], detail: Var[Option[Book]], search: Var[String], showFiltersMenu: Var[Boolean] @@ -23,7 +23,7 @@ object Filters { implicit ctx: Ctx.Owner ): Frag = { - val filtersCount: Rx[Int] = Rx(filters().length) + val filtersCount: Rx[Int] = Rx(filters().size) div( FiltersStyle.render, @@ -51,7 +51,7 @@ object Filters { "Effacer les filtres" ), - filters().sortBy(_.name).map { filter => + filters().toSeq.sortBy(_.name).map { filter => a( FiltersStyle.filter, onclick := (() => FilterUtils.remove(filters, detail, search, filter)), diff --git a/src/main/scala/reading/component/index/Header.scala b/src/main/scala/reading/component/index/Header.scala index 0809b0c..2d9504e 100644 --- a/src/main/scala/reading/component/index/Header.scala +++ b/src/main/scala/reading/component/index/Header.scala @@ -13,7 +13,7 @@ import reading.utils.RxUtils._ object Header { def apply( books: Rx[Seq[Book]], - filters: Var[Seq[Filter]], + filters: Var[Set[Filter]], detail: Var[Option[Book]], search: Var[String], showFiltersMenu: Var[Boolean] diff --git a/src/main/scala/reading/component/index/Menu.scala b/src/main/scala/reading/component/index/Menu.scala index 0ea6ca4..ec6c21f 100644 --- a/src/main/scala/reading/component/index/Menu.scala +++ b/src/main/scala/reading/component/index/Menu.scala @@ -14,7 +14,7 @@ import reading.utils.RxUtils._ object Menu { def apply( books: Rx[Seq[Book]], - filters: Var[Seq[Filter]], + filters: Var[Set[Filter]], detail: Var[Option[Book]], search: Var[String], showFiltersMenu: Var[Boolean] @@ -27,14 +27,14 @@ object Menu { Rx(if (showFiltersMenu()) MenuStyle.show else MenuStyle.empty), MenuStyle.menu, - Rx(header(showFiltersMenu, filters().length)), + Rx(header(showFiltersMenu, filters().size)), 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).sorted + val programs = Program.values.filter(p => Grade.from(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(_))) @@ -43,7 +43,7 @@ object Menu { Rx { filters().find(_.kind == FilterKind.GroupedTheme) match { case Some(groupedTheme) => - val themes = Theme.values.filter(t => Theme.grouped(t).toString() == groupedTheme.nonFormattedName).sorted + val themes = Theme.values.filter(t => GroupedTheme.from(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(_))) @@ -66,7 +66,7 @@ object Menu { def group( books: Rx[Seq[Book]], - filters: Var[Seq[Filter]], + filters: Var[Set[Filter]], detail: Var[Option[Book]], search: Var[String], name: String, @@ -100,7 +100,7 @@ object Menu { if (parentFilter.isDefined) MenuStyle.activeFilter else "", if (filtersCount() > 1) pluralName else name, Rx { - val count = filters().filter(f => groupFilters.exists(f == _)).length + val count = filters().filter(f => groupFilters.exists(f == _)).size if (count > 0) span(MenuStyle.filterTitleCount, count) else span("") } ), @@ -109,7 +109,7 @@ object Menu { case (filter, count) => { val isActive = Filter.contains(filters(), filter) val route = Route.Books( - filters = if (isActive) Filter.remove(filters(), filter) else filter +: filters() + filters = if (isActive) Filter.remove(filters(), filter) else filters() + filter ) a( @@ -120,7 +120,7 @@ object Menu { if (isActive) FilterUtils.remove(filters, detail, search, filter) else - FilterUtils.add(filters, detail, search, filter)), + FilterUtils.set(filters, detail, search, filters.now + filter)), span( span(filter.name.capitalize), span(MenuStyle.filterCount, count) @@ -135,7 +135,7 @@ object Menu { def footer( books: Rx[Seq[Book]], - filters: Var[Seq[Filter]], + filters: Var[Set[Filter]], detail: Var[Option[Book]], search: Var[String], showFiltersMenu: Var[Boolean] diff --git a/src/main/scala/reading/component/index/style/BookDetail.scala b/src/main/scala/reading/component/index/style/BookDetail.scala index 2ce0632..eaad862 100644 --- a/src/main/scala/reading/component/index/style/BookDetail.scala +++ b/src/main/scala/reading/component/index/style/BookDetail.scala @@ -71,6 +71,15 @@ object BookDetail extends StyleSheet.Inline { lineHeight(1.4.em) ) + val definitionFilter = style( + &.hover(color(C.stiletto.value)), + &.not(_.lastChild).after( + content := "\",\"", + marginRight(5.px), + &.hover(color(C.black.value)) + ) + ) + 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 a1f0374..808b937 100644 --- a/src/main/scala/reading/component/index/style/Books.scala +++ b/src/main/scala/reading/component/index/style/Books.scala @@ -21,7 +21,8 @@ object Books extends StyleSheet.Inline { ) val listParent = style( - overflowY.scroll + overflowY.scroll, + paddingTop(15.px) ) val list = style( diff --git a/src/main/scala/reading/component/index/style/Header.scala b/src/main/scala/reading/component/index/style/Header.scala index 10ce059..a871e9c 100644 --- a/src/main/scala/reading/component/index/style/Header.scala +++ b/src/main/scala/reading/component/index/style/Header.scala @@ -9,7 +9,8 @@ object Header extends StyleSheet.Inline { import dsl._ val header = style( - margin(30.px, 0.px) + marginTop(30.px), + marginBottom(20.px) ) val searchAndCount = style( diff --git a/src/main/scala/reading/models/Filter.scala b/src/main/scala/reading/models/Filter.scala index 7ec6340..e4c8b93 100644 --- a/src/main/scala/reading/models/Filter.scala +++ b/src/main/scala/reading/models/Filter.scala @@ -32,10 +32,10 @@ object Filter { case FilterKind.Grade => Grade.withNameOption(nonFormattedName).map(apply[Grade]) } - def contains(filters: Seq[Filter], filter: Filter): Boolean = + def contains(filters: Set[Filter], filter: Filter): Boolean = filters.find(_ == filter).nonEmpty - def remove(fs: Seq[Filter], rf: Filter): Seq[Filter] = + def remove(fs: Set[Filter], rf: Filter): Set[Filter] = fs.filterNot { f => (f == rf || rf.kind == FilterKind.Grade && f.kind == FilterKind.Program @@ -56,7 +56,7 @@ object Filter { .map(f => (f, Books().filter(f.filter))) .toMap - def add(books: Seq[Book], filters: Seq[Filter]): Seq[Book] = + def add(books: Seq[Book], filters: Set[Filter]): Seq[Book] = filters.foldLeft(books)(add) def add(books: Seq[Book], filter: Filter): Seq[Book] = diff --git a/src/main/scala/reading/models/FilterFactory.scala b/src/main/scala/reading/models/FilterFactory.scala index d900af5..d8d7151 100644 --- a/src/main/scala/reading/models/FilterFactory.scala +++ b/src/main/scala/reading/models/FilterFactory.scala @@ -15,10 +15,10 @@ object FilterFactory { } } - implicit object GroupedTheme extends FilterFactory[GroupedTheme] { + implicit object GroupedThemeFilter extends FilterFactory[GroupedTheme] { def create(groupedTheme: GroupedTheme): Filter = new Filter { - def filter(book: Book): Boolean = book.themes.map(Theme.grouped).contains(groupedTheme) + def filter(book: Book): Boolean = book.themes.map(GroupedTheme.from).contains(groupedTheme) val kind: FilterKind = FilterKind.GroupedTheme val nonFormattedName: String = groupedTheme.toString() val name: String = groupedTheme.prettyPrint() @@ -58,7 +58,7 @@ object FilterFactory { implicit object GradeFilter extends FilterFactory[Grade] { def create(grade: Grade): Filter = new Filter { - def filter(book: Book): Boolean = book.programs.map(Program.grade).contains(grade) + def filter(book: Book): Boolean = book.programs.map(Grade.from).contains(grade) val kind: FilterKind = FilterKind.Grade val nonFormattedName: String = grade.toString() val name: String = grade.prettyPrint() diff --git a/src/main/scala/reading/models/Grade.scala b/src/main/scala/reading/models/Grade.scala index f54211d..c711e8b 100644 --- a/src/main/scala/reading/models/Grade.scala +++ b/src/main/scala/reading/models/Grade.scala @@ -24,4 +24,15 @@ object Grade extends Enum[Grade] { case object Cinquieme extends Grade case object Quatrieme extends Grade case object Troisieme extends Grade + + def from(program: Program): Grade = { + import Program._ + + program match { + case Monstre | RecitAventure | CreationPoetique | Resister => Sixieme + case VoyageEtAventure | Autrui | UniversNouveaux | Heros | HommeEtNature => Cinquieme + case DireAmour | Valeurs | Reel | Informer | Ville => Quatrieme + case SeRaconter | TraversSociete | VisionsPoetiques | Agir | ProgresReveScientifique => Troisieme + } + } } diff --git a/src/main/scala/reading/models/GroupedTheme.scala b/src/main/scala/reading/models/GroupedTheme.scala index 61a5281..1a26bc1 100644 --- a/src/main/scala/reading/models/GroupedTheme.scala +++ b/src/main/scala/reading/models/GroupedTheme.scala @@ -39,4 +39,23 @@ object GroupedTheme extends Enum[GroupedTheme] { case object Mort extends GroupedTheme case object Difference extends GroupedTheme case object Imaginaire extends GroupedTheme + + def from(theme: Theme): GroupedTheme = { + import Theme._ + + theme match { + case Art | Americain | Danse | Litterature | Musique | Mythologie | Poetique | Reecriture | Sport => Culture + case Nature | Aventure | Initiation | Pirate | Voyage | Animal => Decouverte + case Dragon | Magie => Imaginaire + case Mutant | Beaute | Handicap | Homosexualite | Laideur | Metamorphose | Marginalite | Migration | Metissage | Pauvrete | Discrimination => Difference + case Maltraitance | Viol | Combat | Conflit | Crime | Harcelement | Exclusion | Racisme | Antisemitisme | SegregationRaciale | Prison => Violence + case Chevalerie | Cour | Dictature | Guerre | Nazisme | Revolution => Histoire + case Theme.Humour => GroupedTheme.Humour + case Enquete | Manipulation | Mensonge | Secret | Espionnage | Complot => Stratageme + case Fantome | Folie | Malediction | Vampire | Monstrueux => Peur + case Suicide | Deuil | Maladie | Immortalite | Vieillesse => Mort + case Adolescence | Amour | Amitie | Apprentissage | College | Ecole | Emancipation | Sentiment | Sexualite | Enfants | Femme | Parents | Famille => Quotidien + case Genetique | Internet | JeuVideo | Robot => Technologie + } + } } diff --git a/src/main/scala/reading/models/Program.scala b/src/main/scala/reading/models/Program.scala index d33d23a..4c647f3 100644 --- a/src/main/scala/reading/models/Program.scala +++ b/src/main/scala/reading/models/Program.scala @@ -60,15 +60,4 @@ object Program extends Enum[Program] { case object VisionsPoetiques extends Program case object Agir extends Program case object ProgresReveScientifique extends Program - - def grade(program: Program): Grade = { - import Grade._ - - program match { - case Monstre | RecitAventure | CreationPoetique | Resister => Sixieme - case VoyageEtAventure | Autrui | UniversNouveaux | Heros | HommeEtNature => Cinquieme - case DireAmour | Valeurs | Reel | Informer | Ville => Quatrieme - case SeRaconter | TraversSociete | VisionsPoetiques | Agir | ProgresReveScientifique => Troisieme - } - } } diff --git a/src/main/scala/reading/models/Theme.scala b/src/main/scala/reading/models/Theme.scala index 66870a4..2a76611 100644 --- a/src/main/scala/reading/models/Theme.scala +++ b/src/main/scala/reading/models/Theme.scala @@ -173,23 +173,4 @@ object Theme extends Enum[Theme] { case object Vieillesse extends Theme case object Viol extends Theme case object Voyage extends Theme - - def grouped(theme: Theme): GroupedTheme = { - import GroupedTheme._ - - theme match { - case Art | Americain | Danse | Litterature | Musique | Mythologie | Poetique | Reecriture | Sport => Culture - case Nature | Aventure | Initiation | Pirate | Voyage | Animal => Decouverte - case Dragon | Magie => Imaginaire - case Mutant | Beaute | Handicap | Homosexualite | Laideur | Metamorphose | Marginalite | Migration | Metissage | Pauvrete | Discrimination => Difference - case Maltraitance | Viol | Combat | Conflit | Crime | Harcelement | Exclusion | Racisme | Antisemitisme | SegregationRaciale | Prison => Violence - case Chevalerie | Cour | Dictature | Guerre | Nazisme | Revolution => Histoire - case Theme.Humour => GroupedTheme.Humour - case Enquete | Manipulation | Mensonge | Secret | Espionnage | Complot => Stratageme - case Fantome | Folie | Malediction | Vampire | Monstrueux => Peur - case Suicide | Deuil | Maladie | Immortalite | Vieillesse => Mort - case Adolescence | Amour | Amitie | Apprentissage | College | Ecole | Emancipation | Sentiment | Sexualite | Enfants | Femme | Parents | Famille => Quotidien - case Genetique | Internet | JeuVideo | Robot => Technologie - } - } } -- cgit v1.2.3