0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-09 08:20:45 -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 (ns app.util.storage
(:require (:require
["lodash/debounce" :as ldebounce]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.transit :as t] [app.common.transit :as t]
[app.util.globals :as g] [app.util.globals :as g]
[app.util.timers :as tm])) [cuerdas.core :as str]))
(defn- persist ;; Using ex/ignoring because can receive a DOMException like this when
[storage prev curr] ;; importing the code as a library: Failed to read the 'localStorage'
(run! (fn [key] ;; property from 'Window': Storage is disabled inside 'data:' URLs.
(let [prev* (get prev key) (defonce ^:private local-storage
curr* (get curr key)] (ex/ignoring (unchecked-get g/global "localStorage")))
(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)))))))
(into #{} (concat (keys curr) (defn- encode-key
(keys prev))))) [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 (defn- load
[storage] []
(when storage (when (some? local-storage)
(let [len (.-length ^js storage)] (let [length (.-length ^js local-storage)]
(reduce (fn [res index] (loop [index 0
(let [key (.key ^js storage index) result (transient {})]
val (.getItem ^js storage key)] (if (< index length)
(recur (inc index)
(lookup-by-index result index))
(persistent! result))))))
(defonce ^:private latest-state (load))
(defn- on-change*
[curr-state]
(let [prev-state latest-state]
(try (try
(assoc res (t/decode-str key) (t/decode-str val)) (run! (fn [key]
(catch :default _e (let [prev-val (get prev-state key)
res)))) curr-val (get curr-state key)]
{} (when-not (identical? curr-val prev-val)
(range len))))) (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)))))
;; Using ex/ignoring because can receive a DOMException like this when importing the code as a library: (defonce on-change
;; Failed to read the 'localStorage' property from 'Window': Storage is disabled inside 'data:' URLs. (ldebounce on-change* 2000 #js {:leading false :trailing true}))
(defonce storage (atom (load (ex/ignoring (unchecked-get g/global "localStorage")))))
(add-watch storage :persistence #(persist js/localStorage %3 %4))
(defonce storage (atom latest-state))
(add-watch storage :persistence
(fn [_ _ _ curr-state]
(on-change curr-state)))