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 =
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
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 =
|
||||
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
|
||||
|
@ -350,8 +376,6 @@ viewCardBrowser model =
|
|||
, E.clip
|
||||
, E.width <| E.px dimensions.width
|
||||
, E.height <| E.px dimensions.height
|
||||
, Events.onMouseEnter <| ShowCardDetails cardModel
|
||||
, Events.onMouseLeave <| ClearCardDetails
|
||||
]
|
||||
<|
|
||||
E.image
|
||||
|
@ -370,22 +394,6 @@ viewCardBrowser model =
|
|||
, 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
|
||||
|
@ -403,6 +411,9 @@ viewCardBrowser model =
|
|||
, E.el [ E.width <| E.fillPortion 2, Font.alignRight ] <| E.text amount
|
||||
]
|
||||
|
||||
cardInfoWidth =
|
||||
E.px 300
|
||||
|
||||
cardDetails card =
|
||||
let
|
||||
prices =
|
||||
|
@ -419,71 +430,121 @@ viewCardBrowser model =
|
|||
, 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.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.row
|
||||
[ E.width E.fill
|
||||
, E.height (E.px (cardRows * (cardHeight + cardSpacing)))
|
||||
, E.centerX
|
||||
E.el
|
||||
[ 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
|
||||
]
|
||||
E.html <|
|
||||
Spinner.view manaSpinner model.spinner
|
||||
|
||||
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
|
||||
]
|
||||
]
|
||||
[ E.width E.fill
|
||||
, E.height E.fill
|
||||
, Font.color colors.text
|
||||
, E.scrollbarY
|
||||
]
|
||||
[ details, cards cardPage ]
|
||||
|
||||
|
||||
onEnter : msg -> E.Attribute msg
|
||||
|
@ -507,51 +568,25 @@ 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.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
|
||||
]
|
||||
[ 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.text "footer"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ type alias Card =
|
|||
, prices : Prices
|
||||
}
|
||||
|
||||
|
||||
decode : Json.Decode.Decoder Card
|
||||
decode =
|
||||
Json.Decode.succeed Card
|
||||
|
|
Loading…
Reference in a new issue