Start switching from sync to room data

This commit is contained in:
Danila Fedorin 2019-09-10 23:24:47 -07:00
parent 676d6c28a7
commit 29e81a88ac
9 changed files with 110 additions and 79 deletions

View File

@ -1,8 +1,11 @@
import Browser exposing (application, UrlRequest(..)) import Browser exposing (application, UrlRequest(..))
import Browser.Navigation as Nav import Browser.Navigation as Nav
import Browser.Dom exposing (Viewport, setViewportOf) import Browser.Dom exposing (Viewport, setViewportOf)
import Scylla.Room exposing (OpenRooms, applySync)
import Scylla.Sync exposing (..) import Scylla.Sync exposing (..)
import Scylla.Sync.Events exposing (toMessageEvent, getType, getSender, getUnsigned) import Scylla.Sync.Events exposing (toMessageEvent, getType, getSender, getUnsigned)
import Scylla.Sync.AccountData exposing (..)
import Scylla.ListUtils exposing (..)
import Scylla.Messages exposing (..) import Scylla.Messages exposing (..)
import Scylla.Login exposing (..) import Scylla.Login exposing (..)
import Scylla.Api exposing (..) import Scylla.Api exposing (..)
@ -14,7 +17,7 @@ import Scylla.UserData exposing (..)
import Scylla.Notification exposing (..) import Scylla.Notification exposing (..)
import Scylla.Storage exposing (..) import Scylla.Storage exposing (..)
import Scylla.Markdown exposing (..) import Scylla.Markdown exposing (..)
import Scylla.AccountData exposing (..) import Scylla.Room exposing (..)
import Url exposing (Url) import Url exposing (Url)
import Url.Parser exposing (parse) import Url.Parser exposing (parse)
import Url.Builder import Url.Builder
@ -55,6 +58,7 @@ init _ url key =
, roomNames = Dict.empty , roomNames = Dict.empty
, connected = True , connected = True
, searchText = "" , searchText = ""
, rooms = emptyOpenRooms
} }
cmd = getStoreValuePort "scylla.loginInfo" cmd = getStoreValuePort "scylla.loginInfo"
in in
@ -355,6 +359,7 @@ updateSyncResponse model r notify =
{ model | sync = newSync sr { model | sync = newSync sr
, sending = sending (mergeSyncResponse model.sync sr) , sending = sending (mergeSyncResponse model.sync sr)
, roomNames = computeRoomsDisplayNames model.userData (newSync sr) , roomNames = computeRoomsDisplayNames model.userData (newSync sr)
, rooms = applySync sr model.rooms
} }
in in
case r of case r of

View File

@ -1,23 +0,0 @@
module Scylla.AccountData exposing (..)
import Scylla.Sync exposing (SyncResponse, roomAccountData)
import Scylla.Sync.AccountData exposing (AccountData)
import Scylla.Sync.Rooms exposing (JoinedRoom)
import Json.Decode as Decode
import Json.Encode as Encode
import Dict exposing (Dict)
type alias DirectMessages = Dict String String
type alias DirectMessagesRaw = Dict String (List String)
directMessagesDecoder : Decode.Decoder DirectMessages
directMessagesDecoder =
Decode.dict (Decode.list Decode.string)
|> Decode.map (invertDirectMessages)
invertDirectMessages : DirectMessagesRaw -> DirectMessages
invertDirectMessages dmr =
Dict.foldl
(\k lv acc -> List.foldl (\v -> Dict.insert v k) acc lv)
Dict.empty
dmr

39
src/Scylla/ListUtils.elm Normal file
View File

@ -0,0 +1,39 @@
module Scylla.ListUtils exposing (..)
import Dict exposing (Dict)
import Set exposing (Set)
groupBy : (a -> comparable) -> List a -> Dict comparable (List a)
groupBy f xs =
let
update v ml = case ml of
Just l -> Just (v::l)
Nothing -> Just [ v ]
in
List.foldl (\v acc -> Dict.update (f v) (update v) acc) Dict.empty xs
uniqueByTailRecursive : (a -> comparable) -> List a -> Set comparable -> List a -> List a
uniqueByTailRecursive f l s acc =
case l of
x::tail ->
if Set.member (f x) s
then uniqueByTailRecursive f tail s acc
else uniqueByTailRecursive f tail (Set.insert (f x) s) (x::acc)
[] -> List.reverse acc
uniqueBy : (a -> comparable) -> List a -> List a
uniqueBy f l = uniqueByTailRecursive f l Set.empty []
findFirst : (a -> Bool) -> List a -> Maybe a
findFirst cond l = case l of
x::xs -> if cond x then Just x else findFirst cond xs
[] -> Nothing
findLast : (a -> Bool) -> List a -> Maybe a
findLast cond l = findFirst cond <| List.reverse l
findFirstBy : (a -> comparable) -> (a -> Bool) -> List a -> Maybe a
findFirstBy sortFunction cond l = findFirst cond <| List.sortBy sortFunction l
findLastBy : (a -> comparable) -> (a -> Bool) -> List a -> Maybe a
findLastBy sortFunction cond l = findLast cond <| List.sortBy sortFunction l

