From 9a7a99e67acf7176ebb019d7d5c421c7947fbb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 19 Mar 2024 17:31:24 +0100 Subject: [PATCH] :bug: Advance nested copies when duplicated --- common/src/app/common/types/container.cljc | 25 ++++++++++++++++ common/src/app/common/types/file.cljc | 14 ++++----- frontend/src/app/main/data/workspace.cljs | 30 +++++++++++++++++++ .../app/main/data/workspace/selection.cljs | 21 ++++++++----- 4 files changed, 75 insertions(+), 15 deletions(-) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index ee2be1ae6..92f2969e0 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -154,6 +154,17 @@ :else (get-head-shape objects (get objects (:parent-id shape)) options)))) +(defn get-child-heads + "Get all recursive childs that are heads (when a head is found, do not + continue down looking for subsequent nested heads)." + [objects shape-id] + (let [shape (get objects shape-id)] + (if (nil? shape) + [] + (if (ctk/instance-head? shape) + [shape] + (mapcat #(get-child-heads objects %) (:shapes shape)))))) + (defn get-parent-heads "Get all component heads that are ancestors of the shape, in top-down order (include self if it's also a head)." @@ -170,6 +181,20 @@ (filter #(and (ctk/instance-head? %) (ctk/in-component-copy? %))) (reverse))) +(defn get-nesting-level-delta + "Get how many levels a shape will 'go up' if moved under the new parent." + [objects shape new-parent] + (let [orig-heads (->> (get-parent-copy-heads objects shape) + (remove #(= (:id %) (:id shape)))) + dest-heads (get-parent-copy-heads objects new-parent) + + ;; Calculate how many parent heads share in common the original + ;; shape and the new parent. + pairs (map vector orig-heads dest-heads) + common-count (count (take-while (fn [a b] (= a b)) pairs))] + + (- (count orig-heads) common-count))) + (defn get-instance-root "Get the parent shape at the top of the component instance (main or copy)." [objects shape] diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 3afb89c24..12775f322 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -216,14 +216,14 @@ (some find-ref-shape-in-head (ctn/get-parent-heads (:objects container) shape)))) -(defn find-original-ref-shape - "Recursively call to find-ref-shape until find the original shape of the original component" - [file container libraries shape & options] +(defn advance-shape-ref + "Get the shape-ref of the near main of the shape, recursively repeated as many times + as the given levels." + [file container libraries shape levels & options] (let [ref-shape (find-ref-shape file container libraries shape options)] - (if (nil? (:shape-ref ref-shape)) - ref-shape - (find-original-ref-shape file container libraries ref-shape options)))) - + (if (or (nil? (:shape-ref ref-shape)) (not (pos? levels))) + (:id ref-shape) + (advance-shape-ref file container libraries ref-shape (dec levels) options)))) (defn find-ref-component "Locate the nearest component in the local file or libraries that is referenced by the diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index da8ac8eab..e2bf369b6 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -25,6 +25,7 @@ [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] + [app.common.types.file :as ctf] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] @@ -1598,6 +1599,34 @@ (let [frame (get objects parent-frame-id)] (gsh/translate-to-frame shape frame)))) + ;; When copying an instance that is nested inside another one, we need to + ;; advance the shape refs to one or more levels of remote mains. + (advance-copies [state selected data] + (let [file (wsh/get-local-file-full state) + libraries (wsh/get-libraries state) + page (wsh/lookup-page state) + heads (mapcat #(ctn/get-child-heads (:objects data) %) selected)] + (update data :objects + #(reduce (partial advance-copy file libraries page) + % + heads)))) + + (advance-copy [file libraries page objects shape] + (if (and (ctk/instance-head? shape) (not (ctk/main-instance? shape))) + (let [level-delta (ctn/get-nesting-level-delta (:objects page) shape uuid/zero)] + (if (pos? level-delta) + (reduce (partial advance-shape file libraries page level-delta) + objects + (cfh/get-children-with-self objects (:id shape))) + objects)) + objects)) + + (advance-shape [file libraries page level-delta objects shape] + (let [new-shape-ref (ctf/advance-shape-ref file page libraries shape level-delta {:include-deleted? true})] + (cond-> objects + (and (some? new-shape-ref) (not= new-shape-ref (:shape-ref shape))) + (assoc-in [(:id shape) :shape-ref] new-shape-ref)))) + (on-copy-error [error] (js/console.error "clipboard blocked:" error) (rx/empty))] @@ -1636,6 +1665,7 @@ (rx/merge-map (partial prepare-object objects frame-id)) (rx/reduce collect-data initial) (rx/map (partial sort-selected state)) + (rx/map (partial advance-copies state selected)) (rx/map #(t/encode-str % {:type :json-verbose})) (rx/map wapi/write-to-clipboard) (rx/catch on-copy-error) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 5fa17a631..22ec299c0 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -404,6 +404,7 @@ ids-map %2 delta + nil libraries library-data it @@ -459,10 +460,10 @@ ;; TODO: move to common.files.shape-helpers (defn- prepare-duplicate-shape-change - ([changes objects page unames update-unames! ids-map obj delta libraries library-data it file-id] - (prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta libraries library-data it file-id (:frame-id obj) (:parent-id obj) false false)) + ([changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data it file-id] + (prepare-duplicate-shape-change changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data it file-id (:frame-id obj) (:parent-id obj) false false)) - ([changes objects page unames update-unames! ids-map obj delta libraries library-data it file-id frame-id parent-id duplicating-component? child?] + ([changes objects page unames update-unames! ids-map obj delta level-delta libraries library-data it file-id frame-id parent-id duplicating-component? child?] (cond (nil? obj) changes @@ -486,11 +487,14 @@ duplicating-component? (or duplicating-component? (ctk/instance-head? obj)) is-component-main? (ctk/main-instance? obj) - original-ref-shape (-> (ctf/find-original-ref-shape nil page libraries obj {:include-deleted? true}) - :id) into-component? (and duplicating-component? (ctn/in-any-component? objects parent)) + level-delta (if (some? level-delta) + level-delta + (ctn/get-nesting-level-delta objects obj parent)) + new-shape-ref (ctf/advance-shape-ref nil page libraries obj level-delta {:include-deleted? true}) + regenerate-component (fn [changes shape] (let [components-v2 (dm/get-in library-data [:options :components-v2]) @@ -518,9 +522,9 @@ (cond-> (or frame? group? bool?) (assoc :shapes [])) - (cond-> (and (some? original-ref-shape) - (not= original-ref-shape (:shape-ref obj))) - (assoc :shape-ref original-ref-shape)) + (cond-> (and (some? new-shape-ref) + (not= new-shape-ref (:shape-ref obj))) + (assoc :shape-ref new-shape-ref)) (gsh/move delta) (d/update-when :interactions #(ctsi/remap-interactions % ids-map objects)) @@ -561,6 +565,7 @@ ids-map child delta + level-delta libraries library-data it