Update a ton of things

This commit is contained in:
Richard Feldman
2016-06-24 17:40:54 -07:00
parent 908ca61c77
commit c8e9a117f5
56 changed files with 1023 additions and 1466 deletions

View File

@@ -10,7 +10,7 @@ import Html.App
import Auth import Auth
import Http import Http
import Task exposing (Task) import Task exposing (Task)
import Json.Decode exposing (Decoder, (:=)) import Json.Decode exposing (Decoder)
main : Program Never main : Program Never

View File

@@ -5,7 +5,7 @@ Getting Started
1. Install [Node.js](http://nodejs.org) 4.0.0 or higher 1. Install [Node.js](http://nodejs.org) 4.0.0 or higher
2. Add an [editor plugin](http://elm-lang.org/install#syntax-highlighting) for your editor of choice. 2. Add a plugin for your editor of choice: [Atom](https://atom.io/packages/language-elm), [Sublime Text](https://packagecontrol.io/packages/Elm%20Language%20Support), [VS Code](https://github.com/sbrink/vscode-elm), [Light Table](https://github.com/rundis/elm-light), [Vim](https://github.com/lambdatoast/elm.vim), [Emacs](https://github.com/jcollard/elm-mode), [Brackets](https://github.com/lepinay/elm-brackets)
3. Not required, but **highly** recommended: [install elm-format](https://github.com/avh4/elm-format#installation-) and integrate it into your editor so that it runs on save. 3. Not required, but **highly** recommended: [install elm-format](https://github.com/avh4/elm-format#installation-) and integrate it into your editor so that it runs on save.

View File

@@ -16,7 +16,7 @@ model =
view model = view model =
div [ class "content" ] div [ class "content" ]
[ header [] [ header []
[ -- TODO add the equivalent of <h1>ElmHub</h1> right before the tagline [ -- TODO add the equivalent of <h1>ElmHub</h1> right before this <span>
span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
] ]
, ul [ class "results" ] , ul [ class "results" ]

View File

@@ -4,19 +4,19 @@ Part 1
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```
## References ## References
* [html-to-elm](http://mbylstra.github.io/html-to-elm/) - paste in HTML, get elm-html code * [html-to-elm](http://mbylstra.github.io/html-to-elm/) - paste in HTML, get elm-html code
* [elm-html documentation](http://package.elm-lang.org/packages/evancz/elm-html/4.0.2/) * [elm-html documentation](http://package.elm-lang.org/packages/evancz/elm-html/4.0.2/)
* [record syntax](http://elm-lang.org/docs/syntax#records) (e.g. `{ foo = 1, bar = 2`) * [record syntax](http://elm-lang.org/docs/syntax#records) (e.g. `{ foo = 1, bar = 2 }`)

View File

@@ -1,119 +1,100 @@
module ElmHub (..) where module ElmHub exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, target, href, property) import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..) import Html.Events exposing (..)
import Html.Lazy exposing (..)
import Http import Http
import Auth import Auth
import Task exposing (Task) import Task exposing (Task)
import Effects exposing (Effects) import Json.Decode exposing (Decoder)
import Json.Decode exposing (Decoder, (:=))
import Json.Encode
import Signal exposing (Address)
import Dict exposing (Dict) import Dict exposing (Dict)
import SearchResult import SearchResult
searchFeed : String -> Task x Action searchFeed : String -> Cmd Msg
searchFeed query = searchFeed query =
let let
-- See https://developer.github.com/v3/search/#example for how to customize! url =
url = "https://api.github.com/search/repositories?access_token="
"https://api.github.com/search/repositories?access_token=" ++ Auth.token
++ Auth.token ++ "&q="
++ "&q=" ++ query
++ query ++ "+language:elm&sort=stars&order=desc"
++ "+language:elm&sort=stars&order=desc" in
Task.perform HandleSearchError HandleSearchResponse (Http.get responseDecoder url)
task =
Http.get responseDecoder url
|> Task.map SetResults
in
Task.onError task (\_ -> Task.succeed (SetResults []))
responseDecoder : Decoder (List SearchResult.Model) responseDecoder : Decoder (List SearchResult.Model)
responseDecoder = responseDecoder =
-- TODO make use of SearchResult's decoder -- TODO make use of SearchResult's decoder
Json.Decode.succeed [] Json.Decode.succeed []
type alias Model = type alias Model =
{ query : String { query : String
, results : Dict SearchResult.ResultId SearchResult.Model , results : Dict SearchResult.ResultId SearchResult.Model
} }
initialModel : Model initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = Dict.empty , results = Dict.empty
} }
view : Address Action -> Model -> Html view : Model -> Html Msg
view address model = view model =
div div [ class "content" ]
[ class "content" ] [ header []
[ header [ h1 [] [ text "ElmHub" ]
[] , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
[ h1 [] [ text "ElmHub" ] ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ]
, ul [ class "results" ] (viewSearchResults model.results)
] ]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ]
, ul
[ class "results" ]
(viewSearchResults address model.results)
]
viewSearchResults : Address Action -> Dict SearchResult.ResultId SearchResult.Model -> List Html viewSearchResults : Dict SearchResult.ResultId SearchResult.Model -> List (Html a)
viewSearchResults address results = viewSearchResults results =
results results
|> Dict.values |> Dict.values
|> List.sortBy (.stars >> negate) |> List.sortBy (.stars >> negate)
|> List.map (\_ -> div [] [ text "TODO replace this line with view logic from SearchResult" ]) |> List.map (\_ -> div [] [ text "TODO replace this line with view logic from SearchResult" ])
onInput address wrap = type Msg
on "input" targetValue (\val -> Signal.message address (wrap val)) = Search
| SetQuery String
| DeleteById SearchResult.ResultId
| SetResults (List SearchResult.Model)
| HandleSearchResponse (List SearchResult.Model)
| HandleSearchError Http.Error
defaultValue str = update : Msg -> Model -> ( Model, Cmd Msg )
property "defaultValue" (Json.Encode.string str) update msg model =
case msg of
Search ->
( model, searchFeed model.query )
SetQuery query ->
( { model | query = query }, Cmd.none )
type Action SetResults results ->
= Search let
| SetQuery String resultsById : Dict SearchResult.ResultId SearchResult.Model
| DeleteById SearchResult.ResultId resultsById =
| SetResults (List SearchResult.Model) results
|> List.map (\result -> ( result.id, result ))
|> Dict.fromList
in
( { model | results = resultsById }, Cmd.none )
DeleteById id ->
update : Action -> Model -> ( Model, Effects Action ) let
update action model = newModel =
case action of { model | results = Dict.remove id model.results }
Search -> in
( model, Effects.task (searchFeed model.query) ) ( newModel, Cmd.none )
SetQuery query ->
( { model | query = query }, Effects.none )
SetResults results ->
let
resultsById : Dict SearchResult.ResultId SearchResult.Model
resultsById =
results
|> List.map (\result -> ( result.id, result ))
|> Dict.fromList
in
( { model | results = resultsById }, Effects.none )
DeleteById id ->
let
newModel =
{ model | results = Dict.remove id model.results }
in
( newModel, Effects.none )

View File

@@ -1,27 +1,13 @@
module Main (..) where module Main exposing (..)
import StartApp
import ElmHub exposing (..) import ElmHub exposing (..)
import Effects exposing (Effects)
import Task exposing (Task)
import Html exposing (Html)
main : Signal Html main : Program Never
main = main =
app.html Html.App.program
{ view = view
, update = update
app : StartApp.App Model , init = ( initialModel, searchFeed initialModel.query )
app = , inputs = []
StartApp.start }
{ view = view
, update = update
, init = ( initialModel, Effects.task (searchFeed initialModel.query) )
, inputs = []
}
port tasks : Signal (Task Effects.Never ())
port tasks =
app.tasks

View File

@@ -4,14 +4,14 @@ Part 10
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```

View File

@@ -1,45 +1,41 @@
module SearchResult (..) where module SearchResult exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, target, href, property) import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..) import Json.Decode exposing (Decoder)
import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Signal exposing (Address)
import Dict exposing (Dict)
type alias ResultId = type alias ResultId =
Int Int
type alias Model = type alias Model =
{ id : ResultId { id : ResultId
, name : String , name : String
, stars : Int , stars : Int
} }
decoder : Decoder Model decoder : Decoder Model
decoder = decoder =
decode Model decode Model
|> required "id" Json.Decode.int |> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string |> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int |> required "stargazers_count" Json.Decode.int
view : Address a -> Model -> Html view : Model -> Html a
view address result = view result =
li li []
[] [ span [ class "star-count" ] [ text (toString result.stars) ]
[ span [ class "star-count" ] [ text (toString result.stars) ] , a
, a [ href ("https://github.com/" ++ result.name)
[ href ("https://github.com/" ++ result.name) , target "_blank"
, target "_blank" ]
[ text result.name ]
, button
-- TODO onClick, send a delete action to the address
[ class "hide-result" ]
[ text "X" ]
] ]
[ text result.name ]
, button
-- TODO onClick, send a delete action to the address
[ class "hide-result" ]
[ text "X" ]
]

View File

@@ -10,11 +10,9 @@
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -9,13 +9,10 @@
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"deadfoxygrandpa/elm-test": "3.1.1 <= v < 4.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0",
"laszlopandy/elm-console": "1.0.3 <= v < 2.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -1,4 +1,4 @@
module ElmHub (..) where module ElmHub exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
@@ -6,120 +6,101 @@ import Html.Events exposing (..)
import Http import Http
import Auth import Auth
import Task exposing (Task) import Task exposing (Task)
import Effects exposing (Effects) import Json.Decode exposing (Decoder)
import Json.Decode exposing (Decoder, (:=))
import Json.Encode import Json.Encode
import Signal exposing (Address)
import Dict exposing (Dict) import Dict exposing (Dict)
import SearchResult import SearchResult
searchFeed : String -> Task x Action searchFeed : String -> Cmd Msg
searchFeed query = searchFeed query =
let let
-- See https://developer.github.com/v3/search/#example for how to customize! url =
url = "https://api.github.com/search/repositories?access_token="
"https://api.github.com/search/repositories?access_token=" ++ Auth.token
++ Auth.token ++ "&q="
++ "&q=" ++ query
++ query ++ "+language:elm&sort=stars&order=desc"
++ "+language:elm&sort=stars&order=desc" in
Task.perform HandleSearchError HandleSearchResponse (Http.get responseDecoder url)
task =
Http.get responseDecoder url
|> Task.map SetResults
in
Task.onError task (\_ -> Task.succeed (SetResults []))
responseDecoder : Decoder (List SearchResult.Model) responseDecoder : Decoder (List SearchResult.Model)
responseDecoder = responseDecoder =
"items" := Json.Decode.list SearchResult.decoder "items" := Json.Decode.list SearchResult.decoder
type alias Model = type alias Model =
{ query : String { query : String
, results : Dict SearchResult.ResultId SearchResult.Model , results : Dict SearchResult.ResultId SearchResult.Model
} }
initialModel : Model initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = Dict.empty , results = Dict.empty
} }
view : Address Action -> Model -> Html view : Model -> Html Msg
view address model = view model =
div div [ class "content" ]
[ class "content" ] [ header []
[ header [ h1 [] [ text "ElmHub" ]
[] , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
[ h1 [] [ text "ElmHub" ] ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ]
, ul [ class "results" ] (viewSearchResults model.results)
] ]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ]
, ul
[ class "results" ]
(viewSearchResults address model.results)
]
viewSearchResults : Address Action -> Dict SearchResult.ResultId SearchResult.Model -> List Html viewSearchResults : Dict SearchResult.ResultId SearchResult.Model -> List (Html a)
viewSearchResults address results = viewSearchResults results =
results results
|> Dict.values |> Dict.values
|> List.sortBy (.stars >> negate) |> List.sortBy (.stars >> negate)
|> filterResults |> filterResults
|> List.map (SearchResult.view address DeleteById) |> List.map (SearchResult.view address DeleteById)
filterResults : List SearchResult.Model -> List SearchResult.Model filterResults : List SearchResult.Model -> List SearchResult.Model
filterResults results = filterResults results =
-- TODO filter out repos with 0 stars -- TODO filter out repos with 0 stars
-- using a case-expression rather than List.filter -- using a case-expression rather than List.filter
results results
onInput address wrap = type Msg
on "input" targetValue (\val -> Signal.message address (wrap val)) = Search
| SetQuery String
| DeleteById SearchResult.ResultId
| SetResults (List SearchResult.Model)
defaultValue str = update : Msg -> Model -> ( Model, Cmd Msg )
property "defaultValue" (Json.Encode.string str) update msg model =
case msg of
Search ->
( model, searchFeed model.query )
SetQuery query ->
( { model | query = query }, Cmd.none )
type Action SetResults results ->
= Search let
| SetQuery String resultsById : Dict SearchResult.ResultId SearchResult.Model
| DeleteById SearchResult.ResultId resultsById =
| SetResults (List SearchResult.Model) results
|> List.map (\result -> ( result.id, result ))
|> Dict.fromList
in
( { model | results = resultsById }, Cmd.none )
DeleteById id ->
update : Action -> Model -> ( Model, Effects Action ) let
update action model = newModel =
case action of { model | results = Dict.remove id model.results }
Search -> in
( model, Effects.task (searchFeed model.query) ) ( newModel, Cmd.none )
SetQuery query ->
( { model | query = query }, Effects.none )
SetResults results ->
let
resultsById : Dict SearchResult.ResultId SearchResult.Model
resultsById =
results
|> List.map (\result -> ( result.id, result ))
|> Dict.fromList
in
( { model | results = resultsById }, Effects.none )
DeleteById id ->
let
newModel =
{ model | results = Dict.remove id model.results }
in
( newModel, Effects.none )

View File

@@ -1,27 +1,13 @@
module Main (..) where module Main exposing (..)
import StartApp
import ElmHub exposing (..) import ElmHub exposing (..)
import Effects exposing (Effects)
import Task exposing (Task)
import Html exposing (Html)
main : Signal Html main : Program Never
main = main =
app.html Html.App.program
{ view = view
, update = update
app : StartApp.App Model , init = ( initialModel, searchFeed initialModel.query )
app = , inputs = []
StartApp.start }
{ view = view
, update = update
, init = ( initialModel, Effects.task (searchFeed initialModel.query) )
, inputs = []
}
port tasks : Signal (Task Effects.Never ())
port tasks =
app.tasks

View File

@@ -4,14 +4,14 @@ Part 11
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```

View File

@@ -1,50 +1,45 @@
module SearchResult (..) where module SearchResult exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (..) import Html.Events exposing (..)
import Json.Decode exposing (Decoder, (:=)) import Json.Decode exposing (Decoder)
import Signal exposing (Address)
import Dict exposing (Dict) import Dict exposing (Dict)
type alias ResultId = type alias ResultId =
Int Int
type alias Model = type alias Model =
{ id : ResultId { id : ResultId
, name : String , name : String
, stars : Int , stars : Int
} }
decoder : Decoder Model decoder : Decoder Model
decoder = decoder =
Json.Decode.object3 Json.Decode.object3 Model
Model ("id" := Json.Decode.int)
("id" := Json.Decode.int) ("full_name" := Json.Decode.string)
("full_name" := Json.Decode.string) ("stargazers_count" := Json.Decode.int)
("stargazers_count" := Json.Decode.int)
view : Address a -> (Int -> a) -> Model -> Html view : Address a -> (Int -> a) -> Model -> Html
view address delete result = view address delete result =
li li []
[] [ span [ class "star-count" ] [ text (toString result.stars) ]
[ span [ class "star-count" ] [ text (toString result.stars) ] , a
, a [ href
[ href ("https://github.com/"
("https://github.com/" ++ (Debug.log "TODO we should not see this when typing in the search box!"
++ (Debug.log result.name
"TODO we should not see this when typing in the search box!" )
result.name )
) , target "_blank"
) ]
, target "_blank" [ text result.name ]
, button [ class "hide-result", onClick (delete result.id) ]
[ text "X" ]
] ]
[ text result.name ]
, button
[ class "hide-result", onClick address (delete result.id) ]
[ text "X" ]
]

View File

@@ -10,11 +10,9 @@
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -9,13 +9,10 @@
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"deadfoxygrandpa/elm-test": "3.1.1 <= v < 4.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0",
"laszlopandy/elm-console": "1.0.3 <= v < 2.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -1,4 +1,4 @@
module ElmHub (..) where module ElmHub exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
@@ -7,133 +7,118 @@ import Html.Lazy exposing (..)
import Http import Http
import Auth import Auth
import Task exposing (Task) import Task exposing (Task)
import Effects exposing (Effects) import Json.Decode exposing (Decoder)
import Json.Decode exposing (Decoder, (:=))
import Json.Encode import Json.Encode
import Signal exposing (Address)
import Dict exposing (Dict) import Dict exposing (Dict)
import SearchResult exposing (ResultId) import SearchResult exposing (ResultId)
searchFeed : String -> Task x Action searchFeed : String -> Task x Msg
searchFeed query = searchFeed query =
let let
-- See https://developer.github.com/v3/search/#example for how to customize! -- See https://developer.github.com/v3/search/#example for how to customize!
url = url =
"https://api.github.com/search/repositories?access_token=" "https://api.github.com/search/repositories?access_token="
++ Auth.token ++ Auth.token
++ "&q=" ++ "&q="
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm&sort=stars&order=desc"
task = task =
Http.get responseDecoder url Http.get responseDecoder url
|> Task.map SetResults |> Task.map SetResults
in in
Task.onError task (\_ -> Task.succeed (SetResults [])) Task.onError task (\_ -> Task.succeed (SetResults []))
responseDecoder : Decoder (List SearchResult.Model) responseDecoder : Decoder (List SearchResult.Model)
responseDecoder = responseDecoder =
"items" := Json.Decode.list SearchResult.decoder "items" := Json.Decode.list SearchResult.decoder
type alias Model = type alias Model =
{ query : String { query : String
, results : Dict SearchResult.ResultId SearchResult.Model , results : Dict SearchResult.ResultId SearchResult.Model
} }
initialModel : Model initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = Dict.empty , results = Dict.empty
} }
view : Address Action -> Model -> Html view : Model -> Html Msg
view address model = view model =
div div [ class "content" ]
[ class "content" ] [ header []
[ header [ h1 [] [ text "ElmHub" ]
[] , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
[ h1 [] [ text "ElmHub" ] ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ]
, ul [ class "results" ] (viewSearchResults model.results)
] ]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ]
, ul
[ class "results" ]
(viewSearchResults address model.results)
]
viewSearchResults : Address Action -> Dict ResultId SearchResult.Model -> List Html viewSearchResults : Dict ResultId SearchResult.Model -> List Html
viewSearchResults address results = viewSearchResults results =
results results
|> Dict.values |> Dict.values
|> List.sortBy (.stars >> negate) |> List.sortBy (.stars >> negate)
|> List.map (viewSearchResult address) |> List.map (viewSearchResult address)
viewSearchResult : Address Action -> SearchResult.Model -> Html viewSearchResult : SearchResult.Model -> Html Msg
viewSearchResult address result = viewSearchResult result =
SearchResult.view SearchResult.view (Signal.forwardTo address (UpdateSearchResult result.id))
(Signal.forwardTo address (UpdateSearchResult result.id)) result
result
onInput address wrap = type Msg
on "input" targetValue (\val -> Signal.message address (wrap val)) = Search
| SetQuery String
| SetResults (List SearchResult.Model)
| UpdateSearchResult ResultId SearchResult.Msg
defaultValue str = update : Msg -> Model -> ( Model, Cmd Msg )
property "defaultValue" (Json.Encode.string str) update msg model =
case msg of
Search ->
( model, searchFeed model.query )
SetQuery query ->
( { model | query = query }, Cmd.none )
type Action SetResults results ->
= Search
| SetQuery String
| SetResults (List SearchResult.Model)
| UpdateSearchResult ResultId SearchResult.Action
update : Action -> Model -> ( Model, Effects Action )
update action model =
case action of
Search ->
( model, Effects.task (searchFeed model.query) )
SetQuery query ->
( { model | query = query }, Effects.none )
SetResults results ->
let
resultsById : Dict SearchResult.ResultId SearchResult.Model
resultsById =
results
|> List.map (\result -> ( result.id, result ))
|> Dict.fromList
in
( { model | results = resultsById }, Effects.none )
UpdateSearchResult id childAction ->
let
updated =
model.results
|> Dict.get id
|> Maybe.map (SearchResult.update childAction)
in
case updated of
Nothing ->
( model, Effects.none )
Just ( newChildModel, childEffects ) ->
let let
effects = resultsById : Dict SearchResult.ResultId SearchResult.Model
Effects.map (UpdateSearchResult id) childEffects resultsById =
results
newResults = |> List.map (\result -> ( result.id, result ))
Dict.insert id newChildModel model.results |> Dict.fromList
in in
( { model | results = newResults }, effects ) ( { model | results = resultsById }, Cmd.none )
UpdateSearchResult id childMsg ->
let
updated =
model.results
|> Dict.get id
|> Maybe.map (SearchResult.update childMsg)
in
case updated of
Nothing ->
( model, Cmd.none )
Just ( newChildModel, childEffects ) ->
let
effects =
Effects.map (UpdateSearchResult id) childEffects
newResults =
Dict.insert id newChildModel model.results
in
( { model | results = newResults }, effects )

View File

@@ -1,27 +1,13 @@
module Main (..) where module Main exposing (..)
import StartApp
import ElmHub exposing (..) import ElmHub exposing (..)
import Effects exposing (Effects)
import Task exposing (Task)
import Html exposing (Html)
main : Signal Html main : Program Never
main = main =
app.html Html.App.program
{ view = view
, update = update
app : StartApp.App Model , init = ( initialModel, searchFeed initialModel.query )
app = , inputs = []
StartApp.start }
{ view = view
, update = update
, init = ( initialModel, Effects.task (searchFeed initialModel.query) )
, inputs = []
}
port tasks : Signal (Task Effects.Never ())
port tasks =
app.tasks

View File

@@ -4,16 +4,16 @@ Part 12
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```
## References ## References

View File

@@ -1,63 +1,59 @@
module SearchResult (..) where module SearchResult exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, target, href, property) import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..) import Html.Events exposing (..)
import Signal exposing (Address) import Json.Decode exposing (Decoder)
import Effects exposing (Effects)
import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
type alias Model = type alias Model =
{ id : Int { id : Int
, name : String , name : String
, stars : Int , stars : Int
, expanded : Bool , expanded : Bool
} }
type alias ResultId = type alias ResultId =
Int Int
type Action type Msg
= Expand = Expand
| Collapse | Collapse
decoder : Decoder Model decoder : Decoder Model
decoder = decoder =
decode Model decode Model
|> required "id" Json.Decode.int |> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string |> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int |> required "stargazers_count" Json.Decode.int
|> hardcoded True |> hardcoded True
update : Action -> Model -> ( Model, Effects Action ) update : Msg -> Model -> ( Model, Cmd Msg )
update action model = update msg model =
-- TODO implement Expand and Collapse logic -- TODO implement Expand and Collapse logic
( model, Effects.none ) ( model, Cmd.none )
view : Address Action -> Model -> Html view : Model -> Html Msg
view address model = view model =
li li [] <|
[] if model.expanded then
<| if model.expanded then [ span [ class "star-count" ] [ text (toString model.stars) ]
[ span [ class "star-count" ] [ text (toString model.stars) ] , a [ href ("https://github.com/" ++ model.name), target "_blank" ]
, a [ text model.name ]
[ href ("https://github.com/" ++ model.name), target "_blank" ] , button
[ text model.name ] -- TODO when the user clicks, send a Collapse action
, button [ class "hide-result" ]
-- TODO when the user clicks, send a Collapse action [ text "X" ]
[ class "hide-result" ] ]
[ text "X" ] else
] [ button
else -- TODO when the user clicks, send an Expand action
[ button [ class "expand-result" ]
-- TODO when the user clicks, send an Expand action [ text "Show" ]
[ class "expand-result" ] ]
[ text "Show" ]
]

View File

@@ -10,11 +10,9 @@
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -9,13 +9,10 @@
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"deadfoxygrandpa/elm-test": "3.1.1 <= v < 4.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0",
"laszlopandy/elm-console": "1.0.3 <= v < 2.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -1,4 +1,4 @@
module ElmHub (..) where module ElmHub exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
@@ -7,133 +7,118 @@ import Html.Lazy exposing (..)
import Http import Http
import Auth import Auth
import Task exposing (Task) import Task exposing (Task)
import Effects exposing (Effects) import Json.Decode exposing (Decoder)
import Json.Decode exposing (Decoder, (:=))
import Json.Encode import Json.Encode
import Signal exposing (Address)
import Dict exposing (Dict) import Dict exposing (Dict)
import SearchResult exposing (ResultId) import SearchResult exposing (ResultId)
searchFeed : String -> Task x Action searchFeed : String -> Task x Msg
searchFeed query = searchFeed query =
let let
-- See https://developer.github.com/v3/search/#example for how to customize! -- See https://developer.github.com/v3/search/#example for how to customize!
url = url =
"https://api.github.com/search/repositories?access_token=" "https://api.github.com/search/repositories?access_token="
++ Auth.token ++ Auth.token
++ "&q=" ++ "&q="
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm&sort=stars&order=desc"
task = task =
Http.get responseDecoder url Http.get responseDecoder url
|> Task.map SetResults |> Task.map SetResults
in in
Task.onError task (\_ -> Task.succeed (SetResults [])) Task.onError task (\_ -> Task.succeed (SetResults []))
responseDecoder : Decoder (List SearchResult.Model) responseDecoder : Decoder (List SearchResult.Model)
responseDecoder = responseDecoder =
"items" := Json.Decode.list SearchResult.decoder "items" := Json.Decode.list SearchResult.decoder
type alias Model = type alias Model =
{ query : String { query : String
, results : Dict SearchResult.ResultId SearchResult.Model , results : Dict SearchResult.ResultId SearchResult.Model
} }
initialModel : Model initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = Dict.empty , results = Dict.empty
} }
view : Address Action -> Model -> Html view : Model -> Html Msg
view address model = view model =
div div [ class "content" ]
[ class "content" ] [ header []
[ header [ h1 [] [ text "ElmHub" ]
[] , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
[ h1 [] [ text "ElmHub" ] ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ]
, ul [ class "results" ] (viewSearchResults model.results)
] ]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ]
, ul
[ class "results" ]
(viewSearchResults address model.results)
]
viewSearchResults : Address Action -> Dict ResultId SearchResult.Model -> List Html viewSearchResults : Dict ResultId SearchResult.Model -> List Html
viewSearchResults address results = viewSearchResults results =
results results
|> Dict.values |> Dict.values
|> List.sortBy (.stars >> negate) |> List.sortBy (.stars >> negate)
|> List.map (viewSearchResult address) |> List.map (viewSearchResult address)
viewSearchResult : Address Action -> SearchResult.Model -> Html viewSearchResult : Address Action -> SearchResult.Model -> Html
viewSearchResult address result = viewSearchResult result =
SearchResult.view SearchResult.view (Signal.forwardTo address (UpdateSearchResult result.id))
(Signal.forwardTo address (UpdateSearchResult result.id)) result
result
onInput address wrap = type Msg
on "input" targetValue (\val -> Signal.message address (wrap val)) = Search
| SetQuery String
| SetResults (List SearchResult.Model)
| UpdateSearchResult ResultId SearchResult.Action
defaultValue str = update : Msg -> Model -> ( Model, Cmd Msg )
property "defaultValue" (Json.Encode.string str) update msg model =
case msg of
Search ->
( model, searchFeed model.query )
SetQuery query ->
( { model | query = query }, Cmd.none )
type Action SetResults results ->
= Search
| SetQuery String
| SetResults (List SearchResult.Model)
| UpdateSearchResult ResultId SearchResult.Action
update : Action -> Model -> ( Model, Effects Action )
update action model =
case action of
Search ->
( model, Effects.task (searchFeed model.query) )
SetQuery query ->
( { model | query = query }, Effects.none )
SetResults results ->
let
resultsById : Dict SearchResult.ResultId SearchResult.Model
resultsById =
results
|> List.map (\result -> ( result.id, result ))
|> Dict.fromList
in
( { model | results = resultsById }, Effects.none )
UpdateSearchResult id childAction ->
let
updated =
model.results
|> Dict.get id
|> Maybe.map (SearchResult.update childAction)
in
case updated of
Nothing ->
( model, Effects.none )
Just ( newChildModel, childEffects ) ->
let let
effects = resultsById : Dict SearchResult.ResultId SearchResult.Model
Effects.map (UpdateSearchResult id) childEffects resultsById =
results
newResults = |> List.map (\result -> ( result.id, result ))
Dict.insert id newChildModel model.results |> Dict.fromList
in in
( { model | results = newResults }, effects ) ( { model | results = resultsById }, Cmd.none )
UpdateSearchResult id childAction ->
let
updated =
model.results
|> Dict.get id
|> Maybe.map (SearchResult.update childAction)
in
case updated of
Nothing ->
( model, Cmd.none )
Just ( newChildModel, childEffects ) ->
let
effects =
Effects.map (UpdateSearchResult id) childEffects
newResults =
Dict.insert id newChildModel model.results
in
( { model | results = newResults }, effects )

View File

@@ -1,27 +1,13 @@
module Main (..) where module Main exposing (..)
import StartApp
import ElmHub exposing (..) import ElmHub exposing (..)
import Effects exposing (Effects)
import Task exposing (Task)
import Html exposing (Html)
main : Signal Html main : Program Never
main = main =
app.html Html.App.program
{ view = view
, update = update
app : StartApp.App Model , init = ( initialModel, searchFeed initialModel.query )
app = , inputs = []
StartApp.start }
{ view = view
, update = update
, init = ( initialModel, Effects.task (searchFeed initialModel.query) )
, inputs = []
}
port tasks : Signal (Task Effects.Never ())
port tasks =
app.tasks

View File

@@ -4,16 +4,16 @@ Part 13
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```
## Compiling CSS ## Compiling CSS

View File

@@ -1,65 +1,59 @@
module SearchResult (..) where module SearchResult exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, target, href, property) import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..) import Html.Events exposing (..)
import Signal exposing (Address) import Json.Decode exposing (Decoder)
import Effects exposing (Effects)
import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
type alias Model = type alias Model =
{ id : Int { id : Int
, name : String , name : String
, stars : Int , stars : Int
, expanded : Bool , expanded : Bool
} }
type alias ResultId = type alias ResultId =
Int Int
type Action type Msg
= Expand = Expand
| Collapse | Collapse
decoder : Decoder Model decoder : Decoder Model
decoder = decoder =
decode Model decode Model
|> required "id" Json.Decode.int |> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string |> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int |> required "stargazers_count" Json.Decode.int
|> hardcoded True |> hardcoded True
update : Action -> Model -> ( Model, Effects Action ) update : Msg -> Model -> ( Model, Cmd Msg )
update action model = update msg model =
case action of case msg of
Expand -> Expand ->
( { model | expanded = True }, Effects.none ) ( { model | expanded = True }, Cmd.none )
Collapse -> Collapse ->
( { model | expanded = False }, Effects.none ) ( { model | expanded = False }, Cmd.none )
view : Address Action -> Model -> Html view : Model -> Html Msg
view address model = view model =
li li []
[] <| if model.expanded then
<| if model.expanded then [ span [ class "star-count" ] [ text (toString model.stars) ]
[ span [ class "star-count" ] [ text (toString model.stars) ] , a [ href ("https://github.com/" ++ model.name), target "_blank" ]
, a [ text model.name ]
[ href ("https://github.com/" ++ model.name), target "_blank" ] , button [ class "hide-result", onClick Collapse ]
[ text model.name ] [ text "X" ]
, button ]
[ class "hide-result", onClick address Collapse ] else
[ text "X" ] [ button [ class "expand-result", onClick Expand ]
] [ text "Show" ]
else ]
[ button
[ class "expand-result", onClick address Expand ]
[ text "Show" ]
]

View File

@@ -10,11 +10,10 @@
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "rtfeldman/elm-css": "1.0.0 <= v < 2.0.0",
"evancz/elm-http": "3.0.0 <= v < 4.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/start-app": "2.0.0 <= v < 3.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -10,12 +10,10 @@
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0",
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"rtfeldman/elm-css": "1.0.0 <= v < 2.0.0", "rtfeldman/elm-css": "1.0.0 <= v < 2.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0" "evancz/elm-http": "3.0.1 <= v < 4.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -9,13 +9,11 @@
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"deadfoxygrandpa/elm-test": "3.1.1 <= v < 4.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "rtfeldman/elm-css": "1.0.0 <= v < 2.0.0",
"evancz/elm-http": "3.0.0 <= v < 4.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/start-app": "2.0.0 <= v < 3.0.0",
"laszlopandy/elm-console": "1.0.3 <= v < 2.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -50,14 +50,19 @@ model =
} }
elmHubHeader : Html a
elmHubHeader =
header []
[ h1 [] [ text "ElmHub" ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
]
{-| TODO add a type annotation to this function {-| TODO add a type annotation to this function
-} -}
view model = view model =
div [ class "content" ] div [ class "content" ]
[ header [] [ elmHubHeader
[ h1 [] [ text "ElmHub" ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
]
, ul [ class "results" ] , ul [ class "results" ]
[{- TODO use model.results and viewSearchResults to display results -}] [{- TODO use model.results and viewSearchResults to display results -}]
] ]

View File

@@ -4,16 +4,16 @@ Part 2
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```
## References ## References

