Select correct component to switch when changing variant property (#6346)

This commit is contained in:
Pablo Alba 2025-04-22 18:48:18 +02:00 committed by GitHub
parent 77d8504baf
commit a3ccc3dfef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 151 additions and 31 deletions

View file

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

View file

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

View file

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