diff options
| author | Jan Tuomi <jan.tuomi@valuemotive.com> | 2021-10-29 15:22:52 +0300 |
|---|---|---|
| committer | Jan Tuomi <jan.tuomi@valuemotive.com> | 2021-10-29 15:22:52 +0300 |
| commit | a972058d20b773f4488df51babb40156c5a335e1 (patch) | |
| tree | 80b8e18462134da7e5d00a2a32fc25bfd5855667 /src | |
| parent | f4dacee4908300f63200864b9f089874152a568c (diff) | |
Add feed
Diffstat (limited to 'src')
| -rw-r--r-- | src/Config.elm | 23 | ||||
| -rw-r--r-- | src/Main.elm | 136 | ||||
| -rw-r--r-- | src/Pages/Feed.elm | 95 | ||||
| -rw-r--r-- | src/Types.elm | 19 |
4 files changed, 247 insertions, 26 deletions
diff --git a/src/Config.elm b/src/Config.elm new file mode 100644 index 0000000..c3df2b0 --- /dev/null +++ b/src/Config.elm @@ -0,0 +1,23 @@ +module Config exposing (ApiResource(..), makeApiUrl) + + +baseUrl : String +baseUrl = + "http://localhost:3000" + + +type ApiResource + = ApiPosts + | ApiLikePost String + + +makeApiUrl : ApiResource -> String +makeApiUrl res = + String.replace "{baseUrl}" baseUrl <| + case res of + ApiPosts -> + "{baseUrl}/posts" + + ApiLikePost postId -> + "{baseUrl}/posts/{postId}" + |> String.replace "{postId}" postId diff --git a/src/Main.elm b/src/Main.elm index 54352d6..d359eaa 100644 --- a/src/Main.elm +++ b/src/Main.elm @@ -2,8 +2,15 @@ module Main exposing (..) import Browser exposing (Document) import Browser.Navigation exposing (Key) +import Config exposing (makeApiUrl) import Html.Styled exposing (..) -import Types exposing (DisplayableError(..), Model, Msg(..)) +import Http +import Json.Decode exposing (Decoder, field, int, list, map2, map5, string) +import Json.Encode +import Pages.Feed exposing (feedView) +import Task +import Time +import Types exposing (Author, DisplayableError(..), Model, Msg(..), Post) import Url exposing (Url) @@ -13,7 +20,7 @@ main = { init = init , view = view , update = update - , subscriptions = \_ -> Sub.none + , subscriptions = subscriptions , onUrlRequest = \_ -> NoOp , onUrlChange = \_ -> NoOp } @@ -25,18 +32,11 @@ main = init : () -> Url -> Key -> ( Model, Cmd Msg ) init _ _ _ = - ( { posts = - [ { id = "123" - , content = "foobar" - , author = - { id = "456" - , name = "George Technoman" - } - } - ] - , displayError = NoError + ( { posts = [] + , displayError = DNoError + , now = Time.millisToPosix 0 } - , Cmd.none + , Cmd.batch [ getPosts, Task.perform SetNowPosix Time.now ] ) @@ -50,11 +50,108 @@ update msg model = NoOp -> ( model, Cmd.none ) - FetchPostsSuccess posts -> + SetNowPosix now -> + ( { model | now = now }, Cmd.none ) + + GetPosts -> + ( model, getPosts ) + + GotPosts (Result.Ok posts) -> ( { model | posts = posts }, Cmd.none ) - FetchPostsFailure error -> - ( { model | displayError = error }, Cmd.none ) + GotPosts (Result.Err httpErr) -> + ( { model | displayError = DHttpError httpErr } + , Cmd.none + ) + + LikePost post -> + ( model, likePost post ) + + LikedPost (Result.Ok post) -> + ( { model | posts = replaceMatchingPost post model.posts } + , Cmd.none + ) + + LikedPost (Result.Err httpErr) -> + ( { model | displayError = DHttpError httpErr } + , Cmd.none + ) + + +replaceMatchingPost : Post -> List Post -> List Post +replaceMatchingPost post posts = + List.map + (\p -> + if p.id == post.id then + post + + else + p + ) + posts + + +getPosts : Cmd Msg +getPosts = + Http.get + { url = makeApiUrl Config.ApiPosts + , expect = Http.expectJson GotPosts postsDecoder + } + + +likePost : Post -> Cmd Msg +likePost post = + Http.request + { method = "PATCH" + , headers = [] + , url = makeApiUrl (Config.ApiLikePost post.id) + , body = + Http.jsonBody + (Json.Encode.object + [ ( "likes", Json.Encode.int (post.likes + 1) ) ] + ) + , expect = Http.expectJson LikedPost postDecoder + , timeout = Nothing + , tracker = Nothing + } + + +subscriptions : Model -> Sub Msg +subscriptions _ = + Time.every 5000 SetNowPosix + + + +-- JSON + + +decodeTime : Decoder Time.Posix +decodeTime = + int + |> Json.Decode.andThen + (\ms -> + Json.Decode.succeed <| Time.millisToPosix (ms * 1000) + ) + + +postsDecoder : Decoder (List Post) +postsDecoder = + list postDecoder + + +postDecoder : Decoder Post +postDecoder = + map5 Post + (field "id" string) + (field "content" string) + (field "author" + (map2 Author + (field "id" string) + (field "name" string) + ) + ) + (field "createdAt" decodeTime) + (field "likes" int) @@ -64,10 +161,5 @@ update msg model = view : Model -> Document Msg view model = { title = "Shoob book" - , body = [ body model ] |> List.map toUnstyled + , body = feedView model |> List.map toUnstyled } - - -body : Model -> Html Msg -body model = - main_ [] (List.map (\post -> div [] [ text post.id ]) model.posts) diff --git a/src/Pages/Feed.elm b/src/Pages/Feed.elm new file mode 100644 index 0000000..a848200 --- /dev/null +++ b/src/Pages/Feed.elm @@ -0,0 +1,95 @@ +module Pages.Feed exposing (..) + +import Css exposing (..) +import DateFormat.Relative exposing (relativeTime) +import Html.Styled exposing (..) +import Html.Styled.Attributes exposing (css) +import Html.Styled.Events exposing (onClick) +import Time exposing (now) +import Types exposing (DisplayableError(..), Model, Msg(..), Post) + + +styles = + { page = + css + [ maxWidth (px 600) + , margin2 (px 10) auto + ] + , post = + css + [ padding2 (px 15) (px 20) + , backgroundColor (hex "#eee") + ] + , postTitle = + css + [ margin (px 0) ] + , postLikeButton = + css + [ marginRight (px 5) + , cursor pointer + ] + } + + +derrorToHtml : DisplayableError -> Html Msg +derrorToHtml derror = + case derror of + DNoError -> + text "" + + DHttpError httpErr -> + text <| Debug.toString httpErr + + +derrorDiv : Model -> Html Msg +derrorDiv model = + div [] + [ derrorToHtml model.displayError ] + + +postDiv : Time.Posix -> Post -> Html Msg +postDiv now post = + div [ styles.post ] + [ h3 [ styles.postTitle ] [ text post.author.name ] + , i [] + [ text <| relativeTime now post.createdAt + ] + , p [] [ text post.content ] + , span [] + [ button [ styles.postLikeButton, onClick (LikePost post) ] [ text "❤️ Like" ] + , text <| String.fromInt post.likes + ] + ] + + +postsDiv : Model -> Html Msg +postsDiv model = + div [] <| List.map (postDiv model.now) model.posts + + +headerSection : Model -> Html msg +headerSection _ = + header [] + [ h1 [] [ text "Shoob book" ] + ] + + +mainSection : Model -> Html Msg +mainSection model = + main_ [] <| + [ derrorDiv model + , postsDiv model + ] + + +body : Model -> Html Msg +body model = + div [ styles.page ] + [ headerSection model + , mainSection model + ] + + +feedView : Model -> List (Html Msg) +feedView model = + [ body model ] diff --git a/src/Types.elm b/src/Types.elm index de1cccf..02e1257 100644 --- a/src/Types.elm +++ b/src/Types.elm @@ -1,21 +1,30 @@ module Types exposing (..) +import Http +import Time + type alias Model = { posts : List Post , displayError : DisplayableError + , now : Time.Posix } type Msg = NoOp - | FetchPostsSuccess (List Post) - | FetchPostsFailure DisplayableError + -- TIME + | SetNowPosix Time.Posix + -- POSTS + | GetPosts + | GotPosts (Result Http.Error (List Post)) + | LikePost Post + | LikedPost (Result Http.Error Post) type DisplayableError - = Error String - | NoError + = DHttpError Http.Error + | DNoError type alias Author = @@ -28,4 +37,6 @@ type alias Post = { id : String , content : String , author : Author + , createdAt : Time.Posix + , likes : Int } |