View File

@ -1,9 +1,10 @@
module Scylla.Model exposing (..) module Scylla.Model exposing (..)
import Scylla.Api exposing (..) import Scylla.Api exposing (..)
import Scylla.Sync exposing (SyncResponse, HistoryResponse, senderName, roomName, roomJoinedUsers, findFirst) import Scylla.Sync exposing (SyncResponse, HistoryResponse, senderName, roomName, roomJoinedUsers)
import Scylla.ListUtils exposing (findFirst)
import Scylla.Room exposing (OpenRooms)
import Scylla.Sync.Rooms exposing (JoinedRoom) import Scylla.Sync.Rooms exposing (JoinedRoom)
import Scylla.Sync.AccountData exposing (AccountData) import Scylla.Sync.AccountData exposing (AccountData, directMessagesDecoder)
import Scylla.AccountData exposing (directMessagesDecoder)
import Scylla.Login exposing (LoginResponse, Username, Password) import Scylla.Login exposing (LoginResponse, Username, Password)
import Scylla.UserData exposing (UserData) import Scylla.UserData exposing (UserData)
import Scylla.Route exposing (Route(..), RoomId) import Scylla.Route exposing (Route(..), RoomId)
@ -37,6 +38,7 @@ type alias Model =
, roomNames : Dict RoomId String , roomNames : Dict RoomId String
, connected : Bool , connected : Bool
, searchText : String , searchText : String
, rooms : OpenRooms
} }
type Msg = type Msg =

View File

@ -1,7 +1,6 @@
port module Scylla.Notification exposing (..) port module Scylla.Notification exposing (..)
import Scylla.Sync exposing (SyncResponse, joinedRoomsTimelineEvents) import Scylla.Sync exposing (SyncResponse, joinedRoomsTimelineEvents)
import Scylla.Sync.Events exposing (RoomEvent, MessageEvent, toMessageEvent) import Scylla.Sync.Events exposing (RoomEvent, MessageEvent, toMessageEvent)
import Scylla.AccountData exposing (..)
import Json.Decode as Decode exposing (string, field) import Json.Decode as Decode exposing (string, field)
import Dict import Dict

View File

@ -1,10 +1,12 @@
module Scylla.Room exposing (..) module Scylla.Room exposing (..)
import Scylla.Route exposing (RoomId) import Scylla.Route exposing (RoomId)
import Scylla.Sync exposing (SyncResponse) import Scylla.Sync exposing (SyncResponse)
import Scylla.Login exposing (Username)
import Scylla.UserData exposing (UserData)
import Scylla.Sync.Events exposing (MessageEvent, StateEvent, toStateEvent, toMessageEvent) import Scylla.Sync.Events exposing (MessageEvent, StateEvent, toStateEvent, toMessageEvent)
import Scylla.Sync.AccountData exposing (AccountData) import Scylla.Sync.AccountData exposing (AccountData, getDirectMessages)
import Scylla.Sync.Rooms exposing (JoinedRoom, UnreadNotificationCounts, Ephemeral) import Scylla.Sync.Rooms exposing (JoinedRoom, UnreadNotificationCounts, Ephemeral)
import Json.Decode as Decode exposing (Value) import Json.Decode as Decode exposing (Decoder, Value, decodeValue)
import Dict exposing (Dict) import Dict exposing (Dict)
type alias RoomState = Dict (String, String) Value type alias RoomState = Dict (String, String) Value
@ -108,3 +110,19 @@ applySync sr or =
|> Maybe.withDefault Dict.empty |> Maybe.withDefault Dict.empty
in in
Dict.foldl applyJoinedRoom or joinedRooms Dict.foldl applyJoinedRoom or joinedRooms
getStateData : (String, String) -> Decoder a -> RoomData -> Maybe a
getStateData k d rd = Dict.get k rd.roomState
|> Maybe.andThen (Result.toMaybe << decodeValue d)
getRoomName : Maybe AccountData -> Dict Username UserData -> RoomId -> RoomData -> String
getRoomName ad ud rid rd =
let
customName = getStateData ("m.room.name", "") Decode.string rd
direct = Maybe.andThen getDirectMessages ad
|> Maybe.andThen (Dict.get rid)
in
case (customName, direct) of
(Just cn, _) -> cn
(_, Just d) -> d
_ -> rid

View File

@ -2,6 +2,7 @@ module Scylla.Sync exposing (..)
import Scylla.Api exposing (..) import Scylla.Api exposing (..)
import Scylla.Login exposing (Username) import Scylla.Login exposing (Username)
import Scylla.Route exposing (RoomId) import Scylla.Route exposing (RoomId)
import Scylla.ListUtils exposing (..)
import Scylla.Sync.DecodeTools exposing (maybeDecode) import Scylla.Sync.DecodeTools exposing (maybeDecode)
import Scylla.Sync.Events exposing (..) import Scylla.Sync.Events exposing (..)
import Scylla.Sync.Rooms exposing (..) import Scylla.Sync.Rooms exposing (..)
@ -51,41 +52,6 @@ historyResponseDecoder =
|> required "chunk" (list roomEventDecoder) |> required "chunk" (list roomEventDecoder)
-- Business Logic: Helper Functions -- Business Logic: Helper Functions
groupBy : (a -> comparable) -> List a -> Dict comparable (List a)
groupBy f xs =
let
update v ml = case ml of
Just l -> Just (v::l)
Nothing -> Just [ v ]
in
List.foldl (\v acc -> Dict.update (f v) (update v) acc) Dict.empty xs
uniqueByTailRecursive : (a -> comparable) -> List a -> Set comparable -> List a -> List a
uniqueByTailRecursive f l s acc =
case l of
x::tail ->
if Set.member (f x) s
then uniqueByTailRecursive f tail s acc
else uniqueByTailRecursive f tail (Set.insert (f x) s) (x::acc)
[] -> List.reverse acc
uniqueBy : (a -> comparable) -> List a -> List a
uniqueBy f l = uniqueByTailRecursive f l Set.empty []
findFirst : (a -> Bool) -> List a -> Maybe a
findFirst cond l = case l of
x::xs -> if cond x then Just x else findFirst cond xs
[] -> Nothing
findLast : (a -> Bool) -> List a -> Maybe a
findLast cond l = findFirst cond <| List.reverse l
findFirstBy : (a -> comparable) -> (a -> Bool) -> List a -> Maybe a
findFirstBy sortFunction cond l = findFirst cond <| List.sortBy sortFunction l
findLastBy : (a -> comparable) -> (a -> Bool) -> List a -> Maybe a
findLastBy sortFunction cond l = findLast cond <| List.sortBy sortFunction l
findFirstEvent : ({ a | originServerTs : Int } -> Bool) -> List { a | originServerTs : Int } -> Maybe { a | originServerTs : Int } findFirstEvent : ({ a | originServerTs : Int } -> Bool) -> List { a | originServerTs : Int } -> Maybe { a | originServerTs : Int }
findFirstEvent = findFirstBy .originServerTs findFirstEvent = findFirstBy .originServerTs

View File

@ -1,7 +1,9 @@
module Scylla.Sync.AccountData exposing (..) module Scylla.Sync.AccountData exposing (..)
import Scylla.ListUtils exposing (..)
import Scylla.Sync.DecodeTools exposing (maybeDecode) import Scylla.Sync.DecodeTools exposing (maybeDecode)
import Scylla.Sync.Events exposing (Event, eventDecoder) import Scylla.Sync.Events exposing (Event, eventDecoder)
import Json.Decode as Decode exposing (Decoder, list) import Json.Decode as Decode exposing (Decoder, list, decodeValue)
import Dict exposing (Dict)
type alias AccountData = type alias AccountData =
{ events : Maybe (List Event) { events : Maybe (List Event)
@ -12,3 +14,27 @@ accountDataDecoder =
Decode.succeed AccountData Decode.succeed AccountData
|> maybeDecode "events" (list eventDecoder) |> maybeDecode "events" (list eventDecoder)
type alias DirectMessages = Dict String String
directMessagesDecoder : Decode.Decoder DirectMessages
directMessagesDecoder =
Decode.dict (Decode.list Decode.string)
|> Decode.map (invertDirectMessages)
type alias DirectMessagesRaw = Dict String (List String)
invertDirectMessages : DirectMessagesRaw -> DirectMessages
invertDirectMessages dmr =
Dict.foldl
(\k lv acc -> List.foldl (\v -> Dict.insert v k) acc lv)
Dict.empty
dmr
getAccountData : String -> Decode.Decoder a -> AccountData -> Maybe a
getAccountData key d ad = ad.events
|> Maybe.andThen (findFirst ((==) key << .type_))
|> Maybe.map .content
|> Maybe.andThen (Result.toMaybe << decodeValue d)
getDirectMessages : AccountData -> Maybe DirectMessages
getDirectMessages = getAccountData "m.direct" directMessagesDecoder

View File

@ -3,6 +3,7 @@ import Scylla.Model exposing (..)
import Scylla.Sync exposing (..) import Scylla.Sync exposing (..)
import Scylla.Sync.Events exposing (..) import Scylla.Sync.Events exposing (..)
import Scylla.Sync.Rooms exposing (..) import Scylla.Sync.Rooms exposing (..)
import Scylla.Room exposing (RoomData, emptyOpenRooms, getRoomName)
import Scylla.Route exposing (..) import Scylla.Route exposing (..)
import Scylla.Fnv as Fnv import Scylla.Fnv as Fnv
import Scylla.Messages exposing (..) import Scylla.Messages exposing (..)
@ -10,6 +11,7 @@ import Scylla.Login exposing (Username)
import Scylla.UserData exposing (UserData) import Scylla.UserData exposing (UserData)
import Scylla.Http exposing (fullMediaUrl) import Scylla.Http exposing (fullMediaUrl)
import Scylla.Api exposing (ApiUrl) import Scylla.Api exposing (ApiUrl)
import Scylla.ListUtils exposing (groupBy)
import Html.Parser import Html.Parser
import Html.Parser.Util import Html.Parser.Util
import Svg import Svg
@ -85,11 +87,8 @@ reconnectView m = if m.connected
roomListView : Model -> Html Msg roomListView : Model -> Html Msg
roomListView m = roomListView m =
let let
rooms = Maybe.withDefault (Dict.empty)
<| Maybe.andThen .join
<| m.sync.rooms
groups = roomGroups groups = roomGroups
<| Dict.toList rooms <| Dict.toList m.rooms
homeserverList = div [ class "homeservers-list" ] homeserverList = div [ class "homeservers-list" ]
<| List.map (\(k, v) -> homeserverView m k v) <| List.map (\(k, v) -> homeserverView m k v)
<| Dict.toList groups <| Dict.toList groups
@ -106,22 +105,22 @@ roomListView m =
, homeserverList , homeserverList
] ]
roomGroups : List (String, JoinedRoom) -> Dict String (List (String, JoinedRoom)) roomGroups : List (String, RoomData) -> Dict String (List (String, RoomData))
roomGroups jrs = groupBy (homeserver << Tuple.first) jrs roomGroups jrs = groupBy (homeserver << Tuple.first) jrs
homeserverView : Model -> String -> List (String, JoinedRoom) -> Html Msg homeserverView : Model -> String -> List (String, RoomData) -> Html Msg
homeserverView m hs rs = homeserverView m hs rs =
let let
roomList = div [ class "rooms-list" ] roomList = div [ class "rooms-list" ]
<| List.map (\(rid, r) -> roomListElementView m rid r) <| List.map (\(rid, r) -> roomListElementView m rid r)
<| List.sortBy (\(rid, r) -> roomDisplayName m.roomNames rid) rs <| List.sortBy (\(rid, r) -> getRoomName m.sync.accountData m.userData rid r) rs
in in
div [ class "homeserver-wrapper" ] [ h3 [] [ text hs ], roomList ] div [ class "homeserver-wrapper" ] [ h3 [] [ text hs ], roomList ]
roomListElementView : Model -> RoomId -> JoinedRoom -> Html Msg roomListElementView : Model -> RoomId -> RoomData -> Html Msg
roomListElementView m rid jr = roomListElementView m rid rd =
let let
name = roomDisplayName m.roomNames rid name = getRoomName m.sync.accountData m.userData rid rd
isVisible = m.searchText == "" || (String.contains (String.toLower m.searchText) <| String.toLower name) isVisible = m.searchText == "" || (String.contains (String.toLower m.searchText) <| String.toLower name)
isCurrentRoom = case currentRoomId m of isCurrentRoom = case currentRoomId m of
Nothing -> False Nothing -> False
@ -133,10 +132,10 @@ roomListElementView m rid jr =
, ("hidden", not isVisible) , ("hidden", not isVisible)
] ]
] ]
<| roomNotificationCountView jr.unreadNotifications ++ <| roomNotificationCountView rd.unreadNotifications ++
[ a [ href <| roomUrl rid ] [ text name ] ] [ a [ href <| roomUrl rid ] [ text name ] ]
roomNotificationCountView : Maybe UnreadNotificationCounts -> List (Html Msg) roomNotificationCountView : UnreadNotificationCounts -> List (Html Msg)
roomNotificationCountView ns = roomNotificationCountView ns =
let let
wrap b = span wrap b = span
@ -145,7 +144,7 @@ roomNotificationCountView ns =
, ("bright", b) , ("bright", b)
] ]
] ]
getCount f = Maybe.withDefault 0 << Maybe.andThen f getCount f = Maybe.withDefault 0 << f
in in
case (getCount .notificationCount ns, getCount .highlightCount ns) of case (getCount .notificationCount ns, getCount .highlightCount ns) of
(0, 0) -> [] (0, 0) -> []