/
core.cljc
78 lines (72 loc) · 3.58 KB
/
core.cljc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
(ns locket.core
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[re-frame.loggers :as loggers]
[re-frame.registrar :as registrar]
[re-frame.events :as events]
[re-frame.cofx :as cofx]
[re-frame.fx :as fx]))
(defn states
"Returns all the states from a state machine"
[state-machine]
(let [{:keys [transitions]} state-machine]
(into #{} (map first transitions))))
(defn transitions
"Returns all the transition events in a state machine.
If a state is provided as a second argument, returns all the transitions from that state"
([state-machine]
(let [{:keys [transitions]} state-machine]
(into #{} (comp (map second) cat (map first)) transitions)))
([state-machine state]
(let [{:keys [transitions]} state-machine]
(into #{} (map first) (get transitions state)))))
(defn interceptor
"Generic interceptor for state machine interactions.
Updates the state of the db in the given path"
[state-machine]
(let [{:keys [path debug? on-invalid-transition]} state-machine]
(re-frame/->interceptor
:id path
:before (fn [context]
(let [{:keys [db event]} (get context :coeffects)
[ev & args] event
current-state (get-in db path)
new-state (or (get-in state-machine [:transitions current-state ev])
(when (= :warn on-invalid-transition)
(loggers/console :warn (let [expected (transitions state-machine current-state)]
(str "No transition found for event " ev " in state " current-state
(cond
(= (count expected) 1) (str "\nExpected:\n" (first expected))
(= (count expected) 0) (str "\nExpected one of:\n" (string/join "\n" expected)))))))
current-state)]
(when debug?
(loggers/console :log (str "\n" path " " current-state "\n------\n" ev " -> " new-state)))
(assoc-in context [:coeffects :db] (assoc-in db path new-state))))
:after (fn [context]
(if (and (get-in context [:coeffects :db])
(not (get-in context [:effects :db])))
(assoc-in context [:effects :db] (get-in context [:coeffects :db]))
context)))))
(defn add-handlers
"Adds all the state machine handlers for transitions"
[state-machine]
(let [{:keys [path]} state-machine
evs (transitions state-machine)
interceptor (interceptor state-machine)]
(doseq [ev evs
:let [old-interceptors (registrar/get-handler :event ev)]]
(if old-interceptors
;; insert the state machine transition before the handler
(let [new-interceptors (concat (butlast old-interceptors)
[interceptor (last old-interceptors)])]
(re-frame/clear-event ev)
(events/register ev new-interceptors))
(events/register ev [cofx/inject-db fx/do-fx interceptor])))))
(re-frame/reg-fx :locket/add-handlers add-handlers)
(re-frame/reg-event-fx
:locket/install-state-machine
(fn [cofx [event state-machine]]
(let [{:keys [db]} cofx
{:keys [path initial-state]} state-machine]
{:locket/add-handlers state-machine
:db (assoc-in db path initial-state)})))