Scylla/src/Scylla/Views.elm

218 lines
7.6 KiB
Elm
Raw Normal View History

2018-12-08 15:06:14 -08:00
module Scylla.Views exposing (..)
import Scylla.Model exposing (..)
2018-12-08 17:15:35 -08:00
import Scylla.Sync exposing (..)
2018-12-08 19:09:20 -08:00
import Scylla.Route exposing (..)
2018-12-10 14:20:06 -08:00
import Scylla.Fnv as Fnv
2018-12-13 13:42:23 -08:00
import Scylla.Login exposing (Username)
2018-12-13 19:45:55 -08:00
import Scylla.Http exposing (fullMediaUrl)
import Scylla.Api exposing (ApiUrl)
import Html.Parser
import Html.Parser.Util
2018-12-10 16:16:39 -08:00
import Svg
import Svg.Attributes
2018-12-08 20:02:29 -08:00
import Url.Builder
2018-12-08 17:15:35 -08:00
import Json.Decode as Decode
2018-12-13 19:45:55 -08:00
import Html exposing (Html, Attribute, div, input, text, button, div, span, a, h2, table, td, tr, img)
import Html.Attributes exposing (type_, value, href, class, style, src, id)
2018-12-13 18:41:54 -08:00
import Html.Events exposing (onInput, onClick, on)
2018-12-08 17:15:35 -08:00
import Dict
2018-12-08 15:06:14 -08:00
2018-12-13 19:45:55 -08:00
contentRepositoryDownloadUrl : ApiUrl -> String -> String
contentRepositoryDownloadUrl apiUrl s =
let
lastIndex = Maybe.withDefault 6 <| List.head <| List.reverse <| String.indexes "/" s
authority = String.slice 6 lastIndex s
content = String.dropLeft (lastIndex + 1) s
in
(fullMediaUrl apiUrl) ++ "/download/" ++ authority ++ "/" ++ content
2018-12-10 14:20:06 -08:00
stringColor : String -> String
stringColor s =
let
hue = String.fromFloat <| (toFloat (Fnv.hash s)) / 4294967296 * 360
in
"hsl(" ++ hue ++ ", 82%, 71%)"
2018-12-08 17:15:35 -08:00
viewFull : Model -> List (Html Msg)
viewFull model =
let
room r = Maybe.map (\jr -> (r, jr))
<| Maybe.andThen (Dict.get r)
<| Maybe.andThen .join model.sync.rooms
2018-12-08 19:09:20 -08:00
core = case model.route of
Login -> loginView model
Base -> baseView model Nothing
Room r -> baseView model <| room r
2018-12-08 19:09:20 -08:00
_ -> div [] []
2018-12-08 17:15:35 -08:00
errorList = errorsView model.errors
in
[ errorList ] ++ [ core ]
errorsView : List String -> Html Msg
errorsView = div [] << List.map errorView
errorView : String -> Html Msg
errorView s = div [] [ text s ]
2018-12-08 15:06:14 -08:00
baseView : Model -> Maybe (String, JoinedRoom) -> Html Msg
baseView m jr =
let
roomView = case jr of
Just (id, r) -> joinedRoomView m id r
Nothing -> div [] []
in
div [ class "base-wrapper" ]
[ roomListView m
, roomView
]
roomListView : Model -> Html Msg
roomListView m =
let
2018-12-08 20:02:29 -08:00
rooms = Maybe.withDefault (Dict.empty) <| Maybe.andThen .join <| m.sync.rooms
roomList = div [ class "rooms-list" ] <| Dict.values <| Dict.map roomListElementView rooms
2018-12-08 20:02:29 -08:00
in
div [ class "rooms-wrapper" ]
[ h2 [] [ text "Rooms" ]
, roomList
]
2018-12-08 20:02:29 -08:00
roomListElementView : String -> JoinedRoom -> Html Msg
roomListElementView s jr =
2018-12-08 20:02:29 -08:00
let
name = Maybe.withDefault "<No Name>" <| roomName jr
in
2018-12-13 17:47:58 -08:00
div [ class "room-link-wrapper" ]
[ a [ href <| roomUrl s ] [ text name ]
, roomNotificationCountView jr.unreadNotifications
]
roomNotificationCountView : Maybe UnreadNotificationCounts -> Html Msg
roomNotificationCountView ns =
let
spanNumber = case Maybe.andThen .notificationCount ns of
Nothing -> ""
Just 0 -> ""
Just i -> String.fromInt i
spanSuffix = case Maybe.andThen .highlightCount ns of
Nothing -> ""
Just 0 -> ""
Just i -> "!"
in
span [ class "notification-count" ] [ text (spanNumber ++ spanSuffix) ]
2018-12-08 15:06:14 -08:00
loginView : Model -> Html Msg
2018-12-10 12:21:08 -08:00
loginView m = div [ class "login-wrapper" ]
[ h2 [] [ text "Log In" ]
, input [ type_ "text", value m.loginUsername, onInput ChangeLoginUsername] []
2018-12-08 15:06:14 -08:00
, input [ type_ "password", value m.loginPassword, onInput ChangeLoginPassword ] []
, input [ type_ "text", value m.apiUrl, onInput ChangeApiUrl ] []
, button [ onClick AttemptLogin ] [ text "Log In" ]
]
2018-12-08 17:15:35 -08:00
2018-12-19 21:52:07 -08:00
joinedRoomView : Model -> RoomId -> JoinedRoom -> Html Msg
2018-12-09 23:38:43 -08:00
joinedRoomView m roomId jr =
2018-12-08 17:15:35 -08:00
let
events = Maybe.withDefault [] <| Maybe.andThen .events jr.timeline
renderedEvents = List.filterMap (eventView m) events
2018-12-19 21:52:07 -08:00
eventWrapper = eventWrapperView m roomId renderedEvents
2018-12-13 16:28:13 -08:00
typing = List.map (displayName m) <| roomTypingUsers jr
typingText = String.join ", " typing
typingSuffix = case List.length typing of
0 -> ""
1 -> " is typing..."
_ -> " are typing..."
typingWrapper = div [ class "typing-wrapper" ] [ text <| typingText ++ typingSuffix ]
messageInput = div [ class "message-wrapper" ]
2018-12-09 23:38:43 -08:00
[ input
[ type_ "text"
, onInput <| ChangeRoomText roomId
2018-12-13 18:41:54 -08:00
, onEnterKey <| SendRoomText roomId
2018-12-09 23:38:43 -08:00
, value <| Maybe.withDefault "" <| Dict.get roomId m.roomText
] []
, button [ onClick <| SendFiles roomId ] [ iconView "file" ]
, button [ onClick <| SendImages roomId ] [ iconView "image" ]
2018-12-10 16:16:39 -08:00
, button [ onClick <| SendRoomText roomId ] [ iconView "send" ]
2018-12-09 23:38:43 -08:00
]
2018-12-08 17:15:35 -08:00
in
div [ class "room-wrapper" ]
[ h2 [] [ text <| Maybe.withDefault "<No Name>" <| roomName jr ]
, eventWrapper
2018-12-13 16:28:13 -08:00
, typingWrapper
, messageInput
]
2018-12-08 17:15:35 -08:00
2018-12-13 18:41:54 -08:00
onEnterKey : Msg -> Attribute Msg
onEnterKey msg =
let
isEnter code = if code == 13 then Decode.succeed msg else Decode.fail "Not ENTER"
in
on "keydown" (Decode.andThen isEnter <| Decode.field "keyCode" Decode.int)
2018-12-10 16:16:39 -08:00
iconView : String -> Html Msg
iconView name =
let
url = Url.Builder.absolute [ "static", "svg", "feather-sprite.svg" ] []
in
Svg.svg
[ Svg.Attributes.class "feather-icon"
] [ Svg.use [ Svg.Attributes.xlinkHref (url ++ "#" ++ name) ] [] ]
2018-12-19 21:52:07 -08:00
eventWrapperView : Model -> RoomId -> List (Html Msg) -> Html Msg
eventWrapperView m rid es = div [ class "events-wrapper", id "events-wrapper" ]
[ a [ class "history-link", onClick <| History rid ] [ text "Load older messages" ]
, table [ class "events-table" ] es
]
2018-12-08 17:15:35 -08:00
eventView : Model -> RoomEvent -> Maybe (Html Msg)
2018-12-10 14:20:06 -08:00
eventView m re =
let
viewFunction = case re.type_ of
"m.room.message" -> Just messageView
_ -> Nothing
createRow mhtml = tr []
[ td [] [ eventSenderView m re.sender ]
2018-12-10 14:20:06 -08:00
, td [] [ mhtml ]
]
in
Maybe.map createRow
<| Maybe.andThen (\f -> f m re) viewFunction
2018-12-13 13:42:23 -08:00
eventSenderView : Model -> Username -> Html Msg
eventSenderView m s =
2018-12-13 13:42:23 -08:00
span [ style "background-color" <| stringColor s, class "sender-wrapper" ] [ text <| displayName m s ]
2018-12-08 17:15:35 -08:00
messageView : Model -> RoomEvent -> Maybe (Html Msg)
messageView m re =
let
msgtype = Decode.decodeValue (Decode.field "msgtype" Decode.string) re.content
in
case msgtype of
Ok "m.text" -> messageTextView m re
2018-12-13 19:45:55 -08:00
Ok "m.image" -> messageImageView m re
2018-12-08 17:15:35 -08:00
_ -> Nothing
messageTextView : Model -> RoomEvent -> Maybe (Html Msg)
messageTextView m re =
let
body = Decode.decodeValue (Decode.field "body" Decode.string) re.content
customHtml = Maybe.map Html.Parser.Util.toVirtualDom
<| Maybe.andThen (Result.toMaybe << Html.Parser.run )
<| Result.toMaybe
<| Decode.decodeValue (Decode.field "formatted_body" Decode.string) re.content
2018-12-10 14:20:06 -08:00
wrap mtext = span [] [ text mtext ]
2018-12-08 17:15:35 -08:00
in
case customHtml of
Just c -> Just <| div [ class "markdown-wrapper" ] c
Nothing -> Maybe.map wrap <| Result.toMaybe body
2018-12-13 19:45:55 -08:00
messageImageView : Model -> RoomEvent -> Maybe (Html Msg)
messageImageView m re =
let
body = Decode.decodeValue (Decode.field "url" Decode.string) re.content
in
Maybe.map (\s -> img [ class "message-image", src s ] [])
<| Maybe.map (contentRepositoryDownloadUrl m.apiUrl)
<| Result.toMaybe body