0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-21 06:16:28 -05:00

🎉 Add drag components in or out a variant container

This commit is contained in:
Pablo Alba 2025-02-20 10:05:01 +01:00 committed by GitHub
parent 6277db8d45
commit 014c297458
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 613 additions and 206 deletions

View file

@ -174,6 +174,26 @@
::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
::applied-changes-count (count redo-changes)))
changes))
;; Page changes
(defn add-empty-page
@ -970,37 +990,37 @@
(apply-changes-local)))))
(defn update-component
([changes id update-fn]
(let [library-data (::library-data (meta changes))
prev-component (get-in library-data [:components id])]
(update-component changes id prev-component update-fn)))
([changes id prev-component update-fn]
(assert-library! changes)
(let [new-component (update-fn prev-component)]
(if prev-component
(-> changes
(update :redo-changes conj {:type :mod-component
:id id
:name (:name new-component)
:path (:path new-component)
:main-instance-id (:main-instance-id new-component)
:main-instance-page (:main-instance-page new-component)
:annotation (:annotation new-component)
:variant-id (:variant-id new-component)
:variant-properties (:variant-properties new-component)
:objects (:objects new-component) ;; this won't exist in components-v2 (except for deleted components)
:modified-at (:modified-at new-component)})
(update :undo-changes conj {:type :mod-component
:id id
:name (:name prev-component)
:path (:path prev-component)
:main-instance-id (:main-instance-id prev-component)
:main-instance-page (:main-instance-page prev-component)
:annotation (:annotation prev-component)
:variant-id (:variant-id prev-component)
:variant-properties (:variant-properties prev-component)
:objects (:objects prev-component)}))
changes))))
[changes id update-fn & {:keys [apply-changes-local-library?]}]
(assert-library! changes)
(let [library-data (::library-data (meta changes))
prev-component (get-in library-data [:components id])
new-component (update-fn prev-component)]
(if prev-component
(-> changes
(update :redo-changes conj {:type :mod-component
:id id
:name (:name new-component)
:path (:path new-component)
:main-instance-id (:main-instance-id new-component)
:main-instance-page (:main-instance-page new-component)
:annotation (:annotation new-component)
:variant-id (:variant-id new-component)
:variant-properties (:variant-properties new-component)
:objects (:objects new-component) ;; this won't exist in components-v2 (except for deleted components)
:modified-at (:modified-at new-component)})
(update :undo-changes conj {:type :mod-component
:id id
:name (:name prev-component)
:path (:path prev-component)
:main-instance-id (:main-instance-id prev-component)
:main-instance-page (:main-instance-page prev-component)
:annotation (:annotation prev-component)
:variant-id (:variant-id prev-component)
:variant-properties (:variant-properties prev-component)
:objects (:objects prev-component)})
(cond-> apply-changes-local-library?
(apply-changes-local-library)))
changes)))
(defn delete-component
[changes id page-id]
@ -1061,3 +1081,11 @@
(reduce reorder-grid changes))]
changes))
(defn get-library-data
[changes]
(::library-data (meta changes)))
(defn get-objects
[changes]
(dm/get-in (::file-data (meta changes)) [:pages-index uuid/zero :objects]))

View file

