Add storage namespacing

Allows separate global properties from user specific properties
This commit is contained in:
Andrey Antukh 2024-09-10 16:32:00 +02:00 committed by Alonso Torres
parent 042b3a71d8
commit c8caca77a3
14 changed files with 144 additions and 85 deletions

View file

@ -15,7 +15,7 @@
[app.util.http :as http] [app.util.http :as http]
[app.util.i18n :as i18n] [app.util.i18n :as i18n]
[app.util.object :as obj] [app.util.object :as obj]
[app.util.storage :refer [storage]] [app.util.storage :as storage]
[app.util.time :as dt] [app.util.time :as dt]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[beicon.v2.operators :as rxo] [beicon.v2.operators :as rxo]
@ -170,7 +170,7 @@
(let [session (atom nil) (let [session (atom nil)
stopper (rx/filter (ptk/type? ::initialize) stream) stopper (rx/filter (ptk/type? ::initialize) stream)
buffer (atom #queue []) buffer (atom #queue [])
profile (->> (rx/from-atom storage {:emit-current-value? true}) profile (->> (rx/from-atom storage/user {:emit-current-value? true})
(rx/map :profile) (rx/map :profile)
(rx/map :id) (rx/map :id)
(rx/pipe (rxo/distinct-contiguous)))] (rx/pipe (rxo/distinct-contiguous)))]

View file

@ -18,7 +18,7 @@
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[app.util.storage :refer [storage]] [app.util.storage :as storage]
[app.util.webapi :as wa] [app.util.webapi :as wa]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -335,8 +335,9 @@
(assoc-in state [:workspace-data :recent-fonts] most-recent-fonts))) (assoc-in state [:workspace-data :recent-fonts] most-recent-fonts)))
ptk/EffectEvent ptk/EffectEvent
(effect [_ state _] (effect [_ state _]
(let [most-recent-fonts (get-in state [:workspace-data :recent-fonts])] (let [most-recent-fonts (get-in state [:workspace-data :recent-fonts])]
(swap! storage assoc ::recent-fonts most-recent-fonts))))) ;; FIXME: this should be prefixed by team
(swap! storage/user assoc ::recent-fonts most-recent-fonts)))))
(defn load-recent-fonts (defn load-recent-fonts
[fonts] [fonts]
@ -344,7 +345,7 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [fonts-map (d/index-by :id fonts) (let [fonts-map (d/index-by :id fonts)
saved-recent-fonts (->> (::recent-fonts @storage) saved-recent-fonts (->> (::recent-fonts storage/user)
(keep #(get fonts-map (:id %))) (keep #(get fonts-map (:id %)))
(into #{}))] (into #{}))]
(assoc-in state [:workspace-data :recent-fonts] saved-recent-fonts))))) (assoc-in state [:workspace-data :recent-fonts] saved-recent-fonts)))))

View file

@ -22,7 +22,7 @@
[app.plugins.register :as register] [app.plugins.register :as register]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt] [app.util.router :as rt]
[app.util.storage :as s] [app.util.storage :as storage]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
@ -52,14 +52,14 @@
(defn get-current-team-id (defn get-current-team-id
[profile] [profile]
(let [team-id (::current-team-id @s/storage)] (let [team-id (::current-team-id storage/user)]
(or team-id (:default-team-id profile)))) (or team-id (:default-team-id profile))))
(defn set-current-team! (defn set-current-team!
[team-id] [team-id]
(if (nil? team-id) (if (nil? team-id)
(swap! s/storage dissoc ::current-team-id) (swap! storage/user dissoc ::current-team-id)
(swap! s/storage assoc ::current-team-id team-id))) (swap! storage/user assoc ::current-team-id team-id)))
;; --- EVENT: fetch-teams ;; --- EVENT: fetch-teams
@ -79,9 +79,9 @@
;; if not, dissoc it from storage. ;; if not, dissoc it from storage.
(let [ids (into #{} (map :id) teams)] (let [ids (into #{} (map :id) teams)]
(when-let [ctid (::current-team-id @s/storage)] (when-let [ctid (::current-team-id storage/user)]
(when-not (contains? ids ctid) (when-not (contains? ids ctid)
(swap! s/storage dissoc ::current-team-id))))))) (swap! storage/user dissoc ::current-team-id)))))))
(defn fetch-teams (defn fetch-teams
[] []
@ -132,10 +132,10 @@
(effect [_ state _] (effect [_ state _]
(let [profile (:profile state) (let [profile (:profile state)
email (:email profile) email (:email profile)
previous-profile (:profile @s/storage) previous-profile (:profile storage/user)
previous-email (:email previous-profile)] previous-email (:email previous-profile)]
(when profile (when profile
(swap! s/storage assoc :profile profile) (swap! storage/user assoc :profile profile)
(i18n/set-locale! (:lang profile)) (i18n/set-locale! (:lang profile))
(when (not= previous-email email) (when (not= previous-email email)
(set-current-team! nil)) (set-current-team! nil))
@ -336,7 +336,7 @@
ptk/EffectEvent ptk/EffectEvent
(effect [_ _ _] (effect [_ _ _]
;; We prefer to keek some stuff in the storage like the current-team-id and the profile ;; We prefer to keek some stuff in the storage like the current-team-id and the profile
(swap! s/storage (constantly {})))))) (swap! storage/user (constantly {}))))))
(defn logout (defn logout
([] (logout {})) ([] (logout {}))

View file

@ -79,7 +79,7 @@
[app.util.http :as http] [app.util.http :as http]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt] [app.util.router :as rt]
[app.util.storage :refer [storage]] [app.util.storage :as storage]
[app.util.timers :as tm] [app.util.timers :as tm]
[app.util.webapi :as wapi] [app.util.webapi :as wapi]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
@ -336,7 +336,7 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assoc state (assoc state
:recent-colors (:recent-colors @storage) :recent-colors (:recent-colors storage/user)
:workspace-ready? false :workspace-ready? false
:current-file-id file-id :current-file-id file-id
:current-project-id project-id :current-project-id project-id

View file

@ -7,22 +7,22 @@
(ns app.main.data.workspace.assets (ns app.main.data.workspace.assets
"Workspace assets management events and helpers." "Workspace assets management events and helpers."
(:require (:require
[app.util.storage :refer [storage]])) [app.util.storage :as storage]))
(defn get-current-assets-ordering (defn get-current-assets-ordering
[] []
(let [ordering (::ordering @storage)] (let [ordering (::ordering storage/user)]
(or ordering :asc))) (or ordering :asc)))
(defn set-current-assets-ordering! (defn set-current-assets-ordering!
[ordering] [ordering]
(swap! storage assoc ::ordering ordering)) (swap! storage/user assoc ::ordering ordering))
(defn get-current-assets-list-style (defn get-current-assets-list-style
[] []
(let [list-style (::list-style @storage)] (let [list-style (::list-style storage/user)]
(or list-style :thumbs))) (or list-style :thumbs)))
(defn set-current-assets-list-style! (defn set-current-assets-list-style!
[list-style] [list-style]
(swap! storage assoc ::list-style list-style)) (swap! storage/user assoc ::list-style list-style))

View file

@ -22,7 +22,7 @@
[app.main.data.workspace.texts :as dwt] [app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu] [app.main.data.workspace.undo :as dwu]
[app.util.color :as uc] [app.util.color :as uc]
[app.util.storage :refer [storage]] [app.util.storage :as storage]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
@ -718,9 +718,9 @@
(defn get-active-color-tab (defn get-active-color-tab
[] []
(let [tab (::tab @storage)] (let [tab (::tab storage/user)]
(or tab :ramp))) (or tab :ramp)))
(defn set-active-color-tab! (defn set-active-color-tab!
[tab] [tab]
(swap! storage assoc ::tab tab)) (swap! storage/user assoc ::tab tab))

View file

@ -10,7 +10,7 @@
[app.common.data :as d] [app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.main.data.events :as ev] [app.main.data.events :as ev]
[app.util.storage :refer [storage]] [app.util.storage :as storage]
[clojure.set :as set] [clojure.set :as set]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
@ -144,7 +144,7 @@
stored in Storage." stored in Storage."
[layout] [layout]
(reduce (fn [layout [flag key]] (reduce (fn [layout [flag key]]
(condp = (get @storage key ::none) (condp = (get storage/user key ::none)
::none layout ::none layout
false (disj layout flag) false (disj layout flag)
true (conj layout flag))) true (conj layout flag)))
@ -155,7 +155,7 @@
"Given a set of layout flags, and persist a subset of them to the Storage." "Given a set of layout flags, and persist a subset of them to the Storage."
[layout] [layout]
(doseq [[flag key] layout-flags-persistence-mapping] (doseq [[flag key] layout-flags-persistence-mapping]
(swap! storage assoc key (contains? layout flag)))) (swap! storage/user assoc key (contains? layout flag))))
(def layout-state-persistence-mapping (def layout-state-persistence-mapping
"A mapping of keys that need to be persisted from `:workspace-global` into Storage." "A mapping of keys that need to be persisted from `:workspace-global` into Storage."
@ -167,7 +167,7 @@
props that are previously persisted in the Storage." props that are previously persisted in the Storage."
[state] [state]
(reduce (fn [state [key skey]] (reduce (fn [state [key skey]]
(let [val (get @storage skey ::none)] (let [val (get storage/user skey ::none)]
(if (= val ::none) (if (= val ::none)
state state
(assoc state key val)))) (assoc state key val))))
@ -181,7 +181,7 @@
(doseq [[key skey] layout-state-persistence-mapping] (doseq [[key skey] layout-state-persistence-mapping]
(let [val (get state key ::does-not-exist)] (let [val (get state key ::does-not-exist)]
(if (= val ::does-not-exist) (if (= val ::does-not-exist)
(swap! storage dissoc skey) (swap! storage/user dissoc skey)
(swap! storage assoc skey val))))) (swap! storage/user assoc skey val)))))

View file

@ -48,7 +48,7 @@
[app.util.color :as uc] [app.util.color :as uc]
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[app.util.router :as rt] [app.util.router :as rt]
[app.util.storage :as s] [app.util.storage :as storage]
[app.util.time :as dt] [app.util.time :as dt]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
@ -147,7 +147,7 @@
ptk/EffectEvent ptk/EffectEvent
(effect [_ state _] (effect [_ state _]
(let [recent-colors (:recent-colors state)] (let [recent-colors (:recent-colors state)]
(swap! s/storage assoc :recent-colors recent-colors))))) (swap! storage/user assoc :recent-colors recent-colors)))))
(def clear-color-for-rename (def clear-color-for-rename
(ptk/reify ::clear-color-for-rename (ptk/reify ::clear-color-for-rename

View file

@ -20,7 +20,7 @@
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt] [app.util.router :as rt]
[app.util.storage :as sto] [app.util.storage :as storage]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
@ -198,7 +198,7 @@
:else :else
(do (do
(swap! sto/storage assoc ::email (:email params)) (swap! storage/user assoc ::email (:email params))
(st/emit! (rt/nav :auth-register-success))))))) (st/emit! (rt/nav :auth-register-success)))))))
on-error on-error
@ -264,7 +264,7 @@
(mf/defc register-success-page (mf/defc register-success-page
{::mf/props :obj} {::mf/props :obj}
[{:keys [params]}] [{:keys [params]}]
(let [email (or (:email params) (::email @sto/storage))] (let [email (or (:email params) (::email storage/user))]
[:div {:class (stl/css :auth-form-wrapper :register-success)} [:div {:class (stl/css :auth-form-wrapper :register-success)}
(when-not (:hide-logo params) (when-not (:hide-logo params)
[:h1 {:class (stl/css :logo-container)} [:h1 {:class (stl/css :logo-container)}

View file

@ -16,7 +16,7 @@
[app.main.store :as st] [app.main.store :as st]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.dom.dnd :as dnd] [app.util.dom.dnd :as dnd]
[app.util.storage :refer [storage]] [app.util.storage :as storage]
[app.util.timers :as ts] [app.util.timers :as ts]
[app.util.webapi :as wapi] [app.util.webapi :as wapi]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
@ -294,7 +294,7 @@
`key` for new values." `key` for new values."
[key default] [key default]
(let [id (mf/use-id) (let [id (mf/use-id)
state* (mf/use-state #(get @storage key default)) state* (mf/use-state #(get storage/user key default))
state (deref state*) state (deref state*)
stream (mf/with-memo [id] stream (mf/with-memo [id]
(->> mbc/stream (->> mbc/stream
@ -304,7 +304,7 @@
(mf/with-effect [state key id] (mf/with-effect [state key id]
(mbc/emit! id key state) (mbc/emit! id key state)
(swap! storage assoc key state)) (swap! storage/user assoc key state))
(use-stream stream (partial reset! state*)) (use-stream stream (partial reset! state*))

View file

@ -14,7 +14,7 @@
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.hooks :as hooks] [app.main.ui.hooks :as hooks]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.storage :refer [storage]] [app.util.storage :as storage]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(log/set-level! :warn) (log/set-level! :warn)
@ -23,7 +23,7 @@
(defn- get-initial-state (defn- get-initial-state
[initial file-id key] [initial file-id key]
(let [saved (dm/get-in @storage [::state file-id key])] (let [saved (dm/get-in storage/user [::state file-id key])]
(d/nilv saved initial))) (d/nilv saved initial)))
(defn- update-persistent-state (defn- update-persistent-state
@ -81,7 +81,7 @@
start-size (mf/ref-val start-size-ref) start-size (mf/ref-val start-size-ref)
new-size (-> (+ start-size delta) (max min-val) (min max-val))] new-size (-> (+ start-size delta) (max min-val) (min max-val))]
(reset! current-size* new-size) (reset! current-size* new-size)
(swap! storage update-persistent-state file-id key new-size))))) (swap! storage/user update-persistent-state file-id key new-size)))))
set-size set-size
(mf/use-fn (mf/use-fn
@ -89,7 +89,7 @@
(fn [new-size] (fn [new-size]
(let [new-size (mth/clamp new-size min-val max-val)] (let [new-size (mth/clamp new-size min-val max-val)]
(reset! current-size* new-size) (reset! current-size* new-size)
(swap! storage update-persistent-state file-id key new-size))))] (swap! storage/user update-persistent-state file-id key new-size))))]
(mf/with-effect [on-change-size current-size] (mf/with-effect [on-change-size current-size]
(when on-change-size (when on-change-size

View file

@ -11,7 +11,7 @@
[app.common.logging :as log] [app.common.logging :as log]
[app.config :as cfg] [app.config :as cfg]
[app.util.globals :as globals] [app.util.globals :as globals]
[app.util.storage :refer [storage]] [app.util.storage :as storage]
[cuerdas.core :as str] [cuerdas.core :as str]
[goog.object :as gobj] [goog.object :as gobj]
[okulary.core :as l] [okulary.core :as l]
@ -76,7 +76,7 @@
cfg/default-language)))) cfg/default-language))))
(defonce translations #js {}) (defonce translations #js {})
(defonce locale (l/atom (or (get @storage ::locale) (defonce locale (l/atom (or (get storage/global ::locale)
(autodetect)))) (autodetect))))
(defn init! (defn init!
@ -93,7 +93,7 @@
(if (or (nil? lname) (if (or (nil? lname)
(str/empty? lname)) (str/empty? lname))
(let [lname (autodetect)] (let [lname (autodetect)]
(swap! storage dissoc ::locale) (swap! storage/global dissoc ::locale)
(reset! locale lname)) (reset! locale lname))
(let [supported (into #{} (map :value supported-locales)) (let [supported (into #{} (map :value supported-locales))
lname (loop [locales (seq (parse-locale lname))] lname (loop [locales (seq (parse-locale lname))]
@ -102,7 +102,7 @@
locale locale
(recur (rest locales))) (recur (rest locales)))
cfg/default-language))] cfg/default-language))]
(swap! storage assoc ::locale lname) (swap! storage/global assoc ::locale lname)
(reset! locale lname)))) (reset! locale lname))))
(deftype C [val] (deftype C [val]

View file

@ -10,7 +10,8 @@
[app.common.transit :as t] [app.common.transit :as t]
[app.util.functions :as fns] [app.util.functions :as fns]
[app.util.globals :as g] [app.util.globals :as g]
[cuerdas.core :as str])) [cuerdas.core :as str]
[okulary.util :as ou]))
;; Using ex/ignoring because can receive a DOMException like this when ;; Using ex/ignoring because can receive a DOMException like this when
;; importing the code as a library: Failed to read the 'localStorage' ;; importing the code as a library: Failed to read the 'localStorage'
@ -19,26 +20,27 @@
(ex/ignoring (unchecked-get g/global "localStorage"))) (ex/ignoring (unchecked-get g/global "localStorage")))
(defn- encode-key (defn- encode-key
[k] [prefix k]
(assert (keyword? k) "key must be keyword") (assert (keyword? k) "key must be keyword")
(let [kns (namespace k) (let [kns (namespace k)
kn (name k)] kn (name k)]
(str "penpot:" kns "/" kn))) (str prefix ":" kns "/" kn)))
(defn- decode-key (defn- decode-key
[k] [prefix k]
(when (str/starts-with? k "penpot:") (when (str/starts-with? k prefix)
(let [k (subs k 7)] (let [l (+ (count prefix) 1)
k (subs k l)]
(if (str/starts-with? k "/") (if (str/starts-with? k "/")
(keyword (subs k 1)) (keyword (subs k 1))
(let [[kns kn] (str/split k "/" 2)] (let [[kns kn] (str/split k "/" 2)]
(keyword kns kn)))))) (keyword kns kn))))))
(defn- lookup-by-index (defn- lookup-by-index
[result index] [prefix result index]
(try (try
(let [key (.key ^js local-storage index) (let [key (.key ^js local-storage index)
key' (decode-key key)] key' (decode-key prefix key)]
(if key' (if key'
(let [val (.getItem ^js local-storage key)] (let [val (.getItem ^js local-storage key)]
(assoc! result key' (t/decode-str val))) (assoc! result key' (t/decode-str val)))
@ -46,39 +48,95 @@
(catch :default _ (catch :default _
result))) result)))
(defn- load (defn- load-data
[] [prefix]
(when (some? local-storage) (if (some? local-storage)
(let [length (.-length ^js local-storage)] (let [length (.-length ^js local-storage)]
(loop [index 0 (loop [index 0
result (transient {})] result (transient {})]
(if (< index length) (if (< index length)
(recur (inc index) (recur (inc index)
(lookup-by-index result index)) (lookup-by-index prefix result index))
(persistent! result)))))) (persistent! result))))
{}))
(defonce ^:private latest-state (load)) (defn create-storage
[prefix]
(let [initial (load-data prefix)
curr-data #js {:content initial}
last-data #js {:content initial}
watches (js/Map.)
(defn- on-change* on-change*
[curr-state] (fn [curr-state]
(let [prev-state latest-state] (let [prev-state (unchecked-get last-data "content")]
(try (try
(run! (fn [key] (run! (fn [key]
(let [prev-val (get prev-state key) (let [prev-val (get prev-state key)
curr-val (get curr-state key)] curr-val (get curr-state key)]
(when-not (identical? curr-val prev-val) (when-not (identical? curr-val prev-val)
(if (some? curr-val) (if (some? curr-val)
(.setItem ^js local-storage (encode-key key) (t/encode-str curr-val)) (.setItem ^js local-storage (encode-key prefix key) (t/encode-str curr-val))
(.removeItem ^js local-storage (encode-key key)))))) (.removeItem ^js local-storage (encode-key prefix key))))))
(into #{} (concat (keys curr-state) (into #{} (concat (keys curr-state)
(keys prev-state)))) (keys prev-state))))
(finally (finally
(set! latest-state curr-state))))) (unchecked-set last-data "content" curr-state)))))
(defonce on-change on-change
(fns/debounce on-change* 2000)) (fns/debounce on-change* 2000)]
(defonce storage (atom latest-state)) (reify
(add-watch storage :persistence IAtom
(fn [_ _ _ curr-state]
(on-change curr-state))) IDeref
(-deref [_] (unchecked-get curr-data "content"))
ILookup
(-lookup [coll k]
(-lookup coll k nil))
(-lookup [_ k not-found]
(let [state (unchecked-get curr-data "content")]
(-lookup state k not-found)))
IReset
(-reset! [self newval]
(let [oldval (unchecked-get curr-data "content")]
(unchecked-set curr-data "content" newval)
(on-change newval)
(when (> (.-size watches) 0)
(-notify-watches self oldval newval))
newval))
ISwap
(-swap! [self f]
(let [state (unchecked-get curr-data "content")]
(-reset! self (f state))))
(-swap! [self f x]
(let [state (unchecked-get curr-data "content")]
(-reset! self (f state x))))
(-swap! [self f x y]
(let [state (unchecked-get curr-data "content")]
(-reset! self (f state x y))))
(-swap! [self f x y more]
(let [state (unchecked-get curr-data "content")]
(-reset! self (apply f state x y more))))
IWatchable
(-notify-watches [self oldval newval]
(ou/doiter
(.entries watches)
(fn [n]
(let [f (aget n 1)
k (aget n 0)]
(f k self oldval newval)))))
(-add-watch [self key f]
(.set watches key f)
self)
(-remove-watch [_ key]
(.delete watches key)))))
(defonce global (create-storage "penpot-global"))
(defonce user (create-storage "penpot-user"))

View file

@ -10,11 +10,11 @@
(:require (:require
[app.config :as cfg] [app.config :as cfg]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.storage :refer [storage]] [app.util.storage :as storage]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(defonce theme (get @storage ::theme cfg/default-theme)) (defonce theme (get storage/global ::theme cfg/default-theme))
(defonce theme-sub (rx/subject)) (defonce theme-sub (rx/subject))
(defonce themes #js {}) (defonce themes #js {})
@ -27,7 +27,7 @@
(when (not= theme v) (when (not= theme v)
(when-some [el (dom/get-element "theme")] (when-some [el (dom/get-element "theme")]
(set! (.-href el) (str "css/main-" v ".css"))) (set! (.-href el) (str "css/main-" v ".css")))
(swap! storage assoc ::theme v) (swap! storage/global assoc ::theme v)
(set! theme v) (set! theme v)
(rx/push! theme-sub v))) (rx/push! theme-sub v)))