-
Notifications
You must be signed in to change notification settings - Fork 33
/
diff.clj
164 lines (135 loc) · 5.27 KB
/
diff.clj
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
(ns fn-fx.diff
(:require [fn-fx.set-once-type :refer [defquasitype set-once!]]
[fn-fx.util :as util]))
(declare diff)
(defprotocol IDom
(create-component! [this type])
(set-child! [this parent id child])
(set-indexed-child! [this parent k idx child])
(delete-indexed-child! [this parent k idx child])
(replace-indexed-child! [this parent k idx child])
(set-property! [this node property value]))
(defquasitype Component [type dom-node props])
(defquasitype UserComponent [type props render-fn render-result])
(defprotocol IUserComponent
(render [this props])
(should-update? [this old-props new-props]))
(defrecord Created [node])
(defrecord Updated [node])
(defrecord NewValue [value])
(defrecord Deleted [node])
(defrecord Noop [node])
(defn render-user-component [{:keys [props render-result] :as comp}]
(when (not render-result)
(set-once! comp :render-result (render comp props)))
(:render-result comp))
(defn val-type [a]
(cond
(nil? a) :nil
(instance? Component a) :comp
(satisfies? IUserComponent a) :ucomp
:else :val))
(defn needs-update? [from to]
(let [{:keys [props]} to]
(if (and (= (:type from) (:type to))
(should-update? to (:props from) props))
(do (set-once! to :render-result nil)
true)
(do (set-once! to :render-result (:render-result from))
false))))
(defn diff-child-list [dom parent-node k a-list b-list]
(dotimes [idx (max (count a-list) (count b-list))]
(let [a (nth a-list idx nil)
b (nth b-list idx nil)]
(let [{:keys [node] :as result} (diff dom a b)]
(condp instance? result
;; TODO: Unmount?
Created (set-indexed-child! dom parent-node k idx node)
Deleted (delete-indexed-child! dom parent-node k idx node)
Updated (replace-indexed-child! dom parent-node k idx node)
Noop nil)))))
(defn diff-component [dom dom-node spec-a spec-b]
(reduce-kv
(fn [_ k va]
(let [vb (get spec-b k)]
(if (sequential? vb)
(diff-child-list dom dom-node k va vb)
(let [result (diff dom va vb)]
(if (or (instance? Created result)
(instance? Updated result))
(set-property! dom dom-node k (:node result)))))))
nil
spec-a)
(reduce-kv
(fn [_ k vb]
(when-not (get spec-a k)
(if (sequential? vb)
(diff-child-list dom dom-node k nil vb)
(let [result (diff dom nil vb)]
(if (or (instance? Created result)
(instance? Updated result))
(set-property! dom dom-node k (:node result)))))))
nil
spec-b))
(defn diff
[dom a b]
(let [refresh-node (fn [node compo-a compo-b]
(set-once! compo-b :dom-node node)
(diff-component dom node (:props compo-a) (:props compo-b))
node)
new-node (fn [compo]
(let [node (create-component! dom (:type compo))]
(assert node "No Node returned by create-component!")
(refresh-node node nil compo)))]
(condp = [(val-type a) (val-type b)]
[:nil :comp] (->Created (new-node b))
[:val :val] (if (= a b)
(->Noop b)
(->Updated b))
[:nil :val] (->Created b)
[:nil :ucomp] (diff dom nil (render-user-component b))
[:ucomp :nil] (diff dom (render-user-component a) nil)
[:ucomp :ucomp] (if (needs-update? a b)
(diff dom (render-user-component a) (render-user-component b))
(->Noop (:dom-node (:render-result b))))
[:ucomp :comp] (diff dom (render-user-component a) b)
[:comp :ucomp] (diff dom a (render-user-component b))
[:comp :comp] (-> (if (= (:type a) (:type b))
(doto (:dom-node a)
(assert (str "No DOM Node" (pr-str a)))
(refresh-node a b))
(new-node b))
->Updated)
[:comp :nil] (->Deleted (:dom-node a))
[:val :nil] (->Deleted a))))
(defn component
([type spec]
(->Component type nil spec)))
(def valid-ui-fns '#{render should-update?})
(defmacro defui [nm & fns]
(let [has-should-update? (atom false)
mp (reduce
(fn [acc [nm :as fn]]
(assert (valid-ui-fns nm) (str "Invalid UI function " nm))
(when (= nm 'should-update?)
(reset! has-should-update? true))
(conj acc fn))
[]
fns)
fn-name (symbol (util/camel->kabob nm))]
`(let [kw# (keyword (name (.getName ^clojure.lang.Namespace *ns*)) ~(name nm))]
(defquasitype ~nm [~'type ~'props ~'render-result]
IUserComponent
~@mp
~@(when (not @has-should-update?)
`[(should-update? [this# old-props# new-props#]
(not= old-props# new-props#))]))
(defn ~fn-name
([] (~fn-name {}))
([k# v# & props#]
(~fn-name (apply hash-map k# v# props#)))
([props#]
(~(symbol (str "->" (name nm)))
kw#
props#
nil))))))