🎉 Create a new variant from an existing one

This commit is contained in:
Pablo Alba 2025-03-04 13:52:40 +01:00 committed by GitHub
parent aa468e2153
commit 8eb2aaa0a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 207 additions and 56 deletions

View file

@ -155,13 +155,14 @@
(dm/get-in data [:pages-index uuid/zero :objects])))
(defn- apply-changes-local
[changes]
[changes & {:keys [apply-to-library?]}]
(dm/assert!
"expected valid changes"
(check-changes! changes))
(if-let [file-data (::file-data (meta changes))]
(let [index (::applied-changes-count (meta changes))
(let [library-data (::library-data (meta changes))
index (::applied-changes-count (meta changes))
redo-changes (:redo-changes changes)
new-changes (if (< index (count redo-changes))
(->> (subvec (:redo-changes changes) index)
@ -169,28 +170,12 @@
(assoc :page-id uuid/zero)
(dissoc :component-id))))
[])
new-file-data (cfc/process-changes file-data new-changes)]
new-file-data (cfc/process-changes file-data new-changes)
new-library-data (if apply-to-library?
(cfc/process-changes library-data new-changes)
library-data)]
(vary-meta changes assoc ::file-data new-file-data
::applied-changes-count (count redo-changes)))
changes))
(defn apply-changes-local-library
[changes]
(dm/assert!
"expected valid changes"
(check-changes! changes))
(if-let [library-data (::library-data (meta changes))]
(let [index (::applied-changes-count (meta changes))
redo-changes (:redo-changes changes)
new-changes (if (< index (count redo-changes))
(->> (subvec (:redo-changes changes) index)
(map #(-> %
(assoc :page-id uuid/zero)
(dissoc :component-id))))
[])
new-library-data (cfc/process-changes library-data new-changes)]
(vary-meta changes assoc ::library-data new-library-data
::library-data new-library-data
::applied-changes-count (count redo-changes)))
changes))
@ -932,8 +917,10 @@
(defn add-component
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page]
(add-component changes id path name new-shapes updated-shapes main-instance-id main-instance-page nil))
(add-component changes id path name new-shapes updated-shapes main-instance-id main-instance-page nil nil nil))
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page annotation]
(add-component changes id path name new-shapes updated-shapes main-instance-id main-instance-page annotation nil nil))
([changes id path name new-shapes updated-shapes main-instance-id main-instance-page annotation variant-id variant-properties & {:keys [apply-changes-local-library?]}]
(assert-page-id! changes)
(assert-objects! changes)
(let [page-id (::page-id (meta changes))
@ -972,7 +959,9 @@
:name name
:main-instance-id main-instance-id
:main-instance-page main-instance-page
:annotation annotation}
:annotation annotation
:variant-id variant-id
:variant-properties variant-properties}
(some? new-shapes) ;; this will be null in components-v2
(assoc :shapes (vec new-shapes))))
(into (map mk-change) updated-shapes))))
@ -987,7 +976,7 @@
(map mk-change))
updated-shapes))))
(apply-changes-local)))))
(apply-changes-local {:apply-to-library? apply-changes-local-library?})))))
(defn update-component
[changes id update-fn & {:keys [apply-changes-local-library?]}]
@ -1019,7 +1008,7 @@
:variant-properties (:variant-properties prev-component)
:objects (:objects prev-component)})
(cond-> apply-changes-local-library?
(apply-changes-local-library)))
(apply-changes-local {:apply-to-library? true})))
changes)))
(defn delete-component

View file

@ -100,10 +100,10 @@
;; ---- Components and instances creation ----
(defn duplicate-component
(defn- duplicate-component
"Clone the root shape of the component and all children. Generate new
ids from all of them."
[component new-component-id library-data]
[component new-component-id library-data force-id]
(let [components-v2 (dm/get-in library-data [:options :components-v2])]
(if components-v2
(let [main-instance-page (ctf/get-component-page library-data component)
@ -139,7 +139,8 @@
(:parent-id main-instance-shape)
(:objects main-instance-page)
:update-new-shape update-new-shape
:update-original-shape update-original-shape)
:update-original-shape update-original-shape
:force-id force-id)
remap-frame
(fn [shape]
@ -179,7 +180,7 @@
(defn generate-duplicate-component
"Create a new component copied from the one with the given id."
[changes library component-id new-component-id components-v2]
[changes library component-id new-component-id components-v2 & {:keys [new-shape-id apply-changes-local-library?]}]
(let [component (ctkl/get-component (:data library) component-id)
new-name (:name component)
@ -191,7 +192,7 @@
[new-component-shape new-component-shapes ; <- null in components-v2
new-main-instance-shape new-main-instance-shapes]
(duplicate-component component new-component-id (:data library))]
(duplicate-component component new-component-id (:data library) new-shape-id)]
[new-main-instance-shape
(-> changes
@ -207,7 +208,10 @@
[]
(:id new-main-instance-shape)
(:id main-instance-page)
(:annotation component))
(:annotation component)
(:variant-id component)
(:variant-properties component)
{:apply-changes-local-library? apply-changes-local-library?})
;; Update grid layout if the new main instance is inside
(pcb/update-shapes
[(:frame-id new-main-instance-shape)]

View file

@ -61,6 +61,17 @@
(into assigned new-properties)))
(defn extract-properties-values
[data objects variant-id]
(->> (find-related-components data objects variant-id)
(mapcat :variant-properties)
(group-by :name)
(map (fn [[k v]]
{:name k
:values (distinct
(map #(if (str/empty? (:value %)) "--" (:value %)) v))}))))
(defn generate-update-property-name
[changes variant-id pos new-name]
(let [data (pcb/get-library-data changes)

View file

@ -333,6 +333,8 @@
(let [parent (get objects (:parent-id shape))]
;; We don't want to change the structure of component copies
(and (not (in-component-copy-not-head? shape))
;; We don't want to duplicate variants
(not (is-variant? shape))
;; Non instance, non copy. We allow
(or (not (instance-head? shape))
(not (in-component-copy? parent))))))

View file

@ -34,12 +34,14 @@
(assoc component :modified-at (dt/now)))
(defn add-component
[fdata {:keys [id name path main-instance-id main-instance-page shapes annotation]}]
[fdata {:keys [id name path main-instance-id main-instance-page shapes annotation variant-id variant-properties]}]
(let [components-v2 (dm/get-in fdata [:options :components-v2])
fdata (update fdata :components assoc id (touch {:id id :name name :path path}))]
(if components-v2
(cond-> (update-in fdata [:components id] assoc :main-instance-id main-instance-id :main-instance-page main-instance-page)
annotation (update-in [:components id] assoc :annotation annotation))
annotation (update-in [:components id] assoc :annotation annotation)
variant-id (update-in [:components id] assoc :variant-id variant-id)
variant-properties (update-in [:components id] assoc :variant-properties variant-properties))
(let [wrap-object-fn cfeat/*wrap-with-objects-map-fn*]
(assoc-in fdata [:components id :objects]

View file

@ -26,6 +26,7 @@
[app.main.data.workspace.texts :as dwtxt]
[app.main.data.workspace.transforms :as dwt]
[app.main.data.workspace.undo :as dwu]
[app.main.data.workspace.variants :as dwv]
[app.main.features :as features]
[app.main.refs :as refs]
[app.main.store :as st]
@ -121,7 +122,7 @@
:duplicate {:tooltip (ds/meta "D")
:command (ds/c-mod "d")
:subsections [:edit]
:fn #(emit-when-no-readonly (dw/duplicate-selected true))}
:fn #(emit-when-no-readonly (dwv/duplicate-or-add-variant))}
:start-editing {:tooltip (ds/enter)
:command "enter"
@ -175,7 +176,7 @@
:create-component {:tooltip (ds/meta "K")
:command (ds/c-mod "k")
:subsections [:modify-layers]
:fn #(emit-when-no-readonly (dwl/add-component))}
:fn #(emit-when-no-readonly (dwv/add-component-or-variant))}
:detach-component {:tooltip (ds/meta-shift "K")
:command (ds/c-mod "shift+k")

View file

@ -7,17 +7,24 @@
(ns app.main.data.workspace.variants
(:require
[app.common.colors :as clr]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.logic.libraries :as cll]
[app.common.logic.variants :as clv]
[app.common.types.component :as ctc]
[app.common.types.components-list :as ctkl]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.helpers :as dsh]
[app.main.data.workspace.colors :as cl]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.undo :as dwu]
[app.main.features :as features]
[app.util.dom :as dom]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
@ -131,6 +138,70 @@
(dch/commit-changes changes)
(dwu/commit-undo-transaction undo-id))))))
(defn focus-property
[shape-id prop-num]
(ptk/reify ::focus-property
ptk/EffectEvent
(effect [_ _ _]
(dom/focus! (dom/get-element (str "variant-prop-" shape-id prop-num))))))
(defn add-new-variant
"Create a new variant and add it to the variant-container"
[shape-id]
(ptk/reify ::add-new-variant
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))
shape (get objects shape-id)
shape (if (ctc/is-variant-container? shape)
(get objects (last (:shapes shape)))
shape)
component-id (:component-id shape)
component (ctkl/get-component data component-id)
new-component-id (uuid/next)
new-shape-id (uuid/next)
value (str clv/value-prefix
(-> (clv/extract-properties-values data objects (:variant-id component))
last
:values
count
inc))
prop-num (dec (count (:variant-properties component)))
[new-shape changes] (-> (pcb/empty-changes it page-id)
(pcb/with-library-data data)
(pcb/with-objects objects)
(pcb/with-page-id page-id)
(cll/generate-duplicate-component
{:data data}
component-id
new-component-id
true
{:new-shape-id new-shape-id :apply-changes-local-library? true}))
changes (-> changes
(clv/generate-update-property-value new-component-id prop-num value)
(pcb/change-parent (:parent-id shape) [new-shape] 0))
undo-id (js/Symbol)]
(rx/concat
(rx/of
(dwu/start-undo-transaction undo-id)
(dch/commit-changes changes)
(dwu/commit-undo-transaction undo-id)
(ptk/data-event :layout/update {:ids [(:parent-id shape)]})
(dws/select-shape new-shape-id))
(->> (rx/of (focus-property new-shape-id prop-num))
(rx/delay 250)))))))
(defn transform-in-variant
"Given the id of a main shape of a component, creates a variant structure for
that component"
@ -174,3 +245,55 @@
(set-variant-id new-component-id variant-id)
(add-new-property variant-id {:fill-values? true})
(dwu/commit-undo-transaction undo-id))))))
(defn add-component-or-variant
[]
(ptk/reify ::add-component-or-variant
ptk/WatchEvent
(watch [_ state _]
(let [variants? (features/active-feature? state "variants/v1")
objects (dsh/lookup-page-objects state)
selected-ids (dsh/lookup-selected state)
selected-shapes (map (d/getf objects) selected-ids)
single? (= 1 (count selected-ids))
first-shape (first selected-shapes)
transform-in-variant? (and variants?
single?
(not (ctc/is-variant? first-shape))
(ctc/main-instance? first-shape))
add-new-variant? (and variants?
(every? ctc/is-variant? selected-shapes))
undo-id (js/Symbol)]
(cond
transform-in-variant?
(rx/of (transform-in-variant (:id first-shape)))
add-new-variant?
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/from (map add-new-variant selected-ids))
(rx/of (dwu/commit-undo-transaction undo-id)))
:else
(rx/of (dwl/add-component)))))))
(defn duplicate-or-add-variant
[]
(ptk/reify ::duplicate-or-add-variant
ptk/WatchEvent
(watch [_ state _]
(let [variants? (features/active-feature? state "variants/v1")
objects (dsh/lookup-page-objects state)
selected-ids (dsh/lookup-selected state)
selected-shapes (map (d/getf objects) selected-ids)
add-new-variant? (and variants?
(every? ctc/is-variant? selected-shapes))
undo-id (js/Symbol)]
(if add-new-variant?
(rx/concat
(rx/of (dwu/start-undo-transaction undo-id))
(rx/from (map add-new-variant selected-ids))
(rx/of (dwu/commit-undo-transaction undo-id)))
(rx/of (dws/duplicate-selected true)))))))

View file

@ -27,6 +27,7 @@
[app.main.data.workspace.shape-layout :as dwsl]
[app.main.data.workspace.shapes :as dwsh]
[app.main.data.workspace.shortcuts :as sc]
[app.main.data.workspace.variants :as dwv]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
@ -555,8 +556,10 @@
can-make-component (every? true? (map #(ctn/valid-shape-for-component? objects %) shapes))
heads (filter ctk/instance-head? shapes)
components-menu-entries (cmm/generate-components-menu-entries heads true)
variant-container? (and single? (ctk/is-variant-container? (first shapes)))
do-add-component #(st/emit! (dwl/add-component))
do-add-multiple-components #(st/emit! (dwl/add-multiple-components))]
do-add-multiple-components #(st/emit! (dwl/add-multiple-components))
do-add-variant #(st/emit! (dwv/add-new-variant (:id (first shapes))))]
[:*
(when can-make-component ;; We don't want to change the structure of component copies
[:*
@ -577,7 +580,13 @@
:title (:title entry)
:shortcut (when (contains? entry :shortcut)
(sc/get-tooltip (:shortcut entry)))
:on-click (:action entry)}])])]))
:on-click (:action entry)}])])
(when variant-container?
[:> menu-separator*]
[:> menu-entry* {:title (tr "workspace.shape.menu.add-variant")
:shortcut (sc/get-tooltip :create-component)
:on-click do-add-variant}])]))
(mf/defc context-menu-delete*
{::mf/props :obj

View file

@ -471,5 +471,6 @@
:action do-update-component})
(when (and variants? (not multi) main-instance?)
{:title (tr "workspace.shape.menu.add-variant")
:shortcut :create-component
:action do-add-variant})]]
(filter (complement nil?) menu-entries)))

View file

@ -288,11 +288,12 @@
[:*
(for [[pos prop] (map vector (range) properties)]
[:div {:key (str (:id shape) (:name prop)) :class (stl/css :variant-property-container)}
[:div {:key (str (:id shape) pos) :class (stl/css :variant-property-container)}
(if (ctk/main-instance? shape)
[:*
[:span {:class (stl/css :variant-property-name :variant-property-name-bg)} (:name prop)]
[:> combobox* {:default-selected (if (str/empty? (:value prop)) "--" (:value prop))
[:> 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)}]]
@ -741,7 +742,7 @@
(mf/defc variant-menu*
[{:keys [shapes]}]
(let [multi (> (count shapes) 1)
(let [multi? (> (count shapes) 1)
shape (first shapes)
shape-name (:name shape)
@ -757,6 +758,11 @@
first-variant (get objects (first (:shapes shape)))
variant-id (:variant-id first-variant)
dashes-to-end (mf/use-fn
(fn [data]
(let [dashes (if (some #(= % "--") data) ["--"] [])]
(concat (remove #(= % "--") data) dashes))))
properties (mf/with-memo [data objects variant-id]
(->> (dwv/find-related-components data objects variant-id)
(mapcat :variant-properties)
@ -764,17 +770,20 @@
(map-indexed (fn [index [k v]]
{:name k
:pos index
:values (distinct
(map #(if (str/empty? (:value %)) "--" (:value %)) v))}))))
:values (->> v
(map #(if (str/empty? (:value %)) "--" (:value %)))
distinct
dashes-to-end)}))))
menu-open* (mf/use-state false)
menu-open? (deref menu-open*)
menu-entries [{:title (tr "workspace.shape.menu.add-variant-property")
:action #(st/emit! (dwv/add-new-property variant-id))}]
show-menu? (seq menu-entries)
:action #(st/emit! (dwv/add-new-property variant-id))}
{:title (tr "workspace.shape.menu.add-variant")
:action #(st/emit! (dwv/add-new-variant (:id shape)))}]
on-menu-click
(mf/use-fn
@ -821,8 +830,8 @@
[:div {:class (stl/css :element-content)}
[:div {:class (stl/css-case :component-wrapper true
:with-actions show-menu?
:without-actions (not show-menu?))}
:with-actions (not multi?)
:without-actions multi?)}
[:button {:class (stl/css-case :component-name-wrapper true
:with-main true
:swappeable false)}
@ -832,12 +841,12 @@
[:div {:class (stl/css :name-wrapper)}
[:div {:class (stl/css :component-name)}
[:span {:class (stl/css :component-name-inside)}
(if multi
(if multi?
(tr "settings.multiple")
(cfh/last-path shape-name))]]]]
(when show-menu?
(when-not multi?
[:div {:class (stl/css :component-actions)}
[:button {:class (stl/css-case :menu-btn true
:selected menu-open?)
@ -848,11 +857,11 @@
:on-close on-menu-close
:menu-entries menu-entries
:main-instance true}]])]
(when-not multi
(when-not multi?
[:*
(for [property properties]
(for [[pos property] (map vector (range) properties)]
(let [val (str/join ", " (:values property))]
[:div {:key (str (:id shape) (:name property)) :class (stl/css :variant-property-row)}
[:div {:key (str (:id shape) pos) :class (stl/css :variant-property-row)}
[:> input-with-values* {:name (:name property)
:values val
:data-position (:pos property)

View file

@ -6218,7 +6218,7 @@ msgid "workspace.shape.menu.create-component"
msgstr "Create component"
msgid "workspace.shape.menu.add-variant"
msgstr "Add variant"
msgstr "Create variant"
msgid "workspace.shape.menu.add-variant-property"
msgstr "Add new property"

View file

@ -6232,7 +6232,7 @@ msgid "workspace.shape.menu.create-component"
msgstr "Crear componente"
msgid "workspace.shape.menu.add-variant"
msgstr "Añadir variante"
msgstr "Crear variante"
msgid "workspace.shape.menu.add-variant-property"
msgstr "Añadir nueva propiedad"