Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesign error overlay and move it to the bottom of the screen #703

Merged
merged 7 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion notebooks/errors/boundaries.clj
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
;; # 😩 Errors

(ns boundaries
(:require [nextjournal.clerk :as clerk]
[nextjournal.clerk.viewer :as v]))

(clerk/add-viewers! [(assoc v/code-block-viewer :render-fn '(fn [] borked))])
#_boom

#_(clerk/with-viewer {:require-cljs true
:render-fn 'errors.render/test} 1)

#_(try (/ 1 0) (catch Exception e (throw (ex-info "boom!" {:foo :bar} e))))

#_(clerk/show! "foo.clj")
6 changes: 6 additions & 0 deletions notebooks/errors/render.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
(ns errors.render)

boom

(defn test []
[:h1 "TEST"])
1 change: 1 addition & 0 deletions render/deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
cider/cider-nrepl {:mvn/version "0.28.3"}
org.babashka/sci {:git/url "https://github.com/babashka/sci"
:git/sha "c556f4474303c61da72e7a07eef496dcbf66a56e"}
org.clojure/clojurescript {:mvn/version "1.11.132"}
io.github.babashka/sci.nrepl {:mvn/version "0.0.2"}
reagent/reagent {:mvn/version "1.2.0"}
io.github.babashka/sci.configs {:git/sha "0702ea5a21ad92e6d7cca6d36de84271083ea68f"
Expand Down
5 changes: 3 additions & 2 deletions src/nextjournal/clerk.clj
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,10 @@
{:file-path path})
{:nav-path (webserver/->nav-path file-or-ns)}
(parser/parse-file {:doc? true} file))
(catch java.io.FileNotFoundException _e
(catch java.io.FileNotFoundException e
(throw (ex-info (str "`nextjournal.clerk/show!` could not find the file: `" (pr-str file-or-ns) "`")
{:file-or-ns file-or-ns})))
{:file-or-ns file-or-ns}
e)))
(catch Exception e
(throw (ex-info (str "`nextjournal.clerk/show!` could not not parse the file: `" (pr-str file-or-ns) "`")
{::doc {:file file-or-ns}}
Expand Down
140 changes: 108 additions & 32 deletions src/nextjournal/clerk/render.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@
(r/atom {:eval-counter 0
:doc nil
:viewers viewer/!viewers
:panels {}}))
:panels {}
:render-errors []}))

(defonce !eval-counter (r/cursor !state [:eval-counter]))
(defonce !doc (r/cursor !state [:doc]))
(defonce !viewers (r/cursor !state [:viewers]))
(defonce !panels (r/cursor !state [:panels]))
(defonce !render-errors (r/cursor !state [:render-errors]))

(defn reagent-atom? [x]
(satisfies? ratom/IReactiveAtom x))
Expand Down Expand Up @@ -198,7 +200,6 @@
(when @!stack-expanded
(str/join "\n" stack))])]))


(defclass ErrorBoundary
(extends react/Component)
(field handle-error)
Expand Down Expand Up @@ -445,33 +446,82 @@
(if (= sort-order :asc) #(compare %1 %2) #(compare %2 %1)))
vec))))

(defn throwable-view [{:keys [via trace]} opts]
[:div.bg-white.max-w-6xl.mx-auto.text-xs.monospace.not-prose
(into
[:div]
(map
(fn [{:as _ex :keys [type message data _trace]}]
[:div.p-4.bg-red-100.border-b.border-b-gray-300
(when type
[:div.font-bold "Unhandled " type])
[:div.font-bold.mt-1 message]
(when data
[:div.mt-1 [inspect-presented opts data]])])
via))
[:div.py-6.overflow-x-auto
[:table.w-full
(into [:tbody]
(map (fn [[call _x file line]]
[:tr.hover:bg-red-100.leading-tight
[:td.text-right.px-6 file ":"]
[:td.text-right.pr-6 line]
[:td.py-1.pr-6 call]]))
trace)]]])
(def eye-icon
[:svg {:width "15"
:height "15"
:viewBox "0 0 15 15"
:fill "none"
:xmlns "http://www.w3.org/2000/svg"}
[:path
{:d "M7.5 11C4.80285 11 2.52952 9.62184 1.09622 7.50001C2.52952 5.37816 4.80285 4 7.5 4C10.1971 4 12.4705 5.37816 13.9038 7.50001C12.4705 9.62183 10.1971 11 7.5 11ZM7.5 3C4.30786 3 1.65639 4.70638 0.0760002 7.23501C-0.0253338 7.39715 -0.0253334 7.60288 0.0760014 7.76501C1.65639 10.2936 4.30786 12 7.5 12C10.6921 12 13.3436 10.2936 14.924 7.76501C15.0253 7.60288 15.0253 7.39715 14.924 7.23501C13.3436 4.70638 10.6921 3 7.5 3ZM7.5 9.5C8.60457 9.5 9.5 8.60457 9.5 7.5C9.5 6.39543 8.60457 5.5 7.5 5.5C6.39543 5.5 5.5 6.39543 5.5 7.5C5.5 8.60457 6.39543 9.5 7.5 9.5Z"
:fill "currentColor"
:fill-rule "evenodd"
:clip-rule "evenodd"}]])

