mirror of
https://github.com/penpot/penpot.git
synced 2025-05-31 11:56:12 +02:00
✨ Select correct component to switch when changing variant property (#6346)
This commit is contained in:
parent
77d8504baf
commit
a3ccc3dfef
3 changed files with 151 additions and 31 deletions
|
@ -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))))
|
||||
|
|
112
common/test/common_tests/types/variant_test.cljc
Normal file
112
common/test/common_tests/types/variant_test.cljc
Normal 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))))
|
||||
|
||||
|
||||
|
||||
|
|
@ -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}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue