Merge pull request #6674 from penpot/niwinz-develop-enhacements-3

 Refactor fills-menu and related components
This commit is contained in:
Andrey Antukh 2025-06-26 11:09:30 +02:00 committed by GitHub
commit f2c4a1eb1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 402 additions and 341 deletions

View file

@ -109,19 +109,27 @@
[fill]
(assoc fills position fill)))))
(defn transform-fill*
"A lower-level companion function for `transform-fill`"
[state ids transform options]
(let [page-id (or (get options :page-id)
(get state :current-page-id))
objects (dsh/lookup-page-objects state page-id)
[text-ids shape-ids]
(split-text-shapes objects ids)]
(rx/concat
(->> (rx/from text-ids)
(rx/map #(dwt/update-text-with-function % transform options)))
(rx/of (dwsh/update-shapes shape-ids transform options)))))
(defn transform-fill
"A low level function that creates a shape fill transformations stream"
([state ids color transform]
(transform-fill state ids color transform nil))
([state ids color transform options]
(let [page-id (or (get options :page-id)
(get state :current-page-id))
objects (dsh/lookup-page-objects state page-id)
[text-ids shape-ids]
(split-text-shapes objects ids)
fill
(let [fill
(cond-> {}
(contains? color :color)
(assoc :fill-color (:color color))
@ -147,12 +155,10 @@
:always
(types.fill/check-fill))
transform-attrs #(transform % fill)]
transform-attrs
#(transform % fill)]
(rx/concat
(->> (rx/from text-ids)
(rx/map #(dwt/update-text-with-function % transform-attrs options)))
(rx/of (dwsh/update-shapes shape-ids transform-attrs options))))))
(transform-fill* state ids transform-attrs options))))
(defn swap-attrs [shape attr index new-index]
(let [first (get-in shape [attr index])
@ -228,12 +234,40 @@
(transform-fill state ids color change-fn options)
(rx/of (dwu/commit-undo-transaction undo-id)))))))))
(defn remove-fill
([ids color position] (remove-fill ids color position nil))
([ids color position options]
(defn detach-fill
([ids position] (detach-fill ids position nil))
([ids position options]
(assert (types.color/check-color color)
"expected a valid color struct")
(assert (number? position)
"expected a valid number for position")
(assert (every? uuid? ids)
"expected a valid coll of uuid's")
(ptk/reify ::detach-fill
ptk/WatchEvent
(watch [_ state _]
(let [detach-fn
(fn [values index]
(update values index dissoc :fill-color-ref-id :fill-color-ref-file))
change-fn
;; The `node` can be a shape or a text content node
(fn [node]
(update node :fills detach-fn position))
undo-id
(js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(transform-fill* state ids change-fn options)
(rx/of (dwu/commit-undo-transaction undo-id))))))))
(defn remove-fill
([ids position] (remove-fill ids position nil))
([ids position options]
(assert (number? position)
"expected a valid number for position")
(assert (every? uuid? ids)
"expected a valid coll of uuid's")
@ -242,37 +276,38 @@
(watch [_ state _]
(let [remove-fill-by-index
(fn [values index]
(->> (d/enumerate values)
(filterv (fn [[idx _]] (not= idx index)))
(mapv second)))
(into []
(comp
(map-indexed (fn [i o] (when (not= i index) o)))
(filter some?))
values))
change-fn
(fn [shape _] (update shape :fills remove-fill-by-index position))
;; The `node` can be a shape or a text content node
(fn [node]
(update node :fills remove-fill-by-index position))
undo-id
(js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(transform-fill state ids color change-fn options)
(transform-fill* state ids change-fn options)
(rx/of (dwu/commit-undo-transaction undo-id))))))))
(defn remove-all-fills
([ids color] (remove-all-fills ids color nil))
([ids color options]
(assert (types.color/check-color color) "expected a valid color struct")
([ids] (remove-all-fills ids nil))
([ids options]
(assert (every? uuid? ids) "expected a valid coll of uuid's")
(ptk/reify ::remove-all-fills
ptk/WatchEvent
(watch [_ state _]
(let [change-fn (fn [shape _] (assoc shape :fills []))
(let [change-fn (fn [node] (assoc node :fills []))
undo-id (js/Symbol)]
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(transform-fill state ids color change-fn options)
(transform-fill* state ids change-fn options)
(rx/of (dwu/commit-undo-transaction undo-id))))))))
(defn change-hide-fill-on-export
@ -743,7 +778,8 @@
[{:keys [type current-color stops gradient opacity] :as state}]
(cond
(= type :color)
(clear-color-components current-color)
(-> (clear-color-components current-color)
(dissoc :offset))
(= type :image)
(clear-image-components current-color)

View file

@ -8,32 +8,27 @@
(:require
[app.common.colors :as cc]
[app.common.data :as d]
[app.main.ui.hooks :as hooks]
[app.util.dom :as dom]
[app.util.globals :as globals]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[goog.events :as events]
[rumext.v2 :as mf]))
(defn clean-color
[value]
(-> value
(defn- get-clean-color
[node]
(-> (dom/get-value node)
(cc/expand-hex)
(cc/parse)
(cc/prepend-hash)))
(mf/defc color-input*
{::mf/wrap-props false
::mf/forward-ref true}
[props external-ref]
(let [value (obj/get props "value")
on-change (obj/get props "onChange")
on-blur (obj/get props "onBlur")
on-focus (obj/get props "onFocus")
select-on-focus? (d/nilv (unchecked-get props "selectOnFocus") true)
class (d/nilv (unchecked-get props "className") "color-input")
aria-label (d/nilv (unchecked-get props "aria-label") (tr "inspect.attributes.color"))
{::mf/forward-ref true}
[{:keys [value on-change on-blur on-focus select-on-focus class aria-label] :rest props} external-ref]
(let [select-on-focus? (d/nilv select-on-focus true)
class (d/nilv class "color-input")
aria-label (or aria-label (tr "inspect.attributes.color"))
;; We need a ref pointing to the input dom element, but the user
;; of this component may provide one (that is forwarded here).
@ -42,25 +37,22 @@
ref (or external-ref local-ref)
;; We need to store the handle-blur ref so we can call it on unmount
handle-blur-ref (mf/use-ref nil)
dirty-ref (mf/use-ref false)
parse-value
(mf/use-fn
(mf/deps ref)
(fn []
(let [input-node (mf/ref-val ref)]
(try
(let [new-value (clean-color (dom/get-value input-node))]
(let [value (get-clean-color input-node)]
(dom/set-validity! input-node "")
new-value)
value)
(catch :default _e
(dom/set-validity! input-node (tr "errors.invalid-color"))
nil)))))
update-input
(mf/use-fn
(mf/deps ref)
(fn [new-value]
(let [input-node (mf/ref-val ref)]
(dom/set-value! input-node (cc/remove-hash new-value)))))
@ -71,8 +63,7 @@
(fn [new-value]
(mf/set-ref-val! dirty-ref false)
(when (and new-value (not= (cc/remove-hash new-value) value))
(when on-change
(on-change new-value))
(when on-change (on-change new-value))
(update-input new-value))))
handle-key-down
@ -80,38 +71,27 @@
(mf/deps apply-value update-input)
(fn [event]
(mf/set-ref-val! dirty-ref true)
(let [enter? (kbd/enter? event)
esc? (kbd/esc? event)
input-node (mf/ref-val ref)]
(when enter?
(dom/prevent-default event)
(let [new-value (parse-value)]
(apply-value new-value)
(dom/blur! input-node)))
(when esc?
(dom/prevent-default event)
(update-input value)
(dom/blur! input-node)))))
(let [input-node (mf/ref-val ref)]
(cond
(kbd/enter? event)
(let [value (parse-value)]
(update-input value)
(dom/prevent-default event)
(dom/blur! input-node))
handle-blur
(mf/use-fn
(mf/deps parse-value apply-value update-input)
(fn [_]
(let [new-value (parse-value)]
(when on-blur
(on-blur))
(if new-value
(apply-value new-value)
(update-input value)))))
(kbd/esc? event)
(do
(update-input value)
(dom/prevent-default event)
(dom/blur! input-node))))))
on-click
(mf/use-fn
(fn [event]
(let [target (dom/get-target event)]
(when (some? ref)
(let [current (mf/ref-val ref)]
(when (and (some? current) (not (.contains current target)))
(dom/blur! current)))))))
(let [target (dom/get-target event)
current (mf/ref-val ref)]
(when (and (some? current) (not (.contains current target)))
(dom/blur! current)))))
on-mouse-up
(mf/use-fn
@ -122,57 +102,53 @@
(mf/use-fn
(fn [event]
(let [target (dom/get-target event)]
(when on-focus
(on-focus event))
(when on-focus (on-focus))
(when select-on-focus?
(-> event (dom/get-target) (.select))
;; In webkit browsers the mouseup event will be called after the on-focus causing and unselect
(.addEventListener target "mouseup" on-mouse-up #js {"once" true})))))
;; In webkit browsers the mouseup event will be called after the on-focus causing and unselect
(.addEventListener target "mouseup" on-mouse-up #js {:once true})))))
props (-> (obj/clone props)
(obj/unset! "selectOnFocus")
(obj/set! "value" mf/undefined)
(obj/set! "onChange" mf/undefined)
(obj/set! "className" class)
(obj/set! "type" "text")
(obj/set! "ref" ref)
(obj/set! "aria-label" aria-label)
;; (obj/set! "list" list-id)
(obj/set! "defaultValue" value)
(obj/set! "onKeyDown" handle-key-down)
(obj/set! "onBlur" handle-blur)
(obj/set! "onFocus" handle-focus))]
handle-blur
(mf/use-fn
(mf/deps parse-value apply-value update-input)
(fn [_]
(let [new-value (parse-value)]
(if new-value
(apply-value new-value)
(update-input value))
(when on-blur
(on-blur)))))
(mf/use-effect
(mf/deps value)
(fn []
(when-let [node (mf/ref-val ref)]
(dom/set-value! node value))))
handle-blur
(hooks/use-ref-callback handle-blur)
(mf/use-effect
(mf/deps handle-blur)
(fn []
(mf/set-ref-val! handle-blur-ref {:fn handle-blur})))
props
(mf/spread-props props
{:class class
:type "text"
:ref ref
:aria-label aria-label
:default-value value
:on-key-down handle-key-down
:on-blur handle-blur
:on-focus handle-focus})]
(mf/use-layout-effect
(fn []
#(when (mf/ref-val dirty-ref)
(let [handle-blur (:fn (mf/ref-val handle-blur-ref))]
(handle-blur)))))
(mf/with-effect [value]
(when-let [node (mf/ref-val ref)]
(dom/set-value! node value)))
(mf/use-layout-effect
(fn []
(let [keys [(events/listen globals/window "pointerdown" on-click)
(events/listen globals/window "click" on-click)]]
#(doseq [key keys]
(events/unlistenByKey key)))))
(mf/with-layout-effect []
;; UNMOUNT: we use layout-effect because we still need the dom
;; node to be present on the refs, for properly execute the
;; on-blur event handler
#(when (mf/ref-val dirty-ref) (handle-blur)))
[:*
[:> :input props]
;; FIXME: this causes some weird interactions because of using apply-value
;; [:datalist {:id list-id}
;; (for [color-name cc/color-names]
;; [:option color-name])]
]))
(mf/with-layout-effect []
(let [key1 (events/listen globals/window "pointerdown" on-click)
key2 (events/listen globals/window "click" on-click)]
#(do
(events/unlistenByKey key1)
(events/unlistenByKey key2))))
[:> :input props]))

View file

@ -7,15 +7,14 @@
(ns app.main.ui.components.reorder-handler
(:require-macros [app.main.style :as stl])
(:require
[app.main.ui.ds.foundations.assets.icon :refer [icon*] :as ic]
[app.main.ui.ds.foundations.assets.icon :as ic]
[rumext.v2 :as mf]))
(mf/defc reorder-handler
{::mf/forward-ref true}
[_ ref]
(mf/defc reorder-handler*
[{:keys [ref]}]
[:*
[:div {:ref ref :class (stl/css :reorder)}
[:> icon*
[:> ic/icon*
{:icon-id ic/reorder
:class (stl/css :reorder-icon)
:aria-hidden true}]]

View file

@ -15,7 +15,7 @@
[app.config :as cfg]
[app.main.features :as features]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.reorder-handler :refer [reorder-handler]]
[app.main.ui.components.reorder-handler :refer [reorder-handler*]]
[app.main.ui.components.select :refer [select]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.formats :as fmt]
@ -139,7 +139,7 @@
:dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot))}
[:& reorder-handler {:ref dref}]
[:> reorder-handler* {:ref dref}]
[:div {:class (stl/css :offset-input-wrapper)}
[:span {:class (stl/css :icon-text)} "%"]

View file

@ -7,8 +7,6 @@
(ns app.main.ui.workspace.sidebar.options.menus.fill
(:require-macros [app.main.style :as stl])
(:require
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.types.color :as ctc]
[app.common.types.fill :as types.fill]
[app.common.types.shape.attrs :refer [default-color]]
@ -24,127 +22,162 @@
[app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf]))
;; FIXME:revisit this
(def fill-attrs
[:fills
:fill-color
:fill-opacity
:fill-color-ref-id
:fill-color-ref-file
:fill-color-gradient
:hide-fill-on-export])
#{:fills :hide-fill-on-export})
(def fill-attrs-shape
(conj fill-attrs :hide-fill-on-export))
(def ^:private
xf:take-max-fills
(take types.fill/MAX-FILLS))
(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)})
(def ^:private
xf:enumerate
(map-indexed
(fn [index item]
(let [color (ctc/fill->color item)]
(with-meta item {:index index :color color})))))
(mf/defc fill-menu
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values"]))]}
[{:keys [ids type values] :as props}]
(let [label (case type
:multiple (tr "workspace.options.selection-fill")
:group (tr "workspace.options.group-fill")
(tr "workspace.options.fill"))
(def ^:private ^boolean binary-fills-enabled?
(contains? cfg/flags :frontend-binary-fills))
;; Excluding nil values
values (d/without-nils values)
fills (if (contains? cfg/flags :frontend-binary-fills)
(take types.fill/MAX-FILLS (d/nilv (:fills values) []))
(:fills values))
has-fills? (or (= :multiple fills) (some? (seq fills)))
can-add-fills? (if (contains? cfg/flags :frontend-binary-fills)
(and (not (= :multiple fills))
(< (count fills) types.fill/MAX-FILLS))
(not (= :multiple fills)))
(def ^:private
xf:process-fills
(if binary-fills-enabled?
(comp xf:take-max-fills xf:enumerate)
xf:enumerate))
state* (mf/use-state has-fills?)
open? (deref state*)
(defn- prepare-fills
"Internal helper hook that prepares fills"
[fills]
(if (= :multiple fills)
fills
(->> fills
(into [] xf:process-fills)
(not-empty))))
toggle-content (mf/use-fn #(swap! state* not))
(defn- check-props
"A fills-menu specific memoize check function that only checks if
specific values are changed on provided props. This allows pass the
whole shape as values without adding additional rerenders when other
shape properties changes."
[n-props o-props]
(and (identical? (unchecked-get n-props "ids")
(unchecked-get o-props "ids"))
(let [o-vals (unchecked-get o-props "values")
n-vals (unchecked-get n-props "values")
o-fills (get o-vals :fills)
n-fills (get n-vals :fills)
o-hide (get o-vals :hide-fill-on-export)
n-hide (get n-vals :hide-fill-on-export)]
(and (identical? o-hide n-hide)
(identical? o-fills n-fills)))))
open-content (mf/use-fn #(reset! state* true))
(mf/defc fill-menu*
{::mf/wrap [#(mf/memo' % check-props)]}
[{:keys [ids type values]}]
(let [fills (get values :fills)
hide-on-export (get values :hide-fill-on-export false)
close-content (mf/use-fn #(reset! state* false))
^boolean
multiple? (= :multiple fills)
hide-fill-on-export? (:hide-fill-on-export values false)
fills (mf/with-memo [fills]
(prepare-fills fills))
checkbox-ref (mf/use-ref)
has-fills? (or multiple? (some? fills))
empty-fills? (and (not multiple?)
(= 0 (count fills)))
open* (mf/use-state has-fills?)
open? (deref open*)
toggle-content (mf/use-fn #(swap! open* not))
open-content (mf/use-fn #(reset! open* true))
close-content (mf/use-fn #(reset! open* false))
checkbox-ref (mf/use-ref)
can-add-fills?
(if binary-fills-enabled?
(and (not multiple?)
(< (count fills) types.fill/MAX-FILLS))
(not ^boolean multiple?))
label
(case type
:multiple (tr "workspace.options.selection-fill")
:group (tr "workspace.options.group-fill")
(tr "workspace.options.fill"))
on-add
(mf/use-fn
(mf/deps ids fills)
(mf/deps ids multiple? empty-fills?)
(fn [_]
(when can-add-fills?
(st/emit! (dc/add-fill ids {:color default-color
:opacity 1}))
(when (or (= :multiple fills)
(not (some? (seq fills))))
(when (or multiple? empty-fills?)
(open-content)))))
on-change
(fn [index]
(fn [color]
(let [color (select-keys color ctc/color-attrs)]
(st/emit! (dc/change-fill ids color index)))))
(mf/use-fn
(mf/deps ids)
(fn [color index]
(let [color (select-keys color ctc/color-attrs)]
(st/emit! (dc/change-fill ids color index)))))
on-reorder
(fn [new-index]
(fn [index]
(st/emit! (dc/reorder-fills ids index new-index))))
(mf/use-fn
(mf/deps ids)
(fn [new-index index]
(st/emit! (dc/reorder-fills ids index new-index))))
on-remove
(fn [index]
(fn []
(st/emit! (dc/remove-fill ids {:color default-color
:opacity 1} index))
(when (or (= :multiple fills)
(= 0 (count (seq fills))))
(close-content))))
(mf/use-fn
(mf/deps ids multiple? empty-fills?)
(fn [index _event]
(st/emit! (dc/remove-fill ids index))
(when (or multiple? empty-fills?)
(close-content))))
on-remove-all
(fn [_]
(st/emit! (dc/remove-all-fills ids {:color clr/black
:opacity 1})))
(mf/use-fn
(mf/deps ids)
#(st/emit! (dc/remove-all-fills ids)))
on-detach
(mf/use-fn
(mf/deps ids)
(fn [index]
(fn [color]
(let [color (dissoc color :ref-id :ref-file)]
(st/emit! (dc/change-fill ids color index))))))
(fn [index _event]
(st/emit! (dc/detach-fill ids index))))
on-change-show-fill-on-export
on-change-show-on-export
(mf/use-fn
(mf/deps ids)
(fn [event]
(let [value (-> event dom/get-target dom/checked?)]
(st/emit! (dc/change-hide-fill-on-export ids (not value))))))
disable-drag (mf/use-state false)
disable-drag*
(mf/use-state false)
on-focus (fn [_]
(reset! disable-drag true))
disable-drag?
(deref disable-drag*)
on-blur (fn [_]
(reset! disable-drag false))]
on-focus
(mf/use-fn
#(reset! disable-drag* true))
(mf/use-layout-effect
(mf/deps hide-fill-on-export?)
#(let [checkbox (mf/ref-val checkbox-ref)]
(when checkbox
;; Note that the "indeterminate" attribute only may be set by code, not as a static attribute.
;; See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#attr-indeterminate
(if (= hide-fill-on-export? :multiple)
(dom/set-attribute! checkbox "indeterminate" true)
(dom/remove-attribute! checkbox "indeterminate")))))
on-blur
(mf/use-fn #(reset! disable-drag* false))]
(mf/with-layout-effect [hide-on-export]
(when-let [checkbox (mf/ref-val checkbox-ref)]
;; Note that the "indeterminate" attribute only may be set by code, not as a static attribute.
;; See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#attr-indeterminate
(if (= hide-on-export :multiple)
(dom/set-attribute! checkbox "indeterminate" true)
(dom/remove-attribute! checkbox "indeterminate"))))
[:div {:class (stl/css :element-set)}
[:div {:class (stl/css :element-title)}
@ -174,34 +207,38 @@
:on-click on-remove-all
:icon "remove"}]]
(seq fills)
(some? fills)
[:& h/sortable-container {}
(for [[index value] (d/enumerate fills)]
[:> color-row* {:color (ctc/fill->color value)
:key index
: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)
:disable-drag disable-drag
:on-focus on-focus
:select-on-focus (not @disable-drag)
:on-blur on-blur}])])
(for [value fills]
(let [mdata (meta value)
index (get mdata :index)
color (get mdata :color)]
[:> color-row* {:color color
:key index
:index index
:title (tr "workspace.options.fill")
:on-change on-change
:on-reorder on-reorder
:on-detach on-detach
:on-remove on-remove
:disable-drag disable-drag?
:on-focus on-focus
:select-on-focus (not disable-drag?)
:on-blur on-blur}]))])
(when (or (= type :frame)
(and (= type :multiple) (some? (:hide-fill-on-export values))))
(and (= type :multiple)
(some? hide-on-export)))
[:div {:class (stl/css :checkbox)}
[:label {:for "show-fill-on-export"
:class (stl/css-case :global/checked (not hide-fill-on-export?))}
:class (stl/css-case :global/checked (not hide-on-export))}
[:span {:class (stl/css-case :check-mark true
:checked (not hide-fill-on-export?))}
(when (not hide-fill-on-export?)
:checked (not hide-on-export))}
(when (not hide-on-export)
i/status-tick)]
(tr "workspace.options.show-fill-on-export")
[:input {:type "checkbox"
:id "show-fill-on-export"
:ref checkbox-ref
:checked (not hide-fill-on-export?)
:on-change on-change-show-fill-on-export}]]])])]))
:checked (not hide-on-export)
:on-change on-change-show-on-export}]]])])]))

View file

@ -17,7 +17,7 @@
[app.main.data.workspace.undo :as dwu]
[app.main.store :as st]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.reorder-handler :refer [reorder-handler]]
[app.main.ui.components.reorder-handler :refer [reorder-handler*]]
[app.main.ui.components.select :refer [select]]
[app.main.ui.components.title-bar :refer [title-bar]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
@ -127,7 +127,7 @@
:dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot))}
(when (some? on-reorder)
[:& reorder-handler {:ref dref}])
[:> reorder-handler* {:ref dref}])
[:*
[:div {:class (stl/css :basic-options)}

View file

@ -19,7 +19,7 @@
[app.main.ui.components.color-bullet :as cb]
[app.main.ui.components.color-input :refer [color-input*]]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.reorder-handler :refer [reorder-handler]]
[app.main.ui.components.reorder-handler :refer [reorder-handler*]]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.formats :as fmt]
[app.main.ui.hooks :as h]
@ -50,7 +50,6 @@
on-change on-reorder on-detach on-open on-close on-remove
disable-drag on-focus on-blur select-only select-on-focus]}]
(let [libraries (mf/deref refs/files)
hover-detach (mf/use-state false)
on-change (h/use-ref-callback on-change)
file-id (or (:ref-file color) (:file-id color))
@ -76,21 +75,21 @@
(not library-color?)
(not disable-opacity))
on-focus
on-focus'
(mf/use-fn
(mf/deps on-focus)
(fn [event]
(fn [_]
(reset! editing-text* true)
(when on-focus
(on-focus event))))
(on-focus))))
on-blur
on-blur'
(mf/use-fn
(mf/deps on-blur)
(fn [event]
(fn [_]
(reset! editing-text* false)
(when on-blur
(on-blur event))))
(on-blur))))
parse-color
(mf/use-fn
@ -99,10 +98,10 @@
detach-value
(mf/use-fn
(mf/deps on-detach color)
(fn []
(mf/deps on-detach index)
(fn [_]
(when on-detach
(on-detach color))))
(on-detach index))))
handle-select
(mf/use-fn
@ -110,27 +109,27 @@
(fn []
(select-only color)))
handle-value-change
on-color-change
(mf/use-fn
(mf/deps color on-change)
(fn [value]
(mf/deps color index on-change)
(fn [value _event]
(let [color (-> color
(assoc :color value)
(dissoc :gradient)
(select-keys types.color/color-attrs))]
(st/emit! (dwc/add-recent-color color)
(on-change color)))))
(on-change color index)))))
handle-opacity-change
on-opacity-change
(mf/use-fn
(mf/deps color on-change)
(mf/deps color index on-change)
(fn [value]
(let [color (-> color
(assoc :opacity (/ value 100))
(dissoc :ref-id :ref-file)
(select-keys types.color/color-attrs))]
(st/emit! (dwc/add-recent-color color)
(on-change color)))))
(on-change color index)))))
handle-click-color
(mf/use-fn
@ -157,7 +156,7 @@
:disable-opacity disable-opacity
:disable-image disable-image
;; on-change second parameter means if the source is the color-picker
:on-change #(on-change % true)
:on-change #(on-change % index)
:on-close (fn [value opacity id file-id]
(when on-close
(on-close value opacity id file-id)))
@ -169,40 +168,51 @@
(when-not disable-picker
(modal/show! :colorpicker props)))))
prev-color (h/use-previous color)
on-remove'
(mf/use-fn
(mf/deps index)
(fn [_]
(when on-remove
(on-remove index))))
prev-color
(h/use-previous color)
on-drop
(mf/use-fn
(mf/deps on-reorder)
(mf/deps on-reorder index)
(fn [_ data]
(on-reorder (:index data))))
(on-reorder index (: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])
row-class
(stl/css-case :color-data true
:hidden hidden
:dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot))]
[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/with-effect [color prev-color disable-picker]
(when (and (not disable-picker) (not= prev-color color))
(modal/update-props! :colorpicker {:data (parse-color color)})))
[:div {:class (dm/str
class
(stl/css-case
:color-data true
:hidden hidden
:dnd-over-top (= (:over dprops) :top)
:dnd-over-bot (= (:over dprops) :bot)))}
[:div {:class [class row-class]}
;; Drag handler
(when (some? on-reorder)
[:& reorder-handler {:ref dref}])
[:> reorder-handler* {:ref dref}])
[:div {:class (stl/css :color-info)}
[:div {:class (stl/css-case :color-name-wrapper true
@ -228,22 +238,18 @@
[:button
{:class (stl/css :detach-btn)
:title (tr "settings.detach")
:on-pointer-enter #(reset! hover-detach true)
:on-pointer-leave #(reset! hover-detach false)
:on-click detach-value}
detach-icon])]
;; Rendering a gradient
;; Rendering a gradient
gradient-color?
[:*
[:div {:class (stl/css :color-name)}
(uc/gradient-type->string (dm/get-in color [:gradient :type]))]]
[:div {:class (stl/css :color-name)}
(uc/gradient-type->string (dm/get-in color [:gradient :type]))]
;; Rendering an image
;; Rendering an image
image-color?
[:*
[:div {:class (stl/css :color-name)}
(tr "media.image")]]
[:div {:class (stl/css :color-name)}
(tr "media.image")]
;; Rendering a plain color
:else
@ -252,22 +258,22 @@
""
(-> color :color cc/remove-hash))
:placeholder (tr "settings.multiple")
:data-index index
:class (stl/css :color-input)
:on-focus on-focus
:on-blur on-blur
:on-change handle-value-change}]])]
:on-focus on-focus'
:on-blur on-blur'
:on-change on-color-change}]])]
(when opacity?
[:div {:class (stl/css :opacity-element-wrapper)}
[:span {:class (stl/css :icon-text)}
"%"]
[:span {:class (stl/css :icon-text)} "%"]
[:> numeric-input* {:value (-> color :opacity opacity->string)
:className (stl/css :opacity-input)
:class (stl/css :opacity-input)
:placeholder "--"
:select-on-focus select-on-focus
:on-focus on-focus
:on-blur on-blur
:on-change handle-opacity-change
:on-focus on-focus'
:on-blur on-blur'
:on-change on-opacity-change
:data-testid "opacity-input"
:default 100
:min 0
@ -276,7 +282,7 @@
(when (some? on-remove)
[:> icon-button* {:variant "ghost"
:aria-label (tr "settings.remove-color")
:on-click on-remove
:on-click on-remove'
:icon "remove"}])
(when select-only
[:> icon-button* {:variant "ghost"

View file

@ -10,7 +10,7 @@
[app.common.data :as d]
[app.common.types.color :as ctc]
[app.main.ui.components.numeric-input :refer [numeric-input*]]
[app.main.ui.components.reorder-handler :refer [reorder-handler]]
[app.main.ui.components.reorder-handler :refer [reorder-handler*]]
[app.main.ui.components.select :refer [select]]
[app.main.ui.hooks :as h]
[app.main.ui.icons :as i]
@ -145,7 +145,7 @@
:dnd-over-bot (= (:over dprops) :bot))}
(when (some? on-reorder)
[:& reorder-handler {:ref dref}])
[:> reorder-handler* {:ref dref}])
;; Stroke Color
;; FIXME: memorize stroke color

View file

@ -11,7 +11,7 @@
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
@ -82,9 +82,11 @@
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& fill-menu {:ids ids
:type type
:values (select-keys shape fill-attrs)}]
[:> fill/fill-menu*
{:ids ids
:type type
:values shape}]
[:& stroke-menu {:ids ids
:type type
:show-caps true

View file

@ -11,7 +11,7 @@
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
@ -83,9 +83,10 @@
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& fill-menu {:ids ids
:type type
:values (select-keys shape fill-attrs)}]
[:> fill/fill-menu*
{:ids ids
:type type
:values shape}]
[:& stroke-menu {:ids ids
:type type
:values stroke-values}]

View file

@ -15,7 +15,7 @@
[app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]]
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu variant-menu*]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
@ -117,9 +117,11 @@
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& fill-menu {:ids ids
:type shape-type
:values (select-keys shape fill-attrs-shape)}]
[:> fill/fill-menu*
{:ids ids
:type shape-type
:values shape}]
[:& stroke-menu {:ids ids
:type shape-type
:values stroke-values}]

View file

@ -14,7 +14,7 @@
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
@ -95,7 +95,7 @@
[:& constraints-menu {:ids constraint-ids :values constraint-values}])
(when-not (empty? fill-ids)
[:& fill-menu {:type type :ids fill-ids :values fill-values}])
[:> fill/fill-menu* {:type type :ids fill-ids :values fill-values}])
(when-not (empty? stroke-ids)
[:& stroke-menu {:type type :ids stroke-ids :values stroke-values}])

