mirror of
https://github.com/penpot/penpot.git
synced 2025-04-29 20:16:20 +02:00
✨ Add, edit and delete variant properties from layer panel (#6257)
This commit is contained in:
parent
b6f2a434cf
commit
e7144142e5
6 changed files with 236 additions and 24 deletions
|
@ -59,14 +59,14 @@
|
|||
|
||||
|
||||
(defn generate-add-new-property
|
||||
[changes variant-id & {:keys [fill-values?]}]
|
||||
[changes variant-id & {:keys [fill-values? property-name]}]
|
||||
(let [data (pcb/get-library-data changes)
|
||||
objects (pcb/get-objects changes)
|
||||
related-components (cfv/find-variant-components data objects variant-id)
|
||||
|
||||
props (-> related-components first :variant-properties)
|
||||
next-prop-num (ctv/next-property-number props)
|
||||
property-name (str ctv/property-prefix next-prop-num)
|
||||
property-name (or property-name (str ctv/property-prefix next-prop-num))
|
||||
|
||||
[_ changes]
|
||||
(reduce (fn [[num changes] component]
|
||||
|
|
|
@ -86,3 +86,60 @@
|
|||
new-properties (map-indexed (fn [i v] {:name (str property-prefix (+ next-prop-num i))
|
||||
:value v}) remaining)]
|
||||
(into assigned new-properties)))
|
||||
|
||||
|
||||
(defn properties-map-to-string
|
||||
"Transforms a map of properties to a string of properties omitting the empty ones"
|
||||
[properties]
|
||||
(->> properties
|
||||
(keep (fn [{:keys [name value]}]
|
||||
(when (not (str/blank? value))
|
||||
(str name "=" value))))
|
||||
(str/join ", ")))
|
||||
|
||||
|
||||
(defn properties-string-to-map
|
||||
"Transforms a string of properties to a map of properties"
|
||||
[s]
|
||||
(->> (str/split s ",")
|
||||
(mapv #(str/split % "="))
|
||||
(mapv (fn [[k v]]
|
||||
{:name (str/trim k)
|
||||
:value (str/trim v)}))))
|
||||
|
||||
|
||||
(defn valid-properties-string?
|
||||
"Checks if a string of properties has a processable format or not"
|
||||
[s]
|
||||
(let [pattern #"^([a-zA-Z0-9\s]+=[a-zA-Z0-9\s]+)(,\s*[a-zA-Z0-9\s]+=[a-zA-Z0-9\s]+)*$"]
|
||||
(not (nil? (re-matches pattern s)))))
|
||||
|
||||
|
||||
(defn find-properties-to-remove
|
||||
"Compares two property maps to find which properties should be removed"
|
||||
[prev-props upd-props]
|
||||
(let [upd-names (set (map :name upd-props))]
|
||||
(filterv #(not (contains? upd-names (:name %))) prev-props)))
|
||||
|
||||
|
||||
(defn find-properties-to-update
|
||||
"Compares two property maps to find which properties should be updated"
|
||||
[prev-props upd-props]
|
||||
(filterv #(some (fn [prop] (and (= (:name %) (:name prop))
|
||||
(not= (:value %) (:value prop)))) prev-props) upd-props))
|
||||
|
||||
|
||||
(defn find-properties-to-add
|
||||
"Compares two property maps to find which properties should be added"
|
||||
[prev-props upd-props]
|
||||
(let [prev-names (set (map :name prev-props))]
|
||||
(filterv #(not (contains? prev-names (:name %))) upd-props)))
|
||||
|
||||
|
||||
(defn find-index-for-property-name
|
||||
"Finds the index of a name in a property map"
|
||||
[props name]
|
||||
(some (fn [[idx prop]]
|
||||
(when (= (:name prop) name)
|
||||
idx))
|
||||
(map-indexed vector props)))
|
||||
|
|
81
common/test/common_tests/variant_test.cljc
Normal file
81
common/test/common_tests/variant_test.cljc
Normal file
|
@ -0,0 +1,81 @@
|
|||
;; 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.variant-test
|
||||
(:require
|
||||
[app.common.types.variant :as ctv]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/deftest convert-between-variant-properties-maps-and-strings
|
||||
(let [map-with-two-props [{:name "border" :value "yes"} {:name "color" :value "gray"}]
|
||||
map-with-two-props-one-blank [{:name "border" :value "no"} {:name "color" :value ""}]
|
||||
map-with-one-prop [{:name "border" :value "no"}]
|
||||
map-with-spaces [{:name "border 1" :value "of course"} {:name "color 2" :value "dark gray"}]
|
||||
|
||||
string-valid-with-two-props "border=yes, color=gray"
|
||||
string-valid-with-one-prop "border=no"
|
||||
string-valid-with-spaces "border 1=of course, color 2=dark gray"
|
||||
string-invalid "border=yes, color="]
|
||||
|
||||
(t/testing "convert map to string"
|
||||
(t/is (= (ctv/properties-map-to-string map-with-two-props) string-valid-with-two-props))
|
||||
(t/is (= (ctv/properties-map-to-string map-with-two-props-one-blank) string-valid-with-one-prop))
|
||||
(t/is (= (ctv/properties-map-to-string map-with-spaces) string-valid-with-spaces)))
|
||||
|
||||
(t/testing "convert string to map"
|
||||
(t/is (= (ctv/properties-string-to-map string-valid-with-two-props) map-with-two-props))
|
||||
(t/is (= (ctv/properties-string-to-map string-valid-with-one-prop) map-with-one-prop))
|
||||
(t/is (= (ctv/properties-string-to-map string-valid-with-spaces) map-with-spaces)))
|
||||
|
||||
(t/testing "check if a string is valid"
|
||||
(t/is (= (ctv/valid-properties-string? string-valid-with-two-props) true))
|
||||
(t/is (= (ctv/valid-properties-string? string-valid-with-one-prop) true))
|
||||
(t/is (= (ctv/valid-properties-string? string-valid-with-spaces) true))
|
||||
(t/is (= (ctv/valid-properties-string? string-invalid) false)))))
|
||||
|
||||
|
||||
(t/deftest compare-property-maps
|
||||
(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"}]
|
||||
upd-props-3 [{:name "border" :value "yes"} {:name "color" :value "gray"} {:name "shadow" :value "large"}]
|
||||
upd-props-4 [{:name "color" :value "yellow"} {:name "shadow" :value "large"}]]
|
||||
|
||||
(t/testing "a property to remove"
|
||||
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-1)
|
||||
[{:name "color" :value "gray"}]))
|
||||
(t/is (= (ctv/find-properties-to-update prev-props upd-props-1)
|
||||
[]))
|
||||
(t/is (= (ctv/find-properties-to-add prev-props upd-props-1)
|
||||
[])))
|
||||
|
||||
(t/testing "a property to update"
|
||||
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-2)
|
||||
[]))
|
||||
(t/is (= (ctv/find-properties-to-update prev-props upd-props-2)
|
||||
[{:name "color" :value "blue"}]))
|
||||
(t/is (= (ctv/find-properties-to-add prev-props upd-props-2)
|
||||
[])))
|
||||
|
||||
(t/testing "a property to add"
|
||||
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-3)
|
||||
[]))
|
||||
(t/is (= (ctv/find-properties-to-update prev-props upd-props-3)
|
||||
[]))
|
||||
(t/is (= (ctv/find-properties-to-add prev-props upd-props-3)
|
||||
[{:name "shadow" :value "large"}])))
|
||||
|
||||
(t/testing "properties to remove, update & add"
|
||||
(t/is (= (ctv/find-properties-to-remove prev-props upd-props-4)
|
||||
[{:name "border" :value "yes"}]))
|
||||
(t/is (= (ctv/find-properties-to-update prev-props upd-props-4)
|
||||
[{:name "color" :value "yellow"}]))
|
||||
(t/is (= (ctv/find-properties-to-add prev-props upd-props-4)
|
||||
[{:name "shadow" :value "large"}])))
|
||||
|
||||
(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)))))
|
|
@ -17,6 +17,7 @@
|
|||
[app.common.types.component :as ctc]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.shape.layout :as ctsl]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.data.changes :as dch]
|
||||
[app.main.data.helpers :as dsh]
|
||||
|
@ -32,6 +33,59 @@
|
|||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
(defn update-properties-names-and-values
|
||||
"Compares the previous properties with the updated ones and executes the correspondent action
|
||||
for each one depending on if it needs to be removed, updated or added"
|
||||
[component-id variant-id previous-properties updated-properties]
|
||||
(ptk/reify ::update-properties-names-and-values
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update state :workspace-local dissoc :shape-for-rename))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [it state _]
|
||||
(let [page-id (:current-page-id state)
|
||||
data (dsh/lookup-file-data state)
|
||||
objects (-> (dsh/get-page data page-id)
|
||||
(get :objects))
|
||||
|
||||
properties-to-remove (ctv/find-properties-to-remove previous-properties updated-properties)
|
||||
properties-to-add (ctv/find-properties-to-add previous-properties updated-properties)
|
||||
properties-to-update (ctv/find-properties-to-update previous-properties updated-properties)
|
||||
|
||||
changes (-> (pcb/empty-changes it page-id)
|
||||
(pcb/with-objects objects)
|
||||
(pcb/with-library-data data))
|
||||
|
||||
changes (reduce
|
||||
(fn [changes {:keys [name]}]
|
||||
(-> changes
|
||||
(clvp/generate-update-property-value component-id (ctv/find-index-for-property-name previous-properties name) "")))
|
||||
changes
|
||||
properties-to-remove)
|
||||
|
||||
changes (reduce
|
||||
(fn [changes {:keys [name value]}]
|
||||
(-> changes
|
||||
(clvp/generate-update-property-value component-id (ctv/find-index-for-property-name previous-properties name) value)))
|
||||
changes
|
||||
properties-to-update)
|
||||
|
||||
changes (reduce
|
||||
(fn [changes [idx {:keys [name value]}]]
|
||||
(-> changes
|
||||
(clvp/generate-add-new-property variant-id {:property-name name})
|
||||
(clvp/generate-update-property-value component-id (+ idx (count previous-properties)) value)))
|
||||
changes
|
||||
(map-indexed vector properties-to-add))
|
||||
|
||||
undo-id (js/Symbol)]
|
||||
|
||||
(rx/of
|
||||
(dwu/start-undo-transaction undo-id)
|
||||
(dch/commit-changes changes)
|
||||
(dwu/commit-undo-transaction undo-id))))))
|
||||
|
||||
(defn update-property-name
|
||||
"Update the variant property name on the position pos
|
||||
in all the components with this variant-id"
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
[app.common.data.macros :as dm]
|
||||
[app.common.files.helpers :as cfh]
|
||||
[app.common.types.component :as ctk]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.common.types.container :as ctn]
|
||||
[app.common.types.shape.layout :as ctl]
|
||||
[app.common.uuid :as uuid]
|
||||
|
@ -23,7 +24,7 @@
|
|||
[app.main.ui.context :as ctx]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.workspace.sidebar.layer-name :refer [layer-name]]
|
||||
[app.main.ui.workspace.sidebar.layer-name :refer [layer-name*]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
|
@ -54,10 +55,16 @@
|
|||
(= uuid/zero (:parent-id item)))
|
||||
absolute? (ctl/item-absolute? item)
|
||||
main-instance? (:main-instance item)
|
||||
|
||||
variants? (features/use-feature "variants/v1")
|
||||
is-variant? (when variants? (ctk/is-variant? item))
|
||||
is-variant-container? (when variants? (ctk/is-variant-container? item))
|
||||
variant-id (when is-variant? (:variant-id item))
|
||||
variant-name (when is-variant? (:variant-name item))
|
||||
is-variant-container? (when variants? (ctk/is-variant-container? item))]
|
||||
|
||||
data (deref refs/workspace-data)
|
||||
component (ctkl/get-component data (:component-id item))
|
||||
variant-properties (:variant-properties component)]
|
||||
[:*
|
||||
[:div {:id id
|
||||
:ref dref
|
||||
|
@ -121,7 +128,7 @@
|
|||
{:shape item
|
||||
:main-instance? main-instance?}]]])
|
||||
|
||||
[:& layer-name {:ref name-ref
|
||||
[:> layer-name* {:ref name-ref
|
||||
:shape-id id
|
||||
:shape-name name
|
||||
:is-shape-touched touched?
|
||||
|
@ -134,7 +141,10 @@
|
|||
:is-selected selected?
|
||||
:type-comp (or component-tree? is-variant-container?)
|
||||
:type-frame (cfh/frame-shape? item)
|
||||
:variant-id variant-id
|
||||
:variant-name variant-name
|
||||
:variant-properties variant-properties
|
||||
:component-id (:id component)
|
||||
:is-hidden hidden?}]
|
||||
|
||||
(when (not read-only?)
|
||||
|
|
|
@ -9,7 +9,9 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.variant :as ctv]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.variants :as dwv]
|
||||
[app.main.store :as st]
|
||||
[app.util.debug :as dbg]
|
||||
[app.util.dom :as dom]
|
||||
|
@ -24,12 +26,13 @@
|
|||
(-> (l/in [:workspace-local :shape-for-rename])
|
||||
(l/derived st/state)))
|
||||
|
||||
(mf/defc layer-name
|
||||
(mf/defc layer-name*
|
||||
{::mf/wrap-props false
|
||||
::mf/forward-ref true}
|
||||
[{:keys [shape-id shape-name is-shape-touched disabled-double-click
|
||||
on-start-edit on-stop-edit depth parent-size is-selected
|
||||
type-comp type-frame variant-name is-hidden is-blocked]} external-ref]
|
||||
type-comp type-frame variant-id variant-name variant-properties
|
||||
component-id is-hidden is-blocked]} external-ref]
|
||||
(let [edition* (mf/use-state false)
|
||||
edition? (deref edition*)
|
||||
|
||||
|
@ -39,6 +42,9 @@
|
|||
shape-for-rename (mf/deref lens:shape-for-rename)
|
||||
|
||||
shape-name (d/nilv variant-name shape-name)
|
||||
default-value (if variant-id
|
||||
(ctv/properties-map-to-string variant-properties)
|
||||
shape-name)
|
||||
|
||||
has-path? (str/includes? shape-name "/")
|
||||
|
||||
|
@ -54,13 +60,17 @@
|
|||
|
||||
accept-edit
|
||||
(mf/use-fn
|
||||
(mf/deps shape-id on-stop-edit)
|
||||
(mf/deps shape-id on-stop-edit component-id variant-id variant-name variant-properties)
|
||||
(fn []
|
||||
(let [name-input (mf/ref-val ref)
|
||||
name (str/trim (dom/get-value name-input))]
|
||||
(on-stop-edit)
|
||||
(reset! edition* false)
|
||||
(st/emit! (dw/end-rename-shape shape-id name)))))
|
||||
(if variant-name
|
||||
(let [valid? (ctv/valid-properties-string? name)
|
||||
props (if valid? (ctv/properties-string-to-map name) {})]
|
||||
(st/emit! (dwv/update-properties-names-and-values component-id variant-id variant-properties props)))
|
||||
(st/emit! (dw/end-rename-shape shape-id name))))))
|
||||
|
||||
cancel-edit
|
||||
(mf/use-fn
|
||||
|
@ -99,7 +109,7 @@
|
|||
:on-blur accept-edit
|
||||
:on-key-down on-key-down
|
||||
:auto-focus true
|
||||
:default-value (d/nilv shape-name "")}]
|
||||
:default-value (d/nilv default-value "")}]
|
||||
[:*
|
||||
[:span
|
||||
{:class (stl/css-case
|
||||
|
|
Loading…
Add table
Reference in a new issue