Commit 1064c5c2 authored by Ricardo J. Mendez's avatar Ricardo J. Mendez

Re-working storage, WIP

- Storing data as transit in a single block to avoid issues with using
  URLs as keys and storage/get keywordization.
- Removed :storage-loaded event, everything can go through :data-import
  for consistency.
- Removed snapshot code
- Displaying tab time

We are now fully updating storage from two different spots, since the
group saving code is still in there.  Created a separate task for that.
parent d1aeda19
(ns booklet.background
(:require [cljs.core.async :refer [>! <!]]
[booklet.utils :refer [dispatch-on-channel]]
[booklet.utils :refer [dispatch-on-channel to-transit]]
[cognitect.transit :as transit]
[khroma.log :as console]
[khroma.runtime :as runtime]
[khroma.windows :as windows]
......@@ -9,7 +10,8 @@
[khroma.tabs :as tabs]
[reagent.core :as reagent]
[re-frame.core :refer [dispatch register-sub register-handler subscribe dispatch-sync]])
(:require-macros [cljs.core.async.macros :refer [go go-loop]]))
(:require-macros [cljs.core.async.macros :refer [go go-loop]]
))
......@@ -26,13 +28,13 @@
(defn now [] (.now js/Date))
(def relevant-tab-keys [:windowId :id :active :url :selected :start-time])
(def relevant-tab-keys [:windowId :id :active :url :selected :start-time :title :favIconUrl])
(def select-tab-keys #(select-keys % relevant-tab-keys))
(def add-tab-times #(assoc % :start-time (if (:active %) (now) 0)))
(def tab-data-path [:app-state :tab-tracking])
(def url-time-path [:data :urls])
(def url-time-path [:data :url-times])
(defn process-tab
"Filters a tab down to the relevant keys, and adds a start time which is
......@@ -63,6 +65,17 @@
;;;;-------------------------------------
(register-handler
:data-set
(fn [app-state [_ key item]]
(let [new-state (assoc-in app-state [:data key] item)
transit-data (to-transit (:data new-state))]
(console/log "Saving" key)
(storage/set {:data transit-data})
(console/log "New state" new-state)
new-state)
))
(register-handler
:handle-activation
(fn [app-state [_ tab]]
......@@ -83,7 +96,7 @@
(console/log "Deactivating" tab removed?)
(when (or (:active tab)
(< 0 (:start-time tab)))
(dispatch [:track-time (:url tab) (- (now) (:start-time tab))]))
(dispatch [:track-time tab (- (now) (:start-time tab))]))
(if removed?
app-state
(assoc-in app-state
......@@ -112,10 +125,18 @@
(if active-tabs
(assoc-in app-state [:app-state :idle] active-tabs)
app-state)
)))
(register-handler
:start-tracking
(fn [app-state [_ tabs]]
; (dispatch [:data-set :url-times {}])
(-> app-state
(assoc-in tab-data-path (process-tabs tabs))
(assoc-in url-time-path
(or (get-in app-state url-time-path) {})))))
)))
(register-handler
::tab-activated
......@@ -132,14 +153,15 @@
(doseq [tab prev-active]
(dispatch [:handle-deactivation tab]))
(dispatch [:handle-activation (get all-tabs tabId)])
(console/log "Activated" tabId "from window" windowId)
(console/log "Previously active" prev-active))
; (console/log "Activated" tabId "from window" windowId)
; (console/log "Previously active" prev-active)
)
app-state))
(register-handler
::tab-created
(fn [app-state [_ {:keys [tab]}]]
(console/log "Created" tab)
; (console/log "Created" tab)
(when (:active tab)
;; If we just created an active tab, make sure we go through the activation cycle
(dispatch [::tab-activated {:activeInfo {:tabId (:id tab)
......@@ -156,18 +178,17 @@
(let [id (:tabId msg)
tabs (get-in app-state tab-data-path)
tab (get tabs id)]
(console/trace "Removed id:" id "Previous" tab (:active tab))
; (console/trace "Removed id:" id "Previous" tab (:active tab))
(dispatch [:handle-deactivation tab true]) ; We're not only deactivating it, we're destroying it
(assoc-in app-state tab-data-path (dissoc tabs id)))))
(register-handler
::tab-replaced
(fn [app-state [_ {:keys [added removed]}]]
;; When we get a tab-replaced, we only get two ids. We don't get any
;; other tab information. We'll treat this as a remove and a create,
;; and let those event handlers handle it.
(console/log "Replaced" added removed)
; (console/log "Replaced" added removed)
(dispatch [::tab-removed {:tabId removed}])
(go (dispatch [::tab-created {:tab (<! (tabs/get-tab added))}]))
app-state
......@@ -180,22 +201,37 @@
(when (and (:active tab)
(not= (:url old-tab)
(:url tab)))
(do
(console/log "Tab URL changed while active")
(dispatch [:handle-deactivation old-tab])
(dispatch [:handle-activation tab]))
)
;; TODO: Handle case where the url changed
)
(console/log "Updated" tabId tab (get-in app-state (conj tab-data-path tabId)))
(console/log "Tab URL changed while active")
(dispatch [:handle-deactivation old-tab])
(dispatch [:handle-activation tab])
))
; (console/log "Updated" tabId tab (get-in app-state (conj tab-data-path tabId)))
app-state
))
(register-handler
:track-time
(fn [app-state [_ url time]]
(console/log time "milliseconds spent at" url)
app-state))
(fn [app-state [_ tab time]]
(let [url-times (get-in app-state url-time-path)
url-key (:url tab)
url-time (or (get url-times url-key)
{:url (:url tab)
:time 0
:timestamp 0})
;; Don't track two messages too close
track? (and (not-empty url-key)
(< 100 (- (now) (:timestamp url-time))))
new-time (assoc url-time :time (+ (:time url-time) time)
:title (:title tab)
:favIconUrl (:favIconUrl tab)
:timestamp (now))]
(console/log time track? "milliseconds spent at" url-key)
(console/log "Previous" url-time)
(when track?
(dispatch [:data-set :url-times (assoc url-times url-key new-time)]))
app-state
)))
;; TODO
;; - Track URL state changes
......@@ -205,13 +241,7 @@
;; TODO: Handle detached tabs, looks like we don't get an activate for them.
(register-handler
:start-tracking
(fn [app-state [_ tabs]]
(-> app-state
(assoc-in tab-data-path (process-tabs tabs))
(assoc-in url-time-path
(or (get-in app-state url-time-path) {})))))
(defn start-tracking []
......
(ns booklet.core
(:require [ajax.core :refer [GET POST PUT]]
[booklet.utils :refer [dispatch-on-channel]]
[booklet.utils :refer [dispatch-on-channel from-transit]]
[cljs.core.async :refer [>! <!]]
[cljs.core :refer [random-uuid]]
[cljsjs.react-bootstrap]
......@@ -11,7 +11,8 @@
[khroma.tabs :as tabs]
[khroma.windows :as windows]
[reagent.core :as reagent]
[re-frame.core :refer [dispatch register-sub register-handler subscribe dispatch-sync]])
[re-frame.core :refer [dispatch register-sub register-handler subscribe dispatch-sync]]
[cognitect.transit :as transit])
(:require-macros [cljs.core :refer [goog-define]]
[cljs.core.async.macros :refer [go go-loop]]
[reagent.ratom :refer [reaction]]))
......@@ -21,8 +22,6 @@
;;;; Queries
;;;;------------------------------
(goog-define api-uri "http://localhost:3000")
(defn general-query
[db path]
(reaction (get-in @db path)))
......@@ -85,30 +84,26 @@
(register-handler
:data-import
(fn [app-state [_ json-data]]
(let [new-data (clojure.walk/keywordize-keys (js->clj (.parse js/JSON json-data)))]
(fn [app-state [_ transit-data]]
(let [new-data (from-transit transit-data)]
(console/log "New data on import" new-data)
(doseq [[key item] new-data]
;; Dispatch instead of just doing an assoc so that it's also saved
(dispatch [:data-set key item]))
(when (empty? (:instance-id new-data))
(dispatch [:data-set :instance-id (.-uuid (random-uuid))]))
(-> app-state
(assoc-in [:ui-state :section] :groups)
(assoc-in [:ui-state :section] :time-track)
(assoc-in [:app-state :import] nil))
)))
(register-handler
:data-set
(fn [app-state [_ key item]]
(storage/set {key item})
(assoc-in app-state [:data key] item)
))
(register-handler
:initialize
(fn [_ [_ tabs]]
(go (dispatch [:storage-loaded (<! (storage/get))]))
(go (dispatch [:data-import (:data (<! (storage/get)))]))
{:app-state {:tabs tabs}
:ui-state {:section :monitor}}))
:ui-state {:section :time-track}}))
(register-handler
:group-add
......@@ -177,68 +172,16 @@
(register-handler
:snapshot-take
(fn [app-state [_]]
(let [path [:data :snapshots]
snapshots (or (get-in app-state path) '())
tabs (->> (get-in app-state [:app-state :tabs])
filter-tabs
(map #(select-keys % relevant-tab-items)))
new-group (group-from-tabs tabs)
last-snap (or (get-in app-state [:app-state :last-snapshot])
(first (sort-by #(* -1 (:date %)) snapshots)))
is-last? (= (set (map :url tabs))
(set (map :url (:tabs last-snap))))
save? (and (< 1 (count tabs))
(not is-last?))
]
(when save? (dispatch [:data-set :snapshots (conj snapshots new-group)]))
; (console/log "Current" (set tabs) "Last" (set (:tabs last-snap)) "Last snap" last-snap)
; (console/log "Tick tock snapshot " save? (.now js/Date))
(if save?
(assoc-in app-state [:app-state :last-snapshot] new-group)
app-state)
)
))
(register-handler
:snapshot-post
(fn [app-state [_]]
#_(let [to-send (select-keys (:data app-state) [:instance-id :snapshots])]
(GET (str api-uri "/api/echo/" "hello")
{:handler #(console/log "GET Handler" %)
:error-handler #(console/log "GET Error" %)})
(POST (str api-uri "/api/snapshot/many")
{:params to-send
:handler #(dispatch [:snapshot-post-success (:snapshots to-send)])
:error-handler #(dispatch [:log-content %])})
)
app-state
)
)
(register-handler
:snapshot-post-success
(fn [app-state [_ snapshots]]
(console/log "Saved" snapshots)
;; TODO: Remove only the snapshots we saved
(dispatch [:data-set :snapshots nil])
app-state
))
(register-handler
:storage-loaded
(fn [app-state [_ data]]
;; We create a new id if fir any reason we don't have one
(when (empty? (:instance-id data))
(dispatch [:data-set :instance-id (.-uuid (random-uuid))]))
(dispatch [:snapshot-post])
;; Return new data state. We don't return the new id because data-set also
;; saves it, the call below only sets the internal state
(->> data
(merge (:data app-state))
(assoc app-state :data))
::storage-changed
(fn [app-state [_ message]]
(let [new-value (get-in message [:changes :data :newValue])
data (from-transit new-value)]
(console/log "Storage changed:" message)
(console/log "New data value " data)
(if (not-empty data)
(assoc app-state :data data)
app-state
))
))
......@@ -312,9 +255,10 @@
[:a {:class "navbar-brand" :href "http://numergent.com" :target "_new"} "Booklet"]]
[:div {:class "collapse navbar-collapse", :id "bs-example-navbar-collapse-1"}
[:ul {:class "nav navbar-nav"}
[navbar-item "Times" :time-track @section]
[navbar-item "Monitor" :monitor @section]
[navbar-item "Groups" :groups @section]
[navbar-item "Snapshots" :snapshots @section]]
]
[:form {:class "navbar-form navbar-left", :role "search"}
[:div {:class "form-group"}
[:input {:type "text", :class "form-control", :placeholder "Search"}]]
......@@ -345,7 +289,7 @@
:on-click (:action @modal-info)} (:action-label @modal-info)]
]])))
(defn list-tabs [tabs is-history?]
(defn list-tabs [tabs disp-key]
(->>
tabs
(sort-by :index)
......@@ -355,11 +299,10 @@
favicon (:favIconUrl tab)
action (if is-history?
{:href url :target "_blank"}
{:on-click #(tabs/activate (:id tab))}
)]
{:on-click #(tabs/activate (:id tab))})]
^{:key i}
[:tr
[:td {:class "col-sm-1"} (if-not is-history? (:id tab))]
[:td {:class "col-sm-1"} (when disp-key (disp-key tab))]
[:td {:class "col-sm-6"} [:a
action
(if favicon
......@@ -381,7 +324,7 @@
[:th "Title"]
[:th "URL"]]]
[:tbody
(list-tabs @tabs false)]
(list-tabs @tabs :id)]
]
[:a {:class "btn btn-primary btn-sm"
:on-click #(dispatch [:group-add])} "Save me"]
......@@ -420,7 +363,7 @@
[:th "Title"]
[:th "URL"]]]
[:tbody
(list-tabs (filter-tabs (:tabs group)) true)]
(list-tabs (filter-tabs (:tabs group)) nil)]
]])
)
......@@ -437,14 +380,22 @@
])
))
(defn div-snapshots []
(let [snapshots (subscribe [:data :snapshots])
to-list (reaction (sort-by #(* -1 (:date %)) @snapshots))]
(defn div-timetrack []
(let [url-times (subscribe [:data :url-times])
url-values (reaction (vals @url-times))
to-list (reaction (sort-by #(* -1 (:time %)) @url-values))]
(fn []
[:div
[:div {:class "page-header"} [:h2 "Snapshots"]]
(list-groups @to-list false nil nil)
])
[:div {:class "page-header"} [:h2 "Times"]]
[:table {:class "table table-striped table-hover"}
[:thead
[:tr
[:th "#"]
[:th "Title"]
[:th "URL"]]]
[:tbody
(list-tabs @to-list :time)]
]])
))
......@@ -481,11 +432,11 @@
])))
(def component-dir {:monitor current-tabs
:groups div-tab-groups
:export data-export
:import data-import
:snapshots div-snapshots})
(def component-dir {:monitor current-tabs
:groups div-tab-groups
:export data-export
:import data-import
:time-track div-timetrack})
(defn main-section []
......@@ -508,12 +459,10 @@
(defn init []
(console/log "Initialized booklet.core")
(console/log api-uri)
(go (let [window (<! (windows/get-current))
state (<! (idle/query-state 30))]
(go (let [window (<! (windows/get-current))]
(dispatch-sync [:initialize (:tabs window)])))
(let [bg (runtime/connect)]
(dispatch-on-channel :log-content storage/on-changed)
(dispatch-on-channel ::storage-changed storage/on-changed)
(dispatch-on-channel :tab-created tabs/on-created)
(dispatch-on-channel :tab-removed tabs/on-removed)
(dispatch-on-channel :tab-updated tabs/on-updated)
......
(ns booklet.utils
(:require [re-frame.core :refer [dispatch dispatch-sync]])
(:require [re-frame.core :refer [dispatch dispatch-sync]]
[cognitect.transit :as transit])
(:require-macros [cljs.core.async.macros :refer [go go-loop]]))
......@@ -12,3 +13,13 @@
(dispatch [msg (<! channel)])
(recur channel)
))
(defn to-transit
[data]
(transit/write (transit/writer :json) data))
(defn from-transit
[transit-data]
(transit/read (transit/reader :json) transit-data)
)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment