🐛 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]) (let [selected (get-in state [:workspace-local :selected])
page-id (:current-page-id state) page-id (:current-page-id state)
objects (get-in state [:workspace-data :pages-index page-id :objects])] 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 =))) (l/derived selector st/state =)))
(def selected-shapes-with-children (def selected-shapes-with-children
@ -181,7 +183,9 @@
(let [selected (get-in state [:workspace-local :selected]) (let [selected (get-in state [:workspace-local :selected])
page-id (:current-page-id state) page-id (:current-page-id state)
objects (get-in state [:workspace-data :pages-index page-id :objects]) 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)))] (into selected children)))]
(l/derived selector st/state =))) (l/derived selector st/state =)))
@ -192,7 +196,9 @@
(let [selected (get-in state [:workspace-local :selected]) (let [selected (get-in state [:workspace-local :selected])
page-id (:current-page-id state) page-id (:current-page-id state)
objects (get-in state [:workspace-data :pages-index page-id :objects]) 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)] shapes (into selected children)]
(mapv #(get objects %) shapes)))] (mapv #(get objects %) shapes)))]
(l/derived selector st/state =))) (l/derived selector st/state =)))

View file

@ -11,105 +11,50 @@
(ns app.main.ui.workspace.sidebar.options.group (ns app.main.ui.workspace.sidebar.options.group
(:require (:require
[rumext.alpha :as mf] [rumext.alpha :as mf]
[app.common.attrs :as attrs] [app.common.data :as d]
[app.common.geom.shapes :as geom] [app.main.ui.workspace.sidebar.options.multiple :refer [get-attrs]]
[app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.measures :refer [measures-menu]]
[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.main.ui.workspace.sidebar.options.component :refer [component-attrs component-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.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.shadow :refer [shadow-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])) [app.main.ui.workspace.sidebar.options.text :as ot]))
(mf/defc options (mf/defc options
[{:keys [shape shape-with-children] :as props}] {::mf/wrap [mf/memo]
(let [id (:id shape) ::mf/wrap-props false}
ids-with-children (map :id shape-with-children) [props]
text-ids (map :id (filter #(= (:type %) :text) shape-with-children)) (let [shape (unchecked-get props "shape")
other-ids (map :id (filter #(not= (:type %) :text) shape-with-children)) 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 [:div.options
(merge [:& measures-menu {:type type :ids measure-ids :values measure-values}]
;; All values extracted from the group shape, except [:& component-menu {:ids comp-ids :values comp-values}]
;; 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]))
component-values (when-not (empty? fill-ids)
(select-keys shape component-attrs) [:& fill-menu {:type type :ids fill-ids :values fill-values}])
fill-values (when-not (empty? shadow-ids)
(attrs/get-attrs-multi shape-with-children fill-attrs) [:& shadow-menu {:type type :ids shadow-ids :values shadow-values}])
stroke-values (when-not (empty? blur-ids)
(attrs/get-attrs-multi (map #(get-shape-attrs [:& blur-menu {:type type :ids blur-ids :values blur-values}])
%
stroke-attrs
nil
nil
nil)
shape-with-children)
stroke-attrs)
root-values (extract {:shapes shape-with-children (when-not (empty? stroke-ids)
:text-attrs ot/root-attrs [:& stroke-menu {:type type :ids stroke-ids :values stroke-values}])
:extract-fn dwt/current-root-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) (when-not (empty? text-ids)
[:& ot/text-menu {:ids text-ids [:& ot/text-menu {:type type :ids text-ids :values text-values}])]))
:type type
:editor nil
:shapes shape-with-children
:values (merge root-values
paragraph-values
text-values)}])]))

View file

@ -9,10 +9,10 @@
(ns app.main.ui.workspace.sidebar.options.multiple (ns app.main.ui.workspace.sidebar.options.multiple
(:require (:require
[app.common.data :as d]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[app.common.geom.shapes :as geom]
[app.common.attrs :as attrs] [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.measures :refer [measure-attrs measures-menu]]
[app.main.ui.workspace.sidebar.options.fill :refer [fill-attrs fill-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]] [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.stroke :refer [stroke-attrs stroke-menu]]
[app.main.ui.workspace.sidebar.options.text :as ot])) [app.main.ui.workspace.sidebar.options.text :as ot]))
(defn get-shape-attrs ;; We define a map that goes from type to
[shape attrs text-attrs convert-attrs extract-fn] ;; attribute and how to handle them
(if (not= (:type shape) :text) (def type->props
(when attrs {:frame
(select-keys shape attrs)) {:measure :shape
(when text-attrs :fill :shape
(let [text-values (extract-fn {:editor nil :shadow :children
:shape shape :blur :children
:attrs text-attrs}) :stroke :children
:text :children}
converted-values (if convert-attrs :group
(zipmap convert-attrs {:measure :shape
(map #(% text-values) text-attrs)) :fill :children
text-values)] :shadow :shape
converted-values)))) :blur :shape
:stroke :children
:text :children}
(defn extract [{:keys [shapes attrs text-attrs convert-attrs extract-fn]}] :path
(let [mapfn {:measure :shape
(fn [shape] :fill :shape
(get-shape-attrs shape :shadow :shape
attrs :blur :shape
text-attrs :stroke :shape
convert-attrs :text :ignore}
extract-fn))]
(attrs/get-attrs-multi (map mapfn shapes) (or attrs text-attrs))))
(mf/defc options :text
{::mf/wrap [mf/memo]} {:measure :shape
[{:keys [shapes shapes-with-children] :as props}] :fill :text
(let [ids (map :id shapes) :shadow :shape
ids-with-children (map :id shapes-with-children) :blur :shape
text-ids (map :id (filter #(= (:type %) :text) shapes-with-children)) :stroke :ignore
other-ids (map :id (filter #(not= (:type %) :text) shapes-with-children)) :text :text}
:image
{:measure :shape
:fill :ignore
:shadow :shape
:blur :shape
:stroke :ignore
:text :ignore}
measure-values (attrs/get-attrs-multi shapes measure-attrs) :rect
{:measure :shape
:fill :shape
:shadow :shape
:blur :shape
:stroke :shape
:text :ignore}
shadow-values (let [keys [:style :color :offset-x :offset-y :blur :spread]] :circle
(attrs/get-attrs-multi {:measure :shape
shapes shadow-attrs :fill :shape
(fn [s1 s2] :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)) (and (= (count s1) (count s2))
(->> (map vector s1 s2) (->> (map vector s1 s2)
(every? (fn [[v1 v2]] (every? (fn [[v1 v2]]
(= (select-keys v1 keys) (select-keys v2 keys))))))) (= (select-keys v1 shadow-keys)
(fn [v] (select-keys v2 shadow-keys)))))))
(mapv #(select-keys % keys) v))))
blur-values (let [keys [:type :value]] (defn shadow-sel
(attrs/get-attrs-multi "Function to select the attributes that interest us for the multiple selections"
shapes blur-attrs [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] (fn [v1 v2]
(= (select-keys v1 keys) (select-keys v2 keys))) (cond
(fn [v] (select-keys v keys)))) (= 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)))
fill-values (extract {:shapes shapes-with-children extract-attrs
:attrs fill-attrs (fn [[ids values] {:keys [id type shapes content] :as shape}]
:text-attrs ot/text-fill-attrs (let [conj (fnil conj [])
:convert-attrs fill-attrs props (get-in type->props [type attr-type])
:extract-fn dwt/current-text-values}) 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)))
stroke-values (extract {:shapes shapes-with-children (mf/defc options
:attrs stroke-attrs}) {::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))))
root-values (extract {:shapes shapes-with-children type :multiple
:text-attrs ot/root-attrs [measure-ids measure-values] (get-attrs shapes objects :measure)
:extract-fn dwt/current-root-values}) [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)]
paragraph-values (extract {:shapes shapes-with-children [:div.options
:text-attrs ot/paragraph-attrs (when-not (empty? measure-ids)
:extract-fn dwt/current-paragraph-values}) [:& measures-menu {:type type :ids measure-ids :values measure-values}])
text-values (extract {:shapes shapes-with-children (when-not (empty? fill-ids)
:text-attrs ot/text-attrs [:& fill-menu {:type type :ids fill-ids :values fill-values}])
: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 (when-not (empty? shadow-ids)
:type :multiple [:& shadow-menu {:type type :ids shadow-ids :values shadow-values}])
:values shadow-values}]
[:& blur-menu {:ids ids (when-not (empty? blur-ids)
:type :multiple [:& blur-menu {:type type :ids blur-ids :values blur-values}])
:values blur-values}]
(when-not (empty? stroke-ids)
[:& stroke-menu {:type type :ids stroke-ids :values stroke-values}])
(when-not (empty? other-ids)
[:& stroke-menu {:ids other-ids
:type :multiple
:values stroke-values}])
(when-not (empty? text-ids) (when-not (empty? text-ids)
[:& ot/text-menu {:ids text-ids [:& ot/text-menu {:type type :ids text-ids :values text-values}])]))
:type :multiple
:editor nil
:shapes shapes-with-children
:values (merge root-values
paragraph-values
text-values)}])]))

View file

@ -171,11 +171,7 @@
(mf/defc text-menu (mf/defc text-menu
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]}
[{:keys [ids [{:keys [ids type editor values] :as props}]
type
editor
values
shapes] :as props}]
(let [locale (mf/deref i18n/locale) (let [locale (mf/deref i18n/locale)
current-file-id (mf/use-ctx ctx/current-file-id) current-file-id (mf/use-ctx ctx/current-file-id)
@ -241,7 +237,6 @@
opts #js {:editor editor opts #js {:editor editor
:ids ids :ids ids
:values values :values values
:shapes shapes
:on-change (fn [attrs] :on-change (fn [attrs]
(run! #(emit-update! % attrs) ids)) (run! #(emit-update! % attrs) ids))
:locale locale}] :locale locale}]
@ -329,5 +324,4 @@
[:& text-menu {:ids ids [:& text-menu {:ids ids
:type type :type type
:values text-values :values text-values
:editor editor :editor editor}]]))
:shapes [shape]}]]))

View file

@ -1,6 +1,7 @@
(ns app.util.text (ns app.util.text
(:require (:require
[cuerdas.core :as str])) [cuerdas.core :as str]
[app.common.attrs :refer [get-attrs-multi]]))
(defonce default-text-attrs (defonce default-text-attrs
{:typography-ref-file nil {:typography-ref-file nil
@ -84,10 +85,19 @@
(defn search-text-attrs (defn search-text-attrs
[node attrs] [node attrs]
(let [rec-fn (let [rec-fn
(fn rec-fn [current node] (fn rec-fn [current node]
(let [current (reduce rec-fn current (:children node []))] (let [current (reduce rec-fn current (:children node []))]
(merge current (merge current
(select-keys node attrs))))] (select-keys node attrs))))]
(rec-fn {} node))) (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)))