0
Fork 0
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:
Andrey Antukh 2024-09-04 16:04:43 +02:00
parent b2c0bed84c
commit b8c6103858

View file

@ -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)))