View file

@ -11,7 +11,7 @@
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
@ -29,7 +29,6 @@
measure-values (select-keys shape measure-attrs)
layer-values (select-keys shape layer-attrs)
constraint-values (select-keys shape constraint-attrs)
fill-values (select-keys shape fill-attrs)
stroke-values (select-keys shape stroke-attrs)
layout-item-values (select-keys shape layout-item-attrs)
layout-container-values (select-keys shape layout-container-flex-attrs)
@ -83,9 +82,10 @@
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& fill-menu {:ids ids
:type type
:values fill-values}]
[:> fill/fill-menu*
{:ids ids
:type type
:values shape}]
[:& stroke-menu {:ids ids
:type type

View file

@ -22,7 +22,7 @@
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-attrs exports-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
@ -159,7 +159,7 @@
{:measure measure-attrs
:layer layer-attrs
:constraint constraint-attrs
:fill fill-attrs
:fill fill/fill-attrs
:shadow shadow-attrs
:blur blur-attrs
:stroke stroke-attrs
@ -388,7 +388,7 @@
[:& ot/text-menu {:type type :ids text-ids :values text-values}])
(when-not (empty? fill-ids)
[:& fill-menu {:type type :ids fill-ids :values fill-values}])
[:> fill/fill-menu* {:type type :ids fill-ids :values fill-values}])
(when-not (empty? stroke-ids)
[:& stroke-menu {:type type :ids stroke-ids :show-caps show-caps :values stroke-values

View file

@ -11,7 +11,7 @@
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
@ -82,9 +82,11 @@
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& fill-menu {:ids ids
:type type
:values (select-keys shape fill-attrs)}]
[:> fill/fill-menu*
{:ids ids
:type type
:values shape}]
[:& stroke-menu {:ids ids
:type type
:show-caps true

View file

@ -11,7 +11,7 @@
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
@ -32,7 +32,6 @@
measure-values (select-measure-keys shape)
layer-values (select-keys shape layer-attrs)
constraint-values (select-keys shape constraint-attrs)
fill-values (select-keys shape fill-attrs)
stroke-values (select-keys shape stroke-attrs)
layout-item-values (select-keys shape layout-item-attrs)
layout-container-values (select-keys shape layout-container-flex-attrs)
@ -85,9 +84,10 @@
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& fill-menu {:ids ids
:type type
:values fill-values}]
[:> fill/fill-menu*
{:ids ids
:type type
:values shape}]
[:& stroke-menu {:ids ids
:type type

View file

@ -13,7 +13,7 @@
[app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]]
@ -53,7 +53,7 @@
(defn get-fill-values [shape]
(let [fill-values (select-keys shape fill-attrs)
(let [fill-values (select-keys shape fill/fill-attrs)
color (-> (or (get-in shape [:content :attrs :fill])
(get-in shape [:content :attrs :style :fill]))
(parse-color))
@ -154,9 +154,10 @@
[:& constraints-menu {:ids ids
:values constraint-values}])
[:& fill-menu {:ids ids
:type type
:values fill-values}]
[:> fill/fill-menu*
{:ids ids
:type type
:values fill-values}]
[:& stroke-menu {:ids ids
:type type

View file

@ -17,7 +17,7 @@
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu*]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu fill-attrs]]
[app.main.ui.workspace.sidebar.options.menus.fill :as fill]
[app.main.ui.workspace.sidebar.options.menus.grid-cell :as grid-cell]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]]
@ -60,12 +60,11 @@
editor-instance (when (features/active-feature? @st/state "text-editor/v2")
(mf/deref refs/workspace-editor))
fill-values (-> (dwt/current-text-values
{:editor-state editor-state
:editor-instance editor-instance
:shape shape
:attrs (conj txt/text-fill-attrs :fills)})
(d/update-in-when [:fill-color-gradient :type] keyword))
fill-values (dwt/current-text-values
{:editor-state editor-state
:editor-instance editor-instance
:shape shape
:attrs (conj txt/text-fill-attrs :fills)})
fill-values (if (not (contains? fill-values :fills))
;; Old fill format
@ -76,7 +75,7 @@
text-values (d/merge
(select-keys shape [:grow-type])
(select-keys shape fill-attrs)
(select-keys shape fill/fill-attrs)
(dwt/current-root-values
{:shape shape
:attrs txt/root-attrs})
@ -133,7 +132,7 @@
:type type
:values text-values}]
[:& fill-menu
[:> fill/fill-menu*
{:ids ids
:type type
:values fill-values}]