@ -10,12 +10,14 @@
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh]
[app.common.logic.variants :as clv]
[app.common.types.component :as ctk]
[app.common.types.container :as ctn]
[app.common.types.shape.interactions :as ctsi]
[app.common.types.shape.layout :as ctl]
[app.common.types.token :as cto]
[app.common.uuid :as uuid]))
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
(defn- generate-unapply-tokens
"When updating attributes that have a token applied, we must unapply it, because the value
@ -239,21 +241,21 @@
(defn generate-relocate
[changes objects parent-id page-id to-index ids & {:keys [cell ignore-parents?]}]
(let [ids (cfh/order-by-indexed-shapes objects ids)
shapes (map (d/getf objects) ids)
parent (get objects parent-id)
[changes parent-id to-index ids & {:keys [cell ignore-parents?]}]
(let [objects (pcb/get-objects changes)
ids (cfh/order-by-indexed-shapes objects ids)
shapes (map (d/getf objects) ids)
parent (get objects parent-id)
all-parents (into #{parent-id} (map #(cfh/get-parent-id objects %)) ids)
parents (if ignore-parents? #{parent-id} all-parents)
parents (if ignore-parents? #{parent-id} all-parents)
children-ids
(->> ids
(mapcat #(cfh/get-children-ids-with-self objects %)))
children-ids (mapcat #(cfh/get-children-ids-with-self objects %) ids)
child-heads
(->> ids
(mapcat #(ctn/get-child-heads objects %))
(map :id))
child-heads (mapcat #(ctn/get-child-heads objects %) ids)
child-heads-ids (map :id child-heads)
variant-heads (filter ctk/is-variant? child-heads)
component-main-parent
(ctn/find-component-main objects parent false)
@ -340,9 +342,6 @@
cell (or cell (and index-cell-data [(:row index-cell-data) (:column index-cell-data)]))]
(-> changes
(pcb/with-page-id page-id)
(pcb/with-objects objects)
;; Remove layout-item properties when moving a shape outside a layout
(cond-> (not (ctl/any-layout? parent))
(pcb/update-shapes ids ctl/remove-layout-item-data))
@ -353,7 +352,7 @@
;; Remove the swap slots if it is moving to a different component
(pcb/update-shapes
child-heads
child-heads-ids
(fn [shape]
(cond-> shape
(not= component-main-parent (ctn/find-component-main objects shape false))
@ -365,7 +364,86 @@
;; Add component-root property when moving a component outside a component
(cond-> (not (ctn/get-instance-root objects parent))
(pcb/update-shapes child-heads #(assoc % :component-root true)))
(pcb/update-shapes child-heads-ids #(assoc % :component-root true)))
;; Remove variant info and rename when moving outside a variant-container
(cond-> (not (ctk/is-variant-container? parent))
((fn [changes]
(reduce
(fn [changes shape]
(let [new-name (str/replace (:variant-name shape) #", " " / ")
[cpath cname] (cfh/parse-path-name new-name)]
(-> changes
(pcb/update-component (:component-id shape)
#(-> (dissoc % :variant-id :variant-properties)
(assoc :name cname
:path cpath))
{:apply-changes-local-library? true})
(pcb/update-shapes [(:id shape)]
#(-> (dissoc % :variant-id :variant-name)
(assoc :name new-name))))))
changes
variant-heads))))
;; Add variant info and rename when moving into a different variant-container
(cond-> (ctk/is-variant-container? parent)
((fn [changes]
(let [get-base-name #(if (some? (:variant-name %))
(str/replace (:variant-name %) #", " " / ")
(:name %))
calc-num-props #(-> %
get-base-name
cfh/split-path
count)
max-path-items (apply max (map calc-num-props child-heads))
first-comp-id (->> parent
:shapes
first
(get objects)
:component-id)
data (pcb/get-library-data changes)
variant-properties (get-in data [:components first-comp-id :variant-properties])
num-props (count variant-properties)
num-new-props (if (< max-path-items num-props)
0
(- max-path-items num-props))
changes (nth
(iterate #(clv/generate-add-new-property % (:id parent)) changes)
num-new-props)]
(reduce
(fn [changes shape]
(if (= (:id parent) (:variant-id shape))
changes ;; do nothing if we aren't changing the parent
(let [base-name (get-base-name shape)
;; we need to get the updated library data to have access to the current properties
data (pcb/get-library-data changes)
props (clv/path-to-properties
base-name
(get-in data [:components first-comp-id :variant-properties]))
variant-name (clv/properties-to-name props)
[cpath cname] (cfh/parse-path-name (:name parent))]
(-> (pcb/update-component changes
(:component-id shape)
#(assoc % :variant-id (:id parent)
:variant-properties props
:name cname
:path cpath)
{:apply-changes-local-library? true})
(pcb/update-shapes [(:id shape)]
#(assoc % :variant-id (:id parent)
:variant-name variant-name
:name (:name parent)))))))
changes
child-heads)))))
;; Move the shapes
(pcb/change-parent parent-id

View file

@ -5,58 +5,138 @@
;; Copyright (c) KALEIDOS INC
(ns app.common.logic.variants
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.files.helpers :as cfh]
[app.common.types.components-list :as ctcl]
[cuerdas.core :as str]))
(def property-prefix "Property")
(def property-regex (re-pattern (str property-prefix "(\\d+)")))
(def value-prefix "Value")
(defn find-related-components
"Find a list of the components thet belongs to this variant-id"
[data objects variant-id]
(->> (dm/get-in objects [variant-id :shapes])
(map #(dm/get-in objects [% :component-id]))
(map #(ctcl/get-component data % true))
reverse))
(defn properties-to-name
"Transform the properties into a name, with the values separated by comma"
[properties]
(->> properties
(map :value)
(remove str/empty?)
(str/join ", ")))
(defn next-property-number
"Returns the next property number, to avoid duplicates on the property names"
[properties]
(let [numbers (keep
#(some->> (:name %) (re-find property-regex) second d/parse-integer)
properties)
max-num (if (seq numbers)
(apply max numbers)
0)]
(inc (max max-num (count properties)))))
(defn path-to-properties
"From a list of properties and a name with path, assign each token of the
path as value of a different property"
[path properties]
(let [next-prop-num (next-property-number properties)
cpath (cfh/split-path path)
assigned (mapv #(assoc % :value (nth cpath %2 "")) properties (range))
remaining (drop (count properties) cpath)
new-properties (map-indexed (fn [i v] {:name (str property-prefix (+ next-prop-num i))
:value v}) remaining)]
(into assigned new-properties)))
(defn generate-update-property-name
[changes related-components pos new-name]
(reduce (fn [changes component]
(pcb/update-component
changes (:id component)
#(assoc-in % [:variant-properties pos :name] new-name)))
changes
related-components))
[changes variant-id pos new-name]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (find-related-components data objects variant-id)]
(reduce (fn [changes component]
(pcb/update-component
changes (:id component)
#(assoc-in % [:variant-properties pos :name] new-name)
{:apply-changes-local-library? true}))
changes
related-components)))
(defn generate-remove-property
[changes related-components pos]
(reduce (fn [changes component]
(let [props (:variant-properties component)
props (vec (concat (subvec props 0 pos) (subvec props (inc pos))))
main-id (:main-instance-id component)
name (properties-to-name props)]
(-> changes
(pcb/update-component (:id component) #(assoc % :variant-properties props))
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
changes
related-components))
[changes variant-id pos]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (find-related-components data objects variant-id)]
(reduce (fn [changes component]
(let [props (:variant-properties component)
props (d/remove-at-index props pos)
main-id (:main-instance-id component)
name (properties-to-name props)]
(-> changes
(pcb/update-component (:id component) #(assoc % :variant-properties props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
changes
related-components)))
(defn generate-update-property-value
[changes component-id main-id pos value name]
(-> changes
(pcb/update-component component-id #(assoc-in % [:variant-properties pos :value] value))
(pcb/update-shapes [main-id] #(assoc % :variant-name name))))
[changes component-id pos value]
(let [data (pcb/get-library-data changes)
component (ctcl/get-component data component-id true)
main-id (:main-instance-id component)
name (-> (:variant-properties component)
(update pos assoc :value value)
properties-to-name)]
(-> changes
(pcb/update-component component-id #(assoc-in % [:variant-properties pos :value] value)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(assoc % :variant-name name)))))
(defn generate-add-new-property
[changes related-components property-name]
(let [[_ changes]
[changes variant-id & {:keys [fill-values?]}]
(let [data (pcb/get-library-data changes)
objects (pcb/get-objects changes)
related-components (find-related-components data objects variant-id)
props (-> related-components first :variant-properties)
next-prop-num (next-property-number props)
property-name (str property-prefix next-prop-num)
[_ changes]
(reduce (fn [[num changes] component]
(let [props (-> (or (:variant-properties component) [])
(conj {:name property-name :value (str "Value" num)}))
main-id (:main-instance-id component)
variant-name (properties-to-name props)]
(let [main-id (:main-instance-id component)
update-props #(-> (d/nilv % [])
(conj {:name property-name
:value (if fill-values? (str value-prefix num) "")}))
update-name #(if fill-values?
(if (str/empty? %)
(str value-prefix num)
(str % ", " value-prefix num))
%)]
[(inc num)
(-> changes
(pcb/update-component (:id component) #(assoc % :variant-properties props))
(pcb/update-shapes [main-id] #(assoc % :variant-name variant-name)))]))
(pcb/update-component (:id component)
#(update % :variant-properties update-props)
{:apply-changes-local-library? true})
(pcb/update-shapes [main-id] #(update % :variant-name update-name)))]))
[1 changes]
related-components)]
changes))

View file

@ -57,6 +57,14 @@
:main-instance-page (:id page)
:shapes updated-shapes))))))))
(defn update-component
[file component-label & {:keys [] :as params}]
(let [component-id (thi/id component-label)]
(ctf/update-file-data
file
(fn [file-data]
(ctkl/update-component file-data component-id #(merge % params))))))
(defn get-component
[file label & {:keys [include-deleted?] :or {include-deleted? false}}]
(ctkl/get-component (:data file) (thi/id label) include-deleted?))

View file

@ -0,0 +1,25 @@
;; 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 app.common.test-helpers.variants
(:require
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]))
(defn add-variant
[file variant-label component1-label root1-label component2-label root2-label
& {:keys []}]
(let [file (ths/add-sample-shape file variant-label :type :frame :is-variant-container true)
variant-id (thi/id variant-label)]
(-> file
(ths/add-sample-shape root2-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value2")
(ths/add-sample-shape root1-label :type :frame :parent-label variant-label :variant-id variant-id :variant-name "Value1")
(thc/make-component component1-label root1-label)
(thc/update-component component1-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value1"}]})
(thc/make-component component2-label root2-label)
(thc/update-component component2-label {:variant-id variant-id :variant-properties [{:name "Property1" :value "Value1"}]}))))

View file

@ -535,11 +535,17 @@
(letfn [(get-frame [parent-id]
(if (cfh/frame-shape? objects parent-id) parent-id (get-in objects [parent-id :frame-id])))]
(let [parent (get objects parent-id)
;; We can always move the children to the parent they already have.
;; We can always move the children to the parent they already have.
;; But if we are pasting, those are new items, so it is considered a change
no-changes?
(->> children (every? #(= parent-id (:parent-id %))))]
;; In case no-changes is true we must ensure we are copy pasting the children in the same position
(if (or (and no-changes? (not pasting?)) (not (invalid-structure-for-component? objects parent children pasting? libraries)))
(and (->> children (every? #(= parent-id (:parent-id %))))
(not pasting?))
all-main?
(->> children (every? #(ctk/main-instance? %)))]
(if (or no-changes?
(and (not (invalid-structure-for-component? objects parent children pasting? libraries))
;; If we are moving into a variant-container, all the items should be main
(or all-main? (not (ctk/is-variant-container? parent)))))
[parent-id (get-frame parent-id)]
(recur (:parent-id parent) objects children pasting? libraries))))))

View file

@ -61,10 +61,10 @@
blue1 (ths/get-shape file :blue1)
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
uuid/zero ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@ -91,10 +91,10 @@
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id b2) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@ -121,10 +121,10 @@
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@ -134,10 +134,10 @@
yellow' (ths/get-shape file' :frame-yellow)
;; Move yellow into root
changes' (cls/generate-relocate (pcb/empty-changes nil)
(:objects page')
changes' (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page'))
(pcb/with-objects (:objects page')))
uuid/zero ;; parent-id
(:id page') ;; page-id
0 ;; to-index
#{(:id yellow')}) ;; ids
@ -164,10 +164,10 @@
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@ -178,10 +178,10 @@
b2' (ths/get-shape file' :frame-b2)
;; Move yellow into b2
changes' (cls/generate-relocate (pcb/empty-changes nil)
(:objects page')
changes' (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page'))
(pcb/with-objects (:objects page')))
(:id b2') ;; parent-id
(:id page') ;; page-id
0 ;; to-index
#{(:id yellow')}) ;; ids
@ -254,10 +254,10 @@
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@ -308,10 +308,10 @@
blue1 (ths/get-shape file :blue1)
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:parent-id blue1) ;; parent-id
(:id page) ;; page-id
2 ;; to-index
#{(:id blue1)}) ;; ids
@ -338,10 +338,10 @@
;; ==== Action
;; Move blue1 into yellow
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@ -351,10 +351,10 @@
page' (thf/current-page file')
blue1' (ths/get-shape file' :blue1)
b1' (ths/get-shape file' :frame-b1)
changes' (cls/generate-relocate (pcb/empty-changes nil)
(:objects page')
changes' (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page'))
(pcb/with-objects (:objects page')))
(:id b1') ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1')}) ;; ids
@ -382,10 +382,10 @@
;; ==== Action
;; Relocate blue1 into yellow
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id yellow) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id blue1)}) ;; ids
@ -396,10 +396,10 @@
page' (thf/current-page file')
blue1' (ths/get-shape file' :blue1)
b1' (ths/get-shape file' :frame-b1)
changes' (cls/generate-relocate (pcb/empty-changes nil)
(:objects page')
changes' (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page'))
(pcb/with-objects (:objects page')))
(:id b1') ;; parent-id
(:id page') ;; page-id
0 ;; to-index
#{(:id blue1')}) ;; ids
@ -428,10 +428,10 @@
green-copy (ths/get-shape file :green-copy)
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
uuid/zero ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id green-copy)}) ;; ids
@ -459,10 +459,10 @@
b2 (ths/get-shape file :frame-b2)
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id b2) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id green-copy)}) ;; ids

View file

@ -136,10 +136,10 @@
;; IMPORTANT: as modifying copies structure is now forbidden, this action
;; will not have any effect, and so the parent shape won't also be touched.
changes (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :copy-root) ; parent-id
(:id page) ; page-id
0 ; to-index
#{(thi/id :free-shape)}) ; ids
@ -231,10 +231,10 @@
;; IMPORTANT: as modifying copies structure is now forbidden, this action
;; will not have any effect, and so the parent shape won't also be touched.
changes (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :copy-root) ; parent-id
(:id page) ; page-id
2 ; to-index
#{(:id copy-child1)}) ; ids

View file

@ -195,10 +195,10 @@
page (thf/current-page file)
;; ==== Action
changes1 (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes1 (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :main-root) ; parent-id
(:id page) ; page-id
0 ; to-index
#{(thi/id :free-shape)}) ; ids
@ -292,10 +292,10 @@
main-child1 (ths/get-shape file :main-child1)
;; ==== Action
changes1 (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes1 (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :main-root) ; parent-id
(:id page) ; page-id
2 ; to-index
#{(:id main-child1)}) ; ids

View file

@ -112,10 +112,10 @@
;; IMPORTANT: as modifying copies structure is now forbidden, this action
;; will not have any effect, and so the parent shape won't also be touched.
changes (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :copy-root) ; parent-id
(:id page) ; page-id
0 ; to-index
#{(thi/id :free-shape)}) ; ids
@ -187,10 +187,10 @@
;; IMPORTANT: as modifying copies structure is now forbidden, this action
;; will not have any effect, and so the parent shape won't also be touched.
changes (cls/generate-relocate (pcb/empty-changes)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(thi/id :copy-root) ; parent-id
(:id page) ; page-id
2 ; to-index
#{(:id copy-child1)}) ; ids

View file

@ -29,10 +29,10 @@
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
(:id frame-parent) ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id frame-to-move)}) ;; ids
@ -61,10 +61,10 @@
;; ==== Action
changes (cls/generate-relocate (pcb/empty-changes nil)
(:objects page)
changes (cls/generate-relocate (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-objects (:objects page)))
uuid/zero ;; parent-id
(:id page) ;; page-id
0 ;; to-index
#{(:id circle)}) ;; ids

View file

@ -0,0 +1,194 @@
;; 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.logic.variants-test
(:require
[app.common.files.changes-builder :as pcb]
[app.common.logic.variants :as clv]
[app.common.test-helpers.components :as thc]
[app.common.test-helpers.files :as thf]
[app.common.test-helpers.ids-map :as thi]
[app.common.test-helpers.shapes :as ths]
[app.common.test-helpers.variants :as thv]
[clojure.test :as t]))
(t/use-fixtures :each thi/test-fixture)
(t/deftest test-update-property-name
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-update-property-name v-id 0 "NewName1")
(clv/generate-update-property-name v-id 1 "NewName2"))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (-> comp01' :variant-properties first :name) "NewName1"))
(t/is (= (-> comp01' :variant-properties last :name) "NewName2"))
(t/is (= (-> comp02' :variant-properties first :name) "NewName1"))
(t/is (= (-> comp02' :variant-properties last :name) "NewName2"))))
(t/deftest test-add-new-property-without-values
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-add-new-property v-id))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (count (:variant-properties comp01)) 1))
(t/is (= (count (:variant-properties comp01')) 2))
(t/is (= (count (:variant-properties comp02)) 1))
(t/is (= (count (:variant-properties comp02')) 2))
(t/is (= (-> comp01' :variant-properties last :value) ""))))
(t/deftest test-add-new-property-with-values
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-add-new-property v-id {:fill-values? true}))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (count (:variant-properties comp01)) 1))
(t/is (= (count (:variant-properties comp01')) 2))
(t/is (= (count (:variant-properties comp02)) 1))
(t/is (= (count (:variant-properties comp02')) 2))
(t/is (= (-> comp01' :variant-properties last :value) "Value1"))))
(t/deftest test-remove-property
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
v-id (-> (ths/get-shape file :v01) :id)
page (thf/current-page file)
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-add-new-property v-id))
file (thf/apply-changes file changes)
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-remove-property v-id 0))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (count (:variant-properties comp01)) 2))
(t/is (= (count (:variant-properties comp01')) 1))
(t/is (= (count (:variant-properties comp02)) 2))
(t/is (= (count (:variant-properties comp02')) 1))
(t/is (= (-> comp01' :variant-properties first :name) "Property2"))))
(t/deftest test-update-property-value
(let [;; ==== Setup
file (-> (thf/sample-file :file1)
(thv/add-variant :v01 :c01 :m01 :c02 :m02))
page (thf/current-page file)
comp01 (thc/get-component file :c01)
comp02 (thc/get-component file :c02)
;; ==== Action
changes (-> (pcb/empty-changes nil)
(pcb/with-page-id (:id page))
(pcb/with-library-data (:data file))
(pcb/with-objects (:objects page))
(clv/generate-update-property-value (:id comp01) 0 "NewValue1")
(clv/generate-update-property-value (:id comp02) 0 "NewValue2"))
file' (thf/apply-changes file changes)
;; ==== Get
comp01' (thc/get-component file' :c01)
comp02' (thc/get-component file' :c02)]
;; ==== Check
(t/is (= (-> comp01' :variant-properties first :value) "NewValue1"))
(t/is (= (-> comp02' :variant-properties first :value) "NewValue2"))))

View file

@ -890,6 +890,7 @@
(watch [it state _]
(let [page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
data (dsh/lookup-file-data state)
;; Ignore any shape whose parent is also intended to be moved
ids (cfh/clean-loops objects ids)
@ -899,13 +900,15 @@
all-parents (into #{parent-id} (map #(cfh/get-parent-id objects %)) ids)
changes (cls/generate-relocate (pcb/empty-changes it)
objects
parent-id
page-id
to-index
ids
:ignore-parents? ignore-parents?)
changes (-> (pcb/empty-changes it)
(pcb/with-page-id page-id)
(pcb/with-objects objects)
(pcb/with-library-data data)
(cls/generate-relocate
parent-id
to-index
ids
:ignore-parents? ignore-parents?))
undo-id (js/Symbol)]
(rx/of (dwu/start-undo-transaction undo-id)

View file

@ -232,7 +232,8 @@
ids (->> ids
(remove #(ctn/has-any-copy-parent? objects (get objects %)))
;; components can't be ungrouped
(remove #(ctk/instance-head? (get objects %))))
(remove #(ctk/instance-head? (get objects %)))
(remove #(ctk/is-variant-container? (get objects %))))
changes-list (sequence (keep prepare) ids)

View file

@ -930,14 +930,17 @@
(watch [it state _]
(let [page-id (:current-page-id state)
objects (dsh/lookup-page-objects state page-id)
data (dsh/lookup-file-data state)
ids (cleanup-invalid-moving-shapes ids objects frame-id)
changes (cls/generate-relocate (pcb/empty-changes it)
objects
frame-id
page-id
drop-index
ids
:cell cell)]
changes (-> (pcb/empty-changes it)
(pcb/with-page-id page-id)
(pcb/with-objects objects)
(pcb/with-library-data data)
(cls/generate-relocate
frame-id
drop-index
ids
:cell cell))]
(when (and (some? frame-id) (d/not-empty? changes))
(rx/of (dch/commit-changes changes)

View file

@ -10,7 +10,6 @@
[app.common.data.macros :as dm]
[app.common.files.changes-builder :as pcb]
[app.common.logic.variants :as clv]
[app.common.types.components-list :as ctcl]
[app.common.uuid :as uuid]
[app.main.data.changes :as dch]
[app.main.data.helpers :as dsh]
@ -22,11 +21,7 @@
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(defn find-related-components
[data objects variant-id]
(->> (dm/get-in objects [variant-id :shapes])
(map #(dm/get-in objects [% :component-id]))
(map #(ctcl/get-component data % true))))
(dm/export clv/find-related-components)
(defn update-property-name
"Update the variant property name on the position pos
@ -39,10 +34,11 @@
data (dsh/lookup-file-data state)
objects (-> (dsh/get-page data page-id)
(get :objects))
related-components (find-related-components data objects variant-id)
changes (-> (pcb/empty-changes it page-id)
(pcb/with-objects objects)
(pcb/with-library-data data)
(clv/generate-update-property-name related-components pos new-name))
(clv/generate-update-property-name variant-id pos new-name))
undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
@ -59,17 +55,11 @@
data (dsh/lookup-file-data state)
objects (-> (dsh/get-page data page-id)
(get :objects))
component (ctcl/get-component data component-id true)
main-id (:main-instance-id component)
properties (-> (:variant-properties component)
(update pos assoc :value value))
name (clv/properties-to-name properties)
changes (-> (pcb/empty-changes it page-id)
(pcb/with-library-data data)
(pcb/with-objects objects)
(clv/generate-update-property-value component-id main-id pos value name))
(clv/generate-update-property-value component-id pos value))
undo-id (js/Symbol)]
(rx/of
(dwu/start-undo-transaction undo-id)
@ -88,12 +78,11 @@
data (dsh/lookup-file-data state)
objects (-> (dsh/get-page data page-id)
(get :objects))
related-components (find-related-components data objects variant-id)
changes (-> (pcb/empty-changes it page-id)
(pcb/with-library-data data)
(pcb/with-objects objects)
(clv/generate-remove-property related-components pos))
(clv/generate-remove-property variant-id pos))
undo-id (js/Symbol)]
(rx/of
@ -105,7 +94,7 @@
(defn add-new-property
"Add a new variant property to all the components with this variant-id"
[variant-id]
[variant-id & [options]]
(ptk/reify ::add-new-property
ptk/WatchEvent
(watch [it state _]
@ -114,20 +103,10 @@
objects (-> (dsh/get-page data page-id)
(get :objects))
related-components (find-related-components data objects variant-id)
property-name (str "Property" (-> related-components
first
:variant-properties
count
inc))
changes (-> (pcb/empty-changes it page-id)
(pcb/with-library-data data)
(pcb/with-objects objects)
(clv/generate-add-new-property related-components property-name))
(clv/generate-add-new-property variant-id options))
undo-id (js/Symbol)]
(rx/of
@ -155,7 +134,7 @@
(defn transform-in-variant
"Given the id of a main shape of a component, creates a variant structure for
that component"
[id]
[main-instance-id]
(ptk/reify ::transform-in-variant
ptk/WatchEvent
(watch [_ state _]
@ -165,7 +144,7 @@
file-id (:current-file-id state)
page-id (:current-page-id state)
objects (dsh/lookup-page-objects state file-id page-id)
main (get objects id)
main (get objects main-instance-id)
main-id (:id main)
undo-id (js/Symbol)]
@ -193,5 +172,5 @@
(dwl/duplicate-component file-id (:component-id main) new-component-id)
(set-variant-id (:component-id main) variant-id)
(set-variant-id new-component-id variant-id)
(add-new-property variant-id)
(add-new-property variant-id {:fill-values? true})
(dwu/commit-undo-transaction undo-id))))))

View file

@ -324,8 +324,8 @@
any-in-copy? (some true? (map #(ctn/has-any-copy-parent? objects %) shapes))
;; components can't be ungrouped
has-frame? (->> shapes (d/seek #(and (cfh/frame-shape? %) (not (ctk/instance-head? %)))))
has-group? (->> shapes (d/seek #(and (cfh/group-shape? %) (not (ctk/instance-head? %)))))
has-frame? (->> shapes (d/seek #(and (cfh/frame-shape? %) (not (ctk/instance-head? %)) (not (ctk/is-variant-container? %)))))
has-group? (->> shapes (d/seek #(and (cfh/group-shape? %) (not (ctk/instance-head? %)) (not (ctk/is-variant-container? %)))))
has-bool? (->> shapes (d/seek cfh/bool-shape?))
has-mask? (->> shapes (d/seek :masked-group))

View file

@ -254,11 +254,11 @@
(mf/deps related-components)
(fn [prop-name]
(->> related-components
(mapcat (fn [item]
(map :value (filter (fn [prop] (= (:name prop) prop-name))
(:variant-properties item)))))
(filter some?)
(mapcat (fn [component]
(map :value (filter #(= (:name %) prop-name)
(:variant-properties component)))))
distinct
(map #(if (str/empty? %) "--" %))
(map (fn [val] {:label val :id val})))))
filter-matching
@ -278,8 +278,7 @@
(mf/use-fn
(mf/deps id-component)
(fn [pos value]
(when-not (str/empty? value)
(st/emit! (dwv/update-property-value id-component pos value)))))
(st/emit! (dwv/update-property-value id-component pos value))))
switch-component
(mf/use-fn
@ -293,7 +292,7 @@
(if (ctk/main-instance? shape)
[:*
[:span {:class (stl/css :variant-property-name :variant-property-name-bg)} (:name prop)]
[:> combobox* {:default-selected (str (or (:value prop) ""))
[:> combobox* {:default-selected (if (str/empty? (:value prop)) "--" (:value prop))
:options (clj->js (get-options (:name prop)))
:on-change (partial change-property-value pos)}]]
@ -761,7 +760,10 @@
properties (->> (dwv/find-related-components data objects variant-id)
(mapcat :variant-properties)
(group-by :name)
(map (fn [[k v]] {:name k :values (map :value v)})))
(map (fn [[k v]]
{:name k
:values (distinct
(map #(if (str/empty? (:value %)) "--" (:value %)) v))})))
menu-open* (mf/use-state false)
menu-open? (deref menu-open*)