-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
helpers.cljs
218 lines (193 loc) · 8.85 KB
/
helpers.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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
(ns fulcro.inspect.helpers
(:require [fulcro.client.primitives :as fp]
[fulcro.client.mutations :as mutations]
[fulcro.inspect.lib.local-storage :as storage]))
(defn- om-ident? [x]
(and (vector? x)
(= 2 (count x))
(keyword? (first x))))
(defn query-component
([this]
(let [component (fp/react-type this)
ref (fp/get-ident this)
state (-> this fp/get-reconciler fp/app-state deref)
query (fp/get-query component)]
(fp/db->tree query (get-in state ref) state)))
([this focus-path]
(let [component (fp/react-type this)
ref (fp/get-ident this)
state (-> this fp/get-reconciler fp/app-state deref)
query (fp/focus-query (fp/get-query component) focus-path)]
(-> (fp/db->tree query (get-in state ref) state)
(get-in focus-path)))))
(defn swap-entity! [{:keys [state ref]} & args]
(apply swap! state update-in ref args))
(defn resolve-path [state path]
(loop [[h & t] path
new-path []]
(if h
(let [np (conj new-path h)
c (get-in state np)]
(if (om-ident? c)
(recur t c)
(recur t (conj new-path h))))
new-path)))
(defn get-in-path
"Like get-in, but will resolve path before reading it."
[state path]
(get-in state (resolve-path state path)))
(defn swap-in! [{:keys [state ref]} path & args]
(let [path (resolve-path @state (into ref path))]
(if (and path (get-in @state path))
(apply swap! state update-in path args))))
(defn integrate-ident
"Integrate an ident into any number of places in the app state. This function is safe to use within mutation
implementations as a general helper function.
The named parameters can be specified any number of times. They are:
- set: A vector (path) to a list in your app state where this new object's ident should be set.
- append: A vector (path) to a list in your app state where this new object's ident should be appended. Will not append
the ident if that ident is already in the list.
- prepend: A vector (path) to a list in your app state where this new object's ident should be prepended. Will not append
the ident if that ident is already in the list.
- replace: A vector (path) to a specific location in app-state where this object's ident should be placed. Can target a to-one or to-many.
If the target is a vector element then that element must already exist in the vector."
[state ident & named-parameters]
{:pre [(map? state)]}
(let [actions (partition 2 named-parameters)]
(reduce (fn [state [command data-path]]
(let [already-has-ident-at-path? (fn [data-path] (some #(= % ident) (get-in state data-path)))]
(case command
:set (assoc-in state data-path ident)
:prepend (if (already-has-ident-at-path? data-path)
state
(do
(assert (vector? (get-in state data-path)) (str "Path " data-path " for prepend must target an app-state vector."))
(update-in state data-path #(into [ident] %))))
:append (if (already-has-ident-at-path? data-path)
state
(do
(assert (vector? (get-in state data-path)) (str "Path " data-path " for append must target an app-state vector."))
(update-in state data-path conj ident)))
:replace (let [path-to-vector (butlast data-path)
to-many? (and (seq path-to-vector) (vector? (get-in state path-to-vector)))
index (last data-path)
vector (get-in state path-to-vector)]
(assert (vector? data-path) (str "Replacement path must be a vector. You passed: " data-path))
(when to-many?
(do
(assert (vector? vector) "Path for replacement must be a vector")
(assert (number? index) "Path for replacement must end in a vector index")
(assert (contains? vector index) (str "Target vector for replacement does not have an item at index " index))))
(assoc-in state data-path ident))
(throw (ex-info "Unknown post-op to merge-state!: " {:command command :arg data-path})))))
state actions)))
(defn merge-entity [state x data & named-parameters]
"Starting from a denormalized entity map, normalizes using class x.
It assumes the entity is going to be normalized too, then get all
normalized data and merge back into the app state and idents."
(let [idents (-> (fp/tree->db
(reify
fp/IQuery
(query [_] [{::root (fp/get-query x)}]))
{::root data} true)
(dissoc ::root ::fp/tables))
root-ident (fp/ident x data)
state (merge-with (partial merge-with merge) state idents)]
(if (seq named-parameters)
(apply integrate-ident state root-ident named-parameters)
state)))
(defn create-entity! [{:keys [state ref]} x data & named-parameters]
(let [named-parameters (->> (partition 2 named-parameters)
(map (fn [[op path]] [op (conj ref path)]))
(apply concat))
data' (if (-> data meta ::initialized)
data
(fp/get-initial-state x data))]
(apply swap! state merge-entity x data' named-parameters)
data'))
(defn- dissoc-in [m path]
(cond-> m
(get-in m (butlast path))
(update-in (butlast path) dissoc (last path))))
(defn deep-remove-ref [state ref]
"Remove a ref and all linked refs from it."
(let [item (get-in state ref)
idents (into []
(comp (keep (fn [v]
(cond
(om-ident? v)
[v]
(and (vector? v)
(every? om-ident? v))
v)))
cat)
(vals item))]
(reduce
(fn [s i] (deep-remove-ref s i))
(dissoc-in state ref)
idents)))
(defn remove-edge! [{:keys [state ref]} field]
(let [children (get-in @state (conj ref field))]
(cond
(om-ident? children)
(swap! state (comp #(update-in % ref dissoc field)
#(deep-remove-ref % children)))
(seq children)
(swap! state (comp #(assoc-in % (conj ref field) [])
#(reduce deep-remove-ref % children))))))
(defn vec-remove-index [i v]
"Remove an item from a vector via index."
(->> (concat (subvec v 0 i)
(subvec v (inc i) (count v)))
(vec)))
(mutations/defmutation persistent-set-props [{::keys [local-key storage-key value]}]
(action [env]
(storage/set! storage-key value)
(swap-entity! env assoc local-key value)))
(defn persistent-set! [comp local-key storage-key value]
(fp/transact! comp [(list `persistent-set-props {::local-key local-key
::storage-key storage-key
::value value}) local-key]))
(defn normalize-id [id]
(if-let [[_ prefix] (re-find #"(.+?)(-\d+)$" (str id))]
(cond
(keyword? id) (keyword (subs prefix 1))
(symbol? id) (symbol prefix)
:else prefix)
id))
(defn ref-app-uuid
"Extracts the app uuid from a ident."
[ref]
(assert (and (vector? ref)
(vector? (second ref)))
"Ref with app it must be in the format: [:id-key [::app-id app-id]]")
(let [[_ [_ app-id]] ref]
app-id))
(defn ref-app-id
[state ref]
(let [app-uuid (ref-app-uuid ref)]
(get-in state [:fulcro.inspect.ui.inspector/id
app-uuid
:fulcro.inspect.core/app-id])))
(defn comp-app-uuid
"Read app uuid from a component"
[comp]
(-> comp fp/get-ident ref-app-uuid))
(defn all-apps [state]
(get-in state [:fulcro.inspect.ui.multi-inspector/multi-inspector
"main"
:fulcro.inspect.ui.multi-inspector/inspectors]))
(defn matching-apps [state app-id]
(->> (all-apps state)
(filterv #(= app-id (:fulcro.inspect.core/app-id (get-in state %))))
(mapv second)))
(defn update-matching-apps [state app-id f]
(let [apps (matching-apps state app-id)]
(reduce
(fn [s app]
(f s app))
state
apps)))
(defn remote-mutation [{:keys [ast ref]} key]
(-> (assoc ast :key key)
(assoc-in [:params :fulcro.inspect.core/app-uuid] (ref-app-uuid ref))))