tutor/www/src/UI.elm

404 lines
10 KiB
Elm

module UI exposing
( Dimensions
, cardRow
, colors
, footer
, getViewport
, isMobile
, layout
, manaSpinner
, pageNotFound
, priceBadge
, subtitle
, text
, title
)
import Browser.Dom
import Card
import Color
import Element as E
import Element.Background as Background
import Element.Border as Border
import Element.Font as Font
import Maybe.Extra
import Round
import Spinner
import Symbol
import Task
import Url.Builder
type alias Dimensions =
{ width : Int
, height : Int
}
colors =
let
blue =
E.rgb255 100 100 255
slate =
E.rgb255 150 150 200
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
offwhite =
E.rgb255 200 200 200
mythic =
E.rgb255 205 55 0
rare =
E.rgb255 218 165 32
uncommon =
E.rgb255 112 128 144
common =
E.rgb255 47 79 79
in
{ primary = blue
, secondary = slate
, background = lightGrey
, navBar = darkerGrey
, sidebar = lighterGrey
, selected = darkGrey
, hover = grey
, title = white
, subtitle = offwhite
, text = offwhite
, mythic = mythic
, rare = rare
, uncommon = uncommon
, 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
}
}
}
getViewport : (Dimensions -> msg) -> Cmd msg
getViewport msg =
Task.perform
(\x ->
msg
{ width = floor x.viewport.width
, height = floor x.viewport.height
}
)
Browser.Dom.getViewport
isMobile : E.Device -> Bool
isMobile device =
case device.orientation of
E.Landscape ->
False
E.Portrait ->
True
manaSpinner : Spinner.Config
manaSpinner =
let
color index =
if index < 1.0 then
Color.red
else if index < 2.0 then
Color.green
else if index < 3.0 then
Color.purple
else if index < 4.0 then
Color.blue
else
Color.white
default =
Spinner.defaultConfig
in
{ default
| lines = 5.0
, length = 0.0
, width = 20
, color = color
}
text : String -> E.Element msg
text string =
E.el [ Font.color colors.text ] <| E.text string
title : String -> E.Element msg
title string =
E.el [ Font.color colors.title ] <| E.text string
subtitle : String -> E.Element msg
subtitle string =
E.el [ Font.size 16, Font.italic, Font.color colors.subtitle ] <| E.text string
priceBadge : { currency : String, amount : Float } -> E.Element msg
priceBadge { currency, amount } =
E.el
[ 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 <| Round.round 2 amount
]
cardRow : { foil : Bool, subtitle : String } -> List (E.Attribute msg) -> Symbol.Table -> Card.Card -> E.Element msg
cardRow options attributes symbols card =
let
badge color foil string =
E.el
[ Border.rounded 5
, Border.color color
, Border.width 1
, E.width <| E.px 60
, Font.family [ Font.typeface "sans" ]
, Font.size 10
, Font.color colors.title
]
<|
E.row [ E.height E.fill, E.width E.fill ]
[ E.el [ E.padding 2, E.width E.fill ] <| E.text string
, E.row [ E.padding 1, E.height E.fill, E.width E.fill, Background.color color ]
[ if foil then
E.el
[ E.width E.fill
, E.height E.fill
, Background.gradient
{ angle = 4.0
, steps =
[ E.rgb 148 0 211
, E.rgb 75 0 130
, E.rgb 0 0 255
, E.rgb 0 255 0
, E.rgb 255 255 0
, E.rgb 255 127 0
, E.rgb 255 0 0
]
}
]
E.none
else
E.none
]
]
setBadge : Bool -> E.Element msg
setBadge isFoil =
let
color =
case card.rarity of
"mythic" ->
colors.mythic
"rare" ->
colors.rare
"uncommon" ->
colors.uncommon
_ ->
colors.common
in
badge color isFoil card.setCode
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.row
(List.append
[ E.width E.fill
, E.spacing 10
, E.padding 3
, E.mouseOver [ Background.color colors.hover ]
]
attributes
)
[ 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", card.scryfallId ]
[ Url.Builder.string "format" "image"
, Url.Builder.string "version" "art_crop"
]
, description = card.name
}
, E.column [ E.centerY, E.height E.fill, E.width E.fill, E.clipX ]
[ 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.column [ E.alignRight, E.height E.fill ] <|
setBadge options.foil
:: List.map priceBadge prices
]
pageNotFound : E.Element msg
pageNotFound =
E.column [ E.centerX, E.centerY, E.spacing 20 ]
[ E.el [ E.centerX, Font.size 320, Font.heavy ] <| text "404"
, E.paragraph [ E.centerX, Font.center ]
[ subtitle "The page you requested could not be found" ]
]
footer : E.Element msg -> E.Element msg
footer element =
E.el
[ E.height (E.px 50)
, E.width E.fill
, E.clipX
, E.padding 10
, Font.color colors.text
, Background.color colors.navBar
, E.alignBottom
]
element
type alias Page msg =
{ toolbar : Maybe (E.Element msg)
, sidebar : Maybe (E.Element msg)
, content : E.Element msg
, footer : Maybe (E.Element msg)
}
layout : E.Device -> Page msg -> E.Element msg
layout device page =
let
toolbar : E.Element msg -> E.Element msg
toolbar element =
E.el
[ E.padding 10
, E.spacing 10
, E.width E.fill
, Background.color colors.navBar
]
element
sidebar : E.Element msg -> E.Element msg
sidebar element =
if isMobile device then
E.el
[ E.height E.fill
, E.width E.fill
, E.scrollbarY
, Background.color colors.sidebar
]
element
else
E.el
[ E.alignTop
, E.width <| E.fillPortion 1
, E.height E.fill
, E.scrollbarY
, Background.color colors.sidebar
]
element
content : E.Element msg -> E.Element msg
content element =
E.el
[ E.width <| E.fillPortion 3
, E.height E.fill
]
element
maybe : Maybe (E.Element msg) -> E.Element msg
maybe element =
Maybe.withDefault E.none element
in
E.column
[ E.width E.fill
, E.height E.fill
]
[ maybe <| Maybe.map toolbar page.toolbar
, if isMobile device then
Maybe.withDefault page.content page.sidebar
else
E.row
[ E.width E.fill
, E.height E.fill
, Font.color colors.text
, E.scrollbarY
]
[ maybe <| Maybe.map sidebar page.sidebar
, content page.content
]
, footer <| maybe page.footer
]