Include mana costs in UI

This commit is contained in:
Correl Roush 2023-01-11 17:10:48 -05:00
parent 16a93b08d8
commit c13a45fa86
8 changed files with 229 additions and 16 deletions

View file

@ -224,6 +224,7 @@ async def advanced_search(
rarity=tutor.models.Rarity.from_string(row["rarity"]), rarity=tutor.models.Rarity.from_string(row["rarity"]),
color_identity=tutor.models.Color.from_string(row["color_identity"]), color_identity=tutor.models.Color.from_string(row["color_identity"]),
cmc=row["cmc"], cmc=row["cmc"],
mana_cost=row["mana_cost"],
type_line=row["type_line"], type_line=row["type_line"],
release_date=datetime.date.fromisoformat(row["release_date"]), release_date=datetime.date.fromisoformat(row["release_date"]),
games=set(), games=set(),
@ -373,6 +374,7 @@ async def get_decks(
'name', "oracle_latest"."name", 'name', "oracle_latest"."name",
'color_identity', "oracle_latest"."color_identity", 'color_identity', "oracle_latest"."color_identity",
'cmc', "oracle_latest"."cmc", 'cmc', "oracle_latest"."cmc",
'mana_cost', "oracle_latest"."mana_cost",
'type_line', "oracle_latest"."type_line", 'type_line', "oracle_latest"."type_line",
'edhrec_rank', "oracle_latest"."edhrec_rank", 'edhrec_rank', "oracle_latest"."edhrec_rank",
'oracle_text', "oracle_latest"."oracle_text", 'oracle_text', "oracle_latest"."oracle_text",
@ -407,6 +409,7 @@ async def get_decks(
card["color_identity"] card["color_identity"]
), ),
cmc=card["cmc"], cmc=card["cmc"],
mana_cost=card["mana_cost"],
type_line=card["type_line"], type_line=card["type_line"],
games=set(), games=set(),
legalities={}, legalities={},
@ -441,6 +444,7 @@ async def get_deck(
'name', "oracle_latest"."name", 'name', "oracle_latest"."name",
'color_identity', "oracle_latest"."color_identity", 'color_identity', "oracle_latest"."color_identity",
'cmc', "oracle_latest"."cmc", 'cmc', "oracle_latest"."cmc",
'mana_cost', "oracle_latest"."mana_cost",
'type_line', "oracle_latest"."type_line", 'type_line', "oracle_latest"."type_line",
'edhrec_rank', "oracle_latest"."edhrec_rank", 'edhrec_rank', "oracle_latest"."edhrec_rank",
'oracle_text', "oracle_latest"."oracle_text", 'oracle_text', "oracle_latest"."oracle_text",
@ -474,6 +478,7 @@ async def get_deck(
card["color_identity"] card["color_identity"]
), ),
cmc=card["cmc"], cmc=card["cmc"],
mana_cost=card["mana_cost"],
type_line=card["type_line"], type_line=card["type_line"],
games=set(), games=set(),
legalities={}, legalities={},

View file

@ -14,6 +14,7 @@
"elm/html": "1.0.0", "elm/html": "1.0.0",
"elm/http": "2.0.0", "elm/http": "2.0.0",
"elm/json": "1.1.3", "elm/json": "1.1.3",
"elm/parser": "1.1.0",
"elm/regex": "1.0.0", "elm/regex": "1.0.0",
"elm/url": "1.0.0", "elm/url": "1.0.0",
"elm-community/maybe-extra": "5.2.0", "elm-community/maybe-extra": "5.2.0",

View file

@ -6,7 +6,7 @@ import Browser.Events
import Browser.Navigation import Browser.Navigation
import Card import Card
import Color import Color
import Dict import Dict exposing (Dict)
import Element as E import Element as E
import Element.Background as Background import Element.Background as Background
import Element.Border as Border import Element.Border as Border
@ -23,6 +23,7 @@ import Pages.DeckList
import Paginated import Paginated
import Route import Route
import Spinner import Spinner
import Symbol
import Task import Task
import UI import UI
import Url import Url
@ -37,6 +38,7 @@ type alias Model =
, viewport : UI.Dimensions , viewport : UI.Dimensions
, device : E.Device , device : E.Device
, route : Maybe Route.Route , route : Maybe Route.Route
, symbols : Dict String Symbol.Symbol
, page : Page , page : Page
} }
@ -48,6 +50,7 @@ type Msg
| CollectionMsg Pages.Collection.Msg | CollectionMsg Pages.Collection.Msg
| DeckListMsg Pages.DeckList.Msg | DeckListMsg Pages.DeckList.Msg
| DeckEditorMsg Pages.DeckEditor.Msg | DeckEditorMsg Pages.DeckEditor.Msg
| GotSymbology (Result Http.Error (List Symbol.Symbol))
| SpinnerMsg Spinner.Msg | SpinnerMsg Spinner.Msg
@ -70,6 +73,9 @@ init _ url key =
route = route =
Route.fromUrl url Route.fromUrl url
symbols =
Dict.empty
initWith : (pageModel -> Page) -> (pageMsg -> Msg) -> ( pageModel, Cmd pageMsg ) -> ( Page, Cmd Msg ) initWith : (pageModel -> Page) -> (pageMsg -> Msg) -> ( pageModel, Cmd pageMsg ) -> ( Page, Cmd Msg )
initWith pageType pageMsg ( subModel, subCmd ) = initWith pageType pageMsg ( subModel, subCmd ) =
( pageType subModel, Cmd.map pageMsg subCmd ) ( pageType subModel, Cmd.map pageMsg subCmd )
@ -78,7 +84,7 @@ init _ url key =
case route of case route of
Just Route.Collection -> Just Route.Collection ->
initWith Collection CollectionMsg <| initWith Collection CollectionMsg <|
Pages.Collection.init key url device Pages.Collection.init key url device symbols
Just Route.DeckList -> Just Route.DeckList ->
initWith DeckList DeckListMsg <| initWith DeckList DeckListMsg <|
@ -86,7 +92,7 @@ init _ url key =
Just (Route.Deck deckId) -> Just (Route.Deck deckId) ->
initWith DeckEditor DeckEditorMsg <| initWith DeckEditor DeckEditorMsg <|
Pages.DeckEditor.init key url device deckId Pages.DeckEditor.init key url device symbols deckId
_ -> _ ->
( NotFound, Cmd.none ) ( NotFound, Cmd.none )
@ -96,10 +102,12 @@ init _ url key =
, viewport = viewport , viewport = viewport
, device = device , device = device
, route = route , route = route
, symbols = symbols
, page = page , page = page
} }
, Cmd.batch , Cmd.batch
[ UI.getViewport ViewportChanged [ UI.getViewport ViewportChanged
, getSymbology
, pageCmd , pageCmd
] ]
) )
@ -128,7 +136,7 @@ update msg model =
|> updateWith Collection CollectionMsg model |> updateWith Collection CollectionMsg model
( _, Just Route.Collection ) -> ( _, Just Route.Collection ) ->
Pages.Collection.init model.navigationKey url model.device Pages.Collection.init model.navigationKey url model.device model.symbols
|> updateWith Collection CollectionMsg model |> updateWith Collection CollectionMsg model
( _, Just Route.DeckList ) -> ( _, Just Route.DeckList ) ->
@ -136,7 +144,7 @@ update msg model =
|> updateWith DeckList DeckListMsg model |> updateWith DeckList DeckListMsg model
( _, Just (Route.Deck deckId) ) -> ( _, Just (Route.Deck deckId) ) ->
Pages.DeckEditor.init model.navigationKey url model.device deckId Pages.DeckEditor.init model.navigationKey url model.device model.symbols deckId
|> updateWith DeckEditor DeckEditorMsg model |> updateWith DeckEditor DeckEditorMsg model
_ -> _ ->
@ -160,6 +168,32 @@ update msg model =
, Cmd.none , Cmd.none
) )
( GotSymbology (Ok symbols), _ ) ->
let
table =
symbols
|> List.map (\s -> ( s.symbol, s ))
|> Dict.fromList
( newModel, cmd ) =
case model.page of
Collection pageModel ->
Pages.Collection.update (Pages.Collection.GotSymbols table) pageModel
|> updateWith Collection CollectionMsg model
DeckEditor pageModel ->
Pages.DeckEditor.update (Pages.DeckEditor.GotSymbols table) pageModel
|> updateWith DeckEditor DeckEditorMsg model
_ ->
( model, Cmd.none )
in
( { newModel
| symbols = table
}
, cmd
)
( SpinnerMsg spinnerMsg, Collection pageModel ) -> ( SpinnerMsg spinnerMsg, Collection pageModel ) ->
Pages.Collection.update (Pages.Collection.SpinnerMsg spinnerMsg) pageModel Pages.Collection.update (Pages.Collection.SpinnerMsg spinnerMsg) pageModel
|> updateWith Collection CollectionMsg model |> updateWith Collection CollectionMsg model
@ -290,6 +324,17 @@ subscriptions model =
Sub.batch global Sub.batch global
getSymbology : Cmd Msg
getSymbology =
Http.get
{ url = Url.Builder.crossOrigin "https://api.scryfall.com" [ "symbology" ] []
, expect =
Http.expectJson GotSymbology <|
Json.Decode.at [ "data" ] <|
Json.Decode.list Symbol.decode
}
main : Program Json.Decode.Value Model Msg main : Program Json.Decode.Value Model Msg
main = main =
Browser.application Browser.application

View file

@ -16,6 +16,7 @@ type alias Prices =
type alias Oracle = type alias Oracle =
{ oracleId : String { oracleId : String
, name : String , name : String
, manaCost : String
, typeLine : String , typeLine : String
, oracleText : String , oracleText : String
} }
@ -26,6 +27,7 @@ type alias Card =
, name : String , name : String
, setCode : String , setCode : String
, rarity : String , rarity : String
, manaCost : String
, typeLine : String , typeLine : String
, oracleText : String , oracleText : String
, prices : Prices , prices : Prices
@ -44,6 +46,10 @@ decodeOracle =
Json.Decode.succeed Oracle Json.Decode.succeed Oracle
|> JDP.required "oracle_id" Json.Decode.string |> JDP.required "oracle_id" Json.Decode.string
|> JDP.required "name" Json.Decode.string |> JDP.required "name" Json.Decode.string
|> JDP.required "mana_cost"
(Json.Decode.nullable Json.Decode.string
|> Json.Decode.map (Maybe.withDefault "")
)
|> JDP.required "type_line" Json.Decode.string |> JDP.required "type_line" Json.Decode.string
|> JDP.required "oracle_text" |> JDP.required "oracle_text"
(Json.Decode.nullable Json.Decode.string (Json.Decode.nullable Json.Decode.string
@ -58,6 +64,10 @@ decode =
|> JDP.required "name" Json.Decode.string |> JDP.required "name" Json.Decode.string
|> JDP.required "set_code" Json.Decode.string |> JDP.required "set_code" Json.Decode.string
|> JDP.required "rarity" Json.Decode.string |> JDP.required "rarity" Json.Decode.string
|> JDP.required "mana_cost"
(Json.Decode.nullable Json.Decode.string
|> Json.Decode.map (Maybe.withDefault "")
)
|> JDP.required "type_line" Json.Decode.string |> JDP.required "type_line" Json.Decode.string
|> JDP.required "oracle_text" |> JDP.required "oracle_text"
(Json.Decode.nullable Json.Decode.string (Json.Decode.nullable Json.Decode.string

View file

@ -20,6 +20,7 @@ import Json.Decode.Pipeline as JDP
import Maybe.Extra import Maybe.Extra
import Paginated import Paginated
import Spinner import Spinner
import Symbol
import Task import Task
import UI import UI
import Url import Url
@ -59,6 +60,7 @@ type alias Model =
, cardPage : CardPage , cardPage : CardPage
, activeCard : Maybe Card.Copy , activeCard : Maybe Card.Copy
, collectionStatistics : Maybe Statistics , collectionStatistics : Maybe Statistics
, symbols : Symbol.Table
} }
@ -74,6 +76,7 @@ type Msg
| FoundCards (Result Http.Error (Paginated.Page Card.Copy)) | FoundCards (Result Http.Error (Paginated.Page Card.Copy))
| ShowCardDetails Card.Copy | ShowCardDetails Card.Copy
| ClearCardDetails | ClearCardDetails
| GotSymbols Symbol.Table
type CriteriaMsg type CriteriaMsg
@ -179,8 +182,8 @@ criteriaFromUrl url =
|> Maybe.withDefault emptyCriteria |> Maybe.withDefault emptyCriteria
init : Browser.Navigation.Key -> Url.Url -> E.Device -> ( Model, Cmd Msg ) init : Browser.Navigation.Key -> Url.Url -> E.Device -> Symbol.Table -> ( Model, Cmd Msg )
init key url device = init key url device symbols =
let let
criteria = criteria =
criteriaFromUrl url criteriaFromUrl url
@ -193,6 +196,7 @@ init key url device =
, cardPage = Loading Paginated.empty , cardPage = Loading Paginated.empty
, activeCard = Nothing , activeCard = Nothing
, collectionStatistics = Nothing , collectionStatistics = Nothing
, symbols = symbols
} }
, Cmd.batch , Cmd.batch
[ UI.getViewport ViewportChanged [ UI.getViewport ViewportChanged
@ -287,6 +291,9 @@ update msg model =
ClearCardDetails -> ClearCardDetails ->
( { model | activeCard = Nothing }, Cmd.none ) ( { model | activeCard = Nothing }, Cmd.none )
GotSymbols symbols ->
( { model | symbols = symbols }, Cmd.none )
searchBar : Model -> E.Element Msg searchBar : Model -> E.Element Msg
searchBar model = searchBar model =
@ -480,7 +487,7 @@ viewCardBrowser model =
[] []
) )
in in
UI.cardRow { foil = copy.foil, subtitle = copy.collection } cardAttrs copy.card UI.cardRow { foil = copy.foil, subtitle = copy.collection } cardAttrs model.symbols copy.card
in in
E.column attrs E.column attrs
[ E.row [ E.row

View file

@ -12,6 +12,7 @@ import Http
import Paginated import Paginated
import Route import Route
import Spinner import Spinner
import Symbol
import UI import UI
import Url import Url
import Url.Builder import Url.Builder
@ -22,6 +23,7 @@ type alias Model =
, url : Url.Url , url : Url.Url
, device : E.Device , device : E.Device
, spinner : Spinner.Model , spinner : Spinner.Model
, symbols : Symbol.Table
, deck : Deck , deck : Deck
} }
@ -30,6 +32,7 @@ type Msg
= ViewportChanged UI.Dimensions = ViewportChanged UI.Dimensions
| SpinnerMsg Spinner.Msg | SpinnerMsg Spinner.Msg
| GotDeck (Result Http.Error Deck.Deck) | GotDeck (Result Http.Error Deck.Deck)
| GotSymbols Symbol.Table
type Deck type Deck
@ -85,12 +88,13 @@ groups cards =
] ]
init : Browser.Navigation.Key -> Url.Url -> E.Device -> Int -> ( Model, Cmd Msg ) init : Browser.Navigation.Key -> Url.Url -> E.Device -> Symbol.Table -> Int -> ( Model, Cmd Msg )
init key url device deckId = init key url device symbols deckId =
( { navigationKey = key ( { navigationKey = key
, url = url , url = url
, device = device , device = device
, spinner = Spinner.init , spinner = Spinner.init
, symbols = symbols
, deck = Loading , deck = Loading
} }
, getDeck deckId , getDeck deckId
@ -116,9 +120,12 @@ update msg model =
GotDeck (Err _) -> GotDeck (Err _) ->
( { model | deck = Failed }, Cmd.none ) ( { model | deck = Failed }, Cmd.none )
GotSymbols symbols ->
( { model | symbols = symbols }, Cmd.none )
viewDeck : Deck.Deck -> E.Element Msg
viewDeck deck = viewDeck : Symbol.Table -> Deck.Deck -> E.Element Msg
viewDeck symbols deck =
let let
viewGroup group = viewGroup group =
E.column [ E.spacing 10 ] E.column [ E.spacing 10 ]
@ -131,6 +138,7 @@ viewDeck deck =
, subtitle = "x" ++ String.fromInt dc.quantity , subtitle = "x" ++ String.fromInt dc.quantity
} }
[ E.width <| E.px 400, E.clipX, Background.color UI.colors.background ] [ E.width <| E.px 400, E.clipX, Background.color UI.colors.background ]
symbols
dc.card dc.card
) )
group.cards group.cards
@ -159,7 +167,7 @@ view model =
] ]
[ case model.deck of [ case model.deck of
Ready deck -> Ready deck ->
viewDeck deck viewDeck model.symbols deck
Failed -> Failed ->
E.none E.none

109
www/src/Symbol.elm Normal file
View file

@ -0,0 +1,109 @@
module Symbol exposing (Symbol, Table, Text, decode, text)
import Dict exposing (Dict)
import Element as E
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Json.Decode
import Json.Decode.Pipeline as JDP
import Parser as P exposing ((|.), (|=))
type alias Symbol =
{ symbol : String
, svgURI : String
, english : String
}
type alias Table =
Dict String Symbol
type Text
= Text String
| Inline Symbol
decode : Json.Decode.Decoder Symbol
decode =
Json.Decode.succeed Symbol
|> JDP.required "symbol" Json.Decode.string
|> JDP.required "svg_uri" Json.Decode.string
|> JDP.required "english" Json.Decode.string
symbolTextParser : Dict String Symbol -> P.Parser (List Text)
symbolTextParser available =
let
lookupSymbol : String -> Text
lookupSymbol string =
Dict.get string available
|> Maybe.map Inline
|> Maybe.withDefault (Text "")
textParser : P.Parser Text
textParser =
P.chompUntilEndOr "{"
|> P.getChompedString
|> P.map Text
symbolStringParser : P.Parser String
symbolStringParser =
P.getChompedString <|
P.succeed ()
|. P.symbol "{"
|. P.chompWhile (\c -> c /= '}')
|. P.symbol "}"
symbolParser : P.Parser Text
symbolParser =
symbolStringParser
|> P.map lookupSymbol
in
P.loop []
(\reversedParts ->
P.oneOf
[ P.end
|> P.map (\_ -> P.Done (List.reverse reversedParts))
, symbolParser
|> P.map (\part -> P.Loop (part :: reversedParts))
, textParser
|> P.map (\part -> P.Loop (part :: reversedParts))
]
)
toElement : Int -> Text -> E.Element msg
toElement size symbolText =
let
padding =
size // 6
fontSize =
size - padding
symbol : Symbol -> E.Element msg
symbol s =
E.image
[ E.width <| E.px size
, E.height <| E.px size
]
{ src = s.svgURI
, description = s.english
}
in
case symbolText of
Text s ->
E.el [ Font.size size, E.height <| E.px size ] <| E.text s
Inline s ->
symbol s
text : Dict String Symbol -> Int -> String -> E.Element msg
text available size string =
E.row [ E.spacing (size // 10) ] <|
List.map (toElement size) <|
(P.run (symbolTextParser available) string |> Result.withDefault [])

View file

@ -21,6 +21,7 @@ import Element.Border as Border
import Element.Font as Font import Element.Font as Font
import Maybe.Extra import Maybe.Extra
import Spinner import Spinner
import Symbol
import Task import Task
import Url.Builder import Url.Builder
@ -86,6 +87,32 @@ colors =
, rare = rare , rare = rare
, uncommon = uncommon , uncommon = uncommon
, common = common , common = common
, mana =
{ white =
{ fg = E.rgb255 249 250 244
, bg = E.rgb255 248 231 185
}
, black =
{ fg = E.rgb255 21 11 0
, bg = E.rgb255 166 159 157
}
, blue =
{ fg = E.rgb255 14 104 171
, bg = E.rgb255 179 206 234
}
, green =
{ fg = E.rgb255 0 114 62
, bg = E.rgb255 196 211 202
}
, red =
{ fg = E.rgb255 211 32 42
, bg = E.rgb255 235 159 130
}
, generic =
{ fg = E.rgb255 190 190 190
, bg = E.rgb255 250 250 250
}
}
} }
@ -173,8 +200,8 @@ priceBadge { currency, amount } =
] ]
cardRow : { foil : Bool, subtitle : String } -> List (E.Attribute msg) -> Card.Card -> E.Element msg cardRow : { foil : Bool, subtitle : String } -> List (E.Attribute msg) -> Symbol.Table -> Card.Card -> E.Element msg
cardRow options attributes card = cardRow options attributes symbols card =
let let
badge color foil string = badge color foil string =
E.el E.el
@ -265,7 +292,8 @@ cardRow options attributes card =
, description = card.name , description = card.name
} }
, E.column [ E.centerY, E.height E.fill, E.width E.fill, E.clipX ] , E.column [ E.centerY, E.height E.fill, E.width E.fill, E.clipX ]
[ E.el [ Font.color colors.title ] <| E.text card.name [ Symbol.text symbols 12 card.manaCost
, E.el [ Font.color colors.title ] <| E.text card.name
, E.el [ Font.size 16, Font.italic, Font.color colors.subtitle ] <| E.text options.subtitle , E.el [ Font.size 16, Font.italic, Font.color colors.subtitle ] <| E.text options.subtitle
] ]
, E.column [ E.alignRight, E.height E.fill ] <| , E.column [ E.alignRight, E.height E.fill ] <|