diff --git a/common/src/app/common/types/variant.cljc b/common/src/app/common/types/variant.cljc index 8a5bcb2a0..9dc02811d 100644 --- a/common/src/app/common/types/variant.cljc +++ b/common/src/app/common/types/variant.cljc @@ -230,4 +230,30 @@ {:props (add-new-prop props (:value prop)) :used-pos used-pos}))) {:props (vec props1) :used-pos (matching-indices props1 props2)} props2) - :props))) \ No newline at end of file + :props))) + +(defn compare-properties + "Compares vectors of properties keeping the value if it is the same for all + or setting a custom value where their values do not coincide" + ([props-list] + (compare-properties props-list nil)) + + ([props-list distinct-mark] + (let [grouped (group-by :name (apply concat props-list)) + check-values (fn [values] + (let [vals (map :value values)] + (if (apply = vals) + (first vals) + distinct-mark)))] + (mapv (fn [[name values]] + {:name name :value (check-values values)}) + grouped)))) + +(defn same-variant? + "Determines if all elements belong to the same variant" + [components] + (let [variant-ids (distinct (map :variant-id components)) + not-blank? (complement str/blank?)] + (and + (= 1 (count variant-ids)) + (not-blank? (first variant-ids))))) diff --git a/common/test/common_tests/variant_test.cljc b/common/test/common_tests/variant_test.cljc index a7a0f7d58..78ac25d09 100644 --- a/common/test/common_tests/variant_test.cljc +++ b/common/test/common_tests/variant_test.cljc @@ -37,7 +37,7 @@ (t/is (= (ctv/valid-properties-string? string-invalid) false))))) -(t/deftest compare-property-maps +(t/deftest find-properties (let [prev-props [{:name "border" :value "yes"} {:name "color" :value "gray"}] upd-props-1 [{:name "border" :value "yes"}] upd-props-2 [{:name "border" :value "yes"} {:name "color" :value "blue"}] @@ -79,3 +79,30 @@ (t/testing "find property index" (t/is (= (ctv/find-index-for-property-name prev-props "border") 0)) (t/is (= (ctv/find-index-for-property-name prev-props "color") 1))))) + + +(t/deftest compare-properties + (let [props-1 [{:name "border" :value "yes"} {:name "color" :value "gray"}] + props-2 [{:name "border" :value "yes"} {:name "color" :value "red"}] + props-3 [{:name "border" :value "no"} {:name "color" :value "gray"}]] + + (t/testing "compare properties" + (t/is (= (ctv/compare-properties [props-1 props-2]) + [{:name "border" :value "yes"} {:name "color" :value nil}])) + (t/is (= (ctv/compare-properties [props-1 props-2 props-3]) + [{:name "border" :value nil} {:name "color" :value nil}])) + (t/is (= (ctv/compare-properties [props-1 props-2 props-3] "&") + [{:name "border" :value "&"} {:name "color" :value "&"}]))))) + + +(t/deftest check-belong-same-variant + (let [components-1 [{:variant-id "a variant"} {:variant-id "a variant"}] + components-2 [{:variant-id "a variant"} {:variant-id "another variant"}] + components-3 [{:variant-id "a variant"} {}] + components-4 [{} {}]] + + (t/testing "check-belong-same-variant" + (t/is (= (ctv/same-variant? components-1) true)) + (t/is (= (ctv/same-variant? components-2) false)) + (t/is (= (ctv/same-variant? components-3) false)) + (t/is (= (ctv/same-variant? components-4) false))))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs index 793dc25c4..0fa0a2d88 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/common.cljs @@ -14,6 +14,7 @@ [app.common.types.component :as ctk] [app.common.types.container :as ctn] [app.common.types.file :as ctf] + [app.common.types.variant :as ctv] [app.config :as cf] [app.main.data.helpers :as dsh] [app.main.data.modal :as modal] @@ -381,6 +382,7 @@ variants? (features/use-feature "variants/v1") + same-variant? (ctv/same-variant? shapes) do-detach-component #(st/emit! (dwl/detach-components (map :id copies))) @@ -446,7 +448,7 @@ (when (= 1 (count comps-to-restore)) (ts/schedule 1000 do-show-component))) - menu-entries [(when (and (not multi) main-instance?) + menu-entries [(when (and (or (not multi) same-variant?) main-instance?) {:title (tr "workspace.shape.menu.show-in-assets") :action do-show-in-assets}) (when (and (not multi) main-instance? local-component? lacks-annotation?) @@ -470,7 +472,7 @@ (when can-update-main? {:title (tr "workspace.shape.menu.update-main") :action do-update-component}) - (when (and variants? (not multi) main-instance?) + (when (and variants? (or (not multi) same-variant?) main-instance?) {:title (tr "workspace.shape.menu.add-variant") :shortcut :create-component :action do-add-variant})]] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 315372ebf..b8410858d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -12,6 +12,7 @@ [app.common.files.variant :as cfv] [app.common.types.component :as ctk] [app.common.types.file :as ctf] + [app.common.types.variant :as ctv] [app.main.data.helpers :as dsh] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] @@ -232,10 +233,73 @@ (when (or editing? creating?) [:div {:class (stl/css :counter)} (str size "/300")])]]))) +(mf/defc component-variant-main-instance* + [{:keys [components data]}] + (let [component (first components) + variant-id (:variant-id component) + objects (-> (dsh/get-page data (:main-instance-page component)) + (get :objects)) + + variant-components (cfv/find-variant-components data objects variant-id) + + properties-map (mapv :variant-properties components) + component-ids (mapv :id components) + properties (if (> (count component-ids) 1) + (ctv/compare-properties properties-map false) + (first properties-map)) + + get-options + (mf/use-fn + (mf/deps variant-components) + (fn [prop-name] + (->> variant-components + (mapcat (fn [component] + (map :value (filter #(= (:name %) prop-name) + (:variant-properties component))))) + distinct + (map #(if (str/empty? %) "--" %)) + (map (fn [val] {:label val :id val}))))) + + change-property-value + (mf/use-fn + (mf/deps component-ids) + (fn [pos value] + (doseq [id component-ids] + (st/emit! (dwv/update-property-value id pos value))))) + + update-property-name + (mf/use-fn + (mf/deps variant-id) + (fn [event] + (let [value (dom/get-target-val event) + pos (-> (dom/get-current-target event) + (dom/get-data "position") + int)] + (st/emit! (dwv/update-property-name variant-id pos value)))))] + + [:* + (for [[pos prop] (map vector (range) properties)] + [:div {:key (str variant-id "-" pos) :class (stl/css :variant-property-container)} + [:* + [:div {:class (stl/css :variant-property-name-wrapper)} + [:> input-with-values* {:name (:name prop) + :data-position pos + :on-blur update-property-name}]] + + (let [mixed-value? (= (:value prop) false) + empty-value? (str/empty? (:value prop))] + [:> combobox* {:id (str "variant-prop-" variant-id "-" pos) + :placeholder (if mixed-value? (tr "settings.multiple") "") + :default-selected (cond + mixed-value? "" + empty-value? "--" + :else (:value prop)) + :options (clj->js (get-options (:name prop))) + :on-change (partial change-property-value pos)}])]])])) (mf/defc component-variant* [{:keys [component shape data]}] - (let [id-component (:id component) + (let [component-id (:id component) properties (:variant-properties component) variant-id (:variant-id component) objects (-> (dsh/get-page data (:main-instance-page component)) @@ -250,18 +314,6 @@ variant-properties))) variant-components) - get-options - (mf/use-fn - (mf/deps variant-components) - (fn [prop-name] - (->> variant-components - (mapcat (fn [component] - (map :value (filter #(= (:name %) prop-name) - (:variant-properties component))))) - distinct - (map #(if (str/empty? %) "--" %)) - (map (fn [val] {:label val :id val}))))) - filter-matching (mf/use-fn (mf/deps flat-comps) @@ -275,22 +327,6 @@ (map (fn [item] {:label (get item exclude-key) :value (:id item)})))))) - change-property-value - (mf/use-fn - (mf/deps id-component) - (fn [pos value] - (st/emit! (dwv/update-property-value id-component pos value)))) - - update-property-name - (mf/use-fn - (mf/deps variant-id) - (fn [event] - (let [value (dom/get-target-val event) - pos (-> (dom/get-current-target event) - (dom/get-data "position") - int)] - (st/emit! (dwv/update-property-name variant-id pos value))))) - switch-component (mf/use-fn (mf/deps shape) @@ -299,25 +335,13 @@ [:* (for [[pos prop] (map vector (range) properties)] - [:div {:key (str (:id shape) pos) :class (stl/css :variant-property-container)} - (if (ctk/main-instance? shape) - [:* - [:div {:class (stl/css :variant-property-name-wrapper)} - [:> input-with-values* {:name (:name prop) - :data-position pos - :on-blur update-property-name}]] - [:> combobox* {:id (str "variant-prop-" (:id shape) pos) - :default-selected (if (str/empty? (:value prop)) "--" (:value prop)) - :options (clj->js (get-options (:name prop))) - :on-change (partial change-property-value pos)}]] - - [:* - [:span {:class (stl/css :variant-property-name)} - (:name prop)] - [:& select {:default-value id-component - :options (filter-matching id-component (keyword (:name prop))) - :on-change switch-component}]])])])) + [:* + [:span {:class (stl/css :variant-property-name)} + (:name prop)] + [:& select {:default-value component-id + :options (filter-matching component-id (keyword (:name prop))) + :on-change switch-component}]]])])) (mf/defc component-swap-item {::mf/props :obj} @@ -641,6 +665,12 @@ is-variant? (when variants? (ctk/is-variant? component)) main-instance? (ctk/main-instance? shape) + components (mapv #(ctf/resolve-component % + current-file + libraries + {:include-deleted? true}) shapes) + same-variant? (ctv/same-variant? components) + toggle-content (mf/use-fn #(swap! state* update :show-content not)) @@ -722,7 +752,7 @@ [:div {:class (stl/css :name-wrapper)} [:div {:class (stl/css :component-name)} [:span {:class (stl/css :component-name-inside)} - (if multi + (if (and multi (not same-variant?)) (tr "settings.multiple") (cfh/last-path shape-name))]] @@ -748,8 +778,14 @@ (when (and (not swap-opened?) (not multi)) [:& component-annotation {:id id :shape shape :component component :rerender-fn rerender-fn}]) - (when (and is-variant? (not swap-opened?) (not multi)) - [:> component-variant* {:component component :shape shape :data data}]) + (when (and is-variant? (not main-instance?) (not swap-opened?) (not multi)) + [:> component-variant* {:component component + :shape shape + :data data}]) + + (when (and is-variant? main-instance? same-variant? (not swap-opened?)) + [:> component-variant-main-instance* {:components components + :data data}]) (when (dbg/enabled? :display-touched) [:div ":touched " (str (:touched shape))])])])))