(ns figwheel.client.heads-up (:require [clojure.string :as string] [figwheel.client.socket :as socket] [figwheel.client.utils :as utils] [cljs.core.async :refer [put! chan (.-body js/document) (.appendChild el)))) { :container-el (.getElementById js/document cont-id) :content-area-el (.getElementById js/document content-id) } )) (defn set-style! [{:keys [container-el]} st-map] (mapv (fn [[k v]] (gobj/set (.-style container-el) (name k) v)) st-map)) (defn set-content! [{:keys [content-area-el] :as c} dom-str] (set! (.-innerHTML content-area-el) dom-str)) (defn get-content [{:keys [content-area-el]}] (.-innerHTML content-area-el)) (defn close-link [] (str "" "x" "")) (defn display-heads-up [style msg] (go (let [c (ensure-container)] (set-style! c (merge { :paddingTop "10px" :paddingBottom "10px" :width "100%" :minHeight "68px" :opacity "1.0" } style)) (set-content! c msg) (" s " " sub-head ""))) (defn file-selector-div [file-name line-number column-number msg] (str "
" msg "
")) (defn format-line [msg {:keys [file line column]}] (let [msg (goog.string/htmlEscape msg)] (if (or file line) (file-selector-div file line column msg) (str "
" msg "
")))) (defn escape [x] (goog.string/htmlEscape x)) (defn pad-line-number [n line-number] (let [len (count ((fnil str "") line-number))] (-> (if (< len n) (apply str (repeat (- n len) " ")) "") (str line-number)))) (defn inline-error-line [style line-number line] (str "" "" line-number " " (escape line) "")) (defn format-inline-error-line [[typ line-number line]] (condp = typ :code-line (inline-error-line "color: #999;" line-number line) :error-in-code (inline-error-line "color: #ccc; font-weight: bold;" line-number line) :error-message (inline-error-line "color: #D07D7D;" line-number line) (inline-error-line "color: #666;" line-number line))) (defn pad-line-numbers [inline-error] (let [max-line-number-length (count (str (reduce max (map second inline-error))))] (map #(update-in % [1] (partial pad-line-number max-line-number-length)) inline-error))) (defn format-inline-error [inline-error] (let [lines (map format-inline-error-line (pad-line-numbers inline-error))] (str "
"
         (string/join "\n" lines)
         "
"))) (def flatten-exception #(take-while some? (iterate :cause %))) (defn exception->display-data [{:keys [failed-loading-clj-file failed-compiling reader-exception analysis-exception display-ex-data class file line column message error-inline] :as exception}] (let [last-message (cond (and file line) (str "Please see line " line " of file " file ) file (str "Please see " file) :else nil)] {:head (cond failed-loading-clj-file "Couldn't load Clojure file" analysis-exception "Could not Analyze" reader-exception "Could not Read" failed-compiling "Could not Compile" :else "Compile Exception") :sub-head file :messages (concat (map #(str "
" % "
") (if message [(str (if class (str (escape class) ": ") "") "" (escape message) "") (when display-ex-data (str "
" (utils/pprint-to-string display-ex-data) "
")) (when (pos? (count error-inline)) (format-inline-error error-inline))] (map #(str (escape (:class %)) ": " (escape (:message %))) (flatten-exception (:exception-data exception))))) (when last-message [(str "
" (escape last-message) "
")])) :file file :line line :column column})) (defn auto-notify-source-file-line [{:keys [file line column]}] (socket/send! {:figwheel-event "file-selected" :file-name (str file) :file-line (str line) :file-column (str column)})) (defn display-exception [exception-data] (let [{:keys [head sub-head messages last-message file line column]} (-> exception-data exception->display-data) msg (apply str messages #_(map #(str "
" (goog.string/htmlEscape %) "
") messages))] (display-heads-up {:backgroundColor "rgba(255, 161, 161, 0.95)"} (str (close-link) (heading head sub-head) (file-selector-div file line column msg))))) (defn warning-data->display-data [{:keys [file line column message error-inline] :as warning-data}] (let [last-message (cond (and file line) (str "Please see line " line " of file " file ) file (str "Please see " file) :else nil)] {:head "Compile Warning" :sub-head file :messages (concat (map #(str "
" % "
") [(when message (str "" (escape message) "")) (when (pos? (count error-inline)) (format-inline-error error-inline))]) (when last-message [(str "
" (escape last-message) "
")])) :file file :line line :column column})) (defn display-system-warning [header msg] (display-heads-up {:backgroundColor "rgba(255, 220, 110, 0.95)" } (str (close-link) (heading header) "
" msg "
" #_(format-line msg {})))) (defn display-warning [warning-data] (let [{:keys [head sub-head messages last-message file line column]} (-> warning-data warning-data->display-data) msg (apply str messages)] (display-heads-up {:backgroundColor "rgba(255, 220, 110, 0.95)" } (str (close-link) (heading head sub-head) (file-selector-div file line column msg))))) (defn format-warning-message [{:keys [message file line column] :as warning-data}] (cond-> message line (str " at line " line) (and line column) (str ", column " column) file (str " in file " file)) ) (defn append-warning-message [{:keys [message file line column] :as warning-data}] (when message (let [{:keys [content-area-el]} (ensure-container) el (dom/createElement "div") child-count (.-length (dom/getChildren content-area-el))] (if (< child-count 6) (do (set! (.-innerHTML el) (format-line (format-warning-message warning-data) warning-data)) (dom/append content-area-el el)) (when-let [last-child (dom/getLastElementChild content-area-el)] (if-let [message-count (data/get last-child "figwheel_count")] (let [message-count (inc (js/parseInt message-count))] (data/set last-child "figwheel_count" message-count) (set! (.-innerHTML last-child) (str message-count " more warnings have not been displayed ..."))) (dom/append content-area-el (dom/createDom "div" #js {:data-figwheel_count 1 :style "margin-top: 3px; font-weight: bold"} "1 more warning that has not been displayed ...")))))))) (defn clear [] (go (let [c (ensure-container)] (set-style! c { :opacity "0.0" }) ( ") ;; ---- bad compile helper ui ---- (defn close-bad-compile-screen [] (when-let [el (js/document.getElementById "figwheelFailScreen")] (dom/removeNode el))) (defn bad-compile-screen [] (let [body (-> (dom/getElementsByTagNameAndClass "body") (aget 0))] (close-bad-compile-screen) #_(dom/removeChildren body) (dom/append body (dom/createDom "div" #js {:id "figwheelFailScreen" :style (str "background-color: rgba(24, 26, 38, 0.95);" "position: absolute;" "z-index: 9000;" "width: 100vw;" "height: 100vh;" "top: 0px; left: 0px;" "font-family: monospace")} (dom/createDom "div" #js {:class "message" :style (str "color: #FFF5DB;" "width: 100vw;" "margin: auto;" "margin-top: 10px;" "text-align: center; " "padding: 2px 0px;" "font-size: 13px;" "position: relative")} (dom/createDom "a" #js {:onclick (fn [e] (.preventDefault e) (close-bad-compile-screen)) :href "javascript:" :style "position: absolute; right: 10px; top: 10px; color: #666"} "X") (dom/createDom "h2" #js {:style "color: #FFF5DB"} "Figwheel Says: Your code didn't compile.") (dom/createDom "div" #js {:style "font-size: 12px"} (dom/createDom "p" #js { :style "color: #D07D7D;"} "Keep trying. This page will auto-refresh when your code compiles successfully.") ))))))