diff --git a/project.clj b/project.clj index fd4970d..efe3c05 100644 --- a/project.clj +++ b/project.clj @@ -4,7 +4,8 @@ :dependencies [[org.clojure/clojure "1.6.0"] [org.clojure/clojurescript "0.0-2322"] - [reagent "0.4.3"]] + [reagent "0.4.3"] + [historian "1.0.7"]] :profiles {:debug {:debug true} :dev {:dependencies [[spellhouse/clairvoyant "0.0-48-gf5e59d3"]] @@ -24,7 +25,7 @@ :jar-exclusions [#"(?:^|\/)re-frame-demo\/"] :cljsbuild {:builds [{:id "demo" - :source-paths ["src/demo" "src/re_frame"] + :source-paths ["src" "src/re_frame"] :compiler {:output-to "run/compiled/demo.js" :source-map "run/compiled/demo.js.map" :output-dir "run/compiled/demo" diff --git a/src/re_frame/core.cljs b/src/re_frame/core.cljs deleted file mode 100644 index 87045f5..0000000 --- a/src/re_frame/core.cljs +++ /dev/null @@ -1,4 +0,0 @@ -(ns re-frame.core - (:require [reagent.core :as reagent] - )) - diff --git a/src/re_frame/db.cljs b/src/re_frame/db.cljs new file mode 100644 index 0000000..c29c73c --- /dev/null +++ b/src/re_frame/db.cljs @@ -0,0 +1,11 @@ +(ns re-frame.db + (:require [reagent.core :as r])) + + ;; -- application data --------------------------------------------------------------------------- + ;; + ;; The entire state of the application is stored in this one atom. + ;; It is useful to think of this atom as a database, hence its name. + ;; For example, when it is mutated, we want it done in "a transaction", so it never appears in + ;; an inconsistent state. Atomic operations, etc. + + (def ^:private db (r/atom {})) diff --git a/src/re_frame/handlers.cljs b/src/re_frame/handlers.cljs index 8ec2ad7..e1aeeb9 100644 --- a/src/re_frame/handlers.cljs +++ b/src/re_frame/handlers.cljs @@ -1,4 +1,5 @@ - (ns model.handlers) + (ns re-frame.handlers + (:require [re-frame.db :refer [db]])) ;; -- helpers ------------------------------------------------------------------------------------ @@ -25,14 +26,12 @@ validation-fn)))) -;; -- application data --------------------------------------------------------------------------- -;; -;; The entire state of the application is stored in this one atom. -;; It is useful to think of this atom as a database, hence its name. -;; For example, when it is mutated, we want it done in "a transaction", so it never appears in -;; an inconsistent state. Atomic operations, etc. -;; -(def ^:private db (atom nil)) + (defn get-in-db + "Direct lookup of arbitrary path in state/db without subscription/reaction. + NOTE: While it is convenient to not have to pass values through function chains, + by definition this also incourages non pure functional style, so resist." + [path-v] + (get-in @db path-v)) ;; -- Event Handlers ------------------------------------------------------------------------------ @@ -84,12 +83,12 @@ ;; reagent components call this function when they want to "send" an event. -(defn dispatch ;; TODO: call it (dispatch-event []) +(defn dispatch [event-v] ;; something like [:delete-item 42] (let [event-id (first-in-vector event-v) handler-fn (get @event-id->handler-fn event-id)] (assert (not (nil? handler-fn)) (str "No event handler registered for event: " event-id )) - (handler-fn @db event-v))) + (handler-fn db event-v))) @@ -119,19 +118,20 @@ (def ^:private subscription-key->handler-fn (atom {})) - (defn register-subscription - [key-v handler-fn] - (if (contains? @subscription-key->handler-fn key-v) - (println "Warning: overwritting a subscription-handler: " key-v)) ;; TODO: more generic logging - (swap! subscription-key->handler-fn assoc key-v handler-fn)) +(defn register-subscription + [key-v handler-fn] + (if (contains? @subscription-key->handler-fn key-v) + (println "Warning: overwritting a subscription-handler: " key-v)) ;; TODO: more generic logging + (swap! subscription-key->handler-fn assoc key-v handler-fn)) - (defn subscribe - "returns a reagent/reaction which observes a part of the " - [v] - (let [key-v (first-in-vector v) - handler-fn (get @subscription-key->handler-fn key-v)] - (assert (not (nil? handler-fn)) (str "No subscription handler registered for key: " key-v)) - (handler-fn @db v))) +(defn subscribe + "returns a reagent/reaction which observes a part of the " + [v] + (let [key-v (first-in-vector v) + handler-fn (get @subscription-key->handler-fn key-v)] + (assert (not (nil? handler-fn)) (str "No subscription handler registered for key: " key-v)) + (handler-fn db v))) + diff --git a/src/re_frame/history.cljs b/src/re_frame/history.cljs new file mode 100644 index 0000000..8f5aa4b --- /dev/null +++ b/src/re_frame/history.cljs @@ -0,0 +1,62 @@ + (ns re-frame.history + (:require-macros [reagent.ratom :refer [reaction]] + [historian.core :refer [off-the-record]]) + (:require + [reagent.core :as r] + [re-frame.db :refer [db]] + [re-frame.handlers :refer [register-subscription register-handler]] + [historian.core :as historian] + )) + + + ;; -- History ----------------------------------------------------------- + ;; https://day8.slack.com/files/mikhug/F03238WLX/historian.md + + (historian/record! db :db) + (def undo-list (r/atom [])) + (def redo-list (r/atom [])) + (historian/replace-library! undo-list) + (historian/replace-prophecy! redo-list) + + (defn reset-history! + "privileged protocol: should not be used by client code" + [] + (historian/clear-history!) + (historian/trigger-record!)) + + (register-subscription + :undos? + (fn + [_ _] + "answer true is anything is stored in the undo list" + (reaction (> (count @undo-list) 1)))) + + (register-subscription + :redos? + (fn + [_ _] + "answer true is anything is stored in the redo list" + (reaction (> (count @redo-list) 0)))) + + ;; -------------------------- HANDLERS ---------------------------------------- + + (register-handler + :undo + (fn + [_ _] + (historian/undo!))) ;; (dispatch [:undo]) + + + (register-handler + :redo + (fn + [_ _] + (historian/redo!))) ;; (dispatch [:undo]) + + + (register-handler + :no-history-while + (fn + [db [_ mutation-fn]] + (off-the-record + (reset! db (mutation-fn @db)))))