Update advanced/part1

This commit is contained in:
Richard Feldman
2018-08-13 22:01:57 -04:00
parent 927c6977a7
commit 5dcf50c64f
4 changed files with 339 additions and 258 deletions

View File

@@ -3,20 +3,25 @@ module Page.Home exposing (Model, Msg, init, subscriptions, toSession, update, v
{-| The homepage. You can get here via either the / or /#/ routes.
-}
import Article
import Api
import Article exposing (Article, Preview)
import Article.Feed as Feed
import Article.FeedSources as FeedSources exposing (FeedSources, Source(..))
import Article.Tag as Tag exposing (Tag)
import Browser.Dom as Dom
import Html exposing (..)
import Html.Attributes exposing (attribute, class, classList, href, id, placeholder)
import Html.Events exposing (onClick)
import Http
import HttpBuilder
import Loading
import Log
import Page
import PaginatedList exposing (PaginatedList)
import Session exposing (Session)
import Task exposing (Task)
import Time
import Username exposing (Username)
import Viewer.Cred as Cred exposing (Cred)
@@ -27,6 +32,8 @@ import Viewer.Cred as Cred exposing (Cred)
type alias Model =
{ session : Session
, timeZone : Time.Zone
, feedTab : FeedTab
, feedPage : Int
-- Loaded independently from server
, tags : Status (List Tag)
@@ -41,28 +48,35 @@ type Status a
| Failed
type FeedTab
= YourFeed Cred
| GlobalFeed
| TagFeed Tag
init : Session -> ( Model, Cmd Msg )
init session =
let
feedSources =
feedTab =
case Session.cred session of
Just cred ->
FeedSources.fromLists (YourFeed cred) [ GlobalFeed ]
YourFeed cred
Nothing ->
FeedSources.fromLists GlobalFeed []
GlobalFeed
loadTags =
Tag.list
|> Http.toTask
Http.toTask Tag.list
in
( { session = session
, timeZone = Time.utc
, feedTab = feedTab
, feedPage = 1
, tags = Loading
, feed = Loading
}
, Cmd.batch
[ Feed.init session feedSources
[ fetchFeed session feedTab 1
|> Task.attempt CompletedFeedLoad
, Tag.list
|> Http.send CompletedTagsLoad
@@ -87,7 +101,17 @@ view model =
[ div [ class "col-md-9" ] <|
case model.feed of
Loaded feed ->
viewFeed model.timeZone feed
[ div [ class "feed-toggle" ] <|
List.concat
[ [ viewTabs
(Session.cred model.session)
model.feedTab
]
, Feed.viewArticles model.timeZone feed
|> List.map (Html.map GotFeedMsg)
, [ Feed.viewPagination ClickedFeedPage feed ]
]
]
Loading ->
[]
@@ -130,11 +154,58 @@ viewBanner =
]
viewFeed : Time.Zone -> Feed.Model -> List (Html Msg)
viewFeed timeZone feed =
div [ class "feed-toggle" ]
[ Feed.viewFeedSources feed |> Html.map GotFeedMsg ]
:: (Feed.viewArticles timeZone feed |> List.map (Html.map GotFeedMsg))
-- TABS
viewTabs : Maybe Cred -> FeedTab -> Html Msg
viewTabs maybeCred tab =
case tab of
YourFeed cred ->
Feed.viewTabs [] (yourFeed cred) [ globalFeed ]
GlobalFeed ->
let
otherTabs =
case maybeCred of
Just cred ->
[ yourFeed cred ]
Nothing ->
[]
in
Feed.viewTabs otherTabs globalFeed []
TagFeed tag ->
let
otherTabs =
case maybeCred of
Just cred ->
[ yourFeed cred, globalFeed ]
Nothing ->
[ globalFeed ]
in
Feed.viewTabs otherTabs (tagFeed tag) []
yourFeed : Cred -> ( String, Msg )
yourFeed cred =
( "Your Feed", ClickedTab (YourFeed cred) )
globalFeed : ( String, Msg )
globalFeed =
( "Global Feed", ClickedTab GlobalFeed )
tagFeed : Tag -> ( String, Msg )
tagFeed tag =
( "#" ++ Tag.toString tag, ClickedTab (TagFeed tag) )
-- TAGS
viewTags : List Tag -> Html Msg
@@ -160,6 +231,8 @@ viewTag tagName =
type Msg
= ClickedTag Tag
| ClickedTab FeedTab
| ClickedFeedPage Int
| CompletedFeedLoad (Result Http.Error Feed.Model)
| CompletedTagsLoad (Result Http.Error (List Tag))
| GotTimeZone Time.Zone
@@ -171,12 +244,28 @@ type Msg
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ClickedTag tagName ->
ClickedTag tag ->
let
subCmd =
Feed.selectTag (Session.cred model.session) tagName
feedTab =
TagFeed tag
in
( model, Cmd.map GotFeedMsg subCmd )
( { model | feedTab = feedTab }
, fetchFeed model.session feedTab 1
|> Task.attempt CompletedFeedLoad
)
ClickedTab tab ->
( { model | feedTab = tab }
, fetchFeed model.session tab 1
|> Task.attempt CompletedFeedLoad
)
ClickedFeedPage page ->
( { model | feedPage = page }
, fetchFeed model.session model.feedTab page
|> Task.andThen (\feed -> Task.map (\_ -> feed) scrollToTop)
|> Task.attempt CompletedFeedLoad
)
CompletedFeedLoad (Ok feed) ->
( { model | feed = Loaded feed }, Cmd.none )
@@ -242,6 +331,53 @@ update msg model =
-- HTTP
fetchFeed : Session -> FeedTab -> Int -> Task Http.Error Feed.Model
fetchFeed session feedTabs page =
let
maybeCred =
Session.cred session
builder =
case feedTabs of
YourFeed cred ->
Api.url [ "articles", "feed" ]
|> HttpBuilder.get
|> Cred.addHeader cred
GlobalFeed ->
Api.url [ "articles" ]
|> HttpBuilder.get
|> Cred.addHeaderIfAvailable maybeCred
TagFeed tag ->
Api.url [ "articles" ]
|> HttpBuilder.get
|> Cred.addHeaderIfAvailable maybeCred
|> HttpBuilder.withQueryParam "tag" (Tag.toString tag)
in
builder
|> HttpBuilder.withExpect (Http.expectJson (Feed.decoder maybeCred articlesPerPage))
|> PaginatedList.fromRequestBuilder articlesPerPage page
|> Task.map (Feed.init session)
articlesPerPage : Int
articlesPerPage =
10
scrollToTop : Task x ()
scrollToTop =
Dom.setViewport 0 0
-- It's not worth showing the user anything special if scrolling fails.
-- If anything, we'd log this to an error recording service.
|> Task.onError (\_ -> Task.succeed ())
-- SUBSCRIPTIONS

View File

@@ -3,6 +3,8 @@ module Page.Profile exposing (Model, Msg, init, subscriptions, toSession, update
{-| An Author's profile.
-}
import Api
import Article exposing (Article, Preview)
import Article.Feed as Feed
import Article.FeedSources as FeedSources exposing (FeedSources, Source(..))
import Author exposing (Author(..), FollowedAuthor, UnfollowedAuthor)
@@ -10,9 +12,11 @@ import Avatar exposing (Avatar)
import Html exposing (..)
import Html.Attributes exposing (..)
import Http
import HttpBuilder exposing (RequestBuilder)
import Loading
import Log
import Page
import PaginatedList exposing (PaginatedList)
import Profile exposing (Profile)
import Route
import Session exposing (Session)
@@ -31,6 +35,8 @@ type alias Model =
{ session : Session
, timeZone : Time.Zone
, errors : List String
, feedTab : FeedTab
, feedPage : Int
-- Loaded independently from server
, author : Status Author
@@ -38,6 +44,11 @@ type alias Model =
}
type FeedTab
= MyArticles
| FavoritedArticles
type Status a
= Loading Username
| LoadingSlowly Username
@@ -54,6 +65,8 @@ init session username =
( { session = session
, timeZone = Time.utc
, errors = []
, feedTab = defaultFeedTab
, feedPage = 1
, author = Loading username
, feed = Loading username
}
@@ -62,16 +75,68 @@ init session username =
|> Http.toTask
|> Task.mapError (Tuple.pair username)
|> Task.attempt CompletedAuthorLoad
, defaultFeedSources username
|> Feed.init session
|> Task.mapError (Tuple.pair username)
|> Task.attempt CompletedFeedLoad
, fetchFeed session defaultFeedTab username 1
, Task.perform GotTimeZone Time.here
, Task.perform (\_ -> PassedSlowLoadThreshold) Loading.slowThreshold
]
)
currentUsername : Model -> Username
currentUsername model =
case model.author of
Loading username ->
username
LoadingSlowly username ->
username
Loaded author ->
Author.username author
Failed username ->
username
defaultFeedTab : FeedTab
defaultFeedTab =
MyArticles
-- HTTP
fetchFeed : Session -> FeedTab -> Username -> Int -> Cmd Msg
fetchFeed session feedTabs username page =
let
maybeCred =
Session.cred session
( extraParamName, extraParamVal ) =
case feedTabs of
MyArticles ->
( "author", Username.toString username )
FavoritedArticles ->
( "favorited", Username.toString username )
in
Api.url [ "articles" ]
|> HttpBuilder.get
|> HttpBuilder.withExpect (Http.expectJson (Feed.decoder maybeCred articlesPerPage))
|> HttpBuilder.withQueryParam extraParamName extraParamVal
|> Cred.addHeaderIfAvailable maybeCred
|> PaginatedList.fromRequestBuilder articlesPerPage page
|> Task.map (Feed.init session)
|> Task.mapError (Tuple.pair username)
|> Task.attempt CompletedFeedLoad
articlesPerPage : Int
articlesPerPage =
5
-- VIEW
@@ -145,7 +210,13 @@ view model =
, case model.feed of
Loaded feed ->
div [ class "container" ]
[ div [ class "row" ] [ viewFeed model.timeZone feed ] ]
[ div [ class "row" ]
[ div [ class "col-xs-12 col-md-10 offset-md-1" ] <|
div [ class "articles-toggle" ]
[ viewTabs model.feedTab ]
:: (Feed.viewArticles model.timeZone feed |> List.map (Html.map GotFeedMsg))
]
]
Loading _ ->
text ""
@@ -181,7 +252,7 @@ titleForMe : Maybe Cred -> Username -> String
titleForMe maybeCred username =
case maybeCred of
Just cred ->
if username == cred.username then
if username == Cred.username cred then
myProfileTitle
else
@@ -202,15 +273,27 @@ defaultTitle =
-- FEED
-- TABS
viewFeed : Time.Zone -> Feed.Model -> Html Msg
viewFeed timeZone feed =
div [ class "col-xs-12 col-md-10 offset-md-1" ] <|
div [ class "articles-toggle" ]
[ Feed.viewFeedSources feed |> Html.map GotFeedMsg ]
:: (Feed.viewArticles timeZone feed |> List.map (Html.map GotFeedMsg))
viewTabs : FeedTab -> Html Msg
viewTabs tab =
case tab of
MyArticles ->
Feed.viewTabs [] myArticles [ favoritedArticles ]
FavoritedArticles ->
Feed.viewTabs [ myArticles ] favoritedArticles []
myArticles : ( String, Msg )
myArticles =
( "My Articles", ClickedTab MyArticles )
favoritedArticles : ( String, Msg )
favoritedArticles =
( "Favorited Articles", ClickedTab FavoritedArticles )
@@ -221,6 +304,7 @@ type Msg
= ClickedDismissErrors
| ClickedFollow Cred UnfollowedAuthor
| ClickedUnfollow Cred FollowedAuthor
| ClickedTab FeedTab
| CompletedFollowChange (Result Http.Error Author)
| CompletedAuthorLoad (Result ( Username, Http.Error ) Author)
| CompletedFeedLoad (Result ( Username, Http.Error ) Feed.Model)
@@ -248,6 +332,11 @@ update msg model =
|> Http.send CompletedFollowChange
)
ClickedTab tab ->
( { model | feedTab = tab }
, fetchFeed model.session tab (currentUsername model) 1
)
CompletedFollowChange (Ok newAuthor) ->
( { model | author = Loaded newAuthor }
, Cmd.none
@@ -335,12 +424,3 @@ subscriptions model =
toSession : Model -> Session
toSession model =
model.session
-- INTERNAL
defaultFeedSources : Username -> FeedSources
defaultFeedSources username =
FeedSources.fromLists (AuthorFeed username) [ FavoritedFeed username ]