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
}
+
+
+
diff --git a/DEVELOPERS.md b/DEVELOPERS.md
index 9b62e61..9407119 100644
--- a/DEVELOPERS.md
+++ b/DEVELOPERS.md
@@ -37,6 +37,8 @@ You need both the re-frame-trace project _and_ a test project to develop it agai
:cljsbuild {:builds {:client {:source-paths ["checkouts/re-frame-trace/src"]}}}
```
+- re-frame-trace has a debug panel useful when developing it. You can enable it by adding the :closure-define `"day8.re_frame.trace.debug_QMARK_" true` to your compiler settings.
+
- Now run your test project however you usually run it, and re-frame-trace should be in there. \o/
@@ -52,12 +54,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/README.md b/README.md
index 9d2a30a..4fe5e78 100644
--- a/README.md
+++ b/README.md
@@ -113,6 +113,19 @@ If you are using leiningen, modify `project.clj` in the following ways. When puz
- When the panel is closed, tracing is disabled.
+## Use Cases
+
+### app-db
+
+* Inspect a portion of app-db's state with the path inspector, allowing you to focus on just the parts you care about.
+* Reset app-db to before an event was run to run it again, instead of resetting the whole application
+* Toggle app-db before and after states for running an event, to inspect UI changes.
+
+### Timing
+
+* Answer the question "Why is my app slow when it runs this event?"
+* See whether time is spent in processing an event, or rendering the changes
+
## Troubleshooting
* Try a `lein clean`
@@ -137,3 +150,7 @@ 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
+* [play]() by Bhuvan from the Noun Project
+* [Log Out](https://thenounproject.com/icon/54484/) by Arthur Shlain from the Noun Project
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
diff --git a/project.clj b/project.clj
index 56d8739..2d1f8ee 100644
--- a/project.clj
+++ b/project.clj
@@ -1,16 +1,16 @@
(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"}
- :dependencies [[org.clojure/clojure "1.8.0"]
+ :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"]
+ [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"]
@@ -30,11 +30,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/resources/day8/re_frame/trace/images/arrow-right.svg b/resources/day8/re_frame/trace/images/arrow-right.svg
new file mode 100644
index 0000000..8df6917
--- /dev/null
+++ b/resources/day8/re_frame/trace/images/arrow-right.svg
@@ -0,0 +1,12 @@
+
diff --git a/resources/day8/re_frame/trace/images/copy.svg b/resources/day8/re_frame/trace/images/copy.svg
new file mode 100644
index 0000000..fb267d3
--- /dev/null
+++ b/resources/day8/re_frame/trace/images/copy.svg
@@ -0,0 +1,16 @@
+
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/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/orange-wrench.svg b/resources/day8/re_frame/trace/images/orange-wrench.svg
new file mode 100644
index 0000000..dc90749
--- /dev/null
+++ b/resources/day8/re_frame/trace/images/orange-wrench.svg
@@ -0,0 +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..4ab761a
--- /dev/null
+++ b/resources/day8/re_frame/trace/images/pause.svg
@@ -0,0 +1 @@
+
diff --git a/resources/day8/re_frame/trace/images/play.svg b/resources/day8/re_frame/trace/images/play.svg
new file mode 100644
index 0000000..bb5f406
--- /dev/null
+++ b/resources/day8/re_frame/trace/images/play.svg
@@ -0,0 +1,8 @@
+
diff --git a/resources/day8/re_frame/trace/images/round-arrow.svg b/resources/day8/re_frame/trace/images/round-arrow.svg
new file mode 100644
index 0000000..55304c9
--- /dev/null
+++ b/resources/day8/re_frame/trace/images/round-arrow.svg
@@ -0,0 +1,12 @@
+
diff --git a/resources/day8/re_frame/trace/images/trash.svg b/resources/day8/re_frame/trace/images/trash.svg
new file mode 100644
index 0000000..a8ed01e
--- /dev/null
+++ b/resources/day8/re_frame/trace/images/trash.svg
@@ -0,0 +1,12 @@
+
diff --git a/resources/day8/re_frame/trace/images/triangle-down.svg b/resources/day8/re_frame/trace/images/triangle-down.svg
new file mode 100644
index 0000000..e8aacc1
--- /dev/null
+++ b/resources/day8/re_frame/trace/images/triangle-down.svg
@@ -0,0 +1,12 @@
+
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..19ee3f6
--- /dev/null
+++ b/resources/day8/re_frame/trace/images/wrench.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/day8/re_frame/trace.cljs b/src/day8/re_frame/trace.cljs
index 4c3d788..cc06e57 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,8 +21,10 @@
[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]))
+(goog-define debug? false)
;; from https://github.com/reagent-project/reagent/blob/3fd0f1b1d8f43dbf169d136f0f905030d7e093bd/src/reagent/impl/component.cljs#L274
(defn fiber-component-path [fiber]
@@ -52,7 +54,7 @@
(def static-fns
{:render
- (fn render []
+ (fn mp-render [] ;; Monkeypatched render
(this-as c
(trace/with-trace {:op-type :render
:tags {:component-path (component-path c)}
@@ -73,11 +75,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 do-after-render-trace-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
@@ -89,8 +94,6 @@
:operation (last (str/split name #" > "))}
(real-renderer c)))))
-
-
(set! reagent.impl.component/static-fns static-fns)
(set! reagent.impl.component/custom-wrapper
@@ -106,15 +109,40 @@
(real-custom-wrapper key f))))
- #_(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]
+ ;; Schedule a trace to be emitted after a render if there is nothing else scheduled after that render.
+ ;; This signals the end of the epoch.
- #_(set! reagent.impl.batching/schedule schedule
- #_(fn []
- (reagent.impl.batching/do-after-render (fn [] (trace/with-trace {:op-type :raf-end})))
- (real-schedule)))))
+ #_ (swap! do-after-render-trace-scheduled?
+ (fn [scheduled?]
+ (js/console.log "Setting up scheduled after" scheduled?)
+ (if scheduled?
+ scheduled?
+ (do (reagent.impl.batching/do-after-render ;; a do-after-flush would probably be a better spot to put this if it existed.
+ (fn []
+ (js/console.log "Do after render" reagent.impl.batching/render-queue)
+ (reset! do-after-render-trace-scheduled? false)
+ (when (false? (.-scheduled? reagent.impl.batching/render-queue))
+ (trace/with-trace {:op-type :reagent/quiescent}))))
+ true))))
+ (real-next-tick (fn []
+ (trace/with-trace {:op-type :raf}
+ (f)
+ (trace/with-trace {:op-type :raf-end})
+ (when (false? (.-scheduled? reagent.impl.batching/render-queue))
+ (trace/with-trace {:op-type :reagent/quiescent}))
+
+ )))))
+
+ #_(set! reagent.impl.batching/schedule
+ (fn []
+ (reagent.impl.batching/do-after-render
+ (fn []
+ (when @do-after-render-trace-scheduled?
+ (trace/with-trace {:op-type :do-after-render})
+ (reset! do-after-render-trace-scheduled? false))))
+ (real-schedule)))))
(defn init-tracing!
@@ -205,7 +233,8 @@
(defn inject-devtools! []
(styles/inject-trace-styles js/document)
- (r/render [devtools-outer events/traces {:panel-type :inline}] (panel-div)))
+ (r/render [devtools-outer events/traces {:panel-type :inline
+ :debug? debug?}] (panel-div)))
(defn init-db! []
(trace.db/init-db))
diff --git a/src/day8/re_frame/trace/common_styles.cljs b/src/day8/re_frame/trace/common_styles.cljs
index 5ae1326..383bf8a 100644
--- a/src/day8/re_frame/trace/common_styles.cljs
+++ b/src/day8/re_frame/trace/common_styles.cljs
@@ -1,4 +1,7 @@
-(ns day8.re-frame.trace.common-styles)
+(ns day8.re-frame.trace.common-styles
+ (:require [garden.units :refer [px em]]))
+
+;; TODO: Switch these to BM (or just use BM defs if available)
(def background-blue "#e7f1ff")
(def background-gray "#a8a8a8")
@@ -14,9 +17,241 @@
(def light-blue "lightblue")
(def light-gray "#efeef1")
(def yellow "yellow")
-(def text-color "#222")
+(def text-color "#767A7C") ;; Was "#222" but now using bm: (def default-text-color "#767A7C")
(def text-color-muted "#8f8f8f")
(def event-color dark-gold)
(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...
+;;
+;; 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 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)
+
+(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 navbar-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 "19px"
+ :font-weight "600"
+ :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-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)}]
+
+ [:.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"
+ :border-radius "3px"}]
+ [:.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/db.cljs b/src/day8/re_frame/trace/db.cljs
index 91f0bac..f118519 100644
--- a/src/day8/re_frame/trace/db.cljs
+++ b/src/day8/re_frame/trace/db.cljs
@@ -5,9 +5,9 @@
(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" :event)
filter-items (localstorage/get "filter-items" [])
- app-db-paths (localstorage/get "app-db-paths" '())
+ app-db-paths (into (sorted-map) (localstorage/get "app-db-paths" {}))
json-ml-paths (localstorage/get "app-db-json-ml-expansions" #{})
external-window? (localstorage/get "external-window?" false)
using-trace? (localstorage/get "using-trace?" true)
@@ -21,6 +21,8 @@
(rf/dispatch [:global/launch-external]))
(rf/dispatch [:traces/filter-items filter-items])
(rf/dispatch [:traces/set-categories categories])
+ (rf/dispatch [:traces/update-show-epoch-traces? true]) ;; TODO: source this from LS.
(rf/dispatch [:app-db/paths app-db-paths])
(rf/dispatch [:app-db/set-json-ml-paths json-ml-paths])
- (rf/dispatch [:global/add-unload-hook])))
+ (rf/dispatch [:global/add-unload-hook])
+ (rf/dispatch [:app-db/reagent-id])))
diff --git a/src/day8/re_frame/trace/events.cljs b/src/day8/re_frame/trace/events.cljs
index f02c7b6..0e34290 100644
--- a/src/day8/re_frame/trace/events.cljs
+++ b/src/day8/re_frame/trace/events.cljs
@@ -1,20 +1,23 @@
(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]
+ [re-frame.interop]
[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))
(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
@@ -25,18 +28,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
@@ -65,12 +71,31 @@
(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?]]
(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 _]
@@ -87,6 +112,18 @@
(assoc-in [:settings :using-trace?] using-trace?)
(assoc-in [:settings :show-panel?] now-showing?)))))
+(rf/reg-event-db
+ :settings/pause
+ (fn [db _]
+ (assoc-in db [:settings :paused?] true)))
+
+(rf/reg-event-db
+ :settings/play
+ (fn [db _]
+ (-> db
+ (assoc-in [:settings :paused?] false)
+ (assoc-in [:epochs :current-epoch-index] nil))))
+
;; Global
(defn mount [popup-window popup-document]
@@ -230,16 +267,93 @@
(fn [categories [_ new-categories]]
new-categories))
+(rf/reg-event-db
+ :traces/update-show-epoch-traces?
+ [(rf/path [:traces :show-epoch-traces?])]
+ (fn [_ [_ show-epoch-traces?]]
+ show-epoch-traces?))
+
;; App DB
+(def app-db-path-mw
+ [(rf/path [:app-db :paths]) (rf/after #(localstorage/save! "app-db-paths" %))])
+
+(rf/reg-event-db
+ :app-db/create-path
+ app-db-path-mw
+ (fn [paths _]
+ (assoc paths (js/Date.now) {:diff? false :open? true :path nil :path-str "[]" :valid-path? true})))
+
+(defn read-string-maybe [s]
+ (try (cljs.tools.reader.edn/read-string s)
+ (catch :default e
+ nil)))
+
+;; The core idea with :app-db/update-path and :app-db/update-path-blur
+;; is that we need to separate the users text input (`path-str`) with the
+;; parsing of that string (`path`). We let the user type any string that
+;; they like, and check it for validity on each change. If it is valid
+;; then we update `path` and mark the pod as valid. If it isn't valid then
+;; we don't update `path` and mark the pod as invalid.
+;;
+;; On blur of the input, we reset path-str to the last valid path, if
+;; the pod isn't currently valid.
+
+(rf/reg-event-db
+ :app-db/update-path
+ app-db-path-mw
+ (fn [paths [_ path-id path-str]]
+ (let [path (read-string-maybe path-str)
+ paths (assoc-in paths [path-id :path-str] path-str)]
+ (if (or (and (some? path)
+ (sequential? path))
+ (str/blank? path-str))
+ (-> paths
+ (assoc-in [path-id :path] path)
+ (assoc-in [path-id :valid-path?] true))
+ (assoc-in paths [path-id :valid-path?] false)))))
+
+(rf/reg-event-db
+ :app-db/update-path-blur
+ app-db-path-mw
+ (fn [paths [_ path-id]]
+ (let [{:keys [valid-path? path]} (get paths path-id)]
+ (if valid-path?
+ paths
+ (-> (assoc-in paths [path-id :path-str] (pr-str path))
+ (assoc-in [path-id :valid-path?] true))))))
+
+(rf/reg-event-db
+ :app-db/set-path-visibility
+ app-db-path-mw
+ (fn [paths [_ path-id open?]]
+ (assoc-in paths [path-id :open?] open?)))
+
+(rf/reg-event-db
+ :app-db/set-diff-visibility
+ app-db-path-mw
+ (fn [paths [_ path-id diff?]]
+ (let [open? (if diff?
+ true
+ (get-in paths [path-id :open?]))]
+ (-> paths
+ (assoc-in [path-id :diff?] diff?)
+ ;; If we turn on diffing then we want to also expand the path
+ (assoc-in [path-id :open?] open?)))))
+
+(rf/reg-event-db
+ :app-db/remove-path
+ app-db-path-mw
+ (fn [paths [_ path-id]]
+ (dissoc paths path-id)))
+
(rf/reg-event-db
:app-db/paths
+ app-db-path-mw
(fn [db [_ paths]]
- (let [new-paths (into [] paths)] ;; Don't use sets, use vectors
- (localstorage/save! "app-db-paths" paths)
- (assoc-in db [:app-db :paths] paths))))
+ paths))
-(rf/reg-event-db
+#_(rf/reg-event-db
:app-db/remove-path
(fn [db [_ path]]
(let [new-db (update-in db [:app-db :paths] #(remove (fn [p] (= p path)) %))]
@@ -247,7 +361,7 @@
;; TODO: remove from json-ml expansions too.
new-db)))
-(rf/reg-event-db
+#_(rf/reg-event-db
:app-db/add-path
(fn [db _]
(let [search-string (get-in db [:app-db :search-string])
@@ -287,14 +401,61 @@
new-paths)))
(rf/reg-event-db
- :snapshot/save-snapshot
- [(rf/path [:snapshot])]
- (fn [snapshot _]
- (assoc snapshot :current-snapshot @re-frame.db/app-db)))
+ :app-db/reagent-id
+ [(rf/path [:app-db :reagent-id])]
+ (fn [paths _]
+ (re-frame.interop/reagent-id re-frame.db/app-db)))
(rf/reg-event-db
:snapshot/load-snapshot
- [(rf/path [:snapshot])]
- (fn [snapshot _]
- (reset! re-frame.db/app-db (:current-snapshot snapshot))
- snapshot))
+ (fn [db [_ new-db]]
+ (reset! re-frame.db/app-db new-db)
+ db))
+
+;;;
+
+(rf/reg-event-db
+ :epochs/update-epochs
+ [(rf/path [:epochs :matches])]
+ (fn [matches [_ rt]]
+ (:matches rt)))
+
+(rf/reg-event-fx
+ :epochs/previous-epoch
+ [(rf/path [:epochs :current-epoch-index])]
+ (fn [ctx _]
+ {:db ((fnil dec 0) (:db ctx))
+ :dispatch [:settings/pause]}))
+
+(rf/reg-event-fx
+ :epochs/next-epoch
+ [(rf/path [:epochs :current-epoch-index])]
+ (fn [ctx _]
+ {:db ((fnil inc 0) (:db ctx))
+ :dispatch [:settings/pause]}))
+
+(rf/reg-event-db
+ :traces/update-traces
+ [(rf/path [:traces :all-traces])]
+ (fn [_ [_ traces]]
+ traces))
+
+;;
+
+(rf/reg-event-db
+ :subs/ignore-unchanged-subs?
+ [(rf/path [:subs :ignore-unchanged-subs?])]
+ (fn [_ [_ ignore?]]
+ ignore?))
+
+(rf/reg-event-db
+ :subs/open-pod?
+ [(rf/path [:subs :expansions])]
+ (fn [expansions [_ id open?]]
+ (assoc-in expansions [id :open?] open?)))
+
+(rf/reg-event-db
+ :subs/diff-pod?
+ [(rf/path [:subs :expansions])]
+ (fn [expansions [_ id diff?]]
+ (assoc-in expansions [id :diff?] diff?)))
diff --git a/src/day8/re_frame/trace/metamorphic.cljc b/src/day8/re_frame/trace/metamorphic.cljc
new file mode 100644
index 0000000..688e270
--- /dev/null
+++ b/src/day8/re_frame/trace/metamorphic.cljc
@@ -0,0 +1,233 @@
+(ns day8.re-frame.trace.metamorphic
+ (:require [mranderson047.re-frame.v0v10v2.re-frame.utils :as utils]))
+
+;; What starts an epoch?
+
+;;; idle -> dispatch -> running
+;;; running -> dispatch -> handling new event
+
+;; What ends an epoch?
+
+;;; the start of a new epoch
+;;; a Reagent animation frame ending AND nothing else being scheduled
+
+;; Slight wrinkles
+
+;;; Any renders that run between epochs deserve their own epoch really.
+;;; Dispatch-sync's
+
+;;;
+
+;
+;(defn add-event-from-idle? [event history pattern-sequence pattern]
+; #_(println @history event)
+;
+; (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 epoch-started? [event history pattern-sequence pattern]
+; (or (add-event-from-idle? event history pattern-sequence pattern)
+; (and (event-run? event history pattern-sequence pattern)
+; (empty? @history))))
+;
+(defn fsm-trigger? [event]
+ (= :re-frame.router/fsm-trigger (:op-type event)))
+;
+;(defn redispatched-event? [event history pattern-sequence pattern]
+; (and (fsm-trigger? event)
+; (= (:operation event)
+; [:running :add-event])))
+;
+;(defn router-scheduled? [event history pattern-sequence pattern]
+; (and (fsm-trigger? 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 (fsm-trigger? event)
+; (= (:operation event)
+; [:running :finish-run])
+; (= :running (get-in event [:tags :current-state]))
+; (= :idle (get-in event [:tags :new-state]))))
+;
+;(defn quiescent? [event _ _ _]
+; (= :reagent/quiescent (:op-type event)))
+;
+;(defn epoch-ended? [event history pattern-sequence pattern]
+; (or (quiescent? event history pattern-sequence pattern)
+; (epoch-started? event history pattern-sequence pattern)))
+;
+
+(defn elapsed-time [ev1 ev2]
+ (let [start-of-epoch (:start ev1)
+ end-of-epoch (:end ev2)]
+ (when (and (some? start-of-epoch) (some? end-of-epoch))
+ #?(:cljs (js/Math.round (- end-of-epoch start-of-epoch))
+ :clj (Math/round ^double (- end-of-epoch start-of-epoch))))))
+
+(defn run-queue? [event]
+ (and (fsm-trigger? event)
+ (= (:operation event)
+ [:scheduled :run-queue])))
+;
+(defn request-animation-frame? [event]
+ (= :raf (:op-type event)))
+;
+;(defn request-animation-frame-end? [event history pattern-sequence pattern]
+; (= :raf-end (:op-type event)))
+;
+(defn summarise-event [ev]
+ (-> ev
+ (dissoc :start :duration :end :child-of)
+ (utils/dissoc-in [:tags :app-db-before])
+ (utils/dissoc-in [:tags :app-db-after])))
+
+
+(defn summarise-match [match]
+ (map summarise-event match))
+;
+(defn beginning-id [match]
+ (:id (first match)))
+
+(defn ending-id [match]
+ (:id (last match)))
+;
+;(defn parse-traces-metam
+; "Returns a metamorphic runtime"
+; [traces]
+; (let [runtime (-> (m/new-pattern-sequence "simple traces")
+; (m/begin "new-epoch-started" epoch-started?)
+; #_(m/followed-by "run-queue" run-queue? {: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?)
+; (m/followed-by "epoch-ended" epoch-ended?)
+; (rt/initialize-runtime))
+; rt (reduce rt/evaluate-event runtime traces)]
+; #_(println "Count"
+; (count (:matches rt))
+; (map count (:matches rt)))
+; #_(map summarise-match (:matches rt))
+; rt))
+
+;;;;;;
+
+;; TODO: this needs to be included too as a starting point.
+(defn add-event-from-idle? [event]
+ (and (= :re-frame.router/fsm-trigger (:op-type event))
+ (= (:operation event)
+ [:idle :add-event])))
+
+(defn subscription? [trace]
+ (and (= "sub" (namespace (:op-type trace)))
+ (not (get-in trace [:tags :cached?]))))
+
+(defn subscription-created? [trace]
+ (and (= :sub/create (:op-type trace))
+ (not (get-in trace [:tags :cached?]))))
+
+(defn subscription-re-run? [trace]
+ (= :sub/run (:op-type trace)))
+
+(defn subscription-destroyed? [trace]
+ (= :sub/dispose (:op-type trace)))
+
+(defn subscription-not-run? [trace]
+ false)
+
+(defn unchanged-l2-subscription? [sub]
+ ;; TODO: check if value changed
+ (and
+ (= :re-run (:type sub))
+ (= 2 (:layer sub))
+ ;; Show any subs that ran multiple times
+ (nil? (:run-times sub))))
+
+
+(defn finish-run? [event]
+ (and (fsm-trigger? event)
+ (= (:operation event)
+ [:running :finish-run])))
+
+(defn event-run? [event]
+ (= :event (:op-type event)))
+
+(defn start-of-epoch?
+ "Detects the start of a re-frame epoch
+
+ Normally an epoch would always start with the queue being run, but with a dispatch-sync, the event is run directly."
+ [event]
+ (or (run-queue? event)
+ (event-run? event)))
+
+(defn start-of-epoch-and-prev-end?
+ "Detects that a new epoch has started and that the previous one ended on the previous event.
+
+ If multiple events are dispatched while processing the first event, each one is considered its
+ own epoch."
+ [event state]
+ (or (run-queue? event)
+ ;; An event ran, and the previous event was not
+ ;; a run-queue.
+ (and (event-run? event)
+ (not (run-queue? (:previous-event state))))))
+
+(defn quiescent? [event]
+ (= :reagent/quiescent (:op-type event)))
+
+(defn parse-traces [traces]
+ (let [partitions (reduce
+ (fn [state event]
+ (let [current-match (:current-match state)
+ previous-event (:previous-event state)
+ no-match? (nil? current-match)]
+ (-> (cond
+
+ ;; No current match yet, check if this is the start of an epoch
+ no-match?
+ (if (start-of-epoch? event)
+ (assoc state :current-match [event])
+ state)
+
+ ;; We are in an epoch match, and reagent has gone to a quiescent state
+ (quiescent? event)
+ (-> state
+ (update :partitions conj (conj current-match event))
+ (assoc :current-match nil))
+
+ ;; We are in an epoch match, and we have started a new epoch
+ ;; The previously seen event was the last event of the old epoch,
+ ;; and we need to start a new one from this event.
+ (start-of-epoch-and-prev-end? event state)
+ (-> state
+ (update :partitions conj (conj current-match previous-event))
+ (assoc :current-match [event]))
+
+ (event-run? event)
+ (update state :current-match conj event)
+
+
+ :else
+ state
+ ;; Add a timeout/warning if a match goes on for more than a second?
+
+ )
+ (assoc :previous-event event))))
+ {:current-match nil
+ :previous-event nil
+ :partitions []}
+ traces)
+ matches (:partitions partitions)]
+ {:matches matches}))
+
+(defn matched-event [match]
+ (->> match
+ (filter event-run?)
+ (first)))
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 28dec04..672a25a 100644
--- a/src/day8/re_frame/trace/styles.cljs
+++ b/src/day8/re_frame/trace/styles.cljs
@@ -5,9 +5,9 @@
[garden.color :as color]
[garden.selectors :as s]
[day8.re-frame.trace.common-styles :as common]
- [day8.re-frame.trace.utils.re-com :as rc]))
+ [day8.re-frame.trace.utils.re-com :as rc]
+ [day8.re-frame.trace.view.app-db :as app-db]))
-(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)
@@ -42,9 +42,9 @@
:font-size (em 1)}]
;; Text-level semantics
- [(s/a) (s/a s/visited) {:color text-color
- :border-bottom [[(px 1) "#333" "dotted"]]}]
- [(s/a s/hover) (s/a s/focus) {:border-bottom [[(px 1) "#666666" "solid"]]}]
+ [(s/a) (s/a s/visited) {:color text-color
+ :cursor "pointer"
+ :text-decoration "underline"}]
[:code {:font-family "monospace"
:font-size (em 1)}]
@@ -58,17 +58,20 @@
[: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")
(s/attr= "type" "submit")
{:-webkit-appearance "button"}]
+ [(s/input (s/attr= "type" "checkbox"))
+ {:-webkit-appearance "checkbox"
+ :box-sizing "border-box"}]
[:button:-moz-focusring
(s/attr= "type" "button")
@@ -88,7 +91,7 @@
:-webkit-font-smoothing "inherit"
:letter-spacing "inherit"
:background "none"
- #_ #_ :cursor "pointer"}]
+ #_#_:cursor "pointer"}]
[:img {:max-width (percent 100)
:height "auto"
:border "0"}]
@@ -115,7 +118,28 @@
[:thead {:display "table-header-group"}]
[:tbody {:display "table-row-group"}]
[:th :td {:display "table-cell"}]
- [:tr {:display "table-row"}]])
+ [:tr {:display "table-row"}]
+
+ ;; SVG Reset
+ ;; From https://chromium.googlesource.com/chromium/blink/+/master/Source/core/css/svg.css
+ ["svg:not(:root), symbol, image, marker, pattern, foreignObject"
+ {:overflow "hidden"}]
+ ["svg:root"
+ {:width "100%"
+ :height "100%"}]
+ ["text, foreignObject"
+ {:display "block"}]
+ ["text"
+ {:white-space "nowrap"}]
+ ["tspan, textPath"
+ {:white-space "inherit"}]
+ ;; No :focus rule
+ ["*"
+ {:transform-origin "0px 0px 0px"}]
+ ["html|* > svg"
+ {:transform-origin "50% 50%"}]
+
+ ])
(def label-mixin {:color text-color
:background background-gray-hint
@@ -134,9 +158,9 @@
(def re-frame-trace-styles
[:#--re-frame-trace--
- {:background "white"
- :font-family ["'courier new'" "monospace"]
- :color text-color}
+ {:background-color common/background-gray
+ :font-family common/font-stack
+ :color text-color}
[:.label label-mixin]
@@ -156,7 +180,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}]]
@@ -219,16 +243,15 @@
[:.button {:padding "5px 5px 3px"
:margin "5px"
:border-radius "2px"
- #_ #_ :cursor "pointer"}]
+ #_#_: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 {}]
+ [:button.tab {:font-weight 300}]
[:.nav-icon
- {:width "15px"
- :height "15px"
+ {:width "30px"
+ :height "30px"
:cursor "pointer"
:padding "0 5px"
:margin "0 5px"}
@@ -237,16 +260,16 @@
[:.tab
{:background "transparent"
:border-radius 0
- :text-transform "uppercase"
- :font-family "monospace"
- :letter-spacing "2px"
- :margin-bottom 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"
- :border-bottom [[(px 3) "solid" dark-gray]]
+ :color common/blue-modern-color
+ :border-bottom [[(px 3) "solid" common/blue-modern-color]]
:border-radius 0
:padding-bottom (px 1)}]
@@ -278,7 +301,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,12 +319,37 @@
[:.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 ;; Should this be a button instead of a span?
+ :background-color common/standard-background-color
+ :padding (px 5)
+ :cursor "pointer"
+ :user-select "none"}]
+ [:span.arrow__disabled {:color common/disabled-background-color
+ :cursor "auto"}]
+ [:span.event-header {:color common/text-color
+ :background-color common/standard-background-color
+ :padding (px 5)
+ :font-weight "600"
+ ;; TODO: figure out how to hide long events
+ :text-overflow "ellipsis"}]
+ ]
[(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 {:background-color common/white-background-color :padding-left common/gs-19}]
[:.panel-content-scrollable panel-mixin]
[:.epoch-panel panel-mixin]
[:.tab-contents {:display "flex"
@@ -319,6 +367,7 @@
:margin "5px"
:opacity "0.3"}]
[:.active {:opacity 1}]
+
[:.re-frame-trace--object
[:.toggle {:color text-color-muted
:cursor "pointer"
@@ -330,10 +379,19 @@
:width (px 16)
:padding "0 2px"
:vertical-align "middle"}]
+ [:.bm-muted-button {:font-size "14px"
+ :height "23px"
+ :padding "0px 7px"}]
+ [:.noselect {:-webkit-touch-callout "none"
+ :-webkit-user-select "none"
+ :-khtml-user-select "none"
+ :-moz-user-select "none"
+ :-ms-user-select "none"
+ :user-select "none"}]
])
-(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 [:#--re-frame-trace-- rc/re-com-css] common/blue-modern re-frame-trace-styles app-db/app-db-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..b40a032 100644
--- a/src/day8/re_frame/trace/subs.cljs
+++ b/src/day8/re_frame/trace/subs.cljs
@@ -1,5 +1,7 @@
(ns day8.re-frame.trace.subs
- (:require [mranderson047.re-frame.v0v10v2.re-frame.core :as rf]))
+ (:require [mranderson047.re-frame.v0v10v2.re-frame.core :as rf]
+ [day8.re-frame.trace.metamorphic :as metam]
+ [day8.re-frame.trace.utils.utils :as utils]))
(rf/reg-sub
:settings/root
@@ -22,7 +24,15 @@
:settings/selected-tab
:<- [:settings/root]
(fn [settings _]
- (get settings :selected-tab)))
+ (if (:showing-settings? settings)
+ :settings
+ (get settings :selected-tab))))
+
+(rf/reg-sub
+ :settings/paused?
+ :<- [:settings/root]
+ (fn [settings _]
+ (:paused? settings)))
;; App DB
@@ -31,11 +41,24 @@
(fn [db _]
(get db :app-db)))
+(rf/reg-sub
+ :app-db/current-epoch-app-db-after
+ :<- [:epochs/current-event-trace]
+ (fn [trace _]
+ (get-in trace [:tags :app-db-after])))
+
+(rf/reg-sub
+ :app-db/current-epoch-app-db-before
+ :<- [:epochs/current-event-trace]
+ (fn [trace _]
+ (get-in trace [:tags :app-db-before])))
+
(rf/reg-sub
:app-db/paths
:<- [:app-db/root]
(fn [app-db-settings _]
- (get app-db-settings :paths)))
+ (map #(assoc (val %) :id (key %))
+ (get app-db-settings :paths))))
(rf/reg-sub
:app-db/search-string
@@ -55,8 +78,19 @@
(fn [expansions [_ path]]
(contains? expansions path)))
+(rf/reg-sub
+ :app-db/reagent-id
+ :<- [:app-db/root]
+ (fn [root _]
+ (:reagent-id root)))
+
;;
+(rf/reg-sub
+ :traces/trace-root
+ (fn [db _]
+ (:traces db)))
+
(rf/reg-sub
:traces/filter-items
(fn [db _]
@@ -72,6 +106,34 @@
(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] _]
+ (into [] (filter #(<= beginning (:id %) ending)) traces)))
+
+(rf/reg-sub
+ :traces/show-epoch-traces?
+ :<- [:traces/trace-root]
+ (fn [trace-root]
+ (:show-epoch-traces? trace-root)))
+
+;;
+
(rf/reg-sub
:global/unloading?
(fn [db _]
@@ -89,3 +151,261 @@
:<- [:snapshot/snapshot-root]
(fn [snapshot _]
(contains? snapshot :current-snapshot)))
+
+;;
+
+(rf/reg-sub
+ :epochs/epoch-root
+ (fn [db _]
+ (:epochs db)))
+
+(rf/reg-sub
+ :epochs/current-match
+ :<- [: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))]
+ match)))
+
+(rf/reg-sub
+ :epochs/current-event-trace
+ :<- [:epochs/current-match]
+ (fn [match _]
+ (metam/matched-event match)))
+
+(rf/reg-sub
+ :epochs/current-event
+ :<- [:epochs/current-event-trace]
+ (fn [trace _]
+ (get-in trace [:tags :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/current-match]
+ (fn [match]
+ (:id (first match))))
+
+(rf/reg-sub
+ :epochs/ending-trace-id
+ :<- [:epochs/current-match]
+ (fn [match]
+ (:id (last match))))
+
+(rf/reg-sub
+ :epochs/older-epochs-available?
+ :<- [:epochs/current-event-index]
+ :<- [:epochs/number-of-matches]
+ (fn [[current total]]
+ (pos? (+ current total -1))))
+
+(rf/reg-sub
+ :epochs/newer-epochs-available?
+ :<- [:epochs/current-event-index]
+ :<- [:epochs/number-of-matches]
+ (fn [[current total]]
+ (and (not (zero? current))
+ (some? current))))
+
+;;
+
+(rf/reg-sub
+ :timing/total-epoch-time
+ :<- [:traces/current-event-traces]
+ (fn [traces]
+ (let [start-of-epoch (nth traces 0)
+ end-of-epoch (utils/last-in-vec traces)]
+ (metam/elapsed-time start-of-epoch end-of-epoch))))
+
+(rf/reg-sub
+ :timing/animation-frame-count
+ :<- [:traces/current-event-traces]
+ (fn [traces]
+ (count (filter metam/request-animation-frame? traces))))
+
+(rf/reg-sub
+ :timing/event-processing-time
+ :<- [:traces/current-event-traces]
+ (fn [traces]
+ (let [start-of-epoch (nth traces 0)
+ finish-run (first (filter metam/finish-run? traces))]
+ (metam/elapsed-time start-of-epoch finish-run))))
+
+(rf/reg-sub
+ :timing/render-time
+ :<- [:traces/current-event-traces]
+ (fn [traces]
+ (let [start-of-render (first (filter metam/request-animation-frame? traces))
+ end-of-epoch (utils/last-in-vec traces)]
+ (metam/elapsed-time start-of-render end-of-epoch))))
+
+(rf/reg-sub
+ :timing/data-available?
+ :<- [:traces/current-event-traces]
+ (fn [traces]
+ (not (empty? traces))))
+
+;;
+
+(rf/reg-sub
+ :subs/root
+ (fn [db _]
+ (:subs db)))
+
+(rf/reg-sub
+ :subs/all-sub-traces
+ :<- [:traces/current-event-traces]
+ (fn [traces]
+ (filter metam/subscription? traces)))
+
+(defn sub-sort-val
+ [sub]
+ (case (:type sub)
+ :created 1
+ :re-run 2
+ :destroyed 3
+ :not-run 4))
+
+(def subscription-comparator
+ (fn [x y]
+ (compare (sub-sort-val x) (sub-sort-val y))))
+
+(defn sub-op-type->type [t]
+ (case (:op-type t)
+ :sub/create :created
+ :sub/run :re-run
+ :sub/dispose :destroyed
+
+ :not-run))
+
+(rf/reg-sub
+ :subs/all-subs
+ :<- [:subs/all-sub-traces]
+ :<- [:app-db/reagent-id]
+ (fn [[traces app-db-id]]
+ (let [raw (map (fn [trace] (let [pod-type (sub-op-type->type trace)
+ path-data (get-in trace [:tags :query-v])
+ ;; TODO: detect layer 2/3 for sub/create and sub/destroy
+ ;; This information needs to be accumulated.
+ layer (if (some #(= app-db-id %) (get-in trace [:tags :input-signals]))
+ 2
+ 3)]
+ {:id (str pod-type (get-in trace [:tags :reaction]))
+ :type pod-type
+ :layer layer
+ :path-data path-data
+ :path (pr-str path-data)
+ :value (get-in trace [:tags :value])
+
+ ;; TODO: Get not run subscriptions
+ }))
+ traces)
+ re-run (->> raw
+ (filter #(= :re-run (:type %)))
+ (map (juxt :path-data identity))
+ (into {}))
+ created (->> raw
+ (filter #(= :created (:type %)))
+ (map (juxt :path-data identity))
+ (into {}))
+ raw (keep (fn [sub]
+ (case (:type sub)
+ :created (if-some [re-run-sub (get re-run (:path-data sub))]
+ (assoc sub :value (:value re-run-sub))
+ sub)
+
+ :re-run (when-not (contains? created (:path-data sub))
+ sub)
+
+ sub))
+ raw)
+
+ ;; Filter out run if it was created
+ ;; Group together run time
+ run-multiple? (into {}
+ (filter (fn [[k v]] (< 1 v)))
+ (frequencies (map :id raw)))
+
+ output (map (fn [sub] (assoc sub :run-times (get run-multiple? (:id sub)))) raw)]
+ (js/console.log "Output" output)
+ (js/console.log "Traces" traces)
+ (js/console.log "rerun" re-run)
+ (sort-by identity subscription-comparator output))))
+
+(rf/reg-sub
+ :subs/visible-subs
+ :<- [:subs/all-subs]
+ :<- [:subs/ignore-unchanged-subs?]
+ (fn [[all-subs ignore-unchanged-l2?]]
+ (if ignore-unchanged-l2?
+ (remove metam/unchanged-l2-subscription? all-subs)
+ all-subs)))
+
+(rf/reg-sub
+ :subs/sub-counts
+ :<- [:subs/visible-subs]
+ (fn [subs _]
+ (->> subs
+ (map :type)
+ (frequencies))))
+
+(rf/reg-sub
+ :subs/created-count
+ :<- [:subs/sub-counts]
+ (fn [counts]
+ (get counts :created 0)))
+
+(rf/reg-sub
+ :subs/re-run-count
+ :<- [:subs/sub-counts]
+ (fn [counts]
+ (get counts :re-run 0)))
+
+(rf/reg-sub
+ :subs/destroyed-count
+ :<- [:subs/sub-counts]
+ (fn [counts]
+ (get counts :destroyed 0)))
+
+(rf/reg-sub
+ :subs/not-run-count
+ :<- [:subs/sub-counts]
+ (fn [counts]
+ (get counts :not-run 0)))
+
+(rf/reg-sub
+ :subs/unchanged-l2-subs-count
+ :<- [:subs/all-subs]
+ (fn [subs]
+ (count (filter metam/unchanged-l2-subscription? subs))))
+
+(rf/reg-sub
+ :subs/ignore-unchanged-subs?
+ :<- [:subs/root]
+ (fn [subs _]
+ (:ignore-unchanged-subs? subs true)))
+
+(rf/reg-sub
+ :subs/sub-expansions
+ :<- [:subs/root]
+ (fn [subs _]
+ (:expansions subs)))
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..5a6506e 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,150 @@
[& 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]])
+
+(defn button
+ "Returns the markup for a basic button"
+ []
+ (let [showing? (reagent/atom false)]
+ (fn
+ [& {:keys [label on-click disabled? class style attr]
+ :or {class "btn-default"}
+ :as args}]
+ (let [disabled? (deref-or-value disabled?)
+ the-button [:button
+ (merge
+ {:class (str "rc-button btn noselect " class)
+ :style (merge
+ (flex-child-style "none")
+ style)
+ :disabled disabled?
+ :on-click (handler-fn
+ (when (and on-click (not disabled?))
+ (on-click event)))}
+ attr)
+ label]]
+ (when disabled?
+ (reset! showing? false))
+ [box ;; Wrapper box is unnecessary but keeps the same structure as the re-com button
+ :class "rc-button-wrapper display-inline-flex"
+ :align :start
+ :child the-button]))))
+
+(defn hyperlink
+ "Renders an underlined text hyperlink component.
+ This is very similar to the button component above but styled to looks like a hyperlink.
+ Useful for providing button functionality for less important functions, e.g. Cancel"
+ []
+ (let [showing? (reagent/atom false)]
+ (fn
+ [& {:keys [label on-click disabled? class style attr] :as args}]
+ (let [label (deref-or-value label)
+ disabled? (deref-or-value disabled?)
+ the-button [box
+ :align :start
+ :child [:a
+ (merge
+ {:class (str "rc-hyperlink noselect " class)
+ :style (merge
+ (flex-child-style "none")
+ {:cursor (if disabled? "not-allowed" "pointer")
+ :color (when disabled? "grey")}
+ style)
+ :on-click (handler-fn
+ (when (and on-click (not disabled?))
+ (on-click event)))}
+ attr)
+ label]]]
+ [box
+ :class "rc-hyperlink-wrapper display-inline-flex"
+ :align :start
+ :child the-button]))))
+
+(defn hyperlink-href
+ "Renders an underlined text hyperlink component.
+ This is very similar to the button component above but styled to looks like a hyperlink.
+ Useful for providing button functionality for less important functions, e.g. Cancel"
+ []
+ (let [showing? (reagent/atom false)]
+ (fn
+ [& {:keys [label href target tooltip tooltip-position class style attr] :as args}]
+ (when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
+ (let [label (deref-or-value label)
+ href (deref-or-value href)
+ target (deref-or-value target)
+ the-button [:a
+ (merge {:class (str "rc-hyperlink-href noselect " class)
+ :style (merge (flex-child-style "none")
+ style)
+ :href href
+ :target target}
+ (when tooltip
+ {:on-mouse-over (handler-fn (reset! showing? true))
+ :on-mouse-out (handler-fn (reset! showing? false))})
+ attr)
+ label]]
+
+ [box
+ :class "rc-hyperlink-href-wrapper display-inline-flex"
+ :align :start
+ :child the-button]))))
+
+(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])]]))
+
+(defn css-join [& args]
+ "Creates a single string from all passed args, separated by spaces (all args are coerced to strings)
+ Very simple, but handy
+ e.g. {:padding (css-join common/gs-12s (px 25))}"
+ (clojure.string/join " " args))
+
(def re-com-css
[[:.display-flex {:display "flex"}]
[:.display-inline-flex {:display "flex"}]])
diff --git a/src/day8/re_frame/trace/utils/utils.cljs b/src/day8/re_frame/trace/utils/utils.cljs
index dfbf190..7405f78 100644
--- a/src/day8/re_frame/trace/utils/utils.cljs
+++ b/src/day8/re_frame/trace/utils/utils.cljs
@@ -1,2 +1,16 @@
(ns day8.re-frame.trace.utils.utils)
+(defn last-in-vec
+ "Get the last element in the vector"
+ [v]
+ (nth v (dec (count v))))
+
+(defn find-all-indexes-in-vec
+ "Gets the index of all items in vec that match the predicate"
+ [pred v]
+ (keep-indexed #(when (pred %2) %1) v))
+
+(defn find-index-in-vec
+ "Gets the index of the first item in vec that matches the predicate"
+ [pred v]
+ (first (find-all-indexes-in-vec pred v)))
diff --git a/src/day8/re_frame/trace/view/app_db.cljs b/src/day8/re_frame/trace/view/app_db.cljs
index 65e1144..4924b21 100644
--- a/src/day8/re_frame/trace/view/app_db.cljs
+++ b/src/day8/re_frame/trace/view/app_db.cljs
@@ -1,25 +1,314 @@
(ns day8.re-frame.trace.view.app-db
- (:require [reagent.core :as r]
- [clojure.string :as str]
- [devtools.prefs]
+ (:require [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]
- [day8.re-frame.trace.utils.re-com :as rc])
+ [mranderson047.reagent.v0v6v0.reagent.core :as r]
+ [day8.re-frame.trace.utils.re-com :as rc :refer [css-join]]
+ [day8.re-frame.trace.common-styles :as common]
+ [clojure.data])
(: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"))
+(def round-arrow (macros/slurp-macro "day8/re_frame/trace/images/round-arrow.svg"))
+(def arrow-right (macros/slurp-macro "day8/re_frame/trace/images/arrow-right.svg"))
+(def copy (macros/slurp-macro "day8/re_frame/trace/images/copy.svg"))
+(def trash (macros/slurp-macro "day8/re_frame/trace/images/trash.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)]
+(def cljs-dev-tools-background "#e8ffe8")
+(def pod-gap common/gs-19s)
+(def pad-padding common/gs-7s)
+(def border-radius "3px")
+
+(def app-db-styles
+ [:#--re-frame-trace--
+ [:.app-db-path
+ {:background-color common/white-background-color
+ :border-bottom-left-radius border-radius
+ :border-bottom-right-radius border-radius}]
+
+ [:.app-db-path--header
+ {:background-color "#797B7B" ; Name this navbar tint-lighter
+ :color "white"
+ :height common/gs-31
+ :border-top-left-radius border-radius
+ :border-top-right-radius border-radius}]
+ [:.app-db-path--header__closed
+ {:border-bottom-left-radius border-radius
+ :border-bottom-right-radius border-radius}]
+
+ [:.app-db-path--button
+ {:width "25px"
+ :height "25px"
+ :padding "0px"
+ :border-radius border-radius
+ :cursor "pointer"}]
+
+
+ [:.app-db-path--label
+ {:color "#2D9CDB"
+ ;:font-variant "small-caps"
+ ;:text-transform "lowercase"
+ :text-decoration "underline"
+ :font-size "11px"
+ :margin-bottom "2px"
+ ;: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"}]
+
+ [:.app-db-path--link
+ {:margin (css-join "0px" pad-padding)
+ :height common/gs-19s}]
+
+ [:.app-db-panel-button
+ {:width "129px"
+ :padding "0px"}]
+
+ [:.data-viewer
+ {:background-color cljs-dev-tools-background
+ :padding common/gs-7s
+ :margin (css-join "0px" pad-padding)
+ :min-width "100px"
+ }]
+
+ ])
+
+;; TODO: START ========== LOCAL DATA - REPLACE WITH SUBS AND EVENTS
+
+(def *pods (r/atom [{:id (gensym) :path "[\"x\" \"y\"]" :open? true :diff? true}
+ {:id (gensym) :path "[:abc 123]" :open? true :diff? false}
+ {:id (gensym) :path "[:a :b :c]" :open? false :diff? true}
+ {:id (gensym) :path nil :open? false :diff? false}
+ {:id (gensym) :path [:boot-state] :open? true :diff? true}]))
+
+(defn add-pod []
+ (let [id (gensym)]
+ ;(println "Added pod" id)
+ (swap! *pods concat [{:id id :path "" :open? true :diff? false}])))
+
+(defn delete-pod [id]
+ ;(println "Deleted pod" id)
+ (reset! *pods (filterv #(not= id (:id %)) @*pods)))
+
+(defn update-pod-field
+ [id field new-val]
+ (let [f (fn [pod]
+ (if (= id (:id pod))
+ (do
+ ;(println "Updated" field "in" (:id pod) "from" (get pod field) "to" new-val)
+ (assoc pod field new-val))
+ pod))]
+ (reset! *pods (mapv f @*pods))))
+
+;; TODO: END ========== LOCAL DATA - REPLACE WITH SUBS AND EVENTS
+
+(defn panel-header []
+ (let [app-db-after (rf/subscribe [:app-db/current-epoch-app-db-after])
+ app-db-before (rf/subscribe [:app-db/current-epoch-app-db-before])]
+ [rc/h-box
+ :justify :between
+ :align :center
+ :margin (css-join common/gs-19s "0px")
+ :children [[rc/button
+ :class "app-db-panel-button bm-muted-button"
+ :label [rc/v-box
+ :align :center
+ :children ["+ path inspector"]]
+ :on-click #(rf/dispatch [:app-db/create-path])]
+ [rc/h-box
+ :align :center
+ :gap common/gs-7s
+ :height "48px"
+ :padding (css-join "0px" common/gs-12s)
+ :style {:background-color "#fafbfc"
+ :border "1px solid #e3e9ed"
+ :border-radius border-radius}
+ :children [[rc/label :label "reset app-db to:"]
+ [rc/button
+ :class "app-db-panel-button bm-muted-button"
+ :label [rc/v-box
+ :align :center
+ :children ["initial epoch state"]]
+ :on-click #(rf/dispatch [:snapshot/load-snapshot @app-db-before])]
+ [rc/v-box
+ :width common/gs-81s
+ :align :center
+ :children [[rc/label
+ :style {:font-size "9px"}
+ :label "EVENT"]
+ [:img {:src (str "data:image/svg+xml;utf8," arrow-right)}]
+ [rc/label
+ :style {:font-size "9px"
+ :margin-top "-1px"}
+ :label "PROCESSING"]]]
+ [rc/button
+ :class "app-db-panel-button bm-muted-button"
+ :label [rc/v-box
+ :align :center
+ :children ["end epoch state"]]
+ :on-click #(rf/dispatch [:snapshot/load-snapshot @app-db-after])]]]]]))
+
+(defn pod-header [{:keys [id path path-str open? diff?]}]
+ [rc/h-box
+ :class (str "app-db-path--header " (when-not open? "app-db-path--header__closed"))
+ :align :center
+ :height common/gs-31s
+ :children [[rc/box
+ :width "36px"
+ :height common/gs-31s
+ :class "noselect"
+ :style {:cursor "pointer"}
+ :attr {:title (str (if open? "Close" "Open") " the pod bay doors, HAL")
+ :on-click #(rf/dispatch [:app-db/set-path-visibility id (not open?)])}
+ :child [rc/box
+ :margin "auto"
+ :child [:span.arrow (if open? "â–¼" "â–¶")]]]
+ [rc/h-box
+ :class "app-db-path--path-header"
+ :size "auto"
+ :children [[rc/input-text
+ :attr {:on-blur (fn [e] (rf/dispatch [:app-db/update-path-blur id]))}
+ :style {:height "25px"
+ :padding (css-join "0px" common/gs-7s)
+ :width "-webkit-fill-available"} ;; This took a bit of finding!
+ :width "100%"
+ :model path-str
+ :on-change #(rf/dispatch [:app-db/update-path id %]) ;;(fn [input-string] (rf/dispatch [:app-db/search-string input-string]))
+ :on-submit #() ;; #(rf/dispatch [:app-db/add-path %])
+ :change-on-blur? false
+ :placeholder "Showing all of app-db. Try entering a path like [:todos 1]"]]]
+ [rc/gap-f :size common/gs-12s]
+ [rc/box
+ :class "app-db-path--button bm-muted-button noselect"
+ :attr {:title "Show diff"
+ :on-click #(rf/dispatch [:app-db/set-diff-visibility id (not diff?)])}
+ :child [:img
+ {:src (str "data:image/svg+xml;utf8," copy)
+ :style {:width "19px"
+ :margin "0px 3px"}}]]
+ [rc/gap-f :size common/gs-12s]
+ [rc/box
+ :class "app-db-path--button bm-muted-button noselect"
+ :attr {:title "Remove this pod"
+ :on-click #(rf/dispatch [:app-db/remove-path id])}
+ :child [:img
+ {:src (str "data:image/svg+xml;utf8," trash)
+ :style {:width "13px"
+ :margin "0px 6px"}}]]
+ [rc/gap-f :size common/gs-12s]]])
+
+(defn pod [{:keys [id path open? diff?] :as pod-info}]
+ (let [render-diff? (and open? diff?)
+ app-db-after (rf/subscribe [:app-db/current-epoch-app-db-after])
+ app-db-before (rf/subscribe [:app-db/current-epoch-app-db-before])
+ [diff-before diff-after _] (when render-diff?
+ (clojure.data/diff (get-in @app-db-before path)
+ (get-in @app-db-after path)))]
+ [rc/v-box
+ :class "app-db-path"
+ :children [[pod-header pod-info]
+ (when open?
+ [rc/v-box
+ :class "data-viewer"
+ :style {:margin (css-join pad-padding pad-padding "0px" pad-padding)}
+ :children [[components/simple-render
+ (get-in @app-db-after path)
+
+ #_{:todos [1 2 3]}
+ #_(get-in @app-db path)
+ #_[rc/h-box
+ :align :center
+ :children [[:button.subtree-button
+ [:span.subtree-button-string
+ (str path)]]
+ [:img
+ {:src (str "data:image/svg+xml;utf8," delete)
+ :style {:cursor "pointer"
+ :height "10px"}
+ :on-click #(rf/dispatch [:app-db/remove-path path])}]]]
+ #_[path]]
+
+ #_"---main-section---"]])
+ (when render-diff?
+ (list
+ ^{:key "only-before"}
+ [rc/v-box
+ :class "app-db-path--link"
+ :justify :end
+ :children [[rc/hyperlink-href
+ ;:class "app-db-path--label"
+ :label "ONLY BEFORE"
+ :href "https://github.com/Day8/re-frame-trace/wiki/app-db#diff"]]]
+
+ ^{:key "only-before-diff"}
+ [rc/v-box
+ :class "data-viewer"
+ :children [[components/simple-render
+ diff-before]]]
+
+ ^{:key "only-after"}
+ [rc/v-box
+ :class "app-db-path--link"
+ :justify :end
+ :children [[rc/hyperlink-href
+ ;:class "app-db-path--label"
+ :label "ONLY AFTER"
+ :href "https://github.com/Day8/re-frame-trace/wiki/app-db#diff"]]]
+
+ ^{:key "only-after-diff"}
+ [rc/v-box
+ :class "data-viewer"
+ :children [[components/simple-render
+ diff-after]]]))
+ (when open?
+ [rc/gap-f :size pad-padding])]]))
+
+(defn no-pods []
+ [rc/h-box
+ :margin (css-join "0px 0px 0px" common/gs-19s)
+ :gap common/gs-7s
+ :align :start
+ :align-self :start
+ :children [[:img {:src (str "data:image/svg+xml;utf8," round-arrow)}]
+ [rc/label
+ :style {:width "150px"
+ :margin-top "22px"}
+ :label "add inspectors to show what happened to app-db"]]])
+
+(defn pod-section []
+ (let [pods @(rf/subscribe [:app-db/paths])]
+ [rc/v-box
+ :gap pod-gap
+ :children (if (empty? pods)
+ [[no-pods]]
+ (doall (for [p pods]
+ ^{:key (:id pods)}
+ [pod p])))]))
+
+;; TODO: OLD UI - REMOVE
+(defn original-render [app-db]
+ (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
+ {:style {:flex "1 1 auto"
+ :display "flex"
+ :flex-direction "column"
+ :border "1px solid lightgrey"}}
[:div.panel-content-scrollable
- [re-com/input-text
+ [rc/input-text
:model search-string
:on-change (fn [input-string] (rf/dispatch [:app-db/search-string input-string]))
:on-submit #(rf/dispatch [:app-db/add-path %])
@@ -30,6 +319,8 @@
; [:div.input-error {:style {:color "red" :margin-top 5}}
; "Please enter a valid path."])]]
+
+
[:div.subtrees {:style {:margin "20px 0"}}
(doall
(map (fn [path]
@@ -37,19 +328,26 @@
[:div.subtree-wrapper {:style {:margin "10px 0"}}
[:div.subtree
[components/subtree
- (get-in @data path)
+ (get-in @app-db path)
[rc/h-box
:align :center
- :children
- [[:button.subtree-button
- [:span.subtree-button-string
- (str path)]]
- [:img
- {:src (str "data:image/svg+xml;utf8," delete)
- :style {:cursor "pointer"
- :height "10px"}
- :on-click #(rf/dispatch [:app-db/remove-path path])}]]]
+ :children [[:button.subtree-button
+ [:span.subtree-button-string
+ (str path)]]
+ [:img
+ {:src (str "data:image/svg+xml;utf8," delete)
+ :style {:cursor "pointer"
+ :height "10px"}
+ :on-click #(rf/dispatch [:app-db/remove-path path])}]]]
[path]]]])
@subtree-paths))]
[:div {:style {:margin-bottom "20px"}}
- [components/subtree @data [:span.label "app-db"] [:app-db]]]]])))
+ [components/subtree @app-db [:span.label "app-db"] [:app-db]]]]])))
+
+(defn render [app-db]
+ [rc/v-box
+ :style {:margin-right common/gs-19s
+ :overflow "hidden"}
+ :children [[panel-header]
+ [pod-section]
+ [rc/gap-f :size pod-gap]]])
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 88145a7..9ace15d 100644
--- a/src/day8/re_frame/trace/view/container.cljs
+++ b/src/day8/re_frame/trace/view/container.cljs
@@ -2,83 +2,159 @@
(: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.event :as event]
[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.timing :as timing]
+ [day8.re-frame.trace.view.debug :as debug]
[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]))
+ [day8.re-frame.trace.utils.re-com :as rc]
+ [day8.re-frame.trace.common-styles :as common]))
+(def triangle-down (macros/slurp-macro "day8/re_frame/trace/images/triangle-down.svg"))
(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"))
- :on-click #(rf/dispatch [:settings/selected-tab panel-id])} title]))
+ [rc/v-box
+ :style {:margin-bottom "-8px"
+ :z-index 1}
+ :children [[:button {:class (str "tab button bm-heading-text " (when (= selected-tab panel-id) "active"))
+ :on-click #(rf/dispatch [:settings/selected-tab panel-id])} title]
+ [:img {:src (str "data:image/svg+xml;utf8," triangle-down)
+ :style {:opacity (if (= selected-tab panel-id) "1" "0")}}]]]))
-(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 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 play-svg (macros/slurp-macro "day8/re_frame/trace/images/play.svg"))
-(def settings-svg (macros/slurp-macro "day8/re_frame/trace/images/settings.svg"))
+(def outer-margins {:margin (str "0px " common/gs-19s)})
+
+
+
+(defn right-hand-buttons [external-window?]
+ (let [selected-tab (rf/subscribe [:settings/selected-tab])
+ paused? (rf/subscribe [:settings/paused?])
+ 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"])
+ (if @paused?
+ [:img.nav-icon.noselect
+ {:title "Play"
+ :src (str "data:image/svg+xml;utf8,"
+ play-svg)
+ :on-click #(rf/dispatch [:settings/play])}]
+ [:img.nav-icon.noselect
+ {:title "Pause"
+ :src (str "data:image/svg+xml;utf8,"
+ pause-svg)
+ :on-click #(rf/dispatch [:settings/pause])}])
+ [:img.nav-icon.noselect
+ {: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.noselect
+ {:title "Pop out"
+ :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?]
+ (let [current-event @(rf/subscribe [:epochs/current-event])
+ older-epochs-available? @(rf/subscribe [:epochs/older-epochs-available?])
+ newer-epochs-available? @(rf/subscribe [:epochs/newer-epochs-available?])]
+ [[rc/h-box
+ :align :center
+ :size "auto"
+ :gap common/gs-12s
+ :children [[:span.arrow (if older-epochs-available?
+ {:on-click #(rf/dispatch [:epochs/previous-epoch])}
+ {:class "arrow__disabled"}) "â—€"]
+ [rc/v-box
+ :size "auto"
+ :children [[:span.event-header (prn-str current-event)]]]
+ [:span.arrow (if newer-epochs-available?
+ {:on-click #(rf/dispatch [:epochs/next-epoch])}
+ {:class "arrow__disabled"})
+ "â–¶"]]]
+ [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)
- external-window? (= panel-type :popup)
- unloading? (rf/subscribe [:global/unloading?])
- snapshot-ready? (rf/subscribe [:snapshot/snapshot-ready?])]
+ (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"}}
- [rc/h-box
- :class "panel-content-top nav"
- :justify :between
- :children
- [[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])}]]]
-
-
+ {:style {:width "100%" :display "flex" :flex-direction "column" :background-color common/standard-background-color}}
+ (if showing-settings?
[rc/h-box
- :align :center
- :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])}]
- (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])}])]]]]
+ :class "panel-content-top nav"
+ :style {:padding "0px 19px"}
+ :children (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"
+ :justify :between
+ :children [[rc/h-box
+ :gap "7px"
+ :align :end
+ :height "50px"
+ :children [(tab-button :event "Event")
+ (tab-button :app-db "app-db")
+ (tab-button :subs "Subs")
+ ;(tab-button :views "Views")
+ (tab-button :traces "Trace")
+ (tab-button :timing "Timing")
+ (when (:debug? opts)
+ (tab-button :debug "Debug"))]]]])
+ [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?)
[: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]
- :app-db [app-db/render-state db/app-db]
- :subs [subs/subs-panel]
- :settings [settings/render]
- [app-db/render-state db/app-db])]))
+ [rc/v-box
+ :size "auto"
+ :style {:margin-left common/gs-19s
+ :overflow "auto"}
+ :children [(case @selected-tab
+ :event [event/render traces]
+ :app-db [app-db/render db/app-db]
+ :subs [subs/render]
+ :views [views/render]
+ :traces [traces/render traces]
+ :timing [timing/render]
+ :debug [debug/render]
+ :settings [settings/render]
+ [app-db/render db/app-db])]]]))
+
diff --git a/src/day8/re_frame/trace/view/debug.cljs b/src/day8/re_frame/trace/view/debug.cljs
new file mode 100644
index 0000000..aa1860f
--- /dev/null
+++ b/src/day8/re_frame/trace/view/debug.cljs
@@ -0,0 +1,23 @@
+(ns day8.re-frame.trace.view.debug
+ (:require [day8.re-frame.trace.utils.re-com :as rc]
+ [mranderson047.re-frame.v0v10v2.re-frame.core :as rf]
+ [day8.re-frame.trace.metamorphic :as metam]))
+
+(defn render []
+ [rc/v-box
+ :gap "5px"
+ :children
+ [
+ [rc/label :label (str "Number of epochs " (prn-str @(rf/subscribe [:epochs/number-of-matches])))]
+ [rc/label :label (str "Beginning trace " (prn-str @(rf/subscribe [:epochs/beginning-trace-id])))]
+ [rc/label :label (str "Ending " (prn-str @(rf/subscribe [:epochs/ending-trace-id])))]
+
+ [rc/label :label "Epochs"]
+ (for [match (:matches @(rf/subscribe [:epochs/epoch-root]))]
+ ^{:key (:id (first match))}
+ [rc/v-box
+ :style {:border "1px solid black"}
+ :children (doall (map (fn [event] [rc/label :label (prn-str event)]) (metam/summarise-match match)))
+ ])
+ ]]
+ )
diff --git a/src/day8/re_frame/trace/view/event.cljs b/src/day8/re_frame/trace/view/event.cljs
new file mode 100644
index 0000000..ef6eaa6
--- /dev/null
+++ b/src/day8/re_frame/trace/view/event.cljs
@@ -0,0 +1,19 @@
+(ns day8.re-frame.trace.view.event
+ (:require [day8.re-frame.trace.utils.re-com :as rc]
+ [day8.re-frame.trace.metamorphic :as metam]))
+
+(defn render [traces]
+ [rc/v-box
+ :padding "12px 0px"
+ :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 a7e0fad..04d96b5 100644
--- a/src/day8/re_frame/trace/view/settings.cljs
+++ b/src/day8/re_frame/trace/view/settings.cljs
@@ -1,6 +1,63 @@
-(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 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 []
- [:h1 "Settings"]
+ [rc/v-box
+ :style {:padding-top common/gs-31s}
+ :gap common/gs-19s
+ :children
+ [(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]
+
+ [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/line]
+
+ [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/line]
+
+ [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)."]]]
+
+ ]])
diff --git a/src/day8/re_frame/trace/view/subs.cljs b/src/day8/re_frame/trace/view/subs.cljs
index 524e3f2..501fb7b 100644
--- a/src/day8/re_frame/trace/view/subs.cljs
+++ b/src/day8/re_frame/trace/view/subs.cljs
@@ -1,9 +1,10 @@
(ns day8.re-frame.trace.view.subs
- (:require [re-frame.subs :as subs]
- [day8.re-frame.trace.utils.re-com :as rc]
- ;[cljs.spec.alpha :as s]
- [day8.re-frame.trace.view.components :as components]
- [mranderson047.re-frame.v0v10v2.re-frame.core :as rf]))
+ (:require [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 :refer [css-join]]
+ [day8.re-frame.trace.common-styles :as common]
+ [day8.re-frame.trace.view.components :as components])
+ (:require-macros [day8.re-frame.trace.utils.macros :as macros]))
;(s/def ::query-v any?)
;(s/def ::dyn-v any?)
@@ -12,29 +13,270 @@
;(s/def ::query-cache (s/map-of ::query-cache-params ::deref))
;(assert (s/valid? ::query-cache (rc/deref-or-value-peek subs/query->reaction)))
+(def copy (macros/slurp-macro "day8/re_frame/trace/images/copy.svg"))
+(def cljs-dev-tools-background "#e8ffe8")
+(def pod-gap common/gs-19s)
+(def pad-padding common/gs-7s)
+;; TODO: START ========== LOCAL DATA - REPLACE WITH SUBS AND EVENTS
-(defn subs-panel []
+(def *pods (r/atom [{:id (gensym) :type :destroyed :layer "3" :path "[:todo/blah]" :open? true :diff? false}
+ {:id (gensym) :type :created :layer "3" :path "[:todo/completed]" :open? true :diff? true}
+ {:id (gensym) :type :re-run :layer "3" :path "[:todo/completed]" :open? true :diff? false}
+ {:id (gensym) :type :re-run :layer "2" :path "[:todo/blah]" :open? true :diff? false}
+ {:id (gensym) :type :not-run :layer "3" :path "[:todo/blah]" :open? true :diff? false}]))
+
+(defn update-pod-field
+ [id field new-val]
+ (let [f (fn [pod]
+ (if (= id (:id pod))
+ (do
+ ;(println "Updated" field "in" (:id pod) "from" (get pod field) "to" new-val)
+ (assoc pod field new-val))
+ pod))]
+ (reset! *pods (mapv f @*pods))))
+
+;; TODO: END ========== LOCAL DATA - REPLACE WITH SUBS AND EVENTS
+
+(defn tag-color [type]
+ (let [types {:created "#9b51e0"
+ :destroyed "#f2994a"
+ :re-run "#219653"
+ :not-run "#bdbdbd"}]
+ (get types type "black")))
+
+(def tag-types {:created {:long "CREATED" :short "CREATED"}
+ :destroyed {:long "DESTROYED" :short "DESTROY"}
+ :re-run {:long "RE-RUN" :short "RE-RUN"}
+ :not-run {:long "NOT-RUN" :short "NOT-RUN"}})
+
+(defn long-tag-desc [type]
+ (get-in tag-types [type :long] "???"))
+
+(defn short-tag-desc [type]
+ (get-in tag-types [type :short] "???"))
+
+(defn tag [type label]
+ [rc/box
+ :class "noselect"
+ :style {:color "white"
+ :background-color (tag-color type)
+ :width "48px" ;common/gs-50s
+ :height "17px" ;common/gs-19s
+ :font-size "10px"
+ :font-weight "bold"
+ :border "1px solid #bdbdbd"
+ :border-radius "3px"}
+ :child [:span {:style {:margin "auto"}} label]])
+
+(defn title-tag [type title label]
+ [rc/v-box
+ :class "noselect"
+ :align :center
+ :gap "2px"
+ :children [[:span {:style {:font-size "9px"}} title]
+ [tag type label]]])
+
+(defn panel-header []
+ (let [created-count (rf/subscribe [:subs/created-count])
+ re-run-count (rf/subscribe [:subs/re-run-count])
+ destroyed-count (rf/subscribe [:subs/destroyed-count])
+ not-run-count (rf/subscribe [:subs/not-run-count])
+ ignore-unchanged? (rf/subscribe [:subs/ignore-unchanged-subs?])
+ ignore-unchanged-l2-count (rf/subscribe [:subs/unchanged-l2-subs-count])]
+ [rc/h-box
+ :justify :between
+ :align :center
+ :margin (css-join common/gs-19s "0px")
+ :children [[rc/h-box
+ :align :center
+ :gap common/gs-19s
+ :height "48px"
+ :padding (css-join "0px" common/gs-19s)
+ :style {:background-color "#fafbfc"
+ :border "1px solid #e3e9ed"
+ :border-radius "3px"}
+ :children [[:span {:style {:color "#828282"
+ :font-size "18px"
+ :font-weight "lighter"}}
+ "Summary:"]
+ [title-tag :created (long-tag-desc :created) @created-count]
+ [title-tag :re-run (long-tag-desc :re-run) @re-run-count]
+ [title-tag :destroyed (long-tag-desc :destroyed) @destroyed-count]
+ [title-tag :not-run (long-tag-desc :not-run) @not-run-count]]]
+ [rc/h-box
+ :align :center
+ :gap common/gs-19s
+ :height "48px"
+ :padding (css-join "0px" common/gs-19s)
+ :style {:background-color "#fafbfc"
+ :border "1px solid #e3e9ed"
+ :border-radius "3px"}
+ :children [[rc/checkbox
+ :model ignore-unchanged?
+ :label [:span "Ignore " [:b {:style {:font-weight "700"}} @ignore-unchanged-l2-count] " unchanged" [:br] "layer 2 subs"]
+ :style {:margin-top "6px"}
+ :on-change #(rf/dispatch [:subs/ignore-unchanged-subs? %])]]]]]))
+
+(defn pod-header [{:keys [id type layer path open? diff? run-times]}]
+ [rc/h-box
+ :class "app-db-path--header"
+ :style (merge {:border-top-left-radius "3px"
+ :border-top-right-radius "3px"}
+ (when-not open?
+ {:border-bottom-left-radius "3px"
+ :border-bottom-right-radius "3px"}))
+ :align :center
+ :height common/gs-31s
+ :children [[rc/box
+ :width "36px"
+ :height common/gs-31s
+ :class "noselect"
+ :style {:cursor "pointer"}
+ :attr {:title (str (if open? "Close" "Open") " the pod bay doors, HAL")
+ :on-click #(rf/dispatch [:subs/open-pod? id (not open?)])}
+ :child [rc/box
+ :margin "auto"
+ :child [:span.arrow (if open? "â–¼" "â–¶")]]]
+ [rc/box
+ :width "64px" ;; (100-36)px from box above
+ :child [tag type (short-tag-desc type)]]
+ (when run-times
+ [:span "Warning: run " run-times " times"])
+ [rc/h-box
+ :size "auto"
+ :class "app-db-path--path-header"
+ :children [[rc/input-text
+ :style {:height "25px"
+ :padding (css-join "0px" common/gs-7s)
+ :width "-webkit-fill-available"} ;; This took a bit of finding!
+ :width "100%"
+ :model path
+ :disabled? true
+ :on-change #(update-pod-field id :path %) ;;(fn [input-string] (rf/dispatch [:app-db/search-string input-string]))
+ :on-submit #() ;; #(rf/dispatch [:app-db/add-path %])
+ :change-on-blur? false
+ :placeholder "Showing all of app-db. Try entering a path like [:todos 1]"]]]
+ [rc/gap-f :size common/gs-12s]
+ [rc/label :label (str "Layer " layer)]
+ [rc/gap-f :size common/gs-12s]
+ [rc/box
+ :class "bm-muted-button noselect"
+ :style {:width "25px"
+ :height "25px"
+ :padding "0px"
+ :border-radius "3px"
+ :cursor "pointer"}
+ :attr {:title "Show diff"
+ :on-click #(rf/dispatch [:subs/diff-pod? id (not diff?)])}
+ :child [:img
+ {:src (str "data:image/svg+xml;utf8," copy)
+ :style {:width "19px"
+ :margin "0px 3px"}}]]
+ [rc/gap-f :size common/gs-12s]]])
+
+(defn pod [{:keys [id type layer path open? diff?] :as pod-info}]
+ [rc/v-box
+ :class "app-db-path"
+ :style {:border-bottom-left-radius "3px"
+ :border-bottom-right-radius "3px"}
+ :children [[pod-header pod-info]
+ (when open?
+ [rc/v-box
+ :min-width "100px"
+ :style {:background-color cljs-dev-tools-background
+ :padding common/gs-7s
+ :margin (css-join pad-padding pad-padding "0px" pad-padding)}
+ :children [[components/simple-render
+ (:value pod-info)]]])
+ (when (and open? diff?)
+ [rc/v-box
+ :height common/gs-19s
+ :justify :end
+ :style {:margin (css-join "0px" pad-padding)}
+ :children [[rc/hyperlink
+ ;:class "app-db-path--label"
+ :label "ONLY BEFORE"
+ :on-click #(println "Clicked [ONLY BEFORE]")]]])
+ (when (and open? diff?)
+ [rc/v-box
+ :height "60px"
+ :min-width "100px"
+ :style {:background-color cljs-dev-tools-background
+ :padding common/gs-7s
+ :margin (css-join "0px" pad-padding)}
+ :children ["---before-diff---"]])
+ (when (and open? diff?)
+ [rc/v-box
+ :height common/gs-19s
+ :justify :end
+ :style {:margin (css-join "0px" pad-padding)}
+ :children [[rc/hyperlink
+ ;:class "app-db-path--label"
+ :label "ONLY AFTER"
+ :on-click #(println "Clicked [ONLY AFTER]")]]])
+ (when (and open? diff?)
+ [rc/v-box
+ :height "60px"
+ :min-width "100px"
+ :style {:background-color cljs-dev-tools-background
+ :padding common/gs-7s
+ :margin (css-join "0px" pad-padding)}
+ :children ["---after-diff---"]])
+ (when open?
+ [rc/gap-f :size pad-padding])]])
+
+(defn no-pods []
+ [rc/h-box
+ :margin (css-join "0px 0px 0px" common/gs-19s)
+ :gap common/gs-7s
+ :align :start
+ :align-self :start
+ :children [[rc/label :label "There are no subscriptions to show"]]])
+
+(defn pod-section []
+ (let [all-subs @(rf/subscribe [:subs/visible-subs])
+ sub-expansions @(rf/subscribe [:subs/sub-expansions])]
+ (js/console.log sub-expansions)
+ [rc/v-box
+ :gap pod-gap
+ :children (if (empty? all-subs)
+ [[no-pods]]
+ (doall (for [p all-subs]
+ ^{:key (:id p)}
+ [pod (merge p (get sub-expansions (:id p)))])))]))
+
+(defn render []
[]
- [:div {:style {:flex "1 1 auto" :display "flex" :flex-direction "column"}}
- [:div.panel-content-scrollable
- [:div.subtrees {:style {:margin "20px 0"}}
- (doall
- (->> @subs/query->reaction
- (sort-by (fn [me] (ffirst (key me))))
- (map (fn [me]
- (let [[query-v dyn-v :as inputs] (key me)]
- ^{:key query-v}
- [:div.subtree-wrapper {:style {:margin "10px 0"}}
- [:div.subtree
- [components/subscription-render
- (rc/deref-or-value-peek (val me))
- [:button.subtree-button {:on-click #(rf/dispatch [:app-db/remove-path (key me)])}
- [:span.subtree-button-string
- (prn-str (first (key me)))]]
- (into [:subs] query-v)]]]))
- )))
- (do @re-frame.db/app-db
- nil)]]])
+ [rc/v-box
+ :style {:margin-right common/gs-19s
+ :overflow "hidden"}
+ :children [[panel-header]
+ [pod-section]
+ [rc/gap-f :size pod-gap]
+
+ ;; TODO: OLD UI - REMOVE
+ #_[:div.panel-content-scrollable
+ {:style {:border "1px solid lightgrey"
+ :margin "0px"}}
+ [:div.subtrees
+ {:style {:margin "20px 0"}}
+ (doall
+ (->> @subs/query->reaction
+ (sort-by (fn [me] (ffirst (key me))))
+ (map (fn [me]
+ (let [[query-v dyn-v :as inputs] (key me)]
+ ^{:key query-v}
+ [:div.subtree-wrapper {:style {:margin "10px 0"}}
+ [:div.subtree
+ [components/subscription-render
+ (rc/deref-or-value-peek (val me))
+ [:button.subtree-button {:on-click #(rf/dispatch [:app-db/remove-path (key me)])}
+ [:span.subtree-button-string
+ (prn-str (first (key me)))]]
+ (into [:subs] query-v)]]]))
+ )))
+ (do @re-frame.db/app-db
+ nil)]]]])
diff --git a/src/day8/re_frame/trace/view/timing.cljs b/src/day8/re_frame/trace/view/timing.cljs
new file mode 100644
index 0000000..a67ee2e
--- /dev/null
+++ b/src/day8/re_frame/trace/view/timing.cljs
@@ -0,0 +1,24 @@
+(ns day8.re-frame.trace.view.timing
+ (:require [clojure.string :as str]
+ [devtools.prefs]
+ [devtools.formatters.core]
+ [mranderson047.re-frame.v0v10v2.re-frame.core :as rf]
+ [day8.re-frame.trace.utils.re-com :as rc])
+ (:require-macros [day8.re-frame.trace.utils.macros :as macros]))
+
+(defn render []
+ (let [timing-data-available? @(rf/subscribe [:timing/data-available?])]
+ (if timing-data-available?
+ [rc/v-box
+ :padding "12px 0px"
+ :children [[rc/label :label "Total Epoch Time"]
+ [rc/label :label (str @(rf/subscribe [:timing/total-epoch-time]) "ms")]
+ [rc/label :label "Animation Frames"]
+ [rc/label :label @(rf/subscribe [:timing/animation-frame-count])]
+ [rc/label :label "Event time"]
+ [rc/label :label (str @(rf/subscribe [:timing/event-processing-time]) "ms")]
+ [rc/label :label "Render/Subscription time"]
+ [rc/label :label (str @(rf/subscribe [:timing/render-time]) "ms")]]]
+ [rc/v-box
+ :padding "12px 0px"
+ :children [[:h1 "No timing data available currently."]]])))
diff --git a/src/day8/re_frame/trace/view/traces.cljs b/src/day8/re_frame/trace/view/traces.cljs
index 53259b0..552a3df 100644
--- a/src/day8/re_frame/trace/view/traces.cljs
+++ b/src/day8/re_frame/trace/view/traces.cljs
@@ -3,11 +3,12 @@
[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.re-frame.v0v10v2.re-frame.core :as rf]))
+ [mranderson047.reagent.v0v6v0.reagent.core :as r]
+ [mranderson047.re-frame.v0v10v2.re-frame.core :as rf]
+ [day8.re-frame.trace.utils.re-com :as rc]))
(defn query->fn [query]
(if (= :contains (:filter-type query))
@@ -64,7 +65,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}
@@ -80,22 +82,30 @@
[:td.trace--meta.trace--details-icon
{:on-click #(.log js/console tags)}]]))))))))
-(defn render-trace-panel [traces]
+(defn render [traces]
(let [filter-input (r/atom "")
filter-items (rf/subscribe [:traces/filter-items])
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])
+ current-traces (rf/subscribe [:traces/current-event-traces])
+ show-epoch-traces? (rf/subscribe [:traces/show-epoch-traces?])]
(fn []
(let [toggle-category-fn #(rf/dispatch [:traces/toggle-categories %])
- visible-traces (cond->> @traces
+ traces-to-filter (if @show-epoch-traces?
+ @current-traces
+ @traces)
+ visible-traces (cond->> traces-to-filter
;; Remove cached subscriptions. Could add this back in as a setting later
;; but it's pretty low signal/noise 99% of the time.
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 +113,7 @@
(do
(reset! input-error false)
(add-filter filter-items @filter-input @filter-type))))]
+
[:div.tab-contents
[:div.filter
[:div.filter-control
@@ -119,6 +130,10 @@
[:li.filter-category {:class (when (contains? @categories :re-frame.router/fsm-trigger) "active")
:on-click #(rf/dispatch [:traces/toggle-categories #{:re-frame.router/fsm-trigger :componentWillUnmount}])}
"internals"]]
+ [rc/checkbox
+ :model show-epoch-traces?
+ :on-change #(rf/dispatch [:traces/update-show-epoch-traces? %])
+ :label "Show only traces for this epoch?"]
[:div.filter-fields
[:select {:value @filter-type
:on-change #(reset! filter-type (keyword (.. % -target -value)))}
@@ -154,10 +169,10 @@
:on-click #(rf/dispatch [:traces/reset-filter-items])}
(when (pos? (count @filter-items))
(str (count visible-traces) " of "))
- (str (count @traces))]
+ (str (count @current-traces))]
" traces "
- (when (pos? (count @traces))
- [:span "(" [:button.text-button {:on-click #(do (trace/reset-tracing!) (reset! traces []))} "clear"] ")"])]
+ (when (pos? (count @current-traces))
+ [:span "(" [:button.text-button {:on-click #(do (trace/reset-tracing!) (reset! current-traces []))} "clear"] ")"])]
[:th {:style {:text-align "right"}} "meta"]]
[:tbody (render-traces visible-traces filter-items filter-input trace-detail-expansions)]]]]))))
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 [])
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))
diff --git a/test-resources/app-trace1.edn b/test-resources/app-trace1.edn
new file mode 100644
index 0000000..bde5ecb
--- /dev/null
+++ b/test-resources/app-trace1.edn
@@ -0,0 +1 @@
+[{:id 1, :operation :bootstrap, :op-type :event, :tags {:event [:bootstrap]}, :child-of nil, :start 2897.5700000000006, :duration 36.46499999999969, :end 2934.0400000000004} {:id 2, :operation [:idle :add-event], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :idle, :new-state :scheduled}, :child-of 1, :start 2933.815, :duration 0.125, :end 2933.945} {:id 3, :operation "acme.myapp.main.main", :op-type :render, :tags {:component-path "acme.myapp.main.main", :reaction "rx65", :input-signals ("ra93" "rx64")}, :child-of nil, :start 2974.9950000000003, :duration 1.9349999999999454, :end 2976.935} {:id 4, :operation :boot-state, :op-type :sub/create, :tags {:query-v [:boot-state], :cached? false, :reaction "rx64"}, :child-of 3, :start 2975.0850000000005, :duration 0.5399999999999636, :end 2975.63} {:id 5, :operation :boot-state, :op-type :sub/run, :tags {:query-v [:boot-state], :reaction "rx64", :input-signals (nil nil nil nil nil), :value :booting}, :child-of 3, :start 2975.8650000000002, :duration 0.1000000000003638, :end 2975.9700000000003} {:id 6, :operation "re_com.box.v_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box", :reaction nil, :input-signals nil}, :child-of nil, :start 2977.275, :duration 3.4600000000000364, :end 2980.77} {:id 7, :operation "acme.myapp.main.busy_panel", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel", :reaction "rx67", :input-signals ("ra93" "rx66" "rx66")}, :child-of nil, :start 2981.4800000000005, :duration 1.974999999999909, :end 2983.4600000000005} {:id 8, :operation :progress, :op-type :sub/create, :tags {:query-v [:progress], :cached? false, :reaction "rx66"}, :child-of 7, :start 2981.565, :duration 0.5750000000002728, :end 2982.1600000000003} {:id 9, :operation :progress, :op-type :sub/run, :tags {:query-v [:progress], :reaction "rx66", :input-signals (nil nil nil nil nil), :value {:complete? false, :description "Starting MyApp v0.0.1-SNAPSHOT...", :event :bootstrap}}, :child-of 7, :start 2982.4100000000003, :duration 0.1799999999998363, :end 2982.61} {:id 10, :operation "re_com.modal_panel.modal_panel", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel", :reaction nil, :input-signals nil}, :child-of nil, :start 2983.8250000000003, :duration 1.625, :end 2985.4550000000004} {:id 11, :operation "re_com.box.border", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border", :reaction nil, :input-signals nil}, :child-of nil, :start 2986.145, :duration 1.3350000000000364, :end 2987.48} {:id 12, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of nil, :start 2987.9350000000004, :duration 1.6549999999997453, :end 2989.5950000000003} {:id 13, :operation "re_com.misc.throbber", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber", :reaction nil, :input-signals nil}, :child-of nil, :start 2990.2650000000003, :duration 0.9250000000001819, :end 2991.195} {:id 14, :operation "re_com.box.box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box", :reaction nil, :input-signals nil}, :child-of nil, :start 2991.7250000000004, :duration 1.6950000000001637, :end 2993.425} {:id 15, :operation "reagent1", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil, :input-signals nil}, :child-of nil, :start 2994.03, :duration 0.27999999999974534, :end 2994.3150000000005} {:id 16, :operation "reagent1", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil, :input-signals nil}, :child-of nil, :start 2994.8100000000004, :duration 0.125, :end 2994.94} {:id 17, :operation "reagent1", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil, :input-signals nil}, :child-of nil, :start 2995.4, :duration 0.1000000000003638, :end 2995.505} {:id 18, :operation "reagent1", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil, :input-signals nil}, :child-of nil, :start 2996.0250000000005, :duration 0.0999999999994543, :end 2996.125} {:id 19, :operation "reagent1", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil, :input-signals nil}, :child-of nil, :start 2996.515, :duration 0.08500000000049113, :end 2996.6150000000002} {:id 20, :operation "reagent1", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil, :input-signals nil}, :child-of nil, :start 2996.9400000000005, :duration 0.08499999999958163, :end 2997.03} {:id 21, :operation "reagent1", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil, :input-signals nil}, :child-of nil, :start 2997.52, :duration 0.09000000000014552, :end 2997.61} {:id 22, :operation "reagent1", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil, :input-signals nil}, :child-of nil, :start 2998.5750000000003, :duration 0.10500000000001819, :end 2998.6800000000003} {:id 23, :operation "re_com.box.gap", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.box.gap", :reaction nil, :input-signals nil}, :child-of nil, :start 2999.2150000000006, :duration 1.0799999999999272, :end 3000.3} {:id 24, :operation "re_com.text.label", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.text.label", :reaction nil, :input-signals nil}, :child-of nil, :start 3000.9, :duration 0.6400000000003274, :end 3001.545} {:id 25, :operation "re_com.box.box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.text.label > re_com.box.box", :reaction nil, :input-signals nil}, :child-of nil, :start 3001.775, :duration 0.8400000000001455, :end 3002.6200000000003} {:id 26, :operation "acme.myapp.main.app_notification", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_notification", :reaction "rx69", :input-signals ("ra93" "rx68")}, :child-of nil, :start 3003.3800000000006, :duration 0.9799999999995634, :end 3004.3650000000002} {:id 27, :operation :app-notification, :op-type :sub/create, :tags {:query-v [:app-notification], :cached? false, :reaction "rx68"}, :child-of 26, :start 3003.4300000000003, :duration 0.2899999999999636, :end 3003.7250000000004} {:id 28, :operation :app-notification, :op-type :sub/run, :tags {:query-v [:app-notification], :reaction "rx68", :input-signals (nil nil nil nil nil), :value nil}, :child-of 26, :start 3003.885, :duration 0.09500000000025466, :end 3003.985} {:id 29, :operation nil, :op-type :raf, :tags nil, :child-of nil, :start 3025.9300000000003, :duration 5.739999999999782, :end 3031.67} {:id 30, :operation "acme.myapp.main.main", :op-type :render, :tags {:component-path "acme.myapp.main.main", :reaction "rx65", :input-signals ("ra93" "rx64")}, :child-of 29, :start 3026.2700000000004, :duration 0.45499999999992724, :end 3026.73} {:id 31, :operation :boot-state, :op-type :sub/create, :tags {:query-v [:boot-state], :cached? true, :reaction "rx64"}, :child-of 30, :start 3026.3450000000003, :duration 0.13500000000021828, :end 3026.485} {:id 32, :operation "acme.myapp.main.busy_panel", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel", :reaction "rx67", :input-signals ("rx66" "rx66")}, :child-of 29, :start 3027.07, :duration 0.2699999999999818, :end 3027.34} {:id 33, :operation nil, :op-type :raf-end, :tags nil, :child-of 29, :start 3027.51, :duration 0.005000000000109139, :end 3027.5150000000003} {:id 34, :operation nil, :op-type :reagent/quiescent, :tags nil, :child-of 29, :start 3031.6050000000005, :duration 0, :end 3031.61} {:id 35, :operation [:scheduled :run-queue], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :scheduled, :new-state :running}, :child-of nil, :start 3033.5650000000005, :duration 1.1700000000000728, :end 3034.7400000000002} {:id 36, :operation :acme.myapp.events/boot-flow, :op-type :event, :tags {:event [:acme.myapp.events/boot-flow :setup]}, :child-of 35, :start 3033.7850000000003, :duration 0.6649999999999636, :end 3034.46} {:id 37, :operation [:running :add-event], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :running, :new-state :running}, :child-of 36, :start 3034.19, :duration 0.07000000000016371, :end 3034.2700000000004} {:id 38, :operation [:running :finish-run], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :running, :new-state :scheduled}, :child-of 35, :start 3034.55, :duration 0.13000000000010914, :end 3034.685} {:id 39, :operation [:scheduled :run-queue], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :scheduled, :new-state :running}, :child-of nil, :start 3034.815, :duration 3.269999999999982, :end 3038.0900000000006} {:id 40, :operation :acme.myapp.events/init-db, :op-type :event, :tags {:event [:acme.myapp.events/init-db]}, :child-of 39, :start 3034.9150000000004, :duration 2.7400000000002365, :end 3037.6600000000003} {:id 41, :operation [:running :add-event], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :running, :new-state :running}, :child-of 39, :start 3037.8, :duration 0.08500000000003638, :end 3037.885} {:id 42, :operation [:running :finish-run], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :running, :new-state :scheduled}, :child-of 39, :start 3037.9600000000005, :duration 0.09499999999979991, :end 3038.0550000000003} {:id 43, :operation [:scheduled :run-queue], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :scheduled, :new-state :running}, :child-of nil, :start 3038.135, :duration 1.0299999999997453, :end 3039.1700000000005} {:id 44, :operation :acme.myapp.events/boot-flow, :op-type :event, :tags {:event [:acme.myapp.events/boot-flow [:acme.myapp.events/init-db]]}, :child-of 43, :start 3038.205, :duration 0.7950000000000728, :end 3039} {:id 45, :operation [:running :add-event], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :running, :new-state :running}, :child-of 44, :start 3038.55, :duration 0.04500000000007276, :end 3038.5950000000003} {:id 46, :operation [:running :add-event], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :running, :new-state :running}, :child-of 44, :start 3038.7100000000005, :duration 0.0799999999994725, :end 3038.7950000000005} {:id 47, :operation [:running :finish-run], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :running, :new-state :scheduled}, :child-of 43, :start 3039.045, :duration 0.08000000000038199, :end 3039.13} {:id 48, :operation [:scheduled :run-queue], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :scheduled, :new-state :running}, :child-of nil, :start 3039.2300000000005, :duration 18.95999999999958, :end 3058.1950000000006} {:id 49, :operation :acme.myapp.events/start-intercom, :op-type :event, :tags {:event [:acme.myapp.events/start-intercom]}, :child-of 48, :start 3039.3050000000003, :duration 4.4050000000002, :end 3043.7100000000005} {:id 50, :operation :acme.myapp.events/success-bootstrap, :op-type :event, :tags {:event [:acme.myapp.events/success-bootstrap]}, :child-of 48, :start 3043.7900000000004, :duration 14.004999999999654, :end 3057.795} {:id 51, :operation [:running :finish-run], :op-type :re-frame.router/fsm-trigger, :tags {:current-state :running, :new-state :idle}, :child-of 48, :start 3058.07, :duration 0.08000000000038199, :end 3058.1500000000005} {:id 52, :operation nil, :op-type :raf, :tags nil, :child-of nil, :start 3153.215, :duration 93.58500000000004, :end 3246.83} {:id 53, :operation :boot-state, :op-type :sub/run, :tags {:query-v [:boot-state], :reaction "rx64", :input-signals (nil nil nil nil nil), :value :boot-success}, :child-of 52, :start 3153.2950000000005, :duration 0.24999999999954525, :end 3153.56} {:id 54, :operation :progress, :op-type :sub/run, :tags {:query-v [:progress], :reaction "rx66", :input-signals (nil nil nil nil nil), :value {:complete? true, :description nil, :event nil}}, :child-of 52, :start 3153.8350000000005, :duration 1.3299999999999272, :end 3155.175} {:id 55, :operation :app-notification, :op-type :sub/run, :tags {:query-v [:app-notification], :reaction "rx68", :input-signals (nil nil nil nil nil), :value nil}, :child-of 52, :start 3155.3500000000004, :duration 0.09499999999979991, :end 3155.4550000000004} {:id 56, :operation "acme.myapp.main.main", :op-type :render, :tags {:component-path "acme.myapp.main.main", :reaction "rx65", :input-signals ("ra93" "rx64")}, :child-of 52, :start 3155.8, :duration 0.4050000000002001, :end 3156.2150000000006} {:id 57, :operation :boot-state, :op-type :sub/create, :tags {:query-v [:boot-state], :cached? true, :reaction "rx64"}, :child-of 56, :start 3155.85, :duration 0.11500000000023647, :end 3155.9700000000003} {:id 58, :operation "re_com.box.v_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3156.4400000000005, :duration 6.454999999999927, :end 3162.9} {:id 59, :operation "acme.myapp.main.app_content", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content", :reaction "rx77", :input-signals ("ra93" "ra93" "ra93" "ra93" "ra93" "ra93" "rx71" "rx71" "rx73" "ra94" "rx70")}, :child-of 52, :start 3163.4050000000007, :duration 4.0549999999993815, :end 3167.46} {:id 60, :operation :navigation/page, :op-type :sub/create, :tags {:query-v [:navigation/page], :cached? false, :reaction "rx70"}, :child-of 59, :start 3163.4800000000005, :duration 0.2849999999998545, :end 3163.7700000000004} {:id 61, :operation :navigation/visible?, :op-type :sub/create, :tags {:query-v [:navigation/visible?], :cached? false, :reaction "rx71"}, :child-of 59, :start 3163.905, :duration 0.21000000000003638, :end 3164.1150000000002} {:id 62, :operation :system-name, :op-type :sub/create, :tags {:query-v [:system-name], :cached? false, :reaction "rx73"}, :child-of 59, :start 3164.225, :duration 0.41000000000030923, :end 3164.635} {:id 63, :operation :site-config, :op-type :sub/create, :tags {:query-v [:site-config], :cached? false, :reaction "rx72"}, :child-of 62, :start 3164.3200000000006, :duration 0.11499999999932697, :end 3164.435} {:id 64, :operation :undos?, :op-type :sub/create, :tags {:query-v [:undos?], :cached? false, :reaction "rx74"}, :child-of 59, :start 3164.7450000000003, :duration 0.169999999999618, :end 3164.915} {:id 65, :operation :redos?, :op-type :sub/create, :tags {:query-v [:redos?], :cached? false, :reaction "rx75"}, :child-of 59, :start 3165.005, :duration 0.5000000000004547, :end 3165.51} {:id 66, :operation :navigation/visible?, :op-type :sub/run, :tags {:query-v [:navigation/visible?], :reaction "rx71", :input-signals (nil nil), :value true}, :child-of 59, :start 3165.71, :duration 1.0850000000004911, :end 3166.8} {:id 67, :operation :navigation/page, :op-type :sub/create, :tags {:query-v [:navigation/page], :cached? true, :reaction "rx70"}, :child-of 66, :start 3165.8250000000003, :duration 0.05999999999994543, :end 3165.885} {:id 68, :operation :navigation/page, :op-type :sub/run, :tags {:query-v [:navigation/page], :reaction "rx70", :input-signals (nil nil nil nil nil), :value :main/define}, :child-of 66, :start 3166.03, :duration 0.04500000000007276, :end 3166.0750000000003} {:id 69, :operation :section1/editing-sav, :op-type :sub/create, :tags {:query-v [:section1/editing-sav], :cached? false, :reaction "rx76"}, :child-of 66, :start 3166.29, :duration 0.13500000000021828, :end 3166.425} {:id 70, :operation :section1/editing-sav, :op-type :sub/run, :tags {:query-v [:section1/editing-sav], :reaction "rx76", :input-signals (nil nil nil nil nil), :value nil}, :child-of 66, :start 3166.5550000000003, :duration 0.04500000000007276, :end 3166.6000000000004} {:id 71, :operation :system-name, :op-type :sub/run, :tags {:query-v [:system-name], :reaction "rx73", :input-signals (nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil), :value "MyApp"}, :child-of 59, :start 3166.9300000000003, :duration 0.22499999999990905, :end 3167.1650000000004} {:id 72, :operation :site-config, :op-type :sub/run, :tags {:query-v [:site-config], :reaction "rx72", :input-signals (nil nil nil nil nil), :value #object[acme.system-configuration.Configuration]}, :child-of 71, :start 3166.9500000000003, :duration 0.03500000000030923, :end 3166.9900000000002} {:id 73, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3167.7000000000003, :duration 1.2399999999997817, :end 3168.94} {:id 74, :operation "re_com.box.scroller", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller", :reaction nil, :input-signals nil}, :child-of 52, :start 3169.315, :duration 0.9950000000003456, :end 3170.3150000000005} {:id 75, :operation "re_com.box.v_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3170.6150000000002, :duration 1.1599999999998545, :end 3171.78} {:id 76, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3172.105, :duration 1.1650000000004184, :end 3173.2700000000004} {:id 77, :operation "re_com.box.gap", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > re_com.box.h_box > re_com.box.gap", :reaction nil, :input-signals nil}, :child-of 52, :start 3173.7400000000002, :duration 0.4050000000002001, :end 3174.1450000000004} {:id 78, :operation "re_com.text.label", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > re_com.box.h_box > re_com.text.label", :reaction nil, :input-signals nil}, :child-of 52, :start 3176.4500000000003, :duration 0.5599999999999454, :end 3177.01} {:id 79, :operation "re_com.box.box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > re_com.box.h_box > re_com.text.label > re_com.box.box", :reaction nil, :input-signals nil}, :child-of 52, :start 3177.3100000000004, :duration 0.900000000000091, :end 3178.2100000000005} {:id 80, :operation "re_com.box.gap", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > re_com.box.gap", :reaction nil, :input-signals nil}, :child-of 52, :start 3178.7750000000005, :duration 0.5099999999997635, :end 3179.2900000000004} {:id 81, :operation "acme.myapp.main.sidepanel_tab_list", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list", :reaction "rx78", :input-signals ("ra93" "rx72" "ra93")}, :child-of 52, :start 3179.755, :duration 1.0150000000003274, :end 3180.7700000000004} {:id 82, :operation :site-config, :op-type :sub/create, :tags {:query-v [:site-config], :cached? true, :reaction "rx72"}, :child-of 81, :start 3179.8100000000004, :duration 0.125, :end 3179.9350000000004} {:id 83, :operation :navigation/page, :op-type :sub/create, :tags {:query-v [:navigation/page], :cached? true, :reaction "rx70"}, :child-of 81, :start 3180.1450000000004, :duration 0.0749999999998181, :end 3180.225} {:id 84, :operation "acme.myapp.components.nav_tabs.tabs_v", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list > acme.myapp.components.nav_tabs.tabs_v", :reaction "rx79", :input-signals ("rx70" "ra95" "ra95" "ra95" "ra95")}, :child-of 52, :start 3181.105, :duration 1.3350000000000364, :end 3182.44} {:id 85, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list > acme.myapp.components.nav_tabs.tabs_v > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3183.0900000000006, :duration 1.0199999999999818, :end 3184.1150000000002} {:id 86, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list > acme.myapp.components.nav_tabs.tabs_v > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3184.6800000000003, :duration 0.7299999999995634, :end 3185.41} {:id 87, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list > acme.myapp.components.nav_tabs.tabs_v > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3185.9050000000007, :duration 0.6599999999998545, :end 3186.57} {:id 88, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list > acme.myapp.components.nav_tabs.tabs_v > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3187.01, :duration 0.6849999999999454, :end 3187.7000000000003} {:id 89, :operation "re_com.box.gap", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > re_com.box.gap", :reaction nil, :input-signals nil}, :child-of 52, :start 3188.0750000000003, :duration 0.30500000000029104, :end 3188.385} {:id 90, :operation "acme.myapp.main.system_version", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.system_version", :reaction "rx80", :input-signals ("ra93" "rx73" "ra93" "rx72")}, :child-of 52, :start 3188.635, :duration 0.44499999999970896, :end 3189.0850000000005} {:id 91, :operation :system-name, :op-type :sub/create, :tags {:query-v [:system-name], :cached? true, :reaction "rx73"}, :child-of 90, :start 3188.675, :duration 0.05999999999994543, :end 3188.7400000000002} {:id 92, :operation :site-config, :op-type :sub/create, :tags {:query-v [:site-config], :cached? true, :reaction "rx72"}, :child-of 90, :start 3188.84, :duration 0.04500000000007276, :end 3188.8900000000003} {:id 93, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.system_version > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3189.2650000000003, :duration 0.724999999999909, :end 3189.9950000000003} {:id 94, :operation "re_com.text.label", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.system_version > re_com.box.h_box > re_com.text.label", :reaction nil, :input-signals nil}, :child-of 52, :start 3190.26, :duration 0.36999999999989086, :end 3190.63} {:id 95, :operation "re_com.box.box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.system_version > re_com.box.h_box > re_com.text.label > re_com.box.box", :reaction nil, :input-signals nil}, :child-of 52, :start 3190.8450000000003, :duration 0.9299999999998363, :end 3191.8} {:id 96, :operation "re_com.box.scroller", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller", :reaction nil, :input-signals nil}, :child-of 52, :start 3192.2200000000003, :duration 0.7799999999997453, :end 3193} {:id 97, :operation "re_com.box.v_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3193.2500000000005, :duration 0.7600000000002183, :end 3194.0100000000007} {:id 98, :operation "re_com.box.box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box", :reaction nil, :input-signals nil}, :child-of 52, :start 3196.15, :duration 0.9700000000002547, :end 3197.1200000000003} {:id 99, :operation "acme.myapp.panels.section1.view.panel", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel", :reaction "rx85", :input-signals ("ra93" "ra93" "ra93" "ra93" "ra93" "ra93" "rx76" "rx76" "rx76" "rx76" "rx81" "rx81" "rx76" "rx82" "rx81")}, :child-of 52, :start 3197.4600000000005, :duration 1.9049999999997453, :end 3199.3650000000002} {:id 100, :operation :section1/section1, :op-type :sub/create, :tags {:query-v [:section1/section1], :cached? false, :reaction "rx81"}, :child-of 99, :start 3197.4900000000002, :duration 0.14500000000043656, :end 3197.6350000000007} {:id 101, :operation :section1/app-features, :op-type :sub/create, :tags {:query-v [:section1/app-features], :cached? false, :reaction "rx82"}, :child-of 99, :start 3197.7450000000003, :duration 0.23500000000012733, :end 3197.985} {:id 102, :operation :section1/section1, :op-type :sub/create, :tags {:query-v [:section1/section1], :cached? true, :reaction "rx81"}, :child-of 101, :start 3197.8000000000006, :duration 0.03999999999950887, :end 3197.84} {:id 103, :operation :section1/editing-sav, :op-type :sub/create, :tags {:query-v [:section1/editing-sav], :cached? true, :reaction "rx76"}, :child-of 99, :start 3198.0600000000004, :duration 0.03999999999996362, :end 3198.1050000000005} {:id 104, :operation :section1/dirty?, :op-type :sub/create, :tags {:query-v [:section1/dirty?], :cached? false, :reaction "rx83"}, :child-of 99, :start 3198.1800000000003, :duration 0.09000000000014552, :end 3198.2700000000004} {:id 105, :operation :section1/file-name, :op-type :sub/create, :tags {:query-v [:section1/file-name], :cached? false, :reaction "rx84"}, :child-of 99, :start 3198.3450000000003, :duration 0.0749999999998181, :end 3198.42} {:id 106, :operation :section1/section1, :op-type :sub/run, :tags {:query-v [:section1/section1], :reaction "rx81", :input-signals (nil nil nil nil nil), :value nil}, :child-of 99, :start 3198.5950000000003, :duration 0.04500000000007276, :end 3198.6450000000004} {:id 107, :operation :section1/app-features, :op-type :sub/run, :tags {:query-v [:section1/app-features], :reaction "rx82", :input-signals (), :value nil}, :child-of 99, :start 3198.9150000000004, :duration 0.10500000000001819, :end 3199.025} {:id 108, :operation "re_com.box.v_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3199.65, :duration 1.2950000000000728, :end 3200.9500000000003} {:id 109, :operation "acme.myapp.app_ux_common.panel_title", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.app_ux_common.panel_title", :reaction nil, :input-signals nil}, :child-of 52, :start 3201.38, :duration 0.14000000000032742, :end 3201.525} {:id 110, :operation "re_com.box.box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.app_ux_common.panel_title > re_com.box.box", :reaction nil, :input-signals nil}, :child-of 52, :start 3201.7750000000005, :duration 0.9099999999998545, :end 3202.6850000000004} {:id 111, :operation "re_com.box.gap", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.gap", :reaction nil, :input-signals nil}, :child-of 52, :start 3203.1600000000003, :duration 0.3599999999996726, :end 3203.52} {:id 112, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3203.8, :duration 0.9450000000001637, :end 3204.75} {:id 113, :operation "acme.myapp.panels.section1.view.unsaved_changes_popup", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup", :reaction nil, :input-signals nil}, :child-of 52, :start 3205.0950000000003, :duration 0.27999999999974534, :end 3205.375} {:id 114, :operation "re_com.popover.popover_anchor_wrapper", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper", :reaction "rx86", :input-signals ("ra96")}, :child-of 52, :start 3205.6400000000003, :duration 0.875, :end 3206.5200000000004} {:id 115, :operation "popover-anchor-wrapper", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper", :reaction "rx87", :input-signals ("rx97" "ra96" "ra98" "ra99")}, :child-of 52, :start 3206.8050000000003, :duration 1.2549999999996544, :end 3208.0650000000005} {:id 116, :operation "acme.apps_lib.components.icon_button.icon_button", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > acme.apps_lib.components.icon_button.icon_button", :reaction nil, :input-signals nil}, :child-of 52, :start 3208.465, :duration 0.2699999999999818, :end 3208.735} {:id 117, :operation "re_com.buttons.button", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > acme.apps_lib.components.icon_button.icon_button > re_com.buttons.button", :reaction nil, :input-signals nil}, :child-of 52, :start 3209.0250000000005, :duration 1.2550000000001091, :end 3210.2800000000007} {:id 118, :operation "re_com.box.box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > acme.apps_lib.components.icon_button.icon_button > re_com.buttons.button > re_com.box.box", :reaction nil, :input-signals nil}, :child-of 52, :start 3210.5800000000004, :duration 2.2299999999995634, :end 3212.8150000000005} {:id 119, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > acme.apps_lib.components.icon_button.icon_button > re_com.buttons.button > re_com.box.box > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3213.4600000000005, :duration 1.074999999999818, :end 3214.5400000000004} {:id 120, :operation "re_com.box.gap", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > acme.apps_lib.components.icon_button.icon_button > re_com.buttons.button > re_com.box.box > re_com.box.h_box > re_com.box.gap", :reaction nil, :input-signals nil}, :child-of 52, :start 3215.4100000000003, :duration 0.3949999999999818, :end 3215.8050000000003} {:id 121, :operation "acme.apps_lib.components.load_save.hidden_file_input", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.apps_lib.components.load_save.hidden_file_input", :reaction nil, :input-signals nil}, :child-of 52, :start 3216.225, :duration 0.6449999999999818, :end 3216.8750000000005} {:id 122, :operation "ReagentInput", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.apps_lib.components.load_save.hidden_file_input > ReagentInput", :reaction nil, :input-signals nil}, :child-of 52, :start 3217.135, :duration 0.07000000000016371, :end 3217.2050000000004} {:id 123, :operation "re_com.box.gap", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > re_com.box.gap", :reaction nil, :input-signals nil}, :child-of 52, :start 3217.655, :duration 0.31500000000005457, :end 3217.9700000000003} {:id 124, :operation "acme.myapp.panels.section1.view.unsaved_changes_popup", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup", :reaction nil, :input-signals nil}, :child-of 52, :start 3218.26, :duration 0.15000000000009095, :end 3218.4100000000003} {:id 125, :operation "re_com.popover.popover_anchor_wrapper", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper", :reaction "rx88", :input-signals ("ra100")}, :child-of 52, :start 3218.65, :duration 0.6849999999999454, :end 3219.335} {:id 126, :operation "popover-anchor-wrapper", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper", :reaction "rx89", :input-signals ("rx101" "ra100" "ra102" "ra103")}, :child-of 52, :start 3219.5950000000003, :duration 0.7550000000001091, :end 3220.3500000000004} {:id 127, :operation "acme.apps_lib.components.icon_button.icon_button", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > acme.apps_lib.components.icon_button.icon_button", :reaction nil, :input-signals nil}, :child-of 52, :start 3220.7450000000003, :duration 0.11999999999989086, :end 3220.8700000000003} {:id 128, :operation "re_com.buttons.button", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > acme.apps_lib.components.icon_button.icon_button > re_com.buttons.button", :reaction nil, :input-signals nil}, :child-of 52, :start 3221.255, :duration 0.9200000000005275, :end 3222.1800000000003} {:id 129, :operation "re_com.box.box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > acme.apps_lib.components.icon_button.icon_button > re_com.buttons.button > re_com.box.box", :reaction nil, :input-signals nil}, :child-of 52, :start 3222.92, :duration 0.6849999999999454, :end 3223.605} {:id 130, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > acme.apps_lib.components.icon_button.icon_button > re_com.buttons.button > re_com.box.box > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3224.015, :duration 0.635000000000673, :end 3224.655} {:id 131, :operation "re_com.box.gap", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > acme.myapp.panels.section1.view.unsaved_changes_popup > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > acme.apps_lib.components.icon_button.icon_button > re_com.buttons.button > re_com.box.box > re_com.box.h_box > re_com.box.gap", :reaction nil, :input-signals nil}, :child-of 52, :start 3225.1250000000005, :duration 0.3649999999997817, :end 3225.4900000000002} {:id 132, :operation "re_com.box.gap", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > re_com.box.h_box > re_com.box.gap", :reaction nil, :input-signals nil}, :child-of 52, :start 3225.8450000000003, :duration 0.330000000000382, :end 3226.1800000000003} {:id 133, :operation "acme.myapp.panels.section1.welcome.panel", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel", :reaction nil, :input-signals nil}, :child-of 52, :start 3226.525, :duration 0.11000000000012733, :end 3226.6400000000003} {:id 134, :operation "re_com.box.v_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel > re_com.box.v_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3226.8650000000002, :duration 0.8049999999998363, :end 3227.67} {:id 135, :operation "re_com.text.title", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel > re_com.box.v_box > re_com.text.title", :reaction nil, :input-signals nil}, :child-of 52, :start 3228.0050000000006, :duration 0.3649999999997817, :end 3228.3700000000003} {:id 136, :operation "re_com.box.v_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel > re_com.box.v_box > re_com.text.title > re_com.box.v_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3228.5800000000004, :duration 0.7049999999999272, :end 3229.2900000000004} {:id 137, :operation "re_com.box.gap", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel > re_com.box.v_box > re_com.box.gap", :reaction nil, :input-signals nil}, :child-of 52, :start 3229.775, :duration 0.4250000000001819, :end 3230.2050000000004} {:id 138, :operation "re_com.box.v_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel > re_com.box.v_box > re_com.box.v_box", :reaction nil, :input-signals nil}, :child-of 52, :start 3230.5050000000006, :duration 1.544999999999618, :end 3232.0550000000003} {:id 139, :operation "re_com.popover.popover_anchor_wrapper", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel > re_com.box.v_box > re_com.box.v_box > re_com.popover.popover_anchor_wrapper", :reaction "rx90", :input-signals ("ra104")}, :child-of 52, :start 3232.76, :duration 0.6300000000001091, :end 3233.3950000000004} {:id 140, :operation "popover-anchor-wrapper", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel > re_com.box.v_box > re_com.box.v_box > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper", :reaction "rx91", :input-signals ("rx105" "ra104" "ra106" "ra107")}, :child-of 52, :start 3233.78, :duration 1.199999999999818, :end 3234.9850000000006} {:id 141, :operation "re_com.buttons.hyperlink", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel > re_com.box.v_box > re_com.box.v_box > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > re_com.buttons.hyperlink", :reaction nil, :input-signals nil}, :child-of 52, :start 3235.38, :duration 0.8899999999998727, :end 3236.2750000000005} {:id 142, :operation "re_com.box.box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel > re_com.box.v_box > re_com.box.v_box > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > re_com.buttons.hyperlink > re_com.box.box", :reaction nil, :input-signals nil}, :child-of 52, :start 3236.5350000000003, :duration 0.5650000000000546, :end 3237.1000000000004} {:id 143, :operation "re_com.box.box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > > > re_com.box.box > acme.myapp.panels.section1.view.panel > re_com.box.v_box > acme.myapp.panels.section1.welcome.panel > re_com.box.v_box > re_com.box.v_box > re_com.popover.popover_anchor_wrapper > popover-anchor-wrapper > re_com.buttons.hyperlink > re_com.box.box > re_com.box.box", :reaction nil, :input-signals nil}, :child-of 52, :start 3237.4300000000003, :duration 0.5350000000003092, :end 3237.9700000000003} {:id 144, :operation "acme.myapp.main.busy_panel", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel", :reaction "rx67", :input-signals ("rx66")}, :child-of 52, :start 3239.37, :duration 0.0950000000007094, :end 3239.4700000000003} {:id 145, :operation "re_com.modal_panel.modal_panel", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel", :reaction nil}, :child-of 52, :start 3239.64, :duration 0.005000000000563887, :end 3239.6450000000004} {:id 146, :operation "re_com.box.border", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border", :reaction nil}, :child-of 52, :start 3239.84, :duration 0.005000000000109139, :end 3239.8550000000005} {:id 147, :operation "re_com.box.h_box", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box", :reaction nil}, :child-of 52, :start 3239.9950000000003, :duration 0, :end 3240} {:id 148, :operation "re_com.misc.throbber", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber", :reaction nil}, :child-of 52, :start 3240.135, :duration 0, :end 3240.135} {:id 149, :operation "re_com.box.box", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box", :reaction nil}, :child-of 52, :start 3240.2650000000003, :duration 0.004999999999654392, :end 3240.27} {:id 150, :operation "reagent1", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil}, :child-of 52, :start 3240.42, :duration 0.005000000000109139, :end 3240.425} {:id 151, :operation "reagent1", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil}, :child-of 52, :start 3240.5800000000004, :duration 0.005000000000109139, :end 3240.5850000000005} {:id 152, :operation "reagent1", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil}, :child-of 52, :start 3240.73, :duration 0, :end 3240.73} {:id 153, :operation "reagent1", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil}, :child-of 52, :start 3240.8700000000003, :duration 0, :end 3240.8700000000003} {:id 154, :operation "reagent1", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil}, :child-of 52, :start 3241.0050000000006, :duration 0.004999999999654392, :end 3241.01} {:id 155, :operation "reagent1", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil}, :child-of 52, :start 3241.1550000000007, :duration 0, :end 3241.1600000000003} {:id 156, :operation "reagent1", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil}, :child-of 52, :start 3241.2950000000005, :duration 0, :end 3241.3} {:id 157, :operation "reagent1", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.misc.throbber > re_com.box.box > reagent1", :reaction nil}, :child-of 52, :start 3241.445, :duration 0, :end 3241.445} {:id 158, :operation "re_com.box.gap", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.box.gap", :reaction nil}, :child-of 52, :start 3241.6050000000005, :duration 0, :end 3241.6050000000005} {:id 159, :operation "re_com.text.label", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.text.label", :reaction nil}, :child-of 52, :start 3241.7400000000002, :duration 0, :end 3241.7400000000002} {:id 160, :operation "re_com.box.box", :op-type :componentWillUnmount, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.busy_panel > re_com.modal_panel.modal_panel > re_com.box.border > re_com.box.h_box > re_com.text.label > re_com.box.box", :reaction nil}, :child-of 52, :start 3241.8700000000003, :duration 0, :end 3241.8700000000003} {:id 161, :operation nil, :op-type :raf-end, :tags nil, :child-of 52, :start 3242.485, :duration 0.005000000000109139, :end 3242.5} {:id 162, :operation nil, :op-type :raf, :tags nil, :child-of nil, :start 3270.0850000000005, :duration 13.139999999999873, :end 3283.23} {:id 163, :operation :navigation/visible?, :op-type :sub/run, :tags {:query-v [:navigation/visible?], :reaction "rx71", :input-signals (nil nil), :value true}, :child-of 162, :start 3270.1200000000003, :duration 0.13999999999987267, :end 3270.2650000000003} {:id 164, :operation "acme.myapp.main.main", :op-type :render, :tags {:component-path "acme.myapp.main.main", :reaction "rx65", :input-signals ("ra93" "rx64")}, :child-of 162, :start 3270.635, :duration 0.2899999999999636, :end 3270.925} {:id 165, :operation :boot-state, :op-type :sub/create, :tags {:query-v [:boot-state], :cached? true, :reaction "rx64"}, :child-of 164, :start 3270.6600000000003, :duration 0.07999999999992724, :end 3270.7400000000002} {:id 166, :operation "acme.myapp.main.app_notification", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_notification", :reaction "rx69", :input-signals ("ra93" "rx68")}, :child-of 162, :start 3271.23, :duration 0.19000000000050932, :end 3271.425} {:id 167, :operation :app-notification, :op-type :sub/create, :tags {:query-v [:app-notification], :cached? true, :reaction "rx68"}, :child-of 166, :start 3271.2550000000006, :duration 0.06499999999959982, :end 3271.3250000000003} {:id 168, :operation "acme.myapp.main.app_content", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content", :reaction "rx77", :input-signals ("rx71" "rx71" "rx73" "ra94" "rx70")}, :child-of 162, :start 3271.6300000000006, :duration 1.1899999999995998, :end 3272.8250000000003} {:id 169, :operation "acme.myapp.main.sidepanel_tab_list", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list", :reaction "rx78", :input-signals ("ra93" "rx72" "ra93")}, :child-of 162, :start 3273.13, :duration 0.34999999999990905, :end 3273.4850000000006} {:id 170, :operation :site-config, :op-type :sub/create, :tags {:query-v [:site-config], :cached? true, :reaction "rx72"}, :child-of 169, :start 3273.1600000000003, :duration 0.06499999999959982, :end 3273.225} {:id 171, :operation :navigation/page, :op-type :sub/create, :tags {:query-v [:navigation/page], :cached? true, :reaction "rx70"}, :child-of 169, :start 3273.315, :duration 0.04000000000041837, :end 3273.3550000000005} {:id 172, :operation "acme.myapp.components.nav_tabs.tabs_v", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list > acme.myapp.components.nav_tabs.tabs_v", :reaction "rx79", :input-signals ("rx70" "ra95" "ra95" "ra95" "ra95")}, :child-of 162, :start 3273.675, :duration 0.599999999999909, :end 3274.28} {:id 173, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list > acme.myapp.components.nav_tabs.tabs_v > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 162, :start 3274.65, :duration 0.6200000000003456, :end 3275.275} {:id 174, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list > acme.myapp.components.nav_tabs.tabs_v > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 162, :start 3275.635, :duration 0.5900000000001455, :end 3276.2250000000004} {:id 175, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list > acme.myapp.components.nav_tabs.tabs_v > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 162, :start 3276.5700000000006, :duration 0.5649999999995998, :end 3277.135} {:id 176, :operation "re_com.box.h_box", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.sidepanel_tab_list > acme.myapp.components.nav_tabs.tabs_v > re_com.box.h_box", :reaction nil, :input-signals nil}, :child-of 162, :start 3277.525, :duration 0.7199999999997999, :end 3278.2500000000005} {:id 177, :operation "acme.myapp.main.system_version", :op-type :render, :tags {:component-path "acme.myapp.main.main > re_com.box.v_box > acme.myapp.main.app_content > re_com.box.h_box > re_com.box.scroller > re_com.box.v_box > acme.myapp.main.system_version", :reaction "rx80", :input-signals ("ra93" "rx73" "ra93" "rx72")}, :child-of 162, :start 3278.6700000000005, :duration 0.32999999999992724, :end 3279.005} {:id 178, :operation :system-name, :op-type :sub/create, :tags {:query-v [:system-name], :cached? true, :reaction "rx73"}, :child-of 177, :start 3278.7150000000006, :duration 0.04999999999972715, :end 3278.77} {:id 179, :operation :site-config, :op-type :sub/create, :tags {:query-v [:site-config], :cached? true, :reaction "rx72"}, :child-of 177, :start 3278.84, :duration 0.04000000000041837, :end 3278.885} {:id 180, :operation nil, :op-type :raf-end, :tags nil, :child-of 162, :start 3279.1050000000005, :duration 0, :end 3279.1050000000005} {:id 181, :operation nil, :op-type :reagent/quiescent, :tags nil, :child-of 162, :start 3283.125, :duration 0.010000000000218279, :end 3283.155}]
diff --git a/test/day8/re_frame/trace/graph_test.clj b/test/day8/re_frame/trace/graph_test.clj
deleted file mode 100644
index 00d78be..0000000
--- a/test/day8/re_frame/trace/graph_test.clj
+++ /dev/null
@@ -1,53 +0,0 @@
-(ns day8.re-frame.trace.graph-test
- (:require [day8.re-frame.trace.graph :as graph]
- [clojure.test :refer :all]))
-
-(def t1
- '({:id 1, :operation :initialise-db, :type :event, :tags {:event [:initialise-db]}, :child-of nil}
- {:id 2, :operation "todomvc.core.wrapper", :type :render, :tags {:component-path "todomvc.core.wrapper", :reaction "rx2", :input-signals ("ra18")}, :child-of nil}
- {:id 5, :operation :sorted-todos, :type :sub/create, :tags {:query-v [:sorted-todos], :cached? false, :reaction "rx3"}, :child-of 4}
- {:id 4, :operation :todos, :type :sub/create, :tags {:query-v [:todos], :cached? false, :reaction "rx4"}, :child-of 3}
- {:id 7, :operation :sorted-todos, :type :sub/run, :tags {:query-v [:sorted-todos], :reaction "rx3", :input-signals ["ra5"]}, :child-of 6}
- {:id 6, :operation :todos, :type :sub/run, :tags {:query-v [:todos], :reaction "rx4", :input-signals ["rx3"]}, :child-of 3}
- {:id 3, :operation "todomvc.views.todo_app", :type :render, :tags {:component-path "todomvc.core.wrapper > todomvc.views.todo_app", :reaction "rx6", :input-signals ("rx4")}, :child-of nil}
- {:id 8, :operation "todomvc.views.task_entry", :type :render, :tags {:component-path "todomvc.core.wrapper > todomvc.views.todo_app > todomvc.views.task_entry", :reaction nil, :input-signals nil}, :child-of nil}
- {:id 9, :operation "todomvc.views.todo_input", :type :render, :tags {:component-path "todomvc.core.wrapper > todomvc.views.todo_app > todomvc.views.task_entry > todomvc.views.todo_input", :reaction "rx7", :input-signals ("ra19")}, :child-of nil}
- {:id 10, :operation "ReagentInput", :type :render, :tags {:component-path "todomvc.core.wrapper > todomvc.views.todo_app > todomvc.views.task_entry > todomvc.views.todo_input > ReagentInput", :reaction nil, :input-signals nil}, :child-of nil}
- {:id 13, :operation :todos, :type :sub/create, :tags {:query-v [:todos], :cached? true, :reaction "rx4"}, :child-of 12}
- {:id 14, :operation :showing, :type :sub/create, :tags {:query-v [:showing], :cached? false, :reaction "rx8"}, :child-of 12}
- {:id 12, :operation :visible-todos, :type :sub/create, :tags {:query-v [:visible-todos], :cached? false, :reaction "rx9"}, :child-of 11}
- {:id 16, :operation :todos, :type :sub/create, :tags {:query-v [:todos], :cached? true, :reaction "rx4"}, :child-of 15}
- {:id 15, :operation :all-complete?, :type :sub/create, :tags {:query-v [:all-complete?], :cached? false, :reaction "rx10"}, :child-of 11}
- {:id 17, :operation :all-complete?, :type :sub/run, :tags {:query-v [:all-complete?], :reaction "rx10", :input-signals ["rx4"]}, :child-of 11}
- {:id 19, :operation :showing, :type :sub/run, :tags {:query-v [:showing], :reaction "rx8", :input-signals ["ra5"]}, :child-of 18}
- {:id 18, :operation :visible-todos, :type :sub/run, :tags {:query-v [:visible-todos], :reaction "rx9", :input-signals ("rx4" "rx8")}, :child-of 11}
- {:id 11, :operation "todomvc.views.task_list", :type :render, :tags {:component-path "todomvc.core.wrapper > todomvc.views.todo_app > todomvc.views.task_list", :reaction "rx11", :input-signals ("rx10" "rx9")}, :child-of nil}
- {:id 20, :operation "ReagentInput", :type :render, :tags {:component-path "todomvc.core.wrapper > todomvc.views.todo_app > todomvc.views.task_list > ReagentInput", :reaction nil, :input-signals nil}, :child-of nil}
- {:id 21, :operation "todomvc.views.todo_item", :type :render, :tags {:component-path "todomvc.core.wrapper > todomvc.views.todo_app > todomvc.views.task_list > todomvc.views.todo_item", :reaction "rx12", :input-signals ("ra20" "ra20")}, :child-of nil}
- {:id 22, :operation "ReagentInput", :type :render, :tags {:component-path "todomvc.core.wrapper > todomvc.views.todo_app > todomvc.views.task_list > todomvc.views.todo_item > ReagentInput", :reaction nil, :input-signals nil}, :child-of nil}
- {:id 24, :operation :footer-counts, :type :sub/create, :tags {:query-v [:footer-counts], :cached? false, :reaction "rx13"}, :child-of 23}
- {:id 25, :operation :showing, :type :sub/create, :tags {:query-v [:showing], :cached? true, :reaction "rx8"}, :child-of 23}
- {:id 27, :operation :todos, :type :sub/create, :tags {:query-v [:todos], :cached? true, :reaction "rx4"}, :child-of 26}
- {:id 29, :operation :todos, :type :sub/create, :tags {:query-v [:todos], :cached? true, :reaction "rx4"}, :child-of 28}
- {:id 28, :operation :completed-count, :type :sub/create, :tags {:query-v [:completed-count], :cached? false, :reaction "rx14"}, :child-of 26}
- {:id 30, :operation :completed-count, :type :sub/run, :tags {:query-v [:completed-count], :reaction "rx14", :input-signals ["rx4"]}, :child-of 26}
- {:id 26, :operation :footer-counts, :type :sub/run, :tags {:query-v [:footer-counts], :reaction "rx13", :input-signals ("rx4" "rx14")}, :child-of 23}
- {:id 23, :operation "todomvc.views.footer_controls", :type :render, :tags {:component-path "todomvc.core.wrapper > todomvc.views.todo_app > todomvc.views.footer_controls", :reaction "rx15", :input-signals ("rx13" "rx8" "rx8" "rx8")}, :child-of nil}))
-
-(deftest sub-graph-test
- (is (= {:links []
- :nodes [{:id "rx4"
- :r 10
- :title ""
- :group 2
- :data {:id 1
- :tags {:cached? false
- :reaction "rx4"}
- :type :sub/create}}]}
- (graph/trace->sub-graph [{:id 1 :type :sub/create :tags {:cached? false :reaction "rx4"}}] []))))
-
-(deftest dispose-view-test
- (is (= {:links []
- :nodes []}
- (graph/trace->sub-graph [{:id 1 :type :render :tags {:cached? false :reaction "rx4"}}
- {:id 2 :type :componentWillUnmount :tags {:reaction "rx4"}}] []))))
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..c180a7d
--- /dev/null
+++ b/test/day8/re_frame/trace/metamorphic_test.clj
@@ -0,0 +1,39 @@
+(ns day8.re-frame.trace.metamorphic-test
+ (:require [clojure.test :refer :all])
+ (:require [day8.re-frame.trace.metamorphic :as m]))
+
+(defn trace-events [file]
+ (->> (slurp (str "test-resources/" file))
+ (clojure.edn/read-string {:readers {'utc identity
+ 'object (fn [x] "