View File

@@ -3,6 +3,7 @@ module Main exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.App import Html.App
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onClick)
type alias Model = type alias Model =
@@ -50,19 +51,27 @@ initialModel =
} }
view : Model -> Html Msg elmHubHeader : Html a
view model = elmHubHeader =
div [ class "content" ] header []
[ header [] [ h1 [] [ text "ElmHub" ]
[ h1 [] [ text "ElmHub" ] , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
]
, ul [ class "results" ]
(List.map viewSearchResult model.results)
] ]
viewSearchResult : SearchResult -> Html Msg {-| TODO revise this type annotation once we add our onClick handler
-}
view : Model -> Html a
view model =
div [ class "content" ]
[ elmHubHeader
, ul [ class "results" ] (List.map viewSearchResult model.results)
]
{-| TODO revise this type annotation once we add our onClick handler
-}
viewSearchResult : SearchResult -> Html a
viewSearchResult result = viewSearchResult result =
li [] li []
[ span [ class "star-count" ] [ text (toString result.stars) ] [ span [ class "star-count" ] [ text (toString result.stars) ]

View File

@@ -4,16 +4,16 @@ Part 3
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```
## References ## References

View File

@@ -3,6 +3,7 @@ module Main exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.App import Html.App
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
type alias Model = type alias Model =
@@ -63,13 +64,12 @@ view model =
] ]
, input , input
[ class "search-query" [ class "search-query"
-- TODO when we receive onInput, set the query in the model -- TODO onInput, set the query in the model
, defaultValue model.query , defaultValue model.query
] ]
[] []
, button [ class "search-button" ] [ text "Search" ] , button [ class "search-button" ] [ text "Search" ]
, ul [ class "results" ] , ul [ class "results" ] (List.map viewSearchResult model.results)
(List.map viewSearchResult model.results)
] ]

View File

@@ -4,16 +4,16 @@ Part 4
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```
## References ## References

View File

@@ -69,7 +69,7 @@ responseDecoder =
searchResultDecoder : Decoder SearchResult searchResultDecoder : Decoder SearchResult
searchResultDecoder = searchResultDecoder =
-- See https://developer.github.com/v3/search/#example -- See https://developer.github.com/v3/search/#example
-- TODO replace these `hardcoded` with calls to `require` -- TODO replace these calls to `hardcoded` with calls to `require`
decode SearchResult decode SearchResult
|> hardcoded 0 |> hardcoded 0
|> hardcoded "" |> hardcoded ""

View File

@@ -4,14 +4,14 @@ Part 5
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```

View File

@@ -1,200 +1,152 @@
module ElmHub (..) where module ElmHub exposing (..)
import Auth import Auth
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, target, href, property) import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..) import Html.Events exposing (..)
import Http import Http
import Task exposing (Task) import Task exposing (Task)
import Effects exposing (Effects) import Json.Decode exposing (Decoder)
import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Json.Encode
import Signal exposing (Address)
searchFeed : String -> Effects Action searchFeed : String -> Cmd Msg
searchFeed query = searchFeed query =
let let
url = url =
"https://api.github.com/search/repositories?access_token=" "https://api.github.com/search/repositories?access_token="
++ Auth.token ++ Auth.token
++ "&q=" ++ "&q="
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm&sort=stars&order=desc"
-- TODO define task as: -- TODO use these ingredients to give Task.perform the arguments it needs:
-- --
-- task = performAction argument1 argument2 argument3 -- Http.get
-- -- url
-- Use these "ingredients" to give `performAction` the arguments it needs: -- responseDecoder
-- -- HandleSearchResponse
-- Http.get -- HandleSearchError
-- url --
-- responseDecoder -- Hint: http://package.elm-lang.org/packages/evancz/elm-http/3.0.0/Http#get
-- HandleSearchResponse task =
-- HandleSearchError "TODO call Task.perform ..."
-- in
-- Hint: http://package.elm-lang.org/packages/evancz/elm-http/3.0.0/Http#get -- TODO replace this Cmd.none with a call to Task.perform
task = Cmd.none
"TODO performAction ..."
in
-- TODO replace this `Effects.none` with a call to:
--
-- Effects.task task
Effects.none
{-| Note: this will be a standard function in the next release of Elm.
Example:
type Action =
HandleResponse String | HandleError Http.Error
performAction
(\responseString -> HandleResponse responseString)
(\httpError -> HandleError httpError)
(Http.getString "https://google.com?q=something")
-}
performAction : (a -> b) -> (y -> b) -> Task y a -> Task x b
performAction successToAction errorToAction task =
let
successTask =
Task.map successToAction task
in
Task.onError successTask (\err -> Task.succeed (errorToAction err))
responseDecoder : Decoder (List SearchResult) responseDecoder : Decoder (List SearchResult)
responseDecoder = responseDecoder =
"items" := Json.Decode.list searchResultDecoder Json.Decode.at [ "items" ] (Json.Decode.list searchResultDecoder)
searchResultDecoder : Decoder SearchResult searchResultDecoder : Decoder SearchResult
searchResultDecoder = searchResultDecoder =
decode SearchResult decode SearchResult
|> required "id" Json.Decode.int |> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string |> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int |> required "stargazers_count" Json.Decode.int
type alias Model = type alias Model =
{ query : String { query : String
, results : List SearchResult , results : List SearchResult
, errorMessage : Maybe String , errorMessage : Maybe String
} }
type alias SearchResult = type alias SearchResult =
{ id : ResultId { id : ResultId
, name : String , name : String
, stars : Int , stars : Int
} }
type alias ResultId = type alias ResultId =
Int Int
initialModel : Model initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = [] , results = []
, errorMessage = Nothing , errorMessage = Nothing
} }
view : Address Action -> Model -> Html view : Model -> Html Msg
view address model = view model =
div div [ class "content" ]
[ class "content" ] [ header []
[ header [ h1 [] [ text "ElmHub" ]
[] , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
[ h1 [] [ text "ElmHub" ] ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ]
, viewErrorMessage model.errorMessage
, ul [ class "results" ] (List.map viewSearchResult model.results)
] ]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ]
, viewErrorMessage model.errorMessage
, ul
[ class "results" ]
(List.map (viewSearchResult address) model.results)
]
viewErrorMessage : Maybe String -> Html viewErrorMessage : Maybe String -> Html a a
viewErrorMessage errorMessage = viewErrorMessage errorMessage =
case errorMessage of case errorMessage of
Just message -> Just message ->
div [ class "error" ] [ text message ] div [ class "error" ] [ text message ]
Nothing -> Nothing ->
text "" text ""
onInput address wrap = viewSearchResult : SearchResult -> Html Msg
on "input" targetValue (\val -> Signal.message address (wrap val)) viewSearchResult result =
li []
[ span [ class "star-count" ] [ text (toString result.stars) ]
, a [ href ("https://github.com/" ++ result.name), target "_blank" ]
[ text result.name ]
, button [ class "hide-result", onClick (DeleteById result.id) ]
[ text "X" ]
]
defaultValue str = type Msg
property "defaultValue" (Json.Encode.string str) = Search
| SetQuery String
| DeleteById ResultId
| HandleSearchResponse (List SearchResult)
| HandleSearchError Http.Error
viewSearchResult : Address Action -> SearchResult -> Html update : Msg -> Model -> ( Model, Cmd Msg )
viewSearchResult address result = update msg model =
li case msg of
[] Search ->
[ span [ class "star-count" ] [ text (toString result.stars) ] ( model, searchFeed model.query )
, a
[ href ("https://github.com/" ++ result.name), target "_blank" ]
[ text result.name ]
, button
[ class "hide-result", onClick address (DeleteById result.id) ]
[ text "X" ]
]
HandleSearchResponse results ->
( { model | results = results }, Cmd.none )
type Action HandleSearchError error ->
= Search -- TODO if decoding failed, store the message in model.errorMessage
| SetQuery String --
| DeleteById ResultId -- Hint 1: look for "decode" in the documentation for this union type:
| HandleSearchResponse (List SearchResult) -- http://package.elm-lang.org/packages/evancz/elm-http/3.0.1/Http#Error
| HandleSearchError Http.Error --
-- Hint 2: to check if this is working, break responseDecoder
-- by changing "stargazers_count" to "description"
( model, Cmd.none )
SetQuery query ->
( { model | query = query }, Cmd.none )
update : Action -> Model -> ( Model, Effects Action ) DeleteById idToHide ->
update action model = let
case action of newResults =
Search -> model.results
( model, searchFeed model.query ) |> List.filter (\{ id } -> id /= idToHide)
HandleSearchResponse results -> newModel =
( { model | results = results }, Effects.none ) { model | results = newResults }
in
HandleSearchError error -> ( newModel, Cmd.none )
-- TODO if decoding failed, store the message in model.errorMessage
--
-- Hint 1: look for "decode" in the documentation for this union type:
-- http://package.elm-lang.org/packages/evancz/elm-http/3.0.0/Http#Error
--
-- Hint 2: to check if this is working, break responseDecoder
-- by changing "stargazers_count" to "description"
( model, Effects.none )
SetQuery query ->
( { model | query = query }, Effects.none )
DeleteById idToHide ->
let
newResults =
model.results
|> List.filter (\{ id } -> id /= idToHide)
newModel =
{ model | results = newResults }
in
( newModel, Effects.none )

View File

@@ -1,27 +1,13 @@
module Main (..) where module Main exposing (..)
import StartApp
import ElmHub exposing (..) import ElmHub exposing (..)
import Effects exposing (Effects)
import Task exposing (Task)
import Html exposing (Html)
main : Signal Html main : Program Never
main = main =
app.html Html.App.program
{ view = view
, update = update
app : StartApp.App Model , init = ( initialModel, searchFeed initialModel.query )
app = , inputs = []
StartApp.start }
{ view = view
, update = update
, init = ( initialModel, searchFeed initialModel.query )
, inputs = []
}
port tasks : Signal (Task Effects.Never ())
port tasks =
app.tasks

View File

@@ -4,16 +4,16 @@ Part 6
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```
## References ## References

View File

@@ -10,11 +10,9 @@
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -1,189 +1,141 @@
module ElmHub (..) where module ElmHub exposing (..)
import Auth import Auth
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, target, href, property) import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..) import Html.Events exposing (..)
import Http import Http
import Task exposing (Task) import Task exposing (Task)
import Effects exposing (Effects) import Json.Decode exposing (Decoder)
import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Json.Encode
import Signal exposing (Address)
searchFeed : String -> Effects Action searchFeed : String -> Cmd Msg
searchFeed query = searchFeed query =
let let
url = url =
"https://api.github.com/search/repositories?access_token=" "https://api.github.com/search/repositories?access_token="
++ Auth.token ++ Auth.token
++ "&q=" ++ "&q="
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm&sort=stars&order=desc"
in
task = Task.perform HandleSearchError HandleSearchResponse (Http.get responseDecoder url)
performAction
(\response -> HandleSearchResponse response)
(\error -> HandleSearchError error)
(Http.get responseDecoder url)
in
Effects.task task
responseDecoder : Decoder (List SearchResult) responseDecoder : Decoder (List SearchResult)
responseDecoder = responseDecoder =
"items" := Json.Decode.list searchResultDecoder Json.Decode.at [ "items" ] (Json.Decode.list searchResultDecoder)
searchResultDecoder : Decoder SearchResult searchResultDecoder : Decoder SearchResult
searchResultDecoder = searchResultDecoder =
decode SearchResult decode SearchResult
|> required "id" Json.Decode.int |> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string |> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int |> required "stargazers_count" Json.Decode.int
{-| Note: this will be a standard function in the next release of Elm.
Example:
type Action =
HandleResponse String | HandleError Http.Error
performAction
(\responseString -> HandleResponse responseString)
(\httpError -> HandleError httpError)
(Http.getString "https://google.com?q=something")
-}
performAction : (a -> b) -> (y -> b) -> Task y a -> Task x b
performAction successToAction errorToAction task =
let
successTask =
Task.map successToAction task
in
Task.onError successTask (\err -> Task.succeed (errorToAction err))
type alias Model = type alias Model =
{ query : String { query : String
, results : List SearchResult , results : List SearchResult
, errorMessage : Maybe String , errorMessage : Maybe String
} }
type alias SearchResult = type alias SearchResult =
{ id : ResultId { id : ResultId
, name : String , name : String
, stars : Int , stars : Int
} }
type alias ResultId = type alias ResultId =
Int Int
initialModel : Model initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = [] , results = []
, errorMessage = Nothing , errorMessage = Nothing
} }
view : Address Action -> Model -> Html view : Model -> Html Msg
view address model = view model =
div div [ class "content" ]
[ class "content" ] [ header []
[ header [ h1 [] [ text "ElmHub" ]
[] , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
[ h1 [] [ text "ElmHub" ] ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ]
, viewErrorMessage model.errorMessage
, ul [ class "results" ] (List.map viewSearchResult model.results)
] ]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ]
, viewErrorMessage model.errorMessage
, ul
[ class "results" ]
(List.map (viewSearchResult address) model.results)
]
viewErrorMessage : Maybe String -> Html viewErrorMessage : Maybe String -> Html a
viewErrorMessage errorMessage = viewErrorMessage errorMessage =
case errorMessage of case errorMessage of
Just message -> Just message ->
div [ class "error" ] [ text message ] div [ class "error" ] [ text message ]
Nothing -> Nothing ->
text "" text ""
onInput address wrap = viewSearchResult : SearchResult -> Html Msg
on "input" targetValue (\val -> Signal.message address (wrap val)) viewSearchResult result =
li []
[ span [ class "star-count" ] [ text (toString result.stars) ]
, a [ href ("https://github.com/" ++ result.name), target "_blank" ]
[ text result.name ]
, button [ class "hide-result", onClick (DeleteById result.id) ]
[ text "X" ]
]
defaultValue str = type Msg
property "defaultValue" (Json.Encode.string str) = Search
| SetQuery String
| DeleteById ResultId
| HandleSearchResponse (List SearchResult)
| HandleSearchError Http.Error
viewSearchResult : Address Action -> SearchResult -> Html update : Msg -> Model -> ( Model, Cmd Msg )
viewSearchResult address result = update msg model =
li case msg of
[] Search ->
[ span [ class "star-count" ] [ text (toString result.stars) ] ( model, searchFeed model.query )
, a
[ href ("https://github.com/" ++ result.name), target "_blank" ]
[ text result.name ]
, button
[ class "hide-result", onClick address (DeleteById result.id) ]
[ text "X" ]
]
HandleSearchResponse results ->
( { model | results = results }, Cmd.none )
type Action HandleSearchError error ->
= Search let
| SetQuery String errorMessage =
| DeleteById ResultId case error of
| HandleSearchResponse (List SearchResult) Http.UnexpectedPayload message ->
| HandleSearchError Http.Error Just message
_ ->
Nothing
in
( { model | errorMessage = errorMessage }, Cmd.none )
update : Action -> Model -> ( Model, Effects Action ) SetQuery query ->
update action model = ( { model | query = query }, Cmd.none )
case action of
Search ->
( model, searchFeed model.query )
HandleSearchResponse results -> DeleteById idToHide ->
( { model | results = results }, Effects.none ) let
newResults =
model.results
|> List.filter (\{ id } -> id /= idToHide)
HandleSearchError error -> newModel =
let { model | results = newResults }
errorMessage = in
case error of ( newModel, Cmd.none )
Http.UnexpectedPayload message ->
Just message
_ ->
Nothing
in
( { model | errorMessage = errorMessage }, Effects.none )
SetQuery query ->
( { model | query = query }, Effects.none )
DeleteById idToHide ->
let
newResults =
model.results
|> List.filter (\{ id } -> id /= idToHide)
newModel =
{ model | results = newResults }
in
( newModel, Effects.none )

