mirror of
https://github.com/penpot/penpot.git
synced 2025-04-01 09:31:26 -05:00
✨ Add Selected colors menu
This commit is contained in:
parent
6897c0c3fe
commit
87f5efeadb
11 changed files with 344 additions and 20 deletions
|
@ -4,6 +4,9 @@
|
|||
|
||||
### :boom: Breaking changes
|
||||
### :sparkles: New features
|
||||
|
||||
- Added selected colors widget in right sidebar [Taiga #2485](https://tree.taiga.io/project/penpot/us/2485)
|
||||
|
||||
### :bug: Bugs fixed
|
||||
### :arrow_up: Deps updates
|
||||
### :heart: Community contributions by (Thank you!)
|
||||
|
|
|
@ -1534,3 +1534,44 @@
|
|||
margin-right: $size-2;
|
||||
}
|
||||
}
|
||||
|
||||
.expand-colors {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
|
||||
.text {
|
||||
color: $color-gray-30;
|
||||
font-size: 0.75rem;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: $color-gray-20;
|
||||
stroke: $color-gray-20;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-colors {
|
||||
.color-data {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 5px;
|
||||
|
||||
svg {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.percentil {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.color-data:hover {
|
||||
background-color: $color-gray-60;
|
||||
|
||||
svg {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -390,3 +390,32 @@
|
|||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-global :editing-stop] spot)))))
|
||||
|
||||
(defn color-att->text
|
||||
[color]
|
||||
{:fill-color (:color color)
|
||||
:fill-opacity (:opacity color)
|
||||
:fill-color-ref-id (:id color)
|
||||
:fill-color-ref-file (:file-id color)
|
||||
:fill-color-gradient (:gradient color)})
|
||||
|
||||
(defn change-text-color
|
||||
[old-color new-color index node]
|
||||
(let [fills (:fills node)
|
||||
parsed-color (d/without-nils (color-att->text old-color))
|
||||
parsed-new-color (d/without-nils (color-att->text new-color))
|
||||
has-color? (d/index-of fills parsed-color)]
|
||||
(cond-> node
|
||||
(some? has-color?)
|
||||
(assoc-in [:fills index] parsed-new-color))))
|
||||
|
||||
(defn change-color-in-selected
|
||||
[new-color shapes-by-color old-color]
|
||||
(ptk/reify ::change-color-in-selected
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(->> (rx/from shapes-by-color)
|
||||
(rx/map (fn [shape] (case (:prop shape)
|
||||
:fill (change-fill [(:shape-id shape)] new-color (:index shape))
|
||||
:stroke (change-stroke [(:shape-id shape)] new-color (:index shape))
|
||||
:content (dwt/update-text-with-function (:shape-id shape) (partial change-text-color old-color new-color (:index shape))))))))))
|
||||
|
|
|
@ -236,5 +236,13 @@
|
|||
(events/listen globals/window EventType.CLICK on-click)]]
|
||||
#(doseq [key keys]
|
||||
(events/unlistenByKey key)))))
|
||||
|
||||
(mf/use-layout-effect
|
||||
(mf/deps handle-mouse-wheel)
|
||||
(fn []
|
||||
(let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:pasive false})]]
|
||||
#(doseq [key keys]
|
||||
(events/unlistenByKey key)))))
|
||||
|
||||
|
||||
[:> :input props]))
|
||||
|
|
|
@ -0,0 +1,189 @@
|
|||
;; 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.workspace.sidebar.options.menus.color-selection
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.text :as txt]
|
||||
[app.main.data.workspace.colors :as dc]
|
||||
[app.main.data.workspace.common :as dwc]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(defn fill->color-att
|
||||
[fill]
|
||||
(let [attrs (d/without-nils {:color (:fill-color fill)
|
||||
:opacity (:fill-opacity fill)
|
||||
:id (:fill-color-ref-id fill)
|
||||
:file-id (:fill-color-ref-file fill)
|
||||
:gradient (:fill-color-gradient fill)})]
|
||||
{:attrs attrs
|
||||
:prop :fill
|
||||
:shape-id (:shape-id fill)
|
||||
:index (:index fill)}))
|
||||
|
||||
(defn stroke->color-att
|
||||
[stroke]
|
||||
(let [attrs (d/without-nils {:color (:stroke-color stroke)
|
||||
:opacity (:stroke-opacity stroke)
|
||||
:id (:stroke-color-ref-id stroke)
|
||||
:file-id (:stroke-color-ref-file stroke)
|
||||
:gradient (:stroke-color-gradient stroke)})]
|
||||
{:attrs attrs
|
||||
:prop :stroke
|
||||
:shape-id (:shape-id stroke)
|
||||
:index (:index stroke)}))
|
||||
|
||||
(defn text->color-att
|
||||
[fill]
|
||||
(let [attrs (d/without-nils {:color (:fill-color fill)
|
||||
:opacity (:fill-opacity fill)
|
||||
:id (:fill-color-ref-id fill)
|
||||
:file-id (:fill-color-ref-file fill)
|
||||
:gradient (:fill-color-gradient fill)})]
|
||||
{:attrs attrs
|
||||
:prop :content
|
||||
:shape-id (:shape-id fill)
|
||||
:index (:index fill)}))
|
||||
|
||||
(defn treat-node
|
||||
[node shape-id]
|
||||
(map-indexed #(assoc %2 :shape-id shape-id :index %1) node))
|
||||
|
||||
(defn extract-text-colors
|
||||
[text]
|
||||
(let [content (txt/node-seq txt/is-text-node? (:content text))
|
||||
content-filtered (map :fills content)
|
||||
indexed (mapcat #(treat-node % (:id text)) content-filtered)]
|
||||
(map text->color-att indexed)))
|
||||
|
||||
(defn- extract-all-colors
|
||||
[shapes]
|
||||
(reduce
|
||||
(fn [list shape]
|
||||
(let [fill-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:fills shape))
|
||||
stroke-obj (map-indexed #(assoc %2 :shape-id (:id shape) :index %1) (:strokes shape))]
|
||||
(if (= :text (:type shape))
|
||||
(-> list
|
||||
(into (map stroke->color-att) stroke-obj)
|
||||
(into (extract-text-colors shape)))
|
||||
|
||||
(-> list
|
||||
(into (map fill->color-att) fill-obj)
|
||||
(into (map stroke->color-att) stroke-obj)))))
|
||||
[]
|
||||
shapes))
|
||||
|
||||
(defn- prepare-colors
|
||||
[shapes]
|
||||
(let [data (extract-all-colors shapes)
|
||||
grouped-colors (group-by :attrs data)
|
||||
all-colors (distinct (mapv :attrs data))
|
||||
|
||||
tmp (group-by #(some? (:id %)) all-colors)
|
||||
library-colors (get tmp true)
|
||||
colors (get tmp false)]
|
||||
|
||||
{:grouped-colors grouped-colors
|
||||
:all-colors all-colors
|
||||
:colors colors
|
||||
:library-colors library-colors}))
|
||||
|
||||
(mf/defc color-selection-menu
|
||||
{::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]}
|
||||
[{:keys [shapes] :as props}]
|
||||
(let [{:keys [all-colors grouped-colors library-colors colors]} (mf/with-memo [shapes]
|
||||
(prepare-colors shapes))
|
||||
expand-lib-color (mf/use-state false)
|
||||
expand-color (mf/use-state false)
|
||||
|
||||
grouped-colors* (mf/use-var nil)
|
||||
prev-color* (mf/use-var nil)
|
||||
|
||||
on-change
|
||||
(mf/use-fn
|
||||
(fn [new-color old-color]
|
||||
(let [old-color (-> (or @prev-color* old-color)
|
||||
(dissoc :name)
|
||||
(dissoc :path)
|
||||
(d/without-nils))
|
||||
shapes-by-color (get @grouped-colors* old-color)]
|
||||
(reset! prev-color* new-color)
|
||||
(st/emit! (dc/change-color-in-selected new-color shapes-by-color old-color)))))
|
||||
|
||||
on-open (mf/use-fn
|
||||
(fn [color]
|
||||
(reset! prev-color* color)))
|
||||
|
||||
on-detach
|
||||
(mf/use-fn
|
||||
(fn [color]
|
||||
(let [shapes-by-color (get @grouped-colors* color)
|
||||
new-color (assoc color :id nil :file-id nil)]
|
||||
(st/emit! (dc/change-color-in-selected new-color shapes-by-color color)))))
|
||||
|
||||
select-only
|
||||
(mf/use-fn
|
||||
(fn [color]
|
||||
(let [shapes-by-color (get @grouped-colors* color)
|
||||
ids (into (d/ordered-set) (map :shape-id) shapes-by-color)]
|
||||
(st/emit! (dwc/select-shapes ids)))))]
|
||||
|
||||
(mf/with-effect [grouped-colors]
|
||||
(reset! grouped-colors* grouped-colors))
|
||||
|
||||
(when (> (count all-colors) 1)
|
||||
[:div.element-set
|
||||
[:div.element-set-title
|
||||
[:span (tr "workspace.options.selection-color")]]
|
||||
[:div.element-set-content
|
||||
[:div.selected-colors
|
||||
(for [[index color] (d/enumerate (take 3 library-colors))]
|
||||
[:& color-row {:key (dm/str "color-" index)
|
||||
:color color
|
||||
:index index
|
||||
:on-detach on-detach
|
||||
:select-only select-only
|
||||
:on-change #(on-change % color)
|
||||
:on-open on-open}])
|
||||
(when (and (false? @expand-lib-color) (< 3 (count library-colors)))
|
||||
[:div.expand-colors {:on-click #(reset! expand-lib-color true)}
|
||||
[:span i/actions]
|
||||
[:span.text (tr "workspace.options.more-lib-colors")]])
|
||||
(when @expand-lib-color
|
||||
(for [[index color] (d/enumerate (drop 3 library-colors))]
|
||||
[:& color-row {:key (dm/str "color-" index)
|
||||
:color color
|
||||
:index index
|
||||
:on-detach on-detach
|
||||
:select-only select-only
|
||||
:on-change #(on-change % color)
|
||||
:on-open on-open}]))]
|
||||
|
||||
[:div.selected-colors
|
||||
(for [[index color] (d/enumerate (take 3 colors))]
|
||||
[:& color-row {:key (dm/str "color-" index)
|
||||
:color color
|
||||
:index index
|
||||
:select-only select-only
|
||||
:on-change #(on-change % color)
|
||||
:on-open on-open}])
|
||||
(when (and (false? @expand-color) (< 3 (count colors)))
|
||||
[:div.expand-colors {:on-click #(reset! expand-color true)}
|
||||
[:span i/actions]
|
||||
[:span.text (tr "workspace.options.more-colors")]])
|
||||
(when @expand-color
|
||||
(for [[index color] (d/enumerate (drop 3 colors))]
|
||||
[:& color-row {:key (dm/str "color-" index)
|
||||
:color color
|
||||
:index index
|
||||
:select-only select-only
|
||||
:on-change #(on-change % color)
|
||||
:on-open on-open}]))]]])))
|
|
@ -14,6 +14,7 @@
|
|||
[app.main.ui.components.color-input :refer [color-input]]
|
||||
[app.main.ui.components.numeric-input :refer [numeric-input]]
|
||||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.formats :as fmt]
|
||||
[app.main.ui.hooks :as h]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.color :as uc]
|
||||
|
@ -45,7 +46,7 @@
|
|||
:on-change handle-change-color
|
||||
:on-close handle-close
|
||||
:data color}]
|
||||
(handle-open)
|
||||
(handle-open color)
|
||||
(modal/show! :colorpicker props))))
|
||||
|
||||
(defn opacity->string [opacity]
|
||||
|
@ -53,13 +54,16 @@
|
|||
""
|
||||
(str (-> opacity
|
||||
(d/coalesce 1)
|
||||
(* 100)))))
|
||||
(* 100)
|
||||
(fmt/format-number)))))
|
||||
|
||||
(defn remove-multiple [v]
|
||||
(if (= v :multiple) nil v))
|
||||
|
||||
(mf/defc color-row
|
||||
[{:keys [index color disable-gradient disable-opacity on-change on-reorder on-detach on-open on-close title on-remove disable-drag select-all on-blur]}]
|
||||
[{:keys [index color disable-gradient disable-opacity on-change
|
||||
on-reorder on-detach on-open on-close title on-remove
|
||||
disable-drag select-all on-blur select-only]}]
|
||||
(let [current-file-id (mf/use-ctx ctx/current-file-id)
|
||||
file-colors (mf/deref refs/workspace-file-colors)
|
||||
shared-libs (mf/deref refs/workspace-libraries)
|
||||
|
@ -92,7 +96,11 @@
|
|||
handle-pick-color (fn [color]
|
||||
(when on-change (on-change (merge uc/empty-color color))))
|
||||
|
||||
handle-open (fn [] (when on-open (on-open)))
|
||||
handle-select (fn []
|
||||
(select-only color))
|
||||
|
||||
handle-open (fn [color]
|
||||
(when on-open (on-open (merge uc/empty-color color))))
|
||||
|
||||
handle-close (fn [value opacity id file-id]
|
||||
(when on-close (on-close value opacity id file-id)))
|
||||
|
@ -104,14 +112,12 @@
|
|||
handle-opacity-change (fn [value]
|
||||
(change-opacity (/ value 100)))
|
||||
|
||||
handle-click-color (mf/use-callback
|
||||
(mf/deps color)
|
||||
(color-picker-callback color
|
||||
disable-gradient
|
||||
disable-opacity
|
||||
handle-pick-color
|
||||
handle-open
|
||||
handle-close))
|
||||
handle-click-color (color-picker-callback color
|
||||
disable-gradient
|
||||
disable-opacity
|
||||
handle-pick-color
|
||||
handle-open
|
||||
handle-close)
|
||||
|
||||
prev-color (h/use-previous color)
|
||||
|
||||
|
@ -155,14 +161,23 @@
|
|||
{:on-mouse-enter #(reset! hover-detach true)
|
||||
:on-mouse-leave #(reset! hover-detach false)
|
||||
:on-click detach-value}
|
||||
(if @hover-detach i/unchain i/chain)])]
|
||||
(if @hover-detach i/unchain i/chain)])
|
||||
|
||||
(when select-only
|
||||
[:div.element-set-actions-button {:on-click handle-select}
|
||||
i/pointer-inner])]
|
||||
|
||||
;; Rendering a gradient
|
||||
(and (not (uc/multiple? color))
|
||||
(:gradient color)
|
||||
(get-in color [:gradient :type]))
|
||||
[:div.color-info
|
||||
[:div.color-name (cb/gradient-type->string (get-in color [:gradient :type]))]]
|
||||
[:*
|
||||
[:div.color-info
|
||||
[:div.color-name (cb/gradient-type->string (get-in color [:gradient :type]))]]
|
||||
(when select-only
|
||||
[:div.element-set-actions-button {:on-click handle-select}
|
||||
i/pointer-inner])]
|
||||
|
||||
|
||||
;; Rendering a plain color/opacity
|
||||
:else
|
||||
|
@ -186,7 +201,10 @@
|
|||
:on-blur on-blur
|
||||
:on-change handle-opacity-change
|
||||
:min 0
|
||||
:max 100}]])])
|
||||
:max 100}]])
|
||||
(when select-only
|
||||
[:div.element-set-actions-button {:on-click handle-select}
|
||||
i/pointer-inner])])
|
||||
(when (some? on-remove)
|
||||
[:div.element-set-actions-button.remove {:on-click on-remove} i/minus])]))
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[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.component :refer [component-attrs component-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraints-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]]
|
||||
|
@ -49,15 +50,19 @@
|
|||
(when-not (empty? fill-ids)
|
||||
[:& 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}])
|
||||
|
||||
(when (> (count objects) 2)
|
||||
[:& color-selection-menu {:type type
|
||||
:shapes (vals objects)}])
|
||||
|
||||
(when-not (empty? shadow-ids)
|
||||
[:& shadow-menu {:type type :ids shadow-ids :values shadow-values}])
|
||||
|
||||
(when-not (empty? blur-ids)
|
||||
[:& blur-menu {:type type :ids blur-ids :values blur-values}])
|
||||
|
||||
(when-not (empty? stroke-ids)
|
||||
[:& stroke-menu {:type type :ids stroke-ids :values stroke-values}])
|
||||
|
||||
(when-not (empty? text-ids)
|
||||
[:& ot/text-menu {:type type :ids text-ids :values text-values}])
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[app.common.text :as txt]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-attrs 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.exports :refer [exports-attrs exports-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
|
||||
|
@ -280,6 +281,9 @@
|
|||
|
||||
(when-not (empty? stroke-ids)
|
||||
[:& stroke-menu {:type type :ids stroke-ids :show-caps show-caps :values stroke-values}])
|
||||
|
||||
(when-not (empty? shapes)
|
||||
[:& color-selection-menu {:type type :shapes (vals objects-no-measures)}])
|
||||
|
||||
(when-not (empty? shadow-ids)
|
||||
[:& shadow-menu {:type type :ids shadow-ids :values shadow-values}])
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.refs :as refs]
|
||||
[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.layer :refer [layer-attrs layer-menu]]
|
||||
|
@ -58,7 +59,6 @@
|
|||
:attrs text-attrs}))]
|
||||
|
||||
[:*
|
||||
|
||||
[:& measures-menu
|
||||
{:ids ids
|
||||
:type type
|
||||
|
@ -82,6 +82,9 @@
|
|||
:type type
|
||||
:values stroke-values}]
|
||||
|
||||
(when (= :multiple (:fills fill-values))
|
||||
[:& color-selection-menu {:type type :shapes [shape]}])
|
||||
|
||||
[:& shadow-menu
|
||||
{:ids ids
|
||||
:values (select-keys shape [:shadow])}]
|
||||
|
|
|
@ -3011,6 +3011,18 @@ msgstr "Selection fill"
|
|||
msgid "workspace.options.selection-stroke"
|
||||
msgstr "Selection stroke"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs
|
||||
msgid "workspace.options.selection-color"
|
||||
msgstr "Selected colors"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs
|
||||
msgid "workspace.options.more-colors"
|
||||
msgstr "More colors"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs
|
||||
msgid "workspace.options.more-lib-colors"
|
||||
msgstr "More library colors"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
|
||||
msgid "workspace.options.shadow-options.blur"
|
||||
msgstr "Blur"
|
||||
|
|
|
@ -3024,6 +3024,18 @@ msgstr "Relleno de selección"
|
|||
msgid "workspace.options.selection-stroke"
|
||||
msgstr "Borde de selección"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs
|
||||
msgid "workspace.options.selection-color"
|
||||
msgstr "Colores seleccionados"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs
|
||||
msgid "workspace.options.more-colors"
|
||||
msgstr "Más colores"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs
|
||||
msgid "workspace.options.more-lib-colors"
|
||||
msgstr "Más colores de la biblioteca"
|
||||
|
||||
#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
|
||||
msgid "workspace.options.shadow-options.blur"
|
||||
msgstr "Desenfoque"
|
||||
|
|
Loading…
Add table
Reference in a new issue