Compare commits

...

3 Commits

8 changed files with 90 additions and 16 deletions

View File

@ -36,6 +36,7 @@ init flags url key =
, errors = [] , errors = []
, roomText = Dict.empty , roomText = Dict.empty
, transactionId = 0 , transactionId = 0
, userData = Dict.empty
} }
cmd = case flags.token of cmd = case flags.token of
Just _ -> Cmd.none Just _ -> Cmd.none
@ -58,7 +59,9 @@ update msg model = case msg of
TryUrl urlRequest -> updateTryUrl model urlRequest TryUrl urlRequest -> updateTryUrl model urlRequest
ChangeRoute r -> ({ model | route = r }, Cmd.none) ChangeRoute r -> ({ model | route = r }, Cmd.none)
ReceiveLoginResponse r -> updateLoginResponse model r ReceiveLoginResponse r -> updateLoginResponse model r
ReceiveSyncResponse r -> updateSyncResponse model r ReceiveFirstSyncResponse r -> updateSyncResponse model r False
ReceiveSyncResponse r -> updateSyncResponse model r True
ReceiveUserData s r -> (model, Cmd.none)
ChangeRoomText r t -> ({ model | roomText = Dict.insert r t model.roomText}, Cmd.none) ChangeRoomText r t -> ({ model | roomText = Dict.insert r t model.roomText}, Cmd.none)
SendRoomText r -> updateSendRoomText model r SendRoomText r -> updateSendRoomText model r
SendRoomTextResponse r -> (model, Cmd.none) SendRoomTextResponse r -> (model, Cmd.none)
@ -87,8 +90,8 @@ updateLoginResponse model r = case r of
] ) ] )
Err e -> (model, Cmd.none) Err e -> (model, Cmd.none)
updateSyncResponse : Model -> Result Http.Error SyncResponse -> (Model, Cmd Msg) updateSyncResponse : Model -> Result Http.Error SyncResponse -> Bool -> (Model, Cmd Msg)
updateSyncResponse model r = updateSyncResponse model r notify =
let let
token = Maybe.withDefault "" model.token token = Maybe.withDefault "" model.token
nextBatch = Result.withDefault model.sync.nextBatch nextBatch = Result.withDefault model.sync.nextBatch

View File

@ -3,6 +3,7 @@ import Scylla.Model exposing (..)
import Scylla.Api exposing (..) import Scylla.Api exposing (..)
import Scylla.Sync exposing (syncResponseDecoder) import Scylla.Sync exposing (syncResponseDecoder)
import Scylla.Login exposing (loginResponseDecoder, Username, Password) import Scylla.Login exposing (loginResponseDecoder, Username, Password)
import Scylla.UserData exposing (userDataDecoder, UserData)
import Json.Encode exposing (object, string, int) import Json.Encode exposing (object, string, int)
import Http exposing (request, emptyBody, jsonBody, expectJson, expectWhatever) import Http exposing (request, emptyBody, jsonBody, expectJson, expectWhatever)
@ -16,7 +17,7 @@ firstSync apiUrl token = request
, headers = authenticatedHeaders token , headers = authenticatedHeaders token
, url = (fullUrl apiUrl) ++ "/sync" , url = (fullUrl apiUrl) ++ "/sync"
, body = emptyBody , body = emptyBody
, expect = expectJson ReceiveSyncResponse syncResponseDecoder , expect = expectJson ReceiveFirstSyncResponse syncResponseDecoder
, timeout = Nothing , timeout = Nothing
, tracker = Nothing , tracker = Nothing
} }
@ -66,3 +67,14 @@ login apiUrl username password = request
, timeout = Nothing , timeout = Nothing
, tracker = Nothing , tracker = Nothing
} }
userData : ApiUrl -> ApiToken -> Username -> Cmd Msg
userData apiUrl token username = request
{ method = "GET"
, headers = authenticatedHeaders token
, url = (fullUrl apiUrl) ++ "/profile/" ++ username
, body = emptyBody
, expect = expectJson (ReceiveUserData username) userDataDecoder
, timeout = Nothing
, tracker = Nothing
}

View File

@ -2,6 +2,7 @@ module Scylla.Model exposing (..)
import Scylla.Api exposing (..) import Scylla.Api exposing (..)
import Scylla.Sync exposing (SyncResponse, JoinedRoom) import Scylla.Sync exposing (SyncResponse, JoinedRoom)
import Scylla.Login exposing (LoginResponse, Username, Password) import Scylla.Login exposing (LoginResponse, Username, Password)
import Scylla.UserData exposing (UserData)
import Scylla.Route exposing (Route) import Scylla.Route exposing (Route)
import Browser.Navigation as Nav import Browser.Navigation as Nav
import Dict exposing (Dict) import Dict exposing (Dict)
@ -20,6 +21,7 @@ type alias Model =
, errors : List String , errors : List String
, roomText : Dict String String , roomText : Dict String String
, transactionId : Int , transactionId : Int
, userData : Dict Username UserData
} }
type Msg = type Msg =
@ -32,6 +34,8 @@ type Msg =
| ChangeRoomText String String -- Change to a room's input text | ChangeRoomText String String -- Change to a room's input text
| SendRoomText String -- Sends a message typed into a given room's input | SendRoomText String -- Sends a message typed into a given room's input
| SendRoomTextResponse (Result Http.Error ()) -- A send message response finished | SendRoomTextResponse (Result Http.Error ()) -- A send message response finished
| ReceiveFirstSyncResponse (Result Http.Error SyncResponse) -- HTTP, Sync has finished
| ReceiveSyncResponse (Result Http.Error SyncResponse) -- HTTP, Sync has finished | ReceiveSyncResponse (Result Http.Error SyncResponse) -- HTTP, Sync has finished
| ReceiveLoginResponse (Result Http.Error LoginResponse) -- HTTP, Login has finished | ReceiveLoginResponse (Result Http.Error LoginResponse) -- HTTP, Login has finished
| ReceiveUserData Username (Result Http.Error UserData)

