Reorganize 11 and 12

This commit is contained in:
Richard Feldman
2016-03-27 09:37:35 -07:00
parent f2ed5c4c39
commit 89182d2156
16 changed files with 207 additions and 249 deletions

View File

@@ -1,52 +0,0 @@
module Component.SearchResult (..) where
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Signal exposing (Address)
import Effects exposing (Effects)
type alias Model =
{ id : Int
, name : String
, stars : Int
, expanded : Bool
}
type alias ResultId =
Int
type Action
= Expand
| Collapse
update : Action -> Model -> ( Model, Effects Action )
update action model =
-- TODO make expand and collapse work
( model, Effects.none )
view : Address Action -> Model -> Html
view address model =
li
[]
<| if model.expanded then
[ span [ class "star-count" ] [ text (toString model.stars) ]
, a
[ href ("https://github.com/" ++ model.name), target "_blank" ]
[ text model.name ]
, button
-- TODO when the user clicks, send a Collapse action
[ class "hide-result" ]
[ text "X" ]
]
else
[ button
-- TODO when the user clicks, send an Expand action
[ class "expand-result" ]
[ text "Show" ]
]

View File

@@ -1,4 +1,4 @@
module Component.ElmHub (..) where module ElmHub (..) where
import Html exposing (..) import Html exposing (..)
import Html.Attributes exposing (..) import Html.Attributes exposing (..)
@@ -10,7 +10,8 @@ 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 Signal exposing (Address)
import Component.SearchResult exposing (ResultId) import Dict exposing (Dict)
import SearchResult
searchFeed : String -> Task x Action searchFeed : String -> Task x Action
@@ -20,7 +21,7 @@ searchFeed query =
url = url =
"https://api.github.com/search/repositories?q=" "https://api.github.com/search/repositories?q="
++ query ++ query
++ "+language:elm&sort=stars&order=desc" ++ "+language:elm"
task = task =
Http.get responseDecoder url Http.get responseDecoder url
@@ -29,31 +30,21 @@ searchFeed query =
Task.onError task (\_ -> Task.succeed (SetResults [])) Task.onError task (\_ -> Task.succeed (SetResults []))
responseDecoder : Decoder (List Component.SearchResult.Model) responseDecoder : Decoder (List SearchResult.Model)
responseDecoder = responseDecoder =
"items" := Json.Decode.list searchResultDecoder "items" := Json.Decode.list SearchResult.decoder
searchResultDecoder : Decoder Component.SearchResult.Model
searchResultDecoder =
Json.Decode.object4
Component.SearchResult.Model
("id" := Json.Decode.int)
("full_name" := Json.Decode.string)
("stargazers_count" := Json.Decode.int)
(Json.Decode.succeed True)
type alias Model = type alias Model =
{ query : String { query : String
, results : List Component.SearchResult.Model , results : Dict SearchResult.ResultId SearchResult.Model
} }
initialModel : Model initialModel : Model
initialModel = initialModel =
{ query = "tutorial" { query = "tutorial"
, results = [] , results = Dict.empty
} }
@@ -74,22 +65,24 @@ view address model =
] ]
viewSearchResults : Address Action -> List Component.SearchResult.Model -> List Html viewSearchResults : Address Action -> Dict SearchResult.ResultId SearchResult.Model -> List Html
viewSearchResults address results = viewSearchResults address results =
results results
|> Dict.values
|> List.sortBy (.stars >> negate)
|> filterResults |> filterResults
|> List.map (lazy2 viewSearchResult address) |> List.map (lazy3 SearchResult.view address DeleteById)
filterResults : List Component.SearchResult.Model -> List Component.SearchResult.Model filterResults : List SearchResult.Model -> List SearchResult.Model
filterResults results = filterResults results =
case results of case results of
[] -> [] ->
[] []
first :: rest -> result :: rest ->
if first.stars > 0 then if result.stars > 0 then
first :: (filterResults rest) result :: (filterResults rest)
else else
filterResults rest filterResults rest
@@ -102,18 +95,11 @@ defaultValue str =
property "defaultValue" (Json.Encode.string str) property "defaultValue" (Json.Encode.string str)
viewSearchResult : Address Action -> Component.SearchResult.Model -> Html
viewSearchResult address result =
Component.SearchResult.view
(Signal.forwardTo address (UpdateSearchResult result.id))
(Debug.log "rendering result..." result)
type Action type Action
= Search = Search
| SetQuery String | SetQuery String
| SetResults (List Component.SearchResult.Model) | DeleteById SearchResult.ResultId
| UpdateSearchResult ResultId Component.SearchResult.Action | SetResults (List SearchResult.Model)
update : Action -> Model -> ( Model, Effects Action ) update : Action -> Model -> ( Model, Effects Action )
@@ -126,32 +112,18 @@ update action model =
( { model | query = query }, Effects.none ) ( { model | query = query }, Effects.none )
SetResults results -> 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 let
newModel = newModel =
{ model | results = results } { model | results = Dict.remove id model.results }
in in
( newModel, Effects.none ) ( newModel, Effects.none )
UpdateSearchResult id childAction ->
let
updateResult childModel =
if childModel.id == id then
let
( newChildModel, childEffects ) =
Component.SearchResult.update childAction childModel
in
( newChildModel
, Effects.map (UpdateSearchResult id) childEffects
)
else
( childModel, Effects.none )
( newResults, effects ) =
model.results
|> List.map updateResult
|> List.unzip
newModel =
{ model | results = newResults }
in
( newModel, Effects.batch effects )

14
stages/11/ElmHub/Css.elm Normal file
View File

@@ -0,0 +1,14 @@
module ElmHub.Css (..) where
import Css exposing (..)
css =
stylesheet
[ ((.) "content")
[ width (px 960)
, margin2 zero auto
, padding (px 30)
, fontFamilies [ "Helvetica", "Arial", "serif" ]
]
]

View File

@@ -1,7 +1,7 @@
module Main (..) where module Main (..) where
import StartApp import StartApp
import Component.ElmHub exposing (..) import ElmHub exposing (..)
import Effects exposing (Effects) import Effects exposing (Effects)
import Task exposing (Task) import Task exposing (Task)
import Html exposing (Html) import Html exposing (Html)

View File

@@ -1,4 +1,4 @@
Stage 11 Stage 12
======== ========
## Installation ## Installation
@@ -16,10 +16,8 @@ to fail; in that case, just run `elm package install` again.)
elm live Main.elm --open -- --output=elm.js elm live Main.elm --open -- --output=elm.js
``` ```
## Running Tests ## Compiling CSS
```bash ```bash
cd test elm css Stylesheets.elm
elm package install
elm test TestRunner.elm
``` ```

View File

@@ -0,0 +1,44 @@
module SearchResult (..) where
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Json.Decode exposing (Decoder, (:=))
import Signal exposing (Address)
import Dict exposing (Dict)
type alias ResultId =
Int
type alias Model =
{ id : ResultId
, name : String
, stars : Int
}
decoder : Decoder Model
decoder =
Json.Decode.object3
Model
("id" := Json.Decode.int)
("full_name" := Json.Decode.string)
("stargazers_count" := Json.Decode.int)
view : Address a -> (Int -> a) -> Model -> Html
view address delete 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 (delete result.id) ]
[ text "X" ]
]

View File

@@ -1,10 +1,10 @@
module Stylesheets (..) where module Stylesheets (..) where
import Css.File exposing (..) import Css.File exposing (..)
import ElmHub import ElmHub.Css
port files : CssFileStructure port files : CssFileStructure
port files = port files =
toFileStructure toFileStructure
[ ( "style.css", compile ElmHub.css ) ] [ ( "style.css", compile ElmHub.Css.css ) ]

View File

@@ -12,7 +12,8 @@
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "evancz/elm-effects": "2.0.0 <= v < 3.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-html": "4.0.0 <= v < 5.0.0",
"evancz/elm-http": "3.0.0 <= v < 4.0.0", "evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0" "evancz/start-app": "2.0.0 <= v < 3.0.0",
"rtfeldman/elm-css": "1.0.0 <= v < 2.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.16.0 <= v < 0.17.0"
} }

View File

@@ -1,92 +1,6 @@
.content { .content {
width: 960px; width: 960px;
margin: 0 auto; margin: 0 auto;
padding: 30px; padding: 30px;
font-family: Helvetica, Arial, serif; font-family: Helvetica, Arial, serif;
} }
header {
position: relative;
padding: 6px 12px;
height: 36px;
background-color: rgb(96, 181, 204);
}
h1 {
color: white;
font-weight: normal;
margin: 0;
}
.tagline {
color: #eee;
position: absolute;
right: 16px;
top: 12px;
font-size: 24px;
font-style: italic;
}
.results {
list-style-image: url('http://img-cache.cdn.gaiaonline.com/76bd5c99d8f2236e9d3672510e933fdf/http://i278.photobucket.com/albums/kk81/d3m3nt3dpr3p/Tiny-Star-Icon.png');
list-style-position: inside;
padding: 0;
}
.results li {
font-size: 18px;
margin-bottom: 16px;
}
.star-count {
font-weight: bold;
margin-right: 16px;
}
a {
color: rgb(96, 181, 204);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.search-query {
padding: 8px;
font-size: 24px;
margin-bottom: 18px;
margin-top: 36px;
}
.search-button {
padding: 8px 16px;
font-size: 24px;
color: white;
border: 1px solid #ccc;
background-color: rgb(96, 181, 204);
margin-left: 12px
}
.search-button:hover {
color: rgb(96, 181, 204);
background-color: white;
}
.hide-result {
background-color: transparent;
border: 0;
font-weight: bold;
font-size: 18px;
margin-left: 18px;
cursor: pointer;
}
.hide-result:hover {
color: rgb(96, 181, 204);
}
button:focus, input:focus {
outline: none;
}

View File

@@ -82,21 +82,16 @@ viewSearchResults address results =
filterResults : List Component.SearchResult.Model -> List Component.SearchResult.Model filterResults : List Component.SearchResult.Model -> List Component.SearchResult.Model
filterResults = filterResults results =
filterResultsHelp []
filterResultsHelp : List Component.SearchResult.Model -> List Component.SearchResult.Model -> List Component.SearchResult.Model
filterResultsHelp output results =
case results of case results of
[] -> [] ->
output []
first :: rest -> first :: rest ->
if first.stars > 0 then if first.stars > 0 then
filterResultsHelp (first :: output) rest first :: (filterResults rest)
else else
filterResultsHelp output rest filterResults rest
onInput address wrap = onInput address wrap =
@@ -111,7 +106,7 @@ viewSearchResult : Address Action -> Component.SearchResult.Model -> Html
viewSearchResult address result = viewSearchResult address result =
Component.SearchResult.view Component.SearchResult.view
(Signal.forwardTo address (UpdateSearchResult result.id)) (Signal.forwardTo address (UpdateSearchResult result.id))
result (Debug.log "rendering result..." result)
type Action type Action

View File

@@ -26,12 +26,8 @@ type Action
update : Action -> Model -> ( Model, Effects Action ) update : Action -> Model -> ( Model, Effects Action )
update action model = update action model =
case action of -- TODO make expand and collapse work
Expand -> ( model, Effects.none )
( { model | expanded = True }, Effects.none )
Collapse ->
( { model | expanded = False }, Effects.none )
view : Address Action -> Model -> Html view : Address Action -> Model -> Html
@@ -44,11 +40,13 @@ view address model =
[ href ("https://github.com/" ++ model.name), target "_blank" ] [ href ("https://github.com/" ++ model.name), target "_blank" ]
[ text model.name ] [ text model.name ]
, button , button
[ class "hide-result", onClick address Collapse ] -- TODO when the user clicks, send a Collapse action
[ class "hide-result" ]
[ text "X" ] [ text "X" ]
] ]
else else
[ button [ button
[ class "expand-result", onClick address Expand ] -- TODO when the user clicks, send an Expand action
[ class "expand-result" ]
[ text "Show" ] [ text "Show" ]
] ]

View File

@@ -1,8 +0,0 @@
module ElmHub (..) where
import Css exposing (..)
css =
stylesheet
[ (.) "foo" [] ]

View File

@@ -1,4 +1,4 @@
Stage 12 Stage 11
======== ========
## Installation ## Installation
@@ -16,8 +16,10 @@ to fail; in that case, just run `elm package install` again.)
elm live Main.elm --open -- --output=elm.js elm live Main.elm --open -- --output=elm.js
``` ```
## Compiling CSS ## Running Tests
```bash ```bash
elm css css/Stylesheets.elm cd test
elm package install
elm test TestRunner.elm
``` ```

View File

@@ -1,10 +0,0 @@
module Stylesheets (..) where
import Css.File exposing (..)
import ElmHub
port files : CssFileStructure
port files =
toFileStructure
[ ( "style.css", compile ElmHub.css ) ]

View File

@@ -12,8 +12,7 @@
"evancz/elm-effects": "2.0.0 <= v < 3.0.0", "evancz/elm-effects": "2.0.0 <= v < 3.0.0",
"evancz/elm-html": "4.0.0 <= v < 5.0.0", "evancz/elm-html": "4.0.0 <= v < 5.0.0",
"evancz/elm-http": "3.0.0 <= v < 4.0.0", "evancz/elm-http": "3.0.0 <= v < 4.0.0",
"evancz/start-app": "2.0.0 <= v < 3.0.0", "evancz/start-app": "2.0.0 <= v < 3.0.0"
"rtfeldman/elm-css": "1.0.0 <= v < 2.0.0"
}, },
"elm-version": "0.16.0 <= v < 0.17.0" "elm-version": "0.16.0 <= v < 0.17.0"
} }

View File

@@ -1 +1,92 @@
.content {
width: 960px;
margin: 0 auto;
padding: 30px;
font-family: Helvetica, Arial, serif;
}
header {
position: relative;
padding: 6px 12px;
height: 36px;
background-color: rgb(96, 181, 204);
}
h1 {
color: white;
font-weight: normal;
margin: 0;
}
.tagline {
color: #eee;
position: absolute;
right: 16px;
top: 12px;
font-size: 24px;
font-style: italic;
}
.results {
list-style-image: url('http://img-cache.cdn.gaiaonline.com/76bd5c99d8f2236e9d3672510e933fdf/http://i278.photobucket.com/albums/kk81/d3m3nt3dpr3p/Tiny-Star-Icon.png');
list-style-position: inside;
padding: 0;
}
.results li {
font-size: 18px;
margin-bottom: 16px;
}
.star-count {
font-weight: bold;
margin-right: 16px;
}
a {
color: rgb(96, 181, 204);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.search-query {
padding: 8px;
font-size: 24px;
margin-bottom: 18px;
margin-top: 36px;
}
.search-button {
padding: 8px 16px;
font-size: 24px;
color: white;
border: 1px solid #ccc;
background-color: rgb(96, 181, 204);
margin-left: 12px
}
.search-button:hover {
color: rgb(96, 181, 204);
background-color: white;
}
.hide-result {
background-color: transparent;
border: 0;
font-weight: bold;
font-size: 18px;
margin-left: 18px;
cursor: pointer;
}
.hide-result:hover {
color: rgb(96, 181, 204);
}
button:focus, input:focus {
outline: none;
}