chore: Move everything into an internal module
This commit is contained in:
parent
796b1697b8
commit
a0c0a8354a
4 changed files with 755 additions and 746 deletions
|
|
@ -1,370 +1,8 @@
|
|||
port module PhotoGroove exposing (main, photoDecoder)
|
||||
module PhotoGroove exposing (main)
|
||||
|
||||
import Browser
|
||||
import Html exposing (Attribute, Html, button, canvas, div, h1, h3, img, input, label, node, text)
|
||||
import Html.Attributes as Attr exposing (..)
|
||||
import Html.Events exposing (on, onClick)
|
||||
import Http
|
||||
import Json.Decode as D exposing (Decoder)
|
||||
import Json.Decode.Pipeline as D
|
||||
import Json.Encode
|
||||
import Random
|
||||
import PhotoGroove.Internal as Internal
|
||||
|
||||
|
||||
type Status
|
||||
= Loading
|
||||
| Loaded (List Photo) String
|
||||
| Errored String
|
||||
|
||||
|
||||
type alias FilterValues =
|
||||
{ hue : Int
|
||||
, ripple : Int
|
||||
, noise : Int
|
||||
}
|
||||
|
||||
|
||||
type alias FilterOptions =
|
||||
{ url : String
|
||||
, filters : List { name : String, amount : Float }
|
||||
}
|
||||
|
||||
|
||||
port setFilters : FilterOptions -> Cmd msg
|
||||
|
||||
|
||||
port activityChanges : (String -> msg) -> Sub msg
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ status : Status
|
||||
, chosenSize : ThumbnailSize
|
||||
, filterValues : FilterValues
|
||||
, activity : String
|
||||
}
|
||||
|
||||
|
||||
type alias Photo =
|
||||
{ url : String
|
||||
, size : Int
|
||||
, title : String
|
||||
}
|
||||
|
||||
|
||||
photoDecoder : Decoder Photo
|
||||
photoDecoder =
|
||||
D.succeed Photo
|
||||
|> D.required "url" D.string
|
||||
|> D.required "size" D.int
|
||||
|> D.optional "title" D.string "(untitled)"
|
||||
|
||||
|
||||
type ThumbnailSize
|
||||
= Small
|
||||
| Medium
|
||||
| Large
|
||||
|
||||
|
||||
initialModel : Model
|
||||
initialModel =
|
||||
{ status = Loading
|
||||
, chosenSize = Medium
|
||||
, filterValues = { hue = 0, ripple = 0, noise = 0 }
|
||||
, activity = ""
|
||||
}
|
||||
|
||||
|
||||
initialCommand : Cmd Message
|
||||
initialCommand =
|
||||
Http.get
|
||||
{ url = "list"
|
||||
, expect = Http.expectJson GotPhotos (D.list photoDecoder)
|
||||
}
|
||||
|
||||
|
||||
view : Model -> Html Message
|
||||
view model =
|
||||
div [ class "content" ] <|
|
||||
case model.status of
|
||||
Loading ->
|
||||
[]
|
||||
|
||||
Loaded photos selected ->
|
||||
viewLoaded model photos selected
|
||||
|
||||
Errored error ->
|
||||
[ text ("Error: " ++ error) ]
|
||||
|
||||
|
||||
viewLoaded : Model -> List Photo -> String -> List (Html Message)
|
||||
viewLoaded model photos selected =
|
||||
[ h1 [] [ text "Photo Groove" ]
|
||||
, button [ onClick ClickedSurpriseMe ] [ text "Surprise me!" ]
|
||||
, div [ class "activity" ] [ text model.activity ]
|
||||
, div [ class "filters" ]
|
||||
[ viewFilter Hue model.filterValues.hue
|
||||
, viewFilter Ripple model.filterValues.ripple
|
||||
, viewFilter Noise model.filterValues.noise
|
||||
]
|
||||
, h3 [] [ text "Thumbnail Size:" ]
|
||||
, div [ id "choose-size" ]
|
||||
(List.map viewSizeChooser [ Small, Medium, Large ])
|
||||
, div
|
||||
[ id "thumbnails"
|
||||
, class (sizeToClass model.chosenSize)
|
||||
]
|
||||
(List.map (viewThumbnail selected) photos)
|
||||
, img [ class "large", src (urlPrefix ++ "large/" ++ selected) ] []
|
||||
, canvas [ id "canvas-main", class "large" ] []
|
||||
]
|
||||
|
||||
|
||||
viewThumbnail : String -> Photo -> Html Message
|
||||
viewThumbnail selectedUrl thumb =
|
||||
img
|
||||
[ src (urlPrefix ++ thumb.url)
|
||||
, title (thumb.title ++ " [" ++ String.fromInt thumb.size ++ " KB]")
|
||||
, classList [ ( "selected", selectedUrl == thumb.url ) ]
|
||||
, onClick (ClickedThumbnail thumb.url)
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
viewSizeChooser : ThumbnailSize -> Html Message
|
||||
viewSizeChooser size =
|
||||
label []
|
||||
[ input
|
||||
[ type_ "radio"
|
||||
, name "size"
|
||||
, onClick (ClickedSize size)
|
||||
]
|
||||
[]
|
||||
, text (sizeToString size)
|
||||
]
|
||||
|
||||
|
||||
viewFilter : FilterType -> Int -> Html Message
|
||||
viewFilter filterType magnitude =
|
||||
div
|
||||
[ class "filter-slider"
|
||||
]
|
||||
[ label [] [ text <| filterTypeToName filterType ]
|
||||
, rangeSlider
|
||||
[ Attr.max "11"
|
||||
, property "val" (Json.Encode.int magnitude)
|
||||
, onSlide (ChangedFilter filterType)
|
||||
]
|
||||
[]
|
||||
, label [] [ text (String.fromInt magnitude) ]
|
||||
]
|
||||
|
||||
|
||||
sizeToString : ThumbnailSize -> String
|
||||
sizeToString size =
|
||||
case size of
|
||||
Small ->
|
||||
"small"
|
||||
|
||||
Medium ->
|
||||
"medium"
|
||||
|
||||
Large ->
|
||||
"large"
|
||||
|
||||
|
||||
sizeToClass : ThumbnailSize -> String
|
||||
sizeToClass size =
|
||||
case size of
|
||||
Small ->
|
||||
"small"
|
||||
|
||||
Medium ->
|
||||
"med"
|
||||
|
||||
Large ->
|
||||
"large"
|
||||
|
||||
|
||||
type Message
|
||||
= ClickedThumbnail String
|
||||
| ClickedSurpriseMe
|
||||
| ClickedSize ThumbnailSize
|
||||
| ChangedFilter FilterType Int
|
||||
| GotRandomPhoto Photo
|
||||
| GotPhotos (Result Http.Error (List Photo))
|
||||
| GotActivity String
|
||||
|
||||
|
||||
type FilterType
|
||||
= Hue
|
||||
| Ripple
|
||||
| Noise
|
||||
|
||||
|
||||
filterTypeToName : FilterType -> String
|
||||
filterTypeToName t =
|
||||
case t of
|
||||
Hue ->
|
||||
"Hue"
|
||||
|
||||
Ripple ->
|
||||
"Ripple"
|
||||
|
||||
Noise ->
|
||||
"Noise"
|
||||
|
||||
|
||||
update : Message -> Model -> ( Model, Cmd Message )
|
||||
update msg model =
|
||||
case msg of
|
||||
ClickedThumbnail thumb ->
|
||||
applyFilters { model | status = selectUrl thumb model.status }
|
||||
|
||||
ClickedSurpriseMe ->
|
||||
case model.status of
|
||||
Loaded (photo :: morePhotos) _ ->
|
||||
Random.uniform photo morePhotos
|
||||
|> Random.generate GotRandomPhoto
|
||||
|> Tuple.pair model
|
||||
|
||||
Loaded [] _ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
Loading ->
|
||||
( model, Cmd.none )
|
||||
|
||||
Errored _ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
ClickedSize size ->
|
||||
( { model | chosenSize = size }, Cmd.none )
|
||||
|
||||
ChangedFilter f val ->
|
||||
applyFilters { model | filterValues = setFilterValue f val model.filterValues }
|
||||
|
||||
GotRandomPhoto photo ->
|
||||
applyFilters { model | status = selectUrl photo.url model.status }
|
||||
|
||||
GotPhotos (Ok ((firstPhoto :: _) as photos)) ->
|
||||
applyFilters { model | status = Loaded photos firstPhoto.url }
|
||||
|
||||
GotPhotos (Ok []) ->
|
||||
( { model | status = Errored "No photos!" }, Cmd.none )
|
||||
|
||||
GotPhotos (Err httpError) ->
|
||||
( { model | status = Errored <| ("Failed to load photos: " ++ httpErrorToString httpError) }, Cmd.none )
|
||||
|
||||
GotActivity activity ->
|
||||
( { model | activity = activity }, Cmd.none )
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Message
|
||||
subscriptions _ =
|
||||
activityChanges GotActivity
|
||||
|
||||
|
||||
init : Float -> ( Model, Cmd Message )
|
||||
init flags =
|
||||
let
|
||||
activity =
|
||||
"Initializing Pasta v" ++ String.fromFloat flags
|
||||
in
|
||||
( { initialModel | activity = activity }, initialCommand )
|
||||
|
||||
|
||||
setFilterValue : FilterType -> Int -> FilterValues -> FilterValues
|
||||
setFilterValue filterType val values =
|
||||
case filterType of
|
||||
Hue ->
|
||||
{ values | hue = val }
|
||||
|
||||
Ripple ->
|
||||
{ values | ripple = val }
|
||||
|
||||
Noise ->
|
||||
{ values | noise = val }
|
||||
|
||||
|
||||
applyFilters : Model -> ( Model, Cmd msg )
|
||||
applyFilters model =
|
||||
case model.status of
|
||||
Loaded photos selectedUrl ->
|
||||
let
|
||||
inPecent v =
|
||||
toFloat v / 11
|
||||
|
||||
filters =
|
||||
[ { name = filterTypeToName Hue, amount = inPecent model.filterValues.hue }
|
||||
, { name = filterTypeToName Ripple, amount = inPecent model.filterValues.ripple }
|
||||
, { name = filterTypeToName Noise, amount = inPecent model.filterValues.noise }
|
||||
]
|
||||
|
||||
url =
|
||||
urlPrefix ++ "large/" ++ selectedUrl
|
||||
in
|
||||
( model, setFilters { url = url, filters = filters } )
|
||||
|
||||
Loading ->
|
||||
( model, Cmd.none )
|
||||
|
||||
Errored _ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
selectUrl : String -> Status -> Status
|
||||
selectUrl url status =
|
||||
case status of
|
||||
Loading ->
|
||||
status
|
||||
|
||||
Loaded photos _ ->
|
||||
Loaded photos url
|
||||
|
||||
Errored _ ->
|
||||
status
|
||||
|
||||
|
||||
httpErrorToString : Http.Error -> String
|
||||
httpErrorToString err =
|
||||
case err of
|
||||
Http.BadUrl msg ->
|
||||
"bad URL: " ++ msg
|
||||
|
||||
Http.Timeout ->
|
||||
"timeout"
|
||||
|
||||
Http.NetworkError ->
|
||||
"network error"
|
||||
|
||||
Http.BadStatus status ->
|
||||
"bad status: " ++ String.fromInt status
|
||||
|
||||
Http.BadBody msg ->
|
||||
"bad body: " ++ msg
|
||||
|
||||
|
||||
urlPrefix : String
|
||||
urlPrefix =
|
||||
"http://elm-in-action.com/"
|
||||
|
||||
|
||||
main : Program Float Model Message
|
||||
main : Program Float Internal.Model Internal.Message
|
||||
main =
|
||||
Browser.element
|
||||
{ init = init
|
||||
, subscriptions = subscriptions
|
||||
, view = view
|
||||
, update = update
|
||||
}
|
||||
|
||||
|
||||
rangeSlider : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
rangeSlider =
|
||||
node "range-slider"
|
||||
|
||||
|
||||
onSlide : (Int -> msg) -> Attribute msg
|
||||
onSlide toMsg =
|
||||
D.at [ "detail", "userSlidTo" ] D.int
|
||||
|> D.map toMsg
|
||||
|> on "slide"
|
||||
Internal.main
|
||||
|
|
|
|||
370
src/PhotoGroove/Internal.elm
Normal file
370
src/PhotoGroove/Internal.elm
Normal file
|
|
@ -0,0 +1,370 @@
|
|||
port module PhotoGroove.Internal exposing (..)
|
||||
|
||||
import Browser
|
||||
import Html exposing (Attribute, Html, button, canvas, div, h1, h3, img, input, label, node, text)
|
||||
import Html.Attributes as Attr exposing (..)
|
||||
import Html.Events exposing (on, onClick)
|
||||
import Http
|
||||
import Json.Decode as D exposing (Decoder)
|
||||
import Json.Decode.Pipeline as D
|
||||
import Json.Encode
|
||||
import Random
|
||||
|
||||
|
||||
type Status
|
||||
= Loading
|
||||
| Loaded (List Photo) String
|
||||
| Errored String
|
||||
|
||||
|
||||
type alias FilterValues =
|
||||
{ hue : Int
|
||||
, ripple : Int
|
||||
, noise : Int
|
||||
}
|
||||
|
||||
|
||||
type alias FilterOptions =
|
||||
{ url : String
|
||||
, filters : List { name : String, amount : Float }
|
||||
}
|
||||
|
||||
|
||||
port setFilters : FilterOptions -> Cmd msg
|
||||
|
||||
|
||||
port activityChanges : (String -> msg) -> Sub msg
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ status : Status
|
||||
, chosenSize : ThumbnailSize
|
||||
, filterValues : FilterValues
|
||||
, activity : String
|
||||
}
|
||||
|
||||
|
||||
type alias Photo =
|
||||
{ url : String
|
||||
, size : Int
|
||||
, title : String
|
||||
}
|
||||
|
||||
|
||||
photoDecoder : Decoder Photo
|
||||
photoDecoder =
|
||||
D.succeed Photo
|
||||
|> D.required "url" D.string
|
||||
|> D.required "size" D.int
|
||||
|> D.optional "title" D.string "(untitled)"
|
||||
|
||||
|
||||
type ThumbnailSize
|
||||
= Small
|
||||
| Medium
|
||||
| Large
|
||||
|
||||
|
||||
initialModel : Model
|
||||
initialModel =
|
||||
{ status = Loading
|
||||
, chosenSize = Medium
|
||||
, filterValues = { hue = 0, ripple = 0, noise = 0 }
|
||||
, activity = ""
|
||||
}
|
||||
|
||||
|
||||
initialCommand : Cmd Message
|
||||
initialCommand =
|
||||
Http.get
|
||||
{ url = "list"
|
||||
, expect = Http.expectJson GotPhotos (D.list photoDecoder)
|
||||
}
|
||||
|
||||
|
||||
view : Model -> Html Message
|
||||
view model =
|
||||
div [ class "content" ] <|
|
||||
case model.status of
|
||||
Loading ->
|
||||
[]
|
||||
|
||||
Loaded photos selected ->
|
||||
viewLoaded model photos selected
|
||||
|
||||
Errored error ->
|
||||
[ text ("Error: " ++ error) ]
|
||||
|
||||
|
||||
viewLoaded : Model -> List Photo -> String -> List (Html Message)
|
||||
viewLoaded model photos selected =
|
||||
[ h1 [] [ text "Photo Groove" ]
|
||||
, button [ onClick ClickedSurpriseMe ] [ text "Surprise me!" ]
|
||||
, div [ class "activity" ] [ text model.activity ]
|
||||
, div [ class "filters" ]
|
||||
[ viewFilter Hue model.filterValues.hue
|
||||
, viewFilter Ripple model.filterValues.ripple
|
||||
, viewFilter Noise model.filterValues.noise
|
||||
]
|
||||
, h3 [] [ text "Thumbnail Size:" ]
|
||||
, div [ id "choose-size" ]
|
||||
(List.map viewSizeChooser [ Small, Medium, Large ])
|
||||
, div
|
||||
[ id "thumbnails"
|
||||
, class (sizeToClass model.chosenSize)
|
||||
]
|
||||
(List.map (viewThumbnail selected) photos)
|
||||
, img [ class "large", src (urlPrefix ++ "large/" ++ selected) ] []
|
||||
, canvas [ id "canvas-main", class "large" ] []
|
||||
]
|
||||
|
||||
|
||||
viewThumbnail : String -> Photo -> Html Message
|
||||
viewThumbnail selectedUrl thumb =
|
||||
img
|
||||
[ src (urlPrefix ++ thumb.url)
|
||||
, title (thumb.title ++ " [" ++ String.fromInt thumb.size ++ " KB]")
|
||||
, classList [ ( "selected", selectedUrl == thumb.url ) ]
|
||||
, onClick (ClickedThumbnail thumb.url)
|
||||
]
|
||||
[]
|
||||
|
||||
|
||||
viewSizeChooser : ThumbnailSize -> Html Message
|
||||
viewSizeChooser size =
|
||||
label []
|
||||
[ input
|
||||
[ type_ "radio"
|
||||
, name "size"
|
||||
, onClick (ClickedSize size)
|
||||
]
|
||||
[]
|
||||
, text (sizeToString size)
|
||||
]
|
||||
|
||||
|
||||
viewFilter : FilterType -> Int -> Html Message
|
||||
viewFilter filterType magnitude =
|
||||
div
|
||||
[ class "filter-slider"
|
||||
]
|
||||
[ label [] [ text <| filterTypeToName filterType ]
|
||||
, rangeSlider
|
||||
[ Attr.max "11"
|
||||
, property "val" (Json.Encode.int magnitude)
|
||||
, onSlide (ChangedFilter filterType)
|
||||
]
|
||||
[]
|
||||
, label [] [ text (String.fromInt magnitude) ]
|
||||
]
|
||||
|
||||
|
||||
sizeToString : ThumbnailSize -> String
|
||||
sizeToString size =
|
||||
case size of
|
||||
Small ->
|
||||
"small"
|
||||
|
||||
Medium ->
|
||||
"medium"
|
||||
|
||||
Large ->
|
||||
"large"
|
||||
|
||||
|
||||
sizeToClass : ThumbnailSize -> String
|
||||
sizeToClass size =
|
||||
case size of
|
||||
Small ->
|
||||
"small"
|
||||
|
||||
Medium ->
|
||||
"med"
|
||||
|
||||
Large ->
|
||||
"large"
|
||||
|
||||
|
||||
type Message
|
||||
= ClickedThumbnail String
|
||||
| ClickedSurpriseMe
|
||||
| ClickedSize ThumbnailSize
|
||||
| ChangedFilter FilterType Int
|
||||
| GotRandomPhoto Photo
|
||||
| GotPhotos (Result Http.Error (List Photo))
|
||||
| GotActivity String
|
||||
|
||||
|
||||
type FilterType
|
||||
= Hue
|
||||
| Ripple
|
||||
| Noise
|
||||
|
||||
|
||||
filterTypeToName : FilterType -> String
|
||||
filterTypeToName t =
|
||||
case t of
|
||||
Hue ->
|
||||
"Hue"
|
||||
|
||||
Ripple ->
|
||||
"Ripple"
|
||||
|
||||
Noise ->
|
||||
"Noise"
|
||||
|
||||
|
||||
update : Message -> Model -> ( Model, Cmd Message )
|
||||
update msg model =
|
||||
case msg of
|
||||
ClickedThumbnail thumb ->
|
||||
applyFilters { model | status = selectUrl thumb model.status }
|
||||
|
||||
ClickedSurpriseMe ->
|
||||
case model.status of
|
||||
Loaded (photo :: morePhotos) _ ->
|
||||
Random.uniform photo morePhotos
|
||||
|> Random.generate GotRandomPhoto
|
||||
|> Tuple.pair model
|
||||
|
||||
Loaded [] _ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
Loading ->
|
||||
( model, Cmd.none )
|
||||
|
||||
Errored _ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
ClickedSize size ->
|
||||
( { model | chosenSize = size }, Cmd.none )
|
||||
|
||||
ChangedFilter f val ->
|
||||
applyFilters { model | filterValues = setFilterValue f val model.filterValues }
|
||||
|
||||
GotRandomPhoto photo ->
|
||||
applyFilters { model | status = selectUrl photo.url model.status }
|
||||
|
||||
GotPhotos (Ok ((firstPhoto :: _) as photos)) ->
|
||||
applyFilters { model | status = Loaded photos firstPhoto.url }
|
||||
|
||||
GotPhotos (Ok []) ->
|
||||
( { model | status = Errored "No photos!" }, Cmd.none )
|
||||
|
||||
GotPhotos (Err httpError) ->
|
||||
( { model | status = Errored <| ("Failed to load photos: " ++ httpErrorToString httpError) }, Cmd.none )
|
||||
|
||||
GotActivity activity ->
|
||||
( { model | activity = activity }, Cmd.none )
|
||||
|
||||
|
||||
subscriptions : Model -> Sub Message
|
||||
subscriptions _ =
|
||||
activityChanges GotActivity
|
||||
|
||||
|
||||
init : Float -> ( Model, Cmd Message )
|
||||
init flags =
|
||||
let
|
||||
activity =
|
||||
"Initializing Pasta v" ++ String.fromFloat flags
|
||||
in
|
||||
( { initialModel | activity = activity }, initialCommand )
|
||||
|
||||
|
||||
setFilterValue : FilterType -> Int -> FilterValues -> FilterValues
|
||||
setFilterValue filterType val values =
|
||||
case filterType of
|
||||
Hue ->
|
||||
{ values | hue = val }
|
||||
|
||||
Ripple ->
|
||||
{ values | ripple = val }
|
||||
|
||||
Noise ->
|
||||
{ values | noise = val }
|
||||
|
||||
|
||||
applyFilters : Model -> ( Model, Cmd msg )
|
||||
applyFilters model =
|
||||
case model.status of
|
||||
Loaded photos selectedUrl ->
|
||||
let
|
||||
inPecent v =
|
||||
toFloat v / 11
|
||||
|
||||
filters =
|
||||
[ { name = filterTypeToName Hue, amount = inPecent model.filterValues.hue }
|
||||
, { name = filterTypeToName Ripple, amount = inPecent model.filterValues.ripple }
|
||||
, { name = filterTypeToName Noise, amount = inPecent model.filterValues.noise }
|
||||
]
|
||||
|
||||
url =
|
||||
urlPrefix ++ "large/" ++ selectedUrl
|
||||
in
|
||||
( model, setFilters { url = url, filters = filters } )
|
||||
|
||||
Loading ->
|
||||
( model, Cmd.none )
|
||||
|
||||
Errored _ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
selectUrl : String -> Status -> Status
|
||||
selectUrl url status =
|
||||
case status of
|
||||
Loading ->
|
||||
status
|
||||
|
||||
Loaded photos _ ->
|
||||
Loaded photos url
|
||||
|
||||
Errored _ ->
|
||||
status
|
||||
|
||||
|
||||
httpErrorToString : Http.Error -> String
|
||||
httpErrorToString err =
|
||||
case err of
|
||||
Http.BadUrl msg ->
|
||||
"bad URL: " ++ msg
|
||||
|
||||
Http.Timeout ->
|
||||
"timeout"
|
||||
|
||||
Http.NetworkError ->
|
||||
"network error"
|
||||
|
||||
Http.BadStatus status ->
|
||||
"bad status: " ++ String.fromInt status
|
||||
|
||||
Http.BadBody msg ->
|
||||
"bad body: " ++ msg
|
||||
|
||||
|
||||
urlPrefix : String
|
||||
urlPrefix =
|
||||
"http://elm-in-action.com/"
|
||||
|
||||
|
||||
main : Program Float Model Message
|
||||
main =
|
||||
Browser.element
|
||||
{ init = init
|
||||
, subscriptions = subscriptions
|
||||
, view = view
|
||||
, update = update
|
||||
}
|
||||
|
||||
|
||||
rangeSlider : List (Html.Attribute msg) -> List (Html msg) -> Html msg
|
||||
rangeSlider =
|
||||
node "range-slider"
|
||||
|
||||
|
||||
onSlide : (Int -> msg) -> Attribute msg
|
||||
onSlide toMsg =
|
||||
D.at [ "detail", "userSlidTo" ] D.int
|
||||
|> D.map toMsg
|
||||
|> on "slide"
|
||||
|
|
@ -4,7 +4,7 @@ import Expect exposing (Expectation)
|
|||
import Fuzz exposing (Fuzzer, int, list, string)
|
||||
import Json.Decode as J
|
||||
import Json.Encode as Encode exposing (Value)
|
||||
import PhotoGroove as Testee
|
||||
import PhotoGroove.Internal as Testee
|
||||
import Test exposing (..)
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue