🐛 Fix mask issues with component swap #7675

The logic to swap a component would delete the swapped out component
first before bringing in the new one.

In the process of doing so, the sanitization code would unmask the
group, now orphan of its mask shape component, when it was the first
element of the group.

The fix  was to pass an optional argument to the generate-delete-shapes
function to ignore mask in special cases like this.

Signed-off-by: Dalai Felinto <dalai@blender.org>
This commit is contained in:
Dalai Felinto
2025-11-30 13:50:09 +01:00
committed by Pablo Alba
parent fe72d0af82
commit d63d692d34
3 changed files with 23 additions and 15 deletions

View File

@@ -8,6 +8,8 @@
### :heart: Community contributions (Thank you!) ### :heart: Community contributions (Thank you!)
- Fix mask issues with component swap (by @dfelinto) [Github #7675](https://github.com/penpot/penpot/issues/7675)
### :sparkles: New features & Enhancements ### :sparkles: New features & Enhancements
### :bug: Bugs fixed ### :bug: Bugs fixed

View File

@@ -2439,11 +2439,13 @@
(ctk/get-swap-slot)) (ctk/get-swap-slot))
(constantly false)) (constantly false))
;; In the cases where the swapped shape was the first element of the masked group it would make the group to loose the
;; mask property as part of the sanitization check on generate-delete-shapes, passing "ignore-mask" to prevent this
[all-parents changes] [all-parents changes]
(-> changes (-> changes
(cls/generate-delete-shapes (cls/generate-delete-shapes
file page objects (d/ordered-set (:id shape)) file page objects (d/ordered-set (:id shape))
{:allow-altering-copies true :ignore-children-fn ignore-swapped-fn})) {:allow-altering-copies true :ignore-children-fn ignore-swapped-fn :ignore-mask true}))
[new-shape changes] [new-shape changes]
(-> changes (-> changes
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))] (generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]

View File

@@ -123,8 +123,10 @@
;; ignore-children-fn is used to ignore some descendants ;; ignore-children-fn is used to ignore some descendants
;; on the deletion process. It should receive a shape and ;; on the deletion process. It should receive a shape and
;; return a boolean ;; return a boolean
ignore-children-fn] ignore-children-fn
:or {ignore-children-fn (constantly false)}}] ignore-mask]
:or {ignore-children-fn (constantly false)
ignore-mask false}}]
(let [objects (pcb/get-objects changes) (let [objects (pcb/get-objects changes)
data (pcb/get-library-data changes) data (pcb/get-library-data changes)
page-id (pcb/get-page-id changes) page-id (pcb/get-page-id changes)
@@ -162,18 +164,20 @@
lookup (d/getf objects) lookup (d/getf objects)
groups-to-unmask groups-to-unmask
(reduce (fn [group-ids id] (when-not ignore-mask
;; When the shape to delete is the mask of a masked group, (reduce (fn [group-ids id]
;; the mask condition must be removed, and it must be ;; When the shape to delete is the mask of a masked group,
;; converted to a normal group. ;; the mask condition must be removed, and it must be
(let [obj (lookup id) ;; converted to a normal group.
parent (lookup (:parent-id obj))] (let [obj (lookup id)
(if (and (:masked-group parent) parent (lookup (:parent-id obj))]
(= id (first (:shapes parent)))) (if (and (:masked-group parent)
(conj group-ids (:id parent)) (= id (first (:shapes parent))))
group-ids))) (conj group-ids (:id parent))
#{} group-ids)))
ids-to-delete) #{}
ids-to-delete)
[])
interacting-shapes interacting-shapes
(filter (fn [shape] (filter (fn [shape]