View File

@@ -1,27 +1,13 @@
module Main (..) where module Main exposing (..)
import StartApp
import ElmHub exposing (..) import ElmHub exposing (..)
import Effects exposing (Effects)
import Task exposing (Task)
import Html exposing (Html)
main : Signal Html main : Program Never
main = main =
app.html Html.App.program
{ view = view
, update = update
app : StartApp.App Model , init = ( initialModel, searchFeed initialModel.query )
app = , inputs = []
StartApp.start }
{ view = view
, update = update
, init = ( initialModel, searchFeed initialModel.query )
, inputs = []
}
port tasks : Signal (Task Effects.Never ())
port tasks =
app.tasks

View File

@@ -4,23 +4,23 @@ Part 7
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```
## Running Tests ## Running Tests
```bash ```bash
cd test cd test
elm package install elm-package install
elm test TestRunner.elm elm test TestRunner.elm
``` ```

View File

@@ -10,11 +10,9 @@
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -9,14 +9,10 @@
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"deadfoxygrandpa/elm-test": "3.1.1 <= v < 4.0.0",
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0",
"laszlopandy/elm-console": "1.0.3 <= v < 2.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -1,187 +1,142 @@
module ElmHub (..) where module ElmHub exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, target, href, property) import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..) import Html.Events exposing (..)
import Http
import Auth import Auth
import Task exposing (Task) import Task exposing (Task)
import Effects exposing (Effects) import Json.Decode exposing (Decoder)
import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Json.Encode import Json.Encode
import Signal exposing (Address)
searchFeed : Address String -> String -> Effects Action getQueryUrl : String -> String
searchFeed address query = getQueryUrl query =
let
-- See https://developer.github.com/v3/search/#example for how to customize! -- See https://developer.github.com/v3/search/#example for how to customize!
url = "https://api.github.com/search/repositories?access_token="
"https://api.github.com/search/repositories?access_token="
++ Auth.token ++ Auth.token
++ "&q=" ++ "&q="
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm&sort=stars&order=desc"
-- These only talk to JavaScript ports now. They never result in Actions
-- actually do any actions themselves.
task =
performAction
(\_ -> DoNothing)
(\_ -> DoNothing)
(Signal.send address query)
in
Effects.task task
responseDecoder : Decoder (List SearchResult) responseDecoder : Decoder (List SearchResult)
responseDecoder = responseDecoder =
"items" := Json.Decode.list searchResultDecoder Json.Decode.at [ "items" ] (Json.Decode.list searchResultDecoder)
searchResultDecoder : Decoder SearchResult searchResultDecoder : Decoder SearchResult
searchResultDecoder = searchResultDecoder =
decode SearchResult decode SearchResult
|> required "id" Json.Decode.int |> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string |> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int |> required "stargazers_count" Json.Decode.int
{-| Note: this will be a standard function in the next release of Elm.
Example:
type Action =
HandleResponse String | HandleError Http.Error
performAction
(\responseString -> HandleResponse responseString)
(\httpError -> HandleError httpError)
(Http.getString "https://google.com?q=something")
-}
performAction : (a -> b) -> (y -> b) -> Task y a -> Task x b
performAction successToAction errorToAction task =
let
successTask =
Task.map successToAction task
in
Task.onError successTask (\err -> Task.succeed (errorToAction err))
type alias Model = type alias Model =
{ query : String { query : String
, results : List SearchResult , results : List SearchResult
, errorMessage : Maybe String , errorMessage : Maybe String
} }
type alias SearchResult = type alias SearchResult =
{ id : ResultId { id : ResultId
, name : String , name : String
, stars : Int , stars : Int
} }
type alias ResultId = type alias ResultId =
Int Int
initialModel : Model initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = [] , results = []
, errorMessage = Nothing , errorMessage = Nothing
} }
view : Address Action -> Model -> Html view : Model -> Html Msg
view address model = view model =
div div [ class "content" ]
[ class "content" ] [ header []
[ header [ h1 [] [ text "ElmHub" ]
[] , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
[ h1 [] [ text "ElmHub" ] ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ]
, viewErrorMessage model.errorMessage
, ul [ class "results" ] (List.map viewSearchResult model.results)
] ]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ]
, viewErrorMessage model.errorMessage
, ul
[ class "results" ]
(List.map (viewSearchResult address) model.results)
]
viewErrorMessage : Maybe String -> Html viewErrorMessage : Maybe String -> Html a
viewErrorMessage errorMessage = viewErrorMessage errorMessage =
case errorMessage of case errorMessage of
Just message -> Just message ->
div [ class "error" ] [ text message ] div [ class "error" ] [ text message ]
Nothing -> Nothing ->
text "" text ""
onInput address wrap = viewSearchResult : SearchResult -> Html Msg
on "input" targetValue (\val -> Signal.message address (wrap val)) viewSearchResult result =
li []
[ span [ class "star-count" ] [ text (toString result.stars) ]
, a [ href ("https://github.com/" ++ result.name), target "_blank" ]
[ text result.name ]
, button [ class "hide-result", onClick (DeleteById result.id) ]
[ text "X" ]
]
defaultValue str = type Msg
property "defaultValue" (Json.Encode.string str) = Search
| SetQuery String
| DeleteById ResultId
| SetResults (List SearchResult)
| SetErrorMessage (Maybe String)
| DoNothing
viewSearchResult : Address Action -> SearchResult -> Html update : (String -> Cmd Msg) -> Msg -> Model -> ( Model, Cmd Msg )
viewSearchResult address result = update searchFeed msg model =
li case msg of
[] Search ->
[ span [ class "star-count" ] [ text (toString result.stars) ] ( model, searchFeed (getQueryUrl model.query) )
, a
[ href ("https://github.com/" ++ result.name), target "_blank" ] SetQuery query ->
[ text result.name ] ( { model | query = query }, Cmd.none )
, button
[ class "hide-result", onClick address (DeleteById result.id) ] SetResults results ->
[ text "X" ] ( { model | results = results }, Cmd.none )
]
SetErrorMessage errorMessage ->
( { model | errorMessage = errorMessage }, Cmd.none )
DeleteById idToHide ->
let
newResults =
model.results
|> List.filter (\{ id } -> id /= idToHide)
newModel =
{ model | results = newResults }
in
( newModel, Cmd.none )
DoNothing ->
( model, Cmd.none )
type Action decodeGithubResponse : Json.Encode.Value -> Msg
= Search decodeGithubResponse value =
| SetQuery String -- TODO use Json.Decode.DecodeValue to decode the response into an Action.
| DeleteById ResultId --
| SetResults (List SearchResult) -- Hint: look at ElmHub.elm, specifically the definition of Action and
| SetErrorMessage (Maybe String) -- the deefinition of responseDecoder
| DoNothing SetErrorMessage (Just "TODO decode the response!")
update : Address String -> Action -> Model -> ( Model, Effects Action )
update searchAddress action model =
case action of
Search ->
( model, searchFeed searchAddress model.query )
SetQuery query ->
( { model | query = query }, Effects.none )
SetResults results ->
( { model | results = results }, Effects.none )
SetErrorMessage errorMessage ->
( { model | errorMessage = errorMessage }, Effects.none )
DeleteById idToHide ->
let
newResults =
model.results
|> List.filter (\{ id } -> id /= idToHide)
newModel =
{ model | results = newResults }
in
( newModel, Effects.none )
DoNothing ->
( model, Effects.none )

View File

@@ -1,57 +1,31 @@
module Main (..) where port module Main exposing (..)
import StartApp
import ElmHub exposing (..) import ElmHub exposing (..)
import Effects exposing (Effects) import Html.App
import Task exposing (Task) import Json.Decode exposing (Value)
import Html exposing (Html)
import Signal
import Json.Encode
import Json.Decode
main : Signal Html main : Program Never
main = main =
app.html Html.App.program
{ view = view
, update = update githubSearch
, init = ( initialModel, githubSearch (getQueryUrl initialModel.query) )
, subscriptions = \_ -> githubResponse decodeResponse
}
app : StartApp.App Model decodeResponse : Json.Decode.Value -> Msg
app = decodeResponse json =
StartApp.start case Json.Decode.decodeValue responseDecoder json of
{ view = view Err err ->
, update = update search.address SetErrorMessage (Just err)
, init = ( initialModel, searchFeed search.address initialModel.query )
, inputs = [ responseActions ] Ok results ->
} SetResults results
port tasks : Signal (Task Effects.Never ()) port githubSearch : String -> Cmd msg
port tasks =
app.tasks
search : Signal.Mailbox String port githubResponse : (Json.Decode.Value -> msg) -> Sub msg
search =
Signal.mailbox ""
port githubSearch : Signal String
port githubSearch =
search.signal
responseActions : Signal Action
responseActions =
Signal.map decodeGithubResponse githubResponse
decodeGithubResponse : Json.Encode.Value -> Action
decodeGithubResponse value =
-- TODO use Json.Decode.DecodeValue to decode the response into an Action.
--
-- Hint: look at ElmHub.elm, specifically the definition of Action and
-- the deefinition of responseDecoder
SetErrorMessage (Just "TODO decode the response!")
port githubResponse : Signal Json.Encode.Value

View File

@@ -4,22 +4,22 @@ Part 8
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```
## Running Tests ## Running Tests
```bash ```bash
cd test cd test
elm package install elm-package install
elm test TestRunner.elm elm test TestRunner.elm
``` ```

View File

@@ -10,11 +10,9 @@
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -9,14 +9,10 @@
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"deadfoxygrandpa/elm-test": "3.1.1 <= v < 4.0.0",
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0",
"laszlopandy/elm-console": "1.0.3 <= v < 2.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -1,182 +1,126 @@
module ElmHub (..) where module ElmHub exposing (..)
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (class, target, href, property) import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..) import Html.Events exposing (..)
import Http
import Auth import Auth
import Task exposing (Task) import Json.Decode exposing (Decoder)
import Effects exposing (Effects)
import Json.Decode exposing (Decoder, (:=))
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
import Json.Encode
import Signal exposing (Address)
import Dict exposing (Dict) import Dict exposing (Dict)
searchFeed : Address String -> String -> Effects Action getQueryUrl : String -> String
searchFeed address query = getQueryUrl query =
let
-- See https://developer.github.com/v3/search/#example for how to customize! -- See https://developer.github.com/v3/search/#example for how to customize!
url = "https://api.github.com/search/repositories?access_token="
"https://api.github.com/search/repositories?access_token="
++ Auth.token ++ Auth.token
++ "&q=" ++ "&q="
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm&sort=stars&order=desc"
-- These only talk to JavaScript ports now. They never result in Actions
-- actually do any actions themselves.
task =
performAction
(\_ -> DoNothing)
(\_ -> DoNothing)
(Signal.send address query)
in
Effects.task task
responseDecoder : Decoder (List SearchResult) responseDecoder : Decoder (List SearchResult)
responseDecoder = responseDecoder =
"items" := Json.Decode.list searchResultDecoder Json.Decode.at [ "items" ] (Json.Decode.list searchResultDecoder)
searchResultDecoder : Decoder SearchResult searchResultDecoder : Decoder SearchResult
searchResultDecoder = searchResultDecoder =
decode SearchResult decode SearchResult
|> required "id" Json.Decode.int |> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string |> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int |> required "stargazers_count" Json.Decode.int
{-| Note: this will be a standard function in the next release of Elm.
Example:
type Action =
HandleResponse String | HandleError Http.Error
performAction
(\responseString -> HandleResponse responseString)
(\httpError -> HandleError httpError)
(Http.getString "https://google.com?q=something")
-}
performAction : (a -> b) -> (y -> b) -> Task y a -> Task x b
performAction successToAction errorToAction task =
let
successTask =
Task.map successToAction task
in
Task.onError successTask (\err -> Task.succeed (errorToAction err))
type alias Model = type alias Model =
{ query : String { query : String
, results : Dict ResultId SearchResult , results : Dict ResultId SearchResult
, errorMessage : Maybe String , errorMessage : Maybe String
} }
type alias SearchResult = type alias SearchResult =
{ id : ResultId { id : ResultId
, name : String , name : String
, stars : Int , stars : Int
} }
type alias ResultId = type alias ResultId =
Int Int
initialModel : Model initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = Dict.empty , results = Dict.empty
, errorMessage = Nothing , errorMessage = Nothing
} }
view : Address Action -> Model -> Html view : Model -> Html Msg
view address model = view model =
div div [ class "content" ]
[ class "content" ] [ header []
[ header [ h1 [] [ text "ElmHub" ]
[] , span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
[ h1 [] [ text "ElmHub" ] ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ] , input [ class "search-query", onInput SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick Search ] [ text "Search" ]
, ul [ class "results" ] (viewSearchResults model.results)
] ]
, input [ class "search-query", onInput address SetQuery, defaultValue model.query ] []
, button [ class "search-button", onClick address Search ] [ text "Search" ]
, ul
[ class "results" ]
(viewSearchResults address model.results)
]
viewSearchResults : Address Action -> Dict ResultId SearchResult -> List Html viewSearchResults : Dict ResultId SearchResult -> List (Html Msg)
viewSearchResults address results = viewSearchResults results =
-- TODO sort by star count and render -- TODO sort by star count and render
[]
onInput address wrap =
on "input" targetValue (\val -> Signal.message address (wrap val))
defaultValue str =
property "defaultValue" (Json.Encode.string str)
viewSearchResult : Address Action -> SearchResult -> Html
viewSearchResult address result =
li
[] []
[ span [ class "star-count" ] [ text (toString result.stars) ]
, a
[ href ("https://github.com/" ++ result.name), target "_blank" ]
[ text result.name ]
, button
[ class "hide-result", onClick address (DeleteById result.id) ]
[ text "X" ]
]
type Action viewSearchResult : SearchResult -> Html Msg
= Search viewSearchResult result =
| SetQuery String li []
| DeleteById ResultId [ span [ class "star-count" ] [ text (toString result.stars) ]
| SetResults (List SearchResult) , a [ href ("https://github.com/" ++ result.name), target "_blank" ]
| SetErrorMessage (Maybe String) [ text result.name ]
| DoNothing , button [ class "hide-result", onClick (DeleteById result.id) ]
[ text "X" ]
]
update : Address String -> Action -> Model -> ( Model, Effects Action ) type Msg
update searchAddress action model = = Search
case action of | SetQuery String
Search -> | DeleteById ResultId
( model, searchFeed searchAddress model.query ) | SetResults (List SearchResult)
| SetErrorMessage (Maybe String)
| DoNothing
SetQuery query ->
( { model | query = query }, Effects.none )
SetResults results -> update : (String -> Cmd Msg) -> Msg -> Model -> ( Model, Cmd Msg )
let update searchFeed msg model =
resultsById : Dict ResultId SearchResult case msg of
resultsById = Search ->
-- TODO convert results list into a Dict ( model, searchFeed (getQueryUrl model.query) )
Dict.empty
in
( { model | results = resultsById }, Effects.none )
DeleteById id -> SetQuery query ->
-- TODO delete the result with the given id ( { model | query = query }, Cmd.none )
( model, Effects.none )
SetErrorMessage errorMessage -> SetResults results ->
( { model | errorMessage = errorMessage }, Effects.none ) let
resultsById : Dict ResultId SearchResult
resultsById =
-- TODO convert results list into a Dict
Dict.empty
in
( { model | results = resultsById }, Cmd.none )
DoNothing -> DeleteById id ->
( model, Effects.none ) -- TODO delete the result with the given id
( model, Cmd.none )
SetErrorMessage errorMessage ->
( { model | errorMessage = errorMessage }, Cmd.none )
DoNothing ->
( model, Cmd.none )

