mirror of
https://github.com/penpot/penpot.git
synced 2025-01-08 16:00:19 -05:00
⚡ Add performance enhancements for util/storage abstraction layer
This commit is contained in:
parent
b2c0bed84c
commit
b8c6103858
1 changed files with 67 additions and 29 deletions
|
@ -6,42 +6,80 @@
|
|||
|
||||
(ns app.util.storage
|
||||
(:require
|
||||
["lodash/debounce" :as ldebounce]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.common.transit :as t]
|
||||
[app.util.globals :as g]
|
||||
[app.util.timers :as tm]))
|
||||
[cuerdas.core :as str]))
|
||||
|
||||
(defn- persist
|
||||
[storage prev curr]
|
||||
(run! (fn [key]
|
||||
(let [prev* (get prev key)
|
||||
curr* (get curr key)]
|
||||
(when (not= curr* prev*)
|
||||
(tm/schedule-on-idle
|
||||
#(if (some? curr*)
|
||||
(.setItem ^js storage (t/encode-str key) (t/encode-str curr*))
|
||||
(.removeItem ^js storage (t/encode-str key)))))))
|
||||
;; Using ex/ignoring because can receive a DOMException like this when
|
||||
;; importing the code as a library: Failed to read the 'localStorage'
|
||||
;; property from 'Window': Storage is disabled inside 'data:' URLs.
|
||||
(defonce ^:private local-storage
|
||||
(ex/ignoring (unchecked-get g/global "localStorage")))
|
||||
|
||||
(into #{} (concat (keys curr)
|
||||
(keys prev)))))
|
||||
(defn- encode-key
|
||||
[k]
|
||||
(assert (keyword? k) "key must be keyword")
|
||||
(let [kns (namespace k)
|
||||
kn (name k)]
|
||||
(str "penpot:" kns "/" kn)))
|
||||
|
||||
(defn- decode-key
|
||||
[k]
|
||||
(when (str/starts-with? k "penpot:")
|
||||
(let [k (subs k 7)]
|
||||
(if (str/starts-with? k "/")
|
||||
(keyword (subs k 1))
|
||||
(let [[kns kn] (str/split k "/" 2)]
|
||||
(keyword kns kn))))))
|
||||
|
||||
(defn- lookup-by-index
|
||||
[result index]
|
||||
(try
|
||||
(let [key (.key ^js local-storage index)
|
||||
key' (decode-key key)]
|
||||
(if key'
|
||||
(let [val (.getItem ^js local-storage key)]
|
||||
(assoc! result key' (t/decode-str val)))
|
||||
result))
|
||||
(catch :default _
|
||||
result)))
|
||||
|
||||
(defn- load
|
||||
[storage]
|
||||
(when storage
|
||||
(let [len (.-length ^js storage)]
|
||||
(reduce (fn [res index]
|
||||
(let [key (.key ^js storage index)
|
||||
val (.getItem ^js storage key)]
|
||||
(try
|
||||
(assoc res (t/decode-str key) (t/decode-str val))
|
||||
(catch :default _e
|
||||
res))))
|
||||
{}
|
||||
(range len)))))
|
||||
[]
|
||||
(when (some? local-storage)
|
||||
(let [length (.-length ^js local-storage)]
|
||||
(loop [index 0
|
||||
result (transient {})]
|
||||
(if (< index length)
|
||||
(recur (inc index)
|
||||
(lookup-by-index result index))
|
||||
(persistent! result))))))
|
||||
|
||||
;; Using ex/ignoring because can receive a DOMException like this when importing the code as a library:
|
||||
;; Failed to read the 'localStorage' property from 'Window': Storage is disabled inside 'data:' URLs.
|
||||
(defonce storage (atom (load (ex/ignoring (unchecked-get g/global "localStorage")))))
|
||||
(defonce ^:private latest-state (load))
|
||||
|
||||
(add-watch storage :persistence #(persist js/localStorage %3 %4))
|
||||
(defn- on-change*
|
||||
[curr-state]
|
||||
(let [prev-state latest-state]
|
||||
(try
|
||||
(run! (fn [key]
|
||||
(let [prev-val (get prev-state key)
|
||||
curr-val (get curr-state key)]
|
||||
(when-not (identical? curr-val prev-val)
|
||||
(if (some? curr-val)
|
||||
(.setItem ^js local-storage (encode-key key) (t/encode-str curr-val))
|
||||
(.removeItem ^js local-storage (encode-key key))))))
|
||||
(into #{} (concat (keys curr-state)
|
||||
(keys prev-state))))
|
||||
(finally
|
||||
(set! latest-state curr-state)))))
|
||||
|
||||
(defonce on-change
|
||||
(ldebounce on-change* 2000 #js {:leading false :trailing true}))
|
||||
|
||||
|
||||
(defonce storage (atom latest-state))
|
||||
(add-watch storage :persistence
|
||||
(fn [_ _ _ curr-state]
|
||||
(on-change curr-state)))
|
||||
|
|
Loading…
Reference in a new issue