309 lines
7.8 KiB
Elm
309 lines
7.8 KiB
Elm
module Internal.Values.StateManager exposing
|
|
( StateManager
|
|
, empty, singleton, insert, insertIfNotExists, remove, append
|
|
, isEmpty, member, memberKey, get, size, isEqual
|
|
, keys, values, fromList, toList
|
|
, coder, encode, decoder
|
|
)
|
|
|
|
{-| The StateManager tracks the room state based on events, their event types
|
|
and the optional state keys they provide. Instead of making the user loop
|
|
through the room's timeline of events, the StateManager offers the user a
|
|
dictionary-like experience to navigate through the Matrix room state.
|
|
|
|
|
|
## Dictionaries
|
|
|
|
@docs StateManager
|
|
|
|
|
|
## Build
|
|
|
|
@docs empty, singleton, insert, insertIfNotExists, remove, append
|
|
|
|
|
|
## Query
|
|
|
|
@docs isEmpty, member, memberKey, get, size, isEqual
|
|
|
|
|
|
## Lists
|
|
|
|
@docs keys, values, fromList, toList
|
|
|
|
|
|
## JSON coders
|
|
|
|
@docs coder, encode, decoder
|
|
|
|
-}
|
|
|
|
import FastDict as Dict exposing (Dict)
|
|
import Internal.Config.Text as Text
|
|
import Internal.Tools.Json as Json
|
|
import Internal.Tools.Mashdict as Mashdict exposing (Mashdict)
|
|
import Internal.Values.Event as Event exposing (Event)
|
|
|
|
|
|
{-| The StateManager manages the room state by gathering events and looking at
|
|
their details.
|
|
-}
|
|
type StateManager
|
|
= StateManager (Dict String (Mashdict Event))
|
|
|
|
|
|
{-| Add a new statemanager on top of an existing StateManager. This can be
|
|
useful when trying to calculate a room state based on two already existing
|
|
types.
|
|
-}
|
|
append : StateManager -> StateManager -> StateManager
|
|
append sm2 sm1 =
|
|
List.foldl insert sm1 (values sm2)
|
|
|
|
|
|
|
|
-- {-| Remove any floating empty Mashdicts from ALL keys in the dictionary.
|
|
-- -}
|
|
-- cleanAll : StateManager -> StateManager
|
|
-- cleanAll ((StateManager manager) as sm) =
|
|
-- List.foldl cleanKey sm (Dict.keys manager)
|
|
|
|
|
|
{-| To keep the StateManager as simple as possible, you can keep the dictionary
|
|
clean by removing any floating empty Mashdicts in the dictionary.
|
|
|
|
To save time, this function exclusively removes an empty Mashdict at a given
|
|
key. This way, you don't need to run a complete clean of a large dictionary
|
|
every time you just edit a single key in the dictionary.
|
|
|
|
-}
|
|
cleanKey : String -> StateManager -> StateManager
|
|
cleanKey key (StateManager manager) =
|
|
manager
|
|
|> Dict.update key
|
|
(Maybe.andThen
|
|
(\dict ->
|
|
if Mashdict.isEmpty dict then
|
|
Nothing
|
|
|
|
else
|
|
Just dict
|
|
)
|
|
)
|
|
|> StateManager
|
|
|
|
|
|
{-| Define how a StateManager can be encoded to and decoded from a JSON object.
|
|
-}
|
|
coder : Json.Coder StateManager
|
|
coder =
|
|
Event.coder
|
|
|> Mashdict.coder .stateKey
|
|
|> Json.fastDict
|
|
|> Json.map
|
|
{ name = Text.docs.stateManager.name
|
|
, description = Text.docs.stateManager.description
|
|
, forth = StateManager
|
|
, back = \(StateManager manager) -> manager
|
|
}
|
|
|
|
|
|
{-| Decode a StateManager from a JSON value.
|
|
-}
|
|
decoder : Json.Decoder StateManager
|
|
decoder =
|
|
Json.decode coder
|
|
|
|
|
|
{-| Create an empty StateManager.
|
|
-}
|
|
empty : StateManager
|
|
empty =
|
|
StateManager Dict.empty
|
|
|
|
|
|
{-| Encode a StateManager into a JSON value.
|
|
-}
|
|
encode : Json.Encoder StateManager
|
|
encode =
|
|
Json.encode coder
|
|
|
|
|
|
{-| Build a StateManager using a list of events.
|
|
-}
|
|
fromList : List Event -> StateManager
|
|
fromList events =
|
|
List.foldl insert empty events
|
|
|
|
|
|
{-| Get an event based on its event type and state key. If there is no such
|
|
event sent in the room, the function returns `Nothing`.
|
|
-}
|
|
get : { eventType : String, stateKey : String } -> StateManager -> Maybe Event
|
|
get { eventType, stateKey } (StateManager manager) =
|
|
manager
|
|
|> Dict.get eventType
|
|
|> Maybe.andThen (Mashdict.get stateKey)
|
|
|
|
|
|
{-| Insert a new event into the state manager. If the event does not have a
|
|
state key, it is overlooked.
|
|
-}
|
|
insert : Event -> StateManager -> StateManager
|
|
insert event (StateManager manager) =
|
|
manager
|
|
|> Dict.update
|
|
event.eventType
|
|
(\typeDict ->
|
|
case typeDict of
|
|
Nothing ->
|
|
Just <| Mashdict.singleton .stateKey event
|
|
|
|
Just md ->
|
|
Just <| Mashdict.insert event md
|
|
)
|
|
|> StateManager
|
|
|> cleanKey event.eventType
|
|
|
|
|
|
{-| Insert a new event into the state manager ONLY if no such event has already
|
|
been defined.
|
|
|
|
This function is most useful for including older state events that may have been
|
|
overwritten in the future.
|
|
|
|
-}
|
|
insertIfNotExists : Event -> StateManager -> StateManager
|
|
insertIfNotExists event sm =
|
|
case event.stateKey of
|
|
Nothing ->
|
|
sm
|
|
|
|
Just s ->
|
|
case get { eventType = event.eventType, stateKey = s } sm of
|
|
Just _ ->
|
|
sm
|
|
|
|
Nothing ->
|
|
insert event sm
|
|
|
|
|
|
{-| Determine whether the StateManager contains any events.
|
|
-}
|
|
isEmpty : StateManager -> Bool
|
|
isEmpty (StateManager manager) =
|
|
Dict.isEmpty manager
|
|
|
|
|
|
{-| Since the StateManager's internal structure prevents Elm from making (==)
|
|
comparisons, the `isEqual` function allows you to make comparisons that ignore
|
|
the incomparable function.
|
|
-}
|
|
isEqual : StateManager -> StateManager -> Bool
|
|
isEqual (StateManager sm1) (StateManager sm2) =
|
|
if Dict.size sm1 /= Dict.size sm2 then
|
|
False
|
|
|
|
else if Dict.keys sm1 /= Dict.keys sm2 then
|
|
False
|
|
|
|
else
|
|
List.all
|
|
(\key ->
|
|
case ( Dict.get key sm1, Dict.get key sm2 ) of
|
|
( Just s1, Just s2 ) ->
|
|
Mashdict.isEqual s1 s2
|
|
|
|
( _, _ ) ->
|
|
False
|
|
)
|
|
(Dict.keys sm1)
|
|
|
|
|
|
{-| Retrieve all keys from a StateManager.
|
|
-}
|
|
keys : StateManager -> List { eventType : String, stateKey : String }
|
|
keys (StateManager manager) =
|
|
manager
|
|
|> Dict.toList
|
|
|> List.map
|
|
(\( eventType, dict ) ->
|
|
dict
|
|
|> Mashdict.keys
|
|
|> List.map
|
|
(\stateKey ->
|
|
{ eventType = eventType, stateKey = stateKey }
|
|
)
|
|
)
|
|
|> List.concat
|
|
|
|
|
|
{-| Determine whether an event is part of the StateManager.
|
|
-}
|
|
member : Event -> StateManager -> Bool
|
|
member event (StateManager manager) =
|
|
case Dict.get event.eventType manager of
|
|
Just dict ->
|
|
Mashdict.member event dict
|
|
|
|
Nothing ->
|
|
False
|
|
|
|
|
|
{-| Determine whether a given key is part of the StateManager.
|
|
-}
|
|
memberKey : { eventType : String, stateKey : String } -> StateManager -> Bool
|
|
memberKey { eventType, stateKey } (StateManager manager) =
|
|
case Dict.get eventType manager of
|
|
Just dict ->
|
|
Mashdict.memberKey stateKey dict
|
|
|
|
Nothing ->
|
|
False
|
|
|
|
|
|
{-| Get a StateManager without a given event in it. If the StateManager already
|
|
doesn't have the event, nothing changes.
|
|
-}
|
|
remove : Event -> StateManager -> StateManager
|
|
remove event (StateManager manager) =
|
|
manager
|
|
|> Dict.update event.eventType (Maybe.map (Mashdict.remove event))
|
|
|> StateManager
|
|
|> cleanKey event.eventType
|
|
|
|
|
|
{-| Create a StateManager that contains a single event.
|
|
-}
|
|
singleton : Event -> StateManager
|
|
singleton event =
|
|
insert event empty
|
|
|
|
|
|
{-| Determine the StateManager's size by the amount of events.
|
|
-}
|
|
size : StateManager -> Int
|
|
size (StateManager manager) =
|
|
manager
|
|
|> Dict.values
|
|
|> List.map Mashdict.size
|
|
|> List.sum
|
|
|
|
|
|
{-| Transform the StateManager to a list of events.
|
|
-}
|
|
toList : StateManager -> List Event
|
|
toList =
|
|
values
|
|
|
|
|
|
{-| Get the values from the StateManager, ordered by their event type (and by
|
|
their state key, if multiple events are of the same event type).
|
|
-}
|
|
values : StateManager -> List Event
|
|
values (StateManager manager) =
|
|
manager
|
|
|> Dict.values
|
|
|> List.map Mashdict.values
|
|
|> List.concat
|