Skip to content

Instantly share code, notes, and snippets.

@jaidetree
Created September 13, 2021 20:20
Show Gist options
  • Save jaidetree/5e5b26b474feace48d94b714625056b2 to your computer and use it in GitHub Desktop.
Save jaidetree/5e5b26b474feace48d94b714625056b2 to your computer and use it in GitHub Desktop.
First draft of feature combining baconjs stream app state library with a lightweight state machine system
(ns montage.features.slideshow
(:require
[framework.fsm :as fsm]
[framework.features :refer [register]]
[framework.reactor :refer [of-type compose-fx]]
[frmaework.stream :as stream]))
(defn transition-delay
[machine]
(/ (get-in machine [:context :transition-delay]) 2))
(def states
{:idle
{:slideshow/transition
(fn [machine action]
{:state :prepare
:context {:transition-delay (get-in action [:payload :transition-delay])
:from (get-in action [:payload :from])
:to (get-in action [:payload :to])}
:effect {:type :slideshow/wait
:payload {:delay 0
:action {:type :slideshow/begin
:payload nil}}}})}
:prepare
{:slideshow/begin
(fn [machine _action]
{:state :transition-out
:context (:context machine)
:effect {:type :slideshow/wait
:payload {:delay (transition-delay machine)
:action {:type :slideshow/transition-out-complete
:payload nil}}}})}
:transition-out
{:slideshow/transitioned-out-complete
(fn [machine _action]
{:state :transition-in
:context (:context machine)
:effect {:type :slideshow/wait
:payload {:delay (transition-delay machine)
:action {:type :slideshow/transition-in-complete
:payload nil}}}})}
:transition-in
{:slideshow/transition-in-complete
(fn [machine _action]
{:state :idle
:context (merge (:context machine)
{:from nil
:to nil})
:effect nil})}})
(def slideshow
(fsm/create :create/initial
states
{:state :idle
:context {}
:effect nil}))
(def reducer (:reducer slideshow))
(defn wait-fx
[actions _deps]
(-> actions
(of-type :slideshow/wait)
(.flatMap (fn [action]
(stream/later (get-in action [:payload :delay])
(get-in action [:payload :action]))))))
(def fx (compose-fx
(:fx slideshow)
wait-fx))
(register :slideshow {:reducer reducer
:fx fx})
@jaidetree
Copy link
Author

Basically we're creating a state machine that acts as a general app state reducer and an fx handler to process the bacon stream of actions for general {:type :slideshow/wait :payload {:delay number :action {:type action-type :payload action-payload}}}, wait the specified delay, then emit the payload action. This supports waiting for a CSS transition to conclude.

The states data maps the slideshow states the can be in, to actions expected to be dispatched to functions to return the new state machine state which includes the current state key, context data, and an effect action that will be immediately emitted downstream to trigger any subscriptions to the actions bacon stream.

Feature wise:

It expects a {:type :slideshow/transition {:payload {:transition-delay 1000 :from 0 :to 1}}} to kick the transition animation off. The machine will be in the :prepare state and emits an effect to wait 0ms before emitting a :slideshow/begin action. This delay is used to render the target slides to the DOM with their initial CSS state.

After 0ms {:type :transition/begin :payload nil} is emitted, this transitions the machine to the :transition-out state and is intended to represent the duration of time the currently displayed slide is fading out. It uses the delay obtained from the :slideshow/transition, stored in the machine context, to schedule another delayed action that emits :slideshow/transition-out-complete.

The {:type :slideshow/transition-out-complete :payload nil} action implies that the current slide is no longer visible as the fade-out animation completed. This event transitions the machine to the :transition-in state which implies the next slide is fading in over time. It will emit a :slideshow/transition-in-complete action after another delay from the context :transition-delay value.

When {:type :slideshow/transition-in-complete :payload nil} is emitted, it means the next image in the slide show has faded in and is now the current slide. The machine then updates back to the :idle state ready for the next transition. This time no effect is emitted.

Another feature is expected to listen for :slideshow/transition-in-complete to update the app state to update the currently selected slide index. If the :transition-in-complete does not have enough data, the other machine can subscribe to the general :fsm/transition action, test against the name of the state machine, then grab the target index from the previous's state's context.

With this architecture, the feature that controls the cycling through slides, and the timing of transitions can be created additively without this feature needing to know about the others as long as a feature emits the {:type :slideshow/transition {:payload {...} } event, it should transition.

With the current setup of the statemachine defined in states it will ignore any incoming transitions while a transition is in progress.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment