import Html exposing (..)
import Html.Events exposing (..)
import Html.Attributes exposing (href, style, src)
import Task exposing (Task)
import Http exposing (Error)
import JavaScript.Decode exposing (..)
import List as L
--------------
-- HELPER CODE
--------------
-- Operator from Clojure
(>!) : a -> Stream.Input a -> Stream.Message
(>!) a input' = Stream.message input'.address a
-- Javascript Decoding Helper
andMap : Decoder (a -> b) -> Decoder a -> Decoder b
andMap = object2 (<|)
-- Status type
type Status
= Initial
| Loading
| Ready
| Failed
--------
-- MODEL
--------
-- The entire model for the app
type alias Model =
{ status : Status
, posts : List Post
}
type alias Post =
{ url : String
, title : String
, score : Int
}
-- The initial model
initialModel : Model
initialModel =
{ status = Initial
, posts = []
}
------------------
-- Reddit Homepage
------------------
redditHomeUrl : String
redditHomeUrl =
"https://www.reddit.com/.json"
----------------------------
-- JSON Parsing and Decoding
----------------------------
{-| Reddit JSON data appears as :
{
"data": {
"children": [
{
"data": {
"url": "www.catsareawesome.org",
"title": "Cats are amazing",
"score": 100
}
},
{
"data": {
"url": "www.dogsareawesome.org",
"title": "Dogs are amazing",
"score": 90
}
}
]
}
}
-}
type alias RedditJson =
{ data : RedditJsonData }
redditJsonDecoder : Decoder RedditJson
redditJsonDecoder = RedditJson
`map` ("data" := redditJsonDataDecoder)
type alias RedditJsonData =
{ children : List RedditJsonPost }
redditJsonDataDecoder : Decoder RedditJsonData
redditJsonDataDecoder = RedditJsonData
`map` ("children" := list redditJsonPostDecoder)
type alias RedditJsonPost =
{ data : RedditJsonPostData }
redditJsonPostDecoder : Decoder RedditJsonPost
redditJsonPostDecoder = RedditJsonPost
`map` ("data" := redditJsonPostDataDecoder)
type alias RedditJsonPostData =
{ url : String
, title : String
, score : Int
}
redditJsonPostDataDecoder : Decoder RedditJsonPostData
redditJsonPostDataDecoder = RedditJsonPostData
`map` ("url" := string)
`andMap` ("title" := string)
`andMap` ("score" := int)
--------------------------
-- Convert Posts from JSON
--------------------------
postsFromJson : RedditJson -> List Post
postsFromJson json =
let
convertChild : RedditJsonPost -> Post
convertChild child =
{ url = child.data.url
, title = child.data.title
, score = child.data.score
}
in
List.map convertChild json.data.children
---------
-- INPUTS
---------
-- The url(s) requested by the user
input urlRequests : Stream.Input String
-- The results from requesting the url
input results : Stream.Stream (Result Error RedditJson)
input results from
Stream.map requestRedditPage urlRequests.stream
--------
-- TASKS
--------
requestRedditPage : String -> Task Error RedditJson
requestRedditPage url =
Http.get redditJsonDecoder url
----------
-- ACTIONS
----------
type Action
= Load
| SetPosts (List Post)
| Fail
resultToAction : Result Error RedditJson -> Action
resultToAction result = case result of
Err _ -> Fail
Ok json -> SetPosts (postsFromJson json)
actions : Stream Action
actions =
Stream.map resultToAction results
---------
-- UPDATE
---------
update : Action -> Model -> Model
update action model = case action of
Load ->
{ model | status <- Loading }
Fail ->
{ model | status <- Failed }
SetPosts posts ->
{ model | status <- Ready
, posts <- posts
}
-------
-- VIEW
-------
view : Model -> Html
view model = case model.status of
Initial -> viewInitialPage
Loading -> viewLoadingPage
Ready -> viewPage model.posts
Failed -> viewFailedPage
viewInitialPage : Html
viewInitialPage =
button
[ onClick (redditHomeUrl >! urlRequests) ]
[ text "Get Reddit Front Page" ]
viewLoadingPage : Html
viewLoadingPage =
text "Loading Reddit...."
viewFailedPage : Html
viewFailedPage =
section []
[ p [] [ text "Oh noes! Request went bad!" ]
, p [] [ text "Care to try again?" ]
, viewInitialPage
]
viewPage : List Post -> Html
viewPage posts =
div []
[ pageHeader
, ul [] (List.map viewPost posts)
]
viewPost : Post -> Html
viewPost post =
let
scoreStyle = style
[("height", "50px")
,("flex", "1")
,("align-self", "center")
]
itemStyle = style
[("display", "flex")
,("margin-bottom", "10px")
]
linkStyle = style
[("flex", "8")
,("align-self", "flex-start")
]
in
li [ itemStyle ]
[ div [ scoreStyle ]
[ text (toString post.score) ]
,
a
[ linkStyle
, href post.url
]
[ div [] [text post.title] ]
]
pageHeader : Html
pageHeader =
let
headerStyle = style
[ ("display", "flex")
, ("align-items", "center")
]
textStyle = style
[ ("flex", "8") ]
in
header [ headerStyle ]
[ elmLogo
, div [ textStyle ] [ text "Reddit Client in Elm" ]
]
-- The Glorious Elm logo!
elmLogo : Html
elmLogo =
let
logoStyle = style
[("height", "50px")
,("width", "50px")
,("min-height", "40px")
,("min-width", "40px")
,("flex", "1")
]
in
img
[ src "http://elm-lang.org/logo.svg"
, logoStyle
][]
-------
-- MAIN
-------
main =
Varying.map view
(Stream.fold update initialModel actions)