310 lines
8.3 KiB
Elm
310 lines
8.3 KiB
Elm
module Page.Profile exposing (Model, Msg, init, subscriptions, toSession, update, view)
|
|
|
|
{-| An Author's profile.
|
|
-}
|
|
|
|
import Article.Feed as Feed exposing (ListConfig)
|
|
import Article.FeedSources as FeedSources exposing (FeedSources, Source(..))
|
|
import Author exposing (Author(..), FollowedAuthor, UnfollowedAuthor)
|
|
import Avatar exposing (Avatar)
|
|
import Html exposing (..)
|
|
import Html.Attributes exposing (..)
|
|
import Http
|
|
import Loading
|
|
import Log
|
|
import Page
|
|
import Profile exposing (Profile)
|
|
import Session exposing (Session)
|
|
import Task exposing (Task)
|
|
import Time
|
|
import Username exposing (Username)
|
|
import Viewer exposing (Viewer)
|
|
import Viewer.Cred as Cred exposing (Cred)
|
|
|
|
|
|
|
|
-- MODEL
|
|
|
|
|
|
type alias Model =
|
|
{ session : Session
|
|
, timeZone : Time.Zone
|
|
, errors : List String
|
|
|
|
-- Loaded independently from server
|
|
, author : Status Author
|
|
, feed : Status Feed.Model
|
|
}
|
|
|
|
|
|
type Status a
|
|
= Loading Username
|
|
| Loaded a
|
|
| Failed Username
|
|
|
|
|
|
init : Session -> Username -> ( Model, Cmd Msg )
|
|
init session username =
|
|
let
|
|
maybeCred =
|
|
Session.cred session
|
|
in
|
|
( { session = session
|
|
, timeZone = Time.utc
|
|
, errors = []
|
|
, author = Loading username
|
|
, feed = Loading username
|
|
}
|
|
, Cmd.batch
|
|
[ Author.fetch username maybeCred
|
|
|> Http.toTask
|
|
|> Task.mapError (Tuple.pair username)
|
|
|> Task.attempt CompletedAuthorLoad
|
|
, defaultFeedSources username
|
|
|> Feed.init session
|
|
|> Task.mapError (Tuple.pair username)
|
|
|> Task.attempt CompletedFeedLoad
|
|
, Task.perform GotTimeZone Time.here
|
|
]
|
|
)
|
|
|
|
|
|
|
|
-- VIEW
|
|
|
|
|
|
view : Model -> { title : String, content : Html Msg }
|
|
view model =
|
|
let
|
|
title =
|
|
case model.author of
|
|
Loaded (IsViewer _ _) ->
|
|
myProfileTitle
|
|
|
|
Loaded ((IsFollowing followedAuthor) as author) ->
|
|
titleForOther (Author.username author)
|
|
|
|
Loaded ((IsNotFollowing unfollowedAuthor) as author) ->
|
|
titleForOther (Author.username author)
|
|
|
|
Loading username ->
|
|
if Just username == Maybe.map .username (Session.cred model.session) then
|
|
myProfileTitle
|
|
|
|
else
|
|
defaultTitle
|
|
|
|
Failed username ->
|
|
-- We can't follow if it hasn't finished loading yet
|
|
if Just username == Maybe.map .username (Session.cred model.session) then
|
|
myProfileTitle
|
|
|
|
else
|
|
defaultTitle
|
|
in
|
|
{ title = title
|
|
, content =
|
|
case model.author of
|
|
Loaded author ->
|
|
let
|
|
profile =
|
|
Author.profile author
|
|
|
|
username =
|
|
Author.username author
|
|
|
|
followButton =
|
|
case Session.cred model.session of
|
|
Just cred ->
|
|
case author of
|
|
IsViewer _ _ ->
|
|
-- We can't follow ourselves!
|
|
text ""
|
|
|
|
IsFollowing followedAuthor ->
|
|
Author.unfollowButton (ClickedUnfollow cred) followedAuthor
|
|
|
|
IsNotFollowing unfollowedAuthor ->
|
|
Author.followButton (ClickedFollow cred) unfollowedAuthor
|
|
|
|
Nothing ->
|
|
-- We can't follow if we're logged out
|
|
text ""
|
|
in
|
|
div [ class "profile-page" ]
|
|
[ Page.viewErrors ClickedDismissErrors model.errors
|
|
, div [ class "user-info" ]
|
|
[ div [ class "container" ]
|
|
[ div [ class "row" ]
|
|
[ div [ class "col-xs-12 col-md-10 offset-md-1" ]
|
|
[ img [ class "user-img", Avatar.src (Profile.avatar profile) ] []
|
|
, h4 [] [ Username.toHtml username ]
|
|
, p [] [ text (Maybe.withDefault "" (Profile.bio profile)) ]
|
|
, followButton
|
|
]
|
|
]
|
|
]
|
|
]
|
|
, case model.feed of
|
|
Loaded feed ->
|
|
div [ class "container" ]
|
|
[ div [ class "row" ] [ viewFeed model.timeZone feed ] ]
|
|
|
|
Loading _ ->
|
|
Loading.icon
|
|
|
|
Failed _ ->
|
|
Loading.error "feed"
|
|
]
|
|
|
|
Loading _ ->
|
|
Loading.icon
|
|
|
|
Failed _ ->
|
|
Loading.error "profile"
|
|
}
|
|
|
|
|
|
|
|
-- PAGE TITLE
|
|
|
|
|
|
titleForOther : Username -> String
|
|
titleForOther otherUsername =
|
|
"Profile — " ++ Username.toString otherUsername
|
|
|
|
|
|
myProfileTitle : String
|
|
myProfileTitle =
|
|
"My Profile"
|
|
|
|
|
|
defaultTitle : String
|
|
defaultTitle =
|
|
"Profile"
|
|
|
|
|
|
|
|
-- FEED
|
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
-- UPDATE
|
|
|
|
|
|
type Msg
|
|
= ClickedDismissErrors
|
|
| ClickedFollow Cred UnfollowedAuthor
|
|
| ClickedUnfollow Cred FollowedAuthor
|
|
| CompletedFollowChange (Result Http.Error Author)
|
|
| CompletedAuthorLoad (Result ( Username, Http.Error ) Author)
|
|
| CompletedFeedLoad (Result ( Username, Http.Error ) Feed.Model)
|
|
| GotTimeZone Time.Zone
|
|
| GotFeedMsg Feed.Msg
|
|
| GotSession Session
|
|
|
|
|
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
|
update msg model =
|
|
case msg of
|
|
ClickedDismissErrors ->
|
|
( { model | errors = [] }, Cmd.none )
|
|
|
|
ClickedUnfollow cred followedAuthor ->
|
|
( model
|
|
, Author.requestUnfollow followedAuthor cred
|
|
|> Http.send CompletedFollowChange
|
|
)
|
|
|
|
ClickedFollow cred unfollowedAuthor ->
|
|
( model
|
|
, Author.requestFollow unfollowedAuthor cred
|
|
|> Http.send CompletedFollowChange
|
|
)
|
|
|
|
CompletedFollowChange (Ok newAuthor) ->
|
|
( { model | author = Loaded newAuthor }
|
|
, Cmd.none
|
|
)
|
|
|
|
CompletedFollowChange (Err error) ->
|
|
( model
|
|
, Log.error
|
|
)
|
|
|
|
CompletedAuthorLoad (Ok author) ->
|
|
( { model | author = Loaded author }, Cmd.none )
|
|
|
|
CompletedAuthorLoad (Err ( username, err )) ->
|
|
( { model | author = Failed username }
|
|
, Log.error
|
|
)
|
|
|
|
CompletedFeedLoad (Ok feed) ->
|
|
( { model | feed = Loaded feed }
|
|
, Cmd.none
|
|
)
|
|
|
|
CompletedFeedLoad (Err ( username, err )) ->
|
|
( { model | feed = Failed username }
|
|
, Log.error
|
|
)
|
|
|
|
GotFeedMsg subMsg ->
|
|
case model.feed of
|
|
Loaded feed ->
|
|
let
|
|
( newFeed, subCmd ) =
|
|
Feed.update (Session.cred model.session) subMsg feed
|
|
in
|
|
( { model | feed = Loaded newFeed }
|
|
, Cmd.map GotFeedMsg subCmd
|
|
)
|
|
|
|
Loading _ ->
|
|
( model, Log.error )
|
|
|
|
Failed _ ->
|
|
( model, Log.error )
|
|
|
|
GotTimeZone tz ->
|
|
( { model | timeZone = tz }, Cmd.none )
|
|
|
|
GotSession session ->
|
|
( { model | session = session }, Cmd.none )
|
|
|
|
|
|
|
|
-- SUBSCRIPTIONS
|
|
|
|
|
|
subscriptions : Model -> Sub Msg
|
|
subscriptions model =
|
|
Session.changes GotSession (Session.navKey model.session)
|
|
|
|
|
|
|
|
-- EXPORT
|
|
|
|
|
|
toSession : Model -> Session
|
|
toSession model =
|
|
model.session
|
|
|
|
|
|
|
|
-- INTERNAL
|
|
|
|
|
|
defaultFeedSources : Username -> FeedSources
|
|
defaultFeedSources username =
|
|
FeedSources.fromLists (AuthorFeed username) [ FavoritedFeed username ]
|