mirror of
https://github.com/penpot/penpot.git
synced 2025-05-20 19:36:12 +02:00
🎉 Synchronize library colors in all parts of a shape
This commit is contained in:
parent
7581230b6e
commit
02157cbeb9
4 changed files with 249 additions and 128 deletions
|
@ -286,37 +286,38 @@
|
||||||
update-shape
|
update-shape
|
||||||
(fn [changes id]
|
(fn [changes id]
|
||||||
(let [old-obj (get objects 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}
|
uops (cond-> uops
|
||||||
(reduce #(generate-operation %1 %2 old-obj new-obj ignore-geometry?)
|
(seq uops)
|
||||||
{:rops [] :uops []}
|
(d/preconj {:type :set-touched :touched (:touched old-obj)}))
|
||||||
attrs)
|
|
||||||
|
|
||||||
uops (cond-> uops
|
change (cond-> {:type :mod-obj
|
||||||
(seq uops)
|
:id id}
|
||||||
(d/preconj {:type :set-touched :touched (:touched old-obj)}))
|
|
||||||
|
|
||||||
change (cond-> {:type :mod-obj
|
(some? page-id)
|
||||||
:id id}
|
(assoc :page-id page-id)
|
||||||
|
|
||||||
(some? page-id)
|
(some? component-id)
|
||||||
(assoc :page-id page-id)
|
(assoc :component-id component-id))]
|
||||||
|
|
||||||
(some? component-id)
|
(cond-> changes
|
||||||
(assoc :component-id component-id))]
|
(seq rops)
|
||||||
|
(update :redo-changes conj (assoc change :operations rops))
|
||||||
|
|
||||||
(cond-> changes
|
(seq uops)
|
||||||
(seq rops)
|
(update :undo-changes d/preconj (assoc change :operations uops)))))))]
|
||||||
(update :redo-changes conj (assoc change :operations rops))
|
|
||||||
|
|
||||||
(seq uops)
|
(-> (reduce update-shape changes ids)
|
||||||
(update :undo-changes d/preconj (assoc change :operations uops)))))]
|
(apply-changes-local)))))
|
||||||
|
|
||||||
(-> (reduce update-shape changes ids)
|
|
||||||
(apply-changes-local)))))
|
|
||||||
|
|
||||||
(defn remove-objects
|
(defn remove-objects
|
||||||
[changes ids]
|
[changes ids]
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
|
|
||||||
(ns app.common.spec.color
|
(ns app.common.spec.color
|
||||||
(:require
|
(:require
|
||||||
[app.common.spec :as us]
|
[app.common.data :as d]
|
||||||
[clojure.spec.alpha :as s]))
|
[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
|
;; TODO: waiting clojure 1.11 to rename this all :internal.stuff to a
|
||||||
;; more consistent name.
|
;; more consistent name.
|
||||||
|
@ -46,7 +48,7 @@
|
||||||
:internal.gradient/width
|
:internal.gradient/width
|
||||||
:internal.gradient/stops]))
|
:internal.gradient/stops]))
|
||||||
|
|
||||||
;;; --- COLORS
|
;; --- COLORS
|
||||||
|
|
||||||
(s/def :internal.color/name string?)
|
(s/def :internal.color/name string?)
|
||||||
(s/def :internal.color/path (s/nilable 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/color (s/nilable string?))
|
||||||
(s/def :internal.color/opacity (s/nilable ::us/safe-number))
|
(s/def :internal.color/opacity (s/nilable ::us/safe-number))
|
||||||
(s/def :internal.color/gradient (s/nilable ::gradient))
|
(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/def ::color
|
||||||
(s/keys :opt-un [::id
|
(s/keys :opt-un [::id
|
||||||
|
@ -70,3 +81,197 @@
|
||||||
:internal.color/opacity
|
:internal.color/opacity
|
||||||
:internal.color/gradient]))
|
: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 $))))
|
||||||
|
|
|
@ -166,11 +166,11 @@
|
||||||
::blocked
|
::blocked
|
||||||
::collapsed
|
::collapsed
|
||||||
::fills
|
::fills
|
||||||
::fill-color
|
::fill-color ;; TODO: remove these attributes
|
||||||
::fill-opacity
|
::fill-opacity ;; when backward compatibility
|
||||||
::fill-color-gradient
|
::fill-color-gradient ;; is no longer needed
|
||||||
::fill-color-ref-file
|
::fill-color-ref-file ;;
|
||||||
::fill-color-ref-id
|
::fill-color-ref-id ;;
|
||||||
::hide-fill-on-export
|
::hide-fill-on-export
|
||||||
::font-family
|
::font-family
|
||||||
::font-size
|
::font-size
|
||||||
|
@ -196,10 +196,10 @@
|
||||||
::exports
|
::exports
|
||||||
::shapes
|
::shapes
|
||||||
::strokes
|
::strokes
|
||||||
::stroke-color
|
::stroke-color ;; TODO: same thing
|
||||||
::stroke-color-ref-file
|
::stroke-color-ref-file ;;
|
||||||
::stroke-color-ref-id
|
::stroke-color-ref-i ;;
|
||||||
::stroke-opacity
|
::stroke-opacity ;;
|
||||||
::stroke-style
|
::stroke-style
|
||||||
::stroke-width
|
::stroke-width
|
||||||
::stroke-alignment
|
::stroke-alignment
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
[app.common.pages.changes-builder :as pcb]
|
[app.common.pages.changes-builder :as pcb]
|
||||||
[app.common.pages.helpers :as cph]
|
[app.common.pages.helpers :as cph]
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
|
[app.common.spec.color :as color]
|
||||||
[app.common.text :as txt]
|
[app.common.text :as txt]
|
||||||
[app.main.data.workspace.common :as dwc]
|
[app.main.data.workspace.common :as dwc]
|
||||||
[app.main.data.workspace.groups :as dwg]
|
[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
|
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
|
||||||
(log/set-level! :warn)
|
(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-container)
|
||||||
(declare generate-sync-shape)
|
(declare generate-sync-shape)
|
||||||
(declare generate-sync-text-shape)
|
(declare generate-sync-text-shape)
|
||||||
|
@ -297,7 +289,7 @@
|
||||||
|
|
||||||
(defmulti uses-assets?
|
(defmulti uses-assets?
|
||||||
"Checks if a shape uses some asset of the given type in the given library."
|
"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
|
(defmethod uses-assets? :components
|
||||||
[_ shape library-id page?]
|
[_ shape library-id page?]
|
||||||
|
@ -307,22 +299,7 @@
|
||||||
|
|
||||||
(defmethod uses-assets? :colors
|
(defmethod uses-assets? :colors
|
||||||
[_ shape library-id _]
|
[_ shape library-id _]
|
||||||
(if (= (:type shape) :text)
|
(color/uses-library-colors? shape library-id))
|
||||||
(->> 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))))
|
|
||||||
|
|
||||||
(defmethod uses-assets? :typographies
|
(defmethod uses-assets? :typographies
|
||||||
[_ shape library-id _]
|
[_ shape library-id _]
|
||||||
|
@ -346,77 +323,15 @@
|
||||||
(generate-sync-shape-direct changes libraries container shape-id false)))
|
(generate-sync-shape-direct changes libraries container shape-id false)))
|
||||||
|
|
||||||
(defmethod generate-sync-shape :colors
|
(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))
|
(log/debug :msg "Sync colors of shape" :shape (:name shape))
|
||||||
|
|
||||||
;; Synchronize a shape that uses some colors of the library. The value of the
|
;; Synchronize a shape that uses some colors of the library. The value of the
|
||||||
;; color in the library is copied to the shape.
|
;; color in the library is copied to the shape.
|
||||||
(let [colors (get-assets library-id :colors state)]
|
(let [library-colors (get-assets library-id :colors state)]
|
||||||
(if (= :text (:type shape))
|
(pcb/update-shapes changes
|
||||||
(let [update-node (fn [node]
|
[(:id shape)]
|
||||||
(if-let [color (get colors (:fill-color-ref-id node))]
|
#(color/sync-shape-colors % library-id library-colors))))
|
||||||
(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'))))))))))
|
|
||||||
|
|
||||||
(defmethod generate-sync-shape :typographies
|
(defmethod generate-sync-shape :typographies
|
||||||
[_ changes library-id state container shape]
|
[_ changes library-id state container shape]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue