From 00616067c8a27bf93ab5d5788e6c3953c50629e5 Mon Sep 17 00:00:00 2001 From: Correl Date: Thu, 10 Feb 2022 23:05:51 -0500 Subject: [PATCH] Switch the UI to a scrollable list --- www/src/App.elm | 323 ++++++++++++++++++++++++++--------------------- www/src/Card.elm | 1 - 2 files changed, 179 insertions(+), 145 deletions(-) diff --git a/www/src/App.elm b/www/src/App.elm index c5e9b81..46ea41f 100644 --- a/www/src/App.elm +++ b/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" ] ] } diff --git a/www/src/Card.elm b/www/src/Card.elm index 5f895fd..b7db051 100644 --- a/www/src/Card.elm +++ b/www/src/Card.elm @@ -22,7 +22,6 @@ type alias Card = , prices : Prices } - decode : Json.Decode.Decoder Card decode = Json.Decode.succeed Card