Make it easier to create complex state machines from LocalStorage contents.
This is too complicated for an example here, but see ZapMeme.Sequencer for a real-life example.
This does not support simulated LocalStorage. Certainly possible, but it was already plenty complicated without that.
Since an instance of LocalStorageStates is stored in your Model, and it contains two functions which take a model as argument, there needs to be a custom class to prevent infinite type recursion:
type WrappedModel =
WrappedModel Model
type alias Model =
{ localStorageStates : LocalStorageStates WrappedModel Msg
, ...
}
type Msg =
SequenceDone (WrappedModel -> ( WrappedModel, Cmd Msg ))
| ProcessLocalStorage Value
...Opaque type used to store a table of state machines.
Create one with makeLocalStorageStates. Store it in your Model.
Holds the state, label, and LocalStorage subscription port processing function for one state machine.
You will get very confused if you use the same label for two different State values passed to makeLocalStorageStates.
See the definition of initialStorageStates in ZapMeme.Sequencer.
These are converted to and from the strings sent as LocalStorage keys by encodePair and decodePair.
A high-level representation of a LocalStorage operation.
DbNothing does nothing.
DbCustomRequest allows you to send some command other than a message out the LocalStorage cmdPort.
DbBatch allows you to gang more than one request.
The others have obvious correlation to the LocalStorage messages, but DbPut and DbRemove are decoupled, instead of using a Maybe Value.
A high-level represenation of a message returned from LocalStorage through the subscription port.
Since this module doesn't support simulated storage, only DbGot in response to DbGet and DbKeys in response to DbListKeys need to be distinguished.
The other messages are all turned into DbNoResponse, but there wont be any in a non-simulated environment.
Create a LocalStorageStates to store in your Model.
The key type is usually a simple enumerator custom type. You could use strings, but then the compiler won't catch typos for you.
The state type is usually a custom type with one tag per key value.
the model here is usually WrappedModel in your code, to prevent type recursion through wrappers.localStorageStates and wrappers.setLocalStorageStates.
This is how you create a NullState for your Wrappers.
The state you pass will never be used for anything.
You will usually use only update, but the others let you do lower-level processing yourself.
Modulo wrapping and unwrapping, designed to be the entire handler for incoming LocalStorage responses.
funnelDict : FunnelDict Model Msg
funnelDict =
PortFunnels.makeFunnelDict
[ LocalStorageHandler wrappedStorageHandler ]
getCmdPort
wrappedStorageHandler : LocalStorage.Response -> PortFunnels.State -> Model -> ( Model, Cmd Msg )
wrappedStorageHandler response _ model =
let
( WrappedModel mdl, cmd ) =
Sequence.storageHandler
sequenceWrappers
(WrappedModel model)
response
in
( mdl, cmd )Calls process on each of the States registered in the LocalStorageStates.
Accumulates the results, but in practice, only one of them will do anything other than note that the LocalStorage.Response's label doesn't match, and return the model unchanged with Cmd.none.
You will usually call this via update, not directly.
Process a response for a single State.
If the response's label matches the State's, calls state.process, updates the user state inside the State, and turns the returned DbRequest into a Cmd msg (usually by calling send). Otherwise, returns Nothing.
You will usually call this indirectly, via update.
Turn a DbRequest into a Cmd msg
that sends a LocalStorage.Message out to the JavaScript through the Cmd port.
Uses the sender in Wrappers and label in State.
Call injectTask, and use Task.perform to turn that Task into a Cmd.
The (Value -> msg) function will usually be the Msg that receives subscription input from your LocalStorage port.
If you receive something outside of a LocalStorage return, and want to get it back into your state machine, use this.
It returns a Task that, if you send it with your LocalStorage sub port message, will make it seem as if the given DbResponse was received from LocalStorage.
It's a trivial task wrapper on the result of dbResponseToValue, using label property of the State.
Look up the State associated with key in model's LocalStorageStates
(with getFullState). If found, return itsstate field.
Look up the State associated with key in model's LocalStorageStates
Update one State inside the LocalStorageStates inside of the model.
Returns the updated model and State. This version is usually used when you start up a state machine, since you need the State to call send.
Use setStateOnly if you need only the updated model.
Update one State inside the LocalStorageStates inside of the model.
Return the updated model.
Use setState if you also need the updated State (or call getFullState).
Eliminate error checking boilerplate inside State process functions.
decodeExpectedDbGot decoder expectedSubkey responseIf response is not a DbGot, returns Nothing.
If expectedSubkey is not "", and also not the subkey of that DbGot's KeyPair, returns Just (KeyPair, Nothing).
If the DbGot's value is Nothing, returns Just (KeyPair, Nothing).
Otherwise, uses decoder to decode that value. If the result is an Err, returns Just (KeyPair, Nothing).
If all goes well, returns Just (KeyPair, value).
Turn a DbResponse into the Value that would create it,
if received from the LocalStorage port code, in response to a DbGet or DbListKeys request.
NoResponse is treated as DbGot with empty string components in its KeyPair and Nothing as its value. You will likely never want to do that.
Turn a string that comes back from the subscription port into a KeyPair.
Splits it on the first ".".
Turn a KeyPair into a string.
That string is used as the Key arg to LocalStorage.getLabeled or the Prefix arg to LocalStorage.listKeysLabeled.
Communication between
Sequenceand your mainModelandMsg.senderis how you send messages out your command port. It's usually defined in your main program, with something like:localStorageStatesandsetLocalStorageStatesare functions that read/write aLocalStorageStatesfrom/to yourModel.sequenceDonecreates aMsgthat calls its arg with the Model, and expects back the standardupdateresult. E.g.:Make a
NullStatewithmakeNullState.