mirror of
https://github.com/penpot/penpot.git
synced 2025-02-08 08:09:14 -05:00
Merge pull request #5056 from penpot/niwinz-refactor-recent-colors
♻️ Refactor recent colors and local storage abstraction
This commit is contained in:
commit
ea8febdb7d
10 changed files with 161 additions and 106 deletions
|
@ -190,10 +190,9 @@
|
|||
[:type [:= :del-color]]
|
||||
[:id ::sm/uuid]]]
|
||||
|
||||
;; DEPRECATED: remove before 2.3
|
||||
[:add-recent-color
|
||||
[:map {:title "AddRecentColorChange"}
|
||||
[:type [:= :add-recent-color]]
|
||||
[:color ::ctc/recent-color]]]
|
||||
[:map {:title "AddRecentColorChange"}]]
|
||||
|
||||
[:add-media
|
||||
[:map {:title "AddMediaChange"}
|
||||
|
@ -656,18 +655,10 @@
|
|||
[data {:keys [id]}]
|
||||
(ctcl/delete-color data id))
|
||||
|
||||
;; DEPRECATED: remove before 2.3
|
||||
(defmethod process-change :add-recent-color
|
||||
[data {:keys [color]}]
|
||||
;; Moves the color to the top of the list and then truncates up to 15
|
||||
(update
|
||||
data
|
||||
:recent-colors
|
||||
(fn [rc]
|
||||
(let [rc (->> rc (d/removev (partial ctc/eq-recent-color? color)))
|
||||
rc (-> rc (conj color))]
|
||||
(cond-> rc
|
||||
(> (count rc) 15)
|
||||
(subvec 1))))))
|
||||
[data _]
|
||||
data)
|
||||
|
||||
;; -- Media
|
||||
|
||||
|
|
|
@ -607,13 +607,6 @@
|
|||
(reduce resize-parent changes all-parents)))
|
||||
|
||||
;; Library changes
|
||||
|
||||
(defn add-recent-color
|
||||
[changes color]
|
||||
(-> changes
|
||||
(update :redo-changes conj {:type :add-recent-color :color color})
|
||||
(apply-changes-local)))
|
||||
|
||||
(defn add-color
|
||||
[changes color]
|
||||
(-> changes
|
||||
|
|
|
@ -107,17 +107,16 @@
|
|||
[::sm/contains-any {:strict true} [:color :gradient :image]]])
|
||||
|
||||
(sm/register! ::rgb-color type:rgb-color)
|
||||
|
||||
(sm/register! ::color schema:color)
|
||||
(sm/register! ::gradient schema:gradient)
|
||||
(sm/register! ::image-color schema:image-color)
|
||||
(sm/register! ::recent-color schema:recent-color)
|
||||
|
||||
(def check-color!
|
||||
(sm/check-fn schema:color))
|
||||
(def valid-color?
|
||||
(sm/lazy-validator schema:color))
|
||||
|
||||
(def check-recent-color!
|
||||
(sm/check-fn schema:recent-color))
|
||||
(def valid-recent-color?
|
||||
(sm/lazy-validator schema:recent-color))
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;; HELPERS
|
||||
|
@ -392,13 +391,22 @@
|
|||
|
||||
(process-shape-colors shape sync-color)))
|
||||
|
||||
(defn eq-recent-color?
|
||||
(defn- eq-recent-color?
|
||||
[c1 c2]
|
||||
(or (= c1 c2)
|
||||
(and (some? (:color c1))
|
||||
(some? (:color c2))
|
||||
(= (:color c1) (:color c2)))))
|
||||
|
||||
(defn add-recent-color
|
||||
"Moves the color to the top of the list and then truncates up to 15"
|
||||
[state file-id color]
|
||||
(update state file-id (fn [colors]
|
||||
(let [colors (d/removev (partial eq-recent-color? color) colors)
|
||||
colors (conj colors color)]
|
||||
(cond-> colors
|
||||
(> (count colors) 15)
|
||||
(subvec 1))))))
|
||||
|
||||
(defn stroke->color-att
|
||||
[stroke file-id shared-libs]
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
[app.main.repo :as rp]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :refer [storage]]
|
||||
[app.util.storage :as s]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
|
@ -51,14 +51,14 @@
|
|||
|
||||
(defn get-current-team-id
|
||||
[profile]
|
||||
(let [team-id (::current-team-id @storage)]
|
||||
(let [team-id (::current-team-id @s/storage)]
|
||||
(or team-id (:default-team-id profile))))
|
||||
|
||||
(defn set-current-team!
|
||||
[team-id]
|
||||
(if (nil? team-id)
|
||||
(swap! storage dissoc ::current-team-id)
|
||||
(swap! storage assoc ::current-team-id team-id)))
|
||||
(swap! s/storage dissoc ::current-team-id)
|
||||
(swap! s/storage assoc ::current-team-id team-id)))
|
||||
|
||||
;; --- EVENT: fetch-teams
|
||||
|
||||
|
@ -78,9 +78,9 @@
|
|||
;; if not, dissoc it from storage.
|
||||
|
||||
(let [ids (into #{} (map :id) teams)]
|
||||
(when-let [ctid (::current-team-id @storage)]
|
||||
(when-let [ctid (::current-team-id @s/storage)]
|
||||
(when-not (contains? ids ctid)
|
||||
(swap! storage dissoc ::current-team-id)))))))
|
||||
(swap! s/storage dissoc ::current-team-id)))))))
|
||||
|
||||
(defn fetch-teams
|
||||
[]
|
||||
|
@ -131,10 +131,10 @@
|
|||
(effect [_ state _]
|
||||
(let [profile (:profile state)
|
||||
email (:email profile)
|
||||
previous-profile (:profile @storage)
|
||||
previous-profile (:profile @s/storage)
|
||||
previous-email (:email previous-profile)]
|
||||
(when profile
|
||||
(swap! storage assoc :profile profile)
|
||||
(swap! s/storage assoc :profile profile)
|
||||
(i18n/set-locale! (:lang profile))
|
||||
(when (not= previous-email email)
|
||||
(set-current-team! nil)))))))
|
||||
|
@ -320,7 +320,7 @@
|
|||
ptk/EffectEvent
|
||||
(effect [_ _ _]
|
||||
;; We prefer to keek some stuff in the storage like the current-team-id and the profile
|
||||
(set-current-team! nil)))))
|
||||
(swap! s/storage (constantly {}))))))
|
||||
|
||||
(defn logout
|
||||
([] (logout {}))
|
||||
|
|
|
@ -79,6 +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.timers :as tm]
|
||||
[app.util.webapi :as wapi]
|
||||
[beicon.v2.core :as rx]
|
||||
|
@ -335,6 +336,7 @@
|
|||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state
|
||||
:recent-colors (:recent-colors @storage)
|
||||
:workspace-ready? false
|
||||
:current-file-id file-id
|
||||
:current-project-id project-id
|
||||
|
|
|
@ -48,6 +48,7 @@
|
|||
[app.util.color :as uc]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.router :as rt]
|
||||
[app.util.storage :as s]
|
||||
[app.util.time :as dt]
|
||||
[beicon.v2.core :as rx]
|
||||
[cuerdas.core :as str]
|
||||
|
@ -132,16 +133,21 @@
|
|||
|
||||
(defn add-recent-color
|
||||
[color]
|
||||
|
||||
(dm/assert!
|
||||
"expected valid recent color map"
|
||||
(ctc/check-recent-color! color))
|
||||
(ctc/valid-recent-color? color))
|
||||
|
||||
(ptk/reify ::add-recent-color
|
||||
ptk/WatchEvent
|
||||
(watch [it _ _]
|
||||
(let [changes (-> (pcb/empty-changes it)
|
||||
(pcb/add-recent-color color))]
|
||||
(rx/of (dch/commit-changes changes))))))
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [file-id (:current-file-id state)]
|
||||
(update state :recent-colors ctc/add-recent-color file-id color)))
|
||||
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [recent-colors (:recent-colors state)]
|
||||
(swap! s/storage assoc :recent-colors recent-colors)))))
|
||||
|
||||
(def clear-color-for-rename
|
||||
(ptk/reify ::clear-color-for-rename
|
||||
|
@ -168,8 +174,11 @@
|
|||
|
||||
(dm/assert!
|
||||
"expected valid parameters"
|
||||
(and (ctc/check-color! color)
|
||||
(uuid? file-id)))
|
||||
(ctc/valid-color? color))
|
||||
|
||||
(dm/assert!
|
||||
"expected file-id"
|
||||
(uuid? file-id))
|
||||
|
||||
(ptk/reify ::update-color
|
||||
ptk/WatchEvent
|
||||
|
|
|
@ -236,9 +236,10 @@
|
|||
=))
|
||||
|
||||
(def workspace-recent-colors
|
||||
(l/derived (fn [data]
|
||||
(get data :recent-colors []))
|
||||
workspace-data))
|
||||
(l/derived (fn [state]
|
||||
(when-let [file-id (:current-file-id state)]
|
||||
(dm/get-in state [:recent-colors file-id])))
|
||||
st/state))
|
||||
|
||||
(def workspace-recent-fonts
|
||||
(l/derived (fn [data]
|
||||
|
|
|
@ -294,19 +294,21 @@
|
|||
`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 key default))
|
||||
state (deref state*)
|
||||
stream (mf/with-memo [id]
|
||||
(->> mbc/stream
|
||||
(rx/filter #(not= (:id %) id))
|
||||
(rx/filter #(= (:type %) key))
|
||||
(rx/map deref)))]
|
||||
|
||||
(mf/with-effect [@state key id]
|
||||
(mbc/emit! id key @state)
|
||||
(swap! storage assoc key @state))
|
||||
(mf/with-effect [state key id]
|
||||
(mbc/emit! id key state)
|
||||
(swap! storage assoc key state))
|
||||
|
||||
(use-stream stream (partial reset! state))
|
||||
state))
|
||||
(use-stream stream (partial reset! state*))
|
||||
|
||||
state*))
|
||||
|
||||
(defonce ^:private intersection-subject (rx/subject))
|
||||
(defonce ^:private intersection-observer
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
(ns app.main.ui.hooks.resize
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.logging :as log]
|
||||
|
@ -20,6 +21,15 @@
|
|||
|
||||
(def last-resize-type nil)
|
||||
|
||||
(defn- get-initial-state
|
||||
[initial file-id key]
|
||||
(let [saved (dm/get-in @storage [::state file-id key])]
|
||||
(d/nilv saved initial)))
|
||||
|
||||
(defn- update-persistent-state
|
||||
[data file-id key size]
|
||||
(update-in data [::state file-id] assoc key size))
|
||||
|
||||
(defn set-resize-type! [type]
|
||||
(set! last-resize-type type))
|
||||
|
||||
|
@ -28,26 +38,28 @@
|
|||
(use-resize-hook key initial min-val max-val axis negate? resize-type nil))
|
||||
|
||||
([key initial min-val max-val axis negate? resize-type on-change-size]
|
||||
(let [current-file-id (mf/use-ctx ctx/current-file-id)
|
||||
size-state (mf/use-state (or (get-in @storage [::saved-resize current-file-id key]) initial))
|
||||
parent-ref (mf/use-ref nil)
|
||||
(let [file-id (mf/use-ctx ctx/current-file-id)
|
||||
|
||||
dragging-ref (mf/use-ref false)
|
||||
current-size* (mf/use-state #(get-initial-state initial file-id key))
|
||||
current-size (deref current-size*)
|
||||
|
||||
parent-ref (mf/use-ref nil)
|
||||
dragging-ref (mf/use-ref false)
|
||||
start-size-ref (mf/use-ref nil)
|
||||
start-ref (mf/use-ref nil)
|
||||
start-ref (mf/use-ref nil)
|
||||
|
||||
on-pointer-down
|
||||
(mf/use-callback
|
||||
(mf/deps @size-state)
|
||||
(mf/use-fn
|
||||
(mf/deps current-size)
|
||||
(fn [event]
|
||||
(dom/capture-pointer event)
|
||||
(mf/set-ref-val! start-size-ref @size-state)
|
||||
(mf/set-ref-val! start-size-ref current-size)
|
||||
(mf/set-ref-val! dragging-ref true)
|
||||
(mf/set-ref-val! start-ref (dom/get-client-position event))
|
||||
(set! last-resize-type resize-type)))
|
||||
|
||||
on-lost-pointer-capture
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(dom/release-pointer event)
|
||||
(mf/set-ref-val! start-size-ref nil)
|
||||
|
@ -56,40 +68,39 @@
|
|||
(set! last-resize-type nil)))
|
||||
|
||||
on-pointer-move
|
||||
(mf/use-callback
|
||||
(mf/deps min-val max-val negate?)
|
||||
(mf/use-fn
|
||||
(mf/deps min-val max-val negate? file-id key)
|
||||
(fn [event]
|
||||
(when (mf/ref-val dragging-ref)
|
||||
(let [start (mf/ref-val start-ref)
|
||||
pos (dom/get-client-position event)
|
||||
pos (dom/get-client-position event)
|
||||
delta (-> (gpt/to-vec start pos)
|
||||
(cond-> negate? gpt/negate)
|
||||
(get axis))
|
||||
|
||||
start-size (mf/ref-val start-size-ref)
|
||||
new-size (-> (+ start-size delta) (max min-val) (min max-val))]
|
||||
(reset! size-state new-size)
|
||||
(swap! storage assoc-in [::saved-resize current-file-id key] new-size)
|
||||
(when on-change-size (on-change-size new-size))))))
|
||||
(reset! current-size* new-size)
|
||||
(swap! storage update-persistent-state file-id key new-size)))))
|
||||
|
||||
set-size
|
||||
(mf/use-callback
|
||||
(mf/deps on-change-size)
|
||||
(mf/use-fn
|
||||
(mf/deps on-change-size file-id key)
|
||||
(fn [new-size]
|
||||
(let [new-size (mth/clamp new-size min-val max-val)]
|
||||
(reset! size-state new-size)
|
||||
(swap! storage assoc-in [::saved-resize current-file-id key] new-size)
|
||||
(when on-change-size (on-change-size new-size)))))]
|
||||
(reset! current-size* new-size)
|
||||
(swap! storage update-persistent-state file-id key new-size))))]
|
||||
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(when on-change-size (on-change-size @size-state))))
|
||||
(mf/with-effect [on-change-size current-size]
|
||||
(when on-change-size
|
||||
(on-change-size current-size)))
|
||||
|
||||
{:on-pointer-down on-pointer-down
|
||||
:on-lost-pointer-capture on-lost-pointer-capture
|
||||
:on-pointer-move on-pointer-move
|
||||
:parent-ref parent-ref
|
||||
:set-size set-size
|
||||
:size @size-state})))
|
||||
:size current-size})))
|
||||
|
||||
(defn use-resize-observer
|
||||
[callback]
|
||||
|
|
|
@ -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…
Add table
Reference in a new issue