Skip to content

Commit

Permalink
Normalize viewers to support full map form (#77)
Browse files Browse the repository at this point in the history
For the functional (clerk/with-viewer and clerk/with-viewers) and metadata api (^{::clerk/viewer ,,,} and ^{::clerk/viewers ,,,}). Also update sample notebooks accordingly.
  • Loading branch information
mk authored Feb 9, 2022
1 parent 8b99135 commit b1d769c
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 85 deletions.
19 changes: 9 additions & 10 deletions notebooks/dice.clj
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,20 @@
(defonce dice (atom nil))

;; Here, we define a viewer using hiccup that will the dice as well as a button. Note that this button has an `:on-click` event handler that uses `v/clerk-eval` to tell Clerk to evaluate the argument, in this cases `(roll!)` when clicked.
^::clerk/no-cache
(clerk/with-viewer '(fn [side]
(v/html [:div.text-center
(when side
[:div.mt-2 {:style {:font-size "6em"}} side])
[:button.bg-blue-500.hover:bg-blue-700.text-white.font-bold.py-2.px-4.rounded
{:on-click (fn [e] (v/clerk-eval '(roll!)))} "Roll 🎲!"]]))
@dice)
^{::clerk/no-cache true
::clerk/viewer '(fn [side]
(v/html [:div.text-center
(when side
[:div.mt-2 {:style {:font-size "6em"}} side])
[:button.bg-blue-500.hover:bg-blue-700.text-white.font-bold.py-2.px-4.rounded
{:on-click (fn [e] (v/clerk-eval '(roll!)))} "Roll 🎲!"]]))}
@dice

;; Our roll! function `resets!` our `dice` with a random side and prints and says the result. Finally it updates the notebook.
(defn roll! []
(reset! dice (rand-nth sides))
(prn @dice)
(shell/sh "say" (name ('{🟡 :gelb 🟠 :orange 🟢 :grün 🔵 :blau 🃏 :joker} @dice)))
(clerk/show! "notebooks/dice.clj"))
(shell/sh "say" (name ('{🟡 :gelb 🟠 :orange 🟢 :grün 🔵 :blau 🃏 :joker} @dice))))


#_(roll!)
20 changes: 10 additions & 10 deletions notebooks/elements.clj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@
[babashka.fs :as fs]
[nextjournal.clerk :as clerk]))

^{::clerk/viewer {:fetch-fn (fn [_ x] x)
:render-fn '(fn [board]
(let [cell #(vector :div.inline-block
{:class (if (zero? %)
"bg-white border-solid border-2 border-black"
"bg-black")
:style {:width 16 :height 16}})
row #(into [:div.flex.inline-flex] (map cell) %)]
(v/html (into [:div.flex.flex-col] (map row) board))))}}
(let [rule30 {[1 1 1] 0
[1 1 0] 0
[1 0 1] 0
Expand All @@ -16,16 +25,7 @@
n 33
g1 (assoc (vec (repeat n 0)) (/ (dec n) 2) 1)
evolve #(mapv rule30 (partition 3 1 (repeat 0) (cons 0 %)))]
(clerk/with-viewer
(fn [board]
(let [cell (fn [c] (vector :div.inline-block
{:class (if (zero? c)
"bg-white border-solid border-2 border-black"
"bg-black")
:style {:width 16 :height 16}}))
row (fn [r] (into [:div.flex.inline-flex] (map cell) r))]
(v/html (into [:div.flex.flex-col] (map row) board))))
(->> g1 (iterate evolve) (take 17))))
(->> g1 (iterate evolve) (take 17)))

;; Clerk uses static analysis and a tiny bit of data flow to avoid needless recomputation.
(defn fix-case [s]
Expand Down
58 changes: 36 additions & 22 deletions notebooks/interactivity.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,48 @@
(ns ^:nextjournal.clerk/no-cache interactivity
(:require [nextjournal.clerk :as clerk]))

(clerk/set-viewers! [{:pred #(when-let [v (get % ::clerk/var-from-def)]
(and v (instance? clojure.lang.IDeref (deref v))))
:fetch-fn (fn [_ x] x)
:transform-fn (fn [{::clerk/keys [var-from-def]}]
{:var-name (symbol var-from-def) :value @@var-from-def})
:render-fn '(fn [{:keys [var-name value]}]
(v/html (cond (number? value)
[:input {:type :range
:initial-value value
:on-change #(v/clerk-eval `(reset! ~var-name (Integer/parseInt ~(.. % -target -value))))}]
(string? value)
(v/html [:input {:type :text
:placeholder "Schreib mal"
:initial-value value
:class "px-3 py-3 placeholder-blueGray-300 text-blueGray-600 relative bg-white bg-white rounded text-sm border border-blueGray-300 outline-none focus:outline-none focus:ring w-full"
:on-input #(v/clerk-eval `(reset! ~var-name ~(.. % -target -value)))}]))))}])

;; Let's try a little Slider 🎚
(defonce slider-state (atom 42))
;; Let's try a little Slider using `::clerk/viewers` 🎚


#_(ns-unmap *ns* 'slider-state)
^{::clerk/viewers [{:pred ::clerk/var-from-def
:fetch-fn (fn [_ x] x)
:transform-fn (fn [{::clerk/keys [var-from-def]}]
{:var-name (symbol var-from-def) :value @@var-from-def})
:render-fn '(fn [{:keys [var-name value]}]
(v/html [:input {:type :range
:initial-value value
:on-change #(v/clerk-eval `(reset! ~var-name (Integer/parseInt ~(.. % -target -value))))}]))}]}
(defonce slider-state (atom 42))

@slider-state

;; And a second one using `::clerk/viewer` 🎚
^{::clerk/viewer {:fetch-fn (fn [_ x] x)
:transform-fn (fn [{:as x ::clerk/keys [var-from-def]}]
{:var-name (symbol var-from-def) :value @@var-from-def})
:render-fn '(fn [{:as x :keys [var-name value]}]
(v/html [:input {:type :range
:initial-value value
:on-change #(v/clerk-eval `(reset! ~var-name (Integer/parseInt ~(.. % -target -value))))}]))}}
(defonce slider-2-state (atom 42))

@slider-2-state

;; And a text box.

^{::clerk/viewer {:pred #(when-let [v (get % ::clerk/var-from-def)]
(and v (instance? clojure.lang.IDeref (deref v))))
:fetch-fn (fn [_ x] x)
:transform-fn (fn [{::clerk/keys [var-from-def]}]
{:var-name (symbol var-from-def) :value @@var-from-def})
:render-fn '(fn [{:keys [var-name value]}]
(v/html [:input {:type :text
:placeholder "Schreib mal"
:initial-value value
:class "px-3 py-3 placeholder-blueGray-300 text-blueGray-600 relative bg-white bg-white rounded text-sm border border-blueGray-300 outline-none focus:outline-none focus:ring w-full"
:on-input #(v/clerk-eval `(reset! ~var-name ~(.. % -target -value)))}]))}}
(defonce text-state (atom ""))

#_ (reset! text-state "")
#_ (ns-unmap *ns* 'text-state)

@text-state

Expand Down
18 changes: 11 additions & 7 deletions notebooks/viewer_api.clj
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
(clerk/with-viewer '#(v/html [:div "Greetings to " [:strong %] "!"])
"James Clerk Maxwell")

^{::clerk/viewer {:render-fn '#(v/html [:span "The answer is " % "."])
:transform-fn inc}}
(do 41)

(clerk/with-viewers [{:pred number? :render-fn '#(v/html [:div.inline-block [(keyword (str "h" %)) (str "Heading " %)]])}]
[1 2 3 4 5])

Expand All @@ -63,7 +67,7 @@
:class (if (pos? %) "bg-black" "bg-white border-solid border-2 border-black")}])}]
(take 10 (repeatedly #(rand-int 2))))

(clerk/with-viewers
^{::clerk/viewers
[{:pred #(and (string? %)
(re-matches
(re-pattern
Expand All @@ -74,12 +78,12 @@
{:style {:width 16
:height 16
:border "1px solid rgba(0,0,0,.2)"
:background-color %}}])}]
["#571845"
"rgb(144,12,62)"
"rgba(199,0,57,1.0)"
"hsl(11,100%,60%)"
"hsla(46, 97%, 48%, 1.000)"])
:background-color %}}])}]}
["#571845"
"rgb(144,12,62)"
"rgba(199,0,57,1.0)"
"hsl(11,100%,60%)"
"hsla(46, 97%, 48%, 1.000)"]


#_(clerk/show! "notebooks/viewer_api.clj")
32 changes: 32 additions & 0 deletions notebooks/viewer_normalization.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
^{:nextjournal.clerk/visibility :hide}
(ns viewer-normalization
(:require [nextjournal.clerk :as clerk]))

(clerk/with-viewer '(fn [v] (v/html [:span "The answer is " v "."]))
42)

^{::clerk/viewer '#(v/html [:span "The answer is " % "."])}
(do 42)

^{::clerk/viewer {:render-fn '(fn [v] (v/html [:span "The answer is " v "."]))}}
(do 42)


(clerk/with-viewer {:render-fn '(fn [v] (v/html [:span "The answer is " v "."]))}
42)

(clerk/with-viewer {:render-fn '(fn [v] (v/html [:span "The answer is " v "."])) :transform-fn inc}
41)

^{::clerk/viewer {:render-fn '#(v/html [:span "The answer is " % "."]) :transform-fn inc}}
(do 41)

(clerk/with-viewers [{:pred (constantly true) :render-fn '(fn [v] (v/html [:span "The answer is " v "."])) :transform-fn inc}]
41)

^{::clerk/viewer {:render-fn '#(v/html [:span "The answer is " % "."]) :transform-fn inc}}
(do 41)


^{::clerk/viewers [{:pred (constantly true) :render-fn '(fn [v] (v/html [:span "The answer is " v "."])) :transform-fn inc}]}
(do 41)
6 changes: 3 additions & 3 deletions notebooks/visibility.clj
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
;; While this one is completely hidden, without the ability to uncollapse it.
^{::clerk/visibility :hide} (shuffle (range 25))

;; In the rare case you'd like to hide the result of a cell, use `clerk/hide-result`.
^{::clerk/visibility :show}
(clerk/hide-result (range 500))
;; In the rare case you'd like to hide the result of a cell, use the `clerk/hide-result` viewer.
^{::clerk/visibility :show ::clerk/viewer clerk/hide-result}
(def my-range (range 500))

;; In a follow-up, we'll remove the `::clerk/visibility` metadata from the code cells to not distract from the essence.

Expand Down
13 changes: 8 additions & 5 deletions src/nextjournal/clerk.clj
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,12 @@
result)]
(wrapped-with-metadata result visibility blob-id))))

(defn maybe-resolve-viewer [{:as opts :nextjournal/keys [viewer]}]
(defn maybe-eval-viewers [{:as opts :nextjournal/keys [viewer viewers]}]
(cond-> opts
(symbol? viewer)
(update :nextjournal/viewer resolve)))
viewer
(update :nextjournal/viewer eval)
viewers
(update :nextjournal/viewers eval)))

(defn read+eval-cached [results-last-run ->hash doc-visibility codeblock]
(let [{:keys [ns-effect? form var]} codeblock
Expand All @@ -134,9 +136,9 @@
cas-hash
(-> cas-hash ->cache-file fs/exists?))
opts-from-form-meta (-> (meta form)
(select-keys [::viewer ::width])
(select-keys [::viewer ::viewers ::width])
v/normalize-viewer-opts
maybe-resolve-viewer)]
maybe-eval-viewers)]
#_(prn :cached? (cond no-cache? :no-cache
cached-result? true
cas-hash :no-cas-file
Expand Down Expand Up @@ -350,6 +352,7 @@
"visibility"
"viewer_api"
"viewer_api_meta"
"viewer_normalization"
"viewers/html"
"viewers/image"
"viewers/image_layouts"
Expand Down
16 changes: 9 additions & 7 deletions src/nextjournal/clerk/view.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
[clojure.walk :as w]
[multihash.core :as multihash]
[multihash.digest :as digest]
[taoensso.nippy :as nippy]))
[taoensso.nippy :as nippy])
(:import (java.util Base64)))

(defn var->data [v]
(v/wrapped-with-viewer v))
Expand Down Expand Up @@ -82,17 +83,18 @@

(defn base64-encode-value [{:as result :nextjournal/keys [content-type]}]
(update result :nextjournal/value (fn [data] (str "data:" content-type ";base64, "
(.encodeToString (java.util.Base64/getEncoder) data)))))
(.encodeToString (Base64/getEncoder) data)))))

(defn apply-viewer-unwrapping-var-from-def [{:as result :nextjournal/keys [value viewer]}]
(if viewer
(let [value (if (get value :nextjournal.clerk/var-from-def)
(let [{:keys [transform-fn]} (and (map? viewer) viewer)
value (if (and (not transform-fn) (get value :nextjournal.clerk/var-from-def))
(-> value :nextjournal.clerk/var-from-def deref)
value)]
(assoc result :nextjournal/value (if (var? viewer)
(assoc result :nextjournal/value (if (or (var? viewer) (fn? viewer))
(viewer value)
{:nextjournal/value value
:nextjournal/viewer viewer})))
:nextjournal/viewer (v/normalize-viewer viewer)})))
result))

#_(apply-viewer-unwrapping-var-from-def {:nextjournal/value [:h1 "hi"] :nextjournal/viewer :html})
Expand All @@ -106,8 +108,8 @@
base64-encode-value)
described-result))

(defn ->result [ns {:as result :nextjournal/keys [value blob-id]} lazy-load?]
(let [described-result (extract-blobs lazy-load? blob-id (v/describe value {:viewers (v/get-viewers ns (v/viewers value))}))
(defn ->result [ns {:as result :nextjournal/keys [value viewers blob-id]} lazy-load?]
(let [described-result (extract-blobs lazy-load? blob-id (v/describe value {:viewers (concat viewers (v/get-viewers ns (v/viewers value)))}))
opts-from-form-meta (select-keys result [:nextjournal/width])]
(merge {:nextjournal/viewer :clerk/result
:nextjournal/value (cond-> (try {:nextjournal/edn (->edn described-result)}
Expand Down
54 changes: 34 additions & 20 deletions src/nextjournal/clerk/viewer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -277,27 +277,32 @@
(defn find-named-viewer [viewers viewer-name]
(first (filter (comp #{viewer-name} :name) viewers)))

(declare wrapped-with-viewer)

(defn apply-viewer [viewers {:as viewer :keys [render-fn transform-fn]} v]
(let [v' (if transform-fn
(-> v value transform-fn)
v)]
(if (and transform-fn (not render-fn))
(wrapped-with-viewer (value v') viewers)
(merge (wrap-value v' viewer) (when (map? v) (select-keys v [:nextjournal/width]))))))

(defn wrapped-with-viewer
([x] (wrapped-with-viewer x default-viewers))
([x viewers]
(if-let [selected-viewer (viewer x)]
(cond (keyword? selected-viewer)
(if-let [{:as named-viewer :keys [transform-fn]} (find-named-viewer viewers selected-viewer)]
(wrap-value (cond-> x transform-fn transform-fn) named-viewer)
(throw (ex-info (str "cannot find viewer named " selected-viewer) {:selected-viewer selected-viewer :x (value x) :viewers viewers})))
(viewer-fn? selected-viewer)
(wrap-value x selected-viewer))
(let [val (value x)]
(loop [v viewers]
(if-let [{:as matching-viewer :keys [pred]} (first v)]
(if (and (ifn? pred) (pred val))
(let [{:keys [render-fn transform-fn]} matching-viewer
val (cond-> val transform-fn transform-fn)]
(if (and transform-fn (not render-fn))
(wrapped-with-viewer val viewers)
(wrap-value val matching-viewer)))
(recur (rest v)))
(throw (ex-info (str "cannot find matchting viewer for `" (pr-str x) "`") {:viewers viewers :x val}))))))))
(if (keyword? selected-viewer)
(if-let [named-viewer (find-named-viewer viewers selected-viewer)]
(apply-viewer viewers named-viewer x)
(throw (ex-info (str "cannot find viewer named " selected-viewer) {:selected-viewer selected-viewer :x (value x) :viewers viewers})))
(apply-viewer viewers selected-viewer x))
(let [v (value x)]
(loop [vs viewers]
(if-let [{:as matching-viewer :keys [pred]} (first vs)]
(if (and (ifn? pred) (pred v))
(apply-viewer viewers matching-viewer v)
(recur (rest vs)))
(throw (ex-info (str "cannot find matchting viewer for `" (pr-str x) "`") {:viewers vs :x v}))))))))

#_(wrapped-with-viewer {:one :two})
#_(wrapped-with-viewer [1 2 3])
Expand Down Expand Up @@ -541,8 +546,19 @@

(defn normalize-viewer-opts [opts]
(set/rename-keys opts {:nextjournal.clerk/viewer :nextjournal/viewer
:nextjournal.clerk/viewers :nextjournal/viewers
:nextjournal.clerk/width :nextjournal/width}))

(defn normalize-viewer [viewer]
(if (or (keyword? viewer)
(map? viewer))
viewer
{:render-fn viewer}))

#_(normalize-viewer '#(v/html [:h3 "Hello " % "!"]))
#_(normalize-viewer :latex)
#_(normalize-viewer {:render-fn '#(v/html [:h3 "Hello " % "!"]) :transform-fn identity})

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; public api

Expand All @@ -553,9 +569,7 @@
(merge (normalize-viewer-opts opts)
(-> x
wrap-value
(assoc :nextjournal/viewer (cond-> viewer
(or (list? viewer) (symbol? viewer))
->viewer-fn))))))
(assoc :nextjournal/viewer (normalize-viewer viewer))))))

#_(with-viewer :latex "x^2")
#_(with-viewer '#(v/html [:h3 "Hello " % "!"]) "x^2")
Expand Down
Loading

0 comments on commit b1d769c

Please sign in to comment.