diff --git a/notebooks/doc.clj b/notebooks/doc.clj index bf2042058..8cbb03d93 100644 --- a/notebooks/doc.clj +++ b/notebooks/doc.clj @@ -1,43 +1,49 @@ ;; # 📓 Doc Browser (ns doc - {:nextjournal.clerk/visibility {:code :hide}} + {:nextjournal.clerk/visibility {:code :hide :result :hide}} (:require [clojure.string :as str] - [nextjournal.clerk :as clerk])) - -^{::clerk/visibility {:result :hide}} -(def text-input - {:pred ::clerk/var-from-def - :transform-fn (comp (clerk/update-val (fn [{::clerk/keys [var-from-def]}] - {:var-name (symbol var-from-def) :value @@var-from-def})) - clerk/mark-presented) - :render-fn '(fn [{:keys [var-name value]}] - [:div.my-1.relative - [:input {:type :text - :auto-correct "off" - :spell-check "false" - :placeholder "Filter namespaces…" - :value value - :class "px-3 py-2 relative bg-white bg-white rounded text-base font-sans border border-slate-200 shadow-inner outline-none focus:outline-none focus:ring w-full" - :on-input #(v/clerk-eval `(reset! ~var-name ~(.. % -target -value)))}] - [:button.absolute.right-2.text-xl.cursor-pointer - {:class "top-1/2 -translate-y-1/2" - :on-click #(v/clerk-eval `(reset! ~var-name ~(clojure.string/join "." (drop-last (clojure.string/split value #"\.")))))} "⏮"]])}) - -^{::clerk/viewer text-input} + [nextjournal.clerk :as clerk] + [nextjournal.clerk.viewer :as viewer])) + +(def render-input + '(fn [!query] + (prn :query !query) + [:div.my-1.relative + [:input {:type :text + :auto-correct "off" + :spell-check "false" + :placeholder "Filter namespaces…" + :value @!query + :class "px-3 py-2 relative bg-white bg-white rounded text-base font-sans border border-slate-200 shadow-inner outline-none focus:outline-none focus:ring w-full" + :on-input #(reset! !query (.. % -target -value))}] + [:button.absolute.right-2.text-xl.cursor-pointer + {:class "top-1/2 -translate-y-1/2" + :on-click #(reset! !query (clojure.string/join "." (drop-last (clojure.string/split @!query #"\."))))} "⏮"]])) + +^{::clerk/sync true} (defonce !ns-query (atom "nextjournal.clerk")) #_(reset! !ns-query "nextjournal.clerk") + +!ns-query + +^{::clerk/visibility {:result :show} + ::clerk/viewer {:render-fn render-input + :transform-fn clerk/mark-presented}} +(viewer/->viewer-eval `!ns-query) + ^{::clerk/viewers (clerk/add-viewers [{:pred seq? :render-fn '#(into [:div.border.rounded-md.bg-white.shadow.flex.flex-col.mb-1] - (v/inspect-children %2) %1) :page-size 20} + (nextjournal.clerk.render/inspect-children %2) %1) :page-size 20} {:pred string? :render-fn '(fn [ns] [:button.text-xs.font-medium.font-sans.cursor-pointer.px-3.py-2.hover:bg-blue-100.text-slate-700.text-left - {:on-click #(v/clerk-eval `(reset! !ns-query ~ns))} ns])}])} + {:on-click #(reset! doc/!ns-query ns)} ns])}])} + +^{::clerk/visibility {:result :show}} (def ns-matches (filter (partial re-find (re-pattern @!ns-query)) (sort (map str (all-ns))))) -^{::clerk/visibility {:result :hide}} (defn var->doc-viewer "Takes a clojure `var` and returns a Clerk viewer to display its documentation." [var] @@ -54,11 +60,9 @@ #_(var->doc-viewer #'var->doc-viewer) -^{::clerk/visibility {:result :hide}} (def var-doc-viewer {:pred ::clerk/var-from-def :transform-fn (clerk/update-val (comp var->doc-viewer ::clerk/var-from-def))}) -^{::clerk/visibility {:result :hide}} (defn namespace->doc-viewer [ns] (clerk/html [:div.text-sm.mt-6 @@ -70,10 +74,10 @@ (map (comp :nextjournal/value var->doc-viewer val)) (into (sorted-map) (-> ns ns-publics)))])) -^{::clerk/visibility {:result :hide}} (def ns-doc-viewer {:pred #(instance? clojure.lang.Namespace %) :transform-fn (clerk/update-val namespace->doc-viewer)}) +^{::clerk/visibility {:result :show}} (when-let [ns-name (first ns-matches)] (clerk/with-viewer ns-doc-viewer (find-ns (symbol ns-name)))) diff --git a/notebooks/viewers/control_lab.clj b/notebooks/viewers/control_lab.clj new file mode 100644 index 000000000..92f6ad2d2 --- /dev/null +++ b/notebooks/viewers/control_lab.clj @@ -0,0 +1,66 @@ +;; # 🎛 Control Lab 🧑🏼‍🔬 +(ns viewer.control-lab + (:require [clojure.string :as str] + [nextjournal.clerk :as clerk] + [nextjournal.clerk.viewer :as viewer])) + +;; Experimenting with ways of making controls. We start with two +;; little helper functions, one in sci and one in Clojure. + +(def viewer-eval-viewer + {:pred viewer/viewer-eval? + :transform-fn (comp viewer/mark-presented + (viewer/update-val + (fn [x] (cond (viewer/viewer-eval? x) x + (symbol? x) (viewer/->viewer-eval x) + (var? x) (recur (symbol x)) + (viewer/var-from-def? x) (recur (:nextjournal.clerk/var-from-def x)))))) + :render-fn 'nextjournal.clerk.render/inspect}) + +(clerk/add-viewers! [viewer-eval-viewer]) + +(viewer/->viewer-eval + '(defn make-slider [opts] + (fn [!state] + [:input (merge {:type :range :value @!state :on-change #(reset! !state (int (.. % -target -value)))} opts)]))) + +(defn make-slider + ([] (make-slider {})) + ([opts] (assoc viewer-eval-viewer :render-fn (list 'make-slider opts)))) + +;; Let's go through the ways we can use this. + +;; 1️⃣ On a var coming from a def +^{::clerk/sync true ::clerk/viewer (make-slider {})} +(defonce !num (atom 0)) + +;; 2️⃣ On a sharp quoted symbol (works with a fully qualified one as well, ofc). +(clerk/with-viewer (make-slider) + `!num) + +;; 3️⃣ On a var +^{::clerk/viewer (make-slider)} +#'!num + +;; 4️⃣ On an explicit `ViewerEval` type +(clerk/with-viewer (make-slider) + (viewer/->viewer-eval `!num)) + + +#_#_ ;; TODO: plain (not quoted) symbol +^{::clerk/viewer (make-slider {})} +!num +;; TODO: reactivity with default viewer +(viewer/->viewer-eval `!num) + +;; We can customise the slider by passing different opts (that are merged). + +(clerk/with-viewer (make-slider {:max 200}) + `!num) + +;; Or use a completely custom `:render-fn`. +(clerk/with-viewer (assoc viewer-eval-viewer :render-fn '(fn [x] [:h2.bg-green-500.rounded-xl.text-center @x])) + `!num) + +@!num + diff --git a/resources/viewer-js-hash b/resources/viewer-js-hash index 3c4edb603..ec62ea340 100644 --- a/resources/viewer-js-hash +++ b/resources/viewer-js-hash @@ -1 +1 @@ -38F71uHxJsdbeuJsMnSfkow4eTQ2 \ No newline at end of file +41eXh3xrWdsqXCdwuJ43u74YfK4z \ No newline at end of file diff --git a/src/nextjournal/clerk/builder.clj b/src/nextjournal/clerk/builder.clj index 1c71d2d17..d31827e21 100644 --- a/src/nextjournal/clerk/builder.clj +++ b/src/nextjournal/clerk/builder.clj @@ -37,6 +37,7 @@ "viewer_d3_require" "viewers_nested" "viewer_normalization" + "viewers/control_lab" "viewers/custom_markdown" "viewers/grid" "viewers/html" @@ -291,7 +292,6 @@ (report-fn {:stage :done :duration duration}))) (report-fn {:stage :finished :state state :duration duration :total-duration (eval/elapsed-ms start)}))) - #_(build-static-app! {:ssr? true :index "notebooks/rule_30.clj" :browse? true}) #_(build-static-app! {:paths clerk-docs :bundle? true}) #_(build-static-app! {:paths ["index.clj" "notebooks/rule_30.clj" "notebooks/viewer_api.md"] :bundle? true}) diff --git a/src/nextjournal/clerk/render.cljs b/src/nextjournal/clerk/render.cljs index 1fd8e5793..99b132604 100644 --- a/src/nextjournal/clerk/render.cljs +++ b/src/nextjournal/clerk/render.cljs @@ -591,6 +591,10 @@ (js/ws_send (pr-str {:type :swap! :var-name var-name :args [(list 'fn ['_] new-val)]}))) new-val)) +(defn clerk-reset! [atom new-val] + (clerk-swap! atom (constantly new-val)) + new-val) + (defn swap-clerk-atom! [{:as event :keys [var var-name args]}] (apply swap! @var args)) diff --git a/src/nextjournal/clerk/sci_env.cljs b/src/nextjournal/clerk/sci_env.cljs index 5ead265fa..d928fb9be 100644 --- a/src/nextjournal/clerk/sci_env.cljs +++ b/src/nextjournal/clerk/sci_env.cljs @@ -88,6 +88,7 @@ 'nextjournal.clerk.viewer viewer-namespace 'nextjournal.clerk.parser parser-namespace 'clojure.core {'swap! nextjournal.clerk.render/clerk-swap! + 'reset! nextjournal.clerk.render/clerk-reset! 'read-string read-string}} sci.configs.js-interop/namespaces sci.configs.reagent/namespaces)})