mirror of
https://github.com/penpot/penpot.git
synced 2026-10-03 01:06:14 +00:00
⚡ Speed up sidebar menus for large selections (#12028)
Reuse extracted colors for unchanged shapes and cache the text attrs read from each text content.
This commit is contained in:
parent
c148ba5267
commit
822dbd6517
@ -1351,6 +1351,43 @@
|
||||
:shape-id (:shape-id fill)
|
||||
:index (:index fill)})))
|
||||
|
||||
(defn shape-colors
|
||||
[shape file-id libraries]
|
||||
(let [applied-tokens (:applied-tokens shape)
|
||||
applied-fill (get applied-tokens :fill)
|
||||
applied-stroke (get applied-tokens :stroke-color)
|
||||
fills (:fills shape)
|
||||
strokes (:strokes shape)
|
||||
shadows (:shadow shape)
|
||||
shape-id (:id shape)
|
||||
|
||||
fills* (map-indexed
|
||||
(fn [index fill]
|
||||
(cond-> (assoc fill :shape-id shape-id :index index)
|
||||
(and (zero? index) applied-fill)
|
||||
(assoc :has-token-applied true
|
||||
:token-name applied-fill)))
|
||||
fills)
|
||||
|
||||
strokes* (map-indexed
|
||||
(fn [index stroke]
|
||||
(cond-> (assoc stroke :shape-id shape-id :index index)
|
||||
(and (zero? index) applied-stroke)
|
||||
(assoc :has-token-applied true
|
||||
:token-name applied-stroke)))
|
||||
strokes)
|
||||
|
||||
shadows* (map-indexed #(assoc %2 :shape-id shape-id :index %1) shadows)]
|
||||
(if (= :text (:type shape))
|
||||
(-> []
|
||||
(into (keep #(stroke->color-att % file-id libraries)) strokes*)
|
||||
(into (map #(shadow->color-attr % file-id libraries)) shadows*)
|
||||
(into (extract-text-colors shape file-id libraries)))
|
||||
(-> []
|
||||
(into (keep #(fill->color-att % file-id libraries)) fills*)
|
||||
(into (keep #(stroke->color-att % file-id libraries)) strokes*)
|
||||
(into (map #(shadow->color-attr % file-id libraries)) shadows*)))))
|
||||
|
||||
(defn extract-all-colors
|
||||
"Extracts color information from a list of shapes, including fills, strokes, and shadows.
|
||||
If a shape has applied tokens of type :fill or :stroke-color, the first fill or stroke
|
||||
@ -1366,41 +1403,4 @@
|
||||
Returns:
|
||||
A vector of color attribute maps with metadata for each shape."
|
||||
[shapes file-id libraries]
|
||||
(reduce
|
||||
(fn [result shape]
|
||||
(let [applied-tokens (:applied-tokens shape)
|
||||
applied-fill (get applied-tokens :fill)
|
||||
applied-stroke (get applied-tokens :stroke-color)
|
||||
fills (:fills shape)
|
||||
strokes (:strokes shape)
|
||||
shadows (:shadow shape)
|
||||
shape-id (:id shape)
|
||||
|
||||
fills* (map-indexed
|
||||
(fn [index fill]
|
||||
(cond-> (assoc fill :shape-id shape-id :index index)
|
||||
(and (zero? index) applied-fill)
|
||||
(assoc :has-token-applied true
|
||||
:token-name applied-fill)))
|
||||
fills)
|
||||
|
||||
strokes* (map-indexed
|
||||
(fn [index stroke]
|
||||
(cond-> (assoc stroke :shape-id shape-id :index index)
|
||||
(and (zero? index) applied-stroke)
|
||||
(assoc :has-token-applied true
|
||||
:token-name applied-stroke)))
|
||||
strokes)
|
||||
|
||||
shadows* (map-indexed #(assoc %2 :shape-id shape-id :index %1) shadows)]
|
||||
(if (= :text (:type shape))
|
||||
(-> result
|
||||
(into (keep #(stroke->color-att % file-id libraries)) strokes*)
|
||||
(into (map #(shadow->color-attr % file-id libraries)) shadows*)
|
||||
(into (extract-text-colors shape file-id libraries)))
|
||||
(-> result
|
||||
(into (keep #(fill->color-att % file-id libraries)) fills*)
|
||||
(into (keep #(stroke->color-att % file-id libraries)) strokes*)
|
||||
(into (map #(shadow->color-attr % file-id libraries)) shadows*)))))
|
||||
[]
|
||||
shapes))
|
||||
(into [] (mapcat #(shape-colors % file-id libraries)) shapes))
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
(:require-macros [app.main.style :as stl])
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.types.library :as ctl]
|
||||
[app.main.data.workspace.colors :as dwc]
|
||||
[app.main.data.workspace.selection :as dws]
|
||||
[app.main.data.workspace.tokens.application :as dwta]
|
||||
@ -17,6 +18,17 @@
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(defn- cached-shape-colors
|
||||
[prev-cache cache libraries-colors shape file-id libraries]
|
||||
(let [inputs [(:type shape) (:fills shape) (:strokes shape) (:shadow shape)
|
||||
(:applied-tokens shape) (:content shape) file-id libraries-colors]
|
||||
[cached-inputs cached] (when prev-cache (.get prev-cache (:id shape)))
|
||||
colors (if (and cached-inputs (every? true? (map identical? cached-inputs inputs)))
|
||||
cached
|
||||
(dwc/shape-colors shape file-id libraries))]
|
||||
(.set cache (:id shape) [inputs colors])
|
||||
colors))
|
||||
|
||||
(defn- prepare-colors
|
||||
"Prepares and groups extracted color information from shapes.
|
||||
Input:
|
||||
@ -62,8 +74,11 @@
|
||||
This structure allows fast lookups of all shapes using the same visual color,
|
||||
regardless of whether it comes from local fills, strokes or shadow-colors."
|
||||
|
||||
[shapes file-id libraries]
|
||||
(let [data (into [] (remove nil?) (dwc/extract-all-colors shapes file-id libraries))
|
||||
[prev-cache cache libraries-colors shapes file-id libraries]
|
||||
(let [data (into []
|
||||
(comp (mapcat #(cached-shape-colors prev-cache cache libraries-colors % file-id libraries))
|
||||
(remove nil?))
|
||||
shapes)
|
||||
groups (d/group-by :attrs #(dissoc % :attrs) data)
|
||||
|
||||
;; Unique color attribute maps
|
||||
@ -98,9 +113,19 @@
|
||||
(mf/defc color-selection-menu*
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]}
|
||||
[{:keys [shapes file-id libraries]}]
|
||||
(let [{:keys [groups library-colors colors token-colors]}
|
||||
(let [cache* (mf/use-ref nil)
|
||||
libraries-colors* (mf/use-ref nil)
|
||||
|
||||
{:keys [groups library-colors colors token-colors]}
|
||||
(mf/with-memo [file-id shapes libraries]
|
||||
(prepare-colors shapes file-id libraries))
|
||||
(let [current (mapv (fn [[id library]] [id (ctl/get-colors (:data library))]) libraries)
|
||||
previous (mf/ref-val libraries-colors*)
|
||||
libraries-colors (if (= previous current) previous current)
|
||||
cache (js/Map.)
|
||||
result (prepare-colors (mf/ref-val cache*) cache libraries-colors shapes file-id libraries)]
|
||||
(mf/set-ref-val! libraries-colors* libraries-colors)
|
||||
(mf/set-ref-val! cache* cache)
|
||||
result))
|
||||
|
||||
open* (mf/use-state true)
|
||||
open? (deref open*)
|
||||
|
||||
@ -262,6 +262,20 @@
|
||||
(assoc :layout-padding (merge-layout-padding values shape-values)))]
|
||||
(promote-simple-layout-padding-type merged-values)))
|
||||
|
||||
(def ^:private content-attrs-cache (js/WeakMap.))
|
||||
|
||||
(defn- get-content-attrs
|
||||
"Moving a text keeps its `:content`, so the attrs read from it are cached by content."
|
||||
[{:keys [content] :as shape} attrs]
|
||||
(if (some? content)
|
||||
(let [[cached-attrs cached] (.get content-attrs-cache content)]
|
||||
(if (identical? cached-attrs attrs)
|
||||
cached
|
||||
(let [result (attrs/get-text-attrs-multi shape txt/default-text-attrs attrs)]
|
||||
(.set content-attrs-cache content [attrs result])
|
||||
result)))
|
||||
(attrs/get-text-attrs-multi shape txt/default-text-attrs attrs)))
|
||||
|
||||
(defn get-attrs*
|
||||
"Given a group of attributes that we want to extract and the shapes to extract them from
|
||||
returns a list of tuples [id, values] with the extracted properties for the shapes that
|
||||
@ -359,7 +373,7 @@
|
||||
(let [shape-attrs (select-keys shape attrs)
|
||||
|
||||
content-attrs
|
||||
(attrs/get-text-attrs-multi shape txt/default-text-attrs attrs)
|
||||
(get-content-attrs shape attrs)
|
||||
|
||||
new-values
|
||||
(-> values
|
||||
|
||||
@ -117,6 +117,7 @@
|
||||
[frontend-tests.ui.shortcuts-labels-test]
|
||||
[frontend-tests.ui.sidebar-scroll-test]
|
||||
[frontend-tests.ui.stroke-menu-test]
|
||||
[frontend-tests.ui.text-attrs-multiple-test]
|
||||
[frontend-tests.util-clipboard-test]
|
||||
[frontend-tests.util-object-test]
|
||||
[frontend-tests.util-queue-test]
|
||||
@ -254,6 +255,7 @@
|
||||
'frontend-tests.ui.shortcuts-labels-test
|
||||
'frontend-tests.ui.sidebar-scroll-test
|
||||
'frontend-tests.ui.stroke-menu-test
|
||||
'frontend-tests.ui.text-attrs-multiple-test
|
||||
'frontend-tests.util-clipboard-test
|
||||
'frontend-tests.util-object-test
|
||||
'frontend-tests.util-queue-test
|
||||
|
||||
@ -0,0 +1,40 @@
|
||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) KALEIDOS SUBSIDIARY SL
|
||||
|
||||
(ns frontend-tests.ui.text-attrs-multiple-test
|
||||
(:require
|
||||
[app.main.ui.workspace.sidebar.options.shapes.multiple :as multiple]
|
||||
[cljs.test :as t :include-macros true]))
|
||||
|
||||
(defn- content
|
||||
[font-family]
|
||||
{:type "root"
|
||||
:children [{:type "paragraph-set"
|
||||
:children [{:type "paragraph"
|
||||
:children [{:text "a" :font-family font-family}]}]}]})
|
||||
|
||||
(def ^:private inter (content "Inter"))
|
||||
|
||||
(def ^:private objects
|
||||
{:board-1 {:id :board-1 :type :frame :shapes [:inner :text-1]}
|
||||
:inner {:id :inner :type :frame :shapes [:text-2]}
|
||||
:board-2 {:id :board-2 :type :frame :shapes [:text-3 :rect]}
|
||||
:text-1 {:id :text-1 :type :text :content inter :applied-tokens {:typography "body"}}
|
||||
:text-2 {:id :text-2 :type :text :content inter :applied-tokens {:typography "body"}}
|
||||
:text-3 {:id :text-3 :type :text :content (content "Roboto") :applied-tokens {:typography "body"}}
|
||||
:rect {:id :rect :type :rect}})
|
||||
|
||||
(defn- text-attrs
|
||||
[labels objects]
|
||||
(multiple/get-attrs* (map objects labels) objects :text))
|
||||
|
||||
(t/deftest boards-read-nested-text-values
|
||||
(let [[ids values tokens] (text-attrs [:board-1] objects)]
|
||||
(t/is (= [:text-2 :text-1] ids))
|
||||
(t/is (= "Inter" (:font-family values)))
|
||||
(t/is (= "body" (:typography tokens))))
|
||||
(let [[_ values] (text-attrs [:board-1 :board-2] objects)]
|
||||
(t/is (= :multiple (:font-family values)))))
|
||||
Loading…
x
Reference in New Issue
Block a user