diff --git a/public/style.css b/public/style.css index 7f15b78..a355506 100644 --- a/public/style.css +++ b/public/style.css @@ -417,6 +417,40 @@ form.search { width: 100%; } +/* Polls */ + +.poll { + font-size: 14px; + margin: 0 0 10px; +} + +.poll ul { + padding-left: 0; +} + +.poll li { + padding: 4px 0 4px; +} + +.poll .poll-percentage { + display: inline-block; + width: 32px; +} + +.poll .poll-bar { + display: block; + width: 100%; + height: 6px; + border-radius: 3px; + background-color: #9baec8; +} + +.poll .poll-footer { + color: #aaa; + display: block; + padding: 10px 0 0; +} + /* Spoiler */ .spoiled input[type=checkbox] { diff --git a/src/Mastodon/Decoder.elm b/src/Mastodon/Decoder.elm index 53e82d0..5081aa7 100644 --- a/src/Mastodon/Decoder.elm +++ b/src/Mastodon/Decoder.elm @@ -21,6 +21,9 @@ module Mastodon.Decoder , searchResultsDecoder , statusDecoder , webSocketEventDecoder + , pollIdDecoder + , pollOptionDecoder + , pollDecoder ) import Json.Decode as Decode @@ -253,6 +256,28 @@ idDecoder = ] +pollIdDecoder : Decode.Decoder PollId +pollIdDecoder = + idDecoder |> Decode.map PollId + + +pollOptionDecoder : Decode.Decoder PollOption +pollOptionDecoder = + Pipe.decode PollOption + |> Pipe.required "title" Decode.string + |> Pipe.required "votes_count" Decode.int + + +pollDecoder : Decode.Decoder Poll +pollDecoder = + Pipe.decode Poll + |> Pipe.required "id" pollIdDecoder + |> Pipe.required "expired" Decode.bool + |> Pipe.required "voted" Decode.bool + |> Pipe.required "votes_count" Decode.int + |> Pipe.required "options" (Decode.list pollOptionDecoder) + + statusIdDecoder : Decode.Decoder StatusId statusIdDecoder = idDecoder |> Decode.map StatusId @@ -281,6 +306,13 @@ statusDecoder = |> Pipe.required "uri" Decode.string |> Pipe.required "url" (Decode.nullable Decode.string) |> Pipe.required "visibility" Decode.string + |> Pipe.optional "poll" pollDecoder + { id = PollId("") + , expired = False + , votes_count = -1 + , voted = False + , options = [] + } |> Pipe.optional "pinned" Decode.bool False -- Not a real value, used to show pinned indicator diff --git a/src/Mastodon/Model.elm b/src/Mastodon/Model.elm index 69ca275..503b6cb 100644 --- a/src/Mastodon/Model.elm +++ b/src/Mastodon/Model.elm @@ -20,6 +20,9 @@ module Mastodon.Model , Reblog(..) , Relationship , Tag + , PollId(..) + , PollOption + , Poll , SearchResults , Source , Status @@ -239,6 +242,25 @@ type alias Hashtag = } +type PollId + = PollId String + + +type alias PollOption = + { title : String + , votes_count : Int + } + + +type alias Poll = + { id : PollId + , expired : Bool + , voted : Bool + , votes_count : Int + , options : List PollOption + } + + type alias SearchResults = { accounts : List Account , statuses : List Status @@ -277,6 +299,7 @@ type alias Status = , uri : String , url : Maybe String , visibility : String + , poll : Poll , pinned : Bool -- Not a real value, used to show pinned indicator } diff --git a/src/View/Status.elm b/src/View/Status.elm index 018e3b3..5967b75 100644 --- a/src/View/Status.elm +++ b/src/View/Status.elm @@ -86,6 +86,47 @@ attachmentListView context { media_attachments, sensitive } = List.map (keyedEntry attachments) attachments +pollView : Status -> Html Msg +pollView status = + let + pollStatus poll = + if poll.voted then + text "Voted" + else if poll.expired then + text "Expired" + else + text "Not voted" + optionPercentage option = + 100*option.votes_count//status.poll.votes_count + optionPercentageText option = + span [ class "poll-percentage" ] + [ text <| (toString (optionPercentage option))++"%" + ] + optionBar option = + span + [ class "poll-bar" + , style [("width", toString (optionPercentage option)++"%")] + ] [] + optionEntry option = + li [] + [ optionPercentageText option + , text option.title + , optionBar option + ] + in + if status.poll.id == PollId("") then + text "" + else + div [ class "poll" ] + [ ul [] <| + List.map optionEntry status.poll.options + , span [ class "poll-footer" ] + [ text <| (toString status.poll.votes_count)++" votes" + , text " • " + , pollStatus status.poll ] + ] + + statusActionsView : Status -> CurrentUser -> Bool -> Html Msg statusActionsView status currentUser showApp = let @@ -156,6 +197,7 @@ statusContentView context status = "" -> div [ class "status-text" ] [ div [] <| formatContent status.content status.mentions + , pollView status , attachmentListView context status ] @@ -172,6 +214,7 @@ statusContentView context status = , label [ onClickWithStop NoOp, for statusId ] [ text "Reveal content" ] , div [ class "spoiled-content" ] [ div [] <| formatContent status.content status.mentions + , pollView status , attachmentListView context status ] ]