0
Fork 0
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:
alonso.torres 2020-12-21 10:58:37 +01:00 committed by Andrey Antukh
parent e26ece57d1
commit 09bce9c285
5 changed files with 202 additions and 194 deletions

View file

@ -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 =)))

View file

@ -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}])]))

View file

@ -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}])]))

View file

@ -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}]]))

View file

@ -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)))