-
Notifications
You must be signed in to change notification settings - Fork 4
/
main.cljc
99 lines (85 loc) · 4.38 KB
/
main.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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
(ns reacl-c.main
"Functions for using reacl-c within a browser application."
(:require [reacl-c.core :as core]
[reacl-c.base :as base]
[active.clojure.lens :as lens]
[active.clojure.functions :as f]
#?(:cljs [reacl-c.impl.react :as impl])))
(let [h (fn [recursion-limit monitor _ eff]
(loop [effs (list eff)
res (core/return)
n 0]
(cond
(empty? effs) res
;; bail out if an effects keeps on returning new effects over and over.
(> n recursion-limit) (throw (ex-info "Maximum recursion limit exceeded in handling effects." {}))
:else
(let [[res ret] (base/run-effect! eff)
{more-effs true more-acts false} (group-by base/effect? (base/returned-actions ret))]
(when monitor (monitor eff res ret))
(recur (concat (rest effs) more-effs)
(base/merge-returned ret (base/make-returned base/keep-state more-acts []))
(inc n))))))]
(defn execute-effects
"Returns an item that will intercept and execute all effects
emitted by the given `item`. This is automatically wrapped around
the toplevel item when using [[run]], but not when
using [[run-controlled]].
Options can be a map with the following settings:
- `:recursion-limit` When effects continue to return new effects, and exception is when thrown when this limit is reached (defaults to 1000),
- `:monitor` A function called with the effect, its result value and a [[reacl-c.core/return]] value of actions and messages emitted by that effect."
[item & [options]]
(core/handle-effect item (f/partial h
(or (:recursion-limit options) 1000)
(:monitor options)))))
(defn ^:no-doc state-error [st]
;; TODO: no callback arg?
(throw (ex-info (str "Unhandled state change at toplevel: " (pr-str st) ".") {:state st})))
(defn ^:no-doc action-error [a] ;; TODO: no callback arg?
(throw (ex-info (str "Unhandled action at toplevel: " (pr-str a) ".") {:action a})))
#?(:cljs
(defn run-controlled
"Runs the given item as an application underneath the given
native `dom` node. Options are:
`:state`: specifying the state of the item, which defaults to nil.
`set-state!`: a function that should handle a state change of the
item; if not specified, and the item wants to change its state, an
error is thrown.
`handle-action!`: a function called when the item emits an
action (including effects); if not specified, and the item does emit
an action, an error is thrown."
[dom item & [options]]
(assert (every? #{:state :set-state! :handle-action!} (keys options)))
(let [{state :state set-state! :set-state! handle-action! :handle-action!} options]
(impl/run dom
item
state
(or set-state! state-error)
(or handle-action! action-error)))))
#?(:cljs
(defn run
"Runs the given item as an application underneath the given
native `dom` node, automatically managing its state and executing effect actions.
Options are:
`:initial-state`: specifying the initial state of the item, which defaults to nil.
`:handle-action!`: a function called when the item emits an
action (excluding effects); if not specified, and the item does emit
an action, an error is thrown."
[dom item & [options]]
(assert (every? #{:handle-action! :initial-state} (keys options)))
(let [{initial-state :initial-state} options]
(run-controlled dom
(-> (core/local-state initial-state (core/focus lens/second item))
;; should be 'toplevel':
(execute-effects))
;; Note: the item's state is fully local; so toplevel state will never change
(dissoc options :initial-state)))))
(defn send-message!
"Sends a message to a running item, i.e. `app` must be the value
returned from [[run]] or [[run-controlled]]. This can be used together
with [[reacl-c.core/handle-message]] in situations where the
application is not running standalone, but integrated in a different
framework. The optional callback is invoked when any update
triggered by the message is completed."
[app msg & [callback]]
(base/-send-message! app msg callback))