diff --git a/src/day8/re_frame/components.cljs b/src/day8/re_frame/components.cljs new file mode 100644 index 0000000..97a33d6 --- /dev/null +++ b/src/day8/re_frame/components.cljs @@ -0,0 +1,15 @@ +(ns day8.re-frame.trace.components) + +(defn icon-add [] + [:svg.icon.icon-add + {:viewBox "0 0 32 32"} + [:title "add"] + [:path + {:d "M31 12h-11v-11c0-0.552-0.448-1-1-1h-6c-0.552 0-1 0.448-1 1v11h-11c-0.552 0-1 0.448-1 1v6c0 0.552 0.448 1 1 1h11v11c0 0.552 0.448 1 1 1h6c0.552 0 1-0.448 1-1v-11h11c0.552 0 1-0.448 1-1v-6c0-0.552-0.448-1-1-1z"}]]) + +(defn icon-remove [] + [:svg.icon.icon-remove + {:viewBox "0 0 32 32"} + [:title "remove"] + [:path + {:d "M31.708 25.708c-0-0-0-0-0-0l-9.708-9.708 9.708-9.708c0-0 0-0 0-0 0.105-0.105 0.18-0.227 0.229-0.357 0.133-0.356 0.057-0.771-0.229-1.057l-4.586-4.586c-0.286-0.286-0.702-0.361-1.057-0.229-0.13 0.048-0.252 0.124-0.357 0.228 0 0-0 0-0 0l-9.708 9.708-9.708-9.708c-0-0-0-0-0-0-0.105-0.104-0.227-0.18-0.357-0.228-0.356-0.133-0.771-0.057-1.057 0.229l-4.586 4.586c-0.286 0.286-0.361 0.702-0.229 1.057 0.049 0.13 0.124 0.252 0.229 0.357 0 0 0 0 0 0l9.708 9.708-9.708 9.708c-0 0-0 0-0 0-0.104 0.105-0.18 0.227-0.229 0.357-0.133 0.355-0.057 0.771 0.229 1.057l4.586 4.586c0.286 0.286 0.702 0.361 1.057 0.229 0.13-0.049 0.252-0.124 0.357-0.229 0-0 0-0 0-0l9.708-9.708 9.708 9.708c0 0 0 0 0 0 0.105 0.105 0.227 0.18 0.357 0.229 0.356 0.133 0.771 0.057 1.057-0.229l4.586-4.586c0.286-0.286 0.362-0.702 0.229-1.057-0.049-0.13-0.124-0.252-0.229-0.357z"}]]) diff --git a/src/day8/re_frame/styles.cljs b/src/day8/re_frame/styles.cljs new file mode 100644 index 0000000..796e4c0 --- /dev/null +++ b/src/day8/re_frame/styles.cljs @@ -0,0 +1,97 @@ +(ns day8.re-frame.trace.styles) + +(defonce panel-styles " +#--re-frame-trace-- { + background: white; + color: black; + font-family: 'courier new', monospace; +} +#--re-frame-trace-- tbody { + color: #aaa; +} +#--re-frame-trace-- tr:nth-child(even) { + background: aliceblue; +} +#--re-frame-trace-- .button { + padding: 5px 5px 3px; + margin: 5px; + border-radius: 2px; + cursor: pointer; +} +#--re-frame-trace-- .text-button { + border-bottom: 1px dotted #888; + font-weight: normal; +} +#--re-frame-trace-- .button:focus, .text-button:focus { + border-radius: 2px 2px 0 0; + -webkit-box-shadow: inset 0px -5px 0px 0px rgba(0,0,0,0.3); + -moz-box-shadow: inset 0px -5px 0px 0px rgba(0,0,0,0.3); + box-shadow: inset 0px -5px 0px 0px rgba(0,0,0,0.3); +} +#--re-frame-trace-- .icon-button { + font-size: 10px; +} +#--re-frame-trace-- button.tab { + +} +#--re-frame-trace-- .tab { + background: transparent; + border-radius: 0; + text-transform: uppercase; + font-family: monospace; + letter-spacing: 2px; + margin-bottom: 0; + padding-bottom: 4px; + vertical-align: bottom; +} +#--re-frame-trace-- .tab.active { + background: transparent; + border-bottom: 3px solid lightblue; + border-radius: 0; + padding-bottom: 1px; +} +#--re-frame-trace-- ul.filter-items { + list-style-type: none; + padding: 0; + margin: 0 -5px; +} +#--re-frame-trace-- .filter-items li { + color: #333; + background: #efefef; + display: inline-block; + font-size: 0.9em; + margin: 5px; +} +#--re-frame-trace-- .filter-items li .filter-item-string { + color: #616cdb; +} +#--re-frame-trace-- .icon { + display: inline-block; + width: 1em; + height: 1em; + stroke-width: 0; + stroke: currentColor; + fill: currentColor; +} +#--re-frame-trace-- .icon-remove { + margin-left: 10px; +} +#--re-frame-trace-- select { + background: white; + font-family: 'courier new', monospace; + font-size: 1em; +} +#--re-frame-trace-- .nav { + background: #efeef1; + color: #222; +} +#--re-frame-trace-- .panel-content-top { + flex: 1; +} +#--re-frame-trace-- .panel-content-scrollable { + padding: 10px; + flex: 1 0 auto; + height: 100%; + overflow: auto; +} +") diff --git a/src/day8/re_frame/trace.cljs b/src/day8/re_frame/trace.cljs index 2435584..dc2c43c 100644 --- a/src/day8/re_frame/trace.cljs +++ b/src/day8/re_frame/trace.cljs @@ -1,5 +1,7 @@ (ns day8.re-frame.trace (:require [day8.re-frame.trace.subvis :as subvis] + [day8.re-frame.trace.styles :as styles] + [day8.re-frame.trace.components :as components] [re-frame.trace :as trace :include-macros true] [cljs.pprint :as pprint] [clojure.string :as str] @@ -12,8 +14,8 @@ [goog.object :as gob] [re-frame.interop :as interop] - [devtools.formatters.core :as devtools] - )) + [devtools.formatters.core :as devtools])) + (defn comp-name [c] (let [n (or (component/component-path c) @@ -61,9 +63,9 @@ (trace/with-trace {:op-type :render :tags {:component-path (reagent.impl.component/component-path c)} :operation (last (str/split name #" > "))} - (real-renderer c) + (real-renderer c))))) + - )))) (set! reagent.impl.component/static-fns static-fns) @@ -88,8 +90,8 @@ #_(set! reagent.impl.batching/schedule schedule #_(fn [] (reagent.impl.batching/do-after-render (fn [] (trace/with-trace {:op-type :raf-end}))) - (real-schedule))) - )) + (real-schedule))))) + (def traces (interop/ratom [])) (defn log-trace? [trace] @@ -113,80 +115,124 @@ (defn init-tracing! "Sets up any intial state that needs to be there for tracing. Does not enable tracing." [] - (monkey-patch-reagent) - ) + (monkey-patch-reagent)) -(defn search-input [{:keys [title on-save on-stop]}] + +(defn search-input [{:keys [title on-save on-change on-stop]}] (let [val (r/atom title) - save #(let [v (-> @val str clojure.string/trim)] - (on-save v))] + save #(let [v (-> @val str str/trim)] + (when (pos? (count v)) + (on-save v)))] (fn [] - [:input {:type "text" + [:input {:style {:margin-left 7} + :type "text" :value @val :auto-focus true - :on-blur save - :on-change #(reset! val (-> % .-target .-value)) + :on-change #(do (reset! val (-> % .-target .-value)) + (on-change %)) :on-key-down #(case (.-which %) - 13 (save) + 13 (do + (save) + (reset! val "")) nil)}]))) +(defn query->fn [query] + (if (= :contains (:filter-type query)) + (fn [trace] + (str/includes? (str/lower-case (str (:operation trace) " " (:op-type trace))) + (:query query))) + (fn [trace] + (< (:query query) (:duration trace))))) -(defn render-traces [] - (let [search (r/atom "") - slower-than-ms (r/atom "") - slower-than-bold (r/atom "")] +(defn render-traces [showing-traces] + (doall + (for [{:keys [op-type id operation tags duration] :as trace} showing-traces] + (let [padding {:padding "0px 5px 0px 5px"} + row-style (merge padding {:border-top (case op-type :event "1px solid lightgrey" nil)}) + #_#__ (js/console.log (devtools/header-api-call tags))] + (list [:tr {:key id + :style {:color (case op-type + :sub/create "green" + :sub/run "#fd701e" + :event "blue" + :render "purple" + :re-frame.router/fsm-trigger "#fd701e" + nil)}} + [:td {:style row-style} (str op-type)] + [:td {:style row-style} operation] + [:td + {:style (merge row-style { + ; :font-weight (if (< slower-than-bold-int duration) + ; "bold" + ; "") + :white-space "nowrap"})} + + (.toFixed duration 1) " ms"]] + (when true + [:tr {:key (str id "-details")} + [:td {:col-span 3} (with-out-str (pprint/pprint (dissoc tags :query-v :event :duration)))]])))))) + +(defn render-trace-panel [] + (let [filter-input (r/atom "") + filter-items (r/atom []) + filter-type (r/atom :contains) + input-error (r/atom false)] (fn [] - (let [slower-than-ms-int (js/parseInt @slower-than-ms) - slower-than-bold-int (js/parseInt @slower-than-bold) - op-filter (when-not (str/blank? @search) - (filter #(str/includes? (str (:operation %) " " (:op-type %)) @search))) - ms-filter (when-not (str/blank? @slower-than-ms) - (filter #(< slower-than-ms-int (:duration %)))) - transducers (apply comp (remove nil? [ms-filter op-filter])) - showing-traces (sequence transducers @traces) - - filter-msg (if (and (str/blank? @search) (str/blank? @slower-than-ms)) - (str "Filter " (count @traces) " events: ") - (str "Filtering " (count showing-traces) " of " (count @traces) " events:")) - padding {:padding "0px 5px 0px 5px"}] + (let [showing-traces (if (= @filter-items []) + @traces + (filter (apply every-pred (map query->fn @filter-items)) @traces)) + save-query (fn [_] + (if (and (= @filter-type :slower-than) + (js/isNaN (js/parseFloat @filter-input))) + (reset! input-error true) + (do + (reset! input-error false) + (swap! filter-items conj {:id (random-uuid) + :query (if (= @filter-type :contains) + (str/lower-case @filter-input) + (js/parseFloat @filter-input)) + :filter-type @filter-type}))))] [:div - {:style {:padding "10px"}} - [:h1 "TRACES"] - [:span filter-msg [:button {:on-click #(do (trace/reset-tracing!) (reset! traces []))} " Clear traces"]] [:br] - [:span "Filter events " [search-input {:on-save #(reset! search %)}]] [:br] - [:span "Filter slower than " [search-input {:on-save #(reset! slower-than-ms %)}] "ms "] [:br] - [:span "Bold slower than " [search-input {:on-save #(reset! slower-than-bold %)}] "ms "] + [:div.filter-control {:style {:margin-bottom 20}} + [:div.filter-control-input + {:style {:margin-bottom 10}} + [:select {:value @filter-type + :on-change #(reset! filter-type (keyword (.. % -target -value)))} + [:option {:value "contains"} "contains"] + [:option {:value "slower-than"} "slower than"]] + [search-input {:on-save save-query + :on-change #(reset! filter-input (.. % -target -value))}] + [:button.button.icon-button {:on-click save-query + :style {:margin 0}} + [components/icon-add]] + (if @input-error + [:div.input-error {:style {:color "red" :margin-top 5}} + "Please enter a valid number."]) + [:br]] + [:ul.filter-items + (map (fn [item] + ^{:key (:id item)} + [:li.filter-item + [:button.button + {:style {:margin 0} + :on-click (fn [event] (swap! filter-items #(remove (comp (partial = (:query item)) :query) %)))} + (:filter-type item) ": " [:span.filter-item-string (:query item)] + [:span.icon-button [components/icon-remove]]]]) + @filter-items)]] [:table {:cell-spacing "0" :width "100%"} [:thead>tr - [:th "op"] - [:th "event"] + [:th "operations"] + [:th + (when (pos? (count @filter-items)) + (str (count showing-traces) " of ")) + (when (pos? (count @traces)) + (str (count @traces))) + " events " + (when (pos? (count @traces)) + [:span "(" [:button.text-button {:on-click #(do (trace/reset-tracing!) (reset! traces []))} "clear"] ")"])] [:th "meta"]] - [:tbody - (doall - (for [{:keys [op-type id operation tags duration] :as trace} showing-traces] - (let [row-style (merge padding {:border-top (case op-type :event "1px solid lightgrey" nil)}) - #_#__ (js/console.log (devtools/header-api-call tags)) - ] - (list [:tr {:key id - :style {:color (case op-type - :sub/create "green" - :sub/run "red" - :event "blue" - :render "purple" - :re-frame.router/fsm-trigger "red" - nil)}} - [:td {:style row-style} (str op-type)] - [:td {:style row-style} operation] - [:td - {:style (merge row-style {:font-weight (if (< slower-than-bold-int duration) - "bold" - "")})} - (.toFixed duration 1) " ms"]] - (when true - [:tr {:key (str id "-details")} - [:td {:col-span 3} (with-out-str (pprint/pprint (dissoc tags :query-v :event :duration)))]]) - ))))]]])))) + [:tbody (render-traces showing-traces)]]])))) (defn resizer-style [draggable-area] {:position "absolute" :z-index 2 :opacity 0 @@ -198,7 +244,7 @@ ;; Add clear button ;; Filter out different trace types (let [position (r/atom :right) - size (r/atom 0.3) + size (r/atom 0.35) showing? (r/atom false) dragging? (r/atom false) pin-to-bottom? (r/atom true) @@ -229,27 +275,35 @@ transition (if @showing? ease-transition (str ease-transition ", opacity 0.01s linear 0.2s"))] - [:div {:style {:position "fixed" :width "0px" :height "0px" :top "0px" :left "0px" :z-index 99999999}} - [:div {:style {:position "fixed" :z-index 1 :box-shadow "rgba(0, 0, 0, 0.298039) 0px 0px 4px" :background "white" - :left left :top "0px" :width (str (* 100 @size) "%") :height "100%" - :transition transition}} - [:div.resizer {:style (resizer-style draggable-area) - :on-mouse-down #(reset! dragging? true) - :on-mouse-up #(reset! dragging? false) - :on-mouse-move (fn [e] - (when @dragging? - (let [x (.-clientX e) - y (.-clientY e)] - (.preventDefault e) - (reset! size (/ (- full-width x) - full-width)))))}] - [:div {:style {:width "100%" :height "100%" :overflow "auto"}} - [:button {:on-click #(reset! selected-tab :traces)} "Traces"] - [:button {:on-click #(reset! selected-tab :subvis)} "SubVis"] - (case @selected-tab - :traces [render-traces] - :subvis [subvis/render-subvis traces] - [render-traces])]]]))}))) + [:div.panel-wrapper + {:style {:position "fixed" :width "0px" :height "0px" :top "0px" :left "0px" :z-index 99999999}} + [:div.panel + {:style {:position "fixed" :z-index 1 :box-shadow "rgba(0, 0, 0, 0.298039) 0px 0px 4px" :background "white" + :left left :top "0px" :width (str (* 100 @size) "%") :height "100%" + :transition transition}} + [:div.panel-resizer {:style (resizer-style draggable-area) + :on-mouse-down #(reset! dragging? true) + :on-mouse-up #(reset! dragging? false) + :on-mouse-move (fn [e] + (when @dragging? + (let [x (.-clientX e) + y (.-clientY e)] + (.preventDefault e) + (reset! size (/ (- full-width x) + full-width)))))}] + [:div.panel-content + {:style {:width "100%" :height "100%" :display "flex" :flex-direction "column"}} + [:div.panel-content-top + [:div.nav + [:button {:class (str "tab button " (when (= @selected-tab :traces) "active")) + :on-click #(reset! selected-tab :traces)} "Traces"] + [:button {:class (str "tab button " (when (= @selected-tab :subvis) "active")) + :on-click #(reset! selected-tab :subvis)} "SubVis"]]] + [:div.panel-content-scrollable + (case @selected-tab + :traces [render-trace-panel] + :subvis [subvis/render-subvis traces] + [render-trace-panel])]]]]))}))) (defn panel-div [] (let [id "--re-frame-trace--" @@ -259,7 +313,26 @@ (let [new-panel (.createElement js/document "div")] (.setAttribute new-panel "id" id) (.appendChild (.-body js/document) new-panel) + (js/window.focus new-panel) new-panel)))) +(defn inject-styles [] + (let [id "--re-frame-trace-styles--" + styles-el (.getElementById js/document id) + new-styles-el (.createElement js/document "style") + new-styles styles/panel-styles] + (.setAttribute new-styles-el "id" id) + (-> new-styles-el + (.-innerHTML) + (set! new-styles)) + (if styles-el + (-> styles-el + (.-parentNode) + (.replaceChild new-styles-el styles-el)) + (let [] + (.appendChild (.-head js/document) new-styles-el) + new-styles-el)))) + (defn inject-devtools! [] + (inject-styles) (r/render [devtools] (panel-div))) diff --git a/src/day8/re_frame/trace/subvis.cljs b/src/day8/re_frame/trace/subvis.cljs index eb37e1d..f651224 100644 --- a/src/day8/re_frame/trace/subvis.cljs +++ b/src/day8/re_frame/trace/subvis.cljs @@ -43,8 +43,6 @@ simulation-a (atom nil)] (fn [] [:div - {:style {:padding "10px"}} - [:h1 "SUBVIS"] [d3t/create-d3 {:render-component (fn [ratom] [:svg#d3cmp {:width width :height height}]) @@ -184,9 +182,8 @@ (.. simulation (restart) - (alpha 0.3))))) - ] - ))))} + (alpha 0.3)))))]))))} + + traces-ratom] [:hr]]))) -