diff --git a/src/nextjournal/clerk/render.cljs b/src/nextjournal/clerk/render.cljs index 59f890b47..71ba532f3 100644 --- a/src/nextjournal/clerk/render.cljs +++ b/src/nextjournal/clerk/render.cljs @@ -302,7 +302,10 @@ [:div.relative [:div.overflow-x-auto {:ref ref-fn} - [inspect-presented {:!expanded-at !expanded-at :swap-sync-state! swap-sync-state! :!sync-state viewer/!sync-state} @!desc]]]]]]))) + [inspect-presented {:!expanded-at !expanded-at + :swap-sync-state! swap-sync-state! + :!sync-state viewer/!sync-state} + @!desc]]]]]]))) (defn toggle-expanded [!expanded-at path event] (.preventDefault event) @@ -547,25 +550,27 @@ x (let [{:nextjournal/keys [value viewer] :keys [path]} x hash (str (:hash viewer) "@" (peek (:path opts)))] - #_(prn :inspect-presented value :valid-element? (react/isValidElement value) :viewer viewer) ;; each view function must be called in its own 'functional component' so that it gets its own hook state. ^{:key hash} [:> ErrorBoundary {:hash hash} - [(:render-fn viewer) value (merge opts - (:nextjournal/render-opts x) - {:viewer viewer :path path})]])))) + [(:f (:render-fn viewer)) value (merge opts + (:nextjournal/render-opts x) + {:viewer viewer :path path})]])))) (defn inspect [value] - (r/with-let [!state (r/atom nil)] + (r/with-let [!state (r/atom nil) + fetch-fn (fn [fetch-opts] + (let [{:keys [present-elision-fn]} (-> !state deref :desc meta)] + (-> js/Promise + (.resolve (present-elision-fn fetch-opts)) + (.then + (fn [more] + (swap! !state update :desc viewer/merge-presentations more fetch-opts))))))] (when (not= (:value @!state ::not-found) value) (swap! !state assoc :value value :desc (viewer/present value))) - [view-context/provide {:fetch-fn (fn [fetch-opts] - (.then (let [{:keys [present-elision-fn]} (-> !state deref :desc meta)] - (.resolve js/Promise (present-elision-fn fetch-opts))) - (fn [more] - (swap! !state update :desc viewer/merge-presentations more fetch-opts))))} + [view-context/provide {:fetch-fn fetch-fn} [inspect-presented (:desc @!state)]])) (defn show-panel [panel-id panel] @@ -576,11 +581,14 @@ (defn with-fetch-fn [{:nextjournal/keys [presented blob-id]} body-fn] ;; TODO: unify with result-viewer (let [!presented-value (hooks/use-state presented) - body-fn* (hooks/use-callback body-fn)] + body-fn* (hooks/use-callback body-fn) + fetch-fn (hooks/use-callback + (fn [elision] + (-> (fetch! {:blob-id blob-id} elision) + (.then (fn [more] + (swap! !presented-value viewer/merge-presentations more elision))))))] [view-context/provide - {:fetch-fn (fn [elision] - (.then (fetch! {:blob-id blob-id} elision) - (fn [more] (swap! !presented-value viewer/merge-presentations more elision))))} + {:fetch-fn fetch-fn} [body-fn* @!presented-value]])) (defn exception-overlay [title & content] diff --git a/src/nextjournal/clerk/viewer.cljc b/src/nextjournal/clerk/viewer.cljc index 672d3cd0c..1c45601b5 100644 --- a/src/nextjournal/clerk/viewer.cljc +++ b/src/nextjournal/clerk/viewer.cljc @@ -36,8 +36,8 @@ (defrecord ViewerFn [form #?(:cljs f)] #?@(:cljs [IFn - (-invoke [this x] ((:f this) x)) - (-invoke [this x y] ((:f this) x y))])) + (-invoke [this x] (f x)) + (-invoke [this x y] (f x y))])) ;; Make sure `ViewerFn` and `ViewerEval` is changed atomically #?(:clj @@ -65,10 +65,11 @@ #?(:clj ([form] (resolve-aliases (ns-aliases *ns*) form))) ([aliases form] (w/postwalk #(cond->> % (qualified-symbol? %) (resolve-symbol-alias aliases)) form))) -(defn ->viewer-fn - [form] - (map->ViewerFn {:form form - #?@(:cljs [:f (*eval* form)])})) +(def ->viewer-fn + (memoize + (fn [form] + (map->ViewerFn {:form form + #?@(:cljs [:f (*eval* form)])})))) (defn ->viewer-eval [form] (map->ViewerEval {:form form})) @@ -1829,11 +1830,24 @@ #_(desc->values (present (table (mapv vector (range 30))))) #_(desc->values (present (with-viewer `table-viewer (normalize-table-data (repeat 60 ["Adelie" "Biscoe" 50 30 200 5000 :female]))))) +(defn- postwalk-colls + "A variant of postwalk that doesn’t go into records" + [f form] + (cond + (list? form) (f (apply list (map #(postwalk-colls f %) form))) + (map-entry? form) (f #?(:clj (clojure.lang.MapEntry/create (postwalk-colls f (key form)) (postwalk-colls (val form))) + :cljs (cljs.core.MapEntry. (postwalk-colls f (key form)) (postwalk-colls f (val form)) nil))) + (seq? form) (f (doall (map #(postwalk-colls f %) form))) + (record? form) (f form) + (coll? form) (f (into (empty form) (map #(postwalk-colls f %) form))) + :else (f form))) + (defn merge-presentations [root more elision] - (clojure.walk/postwalk (fn [x] (if (some #(= elision (:nextjournal/value %)) (when (coll? x) x)) - (into (pop x) (:nextjournal/value more)) - x)) - root)) + (postwalk-colls + (fn [x] (if (some #(= elision (:nextjournal/value %)) (when (coll? x) x)) + (into (pop x) (:nextjournal/value more)) + x)) + root)) (defn assign-closing-parens ([node] (assign-closing-parens '() node))