305 lines
7.2 KiB
Elm
305 lines
7.2 KiB
Elm
port module PhotoGroove exposing (Model, Msg(..), Photo, Status(..), init, initialModel, main, pageTitle, photoDecoder, subscriptions, update, view)
|
|
|
|
import Browser
|
|
import Html exposing (..)
|
|
import Html.Attributes as Attr exposing (class, classList, id, name, src, title, type_)
|
|
import Html.Events exposing (on, onClick)
|
|
import Http
|
|
import Json.Decode exposing (Decoder, at, int, list, string, succeed)
|
|
import Json.Decode.Pipeline exposing (optional, required)
|
|
import Json.Encode
|
|
import Random
|
|
import UrlPrefix
|
|
|
|
|
|
type Msg
|
|
= ClickedPhoto String
|
|
| GotRandomPhoto Photo
|
|
| ClickedSize ThumbnailSize
|
|
| ClickedSurpriseMe
|
|
| GotPhotos (Result Http.Error (List Photo))
|
|
| SlidHue Int
|
|
| SlidRipple Int
|
|
| SlidNoise Int
|
|
| GotActivity String
|
|
|
|
|
|
pageTitle : String
|
|
pageTitle =
|
|
"Gallery"
|
|
|
|
|
|
view : Model -> Html Msg
|
|
view model =
|
|
div [ class "content" ] <|
|
|
case model.status of
|
|
Loaded photos selectedUrl ->
|
|
viewLoaded photos selectedUrl model
|
|
|
|
Loading ->
|
|
[]
|
|
|
|
Errored errorMessage ->
|
|
[ text ("Error: " ++ errorMessage) ]
|
|
|
|
|
|
viewFilter : (Int -> Msg) -> String -> Int -> Html Msg
|
|
viewFilter toMsg name magnitude =
|
|
div [ class "filter-slider" ]
|
|
[ label [] [ text name ]
|
|
, rangeSlider
|
|
[ Attr.max "11"
|
|
, Attr.property "val" (Json.Encode.int magnitude)
|
|
, onSlide toMsg
|
|
]
|
|
[]
|
|
, label [] [ text (String.fromInt magnitude) ]
|
|
]
|
|
|
|
|
|
viewLoaded : List Photo -> String -> Model -> List (Html Msg)
|
|
viewLoaded photos selectedUrl model =
|
|
[ button
|
|
[ onClick ClickedSurpriseMe ]
|
|
[ text "Surprise Me" ]
|
|
, div [ class "activity" ] [ text model.activity ]
|
|
, div [ class "filters" ]
|
|
[ viewFilter SlidHue "Hue" model.hue
|
|
, viewFilter SlidRipple "Ripple" model.ripple
|
|
, viewFilter SlidNoise "Noise" model.noise
|
|
]
|
|
, h3 [] [ text "Thumbnail Size" ]
|
|
, div [ id "choose-size" ]
|
|
(List.map viewSizeChooser [ Small, Medium, Large ])
|
|
, div [ id "thumbnails", class (sizeToString model.chosenSize) ]
|
|
(List.map (viewThumbnail selectedUrl) photos)
|
|
, canvas [ id "main-canvas", class "large" ] []
|
|
]
|
|
|
|
|
|
viewThumbnail : String -> Photo -> Html Msg
|
|
viewThumbnail selectedUrl thumb =
|
|
img
|
|
[ src (UrlPrefix.thumbUrl thumb.url)
|
|
, title (thumb.title ++ " [" ++ String.fromInt thumb.size ++ " KB]")
|
|
, classList [ ( "selected", selectedUrl == thumb.url ) ]
|
|
, onClick (ClickedPhoto thumb.url)
|
|
]
|
|
[]
|
|
|
|
|
|
viewSizeChooser : ThumbnailSize -> Html Msg
|
|
viewSizeChooser size =
|
|
label []
|
|
[ input [ type_ "radio", name "size", onClick (ClickedSize size) ] []
|
|
, text (sizeToString size)
|
|
]
|
|
|
|
|
|
sizeToString : ThumbnailSize -> String
|
|
sizeToString size =
|
|
case size of
|
|
Small ->
|
|
"small"
|
|
|
|
Medium ->
|
|
"med"
|
|
|
|
Large ->
|
|
"large"
|
|
|
|
|
|
type ThumbnailSize
|
|
= Small
|
|
| Medium
|
|
| Large
|
|
|
|
|
|
port setFilters : FilterOptions -> Cmd msg
|
|
|
|
|
|
port activityChanges : (String -> msg) -> Sub msg
|
|
|
|
|
|
type alias FilterOptions =
|
|
{ url : String
|
|
, filters : List { name : String, amount : Float }
|
|
}
|
|
|
|
|
|
type alias Photo =
|
|
{ url : String
|
|
, size : Int
|
|
, title : String
|
|
}
|
|
|
|
|
|
type Status
|
|
= Loading
|
|
| Loaded (List Photo) String
|
|
| Errored String
|
|
|
|
|
|
type alias Model =
|
|
{ status : Status
|
|
, activity : String
|
|
, chosenSize : ThumbnailSize
|
|
, hue : Int
|
|
, ripple : Int
|
|
, noise : Int
|
|
}
|
|
|
|
|
|
initialModel : Model
|
|
initialModel =
|
|
{ status = Loading
|
|
, activity = ""
|
|
, chosenSize = Medium
|
|
, hue = 5
|
|
, ripple = 5
|
|
, noise = 5
|
|
}
|
|
|
|
|
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
|
update msg model =
|
|
case msg of
|
|
GotRandomPhoto photo ->
|
|
applyFilters { model | status = selectUrl photo.url model.status }
|
|
|
|
ClickedPhoto selectedUrl ->
|
|
applyFilters { model | status = selectUrl selectedUrl model.status }
|
|
|
|
ClickedSize size ->
|
|
( { model | chosenSize = size }, Cmd.none )
|
|
|
|
ClickedSurpriseMe ->
|
|
case model.status of
|
|
Loaded (firstPhoto :: otherPhotos) _ ->
|
|
Random.uniform firstPhoto otherPhotos
|
|
|> Random.generate GotRandomPhoto
|
|
|> Tuple.pair model
|
|
|
|
Loaded [] _ ->
|
|
( model, Cmd.none )
|
|
|
|
Loading ->
|
|
( model, Cmd.none )
|
|
|
|
Errored _ ->
|
|
( model, Cmd.none )
|
|
|
|
GotPhotos (Ok photos) ->
|
|
case photos of
|
|
first :: _ ->
|
|
applyFilters { model | status = Loaded photos first.url }
|
|
|
|
[] ->
|
|
( { model | status = Errored "0 photos found." }, Cmd.none )
|
|
|
|
GotPhotos (Err _) ->
|
|
( { model | status = Errored "Server error!" }, Cmd.none )
|
|
|
|
SlidHue hue ->
|
|
applyFilters { model | hue = hue }
|
|
|
|
SlidRipple ripple ->
|
|
applyFilters { model | ripple = ripple }
|
|
|
|
SlidNoise noise ->
|
|
applyFilters { model | noise = noise }
|
|
|
|
GotActivity activity ->
|
|
( { model | activity = activity }, Cmd.none )
|
|
|
|
|
|
applyFilters : Model -> ( Model, Cmd Msg )
|
|
applyFilters model =
|
|
case model.status of
|
|
Loaded _ selectedUrl ->
|
|
let
|
|
filters =
|
|
[ { name = "Hue", amount = toFloat model.hue / 11 }
|
|
, { name = "Ripple", amount = toFloat model.ripple / 11 }
|
|
, { name = "Noise", amount = toFloat model.noise / 11 }
|
|
]
|
|
|
|
url =
|
|
UrlPrefix.largeUrl selectedUrl
|
|
|
|
cmd =
|
|
setFilters { url = url, filters = filters }
|
|
in
|
|
( model, cmd )
|
|
|
|
Loading ->
|
|
( model, Cmd.none )
|
|
|
|
Errored _ ->
|
|
( model, Cmd.none )
|
|
|
|
|
|
selectUrl : String -> Status -> Status
|
|
selectUrl url status =
|
|
case status of
|
|
Loaded photos _ ->
|
|
Loaded photos url
|
|
|
|
Loading ->
|
|
status
|
|
|
|
Errored _ ->
|
|
status
|
|
|
|
|
|
photoDecoder : Decoder Photo
|
|
photoDecoder =
|
|
succeed Photo
|
|
|> required "url" string
|
|
|> required "size" int
|
|
|> optional "title" string "(untitled)"
|
|
|
|
|
|
initialCommand : Cmd Msg
|
|
initialCommand =
|
|
Http.get
|
|
{ url = "http://elm-in-action.com/photos/list.json"
|
|
, expect = Http.expectJson GotPhotos <| list photoDecoder
|
|
}
|
|
|
|
|
|
main : Program Float Model Msg
|
|
main =
|
|
Browser.element
|
|
{ init = init
|
|
, view = view
|
|
, update = update
|
|
, subscriptions = subscriptions
|
|
}
|
|
|
|
|
|
init : Float -> ( Model, Cmd Msg )
|
|
init flags =
|
|
let
|
|
activity =
|
|
"Initializing Pasta v" ++ String.fromFloat flags
|
|
in
|
|
( { initialModel | activity = activity }, initialCommand )
|
|
|
|
|
|
subscriptions : Model -> Sub Msg
|
|
subscriptions _ =
|
|
activityChanges GotActivity
|
|
|
|
|
|
rangeSlider : List (Attribute msg) -> List (Html msg) -> Html msg
|
|
rangeSlider attributes children =
|
|
node "range-slider" attributes children
|
|
|
|
|
|
onSlide : (Int -> msg) -> Attribute msg
|
|
onSlide toMsg =
|
|
at [ "detail", "userSlidTo" ] int
|
|
|> Json.Decode.map toMsg
|
|
|> on "slide"
|