From 09bce9c285a45a6fe9285b5d52ab367c3ae17a72 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 21 Dec 2020 10:58:37 +0100 Subject: [PATCH] :bug: Fixes problems with multiple selection and groups --- frontend/src/app/main/refs.cljs | 12 +- .../ui/workspace/sidebar/options/group.cljs | 119 +++------ .../workspace/sidebar/options/multiple.cljs | 241 +++++++++++------- .../ui/workspace/sidebar/options/text.cljs | 10 +- frontend/src/app/util/text.cljs | 14 +- 5 files changed, 202 insertions(+), 194 deletions(-) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 6ac073904..55f0edf76 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -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 =))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/group.cljs index c0700fc60..16687671c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/group.cljs @@ -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}])])) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/multiple.cljs index 0738c6c68..1db91f69a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/multiple.cljs @@ -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}])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/text.cljs index 2e51bde04..4a8f2f46a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/text.cljs @@ -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}]])) diff --git a/frontend/src/app/util/text.cljs b/frontend/src/app/util/text.cljs index c00547c9f..b9f8ff5f5 100644 --- a/frontend/src/app/util/text.cljs +++ b/frontend/src/app/util/text.cljs @@ -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))) +