mirror of
https://github.com/penpot/penpot.git
synced 2025-01-25 07:58:49 -05:00
🐛 Fixes problems with multiple selection and groups
This commit is contained in:
parent
e26ece57d1
commit
09bce9c285
5 changed files with 202 additions and 194 deletions
|
@ -173,7 +173,9 @@
|
|||
(let [selected (get-in state [:workspace-local :selected])
|
||||
page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data :pages-index page-id :objects])]
|
||||
(mapv #(get objects %) selected)))]
|
||||
(->> selected
|
||||
(map #(get objects %))
|
||||
(filterv (comp not nil?)))))]
|
||||
(l/derived selector st/state =)))
|
||||
|
||||
(def selected-shapes-with-children
|
||||
|
@ -181,7 +183,9 @@
|
|||
(let [selected (get-in state [:workspace-local :selected])
|
||||
page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data :pages-index page-id :objects])
|
||||
children (mapcat #(cp/get-children % objects) selected)]
|
||||
children (->> selected
|
||||
(mapcat #(cp/get-children % objects))
|
||||
(filterv (comp not nil?)))]
|
||||
(into selected children)))]
|
||||
(l/derived selector st/state =)))
|
||||
|
||||
|
@ -192,7 +196,9 @@
|
|||
(let [selected (get-in state [:workspace-local :selected])
|
||||
page-id (:current-page-id state)
|
||||
objects (get-in state [:workspace-data :pages-index page-id :objects])
|
||||
children (mapcat #(cp/get-children % objects) selected)
|
||||
children (->> selected
|
||||
(mapcat #(cp/get-children % objects))
|
||||
(filterv (comp not nil?)))
|
||||
shapes (into selected children)]
|
||||
(mapv #(get objects %) shapes)))]
|
||||
(l/derived selector st/state =)))
|
||||
|
|
|
@ -11,105 +11,50 @@
|
|||
(ns app.main.ui.workspace.sidebar.options.group
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.attrs :as attrs]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.main.ui.workspace.sidebar.options.multiple :refer [get-shape-attrs extract]]
|
||||
[app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]]
|
||||
[app.common.data :as d]
|
||||
[app.main.ui.workspace.sidebar.options.multiple :refer [get-attrs]]
|
||||
[app.main.ui.workspace.sidebar.options.measures :refer [measures-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.component :refer [component-attrs component-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.fill :refer [fill-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.blur :refer [blur-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.shadow :refer [shadow-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.text :as ot]))
|
||||
|
||||
(mf/defc options
|
||||
[{:keys [shape shape-with-children] :as props}]
|
||||
(let [id (:id shape)
|
||||
ids-with-children (map :id shape-with-children)
|
||||
text-ids (map :id (filter #(= (:type %) :text) shape-with-children))
|
||||
other-ids (map :id (filter #(not= (:type %) :text) shape-with-children))
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
shape-with-children (unchecked-get props "shape-with-children")
|
||||
objects (->> shape-with-children (group-by :id) (d/mapm (fn [_ v] (first v))))
|
||||
|
||||
type (:type shape) ; always be :group
|
||||
type :group
|
||||
[measure-ids measure-values] (get-attrs [shape] objects :measure)
|
||||
[fill-ids fill-values] (get-attrs [shape] objects :fill)
|
||||
[shadow-ids shadow-values] (get-attrs [shape] objects :shadow)
|
||||
[blur-ids blur-values] (get-attrs [shape] objects :blur)
|
||||
[stroke-ids stroke-values] (get-attrs [shape] objects :stroke)
|
||||
[text-ids text-values] (get-attrs [shape] objects :text)
|
||||
[comp-ids comp-values] [[(:id shape)] (select-keys shape component-attrs)]]
|
||||
|
||||
measure-values
|
||||
(merge
|
||||
;; All values extracted from the group shape, except
|
||||
;; border radius, that needs to be looked up from children
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
measure-attrs
|
||||
nil
|
||||
nil
|
||||
nil)
|
||||
[shape])
|
||||
measure-attrs)
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
[:rx :ry]
|
||||
nil
|
||||
nil
|
||||
nil)
|
||||
shape-with-children)
|
||||
[:rx :ry]))
|
||||
[:div.options
|
||||
[:& measures-menu {:type type :ids measure-ids :values measure-values}]
|
||||
[:& component-menu {:ids comp-ids :values comp-values}]
|
||||
|
||||
component-values
|
||||
(select-keys shape component-attrs)
|
||||
(when-not (empty? fill-ids)
|
||||
[:& fill-menu {:type type :ids fill-ids :values fill-values}])
|
||||
|
||||
fill-values
|
||||
(attrs/get-attrs-multi shape-with-children fill-attrs)
|
||||
(when-not (empty? shadow-ids)
|
||||
[:& shadow-menu {:type type :ids shadow-ids :values shadow-values}])
|
||||
|
||||
stroke-values
|
||||
(attrs/get-attrs-multi (map #(get-shape-attrs
|
||||
%
|
||||
stroke-attrs
|
||||
nil
|
||||
nil
|
||||
nil)
|
||||
shape-with-children)
|
||||
stroke-attrs)
|
||||
(when-not (empty? blur-ids)
|
||||
[:& blur-menu {:type type :ids blur-ids :values blur-values}])
|
||||
|
||||
root-values (extract {:shapes shape-with-children
|
||||
:text-attrs ot/root-attrs
|
||||
:extract-fn dwt/current-root-values})
|
||||
(when-not (empty? stroke-ids)
|
||||
[:& stroke-menu {:type type :ids stroke-ids :values stroke-values}])
|
||||
|
||||
paragraph-values (extract {:shapes shape-with-children
|
||||
:text-attrs ot/paragraph-attrs
|
||||
:extract-fn dwt/current-paragraph-values})
|
||||
|
||||
text-values (extract {:shapes shape-with-children
|
||||
:text-attrs ot/text-attrs
|
||||
:extract-fn dwt/current-text-values})]
|
||||
[:*
|
||||
[:& measures-menu {:ids [id]
|
||||
:ids-with-children ids-with-children
|
||||
:type type
|
||||
:values measure-values}]
|
||||
[:& component-menu {:ids [id]
|
||||
:values component-values}]
|
||||
[:& fill-menu {:ids ids-with-children
|
||||
:type type
|
||||
:values fill-values}]
|
||||
|
||||
[:& shadow-menu {:ids [id]
|
||||
:type type
|
||||
:values (select-keys shape [:shadow])}]
|
||||
|
||||
[:& blur-menu {:ids [id]
|
||||
:type type
|
||||
:values (select-keys shape [:blur])}]
|
||||
|
||||
(when-not (empty? other-ids)
|
||||
[:& stroke-menu {:ids other-ids
|
||||
:type type
|
||||
:values stroke-values}])
|
||||
(when-not (empty? text-ids)
|
||||
[:& ot/text-menu {:ids text-ids
|
||||
:type type
|
||||
:editor nil
|
||||
:shapes shape-with-children
|
||||
:values (merge root-values
|
||||
paragraph-values
|
||||
text-values)}])]))
|
||||
[:& ot/text-menu {:type type :ids text-ids :values text-values}])]))
|
||||
|
||||
|
||||
|
|
|
@ -9,10 +9,10 @@
|
|||
|
||||
(ns app.main.ui.workspace.sidebar.options.multiple
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[rumext.alpha :as mf]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.attrs :as attrs]
|
||||
[app.main.data.workspace.texts :as dwt]
|
||||
[app.util.text :as ut]
|
||||
[app.main.ui.workspace.sidebar.options.measures :refer [measure-attrs measures-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.shadow :refer [shadow-attrs shadow-menu]]
|
||||
|
@ -20,107 +20,160 @@
|
|||
[app.main.ui.workspace.sidebar.options.stroke :refer [stroke-attrs stroke-menu]]
|
||||
[app.main.ui.workspace.sidebar.options.text :as ot]))
|
||||
|
||||
(defn get-shape-attrs
|
||||
[shape attrs text-attrs convert-attrs extract-fn]
|
||||
(if (not= (:type shape) :text)
|
||||
(when attrs
|
||||
(select-keys shape attrs))
|
||||
(when text-attrs
|
||||
(let [text-values (extract-fn {:editor nil
|
||||
:shape shape
|
||||
:attrs text-attrs})
|
||||
;; We define a map that goes from type to
|
||||
;; attribute and how to handle them
|
||||
(def type->props
|
||||
{:frame
|
||||
{:measure :shape
|
||||
:fill :shape
|
||||
:shadow :children
|
||||
:blur :children
|
||||
:stroke :children
|
||||
:text :children}
|
||||
|
||||
converted-values (if convert-attrs
|
||||
(zipmap convert-attrs
|
||||
(map #(% text-values) text-attrs))
|
||||
text-values)]
|
||||
converted-values))))
|
||||
:group
|
||||
{:measure :shape
|
||||
:fill :children
|
||||
:shadow :shape
|
||||
:blur :shape
|
||||
:stroke :children
|
||||
:text :children}
|
||||
|
||||
(defn extract [{:keys [shapes attrs text-attrs convert-attrs extract-fn]}]
|
||||
(let [mapfn
|
||||
(fn [shape]
|
||||
(get-shape-attrs shape
|
||||
attrs
|
||||
text-attrs
|
||||
convert-attrs
|
||||
extract-fn))]
|
||||
(attrs/get-attrs-multi (map mapfn shapes) (or attrs text-attrs))))
|
||||
:path
|
||||
{:measure :shape
|
||||
:fill :shape
|
||||
:shadow :shape
|
||||
:blur :shape
|
||||
:stroke :shape
|
||||
:text :ignore}
|
||||
|
||||
:text
|
||||
{:measure :shape
|
||||
:fill :text
|
||||
:shadow :shape
|
||||
:blur :shape
|
||||
:stroke :ignore
|
||||
:text :text}
|
||||
|
||||
:image
|
||||
{:measure :shape
|
||||
:fill :ignore
|
||||
:shadow :shape
|
||||
:blur :shape
|
||||
:stroke :ignore
|
||||
:text :ignore}
|
||||
|
||||
:rect
|
||||
{:measure :shape
|
||||
:fill :shape
|
||||
:shadow :shape
|
||||
:blur :shape
|
||||
:stroke :shape
|
||||
:text :ignore}
|
||||
|
||||
:circle
|
||||
{:measure :shape
|
||||
:fill :shape
|
||||
:shadow :shape
|
||||
:blur :shape
|
||||
:stroke :shape
|
||||
:text :ignore}})
|
||||
|
||||
(def props->attrs
|
||||
{:measure measure-attrs
|
||||
:fill fill-attrs
|
||||
:shadow shadow-attrs
|
||||
:blur blur-attrs
|
||||
:stroke stroke-attrs
|
||||
:text ot/text-attrs})
|
||||
|
||||
(def shadow-keys [:style :color :offset-x :offset-y :blur :spread])
|
||||
|
||||
(defn shadow-eq
|
||||
"Function to check if two shadows are equivalent to the multiple selection (ignores their ids)"
|
||||
[s1 s2]
|
||||
(and (= (count s1) (count s2))
|
||||
(->> (map vector s1 s2)
|
||||
(every? (fn [[v1 v2]]
|
||||
(= (select-keys v1 shadow-keys)
|
||||
(select-keys v2 shadow-keys)))))))
|
||||
|
||||
(defn shadow-sel
|
||||
"Function to select the attributes that interest us for the multiple selections"
|
||||
[v]
|
||||
(mapv #(select-keys % shadow-keys) v))
|
||||
|
||||
(def blur-keys [:type :value])
|
||||
|
||||
(defn blur-eq
|
||||
"Checks if two blurs are equivalent for the multiple selection"
|
||||
[v1 v2]
|
||||
(= (select-keys v1 blur-keys) (select-keys v2 blur-keys)))
|
||||
|
||||
(defn blur-sel
|
||||
"Select interesting keys for multiple selection"
|
||||
[v]
|
||||
(when v (select-keys v blur-keys)))
|
||||
|
||||
(defn get-attrs
|
||||
"Given a `type` of options that we want to extract and the shapes to extract them from
|
||||
returns a list of tuples [id, values] with the extracted properties for the shapes that
|
||||
applies (some of them ignore some attributes)"
|
||||
[shapes objects attr-type]
|
||||
(let [attrs (props->attrs attr-type)
|
||||
merge-attrs
|
||||
(fn [v1 v2]
|
||||
(cond
|
||||
(= attr-type :shadow) (attrs/get-attrs-multi [v1 v2] attrs shadow-eq shadow-sel)
|
||||
(= attr-type :blur) (attrs/get-attrs-multi [v1 v2] attrs blur-eq blur-sel)
|
||||
:else (attrs/get-attrs-multi [v1 v2] attrs)))
|
||||
|
||||
extract-attrs
|
||||
(fn [[ids values] {:keys [id type shapes content] :as shape}]
|
||||
(let [conj (fnil conj [])
|
||||
props (get-in type->props [type attr-type])
|
||||
result (case props
|
||||
:ignore [ids values]
|
||||
:shape [(conj ids id)
|
||||
(merge-attrs values (select-keys shape attrs))]
|
||||
:text [(conj ids id)
|
||||
(merge-attrs values (ut/get-text-attrs-multi content attrs))]
|
||||
:children (let [children (->> (:shapes shape []) (map #(get objects %)))]
|
||||
(get-attrs children objects attr-type)))]
|
||||
result))]
|
||||
(reduce extract-attrs [] shapes)))
|
||||
|
||||
(mf/defc options
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [shapes shapes-with-children] :as props}]
|
||||
(let [ids (map :id shapes)
|
||||
ids-with-children (map :id shapes-with-children)
|
||||
text-ids (map :id (filter #(= (:type %) :text) shapes-with-children))
|
||||
other-ids (map :id (filter #(not= (:type %) :text) shapes-with-children))
|
||||
{::mf/wrap [mf/memo]
|
||||
::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shapes (unchecked-get props "shapes")
|
||||
shapes-with-children (unchecked-get props "shapes-with-children")
|
||||
objects (->> shapes-with-children (group-by :id) (d/mapm (fn [_ v] (first v))))
|
||||
|
||||
type :multiple
|
||||
[measure-ids measure-values] (get-attrs shapes objects :measure)
|
||||
[fill-ids fill-values] (get-attrs shapes objects :fill)
|
||||
[shadow-ids shadow-values] (get-attrs shapes objects :shadow)
|
||||
[blur-ids blur-values] (get-attrs shapes objects :blur)
|
||||
[stroke-ids stroke-values] (get-attrs shapes objects :stroke)
|
||||
[text-ids text-values] (get-attrs shapes objects :text)]
|
||||
|
||||
measure-values (attrs/get-attrs-multi shapes measure-attrs)
|
||||
[:div.options
|
||||
(when-not (empty? measure-ids)
|
||||
[:& measures-menu {:type type :ids measure-ids :values measure-values}])
|
||||
|
||||
shadow-values (let [keys [:style :color :offset-x :offset-y :blur :spread]]
|
||||
(attrs/get-attrs-multi
|
||||
shapes shadow-attrs
|
||||
(fn [s1 s2]
|
||||
(and (= (count s1) (count s2))
|
||||
(->> (map vector s1 s2)
|
||||
(every? (fn [[v1 v2]]
|
||||
(= (select-keys v1 keys) (select-keys v2 keys)))))))
|
||||
(fn [v]
|
||||
(mapv #(select-keys % keys) v))))
|
||||
(when-not (empty? fill-ids)
|
||||
[:& fill-menu {:type type :ids fill-ids :values fill-values}])
|
||||
|
||||
blur-values (let [keys [:type :value]]
|
||||
(attrs/get-attrs-multi
|
||||
shapes blur-attrs
|
||||
(fn [v1 v2]
|
||||
(= (select-keys v1 keys) (select-keys v2 keys)))
|
||||
(fn [v] (select-keys v keys))))
|
||||
(when-not (empty? shadow-ids)
|
||||
[:& shadow-menu {:type type :ids shadow-ids :values shadow-values}])
|
||||
|
||||
fill-values (extract {:shapes shapes-with-children
|
||||
:attrs fill-attrs
|
||||
:text-attrs ot/text-fill-attrs
|
||||
:convert-attrs fill-attrs
|
||||
:extract-fn dwt/current-text-values})
|
||||
(when-not (empty? blur-ids)
|
||||
[:& blur-menu {:type type :ids blur-ids :values blur-values}])
|
||||
|
||||
stroke-values (extract {:shapes shapes-with-children
|
||||
:attrs stroke-attrs})
|
||||
(when-not (empty? stroke-ids)
|
||||
[:& stroke-menu {:type type :ids stroke-ids :values stroke-values}])
|
||||
|
||||
root-values (extract {:shapes shapes-with-children
|
||||
:text-attrs ot/root-attrs
|
||||
:extract-fn dwt/current-root-values})
|
||||
|
||||
paragraph-values (extract {:shapes shapes-with-children
|
||||
:text-attrs ot/paragraph-attrs
|
||||
:extract-fn dwt/current-paragraph-values})
|
||||
|
||||
text-values (extract {:shapes shapes-with-children
|
||||
:text-attrs ot/text-attrs
|
||||
:extract-fn dwt/current-text-values})]
|
||||
[:*
|
||||
[:& measures-menu {:ids ids
|
||||
:type :multiple
|
||||
:values measure-values}]
|
||||
[:& fill-menu {:ids ids-with-children
|
||||
:type :multiple
|
||||
:values fill-values}]
|
||||
|
||||
[:& shadow-menu {:ids ids
|
||||
:type :multiple
|
||||
:values shadow-values}]
|
||||
|
||||
[:& blur-menu {:ids ids
|
||||
:type :multiple
|
||||
:values blur-values}]
|
||||
|
||||
(when-not (empty? other-ids)
|
||||
[:& stroke-menu {:ids other-ids
|
||||
:type :multiple
|
||||
:values stroke-values}])
|
||||
(when-not (empty? text-ids)
|
||||
[:& ot/text-menu {:ids text-ids
|
||||
:type :multiple
|
||||
:editor nil
|
||||
:shapes shapes-with-children
|
||||
:values (merge root-values
|
||||
paragraph-values
|
||||
text-values)}])]))
|
||||
|
||||
[:& ot/text-menu {:type type :ids text-ids :values text-values}])]))
|
||||
|
|
|
@ -171,11 +171,7 @@
|
|||
|
||||
(mf/defc text-menu
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [ids
|
||||
type
|
||||
editor
|
||||
values
|
||||
shapes] :as props}]
|
||||
[{:keys [ids type editor values] :as props}]
|
||||
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
current-file-id (mf/use-ctx ctx/current-file-id)
|
||||
|
@ -241,7 +237,6 @@
|
|||
opts #js {:editor editor
|
||||
:ids ids
|
||||
:values values
|
||||
:shapes shapes
|
||||
:on-change (fn [attrs]
|
||||
(run! #(emit-update! % attrs) ids))
|
||||
:locale locale}]
|
||||
|
@ -329,5 +324,4 @@
|
|||
[:& text-menu {:ids ids
|
||||
:type type
|
||||
:values text-values
|
||||
:editor editor
|
||||
:shapes [shape]}]]))
|
||||
:editor editor}]]))
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
(ns app.util.text
|
||||
(:require
|
||||
[cuerdas.core :as str]))
|
||||
[cuerdas.core :as str]
|
||||
[app.common.attrs :refer [get-attrs-multi]]))
|
||||
|
||||
(defonce default-text-attrs
|
||||
{:typography-ref-file nil
|
||||
|
@ -84,10 +85,19 @@
|
|||
|
||||
(defn search-text-attrs
|
||||
[node attrs]
|
||||
|
||||
(let [rec-fn
|
||||
(fn rec-fn [current node]
|
||||
(let [current (reduce rec-fn current (:children node []))]
|
||||
(merge current
|
||||
(select-keys node attrs))))]
|
||||
(rec-fn {} node)))
|
||||
|
||||
|
||||
(defn get-text-attrs-multi
|
||||
[node attrs]
|
||||
(let [rec-fn
|
||||
(fn rec-fn [current node]
|
||||
(let [current (reduce rec-fn current (:children node []))]
|
||||
(get-attrs-multi [current node] attrs)))]
|
||||
(rec-fn {} node)))
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue