From f7bdf6e414c09e4c673178abe92816313871ffe2 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Thu, 21 Dec 2017 14:57:39 +1300 Subject: [PATCH 01/60] Update styling to use blue modern --- README.md | 2 + .../re_frame/trace/images/open-external.svg | 2 +- .../day8/re_frame/trace/images/pause.svg | 1 + .../day8/re_frame/trace/images/wrench.svg | 1 + src/day8/re_frame/trace/common_styles.cljs | 200 ++++++++++++++++++ src/day8/re_frame/trace/styles.cljs | 39 ++-- src/day8/re_frame/trace/subs.cljs | 4 +- src/day8/re_frame/trace/view/app_db.cljs | 37 +++- src/day8/re_frame/trace/view/container.cljs | 62 +++--- 9 files changed, 292 insertions(+), 56 deletions(-) create mode 100644 resources/day8/re_frame/trace/images/pause.svg create mode 100644 resources/day8/re_frame/trace/images/wrench.svg diff --git a/README.md b/README.md index 66d3403..5b7b88c 100644 --- a/README.md +++ b/README.md @@ -107,3 +107,5 @@ If you want to work on re-frame-trace, see [DEVELOPERS.md](DEVELOPERS.md). * [Camera](https://thenounproject.com/search/?q=snapshot&i=200965) by Christian Shannon from the Noun Project * [Delete](https://thenounproject.com/term/delete/926276) by logan from the Noun Project * [Settings](https://thenounproject.com/search/?q=settings&i=1169241) by arjuazka from the Noun Project +* [Wrench](https://thenounproject.com/icon/1013218/) by Aleksandr Vector from the Noun Project +* [pause](https://thenounproject.com/icon/1376662/) by Bhuvan from the Noun Project diff --git a/resources/day8/re_frame/trace/images/open-external.svg b/resources/day8/re_frame/trace/images/open-external.svg index 9c4a951..9e62c1d 100644 --- a/resources/day8/re_frame/trace/images/open-external.svg +++ b/resources/day8/re_frame/trace/images/open-external.svg @@ -1,3 +1,3 @@ - + diff --git a/resources/day8/re_frame/trace/images/pause.svg b/resources/day8/re_frame/trace/images/pause.svg new file mode 100644 index 0000000..8663b96 --- /dev/null +++ b/resources/day8/re_frame/trace/images/pause.svg @@ -0,0 +1 @@ +13 diff --git a/resources/day8/re_frame/trace/images/wrench.svg b/resources/day8/re_frame/trace/images/wrench.svg new file mode 100644 index 0000000..654209e --- /dev/null +++ b/resources/day8/re_frame/trace/images/wrench.svg @@ -0,0 +1 @@ + diff --git a/src/day8/re_frame/trace/common_styles.cljs b/src/day8/re_frame/trace/common_styles.cljs index 5ae1326..9d1bbb4 100644 --- a/src/day8/re_frame/trace/common_styles.cljs +++ b/src/day8/re_frame/trace/common_styles.cljs @@ -20,3 +20,203 @@ (def event-color dark-gold) (def subs-color dark-purple) (def render-color dark-skyblue) + +;; The colors defined below are (of course) available to your app without further ado +;; +;; However... +;; +;; To get access to the styles, your code needs to add the following requires: +;; +;; [day8.apps-lib.ux.blue-modern :as bm] +;; [day8.apps-lib.ux.stylesheet :as stylesheet] +;; +;; And the following line to the `mount-gui` function: +;; +;; (stylesheet/inject-garden-stylesheet! bm/blue-modern "blue-modern") +;; +;; Then to use the styles, simply add the corresponding names to the `:class` arg of your components: +;; +;; [rc/box +;; :class "standard-background" +;; :child [rc/button +;; :class "strong-button" +;; :label "Add"]] + + +;; ================================================================================================= +;; Blue modern component colours +;; ================================================================================================= + +(def blue-modern-color "#6EC0E6") ;; Our standard rich blue colour + +(def white-background-color "white") +(def white-background-border-color "#E3E9ED") ;; Light grey + +(def standard-background-color "#F3F6F7") ;; Light grey +(def standard-background-border-color "transparent") + +(def light-background-color "#FBFBFB") ;; Medium grey +(def light-background-border-color "#BFCCD6") ;; Slightly darker than medium grey + +(def dark-background-color "#768895") ;; Darker grey +(def dark-background-border-color white-background-border-color) + +(def border-line-color "#DCE3E8") ;; Slightly darker than light grey +(def table-row-line-color "#EAEEF1") ;; Light grey + +(def text-title-color "#3C454B") ;; Darker grey than the standard text color +(def default-text-color "#767A7C") ;; Medium grey + +;(def disabled-text-color "TBA???") ;; Placeholder (currently not specified) +(def disabled-background-color "#ECEDF0") ;; Light grey +(def disabled-border-color border-line-color) + +(def strong-button-text-color "white") +(def strong-button-background-color blue-modern-color) +(def strong-button-border-color "#589AB8") ;; A darker version of the standard blue + +(def muted-button-text-color strong-button-background-color) +(def muted-button-background-color "white") +(def muted-button-border-color white-background-border-color) + +(def hyperlink-text-color strong-button-background-color) + +(def tab-underline-color strong-button-background-color) + +(def sidebar-background-color "#32323C") ;; Dark black +(def sidebar-heading-divider-color "#191919") ;; Darker black +(def sidebar-item-selected-color "#3C3C45") ;; Slightly lighter dark black +(def sidebar-item-check-color strong-button-background-color) +(def sidebar-text-color "white") + +(def wizard-panel-background-color "#636A6F") ;; Very dark grey +(def wizard-panel-text-color "white") +(def wizard-nav-button-background-color "white") +(def wizard-nav-button-text-color "#303234") ;; Almost black (also used for button arrows) +(def wizard-cancel-button-background-color "#D6D8D9") ;; Light grey +(def wizard-step-past-color "#E8FFC1") ;; Muted lime green +(def wizard-step-current-color "#C7FF66") ;; Bright lime green +(def wizard-step-future-color dark-background-color) + +(def font-stack ["\"Segoe UI\"" "Roboto", "Helvetica", "sans-serif"]) + +;; ================================================================================================= +;; Blue modern component styles (in garden format) +;; ================================================================================================= + +(def blue-modern + + [;; ========== Specific blue-modern styles (must be added to :class arg) + :#--re-frame-trace-- + [:.bm-white-background {:background-color white-background-color + :border (str "1px solid " white-background-border-color)}] + [:.bm-standard-background {:background-color standard-background-color + :border (str "1px solid " standard-background-border-color)}] + [:.bm-light-background {:background-color light-background-color + :border (str "1px solid " light-background-border-color)}] + [:.bm-dark-background {:background-color dark-background-color + :border (str "1px solid " dark-background-border-color)}] + + [:.bm-title-text {:font-size "26px" + :color text-title-color + :-webkit-user-select "none" + :cursor "default"}] + [:.bm-heading-text {:font-size "16px" + :color default-text-color + :-webkit-user-select "none" + :cursor "default"}] + [:.bm-body-text {:color default-text-color}] + [:.bm-textbox-label {:font-variant "small-caps" + :color default-text-color + :-webkit-user-select "none" + :cursor "default"}] + + [:.bm-strong-button {:color strong-button-text-color + :background-color strong-button-background-color + :border (str "1px solid " strong-button-border-color)}] + [:.bm-muted-button {:color muted-button-text-color + :background-color muted-button-background-color + :border (str "1px solid " strong-button-border-color)}] + + [:.bm-disabled-button {;:color disabled-text-color (not yet defined) + :background-color disabled-background-color + :border (str "1px solid " strong-button-border-color)}] + + [:.bm-popover-content-wrapper + [:> + [:.popover + [:> + [:.popover-arrow + [:polyline {:fill (str standard-background-color " !important")}]]]]]] + ;; TODO: When there is a title section, the top left and right radius can be seen + [:.bm-popover-content-wrapper + [:> + [:.popover + [:> + [:.popover-content {:background-color standard-background-color + :border-radius "6px"}]]]]] + + ;; ========== General overrides to convert re-com/bootstrap components to blue modern automatically + + ;; Default text color overrides + [:body {:color default-text-color}] + [:.form-control {:color default-text-color}] + [:.btn-default {:color default-text-color}] + [:.raptor-editable-block {:color default-text-color}] + + ;; button components - to 26px high + [:button {:height "26px"}] + [:.btn {:padding "0px 12px"}] + + ;; input-text - set to 26px high + [:.rc-input-text + [:input {:height "26px"}]] + + ;; input-time - set to 26px high + [:.rc-input-time {:height "26px"}] + + ;; hyperlink components - set color + [:a.rc-hyperlink + :a.rc-hyperlink-href {:color hyperlink-text-color}] + + ;; title - set color + [:.rc-title {:color default-text-color + :cursor "default" + :-webkit-user-select "none"}] + + ;; single-dropdown - 26px high (and color it) + [:.chosen-container-single + [:.chosen-single {:height "26px" + :line-height "24px"}]] + [:.chosen-container-single + [:.chosen-single + [:div {:top "-4px"}]]] + [:.rc-dropdown {:align-self "initial !important"}] + [:.chosen-container-single + [:.chosen-default {:color default-text-color}]] + [:.chosen-container + [:.chosen-results {:color default-text-color}]] + + ;; selection-list - set background color of container + [:.rc-selection-list {:background-color "white"}] ; + + ;; datepicker-dropdowns - set to 26px high + [:.dropdown-button {:height "26px"}] + [:.dropdown-button + [:.zmdi-apps {:font-size "19px !important"}]] + [:.form-control.dropdown-button {:padding "3px 12px"}] + + ;; rc-tabs - color + [:.nav-tabs + [:> + [:li.active + [:> + [:a {:color default-text-color} + [:&:hover {:color default-text-color}]]]]]] + [:.btn-default + [:&:hover :&:focus :&:active {:color default-text-color}]] + [:.btn-default.active {:color default-text-color}] + [:.open + [:> + [:.dropdown-toggle.btn-default {:color default-text-color}]]] + ]) diff --git a/src/day8/re_frame/trace/styles.cljs b/src/day8/re_frame/trace/styles.cljs index 28dec04..210ff63 100644 --- a/src/day8/re_frame/trace/styles.cljs +++ b/src/day8/re_frame/trace/styles.cljs @@ -7,7 +7,6 @@ [day8.re-frame.trace.common-styles :as common] [day8.re-frame.trace.utils.re-com :as rc])) -(def background-blue common/background-blue) (def background-gray common/background-gray) (def background-gray-hint common/background-gray-hint) (def dark-green common/dark-green) @@ -58,12 +57,12 @@ [:img {:border-style "none"}] [:option {:display "block"}] [:button :input :optgroup :select :textarea - {:font-family ["\"courier new\"" "monospace"] + {:font-family common/font-stack :font-size (percent 100) :padding [[(px 3) (px 3) (px 1) (px 3)]] :border [[(px 1) "solid" medium-gray]]}] [:button :input {:overflow "visible"}] - [:button :select [(s/& s/focus) {:outline [[medium-gray "dotted" (px 1)]]}]] + #_[:button :select [(s/& s/focus) {:outline [[medium-gray "dotted" (px 1)]]}]] [:button (s/html (s/attr= "type" "button")) (s/attr= "type" "reset") @@ -134,8 +133,8 @@ (def re-frame-trace-styles [:#--re-frame-trace-- - {:background "white" - :font-family ["'courier new'" "monospace"] + {:background-color common/background-gray + :font-family common/font-stack :color text-color} [:.label label-mixin] @@ -221,8 +220,7 @@ :border-radius "2px" #_ #_ :cursor "pointer"}] [:.text-button {:border-bottom "1px dotted #888" - :font-weight "normal"} - [(s/& s/focus) {:outline [[medium-gray "dotted" (px 1)]]}]] + :font-weight "normal"}] [:.icon-button {:font-size "10px"}] [:button.tab {}] @@ -237,18 +235,14 @@ [:.tab {:background "transparent" :border-radius 0 - :text-transform "uppercase" - :font-family "monospace" - :letter-spacing "2px" + :font-family common/font-stack :margin-bottom 0 :padding-bottom "4px" :vertical-align "bottom"}] [:.tab.active {:background "transparent" - :border-bottom [[(px 3) "solid" dark-gray]] - :border-radius 0 - :padding-bottom (px 1)}] + :color common/blue-modern-color}] [:ul.filter-items :.subtrees {:list-style-type "none" @@ -278,7 +272,7 @@ :border-bottom [[(px 1) "solid" text-color-muted]] :background "white" :display "inline-block" - :font-family "'courier new', monospace" + :font-family common/font-stack :font-size (em 1) :padding "2px 0 0 0" :-moz-appearance "menulist" @@ -296,8 +290,19 @@ [:.filter-control-input {:display "flex" :flex "0 0 auto"}] - [:.nav {:background light-gray - :color text-color}] + [:.nav {:background common/sidebar-background-color + :height (px 50) + :color "white"} + [:span.arrow {:color common/blue-modern-color + :background-color common/standard-background-color + :padding (px 5) + :margin (px 5)}] + [:span.event-header {:color common/text-color + :background-color common/standard-background-color + :padding (px 5) + :margin (px 5) + :font-weight "600"}] + ] [(s/& :.external-window) {:display "flex" :height (percent 100) :flex "1 1 auto"}] @@ -333,7 +338,7 @@ ]) -(def panel-styles (apply garden/css [css-reset (into [:#--re-frame-trace--] rc/re-com-css) re-frame-trace-styles])) +(def panel-styles (apply garden/css [css-reset (into [:#--re-frame-trace--] rc/re-com-css) common/blue-modern re-frame-trace-styles])) ;(def panel-styles (macros/slurp-macro "day8/re_frame/trace/main.css")) diff --git a/src/day8/re_frame/trace/subs.cljs b/src/day8/re_frame/trace/subs.cljs index d726300..6e0f969 100644 --- a/src/day8/re_frame/trace/subs.cljs +++ b/src/day8/re_frame/trace/subs.cljs @@ -22,7 +22,9 @@ :settings/selected-tab :<- [:settings/root] (fn [settings _] - (get settings :selected-tab))) + (if (:showing-settings? settings) + :settings + (get settings :selected-tab)))) ;; App DB diff --git a/src/day8/re_frame/trace/view/app_db.cljs b/src/day8/re_frame/trace/view/app_db.cljs index 65e1144..a2bb465 100644 --- a/src/day8/re_frame/trace/view/app_db.cljs +++ b/src/day8/re_frame/trace/view/app_db.cljs @@ -10,12 +10,18 @@ (:require-macros [day8.re-frame.trace.utils.macros :as macros])) (def delete (macros/slurp-macro "day8/re_frame/trace/images/delete.svg")) +(def reload (macros/slurp-macro "day8/re_frame/trace/images/reload.svg")) +(def reload-disabled (macros/slurp-macro "day8/re_frame/trace/images/reload-disabled.svg")) +(def snapshot (macros/slurp-macro "day8/re_frame/trace/images/snapshot.svg")) +(def snapshot-ready (macros/slurp-macro "day8/re_frame/trace/images/snapshot-ready.svg")) + (defn render-state [data] - (let [subtree-input (r/atom "") - subtree-paths (rf/subscribe [:app-db/paths]) - search-string (rf/subscribe [:app-db/search-string]) - input-error (r/atom false)] + (let [subtree-input (r/atom "") + subtree-paths (rf/subscribe [:app-db/paths]) + search-string (rf/subscribe [:app-db/search-string]) + input-error (r/atom false) + snapshot-ready? (rf/subscribe [:snapshot/snapshot-ready?])] (fn [] [:div {:style {:flex "1 1 auto" :display "flex" :flex-direction "column"}} [:div.panel-content-scrollable @@ -30,6 +36,25 @@ ; [:div.input-error {:style {:color "red" :margin-top 5}} ; "Please enter a valid path."])]] + [rc/h-box + :children + [[:img.nav-icon + {:title "Load app-db snapshot" + :class (when-not @snapshot-ready? "inactive") + :src (str "data:image/svg+xml;utf8," + (if @snapshot-ready? + reload + reload-disabled)) + :on-click #(when @snapshot-ready? (rf/dispatch-sync [:snapshot/load-snapshot]))}] + [:img.nav-icon + {:title "Snapshot app-db" + :class (when @snapshot-ready? "active") + :src (str "data:image/svg+xml;utf8," + (if @snapshot-ready? + snapshot-ready + snapshot)) + :on-click #(rf/dispatch-sync [:snapshot/save-snapshot])}]]] + [:div.subtrees {:style {:margin "20px 0"}} (doall (map (fn [path] @@ -46,8 +71,8 @@ (str path)]] [:img {:src (str "data:image/svg+xml;utf8," delete) - :style {:cursor "pointer" - :height "10px"} + :style {:cursor "pointer" + :height "10px"} :on-click #(rf/dispatch [:app-db/remove-path path])}]]] [path]]]]) @subtree-paths))] diff --git a/src/day8/re_frame/trace/view/container.cljs b/src/day8/re_frame/trace/view/container.cljs index 88145a7..c257c65 100644 --- a/src/day8/re_frame/trace/view/container.cljs +++ b/src/day8/re_frame/trace/view/container.cljs @@ -8,29 +8,27 @@ [day8.re-frame.trace.view.settings :as settings] [re-frame.trace] [reagent.core :as r] - [day8.re-frame.trace.utils.re-com :as rc])) + [day8.re-frame.trace.utils.re-com :as rc] + [day8.re-frame.trace.common-styles :as common])) (defn tab-button [panel-id title] (let [selected-tab @(rf/subscribe [:settings/selected-tab])] - [:button {:class (str "tab button " (when (= selected-tab panel-id) "active")) + [:button {:class (str "tab button bm-heading-text " (when (= selected-tab panel-id) "active")) :on-click #(rf/dispatch [:settings/selected-tab panel-id])} title])) -(def reload (macros/slurp-macro "day8/re_frame/trace/images/reload.svg")) -(def reload-disabled (macros/slurp-macro "day8/re_frame/trace/images/reload-disabled.svg")) (def open-external (macros/slurp-macro "day8/re_frame/trace/images/open-external.svg")) -(def snapshot (macros/slurp-macro "day8/re_frame/trace/images/snapshot.svg")) -(def snapshot-ready (macros/slurp-macro "day8/re_frame/trace/images/snapshot-ready.svg")) -(def settings-svg (macros/slurp-macro "day8/re_frame/trace/images/settings.svg")) +(def settings-svg (macros/slurp-macro "day8/re_frame/trace/images/wrench.svg")) +(def pause-svg (macros/slurp-macro "day8/re_frame/trace/images/pause.svg")) (defn devtools-inner [traces opts] (let [selected-tab (rf/subscribe [:settings/selected-tab]) panel-type (:panel-type opts) external-window? (= panel-type :popup) unloading? (rf/subscribe [:global/unloading?]) - snapshot-ready? (rf/subscribe [:snapshot/snapshot-ready?])] + show-tabs? (not= @selected-tab :settings)] [:div.panel-content - {:style {:width "100%" :display "flex" :flex-direction "column"}} + {:style {:width "100%" :display "flex" :flex-direction "column" :background-color common/standard-background-color}} [rc/h-box :class "panel-content-top nav" :justify :between @@ -38,40 +36,42 @@ [[rc/h-box :align :center :children - [(tab-button :traces "Traces") - (tab-button :app-db "App DB") - (tab-button :subs "Subs") - #_[:img.nav-icon - {:title "Settings" - :src (str "data:image/svg+xml;utf8," - settings-svg) - :on-click #(rf/dispatch [:settings/selected-tab :settings])}]]] - - + [[:span.arrow "◀"] + [:span.event-header "[:some-namespace/blah 34 \"Hello\""] + [:span.arrow "▶"]]] [rc/h-box :align :center :children [[:img.nav-icon - {:title "Load app-db snapshot" - :class (when-not @snapshot-ready? "inactive") + {:title "Pause" :src (str "data:image/svg+xml;utf8," - (if @snapshot-ready? - reload - reload-disabled)) - :on-click #(when @snapshot-ready? (rf/dispatch-sync [:snapshot/load-snapshot]))}] + pause-svg) + :on-click #(rf/dispatch [:settings/selected-tab :settings])}] [:img.nav-icon - {:title "Snapshot app-db" - :class (when @snapshot-ready? "active") + {:title "Settings" :src (str "data:image/svg+xml;utf8," - (if @snapshot-ready? - snapshot-ready - snapshot)) - :on-click #(rf/dispatch-sync [:snapshot/save-snapshot])}] + settings-svg) + :on-click #(rf/dispatch [:settings/toggle-settings])}] (when-not external-window? [:img.nav-icon.active {:src (str "data:image/svg+xml;utf8," open-external) :on-click #(rf/dispatch-sync [:global/launch-external])}])]]]] + (when show-tabs? + [rc/h-box + :class "panel-content-tabs" + :justify :between + :children + [[rc/h-box + :align :center + :children + [(tab-button :overview "Overview") + (tab-button :app-db "app-db") + (tab-button :subs "Subs") + (tab-button :views "Views") + (tab-button :traces "Trace")]] + ]]) + [rc/line :style {:margin "0px 10px"}] (when (and external-window? @unloading?) [:h1.host-closed "Host window has closed. Reopen external window to continue tracing."]) (when-not (re-frame.trace/is-trace-enabled?) From a5c052fd5965a3f1fa49f5bb01def2f2ca6c6b2c Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Thu, 21 Dec 2017 14:57:46 +1300 Subject: [PATCH 02/60] Toggle settings --- src/day8/re_frame/trace/db.cljs | 2 +- src/day8/re_frame/trace/events.cljs | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/day8/re_frame/trace/db.cljs b/src/day8/re_frame/trace/db.cljs index 91f0bac..1a54b79 100644 --- a/src/day8/re_frame/trace/db.cljs +++ b/src/day8/re_frame/trace/db.cljs @@ -5,7 +5,7 @@ (defn init-db [] (let [panel-width% (localstorage/get "panel-width-ratio" 0.35) show-panel? (localstorage/get "show-panel" false) - selected-tab (localstorage/get "selected-tab" :traces) + selected-tab (localstorage/get "selected-tab" :overview) filter-items (localstorage/get "filter-items" []) app-db-paths (localstorage/get "app-db-paths" '()) json-ml-paths (localstorage/get "app-db-json-ml-expansions" #{}) diff --git a/src/day8/re_frame/trace/events.cljs b/src/day8/re_frame/trace/events.cljs index f02c7b6..dacacc6 100644 --- a/src/day8/re_frame/trace/events.cljs +++ b/src/day8/re_frame/trace/events.cljs @@ -65,6 +65,11 @@ (localstorage/save! "selected-tab" selected-tab) (assoc-in db [:settings :selected-tab] selected-tab))) +(rf/reg-event-db + :settings/toggle-settings + (fn [db _] + (update-in db [:settings :showing-settings?] not))) + (rf/reg-event-db :settings/show-panel? (fn [db [_ show-panel?]] From 77463897413b75afb3f325a096dd31078fd1cef0 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Thu, 21 Dec 2017 15:00:34 +1300 Subject: [PATCH 03/60] Add placeholder namespaces for view and overview panels --- src/day8/re_frame/trace/view/container.cljs | 8 ++++++-- src/day8/re_frame/trace/view/overview.cljs | 4 ++++ src/day8/re_frame/trace/view/views.cljs | 3 +++ 3 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 src/day8/re_frame/trace/view/overview.cljs create mode 100644 src/day8/re_frame/trace/view/views.cljs diff --git a/src/day8/re_frame/trace/view/container.cljs b/src/day8/re_frame/trace/view/container.cljs index c257c65..c761eb3 100644 --- a/src/day8/re_frame/trace/view/container.cljs +++ b/src/day8/re_frame/trace/view/container.cljs @@ -2,9 +2,11 @@ (:require-macros [day8.re-frame.trace.utils.macros :as macros]) (:require [mranderson047.re-frame.v0v10v2.re-frame.core :as rf] [re-frame.db :as db] + [day8.re-frame.trace.view.overview :as overview] [day8.re-frame.trace.view.app-db :as app-db] - [day8.re-frame.trace.view.traces :as traces] [day8.re-frame.trace.view.subs :as subs] + [day8.re-frame.trace.view.views :as views] + [day8.re-frame.trace.view.traces :as traces] [day8.re-frame.trace.view.settings :as settings] [re-frame.trace] [reagent.core :as r] @@ -77,8 +79,10 @@ (when-not (re-frame.trace/is-trace-enabled?) [:h1.host-closed {:style {:word-wrap "break-word"}} "Tracing is not enabled. Please set " [:pre "{\"re_frame.trace.trace_enabled_QMARK_\" true}"] " in " [:pre ":closure-defines"]]) (case @selected-tab - :traces [traces/render-trace-panel traces] + :overview [overview/render] :app-db [app-db/render-state db/app-db] :subs [subs/subs-panel] + :views [views/render] + :traces [traces/render-trace-panel traces] :settings [settings/render] [app-db/render-state db/app-db])])) diff --git a/src/day8/re_frame/trace/view/overview.cljs b/src/day8/re_frame/trace/view/overview.cljs new file mode 100644 index 0000000..689c15c --- /dev/null +++ b/src/day8/re_frame/trace/view/overview.cljs @@ -0,0 +1,4 @@ +(ns day8.re-frame.trace.view.overview) + +(defn render [] + ) diff --git a/src/day8/re_frame/trace/view/views.cljs b/src/day8/re_frame/trace/view/views.cljs new file mode 100644 index 0000000..645ba0f --- /dev/null +++ b/src/day8/re_frame/trace/view/views.cljs @@ -0,0 +1,3 @@ +(ns day8.re-frame.trace.view.views) + +(defn render []) From 38756239a739e4743e1084361b4e2a64ca6efe50 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Thu, 21 Dec 2017 16:48:02 +1300 Subject: [PATCH 04/60] Fill out settings panel --- src/day8/re_frame/trace/common_styles.cljs | 29 +++++- src/day8/re_frame/trace/events.cljs | 17 +++- src/day8/re_frame/trace/styles.cljs | 12 +-- .../re_frame/trace/utils/localstorage.cljs | 14 ++- src/day8/re_frame/trace/utils/re_com.cljs | 90 +++++++++++++++++++ src/day8/re_frame/trace/view/container.cljs | 30 ++++--- src/day8/re_frame/trace/view/settings.cljs | 37 +++++++- 7 files changed, 207 insertions(+), 22 deletions(-) diff --git a/src/day8/re_frame/trace/common_styles.cljs b/src/day8/re_frame/trace/common_styles.cljs index 9d1bbb4..45f984d 100644 --- a/src/day8/re_frame/trace/common_styles.cljs +++ b/src/day8/re_frame/trace/common_styles.cljs @@ -1,4 +1,5 @@ -(ns day8.re-frame.trace.common-styles) +(ns day8.re-frame.trace.common-styles + (:require [garden.units :refer [px em]])) (def background-blue "#e7f1ff") (def background-gray "#a8a8a8") @@ -21,6 +22,29 @@ (def subs-color dark-purple) (def render-color dark-skyblue) +;; Golden section, base 50 +(def gs-5 (px 5)) +(def gs-7 (px 7)) +(def gs-12 (px 12)) +(def gs-19 (px 19)) +(def gs-31 (px 31)) +(def gs-50 (px 50)) +(def gs-81 (px 81)) +(def gs-131 (px 131)) + +;; TODO: figure out how to cast gs-* into strings, rather than manually making them here. +(def gs-5s "5px") +(def gs-7s "7px") +(def gs-12s "12px") +(def gs-19s "19px") +(def gs-31s "31px") +(def gs-50s "50px") +(def gs-81s "81px") +(def gs-131s "131px") + + + + ;; The colors defined below are (of course) available to your app without further ado ;; ;; However... @@ -121,7 +145,8 @@ :color text-title-color :-webkit-user-select "none" :cursor "default"}] - [:.bm-heading-text {:font-size "16px" + [:.bm-heading-text {:font-size "19px" + :font-weight "600" :color default-text-color :-webkit-user-select "none" :cursor "default"}] diff --git a/src/day8/re_frame/trace/events.cljs b/src/day8/re_frame/trace/events.cljs index dacacc6..7f08619 100644 --- a/src/day8/re_frame/trace/events.cljs +++ b/src/day8/re_frame/trace/events.cljs @@ -14,7 +14,8 @@ (defonce total-traces (r/atom 0)) (defn log-trace? [trace] - (let [render-operation? (= (:op-type trace) :render) + (let [render-operation? (or (= (:op-type trace) :render) + (= (:op-type trace) :componentWillUnmount)) component-path (get-in trace [:tags :component-path] "")] (if-not render-operation? true @@ -76,6 +77,20 @@ (localstorage/save! "show-panel" show-panel?) (assoc-in db [:settings :show-panel?] show-panel?))) +(rf/reg-event-db + :settings/factory-reset + (fn [db _] + (localstorage/delete-all-keys!) + (js/location.reload) + db)) + +(rf/reg-event-db + :settings/clear-epochs + (fn [db _] + (reset! traces []) + (reset! total-traces 0) + db)) + (rf/reg-event-db :settings/user-toggle-panel (fn [db _] diff --git a/src/day8/re_frame/trace/styles.cljs b/src/day8/re_frame/trace/styles.cljs index 210ff63..d518834 100644 --- a/src/day8/re_frame/trace/styles.cljs +++ b/src/day8/re_frame/trace/styles.cljs @@ -235,14 +235,17 @@ [:.tab {:background "transparent" :border-radius 0 + :margin "10px 0 0 0" :font-family common/font-stack - :margin-bottom 0 :padding-bottom "4px" :vertical-align "bottom"}] [:.tab.active {:background "transparent" - :color common/blue-modern-color}] + :color common/blue-modern-color + :border-bottom [[(px 3) "solid" common/blue-modern-color]] + :border-radius 0 + :padding-bottom (px 1)}] [:ul.filter-items :.subtrees {:list-style-type "none" @@ -295,18 +298,17 @@ :color "white"} [:span.arrow {:color common/blue-modern-color :background-color common/standard-background-color - :padding (px 5) - :margin (px 5)}] + :padding (px 5)}] [:span.event-header {:color common/text-color :background-color common/standard-background-color :padding (px 5) - :margin (px 5) :font-weight "600"}] ] [(s/& :.external-window) {:display "flex" :height (percent 100) :flex "1 1 auto"}] [:.panel-content-top {}] + [:.panel-content-tabs {:margin-left common/gs-19}] [:.panel-content-scrollable panel-mixin] [:.epoch-panel panel-mixin] [:.tab-contents {:display "flex" diff --git a/src/day8/re_frame/trace/utils/localstorage.cljs b/src/day8/re_frame/trace/utils/localstorage.cljs index 7de4ef4..cd92f10 100644 --- a/src/day8/re_frame/trace/utils/localstorage.cljs +++ b/src/day8/re_frame/trace/utils/localstorage.cljs @@ -1,14 +1,17 @@ (ns day8.re-frame.trace.utils.localstorage (:require [goog.storage.Storage :as Storage] [goog.storage.mechanism.HTML5LocalStorage :as html5localstore] - [cljs.reader :as reader]) + [cljs.reader :as reader] + [clojure.string :as str]) (:refer-clojure :exclude [get])) (def storage (goog.storage.Storage. (goog.storage.mechanism.HTML5LocalStorage.))) +(def safe-prefix "day8.re-frame.trace.") + (defn- safe-key [key] "Adds a unique prefix to local storage keys to ensure they don't collide with the host application" - (str "day8.re-frame.trace." key)) + (str safe-prefix key)) (defn get "Gets a re-frame-trace value from local storage." @@ -24,3 +27,10 @@ "Saves a re-frame-trace value to local storage." [key value] (.set storage (safe-key key) (pr-str value))) + +(defn delete-all-keys! + "Deletes all re-frame-trace config keys" + [] + (doseq [k (js/Object.keys js/localStorage)] + (when (str/starts-with? k safe-prefix) + (.remove storage k)))) diff --git a/src/day8/re_frame/trace/utils/re_com.cljs b/src/day8/re_frame/trace/utils/re_com.cljs index 9855df4..432ad66 100644 --- a/src/day8/re_frame/trace/utils/re_com.cljs +++ b/src/day8/re_frame/trace/utils/re_com.cljs @@ -203,6 +203,78 @@ attr)] children))) +(defn scroll-style + "Determines the value for the 'overflow' attribute. + The scroll parameter is a keyword. + Because we're translating scroll into overflow, the keyword doesn't appear to match the attribute value" + [attribute scroll] + {attribute (case scroll + :auto "auto" + :off "hidden" + :on "scroll" + :spill "visible")}) + + +(defn- box-base + "This should generally NOT be used as it is the basis for the box, scroller and border components" + [& {:keys [size scroll h-scroll v-scroll width height min-width min-height max-width max-height justify align align-self + margin padding border l-border r-border t-border b-border radius bk-color child class-name class style attr]}] + (let [s (merge + (flex-flow-style "inherit") + (flex-child-style size) + (when scroll (scroll-style :overflow scroll)) + (when h-scroll (scroll-style :overflow-x h-scroll)) + (when v-scroll (scroll-style :overflow-y v-scroll)) + (when width {:width width}) + (when height {:height height}) + (when min-width {:min-width min-width}) + (when min-height {:min-height min-height}) + (when max-width {:max-width max-width}) + (when max-height {:max-height max-height}) + (when justify (justify-style justify)) + (when align (align-style :align-items align)) + (when align-self (align-style :align-self align-self)) + (when margin {:margin margin}) ;; margin and padding: "all" OR "top&bottom right&left" OR "top right bottom left" + (when padding {:padding padding}) + (when border {:border border}) + (when l-border {:border-left l-border}) + (when r-border {:border-right r-border}) + (when t-border {:border-top t-border}) + (when b-border {:border-bottom b-border}) + (when radius {:border-radius radius}) + (when bk-color + {:background-color bk-color}) + style)] + [:div + (merge + {:class (str class-name "display-flex " class) :style s} + attr) + child])) + +(defn box + "Returns hiccup which produces a box, which is generally used as a child of a v-box or an h-box. + By default, it also acts as a container for further child compenents, or another h-box or v-box" + [& {:keys [size width height min-width min-height max-width max-height justify align align-self margin padding child class style attr] + :or {size "none"} + :as args}] + (box-base :size size + :width width + :height height + :min-width min-width + :min-height min-height + :max-width max-width + :max-height max-height + :justify justify + :align align + :align-self align-self + :margin margin + :padding padding + :child child + :class-name "rc-box " + :class class + :style style + :attr attr)) + (defn line "Returns a component which produces a line between children in a v-box/h-box along the main axis. Specify size in pixels and a stancard CSS color. Defaults to a 1px lightgray line" @@ -297,6 +369,24 @@ [& args] (apply input-text-base :input-type :input args)) +(defn label + "Returns markup for a basic label" + [& {:keys [label on-click width class style attr] + :as args}] + [box + :class "rc-label-wrapper display-inline-flex" + :width width + :align :start + :child [:span + (merge + {:class (str "rc-label " class) + :style (merge (flex-child-style "none") + style)} + (when on-click + {:on-click (handler-fn (on-click))}) + attr) + label]]) + (def re-com-css [[:.display-flex {:display "flex"}] [:.display-inline-flex {:display "flex"}]]) diff --git a/src/day8/re_frame/trace/view/container.cljs b/src/day8/re_frame/trace/view/container.cljs index c761eb3..b90d621 100644 --- a/src/day8/re_frame/trace/view/container.cljs +++ b/src/day8/re_frame/trace/view/container.cljs @@ -8,6 +8,8 @@ [day8.re-frame.trace.view.views :as views] [day8.re-frame.trace.view.traces :as traces] [day8.re-frame.trace.view.settings :as settings] + [garden.core :refer [css style]] + [garden.units :refer [px]] [re-frame.trace] [reagent.core :as r] [day8.re-frame.trace.utils.re-com :as rc] @@ -23,6 +25,8 @@ (def settings-svg (macros/slurp-macro "day8/re_frame/trace/images/wrench.svg")) (def pause-svg (macros/slurp-macro "day8/re_frame/trace/images/pause.svg")) +(def outer-margins {:margin (str "0px " common/gs-19s)}) + (defn devtools-inner [traces opts] (let [selected-tab (rf/subscribe [:settings/selected-tab]) panel-type (:panel-type opts) @@ -33,10 +37,12 @@ {:style {:width "100%" :display "flex" :flex-direction "column" :background-color common/standard-background-color}} [rc/h-box :class "panel-content-top nav" + :style {:padding "0px 19px"} :justify :between :children [[rc/h-box :align :center + :gap common/gs-12s :children [[:span.arrow "◀"] [:span.event-header "[:some-namespace/blah 34 \"Hello\""] @@ -48,7 +54,7 @@ {:title "Pause" :src (str "data:image/svg+xml;utf8," pause-svg) - :on-click #(rf/dispatch [:settings/selected-tab :settings])}] + :on-click #(rf/dispatch [:settings/pause])}] [:img.nav-icon {:title "Settings" :src (str "data:image/svg+xml;utf8," @@ -65,6 +71,7 @@ :justify :between :children [[rc/h-box + :gap "7px" :align :center :children [(tab-button :overview "Overview") @@ -73,16 +80,19 @@ (tab-button :views "Views") (tab-button :traces "Trace")]] ]]) - [rc/line :style {:margin "0px 10px"}] + [rc/line :style outer-margins] (when (and external-window? @unloading?) [:h1.host-closed "Host window has closed. Reopen external window to continue tracing."]) (when-not (re-frame.trace/is-trace-enabled?) [:h1.host-closed {:style {:word-wrap "break-word"}} "Tracing is not enabled. Please set " [:pre "{\"re_frame.trace.trace_enabled_QMARK_\" true}"] " in " [:pre ":closure-defines"]]) - (case @selected-tab - :overview [overview/render] - :app-db [app-db/render-state db/app-db] - :subs [subs/subs-panel] - :views [views/render] - :traces [traces/render-trace-panel traces] - :settings [settings/render] - [app-db/render-state db/app-db])])) + [rc/v-box + :size "auto" + :children + [(case @selected-tab + :overview [overview/render] + :app-db [app-db/render-state db/app-db] + :subs [subs/subs-panel] + :views [views/render] + :traces [traces/render-trace-panel traces] + :settings [settings/render] + [app-db/render-state db/app-db])]]])) diff --git a/src/day8/re_frame/trace/view/settings.cljs b/src/day8/re_frame/trace/view/settings.cljs index a7e0fad..ae037d1 100644 --- a/src/day8/re_frame/trace/view/settings.cljs +++ b/src/day8/re_frame/trace/view/settings.cljs @@ -1,6 +1,39 @@ -(ns day8.re-frame.trace.view.settings) +(ns day8.re-frame.trace.view.settings + (:require [mranderson047.re-frame.v0v10v2.re-frame.core :as rf] + [day8.re-frame.trace.utils.re-com :as rc] + [day8.re-frame.trace.common-styles :as common])) (defn render [] - [:h1 "Settings"] + [rc/v-box + :gap common/gs-19s + :children + [[rc/label + :label "Settings" + :class "bm-title-text"] + [rc/label + :label "Limits" + :class "bm-heading-text"] + + [rc/label + :label "Event Filters" + :class "bm-heading-text" + ] + + [rc/label + :label "View Filters" + :class "bm-heading-text"] + + [rc/label + :label "Low Level Trace Filters" + :class "bm-heading-text"] + + [rc/label + :label "Reset" + :class "bm-heading-text"] + + [:button {:on-click #(rf/dispatch [:settings/clear-epochs])} "Clear Epochs"] + [:button {:on-click #(rf/dispatch [:settings/factory-reset])} "Factory Reset"] + [:p "Will refresh page"] + ]] ) From a3806a8afb0d796db6f4ae2a0ffe43abb2150fec Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Thu, 21 Dec 2017 20:48:26 +1300 Subject: [PATCH 05/60] Update logos and styles --- README.md | 1 + .../day8/re_frame/trace/images/logout.svg | 1 + .../day8/re_frame/trace/images/pause.svg | 2 +- .../day8/re_frame/trace/images/wrench.svg | 15 ++++++++- src/day8/re_frame/trace/styles.cljs | 9 +++-- src/day8/re_frame/trace/utils/re_com.cljs | 33 +++++++++++++++++++ src/day8/re_frame/trace/view/container.cljs | 4 +-- src/day8/re_frame/trace/view/overview.cljs | 18 ++++++++-- src/day8/re_frame/trace/view/settings.cljs | 3 ++ src/day8/re_frame/trace/view/subs.cljs | 13 ++++++++ 10 files changed, 91 insertions(+), 8 deletions(-) create mode 100644 resources/day8/re_frame/trace/images/logout.svg diff --git a/README.md b/README.md index 5b7b88c..270bfb7 100644 --- a/README.md +++ b/README.md @@ -109,3 +109,4 @@ If you want to work on re-frame-trace, see [DEVELOPERS.md](DEVELOPERS.md). * [Settings](https://thenounproject.com/search/?q=settings&i=1169241) by arjuazka from the Noun Project * [Wrench](https://thenounproject.com/icon/1013218/) by Aleksandr Vector from the Noun Project * [pause](https://thenounproject.com/icon/1376662/) by Bhuvan from the Noun Project +* [Log Out](https://thenounproject.com/icon/54484/) by Arthur Shlain from the Noun Project diff --git a/resources/day8/re_frame/trace/images/logout.svg b/resources/day8/re_frame/trace/images/logout.svg new file mode 100644 index 0000000..006ffae --- /dev/null +++ b/resources/day8/re_frame/trace/images/logout.svg @@ -0,0 +1 @@ + diff --git a/resources/day8/re_frame/trace/images/pause.svg b/resources/day8/re_frame/trace/images/pause.svg index 8663b96..4ab761a 100644 --- a/resources/day8/re_frame/trace/images/pause.svg +++ b/resources/day8/re_frame/trace/images/pause.svg @@ -1 +1 @@ -13 +13 diff --git a/resources/day8/re_frame/trace/images/wrench.svg b/resources/day8/re_frame/trace/images/wrench.svg index 654209e..222e66e 100644 --- a/resources/day8/re_frame/trace/images/wrench.svg +++ b/resources/day8/re_frame/trace/images/wrench.svg @@ -1 +1,14 @@ - + +Vector +Created using Figma + + + + + + + + + diff --git a/src/day8/re_frame/trace/styles.cljs b/src/day8/re_frame/trace/styles.cljs index d518834..c938065 100644 --- a/src/day8/re_frame/trace/styles.cljs +++ b/src/day8/re_frame/trace/styles.cljs @@ -68,6 +68,11 @@ (s/attr= "type" "reset") (s/attr= "type" "submit") {:-webkit-appearance "button"}] +"input[type=\"checkbox\" i] {\n -webkit-appearance: checkbox;\n box-sizing: border-box;\n}" + [:input + (s/attr= "type" "checkbox") + {:-webkit-appearance "checkbox" + :box-sizing "border-box"}] [:button:-moz-focusring (s/attr= "type" "button") @@ -225,8 +230,8 @@ [:.icon-button {:font-size "10px"}] [:button.tab {}] [:.nav-icon - {:width "15px" - :height "15px" + {:width "30px" + :height "30px" :cursor "pointer" :padding "0 5px" :margin "0 5px"} diff --git a/src/day8/re_frame/trace/utils/re_com.cljs b/src/day8/re_frame/trace/utils/re_com.cljs index 432ad66..8c03877 100644 --- a/src/day8/re_frame/trace/utils/re_com.cljs +++ b/src/day8/re_frame/trace/utils/re_com.cljs @@ -387,6 +387,39 @@ attr) label]]) +(defn checkbox + "I return the markup for a checkbox, with an optional RHS label" + [& {:keys [model on-change label disabled? label-class label-style class style attr] + :as args}] + (let [cursor "default" + model (deref-or-value model) + disabled? (deref-or-value disabled?) + callback-fn #(when (and on-change (not disabled?)) + (on-change (not model)))] ;; call on-change with either true or false + [h-box + :class "rc-checkbox-wrapper noselect" + :align :start + :children [[:input + (merge + {:class (str "rc-checkbox " class) + :type "checkbox" + :style (merge (flex-child-style "none") + {:cursor cursor} + style) + :disabled disabled? + :checked (boolean model) + :on-change (handler-fn (callback-fn))} + attr)] + (when label + [:span + {:class label-class + :style (merge (flex-child-style "none") + {:padding-left "8px" + :cursor cursor} + label-style) + :on-click (handler-fn (callback-fn))} + label])]])) + (def re-com-css [[:.display-flex {:display "flex"}] [:.display-inline-flex {:display "flex"}]]) diff --git a/src/day8/re_frame/trace/view/container.cljs b/src/day8/re_frame/trace/view/container.cljs index b90d621..5b2d233 100644 --- a/src/day8/re_frame/trace/view/container.cljs +++ b/src/day8/re_frame/trace/view/container.cljs @@ -20,8 +20,7 @@ [:button {:class (str "tab button bm-heading-text " (when (= selected-tab panel-id) "active")) :on-click #(rf/dispatch [:settings/selected-tab panel-id])} title])) -(def open-external (macros/slurp-macro "day8/re_frame/trace/images/open-external.svg")) - +(def open-external (macros/slurp-macro "day8/re_frame/trace/images/logout.svg")) (def settings-svg (macros/slurp-macro "day8/re_frame/trace/images/wrench.svg")) (def pause-svg (macros/slurp-macro "day8/re_frame/trace/images/pause.svg")) @@ -87,6 +86,7 @@ [:h1.host-closed {:style {:word-wrap "break-word"}} "Tracing is not enabled. Please set " [:pre "{\"re_frame.trace.trace_enabled_QMARK_\" true}"] " in " [:pre ":closure-defines"]]) [rc/v-box :size "auto" + :style {:margin-left common/gs-19s} :children [(case @selected-tab :overview [overview/render] diff --git a/src/day8/re_frame/trace/view/overview.cljs b/src/day8/re_frame/trace/view/overview.cljs index 689c15c..39bf572 100644 --- a/src/day8/re_frame/trace/view/overview.cljs +++ b/src/day8/re_frame/trace/view/overview.cljs @@ -1,4 +1,18 @@ -(ns day8.re-frame.trace.view.overview) +(ns day8.re-frame.trace.view.overview + (:require [day8.re-frame.trace.utils.re-com :as rc])) (defn render [] - ) + [rc/v-box + :children + [[rc/label :label "Event"] + [rc/label :label "Dispatch Point"] + [rc/label :label "Coeffects"] + [rc/label :label "Effects"] + [rc/label :label "Interceptors"] + + [rc/h-box + :children [[:p "Subs Run"] [:p "Created"] [:p "Destroyed"]]] + [:p "Views Rendered"] + [rc/h-box + :children [[:p "Timing"] [:p "Animation Frames"]]] + ]]) diff --git a/src/day8/re_frame/trace/view/settings.cljs b/src/day8/re_frame/trace/view/settings.cljs index ae037d1..105e939 100644 --- a/src/day8/re_frame/trace/view/settings.cljs +++ b/src/day8/re_frame/trace/view/settings.cljs @@ -28,6 +28,9 @@ :label "Low Level Trace Filters" :class "bm-heading-text"] + [rc/checkbox :model false :on-change #(rf/dispatch [:settings/low-level-trace :reagent %]) :label "reagent internals"] + [rc/checkbox :model false :on-change #(rf/dispatch [:settings/low-level-trace :re-frame %]) :label "re-frame internals"] + [rc/label :label "Reset" :class "bm-heading-text"] diff --git a/src/day8/re_frame/trace/view/subs.cljs b/src/day8/re_frame/trace/view/subs.cljs index 524e3f2..ebdb1b4 100644 --- a/src/day8/re_frame/trace/view/subs.cljs +++ b/src/day8/re_frame/trace/view/subs.cljs @@ -18,6 +18,19 @@ (defn subs-panel [] [] [:div {:style {:flex "1 1 auto" :display "flex" :flex-direction "column"}} + [rc/label + :label "Created" + :class "bm-heading-text"] + [rc/label + :label "Run" + :class "bm-heading-text"] + [rc/label + :label "Destroyed" + :class "bm-heading-text"] + [rc/label + :label "Not Run" + :class "bm-heading-text"] + [:div.panel-content-scrollable [:div.subtrees {:style {:margin "20px 0"}} (doall From 2b62d0e2e471f66a5412c042b03812ce95978f53 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Fri, 22 Dec 2017 11:41:39 +1300 Subject: [PATCH 06/60] Fixup checkbox styling --- src/day8/re_frame/trace/styles.cljs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/day8/re_frame/trace/styles.cljs b/src/day8/re_frame/trace/styles.cljs index c938065..1b840c8 100644 --- a/src/day8/re_frame/trace/styles.cljs +++ b/src/day8/re_frame/trace/styles.cljs @@ -68,11 +68,9 @@ (s/attr= "type" "reset") (s/attr= "type" "submit") {:-webkit-appearance "button"}] -"input[type=\"checkbox\" i] {\n -webkit-appearance: checkbox;\n box-sizing: border-box;\n}" - [:input - (s/attr= "type" "checkbox") + [(s/input (s/attr= "type" "checkbox")) {:-webkit-appearance "checkbox" - :box-sizing "border-box"}] + :box-sizing "border-box"}] [:button:-moz-focusring (s/attr= "type" "button") From 76f8147f1d7b822b25e1bd8e81ac3ed4e965460a Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Fri, 22 Dec 2017 11:44:53 +1300 Subject: [PATCH 07/60] Reformat styles --- src/day8/re_frame/trace/styles.cljs | 31 ++++++++++++++++------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/day8/re_frame/trace/styles.cljs b/src/day8/re_frame/trace/styles.cljs index 1b840c8..463c143 100644 --- a/src/day8/re_frame/trace/styles.cljs +++ b/src/day8/re_frame/trace/styles.cljs @@ -90,7 +90,7 @@ :-webkit-font-smoothing "inherit" :letter-spacing "inherit" :background "none" - #_ #_ :cursor "pointer"}] + #_#_:cursor "pointer"}] [:img {:max-width (percent 100) :height "auto" :border "0"}] @@ -136,9 +136,9 @@ (def re-frame-trace-styles [:#--re-frame-trace-- - {:background-color common/background-gray - :font-family common/font-stack - :color text-color} + {:background-color common/background-gray + :font-family common/font-stack + :color text-color} [:.label label-mixin] @@ -221,7 +221,7 @@ [:.button {:padding "5px 5px 3px" :margin "5px" :border-radius "2px" - #_ #_ :cursor "pointer"}] + #_#_:cursor "pointer"}] [:.text-button {:border-bottom "1px dotted #888" :font-weight "normal"}] @@ -238,14 +238,15 @@ [:.tab {:background "transparent" :border-radius 0 - :margin "10px 0 0 0" + :margin "10px 0 0 0" :font-family common/font-stack :padding-bottom "4px" - :vertical-align "bottom"}] + :vertical-align "bottom" + :cursor "pointer"}] [:.tab.active {:background "transparent" - :color common/blue-modern-color + :color common/blue-modern-color :border-bottom [[(px 3) "solid" common/blue-modern-color]] :border-radius 0 :padding-bottom (px 1)}] @@ -297,15 +298,17 @@ {:display "flex" :flex "0 0 auto"}] [:.nav {:background common/sidebar-background-color - :height (px 50) + :height (px 50) :color "white"} - [:span.arrow {:color common/blue-modern-color + [:span.arrow {:color common/blue-modern-color ;; Should this be a button instead of a span? :background-color common/standard-background-color - :padding (px 5)}] - [:span.event-header {:color common/text-color + :padding (px 5) + :cursor "pointer" + :user-select "none"}] + [:span.event-header {:color common/text-color :background-color common/standard-background-color - :padding (px 5) - :font-weight "600"}] + :padding (px 5) + :font-weight "600"}] ] [(s/& :.external-window) {:display "flex" :height (percent 100) From ad7d7393c7f95eb62798c2930e3b371b07574b2d Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Fri, 22 Dec 2017 16:40:43 +1300 Subject: [PATCH 08/60] Add metamorphic event processing --- .gitignore | 1 + project.clj | 3 +- src/day8/re_frame/trace.cljs | 1 + src/day8/re_frame/trace/metamorphic.clj | 95 +++++++++++++++++++ test/day8/re_frame/trace/metamorphic_test.clj | 16 ++++ 5 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/day8/re_frame/trace/metamorphic.clj create mode 100644 test/day8/re_frame/trace/metamorphic_test.clj diff --git a/.gitignore b/.gitignore index 671906a..b08cda4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ misc/ .flooignore node_modules/ examples/todomvc/.idea/ +test-resources/*.edn diff --git a/project.clj b/project.clj index 56d8739..8829d02 100644 --- a/project.clj +++ b/project.clj @@ -2,11 +2,12 @@ :description "Tracing and developer tools for re-frame apps" :url "https://github.com/Day8/re-frame-trace" :license {:name "MIT"} - :dependencies [[org.clojure/clojure "1.8.0"] + :dependencies [[org.clojure/clojure "1.9.0"] [org.clojure/clojurescript "1.9.671"] [reagent "0.6.0" :scope "provided"] [re-frame "0.10.3-alpha2" :scope "provided"] [binaryage/devtools "0.9.4"] + [io.pyroclast/metamorphic "0.1.0-alpha1"] [garden "1.3.3"]] :plugins [[thomasa/mranderson "0.4.7"] [lein-less "RELEASE"]] diff --git a/src/day8/re_frame/trace.cljs b/src/day8/re_frame/trace.cljs index 4c3d788..ac90ab0 100644 --- a/src/day8/re_frame/trace.cljs +++ b/src/day8/re_frame/trace.cljs @@ -106,6 +106,7 @@ (real-custom-wrapper key f)))) + ;; When this is enabled, the rendering of the trace panel causes an infinite loop. #_(set! reagent.impl.batching/next-tick (fn [f] (real-next-tick (fn [] (trace/with-trace {:op-type :raf} diff --git a/src/day8/re_frame/trace/metamorphic.clj b/src/day8/re_frame/trace/metamorphic.clj new file mode 100644 index 0000000..7900a9c --- /dev/null +++ b/src/day8/re_frame/trace/metamorphic.clj @@ -0,0 +1,95 @@ +(ns day8.re-frame.trace.metamorphic + (:require [metamorphic.api :as m] + [metamorphic.runtime :as rt] + [metamorphic.viz :as v])) + +;; Next, we define predicate functions that take exactly 4 arguments. +;; These predicates are obviously incredibly boring, but they help +;; save your brain power for the real concepts. + +;; Each predicate will receive each event as it arrives, a history (which we'll discuss later), +;; the entire pattern sequence, and the particular pattern that this predicate +;; is being used in. This is helpful for parameterizing a predicate. + +(defn a? [event history pattern-sequence pattern] + (= event "a")) + +(defn b? [event history pattern-sequence pattern] + (= event "b")) + +(defn c? [event history pattern-sequence pattern] + (= event "c")) + +;; Now let's create a pattern sequence. We're looking for "a", "b", then "c". +;; This pattern says: find "a", then immediately look for "b". After you find "b", +;; look for "c", but if there's something that doesn't match in the middle, that's +;; okay. The relaxation of looking for "c" is called a contiguity constraint, denoted +;; by "followed-by" instead of "next". + +(defn run-test [] + (let [runtime (-> (m/new-pattern-sequence "a b c") + (m/begin "a" a?) + (m/next "b" b?) + (m/followed-by "c" c?) + (rt/initialize-runtime)) + events ["a" "b" "q" "c" "z" "a" "b" "d" "x" "c"]] + (:matches (reduce rt/evaluate-event runtime events)))) + + +;;; + +(defn new-epoch-started? [event history pattern-sequence pattern] + (and (= :re-frame.router/fsm-trigger (:op-type event)) + (= (:operation event) + [:idle :add-event]))) + +(defn event-run? [event history pattern-sequence pattern] + (= :event (:op-type event))) + +(defn redispatched-event? [event history pattern-sequence pattern] + (and (= :re-frame.router/fsm-trigger (:op-type event)) + (= (:operation event) + [:running :add-event]))) + +(defn router-scheduled? [event history pattern-sequence pattern] + (and (= :re-frame.router/fsm-trigger (:op-type event)) + (= (:operation event) + [:running :finish-run]) + (= :running (get-in event [:tags :current-state])) + (= :scheduled (get-in event [:tags :new-state])))) + +(defn router-finished? [event history pattern-sequence pattern] + (and (= :re-frame.router/fsm-trigger (:op-type event)) + (= (:operation event) + [:running :finish-run]) + (= :running (get-in event [:tags :current-state])) + (= :idle (get-in event [:tags :new-state])))) + + +(defn trace-events [] (->> (slurp "test-resources/events2.edn") + (clojure.edn/read-string {:readers {'utc identity + 'object (fn [x] "")}}) + (sort-by :id)) + ) + + +(defn summarise-event [ev] + (dissoc ev :start :duration :end :child-of)) + +(defn summarise-match [match] + (map summarise-event match)) + +(defn parse-events [] + (let [runtime (-> (m/new-pattern-sequence "simple traces") + (m/begin "new-epoch-started" new-epoch-started?) + #_(m/followed-by "redispatched-event" redispatched-event? {:optional? true}) + #_ (m/followed-by "router-scheduled" router-scheduled? {:optional? true}) + (m/followed-by "event-run" event-run?) + (m/followed-by "router-finished" router-finished?) + (rt/initialize-runtime)) + events (trace-events) + rt (reduce rt/evaluate-event runtime events)] + #_(println "Count" + (count (:matches rt)) + (map count (:matches rt))) + (map summarise-match (:matches rt)))) diff --git a/test/day8/re_frame/trace/metamorphic_test.clj b/test/day8/re_frame/trace/metamorphic_test.clj new file mode 100644 index 0000000..d7a8c8e --- /dev/null +++ b/test/day8/re_frame/trace/metamorphic_test.clj @@ -0,0 +1,16 @@ +(ns day8.re-frame.trace.metamorphic-test + (:require [clojure.test :refer :all]) + (:require [day8.re-frame.trace.metamorphic :as m])) + +(deftest parse-events-test + (= (m/parse-events) + '(({:id 327, + :operation [:idle :add-event], + :op-type :re-frame.router/fsm-trigger, + :tags {:current-state :idle, :new-state :scheduled}} + {:id 329, :operation :estimate/new, :op-type :event, :tags {:event [:estimate/new]}} + {:id 330, + :operation [:running :finish-run], + :op-type :re-frame.router/fsm-trigger, + :tags {:current-state :running, :new-state :idle}})) + )) From 4e3fb7fae696e0e1c24311592e071122ebdb554d Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Fri, 22 Dec 2017 16:40:55 +1300 Subject: [PATCH 09/60] Add ADR's for use cases --- docs/architecture-decisions/adr-001-epochs.md | 49 ++++++++++++++++ .../adr-002-use-cases.md | 58 +++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 docs/architecture-decisions/adr-001-epochs.md create mode 100644 docs/architecture-decisions/adr-002-use-cases.md diff --git a/docs/architecture-decisions/adr-001-epochs.md b/docs/architecture-decisions/adr-001-epochs.md new file mode 100644 index 0000000..3533366 --- /dev/null +++ b/docs/architecture-decisions/adr-001-epochs.md @@ -0,0 +1,49 @@ +# Capturing Epochs + +**Status:** proposed + +## Context: + +### Intro + +Conceptually, re-frame is built around an event loop. The user makes an action, which causes an event to be dispatched, which changes app-db, which causes subscriptions to rerun, which causes the UI to update. We will refer to this cycle as an epoch. A user developing a re-frame application will want to be able to debug from this perspective. + +Currently, re-frame-trace offers three panels: app-db, subs, and traces. Each of these offers a view into the application state and allows the programmer to build up a mental model of what is happening. They are not going to go away, but there is room for a more integrated and holistic panel. + +### Requirements + +The new panel is organised around epochs. Information is grouped by epochs, and the user can switch between different epochs. + +### Defining + +There are several ways of defining an epoch: + +* Starting when an event was dispatched and ending when a new event is dispatched. - This doesn't work well when one event causes others to be dispatched. It also doesn't give you very accurate timing for how long an epoch as a whole takes to run. +* Starting when an event was dispatched and ending when a new event is dispatched that causes the router to start running again. - This handles one event dispatching several other events, but it doesn't give you overall timing. +* Starting when an event was dispatched, and ending when there is a period of no traces being fired. - This is based on heuristics, rather than actually measuring. +* Starting when an event was dispatched, including all subscriptions and renders that happened, and ending when there is no re-render scheduled in Reagent. - What about events that trigger a dispatch to run in 50 ms, or other async callbacks? + +We also have an additional wrinkle. Traces which are produced outside of an epoch are added to a mini epoch. This is for collecting traces which occur when Reagent re-renders, like when a local ratom hover state changes. No events are dispatched, so it is not a real epoch, but it is still useful information. We also have Figwheel re-renders which don't dispatch an event, but do cause a re-render and subscription creations and deletions. Epoch's will need to have a source property that can distinguish between user clicks, callbacks, figwheel re-renders, inter-epoch renders, and possibly other sources. + +### Capturing epochs + +From a JavaScript perspective, there are three separate calls which make up an epoch: + +1. The initial dispatch from an on-click handler or callback. This adds the event to the queue but doesn't process it, instead deferring processing until the next tick. +2. Processing the event and updating app-db +3. Rendering the UI, which includes creating, running, disposing of subscriptions; creating and evaluating Hiccup (including sorting/filtering data structures); and React rendering. These are all intermingled due to the way that Reagent works. + +## Decision: + +* Each epoch has only one event in it, and starts when an event is handled, if multiple events are processed in the same router queue (either because the first event dispatched the second, or that two events were concurrently added to the queue) they will be treated as multiple epochs. +* End of epoch is when there is no longer any work in the reagent queue + +### First approach + +The start of the epoch will be defined as any event trace being emitted. The end of the epoch will be either a new event trace being emitted, or a Reagent callback being called when nothing is scheduled. This is not completely correct, as a downstream event will create its own epoch, but it should be good enough to start building a useful UI and gain more information about the approach. + +After this is built we can review our understanding of what an epoch is and iterate on a second approach. + +## Consequences: + +TBA diff --git a/docs/architecture-decisions/adr-002-use-cases.md b/docs/architecture-decisions/adr-002-use-cases.md new file mode 100644 index 0000000..7c2a1f7 --- /dev/null +++ b/docs/architecture-decisions/adr-002-use-cases.md @@ -0,0 +1,58 @@ +What just happened + +Reset to previous state and rerun + +Compare previous events to understand the effect of my change + +Performance, numbers are highly precise but not very accurate. +Measure the whole epoch +Rerun 50 times + - Timings + +To assist a new user in navigating the codebase, file locations and line numbers +- reg-event-db, grab stack trace when registering event and subscription + + + +;;; + +How do we avoid people drifting away? + +- Setup cost + - Paid by one person on the team + + +- Save the filtering across states + - + +- Remove the debug interceptor + +- Nominate which kinds of events to filter out + - Number of epochs + + + Capturing app-db + Capturing subscriptions + + - Filter out low level stuff + - Processing + - Capturing + - Showing + + - Filter out views + - It does mean something that you have h-box and v-box + - How do we do it? + - Filtering on namespaces? + - Filtering in or filtering out? + + - Filter out subscriptions + - + + +# Sources of failure + +- Usability issues + +;; + +Put together From 9cfe463d02ec9efb0504186c9e65e00321cc5ee6 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Fri, 22 Dec 2017 16:42:48 +1300 Subject: [PATCH 10/60] Add Reagent to source dependencies --- DEVELOPERS.md | 5 +- project.clj | 32 +- .../re_frame/v0v10v2/re_frame/interop.clj | 2 +- .../re_frame/v0v10v2/re_frame/interop.cljs | 26 +- .../reagent/v0v6v0/reagent/core.clj | 9 + .../reagent/v0v6v0/reagent/core.cljs | 359 +++++++++++ .../reagent/v0v6v0/reagent/debug.clj | 73 +++ .../reagent/v0v6v0/reagent/debug.cljs | 27 + .../reagent/v0v6v0/reagent/dom.cljs | 78 +++ .../reagent/v0v6v0/reagent/dom/server.cljs | 33 + .../reagent/v0v6v0/reagent/impl/batching.cljs | 113 ++++ .../v0v6v0/reagent/impl/component.cljs | 317 ++++++++++ .../reagent/v0v6v0/reagent/impl/template.cljs | 395 ++++++++++++ .../reagent/v0v6v0/reagent/impl/util.cljs | 102 +++ .../reagent/v0v6v0/reagent/interop.clj | 75 +++ .../reagent/v0v6v0/reagent/interop.cljs | 2 + .../reagent/v0v6v0/reagent/ratom.clj | 53 ++ .../reagent/v0v6v0/reagent/ratom.cljs | 592 ++++++++++++++++++ 18 files changed, 2264 insertions(+), 29 deletions(-) create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/core.clj create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/core.cljs create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/debug.clj create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/debug.cljs create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/dom.cljs create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/dom/server.cljs create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/impl/batching.cljs create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/impl/component.cljs create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/impl/template.cljs create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/impl/util.cljs create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/interop.clj create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/interop.cljs create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/ratom.clj create mode 100644 src/mranderson047/reagent/v0v6v0/reagent/ratom.cljs diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 9b62e61..ca5b211 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -52,12 +52,13 @@ We are using CSS preprocessing to isolate the panel styles, by namespacing the p ### Updating the internal version of re-frame used -We want to use re-frame, but we don't want to use the re-frame that the host is using, or tracing will get very messy. Instead, we use [mranderson](https://github.com/benedekfazekas/mranderson) to create source dependencies of re-frame. +We want to use re-frame, but we don't want to use the re-frame that the host is using, or tracing will get very messy. Instead, we use [mranderson](https://github.com/benedekfazekas/mranderson) to create source dependencies of re-frame and reagent. ```console $ lein do clean $ lein with-profile mranderson source-deps -$ cp -r target/srcdeps/mranderson047 src +$ cp -r target/srcdeps/mranderson047 src +# Then delete the META-INF directories ``` ### How does re-frame-trace build?? I don't see anything in the project.clj that looks like it will build. diff --git a/project.clj b/project.clj index 8829d02..c50f990 100644 --- a/project.clj +++ b/project.clj @@ -1,17 +1,17 @@ (defproject day8.re-frame/trace "0.1.15-SNAPSHOT" :description "Tracing and developer tools for re-frame apps" - :url "https://github.com/Day8/re-frame-trace" - :license {:name "MIT"} + :url "https://github.com/Day8/re-frame-trace" + :license {:name "MIT"} :dependencies [[org.clojure/clojure "1.9.0"] [org.clojure/clojurescript "1.9.671"] - [reagent "0.6.0" :scope "provided"] + [reagent "0.6.0" :scope "provided"] [re-frame "0.10.3-alpha2" :scope "provided"] - [binaryage/devtools "0.9.4"] [io.pyroclast/metamorphic "0.1.0-alpha1"] + [binaryage/devtools "0.9.4"] [garden "1.3.3"]] :plugins [[thomasa/mranderson "0.4.7"] [lein-less "RELEASE"]] - :deploy-repositories {"releases" :clojars + :deploy-repositories {"releases" :clojars "snapshots" :clojars} ;:source-paths ["target/srcdeps"] @@ -31,11 +31,17 @@ :target-path "resources/day8/re_frame/trace"} :profiles {:dev {:dependencies [[binaryage/dirac "RELEASE"]]} - :mranderson {:dependencies [^:source-dep [re-frame "0.10.2" :scope "provided" - :exclusions [org.clojure/clojurescript - reagent - cljsjs/react - cljsjs/react-dom - cljsjs/react-dom-server - org.clojure/tools.logging - net.cgrand/macrovich]]]}}) + :mranderson {:dependencies ^:replace [^:source-dep [re-frame "0.10.2" + :exclusions [org.clojure/clojurescript + cljsjs/react + cljsjs/react-dom + cljsjs/react-dom-server + org.clojure/tools.logging + net.cgrand/macrovich]] + ^:source-dep [reagent "0.6.0" + :exclusions [org.clojure/clojurescript + cljsjs/react + cljsjs/react-dom + cljsjs/react-dom-server + org.clojure/tools.logging + net.cgrand/macrovich]]]}}) diff --git a/src/mranderson047/re_frame/v0v10v2/re_frame/interop.clj b/src/mranderson047/re_frame/v0v10v2/re_frame/interop.clj index 1551302..15ddd31 100644 --- a/src/mranderson047/re_frame/v0v10v2/re_frame/interop.clj +++ b/src/mranderson047/re_frame/v0v10v2/re_frame/interop.clj @@ -48,7 +48,7 @@ (defn make-reaction "On JVM Clojure, return a `deref`-able thing which invokes the given function on every `deref`. That is, `make-reaction` here provides precisely none of the - benefits of `reagent.ratom/make-reaction` (which only invokes its function if + benefits of `mranderson047.reagent.v0v6v0.reagent.ratom/make-reaction` (which only invokes its function if the reactions that the function derefs have changed value). But so long as `f` only depends on other reactions (which also behave themselves), the only difference is one of efficiency. That is, your tests should see no difference diff --git a/src/mranderson047/re_frame/v0v10v2/re_frame/interop.cljs b/src/mranderson047/re_frame/v0v10v2/re_frame/interop.cljs index 6fbc41f..26c1841 100644 --- a/src/mranderson047/re_frame/v0v10v2/re_frame/interop.cljs +++ b/src/mranderson047/re_frame/v0v10v2/re_frame/interop.cljs @@ -1,13 +1,13 @@ (ns mranderson047.re-frame.v0v10v2.re-frame.interop (:require [goog.async.nextTick] - [reagent.core] - [reagent.ratom])) + [mranderson047.reagent.v0v6v0.reagent.core] + [mranderson047.reagent.v0v6v0.reagent.ratom])) (def next-tick goog.async.nextTick) (def empty-queue #queue []) -(def after-render reagent.core/after-render) +(def after-render mranderson047.reagent.v0v6v0.reagent.core/after-render) ;; Make sure the Google Closure compiler sees this as a boolean constant, ;; otherwise Dead Code Elimination won't happen in `:advanced` builds. @@ -16,23 +16,23 @@ (def ^boolean debug-enabled? "@define {boolean}" ^boolean js/goog.DEBUG) (defn ratom [x] - (reagent.core/atom x)) + (mranderson047.reagent.v0v6v0.reagent.core/atom x)) (defn ratom? [x] - (satisfies? reagent.ratom/IReactiveAtom x)) + (satisfies? mranderson047.reagent.v0v6v0.reagent.ratom/IReactiveAtom x)) (defn deref? [x] (satisfies? IDeref x)) (defn make-reaction [f] - (reagent.ratom/make-reaction f)) + (mranderson047.reagent.v0v6v0.reagent.ratom/make-reaction f)) (defn add-on-dispose! [a-ratom f] - (reagent.ratom/add-on-dispose! a-ratom f)) + (mranderson047.reagent.v0v6v0.reagent.ratom/add-on-dispose! a-ratom f)) (defn dispose! [a-ratom] - (reagent.ratom/dispose! a-ratom)) + (mranderson047.reagent.v0v6v0.reagent.ratom/dispose! a-ratom)) (defn set-timeout! [f ms] (js/setTimeout f ms)) @@ -46,11 +46,11 @@ "Produces an id for reactive Reagent values e.g. reactions, ratoms, cursors." [reactive-val] - (when (implements? reagent.ratom/IReactiveAtom reactive-val) + (when (implements? mranderson047.reagent.v0v6v0.reagent.ratom/IReactiveAtom reactive-val) (str (condp instance? reactive-val - reagent.ratom/RAtom "ra" - reagent.ratom/RCursor "rc" - reagent.ratom/Reaction "rx" - reagent.ratom/Track "tr" + mranderson047.reagent.v0v6v0.reagent.ratom/RAtom "ra" + mranderson047.reagent.v0v6v0.reagent.ratom/RCursor "rc" + mranderson047.reagent.v0v6v0.reagent.ratom/Reaction "rx" + mranderson047.reagent.v0v6v0.reagent.ratom/Track "tr" "other") (hash reactive-val)))) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/core.clj b/src/mranderson047/reagent/v0v6v0/reagent/core.clj new file mode 100644 index 0000000..1503562 --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/core.clj @@ -0,0 +1,9 @@ +(ns mranderson047.reagent.v0v6v0.reagent.core + (:require [mranderson047.reagent.v0v6v0.reagent.ratom :as ra])) + +(defmacro with-let [bindings & body] + "Bind variables as with let, except that when used in a component + the bindings are only evaluated once. Also takes an optional finally + clause at the end, that is executed when the component is + destroyed." + `(ra/with-let ~bindings ~@body)) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/core.cljs b/src/mranderson047/reagent/v0v6v0/reagent/core.cljs new file mode 100644 index 0000000..fd56926 --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/core.cljs @@ -0,0 +1,359 @@ +(ns mranderson047.reagent.v0v6v0.reagent.core + (:require-macros [mranderson047.reagent.v0v6v0.reagent.core]) + (:refer-clojure :exclude [partial atom flush]) + (:require [mranderson047.reagent.v0v6v0.reagent.impl.template :as tmpl] + [mranderson047.reagent.v0v6v0.reagent.impl.component :as comp] + [mranderson047.reagent.v0v6v0.reagent.impl.util :as util] + [mranderson047.reagent.v0v6v0.reagent.impl.batching :as batch] + [mranderson047.reagent.v0v6v0.reagent.ratom :as ratom] + [mranderson047.reagent.v0v6v0.reagent.debug :as deb :refer-macros [dbg prn]] + [mranderson047.reagent.v0v6v0.reagent.interop :refer-macros [$ $!]] + [mranderson047.reagent.v0v6v0.reagent.dom :as dom] + [mranderson047.reagent.v0v6v0.reagent.dom.server :as server])) + +(def is-client util/is-client) + +(def react util/react) + +(defn create-element + "Create a native React element, by calling React.createElement directly. + + That means the second argument must be a javascript object (or nil), and + that any Reagent hiccup forms must be processed with as-element. For example + like this: + + (r/create-element \"div\" #js{:className \"foo\"} + \"Hi \" (r/as-element [:strong \"world!\"]) + + which is equivalent to + + [:div.foo \"Hi\" [:strong \"world!\"]]" + ([type] + (create-element type nil)) + ([type props] + (assert (not (map? props))) + ($ react createElement type props)) + ([type props child] + (assert (not (map? props))) + ($ react createElement type props child)) + ([type props child & children] + (assert (not (map? props))) + (apply ($ react :createElement) type props child children))) + +(defn as-element + "Turns a vector of Hiccup syntax into a React element. Returns form + unchanged if it is not a vector." + [form] + (tmpl/as-element form)) + +(defn adapt-react-class + "Returns an adapter for a native React class, that may be used + just like a Reagent component function or class in Hiccup forms." + [c] + (assert c) + (tmpl/adapt-react-class c)) + +(defn reactify-component + "Returns an adapter for a Reagent component, that may be used from + React, for example in JSX. A single argument, props, is passed to + the component, converted to a map." + [c] + (assert c) + (comp/reactify-component c)) + +(defn render + "Render a Reagent component into the DOM. The first argument may be + either a vector (using Reagent's Hiccup syntax), or a React element. + The second argument should be a DOM node. + + Optionally takes a callback that is called when the component is in place. + + Returns the mounted component instance." + ([comp container] + (dom/render comp container)) + ([comp container callback] + (dom/render comp container callback))) + +(defn unmount-component-at-node + "Remove a component from the given DOM node." + [container] + (dom/unmount-component-at-node container)) + +(defn render-to-string + "Turns a component into an HTML string." + [component] + (server/render-to-string component)) + +;; For backward compatibility +(def as-component as-element) +(def render-component render) +(def render-component-to-string render-to-string) + +(defn render-to-static-markup + "Turns a component into an HTML string, without data-react-id attributes, etc." + [component] + (server/render-to-static-markup component)) + +(defn ^:export force-update-all + "Force re-rendering of all mounted Reagent components. This is + probably only useful in a development environment, when you want to + update components in response to some dynamic changes to code. + + Note that force-update-all may not update root components. This + happens if a component 'foo' is mounted with `(render [foo])` (since + functions are passed by value, and not by reference, in + ClojureScript). To get around this you'll have to introduce a layer + of indirection, for example by using `(render [#'foo])` instead." + [] + (ratom/flush!) + (dom/force-update-all) + (batch/flush-after-render)) + +(defn create-class + "Create a component, React style. Should be called with a map, + looking like this: + + {:get-initial-state (fn [this]) + :component-will-receive-props (fn [this new-argv]) + :should-component-update (fn [this old-argv new-argv]) + :component-will-mount (fn [this]) + :component-did-mount (fn [this]) + :component-will-update (fn [this new-argv]) + :component-did-update (fn [this old-argv]) + :component-will-unmount (fn [this]) + :reagent-render (fn [args....])} ;; or :render (fn [this]) + + Everything is optional, except either :reagent-render or :render." + [spec] + (comp/create-class spec)) + + +(defn current-component + "Returns the current React component (a.k.a this) in a component + function." + [] + comp/*current-component*) + +(defn state-atom + "Returns an atom containing a components state." + [this] + (assert (comp/reagent-component? this)) + (comp/state-atom this)) + +(defn state + "Returns the state of a component, as set with replace-state or set-state. + Equivalent to (deref (r/state-atom this))" + [this] + (assert (comp/reagent-component? this)) + (deref (state-atom this))) + +(defn replace-state + "Set state of a component. + Equivalent to (reset! (state-atom this) new-state)" + [this new-state] + (assert (comp/reagent-component? this)) + (assert (or (nil? new-state) (map? new-state))) + (reset! (state-atom this) new-state)) + +(defn set-state + "Merge component state with new-state. + Equivalent to (swap! (state-atom this) merge new-state)" + [this new-state] + (assert (comp/reagent-component? this)) + (assert (or (nil? new-state) (map? new-state))) + (swap! (state-atom this) merge new-state)) + +(defn force-update + "Force a component to re-render immediately. + + If the second argument is true, child components will also be + re-rendered, even is their arguments have not changed." + ([this] + (force-update this false)) + ([this deep] + (ratom/flush!) + (util/force-update this deep) + (batch/flush-after-render))) + +(defn props + "Returns the props passed to a component." + [this] + (assert (comp/reagent-component? this)) + (comp/get-props this)) + +(defn children + "Returns the children passed to a component." + [this] + (assert (comp/reagent-component? this)) + (comp/get-children this)) + +(defn argv + "Returns the entire Hiccup form passed to the component." + [this] + (assert (comp/reagent-component? this)) + (comp/get-argv this)) + +(defn dom-node + "Returns the root DOM node of a mounted component." + [this] + (dom/dom-node this)) + +(defn merge-props + "Utility function that merges two maps, handling :class and :style + specially, like React's transferPropsTo." + [defaults props] + (util/merge-props defaults props)) + +(defn flush + "Render dirty components immediately to the DOM. + + Note that this may not work in event handlers, since React.js does + batching of updates there." + [] + (batch/flush)) + + + +;; Ratom + +(defn atom + "Like clojure.core/atom, except that it keeps track of derefs. + Reagent components that derefs one of these are automatically + re-rendered." + ([x] (ratom/atom x)) + ([x & rest] (apply ratom/atom x rest))) + +(defn track + "Takes a function and optional arguments, and returns a derefable + containing the output of that function. If the function derefs + Reagent atoms (or track, etc), the value will be updated whenever + the atom changes. + + In other words, @(track foo bar) will produce the same result + as (foo bar), but foo will only be called again when the atoms it + depends on changes, and will only trigger updates of components when + its result changes. + + track is lazy, i.e the function is only evaluated on deref." + [f & args] + {:pre [(ifn? f)]} + (ratom/make-track f args)) + +(defn track! + "An eager version of track. The function passed is called + immediately, and continues to be called when needed, until stopped + with dispose!." + [f & args] + {:pre [(ifn? f)]} + (ratom/make-track! f args)) + +(defn dispose! + "Stop the result of track! from updating." + [x] + (ratom/dispose! x)) + +(defn wrap + "Provide a combination of value and callback, that looks like an atom. + + The first argument can be any value, that will be returned when the + result is deref'ed. + + The second argument should be a function, that is called with the + optional extra arguments provided to wrap, and the new value of the + resulting 'atom'. + + Use for example like this: + + (wrap (:foo @state) + swap! state assoc :foo) + + Probably useful only for passing to child components." + [value reset-fn & args] + (assert (ifn? reset-fn)) + (ratom/make-wrapper value reset-fn args)) + + +;; RCursor + +(defn cursor + "Provide a cursor into a Reagent atom. + + Behaves like a Reagent atom but focuses updates and derefs to + the specified path within the wrapped Reagent atom. e.g., + (let [c (cursor ra [:nested :content])] + ... @c ;; equivalent to (get-in @ra [:nested :content]) + ... (reset! c 42) ;; equivalent to (swap! ra assoc-in [:nested :content] 42) + ... (swap! c inc) ;; equivalence to (swap! ra update-in [:nested :content] inc) + ) + + The first parameter can also be a function, that should look + something like this: + + (defn set-get + ([k] (get-in @state k)) + ([k v] (swap! state assoc-in k v))) + + The function will be called with one argument – the path passed to + cursor – when the cursor is deref'ed, and two arguments (path and + new value) when the cursor is modified. + + Given that set-get function, (and that state is a Reagent atom, or + another cursor) these cursors are equivalent: + (cursor state [:foo]) and (cursor set-get [:foo]). + + Note that a cursor is lazy: its value will not change until it is + used. This may be noticed with add-watch." + ([src path] + (ratom/cursor src path))) + + +;; Utilities + +(defn rswap! + "Swaps the value of a to be (apply f current-value-of-atom args). + + rswap! works like swap!, except that recursive calls to rswap! on + the same atom are allowed – and it always returns nil." + [a f & args] + {:pre [(satisfies? IAtom a) + (ifn? f)]} + (if a.rswapping + (-> (or a.rswapfs (set! a.rswapfs (array))) + (.push #(apply f % args))) + (do (set! a.rswapping true) + (try (swap! a (fn [state] + (loop [s (apply f state args)] + (if-some [sf (some-> a.rswapfs .shift)] + (recur (sf s)) + s)))) + (finally + (set! a.rswapping false))))) + nil) + +(defn next-tick + "Run f using requestAnimationFrame or equivalent. + + f will be called just before components are rendered." + [f] + (batch/do-before-flush f)) + +(defn after-render + "Run f using requestAnimationFrame or equivalent. + + f will be called just after any queued renders in the next animation + frame (and even if no renders actually occur)." + [f] + (batch/do-after-render f)) + +(defn partial + "Works just like clojure.core/partial, except that it is an IFn, and + the result can be compared with =" + [f & args] + (util/partial-ifn. f args nil)) + +(defn component-path + ;; Try to return the path of component c as a string. + ;; Maybe useful for debugging and error reporting, but may break + ;; with future versions of React (and return nil). + [c] + (comp/component-path c)) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/debug.clj b/src/mranderson047/reagent/v0v6v0/reagent/debug.clj new file mode 100644 index 0000000..45a21d4 --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/debug.clj @@ -0,0 +1,73 @@ +(ns mranderson047.reagent.v0v6v0.reagent.debug + (:refer-clojure :exclude [prn println time])) + +(defmacro log + "Print with console.log, if it exists." + [& forms] + `(when mranderson047.reagent.v0v6v0.reagent.debug.has-console + (.log js/console ~@forms))) + +(defmacro warn + "Print with console.warn." + [& forms] + (when *assert* + `(when mranderson047.reagent.v0v6v0.reagent.debug.has-console + (.warn (if mranderson047.reagent.v0v6v0.reagent.debug.tracking + mranderson047.reagent.v0v6v0.reagent.debug.track-console js/console) + (str "Warning: " ~@forms))))) + +(defmacro warn-unless + [cond & forms] + (when *assert* + `(when (not ~cond) + (warn ~@forms)))) + +(defmacro error + "Print with console.error." + [& forms] + (when *assert* + `(when mranderson047.reagent.v0v6v0.reagent.debug.has-console + (.error (if mranderson047.reagent.v0v6v0.reagent.debug.tracking + mranderson047.reagent.v0v6v0.reagent.debug.track-console js/console) + (str ~@forms))))) + +(defmacro println + "Print string with console.log" + [& forms] + `(log (str ~@forms))) + +(defmacro prn + "Like standard prn, but prints using console.log (so that we get +nice clickable links to source in modern browsers)." + [& forms] + `(log (pr-str ~@forms))) + +(defmacro dbg + "Useful debugging macro that prints the source and value of x, +as well as package name and line number. Returns x." + [x] + (let [ns (str cljs.analyzer/*cljs-ns*)] + `(let [x# ~x] + (println (str "dbg " + ~ns ":" + ~(:line (meta &form)) + ": " + ~(pr-str x) + ": " + (pr-str x#))) + x#))) + +(defmacro dev? + "True if assertions are enabled." + [] + (if *assert* true false)) + +(defmacro time [& forms] + (let [ns (str cljs.analyzer/*cljs-ns*) + label (str ns ":" (:line (meta &form)))] + `(let [label# ~label + res# (do + (js/console.time label#) + ~@forms)] + (js/console.timeEnd label#) + res#))) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/debug.cljs b/src/mranderson047/reagent/v0v6v0/reagent/debug.cljs new file mode 100644 index 0000000..3b4818a --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/debug.cljs @@ -0,0 +1,27 @@ +(ns mranderson047.reagent.v0v6v0.reagent.debug + (:require-macros [mranderson047.reagent.v0v6v0.reagent.debug])) + +(def ^:const has-console (exists? js/console)) + +(def ^boolean tracking false) + +(defonce warnings (atom nil)) + +(defonce track-console + (let [o #js{}] + (set! (.-warn o) + (fn [& args] + (swap! warnings update-in [:warn] conj (apply str args)))) + (set! (.-error o) + (fn [& args] + (swap! warnings update-in [:error] conj (apply str args)))) + o)) + +(defn track-warnings [f] + (set! tracking true) + (reset! warnings nil) + (f) + (let [warns @warnings] + (reset! warnings nil) + (set! tracking false) + warns)) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/dom.cljs b/src/mranderson047/reagent/v0v6v0/reagent/dom.cljs new file mode 100644 index 0000000..bc1fd19 --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/dom.cljs @@ -0,0 +1,78 @@ +(ns mranderson047.reagent.v0v6v0.reagent.dom + (:require [cljsjs.react.dom] + [mranderson047.reagent.v0v6v0.reagent.impl.util :as util] + [mranderson047.reagent.v0v6v0.reagent.impl.template :as tmpl] + [mranderson047.reagent.v0v6v0.reagent.impl.batching :as batch] + [mranderson047.reagent.v0v6v0.reagent.ratom :as ratom] + [mranderson047.reagent.v0v6v0.reagent.debug :refer-macros [dbg]] + [mranderson047.reagent.v0v6v0.reagent.interop :refer-macros [$ $!]])) + +(defonce ^:private imported nil) + +(defn module [] + (cond + (some? imported) imported + (exists? js/ReactDOM) (set! imported js/ReactDOM) + (exists? js/require) (or (set! imported (js/require "react-dom")) + (throw (js/Error. "require('react-dom') failed"))) + :else + (throw (js/Error. "js/ReactDOM is missing")))) + + +(defonce ^:private roots (atom {})) + +(defn- unmount-comp [container] + (swap! roots dissoc container) + ($ (module) unmountComponentAtNode container)) + +(defn- render-comp [comp container callback] + (binding [util/*always-update* true] + (->> ($ (module) render (comp) container + (fn [] + (binding [util/*always-update* false] + (swap! roots assoc container [comp container]) + (batch/flush-after-render) + (if (some? callback) + (callback)))))))) + +(defn- re-render-component [comp container] + (render-comp comp container nil)) + +(defn render + "Render a Reagent component into the DOM. The first argument may be + either a vector (using Reagent's Hiccup syntax), or a React element. The second argument should be a DOM node. + + Optionally takes a callback that is called when the component is in place. + + Returns the mounted component instance." + ([comp container] + (render comp container nil)) + ([comp container callback] + (ratom/flush!) + (let [f (fn [] + (tmpl/as-element (if (fn? comp) (comp) comp)))] + (render-comp f container callback)))) + +(defn unmount-component-at-node [container] + (unmount-comp container)) + +(defn dom-node + "Returns the root DOM node of a mounted component." + [this] + ($ (module) findDOMNode this)) + +(defn force-update-all + "Force re-rendering of all mounted Reagent components. This is + probably only useful in a development environment, when you want to + update components in response to some dynamic changes to code. + + Note that force-update-all may not update root components. This + happens if a component 'foo' is mounted with `(render [foo])` (since + functions are passed by value, and not by reference, in + ClojureScript). To get around this you'll have to introduce a layer + of indirection, for example by using `(render [#'foo])` instead." + [] + (ratom/flush!) + (doseq [v (vals @roots)] + (apply re-render-component v)) + "Updated") diff --git a/src/mranderson047/reagent/v0v6v0/reagent/dom/server.cljs b/src/mranderson047/reagent/v0v6v0/reagent/dom/server.cljs new file mode 100644 index 0000000..85999d3 --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/dom/server.cljs @@ -0,0 +1,33 @@ +(ns mranderson047.reagent.v0v6v0.reagent.dom.server + (:require [cljsjs.react.dom.server] + [mranderson047.reagent.v0v6v0.reagent.impl.util :as util] + [mranderson047.reagent.v0v6v0.reagent.impl.template :as tmpl] + [mranderson047.reagent.v0v6v0.reagent.ratom :as ratom] + [mranderson047.reagent.v0v6v0.reagent.interop :refer-macros [$ $!]])) + +(defonce ^:private imported nil) + +(defn module [] + (cond + (some? imported) imported + (exists? js/ReactDOMServer) (set! imported js/ReactDOMServer) + (exists? js/require) (or (set! imported (js/require "react-dom/server")) + (throw (js/Error. + "require('react-dom/server') failed"))) + :else + (throw (js/Error. "js/ReactDOMServer is missing")))) + + +(defn render-to-string + "Turns a component into an HTML string." + [component] + (ratom/flush!) + (binding [util/*non-reactive* true] + ($ (module) renderToString (tmpl/as-element component)))) + +(defn render-to-static-markup + "Turns a component into an HTML string, without data-react-id attributes, etc." + [component] + (ratom/flush!) + (binding [util/*non-reactive* true] + ($ (module) renderToStaticMarkup (tmpl/as-element component)))) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/impl/batching.cljs b/src/mranderson047/reagent/v0v6v0/reagent/impl/batching.cljs new file mode 100644 index 0000000..71ef7df --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/impl/batching.cljs @@ -0,0 +1,113 @@ +(ns mranderson047.reagent.v0v6v0.reagent.impl.batching + (:refer-clojure :exclude [flush]) + (:require [mranderson047.reagent.v0v6v0.reagent.debug :refer-macros [dbg]] + [mranderson047.reagent.v0v6v0.reagent.interop :refer-macros [$ $!]] + [mranderson047.reagent.v0v6v0.reagent.impl.util :refer [is-client]] + [clojure.string :as string])) + +;;; Update batching + +(defonce mount-count 0) + +(defn next-mount-count [] + (set! mount-count (inc mount-count))) + +(defn fake-raf [f] + (js/setTimeout f 16)) + +(def next-tick + (if-not is-client + fake-raf + (let [w js/window] + (or ($ w :requestAnimationFrame) + ($ w :webkitRequestAnimationFrame) + ($ w :mozRequestAnimationFrame) + ($ w :msRequestAnimationFrame) + fake-raf)))) + +(defn compare-mount-order [c1 c2] + (- ($ c1 :cljsMountOrder) + ($ c2 :cljsMountOrder))) + +(defn run-queue [a] + ;; sort components by mount order, to make sure parents + ;; are rendered before children + (.sort a compare-mount-order) + (dotimes [i (alength a)] + (let [c (aget a i)] + (when (true? ($ c :cljsIsDirty)) + ($ c forceUpdate))))) + + +;; Set from ratom.cljs +(defonce ratom-flush (fn [])) + +(deftype RenderQueue [^:mutable ^boolean scheduled?] + Object + (enqueue [this k f] + (assert (some? f)) + (when (nil? (aget this k)) + (aset this k (array))) + (.push (aget this k) f) + (.schedule this)) + + (run-funs [this k] + (when-some [fs (aget this k)] + (aset this k nil) + (dotimes [i (alength fs)] + ((aget fs i))))) + + (schedule [this] + (when-not scheduled? + (set! scheduled? true) + (next-tick #(.run-queues this)))) + + (queue-render [this c] + (.enqueue this "componentQueue" c)) + + (add-before-flush [this f] + (.enqueue this "beforeFlush" f)) + + (add-after-render [this f] + (.enqueue this "afterRender" f)) + + (run-queues [this] + (set! scheduled? false) + (.flush-queues this)) + + (flush-after-render [this] + (.run-funs this "afterRender")) + + (flush-queues [this] + (.run-funs this "beforeFlush") + (ratom-flush) + (when-some [cs (aget this "componentQueue")] + (aset this "componentQueue" nil) + (run-queue cs)) + (.flush-after-render this))) + +(defonce render-queue (RenderQueue. false)) + +(defn flush [] + (.flush-queues render-queue)) + +(defn flush-after-render [] + (.flush-after-render render-queue)) + +(defn queue-render [c] + (when-not ($ c :cljsIsDirty) + ($! c :cljsIsDirty true) + (.queue-render render-queue c))) + +(defn mark-rendered [c] + ($! c :cljsIsDirty false)) + +(defn do-before-flush [f] + (.add-before-flush render-queue f)) + +(defn do-after-render [f] + (.add-after-render render-queue f)) + +(defn schedule [] + (when (false? (.-scheduled? render-queue)) + (.schedule render-queue))) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/impl/component.cljs b/src/mranderson047/reagent/v0v6v0/reagent/impl/component.cljs new file mode 100644 index 0000000..2d4a26b --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/impl/component.cljs @@ -0,0 +1,317 @@ +(ns mranderson047.reagent.v0v6v0.reagent.impl.component + (:require [mranderson047.reagent.v0v6v0.reagent.impl.util :as util] + [mranderson047.reagent.v0v6v0.reagent.impl.batching :as batch] + [mranderson047.reagent.v0v6v0.reagent.ratom :as ratom] + [mranderson047.reagent.v0v6v0.reagent.interop :refer-macros [$ $!]] + [mranderson047.reagent.v0v6v0.reagent.debug :refer-macros [dbg prn dev? warn error warn-unless]])) + +(declare ^:dynamic *current-component*) + + +;;; Argv access + +(defn shallow-obj-to-map [o] + (let [ks (js-keys o) + len (alength ks)] + (loop [m {} i 0] + (if (< i len) + (let [k (aget ks i)] + (recur (assoc m (keyword k) (aget o k)) (inc i))) + m)))) + +(defn extract-props [v] + (let [p (nth v 1 nil)] + (if (map? p) p))) + +(defn extract-children [v] + (let [p (nth v 1 nil) + first-child (if (or (nil? p) (map? p)) 2 1)] + (if (> (count v) first-child) + (subvec v first-child)))) + +(defn props-argv [c p] + (if-some [a ($ p :argv)] + a + [(.-constructor c) (shallow-obj-to-map p)])) + +(defn get-argv [c] + (props-argv c ($ c :props))) + +(defn get-props [c] + (let [p ($ c :props)] + (if-some [v ($ p :argv)] + (extract-props v) + (shallow-obj-to-map p)))) + +(defn get-children [c] + (let [p ($ c :props)] + (if-some [v ($ p :argv)] + (extract-children v) + (->> ($ p :children) + ($ util/react Children.toArray) + (into []))))) + +(defn ^boolean reagent-class? [c] + (and (fn? c) + (some? (some-> c .-prototype ($ :reagentRender))))) + +(defn ^boolean react-class? [c] + (and (fn? c) + (some? (some-> c .-prototype ($ :render))))) + +(defn ^boolean reagent-component? [c] + (some? ($ c :reagentRender))) + +(defn cached-react-class [c] + ($ c :cljsReactClass)) + +(defn cache-react-class [c constructor] + ($! c :cljsReactClass constructor)) + + +;;; State + +(defn state-atom [this] + (let [sa ($ this :cljsState)] + (if-not (nil? sa) + sa + ($! this :cljsState (ratom/atom nil))))) + +;; avoid circular dependency: this gets set from template.cljs +(defonce as-element nil) + + +;;; Rendering + +(defn wrap-render [c] + (let [f ($ c :reagentRender) + _ (assert (ifn? f)) + res (if (true? ($ c :cljsLegacyRender)) + (.call f c c) + (let [v (get-argv c) + n (count v)] + (case n + 1 (.call f c) + 2 (.call f c (nth v 1)) + 3 (.call f c (nth v 1) (nth v 2)) + 4 (.call f c (nth v 1) (nth v 2) (nth v 3)) + 5 (.call f c (nth v 1) (nth v 2) (nth v 3) (nth v 4)) + (.apply f c (.slice (into-array v) 1)))))] + (cond + (vector? res) (as-element res) + (ifn? res) (let [f (if (reagent-class? res) + (fn [& args] + (as-element (apply vector res args))) + res)] + ($! c :reagentRender f) + (recur c)) + :else res))) + +(declare comp-name) + +(defn do-render [c] + (binding [*current-component* c] + (if (dev?) + ;; Log errors, without using try/catch (and mess up call stack) + (let [ok (array false)] + (try + (let [res (wrap-render c)] + (aset ok 0 true) + res) + (finally + (when-not (aget ok 0) + (error (str "Error rendering component" + (comp-name))))))) + (wrap-render c)))) + + +;;; Method wrapping + +(def rat-opts {:no-cache true}) + +(def static-fns + {:render + (fn render [] + (this-as c (if util/*non-reactive* + (do-render c) + (let [rat ($ c :cljsRatom)] + (batch/mark-rendered c) + (if (nil? rat) + (ratom/run-in-reaction #(do-render c) c "cljsRatom" + batch/queue-render rat-opts) + (._run rat false))))))}) + +(defn custom-wrapper [key f] + (case key + :getDefaultProps + (assert false "getDefaultProps not supported") + + :getInitialState + (fn getInitialState [] + (this-as c (reset! (state-atom c) (.call f c c)))) + + :componentWillReceiveProps + (fn componentWillReceiveProps [nextprops] + (this-as c (.call f c c (props-argv c nextprops)))) + + :shouldComponentUpdate + (fn shouldComponentUpdate [nextprops nextstate] + (or util/*always-update* + (this-as c + ;; Don't care about nextstate here, we use forceUpdate + ;; when only when state has changed anyway. + (let [old-argv ($ c :props.argv) + new-argv ($ nextprops :argv) + noargv (or (nil? old-argv) (nil? new-argv))] + (cond + (nil? f) (or noargv (not= old-argv new-argv)) + noargv (.call f c c (get-argv c) (props-argv c nextprops)) + :else (.call f c c old-argv new-argv)))))) + + :componentWillUpdate + (fn componentWillUpdate [nextprops] + (this-as c (.call f c c (props-argv c nextprops)))) + + :componentDidUpdate + (fn componentDidUpdate [oldprops] + (this-as c (.call f c c (props-argv c oldprops)))) + + :componentWillMount + (fn componentWillMount [] + (this-as c + ($! c :cljsMountOrder (batch/next-mount-count)) + (when-not (nil? f) + (.call f c c)))) + + :componentDidMount + (fn componentDidMount [] + (this-as c (.call f c c))) + + :componentWillUnmount + (fn componentWillUnmount [] + (this-as c + (some-> ($ c :cljsRatom) + ratom/dispose!) + (batch/mark-rendered c) + (when-not (nil? f) + (.call f c c)))) + + nil)) + +(defn get-wrapper [key f name] + (let [wrap (custom-wrapper key f)] + (when (and wrap f) + (assert (ifn? f) + (str "Expected function in " name key " but got " f))) + (or wrap f))) + +(def obligatory {:shouldComponentUpdate nil + :componentWillMount nil + :componentWillUnmount nil}) + +(def dash-to-camel (util/memoize-1 util/dash-to-camel)) + +(defn camelify-map-keys [fun-map] + (reduce-kv (fn [m k v] + (assoc m (-> k dash-to-camel keyword) v)) + {} fun-map)) + +(defn add-obligatory [fun-map] + (merge obligatory fun-map)) + +(defn wrap-funs [fmap] + (when (dev?) + (let [renders (select-keys fmap [:render :reagentRender :componentFunction]) + render-fun (-> renders vals first)] + (assert (pos? (count renders)) "Missing reagent-render") + (assert (== 1 (count renders)) "Too many render functions supplied") + (assert (ifn? render-fun) (str "Render must be a function, not " + (pr-str render-fun))))) + (let [render-fun (or (:reagentRender fmap) + (:componentFunction fmap)) + legacy-render (nil? render-fun) + render-fun (or render-fun + (:render fmap)) + name (str (or (:displayName fmap) + (util/fun-name render-fun))) + name (case name + "" (str (gensym "reagent")) + name) + fmap (reduce-kv (fn [m k v] + (assoc m k (get-wrapper k v name))) + {} fmap)] + (assoc fmap + :displayName name + :autobind false + :cljsLegacyRender legacy-render + :reagentRender render-fun + :render (:render static-fns)))) + +(defn map-to-js [m] + (reduce-kv (fn [o k v] + (doto o + (aset (name k) v))) + #js{} m)) + +(defn cljsify [body] + (-> body + camelify-map-keys + add-obligatory + wrap-funs + map-to-js)) + +(defn create-class [body] + {:pre [(map? body)]} + (->> body + cljsify + ($ util/react createClass))) + +(defn component-path [c] + (let [elem (some-> (or (some-> c ($ :_reactInternalInstance)) + c) + ($ :_currentElement)) + name (some-> elem + ($ :type) + ($ :displayName)) + path (some-> elem + ($ :_owner) + component-path + (str " > ")) + res (str path name)] + (when-not (empty? res) res))) + +(defn comp-name [] + (if (dev?) + (let [c *current-component* + n (or (component-path c) + (some-> c .-constructor util/fun-name))] + (if-not (empty? n) + (str " (in " n ")") + "")) + "")) + +(defn fn-to-class [f] + (assert (ifn? f) (str "Expected a function, not " (pr-str f))) + (warn-unless (not (and (react-class? f) + (not (reagent-class? f)))) + "Using native React classes directly in Hiccup forms " + "is not supported. Use create-element or " + "adapt-react-class instead: " (let [n (util/fun-name f)] + (if (empty? n) f n)) + (comp-name)) + (if (reagent-class? f) + (cache-react-class f f) + (let [spec (meta f) + withrender (assoc spec :reagent-render f) + res (create-class withrender)] + (cache-react-class f res)))) + +(defn as-class [tag] + (if-some [cached-class (cached-react-class tag)] + cached-class + (fn-to-class tag))) + +(defn reactify-component [comp] + (if (react-class? comp) + comp + (as-class comp))) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/impl/template.cljs b/src/mranderson047/reagent/v0v6v0/reagent/impl/template.cljs new file mode 100644 index 0000000..2347898 --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/impl/template.cljs @@ -0,0 +1,395 @@ +(ns mranderson047.reagent.v0v6v0.reagent.impl.template + (:require [clojure.string :as string] + [clojure.walk :refer [prewalk]] + [mranderson047.reagent.v0v6v0.reagent.impl.util :as util :refer [is-client]] + [mranderson047.reagent.v0v6v0.reagent.impl.component :as comp] + [mranderson047.reagent.v0v6v0.reagent.impl.batching :as batch] + [mranderson047.reagent.v0v6v0.reagent.ratom :as ratom] + [mranderson047.reagent.v0v6v0.reagent.interop :refer-macros [$ $!]] + [mranderson047.reagent.v0v6v0.reagent.debug :refer-macros [dbg prn println log dev? + warn warn-unless]])) + +;; From Weavejester's Hiccup, via pump: +(def ^{:doc "Regular expression that parses a CSS-style id and class + from a tag name."} + re-tag #"([^\s\.#]+)(?:#([^\s\.#]+))?(?:\.([^\s#]+))?") + +(deftype NativeWrapper []) + + +;;; Common utilities + +(defn ^boolean named? [x] + (or (keyword? x) + (symbol? x))) + +(defn ^boolean hiccup-tag? [x] + (or (named? x) + (string? x))) + +(defn ^boolean valid-tag? [x] + (or (hiccup-tag? x) + (ifn? x) + (instance? NativeWrapper x))) + + +;;; Props conversion + +(def prop-name-cache #js{:class "className" + :for "htmlFor" + :charset "charSet"}) + +(defn cache-get [o k] + (when ^boolean (.hasOwnProperty o k) + (aget o k))) + +(defn cached-prop-name [k] + (if (named? k) + (if-some [k' (cache-get prop-name-cache (name k))] + k' + (aset prop-name-cache (name k) + (util/dash-to-camel k))) + k)) + +(defn ^boolean js-val? [x] + (not (identical? "object" (goog/typeOf x)))) + +(declare convert-prop-value) + +(defn kv-conv [o k v] + (doto o + (aset (cached-prop-name k) + (convert-prop-value v)))) + +(defn convert-prop-value [x] + (cond (js-val? x) x + (named? x) (name x) + (map? x) (reduce-kv kv-conv #js{} x) + (coll? x) (clj->js x) + (ifn? x) (fn [& args] + (apply x args)) + :else (clj->js x))) + +(defn oset [o k v] + (doto (if (nil? o) #js{} o) + (aset k v))) + +(defn oget [o k] + (if (nil? o) nil (aget o k))) + +(defn set-id-class [p id-class] + (let [id ($ id-class :id) + p (if (and (some? id) + (nil? (oget p "id"))) + (oset p "id" id) + p)] + (if-some [class ($ id-class :className)] + (let [old (oget p "className")] + (oset p "className" (if (nil? old) + class + (str class " " old)))) + p))) + +(defn convert-props [props id-class] + (-> props + convert-prop-value + (set-id-class id-class))) + + +;;; Specialization for input components + +;; +;; The properites 'selectionStart' and 'selectionEnd' only exist on some inputs +;; See: https://html.spec.whatwg.org/multipage/forms.html#do-not-apply +(def these-inputs-have-selection-api #{"text" "textarea" "password" "search" + "tel" "url"}) + +(defn ^boolean has-selection-api? + [input-type] + (contains? these-inputs-have-selection-api input-type)) + +(defn input-set-value [this] + (when-some [node ($ this :cljsInputElement)] + ($! this :cljsInputDirty false) + (let [rendered-value ($ this :cljsRenderedValue) + dom-value ($ this :cljsDOMValue)] + (when (not= rendered-value dom-value) + (if-not (and (identical? node ($ js/document :activeElement)) + (has-selection-api? ($ node :type)) + (string? rendered-value) + (string? dom-value)) + ;; just set the value, no need to worry about a cursor + (do + ($! this :cljsDOMValue rendered-value) + ($! node :value rendered-value)) + + ;; Setting "value" (below) moves the cursor position to the + ;; end which gives the user a jarring experience. + ;; + ;; But repositioning the cursor within the text, turns out to + ;; be quite a challenge because changes in the text can be + ;; triggered by various events like: + ;; - a validation function rejecting a user inputted char + ;; - the user enters a lower case char, but is transformed to + ;; upper. + ;; - the user selects multiple chars and deletes text + ;; - the user pastes in multiple chars, and some of them are + ;; rejected by a validator. + ;; - the user selects multiple chars and then types in a + ;; single new char to repalce them all. + ;; Coming up with a sane cursor repositioning strategy hasn't + ;; been easy ALTHOUGH in the end, it kinda fell out nicely, + ;; and it appears to sanely handle all the cases we could + ;; think of. + ;; So this is just a warning. The code below is simple + ;; enough, but if you are tempted to change it, be aware of + ;; all the scenarios you have handle. + (let [node-value ($ node :value)] + (if (not= node-value dom-value) + ;; IE has not notified us of the change yet, so check again later + (batch/do-after-render #(input-set-value this)) + (let [existing-offset-from-end (- (count node-value) + ($ node :selectionStart)) + new-cursor-offset (- (count rendered-value) + existing-offset-from-end)] + ($! this :cljsDOMValue rendered-value) + ($! node :value rendered-value) + ($! node :selectionStart new-cursor-offset) + ($! node :selectionEnd new-cursor-offset))))))))) + +(defn input-handle-change [this on-change e] + ($! this :cljsDOMValue (-> e .-target .-value)) + ;; Make sure the input is re-rendered, in case on-change + ;; wants to keep the value unchanged + (when-not ($ this :cljsInputDirty) + ($! this :cljsInputDirty true) + (batch/do-after-render #(input-set-value this))) + (on-change e)) + +(defn input-render-setup [this jsprops] + ;; Don't rely on React for updating "controlled inputs", since it + ;; doesn't play well with async rendering (misses keystrokes). + (when (and (some? jsprops) + (.hasOwnProperty jsprops "onChange") + (.hasOwnProperty jsprops "value")) + (let [v ($ jsprops :value) + value (if (nil? v) "" v) + on-change ($ jsprops :onChange)] + (when (nil? ($ this :cljsInputElement)) + ;; set initial value + ($! this :cljsDOMValue value)) + ($! this :cljsRenderedValue value) + (js-delete jsprops "value") + (doto jsprops + ($! :defaultValue value) + ($! :onChange #(input-handle-change this on-change %)) + ($! :ref #($! this :cljsInputElement %1)))))) + +(defn ^boolean input-component? [x] + (case x + ("input" "textarea") true + false)) + +(def reagent-input-class nil) + +(declare make-element) + +(def input-spec + {:display-name "ReagentInput" + :component-did-update input-set-value + :reagent-render + (fn [argv comp jsprops first-child] + (let [this comp/*current-component*] + (input-render-setup this jsprops) + (make-element argv comp jsprops first-child)))}) + +(defn reagent-input [] + (when (nil? reagent-input-class) + (set! reagent-input-class (comp/create-class input-spec))) + reagent-input-class) + + +;;; Conversion from Hiccup forms + +(defn parse-tag [hiccup-tag] + (let [[tag id class] (->> hiccup-tag name (re-matches re-tag) next) + class (when-not (nil? class) + (string/replace class #"\." " "))] + (assert tag (str "Invalid tag: '" hiccup-tag "'" + (comp/comp-name))) + #js{:name tag + :id id + :className class})) + +(defn try-get-key [x] + ;; try catch to avoid clojurescript peculiarity with + ;; sorted-maps with keys that are numbers + (try (get x :key) + (catch :default e))) + +(defn get-key [x] + (when (map? x) + (try-get-key x))) + +(defn key-from-vec [v] + (if-some [k (-> (meta v) get-key)] + k + (-> v (nth 1 nil) get-key))) + +(defn reag-element [tag v] + (let [c (comp/as-class tag) + jsprops #js{:argv v}] + (when-some [key (key-from-vec v)] + ($! jsprops :key key)) + ($ util/react createElement c jsprops))) + +(defn adapt-react-class [c] + (doto (NativeWrapper.) + ($! :name c) + ($! :id nil) + ($! :class nil))) + +(def tag-name-cache #js{}) + +(defn cached-parse [x] + (if-some [s (cache-get tag-name-cache x)] + s + (aset tag-name-cache x (parse-tag x)))) + +(declare as-element) + +(defn native-element [parsed argv first] + (let [comp ($ parsed :name)] + (let [props (nth argv first nil) + hasprops (or (nil? props) (map? props)) + jsprops (convert-props (if hasprops props) parsed) + first-child (+ first (if hasprops 1 0))] + (if (input-component? comp) + (-> [(reagent-input) argv comp jsprops first-child] + (with-meta (meta argv)) + as-element) + (let [key (-> (meta argv) get-key) + p (if (nil? key) + jsprops + (oset jsprops "key" key))] + (make-element argv comp p first-child)))))) + +(defn str-coll [coll] + (if (dev?) + (str (prewalk (fn [x] + (if (fn? x) + (let [n (util/fun-name x)] + (case n "" x (symbol n))) + x)) coll)) + (str coll))) + +(defn hiccup-err [v & msg] + (str (apply str msg) ": " (str-coll v) "\n" (comp/comp-name))) + +(defn vec-to-elem [v] + (assert (pos? (count v)) (hiccup-err v "Hiccup form should not be empty")) + (let [tag (nth v 0 nil)] + (assert (valid-tag? tag) (hiccup-err v "Invalid Hiccup form")) + (cond + (hiccup-tag? tag) + (let [n (name tag) + pos (.indexOf n ">")] + (case pos + -1 (native-element (cached-parse n) v 1) + 0 (let [comp (nth v 1 nil)] + ;; Support [:> comp ...] + (assert (= ">" n) (hiccup-err v "Invalid Hiccup tag")) + (assert (or (string? comp) (fn? comp)) + (hiccup-err v "Expected React component in")) + (native-element #js{:name comp} v 2)) + ;; Support extended hiccup syntax, i.e :div.bar>a.foo + (recur [(subs n 0 pos) + (assoc v 0 (subs n (inc pos)))]))) + + (instance? NativeWrapper tag) + (native-element tag v 1) + + :else (reag-element tag v)))) + +(declare expand-seq) +(declare expand-seq-check) + +(defn as-element [x] + (cond (js-val? x) x + (vector? x) (vec-to-elem x) + (seq? x) (if (dev?) + (expand-seq-check x) + (expand-seq x)) + (named? x) (name x) + (satisfies? IPrintWithWriter x) (pr-str x) + :else x)) + +(set! comp/as-element as-element) + +(defn expand-seq [s] + (let [a (into-array s)] + (dotimes [i (alength a)] + (aset a i (as-element (aget a i)))) + a)) + +(defn expand-seq-dev [s o] + (let [a (into-array s)] + (dotimes [i (alength a)] + (let [val (aget a i)] + (when (and (vector? val) + (nil? (key-from-vec val))) + ($! o :no-key true)) + (aset a i (as-element val)))) + a)) + +(defn expand-seq-check [x] + (let [ctx #js{} + [res derefed] (ratom/check-derefs #(expand-seq-dev x ctx))] + (when derefed + (warn (hiccup-err x "Reactive deref not supported in lazy seq, " + "it should be wrapped in doall"))) + (when ($ ctx :no-key) + (warn (hiccup-err x "Every element in a seq should have a unique :key"))) + res)) + +;; From https://github.com/babel/babel/commit/1d0e68f5a19d721fe8799b1ea331041d8bf9120e +;; (def react-element-type (or (and (exists? js/Symbol) +;; ($ js/Symbol :for) +;; ($ js/Symbol for "react.element")) +;; 60103)) + +;; (defn make-element-fast [argv comp jsprops first-child] +;; (let [key (some-> jsprops ($ :key)) +;; ref (some-> jsprops ($ :ref)) +;; props (if (nil? jsprops) (js-obj) jsprops)] +;; ($! props :children +;; (case (- (count argv) first-child) +;; 0 nil +;; 1 (as-element (nth argv first-child)) +;; (reduce-kv (fn [a k v] +;; (when (>= k first-child) +;; (.push a (as-element v))) +;; a) +;; #js[] argv))) +;; (js-obj "key" key +;; "ref" ref +;; "props" props +;; "$$typeof" react-element-type +;; "type" comp +;; ;; "_store" (js-obj) +;; ))) + +(defn make-element [argv comp jsprops first-child] + (case (- (count argv) first-child) + ;; Optimize cases of zero or one child + 0 ($ util/react createElement comp jsprops) + + 1 ($ util/react createElement comp jsprops + (as-element (nth argv first-child nil))) + + (.apply ($ util/react :createElement) nil + (reduce-kv (fn [a k v] + (when (>= k first-child) + (.push a (as-element v))) + a) + #js[comp jsprops] argv)))) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/impl/util.cljs b/src/mranderson047/reagent/v0v6v0/reagent/impl/util.cljs new file mode 100644 index 0000000..805e6e2 --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/impl/util.cljs @@ -0,0 +1,102 @@ +(ns mranderson047.reagent.v0v6v0.reagent.impl.util + (:require [cljsjs.react] + [mranderson047.reagent.v0v6v0.reagent.debug :refer-macros [dbg log warn]] + [mranderson047.reagent.v0v6v0.reagent.interop :refer-macros [$ $!]] + [clojure.string :as string])) + +(defonce react + (cond (exists? js/React) js/React + (exists? js/require) (or (js/require "react") + (throw (js/Error. "require('react') failed"))) + :else (throw (js/Error. "js/React is missing")))) + +(def is-client (and (exists? js/window) + (-> js/window ($ :document) nil? not))) + +(def ^:dynamic ^boolean *non-reactive* false) + +;;; Props accessors + +;; Misc utilities + +(defn memoize-1 [f] + (let [mem (atom {})] + (fn [arg] + (let [v (get @mem arg)] + (if-not (nil? v) + v + (let [ret (f arg)] + (swap! mem assoc arg ret) + ret)))))) + +(def dont-camel-case #{"aria" "data"}) + +(defn capitalize [s] + (if (< (count s) 2) + (string/upper-case s) + (str (string/upper-case (subs s 0 1)) (subs s 1)))) + +(defn dash-to-camel [dashed] + (if (string? dashed) + dashed + (let [name-str (name dashed) + [start & parts] (string/split name-str #"-")] + (if (dont-camel-case start) + name-str + (apply str start (map capitalize parts)))))) + +(defn fun-name [f] + (let [n (or (and (fn? f) + (or ($ f :displayName) + ($ f :name))) + (and (implements? INamed f) + (name f)) + (let [m (meta f)] + (if (map? m) + (:name m))))] + (-> n + str + (clojure.string/replace "$" ".")))) + +(deftype partial-ifn [f args ^:mutable p] + IFn + (-invoke [_ & a] + (or p (set! p (apply clojure.core/partial f args))) + (apply p a)) + IEquiv + (-equiv [_ other] + (and (= f (.-f other)) (= args (.-args other)))) + IHash + (-hash [_] (hash [f args]))) + +(defn- merge-class [p1 p2] + (let [class (when-let [c1 (:class p1)] + (when-let [c2 (:class p2)] + (str c1 " " c2)))] + (if (nil? class) + p2 + (assoc p2 :class class)))) + +(defn- merge-style [p1 p2] + (let [style (when-let [s1 (:style p1)] + (when-let [s2 (:style p2)] + (merge s1 s2)))] + (if (nil? style) + p2 + (assoc p2 :style style)))) + +(defn merge-props [p1 p2] + (if (nil? p1) + p2 + (do + (assert (map? p1)) + (merge-style p1 (merge-class p1 (merge p1 p2)))))) + + +(def ^:dynamic *always-update* false) + +(defn force-update [comp deep] + (if deep + (binding [*always-update* true] + ($ comp forceUpdate)) + ($ comp forceUpdate))) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/interop.clj b/src/mranderson047/reagent/v0v6v0/reagent/interop.clj new file mode 100644 index 0000000..07d39f9 --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/interop.clj @@ -0,0 +1,75 @@ +(ns mranderson047.reagent.v0v6v0.reagent.interop + (:require [clojure.string :as string :refer [join]] + [clojure.java.io :as io])) + +(defn- js-call [f args] + (let [argstr (->> (repeat (count args) "~{}") + (join ","))] + (list* 'js* (str "~{}(" argstr ")") f args))) + +(defn- dot-args [object member] + (assert (or (symbol? member) + (keyword? member)) + (str "Symbol or keyword expected, not " member)) + (assert (or (not (symbol? object)) + (not (re-find #"\." (name object)))) + (str "Dot not allowed in " object)) + (let [n (name member) + field? (or (keyword? member) + (= (subs n 0 1) "-")) + names (-> (if (symbol? member) + (string/replace n #"^-" "") + n) + (string/split #"\."))] + [field? names])) + +(defmacro $ + "Access member in a javascript object, in a Closure-safe way. + 'member' is assumed to be a field if it is a keyword or if + the name starts with '-', otherwise the named function is + called with the optional args. + 'member' may contain '.', to allow access in nested objects. + If 'object' is a symbol it is not allowed contain '.'. + + ($ o :foo) is equivalent to (.-foo o), except that it gives + the same result under advanced compilation. + ($ o foo arg1 arg2) is the same as (.foo o arg1 arg2)." + [object member & args] + (let [[field names] (dot-args object member)] + (if field + (do + (assert (empty? args) + (str "Passing args to field doesn't make sense: " member)) + `(aget ~object ~@names)) + (js-call (list* 'aget object names) args)))) + +(defmacro $! + "Set field in a javascript object, in a Closure-safe way. + 'field' should be a keyword or a symbol starting with '-'. + 'field' may contain '.', to allow access in nested objects. + If 'object' is a symbol it is not allowed contain '.'. + + ($! o :foo 1) is equivalent to (set! (.-foo o) 1), except that it + gives the same result under advanced compilation." + [object field value] + (let [[field names] (dot-args object field)] + (assert field (str "Field name must start with - in " field)) + `(aset ~object ~@names ~value))) + +(defmacro .' [& args] + ;; Deprecated since names starting with . cause problems with bootstrapped cljs. + (let [ns (str cljs.analyzer/*cljs-ns*) + line (:line (meta &form))] + (binding [*out* *err*] + (println "WARNING: mranderson047.reagent.v0v6v0.reagent.interop/.' is deprecated in " ns " line " line + ". Use mranderson047.reagent.v0v6v0.reagent.interop/$ instead."))) + `($ ~@args)) + +(defmacro .! [& args] + ;; Deprecated since names starting with . cause problems with bootstrapped cljs. + (let [ns (str cljs.analyzer/*cljs-ns*) + line (:line (meta &form))] + (binding [*out* *err*] + (println "WARNING: mranderson047.reagent.v0v6v0.reagent.interop/.! is deprecated in " ns " line " line + ". Use mranderson047.reagent.v0v6v0.reagent.interop/$! instead."))) + `($! ~@args)) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/interop.cljs b/src/mranderson047/reagent/v0v6v0/reagent/interop.cljs new file mode 100644 index 0000000..f69691d --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/interop.cljs @@ -0,0 +1,2 @@ +(ns mranderson047.reagent.v0v6v0.reagent.interop + (:require-macros [mranderson047.reagent.v0v6v0.reagent.interop])) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/ratom.clj b/src/mranderson047/reagent/v0v6v0/reagent/ratom.clj new file mode 100644 index 0000000..b05051b --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/ratom.clj @@ -0,0 +1,53 @@ +(ns mranderson047.reagent.v0v6v0.reagent.ratom + (:refer-clojure :exclude [run!]) + (:require [mranderson047.reagent.v0v6v0.reagent.debug :as d])) + +(defmacro reaction [& body] + `(mranderson047.reagent.v0v6v0.reagent.ratom/make-reaction + (fn [] ~@body))) + +(defmacro run! + "Runs body immediately, and runs again whenever atoms deferenced in the body change. Body should side effect." + [& body] + `(let [co# (mranderson047.reagent.v0v6v0.reagent.ratom/make-reaction (fn [] ~@body) + :auto-run true)] + (deref co#) + co#)) + +(defmacro with-let [bindings & body] + (assert (vector? bindings)) + (let [v (gensym "with-let") + k (keyword v) + init (gensym "init") + bs (into [init `(zero? (alength ~v))] + (map-indexed (fn [i x] + (if (even? i) + x + (let [j (quot i 2)] + `(if ~init + (aset ~v ~j ~x) + (aget ~v ~j))))) + bindings)) + [forms destroy] (let [fin (last body)] + (if (and (list? fin) + (= 'finally (first fin))) + [(butlast body) `(fn [] ~@(rest fin))] + [body nil])) + add-destroy (when destroy + `(let [destroy# ~destroy] + (if (mranderson047.reagent.v0v6v0.reagent.ratom/reactive?) + (when (nil? (.-destroy ~v)) + (set! (.-destroy ~v) destroy#)) + (destroy#)))) + asserting (if *assert* true false)] + `(let [~v (mranderson047.reagent.v0v6v0.reagent.ratom/with-let-values ~k)] + (when ~asserting + (when-some [c# mranderson047.reagent.v0v6v0.reagent.ratom/*ratom-context*] + (when (== (.-generation ~v) (.-ratomGeneration c#)) + (d/error "Warning: The same with-let is being used more " + "than once in the same reactive context.")) + (set! (.-generation ~v) (.-ratomGeneration c#)))) + (let ~bs + (let [res# (do ~@forms)] + ~add-destroy + res#))))) diff --git a/src/mranderson047/reagent/v0v6v0/reagent/ratom.cljs b/src/mranderson047/reagent/v0v6v0/reagent/ratom.cljs new file mode 100644 index 0000000..3ceee38 --- /dev/null +++ b/src/mranderson047/reagent/v0v6v0/reagent/ratom.cljs @@ -0,0 +1,592 @@ +(ns mranderson047.reagent.v0v6v0.reagent.ratom + (:refer-clojure :exclude [atom]) + (:require-macros [mranderson047.reagent.v0v6v0.reagent.ratom :refer [with-let]]) + (:require [mranderson047.reagent.v0v6v0.reagent.impl.util :as util] + [mranderson047.reagent.v0v6v0.reagent.debug :refer-macros [dbg log warn error dev? time]] + [mranderson047.reagent.v0v6v0.reagent.impl.batching :as batch] + [clojure.set :as s])) + +(declare ^:dynamic *ratom-context*) +(defonce ^boolean debug false) +(defonce ^:private generation 0) +(defonce ^:private -running (clojure.core/atom 0)) + +(defn ^boolean reactive? [] + (some? *ratom-context*)) + + +;;; Utilities + +(defn running [] + (+ @-running)) + +(defn- ^number arr-len [x] + (if (nil? x) 0 (alength x))) + +(defn- ^boolean arr-eq [x y] + (let [len (arr-len x)] + (and (== len (arr-len y)) + (loop [i 0] + (or (== i len) + (if (identical? (aget x i) (aget y i)) + (recur (inc i)) + false)))))) + +(defn- in-context [obj f] + (binding [*ratom-context* obj] + (f))) + +(defn- deref-capture [f r] + (set! (.-captured r) nil) + (when (dev?) + (set! (.-ratomGeneration r) (set! generation (inc generation)))) + (let [res (in-context r f) + c (.-captured r)] + (set! (.-dirty? r) false) + ;; Optimize common case where derefs occur in same order + (when-not (arr-eq c (.-watching r)) + (._update-watching r c)) + res)) + +(defn- notify-deref-watcher! [derefed] + (when-some [r *ratom-context*] + (let [c (.-captured r)] + (if (nil? c) + (set! (.-captured r) (array derefed)) + (.push c derefed))))) + +(defn- check-watches [old new] + (when debug + (swap! -running + (- (count new) (count old)))) + new) + +(defn- add-w [this key f] + (let [w (.-watches this)] + (set! (.-watches this) (check-watches w (assoc w key f))) + (set! (.-watchesArr this) nil))) + +(defn- remove-w [this key] + (let [w (.-watches this)] + (set! (.-watches this) (check-watches w (dissoc w key))) + (set! (.-watchesArr this) nil))) + +(defn- notify-w [this old new] + (let [w (.-watchesArr this) + a (if (nil? w) + ;; Copy watches to array for speed + (->> (.-watches this) + (reduce-kv #(doto %1 (.push %2) (.push %3)) #js[]) + (set! (.-watchesArr this))) + w)] + (let [len (alength a)] + (loop [i 0] + (when (< i len) + (let [k (aget a i) + f (aget a (inc i))] + (f k this old new)) + (recur (+ 2 i))))))) + +(defn- pr-atom [a writer opts s] + (-write writer (str "#<" s " ")) + (pr-writer (binding [*ratom-context* nil] (-deref a)) writer opts) + (-write writer ">")) + + +;;; Queueing + +(defonce ^:private rea-queue nil) + +(defn- rea-enqueue [r] + (when (nil? rea-queue) + (set! rea-queue (array)) + (batch/schedule)) + (.push rea-queue r)) + +(defn flush! [] + (loop [] + (let [q rea-queue] + (when-not (nil? q) + (set! rea-queue nil) + (dotimes [i (alength q)] + (._queued-run (aget q i))) + (recur))))) + +(set! batch/ratom-flush flush!) + + +;;; Atom + +(defprotocol IReactiveAtom) + +(deftype RAtom [^:mutable state meta validator ^:mutable watches] + IAtom + IReactiveAtom + + IEquiv + (-equiv [o other] (identical? o other)) + + IDeref + (-deref [this] + (notify-deref-watcher! this) + state) + + IReset + (-reset! [a new-value] + (when-not (nil? validator) + (assert (validator new-value) "Validator rejected reference state")) + (let [old-value state] + (set! state new-value) + (when-not (nil? watches) + (notify-w a old-value new-value)) + new-value)) + + ISwap + (-swap! [a f] (-reset! a (f state))) + (-swap! [a f x] (-reset! a (f state x))) + (-swap! [a f x y] (-reset! a (f state x y))) + (-swap! [a f x y more] (-reset! a (apply f state x y more))) + + IMeta + (-meta [_] meta) + + IPrintWithWriter + (-pr-writer [a w opts] (pr-atom a w opts "Atom:")) + + IWatchable + (-notify-watches [this old new] (notify-w this old new)) + (-add-watch [this key f] (add-w this key f)) + (-remove-watch [this key] (remove-w this key)) + + IHash + (-hash [this] (goog/getUid this))) + +(defn atom + "Like clojure.core/atom, except that it keeps track of derefs." + ([x] (RAtom. x nil nil nil)) + ([x & {:keys [meta validator]}] (RAtom. x meta validator nil))) + + +;;; track + +(declare make-reaction) + +(def ^{:private true :const true} cache-key "reagReactionCache") + +(defn- cached-reaction [f o k obj destroy] + (let [m (aget o cache-key) + m (if (nil? m) {} m) + r (m k nil)] + (cond + (some? r) (-deref r) + (nil? *ratom-context*) (f) + :else (let [r (make-reaction + f :on-dispose (fn [x] + (when debug (swap! -running dec)) + (as-> (aget o cache-key) _ + (dissoc _ k) + (aset o cache-key _)) + (when (some? obj) + (set! (.-reaction obj) nil)) + (when (some? destroy) + (destroy x)))) + v (-deref r)] + (aset o cache-key (assoc m k r)) + (when debug (swap! -running inc)) + (when (some? obj) + (set! (.-reaction obj) r)) + v)))) + +(deftype Track [f args ^:mutable reaction] + IReactiveAtom + + IDeref + (-deref [this] + (if-some [r reaction] + (-deref r) + (cached-reaction #(apply f args) f args this nil))) + + IEquiv + (-equiv [_ other] + (and (instance? Track other) + (= f (.-f other)) + (= args (.-args other)))) + + IHash + (-hash [_] (hash [f args])) + + IPrintWithWriter + (-pr-writer [a w opts] (pr-atom a w opts "Track:"))) + +(defn make-track [f args] + (Track. f args nil)) + +(defn make-track! [f args] + (let [t (make-track f args) + r (make-reaction #(-deref t) + :auto-run true)] + @r + r)) + +(defn track [f & args] + {:pre [(ifn? f)]} + (make-track f args)) + +(defn track! [f & args] + {:pre [(ifn? f)]} + (make-track! f args)) + +;;; cursor + +(deftype RCursor [ratom path ^:mutable reaction + ^:mutable state ^:mutable watches] + IAtom + IReactiveAtom + + IEquiv + (-equiv [_ other] + (and (instance? RCursor other) + (= path (.-path other)) + (= ratom (.-ratom other)))) + + Object + (_peek [this] + (binding [*ratom-context* nil] + (-deref this))) + + (_set-state [this oldstate newstate] + (when-not (identical? oldstate newstate) + (set! state newstate) + (when (some? watches) + (notify-w this oldstate newstate)))) + + IDeref + (-deref [this] + (let [oldstate state + newstate (if-some [r reaction] + (-deref r) + (let [f (if (satisfies? IDeref ratom) + #(get-in @ratom path) + #(ratom path))] + (cached-reaction f ratom path this nil)))] + (._set-state this oldstate newstate) + newstate)) + + IReset + (-reset! [this new-value] + (let [oldstate state] + (._set-state this oldstate new-value) + (if (satisfies? IDeref ratom) + (if (= path []) + (reset! ratom new-value) + (swap! ratom assoc-in path new-value)) + (ratom path new-value)) + new-value)) + + ISwap + (-swap! [a f] (-reset! a (f (._peek a)))) + (-swap! [a f x] (-reset! a (f (._peek a) x))) + (-swap! [a f x y] (-reset! a (f (._peek a) x y))) + (-swap! [a f x y more] (-reset! a (apply f (._peek a) x y more))) + + IPrintWithWriter + (-pr-writer [a w opts] (pr-atom a w opts (str "Cursor: " path))) + + IWatchable + (-notify-watches [this old new] (notify-w this old new)) + (-add-watch [this key f] (add-w this key f)) + (-remove-watch [this key] (remove-w this key)) + + IHash + (-hash [_] (hash [ratom path]))) + +(defn cursor + [src path] + (assert (or (satisfies? IReactiveAtom src) + (and (ifn? src) + (not (vector? src)))) + (str "src must be a reactive atom or a function, not " + (pr-str src))) + (RCursor. src path nil nil nil)) + + +;;; with-let support + +(defn with-let-destroy [v] + (when-some [f (.-destroy v)] + (f))) + +(defn with-let-values [key] + (if-some [c *ratom-context*] + (cached-reaction array c key + nil with-let-destroy) + (array))) + + +;;;; reaction + +(defprotocol IDisposable + (dispose! [this]) + (add-on-dispose! [this f])) + +(defprotocol IRunnable + (run [this])) + +(defn- handle-reaction-change [this sender old new] + (._handle-change this sender old new)) + + +(deftype Reaction [f ^:mutable state ^:mutable ^boolean dirty? ^boolean nocache? + ^:mutable watching ^:mutable watches ^:mutable auto-run + ^:mutable caught] + IAtom + IReactiveAtom + + IWatchable + (-notify-watches [this old new] (notify-w this old new)) + (-add-watch [this key f] (add-w this key f)) + (-remove-watch [this key] + (let [was-empty (empty? watches)] + (remove-w this key) + (when (and (not was-empty) + (empty? watches) + (nil? auto-run)) + (dispose! this)))) + + IReset + (-reset! [a newval] + (assert (fn? (.-on-set a)) "Reaction is read only.") + (let [oldval state] + (set! state newval) + (.on-set a oldval newval) + (notify-w a oldval newval) + newval)) + + ISwap + (-swap! [a f] (-reset! a (f (._peek-at a)))) + (-swap! [a f x] (-reset! a (f (._peek-at a) x))) + (-swap! [a f x y] (-reset! a (f (._peek-at a) x y))) + (-swap! [a f x y more] (-reset! a (apply f (._peek-at a) x y more))) + + Object + (_peek-at [this] + (binding [*ratom-context* nil] + (-deref this))) + + (_handle-change [this sender oldval newval] + (when-not (or (identical? oldval newval) + dirty?) + (if (nil? auto-run) + (do + (set! dirty? true) + (rea-enqueue this)) + (if (true? auto-run) + (._run this false) + (auto-run this))))) + + (_update-watching [this derefed] + (let [new (set derefed) + old (set watching)] + (set! watching derefed) + (doseq [w (s/difference new old)] + (-add-watch w this handle-reaction-change)) + (doseq [w (s/difference old new)] + (-remove-watch w this)))) + + (_queued-run [this] + (when (and dirty? (some? watching)) + (._run this true))) + + (_try-capture [this f] + (try + (set! caught nil) + (deref-capture f this) + (catch :default e + (set! state e) + (set! caught e) + (set! dirty? false)))) + + (_run [this check] + (let [oldstate state + res (if check + (._try-capture this f) + (deref-capture f this))] + (when-not nocache? + (set! state res) + ;; Use = to determine equality from reactions, since + ;; they are likely to produce new data structures. + (when-not (or (nil? watches) + (= oldstate res)) + (notify-w this oldstate res))) + res)) + + (_set-opts [this {:keys [auto-run on-set on-dispose no-cache]}] + (when (some? auto-run) + (set! (.-auto-run this) auto-run)) + (when (some? on-set) + (set! (.-on-set this) on-set)) + (when (some? on-dispose) + (set! (.-on-dispose this) on-dispose)) + (when (some? no-cache) + (set! (.-nocache? this) no-cache))) + + IRunnable + (run [this] + (flush!) + (._run this false)) + + IDeref + (-deref [this] + (when-some [e caught] + (throw e)) + (let [non-reactive (nil? *ratom-context*)] + (when non-reactive + (flush!)) + (if (and non-reactive (nil? auto-run)) + (when dirty? + (let [oldstate state] + (set! state (f)) + (when-not (or (nil? watches) (= oldstate state)) + (notify-w this oldstate state)))) + (do + (notify-deref-watcher! this) + (when dirty? + (._run this false))))) + state) + + IDisposable + (dispose! [this] + (let [s state + wg watching] + (set! watching nil) + (set! state nil) + (set! auto-run nil) + (set! dirty? true) + (doseq [w (set wg)] + (-remove-watch w this)) + (when (some? (.-on-dispose this)) + (.on-dispose this s)) + (when-some [a (.-on-dispose-arr this)] + (dotimes [i (alength a)] + ((aget a i) this))))) + + (add-on-dispose! [this f] + ;; f is called with the reaction as argument when it is no longer active + (if-some [a (.-on-dispose-arr this)] + (.push a f) + (set! (.-on-dispose-arr this) (array f)))) + + IEquiv + (-equiv [o other] (identical? o other)) + + IPrintWithWriter + (-pr-writer [a w opts] (pr-atom a w opts (str "Reaction " (hash a) ":"))) + + IHash + (-hash [this] (goog/getUid this))) + + +(defn make-reaction [f & {:keys [auto-run on-set on-dispose]}] + (let [reaction (Reaction. f nil true false nil nil nil nil)] + (._set-opts reaction {:auto-run auto-run + :on-set on-set + :on-dispose on-dispose}) + reaction)) + + + +(def ^:private temp-reaction (make-reaction nil)) + +(defn run-in-reaction [f obj key run opts] + (let [r temp-reaction + res (deref-capture f r)] + (when-not (nil? (.-watching r)) + (set! temp-reaction (make-reaction nil)) + (._set-opts r opts) + (set! (.-f r) f) + (set! (.-auto-run r) #(run obj)) + (aset obj key r)) + res)) + +(defn check-derefs [f] + (let [ctx (js-obj) + res (in-context ctx f)] + [res (some? (.-captured ctx))])) + + +;;; wrap + +(deftype Wrapper [^:mutable state callback ^:mutable ^boolean changed + ^:mutable watches] + + IAtom + + IDeref + (-deref [this] + (when (dev?) + (when (and changed (some? *ratom-context*)) + (warn "derefing stale wrap: " + (pr-str this)))) + state) + + IReset + (-reset! [this newval] + (let [oldval state] + (set! changed true) + (set! state newval) + (when (some? watches) + (notify-w this oldval newval)) + (callback newval) + newval)) + + ISwap + (-swap! [a f] (-reset! a (f state))) + (-swap! [a f x] (-reset! a (f state x))) + (-swap! [a f x y] (-reset! a (f state x y))) + (-swap! [a f x y more] (-reset! a (apply f state x y more))) + + IEquiv + (-equiv [_ other] + (and (instance? Wrapper other) + ;; If either of the wrappers have changed, equality + ;; cannot be relied on. + (not changed) + (not (.-changed other)) + (= state (.-state other)) + (= callback (.-callback other)))) + + IWatchable + (-notify-watches [this old new] (notify-w this old new)) + (-add-watch [this key f] (add-w this key f)) + (-remove-watch [this key] (remove-w this key)) + + IPrintWithWriter + (-pr-writer [a w opts] (pr-atom a w opts "Wrap:"))) + +(defn make-wrapper [value callback-fn args] + (Wrapper. value + (util/partial-ifn. callback-fn args nil) + false nil)) + + + + +#_(do + (defn ratom-perf [] + (set! debug false) + (dotimes [_ 10] + (let [nite 100000 + a (atom 0) + f (fn [] + (quot @a 10)) + mid (make-reaction f) + res (track! (fn [] + ;; (with-let [x 1]) + ;; @(track f) + (inc @mid) + ))] + @res + (time (dotimes [x nite] + (swap! a inc) + (flush!))) + (dispose! res)))) + (ratom-perf)) From 0a7de66e4d41c8e056e5fd94b9dc2e80dbf13d35 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Fri, 22 Dec 2017 17:33:47 +1300 Subject: [PATCH 11/60] Use the bundled reagent instead of the standard one --- src/day8/re_frame/trace.cljs | 37 ++++++++++++-------- src/day8/re_frame/trace/events.cljs | 2 +- src/day8/re_frame/trace/preload.cljs | 2 +- src/day8/re_frame/trace/styles.cljs | 2 +- src/day8/re_frame/trace/view/app_db.cljs | 4 +-- src/day8/re_frame/trace/view/components.cljs | 6 ++-- src/day8/re_frame/trace/view/container.cljs | 1 - src/day8/re_frame/trace/view/traces.cljs | 6 ++-- 8 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/day8/re_frame/trace.cljs b/src/day8/re_frame/trace.cljs index ac90ab0..5e917be 100644 --- a/src/day8/re_frame/trace.cljs +++ b/src/day8/re_frame/trace.cljs @@ -12,7 +12,7 @@ [cljs.pprint :as pprint] [clojure.string :as str] [clojure.set :as set] - [reagent.core :as r] + [reagent.core :as real-reagent] [reagent.interop :refer-macros [$ $!]] [reagent.impl.util :as util] [reagent.impl.component :as component] @@ -21,7 +21,8 @@ [goog.object :as gob] [re-frame.interop :as interop] [devtools.formatters.core :as devtools] - [mranderson047.re-frame.v0v10v2.re-frame.core :as rf])) + [mranderson047.re-frame.v0v10v2.re-frame.core :as rf] + [mranderson047.reagent.v0v6v0.reagent.core :as r])) ;; from https://github.com/reagent-project/reagent/blob/3fd0f1b1d8f43dbf169d136f0f905030d7e093bd/src/reagent/impl/component.cljs#L274 @@ -73,11 +74,14 @@ res)))))}) +(defonce real-custom-wrapper reagent.impl.component/custom-wrapper) +(defonce real-next-tick reagent.impl.batching/next-tick) +(defonce real-schedule reagent.impl.batching/schedule) +(defonce schedule-fn-scheduled? (atom false)) + (defn monkey-patch-reagent [] (let [#_#_real-renderer reagent.impl.component/do-render - real-custom-wrapper reagent.impl.component/custom-wrapper - real-next-tick reagent.impl.batching/next-tick - real-schedule reagent.impl.batching/schedule] + ] #_(set! reagent.impl.component/do-render @@ -106,16 +110,21 @@ (real-custom-wrapper key f)))) - ;; When this is enabled, the rendering of the trace panel causes an infinite loop. - #_(set! reagent.impl.batching/next-tick (fn [f] - (real-next-tick (fn [] - (trace/with-trace {:op-type :raf} - (f)))))) + (set! reagent.impl.batching/next-tick + (fn [f] + (real-next-tick (fn [] + (trace/with-trace {:op-type :raf} + (f) + (trace/with-trace {:op-type :raf-end})))))) - #_(set! reagent.impl.batching/schedule schedule - #_(fn [] - (reagent.impl.batching/do-after-render (fn [] (trace/with-trace {:op-type :raf-end}))) - (real-schedule))))) + #_(set! reagent.impl.batching/schedule + (fn [] + (reagent.impl.batching/do-after-render + (fn [] + (when @schedule-fn-scheduled? + (trace/with-trace {:op-type :do-after-render}) + (reset! schedule-fn-scheduled? false)))) + (real-schedule))))) (defn init-tracing! diff --git a/src/day8/re_frame/trace/events.cljs b/src/day8/re_frame/trace/events.cljs index 7f08619..22829c1 100644 --- a/src/day8/re_frame/trace/events.cljs +++ b/src/day8/re_frame/trace/events.cljs @@ -1,9 +1,9 @@ (ns day8.re-frame.trace.events (:require [mranderson047.re-frame.v0v10v2.re-frame.core :as rf] + [mranderson047.reagent.v0v6v0.reagent.core :as r] [day8.re-frame.trace.utils.utils :as utils] [day8.re-frame.trace.utils.localstorage :as localstorage] [clojure.string :as str] - [reagent.core :as r] [goog.object] [re-frame.db] [day8.re-frame.trace.view.container :as container] diff --git a/src/day8/re_frame/trace/preload.cljs b/src/day8/re_frame/trace/preload.cljs index c95e277..e207625 100644 --- a/src/day8/re_frame/trace/preload.cljs +++ b/src/day8/re_frame/trace/preload.cljs @@ -5,5 +5,5 @@ ;; Use this namespace with the :preloads compiler option to perform the necessary setup for enabling tracing: ;; {:compiler {:preloads [day8.re-frame.trace.preload] ...}} (trace/init-db!) -(trace/init-tracing!) +(defonce _ (trace/init-tracing!)) (trace/inject-devtools!) diff --git a/src/day8/re_frame/trace/styles.cljs b/src/day8/re_frame/trace/styles.cljs index 463c143..d065e29 100644 --- a/src/day8/re_frame/trace/styles.cljs +++ b/src/day8/re_frame/trace/styles.cljs @@ -158,7 +158,7 @@ [(s/& ".trace--sub-run") [".trace--op" {:color dark-purple}]] [(s/& ".trace--event") - {:border-top [["1px" light-gray "solid"]]} + {:border-top [["2px" common/border-line-color "solid"]]} [".trace--op" {:color common/event-color}]] [(s/& ".trace--render") [".trace--op" {:color dark-skyblue}]] diff --git a/src/day8/re_frame/trace/view/app_db.cljs b/src/day8/re_frame/trace/view/app_db.cljs index a2bb465..6717021 100644 --- a/src/day8/re_frame/trace/view/app_db.cljs +++ b/src/day8/re_frame/trace/view/app_db.cljs @@ -1,11 +1,11 @@ (ns day8.re-frame.trace.view.app-db - (:require [reagent.core :as r] - [clojure.string :as str] + (:require [clojure.string :as str] [devtools.prefs] [devtools.formatters.core] [day8.re-frame.trace.view.components :as components] [day8.re-frame.trace.utils.re-com :as re-com] [mranderson047.re-frame.v0v10v2.re-frame.core :as rf] + [mranderson047.reagent.v0v6v0.reagent.core :as r] [day8.re-frame.trace.utils.re-com :as rc]) (:require-macros [day8.re-frame.trace.utils.macros :as macros])) diff --git a/src/day8/re_frame/trace/view/components.cljs b/src/day8/re_frame/trace/view/components.cljs index 2f102b6..24fe1a3 100644 --- a/src/day8/re_frame/trace/view/components.cljs +++ b/src/day8/re_frame/trace/view/components.cljs @@ -1,11 +1,11 @@ (ns day8.re-frame.trace.view.components - (:require [reagent.core :as r] - [clojure.string :as str] + (:require [clojure.string :as str] [goog.fx.dom :as fx] [mranderson047.re-frame.v0v10v2.re-frame.core :as rf] [day8.re-frame.trace.utils.localstorage :as localstorage] [clojure.string :as str] - [day8.re-frame.trace.utils.re-com :as rc]) + [day8.re-frame.trace.utils.re-com :as rc] + [mranderson047.reagent.v0v6v0.reagent.core :as r]) (:require-macros [day8.re-frame.trace.utils.macros :refer [with-cljs-devtools-prefs]])) (defn search-input [{:keys [title placeholder on-save on-change on-stop]}] diff --git a/src/day8/re_frame/trace/view/container.cljs b/src/day8/re_frame/trace/view/container.cljs index 5b2d233..510467b 100644 --- a/src/day8/re_frame/trace/view/container.cljs +++ b/src/day8/re_frame/trace/view/container.cljs @@ -11,7 +11,6 @@ [garden.core :refer [css style]] [garden.units :refer [px]] [re-frame.trace] - [reagent.core :as r] [day8.re-frame.trace.utils.re-com :as rc] [day8.re-frame.trace.common-styles :as common])) diff --git a/src/day8/re_frame/trace/view/traces.cljs b/src/day8/re_frame/trace/view/traces.cljs index 53259b0..1746d2e 100644 --- a/src/day8/re_frame/trace/view/traces.cljs +++ b/src/day8/re_frame/trace/view/traces.cljs @@ -3,10 +3,10 @@ [day8.re-frame.trace.utils.pretty-print-condensed :as pp] [re-frame.trace :as trace] [clojure.string :as str] - [reagent.core :as r] [day8.re-frame.trace.utils.localstorage :as localstorage] [cljs.pprint :as pprint] [clojure.set :as set] + [mranderson047.reagent.v0v6v0.reagent.core :as r] [mranderson047.re-frame.v0v10v2.re-frame.core :as rf])) (defn query->fn [query] @@ -95,7 +95,8 @@ true (remove (fn [trace] (and (= :sub/create (:op-type trace)) (get-in trace [:tags :cached?])))) (seq @categories) (filter (fn [trace] (when (contains? @categories (:op-type trace)) trace))) - (seq @filter-items) (filter (apply every-pred (map query->fn @filter-items)))) + (seq @filter-items) (filter (apply every-pred (map query->fn @filter-items))) + true (sort-by :id)) save-query (fn [_] (if (and (= @filter-type :slower-than) (js/isNaN (js/parseFloat @filter-input))) @@ -103,6 +104,7 @@ (do (reset! input-error false) (add-filter filter-items @filter-input @filter-type))))] + [:div.tab-contents [:div.filter [:div.filter-control From 6ea438146d33a9c1e12ac850eef5ea2ea05961a2 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Thu, 4 Jan 2018 09:35:14 +1300 Subject: [PATCH 12/60] Make settings wrench orange when opening settings panel --- .../re_frame/trace/images/orange-wrench.svg | 14 ++++++++++++++ src/day8/re_frame/trace/view/container.cljs | 17 ++++++++++------- 2 files changed, 24 insertions(+), 7 deletions(-) create mode 100644 resources/day8/re_frame/trace/images/orange-wrench.svg diff --git a/resources/day8/re_frame/trace/images/orange-wrench.svg b/resources/day8/re_frame/trace/images/orange-wrench.svg new file mode 100644 index 0000000..fa79786 --- /dev/null +++ b/resources/day8/re_frame/trace/images/orange-wrench.svg @@ -0,0 +1,14 @@ + +Vector +Created using Figma + + + + + + + + + diff --git a/src/day8/re_frame/trace/view/container.cljs b/src/day8/re_frame/trace/view/container.cljs index 510467b..711ff33 100644 --- a/src/day8/re_frame/trace/view/container.cljs +++ b/src/day8/re_frame/trace/view/container.cljs @@ -21,16 +21,17 @@ (def open-external (macros/slurp-macro "day8/re_frame/trace/images/logout.svg")) (def settings-svg (macros/slurp-macro "day8/re_frame/trace/images/wrench.svg")) +(def orange-settings-svg (macros/slurp-macro "day8/re_frame/trace/images/orange-wrench.svg")) (def pause-svg (macros/slurp-macro "day8/re_frame/trace/images/pause.svg")) (def outer-margins {:margin (str "0px " common/gs-19s)}) (defn devtools-inner [traces opts] - (let [selected-tab (rf/subscribe [:settings/selected-tab]) - panel-type (:panel-type opts) - external-window? (= panel-type :popup) - unloading? (rf/subscribe [:global/unloading?]) - show-tabs? (not= @selected-tab :settings)] + (let [selected-tab (rf/subscribe [:settings/selected-tab]) + panel-type (:panel-type opts) + external-window? (= panel-type :popup) + unloading? (rf/subscribe [:global/unloading?]) + showing-settings? (= @selected-tab :settings)] [:div.panel-content {:style {:width "100%" :display "flex" :flex-direction "column" :background-color common/standard-background-color}} [rc/h-box @@ -56,14 +57,16 @@ [:img.nav-icon {:title "Settings" :src (str "data:image/svg+xml;utf8," - settings-svg) + (if showing-settings? + orange-settings-svg + settings-svg)) :on-click #(rf/dispatch [:settings/toggle-settings])}] (when-not external-window? [:img.nav-icon.active {:src (str "data:image/svg+xml;utf8," open-external) :on-click #(rf/dispatch-sync [:global/launch-external])}])]]]] - (when show-tabs? + (when-not showing-settings? [rc/h-box :class "panel-content-tabs" :justify :between From 6e060b9903d2bbc492bf5a090604369724632745 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Thu, 4 Jan 2018 09:55:39 +1300 Subject: [PATCH 13/60] Show Settings title and settings button when in settings panel --- src/day8/re_frame/trace/common_styles.cljs | 10 +++++++++- src/day8/re_frame/trace/styles.cljs | 11 ++++++++++- src/day8/re_frame/trace/view/container.cljs | 13 +++++++++---- src/day8/re_frame/trace/view/settings.cljs | 4 ---- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/src/day8/re_frame/trace/common_styles.cljs b/src/day8/re_frame/trace/common_styles.cljs index 45f984d..d58317d 100644 --- a/src/day8/re_frame/trace/common_styles.cljs +++ b/src/day8/re_frame/trace/common_styles.cljs @@ -99,6 +99,9 @@ (def strong-button-background-color blue-modern-color) (def strong-button-border-color "#589AB8") ;; A darker version of the standard blue +(def active-button-text-color "white") +(def active-button-background-color "#F2994A") + (def muted-button-text-color strong-button-background-color) (def muted-button-background-color "white") (def muted-button-border-color white-background-border-color) @@ -112,6 +115,7 @@ (def sidebar-item-selected-color "#3C3C45") ;; Slightly lighter dark black (def sidebar-item-check-color strong-button-background-color) (def sidebar-text-color "white") +(def navbar-text-color "white") (def wizard-panel-background-color "#636A6F") ;; Very dark grey (def wizard-panel-text-color "white") @@ -159,6 +163,9 @@ [:.bm-strong-button {:color strong-button-text-color :background-color strong-button-background-color :border (str "1px solid " strong-button-border-color)}] + [:.bm-active-button {:color active-button-text-color + :background-color active-button-background-color + :border (str "1px solid " active-button-background-color)}] [:.bm-muted-button {:color muted-button-text-color :background-color muted-button-background-color :border (str "1px solid " strong-button-border-color)}] @@ -190,7 +197,8 @@ [:.raptor-editable-block {:color default-text-color}] ;; button components - to 26px high - [:button {:height "26px"}] + [:button {:height "26px" + :border-radius "3px"}] [:.btn {:padding "0px 12px"}] ;; input-text - set to 26px high diff --git a/src/day8/re_frame/trace/styles.cljs b/src/day8/re_frame/trace/styles.cljs index d065e29..3b05ee1 100644 --- a/src/day8/re_frame/trace/styles.cljs +++ b/src/day8/re_frame/trace/styles.cljs @@ -313,7 +313,16 @@ [(s/& :.external-window) {:display "flex" :height (percent 100) :flex "1 1 auto"}] - [:.panel-content-top {}] + [:.panel-content-top {} + [:.bm-title-text {:color common/navbar-text-color}] + [:button {:width "81px" + :height "31px" + :font-weight 700 + :font-size "14px" + :cursor "pointer" + :text-align "center" + :padding "0 5px" + :margin "0 5px"}]] [:.panel-content-tabs {:margin-left common/gs-19}] [:.panel-content-scrollable panel-mixin] [:.epoch-panel panel-mixin] diff --git a/src/day8/re_frame/trace/view/container.cljs b/src/day8/re_frame/trace/view/container.cljs index 711ff33..43447ae 100644 --- a/src/day8/re_frame/trace/view/container.cljs +++ b/src/day8/re_frame/trace/view/container.cljs @@ -43,13 +43,18 @@ :align :center :gap common/gs-12s :children - [[:span.arrow "◀"] - [:span.event-header "[:some-namespace/blah 34 \"Hello\""] - [:span.arrow "▶"]]] + (if showing-settings? + [[rc/label :class "bm-title-text" :label "Settings"]] + [[:span.arrow "◀"] + [:span.event-header "[:some-namespace/blah 34 \"Hello\""] + [:span.arrow "▶"]])] [rc/h-box :align :center :children - [[:img.nav-icon + [(when showing-settings? + [:button {:class "bm-active-button" + :on-click #(rf/dispatch [:settings/toggle-settings])} "Done"]) + [:img.nav-icon {:title "Pause" :src (str "data:image/svg+xml;utf8," pause-svg) diff --git a/src/day8/re_frame/trace/view/settings.cljs b/src/day8/re_frame/trace/view/settings.cljs index 105e939..ce28806 100644 --- a/src/day8/re_frame/trace/view/settings.cljs +++ b/src/day8/re_frame/trace/view/settings.cljs @@ -8,10 +8,6 @@ :gap common/gs-19s :children [[rc/label - :label "Settings" - :class "bm-title-text"] - - [rc/label :label "Limits" :class "bm-heading-text"] From 28cef85b119c75050fa5e18ad259fc0b634fde19 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Fri, 5 Jan 2018 10:32:26 +1300 Subject: [PATCH 14/60] Make header match mockup --- src/day8/re_frame/trace/view/container.cljs | 99 +++++++++++++-------- src/day8/re_frame/trace/view/settings.cljs | 69 +++++++++----- 2 files changed, 110 insertions(+), 58 deletions(-) diff --git a/src/day8/re_frame/trace/view/container.cljs b/src/day8/re_frame/trace/view/container.cljs index 43447ae..d06e93c 100644 --- a/src/day8/re_frame/trace/view/container.cljs +++ b/src/day8/re_frame/trace/view/container.cljs @@ -26,6 +26,61 @@ (def outer-margins {:margin (str "0px " common/gs-19s)}) +(defn right-hand-buttons [external-window?] + (let [selected-tab (rf/subscribe [:settings/selected-tab]) + showing-settings? (= @selected-tab :settings)] + [rc/h-box + :align :center + :children + [(when showing-settings? + [:button {:class "bm-active-button" + :on-click #(rf/dispatch [:settings/toggle-settings])} "Done"]) + [:img.nav-icon + {:title "Pause" + :src (str "data:image/svg+xml;utf8," + pause-svg) + :on-click #(rf/dispatch [:settings/pause])}] + [:img.nav-icon + {:title "Settings" + :src (str "data:image/svg+xml;utf8," + (if showing-settings? orange-settings-svg settings-svg)) + :on-click #(rf/dispatch [:settings/toggle-settings])}] + (when-not external-window? + [:img.nav-icon.active + {:src (str "data:image/svg+xml;utf8," + open-external) + :on-click #(rf/dispatch-sync [:global/launch-external])}])]]) + ) + +(defn settings-header [external-window?] + [[rc/h-box + :align :center + :size "auto" + :gap common/gs-12s + :children + [[rc/label :class "bm-title-text" :label "Settings"]]] + ;; TODO: this line needs to be between Done and other buttons + [rc/gap-f :size common/gs-12s] + [rc/line :size "2px" :color common/sidebar-heading-divider-color] + [rc/gap-f :size common/gs-12s] + [right-hand-buttons external-window?]]) + +(defn standard-header [external-window?] + [[rc/h-box + :align :center + :size "auto" + :gap common/gs-12s + :children + [[:span.arrow "◀"] + [rc/v-box + :size "auto" + :children [[:span.event-header "[:some-namespace/blah 34 \"Hello\""]]] + [:span.arrow "▶"]]] + [rc/gap-f :size common/gs-12s] + [rc/line :size "2px" :color common/sidebar-heading-divider-color] + [right-hand-buttons external-window?]] + ) + (defn devtools-inner [traces opts] (let [selected-tab (rf/subscribe [:settings/selected-tab]) panel-type (:panel-type opts) @@ -34,43 +89,17 @@ showing-settings? (= @selected-tab :settings)] [:div.panel-content {:style {:width "100%" :display "flex" :flex-direction "column" :background-color common/standard-background-color}} - [rc/h-box - :class "panel-content-top nav" - :style {:padding "0px 19px"} - :justify :between - :children - [[rc/h-box - :align :center - :gap common/gs-12s - :children - (if showing-settings? - [[rc/label :class "bm-title-text" :label "Settings"]] - [[:span.arrow "◀"] - [:span.event-header "[:some-namespace/blah 34 \"Hello\""] - [:span.arrow "▶"]])] + (if showing-settings? [rc/h-box - :align :center + :class "panel-content-top nav" + :style {:padding "0px 19px"} :children - [(when showing-settings? - [:button {:class "bm-active-button" - :on-click #(rf/dispatch [:settings/toggle-settings])} "Done"]) - [:img.nav-icon - {:title "Pause" - :src (str "data:image/svg+xml;utf8," - pause-svg) - :on-click #(rf/dispatch [:settings/pause])}] - [:img.nav-icon - {:title "Settings" - :src (str "data:image/svg+xml;utf8," - (if showing-settings? - orange-settings-svg - settings-svg)) - :on-click #(rf/dispatch [:settings/toggle-settings])}] - (when-not external-window? - [:img.nav-icon.active - {:src (str "data:image/svg+xml;utf8," - open-external) - :on-click #(rf/dispatch-sync [:global/launch-external])}])]]]] + (settings-header external-window?)] + [rc/h-box + :class "panel-content-top nav" + :style {:padding "0px 19px"} + :children + (standard-header external-window?)]) (when-not showing-settings? [rc/h-box :class "panel-content-tabs" diff --git a/src/day8/re_frame/trace/view/settings.cljs b/src/day8/re_frame/trace/view/settings.cljs index ce28806..b6b535f 100644 --- a/src/day8/re_frame/trace/view/settings.cljs +++ b/src/day8/re_frame/trace/view/settings.cljs @@ -3,36 +3,59 @@ [day8.re-frame.trace.utils.re-com :as rc] [day8.re-frame.trace.common-styles :as common])) +(defn explanation-text [children] + [rc/v-box + :width "150px" + :gap common/gs-19s + :children children]) + +(defn settings-box + "settings and explanation are both children of re-com boxes" + [settings explanation] + [rc/h-box + :justify :between + :children [[rc/v-box + :children settings] + [explanation-text explanation]]]) + (defn render [] [rc/v-box + :style {:padding-top common/gs-31s} :gap common/gs-19s :children - [[rc/label - :label "Limits" - :class "bm-heading-text"] + [[settings-box + [[rc/label :label "Retain last 10 epochs"] + [:button "Clear All Epochs"]] + [[:p "8 epochs currently retained, involving 10,425 traces."]]] - [rc/label - :label "Event Filters" - :class "bm-heading-text" - ] + [rc/line] - [rc/label - :label "View Filters" - :class "bm-heading-text"] + [settings-box + [[rc/label :label "Ignore epochs for:"] + [:button "+ event-id"]] + [[:p "All trace associated with these events will be ignored."] + [:p "Useful if you want to ignore a periodic background polling event."]]] - [rc/label - :label "Low Level Trace Filters" - :class "bm-heading-text"] + [rc/line] - [rc/checkbox :model false :on-change #(rf/dispatch [:settings/low-level-trace :reagent %]) :label "reagent internals"] - [rc/checkbox :model false :on-change #(rf/dispatch [:settings/low-level-trace :re-frame %]) :label "re-frame internals"] + [settings-box + [[rc/label :label "Filter out trace for views in "] + [:button "+ namespace"]] + [[:p "Sometimes you want to focus on just your own views, and the trace associated with library views is just noise."] + [:p "Nominate one or more namespaces"]]] - [rc/label - :label "Reset" - :class "bm-heading-text"] + [rc/line] - [:button {:on-click #(rf/dispatch [:settings/clear-epochs])} "Clear Epochs"] - [:button {:on-click #(rf/dispatch [:settings/factory-reset])} "Factory Reset"] - [:p "Will refresh page"] - ]] - ) + [settings-box + [[rc/label :label "Remove low level trace"] + [rc/checkbox :model false :on-change #(rf/dispatch [:settings/low-level-trace :reagent %]) :label "reagent internals"] + [rc/checkbox :model false :on-change #(rf/dispatch [:settings/low-level-trace :re-frame %]) :label "re-frame internals"]] + [[:p "Most of the time, low level trace is noisy and you want it filtered out."]]] + + [rc/line] + + [settings-box + [[:button "Factory Reset"]] + [[:p "Reset all settings (will refresh browser)."]]] + + ]]) From 98555588158f43f9e095de1431d6c0af9a87491d Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Fri, 5 Jan 2018 10:37:50 +1300 Subject: [PATCH 15/60] Update font-weight to be light for tab buttons --- src/day8/re_frame/trace/styles.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/day8/re_frame/trace/styles.cljs b/src/day8/re_frame/trace/styles.cljs index 3b05ee1..a261b82 100644 --- a/src/day8/re_frame/trace/styles.cljs +++ b/src/day8/re_frame/trace/styles.cljs @@ -226,7 +226,7 @@ :font-weight "normal"}] [:.icon-button {:font-size "10px"}] - [:button.tab {}] + [:button.tab {:font-weight 300}] [:.nav-icon {:width "30px" :height "30px" From b324d62c4ee8a87207552028a40ee3f3920d0db8 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Mon, 8 Jan 2018 11:52:54 +1300 Subject: [PATCH 16/60] Styling improvements --- src/day8/re_frame/trace/styles.cljs | 37 +++++++++--- src/day8/re_frame/trace/view/app_db.cljs | 67 ++++++++++++++++++++- src/day8/re_frame/trace/view/container.cljs | 2 +- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/src/day8/re_frame/trace/styles.cljs b/src/day8/re_frame/trace/styles.cljs index a261b82..4b00461 100644 --- a/src/day8/re_frame/trace/styles.cljs +++ b/src/day8/re_frame/trace/styles.cljs @@ -315,15 +315,15 @@ :flex "1 1 auto"}] [:.panel-content-top {} [:.bm-title-text {:color common/navbar-text-color}] - [:button {:width "81px" - :height "31px" + [:button {:width "81px" + :height "31px" :font-weight 700 - :font-size "14px" - :cursor "pointer" - :text-align "center" - :padding "0 5px" - :margin "0 5px"}]] - [:.panel-content-tabs {:margin-left common/gs-19}] + :font-size "14px" + :cursor "pointer" + :text-align "center" + :padding "0 5px" + :margin "0 5px"}]] + [:.panel-content-tabs {:background-color common/white-background-color :padding-left common/gs-19}] [:.panel-content-scrollable panel-mixin] [:.epoch-panel panel-mixin] [:.tab-contents {:display "flex" @@ -341,6 +341,27 @@ :margin "5px" :opacity "0.3"}] [:.active {:opacity 1}] + + [:.app-db-path + {:border [[(px 1) "solid" common/white-background-border-color]] + :background-color common/white-background-color}] + [:.app-db-path--header + {:background-color "#48494A" ; Name this navbar tint-lighter + :color "white" + :height common/gs-31}] + [:.app-db-path--label + {:color "#2D9CDB" + :font-variant "small-caps" + :text-transform "lowercase" + :height common/gs-19}] + [:.app-db-path--path-header + {:background-color common/white-background-color + :color "#48494A" + :margin "3px"}] + [:.app-db-path--path-text__empty + {:font-style "italic"}] + + [:.re-frame-trace--object [:.toggle {:color text-color-muted :cursor "pointer" diff --git a/src/day8/re_frame/trace/view/app_db.cljs b/src/day8/re_frame/trace/view/app_db.cljs index 6717021..11d348f 100644 --- a/src/day8/re_frame/trace/view/app_db.cljs +++ b/src/day8/re_frame/trace/view/app_db.cljs @@ -6,7 +6,8 @@ [day8.re-frame.trace.utils.re-com :as re-com] [mranderson047.re-frame.v0v10v2.re-frame.core :as rf] [mranderson047.reagent.v0v6v0.reagent.core :as r] - [day8.re-frame.trace.utils.re-com :as rc]) + [day8.re-frame.trace.utils.re-com :as rc] + [day8.re-frame.trace.common-styles :as common]) (:require-macros [day8.re-frame.trace.utils.macros :as macros])) (def delete (macros/slurp-macro "day8/re_frame/trace/images/delete.svg")) @@ -15,8 +16,72 @@ (def snapshot (macros/slurp-macro "day8/re_frame/trace/images/snapshot.svg")) (def snapshot-ready (macros/slurp-macro "day8/re_frame/trace/images/snapshot-ready.svg")) +(defn top-buttons [] + [rc/h-box + :justify :between + :children [[:button "+ path viewer"] + [rc/h-box + :align :center + :children + [[rc/label :label "reset app-db to:"] + [:button "initial state"] + [rc/v-box + :width common/gs-81s + :align :center + :children [[rc/label :label "event"] + ;; TODO: arrow doesn't show up when there is an alignment + [rc/line] + [rc/label :label "processing"]]] + [:button "end state"]]]]]) + +(defn path-header [p] + [rc/h-box + :class "app-db-path--header" + :align :center + :gap common/gs-12s + :children [">" + [rc/h-box + :size "auto" + :class "app-db-path--path-header" + :children + [[rc/label + :class (str "app-db-path--path-text " (when (nil? p) "app-db-path--path-text__empty")) + :label (if (some? p) + (prn-str p) + "Showing all of app-db. Try entering a path like [:todos 1]")]]] + [:button "diff"] + [:button "trash"]]]) + +(defn app-db-path [p] + ^{:key (str p)} + [rc/v-box + :class "app-db-path" + :children + [[path-header p] + [rc/label :label "Main data"] + ;; TODO: Make these into hyperlinks + [rc/label :class "app-db-path--label" :label "Only Before:"] + [rc/label :label "Before diff"] + [rc/label :class "app-db-path--label" :label "Only After"] + [rc/label :label "After diff"]]]) + +(defn paths [] + [rc/v-box + :gap common/gs-31s + :children + (doall (for [p [["x" "y"] [:abc 123] nil]] + [app-db-path p]))]) + (defn render-state [data] + [rc/v-box + :gap common/gs-31s + :children + [[top-buttons] + [paths]]]) + + +(defn old-render-state [data] (let [subtree-input (r/atom "") subtree-paths (rf/subscribe [:app-db/paths]) search-string (rf/subscribe [:app-db/search-string]) diff --git a/src/day8/re_frame/trace/view/container.cljs b/src/day8/re_frame/trace/view/container.cljs index d06e93c..a60b36e 100644 --- a/src/day8/re_frame/trace/view/container.cljs +++ b/src/day8/re_frame/trace/view/container.cljs @@ -115,7 +115,7 @@ (tab-button :views "Views") (tab-button :traces "Trace")]] ]]) - [rc/line :style outer-margins] + [rc/line :color "#EEEEEE"] (when (and external-window? @unloading?) [:h1.host-closed "Host window has closed. Reopen external window to continue tracing."]) (when-not (re-frame.trace/is-trace-enabled?) From ac19e045729caa35953af1d81777f6cd6004bc26 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Mon, 8 Jan 2018 17:26:06 +1300 Subject: [PATCH 17/60] Only show traces for the current epoch --- src/day8/re_frame/trace/events.cljs | 42 ++++++++-- .../{metamorphic.clj => metamorphic.cljc} | 54 +++++++++---- src/day8/re_frame/trace/subs.cljs | 81 +++++++++++++++++++ src/day8/re_frame/trace/view/container.cljs | 27 ++++--- src/day8/re_frame/trace/view/overview.cljs | 5 +- src/day8/re_frame/trace/view/settings.cljs | 10 ++- src/day8/re_frame/trace/view/traces.cljs | 9 ++- 7 files changed, 187 insertions(+), 41 deletions(-) rename src/day8/re_frame/trace/{metamorphic.clj => metamorphic.cljc} (59%) diff --git a/src/day8/re_frame/trace/events.cljs b/src/day8/re_frame/trace/events.cljs index 22829c1..6b07931 100644 --- a/src/day8/re_frame/trace/events.cljs +++ b/src/day8/re_frame/trace/events.cljs @@ -8,7 +8,8 @@ [re-frame.db] [day8.re-frame.trace.view.container :as container] [day8.re-frame.trace.styles :as styles] - [clojure.set :as set])) + [clojure.set :as set] + [day8.re-frame.trace.metamorphic :as metam])) (defonce traces (r/atom [])) (defonce total-traces (r/atom 0)) @@ -26,18 +27,21 @@ (defn enable-tracing! [] (re-frame.trace/register-trace-cb ::cb (fn [new-traces] - (when-let [new-traces (filter log-trace? new-traces)] + (when-let [new-traces (->> (filter log-trace? new-traces) + (sort-by :id))] (swap! total-traces + (count new-traces)) (swap! traces (fn [existing] (let [new (reduce conj existing new-traces) size (count new)] - (if (< 4000 size) - (let [new2 (subvec new (- size 2000))] - (if (< @total-traces 20000) ;; Create a new vector to avoid structurally sharing all traces forever + (if (< 8000 size) + (let [new2 (subvec new (- size 4000))] + (if (< @total-traces 40000) ;; Create a new vector to avoid structurally sharing all traces forever (do (reset! total-traces 0) (into [] new2)))) - new)))))))) + new)))) + (rf/dispatch [:traces/update-traces @traces]) + (rf/dispatch [:epochs/update-epochs (metam/parse-traces @traces)]))))) (defn dissoc-in "Dissociates an entry from a nested associative structure returning a new @@ -318,3 +322,29 @@ (fn [snapshot _] (reset! re-frame.db/app-db (:current-snapshot snapshot)) snapshot)) + +;;; + +(rf/reg-event-db + :epochs/update-epochs + [(rf/path [:epochs :matches])] + (fn [matches [_ rt]] + (:matches rt))) + +(rf/reg-event-db + :epochs/previous-epoch + [(rf/path [:epochs :current-epoch-index])] + (fn [index _] + ((fnil dec 0) index))) + +(rf/reg-event-db + :epochs/next-epoch + [(rf/path [:epochs :current-epoch-index])] + (fn [index _] + ((fnil inc 0) index))) + +(rf/reg-event-db + :traces/update-traces + [(rf/path [:traces :all-traces])] + (fn [_ [_ traces]] + traces)) diff --git a/src/day8/re_frame/trace/metamorphic.clj b/src/day8/re_frame/trace/metamorphic.cljc similarity index 59% rename from src/day8/re_frame/trace/metamorphic.clj rename to src/day8/re_frame/trace/metamorphic.cljc index 7900a9c..9a57e16 100644 --- a/src/day8/re_frame/trace/metamorphic.clj +++ b/src/day8/re_frame/trace/metamorphic.cljc @@ -1,7 +1,8 @@ (ns day8.re-frame.trace.metamorphic (:require [metamorphic.api :as m] [metamorphic.runtime :as rt] - [metamorphic.viz :as v])) + #?(:clj + [metamorphic.viz :as v]))) ;; Next, we define predicate functions that take exactly 4 arguments. ;; These predicates are obviously incredibly boring, but they help @@ -65,12 +66,17 @@ (= :running (get-in event [:tags :current-state])) (= :idle (get-in event [:tags :new-state])))) +(defn request-animation-frame? [event history pattern-sequence pattern] + (= :raf (:op-type event))) -(defn trace-events [] (->> (slurp "test-resources/events2.edn") - (clojure.edn/read-string {:readers {'utc identity - 'object (fn [x] "")}}) - (sort-by :id)) - ) +(defn request-animation-frame-end? [event history pattern-sequence pattern] + (= :raf-end (:op-type event))) + + +#?(:clj (defn trace-events [] (->> (slurp "test-resources/events2.edn") + (clojure.edn/read-string {:readers {'utc identity + 'object (fn [x] "")}}) + (sort-by :id)))) (defn summarise-event [ev] @@ -79,17 +85,37 @@ (defn summarise-match [match] (map summarise-event match)) -(defn parse-events [] +#?(:clj + (defn parse-events [] + #_ (let [runtime (-> (m/new-pattern-sequence "simple traces") + (m/begin "new-epoch-started" new-epoch-started?) + #_(m/followed-by "redispatched-event" redispatched-event? {:optional? true}) + #_(m/followed-by "router-scheduled" router-scheduled? {:optional? true}) + (m/followed-by "event-run" event-run?) + (m/followed-by "router-finished" router-finished?) + (m/followed-by "raf" request-animation-frame?) + (m/followed-by "raf-end" request-animation-frame-end?) + (rt/initialize-runtime)) + events (trace-events) + rt (reduce rt/evaluate-event runtime events)] + #_(println "Count" + (count (:matches rt)) + (map count (:matches rt))) + (map summarise-match (:matches rt))))) + +(defn parse-traces + "Returns a metamorphic runtime" + [traces] (let [runtime (-> (m/new-pattern-sequence "simple traces") (m/begin "new-epoch-started" new-epoch-started?) - #_(m/followed-by "redispatched-event" redispatched-event? {:optional? true}) - #_ (m/followed-by "router-scheduled" router-scheduled? {:optional? true}) (m/followed-by "event-run" event-run?) (m/followed-by "router-finished" router-finished?) + (m/followed-by "raf" request-animation-frame?) + (m/followed-by "raf-end" request-animation-frame-end?) (rt/initialize-runtime)) - events (trace-events) - rt (reduce rt/evaluate-event runtime events)] + rt (reduce rt/evaluate-event runtime traces)] #_(println "Count" - (count (:matches rt)) - (map count (:matches rt))) - (map summarise-match (:matches rt)))) + (count (:matches rt)) + (map count (:matches rt))) + #_(map summarise-match (:matches rt)) + rt)) diff --git a/src/day8/re_frame/trace/subs.cljs b/src/day8/re_frame/trace/subs.cljs index 6e0f969..56d1fd5 100644 --- a/src/day8/re_frame/trace/subs.cljs +++ b/src/day8/re_frame/trace/subs.cljs @@ -59,6 +59,11 @@ ;; +(rf/reg-sub + :traces/trace-root + (fn [db _] + (:traces db))) + (rf/reg-sub :traces/filter-items (fn [db _] @@ -74,6 +79,29 @@ (fn [db _] (get-in db [:traces :categories]))) +(rf/reg-sub + :traces/all-traces + :<- [:traces/trace-root] + (fn [traces _] + (:all-traces traces))) + +(rf/reg-sub + :traces/number-of-traces + :<- [:traces/trace-root] + (fn [traces _] + (count traces))) + +(rf/reg-sub + :traces/current-event-traces + :<- [:traces/all-traces] + :<- [:epochs/beginning-trace-id] + :<- [:epochs/ending-trace-id] + (fn [[traces beginning ending] _] + (filter #(<= beginning (:id %) ending) traces) + #_traces)) + +;; + (rf/reg-sub :global/unloading? (fn [db _] @@ -91,3 +119,56 @@ :<- [:snapshot/snapshot-root] (fn [snapshot _] (contains? snapshot :current-snapshot))) + +;; + +(rf/reg-sub + :epochs/epoch-root + (fn [db _] + (:epochs db))) + +(rf/reg-sub + :epochs/current-event + :<- [:epochs/epoch-root] + (fn [epochs _] + (let [matches (:matches epochs) + current-index (:current-epoch-index epochs) + match (nth matches (+ (count matches) (or current-index 0)) (last matches)) + event (get-in (second match) [:tags :event])] + event))) + +(rf/reg-sub + :epochs/number-of-matches + :<- [:epochs/epoch-root] + (fn [epochs _] + (count (get epochs :matches)))) + +(rf/reg-sub + :epochs/current-event-index + :<- [:epochs/epoch-root] + (fn [epochs _] + (:current-epoch-index epochs))) + +(rf/reg-sub + :epochs/event-position + :<- [:epochs/current-event-index] + :<- [:epochs/number-of-matches] + (fn [[current total]] + (str current " of " total))) + +(rf/reg-sub + :epochs/beginning-trace-id + :<- [:epochs/epoch-root] + (fn [epochs] + ;; TODO: make it use the real match + (let [match (last (:matches epochs))] + (:id (first match))))) + +(rf/reg-sub + :epochs/ending-trace-id + :<- [:epochs/epoch-root] + (fn [epochs] + ;; TODO: make it use the real match + (let [match (last (:matches epochs))] + (:id (last match))))) + diff --git a/src/day8/re_frame/trace/view/container.cljs b/src/day8/re_frame/trace/view/container.cljs index a60b36e..2417395 100644 --- a/src/day8/re_frame/trace/view/container.cljs +++ b/src/day8/re_frame/trace/view/container.cljs @@ -66,19 +66,20 @@ [right-hand-buttons external-window?]]) (defn standard-header [external-window?] - [[rc/h-box - :align :center - :size "auto" - :gap common/gs-12s - :children - [[:span.arrow "◀"] - [rc/v-box + (let [current-event @(rf/subscribe [:epochs/current-event])] + [[rc/h-box + :align :center :size "auto" - :children [[:span.event-header "[:some-namespace/blah 34 \"Hello\""]]] - [:span.arrow "▶"]]] - [rc/gap-f :size common/gs-12s] - [rc/line :size "2px" :color common/sidebar-heading-divider-color] - [right-hand-buttons external-window?]] + :gap common/gs-12s + :children + [[:span.arrow {:on-click #(rf/dispatch [:epochs/previous-epoch])} "◀"] + [rc/v-box + :size "auto" + :children [[:span.event-header (prn-str current-event)]]] + [:span.arrow {:on-click #(rf/dispatch [:epochs/next-epoch])} "▶"]]] + [rc/gap-f :size common/gs-12s] + [rc/line :size "2px" :color common/sidebar-heading-divider-color] + [right-hand-buttons external-window?]]) ) (defn devtools-inner [traces opts] @@ -125,7 +126,7 @@ :style {:margin-left common/gs-19s} :children [(case @selected-tab - :overview [overview/render] + :overview [overview/render traces] :app-db [app-db/render-state db/app-db] :subs [subs/subs-panel] :views [views/render] diff --git a/src/day8/re_frame/trace/view/overview.cljs b/src/day8/re_frame/trace/view/overview.cljs index 39bf572..4b10f9b 100644 --- a/src/day8/re_frame/trace/view/overview.cljs +++ b/src/day8/re_frame/trace/view/overview.cljs @@ -1,7 +1,8 @@ (ns day8.re-frame.trace.view.overview - (:require [day8.re-frame.trace.utils.re-com :as rc])) + (:require [day8.re-frame.trace.utils.re-com :as rc] + [day8.re-frame.trace.metamorphic :as metam])) -(defn render [] +(defn render [traces] [rc/v-box :children [[rc/label :label "Event"] diff --git a/src/day8/re_frame/trace/view/settings.cljs b/src/day8/re_frame/trace/view/settings.cljs index b6b535f..04d96b5 100644 --- a/src/day8/re_frame/trace/view/settings.cljs +++ b/src/day8/re_frame/trace/view/settings.cljs @@ -23,10 +23,12 @@ :style {:padding-top common/gs-31s} :gap common/gs-19s :children - [[settings-box - [[rc/label :label "Retain last 10 epochs"] - [:button "Clear All Epochs"]] - [[:p "8 epochs currently retained, involving 10,425 traces."]]] + [(let [num-epochs @(rf/subscribe [:epochs/number-of-matches]) + num-traces @(rf/subscribe [:traces/number-of-traces])] + [settings-box + [[rc/label :label "Retain last 10 epochs"] + [:button "Clear All Epochs"]] + [[:p num-epochs " epochs currently retained, involving " num-traces " traces."]]]) [rc/line] diff --git a/src/day8/re_frame/trace/view/traces.cljs b/src/day8/re_frame/trace/view/traces.cljs index 1746d2e..7a042b1 100644 --- a/src/day8/re_frame/trace/view/traces.cljs +++ b/src/day8/re_frame/trace/view/traces.cljs @@ -64,7 +64,8 @@ (str/join ", ") (pp/truncate-string :middle 40)))]]] [:td.trace--meta - (.toFixed duration 1) " ms"]] + id + #_ #_(.toFixed duration 1) " ms"]] (when show-row? [:tr.trace--details {:key (str id "-details") :tab-index 0} @@ -86,7 +87,10 @@ filter-type (r/atom :contains) input-error (r/atom false) categories (rf/subscribe [:traces/categories]) - trace-detail-expansions (rf/subscribe [:traces/expansions])] + trace-detail-expansions (rf/subscribe [:traces/expansions]) + beginning (rf/subscribe [:epochs/beginning-trace-id]) + end (rf/subscribe [:epochs/ending-trace-id]) + traces (rf/subscribe [:traces/current-event-traces])] (fn [] (let [toggle-category-fn #(rf/dispatch [:traces/toggle-categories %]) visible-traces (cond->> @traces @@ -141,6 +145,7 @@ :on-click #(rf/dispatch [:traces/remove-filter (:id item)])} (:filter-type item) ": " [:span.filter-item-string (:query item)]]]) @filter-items)]] + [:pre @beginning " to " @end] [components/autoscroll-list {:class "panel-content-scrollable" :scroll? true} [:table [:thead>tr From f4a6c3e9b998ccf6482345035f85f99134fcc543 Mon Sep 17 00:00:00 2001 From: Gregg8 Date: Tue, 9 Jan 2018 18:09:31 +1100 Subject: [PATCH 18/60] app-db panel UI redesign WIP --- .idea/codeStyleSettings.xml | 3 + src/day8/re_frame/trace/styles.cljs | 13 +- src/day8/re_frame/trace/utils/re_com.cljs | 28 ++ src/day8/re_frame/trace/view/app_db.cljs | 300 +++++++++++++------- src/day8/re_frame/trace/view/container.cljs | 22 +- 5 files changed, 247 insertions(+), 119 deletions(-) diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml index 618f166..06ad0a8 100644 --- a/.idea/codeStyleSettings.xml +++ b/.idea/codeStyleSettings.xml @@ -9,6 +9,9 @@ :cursive.formatting/align-binding-forms true :day8.re-frame.trace.utils.macros/with-cljs-devtools-prefs 1 } + +