SPA for part10

This commit is contained in:
Richard Feldman
2016-09-01 06:39:06 -07:00
parent 4f226ec557
commit 18b8723f16
7 changed files with 319 additions and 142 deletions

View File

@@ -1,43 +1,9 @@
module ElmHub exposing (..) module ElmHub exposing (..)
import Html exposing (..)
import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..)
import Auth
import Json.Decode exposing (Decoder) import Json.Decode exposing (Decoder)
import Json.Decode.Pipeline exposing (..) import Json.Decode.Pipeline exposing (..)
getQueryString : String -> String
getQueryString query =
-- See https://developer.github.com/v3/search/#example for how to customize!
"access_token="
++ Auth.token
++ "&q="
++ query
++ "+language:elm&sort=stars&order=desc"
responseDecoder : Decoder (List SearchResult)
responseDecoder =
Json.Decode.at [ "items" ] (Json.Decode.list searchResultDecoder)
searchResultDecoder : Decoder SearchResult
searchResultDecoder =
decode SearchResult
|> required "id" Json.Decode.int
|> required "full_name" Json.Decode.string
|> required "stargazers_count" Json.Decode.int
type alias Model =
{ query : String
, results : List SearchResult
, errorMessage : Maybe String
}
type alias SearchResult = type alias SearchResult =
{ id : Int { id : Int
, name : String , name : String
@@ -45,93 +11,9 @@ type alias SearchResult =
} }
initialModel : Model searchResultDecoder : Decoder SearchResult
initialModel = searchResultDecoder =
{ query = "tutorial" decode SearchResult
, results = [] |> required "id" Json.Decode.int
, errorMessage = Nothing |> required "full_name" Json.Decode.string
} |> required "stargazers_count" Json.Decode.int
view : Model -> Html Msg
view model =
div [ class "content" ]
[ header []
[ 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)
]
viewErrorMessage : Maybe String -> Html a
viewErrorMessage errorMessage =
case errorMessage of
Just message ->
div [ class "error" ] [ text message ]
Nothing ->
text ""
viewSearchResult : SearchResult -> Html Msg
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" ]
]
type Msg
= Search
| SetQuery String
| DeleteById Int
| HandleSearchResponse (List SearchResult)
| HandleSearchError (Maybe String)
| DoNothing
update : (String -> Cmd Msg) -> Msg -> Model -> ( Model, Cmd Msg )
update searchFeed msg model =
case msg of
Search ->
( model, searchFeed (getQueryString model.query) )
SetQuery query ->
( { model | query = query }, Cmd.none )
HandleSearchResponse results ->
( { model | results = results }, Cmd.none )
HandleSearchError error ->
( { model | errorMessage = error }, 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 )
decodeGithubResponse : Json.Decode.Value -> Msg
decodeGithubResponse value =
case Json.Decode.decodeValue responseDecoder value of
Ok results ->
HandleSearchResponse results
Err err ->
HandleSearchError (Just err)

View File

@@ -1,31 +1,105 @@
port module Main exposing (..) module Main exposing (..)
import ElmHub exposing (..) import Pages.Home
import Pages.Repository
import Navigation
import Page exposing (Page(..))
import Tuple2
import Html exposing (Html, div, h1, header, text, span)
import Html.Attributes exposing (class)
import Html.App as Html import Html.App as Html
import Json.Decode
type Model
= Home Pages.Home.Model
| Repository Pages.Repository.Model
type Msg
= HomeMsg Pages.Home.Msg
| RepositoryMsg Pages.Repository.Msg
main : Program Never main : Program Never
main = main =
Html.program Navigation.program (Navigation.makeParser Page.parser)
{ view = view { init = init
, update = update githubSearch , subscriptions = subscriptions
, init = ( initialModel, githubSearch (getQueryString initialModel.query) ) , view = view
, subscriptions = \_ -> githubResponse decodeResponse , update = update
, urlUpdate = urlUpdate
} }
decodeResponse : Json.Decode.Value -> Msg subscriptions : Model -> Sub Msg
decodeResponse json = subscriptions model =
case Json.Decode.decodeValue responseDecoder json of case model of
Err err -> Home pageModel ->
HandleSearchError (Just err) Pages.Home.subscriptions pageModel
|> Sub.map HomeMsg
Ok results -> Repository pageModel ->
HandleSearchResponse results Sub.none
port githubSearch : String -> Cmd msg init : Result String Page -> ( Model, Cmd Msg )
init result =
Home (fst Pages.Home.init)
|> urlUpdate result
port githubResponse : (Json.Decode.Value -> msg) -> Sub msg view : Model -> Html Msg
view model =
withHeader <|
case model of
Home pageModel ->
Pages.Home.view pageModel
|> Html.map HomeMsg
Repository pageModel ->
Pages.Repository.view pageModel
|> Html.map RepositoryMsg
withHeader : Html msg -> Html msg
withHeader innerContent =
div [ class "content" ]
[ header []
[ h1 [] [ text "ElmHub" ]
, span [ class "tagline" ] [ text "Like GitHub, but for Elm things." ]
]
, innerContent
]
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case ( msg, model ) of
( HomeMsg pageMsg, Home pageModel ) ->
Pages.Home.update pageMsg pageModel
|> Tuple2.mapEach Home (Cmd.map HomeMsg)
( RepositoryMsg pageMsg, Repository pageModel ) ->
Pages.Repository.update pageMsg pageModel
|> Tuple2.mapEach Repository (Cmd.map RepositoryMsg)
( _, _ ) ->
( model, Cmd.none )
urlUpdate : Result String Page -> Model -> ( Model, Cmd Msg )
urlUpdate result model =
case result of
Ok (Page.Home) ->
Pages.Home.init
|> Tuple2.mapEach Home (Cmd.map HomeMsg)
Ok (Page.Repository id) ->
Pages.Repository.init id
|> Tuple2.mapEach Repository (Cmd.map RepositoryMsg)
Ok NotFound ->
( model, Cmd.none )
Err _ ->
( model, Navigation.modifyUrl "/" )

26
part10/Page.elm Normal file
View File

@@ -0,0 +1,26 @@
module Page exposing (..)
import Navigation
import UrlParser exposing (Parser, (</>), format, int, s, string)
import String
type Page
= Home
| Repository Int
| NotFound
pageParser : Parser (Page -> a) a
pageParser =
UrlParser.oneOf
[ format Home (s "")
, format Repository (s "repositories" </> int)
]
parser : Navigation.Location -> Result String Page
parser location =
location.pathname
|> String.dropLeft 1
|> UrlParser.parse identity pageParser

151
part10/Pages/Home.elm Normal file
View File

@@ -0,0 +1,151 @@
port module Pages.Home exposing (..)
import Html exposing (..)
import Html.Attributes exposing (class, target, href, property, defaultValue)
import Html.Events exposing (..)
import Auth
import Json.Decode exposing (Decoder)
import ElmHub exposing (SearchResult)
import Navigation
getQueryString : String -> String
getQueryString query =
-- See https://developer.github.com/v3/search/#example for how to customize!
"access_token="
++ Auth.token
++ "&q="
++ query
++ "+language:elm&sort=stars&order=desc"
responseDecoder : Decoder (List SearchResult)
responseDecoder =
Json.Decode.at [ "items" ] (Json.Decode.list ElmHub.searchResultDecoder)
type alias Model =
{ query : String
, results : List SearchResult
, errorMessage : Maybe String
}
initialQuery : String
initialQuery =
"tutorial"
init : ( Model, Cmd Msg )
init =
( { query = initialQuery
, results = []
, errorMessage = Nothing
}
, githubSearch (getQueryString initialQuery)
)
view : Model -> Html Msg
view model =
div []
[ 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)
]
viewErrorMessage : Maybe String -> Html a
viewErrorMessage errorMessage =
case errorMessage of
Just message ->
div [ class "error" ] [ text message ]
Nothing ->
text ""
viewSearchResult : SearchResult -> Html Msg
viewSearchResult result =
li []
[ span [ class "star-count" ] [ text (toString result.stars) ]
, a [ onClick (Visit ("/repositories/" ++ toString result.id)) ]
[ text result.name ]
, button [ class "hide-result", onClick (DeleteById result.id) ]
[ text "X" ]
]
type Msg
= Search
| Visit String
| SetQuery String
| DeleteById Int
| HandleSearchResponse (List SearchResult)
| HandleSearchError (Maybe String)
| DoNothing
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
Visit url ->
( model, Navigation.newUrl url )
Search ->
( model, githubSearch (getQueryString model.query) )
SetQuery query ->
( { model | query = query }, Cmd.none )
HandleSearchResponse results ->
( { model | results = results }, Cmd.none )
HandleSearchError error ->
( { model | errorMessage = error }, 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 )
decodeGithubResponse : Json.Decode.Value -> Msg
decodeGithubResponse value =
case Json.Decode.decodeValue responseDecoder value of
Ok results ->
HandleSearchResponse results
Err err ->
HandleSearchError (Just err)
decodeResponse : Json.Decode.Value -> Msg
decodeResponse json =
case Json.Decode.decodeValue responseDecoder json of
Err err ->
HandleSearchError (Just err)
Ok results ->
HandleSearchResponse results
subscriptions : Model -> Sub Msg
subscriptions _ =
githubResponse decodeResponse
port githubSearch : String -> Cmd msg
port githubResponse : (Json.Decode.Value -> msg) -> Sub msg

View File

@@ -0,0 +1,37 @@
module Pages.Repository exposing (..)
import Html exposing (..)
import Html.Attributes exposing (class, target, href, property, defaultValue)
import ElmHub exposing (SearchResult)
type alias Model =
SearchResult
init : Int -> ( Model, Cmd Msg )
init id =
( { id = id
, name = ""
, stars = id
}
, Cmd.none
)
view : Model -> Html Msg
view model =
div []
[ div [] [ text ("repo" ++ toString model.stars) ]
]
type Msg
= NoOp
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
NoOp ->
( model, Cmd.none )

View File

@@ -9,10 +9,13 @@
], ],
"exposed-modules": [], "exposed-modules": [],
"dependencies": { "dependencies": {
"Fresheyeball/elm-tuple-extra": "2.1.0 <= v < 3.0.0",
"NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0", "NoRedInk/elm-decode-pipeline": "1.1.2 <= v < 2.0.0",
"elm-lang/core": "4.0.1 <= v < 5.0.0", "elm-lang/core": "4.0.1 <= v < 5.0.0",
"elm-lang/html": "1.0.0 <= v < 2.0.0", "elm-lang/html": "1.0.0 <= v < 2.0.0",
"evancz/elm-http": "3.0.1 <= v < 4.0.0" "elm-lang/navigation": "1.0.0 <= v < 2.0.0",
"evancz/elm-http": "3.0.1 <= v < 4.0.0",
"evancz/url-parser": "1.0.0 <= v < 2.0.0"
}, },
"elm-version": "0.17.0 <= v < 0.18.0" "elm-version": "0.17.0 <= v < 0.18.0"
} }

View File

@@ -99,3 +99,7 @@ button:focus, input:focus {
font-family: monospace; font-family: monospace;
font-size: 18px; font-size: 18px;
} }
a {
cursor: pointer;
}