(def eye-closed-icon
[:svg {:width "15"
:height "15"
:viewBox "0 0 15 15"
:fill "none"
:xmlns "http://www.w3.org/2000/svg"}
[:path
{:d "M14.7649 6.07596C14.9991 6.22231 15.0703 6.53079 14.9239 6.76495C14.4849 7.46743 13.9632 8.10645 13.3702 8.66305L14.5712 9.86406C14.7664 10.0593 14.7664 10.3759 14.5712 10.5712C14.3759 10.7664 14.0593 10.7664 13.8641 10.5712L12.6011 9.30817C11.805 9.90283 10.9089 10.3621 9.93375 10.651L10.383 12.3277C10.4544 12.5944 10.2961 12.8685 10.0294 12.94C9.76267 13.0115 9.4885 12.8532 9.41704 12.5865L8.95917 10.8775C8.48743 10.958 8.00036 10.9999 7.50001 10.9999C6.99965 10.9999 6.51257 10.958 6.04082 10.8775L5.58299 12.5864C5.51153 12.8532 5.23737 13.0115 4.97064 12.94C4.7039 12.8686 4.5456 12.5944 4.61706 12.3277L5.06625 10.651C4.09111 10.3621 3.19503 9.90282 2.3989 9.30815L1.1359 10.5712C0.940638 10.7664 0.624058 10.7664 0.428798 10.5712C0.233537 10.3759 0.233537 10.0593 0.428798 9.86405L1.62982 8.66303C1.03682 8.10643 0.515113 7.46742 0.0760677 6.76495C-0.0702867 6.53079 0.000898544 6.22231 0.235065 6.07596C0.469231 5.9296 0.777703 6.00079 0.924058 6.23496C1.40354 7.00213 1.989 7.68057 2.66233 8.2427C2.67315 8.25096 2.6837 8.25972 2.69397 8.26898C4.00897 9.35527 5.65537 9.99991 7.50001 9.99991C10.3078 9.99991 12.6564 8.5063 14.076 6.23495C14.2223 6.00079 14.5308 5.9296 14.7649 6.07596Z"
:fill "currentColor"
:fill-rule "evenodd"
:clip-rule "evenodd"}]])

(defn throwable-view [{:as _error :keys [via trace]} opts]
(let [!stack-expanded? (nextjournal.clerk.render.hooks/use-state false)
!caused-by-expanded? (nextjournal.clerk.render.hooks/use-state false)
unhandled (first via)]
[:div.bg-red-100.text-sm.w-full.border-t.border-red-200.overflow-auto.font-mono
{:style {:max-height "60vh"}}
[:div.px-5.py-4.border-t.border-red-200.first:border-t-0
(when (:type unhandled)
[:div.font-bold.text-red-600 "Unhandled " (:type unhandled)])
[:div.font-bold.mt-1 (:message unhandled)]
(when (:data unhandled)
[:div.mt-1
[nextjournal.clerk.render/inspect (:data unhandled)]])]
(when-let [caused-by (not-empty (rest via))]
[:div.border-t.border-red-200.text-xs.px-5.py-2.first:border-t-0
[:div.text-xs.text-red-600.font-bold.hover:underline.cursor-pointer.flex.items-center.gap-2
{:on-click #(swap! !caused-by-expanded? not)}
(if @!caused-by-expanded? eye-icon eye-closed-icon)
(if @!caused-by-expanded? "Hide" "Show") " causes (" (count caused-by) ")"]
(when @!caused-by-expanded?
(into [:div]
(map
(fn [{:as _ex :keys [type message data _trace]}]
[:div.px-5.mt-3 {:class "ml-[1px]"}
(when type
[:div.font-bold.text-red-600 "Caused by " type])
[:div.font-bold.mt-1 message]
(when data
[:div.mt-1
[nextjournal.clerk.render/inspect data]])]))
caused-by))])
(when trace
[:div.border-t.border-red-200.text-xs.px-5.py-2.first:border-t-0
[:div.text-xs.text-red-600.font-bold.hover:underline.cursor-pointer.flex.items-center.gap-2
{:on-click #(swap! !stack-expanded? not)}
(if @!stack-expanded? eye-icon eye-closed-icon)
(if @!stack-expanded? "Hide" "Show") " stacktrace"]
(when @!stack-expanded?
[:table.w-full.not-prose
(into [:tbody]
(map (fn [[call _x file line]]
[:tr.hover:bg-red-100.leading-tight
[:td.text-right.px-6 file ":"]
[:td.text-right.pr-6 line]
[:td.py-1.pr-6 call]]))
trace)])])]))

(defn render-throwable [ex opts]
(if (or (:stack ex) (instance? js/Error ex))
[error-view ex]
[throwable-view ex opts]))
[:div.rounded.border.border-red-200.border-t-0.overflow-hidden
[throwable-view ex opts]]))

