diff --git a/common/src/app/common/types/variant.cljc b/common/src/app/common/types/variant.cljc index 9dc02811d..3341240ca 100644 --- a/common/src/app/common/types/variant.cljc +++ b/common/src/app/common/types/variant.cljc @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.files.helpers :as cfh] + [app.common.math :as math] [app.common.schema :as sm] [cuerdas.core :as str])) @@ -257,3 +258,18 @@ (and (= 1 (count variant-ids)) (not-blank? (first variant-ids))))) + +(defn distance + "Computes a weighted distance between two property lists `props1` and `props2`. + Latter properties weight less that previous ones" + [props1 props2] + (let [total-num-props (count props1) + xform (map-indexed + (fn [idx [p1 p2]] + (if (not= p1 p2) + (math/pow 2 (- total-num-props idx)) + 0)))] + (transduce + xform + + + (map vector props1 props2)))) diff --git a/common/test/common_tests/types/variant_test.cljc b/common/test/common_tests/types/variant_test.cljc new file mode 100644 index 000000000..258b41e8a --- /dev/null +++ b/common/test/common_tests/types/variant_test.cljc @@ -0,0 +1,112 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns common-tests.types.variant-test + (:require + [app.common.types.variant :as ctv] + [clojure.test :as t])) + + +(t/deftest variant-distance01 + ;;c1: primary, default, rounded, blue, dark + ;;c2: primary, hover, squared, blue, dark + ;;c3: primary, default, squared, blue, light + + ;; I have a copy of c1, and I change from rounded to squared + ;; c2: 1 difference in pos 2 + ;; c3: 1 differences in pos 5 + ;; The min distance should be c3 + + (let [target [{:name "type" :value "primary"} + {:name "status" :value "default"} + {:name "borders" :value "squared"} + {:name "color" :value "blue"} + {:name "theme" :value "dark"}] + props2 [{:name "type" :value "primary"} + {:name "status" :value "hover"} + {:name "borders" :value "rounded"} + {:name "color" :value "blue"} + {:name "theme" :value "dark"}] + props3 [{:name "type" :value "primary"} + {:name "status" :value "default"} + {:name "borders" :value "rounded"} + {:name "color" :value "blue"} + {:name "theme" :value "light"}] + dist2 (ctv/distance target props2) + dist3 (ctv/distance target props3)] + (t/is (< dist3 dist2)))) + + +(t/deftest variant-distance02 + ;;c1: primary, default, rounded, blue, dark + ;;c2: primary, hover, squared, red, dark + ;;c3: secondary, hover, rounded, blue, dark + + ;; I have a copy of c1, and I change from default to hover + ;; c2: 2 differences in pos 3 and 4 + ;; c3: 1 differences in pos 1 + ;; The min distance should be c2 + + (let [target [{:name "type" :value "primary"} + {:name "status" :value "hover"} + {:name "borders" :value "rounded"} + {:name "color" :value "blue"} + {:name "theme" :value "dark"}] + props2 [{:name "type" :value "primary"} + {:name "status" :value "hover"} + {:name "borders" :value "squared"} + {:name "color" :value "red"} + {:name "theme" :value "dark"}] + props3 [{:name "type" :value "secondary"} + {:name "status" :value "hover"} + {:name "borders" :value "rounded"} + {:name "color" :value "blue"} + {:name "theme" :value "dark"}] + dist2 (ctv/distance target props2) + dist3 (ctv/distance target props3)] + (t/is (< dist2 dist3)))) + +(t/deftest variant-distance03 + ;;c1: primary, default, rounded, blue, dark + ;;c2: secondary, default, rounded, blue, light + ;;c3: secondary, hover, squared, blue, dark + ;;c4: secondary, hover, rounded, blue, dark + + ;; I have a copy of c1, and I change from primary to secondary + ;; c2: 1 difference in pos 4 + ;; c3: 2 differences in pos 1 and 2 + ;; c4: 1 difference in pos 1 + ;; The distances should be c2 < c4 < c3 + + (let [target [{:name "type" :value "secondary"} + {:name "status" :value "default"} + {:name "borders" :value "rounded"} + {:name "color" :value "blue"} + {:name "theme" :value "dark"}] + props2 [{:name "type" :value "secondary"} + {:name "status" :value "default"} + {:name "borders" :value "rounded"} + {:name "color" :value "blue"} + {:name "theme" :value "light"}] + props3 [{:name "type" :value "secondary"} + {:name "status" :value "hover"} + {:name "borders" :value "squared"} + {:name "color" :value "blue"} + {:name "theme" :value "dark"}] + props4 [{:name "type" :value "secondary"} + {:name "status" :value "hover"} + {:name "borders" :value "rounded"} + {:name "color" :value "blue"} + {:name "theme" :value "dark"}] + dist2 (ctv/distance target props2) + dist3 (ctv/distance target props3) + dist4 (ctv/distance target props4)] + (t/is (< dist2 dist4)) + (t/is (< dist4 dist3)))) + + + + 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 b8410858d..df5e979fc 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 @@ -240,24 +240,21 @@ 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)) + prop-vals (mf/with-memo [data objects variant-id] + (cfv/extract-properties-values data objects variant-id)) get-options (mf/use-fn - (mf/deps variant-components) + (mf/deps prop-vals) (fn [prop-name] - (->> variant-components - (mapcat (fn [component] - (map :value (filter #(= (:name %) prop-name) - (:variant-properties component))))) - distinct - (map #(if (str/empty? %) "--" %)) + (->> (filter #(= (:name %) prop-name) prop-vals) + first + :value (map (fn [val] {:label val :id val}))))) change-property-value @@ -307,31 +304,26 @@ variant-components (cfv/find-variant-components data objects variant-id) - flat-comps ;; Get a list like [{:id 0 :prop1 "v1" :prop2 "v2"} {:id 1, :prop1 "v3" :prop2 "v4"}] - (map (fn [{:keys [id variant-properties]}] - (into {:id id} - (map (fn [{:keys [name value]}] [(keyword name) value]) - variant-properties))) - variant-components) + prop-vals (mf/with-memo [data objects variant-id] + (cfv/extract-properties-values data objects variant-id)) - filter-matching + get-options-vals (mf/use-fn - (mf/deps flat-comps) - (fn [id exclude-key] - (let [reference-item (first (filter #(= (:id %) id) flat-comps)) - reference-values (dissoc reference-item :id exclude-key)] - - (->> flat-comps - (filter (fn [item] - (= (dissoc item :id exclude-key) reference-values))) - (map (fn [item] {:label (get item exclude-key) :value (:id item)})))))) - + (mf/deps prop-vals) + (fn [prop-name] + (->> (filter #(= (:name %) prop-name) prop-vals) + first + :value))) switch-component (mf/use-fn (mf/deps shape) - (fn [id] - (st/emit! (dwl/component-swap shape (:component-file shape) id))))] + (fn [pos val] + (let [valid-comps (->> variant-components + (remove #(= (:id %) component-id)) + (filter #(= (dm/get-in % [:variant-properties pos :value]) val))) + comp (apply min-key #(ctv/distance (:variant-properties component) (:variant-properties %)) valid-comps)] + (st/emit! (dwl/component-swap shape (:component-file shape) (:id comp))))))] [:* (for [[pos prop] (map vector (range) properties)] @@ -339,9 +331,9 @@ [:* [: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}]]])])) + [:& select {:default-value (if (str/empty? (:value prop)) "--" (:value prop)) + :options (clj->js (get-options-vals (:name prop))) + :on-change #(switch-component pos %)}]]])])) (mf/defc component-swap-item {::mf/props :obj}