View File

@@ -1,58 +1,31 @@
module Main (..) where port module Main exposing (..)
import StartApp
import ElmHub exposing (..) import ElmHub exposing (..)
import Effects exposing (Effects) import Html.App
import Task exposing (Task) import Json.Decode exposing (Value)
import Html exposing (Html)
import Signal
import Json.Encode
import Json.Decode
main : Signal Html main : Program Never
main = main =
app.html Html.App.program
{ view = view
, update = update githubSearch
, init = ( initialModel, githubSearch (getQueryUrl initialModel.query) )
, subscriptions = \_ -> githubResponse decodeResponse
}
app : StartApp.App Model decodeResponse : Json.Decode.Value -> Msg
app = decodeResponse json =
StartApp.start case Json.Decode.decodeValue responseDecoder json of
{ view = view Err err ->
, update = update search.address SetErrorMessage (Just err)
, init = ( initialModel, searchFeed search.address initialModel.query )
, inputs = [ responseActions ] Ok results ->
} SetResults results
port tasks : Signal (Task Effects.Never ()) port githubSearch : String -> Cmd msg
port tasks =
app.tasks
search : Signal.Mailbox String port githubResponse : (Json.Decode.Value -> msg) -> Sub msg
search =
Signal.mailbox ""
port githubSearch : Signal String
port githubSearch =
search.signal
responseActions : Signal Action
responseActions =
Signal.map decodeGithubResponse githubResponse
decodeGithubResponse : Json.Encode.Value -> Action
decodeGithubResponse value =
case Json.Decode.decodeValue responseDecoder value of
Ok results ->
SetResults results
Err message ->
SetErrorMessage (Just message)
port githubResponse : Signal Json.Encode.Value

View File

@@ -4,14 +4,14 @@ Part 9
## Installation ## Installation
```bash ```bash
elm package install elm-package install
``` ```
(Answer `y` at the prompt. In rare cases a known issue can cause the download (Answer `y` at the prompt. In rare cases a known issue can cause the download
to fail; in that case, just run `elm package install` again.) to fail; in that case, just run `elm-package install` again.)
## Building ## Building
```bash ```bash
elm live Main.elm --open -- --output=elm.js elm-live Main.elm --open -- --output=elm.js
``` ```

View File

@@ -10,11 +10,9 @@
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.0.0 <= v < 2.0.0",
"elm-lang/core": "3.0.0 <= v < 4.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-http": "3.0.1 <= v < 4.0.0"
"evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }