diff --git a/client/src/API/Decklist.elm b/client/src/API/Decklist.elm index 446bdaf7..77d6d97a 100644 --- a/client/src/API/Decklist.elm +++ b/client/src/API/Decklist.elm @@ -13,6 +13,7 @@ module API.Decklist exposing ) import API.Auth exposing (auth) +import Data.Collection exposing (Collection) import Deck exposing (DeckPostSave) import Http import Json.Decode as Decode @@ -75,7 +76,7 @@ type alias ResultRead = Result Http.Error DeckPostSave -read : Shared.Collection -> (ResultRead -> msg) -> String -> Cmd msg +read : Collection -> (ResultRead -> msg) -> String -> Cmd msg read collection msg deckId = Http.get { url = "/api/v1/decklist/" ++ deckId @@ -87,7 +88,7 @@ type alias ResultIndex = Result Http.Error (List DeckPostSave) -index : Shared.Collection -> (ResultIndex -> msg) -> Cmd msg +index : Collection -> (ResultIndex -> msg) -> Cmd msg index collection msg = Http.get { url = "/api/v1/decklist" @@ -95,7 +96,7 @@ index collection msg = } -indexForUser : Shared.Collection -> (ResultIndex -> msg) -> String -> Cmd msg +indexForUser : Collection -> (ResultIndex -> msg) -> String -> Cmd msg indexForUser collection msg userId = Http.get { url = "/api/v1/decklist?userId=" ++ userId diff --git a/client/src/Data/Clan.elm b/client/src/Data/Clan.elm index 64919e1a..4d004825 100644 --- a/client/src/Data/Clan.elm +++ b/client/src/Data/Clan.elm @@ -1,4 +1,4 @@ -module Data.Clan exposing (Clan(..), all, comparable, decoder) +module Data.Clan exposing (Clan(..), all, comparable, decoder, fromString, name, toString) import Enum exposing (Enum) import Json.Decode exposing (Decoder) @@ -29,6 +29,16 @@ enum = ] +toString : Clan -> String +toString = + enum.toString + + +fromString : String -> Maybe Clan +fromString = + enum.fromString + + all : List Clan all = List.map Tuple.second enum.list @@ -65,3 +75,31 @@ comparable c = Ventrue -> 8 + + +name : Clan -> String +name c = + case c of + Brujah -> + "Brujah" + + Gangrel -> + "Gangrel" + + Malkavian -> + "Malkavian" + + Nosferatu -> + "Nosferatu" + + ThinBlood -> + "Thin-blood" + + Toreador -> + "Toreador" + + Tremere -> + "Tremere" + + Ventrue -> + "Ventrue" diff --git a/client/src/Data/Collection.elm b/client/src/Data/Collection.elm new file mode 100644 index 00000000..7da8f71b --- /dev/null +++ b/client/src/Data/Collection.elm @@ -0,0 +1,41 @@ +module Data.Collection exposing (Collection, groupByStack) + +import Cards +import Dict + + +type alias Collection = + Dict.Dict Cards.Id Cards.Card + + +type alias GroupedByStacks = + { agendaStack : List Cards.Agenda + , factionStack : List Cards.Faction + , havenStack : List Cards.Haven + , libraryStack : List Cards.Library + } + + +groupByStack : Collection -> GroupedByStacks +groupByStack = + Dict.values + >> List.foldl + (\card grouped -> + case card of + Cards.AgendaCard c -> + { grouped | agendaStack = c :: grouped.agendaStack } + + Cards.FactionCard c -> + { grouped | factionStack = c :: grouped.factionStack } + + Cards.HavenCard c -> + { grouped | havenStack = c :: grouped.havenStack } + + Cards.LibraryCard c -> + { grouped | libraryStack = c :: grouped.libraryStack } + ) + { agendaStack = [] + , factionStack = [] + , havenStack = [] + , libraryStack = [] + } diff --git a/client/src/Data/GameMode.elm b/client/src/Data/GameMode.elm index 2bec9b00..eb574145 100644 --- a/client/src/Data/GameMode.elm +++ b/client/src/Data/GameMode.elm @@ -63,7 +63,7 @@ shortName mode = allows : GameMode -> GameMode -> Bool allows target current = - current == Both || current == target + current == Both || target == Both || current == target decode : Decoder GameMode diff --git a/client/src/Deck.elm b/client/src/Deck.elm index aefe5255..ee5b2494 100644 --- a/client/src/Deck.elm +++ b/client/src/Deck.elm @@ -24,11 +24,11 @@ module Deck exposing import Cards import Data.Clan exposing (Clan) +import Data.Collection exposing (Collection) import Data.GameMode as GameMode exposing (GameMode) import Dict exposing (Dict) import Json.Decode as Decode exposing (Decoder) import Json.Encode as Encode -import Shared exposing (Collection) diff --git a/client/src/Pages/Deck/Edit/Id_.elm b/client/src/Pages/Deck/Edit/Id_.elm index d062fa7c..5c3a5706 100644 --- a/client/src/Pages/Deck/Edit/Id_.elm +++ b/client/src/Pages/Deck/Edit/Id_.elm @@ -4,6 +4,7 @@ import API.Decklist import Auth import Browser.Navigation as Navigation exposing (Key) import Cards +import Data.Collection exposing (Collection) import Data.GameMode exposing (GameMode) import Deck exposing (DeckPostSave, Name(..)) import Effect exposing (Effect) @@ -49,7 +50,7 @@ type alias Data = } -init : Shared.Collection -> Key -> String -> ( Model, Effect Msg ) +init : Collection -> Key -> String -> ( Model, Effect Msg ) init collection key deckId = ( Loading key , Effect.fromCmd <| API.Decklist.read collection FetchedDecklist deckId diff --git a/client/src/Pages/Decks.elm b/client/src/Pages/Decks.elm index c15542fd..e085dd7b 100644 --- a/client/src/Pages/Decks.elm +++ b/client/src/Pages/Decks.elm @@ -1,11 +1,17 @@ module Pages.Decks exposing (Model, Msg, page) import API.Decklist +import Cards exposing (CardStack(..)) +import Data.Clan as Clan exposing (Clan) +import Data.Collection exposing (Collection) +import Data.GameMode as GameMode exposing (GameMode) import Deck exposing (DeckPostSave) import Effect exposing (Effect) import Gen.Params.Decks exposing (Params) -import Html exposing (div, text) -import Html.Attributes exposing (class) +import Html exposing (Html, div, label, option, p, select, span, text) +import Html.Attributes exposing (class, for, name, value) +import Html.Events exposing (onInput) +import Html.Lazy as Lazy import Page import Request import Shared @@ -31,7 +37,16 @@ page shared _ = type Model = Loading - | Viewing (List DeckPostSave) + | Viewing (List DeckPostSave) Filters + + +type alias Filters = + { gameMode : GameMode + , agenda : Maybe Cards.Id + , haven : Maybe Cards.Id + , leader : Maybe Cards.Id + , clan : Maybe Clan + } init : Shared.Model -> ( Model, Effect Msg ) @@ -49,20 +64,43 @@ init shared = type Msg = FromShared Shared.Msg | FetchedDecklists API.Decklist.ResultIndex + | FilterByGameMode GameMode + | FilterByAgenda (Maybe Cards.Id) + | FilterByHaven (Maybe Cards.Id) + | FilterByLeader (Maybe Cards.Id) + | FilterByClan (Maybe Clan) update : Msg -> Model -> ( Model, Effect Msg ) update msg model = - case msg of - FromShared subMsg -> + case ( model, msg ) of + ( _, FromShared subMsg ) -> ( model, Effect.fromShared subMsg ) - FetchedDecklists (Ok decks) -> - ( Viewing decks, Effect.none ) + ( _, FetchedDecklists (Ok decks) ) -> + ( Viewing decks { gameMode = GameMode.Both, agenda = Nothing, haven = Nothing, leader = Nothing, clan = Nothing }, Effect.none ) - FetchedDecklists (Err _) -> + ( _, FetchedDecklists (Err _) ) -> ( model, Effect.none ) + ( Loading, _ ) -> + ( Loading, Effect.none ) + + ( Viewing decklsit filters, FilterByGameMode gameMode ) -> + ( Viewing decklsit { filters | gameMode = gameMode }, Effect.none ) + + ( Viewing decklsit filters, FilterByAgenda agendaId ) -> + ( Viewing decklsit { filters | agenda = agendaId }, Effect.none ) + + ( Viewing decklsit filters, FilterByHaven havenId ) -> + ( Viewing decklsit { filters | haven = havenId }, Effect.none ) + + ( Viewing decklsit filters, FilterByLeader leaderId ) -> + ( Viewing decklsit { filters | leader = leaderId }, Effect.none ) + + ( Viewing decklsit filters, FilterByClan clan ) -> + ( Viewing decklsit { filters | clan = clan }, Effect.none ) + ---------- @@ -76,16 +114,165 @@ view shared model = Loading -> UI.Layout.Template.view FromShared shared [ text "Loading" ] - Viewing decks -> - viewDecklists shared decks + Viewing decks filters -> + viewDecklists shared decks filters -viewDecklists : Shared.Model -> List DeckPostSave -> View Msg -viewDecklists shared model = +viewDecklists : Shared.Model -> List DeckPostSave -> Filters -> View Msg +viewDecklists shared decks filters = UI.Layout.Template.view FromShared shared [ div [ class "page-decks__content" ] [ UI.Text.header [ text "Decklists" ] - , UI.DecklistsIndex.view model + , Lazy.lazy viewDecklistFilters shared.collection + , UI.DecklistsIndex.view (filterDecks filters decks) ] ] + + +viewDecklistFilters : Collection -> Html Msg +viewDecklistFilters collection = + let + { agendaStack, havenStack, factionStack } = + Data.Collection.groupByStack collection + in + p [ class "deck-index-filters" ] + [ text "Filtering by:" + , span [ class "deck-index-filters__filter" ] + [ label [ for "gameMode" ] [ text "Game Mode:" ] + , select + [ name "gameMode" + , onInput (GameMode.fromString >> Maybe.withDefault GameMode.default >> FilterByGameMode) + ] + ([ GameMode.Both + , GameMode.HeadToHead + , GameMode.Multiplayer + ] + |> List.map + (\mode -> + option [ value <| GameMode.toString mode ] [ text <| GameMode.longName mode ] + ) + ) + ] + , viewSelect { label = "Agenda", onSelect = FilterByAgenda, anyName = "Any agenda" } (List.sortBy .name agendaStack) + , viewSelect { label = "Haven", onSelect = FilterByHaven, anyName = "Any haven" } (List.sortBy .name havenStack) + , span [ class "deck-index-filters__filter" ] + [ label [ for "clan" ] [ text "Clans" ] + , select [ name "clan", onInput (Clan.fromString >> FilterByClan) ] + (option [ value "none" ] [ text "Any clan" ] + :: (Clan.all + |> List.map + (\clan -> + option [ value <| Clan.toString clan ] [ text <| Clan.name clan ] + ) + ) + ) + ] + , viewSelect { label = "Leader", onSelect = FilterByLeader, anyName = "Any leader" } (List.sortBy .name factionStack) + ] + + +type alias SelectSettings msg = + { label : String + , onSelect : Maybe String -> msg + , anyName : String + } + + +viewSelect : SelectSettings Msg -> List { a | id : String, name : String } -> Html Msg +viewSelect settings entries = + span [ class "deck-index-filters__filter" ] + [ label [ for <| String.toLower settings.label ] + [ text settings.label ] + , select + [ name <| String.toLower settings.label + , onInput + (\entryId -> + settings.onSelect + (if entryId == "none" then + Nothing + + else + Just entryId + ) + ) + ] + (option [ value "none" ] [ text settings.anyName ] + :: List.map (\entry -> option [ value entry.id ] [ text entry.name ]) entries + ) + ] + + + +------------- +-- FILTERING +------------- + + +filterDecks : Filters -> List DeckPostSave -> List DeckPostSave +filterDecks filters decks = + decks + |> List.filter + (\deck -> + gameModeAllowed filters deck + && agendaAllowed filters deck + && havenAllowed filters deck + && leaderAllowed filters deck + && clanAllowed filters deck + ) + + +gameModeAllowed : Filters -> DeckPostSave -> Bool +gameModeAllowed filters deck = + GameMode.allows filters.gameMode deck.meta.gameMode + + +agendaAllowed : Filters -> DeckPostSave -> Bool +agendaAllowed filters deck = + case ( filters.agenda, deck.decklist.agenda ) of + ( Just whitelistedAgendaId, Just deckAgenda ) -> + deckAgenda.id == whitelistedAgendaId + + ( Nothing, _ ) -> + True + + ( _, Nothing ) -> + False + + +havenAllowed : Filters -> DeckPostSave -> Bool +havenAllowed filters deck = + case ( filters.haven, deck.decklist.haven ) of + ( Just whitelistedHavenId, Just deckHaven ) -> + deckHaven.id == whitelistedHavenId + + ( Nothing, _ ) -> + True + + ( _, Nothing ) -> + False + + +clanAllowed : Filters -> DeckPostSave -> Bool +clanAllowed filters deck = + case filters.clan of + Just whitelistedClan -> + Deck.clansInFaction deck.decklist.faction + |> List.map Tuple.first + |> List.member whitelistedClan + + Nothing -> + True + + +leaderAllowed : Filters -> DeckPostSave -> Bool +leaderAllowed filters deck = + case ( filters.leader, Deck.leader deck.decklist ) of + ( Just whitelistedLeaderId, Just leader ) -> + leader.id == whitelistedLeaderId + + ( Nothing, _ ) -> + True + + ( _, Nothing ) -> + False diff --git a/client/src/Pages/Decks.sass b/client/src/Pages/Decks.sass index 59ad04db..7e2f61a8 100644 --- a/client/src/Pages/Decks.sass +++ b/client/src/Pages/Decks.sass @@ -2,3 +2,12 @@ .page-decks__content margin-top: $space-medium + +.deck-index-filters + margin-top: $space-small + &__filter + display: inline-block + margin-left: $space-small + + label + display: none diff --git a/client/src/Pages/Search.elm b/client/src/Pages/Search.elm index 81741e1e..cc7cbe9c 100644 --- a/client/src/Pages/Search.elm +++ b/client/src/Pages/Search.elm @@ -2,6 +2,7 @@ module Pages.Search exposing (Model, Msg, page) import Cards exposing (Card) import Data.Clan exposing (Clan) +import Data.Collection exposing (Collection) import Data.Discipline exposing (Discipline) import Dict import Effect exposing (Effect) @@ -13,7 +14,7 @@ import Html.Events exposing (onInput) import Html.Keyed as Keyed import Page import Request -import Shared exposing (Collection) +import Shared import UI.Card import UI.FilterSelection import UI.Layout.Template diff --git a/client/src/Shared.elm b/client/src/Shared.elm index 196944c7..b69495ca 100644 --- a/client/src/Shared.elm +++ b/client/src/Shared.elm @@ -1,6 +1,5 @@ port module Shared exposing - ( Collection - , Flags + ( Flags , Model , Msg(..) , Token @@ -13,6 +12,7 @@ port module Shared exposing import Browser.Navigation exposing (Key) import Cards exposing (cardsDecoder) +import Data.Collection exposing (Collection) import Dict import Gen.Route as Route import Json.Decode as Json @@ -24,10 +24,6 @@ type alias Flags = Json.Value -type alias Collection = - Dict.Dict Cards.Id Cards.Card - - type alias Model = { collection : Collection , user : Maybe User diff --git a/client/src/UI/DeckbuildSelections.elm b/client/src/UI/DeckbuildSelections.elm index 85d22d4f..ec53df12 100644 --- a/client/src/UI/DeckbuildSelections.elm +++ b/client/src/UI/DeckbuildSelections.elm @@ -2,6 +2,7 @@ module UI.DeckbuildSelections exposing (Model, Msg(..), init, update, view) import Cards exposing (Card) import Data.Clan exposing (Clan) +import Data.Collection exposing (Collection) import Data.Discipline exposing (Discipline) import Deck exposing (Decklist) import Dict @@ -11,7 +12,7 @@ import Html.Attributes exposing (checked, class, classList, name, type_) import Html.Events exposing (onClick) import Html.Keyed as Keyed import Html.Lazy as Lazy -import Shared exposing (Collection) +import Shared import UI.Attribute import UI.Card import UI.CardName diff --git a/client/src/UI/DecklistsIndex.elm b/client/src/UI/DecklistsIndex.elm index ee6319bc..10b46e47 100644 --- a/client/src/UI/DecklistsIndex.elm +++ b/client/src/UI/DecklistsIndex.elm @@ -10,7 +10,7 @@ import UI.Card import UI.Icon as Icon -view : List DeckPostSave -> Html unknown +view : List DeckPostSave -> Html msg view decklists = ul [ class "deckindex" ] (decklists |> List.map viewDecklistEntry)