0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-28 15:41:25 -05:00

🎉 Synchronize library colors in all parts of a shape

This commit is contained in:
Andrés Moya 2022-04-13 17:03:10 +02:00
parent 7581230b6e
commit 02157cbeb9
4 changed files with 249 additions and 128 deletions

View file

@ -286,37 +286,38 @@
update-shape
(fn [changes id]
(let [old-obj (get objects id)
new-obj (update-fn old-obj)
new-obj (update-fn old-obj)]
(if (= old-obj new-obj)
changes
(let [attrs (or attrs (d/concat-set (keys old-obj) (keys new-obj)))
attrs (or attrs (d/concat-set (keys old-obj) (keys new-obj)))
{rops :rops uops :uops}
(reduce #(generate-operation %1 %2 old-obj new-obj ignore-geometry?)
{:rops [] :uops []}
attrs)
{rops :rops uops :uops}
(reduce #(generate-operation %1 %2 old-obj new-obj ignore-geometry?)
{:rops [] :uops []}
attrs)
uops (cond-> uops
(seq uops)
(d/preconj {:type :set-touched :touched (:touched old-obj)}))
uops (cond-> uops
(seq uops)
(d/preconj {:type :set-touched :touched (:touched old-obj)}))
change (cond-> {:type :mod-obj
:id id}
change (cond-> {:type :mod-obj
:id id}
(some? page-id)
(assoc :page-id page-id)
(some? page-id)
(assoc :page-id page-id)
(some? component-id)
(assoc :component-id component-id))]
(some? component-id)
(assoc :component-id component-id))]
(cond-> changes
(seq rops)
(update :redo-changes conj (assoc change :operations rops))
(cond-> changes
(seq rops)
(update :redo-changes conj (assoc change :operations rops))
(seq uops)
(update :undo-changes d/preconj (assoc change :operations uops)))))))]
(seq uops)
(update :undo-changes d/preconj (assoc change :operations uops)))))]
(-> (reduce update-shape changes ids)
(apply-changes-local)))))
(-> (reduce update-shape changes ids)
(apply-changes-local)))))
(defn remove-objects
[changes ids]

View file

@ -6,8 +6,10 @@
(ns app.common.spec.color
(:require
[app.common.spec :as us]
[clojure.spec.alpha :as s]))
[app.common.data :as d]
[app.common.spec :as us]
[app.common.text :as txt]
[clojure.spec.alpha :as s]))
;; TODO: waiting clojure 1.11 to rename this all :internal.stuff to a
;; more consistent name.
@ -46,7 +48,7 @@
:internal.gradient/width
:internal.gradient/stops]))
;;; --- COLORS
;; --- COLORS
(s/def :internal.color/name string?)
(s/def :internal.color/path (s/nilable string?))
@ -54,6 +56,15 @@
(s/def :internal.color/color (s/nilable string?))
(s/def :internal.color/opacity (s/nilable ::us/safe-number))
(s/def :internal.color/gradient (s/nilable ::gradient))
(s/def :internal.color/ref-id uuid?)
(s/def :internal.color/ref-file uuid?)
(s/def ::shape-color
(s/keys :req-un [:us/color
:internal.color/opacity]
:opt-un [:internal.color/gradient
:internal.color/ref-id
:internal.color/ref-file]))
(s/def ::color
(s/keys :opt-un [::id
@ -70,3 +81,197 @@
:internal.color/opacity
:internal.color/gradient]))
;; --- Helpers for color in different parts of a shape
;; fill
(defn fill->shape-color
[fill]
(d/without-nils {:color (:fill-color fill)
:opacity (:fill-opacity fill)
:gradient (:fill-color-gradient fill)
:ref-id (:fill-color-ref-id fill)
:ref-file (:fill-color-ref-file fill)}))
(defn set-fill-color
[shape position color opacity gradient]
(update-in shape [:fills position]
(fn [fill]
(d/without-nils (assoc fill
:fill-color color
:fill-opacity opacity
:fill-color-gradient gradient)))))
(defn detach-fill-color
[shape position]
(-> shape
(d/dissoc-in [:fills position :fill-color-ref-id])
(d/dissoc-in [:fills position :fill-color-ref-file])))
;; stroke
(defn stroke->shape-color
[stroke]
(d/without-nils {:color (:stroke-color stroke)
:opacity (:stroke-opacity stroke)
:gradient (:stroke-color-gradient stroke)
:ref-id (:stroke-color-ref-id stroke)
:ref-file (:stroke-color-ref-file stroke)}))
(defn set-stroke-color
[shape position color opacity gradient]
(update-in shape [:strokes position]
(fn [stroke]
(d/without-nils (assoc stroke
:stroke-color color
:stroke-opacity opacity
:stroke-color-gradient gradient)))))
(defn detach-stroke-color
[shape position]
(-> shape
(d/dissoc-in [:strokes position :stroke-color-ref-id])
(d/dissoc-in [:strokes position :stroke-color-ref-file])))
;; shadow
(defn shadow->shape-color
[shadow]
(d/without-nils {:color (-> shadow :color :color)
:opacity (-> shadow :color :opacity)
:gradient (-> shadow :color :gradient)
:ref-id (-> shadow :color :id)
:ref-file (-> shadow :color :file-id)}))
(defn set-shadow-color
[shape position color opacity gradient]
(update-in shape [:shadow position :color]
(fn [shadow-color]
(d/without-nils (assoc shadow-color
:color color
:opacity opacity
:gradient gradient)))))
(defn detach-shadow-color
[shape position]
(-> shape
(d/dissoc-in [:shadow position :color :id])
(d/dissoc-in [:shadow position :color :file-id])))
;; grid
(defn grid->shape-color
[grid]
(d/without-nils {:color (-> grid :params :color :color)
:opacity (-> grid :params :color :opacity)
:gradient (-> grid :params :color :gradient)
:ref-id (-> grid :params :color :id)
:ref-file (-> grid :params :color :file-id)}))
(defn set-grid-color
[shape position color opacity gradient]
(update-in shape [:grids position :params :color]
(fn [grid-color]
(d/without-nils (assoc grid-color
:color color
:opacity opacity
:gradient gradient)))))
(defn detach-grid-color
[shape position]
(-> shape
(d/dissoc-in [:grids position :params :color :id])
(d/dissoc-in [:grids position :params :color :file-id])))
;; --- Helpers for all colors in a shape
(defn get-text-node-colors
"Get all colors used by a node of a text shape"
[node]
(concat (map fill->shape-color (:fills node))
(map stroke->shape-color (:strokes node))))
(defn get-all-colors
"Get all colors used by a shape, in any section."
[shape]
(concat (map fill->shape-color (:fills shape))
(map stroke->shape-color (:strokes shape))
(map shadow->shape-color (:shadow shape))
(when (= (:type shape) :frame)
(map grid->shape-color (:grids shape)))
(when (= (:type shape) :text)
(reduce (fn [colors node]
(concat colors (get-text-node-colors node)))
()
(txt/node-seq (:content shape))))))
(defn uses-library-colors?
"Check if the shape uses any color in the given library."
[shape library-id]
(let [all-colors (get-all-colors shape)]
(some #(and (some? (:ref-id %))
(= (:ref-file %) library-id))
all-colors)))
(defn sync-shape-colors
"Look for usage of any color of the given library inside the shape,
and, in this case, copy the library color into the shape."
[shape library-id library-colors]
(let [sync-color (fn [shape position shape-color set-fn detach-fn]
(if (= (:ref-file shape-color) library-id)
(let [library-color (get library-colors (:ref-id shape-color))]
(if (some? library-color)
(set-fn shape
position
(:color library-color)
(:opacity library-color)
(:gradient library-color))
(detach-fn shape position)))
shape))
sync-fill (fn [shape [position fill]]
(sync-color shape
position
(fill->shape-color fill)
set-fill-color
detach-fill-color))
sync-stroke (fn [shape [position stroke]]
(sync-color shape
position
(stroke->shape-color stroke)
set-stroke-color
detach-stroke-color))
sync-shadow (fn [shape [position shadow]]
(sync-color shape
position
(shadow->shape-color shadow)
set-shadow-color
detach-shadow-color))
sync-grid (fn [shape [position grid]]
(sync-color shape
position
(grid->shape-color grid)
set-grid-color
detach-grid-color))
sync-text-node (fn [node]
(as-> node $
(reduce sync-fill $ (d/enumerate (:fills $)))
(reduce sync-stroke $ (d/enumerate (:strokes $)))))
sync-text (fn [shape]
(let [content (:content shape)
new-content (txt/transform-nodes sync-text-node content)]
(if (not= content new-content)
(assoc shape :content new-content)
shape)))]
(as-> shape $
(reduce sync-fill $ (d/enumerate (:fills $)))
(reduce sync-stroke $ (d/enumerate (:strokes $)))
(reduce sync-shadow $ (d/enumerate (:shadow $)))
(reduce sync-grid $ (d/enumerate (:grids $)))
(sync-text $))))

View file

@ -166,11 +166,11 @@
::blocked
::collapsed
::fills
::fill-color
::fill-opacity
::fill-color-gradient
::fill-color-ref-file
::fill-color-ref-id
::fill-color ;; TODO: remove these attributes
::fill-opacity ;; when backward compatibility
::fill-color-gradient ;; is no longer needed
::fill-color-ref-file ;;
::fill-color-ref-id ;;
::hide-fill-on-export
::font-family
::font-size
@ -196,10 +196,10 @@
::exports
::shapes
::strokes
::stroke-color
::stroke-color-ref-file
::stroke-color-ref-id
::stroke-opacity
::stroke-color ;; TODO: same thing
::stroke-color-ref-file ;;
::stroke-color-ref-i ;;
::stroke-opacity ;;
::stroke-style
::stroke-width
::stroke-alignment

View file

@ -14,6 +14,7 @@
[app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.spec :as us]
[app.common.spec.color :as color]
[app.common.text :as txt]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.groups :as dwg]
@ -24,15 +25,6 @@
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(log/set-level! :warn)
(defonce color-sync-attrs
[[:fill-color-ref-id :fill-color-ref-file :color :fill-color]
[:fill-color-ref-id :fill-color-ref-file :gradient :fill-color-gradient]
[:fill-color-ref-id :fill-color-ref-file :opacity :fill-opacity]
[:stroke-color-ref-id :stroke-color-ref-file :color :stroke-color]
[:stroke-color-ref-id :stroke-color-ref-file :gradient :stroke-color-gradient]
[:stroke-color-ref-id :stroke-color-ref-file :opacity :stroke-opacity]])
(declare generate-sync-container)
(declare generate-sync-shape)
(declare generate-sync-text-shape)
@ -297,7 +289,7 @@
(defmulti uses-assets?
"Checks if a shape uses some asset of the given type in the given library."
(fn [asset-type shape library-id page?] asset-type))
(fn [asset-type _ _ _] asset-type))
(defmethod uses-assets? :components
[_ shape library-id page?]
@ -307,22 +299,7 @@
(defmethod uses-assets? :colors
[_ shape library-id _]
(if (= (:type shape) :text)
(->> shape
:content
;; Check if any node in the content has a reference for the library
(txt/node-seq
#(or (and (some? (:stroke-color-ref-id %))
(= (:stroke-color-ref-file %) library-id))
(and (some? (:fill-color-ref-id %))
(= (:fill-color-ref-file %) library-id)))))
(some
#(let [attr (name %)
attr-ref-id (keyword (str attr "-ref-id"))
attr-ref-file (keyword (str attr "-ref-file"))]
(and (get shape attr-ref-id)
(= library-id (get shape attr-ref-file))))
(map #(nth % 3) color-sync-attrs))))
(color/uses-library-colors? shape library-id))
(defmethod uses-assets? :typographies
[_ shape library-id _]
@ -346,77 +323,15 @@
(generate-sync-shape-direct changes libraries container shape-id false)))
(defmethod generate-sync-shape :colors
[_ changes library-id state container shape]
[_ changes library-id state _ shape]
(log/debug :msg "Sync colors of shape" :shape (:name shape))
;; Synchronize a shape that uses some colors of the library. The value of the
;; color in the library is copied to the shape.
(let [colors (get-assets library-id :colors state)]
(if (= :text (:type shape))
(let [update-node (fn [node]
(if-let [color (get colors (:fill-color-ref-id node))]
(assoc node
:fill-color (:color color)
:fill-opacity (:opacity color)
:fill-color-gradient (:gradient color))
(assoc node
:fill-color-ref-id nil
:fill-color-ref-file nil)))]
(generate-sync-text-shape changes shape container update-node))
(loop [attrs (seq color-sync-attrs)
roperations []
uoperations []]
(let [[attr-ref-id attr-ref-file color-attr attr] (first attrs)]
(if (nil? attr)
(if (empty? roperations)
changes
(-> changes
(update :redo-changes (make-change
container
{:type :mod-obj
:id (:id shape)
:operations roperations}))
(update :undo-changes (make-change
container
{:type :mod-obj
:id (:id shape)
:operations uoperations}))))
(if-not (contains? shape attr-ref-id)
(recur (next attrs)
roperations
uoperations)
(let [color (get colors (get shape attr-ref-id))
roperations' (if color
[{:type :set
:attr attr
:val (color-attr color)
:ignore-touched true}]
;; If the referenced color does no longer exist in the library,
;; we must unlink the color in the shape
[{:type :set
:attr attr-ref-id
:val nil
:ignore-touched true}
{:type :set
:attr attr-ref-file
:val nil
:ignore-touched true}])
uoperations' (if color
[{:type :set
:attr attr
:val (get shape attr)
:ignore-touched true}]
[{:type :set
:attr attr-ref-id
:val (get shape attr-ref-id)
:ignore-touched true}
{:type :set
:attr attr-ref-file
:val (get shape attr-ref-file)
:ignore-touched true}])]
(recur (next attrs)
(into roperations roperations')
(into uoperations uoperations'))))))))))
(let [library-colors (get-assets library-id :colors state)]
(pcb/update-shapes changes
[(:id shape)]
#(color/sync-shape-colors % library-id library-colors))))
(defmethod generate-sync-shape :typographies
[_ changes library-id state container shape]