View File

@ -1,11 +1,11 @@
port module Scylla.Notification exposing (..) port module Scylla.Notification exposing (..)
import Scylla.Model exposing (..)
import Json.Decode import Json.Decode
type alias Notification = type alias Notification =
{ name : String { name : String
, text : String , text : String
, room : String
} }
port sendNotificationPort : Notification -> Cmd msg port sendNotificationPort : Notification -> Cmd msg
port onNotificationClockPort : (Json.Decode.Value -> msg) -> Sub msg port onNotificationClickPort : (Json.Decode.Value -> msg) -> Sub msg

View File

@ -1,5 +1,7 @@
module Scylla.Sync exposing (..) module Scylla.Sync exposing (..)
import Scylla.Api exposing (..) import Scylla.Api exposing (..)
import Scylla.Notification exposing (..)
import Scylla.Login exposing (Username)
import Dict exposing (Dict) import Dict exposing (Dict)
import Json.Decode as Decode exposing (Decoder, int, string, float, list, value, dict, bool, field) import Json.Decode as Decode exposing (Decoder, int, string, float, list, value, dict, bool, field)
import Json.Decode.Pipeline exposing (required, optional) import Json.Decode.Pipeline exposing (required, optional)
@ -267,6 +269,7 @@ uniqueByRecursive f l s = case l of
uniqueBy : (a -> comparable) -> List a -> List a uniqueBy : (a -> comparable) -> List a -> List a
uniqueBy f l = uniqueByRecursive f l Set.empty uniqueBy f l = uniqueByRecursive f l Set.empty
-- Business Logic: Merging
mergeMaybe : (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a mergeMaybe : (a -> a -> a) -> Maybe a -> Maybe a -> Maybe a
mergeMaybe f l r = case (l, r) of mergeMaybe f l r = case (l, r) of
(Just v1, Just v2) -> Just <| f v1 v2 (Just v1, Just v2) -> Just <| f v1 v2
@ -347,6 +350,16 @@ mergeSyncResponse l r =
, accountData = mergeMaybe mergeAccountData l.accountData r.accountData , accountData = mergeMaybe mergeAccountData l.accountData r.accountData
} }
-- Business Logic: Names
senderName : String -> String
senderName s =
let
colonIndex = Maybe.withDefault -1
<| List.head
<| String.indexes ":" s
in
String.slice 1 colonIndex s
roomName : JoinedRoom -> Maybe String roomName : JoinedRoom -> Maybe String
roomName jr = roomName jr =
let let
@ -355,3 +368,35 @@ roomName jr =
name e = Result.toMaybe <| Decode.decodeValue (field "name" string) e.content name e = Result.toMaybe <| Decode.decodeValue (field "name" string) e.content
in in
Maybe.andThen name <| Maybe.andThen nameEvent <| Maybe.andThen .events <| state Maybe.andThen name <| Maybe.andThen nameEvent <| Maybe.andThen .events <| state
-- Business Logic: Event Extraction
notificationEvent : SyncResponse -> Maybe (String, RoomEvent)
notificationEvent s =
let
applyPair k = List.map (\v -> (k, v))
in
List.head
<| List.sortBy (\(k, v) -> v.originServerTs)
<| Dict.foldl (\k v a -> a ++ applyPair k v) []
<| joinedRoomsEvents s
joinedRoomsEvents : SyncResponse -> Dict String (List RoomEvent)
joinedRoomsEvents s =
Maybe.withDefault Dict.empty
<| Maybe.map (Dict.map (\k v -> Maybe.withDefault [] <| Maybe.andThen .events v.timeline))
<| Maybe.andThen .join s.rooms
-- Business Logic: User Extraction
roomsUsers : SyncResponse -> List Username
roomsUsers s =
let
users dict =
List.map .sender
<| (List.concatMap <| Maybe.withDefault [] << .events)
<| (List.filterMap .timeline)
<| Dict.values dict
usersFor f = Maybe.withDefault [] <| Maybe.map users <| Maybe.andThen f s.rooms
joinedUsers = usersFor .join
leftUsers = usersFor .leave
in
leftUsers ++ joinedUsers

14
src/Scylla/UserData.elm Normal file
View File

@ -0,0 +1,14 @@
module Scylla.UserData exposing (..)
import Json.Decode as Decode exposing (Decoder, int, string, float, list, value, dict, bool, field)
import Json.Decode.Pipeline exposing (required, optional)
type alias UserData =
{ displayName : Maybe String
, avatarUrl : Maybe String
}
userDataDecoder : Decoder UserData
userDataDecoder =
Decode.succeed UserData
|> optional "displayname" (Decode.map Just string) Nothing
|> optional "avatar_url" (Decode.map Just string) Nothing

View File

@ -19,15 +19,6 @@ stringColor s =
in in
"hsl(" ++ hue ++ ", 82%, 71%)" "hsl(" ++ hue ++ ", 82%, 71%)"
senderName : String -> String
senderName s =
let
colonIndex = Maybe.withDefault -1
<| List.head
<| String.indexes ":" s
in
String.slice 1 colonIndex s
viewFull : Model -> List (Html Msg) viewFull : Model -> List (Html Msg)
viewFull model = viewFull model =
let let

View File

@ -7,6 +7,11 @@ function setupNotificationPorts(app) {
var options = { var options = {
"body" : data.text "body" : data.text
} }
new Notification(data.name, options) var n = new Notification(data.name, options)
n.onclick = function() {
app.ports.onNotificationClickPort.send({
"room" : data.room
});
}
}) })
} }