Skip to content

Commit

Permalink
Cache render-fns, fixed invalidation of ViewerFn in postwalk, elisiio…
Browse files Browse the repository at this point in the history
…ns do not recreate React components
  • Loading branch information
tonsky committed Oct 9, 2024
1 parent ab78e06 commit e6b97ee
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 25 deletions.
38 changes: 23 additions & 15 deletions src/nextjournal/clerk/render.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down
34 changes: 24 additions & 10 deletions src/nextjournal/clerk/viewer.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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}))
Expand Down Expand Up @@ -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))
Expand Down

0 comments on commit e6b97ee

Please sign in to comment.