-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcore.cljs
619 lines (521 loc) · 22.5 KB
/
core.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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
(ns pou.core
(:require [goog.dom :as gdom]
[cljs.core.async :refer [<!] :refer-macros [go]]
[cljs.core.async.interop :refer-macros [<p!]]
[cljs.reader :refer [read-string]]
[clojure.data :as d]
[cljs-http.client :as http]
[klipse.ui.editors.editor :as kl-ed]
[klipse-clj.repl :as kl-repl]
[klipse.plugin :as klp]
[klipse.utils :as klu]
[klipse.common.registry :as klreg]
[klipse.klipse-editors :as kleds]
[applied-science.js-interop :as j]
[editscript.core :as e]))
; UTILS
(def url-params #(or (klu/url-parameters) {}))
(defn process-url-params [& param-procs]
(let [params (url-params)]
(doseq [pp (partition 2 param-procs)]
(when-let [p ((first pp) params)]
((second pp) p)))))
(def decode64 #(js/atob %))
(def parse64 #(read-string (decode64 %)))
(def flatten64 #(flatten (into [] (parse64 %))))
; BASE STATE & watches
(def pou (atom
{:editors {}
:id-kls {}
:pou-modes {:pou-clj {:kl-mode "eval-clojure"
:external-libs ["https://bonuoq.github.io"]}}
:mode-options (into (sorted-set) (keys @klreg/mode-options))
:mode-selectors (clojure.set/map-invert @klreg/selector->mode)
:klipse-settings (js->clj js/klipse-settings)
:uis {}
:modules []}))
(add-watch klreg/mode-options :reg-mode-options
#(swap! pou assoc :mode-options (keys %4)))
(add-watch klreg/selector->mode :reg-mode-selectors
#(swap! pou assoc :mode-selectors (clojure.set/map-invert %4)))
(defn klipsett! [& kvs]
(apply swap! pou update-in [:klipse-settings] assoc (clj->js kvs)))
; Flexible STATE CHANGES (recursive ediscript diffs and patches)
(defn nod [prev-nod diff-path upd-fn & args]
(let [upd (apply upd-fn prev-nod args)
diff-upd (assoc-in upd diff-path (e/diff upd prev-nod))]
(assoc-in diff-upd diff-path (e/diff diff-upd prev-nod))))
(defn bon [prev-nod n-iter patch-path diff-path]
(->> (update-in prev-nod (butlast patch-path) dissoc (last patch-path))
(iterate #(nod % diff-path e/patch (get-in prev-nod patch-path)))
(take (inc n-iter))))
(defn bonth [prev-nod nth-iter patch-path diff-path]
(last (bon prev-nod nth-iter patch-path diff-path)))
(defn nod! [upd-path diff-path upd-fn & args]
(get-in
(apply swap! pou update-in upd-path nod diff-path upd-fn args)
upd-path))
(defn pou! [path diff-k upd-fn & args]
(apply nod! path [:uoq diff-k] upd-fn args))
(defn uoq [path n patch-k diff-k]
(bon (get-in @pou path) n [:uoq patch-k] [:uoq diff-k]))
(defn uoq! [path n patch-k diff-k]
(get-in
(swap! pou update-in path bonth n [:uoq patch-k] [:uoq diff-k])
path))
(defn drp
([path upd-fn & args]
(apply nod (get-in @pou path) [:uoq :drp] upd-fn args))
([path n]
(when (pos? n)
(uoq path n :drw :drp)))
([path] (drp path 0)))
(defn drps
([path n sel-keys]
(when (pos? n)
(map select-keys (drp path n) (repeat sel-keys))))
([path n]
(drps (butlast path) n [(last path)]))
([path]
(drps path 0)))
(defn drw
([path upd-fn & args]
(apply nod (get-in @pou path) [:uoq :drw] upd-fn args))
([path n]
(when (pos? n)
(uoq path n :drp :drw)))
([path] (drw path 0)))
(defn drws
([path n sel-keys]
(when (pos? n)
(map select-keys (drw path n) (repeat sel-keys))))
([path n]
(drws (butlast path) n [(last path)]))
([path]
(drws path 0)))
(defn drp!
([path upd-fn & args]
(apply nod! path [:uoq :drp] upd-fn args))
([path n]
(when (pos? n) (uoq! path n :drw :drp))))
(defn drps!
([path n sel-keys]
(select-keys (drp! path n) sel-keys)))
(defn drw!
([path upd-fn & args]
(apply nod! path [:uoq :drw] upd-fn args))
([path n]
(when (pos? n) (uoq! path n :drp :drw))))
(defn drws!
([path n sel-keys]
(select-keys (drw! path n) sel-keys)))
; REGISTER FUNCTIONS
(defn reg-editor [{:keys [id kl] :as editor}]
(swap! pou assoc-in [:editors kl] editor)
(swap! pou assoc-in [:id-kls id] kl))
(defn reg-ui [ui-keyword {:keys [append-fn klipsify?] :as ui}]
(let [div-uis (gdom/getElement "uis")
div-new-ui (gdom/createDom "div" (clj->js {:class "pou-ui" :id ui-keyword}))]
(swap! pou assoc-in [:uis ui-keyword] ui)
(if (= ui-keyword :base)
(. div-uis prepend div-new-ui)
(. div-uis appendChild div-new-ui))))
; EDITOR FUNCTIONS
(def get-kl #(if (number? %) % (-> @pou :id-kls (clj->js %))))
(def get-id #(if (number? %) (-> @pou :editors (get %) :id) (clj->js %)))
(defn get-cm [k & {:keys [n] :or {n 0}}]
(-> (str "#" (get-id k) " .CodeMirror") js/document.querySelectorAll (aget n) .-CodeMirror))
(defn call-in-editor [k method & args]
(j/apply
(or (@kleds/editors (get-kl k))
(get-cm (get-id k)))
method (clj->js args)))
(defn call-in-result [k method & args]
(j/apply
(or (@kleds/result-elements (get-kl k))
(get-cm (get-id k) 1))
method (clj->js args)))
(defn set-code [k value] (call-in-editor k :setValue (str value)))
(defn get-code [k] (call-in-editor k :getValue))
(defn get-result [k] (call-in-result k :getValue))
(defn off-code-change [k handler] (call-in-editor k :off "change" handler))
(defn on-code-change [k callback & {:keys [one-shot]}]
(let [cb-handler (fn self [cm]
(when-let [code (.getValue cm)]
(callback code)
(when one-shot
(off-code-change k self))))]
(call-in-editor k :on "change" cb-handler)
cb-handler))
(defn off-res-change [k handler] (call-in-result k :off "change" handler))
(defn on-res-change [k callback & {:keys [one-shot]}]
(let [cb-handler (fn self [cm]
(when-let [res (.getValue cm)]
(when-not (= (last res) \newline)
(callback res)
(when one-shot
(off-res-change k self)))))]
(call-in-result k :on "change" cb-handler)
cb-handler))
(defn res-watch [k cb & {:keys [one-shot]}]
(on-res-change k cb :one-shot one-shot))
(defn res-unwatch [k handler] (off-res-change k handler))
(defn res-reset! [k resp-atom]
(res-watch k #(reset! resp-atom %)))
(defn res-swap! [k resp-atom f & args]
(res-watch k #(reset! resp-atom (apply f % args))))
(defn eval-fn [k] (partial (aget (call-in-editor (get-kl k) :getOption "extraKeys") "Cmd-Enter")))
(defn eval-editor [k] ((eval-fn k)))
(defn set-code-eval [k code]
(do
(set-code k code)
(eval-editor k)))
(defn- drp-code! [k]
(drp! [:editors (get-kl k)] assoc :code (get-code k)))
(defn set-code-eval-drp [k code]
(do
(set-code-eval k code)
(drp-code! k)))
(defn eval-callback
([k callback]
(res-watch k callback :one-shot true)
(eval-editor k))
([k code callback]
(res-watch k callback :one-shot true)
(set-code-eval k code)))
(defn eval-callback-drp [k code callback]
(do
(eval-callback k code callback)
(drp-code! k)))
(defn peval-str [s] (set-code-eval 0 s))
(defn drw-editor!
([k n]
(let [code (:code (drw! [:editors (get-kl k)] n))]
(set-code-eval k code)
code))
([k] (drw-editor! k 1)))
(defn drp-editor!
([k n]
(let [code (:code (drp! [:editors (get-kl k)] n))]
(set-code-eval k code)
code))
([k] (drp-editor! k 1)))
; DOM & AJAX HELPERS
(defn sel-parent [selector]
(last (re-find #"(.*) " (clj->js selector))))
(defn sel-child [selector]
(or (last (re-find #" (.*)" (clj->js selector))) (clj->js selector)))
(defn sel-child-tag [selector]
(not-empty (re-find #"[^\.#]*" (sel-child (clj->js selector)))))
(defn sel-child-id [selector]
(when-let [id-find (re-find #"#[^\.]*" (sel-child (clj->js selector)))]
(subs id-find 1)))
(defn sel-child-class [selector]
(when-let [class-find (re-find #"\.[^#]*" (sel-child (clj->js selector)))]
(clojure.string/replace (subs class-find 1) "." " ")))
(defn dom-select [selector-or-element]
(if ((some-fn string? keyword?) selector-or-element)
(js/document.querySelector (clj->js selector-or-element))
selector-or-element))
(defn dom-nodelist [selector]
(js/document.querySelectorAll selector))
(defn dom-vec [selector]
(-> selector dom-nodelist js/Array.from js->clj))
(defn toggle-hidden!
([selector hidden?]
(doseq [e (dom-vec selector)]
(j/assoc! e :hidden hidden?)))
([selector]
(doseq [e (dom-vec selector)]
(j/update! e :hidden not))))
(defn loaded! []
(toggle-hidden! "div#pou-app" false)
(toggle-hidden! "div#loading" true))
(defn loading! []
(toggle-hidden! "div#loading" false)
(toggle-hidden! "div#pou-app" true))
(defn dom-string [s]
(.createContextualFragment (js/document.createRange) s))
(declare dom-any)
(defn dom-create [selector {:as attrs} & content]
(let [tag (sel-child-tag selector)
id (sel-child-id selector)
cl (sel-child-class selector)
as (cond-> {}
id (assoc :id id)
cl (assoc :class cl)
attrs (merge attrs))
children (dom-any content)]
(apply gdom/createDom tag (clj->js as) (clj->js children))))
(defn dom-any [any]
(condp apply [any]
string? (dom-string any)
coll? (if (map? any)
(let [{:keys [type tag attrs content selector]
:or {selector tag}} any] ; hickory style map but you can omit :type when {:type :element} & you can provide :selector/:tag with selector/tag as string or keyword :tag.class#id})
(case type
:document (map dom-any content)
:element (dom-create selector attrs content)
nil (dom-create selector attrs content)
(dom-string any))) ; provide a default way to represent clj maps as HTML
(if (and (vector? any) (keyword? (first any)))
(apply dom-create any) ; <-- alla hiccup
(map dom-any any)))
any))
(defn dom [selector & {:keys [attrs parent content map-siblings replace? separator]}]
(let [p (dom-select (or (sel-parent selector) parent))
sibling-fn #(dom-create selector (merge attrs (first %)) (rest %))
elms (if map-siblings
(into ;added
(->> map-siblings
(map sibling-fn)
((if separator
(partial interpose separator)
identity)))
content) ;added
[(dom-create selector attrs content)])]
(if p
(if replace?
(j/apply p :replaceChildren (clj->js elms))
(j/apply p :append (clj->js elms)))
elms)))
; (defn fn-apply [function & {:keys [parent-element parent-selector child-tag attrs]})
(defn request [path & {:keys [callback selected-keys read? pre-path options]}] ; return channel?
(go
(let [{:keys [status body]} (<!
(http/get (str pre-path path)
(merge {:with-credentials? false} options)))
content (if read? (read-string body) body)
res (if (= status 200)
(if (not-empty selected-keys)
(if (vector? content)
(mapv select-keys content (repeat selected-keys))
(select-keys content selected-keys))
content)
{:error status :msg body})]
(when callback (callback res))
res)))
; BASE UI
(defn- mode->class [mode]
(->> (get (:mode-selectors @pou) mode)
(get (:klipse-settings @pou))
rest
(apply str)))
(defn- show-hint! [cm completions]
(let [hint-fn (partial kl-ed/list-completions completions)]
(js/setTimeout
(fn []
(.showHint cm (clj->js {:hint (partial kl-ed/list-completions completions)
:completeSingle false}))))))
(defn- get-token-str [cm] (-> cm (.getTokenAt (.getCursor cm)) (aget "string")))
(defn- autocomp-refer! [cm]
(when-let [[_ pre kch findcomp] (re-find #"(.*)([\$.#&%])(.*)" (get-token-str cm))]
(let [completions
(->> @pou :editors vals
(mapv (fn [{:keys [kl id]}]
(clj->js {:displayText (str "." kl " #" id)
:text
(if (= \$ kch)
kl
(str pre
(case kch
\. (str kl)
\# (str id)
\& (get-code kl)
\% (get-result kl))))
:hint
(when (= \$ kch)
(fn [cm _ data]
(let [cursor (. cm getCursor)
token (. cm getTokenAt cursor)
token-start (js/CodeMirror.Pos (.-line cursor) (.-start token))
kl (. data -text)]
(eval-callback kl #(. cm replaceRange (str pre %) token-start cursor)))))})))
(into [nil])
clj->js)]
(show-hint! cm completions))))
(defn- show-completions! [cm hint? info?]
(let [token-str (get-token-str cm)
pre-ns (re-find #".+?\/" token-str)
completions-no-pre-ns (kl-repl/get-completions token-str)
completions (if pre-ns
(mapv (partial str pre-ns) completions-no-pre-ns)
completions-no-pre-ns)]
(when hint?
(show-hint! cm completions))
(when info?
(dom "#pou-info a.pou-completion"
:replace? true
:map-siblings (map
(fn [c] [{:href (str "#doc:" c)
:onclick #(peval-str (str "(doc " c ")"))} (str c)])
(take 20 (rest completions)))))))
(defn- token-doc [cm] (peval-str (str "(doc " (get-token-str cm) ")")))
(defn- cm-reg! [kl]
(let [{:keys [id mode]} (-> @pou :editors (get kl))
cm (or (@kleds/editors kl) (get-cm id))]
(j/assoc! cm :kl kl :id id)
(j/assoc! (. cm getOption "extraKeys")
:Cmd-. #(autocomp-refer! %)
:Alt-Up #(drw-editor! kl 1)
:Alt-Down #(drp-editor! kl 1))
(. cm addKeyMap
(let [f (fn [_] (drp-code! kl) js/CodeMirrorPass)]
#js {:Cmd-Enter f :Ctrl-Enter f}))
(when-let [{:keys [assoc-extra-keys on]} (some-> @pou :pou-modes (get mode) :cm)]
(when assoc-extra-keys
(apply j/assoc! (. cm getOption "extraKeys") (clj->js assoc-extra-keys)))
(when on
(doseq [[event f] on]
(. cm on (clj->js event) f))))))
(swap! pou update-in [:pou-modes :pou-clj :cm] assoc
:assoc-extra-keys [:Tab #(show-completions! % true false)
:Alt-Space (fn [cm] (token-doc cm) js/CodeMirror.Pass)
:Alt-. #(token-doc %)]
:on {:cursorActivity #(show-completions! % false true)})
(defn- when-klipse-ready [on-ready]
(let [observer (js/MutationObserver.
(fn [mutations o]
(let [elm (-> mutations (aget 0) .-addedNodes (aget 0))]
(when (= (. elm -id) "klipse-ready")
(.disconnect o)
(.remove elm)
(when on-ready (on-ready))))))]
(. observer observe js/document.body #js {:childList true})))
(defn klipsify! [on-mounted on-ready]
(when-klipse-ready on-ready)
(let [first-kl @klp/snippet-counter]
(go
(<! (klp/init-clj (:klipse-settings @pou)))
(let [last-kl (dec @klp/snippet-counter)]
(when on-mounted
(on-mounted last-kl))
(doall (map cm-reg! (range first-kl (inc last-kl))))
(call-in-editor last-kl :focus)))))
(defn button-pou-cmd [[cmd f] & args]
(let [c (clj->js cmd)]
(dom-create :button.pou-cmd {:onclick #(apply f args)} c)))
(defn append-editor-base [{:keys [id kl description mode attrs kl-attrs code pou-cmds] :as editor}]
(dom "div.pou-ui#base div"
:attrs (assoc attrs :id id :data-kl kl :data-pou editor)
:content [[:div.pou-intro {}
[[:p.pou-description {} (str kl "> " (or description (str "#" id ", mode: " mode)))
[:p.pou-cmds {} (map button-pou-cmd pou-cmds (repeat kl))]]]]
[:div kl-attrs (str code)]]))
(reg-ui :base {:append-fn append-editor-base
:klipsify? true})
(def default-editor
{:ui :base
:mode :pou-clj
:pou-class ["pou-wrapper"]
:pou-cmds {:eval (fn [kl]
(drp-code! kl)
(eval-editor kl))
:<-drw drw-editor!
:drp-> drp-editor!}})
(defn str-attr-join [connector str-vector]
(->> str-vector
(filter some?) distinct
(clojure.string/join connector) not-empty))
(defn append [editors & {:keys [provide override klipsify? on-mounted on-ready]}]
(dotimes [n (count editors)]
(let [{:keys [id from-gist] :as specific} (get editors n)
pre-pou (merge default-editor provide specific)
{:keys [ui mode kl-mode pou-class attrs kl-attrs eval-time loop? preamble editor-type]
:as editor} (merge pre-pou
(-> @pou :pou-modes (get (:mode pre-pou)))
override
(-> @pou :pou-modes (get (:mode override))))
kl (+ @klp/snippet-counter n)
id (clj->js (or id (:id attrs) (gensym "pou")))
wrapper-class (->> pou-class
(cons (:class attrs))
(str-attr-join " "))
data-external-libs (->> [provide specific override editor]
(map (juxt :external-libs (comp :data-external-libs :kl-attrs)))
flatten
(str-attr-join ","))
new-editor (assoc editor
:kl kl :id id :attrs {:id id :class wrapper-class}
:kl-attrs
(cond-> (assoc kl-attrs :class (mode->class (or (clj->js kl-mode) mode)))
data-external-libs (assoc :data-external-libs data-external-libs)
from-gist (assoc :data-gist-id from-gist)
eval-time (assoc (if loop? :data-loop-msec :data-eval-idle-msec) eval-time)
preamble (assoc :data-preamble preamble)
editor-type (assoc :data-editor-type editor-type)))]
(js/console.log (clj->js new-editor))
(reg-editor new-editor)
(let [append-fn (-> @pou :uis ui :append-fn)]
(append-fn new-editor))))
(let [ui (some :ui [provide default-editor])]
(when (or klipsify? (some-> @pou :uis ui :klipsify?))
(klipsify! on-mounted on-ready))))
(defn aed [& {:keys [code mode id from-gist attrs klipsettings external-libs
provide override klipsify? on-mounted on-ready] :as editor-settings}]
(append [(dissoc editor-settings :provide :override :klipsify? :on-mounted :on-ready)]
:provide provide :override override :klipsify? klipsify? :on-mounted on-mounted :on-ready on-ready))
; LOAD & EXPORT FNS
(defn editors-array
([& sel-keys]
(for [e (-> @pou :editors vals)
:let [ex (dissoc e :kl)]]
(select-keys ex sel-keys)))
([] (editors-array [:code :mode :id :from-gist :external-libs
:eval-time :loop? :preamble :editor-type])))
; defn snapshot!
(defn load-module [module & {:keys [on-ready pre-path post-path]}]
(request (str pre-path module post-path) :read? true
:callback
#(append [%]
:on-ready
(fn []
(swap! pou update-in [:modules] conj (str module))
(when on-ready (on-ready))))))
(defn module-loaded? [module]
(-> @pou :modules (get (str module)) some?))
(defn load-module-chain [chain & {:keys [pre-path post-path]}] ; add last on-ready
(load-module (first chain) :pre-path pre-path :post-path post-path
:on-ready #(load-module-chain (rest chain))))
(defn load-modules [& modules & {:keys [pre-path post-path]}] ; add last on-ready
(go
(doseq [m modules]
(if (coll? m)
(load-module-chain m :pre-path pre-path :post-path post-path)
(<! (load-module m))))))
(defn load-ui [ui & {:keys [on-ready pre-path post-path] :as opts}]
(loading!)
(toggle-hidden! "div#uis .pou-ui")
(load-module ui :pre-path pre-path :post-path post-path
:on-ready (fn []
(when on-ready (on-ready))
(loaded!))))
(defn filext-filter [file-extension files & {:keys [file-key]
:or {file-key identity}}]
(filter #(re-find (re-pattern (str "^(.*)." file-extension)) (file-key %)) files))
; INITIALIZATION
(defn init! []
(process-url-params :ui #(load-ui %)
:editor-base #(append [(parse64 %)])
:editors-base #(append (parse64 %))
:p #(aed :code (decode64 %))
:module #(load-module %)
:modules #(apply load-modules (parse64 %))
:code #(load-module "modules/github.edn"))
(request "https://api.github.com/repos/bonuoq/pou/contents/modules"
:selected-keys [:name :download_url]
:callback (fn [entries]
(doseq [url (map :download_url (filext-filter ".edn" entries :file-key :name))] ; instead of doseq should map async request
(request url :read? true :selected-keys [:description]
:callback #(dom "select.load-module option"
:attrs {:value url}
:content (:description %))))))
(request "https://api.github.com/repos/bonuoq/pou/contents/modules/ui"
:selected-keys [:name :download_url]
:callback (fn [entries]
(doseq [url (map :download_url (filext-filter ".edn" entries :file-key :name))] ; instead of doseq should map async request
(request url :read? true :selected-keys [:description]
:callback #(dom "select.load-ui option"
:attrs {:value url}
:content (:description %))))))
(when-not (:ui (url-params)) (loaded!)))