forked from day8/re-frame
-
Notifications
You must be signed in to change notification settings - Fork 1
/
frank.cljs
185 lines (151 loc) · 7.02 KB
/
frank.cljs
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
(ns re-frame.frank
(:require [re-frame.utils]
[re-frame.interop :as interop]
[re-frame.router :as router]
[re-frame.registrar :as registrar]
[re-frame.events :as events]
[re-frame.interceptor :as interceptor]
[re-frame.loggers :as loggers]
[re-frame.trace :as trace]))
;; Adapted from re-frame.registrar/get-handler to take the handler registry
;; value as parameter
(defprotocol Frankenstein
(dispatch! [this event-v])
(dispatch-sync! [this event-v]))
;; Inspired by scrum reconciler and re-frame internals
(deftype Frank [registry-atom event-queue state-atom]
Object
(equiv [this other]
(-equiv this other))
IAtom
IMeta
(-meta [_] meta)
IEquiv
(-equiv [this other]
(identical? this other))
IDeref
(-deref [_]
(-deref state-atom))
IWatchable
(-add-watch [this key callback]
(add-watch state-atom (list this key)
(fn [_ _ oldv newv]
(when (not= oldv newv)
(callback key this oldv newv))))
this)
(-remove-watch [this key]
(remove-watch state-atom (list this key))
this)
IHash
(-hash [this] (goog/getUid this))
IPrintWithWriter
(-pr-writer [this writer opts]
(-write writer "#object [re-frame.frank.Frank ")
(pr-writer {:val (-deref this)} writer opts)
(-write writer "]"))
Frankenstein
(dispatch! [this event-v]
(if (nil? event-v)
(throw (ex-info "re-frankenstein: you called \"dispatch!\" without an event vector." {})))
(router/push event-queue event-v)
nil) ;; Ensure nil return. See https://github.com/Day8/re-frame/wiki/Beware-Returning-False
(dispatch-sync! [this event-v]
(events/handle-event-using-registry @registry-atom event-v)
;; FIXME when we can use an EventQueue
;; No post-event-callbacks for now
#_(-call-post-event-callbacks event-queue event-v) ;; slightly ugly hack. Run the registered post event callbacks.
nil)) ;; Ensure nil return
;; TODO as this is used somewhere else, maybe create a `utils` namespace?
(defn- map-vals
"Returns a new version of 'm' in which 'f' has been applied to each value.
(map-vals inc {:a 4, :b 2}) => {:a 5, :b 3}"
[f m]
(into (empty m)
(map (fn [[k v]] [k (f v)]))
m))
(defn swap-stateful-interceptors!
"Modifies the registry value by replacing the handlers that refer refer to the
global app-db with very similar handlers that refer to the local-db provided
as argument. Then swap registered stateful interceptors to use new local ones"
[registry-atom local-db frank]
(let [local-db-coeffect-handler
(fn local-db-coeffect-handler [coeffects]
(assoc coeffects :db @local-db))
new-cofx-db-interceptor
(interceptor/->interceptor
:id :coeffects/frank-db
:before (fn coeffects-before [context]
(update context
:coeffects
local-db-coeffect-handler)))
new-do-fx-interceptor
(interceptor/->interceptor
:id :frank/do-fx
:after (fn do-fx-after [context]
(doseq [[effect-k value] (:effects context)]
(if-let [effect-fn
(registrar/get-handler-from-registry-atom registry-atom
:fx
effect-k
true)]
(effect-fn value
{:dispatch! #(dispatch! frank %)
:dispatch-sync! #(dispatch-sync! frank %)})))))]
(reset! registry-atom
(-> @registry-atom
(registrar/register-handler-into-registry :cofx
:db
local-db-coeffect-handler)
(registrar/register-handler-into-registry :fx
:db
(fn [value] (reset! local-db value)))
(registrar/register-handler-into-registry
:fx :dispatch-later
(fn [value {local-dispatch :dispatch!}]
(doseq [{:keys [ms dispatch] :as effect} value]
(if (or (empty? dispatch) (not (number? ms)))
(loggers/console :error "re-frame: ignoring bad :dispatch-later value:" effect)
(interop/set-timeout! #(local-dispatch dispatch) ms)))))
(registrar/register-handler-into-registry
:fx :dispatch
(fn [value {local-dispatch :dispatch!}]
(if-not (vector? value)
(loggers/console :error "re-frame: ignoring bad :dispatch value. Expected a vector, but got:" value)
(local-dispatch value))))
(registrar/register-handler-into-registry
:fx :dispatch-n
(fn [value {local-dispatch :dispatch!}]
(if-not (sequential? value)
(loggers/console :error "re-frame: ignoring bad :dispatch-n value. Expected a collection, got got:" value)
(doseq [event value] (local-dispatch event)))))
(registrar/register-handler-into-registry
:fx :deregister-event-handler
(fn [value]
(let [clear-event (partial registrar/clear-handlers-from-registry-atom registry-atom :fx)]
(doseq [event (if (sequential? value) value [value])]
(clear-event event)))))
(update :event
(fn [event-handlers-by-id]
(map-vals (fn [interceptors]
(map (fn [interceptor]
(case (:id interceptor)
:coeffects/db new-cofx-db-interceptor
:do-fx new-do-fx-interceptor
interceptor))
interceptors))
event-handlers-by-id)))))))
(defn create
([] (create (atom {})))
([starting-atom]
(let [local-db starting-atom
local-registry-atom (atom @registrar/kind->id->handler)
frank (->Frank local-registry-atom
(router/->EventQueue local-registry-atom
:idle ;; Initial queue state
#queue [] ;; Internal storage for actions
{}) ;; Function to be called after every action
local-db)]
(swap-stateful-interceptors! local-registry-atom
local-db
frank)
frank)))