Switch the UI to a scrollable list

This commit is contained in:
Correl Roush 2022-02-10 23:05:51 -05:00
parent bc24b89d25
commit 00616067c8
2 changed files with 179 additions and 145 deletions

View file

@ -292,227 +292,40 @@ colors =
blue =
E.rgb255 100 100 255
grey =
lighterGrey =
E.rgb255 60 60 60
lightGrey =
E.rgb255 50 50 50
grey =
E.rgb255 40 40 40
darkGrey =
E.rgb255 30 30 30
darkerGrey =
E.rgb255 20 20 20
white =
E.rgb255 255 255 255
in
{ primary = blue
, background = grey
, navBar = darkGrey
, background = lightGrey
, navBar = darkerGrey
, sidebar = lighterGrey
, selected = darkGrey
, hover = grey
, text = white
}
viewCardBrowser : Model -> E.Element Msg
viewCardBrowser model =
let
cardWidth =
-- 30% of the Scryfall border_crop image width (480)
144
cardHeight =
-- 30% of the Scryfall border_crop image height (680)
204
cardSpacing =
5
availableWidth =
(model.viewport.width // 5) * 4
cardColumns =
-- Either 6 or 9, based on viewport width
let
availableColumns =
availableWidth
// (cardWidth + cardSpacing)
in
(max 6 >> min 9) (availableColumns // 3 * 3)
cardRows =
18 // cardColumns
minInfoWidth =
1230 // 5
minBrowserWidth =
minInfoWidth * 4
viewCard : Dimensions -> Card.Card -> E.Element Msg
viewCard dimensions cardModel =
E.el
[ Border.rounded 10
, E.clip
, E.width <| E.px dimensions.width
, E.height <| E.px dimensions.height
, Events.onMouseEnter <| ShowCardDetails cardModel
, Events.onMouseLeave <| ClearCardDetails
]
<|
E.image
[ E.width <| E.px dimensions.width
, E.height <| E.px dimensions.height
, E.behindContent <|
E.html <|
Spinner.view manaSpinner model.spinner
]
{ src =
Url.Builder.crossOrigin "https://api.scryfall.com"
[ "cards", cardModel.scryfallId ]
[ Url.Builder.string "format" "image"
, Url.Builder.string "version" "border_crop"
]
, description = cardModel.name
}
navButton text maybeUrl =
case maybeUrl of
Just url ->
Input.button
[ E.height E.fill
, E.width E.fill
, Background.color colors.primary
, Border.rounded 5
, Font.color colors.text
, Font.center
]
{ label = E.text text, onPress = Just (GetPage url) }
Nothing ->
E.el [ E.width E.fill ] E.none
priceBadge { currency, amount } =
E.el
[ Border.solid
, Border.width 1
, Border.rounded 5
, Border.color colors.text
, E.width <| E.px 60
, E.padding 2
, Font.family [ Font.typeface "sans" ]
, Font.size 10
]
<|
E.row [ E.width E.fill ]
[ E.el [ E.width <| E.fillPortion 1 ] <| E.text <| String.toUpper currency
, E.el [ E.width <| E.fillPortion 2, Font.alignRight ] <| E.text amount
]
cardDetails card =
let
prices =
Maybe.Extra.values
[ Maybe.map (\usd -> { currency = "usd", amount = usd }) <|
Maybe.Extra.or card.prices.usd card.prices.usd_foil
, Maybe.map (\eur -> { currency = "eur", amount = eur }) <|
Maybe.Extra.or card.prices.eur card.prices.eur_foil
, Maybe.map (\tix -> { currency = "tix", amount = tix }) card.prices.tix
]
in
E.column
[ E.spacing 20
, E.padding 10
]
<|
E.el [ E.centerX ] (viewCard { width = 192, height = 272 } card)
:: (E.row [ E.spacing 5, E.centerX ] <| List.map priceBadge prices)
:: E.paragraph [ Font.heavy, Font.size 24, Font.center ] [ E.text card.name ]
:: List.map (\text -> E.paragraph [ Font.size 16 ] [ E.text text ])
(String.lines card.oracleText)
in
case model.cardPage of
Failed ->
E.none
Loading cardPage ->
searchBar : Model -> E.Element Msg
searchBar model =
E.row
[ E.width E.fill
, E.height (E.px (cardRows * (cardHeight + cardSpacing)))
, E.centerX
]
<|
[ navButton "" cardPage.prev
, E.el
[ E.width (E.px (cardColumns * (cardWidth + cardSpacing)))
, E.centerX
, E.spacing cardSpacing
]
<|
E.html <|
Spinner.view manaSpinner model.spinner
, navButton "" cardPage.next
]
Ready cardPage ->
E.row
[ E.centerX
, E.height (E.px (3 * (cardHeight + cardSpacing)))
, Font.family
[ Font.typeface
"Libre Baskerville"
, Font.serif
]
]
[ E.column
[ E.height E.fill
, E.width <| E.px minInfoWidth
, Font.color colors.text
]
[ Maybe.map cardDetails model.activeCard |> Maybe.withDefault E.none ]
, E.column
[ E.width (E.fill |> E.minimum minBrowserWidth)
, E.height E.fill
, E.spacing 10
]
<|
[ E.wrappedRow
[ E.width (E.px (cardColumns * (cardWidth + cardSpacing)))
, E.spacing cardSpacing
, E.paddingEach { left = cardSpacing // 2, top = 0, bottom = 0, right = 0 }
, E.centerX
, E.centerY
]
(List.map (viewCard { width = cardWidth, height = cardHeight }) cardPage.values)
, E.row [ E.width E.fill, E.spacing 20 ]
[ navButton "" cardPage.prev
, navButton "" cardPage.next
]
]
]
onEnter : msg -> E.Attribute msg
onEnter msg =
E.htmlAttribute
(Html.Events.on "keyup"
(Json.Decode.field "key" Json.Decode.string
|> Json.Decode.andThen
(\key ->
if key == "Enter" then
Json.Decode.succeed msg
else
Json.Decode.fail "Not the enter key"
)
)
)
view : Model -> Browser.Document Msg
view model =
{ title = "Tutor"
, body =
[ E.layout [ Background.color colors.background ] <|
E.column [ E.width (E.fill |> E.minimum 1280), E.spacing 20 ]
[ E.row
[ E.padding 10
, E.spacing 10
, E.width E.fill
, E.width (E.fill |> E.minimum 800)
, Background.color colors.navBar
]
[ Input.text
@ -551,7 +364,229 @@ view model =
, label = Input.labelRight [ Font.color colors.text ] (E.text "Owned only?")
}
]
viewCardBrowser : Model -> E.Element Msg
viewCardBrowser model =
let
viewCard : Dimensions -> Card.Card -> E.Element Msg
viewCard dimensions cardModel =
E.el
[ Border.rounded 10
, E.clip
, E.width <| E.px dimensions.width
, E.height <| E.px dimensions.height
]
<|
E.image
[ E.width <| E.px dimensions.width
, E.height <| E.px dimensions.height
, E.behindContent <|
E.html <|
Spinner.view manaSpinner model.spinner
]
{ src =
Url.Builder.crossOrigin "https://api.scryfall.com"
[ "cards", cardModel.scryfallId ]
[ Url.Builder.string "format" "image"
, Url.Builder.string "version" "border_crop"
]
, description = cardModel.name
}
priceBadge { currency, amount } =
E.el
[ Border.solid
, Border.width 1
, Border.rounded 5
, Border.color colors.text
, E.width <| E.px 60
, E.padding 2
, Font.family [ Font.typeface "sans" ]
, Font.size 10
]
<|
E.row [ E.width E.fill ]
[ E.el [ E.width <| E.fillPortion 1 ] <| E.text <| String.toUpper currency
, E.el [ E.width <| E.fillPortion 2, Font.alignRight ] <| E.text amount
]
cardInfoWidth =
E.px 300
cardDetails card =
let
prices =
Maybe.Extra.values
[ Maybe.map (\usd -> { currency = "usd", amount = usd }) <|
Maybe.Extra.or card.prices.usd card.prices.usd_foil
, Maybe.map (\eur -> { currency = "eur", amount = eur }) <|
Maybe.Extra.or card.prices.eur card.prices.eur_foil
, Maybe.map (\tix -> { currency = "tix", amount = tix }) card.prices.tix
]
in
E.column
[ E.spacing 20
, E.padding 10
]
<|
E.el [ E.centerX ]
(viewCard { width = 192, height = 272 } card)
:: (E.row [ E.spacing 5, E.centerX ] <| List.map priceBadge prices)
:: E.paragraph [ Font.heavy, Font.size 24, Font.center ] [ E.text card.name ]
:: List.map (\text -> E.paragraph [ Font.size 16 ] [ E.text text ])
(String.lines card.oracleText)
cardRow : Maybe Card.Card -> Card.Card -> E.Element Msg
cardRow activeCard cardModel =
let
interactiveAttributes =
if activeCard == Just cardModel then
[ Background.color colors.selected ]
else
[ E.pointer
, E.mouseOver [ Background.color colors.hover ]
, Events.onClick <| ShowCardDetails cardModel
]
in
E.row
([ E.width E.fill
, E.spacing 10
, E.padding 3
]
++ interactiveAttributes
)
[ E.el [ E.width <| E.px 100 ] <|
E.image
[ E.height <| E.px 60
, E.centerX
]
{ src =
Url.Builder.crossOrigin "https://api.scryfall.com"
[ "cards", cardModel.scryfallId ]
[ Url.Builder.string "format" "image"
, Url.Builder.string "version" "art_crop"
]
, description = cardModel.name
}
, E.el [ E.centerY ] <| E.text cardModel.name
]
details =
E.el
[ E.alignTop
, E.width cardInfoWidth
, E.height E.fill
, Background.color colors.sidebar
]
(Maybe.map
cardDetails
model.activeCard
|> Maybe.withDefault
E.none
)
navButton text maybeUrl =
case maybeUrl of
Just url ->
Input.button
[ E.height (E.px 30)
, E.width E.fill
, Background.color colors.primary
, Border.rounded 5
, Font.color colors.text
, Font.center
]
{ label = E.text text, onPress = Just (GetPage url) }
Nothing ->
E.el [ E.width E.fill ] E.none
cards cardPage =
E.column
[ E.width E.fill
, E.height E.fill
]
[ E.row
[ E.spacing 5
, E.padding 5
, E.width E.fill
]
[ navButton "<-" cardPage.prev
, navButton "->" cardPage.next
]
, E.column
[ E.width E.fill
, E.height E.fill
, E.scrollbarY
]
<|
List.map (cardRow model.activeCard) cardPage.values
]
in
case model.cardPage of
Failed ->
E.none
Loading cardPage ->
E.el
[ E.centerX
]
<|
E.html <|
Spinner.view manaSpinner model.spinner
Ready cardPage ->
E.row
[ E.width E.fill
, E.height E.fill
, Font.color colors.text
, E.scrollbarY
]
[ details, cards cardPage ]
onEnter : msg -> E.Attribute msg
onEnter msg =
E.htmlAttribute
(Html.Events.on "keyup"
(Json.Decode.field "key" Json.Decode.string
|> Json.Decode.andThen
(\key ->
if key == "Enter" then
Json.Decode.succeed msg
else
Json.Decode.fail "Not the enter key"
)
)
)
view : Model -> Browser.Document Msg
view model =
{ title = "Tutor"
, body =
[ E.layout
[ Background.color colors.background
, E.height E.fill
]
<|
E.column
[ E.width E.fill
, E.height E.fill
]
[ searchBar model
, viewCardBrowser model
, E.el
[ E.height (E.px 50)
, E.width E.fill
, Background.color colors.navBar
, E.alignBottom
]
<|
E.text "footer"
]
]
}

View file

@ -22,7 +22,6 @@ type alias Card =
, prices : Prices
}
decode : Json.Decode.Decoder Card
decode =
Json.Decode.succeed Card