initial commit
commit
b6cb2a2750
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"NoRedInk/elm-json-decode-pipeline": "1.0.1",
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/http": "2.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/random": "1.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/bytes": "1.0.8",
|
||||
"elm/file": "1.0.5",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.3"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="http://elm-in-action.com/styles.css">
|
||||
<link rel="stylesheet" href="http://elm-in-action.com/range-slider.css">
|
||||
<script src="http://elm-in-action.com/range-slider.js"></script>
|
||||
<script>
|
||||
class RangeSlider extends HTMLElement {
|
||||
connectedCallback() {
|
||||
var input = document.createElement("input");
|
||||
this.appendChild(input);
|
||||
|
||||
var jsr = new JSR(input, {
|
||||
max: this.max,
|
||||
values: [this.val],
|
||||
sliders: 1,
|
||||
grid: false
|
||||
});
|
||||
|
||||
var rangeSliderNode = this;
|
||||
|
||||
jsr.addEventListener("update", function(elem, value) {
|
||||
var event = new CustomEvent("slide", {
|
||||
detail: {userSlidTo: value}
|
||||
});
|
||||
|
||||
rangeSliderNode.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("range-slider", RangeSlider);
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div> <!-- Elm application renders into this div --!>
|
||||
|
||||
<script src="http://elm-in-action.com/pasta.js"></script>
|
||||
<script src="app.js"></script> <!-- PhotoGroove.elm will get compiled into app.js --!>
|
||||
<script>
|
||||
var app = Elm.PhotoGroove.init({
|
||||
node: document.getElementById("app"),
|
||||
flags: Pasta.version
|
||||
}); // Elm object comes from app.js
|
||||
|
||||
app.ports.setFilters.subscribe(function(options) {
|
||||
requestAnimationFrame(function() {
|
||||
Pasta.apply(document.getElementById("main-canvas"), options);
|
||||
});
|
||||
});
|
||||
|
||||
Pasta.addActivityListener(function(activity) {
|
||||
console.log("Got some activity to send to Elm:", activity);
|
||||
app.ports.activityChanges.send(activity);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,281 @@
|
|||
port module PhotoGroove exposing (main)
|
||||
|
||||
import Array exposing (Array)
|
||||
import Browser
|
||||
import Html exposing (..)
|
||||
import Html.Attributes as Attr exposing (class, classList, id, name, src, title, type_)
|
||||
import Html.Events exposing (onClick, on)
|
||||
import Http
|
||||
import Json.Decode exposing (Decoder, at, int, list, string, succeed)
|
||||
import Json.Decode.Pipeline exposing (optional, required)
|
||||
import Json.Encode
|
||||
import Random
|
||||
|
||||
urlPrefix : String
|
||||
urlPrefix =
|
||||
"https://elm-in-action.com/"
|
||||
|
||||
type Msg
|
||||
= ClickedPhoto String
|
||||
| SetSize ThumbnailSize
|
||||
| ClickedSurpriseMe
|
||||
| GotRandomPhoto Photo
|
||||
| GotActivity String
|
||||
| GotPhotos (Result Http.Error (List Photo))
|
||||
| SlidHue Int
|
||||
| SlidRipple Int
|
||||
| SlidNoise Int
|
||||
|
||||
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 =
|
||||
[ h1 [] [ text "Photo Groove" ]
|
||||
, 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 ++ 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 (SetSize size) ] []
|
||||
, text (sizeToString size)
|
||||
]
|
||||
|
||||
sizeToString : ThumbnailSize -> String
|
||||
sizeToString size =
|
||||
case size of
|
||||
Small ->
|
||||
"small"
|
||||
Medium ->
|
||||
"medium"
|
||||
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
|
||||
}
|
||||
|
||||
photoDecoder : Decoder Photo
|
||||
photoDecoder =
|
||||
succeed Photo
|
||||
|> required "url" string
|
||||
|> required "size" int
|
||||
|> optional "title" string "(untitled)"
|
||||
|
||||
type Status
|
||||
= Loading
|
||||
| Loaded (List Photo) String
|
||||
| Errored String
|
||||
|
||||
type alias Model =
|
||||
{ status : Status
|
||||
, activity : String
|
||||
, chosenSize : ThumbnailSize
|
||||
, hue : Int
|
||||
, ripple : Int
|
||||
, noise : Int
|
||||
}
|
||||
|
||||
update : Msg -> Model -> ( Model, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
GotActivity activity ->
|
||||
( { model | activity = activity }, Cmd.none)
|
||||
|
||||
GotPhotos (Ok photos) ->
|
||||
case photos of
|
||||
first ::rest ->
|
||||
applyFilters
|
||||
{ model
|
||||
| status =
|
||||
case List.head photos of
|
||||
Just photo ->
|
||||
Loaded photos photo.url
|
||||
|
||||
Nothing ->
|
||||
Loaded [] ""
|
||||
}
|
||||
|
||||
[] ->
|
||||
( { model | status = Errored "0 photos found"}, Cmd.none )
|
||||
|
||||
GotPhotos (Err _) ->
|
||||
( model, Cmd.none )
|
||||
|
||||
GotRandomPhoto photo ->
|
||||
applyFilters { model | status = selectUrl photo.url model.status }
|
||||
|
||||
ClickedPhoto url ->
|
||||
applyFilters { model | status = selectUrl url model.status }
|
||||
|
||||
SetSize size ->
|
||||
( { model | chosenSize = size }, Cmd.none )
|
||||
|
||||
ClickedSurpriseMe ->
|
||||
case model.status of
|
||||
Loaded (firstPhoto :: otherPhotos) _ ->
|
||||
( model
|
||||
, Random.generate GotRandomPhoto
|
||||
(Random.uniform firstPhoto otherPhotos)
|
||||
)
|
||||
|
||||
Loaded [] _ ->
|
||||
( model, Cmd.none )
|
||||
|
||||
Loading ->
|
||||
( model, Cmd.none )
|
||||
|
||||
Errored errorMessage ->
|
||||
( model, Cmd.none )
|
||||
|
||||
SlidHue hue ->
|
||||
applyFilters { model | hue = hue }
|
||||
|
||||
SlidRipple ripple ->
|
||||
applyFilters { model | ripple = ripple }
|
||||
|
||||
SlidNoise noise ->
|
||||
applyFilters { model | noise = noise }
|
||||
|
||||
applyFilters : Model -> ( Model, Cmd msg )
|
||||
applyFilters model =
|
||||
case model.status of
|
||||
Loaded photos 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 ++ "large/" ++ selectedUrl
|
||||
in
|
||||
( model, setFilters { url = url, filters = filters } )
|
||||
|
||||
Loading ->
|
||||
( model, Cmd.none )
|
||||
|
||||
Errored errorMessage ->
|
||||
( model, Cmd.none )
|
||||
|
||||
selectUrl : String -> Status -> Status
|
||||
selectUrl url status =
|
||||
case status of
|
||||
Loaded photos _ ->
|
||||
Loaded photos url
|
||||
Loading ->
|
||||
status
|
||||
Errored errorMessage ->
|
||||
status
|
||||
|
||||
initialModel : Model
|
||||
initialModel =
|
||||
{ status = Loading
|
||||
, activity = ""
|
||||
, chosenSize = Medium
|
||||
, hue = 5
|
||||
, ripple = 5
|
||||
, noise = 5
|
||||
}
|
||||
|
||||
initialCmd : Cmd Msg
|
||||
initialCmd =
|
||||
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 }, initialCmd )
|
||||
|
||||
subscriptions : Model -> Sub Msg
|
||||
subscriptions model =
|
||||
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"
|
Loading…
Reference in New Issue