1
0
Fork 0

Fix #168: List user favorites.

- Favorite timeline model and commands.
- 4th column navigation bar.
- Use of SetView for account selector.
- Move timeline views to their own view module.
- Handle favorite timeline updates.
- Integrate contextual menu in account selector view.
This commit is contained in:
Nicolas Perriault 2017-05-13 15:55:46 +02:00
parent 8e187efc10
commit 4d8757ad7c
No known key found for this signature in database
GPG Key ID: DA5E4C83904F7A2A
14 changed files with 252 additions and 161 deletions

View File

@ -46,6 +46,10 @@ body {
min-height: 0;
}
.panel-default .panel-heading {
background-color: inherit;
}
.timeline {
overflow-y: auto;
overflow-x: hidden;
@ -54,6 +58,13 @@ body {
overflow-y: scroll;
}
#favorite-timeline,
#local-timeline,
#global-timeline {
max-height: calc(100vh - 102px);
}
li.load-more {
cursor: wait;
}
@ -119,7 +130,7 @@ li.load-more {
padding-top: 6px;
}
.notifications-panel .btn-group-justified .btn {
.panel .btn-group-justified .btn {
border-radius: 0;
border-bottom: 0;
outline: 0;

View File

@ -14,6 +14,7 @@ module Command
, loadLocalTimeline
, loadGlobalTimeline
, loadAccountTimeline
, loadFavoriteTimeline
, loadNextTimeline
, loadRelationships
, loadThread
@ -317,6 +318,20 @@ loadAccountTimeline client accountId url =
Cmd.none
loadFavoriteTimeline : Maybe Client -> Maybe String -> Cmd Msg
loadFavoriteTimeline client url =
case client of
Just client ->
HttpBuilder.get (Maybe.withDefault ApiUrl.favouriteTimeline url)
|> withClient client
|> withBodyDecoder (Decode.list statusDecoder)
|> withQueryParams [ ( "limit", "60" ) ]
|> send (MastodonEvent << FavoriteTimeline (url /= Nothing))
Nothing ->
Cmd.none
loadTimelines : Maybe Client -> Cmd Msg
loadTimelines client =
Cmd.batch
@ -342,6 +357,9 @@ loadNextTimeline client currentView id next =
"global-timeline" ->
loadGlobalTimeline client (Just next)
"favorite-timeline" ->
loadFavoriteTimeline client (Just next)
"account-timeline" ->
case currentView of
AccountView account ->

View File

@ -17,6 +17,7 @@ init { registration, clients } location =
, homeTimeline = Update.Timeline.empty "home-timeline"
, localTimeline = Update.Timeline.empty "local-timeline"
, globalTimeline = Update.Timeline.empty "global-timeline"
, favoriteTimeline = Update.Timeline.empty "favorite-timeline"
, accountTimeline = Update.Timeline.empty "account-timeline"
, accountFollowers = Update.Timeline.empty "account-followers"
, accountFollowing = Update.Timeline.empty "account-following"
@ -26,7 +27,6 @@ init { registration, clients } location =
, draft = Update.Draft.empty
, errors = []
, location = location
, useGlobalTimeline = False
, viewer = Nothing
, currentView = LocalTimelineView
, currentUser = Nothing

View File

@ -11,6 +11,7 @@ module Mastodon.ApiUrl
, status
, homeTimeline
, publicTimeline
, favouriteTimeline
, notifications
, relationships
, statuses
@ -107,6 +108,11 @@ accountTimeline id =
(account id) ++ "/statuses"
favouriteTimeline : String
favouriteTimeline =
apiPrefix ++ "/favourites"
notifications : String
notifications =
apiPrefix ++ "/notifications"

View File

@ -56,14 +56,15 @@ type MastodonMsg
| CurrentUser (MastodonResult Account)
| FavoriteAdded (MastodonResult Status)
| FavoriteRemoved (MastodonResult Status)
| FavoriteTimeline Bool (MastodonResult (List Status))
| GlobalTimeline Bool (MastodonResult (List Status))
| HomeTimeline Bool (MastodonResult (List Status))
| LocalTimeline Bool (MastodonResult (List Status))
| Notifications Bool (MastodonResult (List Notification))
| Reblogged (MastodonResult Status)
| StatusDeleted (MastodonResult Int)
| StatusPosted (MastodonResult Status)
| Unreblogged (MastodonResult Status)
| HomeTimeline Bool (MastodonResult (List Status))
type WebSocketMsg
@ -73,7 +74,7 @@ type WebSocketMsg
type Msg
= AddFavorite Int
= AddFavorite Status
| AskConfirm String Msg Msg
| ClearError Int
| CloseAccount
@ -91,19 +92,18 @@ type Msg
| MastodonEvent MastodonMsg
| NoOp
| OpenThread Status
| OpenAccountSelector
| ReblogStatus Int
| ReblogStatus Status
| Register
| RemoveFavorite Int
| RemoveFavorite Status
| ScrollColumn ScrollDirection String
| ServerChange String
| SetView CurrentView
| SubmitDraft
| SwitchClient Client
| Tick Time
| UnfollowAccount Int
| UrlChange Navigation.Location
| UseGlobalTimeline Bool
| UnreblogStatus Int
| UnreblogStatus Status
| ViewAccountFollowing Account
| ViewAccountFollowers Account
| ViewAccountStatuses Account
@ -124,6 +124,7 @@ type CurrentView
| AccountFollowingView Account (Timeline Account)
| AccountView Account
| AccountSelectorView
| FavoriteTimelineView
| GlobalTimelineView
| LocalTimelineView
| ThreadView Thread
@ -198,6 +199,7 @@ type alias Model =
, homeTimeline : Timeline Status
, localTimeline : Timeline Status
, globalTimeline : Timeline Status
, favoriteTimeline : Timeline Status
, accountTimeline : Timeline Status
, accountFollowers : Timeline Account
, accountFollowing : Timeline Account
@ -207,7 +209,6 @@ type alias Model =
, draft : Draft
, errors : List ErrorNotification
, location : Navigation.Location
, useGlobalTimeline : Bool
, viewer : Maybe Viewer
, currentUser : Maybe Account
, currentView : CurrentView

View File

@ -54,6 +54,18 @@ update msg model =
Confirmed onConfirm ->
update onConfirm { model | confirm = Nothing }
SetView view ->
case view of
AccountSelectorView ->
{ model | currentView = view, server = "" } ! []
FavoriteTimelineView ->
{ model | currentView = view }
! [ Command.loadFavoriteTimeline (List.head model.clients) Nothing ]
_ ->
{ model | currentView = view } ! []
SwitchClient client ->
let
newClients =
@ -64,13 +76,14 @@ update msg model =
, homeTimeline = Update.Timeline.empty "home-timeline"
, localTimeline = Update.Timeline.empty "local-timeline"
, globalTimeline = Update.Timeline.empty "global-timeline"
, favoriteTimeline = Update.Timeline.empty "favorite-timeline"
, accountTimeline = Update.Timeline.empty "account-timeline"
, accountFollowers = Update.Timeline.empty "account-followers"
, accountFollowing = Update.Timeline.empty "account-following"
, notifications = Update.Timeline.empty "notifications"
, accountRelationships = []
, accountRelationship = Nothing
, currentView = Update.Timeline.preferred model
, currentView = LocalTimelineView
}
! [ Command.loadUserAccount <| Just client
, Command.loadTimelines <| Just client
@ -86,7 +99,7 @@ update msg model =
in
{ model
| clients = newClients
, currentView = Update.Timeline.preferred model
, currentView = LocalTimelineView
}
! [ Command.saveClients newClients
, Command.loadUserAccount newClient
@ -119,11 +132,8 @@ update msg model =
OpenThread status ->
model ! [ Command.loadThread (List.head model.clients) status ]
OpenAccountSelector ->
{ model | currentView = AccountSelectorView, server = "" } ! []
CloseThread ->
{ model | currentView = Update.Timeline.preferred model } ! []
{ model | currentView = LocalTimelineView } ! []
FollowAccount id ->
model ! [ Command.follow (List.head model.clients) id ]
@ -134,21 +144,21 @@ update msg model =
DeleteStatus id ->
model ! [ Command.deleteStatus (List.head model.clients) id ]
ReblogStatus id ->
Update.Timeline.processReblog id True model
! [ Command.reblogStatus (List.head model.clients) id ]
ReblogStatus status ->
Update.Timeline.processReblog status True model
! [ Command.reblogStatus (List.head model.clients) status.id ]
UnreblogStatus id ->
Update.Timeline.processReblog id False model
! [ Command.unreblogStatus (List.head model.clients) id ]
UnreblogStatus status ->
Update.Timeline.processReblog status False model
! [ Command.unreblogStatus (List.head model.clients) status.id ]
AddFavorite id ->
Update.Timeline.processFavourite id True model
! [ Command.favouriteStatus (List.head model.clients) id ]
AddFavorite status ->
Update.Timeline.processFavourite status True model
! [ Command.favouriteStatus (List.head model.clients) status.id ]
RemoveFavorite id ->
Update.Timeline.processFavourite id False model
! [ Command.unfavouriteStatus (List.head model.clients) id ]
RemoveFavorite status ->
Update.Timeline.processFavourite status False model
! [ Command.unfavouriteStatus (List.head model.clients) status.id ]
DraftEvent draftMsg ->
case model.currentUser of
@ -199,16 +209,9 @@ update msg model =
ViewAccountStatuses account ->
{ model | currentView = AccountView account } ! []
UseGlobalTimeline flag ->
let
newModel =
{ model | useGlobalTimeline = flag }
in
{ newModel | currentView = Update.Timeline.preferred newModel } ! []
CloseAccount ->
{ model
| currentView = Update.Timeline.preferred model
| currentView = LocalTimelineView
, accountTimeline = Update.Timeline.empty "account-timeline"
, accountFollowing = Update.Timeline.empty "account-following"
, accountFollowers = Update.Timeline.empty "account-followers"
@ -216,7 +219,7 @@ update msg model =
! []
CloseAccountSelector ->
{ model | currentView = Update.Timeline.preferred model } ! []
{ model | currentView = LocalTimelineView } ! []
FilterNotifications filter ->
{ model | notificationFilter = filter } ! []

View File

@ -82,7 +82,7 @@ update msg model =
Err error ->
{ model
| currentView = Update.Timeline.preferred model
| currentView = LocalTimelineView
, errors = addErrorNotification (errorText error) model
}
! []
@ -149,6 +149,14 @@ update msg model =
Err error ->
{ model | errors = addErrorNotification (errorText error) model } ! []
FavoriteTimeline append result ->
case result of
Ok { decoded, links } ->
{ model | favoriteTimeline = Update.Timeline.update append decoded links model.favoriteTimeline } ! []
Err error ->
{ model | errors = addErrorNotification (errorText error) model } ! []
Reblogged result ->
case result of
Ok _ ->
@ -200,7 +208,7 @@ update msg model =
Err error ->
{ model
| currentView = Update.Timeline.preferred model
| currentView = LocalTimelineView
, errors = addErrorNotification (errorText error) model
}
! []

View File

@ -4,7 +4,6 @@ module Update.Timeline
, deleteStatus
, empty
, markAsLoading
, preferred
, prepend
, processReblog
, processFavourite
@ -25,7 +24,7 @@ deleteStatusFromCurrentView id model =
ThreadView thread ->
if thread.status.id == id then
-- the current thread status as been deleted, close it
preferred model
LocalTimelineView
else
let
update statuses =
@ -49,6 +48,7 @@ deleteStatusFromAllTimelines id model =
| homeTimeline = deleteStatus id model.homeTimeline
, localTimeline = deleteStatus id model.localTimeline
, globalTimeline = deleteStatus id model.globalTimeline
, favoriteTimeline = deleteStatus id model.favoriteTimeline
, accountTimeline = deleteStatus id model.accountTimeline
, notifications = deleteStatusFromNotifications id model.notifications
, currentView = deleteStatusFromCurrentView id model
@ -104,6 +104,9 @@ markAsLoading loading id model =
"global-timeline" ->
{ model | globalTimeline = mark model.globalTimeline }
"favorite-timeline" ->
{ model | favoriteTimeline = mark model.favoriteTimeline }
"account-timeline" ->
case model.currentView of
AccountView account ->
@ -116,47 +119,49 @@ markAsLoading loading id model =
model
preferred : Model -> CurrentView
preferred model =
if model.useGlobalTimeline then
GlobalTimelineView
else
LocalTimelineView
prepend : a -> Timeline a -> Timeline a
prepend entry timeline =
{ timeline | entries = entry :: timeline.entries }
processFavourite : Int -> Bool -> Model -> Model
processFavourite statusId flag model =
updateWithBoolFlag statusId
flag
(\s ->
{ s
| favourited = Just flag
, favourites_count =
if flag then
s.favourites_count + 1
else if s.favourites_count > 0 then
s.favourites_count - 1
else
0
}
)
model
processFavourite : Status -> Bool -> Model -> Model
processFavourite status added model =
let
favoriteTimeline =
if added then
prepend status model.favoriteTimeline
else
deleteStatus status.id model.favoriteTimeline
newModel =
{ model | favoriteTimeline = favoriteTimeline }
in
updateWithBoolFlag status.id
added
(\s ->
{ s
| favourited = Just added
, favourites_count =
if added then
s.favourites_count + 1
else if s.favourites_count > 0 then
s.favourites_count - 1
else
0
}
)
newModel
processReblog : Int -> Bool -> Model -> Model
processReblog statusId flag model =
updateWithBoolFlag statusId
flag
processReblog : Status -> Bool -> Model -> Model
processReblog status added model =
updateWithBoolFlag status.id
added
(\s ->
{ s
| reblogged = Just flag
| reblogged = Just added
, reblogs_count =
if flag then
if added then
s.reblogs_count + 1
else if s.reblogs_count > 0 then
s.reblogs_count - 1
@ -208,6 +213,7 @@ updateWithBoolFlag statusId flag statusUpdater model =
, accountTimeline = updateTimeline updateStatus model.accountTimeline
, localTimeline = updateTimeline updateStatus model.localTimeline
, globalTimeline = updateTimeline updateStatus model.globalTimeline
, favoriteTimeline = updateTimeline updateStatus model.favoriteTimeline
, notifications = updateTimeline updateNotification model.notifications
, currentView =
case model.currentView of

View File

@ -9,6 +9,7 @@ import String.Extra exposing (replace)
import Types exposing (..)
import View.Auth exposing (authForm)
import View.Common exposing (..)
import View.Timeline exposing (contextualTimelineMenu)
type alias CurrentUser =
@ -80,6 +81,7 @@ accountSelectorView model =
div [ class "col-md-3 column" ]
[ div [ class "panel panel-default" ]
[ closeablePanelheading "account-selector" "user" "Account selector" CloseAccountSelector
, contextualTimelineMenu model.currentView
, ul [ class "list-group " ] <|
List.map (accountIdentityView model.currentUser) model.clients
, div [ class "panel-body" ]

View File

@ -1,9 +1,8 @@
module View.App exposing (view)
import Html exposing (..)
import Html.Keyed as Keyed
import Html.Lazy as Lazy
import Html.Attributes exposing (..)
import Html.Lazy as Lazy
import Mastodon.Model exposing (..)
import Types exposing (..)
import View.Account exposing (accountFollowView, accountTimelineView)
@ -12,11 +11,9 @@ import View.Auth exposing (authView)
import View.Common as Common
import View.Draft exposing (draftView)
import View.Error exposing (errorsListView)
import View.Events exposing (..)
import View.Notification exposing (notificationListView)
import View.Settings exposing (settingsView)
import View.Status exposing (statusView, statusActionsView, statusEntryView)
import View.Thread exposing (threadView)
import View.Timeline exposing (contextualTimelineView, homeTimelineView)
import View.Viewer exposing (viewerView)
@ -28,61 +25,10 @@ type alias CurrentUserRelation =
Maybe Relationship
timelineView : ( String, String, CurrentUser, Timeline Status ) -> Html Msg
timelineView ( label, iconName, currentUser, timeline ) =
let
keyedEntry status =
( toString id, statusEntryView timeline.id "" currentUser status )
entries =
List.map keyedEntry timeline.entries
in
div [ class "col-md-3 column" ]
[ div [ class "panel panel-default" ]
[ a
[ href "", onClickWithPreventAndStop <| ScrollColumn ScrollTop timeline.id ]
[ div [ class "panel-heading" ] [ Common.icon iconName, text label ] ]
, Keyed.ul [ id timeline.id, class "list-group timeline" ] <|
(entries ++ [ ( "load-more", Common.loadMoreBtn timeline ) ])
]
]
homeTimelineView : CurrentUser -> Timeline Status -> Html Msg
homeTimelineView currentUser timeline =
Lazy.lazy timelineView
( "Home timeline"
, "home"
, currentUser
, timeline
)
localTimelineView : CurrentUser -> Timeline Status -> Html Msg
localTimelineView currentUser timeline =
Lazy.lazy timelineView
( "Local timeline"
, "th-large"
, currentUser
, timeline
)
globalTimelineView : CurrentUser -> Timeline Status -> Html Msg
globalTimelineView currentUser timeline =
Lazy.lazy timelineView
( "Global timeline"
, "globe"
, currentUser
, timeline
)
sidebarView : Model -> Html Msg
sidebarView model =
div [ class "col-md-3 column" ]
[ Lazy.lazy draftView model
, Lazy.lazy settingsView model
]
@ -102,12 +48,6 @@ homepageView model =
model.notificationFilter
model.notifications
, case model.currentView of
LocalTimelineView ->
localTimelineView currentUser model.localTimeline
GlobalTimelineView ->
globalTimelineView currentUser model.globalTimeline
AccountView account ->
accountTimelineView
currentUser
@ -136,6 +76,30 @@ homepageView model =
ThreadView thread ->
threadView currentUser thread
LocalTimelineView ->
contextualTimelineView
LocalTimelineView
"Local timeline"
"th-large"
currentUser
model.localTimeline
GlobalTimelineView ->
contextualTimelineView
GlobalTimelineView
"Global timeline"
"globe"
currentUser
model.globalTimeline
FavoriteTimelineView ->
contextualTimelineView
FavoriteTimelineView
"Favorites"
"star"
currentUser
model.favoriteTimeline
]

View File

@ -81,7 +81,7 @@ currentUserView currentUser =
[ Common.accountLink False currentUser
, span []
[ text " ("
, a [ href "", onClickWithPreventAndStop <| OpenAccountSelector ]
, a [ href "", onClickWithPreventAndStop <| SetView AccountSelectorView ]
[ text "switch account" ]
, text ")"
]

View File

@ -1,22 +0,0 @@
module View.Settings exposing (settingsView)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Types exposing (..)
import View.Common as Common
settingsView : Model -> Html Msg
settingsView model =
div [ class "panel panel-default options" ]
[ div [ class "panel-heading" ] [ Common.icon "cog", text "options" ]
, div [ class "panel-body" ]
[ div [ class "checkbox" ]
[ label []
[ input [ type_ "checkbox", onCheck UseGlobalTimeline ] []
, text " 4th column renders the global timeline"
]
]
]
]

View File

@ -96,18 +96,18 @@ statusActionsView status currentUser =
( reblogClasses, reblogEvent ) =
case status.reblogged of
Just True ->
( baseBtnClasses ++ " reblogged", UnreblogStatus sourceStatus.id )
( baseBtnClasses ++ " reblogged", UnreblogStatus sourceStatus )
_ ->
( baseBtnClasses, ReblogStatus sourceStatus.id )
( baseBtnClasses, ReblogStatus sourceStatus )
( favClasses, favEvent ) =
case status.favourited of
Just True ->
( baseBtnClasses ++ " favourited", RemoveFavorite sourceStatus.id )
( baseBtnClasses ++ " favourited", RemoveFavorite sourceStatus )
_ ->
( baseBtnClasses, AddFavorite sourceStatus.id )
( baseBtnClasses, AddFavorite sourceStatus )
statusDate =
Date.fromString status.created_at

94
src/View/Timeline.elm Normal file
View File

@ -0,0 +1,94 @@
module View.Timeline
exposing
( contextualTimelineView
, contextualTimelineMenu
, homeTimelineView
)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (..)
import Html.Keyed as Keyed
import Html.Lazy as Lazy
import Mastodon.Model exposing (..)
import Types exposing (..)
import View.Common as Common
import View.Events exposing (..)
import View.Status exposing (statusView, statusActionsView, statusEntryView)
type alias CurrentUser =
Account
type alias CurrentUserRelation =
Maybe Relationship
topScrollableColumn : ( String, String, String ) -> Html Msg -> Html Msg
topScrollableColumn ( label, iconName, timelineId ) content =
div [ class "col-md-3 column" ]
[ div [ class "panel panel-default" ]
[ a
[ href "", onClickWithPreventAndStop <| ScrollColumn ScrollTop timelineId ]
[ div [ class "panel-heading" ] [ Common.icon iconName, text label ] ]
, content
]
]
timelineView : CurrentUser -> Timeline Status -> Html Msg
timelineView currentUser timeline =
let
keyedEntry status =
( toString id, statusEntryView timeline.id "" currentUser status )
entries =
List.map keyedEntry timeline.entries
in
Keyed.ul [ id timeline.id, class "list-group timeline" ] <|
(entries ++ [ ( "load-more", Common.loadMoreBtn timeline ) ])
homeTimelineView : CurrentUser -> Timeline Status -> Html Msg
homeTimelineView currentUser timeline =
Lazy.lazy2 topScrollableColumn
( "Home timeline"
, "home"
, timeline.id
)
(timelineView currentUser timeline)
contextualTimelineMenu : CurrentView -> Html Msg
contextualTimelineMenu currentView =
let
btnView tooltip iconName view =
button
[ class <|
"btn "
++ (if currentView == view then
"btn-primary active"
else
"btn-default"
)
, onClick <| SetView view
, Html.Attributes.title tooltip
]
[ Common.icon iconName ]
in
Common.justifiedButtonGroup ""
[ btnView "Local timeline" "th-large" LocalTimelineView
, btnView "Global timeline" "globe" GlobalTimelineView
, btnView "Favorites" "star" FavoriteTimelineView
, btnView "Accounts" "user" AccountSelectorView
]
contextualTimelineView : CurrentView -> String -> String -> CurrentUser -> Timeline Status -> Html Msg
contextualTimelineView currentView title iconName currentUser timeline =
div []
[ contextualTimelineMenu currentView
, timelineView currentUser timeline
]
|> Lazy.lazy2 topScrollableColumn ( title, iconName, timeline.id )