Switch the UI to a scrollable list
This commit is contained in:
parent
bc24b89d25
commit
00616067c8
2 changed files with 179 additions and 145 deletions
323
www/src/App.elm
323
www/src/App.elm
|
@ -292,57 +292,83 @@ colors =
|
||||||
blue =
|
blue =
|
||||||
E.rgb255 100 100 255
|
E.rgb255 100 100 255
|
||||||
|
|
||||||
grey =
|
lighterGrey =
|
||||||
|
E.rgb255 60 60 60
|
||||||
|
|
||||||
|
lightGrey =
|
||||||
E.rgb255 50 50 50
|
E.rgb255 50 50 50
|
||||||
|
|
||||||
|
grey =
|
||||||
|
E.rgb255 40 40 40
|
||||||
|
|
||||||
darkGrey =
|
darkGrey =
|
||||||
|
E.rgb255 30 30 30
|
||||||
|
|
||||||
|
darkerGrey =
|
||||||
E.rgb255 20 20 20
|
E.rgb255 20 20 20
|
||||||
|
|
||||||
white =
|
white =
|
||||||
E.rgb255 255 255 255
|
E.rgb255 255 255 255
|
||||||
in
|
in
|
||||||
{ primary = blue
|
{ primary = blue
|
||||||
, background = grey
|
, background = lightGrey
|
||||||
, navBar = darkGrey
|
, navBar = darkerGrey
|
||||||
|
, sidebar = lighterGrey
|
||||||
|
, selected = darkGrey
|
||||||
|
, hover = grey
|
||||||
, text = white
|
, text = white
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
searchBar : Model -> E.Element Msg
|
||||||
|
searchBar model =
|
||||||
|
E.row
|
||||||
|
[ E.padding 10
|
||||||
|
, E.spacing 10
|
||||||
|
, E.width (E.fill |> E.minimum 800)
|
||||||
|
, Background.color colors.navBar
|
||||||
|
]
|
||||||
|
[ Input.text
|
||||||
|
[ onEnter Search
|
||||||
|
, Background.color colors.background
|
||||||
|
, Font.color colors.text
|
||||||
|
, E.width E.fill
|
||||||
|
]
|
||||||
|
{ onChange = UpdateCriteria << UpdateName
|
||||||
|
, text = model.criteria.query
|
||||||
|
, placeholder = Nothing
|
||||||
|
, label = Input.labelHidden "Search Input"
|
||||||
|
}
|
||||||
|
, Input.button
|
||||||
|
[ Background.color colors.primary
|
||||||
|
, Font.color colors.text
|
||||||
|
, Border.rounded 10
|
||||||
|
, E.padding 10
|
||||||
|
]
|
||||||
|
{ onPress = Just Search
|
||||||
|
, label = E.text "Search"
|
||||||
|
}
|
||||||
|
, Input.radio [ E.padding 10 ]
|
||||||
|
{ onChange = UpdateCriteria << UpdateSortBy
|
||||||
|
, selected = Just model.criteria.sortBy
|
||||||
|
, label = Input.labelLeft [ Font.color colors.text ] (E.text "Sort by")
|
||||||
|
, options =
|
||||||
|
[ Input.option "rarity" <| E.el [ Font.color colors.text ] <| E.text "Rarity DESC"
|
||||||
|
, Input.option "price" <| E.el [ Font.color colors.text ] <| E.text "Price DESC"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
, Input.checkbox []
|
||||||
|
{ onChange = UpdateCriteria << UpdateOwnedOnly
|
||||||
|
, icon = Input.defaultCheckbox
|
||||||
|
, checked = model.criteria.ownedOnly
|
||||||
|
, label = Input.labelRight [ Font.color colors.text ] (E.text "Owned only?")
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
viewCardBrowser : Model -> E.Element Msg
|
viewCardBrowser : Model -> E.Element Msg
|
||||||
viewCardBrowser model =
|
viewCardBrowser model =
|
||||||
let
|
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 -> Card.Card -> E.Element Msg
|
||||||
viewCard dimensions cardModel =
|
viewCard dimensions cardModel =
|
||||||
E.el
|
E.el
|
||||||
|
@ -350,8 +376,6 @@ viewCardBrowser model =
|
||||||
, E.clip
|
, E.clip
|
||||||
, E.width <| E.px dimensions.width
|
, E.width <| E.px dimensions.width
|
||||||
, E.height <| E.px dimensions.height
|
, E.height <| E.px dimensions.height
|
||||||
, Events.onMouseEnter <| ShowCardDetails cardModel
|
|
||||||
, Events.onMouseLeave <| ClearCardDetails
|
|
||||||
]
|
]
|
||||||
<|
|
<|
|
||||||
E.image
|
E.image
|
||||||
|
@ -370,22 +394,6 @@ viewCardBrowser model =
|
||||||
, description = cardModel.name
|
, 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 } =
|
priceBadge { currency, amount } =
|
||||||
E.el
|
E.el
|
||||||
[ Border.solid
|
[ Border.solid
|
||||||
|
@ -403,6 +411,9 @@ viewCardBrowser model =
|
||||||
, E.el [ E.width <| E.fillPortion 2, Font.alignRight ] <| E.text amount
|
, E.el [ E.width <| E.fillPortion 2, Font.alignRight ] <| E.text amount
|
||||||
]
|
]
|
||||||
|
|
||||||
|
cardInfoWidth =
|
||||||
|
E.px 300
|
||||||
|
|
||||||
cardDetails card =
|
cardDetails card =
|
||||||
let
|
let
|
||||||
prices =
|
prices =
|
||||||
|
@ -419,71 +430,121 @@ viewCardBrowser model =
|
||||||
, E.padding 10
|
, E.padding 10
|
||||||
]
|
]
|
||||||
<|
|
<|
|
||||||
E.el [ E.centerX ] (viewCard { width = 192, height = 272 } card)
|
E.el [ E.centerX ]
|
||||||
|
(viewCard { width = 192, height = 272 } card)
|
||||||
:: (E.row [ E.spacing 5, E.centerX ] <| List.map priceBadge prices)
|
:: (E.row [ E.spacing 5, E.centerX ] <| List.map priceBadge prices)
|
||||||
:: E.paragraph [ Font.heavy, Font.size 24, Font.center ] [ E.text card.name ]
|
:: E.paragraph [ Font.heavy, Font.size 24, Font.center ] [ E.text card.name ]
|
||||||
:: List.map (\text -> E.paragraph [ Font.size 16 ] [ E.text text ])
|
:: List.map (\text -> E.paragraph [ Font.size 16 ] [ E.text text ])
|
||||||
(String.lines card.oracleText)
|
(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
|
in
|
||||||
case model.cardPage of
|
case model.cardPage of
|
||||||
Failed ->
|
Failed ->
|
||||||
E.none
|
E.none
|
||||||
|
|
||||||
Loading cardPage ->
|
Loading cardPage ->
|
||||||
E.row
|
E.el
|
||||||
[ E.width E.fill
|
[ E.centerX
|
||||||
, E.height (E.px (cardRows * (cardHeight + cardSpacing)))
|
|
||||||
, E.centerX
|
|
||||||
]
|
]
|
||||||
<|
|
<|
|
||||||
[ navButton "←" cardPage.prev
|
E.html <|
|
||||||
, E.el
|
Spinner.view manaSpinner model.spinner
|
||||||
[ E.width (E.px (cardColumns * (cardWidth + cardSpacing)))
|
|
||||||
, E.centerX
|
|
||||||
, E.spacing cardSpacing
|
|
||||||
]
|
|
||||||
<|
|
|
||||||
E.html <|
|
|
||||||
Spinner.view manaSpinner model.spinner
|
|
||||||
, navButton "→" cardPage.next
|
|
||||||
]
|
|
||||||
|
|
||||||
Ready cardPage ->
|
Ready cardPage ->
|
||||||
E.row
|
E.row
|
||||||
[ E.centerX
|
[ E.width E.fill
|
||||||
, E.height (E.px (3 * (cardHeight + cardSpacing)))
|
, E.height E.fill
|
||||||
, Font.family
|
, Font.color colors.text
|
||||||
[ Font.typeface
|
, E.scrollbarY
|
||||||
"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
|
|
||||||
]
|
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
[ details, cards cardPage ]
|
||||||
|
|
||||||
|
|
||||||
onEnter : msg -> E.Attribute msg
|
onEnter : msg -> E.Attribute msg
|
||||||
|
@ -507,51 +568,25 @@ view : Model -> Browser.Document Msg
|
||||||
view model =
|
view model =
|
||||||
{ title = "Tutor"
|
{ title = "Tutor"
|
||||||
, body =
|
, body =
|
||||||
[ E.layout [ Background.color colors.background ] <|
|
[ E.layout
|
||||||
E.column [ E.width (E.fill |> E.minimum 1280), E.spacing 20 ]
|
[ Background.color colors.background
|
||||||
[ E.row
|
, E.height E.fill
|
||||||
[ E.padding 10
|
]
|
||||||
, E.spacing 10
|
<|
|
||||||
|
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
|
, E.width E.fill
|
||||||
, Background.color colors.navBar
|
, Background.color colors.navBar
|
||||||
|
, E.alignBottom
|
||||||
]
|
]
|
||||||
[ Input.text
|
<|
|
||||||
[ onEnter Search
|
E.text "footer"
|
||||||
, Background.color colors.background
|
|
||||||
, Font.color colors.text
|
|
||||||
, E.width E.fill
|
|
||||||
]
|
|
||||||
{ onChange = UpdateCriteria << UpdateName
|
|
||||||
, text = model.criteria.query
|
|
||||||
, placeholder = Nothing
|
|
||||||
, label = Input.labelHidden "Search Input"
|
|
||||||
}
|
|
||||||
, Input.button
|
|
||||||
[ Background.color colors.primary
|
|
||||||
, Font.color colors.text
|
|
||||||
, Border.rounded 10
|
|
||||||
, E.padding 10
|
|
||||||
]
|
|
||||||
{ onPress = Just Search
|
|
||||||
, label = E.text "Search"
|
|
||||||
}
|
|
||||||
, Input.radio [ E.padding 10 ]
|
|
||||||
{ onChange = UpdateCriteria << UpdateSortBy
|
|
||||||
, selected = Just model.criteria.sortBy
|
|
||||||
, label = Input.labelLeft [ Font.color colors.text ] (E.text "Sort by")
|
|
||||||
, options =
|
|
||||||
[ Input.option "rarity" <| E.el [ Font.color colors.text ] <| E.text "Rarity DESC"
|
|
||||||
, Input.option "price" <| E.el [ Font.color colors.text ] <| E.text "Price DESC"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
, Input.checkbox []
|
|
||||||
{ onChange = UpdateCriteria << UpdateOwnedOnly
|
|
||||||
, icon = Input.defaultCheckbox
|
|
||||||
, checked = model.criteria.ownedOnly
|
|
||||||
, label = Input.labelRight [ Font.color colors.text ] (E.text "Owned only?")
|
|
||||||
}
|
|
||||||
]
|
|
||||||
, viewCardBrowser model
|
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,6 @@ type alias Card =
|
||||||
, prices : Prices
|
, prices : Prices
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
decode : Json.Decode.Decoder Card
|
decode : Json.Decode.Decoder Card
|
||||||
decode =
|
decode =
|
||||||
Json.Decode.succeed Card
|
Json.Decode.succeed Card
|
||||||
|
|
Loading…
Reference in a new issue