From 1b8714fe7fe8383730c67a4c70c928dbbe4f969a Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Wed, 2 Apr 2025 12:10:18 +0200 Subject: [PATCH] :bug: Fix position problems cutting-pasting a component --- CHANGES.md | 1 + common/src/app/common/files/changes.cljc | 8 ++-- .../src/app/common/files/changes_builder.cljc | 4 +- common/src/app/common/logic/libraries.cljc | 44 ++++++++++--------- common/src/app/common/types/file.cljc | 43 +++++++++--------- 5 files changed, 54 insertions(+), 46 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 078560cbca..85d4d3fd81 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ - Fix path having a wrong selrect [Taiga #10257](https://tree.taiga.io/project/penpot/issue/10257) - Fix SVG `stroke-linecap` property when importing SVGs [Taiga #9489](https://tree.taiga.io/project/penpot/issue/9489) +- Fix position problems cutting-pasting a component [Taiga #10677](https://tree.taiga.io/project/penpot/issue/10677) ## 2.6.0 (Unreleased) diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc index 24da8365b8..71633fe7d3 100644 --- a/common/src/app/common/files/changes.cljc +++ b/common/src/app/common/files/changes.cljc @@ -345,7 +345,9 @@ [:map {:title "DelComponentChange"} [:type [:= :del-component]] [:id ::sm/uuid] - [:main-instance {:optional true} :any] + ;; when it is an undo of a cut-paste, we need to undo the movement + ;; of the shapes so we need to move them delta + [:delta {:optional true} ::gpt/point] [:skip-undelete? {:optional true} :boolean]]] [:restore-component @@ -960,8 +962,8 @@ (ctkl/mod-component data params)) (defmethod process-change :del-component - [data {:keys [id skip-undelete? main-instance]}] - (ctf/delete-component data id skip-undelete? main-instance)) + [data {:keys [id skip-undelete? delta]}] + (ctf/delete-component data id skip-undelete? delta)) (defmethod process-change :restore-component [data {:keys [id page-id parent-id]}] diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index 41284fc781..8711e13f4a 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -1041,7 +1041,7 @@ :page-id page-id}))) (defn restore-component - [changes id page-id main-instance parent-id] + [changes id page-id delta parent-id] (assert-library! changes) (-> changes (update :redo-changes conj {:type :restore-component @@ -1050,7 +1050,7 @@ :parent-id parent-id}) (update :undo-changes conj {:type :del-component :id id - :main-instance main-instance}))) + :delta delta}))) (defn reorder-grid-children [changes ids] diff --git a/common/src/app/common/logic/libraries.cljc b/common/src/app/common/logic/libraries.cljc index 2d9e9634be..419d059047 100644 --- a/common/src/app/common/logic/libraries.cljc +++ b/common/src/app/common/logic/libraries.cljc @@ -359,18 +359,26 @@ (when (some #(= (:id current-page) %) (:pages library-data)) ;; If the page doesn't belong to the library, it's not valid current-page) (ctpl/get-last-page library-data))] - (prepare-restore-component changes library-data component-id page (gpt/point 0 0) nil nil nil))) + (prepare-restore-component changes library-data component-id page nil nil nil nil))) - ([changes library-data component-id page delta old-id parent-id frame-id] + ([changes library-data component-id page position old-id parent-id frame-id] (let [component (ctkl/get-deleted-component library-data component-id) parent (get-in page [:objects parent-id]) main-inst (get-in component [:objects (:main-instance-id component)]) inside-component? (some? (ctn/get-instance-root (:objects page) parent)) shapes (cfh/get-children-with-self (:objects component) (:main-instance-id component)) - shapes (map #(gsh/move % delta) shapes) + is-variant? (ctk/is-variant? component) - first-shape (cond-> (first shapes) + orig-pos (gpt/point (:x main-inst) (:y main-inst)) + delta (if position + (gpt/subtract position orig-pos) + (gpt/point 0 0)) + minusdelta (gpt/point (- (:x delta)) (- (:y delta))) + + moved-shapes (map #(gsh/move % delta) shapes) + + first-shape (cond-> (first moved-shapes) (not (nil? parent-id)) (assoc :parent-id parent-id) (not (nil? frame-id)) @@ -394,9 +402,9 @@ (some? old-id) (pcb/amend-last-change #(assoc % :old-id old-id))) ; on copy/paste old id is used later to reorder the paster layers changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) changes - (rest shapes))] - {:changes (pcb/restore-component changes component-id (:id page) main-inst parent-id) - :shape (first shapes)}))) + (rest moved-shapes))] + {:changes (pcb/restore-component changes component-id (:id page) minusdelta parent-id) + :shape (first moved-shapes)}))) ;; ---- General library synchronization functions ---- @@ -2188,20 +2196,16 @@ ;; When we duplicate a variant along with its variant-container, we will duplicate it in-variant-container? (contains? ids-map (:variant-id main)) - restore-component - #(let [origin-frame (get-in page [:objects frame-id]) - delta (cond-> delta - (some? origin-frame) - (gpt/subtract (-> origin-frame :selrect gpt/point))) - {:keys [shape changes]} (prepare-restore-component changes - library-data - component-id - page - delta - main-id - parent-id - frame-id)] + #(let [{:keys [shape changes]} + (prepare-restore-component changes + library-data + component-id + page + pos + main-id + parent-id + frame-id)] [shape changes]) [_shape changes] diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 722622076c..a7bab4aaa2 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -403,33 +403,34 @@ (defn load-component-objects "Add an :objects property to the component, with only the shapes that belong to it" - [file-data component] - (let [components-v2 (dm/get-in file-data [:options :components-v2])] - (if (and components-v2 component (empty? (:objects component))) ;; This operation may be called twice, e.g. in an idempotent change - (let [component-page (get-component-page file-data component) - page-objects (:objects component-page) - objects (->> (cons (:main-instance-id component) - (cfh/get-children-ids page-objects (:main-instance-id component))) - (map #(get page-objects %)) - (d/index-by :id))] - (assoc component :objects objects)) - component))) + ([file-data component] + (load-component-objects file-data component (gpt/point 0 0))) + ([file-data component delta] + (let [components-v2 (dm/get-in file-data [:options :components-v2])] + (if (and components-v2 component (empty? (:objects component))) ;; This operation may be called twice, e.g. in an idempotent change + (let [component-page (get-component-page file-data component) + page-objects (:objects component-page) + objects (->> (cons (:main-instance-id component) + (cfh/get-children-ids page-objects (:main-instance-id component))) + (map #(get page-objects %)) + ;; when it is an undo of a cut-paste, we need to undo the movement + ;; of the shapes so we need to move them delta + (map #(gsh/move % delta)) + (d/index-by :id))] + (assoc component :objects objects)) + component)))) (defn delete-component "Mark a component as deleted and store the main instance shapes iside it, to be able to be recovered later." - [file-data component-id skip-undelete? main-instance] - (let [components-v2 (dm/get-in file-data [:options :components-v2])] + [file-data component-id skip-undelete? delta] + (let [components-v2 (dm/get-in file-data [:options :components-v2]) + delta (or delta (gpt/point 0 0))] (if (or (not components-v2) skip-undelete?) (ctkl/delete-component file-data component-id) - (let [set-main-instance ;; If there is a saved main-instance, restore it. This happens on the restore-component action - #(if main-instance - (assoc-in % [:objects (:main-instance-id %)] main-instance) - %)] - (-> file-data - (ctkl/update-component component-id (partial load-component-objects file-data)) - (ctkl/update-component component-id set-main-instance) - (ctkl/mark-component-deleted component-id)))))) + (-> file-data + (ctkl/update-component component-id #(load-component-objects file-data % delta)) + (ctkl/mark-component-deleted component-id))))) (defn restore-component "Recover a deleted component and all its shapes and put all this again in place."