(defn render-tagged-value
([tag value] (render-tagged-value {:space? true} tag value))
Expand Down Expand Up @@ -533,20 +583,46 @@
(fn [more] (swap! !presented-value viewer/merge-presentations more elision))))}
[body-fn* @!presented-value]]))

(defn exception-overlay [title & content]
(into
[:div.fixed.bottom-0.left-0.font-mono.w-screen.z-20
[:div.text-4xl.absolute.left-1
{:style {:transform "rotate(-15deg)"
:text-shadow "0 2px 5px rgba(0,0,0,.1)"
:z-index 1
:top -5}}
(rand-nth ["😩" "😬" "😑" "😖"])]
[:div.flex.ml-7
[:div.pl-4.pr-3.pt-1.rounded-t.bg-red-100.text-red-600.text-sm.font-bold.relative.border-t.border-l.border-r.border-red-200
{:style {:bottom -1}}
title]]]
content))

(defn render-errors-overlay [errors]
[exception-overlay
"Render Errors"
(into [:div]
(map (fn [e]
[throwable-view e]))
errors)])

(defn clojure-exception-overlay [presented-value]
(let [!expanded-at (r/atom {})]
[exception-overlay
"Errors"
[inspect-presented {:!expanded-at !expanded-at} presented-value]]))

(defn root []
[:> ErrorBoundary {:hash @!doc}
[:div.fixed.w-full.z-20.top-0.left-0.w-full
(when-let [status (:nextjournal.clerk.sci-env/connection-status @!doc)]
[connection-status status])
(when-let [status (:status @!doc)]
[exec-status status])]
(when-let [{:as wrapped-value :nextjournal/keys [blob-id]} (get-in @!doc [:nextjournal/value :error])]
(let [!expanded-at (r/atom {})]
^{:key blob-id}
[with-fetch-fn wrapped-value
(fn [presented-value]
[:div.fixed.top-0.left-0.w-full.h-full
[inspect-presented {:!expanded-at !expanded-at} presented-value]])]))
(if-let [{:as wrapped-value :nextjournal/keys [blob-id]} (get-in @!doc [:nextjournal/value :error])]
^{:key blob-id} [with-fetch-fn wrapped-value clojure-exception-overlay]
(when-let [render-errors (not-empty @!render-errors)]
[render-errors-overlay render-errors]))
(when (:nextjournal/value @!doc)
[inspect-presented @!doc])
(into [:<>]
Expand Down
4 changes: 3 additions & 1 deletion src/nextjournal/clerk/sci_env.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@
(try (*eval* form)
(catch js/Error e
(js/console.error "error in viewer-eval" e form)
(ex-info (str "error in viewer-eval: " (.-message e)) {:form form} e))))
(swap! render/!render-errors conj (Throwable->map e))
e)))

(defn ordered-map-reader-cljs [coll]
(omap/ordered-map (vec coll)))
Expand Down Expand Up @@ -245,6 +246,7 @@
:nrepl handle-nrepl})

(defn ^:export onmessage [ws-msg]
(reset! render/!render-errors []) ;; need to reset here since `->viewer-eval` runs on read
(let [{:as msg :keys [type]} (read-string (.-data ws-msg))
dispatch-fn (get message-type->fn
type
Expand Down
Loading