mirror of
https://github.com/penpot/penpot.git
synced 2025-05-26 05:26:10 +02:00
✨ Ability to add multiple fills to a shape
This commit is contained in:
parent
aecb8a1464
commit
23a9c74297
25 changed files with 475 additions and 243 deletions
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
### :sparkles: New features
|
### :sparkles: New features
|
||||||
|
|
||||||
|
- Ability to add multiple fills to a shape [Taiga #1394](https://tree.taiga.io/project/penpot/us/1394)
|
||||||
- Team members redesign [Taiga #2283](https://tree.taiga.io/project/penpot/us/2283)
|
- Team members redesign [Taiga #2283](https://tree.taiga.io/project/penpot/us/2283)
|
||||||
- Rotation to snap to 15º intervals with shift [Taiga #2437](https://tree.taiga.io/project/penpot/issue/2437)
|
- Rotation to snap to 15º intervals with shift [Taiga #2437](https://tree.taiga.io/project/penpot/issue/2437)
|
||||||
- Support border radius and stroke properties for images [Taiga #497](https://tree.taiga.io/project/penpot/us/497)
|
- Support border radius and stroke properties for images [Taiga #497](https://tree.taiga.io/project/penpot/us/497)
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
;; :rx nil
|
;; :rx nil
|
||||||
;; :ry nil}
|
;; :ry nil}
|
||||||
;;
|
;;
|
||||||
|
|
||||||
(defn get-attrs-multi
|
(defn get-attrs-multi
|
||||||
([objs attrs]
|
([objs attrs]
|
||||||
(get-attrs-multi objs attrs = identity))
|
(get-attrs-multi objs attrs = identity))
|
||||||
|
|
|
@ -9,12 +9,13 @@
|
||||||
[app.common.colors :as clr]
|
[app.common.colors :as clr]
|
||||||
[app.common.uuid :as uuid]))
|
[app.common.uuid :as uuid]))
|
||||||
|
|
||||||
(def file-version 13)
|
(def file-version 14)
|
||||||
(def default-color clr/gray-20)
|
(def default-color clr/gray-20)
|
||||||
(def root uuid/zero)
|
(def root uuid/zero)
|
||||||
|
|
||||||
(def component-sync-attrs
|
(def component-sync-attrs
|
||||||
{:name :name-group
|
{:name :name-group
|
||||||
|
:fills :fill-group
|
||||||
:fill-color :fill-group
|
:fill-color :fill-group
|
||||||
:fill-opacity :fill-group
|
:fill-opacity :fill-group
|
||||||
:fill-color-gradient :fill-group
|
:fill-color-gradient :fill-group
|
||||||
|
|
|
@ -33,16 +33,16 @@
|
||||||
|
|
||||||
(def default-frame-attrs
|
(def default-frame-attrs
|
||||||
{:frame-id uuid/zero
|
{:frame-id uuid/zero
|
||||||
:fill-color clr/white
|
:fills [{:fill-color clr/white
|
||||||
:fill-opacity 1
|
:fill-opacity 1}]
|
||||||
:shapes []
|
:shapes []
|
||||||
:hide-fill-on-export false})
|
:hide-fill-on-export false})
|
||||||
|
|
||||||
(def ^:private minimal-shapes
|
(def ^:private minimal-shapes
|
||||||
[{:type :rect
|
[{:type :rect
|
||||||
:name "Rect-1"
|
:name "Rect-1"
|
||||||
:fill-color default-color
|
:fills [{:fill-color default-color
|
||||||
:fill-opacity 1
|
:fill-opacity 1}]
|
||||||
:stroke-style :none
|
:stroke-style :none
|
||||||
:stroke-alignment :center
|
:stroke-alignment :center
|
||||||
:stroke-width 0
|
:stroke-width 0
|
||||||
|
@ -53,12 +53,13 @@
|
||||||
|
|
||||||
{:type :image
|
{:type :image
|
||||||
:rx 0
|
:rx 0
|
||||||
:ry 0}
|
:ry 0
|
||||||
|
:fills []}
|
||||||
|
|
||||||
{:type :circle
|
{:type :circle
|
||||||
:name "Circle-1"
|
:name "Circle-1"
|
||||||
:fill-color default-color
|
:fills [{:fill-color default-color
|
||||||
:fill-opacity 1
|
:fill-opacity 1}]
|
||||||
:stroke-style :none
|
:stroke-style :none
|
||||||
:stroke-alignment :center
|
:stroke-alignment :center
|
||||||
:stroke-width 0
|
:stroke-width 0
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
|
|
||||||
{:type :path
|
{:type :path
|
||||||
:name "Path-1"
|
:name "Path-1"
|
||||||
|
:fills []
|
||||||
:stroke-style :solid
|
:stroke-style :solid
|
||||||
:stroke-alignment :center
|
:stroke-alignment :center
|
||||||
:stroke-width 2
|
:stroke-width 2
|
||||||
|
@ -75,8 +77,8 @@
|
||||||
|
|
||||||
{:type :frame
|
{:type :frame
|
||||||
:name "Artboard-1"
|
:name "Artboard-1"
|
||||||
:fill-color clr/white
|
:fills [{:fill-color clr/white
|
||||||
:fill-opacity 1
|
:fill-opacity 1}]
|
||||||
:stroke-style :none
|
:stroke-style :none
|
||||||
:stroke-alignment :center
|
:stroke-alignment :center
|
||||||
:stroke-width 0
|
:stroke-width 0
|
||||||
|
|
|
@ -300,3 +300,33 @@
|
||||||
(update page :objects #(d/mapm update-object %)))]
|
(update page :objects #(d/mapm update-object %)))]
|
||||||
|
|
||||||
(update data :pages-index #(d/mapm update-page %))))
|
(update data :pages-index #(d/mapm update-page %))))
|
||||||
|
|
||||||
|
(defn set-fills
|
||||||
|
[shape]
|
||||||
|
(let [attrs {:fill-color (:fill-color shape)
|
||||||
|
:fill-color-gradient (:fill-color-gradient shape)
|
||||||
|
:fill-color-ref-file (:fill-color-ref-file shape)
|
||||||
|
:fill-color-ref-id (:fill-color-ref-id shape)
|
||||||
|
:fill-opacity (:fill-opacity shape)}
|
||||||
|
|
||||||
|
clean-attrs (d/without-nils attrs)]
|
||||||
|
(-> shape
|
||||||
|
(assoc :fills [clean-attrs])
|
||||||
|
(dissoc :fill-color)
|
||||||
|
(dissoc :fill-color-gradient)
|
||||||
|
(dissoc :fill-color-ref-file)
|
||||||
|
(dissoc :fill-color-ref-id)
|
||||||
|
(dissoc :fill-opacity))))
|
||||||
|
|
||||||
|
;; Add fills to shapes
|
||||||
|
(defmethod migrate 14
|
||||||
|
[data]
|
||||||
|
(letfn [(update-object [_ object]
|
||||||
|
(cond-> object
|
||||||
|
(and (not (= :text (:type object))) (nil? (:fills object)))
|
||||||
|
(set-fills)))
|
||||||
|
|
||||||
|
(update-page [_ page]
|
||||||
|
(update page :objects #(d/mapm update-object %)))]
|
||||||
|
|
||||||
|
(update data :pages-index #(d/mapm update-page %))))
|
||||||
|
|
|
@ -499,6 +499,10 @@
|
||||||
margin-bottom: $size-2;
|
margin-bottom: $size-2;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
&[draggable="true"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
.color-name {
|
.color-name {
|
||||||
font-size: $fs12;
|
font-size: $fs12;
|
||||||
margin: 5px 6px 0px 6px;
|
margin: 5px 6px 0px 6px;
|
||||||
|
|
|
@ -954,6 +954,7 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 12px;
|
width: 12px;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
|
@ -961,6 +962,11 @@
|
||||||
stroke: $color-gray-20;
|
stroke: $color-gray-20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.remove {
|
||||||
|
min-width: 20px;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover svg,
|
&:hover svg,
|
||||||
&.active svg {
|
&.active svg {
|
||||||
fill: $color-primary;
|
fill: $color-primary;
|
||||||
|
|
|
@ -111,42 +111,105 @@
|
||||||
(assoc-in [:workspace-local :picked-color-select] value)
|
(assoc-in [:workspace-local :picked-color-select] value)
|
||||||
(assoc-in [:workspace-local :picked-shift?] shift?)))))
|
(assoc-in [:workspace-local :picked-shift?] shift?)))))
|
||||||
|
|
||||||
|
(defn transform-fill
|
||||||
|
[state ids color transform]
|
||||||
|
(let [page-id (:current-page-id state)
|
||||||
|
objects (wsh/lookup-page-objects state page-id)
|
||||||
|
|
||||||
|
is-text? #(= :text (:type (get objects %)))
|
||||||
|
text-ids (filter is-text? ids)
|
||||||
|
shape-ids (filter (comp not is-text?) ids)
|
||||||
|
|
||||||
|
attrs (cond-> {:fill-color nil
|
||||||
|
:fill-color-gradient nil
|
||||||
|
:fill-color-ref-file nil
|
||||||
|
:fill-color-ref-id nil
|
||||||
|
:fill-opacity nil}
|
||||||
|
|
||||||
|
(contains? color :color)
|
||||||
|
(assoc :fill-color (:color color))
|
||||||
|
|
||||||
|
(contains? color :id)
|
||||||
|
(assoc :fill-color-ref-id (:id color))
|
||||||
|
|
||||||
|
(contains? color :file-id)
|
||||||
|
(assoc :fill-color-ref-file (:file-id color))
|
||||||
|
|
||||||
|
(contains? color :gradient)
|
||||||
|
(assoc :fill-color-gradient (:gradient color))
|
||||||
|
|
||||||
|
(contains? color :opacity)
|
||||||
|
(assoc :fill-opacity (:opacity color)))
|
||||||
|
;; Not nil attrs
|
||||||
|
clean-attrs (d/without-nils attrs)]
|
||||||
|
|
||||||
|
(rx/concat
|
||||||
|
(rx/from (map #(dwt/update-text-attrs {:id % :attrs attrs}) text-ids))
|
||||||
|
(rx/of (dch/update-shapes
|
||||||
|
shape-ids
|
||||||
|
#(transform % clean-attrs))))))
|
||||||
|
|
||||||
|
(defn swap-fills [shape index new-index]
|
||||||
|
(let [first (get-in shape [:fills index])
|
||||||
|
second (get-in shape [:fills new-index])]
|
||||||
|
(-> shape
|
||||||
|
(assoc-in [:fills index] second)
|
||||||
|
(assoc-in [:fills new-index] first))
|
||||||
|
))
|
||||||
|
|
||||||
|
(defn reorder-fills
|
||||||
|
[ids index new-index]
|
||||||
|
(ptk/reify ::reorder-fills
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ _ _]
|
||||||
|
(rx/of (dch/update-shapes
|
||||||
|
ids
|
||||||
|
#(swap-fills % index new-index))))))
|
||||||
|
|
||||||
(defn change-fill
|
(defn change-fill
|
||||||
[ids color]
|
[ids color position]
|
||||||
(ptk/reify ::change-fill
|
(ptk/reify ::change-fill
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state _]
|
(watch [_ state _]
|
||||||
(let [page-id (:current-page-id state)
|
(let [change (fn [shape attrs] (assoc-in shape [:fills position] (into {} attrs)))]
|
||||||
objects (wsh/lookup-page-objects state page-id)
|
(transform-fill state ids color change)))))
|
||||||
|
|
||||||
is-text? #(= :text (:type (get objects %)))
|
(defn change-fill-and-clear
|
||||||
text-ids (filter is-text? ids)
|
[ids color]
|
||||||
shape-ids (filter (comp not is-text?) ids)
|
(ptk/reify ::change-fill-and-clear
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [set (fn [shape attrs] (assoc shape :fills [attrs]))]
|
||||||
|
(transform-fill state ids color set)))))
|
||||||
|
|
||||||
attrs (cond-> {:fill-color nil
|
(defn add-fill
|
||||||
:fill-color-gradient nil
|
[ids color]
|
||||||
::fill-color-ref-file nil
|
(ptk/reify ::add-fill
|
||||||
:fill-color-ref-id nil
|
ptk/WatchEvent
|
||||||
:fill-opacity nil}
|
(watch [_ state _]
|
||||||
|
(let [add (fn [shape attrs] (assoc shape :fills (into [attrs] (:fills shape))))]
|
||||||
|
(transform-fill state ids color add)))))
|
||||||
|
|
||||||
(contains? color :color)
|
(defn remove-fill
|
||||||
(assoc :fill-color (:color color))
|
[ids color position]
|
||||||
|
(ptk/reify ::remove-fill
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [remove-fill-by-index (fn [values index] (->> (d/enumerate values)
|
||||||
|
(filterv (fn [[idx _]] (not= idx index)))
|
||||||
|
(mapv second)))
|
||||||
|
|
||||||
(contains? color :id)
|
remove (fn [shape _] (update shape :fills remove-fill-by-index position))]
|
||||||
(assoc :fill-color-ref-id (:id color))
|
(transform-fill state ids color remove)))))
|
||||||
|
|
||||||
(contains? color :file-id)
|
(defn remove-all-fills
|
||||||
(assoc :fill-color-ref-file (:file-id color))
|
[ids color]
|
||||||
|
(ptk/reify ::remove-all-fills
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state _]
|
||||||
|
(let [remove-all (fn [shape _] (assoc shape :fills []))]
|
||||||
|
(transform-fill state ids color remove-all)))))
|
||||||
|
|
||||||
(contains? color :gradient)
|
|
||||||
(assoc :fill-color-gradient (:gradient color))
|
|
||||||
|
|
||||||
(contains? color :opacity)
|
|
||||||
(assoc :fill-opacity (:opacity color)))]
|
|
||||||
|
|
||||||
(rx/concat
|
|
||||||
(rx/from (map #(dwt/update-text-attrs {:id % :attrs attrs}) text-ids))
|
|
||||||
(rx/of (dch/update-shapes shape-ids (fn [shape] (d/merge shape attrs)))))))))
|
|
||||||
|
|
||||||
(defn change-hide-fill-on-export
|
(defn change-hide-fill-on-export
|
||||||
[ids hide-fill-on-export]
|
[ids hide-fill-on-export]
|
||||||
|
@ -207,7 +270,7 @@
|
||||||
|
|
||||||
update-events
|
update-events
|
||||||
(fn [color]
|
(fn [color]
|
||||||
(rx/of (change-fill ids color)))]
|
(rx/of (change-fill ids color 0)))]
|
||||||
|
|
||||||
(rx/merge
|
(rx/merge
|
||||||
;; Stream that updates the stroke/width and stops if `esc` pressed
|
;; Stream that updates the stroke/width and stops if `esc` pressed
|
||||||
|
|
|
@ -73,26 +73,28 @@
|
||||||
(defn setup-fill [shape]
|
(defn setup-fill [shape]
|
||||||
(cond-> shape
|
(cond-> shape
|
||||||
;; Color present as attribute
|
;; Color present as attribute
|
||||||
(uc/color? (get-in shape [:svg-attrs :fill]))
|
(uc/color? (str/trim (get-in shape [:svg-attrs :fill])))
|
||||||
(-> (update :svg-attrs dissoc :fill)
|
(-> (update :svg-attrs dissoc :fill)
|
||||||
(assoc :fill-color (-> (get-in shape [:svg-attrs :fill])
|
(assoc-in [:fills 0 :fill-color] (-> (get-in shape [:svg-attrs :fill])
|
||||||
(uc/parse-color))))
|
(str/trim)
|
||||||
|
(uc/parse-color))))
|
||||||
|
|
||||||
;; Color present as style
|
;; Color present as style
|
||||||
(uc/color? (get-in shape [:svg-attrs :style :fill]))
|
(uc/color? (str/trim (get-in shape [:svg-attrs :style :fill])))
|
||||||
(-> (update-in [:svg-attrs :style] dissoc :fill)
|
(-> (update-in [:svg-attrs :style] dissoc :fill)
|
||||||
(assoc :fill-color (-> (get-in shape [:svg-attrs :style :fill])
|
(assoc-in [:fills 0 :fill-color] (-> (get-in shape [:svg-attrs :style :fill])
|
||||||
(uc/parse-color))))
|
(str/trim)
|
||||||
|
(uc/parse-color))))
|
||||||
|
|
||||||
(get-in shape [:svg-attrs :fill-opacity])
|
(get-in shape [:svg-attrs :fill-opacity])
|
||||||
(-> (update :svg-attrs dissoc :fill-opacity)
|
(-> (update :svg-attrs dissoc :fill-opacity)
|
||||||
(assoc :fill-opacity (-> (get-in shape [:svg-attrs :fill-opacity])
|
(assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :fill-opacity])
|
||||||
(d/parse-double))))
|
(d/parse-double))))
|
||||||
|
|
||||||
(get-in shape [:svg-attrs :style :fill-opacity])
|
(get-in shape [:svg-attrs :style :fill-opacity])
|
||||||
(-> (update-in [:svg-attrs :style] dissoc :fill-opacity)
|
(-> (update-in [:svg-attrs :style] dissoc :fill-opacity)
|
||||||
(assoc :fill-opacity (-> (get-in shape [:svg-attrs :style :fill-opacity])
|
(assoc-in [:fills 0 :fill-opacity] (-> (get-in shape [:svg-attrs :style :fill-opacity])
|
||||||
(d/parse-double))))))
|
(d/parse-double))))))
|
||||||
|
|
||||||
(defn setup-stroke [shape]
|
(defn setup-stroke [shape]
|
||||||
(let [stroke-linecap (-> (or (get-in shape [:svg-attrs :stroke-linecap])
|
(let [stroke-linecap (-> (or (get-in shape [:svg-attrs :stroke-linecap])
|
||||||
|
@ -378,9 +380,10 @@
|
||||||
:polygon (create-path-shape name frame-id svg-data (-> element-data usvg/polygon->path))
|
:polygon (create-path-shape name frame-id svg-data (-> element-data usvg/polygon->path))
|
||||||
:line (create-path-shape name frame-id svg-data (-> element-data usvg/line->path))
|
:line (create-path-shape name frame-id svg-data (-> element-data usvg/line->path))
|
||||||
:image (create-image-shape name frame-id svg-data element-data)
|
:image (create-image-shape name frame-id svg-data element-data)
|
||||||
#_other (create-raw-svg name frame-id svg-data element-data))
|
#_other (create-raw-svg name frame-id svg-data element-data)))
|
||||||
|
|
||||||
|
shape (assoc shape :fills [])
|
||||||
|
|
||||||
)
|
|
||||||
shape (when (some? shape)
|
shape (when (some? shape)
|
||||||
(-> shape
|
(-> shape
|
||||||
(assoc :svg-defs (select-keys (:defs svg-data) references))
|
(assoc :svg-defs (select-keys (:defs svg-data) references))
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
[props external-ref]
|
[props external-ref]
|
||||||
(let [value (obj/get props "value")
|
(let [value (obj/get props "value")
|
||||||
on-change (obj/get props "onChange")
|
on-change (obj/get props "onChange")
|
||||||
|
on-blur (obj/get props "onBlur")
|
||||||
|
|
||||||
;; We need a ref pointing to the input dom element, but the user
|
;; We need a ref pointing to the input dom element, but the user
|
||||||
;; of this component may provide one (that is forwarded here).
|
;; of this component may provide one (that is forwarded here).
|
||||||
|
@ -92,6 +93,8 @@
|
||||||
(mf/deps parse-value apply-value update-input)
|
(mf/deps parse-value apply-value update-input)
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(let [new-value (parse-value)]
|
(let [new-value (parse-value)]
|
||||||
|
(when on-blur
|
||||||
|
(on-blur))
|
||||||
(if new-value
|
(if new-value
|
||||||
(apply-value new-value)
|
(apply-value new-value)
|
||||||
(update-input value)))))
|
(update-input value)))))
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
|
|
||||||
object (cond-> object
|
object (cond-> object
|
||||||
(:hide-fill-on-export object)
|
(:hide-fill-on-export object)
|
||||||
(assoc :fill-color nil :fill-opacity 0))
|
(assoc :fills []))
|
||||||
|
|
||||||
{:keys [x y width height] :as bs} (calc-bounds object objects)
|
{:keys [x y width height] :as bs} (calc-bounds object objects)
|
||||||
[_ _ width height :as coords] (->> [x y width height] (map #(* % zoom)))
|
[_ _ width height :as coords] (->> [x y width height] (map #(* % zoom)))
|
||||||
|
|
|
@ -79,14 +79,15 @@
|
||||||
"z")}))
|
"z")}))
|
||||||
attrs))
|
attrs))
|
||||||
|
|
||||||
(defn add-fill [attrs shape render-id]
|
(defn add-fill [attrs shape render-id index]
|
||||||
(let [fill-attrs (cond
|
(let [
|
||||||
|
fill-attrs (cond
|
||||||
(contains? shape :fill-image)
|
(contains? shape :fill-image)
|
||||||
(let [fill-image-id (str "fill-image-" render-id)]
|
(let [fill-image-id (str "fill-image-" render-id)]
|
||||||
{:fill (str/format "url(#%s)" fill-image-id)})
|
{:fill (str/format "url(#%s)" fill-image-id)})
|
||||||
|
|
||||||
(contains? shape :fill-color-gradient)
|
(contains? shape :fill-color-gradient)
|
||||||
(let [fill-color-gradient-id (str "fill-color-gradient_" render-id)]
|
(let [fill-color-gradient-id (str "fill-color-gradient_" render-id "_" index)]
|
||||||
{:fill (str/format "url(#%s)" fill-color-gradient-id)})
|
{:fill (str/format "url(#%s)" fill-color-gradient-id)})
|
||||||
|
|
||||||
(contains? shape :fill-color)
|
(contains? shape :fill-color)
|
||||||
|
@ -193,9 +194,23 @@
|
||||||
|
|
||||||
styles (-> (obj/get props "style" (obj/new))
|
styles (-> (obj/get props "style" (obj/new))
|
||||||
(obj/merge! svg-styles)
|
(obj/merge! svg-styles)
|
||||||
(add-fill shape render-id)
|
|
||||||
(add-stroke shape render-id)
|
(add-stroke shape render-id)
|
||||||
(add-layer-props shape))]
|
(add-layer-props shape))
|
||||||
|
|
||||||
|
styles (cond (or (some? (:fill-image shape))
|
||||||
|
(= :image (:type shape))
|
||||||
|
(> (count (:fills shape)) 1)
|
||||||
|
(some #(some? (:fill-color-gradient %)) (:fills shape)))
|
||||||
|
(obj/set! styles "fill" (str "url(#fill-" render-id ")"))
|
||||||
|
|
||||||
|
;; imported svgs can have fill and fill-opacity attributes
|
||||||
|
(obj/contains? svg-styles "fill")
|
||||||
|
(-> styles
|
||||||
|
(obj/set! "fill" (obj/get svg-styles "fill"))
|
||||||
|
(obj/set! "fillOpacity" (obj/get svg-styles "fillOpacity")))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(add-fill styles (get-in shape [:fills 0]) render-id 0))]
|
||||||
|
|
||||||
(-> props
|
(-> props
|
||||||
(obj/merge! svg-attrs)
|
(obj/merge! svg-attrs)
|
||||||
|
@ -208,10 +223,10 @@
|
||||||
(add-style-attrs shape)))
|
(add-style-attrs shape)))
|
||||||
|
|
||||||
(defn extract-fill-attrs
|
(defn extract-fill-attrs
|
||||||
[shape]
|
[shape index]
|
||||||
(let [render-id (mf/use-ctx muc/render-ctx)
|
(let [render-id (mf/use-ctx muc/render-ctx)
|
||||||
fill-styles (-> (obj/get shape "style" (obj/new))
|
fill-styles (-> (obj/get shape "style" (obj/new))
|
||||||
(add-fill shape render-id))]
|
(add-fill shape render-id index))]
|
||||||
(-> (obj/new)
|
(-> (obj/new)
|
||||||
(obj/set! "style" fill-styles))))
|
(obj/set! "style" fill-styles))))
|
||||||
|
|
||||||
|
|
|
@ -103,11 +103,6 @@
|
||||||
(-> (add! :rx)
|
(-> (add! :rx)
|
||||||
(add! :ry)))
|
(add! :ry)))
|
||||||
|
|
||||||
(cond-> image?
|
|
||||||
(-> (add! :fill-color)
|
|
||||||
(add! :fill-opacity)
|
|
||||||
(add! :fill-color-gradient)))
|
|
||||||
|
|
||||||
(cond-> path?
|
(cond-> path?
|
||||||
(-> (add! :stroke-cap-start)
|
(-> (add! :stroke-cap-start)
|
||||||
(add! :stroke-cap-end)))
|
(add! :stroke-cap-end)))
|
||||||
|
@ -293,6 +288,7 @@
|
||||||
:penpot:background-overlay ((d/nilf str) (:background-overlay interaction))
|
:penpot:background-overlay ((d/nilf str) (:background-overlay interaction))
|
||||||
:penpot:preserve-scroll ((d/nilf str) (:preserve-scroll interaction))}])])))
|
:penpot:preserve-scroll ((d/nilf str) (:preserve-scroll interaction))}])])))
|
||||||
|
|
||||||
|
|
||||||
(mf/defc export-data
|
(mf/defc export-data
|
||||||
[{:keys [shape]}]
|
[{:keys [shape]}]
|
||||||
(let [props (-> (obj/new) (add-data shape) (add-library-refs shape))]
|
(let [props (-> (obj/new) (add-data shape) (add-library-refs shape))]
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
;; 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) UXBOX Labs SL
|
|
||||||
|
|
||||||
(ns app.main.ui.shapes.fill-image
|
|
||||||
(:require
|
|
||||||
[app.common.geom.shapes :as gsh]
|
|
||||||
[app.config :as cfg]
|
|
||||||
[app.main.ui.shapes.attrs :as attrs]
|
|
||||||
[app.main.ui.shapes.embed :as embed]
|
|
||||||
[app.util.object :as obj]
|
|
||||||
[rumext.alpha :as mf]))
|
|
||||||
|
|
||||||
(mf/defc fill-image-pattern
|
|
||||||
{::mf/wrap-props false}
|
|
||||||
[props]
|
|
||||||
|
|
||||||
(let [shape (obj/get props "shape")
|
|
||||||
render-id (obj/get props "render-id")]
|
|
||||||
(when (contains? shape :fill-image)
|
|
||||||
(let [{:keys [x y width height]} (:selrect shape)
|
|
||||||
fill-image-id (str "fill-image-" render-id)
|
|
||||||
uri (cfg/resolve-file-media (:fill-image shape))
|
|
||||||
embed (embed/use-data-uris [uri])
|
|
||||||
transform (gsh/transform-matrix shape)
|
|
||||||
shape-without-image (dissoc shape :fill-image)
|
|
||||||
fill-attrs (-> (attrs/extract-fill-attrs shape-without-image)
|
|
||||||
(obj/set! "width" width)
|
|
||||||
(obj/set! "height" height))]
|
|
||||||
|
|
||||||
[:pattern {:id fill-image-id
|
|
||||||
:patternUnits "userSpaceOnUse"
|
|
||||||
:x x
|
|
||||||
:y y
|
|
||||||
:height height
|
|
||||||
:width width
|
|
||||||
:patternTransform transform
|
|
||||||
:data-loading (str (not (contains? embed uri)))}
|
|
||||||
[:g
|
|
||||||
[:> :rect fill-attrs]
|
|
||||||
[:image {:xlinkHref (get embed uri uri)
|
|
||||||
:width width
|
|
||||||
:height height}]
|
|
||||||
]]))))
|
|
64
frontend/src/app/main/ui/shapes/fills.cljs
Normal file
64
frontend/src/app/main/ui/shapes/fills.cljs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
;; 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) UXBOX Labs SL
|
||||||
|
|
||||||
|
(ns app.main.ui.shapes.fills
|
||||||
|
(:require
|
||||||
|
[app.common.data :as d]
|
||||||
|
[app.common.geom.shapes :as gsh]
|
||||||
|
[app.config :as cfg]
|
||||||
|
[app.main.ui.shapes.attrs :as attrs]
|
||||||
|
[app.main.ui.shapes.embed :as embed]
|
||||||
|
[app.main.ui.shapes.gradients :as grad]
|
||||||
|
[app.util.object :as obj]
|
||||||
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
(mf/defc fills
|
||||||
|
{::mf/wrap-props false}
|
||||||
|
[props]
|
||||||
|
|
||||||
|
(let [shape (obj/get props "shape")
|
||||||
|
render-id (obj/get props "render-id")
|
||||||
|
{:keys [x y width height]} (:selrect shape)
|
||||||
|
{:keys [metadata]} shape
|
||||||
|
fill-id (str "fill-" render-id)
|
||||||
|
has-image (or metadata (:fill-image shape))
|
||||||
|
uri (if metadata
|
||||||
|
(cfg/resolve-file-media metadata)
|
||||||
|
(cfg/resolve-file-media (:fill-image shape)))
|
||||||
|
embed (embed/use-data-uris [uri])
|
||||||
|
transform (gsh/transform-matrix shape)
|
||||||
|
pattern-attrs (cond-> #js {:id fill-id
|
||||||
|
:patternUnits "userSpaceOnUse"
|
||||||
|
:x x
|
||||||
|
:y y
|
||||||
|
:height height
|
||||||
|
:width width
|
||||||
|
:data-loading (str (not (contains? embed uri)))}
|
||||||
|
(= :path (:type shape))
|
||||||
|
(obj/set! "patternTransform" transform))]
|
||||||
|
|
||||||
|
[:*
|
||||||
|
(for [[index value] (-> (d/enumerate (:fills shape [])) reverse)]
|
||||||
|
(cond (some? (:fill-color-gradient value))
|
||||||
|
(case (:type (:fill-color-gradient value))
|
||||||
|
:linear [:> grad/linear-gradient #js {:id (str (name :fill-color-gradient) "_" render-id "_" index)
|
||||||
|
:gradient (:fill-color-gradient value)
|
||||||
|
:shape shape}]
|
||||||
|
:radial [:> grad/radial-gradient #js {:id (str (name :fill-color-gradient) "_" render-id "_" index)
|
||||||
|
:gradient (:fill-color-gradient value)
|
||||||
|
:shape shape}])))
|
||||||
|
|
||||||
|
[:> :pattern pattern-attrs
|
||||||
|
[:g
|
||||||
|
(for [[index value] (-> (d/enumerate (:fills shape [])) reverse)]
|
||||||
|
[:> :rect (-> (attrs/extract-fill-attrs value index)
|
||||||
|
(obj/set! "width" width)
|
||||||
|
(obj/set! "height" height))])
|
||||||
|
|
||||||
|
(when has-image
|
||||||
|
[:image {:xlinkHref (get embed uri uri)
|
||||||
|
:width width
|
||||||
|
:height height}])]]]))
|
|
@ -51,10 +51,6 @@
|
||||||
shape (unchecked-get props "shape")
|
shape (unchecked-get props "shape")
|
||||||
{:keys [x y width height]} shape
|
{:keys [x y width height]} shape
|
||||||
|
|
||||||
has-background? (or (some? (:fill-color shape))
|
|
||||||
(some? (:fill-color-gradient shape)))
|
|
||||||
has-stroke? (not= :none (:stroke-style shape))
|
|
||||||
|
|
||||||
props (-> (attrs/extract-style-attrs shape)
|
props (-> (attrs/extract-style-attrs shape)
|
||||||
(obj/merge!
|
(obj/merge!
|
||||||
#js {:x x
|
#js {:x x
|
||||||
|
@ -63,8 +59,8 @@
|
||||||
:height height
|
:height height
|
||||||
:className "frame-background"}))]
|
:className "frame-background"}))]
|
||||||
[:*
|
[:*
|
||||||
(when (or has-background? has-stroke?)
|
[:> :rect props]
|
||||||
[:> :rect props])
|
|
||||||
(for [item childs]
|
(for [item childs]
|
||||||
[:& shape-wrapper {:shape item
|
[:& shape-wrapper {:shape item
|
||||||
:key (:id item)}])])))
|
:key (:id item)}])])))
|
||||||
|
|
|
@ -7,11 +7,8 @@
|
||||||
(ns app.main.ui.shapes.image
|
(ns app.main.ui.shapes.image
|
||||||
(:require
|
(:require
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.config :as cfg]
|
|
||||||
[app.main.ui.context :as muc]
|
|
||||||
[app.main.ui.shapes.attrs :as attrs]
|
[app.main.ui.shapes.attrs :as attrs]
|
||||||
[app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
|
[app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]]
|
||||||
[app.main.ui.shapes.embed :as embed]
|
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
@ -20,19 +17,8 @@
|
||||||
[props]
|
[props]
|
||||||
|
|
||||||
(let [shape (unchecked-get props "shape")
|
(let [shape (unchecked-get props "shape")
|
||||||
{:keys [x y width height metadata]} shape
|
{:keys [x y width height]} shape
|
||||||
uri (cfg/resolve-file-media metadata)
|
|
||||||
embed (embed/use-data-uris [uri])
|
|
||||||
|
|
||||||
transform (gsh/transform-matrix shape)
|
transform (gsh/transform-matrix shape)
|
||||||
|
|
||||||
fill-attrs (-> (attrs/extract-fill-attrs shape)
|
|
||||||
(obj/set! "width" width)
|
|
||||||
(obj/set! "height" height))
|
|
||||||
|
|
||||||
render-id (mf/use-ctx muc/render-ctx)
|
|
||||||
fill-image-id (str "fill-image-" render-id)
|
|
||||||
shape (assoc shape :fill-image fill-image-id)
|
|
||||||
props (-> (attrs/extract-style-attrs shape)
|
props (-> (attrs/extract-style-attrs shape)
|
||||||
(obj/merge! (attrs/extract-border-radius-attrs shape))
|
(obj/merge! (attrs/extract-border-radius-attrs shape))
|
||||||
(obj/merge!
|
(obj/merge!
|
||||||
|
@ -44,19 +30,6 @@
|
||||||
path? (some? (.-d props))]
|
path? (some? (.-d props))]
|
||||||
|
|
||||||
[:g
|
[:g
|
||||||
[:defs
|
|
||||||
[:pattern {:id fill-image-id
|
|
||||||
:patternUnits "userSpaceOnUse"
|
|
||||||
:x x
|
|
||||||
:y y
|
|
||||||
:height height
|
|
||||||
:width width
|
|
||||||
:data-loading (str (not (contains? embed uri)))}
|
|
||||||
[:g
|
|
||||||
[:> :rect fill-attrs]
|
|
||||||
[:image {:xlinkHref (get embed uri uri)
|
|
||||||
:width width
|
|
||||||
:height height}]]]]
|
|
||||||
[:& shape-custom-stroke {:shape shape}
|
[:& shape-custom-stroke {:shape shape}
|
||||||
(if path?
|
(if path?
|
||||||
[:> :path props]
|
[:> :path props]
|
||||||
|
|
|
@ -13,8 +13,6 @@
|
||||||
[app.util.path.format :as upf]
|
[app.util.path.format :as upf]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
;; --- Path Shape
|
|
||||||
|
|
||||||
(mf/defc path-shape
|
(mf/defc path-shape
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
[app.main.ui.shapes.attrs :as attrs]
|
[app.main.ui.shapes.attrs :as attrs]
|
||||||
[app.main.ui.shapes.custom-stroke :as cs]
|
[app.main.ui.shapes.custom-stroke :as cs]
|
||||||
[app.main.ui.shapes.export :as ed]
|
[app.main.ui.shapes.export :as ed]
|
||||||
[app.main.ui.shapes.fill-image :as fim]
|
[app.main.ui.shapes.fills :as fills]
|
||||||
[app.main.ui.shapes.filters :as filters]
|
[app.main.ui.shapes.filters :as filters]
|
||||||
[app.main.ui.shapes.frame :as frame]
|
[app.main.ui.shapes.frame :as frame]
|
||||||
[app.main.ui.shapes.gradients :as grad]
|
[app.main.ui.shapes.gradients :as grad]
|
||||||
|
@ -63,9 +63,12 @@
|
||||||
[:defs
|
[:defs
|
||||||
[:& defs/svg-defs {:shape shape :render-id render-id}]
|
[:& defs/svg-defs {:shape shape :render-id render-id}]
|
||||||
[:& filters/filters {:shape shape :filter-id filter-id}]
|
[:& filters/filters {:shape shape :filter-id filter-id}]
|
||||||
[:& grad/gradient {:shape shape :attr :fill-color-gradient}]
|
|
||||||
[:& grad/gradient {:shape shape :attr :stroke-color-gradient}]
|
[:& grad/gradient {:shape shape :attr :stroke-color-gradient}]
|
||||||
[:& fim/fill-image-pattern {:shape shape :render-id render-id}]
|
(when (or (some? (:fill-image shape))
|
||||||
|
(= :image (:type shape))
|
||||||
|
(> (count (:fills shape)) 1)
|
||||||
|
(some :fill-color-gradient (:fills shape)))
|
||||||
|
[:& fills/fills {:shape shape :render-id render-id}])
|
||||||
[:& cs/stroke-defs {:shape shape :render-id render-id}]
|
[:& cs/stroke-defs {:shape shape :render-id render-id}]
|
||||||
[:& frame/frame-clip-def {:shape shape :render-id render-id}]]
|
[:& frame/frame-clip-def {:shape shape :render-id render-id}]]
|
||||||
children]]))
|
children]]))
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
(and
|
(and
|
||||||
(not (contains? #{:text :group} (:type shape)))
|
(not (contains? #{:text :group} (:type shape)))
|
||||||
(or (:fill-color shape)
|
(or (:fill-color shape)
|
||||||
(:fill-color-gradient shape))))
|
(:fill-color-gradient shape)
|
||||||
|
(seq (:fills shape)))))
|
||||||
|
|
||||||
(defn copy-data [shape]
|
(defn copy-data [shape]
|
||||||
(cg/generate-css-props
|
(cg/generate-css-props
|
||||||
|
@ -55,5 +56,9 @@
|
||||||
[:& copy-button {:data (copy-data (first shapes))}])]
|
[:& copy-button {:data (copy-data (first shapes))}])]
|
||||||
|
|
||||||
(for [shape shapes]
|
(for [shape shapes]
|
||||||
[:& fill-block {:key (str "fill-block-" (:id shape))
|
(if (seq (:fills shape))
|
||||||
:shape shape}])])))
|
(for [value (:fills shape [])]
|
||||||
|
[:& fill-block {:key (str "fill-block-" (:id shape))
|
||||||
|
:shape value}])
|
||||||
|
[:& fill-block {:key (str "fill-block-" (:id shape))
|
||||||
|
:shape shape}]))])))
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(if (kbd/alt? event)
|
(if (kbd/alt? event)
|
||||||
(st/emit! (mdc/change-stroke ids-with-children (merge uc/empty-color color)))
|
(st/emit! (mdc/change-stroke ids-with-children (merge uc/empty-color color)))
|
||||||
(st/emit! (mdc/change-fill ids-with-children (merge uc/empty-color color)))))]
|
(st/emit! (mdc/change-fill ids-with-children (merge uc/empty-color color) 0))))]
|
||||||
|
|
||||||
[:div.color-cell {:on-click select-color}
|
[:div.color-cell {:on-click select-color}
|
||||||
[:& cb/color-bullet {:color color}]
|
[:& cb/color-bullet {:color color}]
|
||||||
|
|
|
@ -799,7 +799,7 @@
|
||||||
(let [ids (wsh/lookup-selected @st/state)]
|
(let [ids (wsh/lookup-selected @st/state)]
|
||||||
(if (kbd/alt? event)
|
(if (kbd/alt? event)
|
||||||
(st/emit! (dc/change-stroke ids color))
|
(st/emit! (dc/change-stroke ids color))
|
||||||
(st/emit! (dc/change-fill ids color)))))
|
(st/emit! (dc/change-fill ids color 0)))))
|
||||||
|
|
||||||
rename-color
|
rename-color
|
||||||
(fn [name]
|
(fn [name]
|
||||||
|
|
|
@ -6,18 +6,23 @@
|
||||||
|
|
||||||
(ns app.main.ui.workspace.sidebar.options.menus.fill
|
(ns app.main.ui.workspace.sidebar.options.menus.fill
|
||||||
(:require
|
(:require
|
||||||
|
[app.common.attrs :as attrs]
|
||||||
|
[app.common.colors :as clr]
|
||||||
|
[app.common.data :as d]
|
||||||
[app.common.pages :as cp]
|
[app.common.pages :as cp]
|
||||||
[app.main.data.workspace.colors :as dc]
|
[app.main.data.workspace.colors :as dc]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.hooks :as h]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
|
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
|
||||||
[app.util.color :as uc]
|
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
|
[cuerdas.core :as str]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(def fill-attrs
|
(def fill-attrs
|
||||||
[:fill-color
|
[:fills
|
||||||
|
:fill-color
|
||||||
:fill-opacity
|
:fill-opacity
|
||||||
:fill-color-ref-id
|
:fill-color-ref-id
|
||||||
:fill-color-ref-file
|
:fill-color-ref-file
|
||||||
|
@ -27,24 +32,60 @@
|
||||||
(def fill-attrs-shape
|
(def fill-attrs-shape
|
||||||
(conj fill-attrs :hide-fill-on-export))
|
(conj fill-attrs :hide-fill-on-export))
|
||||||
|
|
||||||
|
(defn color-values
|
||||||
|
[color]
|
||||||
|
{:color (:fill-color color)
|
||||||
|
:opacity (:fill-opacity color)
|
||||||
|
:id (:fill-color-ref-id color)
|
||||||
|
:file-id (:fill-color-ref-file color)
|
||||||
|
:gradient (:fill-color-gradient color)})
|
||||||
|
|
||||||
(mf/defc fill-menu
|
(mf/defc fill-menu
|
||||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values"]))]}
|
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values"]))]}
|
||||||
[{:keys [ids type values disable-remove?] :as props}]
|
[{:keys [ids type values disable-remove?] :as props}]
|
||||||
(let [show? (or (not (nil? (:fill-color values)))
|
(let [label (case type
|
||||||
(not (nil? (:fill-color-gradient values))))
|
|
||||||
|
|
||||||
label (case type
|
|
||||||
:multiple (tr "workspace.options.selection-fill")
|
:multiple (tr "workspace.options.selection-fill")
|
||||||
:group (tr "workspace.options.group-fill")
|
:group (tr "workspace.options.group-fill")
|
||||||
(tr "workspace.options.fill"))
|
(tr "workspace.options.fill"))
|
||||||
|
|
||||||
color {:color (:fill-color values)
|
;; Excluding nil values
|
||||||
:opacity (:fill-opacity values)
|
values (d/without-nils values)
|
||||||
:id (:fill-color-ref-id values)
|
|
||||||
:file-id (:fill-color-ref-file values)
|
|
||||||
:gradient (:fill-color-gradient values)}
|
|
||||||
|
|
||||||
hide-fill-on-export? (:hide-fill-on-export values)
|
only-shapes? (and (contains? values :fills)
|
||||||
|
;; texts have :fill-* attributes, the rest of the shapes have :fills
|
||||||
|
(= (count (filter #(str/starts-with? (d/name %) "fill-") (keys values))) 0))
|
||||||
|
|
||||||
|
shapes-and-texts? (and (contains? values :fills)
|
||||||
|
;; texts have :fill-* attributes, the rest of the shapes have :fills
|
||||||
|
(> (count (filter #(str/starts-with? (d/name %) "fill-") (keys values))) 0))
|
||||||
|
|
||||||
|
;; Texts still have :fill-* attributes and the rest of the shapes just :fills so we need some extra calculation when multiple selection happens to detect them
|
||||||
|
plain-values (if (vector? (:fills values))
|
||||||
|
(concat (:fills values) [(dissoc values :fills)])
|
||||||
|
values)
|
||||||
|
|
||||||
|
plain-values (attrs/get-attrs-multi plain-values [:fill-color :fill-opacity :fill-color-ref-id :fill-color-ref-file :fill-color-gradient])
|
||||||
|
|
||||||
|
plain-values (if (empty? plain-values)
|
||||||
|
values
|
||||||
|
plain-values)
|
||||||
|
|
||||||
|
;; We must control some rare situations like
|
||||||
|
;; - Selecting texts and shapes with different fills
|
||||||
|
;; - Selecting a text and a shape with empty fills
|
||||||
|
plain-values (if (and shapes-and-texts?
|
||||||
|
(or
|
||||||
|
(= (:fills values) :multiple)
|
||||||
|
(= 0 (count (:fills values)))))
|
||||||
|
{:fills :multiple
|
||||||
|
:fill-color :multiple
|
||||||
|
:fill-opacity :multiple
|
||||||
|
:fill-color-ref-id :multiple
|
||||||
|
:fill-color-ref-file :multiple
|
||||||
|
:fill-color-gradient :multiple}
|
||||||
|
plain-values)
|
||||||
|
|
||||||
|
hide-fill-on-export? (:hide-fill-on-export values false)
|
||||||
|
|
||||||
checkbox-ref (mf/use-ref)
|
checkbox-ref (mf/use-ref)
|
||||||
|
|
||||||
|
@ -52,31 +93,47 @@
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps ids)
|
(mf/deps ids)
|
||||||
(fn [_]
|
(fn [_]
|
||||||
(st/emit! (dc/change-fill ids {:color cp/default-color
|
(st/emit! (dc/add-fill ids {:color cp/default-color
|
||||||
:opacity 1}))))
|
:opacity 1}))))
|
||||||
|
|
||||||
on-delete
|
|
||||||
(mf/use-callback
|
|
||||||
(mf/deps ids)
|
|
||||||
(fn [_]
|
|
||||||
(st/emit! (dc/change-fill ids (into {} uc/empty-color)))))
|
|
||||||
|
|
||||||
on-change
|
on-change
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps ids)
|
||||||
|
(fn [index]
|
||||||
|
(fn [color]
|
||||||
|
(st/emit! (dc/change-fill ids color index)))))
|
||||||
|
|
||||||
|
on-reorder
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps ids)
|
||||||
|
(fn [new-index]
|
||||||
|
(fn [index]
|
||||||
|
(st/emit! (dc/reorder-fills ids index new-index)))))
|
||||||
|
|
||||||
|
on-change-mixed-shapes
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps ids)
|
(mf/deps ids)
|
||||||
(fn [color]
|
(fn [color]
|
||||||
(let [remove-multiple (fn [[_ value]] (not= value :multiple))
|
(st/emit! (dc/change-fill-and-clear ids color))))
|
||||||
color (into {} (filter remove-multiple) color)]
|
|
||||||
(st/emit! (dc/change-fill ids color)))))
|
on-remove
|
||||||
|
(fn [index]
|
||||||
|
(fn []
|
||||||
|
(st/emit! (dc/remove-fill ids {:color cp/default-color
|
||||||
|
:opacity 1} index))))
|
||||||
|
on-remove-all
|
||||||
|
(fn [_]
|
||||||
|
(st/emit! (dc/remove-all-fills ids {:color clr/black
|
||||||
|
:opacity 1})))
|
||||||
|
|
||||||
on-detach
|
on-detach
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps ids)
|
(mf/deps ids)
|
||||||
(fn []
|
(fn [index]
|
||||||
(let [remove-multiple (fn [[_ value]] (not= value :multiple))
|
(fn [color]
|
||||||
color (-> (into {} (filter remove-multiple) color)
|
(let [color (-> color
|
||||||
(assoc :id nil :file-id nil))]
|
(assoc :id nil :file-id nil))]
|
||||||
(st/emit! (dc/change-fill ids color)))))
|
(st/emit! (dc/change-fill ids color index))))))
|
||||||
|
|
||||||
on-change-show-fill-on-export
|
on-change-show-fill-on-export
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
@ -95,18 +152,46 @@
|
||||||
(dom/set-attribute checkbox "indeterminate" true)
|
(dom/set-attribute checkbox "indeterminate" true)
|
||||||
(dom/remove-attribute checkbox "indeterminate")))))
|
(dom/remove-attribute checkbox "indeterminate")))))
|
||||||
|
|
||||||
(if show?
|
|
||||||
[:div.element-set
|
[:div.element-set
|
||||||
[:div.element-set-title
|
[:div.element-set-title
|
||||||
[:span label]
|
[:span label]
|
||||||
(when (not disable-remove?)
|
(when (and (not disable-remove?) (not (= :multiple (:fills values))) only-shapes?)
|
||||||
[:div.add-page {:on-click on-delete} i/minus])]
|
[:div.add-page {:on-click on-add} i/close])]
|
||||||
|
|
||||||
[:div.element-set-content
|
[:div.element-set-content
|
||||||
[:& color-row {:color color
|
|
||||||
:title (tr "workspace.options.fill")
|
(if only-shapes?
|
||||||
:on-change on-change
|
(cond
|
||||||
:on-detach on-detach}]
|
(= :multiple (:fills values))
|
||||||
|
[:div.element-set-options-group
|
||||||
|
[:div.element-set-label (tr "settings.multiple")]
|
||||||
|
[:div.element-set-actions
|
||||||
|
[:div.element-set-actions-button {:on-click on-remove-all}
|
||||||
|
i/minus]]]
|
||||||
|
|
||||||
|
(seq (:fills values))
|
||||||
|
[:& h/sortable-container {}
|
||||||
|
(for [[index value] (d/enumerate (:fills values []))]
|
||||||
|
[:& color-row {:color {:color (:fill-color value)
|
||||||
|
:opacity (:fill-opacity value)
|
||||||
|
:id (:fill-color-ref-id value)
|
||||||
|
:file-id (:fill-color-ref-file value)
|
||||||
|
:gradient (:fill-color-gradient value)}
|
||||||
|
:index index
|
||||||
|
:title (tr "workspace.options.fill")
|
||||||
|
:on-change (on-change index)
|
||||||
|
:on-reorder (on-reorder index)
|
||||||
|
:on-detach (on-detach index)
|
||||||
|
:on-remove (on-remove index)}])])
|
||||||
|
|
||||||
|
[:& color-row {:color {:color (:fill-color plain-values)
|
||||||
|
:opacity (:fill-opacity plain-values)
|
||||||
|
:id (:fill-color-ref-id plain-values)
|
||||||
|
:file-id (:fill-color-ref-file plain-values)
|
||||||
|
:gradient (:fill-color-gradient plain-values)}
|
||||||
|
:title (tr "workspace.options.fill")
|
||||||
|
:on-change on-change-mixed-shapes
|
||||||
|
:on-detach (on-detach 0)}])
|
||||||
|
|
||||||
(when (or (= type :frame)
|
(when (or (= type :frame)
|
||||||
(and (= type :multiple) (some? hide-fill-on-export?)))
|
(and (= type :multiple) (some? hide-fill-on-export?)))
|
||||||
|
@ -118,9 +203,4 @@
|
||||||
:on-change on-change-show-fill-on-export}]
|
:on-change on-change-show-fill-on-export}]
|
||||||
|
|
||||||
[:label {:for "show-fill-on-export"}
|
[:label {:for "show-fill-on-export"}
|
||||||
(tr "workspace.options.show-fill-on-export")]])]]
|
(tr "workspace.options.show-fill-on-export")]])]]))
|
||||||
|
|
||||||
[:div.element-set
|
|
||||||
[:div.element-set-title
|
|
||||||
[:span label]
|
|
||||||
[:div.add-page {:on-click on-add} i/close]]])))
|
|
||||||
|
|
|
@ -61,11 +61,12 @@
|
||||||
(if (= v :multiple) nil v))
|
(if (= v :multiple) nil v))
|
||||||
|
|
||||||
(mf/defc color-row
|
(mf/defc color-row
|
||||||
[{:keys [color disable-gradient disable-opacity on-change on-detach on-open on-close title]}]
|
[{:keys [index color disable-gradient disable-opacity on-change on-reorder on-detach on-open on-close title on-remove]}]
|
||||||
(let [current-file-id (mf/use-ctx ctx/current-file-id)
|
(let [current-file-id (mf/use-ctx ctx/current-file-id)
|
||||||
file-colors (mf/deref refs/workspace-file-colors)
|
file-colors (mf/deref refs/workspace-file-colors)
|
||||||
shared-libs (mf/deref refs/workspace-libraries)
|
shared-libs (mf/deref refs/workspace-libraries)
|
||||||
hover-detach (mf/use-state false)
|
hover-detach (mf/use-state false)
|
||||||
|
disable-drag (mf/use-state false)
|
||||||
|
|
||||||
get-color-name (fn [{:keys [id file-id]}]
|
get-color-name (fn [{:keys [id file-id]}]
|
||||||
(let [src-colors (if (= file-id current-file-id)
|
(let [src-colors (if (= file-id current-file-id)
|
||||||
|
@ -77,6 +78,9 @@
|
||||||
(-> color
|
(-> color
|
||||||
(update :color #(or % (:value color)))))
|
(update :color #(or % (:value color)))))
|
||||||
|
|
||||||
|
detach-value (fn []
|
||||||
|
(when on-detach (on-detach color)))
|
||||||
|
|
||||||
change-value (fn [new-value]
|
change-value (fn [new-value]
|
||||||
(when on-change (on-change (-> color
|
(when on-change (on-change (-> color
|
||||||
(assoc :color new-value)
|
(assoc :color new-value)
|
||||||
|
@ -104,7 +108,12 @@
|
||||||
(change-opacity (/ value 100)))
|
(change-opacity (/ value 100)))
|
||||||
|
|
||||||
select-all (fn [event]
|
select-all (fn [event]
|
||||||
(dom/select-text! (dom/get-target event)))
|
(when (not @disable-drag)
|
||||||
|
(dom/select-text! (dom/get-target event)))
|
||||||
|
(reset! disable-drag true))
|
||||||
|
|
||||||
|
on-blur (fn [_]
|
||||||
|
(reset! disable-drag false))
|
||||||
|
|
||||||
handle-click-color (mf/use-callback
|
handle-click-color (mf/use-callback
|
||||||
(mf/deps color)
|
(mf/deps color)
|
||||||
|
@ -115,7 +124,22 @@
|
||||||
handle-open
|
handle-open
|
||||||
handle-close))
|
handle-close))
|
||||||
|
|
||||||
prev-color (h/use-previous color)]
|
prev-color (h/use-previous color)
|
||||||
|
|
||||||
|
on-drop
|
||||||
|
(fn [_ data]
|
||||||
|
(on-reorder (:index data)))
|
||||||
|
|
||||||
|
[dprops dref] (if (some? on-reorder)
|
||||||
|
(h/use-sortable
|
||||||
|
:data-type "penpot/color-row"
|
||||||
|
:on-drop on-drop
|
||||||
|
:disabled @disable-drag
|
||||||
|
:detect-center? false
|
||||||
|
:data {:id (str "color-row-" index)
|
||||||
|
:index index
|
||||||
|
:name (str "Color row" index)})
|
||||||
|
[nil nil])]
|
||||||
|
|
||||||
(mf/use-effect
|
(mf/use-effect
|
||||||
(mf/deps color prev-color)
|
(mf/deps color prev-color)
|
||||||
|
@ -123,7 +147,11 @@
|
||||||
(when (not= prev-color color)
|
(when (not= prev-color color)
|
||||||
(modal/update-props! :colorpicker {:data (parse-color color)}))))
|
(modal/update-props! :colorpicker {:data (parse-color color)}))))
|
||||||
|
|
||||||
[:div.row-flex.color-data {:title title}
|
[:div.row-flex.color-data {:title title
|
||||||
|
:class (dom/classnames
|
||||||
|
:dnd-over-top (= (:over dprops) :top)
|
||||||
|
:dnd-over-bot (= (:over dprops) :bot))
|
||||||
|
:ref dref}
|
||||||
[:& cb/color-bullet {:color color
|
[:& cb/color-bullet {:color color
|
||||||
:on-click handle-click-color}]
|
:on-click handle-click-color}]
|
||||||
|
|
||||||
|
@ -137,7 +165,7 @@
|
||||||
[:div.element-set-actions-button
|
[:div.element-set-actions-button
|
||||||
{:on-mouse-enter #(reset! hover-detach true)
|
{:on-mouse-enter #(reset! hover-detach true)
|
||||||
:on-mouse-leave #(reset! hover-detach false)
|
:on-mouse-leave #(reset! hover-detach false)
|
||||||
:on-click on-detach}
|
:on-click detach-value}
|
||||||
(if @hover-detach i/unchain i/chain)])]
|
(if @hover-detach i/unchain i/chain)])]
|
||||||
|
|
||||||
;; Rendering a gradient
|
;; Rendering a gradient
|
||||||
|
@ -156,6 +184,7 @@
|
||||||
(-> color :color uc/remove-hash))
|
(-> color :color uc/remove-hash))
|
||||||
:placeholder (tr "settings.multiple")
|
:placeholder (tr "settings.multiple")
|
||||||
:on-click select-all
|
:on-click select-all
|
||||||
|
:on-blur on-blur
|
||||||
:on-change handle-value-change}]]
|
:on-change handle-value-change}]]
|
||||||
|
|
||||||
(when (and (not disable-opacity)
|
(when (and (not disable-opacity)
|
||||||
|
@ -167,5 +196,7 @@
|
||||||
:on-click select-all
|
:on-click select-all
|
||||||
:on-change handle-opacity-change
|
:on-change handle-opacity-change
|
||||||
:min 0
|
:min 0
|
||||||
:max 100}]])])]))
|
:max 100}]])])
|
||||||
|
(when (some? on-remove)
|
||||||
|
[:div.element-set-actions-button.remove {:on-click on-remove} i/minus])]))
|
||||||
|
|
||||||
|
|
|
@ -358,16 +358,40 @@
|
||||||
(= type :path)
|
(= type :path)
|
||||||
(parse-path center svg-data))))
|
(parse-path center svg-data))))
|
||||||
|
|
||||||
|
(defn add-library-refs
|
||||||
|
[props node]
|
||||||
|
|
||||||
|
(let [stroke-color-ref-id (get-meta node :stroke-color-ref-id uuid/uuid)
|
||||||
|
stroke-color-ref-file (get-meta node :stroke-color-ref-file uuid/uuid)
|
||||||
|
component-id (get-meta node :component-id uuid/uuid)
|
||||||
|
component-file (get-meta node :component-file uuid/uuid)
|
||||||
|
shape-ref (get-meta node :shape-ref uuid/uuid)
|
||||||
|
component-root? (get-meta node :component-root str->bool)]
|
||||||
|
|
||||||
|
(cond-> props
|
||||||
|
(some? stroke-color-ref-id)
|
||||||
|
(assoc :stroke-color-ref-id stroke-color-ref-id
|
||||||
|
:stroke-color-ref-file stroke-color-ref-file)
|
||||||
|
|
||||||
|
(some? component-id)
|
||||||
|
(assoc :component-id component-id
|
||||||
|
:component-file component-file)
|
||||||
|
|
||||||
|
component-root?
|
||||||
|
(assoc :component-root? component-root?)
|
||||||
|
|
||||||
|
(some? shape-ref)
|
||||||
|
(assoc :shape-ref shape-ref))))
|
||||||
|
|
||||||
(defn add-fill
|
(defn add-fill
|
||||||
[props node svg-data]
|
[props node svg-data]
|
||||||
|
|
||||||
(let [fill (:fill svg-data)
|
(let [fill (:fill svg-data)
|
||||||
hide-fill-on-export (get-meta node :hide-fill-on-export str->bool)
|
hide-fill-on-export (get-meta node :hide-fill-on-export str->bool)
|
||||||
|
fill-color-ref-id (get-meta node :fill-color-ref-id uuid/uuid)
|
||||||
|
fill-color-ref-file (get-meta node :fill-color-ref-file uuid/uuid)
|
||||||
gradient (when (str/starts-with? fill "url")
|
gradient (when (str/starts-with? fill "url")
|
||||||
(parse-gradient node fill))
|
(parse-gradient node fill))]
|
||||||
meta-fill-color (get-meta node :fill-color)
|
|
||||||
meta-fill-opacity (get-meta node :fill-opacity)
|
|
||||||
meta-fill-color-gradient (get-meta node :fill-color-gradient)]
|
|
||||||
|
|
||||||
(cond-> props
|
(cond-> props
|
||||||
:always
|
:always
|
||||||
|
@ -386,14 +410,9 @@
|
||||||
(some? hide-fill-on-export)
|
(some? hide-fill-on-export)
|
||||||
(assoc :hide-fill-on-export hide-fill-on-export)
|
(assoc :hide-fill-on-export hide-fill-on-export)
|
||||||
|
|
||||||
(some? meta-fill-color)
|
(some? fill-color-ref-id)
|
||||||
(assoc :fill-color meta-fill-color
|
(assoc :fill-color-ref-id fill-color-ref-id
|
||||||
:fill-opacity (d/parse-double meta-fill-opacity))
|
:fill-color-ref-file fill-color-ref-file))))
|
||||||
|
|
||||||
(some? meta-fill-color-gradient)
|
|
||||||
(assoc :fill-color-gradient meta-fill-color-gradient
|
|
||||||
:fill-color nil
|
|
||||||
:fill-opacity nil))))
|
|
||||||
|
|
||||||
(defn add-stroke
|
(defn add-stroke
|
||||||
[props node svg-data]
|
[props node svg-data]
|
||||||
|
@ -658,6 +677,21 @@
|
||||||
|
|
||||||
props)))
|
props)))
|
||||||
|
|
||||||
|
(defn add-fills
|
||||||
|
[props node svg-data]
|
||||||
|
(let [fills (-> node
|
||||||
|
(find-node :defs)
|
||||||
|
(find-node :pattern)
|
||||||
|
(find-node :g)
|
||||||
|
(find-all-nodes :rect)
|
||||||
|
(reverse))
|
||||||
|
fills (if (= 0 (count fills))
|
||||||
|
[(add-fill {} node svg-data)]
|
||||||
|
(map #(add-fill {} node (get-svg-data :rect %)) fills))]
|
||||||
|
(-> props
|
||||||
|
(assoc :fills fills))))
|
||||||
|
|
||||||
|
|
||||||
(defn add-svg-content
|
(defn add-svg-content
|
||||||
[props node]
|
[props node]
|
||||||
(let [svg-content (get-data node :penpot:svg-content)
|
(let [svg-content (get-data node :penpot:svg-content)
|
||||||
|
@ -715,37 +749,6 @@
|
||||||
svg-data (or image-data pattern-data)]
|
svg-data (or image-data pattern-data)]
|
||||||
(:xlink:href svg-data)))
|
(:xlink:href svg-data)))
|
||||||
|
|
||||||
(defn add-library-refs
|
|
||||||
[props node]
|
|
||||||
|
|
||||||
(let [fill-color-ref-id (get-meta node :fill-color-ref-id uuid/uuid)
|
|
||||||
fill-color-ref-file (get-meta node :fill-color-ref-file uuid/uuid)
|
|
||||||
stroke-color-ref-id (get-meta node :stroke-color-ref-id uuid/uuid)
|
|
||||||
stroke-color-ref-file (get-meta node :stroke-color-ref-file uuid/uuid)
|
|
||||||
component-id (get-meta node :component-id uuid/uuid)
|
|
||||||
component-file (get-meta node :component-file uuid/uuid)
|
|
||||||
shape-ref (get-meta node :shape-ref uuid/uuid)
|
|
||||||
component-root? (get-meta node :component-root str->bool)]
|
|
||||||
|
|
||||||
(cond-> props
|
|
||||||
(some? fill-color-ref-id)
|
|
||||||
(assoc :fill-color-ref-id fill-color-ref-id
|
|
||||||
:fill-color-ref-file fill-color-ref-file)
|
|
||||||
|
|
||||||
(some? stroke-color-ref-id)
|
|
||||||
(assoc :stroke-color-ref-id stroke-color-ref-id
|
|
||||||
:stroke-color-ref-file stroke-color-ref-file)
|
|
||||||
|
|
||||||
(some? component-id)
|
|
||||||
(assoc :component-id component-id
|
|
||||||
:component-file component-file)
|
|
||||||
|
|
||||||
component-root?
|
|
||||||
(assoc :component-root? component-root?)
|
|
||||||
|
|
||||||
(some? shape-ref)
|
|
||||||
(assoc :shape-ref shape-ref))))
|
|
||||||
|
|
||||||
(defn parse-data
|
(defn parse-data
|
||||||
[type node]
|
[type node]
|
||||||
|
|
||||||
|
@ -754,7 +757,6 @@
|
||||||
(-> {}
|
(-> {}
|
||||||
(add-common-data node)
|
(add-common-data node)
|
||||||
(add-position type node svg-data)
|
(add-position type node svg-data)
|
||||||
(add-fill node svg-data)
|
|
||||||
(add-stroke node svg-data)
|
(add-stroke node svg-data)
|
||||||
(add-layer-options svg-data)
|
(add-layer-options svg-data)
|
||||||
(add-shadows node)
|
(add-shadows node)
|
||||||
|
@ -762,6 +764,7 @@
|
||||||
(add-exports node)
|
(add-exports node)
|
||||||
(add-svg-attrs node svg-data)
|
(add-svg-attrs node svg-data)
|
||||||
(add-library-refs node)
|
(add-library-refs node)
|
||||||
|
(add-fills node svg-data)
|
||||||
|
|
||||||
(cond-> (= :svg-raw type)
|
(cond-> (= :svg-raw type)
|
||||||
(add-svg-content node))
|
(add-svg-content node))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue