diff --git a/frontend/src/app/main/data/events.cljs b/frontend/src/app/main/data/events.cljs index 1e0cc623f3..06ca2def39 100644 --- a/frontend/src/app/main/data/events.cljs +++ b/frontend/src/app/main/data/events.cljs @@ -15,7 +15,7 @@ [app.util.http :as http] [app.util.i18n :as i18n] [app.util.object :as obj] - [app.util.storage :refer [storage]] + [app.util.storage :as storage] [app.util.time :as dt] [beicon.v2.core :as rx] [beicon.v2.operators :as rxo] @@ -170,7 +170,7 @@ (let [session (atom nil) stopper (rx/filter (ptk/type? ::initialize) stream) 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 :id) (rx/pipe (rxo/distinct-contiguous)))] diff --git a/frontend/src/app/main/data/fonts.cljs b/frontend/src/app/main/data/fonts.cljs index 6274b33548..87f6709035 100644 --- a/frontend/src/app/main/data/fonts.cljs +++ b/frontend/src/app/main/data/fonts.cljs @@ -18,7 +18,7 @@ [app.main.repo :as rp] [app.main.store :as st] [app.util.i18n :refer [tr]] - [app.util.storage :refer [storage]] + [app.util.storage :as storage] [app.util.webapi :as wa] [beicon.v2.core :as rx] [cuerdas.core :as str] @@ -335,8 +335,9 @@ (assoc-in state [:workspace-data :recent-fonts] most-recent-fonts))) ptk/EffectEvent (effect [_ state _] - (let [most-recent-fonts (get-in state [:workspace-data :recent-fonts])] - (swap! storage assoc ::recent-fonts most-recent-fonts))))) + (let [most-recent-fonts (get-in state [:workspace-data :recent-fonts])] + ;; FIXME: this should be prefixed by team + (swap! storage/user assoc ::recent-fonts most-recent-fonts))))) (defn load-recent-fonts [fonts] @@ -344,7 +345,7 @@ ptk/UpdateEvent (update [_ state] (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 %))) (into #{}))] (assoc-in state [:workspace-data :recent-fonts] saved-recent-fonts))))) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index d5fd3c706f..2ea2295771 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -22,7 +22,7 @@ [app.plugins.register :as register] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] - [app.util.storage :as s] + [app.util.storage :as storage] [beicon.v2.core :as rx] [potok.v2.core :as ptk])) @@ -52,14 +52,14 @@ (defn get-current-team-id [profile] - (let [team-id (::current-team-id @s/storage)] + (let [team-id (::current-team-id storage/user)] (or team-id (:default-team-id profile)))) (defn set-current-team! [team-id] (if (nil? team-id) - (swap! s/storage dissoc ::current-team-id) - (swap! s/storage assoc ::current-team-id team-id))) + (swap! storage/user dissoc ::current-team-id) + (swap! storage/user assoc ::current-team-id team-id))) ;; --- EVENT: fetch-teams @@ -79,9 +79,9 @@ ;; if not, dissoc it from storage. (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) - (swap! s/storage dissoc ::current-team-id))))))) + (swap! storage/user dissoc ::current-team-id))))))) (defn fetch-teams [] @@ -132,10 +132,10 @@ (effect [_ state _] (let [profile (:profile state) email (:email profile) - previous-profile (:profile @s/storage) + previous-profile (:profile storage/user) previous-email (:email previous-profile)] (when profile - (swap! s/storage assoc :profile profile) + (swap! storage/user assoc :profile profile) (i18n/set-locale! (:lang profile)) (when (not= previous-email email) (set-current-team! nil)) @@ -336,7 +336,7 @@ ptk/EffectEvent (effect [_ _ _] ;; 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 ([] (logout {})) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index dd62ff70d8..a557992bb5 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -79,7 +79,7 @@ [app.util.http :as http] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] - [app.util.storage :refer [storage]] + [app.util.storage :as storage] [app.util.timers :as tm] [app.util.webapi :as wapi] [beicon.v2.core :as rx] @@ -336,7 +336,7 @@ ptk/UpdateEvent (update [_ state] (assoc state - :recent-colors (:recent-colors @storage) + :recent-colors (:recent-colors storage/user) :workspace-ready? false :current-file-id file-id :current-project-id project-id diff --git a/frontend/src/app/main/data/workspace/assets.cljs b/frontend/src/app/main/data/workspace/assets.cljs index 7db9cc1ce1..5a7e528ee1 100644 --- a/frontend/src/app/main/data/workspace/assets.cljs +++ b/frontend/src/app/main/data/workspace/assets.cljs @@ -7,22 +7,22 @@ (ns app.main.data.workspace.assets "Workspace assets management events and helpers." (:require - [app.util.storage :refer [storage]])) + [app.util.storage :as storage])) (defn get-current-assets-ordering [] - (let [ordering (::ordering @storage)] + (let [ordering (::ordering storage/user)] (or ordering :asc))) (defn set-current-assets-ordering! [ordering] - (swap! storage assoc ::ordering ordering)) + (swap! storage/user assoc ::ordering ordering)) (defn get-current-assets-list-style [] - (let [list-style (::list-style @storage)] + (let [list-style (::list-style storage/user)] (or list-style :thumbs))) (defn set-current-assets-list-style! [list-style] - (swap! storage assoc ::list-style list-style)) + (swap! storage/user assoc ::list-style list-style)) diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index dc0a44d4a2..6bdc488983 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -22,7 +22,7 @@ [app.main.data.workspace.texts :as dwt] [app.main.data.workspace.undo :as dwu] [app.util.color :as uc] - [app.util.storage :refer [storage]] + [app.util.storage :as storage] [beicon.v2.core :as rx] [cuerdas.core :as str] [potok.v2.core :as ptk])) @@ -718,9 +718,9 @@ (defn get-active-color-tab [] - (let [tab (::tab @storage)] + (let [tab (::tab storage/user)] (or tab :ramp))) (defn set-active-color-tab! [tab] - (swap! storage assoc ::tab tab)) + (swap! storage/user assoc ::tab tab)) diff --git a/frontend/src/app/main/data/workspace/layout.cljs b/frontend/src/app/main/data/workspace/layout.cljs index 6b17a1e7ff..51fddd8a18 100644 --- a/frontend/src/app/main/data/workspace/layout.cljs +++ b/frontend/src/app/main/data/workspace/layout.cljs @@ -10,7 +10,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.main.data.events :as ev] - [app.util.storage :refer [storage]] + [app.util.storage :as storage] [clojure.set :as set] [potok.v2.core :as ptk])) @@ -144,7 +144,7 @@ stored in Storage." [layout] (reduce (fn [layout [flag key]] - (condp = (get @storage key ::none) + (condp = (get storage/user key ::none) ::none layout false (disj 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." [layout] (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 "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." [state] (reduce (fn [state [key skey]] - (let [val (get @storage skey ::none)] + (let [val (get storage/user skey ::none)] (if (= val ::none) state (assoc state key val)))) @@ -181,7 +181,7 @@ (doseq [[key skey] layout-state-persistence-mapping] (let [val (get state key ::does-not-exist)] (if (= val ::does-not-exist) - (swap! storage dissoc skey) - (swap! storage assoc skey val))))) + (swap! storage/user dissoc skey) + (swap! storage/user assoc skey val))))) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index a6c6cb8b33..b5f24ac8b1 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -48,7 +48,7 @@ [app.util.color :as uc] [app.util.i18n :refer [tr]] [app.util.router :as rt] - [app.util.storage :as s] + [app.util.storage :as storage] [app.util.time :as dt] [beicon.v2.core :as rx] [cuerdas.core :as str] @@ -147,7 +147,7 @@ ptk/EffectEvent (effect [_ 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 (ptk/reify ::clear-color-for-rename diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 8c3a8a6da1..98cee17f1c 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -20,7 +20,7 @@ [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] - [app.util.storage :as sto] + [app.util.storage :as storage] [beicon.v2.core :as rx] [rumext.v2 :as mf])) @@ -198,7 +198,7 @@ :else (do - (swap! sto/storage assoc ::email (:email params)) + (swap! storage/user assoc ::email (:email params)) (st/emit! (rt/nav :auth-register-success))))))) on-error @@ -264,7 +264,7 @@ (mf/defc register-success-page {::mf/props :obj} [{: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)} (when-not (:hide-logo params) [:h1 {:class (stl/css :logo-container)} diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index 14bff15591..fcf8d15dd4 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -16,7 +16,7 @@ [app.main.store :as st] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] - [app.util.storage :refer [storage]] + [app.util.storage :as storage] [app.util.timers :as ts] [app.util.webapi :as wapi] [beicon.v2.core :as rx] @@ -294,7 +294,7 @@ `key` for new values." [key default] (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*) stream (mf/with-memo [id] (->> mbc/stream @@ -304,7 +304,7 @@ (mf/with-effect [state key id] (mbc/emit! id key state) - (swap! storage assoc key state)) + (swap! storage/user assoc key state)) (use-stream stream (partial reset! state*)) diff --git a/frontend/src/app/main/ui/hooks/resize.cljs b/frontend/src/app/main/ui/hooks/resize.cljs index 7b57c1345a..5a42160543 100644 --- a/frontend/src/app/main/ui/hooks/resize.cljs +++ b/frontend/src/app/main/ui/hooks/resize.cljs @@ -14,7 +14,7 @@ [app.main.ui.context :as ctx] [app.main.ui.hooks :as hooks] [app.util.dom :as dom] - [app.util.storage :refer [storage]] + [app.util.storage :as storage] [rumext.v2 :as mf])) (log/set-level! :warn) @@ -23,7 +23,7 @@ (defn- get-initial-state [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))) (defn- update-persistent-state @@ -81,7 +81,7 @@ start-size (mf/ref-val start-size-ref) new-size (-> (+ start-size delta) (max min-val) (min max-val))] (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 (mf/use-fn @@ -89,7 +89,7 @@ (fn [new-size] (let [new-size (mth/clamp new-size min-val max-val)] (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] (when on-change-size diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 167c641669..06aecacc84 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -11,7 +11,7 @@ [app.common.logging :as log] [app.config :as cfg] [app.util.globals :as globals] - [app.util.storage :refer [storage]] + [app.util.storage :as storage] [cuerdas.core :as str] [goog.object :as gobj] [okulary.core :as l] @@ -76,7 +76,7 @@ cfg/default-language)))) (defonce translations #js {}) -(defonce locale (l/atom (or (get @storage ::locale) +(defonce locale (l/atom (or (get storage/global ::locale) (autodetect)))) (defn init! @@ -93,7 +93,7 @@ (if (or (nil? lname) (str/empty? lname)) (let [lname (autodetect)] - (swap! storage dissoc ::locale) + (swap! storage/global dissoc ::locale) (reset! locale lname)) (let [supported (into #{} (map :value supported-locales)) lname (loop [locales (seq (parse-locale lname))] @@ -102,7 +102,7 @@ locale (recur (rest locales))) cfg/default-language))] - (swap! storage assoc ::locale lname) + (swap! storage/global assoc ::locale lname) (reset! locale lname)))) (deftype C [val] diff --git a/frontend/src/app/util/storage.cljs b/frontend/src/app/util/storage.cljs index df08e0eac3..b3dbb5fb4a 100644 --- a/frontend/src/app/util/storage.cljs +++ b/frontend/src/app/util/storage.cljs @@ -10,7 +10,8 @@ [app.common.transit :as t] [app.util.functions :as fns] [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 ;; importing the code as a library: Failed to read the 'localStorage' @@ -19,26 +20,27 @@ (ex/ignoring (unchecked-get g/global "localStorage"))) (defn- encode-key - [k] + [prefix k] (assert (keyword? k) "key must be keyword") (let [kns (namespace k) kn (name k)] - (str "penpot:" kns "/" kn))) + (str prefix ":" kns "/" kn))) (defn- decode-key - [k] - (when (str/starts-with? k "penpot:") - (let [k (subs k 7)] + [prefix k] + (when (str/starts-with? k prefix) + (let [l (+ (count prefix) 1) + k (subs k l)] (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] + [prefix result index] (try (let [key (.key ^js local-storage index) - key' (decode-key key)] + key' (decode-key prefix key)] (if key' (let [val (.getItem ^js local-storage key)] (assoc! result key' (t/decode-str val))) @@ -46,39 +48,95 @@ (catch :default _ result))) -(defn- load - [] - (when (some? local-storage) +(defn- load-data + [prefix] + (if (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)))))) + (lookup-by-index prefix result index)) + (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* - [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))))) + on-change* + (fn [curr-state] + (let [prev-state (unchecked-get last-data "content")] + (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 prefix key) (t/encode-str curr-val)) + (.removeItem ^js local-storage (encode-key prefix key)))))) + (into #{} (concat (keys curr-state) + (keys prev-state)))) + (finally + (unchecked-set last-data "content" curr-state))))) -(defonce on-change - (fns/debounce on-change* 2000)) + on-change + (fns/debounce on-change* 2000)] -(defonce storage (atom latest-state)) -(add-watch storage :persistence - (fn [_ _ _ curr-state] - (on-change curr-state))) + (reify + IAtom + + 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")) diff --git a/frontend/src/app/util/theme.cljs b/frontend/src/app/util/theme.cljs index a31c79f832..28c17efe58 100644 --- a/frontend/src/app/util/theme.cljs +++ b/frontend/src/app/util/theme.cljs @@ -10,11 +10,11 @@ (:require [app.config :as cfg] [app.util.dom :as dom] - [app.util.storage :refer [storage]] + [app.util.storage :as storage] [beicon.v2.core :as rx] [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 themes #js {}) @@ -27,7 +27,7 @@ (when (not= theme v) (when-some [el (dom/get-element "theme")] (set! (.-href el) (str "css/main-" v ".css"))) - (swap! storage assoc ::theme v) + (swap! storage/global assoc ::theme v) (set! theme v) (rx/push! theme-sub v)))